mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-29 13:47:02 +08:00
commit
581262b0aa
@ -14108,6 +14108,7 @@ exports[`ConfigProvider components InputNumber configProvider 1`] = `
|
||||
class="config-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="config-input-number-handler config-input-number-handler-up"
|
||||
role="button"
|
||||
@ -14134,6 +14135,7 @@ exports[`ConfigProvider components InputNumber configProvider 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="config-input-number-handler config-input-number-handler-down"
|
||||
role="button"
|
||||
@ -14164,12 +14166,8 @@ exports[`ConfigProvider components InputNumber configProvider 1`] = `
|
||||
class="config-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="config-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -14186,6 +14184,7 @@ exports[`ConfigProvider components InputNumber configProvider componentSize larg
|
||||
class="config-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="config-input-number-handler config-input-number-handler-up"
|
||||
role="button"
|
||||
@ -14212,6 +14211,7 @@ exports[`ConfigProvider components InputNumber configProvider componentSize larg
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="config-input-number-handler config-input-number-handler-down"
|
||||
role="button"
|
||||
@ -14242,12 +14242,8 @@ exports[`ConfigProvider components InputNumber configProvider componentSize larg
|
||||
class="config-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="config-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -14264,6 +14260,7 @@ exports[`ConfigProvider components InputNumber configProvider componentSize midd
|
||||
class="config-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="config-input-number-handler config-input-number-handler-up"
|
||||
role="button"
|
||||
@ -14290,6 +14287,7 @@ exports[`ConfigProvider components InputNumber configProvider componentSize midd
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="config-input-number-handler config-input-number-handler-down"
|
||||
role="button"
|
||||
@ -14320,12 +14318,8 @@ exports[`ConfigProvider components InputNumber configProvider componentSize midd
|
||||
class="config-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="config-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -14342,6 +14336,7 @@ exports[`ConfigProvider components InputNumber configProvider virtual and dropdo
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -14368,6 +14363,7 @@ exports[`ConfigProvider components InputNumber configProvider virtual and dropdo
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -14398,12 +14394,8 @@ exports[`ConfigProvider components InputNumber configProvider virtual and dropdo
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -14420,6 +14412,7 @@ exports[`ConfigProvider components InputNumber normal 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -14446,6 +14439,7 @@ exports[`ConfigProvider components InputNumber normal 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -14476,12 +14470,8 @@ exports[`ConfigProvider components InputNumber normal 1`] = `
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -14498,6 +14488,7 @@ exports[`ConfigProvider components InputNumber prefixCls 1`] = `
|
||||
class="prefix-InputNumber-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="prefix-InputNumber-handler prefix-InputNumber-handler-up"
|
||||
role="button"
|
||||
@ -14524,6 +14515,7 @@ exports[`ConfigProvider components InputNumber prefixCls 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="prefix-InputNumber-handler prefix-InputNumber-handler-down"
|
||||
role="button"
|
||||
@ -14554,12 +14546,8 @@ exports[`ConfigProvider components InputNumber prefixCls 1`] = `
|
||||
class="prefix-InputNumber-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="prefix-InputNumber-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
|
@ -2640,6 +2640,7 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -2666,6 +2667,7 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -2696,13 +2698,9 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
id="nest-messages_user_age"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -4347,6 +4345,7 @@ exports[`renders ./components/form/demo/size.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -4373,6 +4372,7 @@ exports[`renders ./components/form/demo/size.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -4403,12 +4403,8 @@ exports[`renders ./components/form/demo/size.md correctly 1`] = `
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -5243,6 +5239,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -5269,6 +5266,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -5305,8 +5303,6 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
id="validate_other_input-number"
|
||||
max="10"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -7267,6 +7263,7 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -7293,6 +7290,7 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -7323,12 +7321,8 @@ exports[`renders ./components/form/demo/validate-static.md correctly 1`] = `
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -7675,6 +7669,7 @@ exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -7701,6 +7696,7 @@ exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -7736,8 +7732,6 @@ exports[`renders ./components/form/demo/without-form-create.md correctly 1`] = `
|
||||
aria-valuenow="11"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="12"
|
||||
min="8"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="11"
|
||||
|
@ -8,6 +8,7 @@ exports[`renders ./components/input-number/demo/basic.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -34,6 +35,7 @@ exports[`renders ./components/input-number/demo/basic.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -69,8 +71,6 @@ exports[`renders ./components/input-number/demo/basic.md correctly 1`] = `
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="10"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -87,6 +87,7 @@ exports[`renders ./components/input-number/demo/borderless.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -113,6 +114,7 @@ exports[`renders ./components/input-number/demo/borderless.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -148,8 +150,6 @@ exports[`renders ./components/input-number/demo/borderless.md correctly 1`] = `
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="10"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -161,11 +161,13 @@ exports[`renders ./components/input-number/demo/borderless.md correctly 1`] = `
|
||||
exports[`renders ./components/input-number/demo/digit.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-input-number"
|
||||
style="width:200px"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -192,6 +194,7 @@ exports[`renders ./components/input-number/demo/digit.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -224,13 +227,12 @@ exports[`renders ./components/input-number/demo/digit.md correctly 1`] = `
|
||||
<input
|
||||
aria-valuemax="10"
|
||||
aria-valuemin="0"
|
||||
aria-valuenow="1"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="10"
|
||||
min="0"
|
||||
role="spinbutton"
|
||||
step="0.1"
|
||||
value=""
|
||||
step="0.00000000000001"
|
||||
value="1.00000000000000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -245,9 +247,9 @@ Array [
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up ant-input-number-handler-up-disabled"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
@ -272,9 +274,9 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="true"
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down ant-input-number-handler-down-disabled"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
@ -309,8 +311,6 @@ Array [
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
disabled=""
|
||||
max="10"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -341,6 +341,7 @@ Array [
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -367,6 +368,7 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -397,13 +399,9 @@ Array [
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
aria-valuenow="1000"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="$ 1,000"
|
||||
@ -444,6 +442,7 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -479,8 +478,6 @@ Array [
|
||||
aria-valuenow="100"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="100"
|
||||
min="0"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="100%"
|
||||
@ -491,96 +488,214 @@ Array [
|
||||
`;
|
||||
|
||||
exports[`renders ./components/input-number/demo/keyboard.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number"
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
class="ant-input-number"
|
||||
>
|
||||
<span
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-label="up"
|
||||
class="anticon anticon-up ant-input-number-handler-up-inner"
|
||||
role="img"
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="up"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="up"
|
||||
class="anticon anticon-up ant-input-number-handler-up-inner"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="up"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-input-number-handler-down-inner"
|
||||
role="img"
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-input-number-handler-down-inner"
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="10"
|
||||
aria-valuemin="1"
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="10"
|
||||
aria-valuemin="1"
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="10"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
</div>
|
||||
<div
|
||||
style="margin-top:20px"
|
||||
class="ant-space-item"
|
||||
>
|
||||
<label
|
||||
class="ant-checkbox-wrapper ant-checkbox-wrapper-checked"
|
||||
>
|
||||
<span
|
||||
class="ant-checkbox ant-checkbox-checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="ant-checkbox-input"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
class="ant-checkbox-inner"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
Toggle keyboard
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/input-number/demo/out-of-range.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-space ant-space-horizontal ant-space-align-center"
|
||||
>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number ant-input-number-out-of-range"
|
||||
>
|
||||
<div
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="true"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up ant-input-number-handler-up-disabled"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="up"
|
||||
class="anticon anticon-up ant-input-number-handler-up-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="up"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
unselectable="on"
|
||||
>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-input-number-handler-down-inner"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="10"
|
||||
aria-valuemin="1"
|
||||
aria-valuenow="99"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="99"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Toggle keyboard
|
||||
Reset
|
||||
</span>
|
||||
</button>
|
||||
</div>,
|
||||
]
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
@ -594,6 +709,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -620,6 +736,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -655,8 +772,6 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="100000"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -670,6 +785,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -696,6 +812,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -731,8 +848,6 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="100000"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
@ -746,6 +861,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -772,6 +888,7 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -807,8 +924,6 @@ exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
|
||||
aria-valuenow="3"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="100000"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="3"
|
||||
|
@ -8,6 +8,7 @@ exports[`InputNumber rtl render component should be rendered correctly in RTL di
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -34,6 +35,7 @@ exports[`InputNumber rtl render component should be rendered correctly in RTL di
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -64,12 +66,8 @@ exports[`InputNumber rtl render component should be rendered correctly in RTL di
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
|
@ -15,7 +15,8 @@ describe('InputNumber', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(<InputNumber defaultValue="1" onChange={onChange} />);
|
||||
wrapper.find('input').simulate('change', { target: { value: '' } });
|
||||
expect(onChange).toHaveBeenLastCalledWith('');
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.find('input').simulate('blur');
|
||||
expect(onChange).toHaveBeenLastCalledWith(null);
|
||||
});
|
||||
|
@ -1,24 +1,35 @@
|
||||
---
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 小数
|
||||
en-US: Decimals
|
||||
zh-CN: 高精度小数
|
||||
en-US: High precision decimals
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
和原生的数字输入框一样,value 的精度由 step 的小数位数决定。
|
||||
通过 `stringMode` 开启高精度小数支持,`onChange` 事件将返回 string 类型。对于旧版游览器,你需要 BigInt polyfill。
|
||||
|
||||
## en-US
|
||||
|
||||
A numeric-only input box whose values can be increased or decreased using a decimal step. The number of decimals (also known as precision) is determined by the step prop.
|
||||
Use `stringMode` to support high precision decimals support. `onChange` will return string value instead. You need polyfill of BigInt if browser not support.
|
||||
|
||||
```jsx
|
||||
```tsx
|
||||
import { InputNumber } from 'antd';
|
||||
|
||||
function onChange(value) {
|
||||
function onChange(value: string) {
|
||||
console.log('changed', value);
|
||||
}
|
||||
|
||||
ReactDOM.render(<InputNumber min={0} max={10} step={0.1} onChange={onChange} />, mountNode);
|
||||
ReactDOM.render(
|
||||
<InputNumber<string>
|
||||
style={{ width: 200 }}
|
||||
defaultValue="1"
|
||||
min="0"
|
||||
max="10"
|
||||
step="0.00000000000001"
|
||||
onChange={onChange}
|
||||
stringMode
|
||||
/>,
|
||||
mountNode,
|
||||
);
|
||||
```
|
||||
|
@ -13,33 +13,25 @@ title:
|
||||
|
||||
Control keyboard behavior by `keyboard`.
|
||||
|
||||
```jsx
|
||||
import { InputNumber, Button } from 'antd';
|
||||
```tsx
|
||||
import { InputNumber, Checkbox, Space } from 'antd';
|
||||
|
||||
class App extends React.Component {
|
||||
state = {
|
||||
keyboard: true,
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
this.setState({
|
||||
keyboard: !this.state.keyboard,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<InputNumber min={1} max={10} keyboard={this.state.keyboard} defaultValue={3} />
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Button onClick={this.toggle} type="primary">
|
||||
Toggle keyboard
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
const App = () => {
|
||||
const [keyboard, setKeyboard] = React.useState(true);
|
||||
return (
|
||||
<Space>
|
||||
<InputNumber min={1} max={10} keyboard={keyboard} defaultValue={3} />
|
||||
<Checkbox
|
||||
onChange={() => {
|
||||
setKeyboard(!keyboard);
|
||||
}}
|
||||
checked={keyboard}
|
||||
>
|
||||
Toggle keyboard
|
||||
</Checkbox>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, mountNode);
|
||||
```
|
||||
|
38
components/input-number/demo/out-of-range.md
Normal file
38
components/input-number/demo/out-of-range.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
order: 6
|
||||
title:
|
||||
zh-CN: 超出边界
|
||||
en-US: Out of range
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
当通过受控将 `value` 超出边界时,提供警告样式。
|
||||
|
||||
## en-US
|
||||
|
||||
Show warning style when `value` is out of range by control.
|
||||
|
||||
```tsx
|
||||
import { InputNumber, Button, Space } from 'antd';
|
||||
|
||||
const Demo = () => {
|
||||
const [value, setValue] = React.useState<string | number>('99');
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<InputNumber min={1} max={10} value={value} onChange={setValue} />
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setValue(99);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Demo />, mountNode);
|
||||
```
|
@ -29,6 +29,7 @@ When a numeric value needs to be provided.
|
||||
| readOnly | If readonly the input | boolean | false | - |
|
||||
| size | The height of input box | `large` \| `middle` \| `small` | - | - |
|
||||
| step | The number to which the current value is increased or decreased. It can be an integer or decimal | number \| string | 1 | - |
|
||||
| stringMode | Set value as string to support high precision decimals. Will return string value by `onChange` | boolean | false | 4.13.0 |
|
||||
| value | The current value | number | - | - |
|
||||
| onChange | The callback triggered when the value is changed | function(value: number \| string \| null) | - | - |
|
||||
| onPressEnter | The callback function that is triggered when Enter key is pressed | function(e) | - | - |
|
||||
@ -44,3 +45,13 @@ When a numeric value needs to be provided.
|
||||
## Notes
|
||||
|
||||
Per issues [#21158](https://github.com/ant-design/ant-design/issues/21158), [#17344](https://github.com/ant-design/ant-design/issues/17344), [#9421](https://github.com/ant-design/ant-design/issues/9421), and [documentation about inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number#Using_number_inputs), it appears this community does not support native inclusion of the `type="number"` in the `<Input />` attributes, so please feel free to include it as needed, and be aware that it is heavily suggested that server side validation be utilized, as client side validation can be edited by power users.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why `value` can exceed `min` or `max` in control?
|
||||
|
||||
Developer handle data by their own in control. It will make data out of sync if InputNumber change display value. It also cause potential data issues when use in form.
|
||||
|
||||
### Why dynamic change `min` or `max` which makes `value` out of range will not trigger `onChange`?
|
||||
|
||||
`onChange` is user trigger event. Auto trigger will makes form lib can not detect data modify source.
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import RcInputNumber from 'rc-input-number';
|
||||
import RcInputNumber, { InputNumberProps as RcInputNumberProps } from 'rc-input-number';
|
||||
import UpOutlined from '@ant-design/icons/UpOutlined';
|
||||
import DownOutlined from '@ant-design/icons/DownOutlined';
|
||||
|
||||
@ -8,37 +8,16 @@ import { ConfigContext } from '../config-provider';
|
||||
import { Omit } from '../_util/type';
|
||||
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
||||
|
||||
// omitting this attrs because they conflicts with the ones defined in InputNumberProps
|
||||
export type OmitAttrs = 'defaultValue' | 'onChange' | 'size';
|
||||
type ValueType = string | number;
|
||||
|
||||
export interface InputNumberProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, OmitAttrs> {
|
||||
export interface InputNumberProps<T extends ValueType = ValueType>
|
||||
extends Omit<RcInputNumberProps<T>, 'size'> {
|
||||
prefixCls?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
value?: number;
|
||||
step?: number | string;
|
||||
defaultValue?: number;
|
||||
tabIndex?: number;
|
||||
onChange?: (value: number | string | undefined | null) => void;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
size?: SizeType;
|
||||
bordered?: boolean;
|
||||
formatter?: (value: number | string | undefined) => string;
|
||||
parser?: (displayValue: string | undefined) => number | string;
|
||||
decimalSeparator?: string;
|
||||
placeholder?: string;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
name?: string;
|
||||
id?: string;
|
||||
precision?: number;
|
||||
onPressEnter?: React.KeyboardEventHandler<HTMLInputElement>;
|
||||
onStep?: (value: number, info: { offset: number; type: 'up' | 'down' }) => void;
|
||||
}
|
||||
|
||||
const InputNumber = React.forwardRef<unknown, InputNumberProps>((props, ref) => {
|
||||
const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const size = React.useContext(SizeContext);
|
||||
|
||||
@ -80,8 +59,8 @@ const InputNumber = React.forwardRef<unknown, InputNumberProps>((props, ref) =>
|
||||
);
|
||||
});
|
||||
|
||||
InputNumber.defaultProps = {
|
||||
step: 1,
|
||||
};
|
||||
|
||||
export default InputNumber;
|
||||
export default InputNumber as (<T extends ValueType = ValueType>(
|
||||
props: React.PropsWithChildren<InputNumberProps<T>> & {
|
||||
ref?: React.Ref<HTMLInputElement>;
|
||||
},
|
||||
) => React.ReactElement) & { displayName?: string };
|
||||
|
@ -32,6 +32,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/XOS8qZ0kU/InputNumber.svg
|
||||
| readOnly | 只读 | boolean | false | - |
|
||||
| size | 输入框大小 | `large` \| `middle` \| `small` | - | - |
|
||||
| step | 每次改变步数,可以为小数 | number \| string | 1 | - |
|
||||
| stringMode | 字符值模式,开启后支持高精度小数。同时 `onChange` 将返回 string 类型 | boolean | false | 4.13.0 |
|
||||
| value | 当前值 | number | - | - |
|
||||
| onChange | 变化回调 | function(value: number \| string \| null) | - | - |
|
||||
| onPressEnter | 按下回车的回调 | function(e) | - | - |
|
||||
@ -43,3 +44,13 @@ cover: https://gw.alipayobjects.com/zos/alicdn/XOS8qZ0kU/InputNumber.svg
|
||||
| ------- | -------- |
|
||||
| blur() | 移除焦点 |
|
||||
| focus() | 获取焦点 |
|
||||
|
||||
## FAQ
|
||||
|
||||
### 为何受控模式下,`value` 可以超出 `min` 和 `max` 范围?
|
||||
|
||||
在受控模式下,开发者可能自行存储相关数据。如果组件将数据约束回范围内,会导致展示数据与实际存储数据不一致的情况。这使得一些如表单场景存在潜在的数据问题。
|
||||
|
||||
### 为何动态修改 `min` 和 `max` 让 `value` 超出范围不会触发 `onChange` 事件?
|
||||
|
||||
`onChange` 事件为用户触发事件,自行触发会导致表单库误以为变更来自用户操作。我们以错误样式展示超出范围的数值。
|
||||
|
@ -197,6 +197,13 @@
|
||||
&-borderless {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// ===================== Out Of Range =====================
|
||||
&-out-of-range {
|
||||
input {
|
||||
color: @error-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import './rtl';
|
||||
|
@ -360,6 +360,7 @@ Array [
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -386,6 +387,7 @@ Array [
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -416,12 +418,8 @@ Array [
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
@ -1748,6 +1746,7 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -1774,6 +1773,7 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Decrease Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-down"
|
||||
role="button"
|
||||
@ -1804,12 +1804,8 @@ exports[`renders ./components/input/demo/group.md correctly 1`] = `
|
||||
class="ant-input-number-input-wrap"
|
||||
>
|
||||
<input
|
||||
aria-valuemax="9007199254740991"
|
||||
aria-valuemin="-9007199254740991"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="9007199254740991"
|
||||
min="-9007199254740991"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value=""
|
||||
|
@ -311,6 +311,7 @@ exports[`renders ./components/slider/demo/input-number.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -373,8 +374,6 @@ exports[`renders ./components/slider/demo/input-number.md correctly 1`] = `
|
||||
aria-valuenow="1"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="20"
|
||||
min="1"
|
||||
role="spinbutton"
|
||||
step="1"
|
||||
value="1"
|
||||
@ -428,6 +427,7 @@ exports[`renders ./components/slider/demo/input-number.md correctly 1`] = `
|
||||
class="ant-input-number-handler-wrap"
|
||||
>
|
||||
<span
|
||||
aria-disabled="false"
|
||||
aria-label="Increase Value"
|
||||
class="ant-input-number-handler ant-input-number-handler-up"
|
||||
role="button"
|
||||
@ -490,8 +490,6 @@ exports[`renders ./components/slider/demo/input-number.md correctly 1`] = `
|
||||
aria-valuenow="0"
|
||||
autocomplete="off"
|
||||
class="ant-input-number-input"
|
||||
max="1"
|
||||
min="0"
|
||||
role="spinbutton"
|
||||
step="0.01"
|
||||
value="0.00"
|
||||
|
@ -216,7 +216,7 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ant-transfer-list ant-transfer-list-with-footer"
|
||||
class="ant-transfer-list"
|
||||
style="width:250px;height:300px"
|
||||
>
|
||||
<div
|
||||
@ -352,19 +352,6 @@ exports[`renders ./components/transfer/demo/advanced.md correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-transfer-list-footer"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-sm"
|
||||
style="float:right;margin:5px"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
reload
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -558,3 +558,31 @@ describe('immutable data', () => {
|
||||
expect(wrapper).toMatchRenderedSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('footer render source and target', () => {
|
||||
// https://github.com/ant-design/ant-design/issues/28082
|
||||
it('currently render footer', () => {
|
||||
const differentFooter = () => ({
|
||||
source: (
|
||||
<Button size="small" className="sourceFooter">
|
||||
reload
|
||||
</Button>
|
||||
),
|
||||
target: (
|
||||
<Button size="small" className="targetFooter">
|
||||
reload
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
const defaultFooter = () => (
|
||||
<Button size="small" className="defaultFooter">
|
||||
reload
|
||||
</Button>
|
||||
);
|
||||
const wrapper = mount(<Transfer footer={differentFooter} />);
|
||||
const wrapper2 = mount(<Transfer footer={defaultFooter} />);
|
||||
expect(wrapper.exists('.sourceFooter')).toEqual(true);
|
||||
expect(wrapper.exists('.targetFooter')).toEqual(true);
|
||||
expect(wrapper2.exists('.defaultFooter')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
@ -50,11 +50,13 @@ class App extends React.Component {
|
||||
this.setState({ targetKeys });
|
||||
};
|
||||
|
||||
renderFooter = () => (
|
||||
<Button size="small" style={{ float: 'right', margin: 5 }} onClick={this.getMock}>
|
||||
reload
|
||||
</Button>
|
||||
);
|
||||
renderFooter = () => ({
|
||||
source: (
|
||||
<Button size="small" style={{ float: 'right', margin: 5 }} onClick={this.getMock}>
|
||||
reload
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
@ -24,7 +24,7 @@ One or more elements can be selected from either column, one click on the proper
|
||||
| dataSource | Used for setting the source data. The elements that are part of this array will be present the left column. Except the elements whose keys are included in `targetKeys` prop | [RecordType extends TransferItem = TransferItem](https://git.io/vMM64)\[] | \[] | |
|
||||
| disabled | Whether disabled transfer | boolean | false | |
|
||||
| filterOption | A function to determine whether an item should show in search result list | (inputValue, option): boolean | - | |
|
||||
| footer | A function used for rendering the footer | (props) => ReactNode | - | |
|
||||
| footer | A function used for rendering the footer | (props) => ReactNode \| { source: ReactNode, target: ReactNode } | - | { source: ReactNode, target: ReactNode }: 4.12.3 |
|
||||
| listStyle | A custom CSS style used for rendering the transfer columns | object \| ({direction: `left` \| `right`}) => object | - | |
|
||||
| locale | The i18n text including filter, empty text, item unit, etc | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | { itemUnit: `item`, itemsUnit: `items`, notFoundContent: `The list is empty`, searchPlaceholder: `Search here` } | |
|
||||
| oneWay | Display as single direction style | boolean | false | 4.3.0 |
|
||||
|
@ -58,7 +58,6 @@ export interface TransferLocale {
|
||||
removeAll: string;
|
||||
removeCurrent: string;
|
||||
}
|
||||
|
||||
export interface TransferProps<RecordType> {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
@ -77,7 +76,14 @@ export interface TransferProps<RecordType> {
|
||||
showSearch?: boolean;
|
||||
filterOption?: (inputValue: string, item: RecordType) => boolean;
|
||||
locale?: Partial<TransferLocale>;
|
||||
footer?: (props: TransferListProps<RecordType>) => React.ReactNode;
|
||||
footer?: (
|
||||
props: TransferListProps<RecordType>,
|
||||
) =>
|
||||
| React.ReactNode
|
||||
| {
|
||||
source?: React.ReactNode;
|
||||
target?: React.ReactNode;
|
||||
};
|
||||
rowKey?: (record: RecordType) => string;
|
||||
onSearch?: (direction: TransferDirection, value: string) => void;
|
||||
onScroll?: (direction: TransferDirection, e: React.SyntheticEvent<HTMLUListElement>) => void;
|
||||
|
@ -27,7 +27,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/QAXskNI4G/Transfer.svg
|
||||
| dataSource | 数据源,其中的数据将会被渲染到左边一栏中,`targetKeys` 中指定的除外 | [RecordType extends TransferItem = TransferItem](https://git.io/vMM64)\[] | \[] | |
|
||||
| disabled | 是否禁用 | boolean | false | |
|
||||
| filterOption | 接收 `inputValue` `option` 两个参数,当 `option` 符合筛选条件时,应返回 true,反之则返回 false | (inputValue, option): boolean | - | |
|
||||
| footer | 底部渲染函数 | (props) => ReactNode | - | |
|
||||
| footer | 底部渲染函数 | (props) => ReactNode \| { source: ReactNode, target: ReactNode } | - | { source: ReactNode, target: ReactNode }: 4.12.3 |
|
||||
| listStyle | 两个穿梭框的自定义样式 | object\|({direction: `left` \| `right`}) => object | - | |
|
||||
| locale | 各种语言 | { itemUnit: string; itemsUnit: string; searchPlaceholder: string; notFoundContent: ReactNode; } | { itemUnit: `项`, itemsUnit: `项`, searchPlaceholder: `请输入搜索内容` } | |
|
||||
| oneWay | 展示为单向样式 | boolean | false | 4.3.0 |
|
||||
|
@ -39,7 +39,10 @@ export interface RenderedItem<RecordType> {
|
||||
}
|
||||
|
||||
type RenderListFunction<T> = (props: TransferListBodyProps<T>) => React.ReactNode;
|
||||
|
||||
type FooterRender = {
|
||||
source?: React.ReactNode;
|
||||
target?: React.ReactNode;
|
||||
};
|
||||
export interface TransferListProps<RecordType> extends TransferLocale {
|
||||
prefixCls: string;
|
||||
titleText: React.ReactNode;
|
||||
@ -59,7 +62,7 @@ export interface TransferListProps<RecordType> extends TransferLocale {
|
||||
itemUnit: string;
|
||||
itemsUnit: string;
|
||||
renderList?: RenderListFunction<RecordType>;
|
||||
footer?: (props: TransferListProps<RecordType>) => React.ReactNode;
|
||||
footer?: (props: TransferListProps<RecordType>) => React.ReactNode | FooterRender;
|
||||
onScroll: (e: React.UIEvent<HTMLUListElement>) => void;
|
||||
disabled?: boolean;
|
||||
direction: TransferDirection;
|
||||
@ -312,10 +315,27 @@ export default class TransferList<
|
||||
showSelectAll,
|
||||
showRemove,
|
||||
pagination,
|
||||
direction,
|
||||
} = this.props;
|
||||
|
||||
// Custom Layout
|
||||
const footerDom = footer && footer(this.props);
|
||||
// Distinguish different footer
|
||||
const tempDom = footer && footer(this.props);
|
||||
let footerDom;
|
||||
function isFooterRender(obj: any): obj is FooterRender {
|
||||
return obj.source || obj.target;
|
||||
}
|
||||
if (tempDom) {
|
||||
if (isFooterRender(tempDom)) {
|
||||
if (direction === 'left') {
|
||||
footerDom = tempDom.source;
|
||||
} else {
|
||||
footerDom = tempDom.target;
|
||||
}
|
||||
} else {
|
||||
footerDom = tempDom;
|
||||
}
|
||||
}
|
||||
|
||||
const listCls = classNames(prefixCls, {
|
||||
[`${prefixCls}-with-pagination`]: pagination,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import RcUpload from 'rc-upload';
|
||||
import RcUpload, { UploadProps as RcUploadProps } from 'rc-upload';
|
||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||
import classNames from 'classnames';
|
||||
import Dragger from './Dragger';
|
||||
import UploadList from './UploadList';
|
||||
@ -13,19 +14,19 @@ import {
|
||||
UploadType,
|
||||
UploadListType,
|
||||
} from './interface';
|
||||
import { T, fileToObject, getFileItem, removeFileItem } from './utils';
|
||||
import { T, wrapFile, getFileItem, removeFileItem } from './utils';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import defaultLocale from '../locale/default';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import useForceUpdate from '../_util/hooks/useForceUpdate';
|
||||
import useFreshState from './useFreshState';
|
||||
|
||||
const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`;
|
||||
|
||||
export { UploadProps };
|
||||
|
||||
const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (props, ref) => {
|
||||
const {
|
||||
fileList: fileListProp,
|
||||
fileList,
|
||||
defaultFileList,
|
||||
onRemove,
|
||||
showUploadList,
|
||||
@ -48,14 +49,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
maxCount,
|
||||
} = props;
|
||||
|
||||
const [dragState, setDragState] = React.useState<string>('drop');
|
||||
const forceUpdate = useForceUpdate();
|
||||
const [mergedFileList, setMergedFileList] = useMergedState(defaultFileList || [], {
|
||||
value: fileList,
|
||||
});
|
||||
|
||||
// Refresh always use fresh data
|
||||
const [getFileList, setFileList] = useFreshState<UploadFile<any>[]>(
|
||||
fileListProp || defaultFileList || [],
|
||||
fileListProp,
|
||||
);
|
||||
const [dragState, setDragState] = React.useState<string>('drop');
|
||||
|
||||
const upload = React.useRef<any>();
|
||||
|
||||
@ -77,13 +75,19 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
React.useEffect(() => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
(fileListProp || []).forEach((file, index) => {
|
||||
file.uid = file.uid ?? `__AUTO__${timestamp}_${index}__`;
|
||||
(fileList || []).forEach((file, index) => {
|
||||
if (!file.uid) {
|
||||
file.uid = `__AUTO__${timestamp}_${index}__`;
|
||||
}
|
||||
});
|
||||
}, [fileListProp]);
|
||||
}, [fileList]);
|
||||
|
||||
const onInternalChange = (info: UploadChangeParam) => {
|
||||
let cloneList = [...info.fileList];
|
||||
const onInternalChange = (
|
||||
file: UploadFile,
|
||||
changedFileList: UploadFile[],
|
||||
event?: { percent: number },
|
||||
) => {
|
||||
let cloneList = [...changedFileList];
|
||||
|
||||
// Cut to match count
|
||||
if (maxCount === 1) {
|
||||
@ -92,19 +96,47 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
cloneList = cloneList.slice(0, maxCount);
|
||||
}
|
||||
|
||||
setFileList(cloneList);
|
||||
setMergedFileList(cloneList);
|
||||
|
||||
onChange?.({
|
||||
...info,
|
||||
const changeInfo: UploadChangeParam = {
|
||||
file,
|
||||
fileList: cloneList,
|
||||
};
|
||||
|
||||
if (event) {
|
||||
changeInfo.event = event;
|
||||
}
|
||||
|
||||
onChange?.(changeInfo);
|
||||
};
|
||||
|
||||
const onBatchStart: RcUploadProps['onBatchStart'] = batchFileInfoList => {
|
||||
// Skip file which marked as `LIST_IGNORE`, these file will not add to file list
|
||||
const filteredFileInfoList = batchFileInfoList.filter(info => !(info.file as any)[LIST_IGNORE]);
|
||||
|
||||
// Nothing to do since no file need upload
|
||||
if (!filteredFileInfoList.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const objectFileList = filteredFileInfoList.map(info => wrapFile(info.file));
|
||||
|
||||
// Concat new files with prev files
|
||||
const newFileList = [...mergedFileList];
|
||||
objectFileList.forEach(fileObj => {
|
||||
if (newFileList.every(existFile => existFile.uid !== fileObj.uid)) {
|
||||
newFileList.push(fileObj);
|
||||
}
|
||||
});
|
||||
|
||||
onInternalChange(filteredFileInfoList[0]?.file, newFileList);
|
||||
};
|
||||
|
||||
const onStart = (file: RcFile) => {
|
||||
const targetItem = fileToObject(file);
|
||||
const targetItem = wrapFile(file);
|
||||
targetItem.status = 'uploading';
|
||||
|
||||
const nextFileList = getFileList().concat();
|
||||
const nextFileList = [...mergedFileList];
|
||||
|
||||
const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === targetItem.uid);
|
||||
if (fileIndex === -1) {
|
||||
@ -113,10 +145,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
nextFileList[fileIndex] = targetItem;
|
||||
}
|
||||
|
||||
onInternalChange({
|
||||
file: targetItem,
|
||||
fileList: nextFileList,
|
||||
});
|
||||
onInternalChange(targetItem, nextFileList);
|
||||
};
|
||||
|
||||
const onSuccess = (response: any, file: UploadFile, xhr: any) => {
|
||||
@ -127,7 +156,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
const targetItem = getFileItem(file, mergedFileList);
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
@ -135,28 +164,21 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
targetItem.status = 'done';
|
||||
targetItem.response = response;
|
||||
targetItem.xhr = xhr;
|
||||
onInternalChange({
|
||||
file: { ...targetItem },
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
onInternalChange(wrapFile(targetItem), [...mergedFileList]);
|
||||
};
|
||||
|
||||
const onProgress = (e: { percent: number }, file: UploadFile) => {
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
const targetItem = getFileItem(file, mergedFileList);
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
}
|
||||
targetItem.percent = e.percent;
|
||||
onInternalChange({
|
||||
event: e,
|
||||
file: { ...targetItem },
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
onInternalChange(wrapFile(targetItem), [...mergedFileList], e);
|
||||
};
|
||||
|
||||
const onError = (error: Error, response: any, file: UploadFile) => {
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
const targetItem = getFileItem(file, mergedFileList);
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
@ -164,10 +186,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
targetItem.error = error;
|
||||
targetItem.response = response;
|
||||
targetItem.status = 'error';
|
||||
onInternalChange({
|
||||
file: { ...targetItem },
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
onInternalChange(wrapFile(targetItem), [...mergedFileList]);
|
||||
};
|
||||
|
||||
const handleRemove = (file: UploadFile) => {
|
||||
@ -178,12 +197,12 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
return;
|
||||
}
|
||||
|
||||
const fileList = getFileList();
|
||||
const removedFileList = removeFileItem(file, fileList);
|
||||
const removedFileList = removeFileItem(file, mergedFileList);
|
||||
|
||||
if (removedFileList) {
|
||||
currentFile = { ...file, status: 'removed' };
|
||||
fileList?.forEach(item => {
|
||||
currentFile = wrapFile(file);
|
||||
currentFile.status = 'removed';
|
||||
mergedFileList?.forEach(item => {
|
||||
const matchKey = currentFile.uid !== undefined ? 'uid' : 'name';
|
||||
if (item[matchKey] === currentFile[matchKey]) {
|
||||
item.status = 'removed';
|
||||
@ -191,10 +210,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
});
|
||||
upload.current?.abort(currentFile);
|
||||
|
||||
onInternalChange({
|
||||
file: currentFile,
|
||||
fileList: removedFileList,
|
||||
});
|
||||
onInternalChange(currentFile, removedFileList);
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -203,43 +219,47 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
setDragState(e.type);
|
||||
};
|
||||
|
||||
const beforeUpload = (file: RcFile, fileListArgs: RcFile[]) => {
|
||||
const { beforeUpload: beforeUploadProp } = props;
|
||||
if (!beforeUploadProp) {
|
||||
return true;
|
||||
}
|
||||
const result = beforeUploadProp(file, fileListArgs);
|
||||
if (result === false) {
|
||||
// Get unique file list
|
||||
const uniqueList: UploadFile<any>[] = [];
|
||||
getFileList()
|
||||
.concat(fileListArgs.map(fileToObject))
|
||||
.forEach(f => {
|
||||
if (uniqueList.every(uf => uf.uid !== f.uid)) {
|
||||
uniqueList.push(f);
|
||||
}
|
||||
});
|
||||
const mergedBeforeUpload = async (file: RcFile, fileListArgs: RcFile[]) => {
|
||||
const { beforeUpload, transformFile } = props;
|
||||
|
||||
onInternalChange({
|
||||
file,
|
||||
fileList: uniqueList,
|
||||
});
|
||||
return false;
|
||||
let parsedFile: File | Blob | string = file;
|
||||
if (beforeUpload) {
|
||||
const result = await beforeUpload(file, fileListArgs);
|
||||
|
||||
if (result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hack for LIST_IGNORE, we add additional info to remove from the list
|
||||
delete (file as any)[LIST_IGNORE];
|
||||
if ((result as any) === LIST_IGNORE) {
|
||||
Object.defineProperty(file, LIST_IGNORE, {
|
||||
value: true,
|
||||
configurable: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof result === 'object' && result) {
|
||||
parsedFile = result as File;
|
||||
}
|
||||
}
|
||||
if (result && (result as PromiseLike<any>).then) {
|
||||
return result;
|
||||
|
||||
if (transformFile) {
|
||||
parsedFile = await transformFile(parsedFile as any);
|
||||
}
|
||||
return true;
|
||||
|
||||
return parsedFile as RcFile;
|
||||
};
|
||||
|
||||
// Test needs
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
onStart,
|
||||
onSuccess,
|
||||
onProgress,
|
||||
onError,
|
||||
fileList: getFileList(),
|
||||
fileList: mergedFileList,
|
||||
upload: upload.current,
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
@ -247,13 +267,14 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
const prefixCls = getPrefixCls('upload', customizePrefixCls);
|
||||
|
||||
const rcUploadProps = {
|
||||
onBatchStart,
|
||||
onStart,
|
||||
onError,
|
||||
onProgress,
|
||||
onSuccess,
|
||||
...props,
|
||||
prefixCls,
|
||||
beforeUpload,
|
||||
beforeUpload: mergedBeforeUpload,
|
||||
onChange: undefined,
|
||||
};
|
||||
|
||||
@ -277,7 +298,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
return (
|
||||
<UploadList
|
||||
listType={listType}
|
||||
items={getFileList(true)}
|
||||
items={mergedFileList}
|
||||
previewFile={previewFile}
|
||||
onPreview={onPreview}
|
||||
onDownload={onDownload}
|
||||
@ -306,7 +327,7 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-drag`]: true,
|
||||
[`${prefixCls}-drag-uploading`]: getFileList().some(file => file.status === 'uploading'),
|
||||
[`${prefixCls}-drag-uploading`]: mergedFileList.some(file => file.status === 'uploading'),
|
||||
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
@ -365,12 +386,15 @@ interface CompoundedComponent
|
||||
React.PropsWithChildren<UploadProps> & React.RefAttributes<any>
|
||||
> {
|
||||
Dragger: typeof Dragger;
|
||||
LIST_IGNORE: {};
|
||||
}
|
||||
|
||||
const Upload = React.forwardRef<unknown, UploadProps>(InternalUpload) as CompoundedComponent;
|
||||
|
||||
Upload.Dragger = Dragger;
|
||||
|
||||
Upload.LIST_IGNORE = LIST_IGNORE;
|
||||
|
||||
Upload.displayName = 'Upload';
|
||||
|
||||
Upload.defaultProps = {
|
||||
|
@ -5,7 +5,7 @@ import { act } from 'react-dom/test-utils';
|
||||
import produce from 'immer';
|
||||
import Upload from '..';
|
||||
import Form from '../../form';
|
||||
import { T, fileToObject, getFileItem, removeFileItem } from '../utils';
|
||||
import { T, wrapFile, getFileItem, removeFileItem } from '../utils';
|
||||
import { setup, teardown } from './mock';
|
||||
import { resetWarned } from '../../_util/devWarning';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
@ -291,11 +291,29 @@ describe('Upload', () => {
|
||||
expect(res).toBe(true);
|
||||
});
|
||||
|
||||
it('should be able to copy file instance', () => {
|
||||
const file = new File([], 'aaa.zip');
|
||||
const copiedFile = fileToObject(file);
|
||||
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
|
||||
expect(key in copiedFile).toBe(true);
|
||||
describe('wrapFile', () => {
|
||||
it('should be able to copy file instance when Proxy not support', () => {
|
||||
const file = new File([], 'aaa.zip');
|
||||
|
||||
const OriginProxy = global.Proxy;
|
||||
global.Proxy = undefined;
|
||||
|
||||
const copiedFile = wrapFile(file);
|
||||
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
|
||||
expect(key in copiedFile).toBe(true);
|
||||
});
|
||||
|
||||
global.Proxy = OriginProxy;
|
||||
});
|
||||
|
||||
it('Proxy support', () => {
|
||||
const file = new File([], 'aaa.zip');
|
||||
|
||||
const copiedFile = wrapFile(file);
|
||||
console.log(Object.keys(copiedFile));
|
||||
['uid', 'lastModified', 'lastModifiedDate', 'name', 'size', 'type'].forEach(key => {
|
||||
expect(key in copiedFile).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -617,15 +635,20 @@ describe('Upload', () => {
|
||||
|
||||
switch (callTimes) {
|
||||
case 1:
|
||||
expect(file.status).toBe(undefined);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
console.log('===>', file.status);
|
||||
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 0 }));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
case 4:
|
||||
expect(file).toEqual(expect.objectContaining({ status: 'uploading', percent: 100 }));
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
expect(file).toEqual(expect.objectContaining({ status: 'done', percent: 100 }));
|
||||
break;
|
||||
|
||||
|
@ -104,6 +104,7 @@ describe('Upload List', () => {
|
||||
expect(linkNode.prop('href')).toBe(file.url);
|
||||
expect(imgNode.prop('src')).toBe(file.thumbUrl);
|
||||
});
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/7269
|
||||
@ -145,6 +146,8 @@ describe('Upload List', () => {
|
||||
// console.log(wrapper.html());
|
||||
|
||||
expect(wrapper.find('.ant-upload-list-text-container').hostNodes().length).toBe(1);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should be uploading when upload a file', done => {
|
||||
@ -157,6 +160,7 @@ describe('Upload List', () => {
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
wrapper.unmount();
|
||||
done();
|
||||
}
|
||||
|
||||
@ -178,15 +182,10 @@ describe('Upload List', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('handle error', done => {
|
||||
let wrapper;
|
||||
const onChange = ({ file }) => {
|
||||
if (file.status !== 'uploading') {
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
done();
|
||||
}
|
||||
};
|
||||
wrapper = mount(
|
||||
it('handle error', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<Upload
|
||||
action="http://jsonplaceholder.typicode.com/posts/"
|
||||
onChange={onChange}
|
||||
@ -200,9 +199,38 @@ describe('Upload List', () => {
|
||||
files: [{ name: 'foo.png' }],
|
||||
},
|
||||
});
|
||||
|
||||
// Wait twice since `errorRequest` also use timeout for mock
|
||||
await sleep();
|
||||
await sleep();
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
file: expect.objectContaining({
|
||||
status: 'error',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
wrapper.update();
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
|
||||
// Error message
|
||||
jest.useFakeTimers();
|
||||
wrapper.find('.ant-upload-list-item').simulate('mouseEnter');
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
|
||||
expect(wrapper.find('Trigger').state().popupVisible).toBeTruthy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('does concat filelist when beforeUpload returns false', () => {
|
||||
it('does concat fileList when beforeUpload returns false', async () => {
|
||||
const handleChange = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const wrapper = mount(
|
||||
@ -222,11 +250,17 @@ describe('Upload List', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await sleep();
|
||||
|
||||
expect(ref.current.fileList.length).toBe(fileList.length + 1);
|
||||
expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('In the case of listType=picture, the error status does not show the download.', () => {
|
||||
global.testName =
|
||||
'In the case of listType=picture, the error status does not show the download.';
|
||||
const file = { status: 'error', uid: 'file' };
|
||||
const wrapper = mount(
|
||||
<Upload listType="picture" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
|
||||
@ -238,9 +272,13 @@ describe('Upload List', () => {
|
||||
wrapper.find('.ant-upload-list-item-error').simulate('mouseenter');
|
||||
|
||||
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('In the case of listType=picture-card, the error status does not show the download.', () => {
|
||||
global.testName =
|
||||
'In the case of listType=picture-card, the error status does not show the download.';
|
||||
const file = { status: 'error', uid: 'file' };
|
||||
const wrapper = mount(
|
||||
<Upload listType="picture-card" fileList={[file]} showUploadList={{ showDownloadIcon: true }}>
|
||||
@ -248,6 +286,8 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('In the case of listType=text, the error status does not show the download.', () => {
|
||||
@ -258,6 +298,8 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
expect(wrapper.find('div.ant-upload-list-item i.anticon-download').length).toBe(0);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support onPreview', () => {
|
||||
@ -271,6 +313,8 @@ describe('Upload List', () => {
|
||||
expect(handlePreview).toHaveBeenCalledWith(fileList[0]);
|
||||
wrapper.find('.anticon-eye').at(1).simulate('click');
|
||||
expect(handlePreview).toHaveBeenCalledWith(fileList[1]);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support onRemove', async () => {
|
||||
@ -292,6 +336,8 @@ describe('Upload List', () => {
|
||||
expect(handleRemove).toHaveBeenCalledWith(fileList[1]);
|
||||
await sleep();
|
||||
expect(handleChange.mock.calls.length).toBe(2);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support onDownload', async () => {
|
||||
@ -316,6 +362,8 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
wrapper.find('.anticon-download').at(0).simulate('click');
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support no onDownload', async () => {
|
||||
@ -338,6 +386,8 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
wrapper.find('.anticon-download').at(0).simulate('click');
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
describe('should generate thumbUrl from file', () => {
|
||||
@ -360,7 +410,7 @@ describe('Upload List', () => {
|
||||
delete newFile.thumbUrl;
|
||||
newFileList.push(newFile);
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
const wrapper = mount(
|
||||
<Upload
|
||||
ref={ref}
|
||||
listType="picture-card"
|
||||
@ -370,7 +420,7 @@ describe('Upload List', () => {
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
ref.current.forceUpdate();
|
||||
wrapper.update();
|
||||
await sleep();
|
||||
|
||||
expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined);
|
||||
@ -383,6 +433,8 @@ describe('Upload List', () => {
|
||||
} else {
|
||||
expect(offsetY === 0).toBeTruthy();
|
||||
}
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -462,6 +514,31 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('not crash when uploading not provides percent', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const wrapper = mount(
|
||||
<Upload
|
||||
listType="picture"
|
||||
defaultFileList={[
|
||||
{
|
||||
name: 'bamboo.png',
|
||||
status: 'uploading',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
jest.runAllTimers();
|
||||
wrapper.update();
|
||||
|
||||
wrapper.unmount();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should support showRemoveIcon and showPreviewIcon', () => {
|
||||
@ -493,6 +570,8 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support custom onClick in custom icon', async () => {
|
||||
@ -525,6 +604,8 @@ describe('Upload List', () => {
|
||||
expect(myClick).toHaveBeenCalled();
|
||||
await sleep();
|
||||
expect(handleChange.mock.calls.length).toBe(2);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should support removeIcon and downloadIcon', () => {
|
||||
@ -574,6 +655,9 @@ describe('Upload List', () => {
|
||||
</Upload>,
|
||||
);
|
||||
expect(wrapper2.render()).toMatchSnapshot();
|
||||
|
||||
wrapper.unmount();
|
||||
wrapper2.unmount();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/7762
|
||||
@ -626,19 +710,22 @@ describe('Upload List', () => {
|
||||
wrapper.find(Form).simulate('submit');
|
||||
await sleep();
|
||||
expect(formRef.getFieldError(['file'])).toEqual([]);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('return when prop onPreview not exists', () => {
|
||||
const ref = React.createRef();
|
||||
mount(<UploadList ref={ref} />);
|
||||
const wrapper = mount(<UploadList ref={ref} />);
|
||||
expect(ref.current.handlePreview()).toBe(undefined);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('return when prop onDownload not exists', () => {
|
||||
const file = new File([''], 'test.txt', { type: 'text/plain' });
|
||||
const items = [{ uid: 'upload-list-item', url: '' }];
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
const wrapper = mount(
|
||||
<UploadList
|
||||
ref={ref}
|
||||
items={items}
|
||||
@ -647,6 +734,7 @@ describe('Upload List', () => {
|
||||
/>,
|
||||
);
|
||||
expect(ref.current.handleDownload(file)).toBe(undefined);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('previewFile should work correctly', async () => {
|
||||
@ -655,7 +743,9 @@ describe('Upload List', () => {
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
|
||||
);
|
||||
return wrapper.props().previewFile(file);
|
||||
expect(wrapper.props().previewFile(file)).toBeTruthy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('downloadFile should work correctly', async () => {
|
||||
@ -670,7 +760,11 @@ describe('Upload List', () => {
|
||||
showUploadList={{ showDownloadIcon: true }}
|
||||
/>,
|
||||
);
|
||||
return wrapper.props().onDownload(file);
|
||||
|
||||
// Not throw
|
||||
wrapper.props().onDownload(file);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('extname should work correctly when url not exists', () => {
|
||||
@ -679,6 +773,7 @@ describe('Upload List', () => {
|
||||
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
|
||||
);
|
||||
expect(wrapper.find('.ant-upload-list-item-thumbnail').length).toBe(1);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('extname should work correctly when url exists', done => {
|
||||
@ -688,6 +783,7 @@ describe('Upload List', () => {
|
||||
listType="picture"
|
||||
onDownload={file => {
|
||||
expect(file.url).toBe('/example');
|
||||
wrapper.unmount();
|
||||
done();
|
||||
}}
|
||||
items={items}
|
||||
@ -705,6 +801,8 @@ describe('Upload List', () => {
|
||||
);
|
||||
expect(wrapper.find('.ant-upload-list-item-thumbnail').length).toBe(1);
|
||||
expect(wrapper.find('.ant-upload-list-item-thumbnail').text()).toBe('uploading');
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('onPreview should be called, when url exists', () => {
|
||||
@ -725,6 +823,8 @@ describe('Upload List', () => {
|
||||
wrapper.setProps({ items: [{ thumbUrl: 'thumbUrl', uid: 'upload-list-item' }] });
|
||||
wrapper.find('.ant-upload-list-item-name').simulate('click');
|
||||
expect(onPreview).toHaveBeenCalled();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('upload image file should be converted to the base64', async () => {
|
||||
@ -735,12 +835,14 @@ describe('Upload List', () => {
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
|
||||
);
|
||||
return wrapper
|
||||
await wrapper
|
||||
.props()
|
||||
.previewFile(mockFile)
|
||||
.then(dataUrl => {
|
||||
expect(dataUrl).toEqual('data:image/png;base64,');
|
||||
});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it("upload non image file shouldn't be converted to the base64", async () => {
|
||||
@ -751,12 +853,14 @@ describe('Upload List', () => {
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
|
||||
);
|
||||
return wrapper
|
||||
await wrapper
|
||||
.props()
|
||||
.previewFile(mockFile)
|
||||
.then(dataUrl => {
|
||||
expect(dataUrl).toBe('');
|
||||
});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
describe('customize previewFile support', () => {
|
||||
@ -775,12 +879,14 @@ describe('Upload List', () => {
|
||||
<button type="button">button</button>
|
||||
</Upload>,
|
||||
);
|
||||
ref.current.forceUpdate();
|
||||
wrapper.update();
|
||||
expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
|
||||
await sleep(100);
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.ant-upload-list-item-thumbnail img').prop('src')).toBe(mockThumbnail);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
}
|
||||
test('File', () => new File([], 'xxx.png'));
|
||||
@ -808,6 +914,8 @@ describe('Upload List', () => {
|
||||
);
|
||||
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
|
||||
expect(imgNode.length).toBe(2);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it('should render <img /> when custom imageUrl return true', () => {
|
||||
const isImageUrl = jest.fn(() => true);
|
||||
@ -819,6 +927,8 @@ describe('Upload List', () => {
|
||||
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
|
||||
expect(isImageUrl).toHaveBeenCalled();
|
||||
expect(imgNode.length).toBe(3);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it('should not render <img /> when custom imageUrl return false', () => {
|
||||
const isImageUrl = jest.fn(() => false);
|
||||
@ -830,6 +940,8 @@ describe('Upload List', () => {
|
||||
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
|
||||
expect(isImageUrl).toHaveBeenCalled();
|
||||
expect(imgNode.length).toBe(0);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@ -897,9 +1009,13 @@ describe('Upload List', () => {
|
||||
});
|
||||
const afterImgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
|
||||
expect(afterImgNode.length).toBeTruthy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('should not render <img /> when upload non-image file without thumbUrl in onChange', done => {
|
||||
global.testName =
|
||||
'should not render <img /> when upload non-image file without thumbUrl in onChange';
|
||||
let wrapper;
|
||||
const onChange = async ({ fileList: files }) => {
|
||||
wrapper.setProps({ fileList: files });
|
||||
@ -907,6 +1023,7 @@ describe('Upload List', () => {
|
||||
await sleep();
|
||||
const imgNode = wrapper.find('.ant-upload-list-item-thumbnail img');
|
||||
expect(imgNode.length).toBe(0);
|
||||
|
||||
done();
|
||||
};
|
||||
wrapper = mount(
|
||||
@ -927,14 +1044,17 @@ describe('Upload List', () => {
|
||||
});
|
||||
|
||||
it('[deprecated] should support transformFile', done => {
|
||||
let wrapper;
|
||||
|
||||
const handleTransformFile = jest.fn();
|
||||
const onChange = ({ file }) => {
|
||||
if (file.status === 'done') {
|
||||
expect(handleTransformFile).toHaveBeenCalled();
|
||||
wrapper.unmount();
|
||||
done();
|
||||
}
|
||||
};
|
||||
const wrapper = mount(
|
||||
wrapper = mount(
|
||||
<Upload
|
||||
action="http://jsonplaceholder.typicode.com/posts/"
|
||||
transformFile={handleTransformFile}
|
||||
@ -972,6 +1092,8 @@ describe('Upload List', () => {
|
||||
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(true);
|
||||
wrapper.setProps({ showUploadList: false });
|
||||
expect(wrapper.exists('.ant-upload-list button.trigger')).toBe(false);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/26536
|
||||
@ -998,7 +1120,7 @@ describe('Upload List', () => {
|
||||
);
|
||||
};
|
||||
|
||||
mount(<MyUpload />);
|
||||
const wrapper = mount(<MyUpload />);
|
||||
|
||||
// Mock async update in a frame
|
||||
const files = ['light', 'bamboo', 'little'];
|
||||
@ -1018,6 +1140,8 @@ describe('Upload List', () => {
|
||||
jest.runAllTimers();
|
||||
expect(uploadRef.current.fileList).toHaveLength(files.length);
|
||||
|
||||
wrapper.unmount();
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
@ -1035,5 +1159,27 @@ describe('Upload List', () => {
|
||||
};
|
||||
const wrapper = mount(<UploadList locale={{}} items={fileList} itemRender={itemRender} />);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('LIST_IGNORE should not add in list', async () => {
|
||||
const beforeUpload = jest.fn(() => Upload.LIST_IGNORE);
|
||||
const wrapper = mount(<Upload beforeUpload={beforeUpload} />);
|
||||
|
||||
act(() => {
|
||||
wrapper.find('input').simulate('change', {
|
||||
target: {
|
||||
files: [{ file: 'foo.png' }],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await sleep();
|
||||
|
||||
expect(beforeUpload).toHaveBeenCalled();
|
||||
expect(wrapper.find('UploadList').props().items).toHaveLength(0);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
@ -7,11 +7,11 @@ title:
|
||||
|
||||
## zh-CN
|
||||
|
||||
`beforeUpload` 返回 `false` 或 `Promise.reject` 时,只用于拦截上传行为,不会阻止文件进入上传列表([原因](https://github.com/ant-design/ant-design/issues/15561#issuecomment-475108235))。如果需要阻止列表展现,可以参照此例配合 `onChange` 进行实现。
|
||||
`beforeUpload` 返回 `false` 或 `Promise.reject` 时,只用于拦截上传行为,不会阻止文件进入上传列表([原因](https://github.com/ant-design/ant-design/issues/15561#issuecomment-475108235))。如果需要阻止列表展现,可以通过返回 `Upload.LIST_IGNORE` 实现。
|
||||
|
||||
## en-US
|
||||
|
||||
`beforeUpload` only prevent upload behavior when return false or reject promise, the prevented file would still show in file list. Here is the example you can keep prevented files out of list by using `onChange`.
|
||||
`beforeUpload` only prevent upload behavior when return false or reject promise, the prevented file would still show in file list. Here is the example you can keep prevented files out of list by return `UPLOAD.LIST_IGNORE`.
|
||||
|
||||
```jsx
|
||||
import React, { useState } from 'react';
|
||||
@ -19,19 +19,15 @@ import { Upload, Button, message } from 'antd';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
const Uploader = () => {
|
||||
const [fileList, updateFileList] = useState([]);
|
||||
const props = {
|
||||
fileList,
|
||||
beforeUpload: file => {
|
||||
if (file.type !== 'image/png') {
|
||||
message.error(`${file.name} is not a png file`);
|
||||
}
|
||||
return file.type === 'image/png';
|
||||
return file.type === 'image/png' ? true : Upload.LIST_IGNORE;
|
||||
},
|
||||
onChange: info => {
|
||||
console.log(info.fileList);
|
||||
// file.status is empty when beforeUpload return false
|
||||
updateFileList(info.fileList.filter(file => !!file.status));
|
||||
},
|
||||
};
|
||||
return (
|
||||
|
@ -37,7 +37,7 @@ export interface UploadFile<T = any> {
|
||||
export interface UploadChangeParam<T extends object = UploadFile> {
|
||||
// https://github.com/ant-design/ant-design/issues/14420
|
||||
file: T;
|
||||
fileList: Array<UploadFile>;
|
||||
fileList: UploadFile[];
|
||||
event?: { percent: number };
|
||||
}
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
import raf from 'rc-util/lib/raf';
|
||||
import useForceUpdate from '../_util/hooks/useForceUpdate';
|
||||
|
||||
// Note. Only for upload usage. Do not export to global util hooks
|
||||
export default function useFreshState<T>(
|
||||
defaultValue: T,
|
||||
propValue?: T,
|
||||
): [(displayValue?: boolean) => T, (newValue: T) => void] {
|
||||
const valueRef = useRef(defaultValue);
|
||||
const forceUpdate = useForceUpdate();
|
||||
const rafRef = useRef<number>();
|
||||
|
||||
// Set value
|
||||
function setValue(newValue: T) {
|
||||
valueRef.current = newValue;
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
raf.cancel(rafRef.current!);
|
||||
}
|
||||
|
||||
function rafSyncValue(newValue: T) {
|
||||
cleanUp();
|
||||
rafRef.current = raf(() => {
|
||||
setValue(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
// Get value
|
||||
function getValue(displayValue = false) {
|
||||
if (displayValue) {
|
||||
return propValue || valueRef.current;
|
||||
}
|
||||
|
||||
return valueRef.current;
|
||||
}
|
||||
|
||||
// Effect will always update in a next frame to avoid sync state overwrite current processing state
|
||||
useEffect(() => {
|
||||
if (propValue) {
|
||||
rafSyncValue(propValue);
|
||||
}
|
||||
}, [propValue]);
|
||||
|
||||
// Clean up
|
||||
useEffect(
|
||||
() => () => {
|
||||
cleanUp();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return [getValue, setValue];
|
||||
}
|
@ -4,11 +4,13 @@ export function T() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fix IE file.status problem
|
||||
// via coping a new Object
|
||||
export function fileToObject(file: RcFile): UploadFile {
|
||||
return {
|
||||
...file,
|
||||
/**
|
||||
* Wrap file with Proxy to provides more info. Will fallback to object if Proxy not support.
|
||||
*
|
||||
* Origin comment: Fix IE file.status problem via coping a new Object
|
||||
*/
|
||||
export function wrapFile(file: RcFile | UploadFile): UploadFile {
|
||||
const filledProps = {
|
||||
lastModified: file.lastModified,
|
||||
lastModifiedDate: file.lastModifiedDate,
|
||||
name: file.name,
|
||||
@ -17,6 +19,41 @@ export function fileToObject(file: RcFile): UploadFile {
|
||||
uid: file.uid,
|
||||
percent: 0,
|
||||
originFileObj: file,
|
||||
};
|
||||
|
||||
if (typeof Proxy !== 'undefined') {
|
||||
const data = new Map<string | symbol, any>(Object.entries(filledProps));
|
||||
|
||||
return new Proxy(file, {
|
||||
get(target, key) {
|
||||
if (data.has(key)) {
|
||||
return data.get(key);
|
||||
}
|
||||
return (target as any)[key];
|
||||
},
|
||||
set(_, key, value) {
|
||||
data.set(key, value);
|
||||
return true;
|
||||
},
|
||||
has(target, prop) {
|
||||
return data.has(prop) || prop in target;
|
||||
},
|
||||
ownKeys(target) {
|
||||
const keys = [...Object.keys(target), ...data.keys()];
|
||||
return [...new Set(keys)];
|
||||
},
|
||||
getOwnPropertyDescriptor() {
|
||||
return {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...file,
|
||||
...filledProps,
|
||||
} as UploadFile;
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,7 @@
|
||||
"rc-dropdown": "~3.2.0",
|
||||
"rc-field-form": "~1.19.0",
|
||||
"rc-image": "~5.2.3",
|
||||
"rc-input-number": "~6.2.0",
|
||||
"rc-input-number": "~7.0.0-alpha.4",
|
||||
"rc-mentions": "~1.5.0",
|
||||
"rc-menu": "~8.10.0",
|
||||
"rc-motion": "^2.4.0",
|
||||
@ -147,8 +147,8 @@
|
||||
"rc-tree": "~4.1.0",
|
||||
"rc-tree-select": "~4.3.0",
|
||||
"rc-trigger": "^5.2.1",
|
||||
"rc-upload": "~3.3.4",
|
||||
"rc-util": "^5.7.0",
|
||||
"rc-upload": "~4.0.0-alpha.6",
|
||||
"rc-util": "^5.8.1",
|
||||
"scroll-into-view-if-needed": "^2.2.25",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
|
2
typings/custom-typings.d.ts
vendored
2
typings/custom-typings.d.ts
vendored
@ -34,8 +34,6 @@ declare module 'rc-tabs*';
|
||||
|
||||
declare module 'rc-tree/lib/util';
|
||||
|
||||
declare module 'rc-input-number';
|
||||
|
||||
declare module 'rc-collapse';
|
||||
|
||||
declare module 'rc-dialog';
|
||||
|
Loading…
Reference in New Issue
Block a user