Merge feature to master (#1945)

* chore: sync ant-design v4.16.9 (#1782)

* refactor(module: tree): rename Checked/Dechecked methods to Check/Uncheck (#1792)

* feat(model: table): support responsive (#1802)

* feat(module: form): add properties Help, ValidateStatus and HasFeedback (#1807)

* Add properties Help, ValidateStatus and HasFeedback to FormItem

* refactor validation messages

Co-authored-by: ElderJames <shunjiey@hotmail.com>

* feat(module: radio): add options for radio group (#1839)

* feat(module: radio): support enum type for `RadioGroup` (#1840)

* feat(module: image): support preview visible (#1842)

* feat(module: tree): add ChildContent for Tree and TreeNode (#1887)

* feat(module: tree): add ChildContent for Tree and TreeNode

Allow user set tree nodes directly.

* fix indent

* docs: Update the style to be compatible with antd 4.16 style. (#1893)

* docs: fix style

* fix logo font

* fix the color less

* feat(module: TreeSelect): add tree-select component 🚀 (#1773)

* fixed. Fix network error when requesting contributor list in local development environment

* fixed. Fix Chinese garbled code

* Update Dynamic.razor

* update. Adjust the base class of the select control to selectbase, and extract the common methods to prepare for tree select.

* fixed. Adjust the type of the parent control to selectbase

* tree-select完成基础编码工作。

* fix. 重新修订冲突解决时误删的变量

* fix. 修复在多选模式下无法显示初始值的bug

* fix some styles

* add. tree add Node UnSelect Event

* fix . Node Multiple Select Bug.

* fix Tree UnSelect invalid.

* fix. tree multiple select support.

* fix. select option sync tree node selected.

* fix. multiple select, old selected values has clear

* fix loop update exception

* 修复合并带来的冲突

* add. allow clear feat. but not finish.

* fix merge bug

* fix merge bug

* fix. supplementary notes

* update demo data with react demo

* add. 添加静态下拉树的能力,以及单选和多选的demo

* fix. 优化下拉显示树时,树选项的选中逻辑。

* fix demo

* fix. 静态下拉树时,出现错选的bug

* fix merge bugs.

* fix merge bugs.

* add. static dataObject Bind Support

* 将下拉树中,对偏平数据源的处理,移到simpleTreeSelect中

* fix ChildrenExpression

* feat: add dynamic component (#1703)

* feat: weakly-typed/dynamic component

* add TypeName support

* override BuildRenderTree instead of using a RenderFragment

* rename

* fix(module: timeline): label missing (#1941)

* chore: sync ant-design v4.16.13 (#1862)

Co-authored-by: lukblazewicz <39852149+lukblazewicz@users.noreply.github.com>
Co-authored-by: YongQuanRao <79885120+JamesGit-hash@users.noreply.github.com>
Co-authored-by: anranruye <54608128+anranruye@users.noreply.github.com>
Co-authored-by: Andrzej Bakun <andrzej@neelyc.com.cy>
Co-authored-by: zxyao <zxyao145@gmail.com>
Co-authored-by: Łukasz Błażewicz <lukasz.blazewicz@homebook.pl>
Co-authored-by: heroboy <yangweiqin@gmail.com>
Co-authored-by: Simon Cropp <simon.cropp@gmail.com>
Co-authored-by: JohnHao421 <544106829@qq.com>
Co-authored-by: haojiajun <haojiajun@vanelink.net>
Co-authored-by: Noah Potash <digitalnugget@gmail.com>
Co-authored-by: Noah Potash <noah.potash@outbreaklabs.com>
Co-authored-by: SmallY <45689960+iamSmallY@users.noreply.github.com>
Co-authored-by: Stefano Driussi <stedri@gmail.com>
Co-authored-by: Stefano Drussi <stefano.driussi@hotmail.it>
Co-authored-by: Guyiming <guyiming2011@126.com>
Co-authored-by: Maksim <maksalmak@gmail.com>
Co-authored-by: Nikolay Krondev <nikolaykrondev@users.noreply.github.com>
Co-authored-by: gmij <gmij@qq.com>
Co-authored-by: anranruye <hehewewe@hotmail.com>
Co-authored-by: Alan.Liu <lxyruanjian@126.com>
Co-authored-by: rabberbock <rabberbock@gmail.com>
Co-authored-by: Andrzej Bakun <anddrzejb@poczta.fm>
Co-authored-by: Chandan Rauniyar <chandankkrr@gmail.com>
Co-authored-by: Luke Parker [SSW] <10430890+Hona@users.noreply.github.com>
Co-authored-by: Tony Yip <tonyyip1969@gmail.com>
Co-authored-by: SmRiley <45205313+SmRiley@users.noreply.github.com>
This commit is contained in:
James Yeung 2021-09-14 23:29:16 +08:00 committed by GitHub
commit d27be8355e
125 changed files with 3968 additions and 2158 deletions

View File

@ -54,6 +54,11 @@
box-shadow: 0 0 0 1px @shadow-color-inverse;
}
// Tricky way to resolve https://github.com/ant-design/ant-design/issues/30088
&-dot.@{number-prefix-cls} {
transition: background 1.5s;
}
&-count,
&-dot,
.@{number-prefix-cls}-custom-component {
@ -157,7 +162,7 @@
}
.@{number-prefix-cls}-custom-component,
.@{ant-prefix}-scroll-number {
.@{number-prefix-cls} {
position: relative;
top: auto;
display: block;

View File

@ -101,7 +101,12 @@
&-icon-only {
.btn-square(@btn-prefix-cls);
vertical-align: -1px;
vertical-align: -3px;
> .@{iconfont-css-prefix} {
display: flex;
justify-content: center;
}
}
&-round {
@ -190,10 +195,15 @@
margin-left: @margin-xs;
}
&-background-ghost {
&&-background-ghost {
color: @btn-default-ghost-color;
background: @btn-default-ghost-bg !important;
border-color: @btn-default-ghost-border;
&,
&:hover,
&:active,
&:focus {
background: @btn-default-ghost-bg;
}
}
&-background-ghost&-primary {

View File

@ -90,27 +90,27 @@
.button-disabled();
}
.button-variant-ghost(@color; @border: @color) {
.button-color(@color; transparent; @border);
.button-color(@color; null; @border);
text-shadow: none;
&:hover,
&:focus {
& when (@border = transparent) {
& when (@theme = dark) {
.button-color(~`colorPalette('@{color}', 7) `; transparent; transparent);
.button-color(~`colorPalette('@{color}', 7) `; null; transparent);
}
& when not (@theme = dark) {
.button-color(~`colorPalette('@{color}', 5) `; transparent; transparent);
.button-color(~`colorPalette('@{color}', 5) `; null; transparent);
}
}
& when not (@border = transparent) {
& when (@theme = dark) {
.button-color(
~`colorPalette('@{color}', 7) `; transparent; ~`colorPalette('@{color}', 7) `
~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) `
);
}
& when not (@theme = dark) {
.button-color(
~`colorPalette('@{color}', 5) `; transparent; ~`colorPalette('@{color}', 5) `
~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) `
);
}
}
@ -118,21 +118,21 @@
&:active {
& when (@border = transparent) {
& when (@theme = dark) {
.button-color(~`colorPalette('@{color}', 5) `; transparent; transparent);
.button-color(~`colorPalette('@{color}', 5) `; null; transparent);
}
& when not (@theme = dark) {
.button-color(~`colorPalette('@{color}', 7) `; transparent; transparent);
.button-color(~`colorPalette('@{color}', 7) `; null; transparent);
}
}
& when not(@border = transparent) {
& when not (@border = transparent) {
& when (@theme = dark) {
.button-color(
~`colorPalette('@{color}', 5) `; transparent; ~`colorPalette('@{color}', 5) `
~`colorPalette('@{color}', 5) `; null; ~`colorPalette('@{color}', 5) `
);
}
& when not (@theme = dark) {
.button-color(
~`colorPalette('@{color}', 7) `; transparent; ~`colorPalette('@{color}', 7) `
~`colorPalette('@{color}', 7) `; null; ~`colorPalette('@{color}', 7) `
);
}
}
@ -141,8 +141,10 @@
}
.button-color(@color; @background; @border) {
color: @color;
background: @background;
border-color: @border; // a inside Button which only work in Chrome
& when not(@background = null) {
background: @background;
}
// http://stackoverflow.com/a/17253457
> a:only-child {
color: currentColor;

View File

@ -144,21 +144,21 @@
&-hidden {
display: none;
}
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
animation-name: antSlideUpIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
animation-name: antSlideDownIn;
}
&.slide-up-leave.slide-up-leave-active&-placement-bottomLeft {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft {
animation-name: antSlideUpOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft {
animation-name: antSlideDownOut;
}
}

View File

@ -60,7 +60,9 @@
position: absolute;
top: 50%;
left: 22%;
// https://github.com/ant-design/ant-design/pull/19452
// https://github.com/ant-design/ant-design/pull/31726
left: 21.5%;
display: table;
width: @check-width;
height: @check-height;

View File

@ -72,8 +72,12 @@
padding-right: @collapse-header-padding-extra;
.@{collapse-prefix-cls}-arrow {
position: absolute;
top: 50%;
right: @padding-md;
left: auto;
margin: 0;
transform: translateY(-50%);
}
}
}

View File

@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace AntDesign
{
public class Component : ComponentBase
{
static Assembly _antAssembly;
[Parameter]
public Type Type { get; set; }
[Parameter]
public string TypeName
{
set
{
if (Type != null) return;
_antAssembly ??= Assembly.GetExecutingAssembly();
Type componentType =
_antAssembly.GetType($"AntDesign.{value}") ??
_antAssembly.GetType(value) ??
Type.GetType(value);
if (componentType == null)
{
throw new ArgumentException($"Not found the component with the name \"{value}\"", nameof(TypeName));
}
Type = componentType;
}
}
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> Parameters { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent(0, Type);
if (Parameters != null)
{
builder.AddMultipleAttributes(1, Parameters);
}
builder.CloseComponent();
}
}
}

View File

@ -16,7 +16,7 @@ namespace AntDesign
private static readonly Func<T, T, T> _aggregateFunction;
private static IEnumerable<T> _valueList;
private static IEnumerable<(T Value, string Label)> _valueLabelList;
private static Type _enumType;
static EnumHelper()
@ -24,6 +24,7 @@ namespace AntDesign
_enumType = THelper.GetUnderlyingType<T>();
_aggregateFunction = BuildAggregateFunction();
_valueList = Enum.GetValues(_enumType).Cast<T>();
_valueLabelList = _valueList.Select(value => (value, GetDisplayName(value)));
}
// There is no constraint or type check for type parameter T, be sure that T is an enumeration type
@ -49,11 +50,16 @@ namespace AntDesign
return _valueList;
}
public static string GetDisplayName(T t)
public static IEnumerable<(T Value, string Label)> GetValueLabelList()
{
var fieldInfo = _enumType.GetField(t.ToString());
return fieldInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name ??
fieldInfo.Name;
return _valueLabelList;
}
public static string GetDisplayName(T enumValue)
{
var enumName = Enum.GetName(_enumType, enumValue);
var fieldInfo = _enumType.GetField(enumName);
return fieldInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name ?? enumName;
}
private static Func<T, T, T> BuildAggregateFunction()

View File

@ -235,27 +235,27 @@
}
}
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-enter.slide-up-enter-active&-placement-topRight,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topRight {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topRight,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topRight {
animation-name: antSlideDownIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,
&.slide-up-enter.slide-up-enter-active&-placement-bottomRight,
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft,
&.slide-up-appear.slide-up-appear-active&-placement-bottomRight {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomRight,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomRight {
animation-name: antSlideUpIn;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft,
&.slide-up-leave.slide-up-leave-active&-placement-topRight {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topRight {
animation-name: antSlideDownOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-bottomLeft,
&.slide-up-leave.slide-up-leave-active&-placement-bottomRight {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomRight {
animation-name: antSlideUpOut;
}
}
@ -346,6 +346,11 @@
border-width: 0 0 @border-width-base 0;
border-radius: 0;
.@{picker-prefix-cls}-content,
table {
text-align: center;
}
&-focused {
border-color: @border-color-split;
}

View File

@ -161,6 +161,7 @@
z-index: 1;
height: @picker-panel-cell-height;
transform: translateY(-50%);
transition: all @animation-duration-slow;
content: '';
}
@ -247,6 +248,7 @@
border-top: @border-width-base dashed @picker-date-hover-range-border-color;
border-bottom: @border-width-base dashed @picker-date-hover-range-border-color;
transform: translateY(-50%);
transition: all @animation-duration-slow;
content: '';
}
}
@ -292,6 +294,7 @@
bottom: 0;
z-index: -1;
background: @picker-date-hover-range-color;
transition: all @animation-duration-slow;
content: '';
}
}
@ -340,10 +343,10 @@
// >>> Disabled
&-disabled {
color: @disabled-color;
pointer-events: none;
.@{cellClassName} {
color: @disabled-color;
background: transparent;
}
@ -366,11 +369,6 @@
color: @text-color;
}
// Disabled
&-disabled {
cursor: not-allowed;
}
.picker-cell-inner(~'@{picker-cell-inner-cls}');
}
@ -385,12 +383,6 @@
.@{picker-cell-inner-cls} {
padding: 0 @padding-xs;
}
.@{picker-prefix-cls}-cell {
&-disabled .@{picker-cell-inner-cls} {
background: @picker-basic-cell-disabled-bg;
}
}
}
&-quarter-panel {

View File

@ -128,6 +128,7 @@
border: 1px solid @border-color-split;
> table {
table-layout: auto;
border-collapse: collapse;
}
}

View File

@ -32,7 +32,7 @@
}
.@{iconfont-css-prefix}-down::before {
transition: transform 0.2s;
transition: transform @animation-duration-base;
}
}
@ -133,7 +133,7 @@
&-item-group-title {
padding: 5px @control-padding-horizontal;
color: @text-color-secondary;
transition: all 0.3s;
transition: all @animation-duration-slow;
}
&-submenu-popup {
@ -154,6 +154,42 @@
}
}
// ======================= Item Content =======================
&-item {
position: relative;
display: flex;
align-items: center;
}
&-item-icon {
min-width: 12px;
margin-right: 8px;
font-size: @font-size-sm;
}
&-title-content {
flex: auto;
> a {
color: inherit;
transition: all @animation-duration-slow;
&:hover {
color: inherit;
}
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
}
}
}
// =========================== Item ===========================
&-item,
&-submenu-title {
clear: both;
@ -165,35 +201,7 @@
line-height: @dropdown-line-height;
white-space: nowrap;
cursor: pointer;
transition: all 0.3s;
> .@{iconfont-css-prefix}:first-child,
> a > .@{iconfont-css-prefix}:first-child,
> span > .@{iconfont-css-prefix}:first-child {
min-width: 12px;
margin-right: 8px;
font-size: @font-size-sm;
vertical-align: -0.1em;
}
> a {
display: block;
margin: -5px -@control-padding-horizontal;
padding: 5px @control-padding-horizontal;
color: @text-color;
transition: all 0.3s;
&:hover {
color: @text-color;
}
}
> .@{iconfont-css-prefix} + span > a {
color: @text-color;
transition: all 0.3s;
&:hover {
color: @text-color;
}
}
transition: all @animation-duration-slow;
&:first-child {
& when (@dropdown-edge-child-vertical-padding = 0) {
@ -207,8 +215,7 @@
}
}
&-selected,
&-selected > a {
&-selected {
color: @dropdown-selected-color;
background-color: @item-active-bg;
}
@ -227,21 +234,8 @@
cursor: not-allowed;
}
> .@{iconfont-css-prefix} + span > a,
> a {
position: relative;
color: @disabled-color;
a {
pointer-events: none;
&::after {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: not-allowed;
content: '';
}
}
}
@ -304,33 +298,33 @@
}
}
&.slide-down-enter.slide-down-enter-active&-placement-bottomLeft,
&.slide-down-appear.slide-down-appear-active&-placement-bottomLeft,
&.slide-down-enter.slide-down-enter-active&-placement-bottomCenter,
&.slide-down-appear.slide-down-appear-active&-placement-bottomCenter,
&.slide-down-enter.slide-down-enter-active&-placement-bottomRight,
&.slide-down-appear.slide-down-appear-active&-placement-bottomRight {
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottomCenter,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottomCenter,
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottomRight,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottomRight {
animation-name: antSlideUpIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft,
&.slide-up-enter.slide-up-enter-active&-placement-topCenter,
&.slide-up-appear.slide-up-appear-active&-placement-topCenter,
&.slide-up-enter.slide-up-enter-active&-placement-topRight,
&.slide-up-appear.slide-up-appear-active&-placement-topRight {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topCenter,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topCenter,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topRight,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topRight {
animation-name: antSlideDownIn;
}
&.slide-down-leave.slide-down-leave-active&-placement-bottomLeft,
&.slide-down-leave.slide-down-leave-active&-placement-bottomCenter,
&.slide-down-leave.slide-down-leave-active&-placement-bottomRight {
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottomCenter,
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottomRight {
animation-name: antSlideUpOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft,
&.slide-up-leave.slide-up-leave-active&-placement-topCenter,
&.slide-up-leave.slide-up-leave-active&-placement-topRight {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topCenter,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topRight {
animation-name: antSlideDownOut;
}
}

View File

@ -16,6 +16,7 @@
}
</AntDesign.Col>
}
<AntDesign.Col @attributes="GetWrapperColAttributes()" Class=@($"{_prefixCls}-control")>
<div class=@($"{_prefixCls}-control-input")>
<div class=@($"{_prefixCls}-control-input-content")>
@ -23,7 +24,19 @@
@ChildContent
</CascadingValue>
</div>
@if (IsShowIcon)
{
<span class=@($"{_prefixCls}-children-icon")><Icon Type="@(_iconMap[ValidateStatus].type)" Theme="@(_iconMap[ValidateStatus].theme)" /></span>
}
</div>
@_formValidationMessages
@foreach (var message in _validationMessages)
{
<div class=@($"{_prefixCls}-explain {_prefixCls}-explain-{(_isValid ? "default" : "error")}")>
<div role="alert">
@message
</div>
</div>
}
</AntDesign.Col>
</Row>

View File

@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Core.Reflection;
using AntDesign.Forms;
using AntDesign.Internal;
@ -11,6 +12,7 @@ using AntDesign.Internal.Form.Validate;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using OneOf;
using static AntDesign.IconType;
namespace AntDesign
{
@ -107,16 +109,35 @@ namespace AntDesign
[Parameter]
public FormValidationRule[] Rules { get; set; }
[Parameter]
public bool HasFeedback { get; set; }
[Parameter]
public FormValidateStatus ValidateStatus { get; set; }
[Parameter]
public string Help { get; set; }
private static readonly Dictionary<FormValidateStatus, (string theme, string type)> _iconMap = new Dictionary<FormValidateStatus, (string theme, string type)>
{
{ FormValidateStatus.Success, (IconThemeType.Fill, Outline.CheckCircle) },
{ FormValidateStatus.Warning, (IconThemeType.Fill, Outline.ExclamationCircle) },
{ FormValidateStatus.Error, (IconThemeType.Fill, Outline.CloseCircle) },
{ FormValidateStatus.Validating, (IconThemeType.Outline, Outline.Loading) }
};
private bool IsShowIcon => HasFeedback && _iconMap.ContainsKey(ValidateStatus);
private EditContext EditContext => Form?.EditContext;
private string[] _validationMessages = Array.Empty<string>();
private bool _isValid = true;
private string _labelCls = "";
private IControlValueAccessor _control;
private RenderFragment _formValidationMessages;
private PropertyReflector _propertyReflector;
private ClassMapper _labelClassMapper = new ClassMapper();
@ -140,6 +161,11 @@ namespace AntDesign
SetRequiredCss();
Form.AddFormItem(this);
if (!string.IsNullOrWhiteSpace(Help))
{
_validationMessages = new[] { Help };
}
}
protected void SetClass()
@ -148,6 +174,10 @@ namespace AntDesign
.Add(_prefixCls)
.If($"{_prefixCls}-with-help {_prefixCls}-has-error", () => _isValid == false)
.If($"{_prefixCls}-rtl", () => RTL)
.If($"{_prefixCls}-has-feedback", () => HasFeedback)
.If($"{_prefixCls}-is-validating", () => ValidateStatus == FormValidateStatus.Validating)
.GetIf(() => $"{_prefixCls}-has-{ValidateStatus.ToString().ToLower()}", () => ValidateStatus.IsIn(FormValidateStatus.Success, FormValidateStatus.Error, FormValidateStatus.Warning))
.If($"{_prefixCls}-with-help", () => !string.IsNullOrEmpty(Help))
;
_labelClassMapper
@ -257,7 +287,6 @@ namespace AntDesign
_fieldIdentifier = control.FieldIdentifier;
this._control = control;
if (Form.ValidateMode.IsIn(FormValidateMode.Rules, FormValidateMode.Complex))
{
_fieldPropertyInfo = _fieldIdentifier.Model.GetType().GetProperty(_fieldIdentifier.FieldName);
@ -265,22 +294,20 @@ namespace AntDesign
_validationStateChangedHandler = (s, e) =>
{
control.ValidationMessages = CurrentEditContext.GetValidationMessages(control.FieldIdentifier).Distinct().ToArray();
this._isValid = !control.ValidationMessages.Any();
_validationMessages = CurrentEditContext.GetValidationMessages(control.FieldIdentifier).Distinct().ToArray();
this._isValid = !_validationMessages.Any();
control.ValidationMessages = _validationMessages;
if (!string.IsNullOrWhiteSpace(Help))
{
_validationMessages = new[] { Help };
}
StateHasChanged();
};
CurrentEditContext.OnValidationStateChanged += _validationStateChangedHandler;
_formValidationMessages = builder =>
{
var i = 0;
builder.OpenComponent<FormValidationMessage<TValue>>(i++);
builder.AddAttribute(i++, "Control", control);
builder.CloseComponent();
};
if (control.ValueExpression is not null)
_propertyReflector = PropertyReflector.Create(control.ValueExpression);
else

View File

@ -0,0 +1,11 @@
namespace AntDesign
{
public enum FormValidateStatus
{
Default,
Success,
Warning,
Error,
Validating
}
}

View File

@ -1,7 +0,0 @@
@namespace AntDesign
@typeparam TValue
@foreach (var message in _control.ValidationMessages)
{
<FormValidationMessageItem AdditionalAttributes="@AdditionalAttributes" Message="@message" @key="message"></FormValidationMessageItem>
}

View File

@ -1,62 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace AntDesign
{
/// <summary>
/// FormValidationMessage is copy from ValidationMessage.
/// Displays a list of validation messages for a specified field within a cascaded <see cref="EditContext"/>.
/// </summary>
public partial class FormValidationMessage<TValue> : ComponentBase, IDisposable
{
private EditContext _previousEditContext;
private Expression<Func<TValue>> _previousFieldAccessor;
//private readonly EventHandler<ValidationStateChangedEventArgs> _validationStateChangedHandler;
private FieldIdentifier _fieldIdentifier;
/// <summary>
/// Gets or sets a collection of additional attributes that will be applied to the created <c>div</c> element.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)]
public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
public AntInputComponentBase<TValue> _control;
[Parameter]
public AntInputComponentBase<TValue> Control
{
get
{
return _control;
}
set
{
if (value != null && _control != value)
{
_control = value;
_fieldIdentifier = _control.FieldIdentifier;
_previousFieldAccessor = _control.ValueExpression;
}
}
}
protected virtual void Dispose(bool disposing)
{
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
}
}
}

View File

@ -1,3 +0,0 @@
@namespace AntDesign
<div @attributes="@AdditionalAttributes" class="ant-form-item-explain ant-form-item-explain-error">@Message</div>

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
public partial class FormValidationMessageItem
{
[Parameter]
public string Message { get; set; }
[Parameter]
public IReadOnlyDictionary<string, object> AdditionalAttributes { get; set; }
}
}

View File

@ -89,9 +89,9 @@
> label {
position: relative;
// display: inline;
display: inline-flex;
align-items: center;
max-width: 100%;
height: @form-item-label-height;
color: @label-color;
font-size: @form-item-label-font-size;
@ -235,27 +235,33 @@
@keyframes diffZoomIn1 {
0% {
transform: scale(0);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes diffZoomIn2 {
0% {
transform: scale(0);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes diffZoomIn3 {
0% {
transform: scale(0);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}

View File

@ -5,8 +5,8 @@
color: @text-color;
}
// 输入框的不同校验状态
.@{ant-prefix}-input,
.@{ant-prefix}-input-affix-wrapper {
:not(.@{ant-prefix}-input-disabled):not(.@{ant-prefix}-input-borderless).@{ant-prefix}-input,
:not(.@{ant-prefix}-input-affix-wrapper-disabled):not(.@{ant-prefix}-input-affix-wrapper-borderless).@{ant-prefix}-input-affix-wrapper {
&,
&:hover {
background-color: @background-color;
@ -19,26 +19,6 @@
}
}
.@{ant-prefix}-input-disabled {
&,
&:hover {
background-color: @input-disabled-bg;
border-color: @input-border-color;
}
}
.@{ant-prefix}-input-affix-wrapper-disabled {
&,
&:hover {
background-color: @input-disabled-bg;
border-color: @input-border-color;
input:focus {
box-shadow: none !important;
}
}
}
.@{ant-prefix}-calendar-picker-open .@{ant-prefix}-calendar-picker-input {
.active(@border-color);
}

View File

@ -6,7 +6,8 @@
<img alt="@Alt" class="ant-image-img" src="@Src" style="@Style"
@onerror="HandleOnError"
@onloadstart="HandleOnLoadStart"
@onload="HandleOnLoad" />
@onload="HandleOnLoad"
@onclick="OnClick"/>
@if (!_loaded && Placeholder != null)
{
@ -17,7 +18,7 @@
@if (_loaded && !_isError && Preview)
{
<div class="ant-image-mask" @onclick="OnPreview">
<div class="ant-image-mask" @onclick="OnPreview" @onclick:stopPropagation>
<div class="ant-image-mask-info">
<Icon Type="eye" /> @Locale.Preview
</div>

View File

@ -26,6 +26,9 @@ namespace AntDesign
[Parameter]
public bool Preview { get; set; } = true;
[Parameter]
public bool PreviewVisible { get; set; } = true;
[Parameter]
public string Src
{
@ -49,6 +52,9 @@ namespace AntDesign
[Parameter]
public string PreviewSrc { get; set; }
[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }
[Parameter]
public ImageLocale Locale { get; set; } = LocaleProvider.CurrentLocale.Image;
@ -108,14 +114,22 @@ namespace AntDesign
_loaded = false;
}
private void OnPreview()
private void OnPreview(MouseEventArgs e)
{
var images = Group?.Images ?? new List<Image>() { this };
var index = images.IndexOf(this);
if (PreviewVisible)
{
var images = Group?.Images ?? new List<Image>() { this };
var index = images.IndexOf(this);
_imageRef = ImageService.OpenImages(images);
_imageRef = ImageService.OpenImages(images);
_imageRef.SwitchTo(index);
_imageRef.SwitchTo(index);
}
if (OnClick.HasDelegate)
{
OnClick.InvokeAsync(e);
}
}
protected override void Dispose(bool disposing)

View File

@ -5,15 +5,38 @@ using Microsoft.AspNetCore.Components;
namespace AntDesign
{
public partial class ImagePreviewGroup
public partial class ImagePreviewGroup : IDisposable
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool PreviewVisible
{
get => _previewVisible;
set
{
if (_previewVisible != value)
{
_previewVisible = value;
HandleVisibleChange(_previewVisible);
}
}
}
[Parameter]
public EventCallback<bool> PreviewVisibleChanged { get; set; }
[Inject]
private ImageService ImageService { get; set; }
internal IList<Image> Images => _images;
private IList<Image> _images;
private ImageRef _imageRef;
private bool _previewVisible = true;
public void AddImage(Image image)
{
_images ??= new List<Image>();
@ -24,5 +47,41 @@ namespace AntDesign
{
_images.Remove(image);
}
public void HandleVisibleChange(bool visible)
{
if (visible)
{
_imageRef = ImageService.OpenImages(_images);
_imageRef.SwitchTo(0);
_imageRef.OnClosed += OnPreviewClose;
}
else
{
if (_imageRef != null)
{
_imageRef.OnClosed -= OnPreviewClose;
ImageService.CloseImage(_imageRef);
}
}
if (PreviewVisibleChanged.HasDelegate)
{
PreviewVisibleChanged.InvokeAsync(visible);
}
}
private void OnPreviewClose()
{
PreviewVisible = false;
}
public void Dispose()
{
if (_imageRef != null)
{
_imageRef.OnClosed -= OnPreviewClose;
}
}
}
}

View File

@ -6,6 +6,8 @@ namespace AntDesign
{
public class ImageRef
{
public event Action OnClosed;
internal string ImageSrc => _showingImageSrc;
internal int CurrentIndex => _currentIndex;
@ -36,6 +38,7 @@ namespace AntDesign
public void Close()
{
OnClosed?.Invoke();
_imageService.CloseImage(this);
}
}

View File

@ -26,6 +26,7 @@
font-weight: bold;
line-height: 0;
text-align: center;
border-left: @border-width-base @border-style-base @input-number-handler-border-color;
transition: all 0.1s linear;
&:active {
background: @input-number-handler-active-bg;
@ -123,7 +124,6 @@
width: 22px;
height: 100%;
background: @input-number-handler-bg;
border-left: @border-width-base @border-style-base @input-number-handler-border-color;
border-radius: 0 @border-radius-base @border-radius-base 0;
opacity: 0;
transition: opacity 0.24s linear 0.1s;

View File

@ -33,7 +33,7 @@
outline: none;
&:focus {
box-shadow: none;
box-shadow: none !important;
}
}

View File

@ -1,8 +1,8 @@
@import './index';
// ========================= Input =========================
.@{ant-prefix}-input-clear-icon {
margin: 0 @input-affix-margin;
.@{iconfont-css-prefix}.@{ant-prefix}-input-clear-icon {
margin: 0;
color: @disabled-color;
font-size: @font-size-sm;
vertical-align: -1px;
@ -23,8 +23,8 @@
visibility: hidden;
}
&:last-child {
margin-right: 0;
&-has-suffix {
margin: 0 @input-affix-margin;
}
}

View File

@ -4,15 +4,17 @@
@import './affix';
@import './allow-clear';
@input-prefix-cls: ~'@{ant-prefix}-input';
// Input styles
.@{ant-prefix}-input {
.@{input-prefix-cls} {
.reset-component();
.input();
//== Style for input-group: input with label, with button or dropdown...
&-group {
.reset-component();
.input-group(~'@{ant-prefix}-input');
.input-group(~'@{input-prefix-cls}');
&-wrapper {
display: inline-block;
width: 100%;
@ -34,10 +36,10 @@
&[type='color'] {
height: @input-height-base;
&.@{ant-prefix}-input-lg {
&.@{input-prefix-cls}-lg {
height: @input-height-lg;
}
&.@{ant-prefix}-input-sm {
&.@{input-prefix-cls}-sm {
height: @input-height-sm;
padding-top: 3px;
padding-bottom: 3px;

View File

@ -36,6 +36,8 @@
.disabled() {
color: @input-disabled-color;
background-color: @input-disabled-bg;
border-color: @input-border-color;
box-shadow: none;
cursor: not-allowed;
opacity: 1;
@ -205,6 +207,17 @@
}
}
}
// https://github.com/ant-design/ant-design/issues/31333
.@{ant-prefix}-cascader-picker {
margin: -9px (-@control-padding-horizontal);
background-color: transparent;
.@{ant-prefix}-cascader-input {
text-align: left;
border: 0;
box-shadow: none;
}
}
}
// Reset rounded corners

View File

@ -47,10 +47,10 @@
// allow-clear
.@{ant-prefix}-input-clear-icon {
&:last-child {
&-has-suffix {
.@{ant-prefix}-input-affix-wrapper-rtl & {
margin-right: @input-affix-margin;
margin-left: 0;
margin-right: 0;
margin-left: @input-affix-margin;
}
}

View File

@ -87,8 +87,7 @@
> span > a {
color: @menu-dark-highlight-color;
}
> .@{menu-prefix-cls}-submenu-title,
> .@{menu-prefix-cls}-submenu-title:hover {
> .@{menu-prefix-cls}-submenu-title {
> .@{menu-prefix-cls}-submenu-arrow {
opacity: 1;
&::after,

View File

@ -40,6 +40,15 @@
list-style: none;
}
// Overflow ellipsis
&-overflow {
display: flex;
&-item {
flex: none;
}
}
&-hidden,
&-submenu-hidden {
display: none;
@ -80,6 +89,10 @@
padding @animation-duration-slow @ease-in-out;
}
&-title-content {
transition: color @animation-duration-slow;
}
&-item a {
color: @menu-item-color;
&:hover {
@ -111,14 +124,6 @@
background-color: @border-color-split;
}
&-item:hover,
&-item-active,
&:not(&-inline) &-submenu-open,
&-submenu-active,
&-submenu-title:hover {
color: @menu-highlight-color;
}
&-horizontal &-item,
&-horizontal &-submenu {
margin-top: -1px;
@ -211,8 +216,6 @@
+ span {
margin-left: @menu-icon-margin-right;
opacity: 1;
// transition: opacity @animation-duration-slow @ease-in-out,
// width @animation-duration-slow @ease-in-out, color @animation-duration-slow;
transition: opacity @animation-duration-slow @ease-in-out, margin @animation-duration-slow,
color @animation-duration-slow;
}
@ -365,19 +368,19 @@
&:not(.@{menu-prefix-cls}-dark) {
> .@{menu-prefix-cls}-item,
> .@{menu-prefix-cls}-submenu {
margin: @menu-item-padding;
margin-top: -1px;
margin-bottom: 0;
padding: @menu-item-padding;
padding-right: 0;
padding-left: 0;
&:hover,
&-active,
&-open,
&-selected {
color: @menu-highlight-color;
border-bottom: 2px solid @menu-highlight-color;
&::after {
border-bottom: 2px solid @menu-highlight-color;
}
}
}
}
@ -388,7 +391,16 @@
top: 1px;
display: inline-block;
vertical-align: bottom;
border-bottom: 2px solid transparent;
&::after {
position: absolute;
right: @menu-item-padding-horizontal;
bottom: 0;
left: @menu-item-padding-horizontal;
border-bottom: 2px solid transparent;
transition: border-color @animation-duration-slow @ease-in-out;
content: '';
}
}
> .@{menu-prefix-cls}-submenu > .@{menu-prefix-cls}-submenu-title {
@ -625,8 +637,12 @@
&-submenu-disabled {
color: @disabled-color !important;
background: none;
border-color: transparent !important;
cursor: not-allowed;
&::after {
border-color: transparent !important;
}
a {
color: @disabled-color !important;
pointer-events: none;
@ -651,5 +667,6 @@
}
}
@import './light';
@import './dark';
@import './rtl';

View File

@ -0,0 +1,12 @@
.@{menu-prefix-cls} {
// light theme
&-light {
.@{menu-prefix-cls}-item:hover,
.@{menu-prefix-cls}-item-active,
.@{menu-prefix-cls}:not(.@{menu-prefix-cls}-inline) .@{menu-prefix-cls}-submenu-open,
.@{menu-prefix-cls}-submenu-active,
.@{menu-prefix-cls}-submenu-title:hover {
color: @menu-highlight-color;
}
}
}

View File

@ -51,7 +51,7 @@
font-size: @font-size-lg;
}
&-notice.move-up-leave.move-up-leave-active {
&-notice.@{ant-prefix}-move-up-leave.@{ant-prefix}-move-up-leave-active {
animation-name: MessageMoveOut;
animation-duration: 0.3s;
}

View File

@ -377,7 +377,6 @@
&-active {
background: @pagination-item-disabled-bg-active;
border-color: transparent;
a {
color: @pagination-item-disabled-color-active;
}

View File

@ -3,6 +3,11 @@
@popover-prefix-cls: ~'@{ant-prefix}-popover';
@popover-arrow-rotate-width: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
@popover-arrow-offset-vertical: 12px;
@popover-arrow-offset-horizontal: 16px;
.@{popover-prefix-cls} {
.reset-component();
@ -109,102 +114,139 @@
}
// Arrows
// .popover-arrow is outer, .popover-arrow:after is inner
&-arrow {
position: absolute;
display: block;
width: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
height: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
width: @popover-arrow-rotate-width;
height: @popover-arrow-rotate-width;
overflow: hidden;
background: transparent;
border-style: solid;
border-width: (sqrt(@popover-arrow-width * @popover-arrow-width * 2) / 2);
transform: rotate(45deg);
pointer-events: none;
&-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
width: @popover-arrow-width;
height: @popover-arrow-width;
margin: auto;
background-color: @popover-bg;
content: '';
pointer-events: auto;
}
}
&-placement-top > &-content > &-arrow,
&-placement-topLeft > &-content > &-arrow,
&-placement-topRight > &-content > &-arrow {
bottom: @popover-distance - @popover-arrow-width + 2.2px;
border-top-color: transparent;
border-right-color: @popover-bg;
border-bottom-color: @popover-bg;
border-left-color: transparent;
box-shadow: 3px 3px 7px fade(@black, 7%);
&-placement-top &-arrow,
&-placement-topLeft &-arrow,
&-placement-topRight &-arrow {
bottom: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 3px 3px 7px fade(@black, 7%);
transform: translateY((-@popover-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-top > &-content > &-arrow {
&-placement-top &-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&-placement-topLeft > &-content > &-arrow {
left: 16px;
}
&-placement-topRight > &-content > &-arrow {
right: 16px;
transform: translateX(-50%);
}
&-placement-right > &-content > &-arrow,
&-placement-rightTop > &-content > &-arrow,
&-placement-rightBottom > &-content > &-arrow {
left: @popover-distance - @popover-arrow-width + 2px;
border-top-color: transparent;
border-right-color: transparent;
border-bottom-color: @popover-bg;
border-left-color: @popover-bg;
box-shadow: -3px 3px 7px fade(@black, 7%);
&-placement-topLeft &-arrow {
left: @popover-arrow-offset-horizontal;
}
&-placement-right > &-content > &-arrow {
&-placement-topRight &-arrow {
right: @popover-arrow-offset-horizontal;
}
&-placement-right &-arrow,
&-placement-rightTop &-arrow,
&-placement-rightBottom &-arrow {
left: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: -3px 3px 7px fade(@black, 7%);
transform: translateX((@popover-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-right &-arrow {
top: 50%;
transform: translateY(-50%) rotate(45deg);
transform: translateY(-50%);
}
&-placement-rightTop > &-content > &-arrow {
top: 12px;
&-placement-rightTop &-arrow {
top: @popover-arrow-offset-vertical;
}
&-placement-rightBottom > &-content > &-arrow {
bottom: 12px;
&-placement-rightBottom &-arrow {
bottom: @popover-arrow-offset-vertical;
}
&-placement-bottom > &-content > &-arrow,
&-placement-bottomLeft > &-content > &-arrow,
&-placement-bottomRight > &-content > &-arrow {
top: @popover-distance - @popover-arrow-width + 2px;
border-top-color: @popover-bg;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: @popover-bg;
box-shadow: -2px -2px 5px fade(@black, 6%);
&-placement-bottom &-arrow,
&-placement-bottomLeft &-arrow,
&-placement-bottomRight &-arrow {
top: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: -2px -2px 5px fade(@black, 6%);
transform: translateY((@popover-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-bottom > &-content > &-arrow {
&-placement-bottom &-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&-placement-bottomLeft > &-content > &-arrow {
left: 16px;
}
&-placement-bottomRight > &-content > &-arrow {
right: 16px;
transform: translateX(-50%);
}
&-placement-left > &-content > &-arrow,
&-placement-leftTop > &-content > &-arrow,
&-placement-leftBottom > &-content > &-arrow {
right: @popover-distance - @popover-arrow-width + 2px;
border-top-color: @popover-bg;
border-right-color: @popover-bg;
border-bottom-color: transparent;
border-left-color: transparent;
box-shadow: 3px -3px 7px fade(@black, 7%);
&-placement-bottomLeft &-arrow {
left: @popover-arrow-offset-horizontal;
}
&-placement-left > &-content > &-arrow {
&-placement-bottomRight &-arrow {
right: @popover-arrow-offset-horizontal;
}
&-placement-left &-arrow,
&-placement-leftTop &-arrow,
&-placement-leftBottom &-arrow {
right: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 3px -3px 7px fade(@black, 7%);
transform: translateX((-@popover-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-left &-arrow {
top: 50%;
transform: translateY(-50%) rotate(45deg);
transform: translateY(-50%);
}
&-placement-leftTop > &-content > &-arrow {
top: 12px;
&-placement-leftTop &-arrow {
top: @popover-arrow-offset-vertical;
}
&-placement-leftBottom > &-content > &-arrow {
bottom: 12px;
&-placement-leftBottom &-arrow {
bottom: @popover-arrow-offset-vertical;
}
}
.generator-popover-preset-color(@i: length(@preset-colors)) when (@i > 0) {
.generator-popover-preset-color(@i - 1);
@color: extract(@preset-colors, @i);
@lightColor: '@{color}-6';
.@{popover-prefix-cls}-@{color} {
.@{popover-prefix-cls}-inner {
background-color: @@lightColor;
}
.@{popover-prefix-cls}-arrow {
&-content {
background-color: @@lightColor;
}
}
}
}
.generator-popover-preset-color();
@import './rtl';

View File

@ -189,15 +189,15 @@
@keyframes ~"@{ant-prefix}-progress-active" {
0% {
width: 0;
transform: translateX(-100%) scaleX(0);
opacity: 0.1;
}
20% {
width: 0;
transform: translateX(-100%) scaleX(0);
opacity: 0.5;
}
100% {
width: 100%;
transform: translateX(0) scaleX(1);
opacity: 0;
}
}

View File

@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
namespace AntDesign
{
public enum RadioButtonStyle
{
Outline,
Solid
}
}

View File

@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AntDesign
{
public class EnumRadioGroup<TEnum> : RadioGroup<TEnum>
{
public EnumRadioGroup()
{
Options = EnumHelper<TEnum>.GetValueLabelList()
.Select(x => new RadioOption<TEnum> { Value = x.Value, Label = x.Label })
.ToArray();
}
}
}

View File

@ -3,9 +3,29 @@
@inherits AntInputComponentBase<TValue>
<CascadingValue Value="this" IsFixed="@true">
<CascadingValue Value="true" Name="InGroup" IsFixed>
<div class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref">
@ChildContent
</div>
</CascadingValue>
<CascadingValue Value="true" Name="InGroup" IsFixed>
<div class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref">
@if (Options.Value != null)
{
if (Options.IsT0)
{
foreach (var item in Options.AsT0)
{
<Radio Value="@item">@item</Radio>
}
}
else
{
@foreach (var radio in Options.AsT1)
{
<Radio TValue="TValue" Value="radio.Value" Disabled="radio.Disabled">@radio.Label</Radio>
}
}
}
else
{
@ChildContent
}
</div>
</CascadingValue>
</CascadingValue>

View File

@ -16,7 +16,7 @@ namespace AntDesign
public bool Disabled { get; set; }
[Parameter]
public string ButtonStyle { get; set; } = "outline";
public RadioButtonStyle ButtonStyle { get; set; } = RadioButtonStyle.Outline;
[Parameter]
public string Name { get; set; }
@ -35,6 +35,9 @@ namespace AntDesign
[Parameter]
public EventCallback<TValue> OnChange { get; set; }
[Parameter]
public OneOf<string[], RadioOption<TValue>[]> Options { get; set; }
private List<Radio<TValue>> _radioItems = new List<Radio<TValue>>();
private TValue _defaultValue;
@ -42,13 +45,19 @@ namespace AntDesign
private bool _hasDefaultValue;
private bool _defaultValueSetted;
private static readonly Dictionary<RadioButtonStyle, string> _buttonStyleDics = new()
{
[RadioButtonStyle.Outline] = "outline",
[RadioButtonStyle.Solid] = "solid",
};
protected override void OnInitialized()
{
string prefixCls = "ant-radio-group";
ClassMapper.Add(prefixCls)
.If($"{prefixCls}-large", () => Size == "large")
.If($"{prefixCls}-small", () => Size == "small")
.GetIf(() => $"{prefixCls}-{ButtonStyle}", () => ButtonStyle.IsIn("outline", "solid"))
.GetIf(() => $"{prefixCls}-{_buttonStyleDics[ButtonStyle]}", () => ButtonStyle.IsIn(RadioButtonStyle.Outline, RadioButtonStyle.Solid))
.If($"{prefixCls}-rtl", () => RTL)
;

View File

@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
namespace AntDesign
{
public class RadioOption<TValue>
{
public string Label { get; set; }
public TValue Value { get; set; }
public bool Disabled { get; set; }
}
}

View File

@ -13,7 +13,6 @@
display: inline-block;
font-size: 0;
line-height: unset;
.@{ant-prefix}-badge-count {
z-index: 1;

View File

@ -33,16 +33,20 @@
}
> div {
transition: all 0.3s;
transition: all 0.3s, outline 0s;
&:hover,
&:focus-visible {
transform: @rate-star-hover-scale;
}
&:focus:not(:focus-visible) {
&:focus {
outline: 0;
}
&:focus-visible {
outline: 1px dashed @rate-star-color;
}
}
&-first,

View File

@ -1,12 +1,12 @@
@using AntDesign.Internal
@using AntDesign.Select.Internal
@namespace AntDesign
@inherits AntInputComponentBase<TItemValue>
@inherits SelectBase<TItemValue, TItem>
@typeparam TItemValue
@typeparam TItem
<CascadingValue Value="this" IsFixed="true">
<CascadingValue Value=@("ant-select-dropdown") Name="PrefixCls">
<CascadingValue Value="this" IsFixed>
<CascadingValue Value=@("ant-select-dropdown") Name="PrefixCls" IsFixed>
<div class="@ClassMapper.Class" style="@Style" id="@Id" tabindex="-1" @ref="Ref">
<OverlayTrigger @ref="@_dropDown"
Visible="Open"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,883 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#region using block
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AntDesign.Internal;
using AntDesign.Select;
using AntDesign.Select.Internal;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using OneOf;
#endregion
namespace AntDesign
{
public abstract class SelectBase<TItemValue, TItem> : AntInputComponentBase<TItemValue>
{
protected const string DefaultWidth = "width: 100%;";
private SelectOptionItem<TItemValue, TItem> _activeOption;
protected OverlayTrigger _dropDown;
internal ElementReference _dropDownRef;
internal ElementReference _inputRef;
protected bool _isInitialized;
protected bool _isPrimitive;
/// <summary>
/// How long (number of characters) a tag will be.
/// Only for Mode = "multiple" or Mode = "tags"
/// </summary>
/// <value>
/// The maximum length of the tag text.
/// </value>
private OneOf<int, ResponsiveTag> _maxTagCount;
protected int _maxTagCountAsInt;
protected string _prevSearchValue = string.Empty;
protected string _searchValue = string.Empty;
protected SelectContent<TItemValue, TItem> _selectContent;
protected IEnumerable<TItemValue> _selectedValues;
protected Action<TItem, string> _setLabel;
protected Action<TItem, TItemValue> _setValue;
/// <summary>
/// Show clear button.
/// </summary>
[Parameter] public bool AllowClear { get; set; }
/// <summary>
/// Whether the current search will be cleared on selecting an item.
/// </summary>
[Parameter] public bool AutoClearSearchValue { get; set; } = true;
/// <summary>
/// Whether the Select component is disabled.
/// </summary>
[Parameter] public bool Disabled { get; set; }
/// <summary>
/// Set mode of Select - default | multiple | tags
/// </summary>
[Parameter] public string Mode { get; set; } = "default";
/// <summary>
/// Indicates whether the search function is active or not. Always true for mode tags.
/// </summary>
[Parameter] public bool EnableSearch { get; set; }
/// <summary>
/// Show loading indicator. You have to write the loading logic on your own.
/// </summary>
[Parameter] public bool Loading { get; set; }
/// <summary>
/// Controlled open state of dropdown.
/// </summary>
[Parameter] public bool Open { get; set; }
/// <summary>
/// Placeholder of select.
/// </summary>
[Parameter] public string Placeholder { get; set; }
/// <summary>
/// Called when focus.
/// </summary>
[Parameter] public Action OnFocus { get; set; }
/// <summary>
/// The name of the property to be used as a group indicator.
/// If the value is set, the entries are displayed in groups.
/// Use additional SortByGroup and SortByLabel.
/// </summary>
[Parameter] public SortDirection SortByGroup { get; set; } = SortDirection.None;
/// <summary>
/// Sort items by label value. None | Ascending | Descending
/// </summary>
[Parameter] public SortDirection SortByLabel { get; set; } = SortDirection.None;
/// <summary>
/// Hides the selected items when they are selected.
/// </summary>
[Parameter] public bool HideSelected { get; set; }
/// <summary>
/// Used for the two-way binding.
/// </summary>
[Parameter] public override EventCallback<TItemValue> ValueChanged { get; set; }
/// <summary>
/// Used for the two-way binding.
/// </summary>
[Parameter] public EventCallback<IEnumerable<TItemValue>> ValuesChanged { get; set; }
/// <summary>
/// The custom suffix icon.
/// </summary>
[Parameter] public RenderFragment SuffixIcon { get; set; }
/// <summary>
/// The custom prefix icon.
/// </summary>
[Parameter] public RenderFragment PrefixIcon { get; set; }
protected IEnumerable<TItemValue> _defaultValues;
protected bool _defaultValuesHasItems;
/// <summary>
///
/// Used when Mode = multiple | tags - The values are used during initialization and when pressing the Reset button within Forms.
/// </summary>
[Parameter]
public IEnumerable<TItemValue> DefaultValues
{
get => _defaultValues;
set
{
if (value != null && _defaultValues != null)
{
var hasChanged = !value.SequenceEqual(_defaultValues);
if (!hasChanged)
return;
_defaultValuesHasItems = value.Any();
_defaultValues = value;
}
else if (value != null && _defaultValues == null)
{
_defaultValuesHasItems = value.Any();
_defaultValues = value;
}
else if (value == null && _defaultValues != null)
{
_defaultValuesHasItems = false;
_defaultValues = default;
}
}
}
/// <summary>
/// Called when the user clears the selection.
/// </summary>
[Parameter] public Action OnClearSelected { get; set; }
internal bool IsResponsive { get; set; }
internal HashSet<SelectOptionItem<TItemValue, TItem>> SelectOptionItems { get; } = new();
internal List<SelectOptionItem<TItemValue, TItem>> SelectedOptionItems { get; } = new();
/// <summary>
/// Called when the selected item changes.
/// </summary>
[Parameter] public Action<TItem> OnSelectedItemChanged { get; set; }
/// <summary>
/// Called when the selected items changes.
/// </summary>
[Parameter] public Action<IEnumerable<TItem>> OnSelectedItemsChanged { get; set; }
internal virtual SelectMode SelectMode => Mode.ToSelectMode();
/// <summary>
/// Currently active (highlighted) option.
/// It does not have to be equal to selected option.
/// </summary>
internal SelectOptionItem<TItemValue, TItem> ActiveOption
{
get => _activeOption;
set
{
if (_activeOption != value)
{
if (_activeOption != null && _activeOption.IsActive)
{
_activeOption.IsActive = false;
}
_activeOption = value;
if (_activeOption != null && !_activeOption.IsActive)
{
_activeOption.IsActive = true;
}
}
}
}
/// <summary>
/// Get or set the selected values.
/// </summary>
[Parameter]
public virtual IEnumerable<TItemValue> Values
{
get => _selectedValues;
set
{
if (value != null && _selectedValues != null)
{
var hasChanged = !value.SequenceEqual(_selectedValues);
if (!hasChanged)
{
return;
}
_selectedValues = value;
_ = OnValuesChangeAsync(value);
}
else if (value != null && _selectedValues == null)
{
_selectedValues = value;
_ = OnValuesChangeAsync(value);
}
else if (value == null && _selectedValues != null)
{
_selectedValues = default;
_ = OnValuesChangeAsync(default);
}
if (_isNotifyFieldChanged && Form?.ValidateOnChange == true)
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
}
/// <summary>
/// Converts custom tag (a string) to TItemValue type.
/// </summary>
[Parameter]
public Func<string, TItemValue> CustomTagLabelToValue { get; set; } =
label => (TItemValue)TypeDescriptor.GetConverter(typeof(TItemValue)).ConvertFromInvariantString(label);
/// <summary>
/// Determines if SelectOptions has any selected items
/// </summary>
/// <returns>true if SelectOptions has any selected Items, otherwise false</returns>
internal bool HasValue => SelectOptionItems.Any(x => x.IsSelected);
/// <summary>
/// Returns the value of EnableSearch parameter
/// </summary>
/// <returns>true if search is enabled</returns>
internal bool IsSearchEnabled => EnableSearch;
/// <summary>
/// Sorted list of SelectOptionItems
/// </summary>
protected internal IEnumerable<SelectOptionItem<TItemValue, TItem>> SortedSelectOptionItems
{
get
{
var selectOption = SelectOptionItems;
if (SortByGroup == SortDirection.Ascending && SortByLabel == SortDirection.None)
{
return selectOption.OrderBy(g => g.GroupName);
}
if (SortByGroup == SortDirection.Descending && SortByLabel == SortDirection.None)
{
return selectOption.OrderByDescending(g => g.GroupName);
}
if (SortByGroup == SortDirection.None && SortByLabel == SortDirection.Ascending)
{
return selectOption.OrderBy(l => l.Label);
}
if (SortByGroup == SortDirection.None && SortByLabel == SortDirection.Descending)
{
return selectOption.OrderByDescending(l => l.Label);
}
if (SortByGroup == SortDirection.Ascending && SortByLabel == SortDirection.Ascending)
{
return selectOption.OrderBy(g => g.GroupName).ThenBy(l => l.Label);
}
if (SortByGroup == SortDirection.Ascending && SortByLabel == SortDirection.Descending)
{
return selectOption.OrderBy(g => g.GroupName).OrderByDescending(l => l.Label);
}
if (SortByGroup == SortDirection.Descending && SortByLabel == SortDirection.Ascending)
{
return selectOption.OrderByDescending(g => g.GroupName).ThenBy(l => l.Label);
}
if (SortByGroup == SortDirection.Descending && SortByLabel == SortDirection.Descending)
{
return selectOption.OrderByDescending(g => g.GroupName).OrderByDescending(l => l.Label);
}
return selectOption;
}
}
/// <summary>
/// Used for rendering select options manually.
/// </summary>
[Parameter] public RenderFragment SelectOptions { get; set; }
internal List<SelectOptionItem<TItemValue, TItem>> AddedTags { get; } = new();
internal SelectOptionItem<TItemValue, TItem> CustomTagSelectOptionItem { get; set; }
internal bool Focused { get; set; }
internal bool HasTagCount { get; set; }
/// <summary>
/// How long (number of characters) a tag will be.
/// Only for Mode = "multiple" or Mode = "tags"
/// </summary>
/// <value>
/// The maximum length of the tag text.
/// </value>
[Parameter] public int MaxTagTextLength { get; set; }
/// <summary>
/// Whether to embed label in value, turn the format of value from TItemValue to string (JSON)
/// e.g. { "value": TItemValue, "label": "Label value" }
/// </summary>
[Parameter]
public bool LabelInValue { get; set; }
/// <summary>
/// Max tag count to show. responsive will cost render performance.
/// </summary>
[Parameter]
public OneOf<int, ResponsiveTag> MaxTagCount
{
get => _maxTagCount;
set
{
_maxTagCount = value;
value.Switch(intValue =>
{
IsResponsive = false;
HasTagCount = intValue > 0;
_maxTagCountAsInt = intValue;
}, enumValue =>
{
IsResponsive = enumValue == ResponsiveTag.Responsive;
HasTagCount = false;
});
}
}
/// <summary>
/// Returns a true/false if the placeholder should be displayed or not.
/// </summary>
/// <returns>true if SelectOptions has no values and the searchValue is empty; otherwise false </returns>
protected bool ShowPlaceholder => !HasValue && string.IsNullOrEmpty(_searchValue);
/// <summary>
/// The Method is called every time if the value of the @bind-Values was changed by the two-way binding.
/// </summary>
protected async Task OnValuesChangeAsync(IEnumerable<TItemValue> values)
{
if (
!_isInitialized) // This is important because otherwise the initial value is overwritten by the EventCallback of ValueChanged and would be NULL.
{
return;
}
if (!SelectOptionItems.Any())
{
return;
}
if (values == null)
{
await ValuesChanged.InvokeAsync(default);
OnSelectedItemsChanged?.Invoke(default);
return;
}
EvaluateValuesChangedOutsideComponent(values);
if (_dropDown.IsOverlayShow())
{
//A delay forces a refresh better than StateHasChanged().
//For example when a tag is added that is causing SelectContent to grow,
//this Task.Delay will actually allow to reposition the Overlay to match
//new size of SelectContent.
await Task.Delay(1);
await UpdateOverlayPositionAsync();
}
OnSelectedItemsChanged?.Invoke(SelectedOptionItems.Select(s => s.Item));
await ValuesChanged.InvokeAsync(Values);
}
/// <summary>
/// When bind-Values is changed outside of the component, then component
/// selected items have to be reselected according to new values passed.
/// TODO: (Perf) Consider using hash to identify if the passed values are different from currently selected.
/// </summary>
/// <param name="values">The values that need to be selected.</param>
private void EvaluateValuesChangedOutsideComponent(IEnumerable<TItemValue> values)
{
var newSelectedItems = new List<TItem>();
var deselectList = SelectedOptionItems.ToDictionary(item => item.Value, item => item);
foreach (var value in values.ToList())
{
SelectOptionItem<TItemValue, TItem> result;
if (SelectMode == SelectMode.Multiple)
{
result = SelectOptionItems.FirstOrDefault(x =>
!x.IsSelected && EqualityComparer<TItemValue>.Default.Equals(x.Value, value));
if (result != null && !result.IsDisabled)
{
result.IsSelected = true;
SelectedOptionItems.Add(result);
}
deselectList.Remove(value);
}
else
{
result = SelectOptionItems.FirstOrDefault(x =>
EqualityComparer<TItemValue>.Default.Equals(x.Value, value));
if (result is null) //tag delivered from outside, needs to be added to the list of options
{
result = CreateSelectOptionItem(value.ToString(), true);
result.IsSelected = true;
AddedTags.Add(result);
SelectOptionItems.Add(result);
SelectedOptionItems.Add(result);
}
else if (result != null && !result.IsSelected && !result.IsDisabled)
{
result.IsSelected = true;
SelectedOptionItems.Add(result);
}
deselectList.Remove(value);
}
}
if (deselectList.Count > 0)
{
foreach (var item in deselectList)
{
item.Value.IsSelected = false;
SelectedOptionItems.Remove(item.Value);
if (item.Value.IsAddedTag)
{
SelectOptionItems.Remove(item.Value);
AddedTags.Remove(item.Value);
}
}
}
}
/// <summary>
/// Creates the select option item. Mostly meant to create new tags, that is why IsAddedTag is hardcoded to true.
/// </summary>
/// <param name="label">Creation based on passed label</param>
/// <param name="isActive">if set to <c>true</c> [is active].</param>
/// <returns></returns>
protected SelectOptionItem<TItemValue, TItem> CreateSelectOptionItem(string label, bool isActive)
{
var value = CustomTagLabelToValue.Invoke(label);
TItem item;
if (_isPrimitive)
{
item = (TItem)TypeDescriptor.GetConverter(typeof(TItem)).ConvertFromInvariantString(_searchValue);
}
else
{
if (_setValue == null)
{
item = THelper.ChangeType<TItem>(value);
}
else
{
item = Activator.CreateInstance<TItem>();
_setValue(item, value);
}
_setLabel?.Invoke(item, _searchValue);
}
return new SelectOptionItem<TItemValue, TItem>
{
Label = label,
Value = value,
Item = item,
IsActive = isActive,
IsSelected = false,
IsAddedTag = true
};
}
internal bool IsDropdownShown()
{
return _dropDown.IsOverlayShow();
}
protected override void OnInitialized()
{
SetClassMap();
if (string.IsNullOrWhiteSpace(Style))
{
Style = DefaultWidth;
}
_isInitialized = true;
base.OnInitialized();
}
protected void OnOverlayHide()
{
if (!IsSearchEnabled)
{
return;
}
if (!AutoClearSearchValue)
{
return;
}
if (string.IsNullOrWhiteSpace(_searchValue))
{
return;
}
_searchValue = string.Empty;
_prevSearchValue = string.Empty;
if (SelectMode != SelectMode.Default && HideSelected)
{
SelectOptionItems.Where(x => !x.IsSelected && x.IsHidden)
.ForEach(i => i.IsHidden = false);
}
else
{
if (CustomTagSelectOptionItem is not null)
{
SelectOptionItems.Remove(CustomTagSelectOptionItem);
CustomTagSelectOptionItem = null;
}
SelectOptionItems.Where(x => x.IsHidden)
.ForEach(i => i.IsHidden = false);
}
}
/// <summary>
/// A separate method to invoke ValuesChanged and OnSelectedItemsChanged to reduce code duplicates.
/// </summary>
protected void InvokeOnSelectedItemChanged(SelectOptionItem<TItemValue, TItem> selectOptionItem = null)
{
if (selectOptionItem == null)
{
OnSelectedItemsChanged?.Invoke(default);
}
else
{
if (LabelInValue && SelectOptions != null)
{
// Embed the label into the value and return the result as json string.
var valueLabel = new ValueLabel<TItemValue>
{
Value = selectOptionItem.Value, Label = selectOptionItem.Label
};
var json = JsonSerializer.Serialize(valueLabel);
OnSelectedItemChanged?.Invoke((TItem)Convert.ChangeType(json, typeof(TItem)));
}
else
{
OnSelectedItemChanged?.Invoke(selectOptionItem.Item);
}
}
}
/// <summary>
/// The method is called every time if the user select/de-select a item by mouse or keyboard.
/// Don't change the IsSelected property outside of this function.
/// </summary>
protected internal async Task SetValueAsync(SelectOptionItem<TItemValue, TItem> selectOption)
{
if (selectOption == null)
{
throw new ArgumentNullException(nameof(selectOption));
}
if (SelectMode == SelectMode.Default)
{
if (SelectedOptionItems.Count > 0)
{
SelectedOptionItems[0].IsSelected = false;
SelectedOptionItems[0] = selectOption;
}
else
{
SelectedOptionItems.Add(selectOption);
}
selectOption.IsSelected = true;
await ValueChanged.InvokeAsync(selectOption.Value);
InvokeOnSelectedItemChanged(selectOption);
}
else
{
selectOption.IsSelected = !selectOption.IsSelected;
if (selectOption.IsSelected)
{
if (HideSelected && !selectOption.IsHidden)
{
selectOption.IsHidden = true;
}
if (IsSearchEnabled && !string.IsNullOrWhiteSpace(_searchValue))
{
ClearSearch();
}
if (selectOption.IsAddedTag)
{
CustomTagSelectOptionItem = null;
AddedTags.Add(selectOption);
SelectOptionItems.Add(selectOption);
}
}
else
{
if (selectOption.IsHidden)
{
selectOption.IsHidden = false;
}
if (selectOption.IsAddedTag)
{
SelectOptionItems.Remove(selectOption);
SelectedOptionItems.Remove(selectOption);
if (selectOption.IsAddedTag && SelectOptions != null)
{
AddedTags.Remove(selectOption);
}
}
if (IsResponsive)
{
await _selectContent.RemovedItem();
}
}
if (EnableSearch || SelectMode == SelectMode.Tags)
{
await SetInputFocusAsync();
}
await InvokeValuesChanged(selectOption);
await UpdateOverlayPositionAsync();
}
}
protected async Task InvokeValuesChanged(SelectOptionItem<TItemValue, TItem> newSelection = null)
{
List<TItemValue> newSelectedValues;
if (newSelection is null || Values is null)
{
newSelectedValues = new List<TItemValue>();
SelectedOptionItems.Clear();
SelectOptionItems.Where(x => x.IsSelected)
.ForEach(i =>
{
newSelectedValues.Add(i.Value);
SelectedOptionItems.Add(i);
});
}
else
{
newSelectedValues = Values.ToList();
if (newSelection.IsSelected)
{
newSelectedValues.Add(newSelection.Value);
SelectedOptionItems.Add(newSelection);
}
else
{
newSelectedValues.Remove(newSelection.Value);
SelectedOptionItems.Remove(newSelection);
}
}
if (ValuesChanged.HasDelegate)
{
await ValuesChanged.InvokeAsync(newSelectedValues);
}
else
{
Values = newSelectedValues;
StateHasChanged();
}
}
protected void ClearSearch()
{
if (SelectMode != SelectMode.Default)
{
foreach (var item in SelectOptionItems)
{
if (item.IsHidden)
{
if (HideSelected && !item.IsSelected || !HideSelected)
{
item.IsHidden = false;
}
}
}
foreach (var item in AddedTags)
{
if (item.IsHidden)
{
if (HideSelected && !item.IsSelected || !HideSelected)
{
item.IsHidden = false;
}
}
}
}
_searchValue = string.Empty;
_prevSearchValue = string.Empty;
}
/// <summary>
/// Method is called via EventCallBack after the user clicked on the Clear icon inside the Input element.
/// Set the IsSelected and IsHidden properties for all items to False. It updates the overlay position if
/// the SelectMode is Tags or Multiple. Invoke the OnClearSelected Action. Set the Value(s) to default.
/// </summary>
protected async Task OnInputClearClickAsync(MouseEventArgs _)
{
List<SelectOptionItem<TItemValue, TItem>> tagItems = new();
SelectOptionItems.Where(c => c.IsSelected)
.ForEach(i =>
{
i.IsSelected = false;
i.IsHidden = false;
if (i.IsAddedTag)
{
tagItems.Add(i);
}
});
//When clearing, also remove all added tags that are kept after adding in SelectOptionItems
if (tagItems.Count > 0)
{
foreach (var item in tagItems)
{
SelectOptionItems.Remove(item);
}
}
AddedTags.Clear();
ActiveOption = SelectOptionItems.FirstOrDefault();
CustomTagSelectOptionItem = null;
SelectedOptionItems.Clear();
await ClearSelectedAsync();
if (SelectMode != SelectMode.Default)
{
await Task.Delay(1); // Todo - Workaround because UI does not refresh
await UpdateOverlayPositionAsync();
StateHasChanged(); // Todo - Workaround because UI does not refresh
}
OnClearSelected?.Invoke();
}
/// <summary>
/// Check if Focused property is False; Set the Focused property to true, change the
/// style and set the Focus on the Input element via DOM. It also invoke the OnFocus Action.
/// </summary>
protected async Task SetInputFocusAsync()
{
if (!Focused)
{
Focused = true;
SetClassMap();
await FocusAsync(_inputRef);
OnFocus?.Invoke();
}
}
/// <summary>
/// Inform the Overlay to update the position.
/// </summary>
internal async Task UpdateOverlayPositionAsync()
{
if (_dropDown.Visible)
{
await _dropDown.GetOverlayComponent().UpdatePosition();
}
}
internal async Task OnArrowClick(MouseEventArgs args)
{
await _dropDown.OnClickDiv(args);
}
/// <summary>
/// Close the overlay
/// </summary>
/// <returns></returns>
internal async Task CloseAsync()
{
await _dropDown.Hide(true);
}
/// <summary>
/// Called by the Form reset method
/// </summary>
internal override void ResetValue()
{
_ = ClearSelectedAsync();
}
/// <summary>
/// Clears the selectValue(s) property and send the null(default) value back through the two-way binding.
/// </summary>
protected async Task ClearSelectedAsync()
{
if (SelectMode == SelectMode.Default)
{
OnSelectedItemChanged?.Invoke(default);
await ValueChanged.InvokeAsync(default);
}
else
{
OnSelectedItemsChanged?.Invoke(default);
await ValuesChanged.InvokeAsync(default);
}
}
protected abstract void SetClassMap();
}
}

View File

@ -15,7 +15,7 @@ namespace AntDesign.Select.Internal
{
public partial class SelectContent<TItemValue, TItem> : IDisposable
{
[CascadingParameter(Name = "ParentSelect")] internal Select<TItemValue, TItem> ParentSelect { get; set; }
[CascadingParameter(Name = "ParentSelect")] internal SelectBase<TItemValue, TItem> ParentSelect { get; set; }
[CascadingParameter(Name = "ParentLabelTemplate")] internal RenderFragment<TItem> ParentLabelTemplate { get; set; }
[CascadingParameter(Name = "ParentMaxTagPlaceholerTemplate")] internal RenderFragment<IEnumerable<TItem>> ParentMaxTagPlaceholerTemplate { get; set; }
[CascadingParameter(Name = "ShowSearchIcon")] internal bool ShowSearchIcon { get; set; }
@ -110,7 +110,7 @@ namespace AntDesign.Select.Internal
{
DomEventListener.AddShared<JsonElement>("window", "beforeunload", Reloading);
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventKeys, ParentSelect._inputRef, new[] { "ArrowUp", "ArrowDown" });
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventEnterOnOverlayVisible, ParentSelect._inputRef, ParentSelect.DropDownRef);
await Js.InvokeVoidAsync(JSInteropConstants.AddPreventEnterOnOverlayVisible, ParentSelect._inputRef, ParentSelect._dropDownRef);
}
if (ParentSelect.IsResponsive)
{

View File

@ -117,7 +117,7 @@
&-arrow {
.iconfont-mixin();
position: absolute;
top: 53%;
top: 50%;
right: @control-padding-horizontal - 1px;
width: @font-size-sm;
height: @font-size-sm;
@ -199,21 +199,21 @@
outline: none;
box-shadow: @box-shadow-base;
&.slide-up-enter.slide-up-enter-active&-placement-bottomLeft,
&.slide-up-appear.slide-up-appear-active&-placement-bottomLeft {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-bottomLeft {
animation-name: antSlideUpIn;
}
&.slide-up-enter.slide-up-enter-active&-placement-topLeft,
&.slide-up-appear.slide-up-appear-active&-placement-topLeft {
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft {
animation-name: antSlideDownIn;
}
&.slide-up-leave.slide-up-leave-active&-placement-bottomLeft {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-bottomLeft {
animation-name: antSlideUpOut;
}
&.slide-up-leave.slide-up-leave-active&-placement-topLeft {
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft {
animation-name: antSlideDownOut;
}
@ -286,6 +286,9 @@
}
&-disabled {
&.@{select-prefix-cls}-item-option-selected {
background-color: @select-multiple-disabled-background;
}
color: @disabled-color;
cursor: not-allowed;
}

View File

@ -7,11 +7,4 @@
input::-ms-reveal {
display: none;
}
&,
*,
*::before,
*::after {
box-sizing: border-box; // 1
}
}

View File

@ -3,8 +3,8 @@
.modal-mask() {
pointer-events: none;
&.zoom-enter,
&.zoom-appear {
&.@{ant-prefix}-zoom-enter,
&.@{ant-prefix}zoom-appear {
transform: none; // reset scale avoid mousePosition bug
opacity: 0;
animation-duration: @animation-duration-slow;

View File

@ -314,6 +314,7 @@
@table-header-sort-bg: #262626;
@table-header-filter-active-bg: #434343;
@table-header-sort-active-bg: #303030;
@table-fixed-header-sort-active-bg: #222;
@table-filter-btns-bg: @popover-background;
@table-expanded-row-bg: @table-header-bg;
@table-filter-dropdown-bg: @popover-background;

View File

@ -149,6 +149,7 @@
// Disabled states
@disabled-color: fade(#000, 25%);
@disabled-bg: @background-color-base;
@disabled-active-bg: tint(@black, 90%);
@disabled-color-dark: fade(#fff, 35%);
// Shadow
@ -270,7 +271,7 @@
@radio-button-color: @btn-default-color;
@radio-button-hover-color: @primary-5;
@radio-button-active-color: @primary-7;
@radio-disabled-button-checked-bg: tint(@black, 90%);
@radio-disabled-button-checked-bg: @disabled-active-bg;
@radio-disabled-button-checked-color: @disabled-color;
@radio-wrapper-margin-right: 8px;
@ -557,7 +558,8 @@
@menu-item-vertical-margin: 4px;
@menu-item-font-size: @font-size-base;
@menu-item-boundary-margin: 8px;
@menu-item-padding: 0 20px;
@menu-item-padding-horizontal: 20px;
@menu-item-padding: 0 @menu-item-padding-horizontal;
@menu-horizontal-line-height: 46px;
@menu-icon-margin-right: 10px;
@menu-icon-size: @menu-item-font-size;
@ -613,6 +615,8 @@
// Sorter
// Legacy: `table-header-sort-active-bg` is used for hover not real active
@table-header-sort-active-bg: rgba(0, 0, 0, 0.04);
@table-fixed-header-sort-active-bg: hsv(0, 0, 96%);
// Filter
@table-header-filter-active-bg: rgba(0, 0, 0, 0.04);
@table-filter-btns-bg: inherit;
@ -636,7 +640,7 @@
@picker-basic-cell-hover-color: @item-hover-bg;
@picker-basic-cell-active-with-range-color: @primary-1;
@picker-basic-cell-hover-with-range-color: lighten(@primary-color, 35%);
@picker-basic-cell-disabled-bg: @disabled-bg;
@picker-basic-cell-disabled-bg: rgba(0, 0, 0, 0.04);
@picker-border-color: @border-color-split;
@picker-date-hover-range-border-color: lighten(@primary-color, 20%);
@picker-date-hover-range-color: @picker-basic-cell-hover-with-range-color;
@ -796,8 +800,8 @@
@pagination-font-weight-active: 500;
@pagination-item-bg-active: @component-background;
@pagination-item-link-bg: @component-background;
@pagination-item-disabled-color-active: @white;
@pagination-item-disabled-bg-active: darken(@disabled-bg, 10%);
@pagination-item-disabled-color-active: @disabled-color;
@pagination-item-disabled-bg-active: @disabled-active-bg;
@pagination-item-input-bg: @component-background;
@pagination-mini-options-size-changer-top: 0px;

View File

@ -62,7 +62,7 @@ else if (!IsHeader && RowSpan != 0 && ColSpan != 0)
</td>
}
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan">
<td class="@ClassMapper.Class" style="@FixedStyle @Style" rowspan="@RowSpan" colspan="@ColSpan" data-label="@Context.HeaderColumns[ColIndex].Title">
@if (ColIndex == Table.TreeExpandIconColumnIndex && Table.TreeMode)
{
<span class="ant-table-row-indent indent-level-@RowData.Level" style="padding-left: @((CssSizeLength)(RowData.Level * Table.IndentSize));"></span>

View File

@ -41,6 +41,8 @@ namespace AntDesign
private TData _field;
public override string Title { get => base.Title ?? DisplayName ?? FieldName; set => base.Title = value; }
[Parameter]
public string DataIndex { get; set; }
@ -120,7 +122,7 @@ namespace AntDesign
private Type _columnDataType;
public string? DisplayName { get; private set; }
public string DisplayName { get; private set; }
public string FieldName { get; private set; }
@ -151,8 +153,6 @@ namespace AntDesign
private string[] _selectedFilterValues;
//private ElementReference _filterTriggerRef;
protected override void OnInitialized()
{
base.OnInitialized();

View File

@ -38,7 +38,7 @@ namespace AntDesign
public bool IsSummary { get; set; }
[Parameter]
public string Title { get; set; }
public virtual string Title { get; set; }
[Parameter]
public RenderFragment TitleTemplate { get; set; }

View File

@ -132,6 +132,9 @@ namespace AntDesign
}
}
[Parameter]
public bool Responsive { get; set; } = true;
[Inject]
private IDomEventListener DomEventListener { get; set; }
@ -151,7 +154,7 @@ namespace AntDesign
private bool _hasFixLeft;
private bool _hasFixRight;
private int _treeExpandIconColumnIndex;
private ClassMapper _wrapperClassMapper = new ClassMapper();
private readonly ClassMapper _wrapperClassMapper = new ClassMapper();
private string TableLayoutStyle => TableLayout == null ? "" : $"table-layout: {TableLayout};";
private ElementReference _tableHeaderRef;
@ -329,6 +332,7 @@ namespace AntDesign
_wrapperClassMapper
.Add($"{prefixCls}-wrapper")
.If($"{prefixCls}-responsive", () => Responsive) // Not implemented in ant design
.If($"{prefixCls}-wrapper-rtl", () => RTL);
}

View File

@ -12,13 +12,12 @@
> .@{table-prefix-cls}-container {
// ============================ Content ============================
border: @table-border;
border-right: 0;
border-bottom: 0;
border-left: @table-border;
> .@{table-prefix-cls}-content,
> .@{table-prefix-cls}-header,
> .@{table-prefix-cls}-body {
> .@{table-prefix-cls}-body,
> .@{table-prefix-cls}-summary {
> table {
// ============================= Cell =============================
> thead > tr > th,
@ -66,6 +65,13 @@
}
}
}
> .@{table-prefix-cls}-content,
> .@{table-prefix-cls}-header {
> table {
border-top: @table-border;
}
}
}
&.@{table-prefix-cls}-scroll-horizontal {

View File

@ -1 +1,2 @@
@import './index.less';
@import './patch.less';

View File

@ -60,6 +60,12 @@
text-overflow: ellipsis;
}
}
.@{table-prefix-cls}-column-title {
overflow: hidden;
text-overflow: ellipsis;
word-break: keep-all;
}
}
// ============================ Title =============================
@ -160,6 +166,8 @@
// =========================== Summary ============================
&-summary {
position: relative;
z-index: @zindex-table-fixed;
background: @table-bg;
div& {
@ -217,6 +225,12 @@
background-color: transparent !important;
}
}
// https://github.com/ant-design/ant-design/issues/30969
&.@{table-prefix-cls}-cell-fix-left:hover,
&.@{table-prefix-cls}-cell-fix-right:hover {
background: @table-fixed-header-sort-active-bg;
}
}
&-thead th.@{table-prefix-cls}-column-sort {
@ -231,6 +245,12 @@
background: @table-body-sort-bg;
}
&-column-title {
position: relative;
z-index: 1;
flex: 1;
}
&-column-sorters {
display: flex;
flex: auto;
@ -623,7 +643,9 @@
&-holder {
position: sticky;
z-index: @table-sticky-zindex;
background: @component-background;
}
&-scroll {
position: sticky;
bottom: 0;

View File

@ -0,0 +1,53 @@
@import './index.less';
.@{table-prefix-cls}-responsive {
@media (max-width: 960px) {
.@{table-prefix-cls} {
table {
table-layout: auto !important;
width: 100% !important;
col {
width: auto !important;
min-width: auto !important;
}
}
&-thead {
display: none;
}
&.@{table-prefix-cls}-fixed-column {
.@{table-prefix-cls}-content {
overflow: hidden !important;
}
}
&.@{table-prefix-cls}-scroll-horizontal {
.@{table-prefix-cls}-body {
overflow-x: hidden !important;
}
}
&-tbody {
.@{table-prefix-cls}-cell {
display: flex;
justify-content: space-between;
&:not(:last-child) {
border: none;
}
}
.@{table-prefix-cls}-cell:before {
content: attr(data-label);
font-weight: 500;
padding-right: 16px;
padding-inline-end: 16px;
padding-inline-start: unset;
}
}
}
}
}

View File

@ -81,6 +81,7 @@
.@{tab-prefix-cls}-nav-add {
min-width: @tabs-card-height;
margin-left: @tabs-card-gutter;
padding: 0 @padding-xs;
background: @tabs-card-head-background;
border: @border-width-base @border-style-base @border-color-split;

View File

@ -58,8 +58,14 @@
> div > .@{tab-prefix-cls}-nav {
.@{tab-prefix-cls}-tab + .@{tab-prefix-cls}-tab {
.@{tab-prefix-cls}-rtl& {
margin-right: 0;
margin-left: @tabs-card-gutter;
margin-right: @tabs-card-gutter;
margin-left: 0;
}
}
.@{tab-prefix-cls}-nav-add {
.@{tab-prefix-cls}-rtl& {
margin-right: @tabs-card-gutter;
margin-left: 0;
}
}
}

View File

@ -1,13 +1,17 @@
@namespace AntDesign
@inherits AntDomComponentBase
<CascadingValue Value="this" IsFixed="@true">
<CascadingValue Value="this" IsFixed>
<ul class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref">
@ChildContent
@foreach (var item in _displayItems)
{
<li class="@item.ItemClass" @ref="item.Ref" style="@item.Style" id="@item.Id">
@if (!string.IsNullOrEmpty(item.Label))
{
<div class="ant-timeline-item-label">@item.Label</div>
}
<div class="ant-timeline-item-tail"></div>
<div class="@item._headClassMapper.Class" style="@item.HeadStyle">
@if (item.Dot != null)

View File

@ -12,7 +12,7 @@ namespace AntDesign
/// 'left' | 'alternate' | 'right'
/// </summary>
[Parameter]
public string Mode
public TimelineMode? Mode
{
get => _mode;
set
@ -51,19 +51,20 @@ namespace AntDesign
_pendingItem = _pending.Value == null ? null : new TimelineItem()
{
Dot = PendingDot ?? _loadingDot,
Class = "ant-timeline-item-pending"
};
_pendingItem?.SetDot(PendingDot ?? _loadingDot);
_pending.Switch(str =>
{
_pendingItem.ChildContent = b =>
{
b.AddContent(0, str);
};
_pendingItem.SetChildContent(b =>
{
b.AddContent(0, str);
});
}, rf =>
{
_pendingItem.ChildContent = rf;
_pendingItem.SetChildContent(rf);
});
_pendingItem?.SetClassMap();
@ -91,11 +92,11 @@ namespace AntDesign
protected IList<TimelineItem> _displayItems = new List<TimelineItem>();
private readonly bool _isPendingBoolean = false;
private OneOf<string, RenderFragment> _pending;
private bool _reverse;
private bool _waitingItemUpdate = false;
private string _mode;
private TimelineMode? _mode;
private bool _hasLabel;
protected override void OnInitialized()
{
@ -126,10 +127,11 @@ namespace AntDesign
var prefix = "ant-timeline";
ClassMapper.Clear()
.Add(prefix)
.If($"{prefix}-right", () => Mode == "right")
.If($"{prefix}-alternate", () => Mode == "alternate")
.If($"{prefix}-right", () => Mode == TimelineMode.Right && !_hasLabel)
.If($"{prefix}-alternate", () => Mode == TimelineMode.Alternate && !_hasLabel)
.If($"{prefix}-pending", () => Pending.Value != null)
.If($"{prefix}-reverse", () => Reverse)
.If($"{prefix}-label", () => _hasLabel)
.If($"{prefix}-rtl", () => RTL);
}
@ -143,6 +145,10 @@ namespace AntDesign
{
this._items.Add(item);
_waitingItemUpdate = true;
if (!string.IsNullOrEmpty(item.Label))
{
_hasLabel = true;
}
}
protected IEnumerable<TimelineItem> UpdateChildren(IEnumerable<TimelineItem> items)
@ -155,11 +161,13 @@ namespace AntDesign
{
var item = items.ElementAt(i);
item.IsLast = i == length - 1;
item.Position =
this.Mode == "left" || Mode == null ? null
: this.Mode == "right" ? "right"
: this.Mode == "alternate" && i % 2 == 0 ? "left"
: "right";
item.Position = _mode switch
{
TimelineMode.Left => "left",
TimelineMode.Right => "right",
TimelineMode.Alternate => i % 2 == 0 ? "left" : "right",
_ => null,
};
item.SetClassMap();

View File

@ -14,6 +14,9 @@ namespace AntDesign
[Parameter]
public string Color { get; set; } = "blue";
[Parameter]
public string Label { get; set; }
[CascadingParameter]
public Timeline ParentTimeline { get; set; }
@ -68,5 +71,15 @@ namespace AntDesign
.If($"{headPrefix}-{Color}", () => _defaultColors.Contains(Color))
.If($"{headPrefix}-custom", () => Dot != null);
}
internal void SetChildContent(RenderFragment childContent)
{
this.ChildContent = childContent;
}
internal void SetDot(RenderFragment dot)
{
this.Dot = dot;
}
}
}

View File

@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
namespace AntDesign
{
public enum TimelineMode
{
Left,
Right,
Alternate
}
}

View File

@ -18,6 +18,7 @@
position: absolute;
z-index: @zindex-tooltip;
display: block;
width: max-content;
max-width: @tooltip-max-width;
visibility: visible;

View File

@ -17,6 +17,11 @@
border: 0;
border-radius: 0;
.@{table-prefix-cls}-selection-column {
width: 40px;
min-width: 40px;
}
> .@{table-prefix-cls}-content {
// Header background color
> .@{table-prefix-cls}-body > table > .@{table-prefix-cls}-thead > tr > th {

View File

@ -0,0 +1,45 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Components;
namespace AntDesign
{
public class SimpleTreeSelect<TItem> : TreeSelect<TItem> where TItem : class
{
protected IEnumerable<TItem> RootData => ChildrenMethodExpression?.Invoke(RootValue);
/// <summary>
/// Specifies a method to return a child node
/// </summary>
[Parameter]
public Func<string, IList<TItem>> ChildrenMethodExpression { get; set; }
protected override Func<TreeNode<TItem>, IList<TItem>> TreeNodeChildrenExpression => node => ChildrenMethodExpression(TreeNodeKeyExpression(node));
protected override Dictionary<string, object> TreeAttributes
{
get
{
return new()
{
{ "DataSource", RootData },
{ "TitleExpression", TreeNodeTitleExpression },
{ "DefaultExpandAll", TreeDefaultExpandAll },
{ "KeyExpression", TreeNodeKeyExpression },
{ "ChildrenExpression", TreeNodeChildrenExpression },
{ "DisabledExpression", TreeNodeDisabledExpression },
{ "IsLeafExpression", TreeNodeIsLeafExpression }
};
}
}
}
}

View File

@ -0,0 +1,77 @@
@namespace AntDesign
@inherits SelectBase<string, TItem>
@typeparam TItem
@using AntDesign.Internal
@using AntDesign.Select.Internal
<CascadingValue Value="this" IsFixed>
<CascadingValue Value=@("ant-select-dropdown") Name="PrefixCls" IsFixed>
<div class="@ClassMapper.Class" style="@Style" id="@Id" tabindex="-1" @ref="Ref">
<OverlayTrigger @ref="@_dropDown"
Visible="Open"
Disabled="Disabled"
Trigger="new[] { Trigger.Click }"
HiddenMode
OnMouseEnter="@(() => { OnMouseEnter?.Invoke(); })"
OnMouseLeave="@(() => { OnMouseLeave?.Invoke(); })"
OnVisibleChange="@OnOverlayVisibleChangeAsync"
PopupContainerSelector="@PopupContainerSelector"
OverlayEnterCls="ant-slide-up-enter ant-slide-up-enter-active ant-slide-up"
OverlayLeaveCls="ant-slide-up-leave ant-slide-up-leave-active ant-slide-up">
<Overlay >
<div style="@_dropdownStyle">
<div class="" style="max-height: @PopupContainerMaxHeight; overflow-y: auto;">
<div>
<div class="" role="listbox" style="display: flex; flex-direction: column;">
<Tree TItem="TItem"
BlockNode @ref="_tree" Multiple="Multiple"
@attributes="TreeAttributes"
SelectedKeys="SelectedKeys"
OnClick="OnTreeNodeClick"
OnUnSelect="OnTreeNodeUnSelect">
<Nodes>
@if (IsInnerModel)
{
<CascadingValue Name="Tree" Value="_tree" IsFixed="true">
@ChildContent
</CascadingValue>
}
</Nodes>
</Tree>
</div>
</div>
</div>
</div>
</Overlay>
<Unbound>
<CascadingValue Value="this" Name=@("ParentSelect") IsFixed>
<CascadingValue Value="@LabelTemplate" Name="ParentLabelTemplate">
<CascadingValue Value="@ShowSearchIcon" Name="ShowSearchIcon">
<CascadingValue Value="@ShowArrowIcon" Name="ShowArrowIcon">
<SelectContent Prefix="ant-select"
RefBack="@context"
TItemValue="string"
TItem="TItem"
SearchValue="@_searchValue"
IsOverlayShow="@_dropDown.IsOverlayShow()"
OnInput="@OnInputAsync"
OnKeyUp="@OnKeyUpAsync"
OnKeyDown="@OnKeyDownAsync"
OnFocus="@OnInputFocusAsync"
OnBlur="@OnInputBlurAsync"
OnClearClick="@OnInputClearClickAsync"
OnRemoveSelected="@OnRemoveSelectedAsync"
Placeholder="@Placeholder"
ShowPlaceholder="@ShowPlaceholder" />
</CascadingValue>
</CascadingValue>
</CascadingValue>
</CascadingValue>
</Unbound>
</OverlayTrigger>
</div>
</CascadingValue>
</CascadingValue>

View File

@ -0,0 +1,466 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using AntDesign.Internal;
using AntDesign.Select.Internal;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using AntDesign.Core.Helpers.MemberPath;
using AntDesign.JsInterop;
using OneOf;
using System.Linq;
namespace AntDesign
{
public partial class TreeSelect<TItem> : SelectBase<string, TItem> where TItem : class
{
[Parameter] public bool ShowExpand { get; set; } = true;
protected Tree<TItem> _tree;
[Parameter]
public bool Multiple
{
get => _multiple;
set
{
_multiple = value;
if (_multiple)
{
Mode = SelectMode.Multiple.ToString("G");
}
}
}
[Parameter] public bool TreeCheckable { get; set; }
[Parameter] public string PopupContainerSelector { get; set; } = "body";
[Parameter] public Action OnMouseEnter { get; set; }
[Parameter] public Action OnMouseLeave { get; set; }
[Parameter] public RenderFragment<TItem> LabelTemplate { get; set; }
[Parameter] public bool ShowSearchIcon { get; set; } = true;
[Parameter] public bool ShowArrowIcon { get; set; } = true;
[Parameter] public TreeNode<TItem>[] Nodes { get; set; }
[Parameter] public IEnumerable<TItem> DataSource { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public bool TreeDefaultExpandAll { get; set; }
[Parameter]
public Func<IEnumerable<TItem>, string, TItem> DataItemExpression { get; set; }
[Parameter]
public Func<IList<TItem>, IEnumerable<string>, IEnumerable<TItem>> DataItemsExpression { get; set; }
[Parameter]
public string RootValue { get; set; } = "0";
protected Func<TreeNode<TItem>, string> TreeNodeTitleExpression
{
get
{
return node => TitleExpression(node.DataItem);
}
}
[Parameter]
public Func<TItem, string> TitleExpression { get; set; }
protected virtual Dictionary<string, object> TreeAttributes
{
get
{
return new()
{
{ "DataSource", DataSource },
{ "TitleExpression", DataSource == null ? null : TreeNodeTitleExpression },
{ "DefaultExpandAll", TreeDefaultExpandAll },
{ "KeyExpression", DataSource == null ? null : TreeNodeKeyExpression },
{ "ChildrenExpression", DataSource == null ? null : TreeNodeChildrenExpression },
{ "DisabledExpression", DataSource == null ? null : TreeNodeDisabledExpression },
{ "IsLeafExpression", DataSource == null ? null : TreeNodeIsLeafExpression }
};
}
}
protected Func<TreeNode<TItem>, string> TreeNodeKeyExpression
{
get
{
return node => KeyExpression(node.DataItem);
}
}
[Parameter]
public Func<TItem, string> KeyExpression { get; set; }
protected Func<TreeNode<TItem>, string> TreeNodeIconExpression
{
get
{
return node => IconExpression(node.DataItem);
}
}
/// <summary>
/// Specifies a method to return the node icon.
/// </summary>
[Parameter]
public Func<TItem, string> IconExpression { get; set; }
protected Func<TreeNode<TItem>, bool> TreeNodeIsLeafExpression
{
get
{
return node => IsLeafExpression(node.DataItem);
}
}
private bool IsInnerModel => ChildContent != null;
/// <summary>
/// Specifies a method that returns whether the expression is a leaf node.
/// </summary>
[Parameter]
public Func<TItem, bool> IsLeafExpression { get; set; }
protected virtual Func<TreeNode<TItem>, IList<TItem>> TreeNodeChildrenExpression
{
get
{
return node => ChildrenExpression == null ? null : ChildrenExpression(node.DataItem);
}
}
[Parameter]
public virtual Func<TItem, IList<TItem>> ChildrenExpression { get; set; }
protected Func<TreeNode<TItem>, bool> TreeNodeDisabledExpression
{
get
{
return node => DisabledExpression != null && DisabledExpression(node.DataItem);
}
}
/// <summary>
/// Specifies a method to return a disabled node
/// </summary>
[Parameter]
public Func<TItem, bool> DisabledExpression { get; set; }
[Parameter] public OneOf<bool, string> DropdownMatchSelectWidth { get; set; } = true;
[Parameter] public string DropdownMaxWidth { get; set; } = "auto";
[Parameter] public string PopupContainerMaxHeight { get; set; } = "256px";
private bool IsMultiple => Multiple || TreeCheckable;
internal override SelectMode SelectMode => IsMultiple ? SelectMode.Multiple : base.SelectMode;
private string[] SelectedKeys => Values?.ToArray();
//private readonly IList<TreeNode<TItem>> _selectedNodes = new List<TreeNode<TItem>>();
private string _dropdownStyle = string.Empty;
private bool _multiple;
private readonly string _dir = "ltr";
public override string Value
{
get => base.Value;
set
{
if (string.IsNullOrEmpty(value))
return;
if (base.Value == value)
return;
base.Value = value;
if (SelectOptionItems.Any(o => o.Value == value))
{
_ = SetValueAsync(SelectOptionItems.First(o => o.Value == value));
}
else
{
var data = DataItemExpression?.Invoke(DataSource, value);
if (data != null)
{
var o = CreateOption(data, true);
_ = SetValueAsync(o);
}
}
}
}
public override IEnumerable<string> Values
{
get => base.Values;
set
{
if (!_isInitialized)
return;
if (!Multiple)
throw new NotImplementedException("not Multiple select, no die");
if (value != null && _selectedValues != null)
{
var hasChanged = !value.SequenceEqual(_selectedValues);
if (!hasChanged)
return;
ClearOptions();
_selectedValues = value;
CreateOptions(value);
_ = OnValuesChangeAsync(value);
}
else if (value != null && _selectedValues == null)
{
_selectedValues = value;
CreateOptions(value);
_ = OnValuesChangeAsync(value);
}
else if (value == null && _selectedValues != null)
{
_selectedValues = default;
ClearOptions();
_ = OnValuesChangeAsync(default);
}
if (_isNotifyFieldChanged && (Form?.ValidateOnChange == true))
{
EditContext?.NotifyFieldChanged(FieldIdentifier);
}
}
}
private void ClearOptions()
{
SelectOptionItems.Clear();
SelectedOptionItems.Clear();
}
private void CreateOptions(IEnumerable<string> data)
{
if (IsInnerModel)
{
var d1 = data.Where(d => !SelectOptionItems.Any(o => o.Value == d));
CreateOptionsByTreeNode(d1);
return;
}
data.ForEach(menuId =>
{
var d = DataItemExpression?.Invoke(DataSource, menuId);
if (d != null)
{
var o = CreateOption(d, true);
}
});
}
private void CreateOptionsByTreeNode(IEnumerable<string> data)
{
data.ForEach(menuId =>
{
var d = _tree.FindFirstOrDefaultNode(n => n.Key == menuId);
if (d != null)
{
var o = CreateOption(d, true);
}
});
}
private SelectOptionItem<string, TItem> CreateOption(TreeNode<TItem> data, bool append = false)
{
var o = new SelectOptionItem<string, TItem>()
{
Label = data.Title,
Value = data.Key,
Item = data.DataItem,
IsAddedTag = SelectMode != SelectMode.Default
};
if (append)
SelectOptionItems.Add(o);
return o;
}
private SelectOptionItem<string, TItem> CreateOption(TItem data, bool append = false)
{
var o = new SelectOptionItem<string, TItem>()
{
Label = TitleExpression(data),
Value = KeyExpression(data),
Item = data,
IsAddedTag = SelectMode != SelectMode.Default
};
if (append)
SelectOptionItems.Add(o);
return o;
}
protected override void OnInitialized()
{
SelectOptions = "".ToRenderFragment();
//_inputValue = Value;
base.OnInitialized();
}
private void OnKeyDownAsync(KeyboardEventArgs args)
{
}
protected async void OnInputAsync(ChangeEventArgs e)
{
}
protected async Task OnKeyUpAsync(KeyboardEventArgs e)
{
}
protected async Task OnInputFocusAsync(FocusEventArgs _)
{
}
protected async Task OnInputBlurAsync(FocusEventArgs _)
{
}
private async Task OnOverlayVisibleChangeAsync(bool visible)
{
if (visible)
{
await SetDropdownStyleAsync();
}
}
protected async Task OnRemoveSelectedAsync(SelectOptionItem<string, TItem> selectOption)
{
if (selectOption == null) throw new ArgumentNullException(nameof(selectOption));
await SetValueAsync(selectOption);
foreach (var item in _tree.SelectedNodesDictionary.Select(x => x.Value).ToList())
{
if (item.Key == selectOption.Value)
item.SetSelected(false);
}
}
private async Task OnTreeNodeClick(TreeEventArgs<TItem> args)
{
if (!args.Node.Selected)
return;
var key = args.Node.Key;
if (Value != null && Value.Equals(key))
return;
if (Values != null && Values.Contains(key))
return;
var data = args.Node;
SelectOptionItem<string, TItem> item;
if (IsInnerModel)
item = CreateOption(data, true);
else
item = CreateOption(data.DataItem, true);
//_selectedNodes.Add(data);
await SetValueAsync(item);
if (SelectMode == SelectMode.Default)
await CloseAsync();
}
protected void OnTreeNodeUnSelect(TreeEventArgs<TItem> args)
{
if (args == null) throw new ArgumentNullException(nameof(args));
var key = args.Node.Key;
var nodes = SelectOptionItems.Where(o => o.Value == key).ToArray();
foreach (var item in nodes)
{
_ = SetValueAsync(item);
}
}
protected async Task SetDropdownStyleAsync()
{
string maxWidth = "", minWidth = "", definedWidth = "";
var domRect = await JsInvokeAsync<DomRect>(JSInteropConstants.GetBoundingClientRect, Ref);
var width = domRect.Width.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture);
minWidth = $"min-width: {width}px;";
if (DropdownMatchSelectWidth.IsT0 && DropdownMatchSelectWidth.AsT0)
{
definedWidth = $"width: {width}px;";
}
else if (DropdownMatchSelectWidth.IsT1)
{
definedWidth = $"width: {DropdownMatchSelectWidth.AsT1};";
}
if (!DropdownMaxWidth.Equals("auto", StringComparison.CurrentCultureIgnoreCase))
maxWidth = $"max-width: {DropdownMaxWidth};";
_dropdownStyle = minWidth + definedWidth + maxWidth;
if (Multiple)
{
if (_selectedValues == null)
return;
_tree._allNodes.ForEach(n => n.SetSelected(_selectedValues.Contains(n.Key)));
}
else
{
_tree?.FindFirstOrDefaultNode(node => node.Key == Value)?.SetSelected(true);
}
}
protected override void SetClassMap()
{
var classPrefix = "ant-select";
ClassMapper
.Add(classPrefix)
.Add("ant-tree-select")
.If("ant-select-lg", () => Size == "large")
.If("ant-select-rtl", () => _dir == "rtl")
.If("ant-select-sm", () => Size == "rtl")
.If("ant-select-disabled", () => Disabled)
.If("ant-select-single", () => SelectMode == SelectMode.Default)
.If($"ant-select-multiple", () => SelectMode != SelectMode.Default)
.If("ant-select-show-arrow", () => !IsMultiple)
.If("ant-select-show-search", () => !IsMultiple)
.If("ant-select-allow-clear", () => AllowClear)
.If("ant-select-open", () => Open)
.If("ant-select-focused", () => Open || Focused)
;
}
}
}

View File

@ -11,10 +11,14 @@
foreach (var item in DataSource)
{
<TreeNode DataItem="@item" @key="item.GetHashCode()"></TreeNode>
}
}
}
else
{
else if (ChildContent!=null)
{
@ChildContent
}
else
{
@Nodes
}
</CascadingValue>

View File

@ -121,6 +121,9 @@ namespace AntDesign
[Parameter]
public RenderFragment Nodes { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// tree childnodes
/// Add values when the node is initialized
@ -186,6 +189,11 @@ namespace AntDesign
{
if (SelectedNodesDictionary.ContainsKey(treeNode.NodeId) == true)
SelectedNodesDictionary.Remove(treeNode.NodeId);
if (OnUnSelect.HasDelegate)
{
OnUnSelect.InvokeAsync(new TreeEventArgs<TItem>(this, treeNode));
}
}
/// <summary>
@ -310,9 +318,9 @@ namespace AntDesign
public EventCallback<string[]> CheckedKeysChanged { get; set; }
/// <summary>
/// Dechecked all selected items
/// Checks all nodes
/// </summary>
public void CheckedAll()
public void CheckAll()
{
foreach (var item in ChildNodes)
{
@ -320,8 +328,10 @@ namespace AntDesign
}
}
// Decheck all of the checked nodes
public void DecheckedAll()
/// <summary>
/// Unchecks all nodes
/// </summary>
public void UncheckAll()
{
foreach (var item in ChildNodes)
{
@ -499,6 +509,9 @@ namespace AntDesign
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnSelect { get; set; }
[Parameter]
public EventCallback<TreeEventArgs<TItem>> OnUnSelect { get; set; }
/// <summary>
/// Click the expansion tree node icon to call back
/// </summary>

View File

@ -29,9 +29,13 @@
foreach (var item in ChildDataItems)
{
<TreeNode DataItem="@item" @key="item.GetHashCode()"></TreeNode>
}
}
}
else
else if (ChildContent != null)
{
@ChildContent
}
else
{
@Nodes
}

View File

@ -34,6 +34,9 @@ namespace AntDesign
[Parameter]
public RenderFragment Nodes { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
internal List<TreeNode<TItem>> ChildNodes { get; set; } = new List<TreeNode<TItem>>();
/// <summary>
@ -501,7 +504,7 @@ namespace AntDesign
hasUnchecked = true;
break;
}
else if (item.Checked)
else if (item.Checked)
{
hasChecked = true;
}

View File

@ -93,6 +93,7 @@
a&-ellipsis,
span&-ellipsis {
display: inline-block;
max-width: 100%;
}
a&,

View File

@ -3,7 +3,7 @@
"AntDesign.Docs.Server": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:5001",
"applicationUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -0,0 +1,107 @@
@using System.ComponentModel.DataAnnotations;
@using System.Text.Json;
@using System.ComponentModel
<Form Model="@model"
LabelColSpan="8"
WrapperColSpan="16">
<FormItem Label="Fail" ValidateStatus=FormValidateStatus.Error Help="Should be combination of numbers &amp; alphabets">
<Input @bind-Value="@context.Username" Placeholder="unavailable choice" />
</FormItem>
<FormItem Label="Warning" ValidateStatus=FormValidateStatus.Warning>
<Input @bind-Value="@context.Username" Placeholder="Warning">
<Prefix>
<Icon Type="smile" Theme="outline"/>
</Prefix>
</Input>
</FormItem>
<FormItem Label="Validating" HasFeedback ValidateStatus=FormValidateStatus.Validating Help="The information is being validated...">
<Input @bind-Value="@context.Username" Placeholder="I'm the content being validated" />
</FormItem>
<FormItem Label="Success" HasFeedback ValidateStatus=FormValidateStatus.Success>
<Input @bind-Value="@context.Username" Placeholder="I'm the content" />
</FormItem>
<FormItem Label="Warning" HasFeedback ValidateStatus=FormValidateStatus.Warning>
<Input @bind-Value="@context.Username" Placeholder="Warning" />
</FormItem>
<FormItem Label="Fail" HasFeedback ValidateStatus=FormValidateStatus.Error Help="Should be combination of numbers &amp; alphabets">
<Input @bind-Value="@context.Username" Placeholder="unavailable choice" />
</FormItem>
<FormItem Label="Success" HasFeedback ValidateStatus=FormValidateStatus.Success>
<DatePicker @bind-Value="@context.DatePicker" Picker="@DatePickerType.Date" Style="width: 100%;" />
</FormItem>
<FormItem Label="Warning" HasFeedback ValidateStatus=FormValidateStatus.Warning>
<TimePicker @bind-Value="@context.TimePicker" Style="width: 100%;" />
</FormItem>
<FormItem Label="Error" HasFeedback ValidateStatus=FormValidateStatus.Error>
<Select DataSource="@_options"
@bind-Value="@context.SelectedOption"
AllowClear>
</Select>
</FormItem>
<FormItem Label="Validating" HasFeedback ValidateStatus=FormValidateStatus.Validating Help="The information is being validated...">
<Select DataSource="@_options2"
@bind-Value="@context.SelectedOption2"
AllowClear
Placeholder="Please select">
</Select>
</FormItem>
<FormItem Label="inline" Style="margin-bottom: 0;">
<ChildContent>
<div style="display:grid;grid-template-columns:45% 1fr 45%;">
<FormItem ValidateStatus=FormValidateStatus.Error Help="Please select the correct date">
<DatePicker @bind-Value="@context.DatePicker2" Style="width:100%;"/>
</FormItem>
<span style="text-align:center;">-</span>
<FormItem>
<DatePicker @bind-Value="@context.DatePicker3" Style="width:100%;" />
</FormItem>
</div>
</ChildContent>
</FormItem>
<FormItem Label="Success" HasFeedback ValidateStatus=FormValidateStatus.Success>
<AntDesign.InputNumber @bind-Value="@context.InputNumber" Style="width:100%;"></AntDesign.InputNumber>
</FormItem>
<FormItem Label="Success" HasFeedback ValidateStatus=FormValidateStatus.Success>
<AntDesign.Input @bind-Value="@context.Input" AllowClear Placeholder="with allowClear"></AntDesign.Input>
</FormItem>
<FormItem Label="Warning" HasFeedback ValidateStatus=FormValidateStatus.Warning>
<AntDesign.InputPassword @bind-Value="@context.Password" Placeholder="with input password"></AntDesign.InputPassword>
</FormItem>
<FormItem Label="Error" HasFeedback ValidateStatus=FormValidateStatus.Error>
<AntDesign.InputPassword @bind-Value="@context.Password" AllowClear Placeholder="with input password and allowClear"></AntDesign.InputPassword>
</FormItem>
</Form>
@code
{
public class Model
{
public string Username { get; set; }
public DateTime? DatePicker { get; set; }
public DateTime? TimePicker { get; set; }
public string SelectedOption { get; set; }
public string SelectedOption2 { get; set; }
public DateTime? DatePicker2 { get; set; }
public DateTime? DatePicker3 { get; set; }
public int? InputNumber { get; set; }
public string Input { get; set; }
public string Password { get; set; }
}
private Model model = new Model();
private List<string> _options;
private List<string> _options2;
protected override void OnInitialized()
{
_options = new List<string>
{
"Option 1", "Option 2", "Option 3"
};
_options2 = new List<string>
{
"Option 1", "Option 2", "Option 3"
};
}
}

View File

@ -0,0 +1,22 @@
---
order: 102
title:
zh-CN: 自定义校验
en-US: Customized Validation
---
## zh-CN
我们提供了 `validateStatus` `help` `hasFeedback` 等属性,你可以不通过 Form 自己定义校验的时机和内容。
1. `validateStatus`: 校验状态,可选 'success', 'warning', 'error', 'validating'。
2. `hasFeedback`:用于给输入框添加反馈图标。
3. `help`:设置校验文案。
## en-US
We provide properties like `validateStatus` `help` `hasFeedback` to customize your own validate status and message, without using Form.
1. `validateStatus`: validate status of form components which could be 'success', 'warning', 'error', 'validating'.
2. `hasFeedback`: display feed icon of input control
3. `help`: display validate message.

View File

@ -1,6 +1,6 @@

<div style="display: flex; justify-content: space-between;">
<RadioGroup @bind-Value="currentTheme" ButtonStyle="solid" Size="large" TValue="string" OnChange="OnThemeChanged">
<RadioGroup @bind-Value="currentTheme" ButtonStyle="@RadioButtonStyle.Solid" Size="large" TValue="string" OnChange="OnThemeChanged">
<Radio RadioButton Value="@("outline")">
<Icon Component="OutlineSvg" />@LanguageService.Resources["app.docs.components.icon.outlined"]
</Radio>

View File

@ -0,0 +1,18 @@
<div>
<Image PreviewVisible="false"
Width="200"
Src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp"
OnClick="@(() => { visible = true; })" />
<div style="display:none;">
<ImagePreviewGroup @bind-PreviewVisible="visible" >
<Image Src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp" />
<Image Src="https://gw.alipayobjects.com/zos/antfincdn/cV16ZqzMjW/photo-1473091540282-9b846e7965e3.webp" />
<Image Src="https://gw.alipayobjects.com/zos/antfincdn/x43I27A55%26/photo-1438109491414-7198515b166b.webp" />
</ImagePreviewGroup>
</div>
</div>
@code {
bool visible;
}

View File

@ -0,0 +1,14 @@
---
order: 6
title:
zh-CN: 相册模式
en-US: Preview from one image
---
## zh-CN
从一张图片点开相册。
## en-US
Preview a collection from one image.

View File

@ -15,6 +15,8 @@ Previewable image.
## API
### Image
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| Alt | Image description | string | - | 0.6.0 |
@ -22,8 +24,15 @@ Previewable image.
| Height | Image height | string | - | 0.6.0 |
| Locale | Locale Object | ImageLocale | - |- |
| Placeholder | Load placeholder | RenderFragment | - | 0.6.0 |
| Preview | preview config, disabled when `false` | boolean | true | 0.6.0 |
| Preview | Whether to enable the preview function | boolean | true | 0.6.0 |
| PreviewSrc | path of the preview image before loading is complete | string | the same as `Src` | 0.6.0 |
| PreviewVisible | Whether to open the preview image when clicking the preview button | true | 0.10.0 |
| Src | Image path | string | - | 0.6.0 |
| Width | Image width | string | - | 0.6.0 |
| OnClick | Triggered when the image is clicked | EventCallback<MouseEventArgs> | 0.10.0 |
### ImagePreviewGroup
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| PreviewVisible | Whether to open the preview image. Two-way binding. | bool | true | 0.10.0 |

View File

@ -16,6 +16,8 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
## API
### Image
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| Alt | 图像描述 | string | - | 0.6.0 |
@ -23,7 +25,16 @@ cover: https://gw.alipayobjects.com/zos/antfincdn/D1dXz9PZqa/image.svg
| Height | 图像高度 | string | - | 4.6.0 |
| Locale | 语言对象 | ImageLocale | - |- |
| Placeholder | 加载占位 | RenderFragment | - | 0.6.0 |
| Preview | 预览参数,为 `false` 时禁用 | boolean | true | 0.6.0 |
| Preview | 设置是否启用预览功能 | bool | true | 0.6.0 |
| PreviewSrc | 加载完成前预览图的地址 | string | 与 `Src` 一样 | 0.6.0 |
| PreviewVisible | 设置是否在点击预览按钮时打开预览框。 | true | 0.10.0 |
| Src | 图片地址 | string | - | 0.6.0 |
| Width | 图像宽度 | string | - | 0.6.0 |
| OnClick | 在点击图片时触发 | EventCallback<MouseEventArgs> | 0.10.0 |
### ImagePreviewGroup
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| PreviewVisible | 是否打开预览图片。支持双向绑定。 | bool | true | 0.10.0 |

View File

@ -1,5 +1,5 @@
<div>
<RadioGroup @bind-Value="radioValue4" ButtonStyle="solid">
<RadioGroup @bind-Value="radioValue4" ButtonStyle="@RadioButtonStyle.Solid">
<Radio RadioButton Value="@("A")">Hangzhou</Radio>
<Radio RadioButton Value="@("B")">Shanghai</Radio>
<Radio RadioButton Value="@("C")">Beijing</Radio>

View File

@ -0,0 +1,19 @@
@using System.ComponentModel.DataAnnotations
<EnumRadioGroup TEnum="Fruits" @bind-Value="_radioValue"></EnumRadioGroup>
<br />
Value: @_radioValue
@code {
Fruits _radioValue = Fruits.Apple;
enum Fruits
{
[Display(Name = "🍎 Apple")]
Apple,
Pear,
Orange
}
}

View File

@ -1,12 +1,16 @@
<RadioGroup @bind-Value="_radioValue">
@foreach (var item in options)
{
<Radio Value="@item">@item</Radio>
}
</RadioGroup>
<RadioGroup Options="@options" @bind-Value="_radioValue"></RadioGroup>
<br />
<RadioGroup Options="@options2" @bind-Value="_radioValue"></RadioGroup>
@code {
string _radioValue = "Apple";
string _radioValue = "Apple";
string[] options = new string[] { "Apple", "Pear", "Orange" };
RadioOption<string>[] options2 = new RadioOption<string>[]
{
new(){ Value = "Apple", Label="🍎 Apple", },
new(){ Value = "Pear", Label="🍐 Pear", },
new(){ Value = "Orange", Label="🍊 Orange", },
};
}

View File

@ -0,0 +1,15 @@
---
order: 8.5
title:
zh-CN: RadioGroup 组合 - 枚举方式
en-US: Radio group - enum type
---
## zh-CN
通过指定枚举类型,来渲染单选框。
## en-US
Render radios by specific enum type.

View File

@ -16,3 +16,16 @@ A table displays rows of data.
## How To Use
Specify `dataSource` of Table as an array of data.
## API
TODO
### Responsive
The table supports responsive by default, and when the screen width is less than 960px, the table would switch to small-screen mode.
In small-screen mode, only certain features are currently supported, and mis-styling will occur in tables with some features such as group, expanded columns, tree data, summary cell, etc.
If you want to disable responsive, you can set `Responsive="false"`.

View File

@ -17,3 +17,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/f-SbcX2Lx/Table.svg
## 如何使用
指定表格的数据源 `dataSource` 为一个数组。
## API
TODO
### 响应式
表格默认支持响应式,当屏幕宽度小于 960px 时,表格的数据列变为小屏模式。
在小屏模式下,目前只支持一定的表格功能,在有 分组合并、展开列、树形数据、总结栏 等属性的表格中会出现样式错乱。
如果想禁用响应式,可以设置 `Responsive="false"`

View File

@ -1,5 +1,5 @@
<div>
<Timeline Mode="alternate" >
<Timeline Mode="@TimelineMode.Alternate" >
<TimelineItem>Create a services site 2015-09-01</TimelineItem>
<TimelineItem Color="green">Solve initial network problems 2015-09-01</TimelineItem>
<TimelineItem Dot="dotTemplate">

View File

@ -1,19 +1,17 @@
<div>
<RadioGroup @bind-Value="@mode" Style="margin-bottom:20px">
<Radio Value="@("left")">Left</Radio>
<Radio Value="@("right")">Right</Radio>
<Radio Value="@("alternate")">Alternate</Radio>
<Radio Value="@TimelineMode.Left">Left</Radio>
<Radio Value="@TimelineMode.Right">Right</Radio>
<Radio Value="@TimelineMode.Alternate">Alternate</Radio>
</RadioGroup>
<Timeline Mode="@mode">
<TimelineItem>Create a services 2015-09-01</TimelineItem>
<TimelineItem>Solve initial network problems 2015-09-01 09:12:11</TimelineItem>
<TimelineItem Label="2015-09-01">Create a services</TimelineItem>
<TimelineItem Label="2015-09-01 09:12:11">Solve initial network problems</TimelineItem>
<TimelineItem>Technical testing</TimelineItem>
<TimelineItem>Network problems being solved 2015-09-01 09:12:11</TimelineItem>
<TimelineItem Label="2015-09-01 09:12:11">Network problems being solved</TimelineItem>
</Timeline>
</div>
@code{
private string mode = "left";
private TimelineMode mode = TimelineMode.Left;
}

View File

@ -1,5 +1,5 @@
<div>
<Timeline Mode="right">
<Timeline Mode="@TimelineMode.Right">
<TimelineItem>Create a services site 2015-09-01</TimelineItem>
<TimelineItem>Solve initial network problems 2015-09-01</TimelineItem>
<TimelineItem Dot="dotTemplate" Color="red">

View File

@ -1,34 +1,26 @@
<Tree TItem="string" Checkable
DefaultExpandedKeys="@(new[] { "0-0-0", "0-0-1"})"
DefaultSelectedKeys="@(new[] {"0-0-0", "0-0-1" })"
DefaultCheckedKeys="@(new[] {"0-0-0", "0-0-1" })"
SelectedNodeChanged="SelectedNodeChanged"
OnSelect="OnSelect"
OnCheck="OnCheck">
<Nodes>
<TreeNode Title="parent 1" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="parent 1-0" Key="0-0-0" Disabled TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0-0" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="leaf" Key="0-0-0-1" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" TItem="string">
<Nodes>
<TreeNode Key="0-0-1-0" TItem="string">
<TitleTemplate>
<span style="color: #1890ff; ">sss</span>
</TitleTemplate>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
DefaultExpandedKeys="@(new[] { "0-0-0", "0-0-1"})"
DefaultSelectedKeys="@(new[] {"0-0-0", "0-0-1" })"
DefaultCheckedKeys="@(new[] {"0-0-0", "0-0-1" })"
SelectedNodeChanged="SelectedNodeChanged"
OnSelect="OnSelect"
OnCheck="OnCheck">
<TreeNode Title="parent 1" Key="0-0" TItem="string">
<TreeNode Title="parent 1-0" Key="0-0-0" Disabled TItem="string">
<TreeNode Title="leaf" Key="0-0-0-0" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="leaf" Key="0-0-0-1" TItem="string"></TreeNode>
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" TItem="string">
<TreeNode Key="0-0-1-0" TItem="string">
<TitleTemplate>
<span style="color: #1890ff; ">sss</span>
</TitleTemplate>
</TreeNode>
</TreeNode>
</TreeNode>
</Tree>
@code{
@code {
void OnSelect(TreeEventArgs<string> args)
{

View File

@ -1,42 +1,32 @@
<Tree @ref="_tree"
Checkable
TItem="string"
@bind-ExpandedKeys="expandedKeys"
@bind-CheckedKeys="checkedKeys"
@bind-SelectedKeys="selectedKeys"
OnExpand="OnExpand"
OnSelect="OnSelect"
OnCheck="OnCheck"
AutoExpandParent="autoExpandParent">
<Nodes>
<TreeNode Title="0-0" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="0-0-0" Key="0-0-0" TItem="string">
<Nodes>
<TreeNode Title="0-0-0-0" Key="0-0-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-1" Key="0-0-0-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-2" Key="0-0-0-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-0-1" Key="0-0-1" TItem="string">
<Nodes>
<TreeNode Title="0-0-1-0" Key="0-0-1-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-1" Key="0-0-1-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-2" Key="0-0-1-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-0-2" Key="0-0-2" TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-1" Key="0-1" TItem="string">
<Nodes>
<TreeNode Title="0-1-0-0" Key="0-1-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-1-0-1" Key="0-1-0-1" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="0-1-0-2" Key="0-1-0-2" Disabled TItem="string"></TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="0-2" Key="0-2" TItem="string"></TreeNode>
</Nodes>
Checkable
TItem="string"
@bind-ExpandedKeys="expandedKeys"
@bind-CheckedKeys="checkedKeys"
@bind-SelectedKeys="selectedKeys"
OnExpand="OnExpand"
OnSelect="OnSelect"
OnCheck="OnCheck"
AutoExpandParent="autoExpandParent">
<TreeNode Title="0-0" Key="0-0" TItem="string">
<TreeNode Title="0-0-0" Key="0-0-0" TItem="string">
<TreeNode Title="0-0-0-0" Key="0-0-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-1" Key="0-0-0-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-0-2" Key="0-0-0-2" TItem="string"></TreeNode>
</TreeNode>
<TreeNode Title="0-0-1" Key="0-0-1" TItem="string">
<TreeNode Title="0-0-1-0" Key="0-0-1-0" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-1" Key="0-0-1-1" TItem="string"></TreeNode>
<TreeNode Title="0-0-1-2" Key="0-0-1-2" TItem="string"></TreeNode>
</TreeNode>
<TreeNode Title="0-0-2" Key="0-0-2" TItem="string"></TreeNode>
</TreeNode>
<TreeNode Title="0-1" Key="0-1" TItem="string">
<TreeNode Title="0-1-0-0" Key="0-1-0-0" TItem="string"></TreeNode>
<TreeNode Title="0-1-0-1" Key="0-1-0-1" DisableCheckbox TItem="string"></TreeNode>
<TreeNode Title="0-1-0-2" Key="0-1-0-2" Disabled TItem="string"></TreeNode>
</TreeNode>
<TreeNode Title="0-2" Key="0-2" TItem="string"></TreeNode>
</Tree>
<br />
@ -44,7 +34,8 @@
<Button OnClick="PrintCheckedNodesKey">Print Checked Nodes Key</Button>
<Button OnClick="DecheckedAll">Dechecked All</Button>
<Button OnClick="CheckAll">Check all nodes</Button>
<Button OnClick="UncheckAll">Uncheck all nodes</Button>
<br />
<br />
@ -59,78 +50,83 @@
@if (string.IsNullOrWhiteSpace(printType) == false)
{
<div>@printType</div>
<ul>
@foreach (var item in printTexts)
{
<li>@item</li>
}
</ul>
<div>@printType</div>
<ul>
@foreach (var item in printTexts)
{
<li>@item</li>
}
</ul>
}
@code {
string printType;
string printType;
string[] printTexts;
string[] printTexts;
Tree<string> _tree;
Tree<string> _tree;
string[] expandedKeys;
string[] checkedKeys;
string[] selectedKeys;
string[] expandedKeys;
string[] checkedKeys;
string[] selectedKeys;
bool autoExpandParent;
bool autoExpandParent;
//private void PrintCheckedNodesTitle()
//{
// printType = "CheckedNodesTitle";
// printTexts = tree.CheckedTitles;
//}
//private void PrintCheckedNodesTitle()
//{
// printType = "CheckedNodesTitle";
// printTexts = tree.CheckedTitles;
//}
private void PrintCheckedNodesKey()
{
printType = "CheckedNodesKey";
printTexts = _tree.CheckedKeys;
}
private void PrintCheckedNodesKey()
{
printType = "CheckedNodesKey";
printTexts = _tree.CheckedKeys;
}
private void DecheckedAll()
{
_tree.DecheckedAll();
}
private void CheckAll()
{
_tree.CheckAll();
}
//private void PrintSelectedNodes()
//{
// printType = "SelectedNodes";
// printTexts = tree.SelectedTitles;
//}
private void UncheckAll()
{
_tree.UncheckAll();
}
private void DeselectAll()
{
_tree.DeselectAll();
}
//private void PrintSelectedNodes()
//{
// printType = "SelectedNodes";
// printTexts = tree.SelectedTitles;
//}
private void ExpandAll()
{
_tree.ExpandAll();
}
private void DeselectAll()
{
_tree.DeselectAll();
}
private void CollapseAll()
{
_tree.CollapseAll();
}
private void ExpandAll()
{
_tree.ExpandAll();
}
private void OnCheck(TreeEventArgs<string> e)
{
Console.WriteLine("OnCheck:" + e.Node.Key);
}
private void CollapseAll()
{
_tree.CollapseAll();
}
private void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine("OnSelect:" + e.Node.Key);
}
private void OnCheck(TreeEventArgs<string> e)
{
Console.WriteLine("OnCheck:" + e.Node.Key);
}
private void OnExpand((string[] ExpandedKeys, TreeNode<string> Node, bool Expanded) e)
{
Console.WriteLine("OnExpand:" + JsonSerializer.Serialize(e.ExpandedKeys));
}
private void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine("OnSelect:" + e.Node.Key);
}
private void OnExpand((string[] ExpandedKeys, TreeNode<string> Node, bool Expanded) e)
{
Console.WriteLine("OnExpand:" + JsonSerializer.Serialize(e.ExpandedKeys));
}
}

View File

@ -1,32 +1,29 @@
<Tree TItem="string"
ShowIcon
DefaultExpandAll
DefaultSelectedKeys="@(new[]{"0-0-0"})"
>
<SwitcherIconTemplate>
@switcherIcon
</SwitcherIconTemplate>
<Nodes>
<TreeNode Title="parent 1" Key="0-0" Icon="smile" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0" Icon="meh" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="leaf" Key="0-0-1" TItem="string">
<IconTemplate Context="node">
@if (node.Selected)
{
<Icon Type="frown" Theme="fill" />
}
else
{
<Icon Type="frown" Theme="outline" />
}
</IconTemplate>
</TreeNode>
</Nodes>
<Tree TItem="string"
ShowIcon
DefaultExpandAll
DefaultSelectedKeys="@(new[]{"0-0-0"})">
<SwitcherIconTemplate>
@switcherIcon
</SwitcherIconTemplate>
<Nodes>
<TreeNode Title="parent 1" Key="0-0" Icon="smile" TItem="string">
<TreeNode Title="leaf" Key="0-0-0" Icon="meh" TItem="string" />
</TreeNode>
<TreeNode Title="leaf" Key="0-0-1" TItem="string">
<IconTemplate Context="node">
@if (node.Selected)
{
<Icon Type="frown" Theme="fill" />
}
else
{
<Icon Type="frown" Theme="outline" />
}
</IconTemplate>
</TreeNode>
</Nodes>
</Tree>
@code {
RenderFragment switcherIcon =@<Icon Type="down" />;
RenderFragment switcherIcon =@<Icon Type="down" />;
}

View File

@ -1,32 +1,26 @@
<DirectoryTree TItem="string"
Multiple
DefaultExpandAll
OnSelect="OnSelect"
OnExpand="OnExpand">
<Nodes>
<TreeNode Title="parent 0" Key="0-0" TItem="string">
<Nodes>
<TreeNode Title="leaf 0-0" Key="0-0-0" TItem="string" />
<TreeNode Title="leaf 0-1" Key="0-0-1" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1" Key="0-1" TItem="string">
<Nodes>
<TreeNode Title="leaf 1-0" Key="0-1-0" TItem="string" />
<TreeNode Title="leaf 1-1" Key="0-1-1" TItem="string" />
</Nodes>
</TreeNode>
</Nodes>
Multiple
DefaultExpandAll
OnSelect="OnSelect"
OnExpand="OnExpand">
<TreeNode Title="parent 0" Key="0-0" TItem="string">
<TreeNode Title="leaf 0-0" Key="0-0-0" TItem="string" />
<TreeNode Title="leaf 0-1" Key="0-0-1" TItem="string" />
</TreeNode>
<TreeNode Title="parent 1" Key="0-1" TItem="string">
<TreeNode Title="leaf 1-0" Key="0-1-0" TItem="string" />
<TreeNode Title="leaf 1-1" Key="0-1-1" TItem="string" />
</TreeNode>
</DirectoryTree>
@code {
void OnSelect()
{
void OnSelect()
{
}
}
void OnExpand()
{
void OnExpand()
{
}
}
}

View File

@ -1,74 +1,59 @@
<div>
<div style="margin-bottom: 16px">
showLine: <Switch @bind-Checked="_showLine" />
<br />
<br />
showIcon: <Switch @bind-Checked="_showIcon" />
<br />
<br />
showLeafIcon: <Switch @bind-Checked="_showLeafIcon" />
</div>
<div style="margin-bottom: 16px">
showLine: <Switch @bind-Checked="_showLine" />
<br />
<br />
showIcon: <Switch @bind-Checked="_showIcon" />
<br />
<br />
showLeafIcon: <Switch @bind-Checked="_showLeafIcon" />
</div>
<Tree
ShowLine="@_showLine"
ShowIcon="@_showIcon"
ShowLeafIcon="@_showLeafIcon"
DefaultExpandedKeys="@(new[]{"0-0-0"})"
OnSelect="OnSelect"
TItem="string">
<Nodes>
<TreeNode Title="parent 1" Key="0-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="parent 1-0" Key="0-0-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-0-0" Icon="carry-out" TItem="string" />
<TreeNode Key="0-0-0-1" Icon="carry-out" TItem="string">
<TitleTemplate>
<div>
<div>multiple line title</div>
<div>multiple line title</div>
</div>
</TitleTemplate>
</TreeNode>
<TreeNode Title="leaf" Key="0-0-0-2" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="left" Key="0-0-1-0" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
<TreeNode Title="parent 1-2" Key="0-0-2" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-0-2-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-0-2-1" Icon="carry-out" TItem="string" SwitcherIcon="form" />
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
<TreeNode Title="parent 2" Key="0-1" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="parent 2-0" Key="0-1-0" Icon="carry-out" TItem="string">
<Nodes>
<TreeNode Title="leaf" Key="0-1-0-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-1-0-1" Icon="carry-out" TItem="string" />
</Nodes>
</TreeNode>
</Nodes>
</TreeNode>
</Nodes>
</Tree>
<Tree ShowLine="@_showLine"
ShowIcon="@_showIcon"
ShowLeafIcon="@_showLeafIcon"
DefaultExpandedKeys="@(new[]{"0-0-0"})"
OnSelect="OnSelect"
TItem="string">
<TreeNode Title="parent 1" Key="0-0" Icon="carry-out" TItem="string">
<TreeNode Title="parent 1-0" Key="0-0-0" Icon="carry-out" TItem="string">
<TreeNode Title="leaf" Key="0-0-0-0" Icon="carry-out" TItem="string" />
<TreeNode Key="0-0-0-1" Icon="carry-out" TItem="string">
<TitleTemplate>
<div>
<div>multiple line title</div>
<div>multiple line title</div>
</div>
</TitleTemplate>
</TreeNode>
<TreeNode Title="leaf" Key="0-0-0-2" Icon="carry-out" TItem="string" />
</TreeNode>
<TreeNode Title="parent 1-1" Key="0-0-1" Icon="carry-out" TItem="string">
<TreeNode Title="left" Key="0-0-1-0" Icon="carry-out" TItem="string" />
</TreeNode>
<TreeNode Title="parent 1-2" Key="0-0-2" Icon="carry-out" TItem="string">
<TreeNode Title="leaf" Key="0-0-2-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-0-2-1" Icon="carry-out" TItem="string" SwitcherIcon="form" />
</TreeNode>
</TreeNode>
<TreeNode Title="parent 2" Key="0-1" Icon="carry-out" TItem="string">
<TreeNode Title="parent 2-0" Key="0-1-0" Icon="carry-out" TItem="string">
<TreeNode Title="leaf" Key="0-1-0-0" Icon="carry-out" TItem="string" />
<TreeNode Title="leaf" Key="0-1-0-1" Icon="carry-out" TItem="string" />
</TreeNode>
</TreeNode>
</Tree>
</div>
@code {
bool _showLine = true;
bool _showIcon = false;
bool _showLeafIcon = true;
bool _showLine = true;
bool _showIcon = false;
bool _showLeafIcon = true;
void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine(JsonSerializer.Serialize(e));
}
void OnSelect(TreeEventArgs<string> e)
{
Console.WriteLine(JsonSerializer.Serialize(e));
}
}

Some files were not shown because too many files have changed in this diff Show More