refactor(form-item): feedback transition

This commit is contained in:
07akioni 2020-10-08 00:06:57 +08:00
parent 37c160d21d
commit 4153980d8b
23 changed files with 265 additions and 206 deletions

View File

@ -1 +1,2 @@
**/*.js
**/*.js
**/*.md

View File

@ -14,7 +14,7 @@ import { h, resolveComponent } from 'vue'
const createColumns = instance => {
return [
{
title: 'Name111',
title: 'Name',
key: 'name',
width: '15%'
},

View File

@ -0,0 +1,83 @@
# Height Debug
Buggy!
```html
<n-form
:model="formValue"
:rules="rules"
:size="size"
ref="form"
>
<n-form-item label="姓名" path="user.name">
<n-input v-model:value="formValue.user.name" placeholder="输入姓名" />
</n-form-item>
<n-form-item label="电话号码" path="phone">
<n-input placeholder="电话号码" v-model:value="formValue.phone"/>
</n-form-item>
<n-form-item>
<n-button @click="handleValidateClick" attr-type="button">验证</n-button>
</n-form-item>
</n-form>
<pre>
{{ JSON.stringify(formValue, 0, 2) }}
</pre>
```
```js
export default {
inject: ['message'],
data () {
return {
size: 'medium',
formValue: {
user: {
name: '',
age: ''
},
phone: ''
},
rules: {
user: {
name: [
{
required: true,
message: 'required',
trigger: 'input'
},
{
message: 'not a',
trigger: 'input',
validator (rule, value) {
return value === 'x'
}
},
{
message: 'not b',
trigger: 'input',
validator (rule, value) {
return value === 'x'
}
}
]
},
phone: {
required: true,
message: '请输入电话号码',
trigger: ['input']
}
}
}
},
methods: {
handleValidateClick (e) {
this.$refs.form.validate(errors => {
if (!errors) {
this.message.success('Valid')
} else {
console.log(errors)
this.message.error('Invalid')
}
})
}
}
}
```

View File

@ -10,6 +10,7 @@ custom-validation
top
left
async
height-debug
validator-debug
```
## Props

View File

@ -1,6 +1,6 @@
{
"name": "naive-ui",
"version": "1.5.5",
"version": "2.0.0",
"description": "A Vue UI Framework. Caring About Styles, Themed, Batteries Included, Not Rather Slow.",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -12,12 +12,12 @@
:value="number"
/>
</transition-group>
<fade-in-expand-transition key="+" mode="width">
<n-fade-in-expand-transition key="+" width>
<slot-machine-number
v-if="max && max < value"
:value="'+'"
/>
</fade-in-expand-transition>
</n-fade-in-expand-transition>
</span>
<span
v-else
@ -28,7 +28,7 @@
</template>
<script>
import FadeInExpandTransition from '../../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../../_transition/FadeInExpandTransition'
import SlotMachineNumber from './SlotMachineNumber.vue'
import usecssr from '../../../_mixins/usecssr.js'
import styles from './styles/index.js'
@ -36,7 +36,7 @@ import styles from './styles/index.js'
export default {
name: 'BaseSlotMachine',
components: {
FadeInExpandTransition,
NFadeInExpandTransition,
SlotMachineNumber
},
mixins: [

View File

@ -5,12 +5,15 @@ const {
cubicBezierEaseInOut
} = commonVariables
export default function ({
name = 'fade-down',
fromOffset = '-4px',
enterCubicBezier = cubicBezierEaseInOut,
leaveCubicBezier = cubicBezierEaseInOut
} = {}) {
export default function (options = {}) {
const {
name = 'fade-down',
fromOffset = '-4px',
enterDuration = '.3s',
leaveDuration = '.3s',
enterCubicBezier = cubicBezierEaseInOut,
leaveCubicBezier = cubicBezierEaseInOut
} = options
return [
c(`&.${namespace}-${name}-transition-enter-from, &.${namespace}-${name}-transition-leave-to`, {
opacity: 0,
@ -21,10 +24,10 @@ export default function ({
transform: 'translateY(0)'
}),
c(`&.${namespace}-${name}-transition-leave-active`, {
transition: `opacity .3s ${leaveCubicBezier}, transform .3s ${leaveCubicBezier}`
transition: `opacity ${leaveDuration} ${leaveCubicBezier}, transform ${leaveDuration} ${leaveCubicBezier}`
}),
c(`&.${namespace}-${name}-transition-enter-active`, {
transition: `opacity .3s ${enterCubicBezier}, transform .3s ${enterCubicBezier}`
transition: `opacity ${enterDuration} ${enterCubicBezier}, transform ${enterDuration} ${enterCubicBezier}`
})
]
}

View File

@ -5,14 +5,16 @@ const cubicBezierEaseInOut = commonVariables.cubicBezierEaseInOut
const cubicBezierEaseOut = commonVariables.cubicBezierEaseOut
const cubicBezierEaseIn = commonVariables.cubicBezierEaseIn
export default function ({
duration = '.3s',
originalTransition = '',
leavingDelay = '0s',
foldPadding = false,
enterToProps = null,
leaveToProps = null
} = {}) {
export default function (options = {}) {
const {
overflow = 'hidden',
duration = '.3s',
originalTransition = '',
leavingDelay = '0s',
foldPadding = false,
enterToProps = null,
leaveToProps = null
} = options
return [
c(`&.${namespace}-fade-in-height-expand-transition-leave-from, &.${namespace}-fade-in-height-expand-transition-enter-to`, {
...enterToProps,
@ -28,7 +30,7 @@ export default function ({
}),
c(`&.${namespace}-fade-in-height-expand-transition-leave-active`, {
raw: `
overflow: hidden;
overflow: ${overflow};
transition:
max-height ${duration} ${cubicBezierEaseInOut} ${leavingDelay},
opacity ${duration} ${cubicBezierEaseOut} ${leavingDelay},
@ -41,7 +43,7 @@ export default function ({
}),
c(`&.${namespace}-fade-in-height-expand-transition-enter-active`, {
raw: `
overflow: hidden;
overflow: ${overflow};
transition:
max-height ${duration} ${cubicBezierEaseInOut},
opacity ${duration} ${cubicBezierEaseIn},

View File

@ -1,4 +1,4 @@
import { h, Transition } from 'vue'
import { h, Transition, TransitionGroup } from 'vue'
export default {
name: 'FadeInExpandTransition',
@ -7,30 +7,26 @@ export default {
type: Boolean,
default: false
},
group: {
type: Boolean,
default: false
},
mode: {
validator (value) {
return ['width', 'height'].includes(value)
},
default: 'height'
type: String,
default: undefined
},
onAfterLeave: {
type: Function,
default: undefined
},
// deprecated
width: {
type: Boolean,
default: false
}
},
computed: {
compitableMode () {
return this.width ? 'width' : this.mode
}
},
methods: {
handleBeforeLeave (el) {
if (this.compitableMode === 'width') {
if (this.width) {
el.style.maxWidth = el.offsetWidth + 'px'
} else {
el.style.maxHeight = el.offsetHeight + 'px'
@ -38,7 +34,7 @@ export default {
void el.offsetWidth
},
handleLeave (el) {
if (this.compitableMode === 'width') {
if (this.width) {
el.style.maxWidth = 0
} else {
el.style.maxHeight = 0
@ -46,7 +42,7 @@ export default {
void el.offsetWidth
},
handleAfterLeave (el) {
if (this.compitableMode === 'width') {
if (this.width) {
el.style.maxWidth = null
} else {
el.style.maxHeight = null
@ -58,7 +54,7 @@ export default {
},
handleEnter (el) {
el.style.transition = 'none'
if (this.compitableMode === 'width') {
if (this.width) {
const memorizedWidth = el.offsetWidth
el.style.maxWidth = 0
void el.offsetWidth
@ -74,7 +70,7 @@ export default {
void el.offsetWidth
},
handleAfterEnter (el) {
if (this.compitableMode === 'width') {
if (this.width) {
el.style.maxWidth = null
} else {
el.style.maxHeight = null
@ -82,10 +78,12 @@ export default {
}
},
render () {
return h(Transition, {
name: this.compitableMode === 'width'
const type = this.group ? TransitionGroup : Transition
return h(type, {
name: this.width
? 'n-fade-in-width-expand-transition'
: 'n-fade-in-height-expand-transition',
mode: this.mode,
appear: this.appear,
onEnter: this.handleEnter,
onAfterEnter: this.handleAfterEnter,

View File

@ -1,99 +0,0 @@
import { h, TransitionGroup } from 'vue'
export default {
name: 'FadeInExpandTransitionGroup',
props: {
appear: {
type: Boolean,
default: false
},
mode: {
validator (value) {
return ['width', 'height'].includes(value)
},
default: 'height'
},
onAfterLeave: {
type: Function,
default: undefined
},
// deprecated
width: {
type: Boolean,
default: false
}
},
computed: {
compitableMode () {
return this.width ? 'width' : this.mode
}
},
methods: {
handleBeforeLeave (el) {
if (this.compitableMode === 'width') {
el.style.maxWidth = el.offsetWidth + 'px'
} else {
el.style.maxHeight = el.offsetHeight + 'px'
}
void el.offsetWidth
},
handleLeave (el) {
if (this.compitableMode === 'width') {
el.style.maxWidth = 0
} else {
el.style.maxHeight = 0
}
void el.offsetWidth
},
handleAfterLeave (el) {
if (this.compitableMode === 'width') {
el.style.maxWidth = null
} else {
el.style.maxHeight = null
}
const {
onAfterLeave
} = this
if (this.onAfterLeave) onAfterLeave()
},
handleEnter (el) {
el.style.transition = 'none'
if (this.compitableMode === 'width') {
const memorizedWidth = el.offsetWidth
el.style.maxWidth = 0
void el.offsetWidth
el.style.transition = null
el.style.maxWidth = memorizedWidth + 'px'
} else {
const memorizedHeight = el.offsetHeight
el.style.maxHeight = 0
void el.offsetWidth
el.style.transition = null
el.style.maxHeight = memorizedHeight + 'px'
}
void el.offsetWidth
},
handleAfterEnter (el) {
if (this.compitableMode === 'width') {
el.style.maxWidth = null
} else {
el.style.maxHeight = null
}
}
},
render () {
return h(TransitionGroup, {
name: this.compitableMode === 'width'
? 'n-fade-in-width-expand-transition'
: 'n-fade-in-height-expand-transition',
appear: this.appear,
onEnter: this.handleEnter,
onAfterEnter: this.handleAfterEnter,
onBeforeLeave: this.handleBeforeLeave,
onLeave: this.handleLeave,
onAfterLeave: this.handleAfterLeave
}, {
default: this.$slots.default
})
}
}

View File

@ -1,5 +1,5 @@
<template>
<fade-in-height-expand-transition @after-leave="handleAfterLeave">
<n-fade-in-expand-transition @after-leave="handleAfterLeave">
<div
v-if="visible"
class="n-alert"
@ -64,12 +64,12 @@
</div>
</div>
</div>
</fade-in-height-expand-transition>
</n-fade-in-expand-transition>
</template>
<script>
import NIcon from '../../icon'
import FadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import {
configurable,
themeable,
@ -89,7 +89,7 @@ export default {
name: 'Alert',
components: {
NIcon,
FadeInHeightExpandTransition,
NFadeInExpandTransition,
SuccessIcon,
WarningIcon,
InfoIcon,

View File

@ -31,7 +31,7 @@
>
<slot />
</div>
<n-fade-in-height-expand-transition width>
<n-fade-in-expand-transition width>
<div
v-if="(hasIcon || loading)"
class="n-button__icon"
@ -55,7 +55,7 @@
</n-icon>
</n-icon-switch-transition>
</div>
</n-fade-in-height-expand-transition>
</n-fade-in-expand-transition>
<div
v-if="!circle && $slots.default && !iconOnRight"
class="n-button__content"
@ -73,7 +73,7 @@ import {
themeable,
usecssr
} from '../../_mixins'
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import NIconSwitchTransition from '../../_transition/IconSwitchTransition'
import NBaseLoading from '../../_base/loading'
import NBaseWave from '../../_base/wave/index.js'
@ -89,7 +89,7 @@ export default {
NBaseWave,
NIcon,
NIconSwitchTransition,
NFadeInHeightExpandTransition
NFadeInExpandTransition
},
inject: {
NButtonGroup: {

View File

@ -1,5 +1,5 @@
import { h, withDirectives, vShow } from 'vue'
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
export default {
name: 'NCollapseItemContent',
@ -25,7 +25,7 @@ export default {
const directives = useVShow ? [
[vShow, show]
] : []
return h(NFadeInHeightExpandTransition, null, {
return h(NFadeInExpandTransition, null, {
default: () => (useVShow || show) ? withDirectives(
h('div', {
class: 'n-collapse-item__content-wrapper'

View File

@ -0,0 +1,36 @@
<template>
<template v-if="feedback !== undefined && feedback !== null">
<div
:key="feedback"
class="n-form-item-feedback__line"
>
{{ feedback }}
</div>
</template>
<template v-else>
<div
v-for="explain in explains"
:key="explain"
class="n-form-item-feedback__line"
>
{{ explain }}
</div>
</template>
</template>
<script>
export default {
name: 'FormItemFeedback',
props: {
explains: {
type: Array,
default: undefined
},
feedback: {
type: [String, Array],
default: undefined
}
}
}
</script>

View File

@ -37,27 +37,57 @@
class="n-form-item-feedback-wrapper"
>
<transition
name="n-form-item-feedback-transition"
name="n-fade-down-transition"
:mode="mergedValidationStatus ? 'out-in' : undefined"
>
<div
v-if="feedback !== undefined && feedback !== null"
class="n-form-item-feedback"
>
{{ feedback }}
</div>
<div v-else-if="explains.length" class="n-form-item-feedback">
<span
v-for="(explain, i) in explains"
:key="i"
>{{ explain }}<br
v-if="i + 1 !== explains.length"
></span>
</div>
<template v-if="hasFeedback">
<div
v-if="mergedValidationStatus === 'warning'"
key="controlled-warning"
class="n-form-item-feedback n-form-item-feedback--warning"
>
<feedbacks
:explains="explains"
:feedback="feedback"
/>
</div>
<div
v-else-if="mergedValidationStatus === 'error'"
key="controlled-error"
class="n-form-item-feedback n-form-item-feedback--error"
>
<feedbacks
:explains="explains"
:feedback="feedback"
/>
</div>
<div
v-else-if="mergedValidationStatus === 'success'"
key="controlled-success"
class="n-form-item-feedback n-form-item-feedback--success"
>
<feedbacks
:explains="explains"
:feedback="feedback"
/>
</div>
<div
v-else
key="controlled-default"
class="n-form-item-feedback"
>
<feedbacks
:explains="explains"
:feedback="feedback"
/>
</div>
</template>
</transition>
</div>
</div>
</div>
</template>
<script>
import Schema from 'async-validator'
import get from 'lodash-es/get'
@ -75,6 +105,7 @@ import {
formItemSize,
formItemRule
} from './utils'
import Feedbacks from './Feedbacks.vue'
function wrapValidator (validator) {
if (typeof validator === 'function') {
@ -113,6 +144,9 @@ function wrapValidator (validator) {
export default {
name: 'FormItem',
cssrName: 'Form',
components: {
Feedbacks
},
mixins: [
registerable('NForm', 'formItems', 'path'),
configurable,
@ -212,6 +246,15 @@ export default {
feedbackId: createId()
}
},
computed: {
hasFeedback () {
const {
feedback
} = this
if (feedback !== undefined && feedback !== null) return true
return this.explains.length
}
},
watch: {
path () {
if (this.ignorePathChange) return

View File

@ -47,7 +47,6 @@ export default c([
raw: `
display: block;
width: 100%;
padding-bottom: 8px;
padding-left: 2px;
`
})
@ -101,28 +100,14 @@ export default c([
]),
cB('form-item-blank', {
raw: `
box-sizing: border-box;
padding-top: 3px;
padding-bottom: 3px;
display: flex;
align-items: center;
position: relative;
`
}, [
cM('error', [
c('+', [
cB('form-item-feedback-wrapper', {
color: feedbackTextColorError
})
])
]),
cM('warning', [
c('+', [
cB('form-item-feedback-wrapper', {
color: feedbackTextColorWarning
})
])
])
]),
}),
cM('required', [
cB('form-item-label', [
c('&::after, &::before', {
@ -154,17 +139,23 @@ export default c([
padding-left: 2px;
padding-top: 0px;
box-sizing: border-box;
min-height: 1.5em;
font-size: 14px;
min-height: 1.25em;
transform-origin: top left;
line-height: 1.5;
line-height: 1.25;
transition: color .3s ${cubicBezierEaseInOut};
`
}, [
cB('form-item-feedback', [
cM('error', {
color: feedbackTextColorError
}),
cM('warning', {
color: feedbackTextColorWarning
}),
fadeDownTranstion({
name: 'form-item-feedback',
fromOffset: '-3px'
fromOffset: '-3px',
enterDuration: '.3s',
leaveDuration: '.2s'
})
])
])

View File

@ -54,7 +54,7 @@ export default create({
boxShadowDisabled: 'inset 0 0 0 1px transparent'
},
warning: {
borderMaskColor: 'transparent',
borderMaskColor: warningColor,
borderMaskColorHover: warningColorHover,
colorFocus: changeColor(warningColor, { alpha: 0.1 }),
borderMaskColorFocus: warningColorHover,
@ -62,7 +62,7 @@ export default create({
caretColor: warningColor
},
error: {
borderMaskColor: 'transparent',
borderMaskColor: errorColor,
borderMaskColorHover: errorColorHover,
colorFocus: changeColor(errorColor, { alpha: 0.1 }),
borderMaskColorFocus: errorColorHover,

View File

@ -1,5 +1,5 @@
import { h, withDirectives, vShow, ref } from 'vue'
import FadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import NPopover from '../../popover/src/Popover'
import NMenuItemContent from './MenuItemContent.vue'
import menuChildMixin from './menu-child-mixin'
@ -119,7 +119,7 @@ export default {
})
}
const createSubmenuChildren = (insidePopover = false) => {
return h(FadeInHeightExpandTransition, null, {
return h(NFadeInExpandTransition, null, {
default: () => {
const {
children,

View File

@ -2,7 +2,7 @@ import { h } from 'vue'
import NTreeNodeSwitcher from './TreeNodeSwitcher.vue'
import NTreeNodeCheckbox from './TreeNodeCheckbox.vue'
import NTreeNodeContent from './TreeNodeContent.vue'
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import { isLeaf, isLoaded } from './utils'
@ -161,7 +161,7 @@ const TreeNode = {
default: () => data.label
}),
this.icon ? this.icon() : null,
!isLeaf(data) ? h(NFadeInHeightExpandTransition, null,
!isLeaf(data) ? h(NFadeInExpandTransition, null,
{
default: () => this.expanded && data.children
? h('ul', {

View File

@ -28,13 +28,13 @@
<slot />
</div>
<div class="n-upload-file-list" :style="fileListStyle">
<n-fade-in-height-expand-transition-group>
<n-fade-in-expand-transition group>
<n-upload-file
v-for="file in syntheticFileList"
:key="file.id"
:file="file"
/>
</n-fade-in-height-expand-transition-group>
</n-fade-in-expand-transition>
</div>
</div>
</template>
@ -47,7 +47,7 @@ import {
import { warn } from '../../_utils/naive'
import { createId } from '../../_utils/vue'
import NUploadFile from './UploadFile.vue'
import NFadeInHeightExpandTransitionGroup from '../../_transition/FadeInHeightExpandTransitionGroup'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import usecssr from '../../_mixins/usecssr'
import styles from './styles'
@ -164,7 +164,7 @@ export default {
name: 'Upload',
components: {
NUploadFile,
NFadeInHeightExpandTransitionGroup
NFadeInExpandTransition
},
provide () {
return {

View File

@ -67,7 +67,7 @@ import NIconSwitchTransition from '../../_transition/IconSwitchTransition'
import { warn } from '../../_utils/naive'
export default {
name: 'NUploadFile',
name: 'UploadFile',
components: {
NButton,
NUploadProgress,

View File

@ -1,5 +1,5 @@
<template>
<n-fade-in-height-expand-transition>
<n-fade-in-expand-transition>
<n-progress
v-if="postponedShow"
type="line"
@ -8,18 +8,18 @@
:status="status"
:height="2"
/>
</n-fade-in-height-expand-transition>
</n-fade-in-expand-transition>
</template>
<script>
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import NFadeInExpandTransition from '../../_transition/FadeInExpandTransition'
import NProgress from '../../progress/index.js'
export default {
name: 'NUploadProgress',
name: 'UploadProgress',
components: {
NProgress,
NFadeInHeightExpandTransition
NFadeInExpandTransition
},
props: {
show: {

View File

@ -1 +1 @@
export default '1.5.5'
export default '2.0.0'