暂存表单修改

This commit is contained in:
zhangxulong 2023-04-24 18:19:32 +08:00
parent 317c1ad14d
commit 48a3329bd6
103 changed files with 2586 additions and 443 deletions

View File

@ -678,7 +678,7 @@ order: 31
}
],
searchable: true,
multiple: false,
multiple: true,
joinValues: true,
clearable: true
}

View File

@ -15,6 +15,7 @@ order: 68
```schema: scope="body"
{
"type": "tabs",
"swipeable": true,
"tabs": [
{
"title": "Tab 1",
@ -23,6 +24,54 @@ order: 68
{
"title": "Tab 2",
"tab": "Content 2"
},
{
"title": "Tab 3",
"tab": "Content 2"
},
{
"title": "Tab 4",
"tab": "Content 2"
},
{
"title": "Tab 5",
"tab": "Content 2"
},
{
"title": "Tab 6",
"tab": "Content 2"
},
{
"title": "Tab 7",
"tab": "Content 2"
},
{
"title": "Tab 8",
"tab": "Content 2"
},
{
"title": "Tab 9",
"tab": "Content 2"
},
{
"title": "Tab 10",
"tab": "Content 2"
},
{
"title": "Tab 11",
"tab": "Content 2"
},
{
"title": "Tab 12",
"tab": "Content 2"
},
{
"title": "Tab 13",
"tab": "Content 2"
},
{
"title": "Tab 14",
"tab": "Content 2"
}
]
}
@ -789,6 +838,7 @@ order: 68
| sidePosition | `left` / `right` | `left` | `sidebar` 模式下,标签栏位置 |
| collapseOnExceed | `number` | | 当 tabs 超出多少个时开始折叠 |
| collapseBtnLabel | `string` | `more` | 用来设置折叠按钮的文字 |
| swipeable | `boolean` | false | 是否开启手势滑动切换(移动端生效) |
## 事件表

View File

@ -1,24 +1,18 @@
export default {
type: 'page',
title: '表单页面',
body: [
{
type: 'form',
mode: 'horizontal',
api: '/api/mock2/form/saveForm',
body: [
{
label: 'Name',
type: 'input-text',
name: 'name'
},
{
label: 'Email',
type: 'input-email',
placeholder: '请输入邮箱地址',
name: 'email'
}
]
}
]
body: {
type: 'form',
body: [
{
label: '多选',
type: 'select',
name: 'select2',
searchable: true,
checkAll: true,
multiple: true,
clearable: true,
source: '/api/mock2/form/getOptions'
}
]
}
};

View File

@ -1676,6 +1676,8 @@
--InputRange-track-onActive-bg: var(--colors-brand-5);
--InputRange-handle-height: var(--sizes-size-9);
--InputRange-handle-width: var(--sizes-size-9);
--InputRange-handle-mobile-height: var(--sizes-base-11);
--InputRange-handle-mobile-width: var(--sizes-base-11);
--InputRange-handle-bg: var(--colors-neutral-fill-11);
--InputRange-handle-top-border-color: var(--colors-brand-5);
--InputRange-handle-top-border-width: 0.0625rem;
@ -1730,6 +1732,7 @@
);
--InputRange-label-position-bottom: calc(100% + 8px);
--InputRange-input-width: var(--sizes-base-40);
--InputRange-input-mobile-width: var(--sizes-base-20);
--InputRange-input-marginTop: var(--sizes-size-0);
--InputRange-input-marginBottom: var(--sizes-size-0);
--InputRange-input-marginLeft: var(--sizes-size-5);
@ -2821,6 +2824,7 @@
--select-tree-active-bg-color: var(--colors-brand-10);
--Form-select-bg: var(--select-base-default-bg-color);
--Form-select-mobile-icon-check-color: var(--colors-brand-5);
--Form-select-height: var(--Form-select-outer-top);
--Form-select-borderColor: var(--select-base-default-top-border-color)
var(--select-base-default-right-border-color)

View File

@ -527,6 +527,10 @@ $Table-strip-bg: transparent;
--ListMenu-item-bg: var(--colors-neutral-fill-11);
--ListMenu-item-color: var(--colors-neutral-text-2);
--ListMenu-item-height: var(--sizes-base-15);
--ListMenu-item-mobile-margin: #{px2rem(5px)};
--ListMenu-item-mobile-width: #{px2rem(90px)};
--ListMenu-item-mobile-bg: #f5f5f5;
--ListMenu-item-mobile-active-bg: #e7f1ff;
--Log-bg: #222;
--Log-padding: var(--gap-sm) 0;
@ -768,6 +772,11 @@ $Table-strip-bg: transparent;
--UserSelect--border-color: #f7f7f9;
--UserSelect--content-bg: #f5f7f8;
--UserSelect--bread-color: #5e626a;
--Cascader-border-color: #f7f7f9;
--Cascader-border-active-bg-color: #f7f7f9;
--Cascader-option-disable-color: #b8babf;
// tag
--Tag-content-fontSize: var(--fontSizeSm);
--Tag-height: #{px2rem(24px)};

View File

@ -123,8 +123,15 @@
border-width: 0;
&--time {
height: fit-content;
height: px2rem(360px);
max-height: 90vh;
.#{$ns}PopUp-content {
overflow: hidden;
}
}
&--years {
height: px2rem(360px);
}
}
@ -246,6 +253,7 @@
align-items: center;
justify-content: center;
border-radius: px2rem(4px);
padding: 0 px2rem(10px);
}
}
@ -282,11 +290,11 @@
line-height: px2rem(48px);
}
.#{$ns}DateRangePicker-rangers {
position: absolute;
padding-left: revert;
white-space: nowrap;
.#{$ns}DateRangePicker-ranger {
margin: 0 px2rem(25px);
}
line-height: inherit;
display: flex;
justify-content: space-between;
}
.#{$ns}DatePicker-shortcuts {
width: auto;

View File

@ -15,6 +15,9 @@
height: px2rem(260px);
overflow-y: auto;
display: inline-block;
padding-left: px2rem(10px);
border: 1px solid var(--Cascader-border-color);
&::-webkit-scrollbar {
display: none;
}
@ -52,13 +55,18 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: px2rem(6px) 0;
padding: px2rem(6px) 0 px2rem(6px) px2rem(10px);
font-size: var(--fontSizeMd);
line-height: var(--Cascader-option-lineHeight);
cursor: pointer;
position: relative;
&.is-active {
background-color: var(--Cascader-border-active-bg-color);
}
&.selected {
span {
.#{$ns}Cascader-option--text {
color: var(--primary);
}
}
@ -68,11 +76,26 @@
}
}
&--text {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
&.disabled {
color: var(--Cascader-option-disable-color);
}
}
&-arrow {
flex: 1;
text-align: right;
padding-right: px2rem(6px);
> svg {
transform: scale(0.8);
}
}
&-selectedNum {
min-width: px2rem(16px);
height: px2rem(16px);

View File

@ -24,7 +24,7 @@
}
}
.#{$ns}Collapse:not(:last-child) {
.#{$ns}Collapse:not(:last-child):not(.is-mobile) {
border-bottom: none;
}
@ -40,4 +40,14 @@
}
}
}
&.is-mobile {
&.icon-position-right {
.#{$ns}Collapse-header {
.#{$ns}Collapse-arrow-wrap {
margin-right: px2rem(-18px);
}
}
}
}
}

View File

@ -42,6 +42,17 @@
background: var(--Collapse-header-onHover-bg);
color: var(--collapse-default-header-hover-color);
}
&.is-mobile {
background: none;
border-radius: 0 !important;
position: relative;
padding-left: 0;
&:hover {
background: none;
}
}
}
.Collapse-arrow {
@ -76,7 +87,7 @@
// display: inline-block;
// }
&.is-active > &-header > &-arrow-wrap > &-arrow {
&.is-active > .#{$ns}Collapse-header > .#{$ns}Collapse-arrow-wrap > &-arrow {
transform: rotate(var(--collapse-icon-rotate));
transform-origin: 50% 50%;
}
@ -133,6 +144,28 @@
line-height: var(--collapse-default-content-lineHeight);
background: var(--collapse-default-bg-color);
}
&.is-mobile {
border-top: none;
border-left: none;
border-right: none;
&:last-child {
border-bottom: none;
}
.#{$ns}Collapse-icon-tranform,
.#{$ns}Collapse-arrow-wrap {
margin-right: px2rem(-18px);
float: right;
margin-top: px2rem(2px);
}
.#{$ns}Collapse-content {
padding-left: 0;
padding-right: 0;
}
}
}
//FieldSet Form + Collapse

View File

@ -64,9 +64,15 @@
height: #{px2rem(250px)};
margin-top: #{px2rem(10px)};
& > div {
&:not(.is-mobile) > div {
width: 0;
}
&.is-mobile {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
}
}
&-settings.only-variable {
@ -389,6 +395,16 @@
justify-content: space-between;
align-items: center;
&-popup {
height: 80vh;
&-inner {
width: 100%;
box-sizing: border-box;
padding: 0 px2rem(16px);
}
}
&-input {
flex: 1;
margin-right: #{px2rem(10px)};

View File

@ -72,4 +72,28 @@
&.is-active &-caret {
transform: rotate(180deg);
}
&.is-mobile {
border: none;
border-radius: 0;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
&.is-error,
.is-error > & {
border-bottom-color: var(--Form-input-onError-borderColor);
}
&.is-focused {
border-bottom-color: var(--Form-input-onFocused-borderColor);
}
&.is-error.is-focused {
border-bottom-color: var(--Form-input-onError-borderColor);
}
&.is-disabled {
border-bottom-color: var(--Form-input-onDisabled-borderColor);
}
}
}

View File

@ -24,6 +24,15 @@
color: var(--Form-input-color);
}
&.is-mobile {
& > span {
border-radius: 0;
border: none;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
}
}
}
&-value {
@ -121,4 +130,12 @@
}
}
}
&.is-mobile {
.#{$ns}Number {
border-radius: 0;
border-bottom: var(--Number-borderWidth) solid
var(--Form-input-borderColor);
}
}
}

View File

@ -67,6 +67,42 @@
)
var(--Form-select-paddingX);
}
&.is-mobile {
display: flex;
flex-wrap: wrap;
.#{$ns}ListMenu-item {
width: calc((100vw - var(--ListMenu-item-mobile-margin) * 7) / 3);
display: inline-flex;
background: var(--ListMenu-item-mobile-bg);
margin: var(--ListMenu-item-mobile-margin);
&.is-active {
background: var(--ListMenu-item-mobile-active-bg);
}
.#{$ns}ListMenu-itemLabel {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.#{$ns}ListMenu-add-wrap {
display: flex;
align-items: center;
justify-content: space-around;
margin: px2rem(20px) 0;
text-align: center;
input {
&::-webkit-input-placeholder {
text-align: center;
}
}
}
.#{$ns}PopOver > .#{$ns}ListMenu {

View File

@ -220,7 +220,7 @@
calc(var(--Panel-marginBottom) / 2);
.#{$ns}Panel-body {
padding: 0 var(--gap-md) var(--gap-md);
// padding: 0 var(--gap-md) var(--gap-md);
}
> .#{$ns}Panel-heading {

View File

@ -132,6 +132,12 @@
background: var(--SearchBox-enhonce-disabled-color);
color: var(--SearchBox-enhonce-disabled-search-color);
}
&.is-mobile {
border-radius: 0;
border: none;
border-bottom: var(--borderWidth) solid var(--borderColor);
}
}
.#{$ns}SearchBox-history {

View File

@ -657,6 +657,21 @@
}
}
&.is-mobile {
.#{$ns}Table-table > tbody > tr {
&:hover,
&.is-hovered {
background: var(--Table-bg);
border-color: var(--Table-borderColor);
color: var(--Table-color);
& + tr {
border-color: var(--Table-borderColor);
}
}
}
}
&Cell-sortBtn,
&Cell-searchBtn,
&Cell-filterBtn {

View File

@ -112,6 +112,18 @@
height: 100%;
overflow-x: hidden;
&.is-mobile {
overflow-x: auto;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
&::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
}
}
.#{$ns}Tabs-links-drag {
position: absolute;
height: 100%;

View File

@ -11,7 +11,7 @@
border-radius: var(--borderRadius);
&-popup {
height: px2rem(400px);
height: px2rem(460px);
}
&:not(.is-disabled) {
@ -122,3 +122,26 @@
border-radius: var(--borderRadius) !important;
box-shadow: var(--ColorPicker-boxShadow) !important;
}
.#{$ns}ColorPicker-popup {
.sketch-picker {
width: 80% !important;
box-shadow: none !important;
input {
&:focus {
outline: none;
}
}
.flexbox-fix {
&:last-child {
> div {
width: 18px !important;
height: 18px !important;
margin: 0px 16px 10px 0px !important;
}
}
}
}
}

View File

@ -320,6 +320,34 @@
background: rgba(0, 0, 0, 0.1);
}
}
&.is-mobile {
.#{$ns}Form-item {
padding: px2rem(10px) 0 0 0;
.#{$ns}Form-rowInner {
flex: 1;
}
}
.#{$ns}Combo-delBtn {
height: px2rem(20px);
}
}
&.is-mobile:not(.#{$ns}Combo--ver) {
.#{$ns}TextareaControl > textarea,
.#{$ns}Form-control > .#{$ns}TextControl-input,
.#{$ns}TextControl.is-focused > .#{$ns}TextControl-input {
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
}
}
&.is-mobile:is(.#{$ns}Combo--hor) {
.#{$ns}Form-control {
padding-top: px2rem(10px);
}
}
}
.#{$ns}ComboTabs > .#{$ns}Tabs-links {

View File

@ -185,10 +185,22 @@
width: auto;
// padding-bottom: var(--gap-sm);
padding: 0;
&.is-mobile {
flex: 1;
.#{$ns}DateRangePicker-end {
margin-top: 0;
}
}
}
.#{$ns}DateRangePicker-picker-wrap {
display: flex;
&.is-vertical {
flex-direction: column;
}
}
.#{$ns}DateRangePicker-start,
@ -274,6 +286,10 @@
border-color: var(--DatePicker-borderColor);
background: var(--DatePicker-bg);
border-radius: var(--DatePicker-borderRadius);
&.is-mobile {
display: block;
}
}
// 移动端输入框样式

View File

@ -456,7 +456,7 @@
color: var(--inputTime-active-color);
background: var(--inputTime-active-bg-color);
}
&:hover {
&:not(.is-mobile):hover {
color: var(--inputTime-hover-color);
background: var(--inputTime-hover-bg-color);
}
@ -466,6 +466,7 @@
.#{$ns}TimeContentWrapper {
display: flex;
justify-content: center;
}
.#{$ns}TimeRangeHeaderWrapper {
@ -473,6 +474,7 @@
padding-top: 10px;
text-align: center;
border-bottom: 1px solid var(--Calendar-input-borderColor);
margin: 0 px2rem(4px);
}
.#{$ns}TimeFooterWrapper {

View File

@ -25,6 +25,10 @@
border-left: 0 !important;
}
> legend.#{$ns}Collapse-header.is-mobile {
position: absolute;
}
.collapse {
position: relative;
}
@ -188,4 +192,13 @@ fieldset.#{$ns}Collapse {
top: calc(var(--fieldSet-size-lg-fontSize) / 2);
}
}
.#{$ns}Collapse-header.is-mobile {
padding-left: var(--gap-xs) !important;
padding-right: px2rem(18px);
&:hover {
background: var(--white);
}
}
}

View File

@ -489,15 +489,21 @@
/* 移动端样式调整 */
@include media-breakpoint-down(sm) {
.#{$ns}Form {
// &::before {
// position: absolute;
// box-sizing: border-box;
// content: ' ';
// pointer-events: none;
// right: 0;
// top: 0;
// left: 0;
// border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
// }
}
.#{$ns}Combo-form {
&::before {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
top: 0;
left: 0;
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
border-bottom: none;
}
}
@ -612,6 +618,7 @@
flex: 1;
flex-wrap: wrap;
font-size: var(--fontSizeLg);
overflow-x: auto;
&.is-disabled > .#{$ns}TextControl-input {
background: transparent;
@ -686,6 +693,7 @@
border: none;
padding: 0 var(--Form-input-paddingX) 0 0;
box-shadow: none;
border-radius: 0;
&:hover,
&:focus,
@ -716,9 +724,9 @@
display: none;
}
.#{$ns}Divider {
display: none;
}
// .#{$ns}Divider {
// display: none;
// }
.#{$ns}Tabs-pane {
padding: 0;
@ -727,4 +735,25 @@
.#{$ns}Form-item .#{$ns}Form-groupColumn > .#{$ns}Form-item {
padding-bottom: var(--Form-input-paddingX);
}
.#{$ns}Form-groupColumn {
position: relative;
&::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
bottom: 0;
left: 0;
border-bottom: var(--Form-input-borderWidth) solid var(--borderColor);
}
}
.#{$ns}Form-groupColumn:nth-last-of-type(1) {
&::after {
display: none;
}
}
}

View File

@ -151,6 +151,12 @@
.#{$ns}Form-static {
margin-right: var(--gap-xs);
}
&.is-mobile {
.#{$ns}Form-control {
display: inline-flex;
}
}
}
.#{$ns}InputGroup:not(.is-inline) {

View File

@ -39,6 +39,10 @@
}
}
&.is-mobile {
border: none;
}
&-placeholder {
color: var(--colors-neutral-text-6);
user-select: none;
@ -72,6 +76,27 @@
top: 0;
}
}
&-popup {
height: px2rem(460px);
&-inner {
flex: 1;
padding: 0 px2rem(16px);
.#{$ns}MapPicker-search {
padding-top: 0;
margin-bottom: px2rem(16px);
.#{$ns}TextControl-input {
border-radius: 0;
border-top: none;
border-right: none;
border-left: none;
}
}
}
}
}
.#{$ns}LocationControl {

View File

@ -1,4 +1,6 @@
.#{$ns}MatrixControl {
max-width: 100%;
&-error {
margin-bottom: 0;
}

View File

@ -71,6 +71,14 @@
box-shadow: var(--inputNumber-base-active-shadow);
}
&.is-mobile {
border: none;
&-focused {
border: none;
}
}
&-disabled {
border-width: var(--inputNumber-base-disabled-top-border-width)
var(--inputNumber-base-disabled-right-border-width)

View File

@ -206,3 +206,15 @@
min-width: px2rem(150px);
}
}
.#{$ns}PickerControl.is-mobile {
width: 100%;
.#{$ns}Form-item {
padding: 0;
}
.#{$ns}Picker-input {
border: none;
}
}

View File

@ -33,6 +33,24 @@
}
}
&.is-mobile {
padding: 0;
.#{$ns}InputRange-input {
width: var(--InputRange-input-mobile-width);
// margin-left: 0;
.#{$ns}Number-handler-wrap {
display: none !important;
}
input {
padding: 0 !important;
text-align: center;
}
}
}
&-clear {
display: flex;
align-items: center;
@ -97,6 +115,11 @@
width: var(--InputRange-handle-width);
height: var(--InputRange-handle-height);
&.is-mobile {
width: var(--InputRange-handle-mobile-width);
height: var(--InputRange-handle-mobile-height);
}
&-icon,
&-drage {
width: 100%;
@ -304,4 +327,16 @@
}
}
}
&.is-mobile {
.#{$ns}InputRange-marks {
div {
top: px2rem(4px);
&:nth-child(2n) {
top: px2rem(-34px);
}
}
}
}
}

View File

@ -436,6 +436,38 @@
// display: inline-block;
max-width: 100%;
}
&.is-mobile {
position: relative;
& > a {
margin-right: calc(
20px + var(--select-base-default-option-paddingRight)
);
}
.#{$ns}Select-option-item-check {
flex: 1;
text-align: left;
border-bottom: px2rem(1px) solid var(--borderColor);
}
.#{$ns}Select-option-mcheck {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: var(--select-base-default-option-paddingRight);
flex: none;
width: px2rem(16px);
color: var(--Form-select-mobile-icon-check-color);
}
&:last-child {
.#{$ns}Select-option-item-check {
border-bottom: none;
}
}
}
}
&-noResult {

View File

@ -237,6 +237,16 @@
color: var(--transfer-search-placeholder-font-color);
}
}
&.is-mobile {
.#{$ns}InputBox {
border: none;
border-bottom: var(--transfer-search-bottom-border-width)
var(--transfer-search-bottom-border-style)
var(--transfer-search-bottom-border-color);
border-radius: 0;
}
}
}
&-mid {
@ -363,6 +373,25 @@
height: auto;
}
}
.#{$ns}Transfer-result {
.#{$ns}Transfer-title {
height: px2rem(40px);
line-height: px2rem(40px);
}
&.is-mobile {
.#{$ns}Transfer-search {
.#{$ns}InputBox {
border: none;
border-bottom: var(--transfer-search-bottom-border-width)
var(--transfer-search-bottom-border-style)
var(--transfer-search-bottom-border-color);
border-radius: 0;
}
}
}
}
}
.#{$ns}TabsTransfer {
@ -463,6 +492,16 @@
}
}
}
.#{$ns}TabsTransfer-search {
&.is-mobile {
.#{$ns}InputBox {
border: none;
border-bottom: 1px solid var(--TabsTransfer-border-color);
border-radius: 0;
}
}
}
}
}
}
@ -528,7 +567,9 @@
&.is-mobile {
width: 100%;
min-width: auto;
}
& > .#{$ns}Transfer-selection {
flex-grow: 1;
max-height: var(--Transfer-selection-maxHeight);

View File

@ -147,9 +147,10 @@
&:hover {
.#{$ns}Tree {
&-itemLabel-item {
&-itemLabel-item:not(.is-mobile) {
background-color: var(--Tree-item-onHover-bg-pure);
}
&-item-icons {
visibility: visible;
}
@ -255,6 +256,15 @@
background: var(--Form-input-onFocused-bg);
}
}
&.is-mobile {
> input {
border-radius: 0;
border: none;
border-bottom: var(--Form-input-borderWidth) solid
var(--Form-input-borderColor);
}
}
}
&-addTopBtn {
@ -398,3 +408,19 @@
}
}
}
.#{$ns}PopUp {
.#{$ns}Tree {
flex: 1;
.#{$ns}Tree-itemLabel {
&:hover {
.#{$ns}Tree {
&-itemLabel-item {
background-color: none !important;
}
}
}
}
}
}

View File

@ -15,6 +15,14 @@
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
border: 1px solid #f9f9f9;
&.is-mobile-year {
width: px2rem(184px);
}
&.is-mobile-embed {
width: px2rem(240px);
}
}
.rdtPickerNotDays {

View File

@ -165,15 +165,17 @@ export class BaiduMapPicker extends React.Component<
if (poiLength) {
for (let i = 0; i < poiLength; i++) {
const poi = result.getPoi(i);
sugs.push(
[
poi.province,
poi.city,
poi.district,
poi.street,
poi.business
].join(' ')
);
if (poi) {
sugs.push(
[
poi.province,
poi.city,
poi.district,
poi.street,
poi.business
].join(' ')
);
}
}
this.setState({
sugs
@ -288,21 +290,23 @@ export class BaiduMapPicker extends React.Component<
if (this.props.coordinatesType == 'gcj02') {
this.covertPoint(point, COORDINATES_BD09, COORDINATES_GCJ02).then(
(convertedPoint: any) => {
(typeof this.props?.onChange === 'function') && this.props.onChange({
address: loc.address.trim() || loc.title,
lat: convertedPoint.lat,
lng: convertedPoint.lng,
city: loc.city
});
typeof this.props?.onChange === 'function' &&
this.props.onChange({
address: loc.address.trim() || loc.title,
lat: convertedPoint.lat,
lng: convertedPoint.lng,
city: loc.city
});
}
);
} else {
(typeof this.props?.onChange === 'function') && this.props?.onChange({
address: loc.address.trim() || loc.title,
lat: loc.lat,
lng: loc.lng,
city: loc.city
});
typeof this.props?.onChange === 'function' &&
this.props?.onChange({
address: loc.address.trim() || loc.title,
lat: loc.lat,
lng: loc.lng,
city: loc.city
});
}
}

View File

@ -11,6 +11,9 @@ import {themeable, ThemeProps} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import {autobind} from 'amis-core';
import Button from './Button';
import {ShortCuts, ShortCutDateRange} from './DatePicker';
import {advancedRanges, availableRanges} from './DateRangePicker';
import {noop} from 'amis-core';
export interface CalendarMobileProps extends ThemeProps, LocaleProps {
className?: string;
@ -49,6 +52,7 @@ export interface CalendarMobileProps extends ThemeProps, LocaleProps {
};
};
defaultDate?: moment.Moment;
ranges?: string | Array<ShortCuts>;
}
export interface CalendarMobileState {
@ -281,6 +285,97 @@ export class CalendarMobile extends React.Component<
return dow;
}
@autobind
renderRanges(ranges: string | Array<ShortCuts> | undefined) {
if (!ranges) {
return null;
}
const {
classPrefix: ns,
embed,
minDate,
maxDate,
confirm,
onChange
} = this.props;
let rangeArr: Array<string | ShortCuts>;
if (typeof ranges === 'string') {
rangeArr = ranges.split(',');
} else {
rangeArr = ranges;
}
const __ = this.props.translate;
return (
<ul className={`${ns}DateRangePicker-rangers`}>
{rangeArr.map(item => {
if (!item) {
return null;
}
let range: any = {};
if (typeof item === 'string') {
if (availableRanges[item]) {
range = availableRanges[item];
range.key = item;
} else {
// 通过正则尝试匹配
for (let i = 0, len = advancedRanges.length; i < len; i++) {
let value = advancedRanges[i];
const m = value.regexp.exec(item);
if (m) {
range = value.resolve.apply(item, [__, ...m]);
range.key = item;
}
}
}
} else if (
(item as ShortCutDateRange).startDate &&
(item as ShortCutDateRange).endDate
) {
range = {
...item,
startDate: () => (item as ShortCutDateRange).startDate,
endDate: () => (item as ShortCutDateRange).endDate
};
}
if (Object.keys(range).length) {
return (
<li
className={`${ns}DateRangePicker-ranger`}
onClick={() => {
const now = moment();
const startDate =
minDate && minDate.isValid()
? moment.max(range.startDate(now.clone()), minDate)
: range.startDate(now.clone());
const endDate =
maxDate && maxDate.isValid()
? moment.min(maxDate, range.endDate(now.clone()))
: range.endDate(now.clone());
this.setState({
startDate,
endDate
});
!embed && onChange && onChange({startDate, endDate}, noop);
// 内嵌模式
embed &&
onChange &&
onChange({startDate, endDate}, () => confirm && confirm());
}}
key={range.key || range.label}
>
<a>{__(range.label)}</a>
</li>
);
} else {
return null;
}
})}
</ul>
);
}
@autobind
handleCalendarClick(isDisabled: boolean) {
if (isDisabled) {
@ -697,7 +792,8 @@ export class CalendarMobile extends React.Component<
footerExtra,
timeFormat,
showViewMode,
isDatePicker
isDatePicker,
ranges
} = this.props;
const __ = this.props.translate;
@ -755,7 +851,7 @@ export class CalendarMobile extends React.Component<
{timeFormat && startDate && this.renderMobileTimePicker()}
<div className={cx('CalendarMobile-footer-toolbar')}>
<div className={cx('CalendarMobile-footer-ranges')}>
{footerExtra}
{this.renderRanges(ranges)}
</div>
{confirm && !embed && (
<Button

View File

@ -12,12 +12,15 @@ import compact from 'lodash/compact';
import find from 'lodash/find';
import uniqBy from 'lodash/uniqBy';
import Button from './Button';
import Checkbox from './Checkbox';
import {flattenTree, findTree, getTreeDepth} from 'amis-core';
import type {OptionsControlProps} from 'amis-core';
import {Icon} from './icons';
export type CascaderOption = {
text?: string;
value?: string | number;
valueField?: string;
color?: string;
disabled?: boolean;
children?: Options;
@ -50,12 +53,12 @@ export type CascaderTab = {
export interface CascaderState {
selectedOptions: Options;
activeTab: number;
tabs: Array<{
options: Options;
}>;
// 用于在只选择子节点模式的时候禁用按钮
disableConfirm: boolean;
activePaths: CascaderOption[];
}
export class Cascader extends React.Component<CascaderProps, CascaderState> {
@ -68,8 +71,8 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
constructor(props: CascaderProps) {
super(props);
this.state = {
activePaths: [],
selectedOptions: this.props.selectedOptions || [],
activeTab: 0,
tabs: [
{
options: this.props.options.slice() || []
@ -109,19 +112,9 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
option.children.forEach((option: Option) => (option.disabled = true));
}
}
return multiple && !onlyLeaf
? {
options: [
{
...option,
isCheckAll: true
},
...(option.children ? option.children : [])
]
}
: {
options: option.children ? option.children : []
};
return {
options: option.children ? option.children : []
};
});
this.setState({
selectedOptions,
@ -129,15 +122,6 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
});
}
@autobind
handleTabSelect(index: number) {
const tabs = this.state.tabs.slice(0, index + 1);
this.setState({
activeTab: index,
tabs
});
}
@autobind
getOptionParent(option: Option) {
const {options, valueField = 'value'} = this.props;
@ -202,6 +186,23 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
return loop(selectedOptions);
}
// 判断配置onlyChildren属性时节点选中情况
@autobind
getOnlyChildrenSelect(option: Option, selectedOptions?: Option[]) {
const {onlyChildren} = this.props;
selectedOptions = selectedOptions || this.state.selectedOptions;
return (
onlyChildren &&
option.children
?.filter(option => !option.children?.length)
.every(
option =>
!option.children?.length && selectedOptions?.includes(option)
)
);
}
@autobind
getSelectedChildNum(option: Option): number {
let count = 0;
@ -267,9 +268,10 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
let index = selectedOptions.findIndex(
(item: Option) => item[valueField] === option[valueField]
);
let isSelect = this.getOnlyChildrenSelect(option, selectedOptions);
if (index !== -1) {
selectedOptions.splice(index, 1);
} else {
} else if (!isSelect) {
if (!(onlyChildren && option.children?.length)) {
selectedOptions.push(option);
}
@ -279,7 +281,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
return;
}
option.children.forEach((item: Option) => {
if (index !== -1) {
if (index !== -1 || isSelect) {
// 删除选中节点及其子节点
selectedOptions = selectedOptions.filter(
(sItem: Option) => sItem[valueField] !== item[valueField]
@ -313,22 +315,27 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
valueField = 'value',
cascade,
onlyLeaf,
onlyChildren
onlyChildren,
withChildren
} = this.props;
let tabs = this.state.tabs.slice();
let {activeTab} = this.state;
let selectedOptions = this.state.selectedOptions;
const isDisable = option.disabled;
if (!isDisable) {
if (multiple) {
// 父子级分离
if (cascade) {
if (
option.isCheckAll ||
!option.children ||
!option.children.length
) {
let index = selectedOptions.findIndex(
(item: Option) => item[valueField] === option[valueField]
);
if (index !== -1) {
selectedOptions.splice(index, 1);
} else {
selectedOptions.push(option);
}
} else {
if (withChildren || onlyChildren) {
selectedOptions = this.dealChildrenSelect(option, selectedOptions);
} else {
let index = selectedOptions.findIndex(
(item: Option) => item[valueField] === option[valueField]
);
@ -338,29 +345,58 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
selectedOptions.push(option);
}
}
} else {
if (
option.isCheckAll ||
!option.children ||
!option.children.length
) {
selectedOptions = this.dealChildrenSelect(option, selectedOptions);
if (!onlyChildren) {
selectedOptions = this.dealParentSelect(option, selectedOptions);
}
}
selectedOptions = this.dealParentSelect(option, selectedOptions);
}
} else {
// 单选
selectedOptions = [option];
if (onlyLeaf) {
if (!option.children?.length) {
selectedOptions = [option];
}
} else {
selectedOptions = [option];
}
}
}
this.dealOptionDisable(selectedOptions);
let disableConfirm = false;
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
disableConfirm = true;
}
this.setState({
selectedOptions,
disableConfirm
});
}
@autobind
handleExpand(option: Option, tabIndex: number) {
const activePaths = this.state.activePaths.slice();
if (option.children?.length) {
activePaths[tabIndex] = option;
} else {
activePaths.splice(tabIndex);
}
let tabs = this.state.tabs.slice();
if (tabs.length > tabIndex + 1) {
tabs = tabs.slice(0, tabIndex + 1);
}
if (option?.children) {
const nextTab = {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
}
requestAnimationFrame(() => {
const tabWidth = this.tabRef.current?.offsetWidth || 1;
const parentTree = this.getParentTree(option, [option]);
@ -370,60 +406,9 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
}
});
if (option?.children && !option.isCheckAll) {
const nextTab =
multiple && !onlyLeaf
? {
options: [
{
...option,
isCheckAll: true
},
...option.children
]
}
: {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
activeTab += 1;
}
let disableConfirm = false;
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
disableConfirm = true;
}
this.setState({
tabs,
activeTab,
selectedOptions,
disableConfirm
});
}
@autobind
onNextClick(option: CascaderOption, tabIndex: number) {
let {activeTab} = this.state;
let tabs = this.state.tabs.slice();
if (option.c)
if (option?.children) {
const nextTab = {
options: option.children
};
if (tabs[tabIndex + 1]) {
tabs[tabIndex + 1] = nextTab;
} else {
tabs.push(nextTab);
}
activeTab += 1;
}
this.setState({
tabs,
activeTab
activePaths,
tabs
});
}
@ -462,15 +447,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
@autobind
confirm() {
const {
onChange,
joinValues,
delimiter,
extractValue,
valueField,
onClose,
onlyLeaf
} = this.props;
const {onChange, onClose, onlyLeaf} = this.props;
const selectedOptions = this.getSelectedOptions();
if (onlyLeaf && selectedOptions.length && selectedOptions[0].children) {
return;
@ -490,15 +467,15 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
@autobind
renderOption(option: CascaderOption, tabIndex: number) {
const {
onlyLeaf,
activeColor,
optionRender,
labelField,
valueField = 'value',
classnames: cx,
cascade,
multiple
multiple,
classnames: cx
} = this.props;
const {selectedOptions} = this.state;
const {selectedOptions, activePaths} = this.state;
const selectedValueArr = selectedOptions.map(item => item[valueField]);
let selfChecked = selectedValueArr.includes(option[valueField]);
@ -508,21 +485,51 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
) : (
<span>{option[labelField]}</span>
);
return (
<li
className={cx(
'Cascader-option',
{
selected: selfChecked,
disabled: option.disabled
'selected': selfChecked,
'disabled': option.disabled,
'is-active': activePaths.includes(option)
},
option.className
)}
style={{color}}
onClick={() => this.onSelect(option, tabIndex)}
key={tabIndex + '-' + option[valueField]}
onClick={() => {
!multiple && this.onSelect(option, tabIndex);
this.handleExpand(option, tabIndex);
}}
>
<span className={cx('Cascader-option--text')}>{Text}</span>
{multiple ? (
<Checkbox
disabled={option.disabled || (onlyLeaf && option.children?.length)}
checked={
selectedOptions.includes(option) ||
this.getOnlyChildrenSelect(option)
}
onChange={() => this.onSelect(option, tabIndex)}
>
<span className={cx('Cascader-option--text')}>{Text}</span>
</Checkbox>
) : (
<span
className={cx('Cascader-option--text', {
disabled: onlyLeaf && option.children?.length
})}
>
{Text}
</span>
)}
{option.children?.length ? (
<span className={cx('Cascader-option-arrow')}>
<Icon icon="right-arrow-bold" className="icon" />
</span>
) : null}
</li>
);
}
@ -551,7 +558,7 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
const {options} = tab;
return (
<div
className={cx(`Cascader-tab`)}
className={cx(`Cascader-tab depth-${tabIndex}`)}
ref={this.tabRef}
key={tabIndex}
>
@ -563,7 +570,10 @@ export class Cascader extends React.Component<CascaderProps, CascaderState> {
? Array(getTreeDepth(options) - tabs.length)
.fill(1)
.map((item: number, index: number) => (
<div className={cx(`Cascader-tab`)} key={index}></div>
<div
className={cx(`Cascader-tab depth-${index + 1}`)}
key={index}
></div>
))
: null}
</div>

View File

@ -14,6 +14,7 @@ import Transition, {
import {autobind} from 'amis-core';
import {isClickOnInput} from 'amis-core';
import {TranslateFn} from 'amis-core';
import {isMobile} from 'amis-core';
import {Icon} from './icons';
const collapseStyles: {
@ -54,6 +55,7 @@ export interface CollapseProps {
propsUpdate?: boolean;
partial?: boolean;
children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
}
export interface CollapseState {
@ -170,19 +172,25 @@ export class Collapse extends React.Component<CollapseProps, CollapseState> {
showArrow,
expandIcon,
disabled,
children
children,
useMobileUI
} = this.props;
const finalHeader = this.state.collapsed
? header
: collapseHeader || header;
const mobileUI = useMobileUI && isMobile();
let dom = [
finalHeader ? (
<HeadingComponent
key="header"
onClick={this.toggleCollapsed}
className={cx(`Collapse-header`, headingClassName)}
className={cx(
`Collapse-header`,
{'is-mobile': mobileUI},
headingClassName
)}
>
{showArrow && collapsable ? (
expandIcon ? (
@ -249,6 +257,7 @@ export class Collapse extends React.Component<CollapseProps, CollapseState> {
className={cx(
`Collapse`,
{
'is-mobile': mobileUI,
'is-active': !this.state.collapsed,
[`Collapse--${size}`]: size,
'Collapse--disabled': disabled,

View File

@ -13,6 +13,7 @@ export interface CollapseItem {
import {ClassNamesFn, themeable, autobind} from 'amis-core';
import type {SchemaNode} from 'amis-core';
import {isMobile} from 'amis-core';
import isEqual from 'lodash/isEqual';
export interface CollapseGroupProps {
@ -26,6 +27,7 @@ export interface CollapseGroupProps {
classnames: ClassNamesFn;
classPrefix: string;
children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
}
export interface CollapseGroupState {
@ -136,8 +138,10 @@ class CollapseGroup extends React.Component<
className,
style,
expandIconPosition,
children
children,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<div
@ -146,6 +150,9 @@ class CollapseGroup extends React.Component<
{
'icon-position-right': expandIconPosition === 'right'
},
{
'is-mobile': mobileUI
},
className
)}
style={style}

View File

@ -40,6 +40,7 @@ export interface ColorControlState {
isOpened: boolean;
isFocused: boolean;
inputValue: string;
tempValue: string;
}
export class ColorControl extends React.PureComponent<
@ -56,7 +57,8 @@ export class ColorControl extends React.PureComponent<
state = {
isOpened: false,
isFocused: false,
inputValue: this.props.value || ''
inputValue: this.props.value || '',
tempValue: this.props.value || ''
};
popover: any;
closeTimer: number;
@ -70,6 +72,8 @@ export class ColorControl extends React.PureComponent<
this.focus = this.focus.bind(this);
this.blur = this.blur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleTempChange = this.handleTempChange.bind(this);
this.handleConfirm = this.handleConfirm.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.clearValue = this.clearValue.bind(this);
@ -206,6 +210,31 @@ export class ColorControl extends React.PureComponent<
// closeOnSelect && this.close();
}
handleTempChange(color: ColorResult) {
let {tempValue} = this.state;
const {format} = this.props;
if (format === 'rgba') {
tempValue = `rgba(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`;
} else if (format === 'rgb') {
tempValue = `rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b})`;
} else if (format === 'hsl') {
tempValue = `hsl(${Math.round(color.hsl.h)}, ${Math.round(
color.hsl.s * 100
)}%, ${Math.round(color.hsl.l * 100)}%)`;
} else {
tempValue = color.hex;
}
this.setState({tempValue});
}
handleConfirm() {
const {onChange} = this.props;
const {tempValue} = this.state;
onChange(tempValue);
this.close();
}
render() {
const {
classPrefix: ns,
@ -227,6 +256,7 @@ export class ColorControl extends React.PureComponent<
const __ = this.props.translate;
const isOpened = this.state.isOpened;
const isFocused = this.state.isFocused;
const tempValue = this.state.tempValue;
const mobileUI = useMobileUI && isMobile();
return (
@ -328,18 +358,20 @@ export class ColorControl extends React.PureComponent<
container={popOverContainer}
isShow={isOpened}
onHide={this.handleClick}
showConfirm
onConfirm={this.handleConfirm}
>
{allowCustomColor ? (
<SketchPicker
styles={{}}
disableAlpha={!!~['rgb', 'hex'].indexOf(format as string)}
color={value}
color={tempValue}
presetColors={presetColors}
onChangeComplete={this.handleChange}
onChangeComplete={this.handleTempChange}
/>
) : (
<GithubPicker
color={value}
color={tempValue}
colors={
Array.isArray(presetColors)
? (presetColors
@ -355,7 +387,7 @@ export class ColorControl extends React.PureComponent<
) as string[])
: undefined
}
onChangeComplete={this.handleChange}
onChangeComplete={this.handleTempChange}
/>
)}
</PopUp>

View File

@ -2,8 +2,15 @@ import React from 'react';
import Modal from './Modal';
import Button from './Button';
import Drawer from './Drawer';
import {localeable, LocaleProps, themeable, ThemeProps} from 'amis-core';
import {
localeable,
LocaleProps,
themeable,
ThemeProps,
isMobile
} from 'amis-core';
import Spinner from './Spinner';
import PopUp from './PopUp';
export interface ConfirmBoxProps extends LocaleProps, ThemeProps {
show?: boolean;
@ -34,6 +41,7 @@ export interface ConfirmBoxProps extends LocaleProps, ThemeProps {
headerClassName?: string;
bodyClassName?: string;
footerClassName?: string;
useMobileUI?: boolean;
}
export function ConfirmBox({
@ -56,7 +64,8 @@ export function ConfirmBox({
classnames: cx,
className,
bodyClassName,
footerClassName
footerClassName,
useMobileUI
}: ConfirmBoxProps) {
const [loading, setLoading] = React.useState<boolean>();
const [error, setError] = React.useState<string>();
@ -90,7 +99,22 @@ export function ConfirmBox({
}, [show]);
function renderDialog() {
return (
const mobileUI = useMobileUI && isMobile();
return mobileUI ? (
<PopUp
isShow={show}
showConfirm
onConfirm={handleConfirm}
onHide={onCancel}
>
{typeof children === 'function'
? children({
bodyRef: bodyRef,
loading
})
: children}
</PopUp>
) : (
<Modal
size={size}
closeOnEsc={closeOnEsc}

View File

@ -1019,7 +1019,8 @@ export class DateRangePicker extends React.Component<
}
selectRannge(range: PlainObject) {
const {closeOnSelect, minDate, maxDate} = this.props;
const {closeOnSelect, minDate, maxDate, useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
const now = moment();
this.setState(
{
@ -1032,7 +1033,7 @@ export class DateRangePicker extends React.Component<
? moment.min(maxDate, range.endDate(now.clone()))
: range.endDate(now.clone())
},
closeOnSelect ? this.confirm : noop
closeOnSelect && !mobileUI ? this.confirm : noop
);
}
@ -1330,7 +1331,9 @@ export class DateRangePicker extends React.Component<
locale,
embed,
type,
viewMode = 'days'
viewMode = 'days',
label,
useMobileUI
} = this.props;
const __ = this.props.translate;
const {startDate, endDate, editState} = this.state;
@ -1348,12 +1351,48 @@ export class DateRangePicker extends React.Component<
!endDate ||
!startDate?.isValid() ||
!endDate?.isValid()));
const mobileUI = useMobileUI && isMobile();
return (
<div className={cx(`${ns}DateRangePicker-wrap`)} ref={this.calendarRef}>
<div
className={cx(`${ns}DateRangePicker-wrap`, {'is-mobile': mobileUI})}
ref={this.calendarRef}
>
{mobileUI && !embed ? (
<div className={cx('PickerColumns-header')}>
{
<Button
className="PickerColumns-cancel"
level="link"
onClick={() => this.close(false)}
>
{__('cancel')}
</Button>
}
{label && typeof label === 'string'
? label
: __('Calendar.datepicker')}
{
<Button
className="PickerColumns-confirm"
level="link"
disabled={isConfirmBtnDisbaled || !startDate || !endDate}
onClick={this.confirm}
>
{__('confirm')}
</Button>
}
</div>
) : null}
{this.renderRanges(ranges)}
<div className={cx(`${ns}DateRangePicker-picker-wrap`)}>
{(!isTimeRange || (editState === 'start' && !embed)) && (
<div
className={cx(`${ns}DateRangePicker-picker-wrap`, {
'is-vertical': embed
})}
>
{(!isTimeRange ||
(editState === 'start' && !embed) ||
(mobileUI && isTimeRange)) && (
<Calendar
className={`${ns}DateRangePicker-start`}
value={startDate}
@ -1380,9 +1419,12 @@ export class DateRangePicker extends React.Component<
renderYear={this.renderYear}
locale={locale}
timeRangeHeader="开始时间"
embed={embed}
/>
)}
{(!isTimeRange || (editState === 'end' && !embed)) && (
{(!isTimeRange ||
(editState === 'end' && !embed) ||
(mobileUI && isTimeRange)) && (
<Calendar
className={`${ns}DateRangePicker-end`}
value={endDate}
@ -1409,11 +1451,12 @@ export class DateRangePicker extends React.Component<
renderYear={this.renderYear}
locale={locale}
timeRangeHeader="结束时间"
embed={embed}
/>
)}
</div>
{embed ? null : (
{embed || mobileUI ? null : (
<div key="button" className={`${ns}DateRangePicker-actions`}>
{/* this.close 这里不可以传参 */}
<Button size="sm" onClick={() => this.close()}>
@ -1571,18 +1614,23 @@ export class DateRangePicker extends React.Component<
close={this.close}
confirm={this.confirm}
onChange={this.handleMobileChange}
footerExtra={this.renderRanges(ranges)}
ranges={ranges}
showViewMode={
viewMode === 'quarters' || viewMode === 'months' ? 'years' : 'months'
}
/>
);
const mobileUI = useMobileUI && isMobile();
if (embed) {
return (
<div
className={cx(
`${ns}DateRangeCalendar`,
{
'is-mobile': mobileUI
},
{
'is-disabled': disabled
},
@ -1686,7 +1734,8 @@ export class DateRangePicker extends React.Component<
`${ns}CalendarMobile-pop--${viewMode}`
)}
onHide={this.close}
header={CalendarMobileTitle}
showClose={false}
// header={CalendarMobileTitle}
>
{useCalendarMobile ? calendarMobile : this.renderCalendar()}
</PopUp>

View File

@ -1,5 +1,5 @@
import React from 'react';
import {ThemeProps, themeable} from 'amis-core';
import {ThemeProps, themeable, isMobile} from 'amis-core';
import Input from './Input';
import {autobind, ucFirst} from 'amis-core';
import {Icon} from './icons';
@ -18,6 +18,7 @@ export interface InputBoxProps
prefix?: JSX.Element;
children?: React.ReactNode | Array<React.ReactNode>;
borderMode?: 'full' | 'half' | 'none';
useMobileUI?: boolean;
}
export interface InputBoxState {
@ -83,13 +84,16 @@ export class InputBox extends React.Component<InputBoxProps, InputBoxState> {
children,
borderMode,
onClick,
useMobileUI,
...rest
} = this.props;
const isFocused = this.state.isFocused;
const mobileUI = useMobileUI && isMobile();
return (
<div
className={cx('InputBox', className, {
'is-mobile': mobileUI,
'is-focused': isFocused,
'is-disabled': disabled,
'is-error': hasError,

View File

@ -20,6 +20,7 @@ export interface InputBoxWithSuggestionProps extends ThemeProps, LocaleProps {
hasError?: boolean;
placeholder?: string;
clearable?: boolean;
useMobileUI?: boolean;
}
const option2value = (item: any) => item.value;
@ -65,7 +66,8 @@ export class InputBoxWithSuggestion extends React.Component<InputBoxWithSuggesti
searchable,
popOverContainer,
clearable,
hasError
hasError,
useMobileUI
} = this.props;
const options = this.filterOptions(
Array.isArray(this.props.options) ? this.props.options : []
@ -103,6 +105,7 @@ export class InputBoxWithSuggestion extends React.Component<InputBoxWithSuggesti
clearable={clearable}
onClick={onClick}
hasError={hasError}
useMobileUI={useMobileUI}
>
<span className={cx('InputBox-caret')}>
<Icon icon="caret" className="icon" />

View File

@ -161,7 +161,7 @@ export function InputTable({
function renderBody() {
return (
<div className={cx(`Table`, className)}>
<div className={cx(`Table`)}>
<div className={cx(`Table-contentWrap`)}>
<table className={cx(`Table-table`)}>
<thead>

View File

@ -2,6 +2,7 @@ import {ThemeProps, themeable} from 'amis-core';
import React from 'react';
import {Options, Option} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
import {isMobile} from 'amis-core';
export interface ListMenuProps extends ThemeProps, LocaleProps {
options: Options;
@ -14,6 +15,7 @@ export interface ListMenuProps extends ThemeProps, LocaleProps {
getItemProps: (props: {item: Option; index: number}) => any;
prefix?: JSX.Element;
children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
}
interface RenderResult {
@ -36,6 +38,7 @@ export class ListMenu extends React.Component<ListMenuProps> {
getItemProps,
highlightIndex,
selectedOptions,
useMobileUI,
onSelect
} = this.props;
@ -86,11 +89,20 @@ export class ListMenu extends React.Component<ListMenuProps> {
}
render() {
const {classnames: cx, options, placeholder, prefix, children} = this.props;
const {
classnames: cx,
options,
placeholder,
prefix,
children,
useMobileUI,
selectedOptions
} = this.props;
const __ = this.props.translate;
const mobileUI = useMobileUI && isMobile();
return (
<div className={cx('ListMenu')}>
<div className={cx('ListMenu', {'is-mobile': mobileUI})}>
{prefix}
{Array.isArray(options) && options.length ? (
options.reduce(

View File

@ -8,6 +8,8 @@ import Alert2 from './Alert2';
import BaiduMapPicker from './BaiduMapPicker';
import GaodeMapPicker from './GaodeMapPicker';
import {LocaleProps, localeable} from 'amis-core';
import {isMobile} from 'amis-core';
import PopUp from './PopUp';
export interface LocationProps extends ThemeProps, LocaleProps {
vendor: 'baidu' | 'gaode' | 'tenxun';
@ -26,6 +28,7 @@ export interface LocationProps extends ThemeProps, LocaleProps {
popoverClassName?: string;
onChange: (value: any) => void;
popOverContainer?: any;
useMobileUI?: boolean;
}
export interface LocationState {
@ -42,6 +45,7 @@ export class LocationPicker extends React.Component<
clearable: false
};
domRef: React.RefObject<HTMLDivElement> = React.createRef();
tempValue: any;
state = {
isFocused: false,
isOpened: false
@ -93,6 +97,7 @@ export class LocationPicker extends React.Component<
},
fn
);
this.tempValue = this.props.value;
}
@autobind
@ -127,6 +132,23 @@ export class LocationPicker extends React.Component<
this.props.onChange(value);
}
@autobind
handleTempChange(value: any) {
if (value) {
value = {
...value,
vendor: this.props.vendor
};
}
this.tempValue = value;
}
@autobind
handleConfirm() {
this.props.onChange(this.tempValue);
this.close();
}
render() {
const {
classnames: cx,
@ -139,10 +161,12 @@ export class LocationPicker extends React.Component<
popOverContainer,
vendor,
coordinatesType,
ak
ak,
useMobileUI
} = this.props;
const __ = this.props.translate;
const {isFocused, isOpened} = this.state;
const mobileUI = useMobileUI && isMobile();
const picker = (() => {
switch (vendor) {
@ -165,9 +189,9 @@ export class LocationPicker extends React.Component<
/>
);
default:
return (<Alert2>{__(`${vendor} 地图控件不支持`, {vendor})}</Alert2>);
return <Alert2>{__(`${vendor} 地图控件不支持`, {vendor})}</Alert2>;
}
})()
})();
return (
<div
@ -178,6 +202,7 @@ export class LocationPicker extends React.Component<
className={cx(
`LocationPicker`,
{
'is-mobile': mobileUI,
'is-disabled': disabled,
'is-focused': isFocused,
'is-active': isOpened
@ -205,22 +230,55 @@ export class LocationPicker extends React.Component<
<Icon icon="location" className="icon" />
</a>
<Overlay
target={this.getTarget}
container={popOverContainer || this.getParent}
rootClose={false}
show={isOpened}
>
<PopOver
className={cx('LocationPicker-popover', popoverClassName)}
{mobileUI ? (
<PopUp
className={cx(`LocationPicker-popup`)}
container={popOverContainer || this.getParent}
isShow={isOpened}
onHide={this.close}
overlay
onClick={this.handlePopOverClick}
style={{width: this.getTarget()?.offsetWidth}}
showConfirm
onConfirm={this.handleConfirm}
>
{picker}
</PopOver>
</Overlay>
<div className={cx('LocationPicker-popup-inner')}>
{vendor === 'baidu' ? (
<BaiduMapPicker
ak={ak}
value={value}
coordinatesType={coordinatesType}
onChange={this.handleTempChange}
/>
) : (
<Alert2>{__('${vendor} 地图控件不支持', {vendor})}</Alert2>
)}
</div>
</PopUp>
) : (
<Overlay
target={this.getTarget}
container={popOverContainer || this.getParent}
rootClose={false}
show={isOpened}
>
<PopOver
className={cx('LocationPicker-popover', popoverClassName)}
onHide={this.close}
overlay
onClick={this.handlePopOverClick}
style={{width: this.getTarget()?.offsetWidth}}
>
{vendor === 'baidu' ? (
<BaiduMapPicker
ak={ak}
value={value}
coordinatesType={coordinatesType}
onChange={this.handleChange}
/>
) : (
<Alert2>{__('${vendor} 地图控件不支持', {vendor})}</Alert2>
)}
</PopOver>
</Overlay>
)}
</div>
);
}

View File

@ -264,6 +264,22 @@ export class Modal extends React.Component<ModalProps, ModalState> {
this.isRootClosed && !e.defaultPrevented && onHide(e);
}
getZIndex() {
let defaultIndex = 1400;
const {classnames: cx} = this.props;
const modals = document.querySelectorAll('.' + cx('Modal'));
const popups = document.querySelectorAll('.' + cx('PopUp'));
[...modals, ...popups].forEach((node: HTMLDialogElement) => {
const style = getComputedStyle(node);
const curIndex = parseInt(style.zIndex, 10);
if (curIndex > defaultIndex) {
defaultIndex = curIndex;
}
});
return defaultIndex;
}
render() {
const {
className,
@ -301,6 +317,7 @@ export class Modal extends React.Component<ModalProps, ModalState> {
},
className
)}
style={{zIndex: this.getZIndex()}}
>
{overlay ? (
<div className={cx(`Modal-overlay`, fadeStyles[status])} />

View File

@ -10,7 +10,14 @@ import getMiniDecimal, {
} from '@rc-component/mini-decimal';
import {Icon} from './icons';
import {ThemeProps, themeable, isNumeric, autobind, ucFirst} from 'amis-core';
import {
ThemeProps,
themeable,
isNumeric,
autobind,
ucFirst,
isMobile
} from 'amis-core';
export type ValueType = string | number;
@ -74,6 +81,7 @@ export interface NumberProps extends ThemeProps {
*
*/
inputControlClassName?: string;
useMobileUI?: boolean;
}
export interface NumberState {
@ -304,11 +312,13 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
displayMode,
inputRef,
keyboard,
inputControlClassName
inputControlClassName,
useMobileUI
} = this.props;
const precisionProps: any = {
precision: NumberInput.normalizePrecision(precision, step)
};
const mobileUI = useMobileUI && isMobile();
return (
<InputNumber
@ -320,6 +330,9 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
: inputControlClassName,
{
[`Number--border${ucFirst(borderMode)}`]: borderMode
},
{
'is-mobile': mobileUI
}
)}
ref={inputRef}
@ -353,9 +366,11 @@ export class NumberInput extends React.Component<NumberProps, NumberState> {
borderMode,
readOnly,
displayMode,
inputControlClassName
inputControlClassName,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<>
{displayMode === 'enhance' ? (

View File

@ -28,7 +28,7 @@ export interface PickerColumnItem {
visibleItemCount?: number;
itemHeight?: number;
options?: PickerOption[];
optionRender?: (option: string | object | PickerOption) => React.ReactNode;
optionRender?: (...params: any) => React.ReactNode;
onChange?: (
value?: PickerOption | string,
index?: number,
@ -319,7 +319,7 @@ const PickerColumn = forwardRef<{}, PickerColumnProps>((props, ref) => {
return (
<li {...data} ref={menuItemRef}>
{props.optionRender ? (
props.optionRender(option)
props.optionRender(option, {index, checked: state.index === index})
) : (
<div {...childData} />
)}

View File

@ -33,6 +33,7 @@ export interface PickerContainerProps
onPickerOpen?: (props: PickerContainerProps) => any;
popOverContainer?: any;
useMobileUI?: boolean;
}
export interface PickerContainerState {
@ -133,7 +134,8 @@ export class PickerContainer extends React.Component<
size,
showFooter,
closeOnEsc,
popOverContainer
popOverContainer,
useMobileUI
} = this.props;
return (
<>
@ -156,6 +158,7 @@ export class PickerContainer extends React.Component<
showFooter={showFooter}
beforeConfirm={this.confirm}
popOverContainer={popOverContainer}
useMobileUI={useMobileUI}
>
{() =>
popOverRender({

View File

@ -24,12 +24,14 @@ export interface PopOverContainerProps {
placement?: string;
overlayWidth?: number | string;
overlayWidthField?: 'minWidth' | 'width';
showConfirm?: boolean;
// 相当于 placement 的简化版
align?: OverlayAlignType;
/** Popover层隐藏前触发的事件 */
onBeforeHide?: () => void;
/** Popover层隐藏后触发的事件 */
onAfterHide?: () => void;
onConfirm?: () => void;
}
export interface PopOverContainerState {
@ -91,6 +93,12 @@ export class PopOverContainer extends React.Component<
return this.getTarget()?.parentElement;
}
@autobind
onConfirm() {
this.props.onConfirm?.();
this.close();
}
static calcOverlayWidth(overlay: PopOverOverlay, targetWidth: number) {
const overlayWidth = overlay && overlay.width;
@ -142,7 +150,9 @@ export class PopOverContainer extends React.Component<
popOverClassName,
popOverRender: dropdownRender,
placement,
align
align,
showConfirm,
onConfirm
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
@ -157,7 +167,9 @@ export class PopOverContainer extends React.Component<
isShow={this.state.isOpened}
container={popOverContainer}
className={popOverClassName}
showConfirm={showConfirm}
onHide={this.close}
onConfirm={this.onConfirm}
>
{dropdownRender({onClose: this.close})}
</PopUp>

View File

@ -71,6 +71,22 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
e.stopPropagation();
}
getZIndex() {
let defaultIndex = 3000;
const {classnames: cx} = this.props;
const modals = document.querySelectorAll('.' + cx('Modal'));
const popups = document.querySelectorAll('.' + cx('PopUp'));
[...modals, ...popups].forEach((node: HTMLDialogElement) => {
const style = getComputedStyle(node);
const curIndex = parseInt(style.zIndex, 10);
if (curIndex > defaultIndex) {
defaultIndex = curIndex;
}
});
return defaultIndex;
}
render() {
const {
style,
@ -93,7 +109,8 @@ export class PopUp extends React.PureComponent<PopUpPorps> {
} = this.props;
const outerStyle: any = {
...style
...style,
zIndex: this.getZIndex()
};
delete outerStyle.top;
return (

View File

@ -17,6 +17,7 @@ import type {ThemeProps} from 'amis-core';
import {themeable} from 'amis-core';
import {autobind, camel} from 'amis-core';
import {stripNumber} from 'amis-core';
import {isMobile} from 'amis-core';
import {findDOMNode} from 'react-dom';
import {Icon} from './icons';
@ -54,6 +55,7 @@ interface HandleItemProps extends ThemeProps {
tipFormatter?: (value: Value) => boolean;
unit?: string;
tooltipPlacement?: string;
useMobileUI?: boolean;
}
interface LabelProps extends ThemeProps {
@ -166,6 +168,35 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
});
}
@autobind
onTouchStart() {
this.setState({
isDrag: true,
labelActive: true
});
}
@autobind
onTouchMove(e: any) {
const {isDrag} = this.state;
const {type = 'min'} = this.props;
if (!isDrag) {
return;
}
this.props.onChange(e.touches[0].clientX, type);
}
@autobind
onTouchEnd() {
const {isDrag} = this.state;
if (isDrag) {
return;
}
this.setState({
labelActive: false
});
}
render() {
const {
classnames: cx,
@ -176,7 +207,8 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
tooltipVisible,
tipFormatter,
unit,
tooltipPlacement = 'auto'
tooltipPlacement = 'auto',
useMobileUI
} = this.props;
const {isDrag, labelActive} = this.state;
const style = {
@ -184,6 +216,8 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
zIndex: isDrag ? 2 : 1
};
const mobileUI = useMobileUI && isMobile();
return disabled ? (
<div className={cx('InputRange-handle')} style={style}>
<div className={cx('InputRange-handle-icon')}>
@ -192,7 +226,9 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
</div>
) : (
<div
className={cx('InputRange-handle')}
className={cx('InputRange-handle', {
'is-mobile': mobileUI
})}
style={style}
ref={this.handleRef}
>
@ -203,6 +239,9 @@ class HandleItem extends React.Component<HandleItemProps, HandleItemState> {
onMouseDown={this.onMouseDown}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onTouchStart={this.onTouchStart}
onTouchMove={this.onTouchMove}
onTouchEnd={this.onTouchEnd}
>
<Icon icon="slider-handle" className="icon" />
</div>
@ -277,6 +316,7 @@ class Label extends React.Component<LabelProps, any> {
// @todo 丰富这个
export interface RangeItemProps extends ThemeProps {
[propName: string]: any;
useMobileUI?: boolean;
}
export class Range extends React.Component<RangeItemProps, any> {
@ -443,13 +483,15 @@ export class Range extends React.Component<RangeItemProps, any> {
* @returns
*/
@autobind
getOffsetLeft(value: number | string) {
getOffsetLeft(value: number | string, getNumber?: boolean) {
const {max, min} = this.props;
if (isString(value) && MARKS_REG.test(value)) {
return value;
return getNumber ? parseFloat(value) : value;
}
value = Math.min(Math.max(+value, min), max);
return ((value - min) * 100) / (max - min) + '%';
return getNumber
? ((value - min) * 100) / (max - min)
: ((value - min) * 100) / (max - min) + '%';
}
/**
@ -489,7 +531,8 @@ export class Range extends React.Component<RangeItemProps, any> {
unit,
tooltipPlacement,
tipFormatter,
onAfterChange
onAfterChange,
useMobileUI
} = this.props;
// trace
@ -520,6 +563,14 @@ export class Range extends React.Component<RangeItemProps, any> {
};
}
const sortMaks = marks
? keys(marks).sort(
(a: keyof MarksType, b: keyof MarksType) =>
(this.getOffsetLeft(a, true) as number) -
(this.getOffsetLeft(b, true) as number)
)
: [];
return (
<div className={cx('InputRange-wrap')}>
<div
@ -550,6 +601,7 @@ export class Range extends React.Component<RangeItemProps, any> {
tooltipVisible={tooltipVisible}
tipFormatter={tipFormatter}
unit={unit}
useMobileUI={useMobileUI}
tooltipPlacement={tooltipPlacement}
onAfterChange={onAfterChange}
onChange={this.onGetChangeValue.bind(this)}
@ -566,6 +618,7 @@ export class Range extends React.Component<RangeItemProps, any> {
tooltipVisible={tooltipVisible}
tipFormatter={tipFormatter}
unit={unit}
useMobileUI={useMobileUI}
tooltipPlacement={tooltipPlacement}
onAfterChange={onAfterChange}
onChange={this.onChange.bind(this)}
@ -575,9 +628,10 @@ export class Range extends React.Component<RangeItemProps, any> {
{/* 刻度标记 */}
{marks && (
<div className={cx('InputRange-marks')}>
{keys(marks).map((key: keyof MarksType) => {
const offsetLeft = this.getOffsetLeft(key);
{sortMaks.map((key: keyof MarksType) => {
const offsetLeft = this.getOffsetLeft(key) as string;
const markMaxWidth = this.getMarkMaxWidth(key, marks);
if (MARKS_REG.test(offsetLeft)) {
return (
<div

View File

@ -29,16 +29,22 @@ export interface ResultBoxProps
overflowTagPopover?: TooltipObject;
actions?: JSX.Element | JSX.Element[];
showInvalidMatch?: boolean;
showArrow?: boolean;
}
export class ResultBox extends React.Component<ResultBoxProps> {
static defaultProps: Pick<
ResultBoxProps,
'clearable' | 'placeholder' | 'itemRender' | 'inputPlaceholder'
| 'clearable'
| 'placeholder'
| 'itemRender'
| 'inputPlaceholder'
| 'showArrow'
> = {
clearable: false,
placeholder: 'placeholder.noData',
inputPlaceholder: 'placeholder.enter',
showArrow: true,
itemRender: (option: any) => (
<span>{`${option.scopeLabel || ''}${option.label}`}</span>
)
@ -68,6 +74,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
@autobind
handleFocus(e: any) {
console.log('eeeeeeee', e);
const onFocus = this.props.onFocus;
onFocus && onFocus(e);
this.setState({
@ -241,11 +248,14 @@ export class ResultBox extends React.Component<ResultBoxProps> {
onClear,
maxTagCount,
overflowTagPopover,
showArrow,
...rest
} = this.props;
const isFocused = this.state.isFocused;
const mobileUI = useMobileUI && isMobile();
console.log('onResultClick-----', onResultClick);
return (
<div
className={cx('ResultBox', className, {
@ -326,7 +336,7 @@ export class ResultBox extends React.Component<ResultBoxProps> {
<Icon icon="right-arrow-bold" className="icon" />
</span>
)}
{!allowInput && mobileUI ? (
{!allowInput && mobileUI && showArrow ? (
<span className={cx('ResultBox-arrow')}>
<Icon icon="caret" className="icon" />
</span>

View File

@ -6,7 +6,7 @@ import moment from 'moment';
import {ThemeProps, themeable} from 'amis-core';
import {Icon} from './icons';
import {uncontrollable} from 'amis-core';
import {autobind} from 'amis-core';
import {autobind, isMobile} from 'amis-core';
import {LocaleProps, localeable} from 'amis-core';
export interface HistoryRecord {
@ -48,6 +48,7 @@ export interface SearchBoxProps extends ThemeProps, LocaleProps {
/** 历史记录配置 */
history?: SearchHistoryOptions;
clearAndSubmit?: boolean;
useMobileUI?: boolean;
}
export interface SearchBoxState {
@ -284,10 +285,12 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
mini,
enhance,
clearable,
useMobileUI,
translate: __
} = this.props;
const {isFocused, inputValue} = this.state;
const {enable} = this.getHistoryOptions();
const mobileUI = useMobileUI && isMobile();
return (
<div
@ -298,7 +301,8 @@ export class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> {
disabled ? 'is-disabled' : '',
isFocused ? 'is-focused' : '',
!mini || active ? 'is-active' : '',
{'is-history': enable}
{'is-history': enable},
{'is-mobile': mobileUI}
)}
style={style}
>

View File

@ -41,6 +41,7 @@ import {RemoteOptionsProps, withRemoteConfig} from './WithRemoteConfig';
import Picker from './Picker';
import PopUp from './PopUp';
import BasePopover, {PopOverOverlay} from './PopOverContainer';
import SelectMobile from './SelectMobile';
import type {TooltipObject} from '../components/TooltipWrapper';
@ -303,7 +304,7 @@ export function normalizeOptions(
const DownshiftChangeTypes = Downshift.stateChangeTypes;
interface SelectProps
export interface SelectProps
extends OptionProps,
ThemeProps,
LocaleProps,
@ -1145,10 +1146,7 @@ export class Select extends React.Component<SelectProps, SelectState> {
};
const mobileUI = isMobile() && useMobileUI;
const column = {
labelField: 'label',
options: filtedOptions
};
const menu = (
<div
ref={this.menu}
@ -1229,14 +1227,21 @@ export class Select extends React.Component<SelectProps, SelectState> {
</div>
);
return mobileUI ? (
<PopUp
className={cx(`Select-popup`)}
container={popOverContainer}
isShow={this.state.isOpen}
onHide={this.close}
>
{menu}
</PopUp>
<SelectMobile
{...this.props}
highlightedIndex={highlightedIndex}
isOpen={isOpen}
getItemProps={getItemProps}
getInputProps={getInputProps}
selectedItem={selectedItem}
onChange={selection => {
this.setState({
isOpen: false
});
this.props.onChange(selection);
}}
onClose={this.close}
/>
) : (
<Overlay
container={popOverContainer || this.getTarget}

View File

@ -0,0 +1,520 @@
import React from 'react';
import {findDOMNode} from 'react-dom';
import Picker from './Picker';
import PopUp from './PopUp';
import {autobind, highlight} from 'amis-core';
import merge from 'lodash/merge';
// @ts-ignore
import {matchSorter} from 'match-sorter';
import {Option, Options, value2array, SelectProps} from './Select';
import VirtualList from './virtual-list';
import Checkbox from './Checkbox';
import Input from './Input';
import {closeIcon, Icon} from './icons';
interface SelectState {
isFocused: boolean;
inputValue: string;
itemHeight: number;
selection: Array<Option>;
}
interface Props extends SelectProps {
isOpen: boolean;
highlightedIndex: any;
selectedItem: any;
getInputProps: (...params: any) => any;
getItemProps: (...params: any) => any;
onClose: () => void;
}
export default class SelectMobile extends React.Component<Props, SelectState> {
input: HTMLInputElement;
target: HTMLElement;
constructor(props: Props) {
super(props);
this.state = {
selection: value2array(props.value, props),
isFocused: false,
inputValue: '',
itemHeight: 32 /** Select选项高度保持一致 */
};
}
@autobind
handleChange([item]: any) {
const {onChange, multiple, simpleValue, valueField, options} = this.props;
let {selection} = this.state;
if (!multiple && item === undefined) {
console.log('执行了');
}
// 单选是字符串
const selectItem = options.find((option: Option) =>
multiple
? option[valueField] === item[valueField]
: option[valueField] === item
);
if (multiple) {
const selectionValues = selection.map(item => item[valueField]);
selection = selection.concat();
const idx = selectionValues.indexOf(selectItem?.[valueField]);
if (~idx) {
selection.splice(idx, 1);
} else {
selectItem && selection.push(selectItem);
}
this.setState({
selection
});
} else {
this.setState({
selection: selectItem ? [selectItem] : []
});
}
}
@autobind
handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) {
const {loadOptions} = this.props;
this.setState(
{
inputValue: evt.currentTarget.value
},
() => loadOptions && loadOptions(this.state.inputValue)
);
}
@autobind
getTarget() {
if (!this.target) {
this.target = findDOMNode(this) as HTMLElement;
}
return this.target as HTMLElement;
}
@autobind
inputRef(ref: HTMLInputElement) {
this.input = ref;
}
@autobind
toggleCheckAll() {
const {
options,
onChange,
simpleValue,
checkAllBySearch,
labelField,
valueField
} = this.props;
const inputValue = this.state.inputValue;
let {selection} = this.state;
let filtedOptions: Array<Option> =
inputValue && checkAllBySearch !== false
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat();
const optionsValues = filtedOptions.map(option => option.value);
const selectionValues = selection.map(select => select.value);
const checkedAll = optionsValues.every(
option => selectionValues.indexOf(option) > -1
);
selection = checkedAll ? [] : filtedOptions;
this.setState({selection});
}
@autobind
handleAddClick() {
const {onAdd} = this.props;
onAdd && onAdd();
}
@autobind
handleEditClick(e: Event, item: any) {
const {onEdit} = this.props;
e.preventDefault();
e.stopPropagation();
onEdit && onEdit(item);
}
@autobind
handleDeleteClick(e: Event, item: any) {
const {onDelete} = this.props;
e.preventDefault();
e.stopPropagation();
onDelete && onDelete(item);
}
@autobind
onFocus(e: any) {
const {simpleValue} = this.props;
const {selection} = this.state;
const value = simpleValue ? selection.map(item => item.value) : selection;
this.props.disabled ||
this.props.isOpen ||
this.setState(
{
isFocused: true
},
this.focus
);
this.props.onFocus &&
this.props.onFocus({
...e,
value
});
}
@autobind
onBlur(e: any) {
const {simpleValue} = this.props;
const {selection} = this.state;
const value = simpleValue ? selection.map(item => item.value) : selection;
this.setState({
isFocused: false
});
this.props.onBlur &&
this.props.onBlur({
...e,
value
});
}
@autobind
focus() {
this.input
? this.input.focus()
: this.getTarget() && this.getTarget().focus();
}
blur() {
this.input
? this.input.blur()
: this.getTarget() && this.getTarget().blur();
}
@autobind
clearSearchValue() {
const {loadOptions} = this.props;
this.setState(
{
inputValue: ''
},
() => loadOptions?.('')
);
}
@autobind
onConfirm() {
const {selection} = this.state;
const {
multiple,
onChange,
simpleValue,
valueField,
options,
loadOptions,
labelField
} = this.props;
if (multiple) {
onChange(
simpleValue ? selection.map(item => item[valueField]) : selection
);
} else {
const inputValue = this.state.inputValue;
let filtedOptions: Array<Option> = (
inputValue && !loadOptions
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat()
).filter((option: Option) => !option.hidden && option.visible !== false);
// picker 打开未滑动时选中第一项
if (!selection.length && filtedOptions.length) {
onChange(
simpleValue ? filtedOptions[0]?.[valueField] : filtedOptions[0]
);
} else {
onChange(simpleValue ? selection[0]?.[valueField] : selection[0]);
}
}
}
render() {
const {
popOverContainer,
options,
value,
valueField,
labelField,
noResultsText,
loadOptions,
creatable,
multiple,
valuesNoWrap,
classnames: cx,
popoverClassName,
popOverContainerSelector,
checkAll,
checkAllLabel,
checkAllBySearch,
searchable,
createBtnLabel,
disabled,
searchPromptText,
editable,
removable,
overlayPlacement,
translate: __,
hideSelected,
renderMenu,
mobileClassName,
virtualThreshold = 100,
useMobileUI = false,
overlay,
isOpen,
highlightedIndex,
onClose,
getInputProps,
getItemProps,
selectedItem
} = this.props;
const {selection} = this.state;
const inputValue = this.state.inputValue;
let checkedAll = false;
let checkedPartial = false;
let filtedOptions: Array<Option> = (
inputValue && isOpen && !loadOptions
? matchSorter(options, inputValue, {
keys: [labelField || 'label', valueField || 'value']
})
: options.concat()
).filter((option: Option) => !option.hidden && option.visible !== false);
const enableVirtualRender =
filtedOptions.length && filtedOptions.length > virtualThreshold;
const selectionValues = selection.map(select => select[valueField]);
if (multiple && checkAll) {
const optionsValues = (
checkAllBySearch !== false ? filtedOptions : options
).map(option => option[valueField]);
checkedAll = optionsValues.every(
option => selectionValues.indexOf(option) > -1
);
checkedPartial = optionsValues.some(
option => selectionValues.indexOf(option) > -1
);
}
// 用于虚拟渲染的每项高度
const virtualItemHeight = this.props.itemHeight || this.state.itemHeight;
// 渲染单个选项
const renderItem = ({index, style}: {index: number; style?: object}) => {
const item = filtedOptions[index];
if (!item) {
return null;
}
const checked =
selectedItem === item || !!~selectionValues.indexOf(item[valueField]);
if (hideSelected && checked) {
return null;
}
return (
<div
{...getItemProps({
key:
typeof item.value === 'string'
? `${item.label}-${item.value}`
: index,
index,
item,
disabled: item.disabled
})}
style={merge(style, enableVirtualRender ? {width: '100%'} : {})}
className={cx(`Select-option`, {
'is-disabled': item.disabled,
'is-active': checked,
'is-mobile': true
})}
>
{renderMenu ? (
multiple ? (
renderMenu(item, {
multiple,
checkAll,
checked,
onChange: () => this.handleChange(item),
inputValue: inputValue || '',
searchable,
index
})
) : (
renderMenu(item, {
multiple,
checkAll,
checked,
onChange: () => this.handleChange(item),
inputValue: inputValue || '',
searchable,
index
})
)
) : multiple ? (
<>
<div
title={item[labelField]}
className={cx('Select-option-item-check')}
onClick={() => !item.disabled && this.handleChange([item])}
>
{item.disabled
? item[labelField]
: highlight(
item[labelField],
inputValue as string,
cx('Select-option-hl')
)}
{item.tip}
</div>
{checked ? (
<Icon icon="check" className={cx('Select-option-mcheck')} />
) : null}
</>
) : (
<span
className={cx('Select-option-content')}
title={
typeof item[labelField] === 'string' ? item[labelField] : ''
}
>
{item.disabled
? item[labelField]
: highlight(
item[labelField],
inputValue as string,
cx('Select-option-hl')
)}
{item.tip}
</span>
)}
</div>
);
};
const menu = (
<div
className={cx('Select-menu', {
'Select--longlist': enableVirtualRender,
'is-mobile': true
})}
>
{searchable ? (
<div
className={cx(`Select-input`, {
'is-focused': this.state.isFocused
})}
>
<Icon icon="search" className="icon" />
<Input
{...getInputProps({
onFocus: this.onFocus,
onBlur: this.onBlur,
disabled: disabled,
placeholder: __(searchPromptText),
onChange: this.handleInputChange,
ref: this.inputRef
})}
/>
{inputValue?.length ? (
<a onClick={this.clearSearchValue} className={cx('Select-clear')}>
<Icon icon="close" className="icon" />
</a>
) : null}
</div>
) : null}
{multiple && valuesNoWrap ? (
<div className={cx('Select-option')}>
({selectionValues.length})
</div>
) : null}
{multiple && checkAll && filtedOptions.length ? (
<div className={cx('Select-option')}>
<Checkbox
checked={checkedPartial}
partial={checkedPartial && !checkedAll}
onChange={this.toggleCheckAll}
size="sm"
>
{__(checkAllLabel)}
</Checkbox>
</div>
) : null}
{filtedOptions.length ? (
filtedOptions.length > virtualThreshold ? ( // 较多数据时才启用 virtuallist避免滚动条问题
<VirtualList
height={
filtedOptions.length > 8
? 266
: filtedOptions.length * virtualItemHeight
}
itemCount={filtedOptions.length}
itemSize={virtualItemHeight}
renderItem={renderItem}
/>
) : (
filtedOptions.map((item, index) => {
return renderItem({index});
})
)
) : (
<div className={cx('Select-noResult')}>{__(noResultsText)}</div>
)}
</div>
);
return (
<PopUp
className={cx(`Select-popup`)}
container={popOverContainer}
isShow={isOpen}
showConfirm={true}
onConfirm={this.onConfirm}
onHide={onClose}
>
{multiple ? (
menu
) : (
<Picker
className={'Select-picker'}
columns={{
options: filtedOptions as Option[],
optionRender: renderMenu
}}
onChange={item => this.handleChange(item as any)}
showToolbar={false}
labelField={labelField}
valueField={valueField}
itemHeight={40}
value={[selection[0]?.[valueField]]}
/>
)}
</PopUp>
);
}
}

View File

@ -15,7 +15,7 @@ import {Icon} from './icons';
import debounce from 'lodash/debounce';
import {findDOMNode} from 'react-dom';
import TooltipWrapper from './TooltipWrapper';
import {resizeSensor} from 'amis-core';
import {resizeSensor, isMobile} from 'amis-core';
import PopOverContainer from './PopOverContainer';
import Sortable from 'sortablejs';
@ -45,6 +45,8 @@ export interface TabProps extends ThemeProps {
iconPosition?: 'left' | 'right';
disabled?: boolean | string;
eventKey: string | number;
prevKey?: string | number;
nextKey?: string | number;
tab?: Schema;
className?: string;
activeKey?: string | number;
@ -53,12 +55,52 @@ export interface TabProps extends ThemeProps {
unmountOnExit?: boolean;
toolbar?: React.ReactNode;
children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
swipeable?: boolean;
onSelect?: (eventKey: string | number) => void;
}
class TabComponent extends React.PureComponent<TabProps> {
contentDom: any;
touch: any = {};
touchStartTime: number;
contentRef = (ref: any) => (this.contentDom = ref);
@autobind
onTouchStart(event: TouchEvent) {
this.touch.startX = event.touches[0].clientX;
this.touch.startY = event.touches[0].clientY;
this.touchStartTime = Date.now();
}
@autobind
onTouchMove(event: TouchEvent) {
const touch = event.touches[0];
const newState = {...this.touch};
newState.deltaX = touch.clientX < 0 ? 0 : touch.clientX - newState.startX;
newState.deltaY = touch.clientY - newState.startY;
newState.offsetX = Math.abs(newState.deltaX);
newState.offsetY = Math.abs(newState.deltaY);
this.touch = newState;
}
@autobind
onTouchEnd() {
const duration = Date.now() - this.touchStartTime;
const speed = this.touch.deltaX / duration;
const shouldSwipe = Math.abs(speed) > 0.25;
const {prevKey, nextKey, onSelect} = this.props;
if (shouldSwipe) {
if (this.touch.deltaX > 0) {
prevKey !== undefined && onSelect?.(prevKey);
} else {
nextKey && onSelect?.(nextKey);
}
}
}
render() {
const {
classnames: cx,
@ -68,9 +110,13 @@ class TabComponent extends React.PureComponent<TabProps> {
eventKey,
activeKey,
children,
className
className,
swipeable,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<Transition
in={activeKey === eventKey}
@ -91,6 +137,10 @@ class TabComponent extends React.PureComponent<TabProps> {
'Tabs-pane',
className
)}
onTouchStart={swipeable && mobileUI && this.onTouchStart}
onTouchMove={swipeable && mobileUI && this.onTouchMove}
onTouchEnd={swipeable && mobileUI && this.onTouchEnd}
onTouchCancel={swipeable && mobileUI && this.onTouchEnd}
>
{children}
</div>
@ -132,6 +182,7 @@ export interface TabsProps extends ThemeProps, LocaleProps {
collapseBtnLabel?: string;
popOverContainer?: any;
children?: React.ReactNode | Array<React.ReactNode>;
useMobileUI?: boolean;
}
export interface IDragInfo {
@ -271,6 +322,29 @@ export class Tabs extends React.Component<TabsProps, any> {
if (!this.scroll && !this.draging && isTabsModified) {
this.computedWidth();
}
// 移动端取消箭头切换,改为滚动切换激活项居中
const {classPrefix: ns, activeKey, useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI && preProps.activeKey !== activeKey) {
const {classPrefix: ns} = this.props;
const dom = findDOMNode(this) as HTMLElement;
const activeTab = dom.querySelector(
`.${ns}Tabs-link.is-active`
) as HTMLElement;
const parentWidth = (activeTab.parentNode?.parentNode as any).offsetWidth;
const offsetLeft = activeTab.offsetLeft;
const offsetWidth = activeTab.offsetWidth;
if (activeTab.parentNode) {
(activeTab.parentNode as any).scrollLeft =
offsetLeft > parentWidth
? (offsetLeft / parentWidth) * parentWidth -
parentWidth / 2 +
offsetWidth / 2
: offsetLeft - parentWidth / 2 + offsetWidth / 2;
}
}
this.scroll = false;
}
@ -760,9 +834,11 @@ export class Tabs extends React.Component<TabsProps, any> {
addable,
draggable,
sidePosition,
addBtnText
addBtnText,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
const {isOverflow} = this.state;
if (!Array.isArray(children)) {
return null;
@ -811,10 +887,12 @@ export class Tabs extends React.Component<TabsProps, any> {
isOverflow && 'Tabs-linksContainer--overflow'
)}
>
{this.renderArrow('left')}
{!mobileUI ? this.renderArrow('left') : null}
<div className={cx('Tabs-linksContainer-main')}>
<ul
className={cx('Tabs-links', linksClassName)}
className={cx('Tabs-links', linksClassName, {
'is-mobile': mobileUI
})}
role="tablist"
ref={this.navMain}
>
@ -823,13 +901,18 @@ export class Tabs extends React.Component<TabsProps, any> {
{!isOverflow && toolButtons}
</ul>
</div>
{this.renderArrow('right')}
{!mobileUI ? this.renderArrow('right') : null}
</div>
{isOverflow && toolButtons}
</div>
) : (
<div className={cx('Tabs-linksWrapper')}>
<ul className={cx('Tabs-links', linksClassName)} role="tablist">
<ul
className={cx('Tabs-links', linksClassName, {
'is-mobile': mobileUI
})}
role="tablist"
>
{this.renderNavs()}
{additionBtns}
{toolbar}

View File

@ -15,6 +15,7 @@ import {ItemRenderStates} from './Selection';
import {Icon} from './icons';
import debounce from 'lodash/debounce';
import {SpinnerExtraProps} from './Spinner';
import {isMobile} from 'amis-core';
export interface TabsTransferProps
extends Omit<
@ -51,6 +52,7 @@ export interface TabsTransferProps
activeKey: number;
onlyChildren?: boolean;
ctx?: Record<string, any>;
useMobileUI?: boolean;
}
export interface TabsTransferState {
@ -266,9 +268,11 @@ export class TabsTransfer extends React.Component<
activeKey,
classnames: cx,
translate: __,
ctx
ctx,
useMobileUI
} = this.props;
const showOptions = options.filter(item => item.visible !== false);
const mobileUI = useMobileUI && isMobile();
if (!Array.isArray(options) || !options.length) {
return (
@ -296,7 +300,9 @@ export class TabsTransfer extends React.Component<
className="TabsTransfer-tab"
>
{option.searchable ? (
<div className={cx('TabsTransfer-search')}>
<div
className={cx('TabsTransfer-search', {'is-mobile': mobileUI})}
>
<InputBox
value={this.state.inputValue}
onChange={(text: string) => this.handleSearch(text, option)}
@ -478,12 +484,14 @@ export class TabsTransfer extends React.Component<
classnames: cx,
optionItemRender,
onSearch,
useMobileUI,
...reset
} = this.props;
return (
<Transfer
{...reset}
useMobileUI={useMobileUI}
statistics={false}
classnames={cx}
className={cx('TabsTransfer', className)}

View File

@ -1,4 +1,4 @@
import {localeable} from 'amis-core';
import {isMobile, localeable} from 'amis-core';
import {themeable} from 'amis-core';
import {uncontrollable} from 'amis-core';
import React from 'react';
@ -14,6 +14,7 @@ export interface TabsTransferPickerProps
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
onFocus?: () => void;
onBlur?: () => void;
useMobileUI?: boolean;
}
export class TransferPicker extends React.Component<TabsTransferPickerProps> {
@ -45,12 +46,15 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
onChange,
size,
labelField = 'label',
useMobileUI,
...rest
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<PickerContainer
title={__('Select.placeholder')}
useMobileUI={useMobileUI}
onFocus={this.onFoucs}
onClose={this.onBlur}
bodyRender={({onClose, value, onChange, setState, ...states}) => {
@ -59,6 +63,7 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
{...rest}
{...states}
value={value}
useMobileUI={useMobileUI}
onChange={(value: any, optionModified) => {
if (optionModified) {
let options = mapTree(rest.options, item => {
@ -96,10 +101,13 @@ export class TransferPicker extends React.Component<TabsTransferPickerProps> {
itemRender={option => (
<span>{(option && option[labelField]) || 'undefiend'}</span>
)}
useMobileUI={useMobileUI}
>
<span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" />
</span>
{!mobileUI ? (
<span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" />
</span>
) : null}
</ResultBox>
)}
</PickerContainer>

View File

@ -60,6 +60,7 @@ export interface TextAreaProps extends ThemeProps, LocaleProps {
disabled?: boolean;
forwardRef?: {current: HTMLTextAreaElement | null};
useMobileUI?: boolean;
}
export interface TextAreaState {

View File

@ -13,6 +13,7 @@ import {ThemeProps, themeable, findTree} from 'amis-core';
import {BaseSelectionProps, BaseSelection, ItemRenderStates} from './Selection';
import {Options, Option} from './Select';
import {uncontrollable} from 'amis-core';
import {isMobile} from 'amis-core';
import ResultList from './ResultList';
import TableSelection from './TableSelection';
import {autobind, flattenTree} from 'amis-core';
@ -115,9 +116,11 @@ export interface TransferProps
checkAllLabel?: string;
/** 树形模式下,给 tree 的属性 */
onlyChildren?: boolean;
useMobileUI?: boolean;
}
export interface TransferState {
tempValue?: Array<Option> | Option;
inputValue: string;
searchResult: Options | null;
isTreeDeferLoad: boolean;
@ -366,7 +369,8 @@ export class Transfer<
options,
statistics,
translate: __,
searchPlaceholder = __('Transfer.searchKeyword')
searchPlaceholder = __('Transfer.searchKeyword'),
useMobileUI
} = props;
if (selectRender) {
@ -395,6 +399,8 @@ export class Transfer<
isEqual
).length;
const mobileUI = useMobileUI && isMobile();
return (
<>
<div
@ -437,7 +443,7 @@ export class Transfer<
</div>
{onSearch ? (
<div className={cx('Transfer-search')}>
<div className={cx('Transfer-search', {'is-mobile': mobileUI})}>
<InputBox
value={this.state.inputValue}
onChange={this.handleSearch}
@ -818,7 +824,8 @@ export class Transfer<
resultListModeFollowSelect,
selectMode = 'list',
translate: __,
valueField = 'value'
valueField = 'value',
useMobileUI
} = this.props as any;
const {searchResult} = this.state;
@ -832,6 +839,7 @@ export class Transfer<
);
const tableType = resultListModeFollowSelect && selectMode === 'table';
const mobileUI = useMobileUI && isMobile();
return (
<div
@ -847,7 +855,7 @@ export class Transfer<
</div>
) : null}
</div>
<div className={cx('Transfer-result')}>
<div className={cx('Transfer-result', {'is-mobile': mobileUI})}>
<div
className={cx(
'Transfer-title',

View File

@ -1,6 +1,6 @@
import {localeable} from 'amis-core';
import {themeable} from 'amis-core';
import {Transfer, TransferProps} from './Transfer';
import {Transfer, TransferProps, TransferState} from './Transfer';
import {uncontrollable, autobind} from 'amis-core';
import React from 'react';
import ResultBox from './ResultBox';
@ -29,11 +29,51 @@ export interface TransferDropDownProps extends TransferProps {
}
export class TransferDropDown extends Transfer<TransferDropDownProps> {
constructor(props: TransferDropDownProps) {
super(props);
this.state = {
tempValue: props.value,
inputValue: '',
searchResult: null,
isTreeDeferLoad: false,
resultSelectMode: 'list'
};
}
componentDidUpdate(prevProps: TransferDropDownProps) {
if (this.props.value !== prevProps.value) {
this.setState({
tempValue: this.props.value
});
}
}
@autobind
handleAfterPopoverHide() {
this.setState({inputValue: '', searchResult: null});
}
@autobind
handleChange(value: any, onClose: () => void) {
const {multiple, onChange, useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
this.setState({tempValue: value});
} else {
onChange?.(value);
if (!multiple) {
onClose();
}
}
}
@autobind
onConfirm() {
const {onChange} = this.props;
onChange?.(this.state.tempValue as typeof Option[]);
}
render() {
const {
classnames: cx,
@ -68,6 +108,8 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
overlayWidth={overlay && overlay?.width}
align={overlay && overlay?.align}
popOverClassName={cx('TransferDropDown-popover')}
showConfirm
onConfirm={this.onConfirm}
popOverRender={({onClose}) => (
<div
className={cx('TransferDropDown-content', {
@ -96,24 +138,14 @@ export class TransferDropDown extends Transfer<TransferDropDownProps> {
{searchResult !== null
? this.renderSearchResult({
...this.props,
value,
onChange: multiple
? onChange
: (value: any) => {
onClose();
onChange?.(value);
},
value: this.state.tempValue,
onChange: value => this.handleChange(value, onClose),
multiple
})
: this.renderOptions({
...this.props,
value,
onChange: multiple
? onChange
: (value: any) => {
onClose();
onChange?.(value);
},
value: this.state.tempValue,
onChange: value => this.handleChange(value, onClose),
multiple
})}
</div>

View File

@ -6,7 +6,7 @@ import React from 'react';
import ResultBox from './ResultBox';
import {Icon} from './icons';
import PickerContainer from './PickerContainer';
import {autobind, mapTree} from 'amis-core';
import {autobind, mapTree, isMobile} from 'amis-core';
export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
// 新的属性?
@ -20,6 +20,7 @@ export interface TransferPickerProps extends Omit<TransferProps, 'itemRender'> {
onFocus?: () => void;
onBlur?: () => void;
useMobileUI?: boolean;
}
export class TransferPicker extends React.Component<TransferPickerProps> {
@ -51,17 +52,21 @@ export class TransferPicker extends React.Component<TransferPickerProps> {
size,
borderMode,
labelField = 'label',
useMobileUI,
...rest
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<PickerContainer
title={__('Select.placeholder')}
onFocus={this.onFoucs}
onClose={this.onBlur}
useMobileUI={useMobileUI}
bodyRender={({onClose, value, onChange, setState, ...states}) => {
return (
<Transfer
useMobileUI={useMobileUI}
{...rest}
{...states}
value={value}
@ -103,10 +108,13 @@ export class TransferPicker extends React.Component<TransferPickerProps> {
itemRender={option => (
<span>{(option && option[labelField]) || 'undefined'}</span>
)}
useMobileUI={useMobileUI}
>
<span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" />
</span>
{!mobileUI ? (
<span className={cx('TransferPicker-icon')}>
<Icon icon="pencil" className="icon" />
</span>
) : null}
</ResultBox>
)}
</PickerContainer>

View File

@ -32,7 +32,7 @@ import {Option, Options, value2array} from './Select';
import {themeable, ThemeProps, highlight} from 'amis-core';
import {Icon, getIcon} from './icons';
import Checkbox from './Checkbox';
import {LocaleProps, localeable} from 'amis-core';
import {LocaleProps, localeable, isMobile} from 'amis-core';
import Spinner, {SpinnerExtraProps} from './Spinner';
import {ItemRenderStates} from './Selection';
import VirtualList from './virtual-list';
@ -149,6 +149,7 @@ interface TreeSelectorProps extends ThemeProps, LocaleProps, SpinnerExtraProps {
// 全选按钮文案
checkAllLabel?: string;
enableDefaultIcon?: boolean;
useMobileUI?: boolean;
}
interface TreeSelectorState {
@ -716,12 +717,21 @@ export class TreeSelector extends React.Component<
}
renderInput(prfix: JSX.Element | null = null) {
const {classnames: cx, translate: __} = this.props;
const {classnames: cx, useMobileUI, translate: __} = this.props;
const {inputValue} = this.state;
const mobileUI = useMobileUI && isMobile();
return (
<div className={cx('Tree-itemLabel')}>
<div className={cx('Tree-itemInput')}>
<div
className={cx('Tree-itemLabel', {
'is-mobile': mobileUI
})}
>
<div
className={cx('Tree-itemInput', {
'is-mobile': mobileUI
})}
>
{prfix}
<input
onChange={this.handleInputChange}
@ -1064,9 +1074,11 @@ export class TreeSelector extends React.Component<
draggable,
loadingConfig,
enableDefaultIcon,
valueField
valueField,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
const item = this.state.flattenedOptions[index];
if (!item) {
@ -1160,7 +1172,7 @@ export class TreeSelector extends React.Component<
{checkbox}
<div className={cx('Tree-itemLabel-item')}>
<div className={cx('Tree-itemLabel-item', {'is-mobile': mobileUI})}>
{showIcon ? (
<i
className={cx(
@ -1308,7 +1320,8 @@ export class TreeSelector extends React.Component<
checkAllLabel,
classnames: cx,
translate: __,
disabled
disabled,
useMobileUI
} = this.props;
if (!multiple || !checkAll) {
@ -1323,6 +1336,7 @@ export class TreeSelector extends React.Component<
const checkedPartial = availableOptions.some(option =>
this.isItemChecked(option)
);
const mobileUI = useMobileUI && isMobile();
return (
<div
@ -1336,7 +1350,11 @@ export class TreeSelector extends React.Component<
partial={checkedPartial && !checkedAll}
/>
<div className={cx('Tree-itemLabel-item')}>
<div
className={cx('Tree-itemLabel-item', {
'is-mobile': mobileUI
})}
>
<span className={cx('Tree-itemText')}>{__(checkAllLabel)}</span>
</div>
</div>

View File

@ -14,6 +14,7 @@ import {
import {PickerOption} from '../PickerColumn';
import 'moment/locale/zh-cn';
import 'moment/locale/de';
import {isMobile} from 'amis-core';
export type DateType =
| 'year'
@ -690,6 +691,8 @@ class BaseDatePicker extends React.Component<
key="dt"
className={cx(
'rdtPicker',
{'is-mobile-year': isMobile() && viewMode === 'years'},
{'is-mobile-embed': isMobile() && viewProps.embed},
timeFormat && !dateFormat
? 'rdtPickerTimeWithoutD'
: timeFormat && dateFormat

View File

@ -683,9 +683,11 @@ export class CustomTimeView extends React.Component<
<div
key={option.value}
className={cx('CalendarInput-sugsItem', {
'is-mobile': isMobile(),
'is-highlight': selectedDate
? option.value === date.format(formatMap[type])
: option.value === options?.[0]?.value
: option.value === options?.[0]?.value &&
!isMobile()
})}
onClick={() => {
this.setTime(type, parseInt(option.value, 10));

View File

@ -22,6 +22,7 @@ import FuncList from './FuncList';
import VariableList from './VariableList';
import CodeMirrorEditor from '../CodeMirror';
import {toast} from '../Toast';
import {isMobile} from 'amis-core';
export interface VariableItem {
label: string;
@ -358,7 +359,9 @@ export class FormulaEditor extends React.Component<
/>
</section>
<section className={cx('FormulaEditor-settings')}>
<section
className={cx('FormulaEditor-settings', {'is-mobile': isMobile()})}
>
<div className={cx('FormulaEditor-panel')}>
{variableMode !== 'tabs' ? (
<div className={cx('FormulaEditor-panel-header')}>

View File

@ -44,7 +44,7 @@ export function FuncList(props: FuncListProps) {
<div className={cx('FormulaEditor-panel-header')}>{title}</div>
<div className={cx('FormulaEditor-panel-body')}>
<div className={cx('FormulaEditor-FuncList-searchBox')}>
<SearchBox mini={false} onSearch={onSearch} />
<SearchBox mini={false} onSearch={onSearch} useMobileUI />
</div>
<div className={cx('FormulaEditor-FuncList-body', bodyClassName)}>
<CollapseGroup

View File

@ -20,7 +20,8 @@ import ResultBox from '../ResultBox';
import Button from '../Button';
import {Icon} from '../icons';
import Modal from '../Modal';
import Input from '../Input';
import PopUp from '../PopUp';
import {isMobile} from 'amis-core';
export interface FormulaPickerProps extends FormulaEditorProps {
// 新的属性?
@ -121,6 +122,7 @@ export interface FormulaPickerProps extends FormulaEditorProps {
onConfirm?: (value?: any) => void;
onRef?: (node: any) => void;
useMobileUI?: boolean;
}
export interface FormulaPickerState {
@ -330,10 +332,12 @@ export class FormulaPicker extends React.Component<
variableMode,
mixedMode,
evalMode,
useMobileUI,
...rest
} = this.props;
const {isOpened, value, editorValue, isError} = this.state;
const iconElement = generateIcon(cx, icon, 'Icon');
const mobileUI = useMobileUI && isMobile();
return (
<>
@ -408,6 +412,8 @@ export class FormulaPicker extends React.Component<
disabled={disabled}
borderMode={borderMode}
placeholder={placeholder}
useMobileUI={useMobileUI}
showArrow={false}
/>
<Button
@ -449,6 +455,8 @@ export class FormulaPicker extends React.Component<
disabled={disabled}
borderMode={borderMode}
placeholder={placeholder}
useMobileUI={useMobileUI}
showArrow={false}
/>
<a
@ -461,41 +469,71 @@ export class FormulaPicker extends React.Component<
)}
</div>
)}
<Modal
size="md"
closeOnEsc
show={this.state.isOpened}
onHide={this.close}
>
<Modal.Header onClose={this.close} className="font-bold">
{__(title || 'FormulaEditor.title')}
</Modal.Header>
<Modal.Body>
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}
onChange={this.handleEditorChange}
selfVariableName={this.props.selfVariableName}
/>
</Modal.Body>
<Modal.Footer>
{!!isError ? (
<div className={cx('Dialog-info')} key="info">
<span className={cx('Dialog-error')}>
{__('FormulaEditor.invalidData', {err: isError})}
</span>
</div>
) : null}
<Button onClick={this.close}>{__('cancel')}</Button>
<Button onClick={this.handleEditorConfirm} level="primary">
{__('confirm')}
</Button>
</Modal.Footer>
</Modal>
{mobileUI ? (
<PopUp
className={cx(`FormulaPicker-popup`)}
isShow={this.state.isOpened}
showConfirm
onHide={this.close}
onConfirm={this.handleEditorConfirm}
>
<div className={cx('FormulaPicker-popup-inner')}>
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}
onChange={this.handleEditorChange}
selfVariableName={this.props.selfVariableName}
/>
{!!isError ? (
<div className={cx('Dialog-info')} key="info">
<span className={cx('Dialog-error')}>
{__('FormulaEditor.invalidData', {err: isError})}
</span>
</div>
) : null}
</div>
</PopUp>
) : (
<Modal
size="md"
closeOnEsc
show={this.state.isOpened}
onHide={this.close}
>
<Modal.Header onClose={this.close} className="font-bold">
{__(title || 'FormulaEditor.title')}
</Modal.Header>
<Modal.Body>
<Editor
{...rest}
evalMode={mixedMode ? true : evalMode}
variables={this.state.variables ?? variables}
functions={this.state.functions ?? functions}
variableMode={this.state.variableMode ?? variableMode}
value={editorValue}
onChange={this.handleEditorChange}
selfVariableName={this.props.selfVariableName}
/>
</Modal.Body>
<Modal.Footer>
{!!isError ? (
<div className={cx('Dialog-info')} key="info">
<span className={cx('Dialog-error')}>
{__('FormulaEditor.invalidData', {err: isError})}
</span>
</div>
) : null}
<Button onClick={this.close}>{__('cancel')}</Button>
<Button onClick={this.handleEditorConfirm} level="primary">
{__('confirm')}
</Button>
</Modal.Footer>
</Modal>
)}
</>
);
}

View File

@ -82,7 +82,7 @@ function VariableList(props: VariableListProps) {
function renderSearchBox() {
return (
<div className={cx('FormulaEditor-VariableList-searchBox')}>
<SearchBox mini={false} onSearch={onSearch} />
<SearchBox mini={false} onSearch={onSearch} useMobileUI />
</div>
);
}

View File

@ -4,6 +4,7 @@ import Button from '../Button';
import {Icon} from '../icons';
import type {InputJSONSchemaItemProps} from './index';
import {InputJSONSchemaItem} from './Item';
import {isMobile} from 'amis-core';
type JSONSchemaArrayMember = {
key: string;
@ -19,7 +20,8 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
disabled,
translate: __,
collapsable,
renderValue
renderValue,
useMobileUI
} = props;
const buildMembers = React.useCallback((schema: any, value: any) => {
const members: Array<JSONSchemaArrayMember> = [];
@ -137,6 +139,7 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
// todo additionalProperties 还有其他格式
const allowAdd = !maxContains || maxContains > members.length;
const allowDelete = !minContains || minContains < members.length;
const mobileUI = useMobileUI && isMobile();
return (
<>
@ -153,6 +156,7 @@ export function InputJSONSchemaArray(props: InputJSONSchemaItemProps) {
<div
className={cx('JSONSchemaObject', {
'is-mobile': mobileUI,
'is-expanded': collapsable && !collapsed
})}
>

View File

@ -20,6 +20,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
value={props.value}
onChange={props.onChange}
placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/>
);
} else if (schema.type == 'integer') {
@ -29,6 +30,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
onChange={props.onChange}
precision={0}
placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/>
);
} else if (schema.type == 'boolean') {
@ -42,6 +44,7 @@ export function InputJSONSchemaItem(props: InputJSONSchemaItemProps) {
value={props.value}
onChange={props.onChange}
placeholder={props.placeholder}
useMobileUI={props.useMobileUI}
/>
);
}

View File

@ -7,6 +7,7 @@ import InputBoxWithSuggestion from '../InputBoxWithSuggestion';
import Select from '../Select';
import type {InputJSONSchemaItemProps} from './index';
import {InputJSONSchemaItem} from './Item';
import {isMobile} from 'amis-core';
type JSONSchemaObjectMember = {
key: string;
@ -25,7 +26,8 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
translate: __,
renderKey,
collapsable,
renderValue
renderValue,
useMobileUI
} = props;
const buildMembers = React.useCallback((schema: any, value: any) => {
const members: Array<JSONSchemaObjectMember> = [];
@ -189,6 +191,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
options.every(o => members.find(m => m.name === o.value))
);
const allowInput = props.schema.additionalProperties !== false;
const mobileUI = useMobileUI && isMobile();
return (
<>
@ -205,6 +208,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
<div
className={cx('JSONSchemaObject', {
'is-mobile': mobileUI,
'is-expanded': collapsable && !collapsed
})}
>
@ -228,7 +232,11 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
return (
<div key={member.key} className={cx('JSONSchemaMember')}>
<div className={cx('JSONSchemaMember-key')}>
<div
className={cx('JSONSchemaMember-key', {
'is-mobile': mobileUI
})}
>
{member.nameMutable ? (
<>
{renderKey ? (
@ -247,6 +255,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
clearable={false}
placeholder={__('JSONSchema.key')}
options={filtedOptions}
useMobileUI={useMobileUI}
/>
) : (
<Select
@ -258,6 +267,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
clearable={false}
placeholder={__('JSONSchema.key')}
options={filtedOptions}
useMobileUI={useMobileUI}
/>
)
) : (
@ -267,6 +277,7 @@ export function InputJSONSchemaObject(props: InputJSONSchemaItemProps) {
onChange={onMemberKeyChange.bind(null, member)}
clearable={false}
placeholder={__('JSONSchema.key')}
useMobileUI={useMobileUI}
/>
)}
</>

View File

@ -29,6 +29,7 @@ export interface InputJSONSchemaItemProps extends ThemeProps, LocaleProps {
) => JSX.Element;
collapsable?: boolean;
placeholder?: string;
useMobileUI?: boolean;
}
export interface InputJSONSchemaProps

View File

@ -41,7 +41,8 @@ export class SchemaEditorItemArray extends SchemaEditorItemCommon {
onTypeChange,
enableAdvancedSetting,
popOverContainer,
placeholder
placeholder,
useMobileUI
} = this.props;
const items = value?.items || {
type: 'string'
@ -73,6 +74,7 @@ export class SchemaEditorItemArray extends SchemaEditorItemCommon {
enableAdvancedSetting={enableAdvancedSetting}
popOverContainer={popOverContainer}
placeholder={placeholder}
useMobileUI={useMobileUI}
/>
</div>
);

View File

@ -54,6 +54,7 @@ export interface SchemaEditorItemCommonProps extends LocaleProps, ThemeProps {
/** 各属性输入控件的placeholder */
placeholder?: SchemaEditorItemPlaceholder;
popOverContainer?: any;
useMobileUI?: boolean;
}
export class SchemaEditorItemCommon<
@ -102,7 +103,8 @@ export class SchemaEditorItemCommon<
prefix,
affix,
types,
placeholder
placeholder,
useMobileUI
} = this.props;
return (
@ -118,6 +120,7 @@ export class SchemaEditorItemCommon<
clearable={false}
disabled={disabled || typeMutable === false}
simpleValue
useMobileUI={useMobileUI}
/>
) : null}
@ -135,6 +138,7 @@ export class SchemaEditorItemCommon<
{enableAdvancedSetting ? (
<PickerContainer
useMobileUI={useMobileUI}
value={value}
bodyRender={({isOpened, value, onChange, ref}) => {
return isOpened ? (
@ -152,6 +156,7 @@ export class SchemaEditorItemCommon<
{...field}
disabled={disabled}
placeholder={__(placeholder?.title ?? '')}
useMobileUI={useMobileUI}
/>
)}
/>
@ -164,6 +169,7 @@ export class SchemaEditorItemCommon<
<Textarea
{...field}
disabled={disabled}
useMobileUI={useMobileUI}
placeholder={__(placeholder?.description ?? '')}
/>
)}
@ -178,6 +184,7 @@ export class SchemaEditorItemCommon<
{...field}
disabled={disabled}
placeholder={__(placeholder?.default ?? '')}
useMobileUI={useMobileUI}
/>
)}
/>

View File

@ -7,7 +7,9 @@ import {SchemaEditorItemObject} from './Object';
export interface SchemaEditorItemProps
extends SchemaEditorItemCommonProps,
LocaleProps,
ThemeProps {}
ThemeProps {
useMobileUI?: boolean;
}
export class SchemaEditorItem extends React.Component<SchemaEditorItemProps> {
render() {

View File

@ -199,7 +199,8 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onTypeChange,
enableAdvancedSetting,
popOverContainer,
placeholder
placeholder,
useMobileUI
} = this.props;
const members = this.state.members;
@ -212,6 +213,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
{members.length ? (
members.map((member, index) => (
<SchemaEditorItem
useMobileUI={useMobileUI}
key={member.id}
types={types}
onTypeChange={onTypeChange}
@ -226,6 +228,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onChange={this.handlePropKeyChange.bind(this, index)}
placeholder={__(placeholder?.key ?? '')}
disabled={disabled || !!value?.$ref}
useMobileUI={useMobileUI}
/>
<InputBox
@ -234,6 +237,7 @@ export class SchemaEditorItemObject extends SchemaEditorItemCommon<
onChange={this.handlePropTitleChange.bind(this, index)}
placeholder={__(placeholder?.title ?? '')}
disabled={disabled || !!value?.$ref}
useMobileUI={useMobileUI}
/>
</>
}

View File

@ -72,6 +72,7 @@ export interface SchemaEditorProps extends LocaleProps, ThemeProps {
*
*/
placeholder?: SchemaEditorItemPlaceholder;
useMobileUI?: boolean;
}
export class SchemaEditor extends React.Component<SchemaEditorProps> {
@ -171,7 +172,8 @@ export class SchemaEditor extends React.Component<SchemaEditorProps> {
definitions,
enableAdvancedSetting,
popOverContainer,
placeholder
placeholder,
useMobileUI
} = this.props;
const value: JSONSchema = this.props.value || {
type: defaultType || 'object'
@ -231,6 +233,7 @@ export class SchemaEditor extends React.Component<SchemaEditorProps> {
enableAdvancedSetting={enableAdvancedSetting}
popOverContainer={popOverContainer}
placeholder={placeholder}
useMobileUI={useMobileUI}
/>
</div>
);

View File

@ -92,6 +92,10 @@ export interface CollapseSchema extends BaseSchema {
*
*/
unmountOnExit?: boolean;
/**
* 线
*/
divideLine?: boolean;
}
export interface CollapseProps
@ -147,9 +151,13 @@ export default class Collapse extends React.Component<CollapseProps, {}> {
disabled,
collapsed,
propsUpdate,
onCollapse
onCollapse,
useMobileUI,
divideLine
} = this.props;
console.log('cooooooo', divideLine);
return (
<BasicCollapse
id={id}
@ -194,7 +202,9 @@ export default class Collapse extends React.Component<CollapseProps, {}> {
? render('body', body)
: null
}
useMobileUI={useMobileUI}
onCollapse={onCollapse}
divideLine={divideLine}
></BasicCollapse>
);
}

View File

@ -60,7 +60,8 @@ export class CollapseGroupRender extends React.Component<
body,
className,
style,
render
render,
useMobileUI
} = this.props;
return (
<CollapseGroup
@ -70,6 +71,7 @@ export class CollapseGroupRender extends React.Component<
expandIconPosition={expandIconPosition}
className={className}
style={style}
useMobileUI={useMobileUI}
>
{render('body', body || '')}
</CollapseGroup>

View File

@ -46,6 +46,7 @@ import {
} from '../../Schema';
import {ListenerAction} from 'amis-core';
import type {SchemaTokenizeableString} from '../../Schema';
import {isMobile} from 'amis-core';
export type ComboCondition = {
test: string;
@ -1260,9 +1261,12 @@ export default class ComboControl extends React.Component<ComboProps> {
itemRemovableOn,
disabled,
removable,
deleteBtn
deleteBtn,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
const finnalRemovable =
store.removable !== false && // minLength ?
!disabled && // 控件自身是否禁用
@ -1330,7 +1334,7 @@ export default class ComboControl extends React.Component<ComboProps> {
onClick={this.deleteItem.bind(this, index)}
key="delete"
className={cx(`Combo-delBtn ${!store.removable ? 'is-disabled' : ''}`)}
data-tooltip={__('delete')}
data-tooltip={!mobileUI ? __('delete') : null}
data-position="bottom"
>
{deleteIcon ? (
@ -1435,11 +1439,13 @@ export default class ComboControl extends React.Component<ComboProps> {
translate: __,
itemClassName,
itemsWrapperClassName,
static: isStatic
static: isStatic,
useMobileUI
} = this.props;
let items = this.props.items;
let value = this.props.value;
const mobileUI = useMobileUI && isMobile();
if (flat && typeof value === 'string') {
value = value.split(delimiter || ',');
@ -1449,6 +1455,9 @@ export default class ComboControl extends React.Component<ComboProps> {
<div
className={cx(
`Combo Combo--multi`,
{
'is-mobile': mobileUI
},
multiLine ? `Combo--ver` : `Combo--hor`,
noBorder ? `Combo--noBorder` : '',
disabled ? 'is-disabled' : '',
@ -1564,9 +1573,11 @@ export default class ComboControl extends React.Component<ComboProps> {
typeSwitchable,
nullable,
translate: __,
itemClassName
itemClassName,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
let items = this.props.items;
const data = isObject(value) ? this.formatValue(value) : this.defaultValue;
let condition: ComboCondition | null = null;
@ -1580,6 +1591,9 @@ export default class ComboControl extends React.Component<ComboProps> {
<div
className={cx(
`Combo Combo--single`,
{
'is-mobile': mobileUI
},
multiLine ? `Combo--ver` : `Combo--hor`,
noBorder ? `Combo--noBorder` : '',
disabled ? 'is-disabled' : ''

View File

@ -196,7 +196,8 @@ export class InputFormulaRenderer extends React.Component<InputFormulaProps> {
functionClassName,
data,
onPickerOpen,
selfVariableName
selfVariableName,
useMobileUI
} = this.props;
let {variables, functions} = this.props;
@ -238,6 +239,7 @@ export class InputFormulaRenderer extends React.Component<InputFormulaProps> {
onPickerOpen={onPickerOpen}
selfVariableName={selfVariableName}
mixedMode={mixedMode}
useMobileUI={useMobileUI}
/>
);
}

View File

@ -9,6 +9,7 @@ import {
anyChanged
} from 'amis-core';
import {FormBaseControlSchema, SchemaCollection} from '../../Schema';
import {isMobile} from 'amis-core';
/**
* InputGroup
@ -190,7 +191,8 @@ export class InputGroup extends React.Component<
data,
classnames: cx,
static: isStatic,
disabled
disabled,
useMobileUI
} = this.props;
const {errorMode} = this.getValidationConfig();
@ -218,6 +220,8 @@ export class InputGroup extends React.Component<
(formHorizontal
? makeHorizontalDeeper(formHorizontal as any, inputs.length)
: undefined);
const mobileUI = useMobileUI && isMobile();
return (
<div
className={cx(
@ -226,6 +230,9 @@ export class InputGroup extends React.Component<
className,
{
'is-focused': this.state.isFocused
},
{
'is-mobile': mobileUI
}
)}
>

View File

@ -11,7 +11,8 @@ import {
autobind,
stripNumber,
filter,
ActionObject
ActionObject,
isMobile
} from 'amis-core';
import {Range as InputRange, NumberInput, Icon} from 'amis-ui';
import {FormBaseControlSchema, SchemaObject} from '../../Schema';
@ -436,7 +437,8 @@ export class Input extends React.Component<RangeItemProps, any> {
classPrefix: ns,
disabled,
max,
min
min,
useMobileUI
} = this.props;
const _value = multiple
? type === 'min'
@ -454,6 +456,7 @@ export class Input extends React.Component<RangeItemProps, any> {
disabled={disabled}
onBlur={this.onBlur}
onFocus={this.onFocus}
useMobileUI={useMobileUI}
/>
</div>
);
@ -638,7 +641,8 @@ export default class RangeControl extends React.PureComponent<
max,
render,
marks,
region
region,
useMobileUI
} = props;
// 处理自定义json配置
@ -653,6 +657,7 @@ export default class RangeControl extends React.PureComponent<
(renderMarks[key] = render(region, item as SchemaObject));
}
});
const mobileUI = useMobileUI && isMobile();
return (
<div
@ -660,6 +665,7 @@ export default class RangeControl extends React.PureComponent<
'RangeControl',
`${ns}InputRange`,
{'is-disabled': disabled},
{'is-mobile': mobileUI},
className
)}
>

View File

@ -74,6 +74,7 @@ export default class RepeatControl extends React.Component<RepeatProps, any> {
placeholder,
disabled,
classPrefix: ns,
useMobileUI,
translate: __
} = this.props;
@ -216,6 +217,7 @@ export default class RepeatControl extends React.Component<RepeatProps, any> {
searchable={false}
disabled={disabled}
joinValues={false}
useMobileUI={useMobileUI}
/>
</div>
</div>

View File

@ -8,6 +8,9 @@ import {Icon} from 'amis-ui';
import {FormBaseControlSchema, FormSchema, SchemaClassName} from '../../Schema';
import Sortable from 'sortablejs';
import {findDOMNode} from 'react-dom';
import {isMobile} from 'amis-core';
import {PopUp} from 'amis-ui';
import {autobind} from 'amis-core';
/**
* SubForm
@ -152,6 +155,7 @@ export default class SubFormControl extends React.PureComponent<
dragTip?: HTMLElement;
sortable?: Sortable;
id: string = guid();
tempValue: any;
constructor(props: SubFormProps) {
super(props);
@ -211,6 +215,7 @@ export default class SubFormControl extends React.PureComponent<
return;
}
this.tempValue = value[index];
this.setState({
dialogData: createObject(this.props.data, value[index]),
dialogCtx: {
@ -255,6 +260,36 @@ export default class SubFormControl extends React.PureComponent<
this.close();
}
@autobind
handlePopupConfirm() {
const values = this.tempValue;
const {multiple, onChange, value} = this.props;
const ctx = this.state.dialogCtx;
if (multiple) {
let newValue = Array.isArray(value) ? value.concat() : [];
if (ctx?.mode === 'add') {
newValue.push({
...values
});
} else {
newValue[ctx!.index!] = {
...newValue[ctx!.index!],
...values
};
}
onChange(newValue);
} else {
onChange({
...value,
...values
});
}
this.close();
}
dragTipRef(ref: any) {
if (!this.dragTip && ref) {
this.initDragging();
@ -332,6 +367,28 @@ export default class SubFormControl extends React.PureComponent<
};
}
buildFormSchema() {
let {form} = this.props;
const dialogProps = [
'title',
'actions',
'name',
'size',
'closeOnEsc',
'closeOnOutside',
'showErrorMsg',
'showCloseButton',
'bodyClassName',
'type'
];
return {
type: 'form',
...omit(form, dialogProps)
};
}
renderMultipe() {
const {
addButtonClassName,
@ -502,20 +559,48 @@ export default class SubFormControl extends React.PureComponent<
}
render() {
const {multiple, classPrefix: ns, className, style, render} = this.props;
const {
multiple,
classPrefix: ns,
className,
style,
render,
useMobileUI
} = this.props;
const dialogData = this.state.dialogData;
const dialogCtx = this.state.dialogCtx;
const mobileUI = useMobileUI && isMobile();
return (
<div className={cx(`${ns}SubFormControl`, className)}>
{multiple ? this.renderMultipe() : this.renderSingle()}
{render(`modal`, this.buildDialogSchema(), {
show: !!dialogCtx,
onClose: this.close,
onConfirm: this.handleDialogConfirm,
data: dialogData,
formStore: undefined
})}
{!mobileUI ? (
render(`modal`, this.buildDialogSchema(), {
show: !!dialogCtx,
onClose: this.close,
onConfirm: this.handleDialogConfirm,
data: dialogData,
formStore: undefined
})
) : (
<PopUp
isShow={!!dialogCtx}
showConfirm
onConfirm={this.handlePopupConfirm}
onHide={this.close}
>
<div className="flex-1 pl-10 pr-10">
{render('form', this.buildFormSchema(), {
data: dialogData,
formStore: undefined,
wrapWithPanel: false,
onChange: (val: any) => {
this.tempValue = val;
}
})}
</div>
</PopUp>
)}
</div>
);
}

View File

@ -10,13 +10,14 @@ import find from 'lodash/find';
import isInteger from 'lodash/isInteger';
import unionWith from 'lodash/unionWith';
import {findDOMNode} from 'react-dom';
import {ResultBox, SpinnerExtraProps} from 'amis-ui';
import {PopUp, ResultBox, SpinnerExtraProps} from 'amis-ui';
import {autobind, filterTree, createObject} from 'amis-core';
import {Spinner} from 'amis-ui';
import {Overlay} from 'amis-core';
import {PopOver} from 'amis-core';
import {ListMenu} from 'amis-ui';
import {ListMenu, Button} from 'amis-ui';
import {ActionObject} from 'amis-core';
import {isMobile} from 'amis-core';
import {FormOptionsSchema} from '../../Schema';
import {supportStatic} from './StaticHoc';
import {TooltipWrapperSchema} from '../TooltipWrapper';
@ -98,6 +99,8 @@ export interface TagState {
inputValue: string;
isFocused?: boolean;
isOpened?: boolean;
selectedOptions: Option[];
cacheOptions: Option[];
}
export default class TagControl extends React.PureComponent<
@ -116,11 +119,16 @@ export default class TagControl extends React.PureComponent<
separator: '-'
};
state = {
isOpened: false,
inputValue: '',
isFocused: false
};
constructor(props: TagProps) {
super(props);
this.state = {
isOpened: false,
inputValue: '',
isFocused: false,
selectedOptions: props.selectedOptions,
cacheOptions: []
};
}
componentDidUpdate(prevProps: TagProps) {
const props = this.props;
@ -242,9 +250,15 @@ export default class TagControl extends React.PureComponent<
}
@autobind
getValue(type: 'push' | 'pop' | 'normal' = 'normal', option: any = {}) {
const {selectedOptions, joinValues, extractValue, delimiter, valueField} =
this.props;
getValue(
type: 'push' | 'pop' | 'normal' = 'normal',
option: any = {},
selectedOptions?: Option[]
) {
const {joinValues, extractValue, delimiter, valueField} = this.props;
selectedOptions = selectedOptions
? selectedOptions
: this.props.selectedOptions;
const newValue = selectedOptions.concat();
if (type === 'push') {
@ -286,11 +300,84 @@ export default class TagControl extends React.PureComponent<
isPrevented || onChange(newValueRes);
}
// 移动端特殊处理
addItem2(option: Option) {
const {useMobileUI, valueField = 'value'} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
const selectedOptions = this.state.selectedOptions.concat();
let index = selectedOptions.findIndex(
item => item[valueField] === option[valueField]
);
if (~index) {
selectedOptions.splice(index, 1);
} else if (!this.isReachMaxFromState()) {
selectedOptions.push(option);
}
this.setState({
selectedOptions
});
}
}
// 手机端校验
isExist(inputValue: string) {
const {options, valueField = 'value'} = this.props;
const {cacheOptions} = this.state;
return (
options.some(item => item[valueField] === inputValue) ||
cacheOptions.some(item => item[valueField] === inputValue)
);
}
@autobind
addSelection() {
let {inputValue} = this.state;
const {maxTagLength} = this.props;
const selectedOptions = this.state.selectedOptions.slice();
const cacheOptions = this.state.cacheOptions.slice();
if (maxTagLength !== undefined) {
inputValue = inputValue.trim();
inputValue = inputValue.slice(0, maxTagLength);
}
if (this.isExist(inputValue)) {
return;
}
if (inputValue && !this.isReachMaxFromState()) {
const addedValues = this.normalizeInputValue(inputValue);
selectedOptions.push(addedValues[0]);
cacheOptions.push(addedValues[0]);
this.setState({
inputValue: '',
selectedOptions,
cacheOptions
});
}
}
@autobind
async onConfirm() {
const {selectedOptions} = this.state;
const {onChange} = this.props;
const newValueRes = this.getValue('normal', {}, selectedOptions);
const isPrevented = await this.dispatchEvent('change', {
value: newValueRes,
selectedItems: selectedOptions
});
isPrevented || onChange(newValueRes);
this.close();
}
@autobind
async handleFocus(e: any) {
this.setState({
isFocused: true,
isOpened: true
isOpened: true,
selectedOptions: this.props.selectedOptions
});
const newValueRes = this.getValue('normal');
@ -303,8 +390,11 @@ export default class TagControl extends React.PureComponent<
@autobind
async handleBlur(e: any) {
const {selectedOptions, onChange} = this.props;
const {selectedOptions, onChange, useMobileUI, options} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI && options.length) {
return;
}
const value = this.state.inputValue.trim();
if (!this.validateInputValue(value)) {
@ -420,6 +510,12 @@ export default class TagControl extends React.PureComponent<
@autobind
handleOptionChange(option: Option) {
const {useMobileUI} = this.props;
const mobileUI = useMobileUI && isMobile();
if (mobileUI) {
this.addItem2(option);
return;
}
if (this.isReachMax() || this.state.inputValue || !option) {
return;
}
@ -448,6 +544,13 @@ export default class TagControl extends React.PureComponent<
return max != null && isInteger(max) && selectedOptions.length >= max;
}
@autobind
isReachMaxFromState() {
const {selectedOptions} = this.state;
const {max} = this.props;
return max != null && isInteger(max) && selectedOptions.length >= max;
}
@supportStatic()
render() {
const {
@ -468,16 +571,17 @@ export default class TagControl extends React.PureComponent<
overflowTagPopover,
translate: __,
loadingConfig,
valueField
valueField,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
const finnalOptions = Array.isArray(options)
? filterTree(
options,
item =>
(Array.isArray(item.children) && !!item.children.length) ||
(item[valueField || 'value'] !== undefined &&
!~selectedOptions.indexOf(item)),
(mobileUI || !~selectedOptions.indexOf(item))),
0,
true
)
@ -488,7 +592,7 @@ export default class TagControl extends React.PureComponent<
return (
<Downshift
selectedItem={selectedOptions}
isOpen={this.state.isFocused}
isOpen={mobileUI ? this.state.isOpened : this.state.isFocused}
inputValue={this.state.inputValue}
onChange={this.handleOptionChange}
itemToString={this.renderItem}
@ -517,7 +621,8 @@ export default class TagControl extends React.PureComponent<
clearable={clearable}
maxTagCount={maxTagCount}
overflowTagPopover={overflowTagPopover}
allowInput
allowInput={!mobileUI || (mobileUI && !options?.length)}
useMobileUI={useMobileUI}
>
{loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" />
@ -525,40 +630,96 @@ export default class TagControl extends React.PureComponent<
</ResultBox>
{dropdown !== false ? (
<Overlay
container={popOverContainer || this.getParent}
target={this.getTarget}
placement={'auto'}
show={isOpen && !!finnalOptions.length}
>
<PopOver
overlay
className={cx('TagControl-popover')}
mobileUI ? (
<PopUp
className={cx(`Tag-popup`)}
container={popOverContainer}
isShow={isOpen && !!finnalOptions.length}
showConfirm={true}
onConfirm={this.onConfirm}
onHide={this.close}
>
<ListMenu
options={finnalOptions}
itemRender={this.renderItem}
highlightIndex={highlightedIndex}
getItemProps={({
item,
index
}: {
item: Option;
index: number;
}) => ({
...getItemProps({
index,
<div>
<ListMenu
selectedOptions={selectedOptions}
useMobileUI={useMobileUI}
options={finnalOptions.concat(this.state.cacheOptions)}
itemRender={this.renderItem}
highlightIndex={highlightedIndex}
getItemProps={({
item,
disabled: reachMax || item.disabled,
className: cx('ListMenu-item', {
'is-disabled': reachMax
index
}: {
item: Option;
index: number;
}) => ({
...getItemProps({
index,
item,
className: cx('ListMenu-item', {
'is-active': ~(
this.state.selectedOptions.map(
item => item[valueField]
) || []
).indexOf(item[valueField])
})
})
})
})}
/>
</PopOver>
</Overlay>
})}
/>
{mobileUI && !this.isReachMaxFromState() ? (
<div className={cx('ListMenu-add-wrap')}>
<ResultBox
placeholder={__('SubForm.add')}
allowInput
value={this.state.inputValue}
useMobileUI={useMobileUI}
clearable
maxTagCount={maxTagCount}
onChange={value => {
this.setState({inputValue: value});
}}
onBlur={this.addSelection}
></ResultBox>
</div>
) : null}
</div>
</PopUp>
) : (
<Overlay
container={popOverContainer || this.getParent}
target={this.getTarget}
placement={'auto'}
show={isOpen && !!finnalOptions.length}
>
<PopOver
overlay
className={cx('TagControl-popover')}
onHide={this.close}
>
<ListMenu
options={finnalOptions}
itemRender={this.renderItem}
highlightIndex={highlightedIndex}
getItemProps={({
item,
index
}: {
item: Option;
index: number;
}) => ({
...getItemProps({
index,
item,
disabled: reachMax || item.disabled,
className: cx('ListMenu-item', {
'is-disabled': reachMax
})
})
})}
/>
</PopOver>
</Overlay>
)
) : (
// 保留原来的展现方式,不推荐
<div className={cx('TagControl-sug')}>

View File

@ -173,6 +173,7 @@ export interface TreeProps
SpinnerExtraProps {
enableNodePath?: boolean;
pathSeparator?: string;
useMobileUI?: boolean;
}
interface TreeState {
@ -377,7 +378,8 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
enableDefaultIcon,
searchable,
searchConfig = {},
heightAuto
heightAuto,
useMobileUI
} = this.props;
let {highlightTxt} = this.props;
const {filteredOptions, keyword} = this.state;
@ -439,6 +441,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined}
itemRender={menuTpl ? this.renderOptionItem : undefined}
enableDefaultIcon={enableDefaultIcon}
useMobileUI={useMobileUI}
/>
);
@ -467,6 +470,7 @@ export default class TreeControl extends React.Component<TreeProps, TreeState> {
clearable={true}
{...omit(searchConfig, 'className', 'sticky')}
onSearch={this.handleSearch}
useMobileUI={useMobileUI}
/>
{TreeCmpt}
</>

View File

@ -138,11 +138,12 @@ export default class JSONSchemaEditorControl extends React.PureComponent<JSONSch
}
render() {
const {enableAdvancedSetting, env, ...rest} = this.props;
const {enableAdvancedSetting, useMobileUI, env, ...rest} = this.props;
return (
<JSONSchemaEditor
{...rest}
useMobileUI={useMobileUI}
placeholder={this.normalizePlaceholder()}
enableAdvancedSetting={enableAdvancedSetting}
renderModalProps={this.renderModalProps}

View File

@ -946,7 +946,7 @@ export default class NestedSelectControl extends React.Component<
onKeyDown={this.handleInputKeyDown}
clearable={clearable}
hasDropDownArrow={true}
allowInput={searchable}
allowInput={searchable && !mobileUI}
>
{loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" />

View File

@ -23,8 +23,10 @@ import {
resolveEventData
} from 'amis-core';
import {Html, Icon} from 'amis-ui';
import {isMobile} from 'amis-core';
import {FormOptionsSchema, SchemaTpl} from '../../Schema';
import intersectionWith from 'lodash/intersectionWith';
import {PopUp} from 'amis-ui';
/**
* Picker
@ -521,11 +523,14 @@ export default class PickerControl extends React.PureComponent<
translate: __,
popOverContainer,
modalTitle,
data
data,
useMobileUI
} = this.props;
const mobileUI = useMobileUI && isMobile();
return (
<div className={cx(`PickerControl`, className)}>
<div className={cx(`PickerControl`, {'is-mobile': mobileUI}, className)}>
{embed ? (
<div className={cx('Picker')}>
{this.renderBody({popOverContainer})}
@ -556,6 +561,7 @@ export default class PickerControl extends React.PureComponent<
onKeyDown={this.handleKeyDown}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
readOnly={mobileUI}
/>
</div>

View File

@ -302,7 +302,8 @@ export class TabsTransferRenderer extends BaseTabsTransferRenderer<TabsTransferP
loadingConfig,
valueField = 'value',
labelField = 'label',
data
data,
useMobileUI
} = this.props;
return (
@ -333,6 +334,7 @@ export class TabsTransferRenderer extends BaseTabsTransferRenderer<TabsTransferP
labelField={labelField}
valueField={valueField}
ctx={data}
useMobileUI={useMobileUI}
/>
<Spinner

View File

@ -106,7 +106,8 @@ export class TabsTransferPickerRenderer extends BaseTabsTransferRenderer<TabsTra
virtualThreshold,
loadingConfig,
labelField = 'label',
valueField = 'value'
valueField = 'value',
useMobileUI
} = this.props;
return (
@ -139,6 +140,7 @@ export class TabsTransferPickerRenderer extends BaseTabsTransferRenderer<TabsTra
virtualThreshold={virtualThreshold}
labelField={labelField}
valueField={valueField}
useMobileUI={useMobileUI}
/>
<Spinner

View File

@ -532,7 +532,8 @@ export class BaseTransferRenderer<
itemHeight,
loadingConfig,
showInvalidMatch,
onlyChildren
onlyChildren,
useMobileUI
} = this.props;
// 目前 LeftOptions 没有接口可以动态加载
@ -593,6 +594,7 @@ export class BaseTransferRenderer<
}
loadingConfig={loadingConfig}
showInvalidMatch={showInvalidMatch}
useMobileUI={useMobileUI}
/>
<Spinner

View File

@ -85,7 +85,8 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
virtualThreshold,
loadingConfig,
labelField = 'label',
valueField = 'value'
valueField = 'value',
useMobileUI
} = this.props;
// 目前 LeftOptions 没有接口可以动态加载
@ -135,6 +136,7 @@ export class TransferPickerRenderer extends BaseTransferRenderer<TabsTransferPro
toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined
}
virtualThreshold={virtualThreshold}
useMobileUI={useMobileUI}
/>
<Spinner

View File

@ -135,6 +135,7 @@ export interface TreeSelectProps
export interface TreeSelectState {
isOpened: boolean;
inputValue: string;
tempValue: string;
}
export default class TreeSelectControl extends React.Component<
@ -182,12 +183,15 @@ export default class TreeSelectControl extends React.Component<
this.state = {
inputValue: '',
tempValue: '',
isOpened: false
};
this.open = this.open.bind(this);
this.close = this.close.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleTempChange = this.handleTempChange.bind(this);
this.handleConfirm = this.handleConfirm.bind(this);
this.clearValue = this.clearValue.bind(this);
this.handleFocus = this.handleFocus.bind(this);
this.handleBlur = this.handleBlur.bind(this);
@ -316,6 +320,22 @@ export default class TreeSelectControl extends React.Component<
);
}
handleTempChange(value: any) {
this.setState({
tempValue: value
});
}
handleConfirm() {
this.close();
this.setState(
{
inputValue: ''
},
() => this.resultChangeEvent(this.state.tempValue)
);
}
handleInputChange(value: string) {
const {autoComplete, data} = this.props;
@ -582,13 +602,15 @@ export default class TreeSelectControl extends React.Component<
virtualThreshold,
itemHeight,
menuTpl,
enableDefaultIcon
enableDefaultIcon,
useMobileUI
} = this.props;
let filtedOptions =
!isEffectiveApi(autoComplete) && searchable && this.state.inputValue
? this.filterOptions(options, this.state.inputValue)
: options;
const mobileUI = useMobileUI && isMobile();
return (
<TreeSelector
@ -599,7 +621,7 @@ export default class TreeSelectControl extends React.Component<
labelField={labelField}
valueField={valueField}
disabled={disabled}
onChange={this.handleChange}
onChange={mobileUI ? this.handleTempChange : this.handleChange}
joinValues={joinValues}
extractValue={extractValue}
delimiter={delimiter}
@ -644,6 +666,7 @@ export default class TreeSelectControl extends React.Component<
itemHeight={toNumber(itemHeight) > 0 ? toNumber(itemHeight) : undefined}
itemRender={menuTpl ? this.renderOptionItem : undefined}
enableDefaultIcon={enableDefaultIcon}
useMobileUI={useMobileUI}
/>
);
}
@ -711,8 +734,10 @@ export default class TreeSelectControl extends React.Component<
onBlur={this.handleBlur}
onKeyDown={this.handleInputKeyDown}
clearable={clearable}
allowInput={searchable || isEffectiveApi(autoComplete)}
allowInput={!mobileUI && (searchable || isEffectiveApi(autoComplete))}
hasDropDownArrow
readOnly={mobileUI}
useMobileUI
>
{loading ? (
<Spinner loadingConfig={loadingConfig} size="sm" />
@ -745,6 +770,8 @@ export default class TreeSelectControl extends React.Component<
className={cx(`${ns}TreeSelect-popup`)}
isShow={isOpened}
onHide={this.close}
showConfirm
onConfirm={this.handleConfirm}
>
{this.renderOuter()}
</PopUp>

View File

@ -196,7 +196,8 @@ export class SearchBoxRenderer extends React.Component<
placeholder,
onChange,
className,
style
style,
useMobileUI
} = this.props;
const value = this.state.value;
@ -221,6 +222,7 @@ export class SearchBoxRenderer extends React.Component<
onChange={this.handleChange}
onFocus={() => this.dispatchEvent('focus')}
onBlur={() => this.dispatchEvent('blur')}
useMobileUI={useMobileUI}
/>
);
}

View File

@ -63,6 +63,7 @@ import {getStyleNumber} from 'amis-core';
import {exportExcel} from './exportExcel';
import type {IColumn, IRow} from 'amis-core';
import intersection from 'lodash/intersection';
import {isMobile} from 'amis-core';
/**
*
@ -3038,7 +3039,8 @@ export default class Table extends React.Component<TableProps, object> {
classnames: cx,
affixColumns,
autoFillHeight,
autoGenerateFilter
autoGenerateFilter,
useMobileUI
} = this.props;
this.renderedToolbars = []; // 用来记录哪些 toolbar 已经渲染了,已经渲染了就不重复渲染了。
@ -3048,10 +3050,11 @@ export default class Table extends React.Component<TableProps, object> {
const tableClassName = cx('Table-table', this.props.tableClassName, {
'Table-table--withCombine': store.combineNum > 0
});
const mobileUI = useMobileUI && isMobile();
return (
<div
className={cx('Table', className, {
className={cx('Table', {'is-mobile': mobileUI}, className, {
'Table--unsaved': !!store.modified || !!store.moved,
'Table--autoFillHeight': autoFillHeight
})}

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