Merge branch '2.0' into pr004
This commit is contained in:
commit
e9dc3c4289
177 changed files with 62359 additions and 18930 deletions
2
src/components/anchor-link/index.js
Normal file
2
src/components/anchor-link/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import AnchorLink from '../anchor/anchor-link.vue';
|
||||
export default AnchorLink;
|
50
src/components/anchor/anchor-link.vue
Normal file
50
src/components/anchor/anchor-link.vue
Normal file
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div :class="anchorLinkClasses">
|
||||
<a :class="linkTitleClasses" href="javascript:void(0)" :data-href="href" @click="goAnchor" :title="title">{{ title }}</a>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { findComponentUpward } from '../../utils/assist';
|
||||
export default {
|
||||
name: 'AnchorLink',
|
||||
props: {
|
||||
href: String,
|
||||
title: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-anchor-link'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
anchorLinkClasses () {
|
||||
return [
|
||||
this.prefix,
|
||||
this.currentLink === this.href ? `${this.prefix}-active` : ''
|
||||
];
|
||||
},
|
||||
linkTitleClasses () {
|
||||
return [
|
||||
`${this.prefix}-title`
|
||||
];
|
||||
},
|
||||
parentAnchor () {
|
||||
return findComponentUpward(this, 'Anchor');
|
||||
},
|
||||
currentLink () {
|
||||
return this.parentAnchor.currentLink;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goAnchor () {
|
||||
this.parentAnchor.turnTo(this.href);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.parentAnchor.init();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
191
src/components/anchor/anchor.vue
Normal file
191
src/components/anchor/anchor.vue
Normal file
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<component :is="wrapperComponent" :offset-top="offsetTop" :offset-bottom="offsetBottom" @on-change="handleAffixStateChange">
|
||||
<div :class="`${prefix}-wrapper`" :style="wrapperStyle">
|
||||
<div :class="`${prefix}`">
|
||||
<div :class="`${prefix}-ink`">
|
||||
<span v-show="showInkBall" :class="`${prefix}-ink-ball`" :style="{top: `${inkTop}px`}"></span>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
<script>
|
||||
import { scrollTop, findComponentDownward, findComponentsDownward, sharpMatcherRegx } from '../../utils/assist';
|
||||
import { on, off } from '../../utils/dom';
|
||||
export default {
|
||||
name: 'Anchor',
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-anchor',
|
||||
isAffixed: false, // current affixed state
|
||||
inkTop: 0,
|
||||
linkHeight: 0,
|
||||
animating: false, // if is scrolling now
|
||||
currentLink: '', // current show link => #href -> currentLink = #href
|
||||
currentId: '', // current show title id => #href -> currentId = href
|
||||
scrollContainer: null,
|
||||
scrollElement: null,
|
||||
titlesOffsetArr: [],
|
||||
wrapperTop: 0,
|
||||
upperFirstTitle: true
|
||||
};
|
||||
},
|
||||
props: {
|
||||
affix: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
offsetTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
offsetBottom: Number,
|
||||
bounds: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
container: [String, HTMLElement],
|
||||
showInkInFixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapperComponent () {
|
||||
return this.affix ? 'Affix' : 'div';
|
||||
},
|
||||
wrapperStyle () {
|
||||
return {
|
||||
maxHeight: this.offsetTop ? `calc(100vh - ${this.offsetTop}px)` : '100vh'
|
||||
};
|
||||
},
|
||||
containerIsWindow () {
|
||||
return this.scrollContainer === window;
|
||||
},
|
||||
showInkBall () {
|
||||
return this.showInkInFixed && (this.isAffixed || (!this.isAffixed && !this.upperFirstTitle && this.scrollContainer !== window));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAffixStateChange (state) {
|
||||
this.isAffixed = this.affix && state;
|
||||
},
|
||||
handleScroll (e) {
|
||||
this.upperFirstTitle = e.target.scrollTop < this.titlesOffsetArr[0].offset;
|
||||
if (this.animating) return;
|
||||
this.updateTitleOffset();
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop || e.target.scrollTop;
|
||||
this.getCurrentScrollAtTitleId(scrollTop);
|
||||
},
|
||||
turnTo (href) {
|
||||
this.currentLink = href;
|
||||
this.$router.push({
|
||||
path: href
|
||||
});
|
||||
this.$emit('on-select', href);
|
||||
},
|
||||
handleHashChange () {
|
||||
const url = window.location.href;
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(url);
|
||||
this.currentLink = sharpLinkMatch[0];
|
||||
this.currentId = sharpLinkMatch[1];
|
||||
},
|
||||
handleScrollTo () {
|
||||
const anchor = document.getElementById(this.currentId);
|
||||
if (!anchor) return;
|
||||
const offsetTop = anchor.offsetTop - this.wrapperTop;
|
||||
this.animating = true;
|
||||
scrollTop(this.scrollContainer, this.scrollElement.scrollTop, offsetTop, 600, () => {
|
||||
this.animating = false;
|
||||
});
|
||||
this.handleSetInkTop();
|
||||
},
|
||||
handleSetInkTop () {
|
||||
const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`);
|
||||
if (!currentLinkElementA) return;
|
||||
const elementATop = currentLinkElementA.offsetTop;
|
||||
const top = (elementATop < 0 ? this.offsetTop : elementATop);
|
||||
this.inkTop = top;
|
||||
},
|
||||
updateTitleOffset () {
|
||||
const links = findComponentsDownward(this, 'AnchorLink').map(link => {
|
||||
return link.href;
|
||||
});
|
||||
const idArr = links.map(link => {
|
||||
return link.split('#')[1];
|
||||
});
|
||||
let offsetArr = [];
|
||||
idArr.forEach(id => {
|
||||
const titleEle = document.getElementById(id);
|
||||
if (titleEle) offsetArr.push({
|
||||
link: `#${id}`,
|
||||
offset: titleEle.offsetTop - this.scrollElement.offsetTop
|
||||
});
|
||||
});
|
||||
this.titlesOffsetArr = offsetArr;
|
||||
},
|
||||
getCurrentScrollAtTitleId (scrollTop) {
|
||||
let i = -1;
|
||||
let len = this.titlesOffsetArr.length;
|
||||
let titleItem = {
|
||||
link: '#',
|
||||
offset: 0
|
||||
};
|
||||
scrollTop += this.bounds;
|
||||
while (++i < len) {
|
||||
let currentEle = this.titlesOffsetArr[i];
|
||||
let nextEle = this.titlesOffsetArr[i + 1];
|
||||
if (scrollTop >= currentEle.offset && scrollTop < ((nextEle && nextEle.offset) || Infinity)) {
|
||||
titleItem = this.titlesOffsetArr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.currentLink = titleItem.link;
|
||||
this.handleSetInkTop();
|
||||
},
|
||||
getContainer () {
|
||||
this.scrollContainer = this.container ? (typeof this.container === 'string' ? document.querySelector(this.container) : this.container) : window;
|
||||
this.scrollElement = this.container ? this.scrollContainer : (document.documentElement || document.body);
|
||||
},
|
||||
removeListener () {
|
||||
off(this.scrollContainer, 'scroll', this.handleScroll);
|
||||
off(window, 'hashchange', this.handleHashChange);
|
||||
},
|
||||
init () {
|
||||
const anchorLink = findComponentDownward(this, 'AnchorLink');
|
||||
this.linkHeight = anchorLink ? anchorLink.$el.getBoundingClientRect().height : 0;
|
||||
this.handleHashChange();
|
||||
this.$nextTick(() => {
|
||||
this.removeListener();
|
||||
this.getContainer();
|
||||
this.wrapperTop = this.containerIsWindow ? 0 : this.scrollElement.offsetTop;
|
||||
this.handleScrollTo();
|
||||
this.handleSetInkTop();
|
||||
this.updateTitleOffset();
|
||||
this.upperFirstTitle = this.scrollElement.scrollTop < this.titlesOffsetArr[0].offset;
|
||||
on(this.scrollContainer, 'scroll', this.handleScroll);
|
||||
on(window, 'hashchange', this.handleHashChange);
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route' () {
|
||||
this.handleHashChange();
|
||||
this.handleScrollTo();
|
||||
},
|
||||
container () {
|
||||
this.init();
|
||||
},
|
||||
currentLink (newHref, oldHref) {
|
||||
this.$emit('on-change', newHref, oldHref);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.init();
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.removeListener();
|
||||
}
|
||||
};
|
||||
</script>
|
2
src/components/anchor/index.js
Normal file
2
src/components/anchor/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Anchor from './anchor.vue';
|
||||
export default Anchor;
|
|
@ -8,6 +8,7 @@
|
|||
:placeholder="placeholder"
|
||||
:size="size"
|
||||
:placement="placement"
|
||||
:value="currentValue"
|
||||
filterable
|
||||
remote
|
||||
auto-complete
|
||||
|
@ -124,7 +125,9 @@
|
|||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.disableEmitChange = true;
|
||||
if(this.currentValue !== val){
|
||||
this.disableEmitChange = true;
|
||||
}
|
||||
this.currentValue = val;
|
||||
},
|
||||
currentValue (val) {
|
||||
|
@ -144,21 +147,20 @@
|
|||
},
|
||||
handleChange (val) {
|
||||
this.currentValue = val;
|
||||
this.$refs.select.model = val;
|
||||
this.$refs.input.blur();
|
||||
this.$emit('on-select', val);
|
||||
},
|
||||
handleFocus () {
|
||||
this.$refs.select.visible = true;
|
||||
handleFocus (event) {
|
||||
this.$emit('on-focus', event);
|
||||
},
|
||||
handleBlur () {
|
||||
this.$refs.select.visible = false;
|
||||
handleBlur (event) {
|
||||
this.$emit('on-blur', event);
|
||||
},
|
||||
handleClear () {
|
||||
if (!this.clearable) return;
|
||||
this.currentValue = '';
|
||||
this.$refs.select.model = '';
|
||||
this.$refs.select.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -32,6 +32,9 @@ export default {
|
|||
modifiers: {
|
||||
computeStyle:{
|
||||
gpuAcceleration: false,
|
||||
},
|
||||
preventOverflow :{
|
||||
boundariesElement: 'window'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -83,21 +86,18 @@ export default {
|
|||
}
|
||||
|
||||
options.placement = this.placement;
|
||||
|
||||
if (options.modifiers) {
|
||||
options.modifiers = {};
|
||||
}
|
||||
if (options.modifiers.offset) {
|
||||
|
||||
if (!options.modifiers.offset) {
|
||||
options.modifiers.offset = {};
|
||||
}
|
||||
options.modifiers.offset = this.offset;
|
||||
options.modifiers.offset.offset = this.offset;
|
||||
options.onCreate =()=>{
|
||||
this.$nextTick(this.updatePopper);
|
||||
this.$emit('created', this);
|
||||
};
|
||||
|
||||
this.popperJS = new Popper(reference, popper, options);
|
||||
|
||||
|
||||
},
|
||||
updatePopper() {
|
||||
if (isServer) return;
|
||||
|
@ -110,6 +110,10 @@ export default {
|
|||
this.popperJS = null;
|
||||
}
|
||||
},
|
||||
updated (){
|
||||
this.$nextTick(()=>this.updatePopper());
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (isServer) return;
|
||||
if (this.popperJS) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<button :class="arrowClasses" class="left" @click="arrowEvent(-1)">
|
||||
<button type="button" :class="arrowClasses" class="left" @click="arrowEvent(-1)">
|
||||
<Icon type="chevron-left"></Icon>
|
||||
</button>
|
||||
<div :class="[prefixCls + '-list']">
|
||||
|
@ -10,7 +10,7 @@
|
|||
<div :class="[prefixCls + '-track', showCopyTrack ? 'higher' : '']" :style="copyTrackStyles" ref="copyTrack" v-if="loop">
|
||||
</div>
|
||||
</div>
|
||||
<button :class="arrowClasses" class="right" @click="arrowEvent(1)">
|
||||
<button type="button" :class="arrowClasses" class="right" @click="arrowEvent(1)">
|
||||
<Icon type="chevron-right"></Icon>
|
||||
</button>
|
||||
<ul :class="dotsClasses">
|
||||
|
@ -18,7 +18,7 @@
|
|||
<li :class="[n - 1 === currentIndex ? prefixCls + '-active' : '']"
|
||||
@click="dotsEvent('click', n - 1)"
|
||||
@mouseover="dotsEvent('hover', n - 1)">
|
||||
<button :class="[radiusDot ? 'radius' : '']"></button>
|
||||
<button type="button" :class="[radiusDot ? 'radius' : '']"></button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="classes" v-clickoutside="handleClose">
|
||||
<div :class="classes" v-click-outside="handleClose">
|
||||
<div :class="[prefixCls + '-rel']" @click="toggleOpen" ref="reference">
|
||||
<input type="hidden" :name="name" :value="currentValue">
|
||||
<slot>
|
||||
|
@ -57,7 +57,7 @@
|
|||
import Drop from '../select/dropdown.vue';
|
||||
import Icon from '../icon/icon.vue';
|
||||
import Caspanel from './caspanel.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
@ -70,7 +70,7 @@
|
|||
name: 'Cascader',
|
||||
mixins: [ Emitter, Locale ],
|
||||
components: { iInput, Drop, Icon, Caspanel },
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
|
|
|
@ -36,15 +36,8 @@
|
|||
|
||||
this.$children.forEach((child, index) => {
|
||||
const name = child.name || index.toString();
|
||||
let isActive = false;
|
||||
|
||||
if (self.accordion) {
|
||||
isActive = activeKey === name;
|
||||
} else {
|
||||
isActive = activeKey.indexOf(name) > -1;
|
||||
}
|
||||
|
||||
child.isActive = isActive;
|
||||
child.isActive = activeKey.indexOf(name) > -1;
|
||||
child.index = index;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,77 +1,103 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-alpha">
|
||||
<div class="ivu-color-picker-alpha-checkboard-wrap">
|
||||
<div class="ivu-color-picker-alpha-checkerboard"></div>
|
||||
<div
|
||||
:class="[prefixCls + '-alpha']"
|
||||
tabindex="0"
|
||||
@click="$el.focus()"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div :class="[prefixCls + '-alpha-checkboard-wrap']">
|
||||
<div :class="[prefixCls + '-alpha-checkerboard']"></div>
|
||||
</div>
|
||||
<div class="ivu-color-picker-alpha-gradient" :style="{background: gradientColor}"></div>
|
||||
<div class="ivu-color-picker-alpha-container" ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div class="ivu-color-picker-alpha-pointer" :style="{left: colors.a * 100 + '%'}">
|
||||
<div class="ivu-color-picker-alpha-picker"></div>
|
||||
<div
|
||||
:style="gradientStyle"
|
||||
:class="[prefixCls + '-alpha-gradient']"></div>
|
||||
<div
|
||||
ref="container"
|
||||
:class="[prefixCls + '-alpha-container']"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div
|
||||
:style="{top: 0, left: `${value.a * 100}%`}"
|
||||
:class="[prefixCls + '-alpha-pointer']">
|
||||
<div :class="[prefixCls + '-alpha-picker']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Alpha',
|
||||
props: {
|
||||
value: Object,
|
||||
onChange: Function
|
||||
import HSAMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp, toRGBAString} from './utils';
|
||||
|
||||
export default {
|
||||
name: 'Alpha',
|
||||
|
||||
mixins: [HSAMixin, Prefixes],
|
||||
|
||||
data() {
|
||||
const normalStep = 1;
|
||||
const jumpStep = 10;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: jumpStep,
|
||||
down: -jumpStep,
|
||||
powerKey: 'shiftKey',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
gradientStyle() {
|
||||
const {r, g, b} = this.value.rgba;
|
||||
const start = toRGBAString({r, g, b, a: 0});
|
||||
const finish = toRGBAString({r, g, b, a: 1});
|
||||
|
||||
return {background: `linear-gradient(to right, ${start} 0%, ${finish} 100%)`};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
return this.value;
|
||||
},
|
||||
gradientColor () {
|
||||
const rgba = this.colors.rgba;
|
||||
const rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
|
||||
return 'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)';
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(newAlpha) {
|
||||
const {h, s, l} = this.value.hsl;
|
||||
const {a} = this.value;
|
||||
|
||||
if (a !== newAlpha) {
|
||||
this.$emit('change', {h, s, l, a: newAlpha, source: 'rgba'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
handleSlide(e, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const left = pageX - xOffset;
|
||||
this.change(clamp(e[this.powerKey] ? direction : Math.round(this.value.hsl.a * 100 + direction) / 100, 0, 1));
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let a;
|
||||
if (left < 0) {
|
||||
a = 0;
|
||||
} else if (left > containerWidth) {
|
||||
a = 1;
|
||||
} else {
|
||||
a = Math.round(left * 100 / containerWidth) / 100;
|
||||
}
|
||||
const left = this.getLeft(e);
|
||||
|
||||
if (this.colors.a !== a) {
|
||||
this.$emit('change', {
|
||||
h: this.colors.hsl.h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: a,
|
||||
source: 'rgba'
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseDown (e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
if (left < 0) {
|
||||
this.change(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
const {clientWidth} = this.$refs.container;
|
||||
|
||||
if (left > clientWidth) {
|
||||
this.change(1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(Math.round(left * 100 / clientWidth) / 100);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,374 +1,453 @@
|
|||
<template>
|
||||
<div :class="classes" v-clickoutside="handleClose">
|
||||
<div ref="reference" @click="toggleVisible" :class="wrapClasses">
|
||||
<input type="hidden" :name="name" :value="currentValue">
|
||||
<i class="ivu-icon ivu-icon-arrow-down-b ivu-input-icon ivu-input-icon-normal"></i>
|
||||
<div :class="inputClasses">
|
||||
<div
|
||||
v-click-outside.capture="handleClose"
|
||||
v-click-outside:mousedown.capture="handleClose"
|
||||
:class="classes">
|
||||
<div
|
||||
ref="reference"
|
||||
:class="wrapClasses"
|
||||
@click="toggleVisible">
|
||||
<input
|
||||
:name="name"
|
||||
:value="currentValue"
|
||||
type="hidden">
|
||||
<i :class="arrowClasses"></i>
|
||||
<div
|
||||
ref="input"
|
||||
:tabindex="disabled ? undefined : 0"
|
||||
:class="inputClasses"
|
||||
@keydown.tab="onTab"
|
||||
@keydown.esc="onEscape"
|
||||
@keydown.up="onArrow"
|
||||
@keydown.down="onArrow"
|
||||
>
|
||||
<div :class="[prefixCls + '-color']">
|
||||
<div :class="[prefixCls + '-color-empty']" v-show="value === '' && !visible">
|
||||
<i class="ivu-icon ivu-icon-ios-close-empty"></i>
|
||||
<div
|
||||
v-show="value === '' && !visible"
|
||||
:class="[prefixCls + '-color-empty']">
|
||||
<i :class="[iconPrefixCls, iconPrefixCls + '-ios-close-empty']"></i>
|
||||
</div>
|
||||
<div v-show="value || visible" :style="{backgroundColor: displayedColor}"></div>
|
||||
<div
|
||||
v-show="value || visible"
|
||||
:style="displayedColorStyle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="transition-drop">
|
||||
<Drop
|
||||
v-transfer-dom
|
||||
v-show="visible"
|
||||
@click.native="handleTransferClick"
|
||||
:class="{ [prefixCls + '-transfer']: transfer }"
|
||||
class-name="ivu-transfer-no-max-height"
|
||||
:placement="placement"
|
||||
ref="drop"
|
||||
:placement="placement"
|
||||
:data-transfer="transfer"
|
||||
v-transfer-dom>
|
||||
<div :class="[prefixCls + '-picker']">
|
||||
<div :class="[prefixCls + '-picker-wrapper']">
|
||||
<div :class="[prefixCls + '-picker-panel']">
|
||||
<Saturation v-model="saturationColors" @change="childChange"></Saturation>
|
||||
:class="dropClasses"
|
||||
>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="visible"
|
||||
:class="[prefixCls + '-picker']">
|
||||
<div :class="[prefixCls + '-picker-wrapper']">
|
||||
<div :class="[prefixCls + '-picker-panel']">
|
||||
<Saturation
|
||||
ref="saturation"
|
||||
v-model="saturationColors"
|
||||
:focused="visible"
|
||||
@change="childChange"
|
||||
@keydown.native.tab="handleFirstTab"
|
||||
></Saturation>
|
||||
</div>
|
||||
<div
|
||||
v-if="hue"
|
||||
:class="[prefixCls + '-picker-hue-slider']">
|
||||
<Hue
|
||||
v-model="saturationColors"
|
||||
@change="childChange"></Hue>
|
||||
</div>
|
||||
<div
|
||||
v-if="alpha"
|
||||
:class="[prefixCls + '-picker-alpha-slider']">
|
||||
<Alpha
|
||||
v-model="saturationColors"
|
||||
@change="childChange"></Alpha>
|
||||
</div>
|
||||
<recommend-colors
|
||||
v-if="colors.length"
|
||||
:list="colors"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
<recommend-colors
|
||||
v-if="!colors.length && recommend"
|
||||
:list="recommendedColor"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
</div>
|
||||
<div v-if="hue" :class="[prefixCls + '-picker-hue-slider']">
|
||||
<Hue v-model="saturationColors" @change="childChange"></Hue>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="[prefixCls + '-confirm-color']">{{formatColor}}</span>
|
||||
<i-button
|
||||
ref="clear"
|
||||
:tabindex="0"
|
||||
size="small"
|
||||
type="ghost"
|
||||
@click.native="handleClear"
|
||||
@keydown.enter="handleClear"
|
||||
@keydown.native.esc="closer"
|
||||
>{{t('i.datepicker.clear')}}</i-button>
|
||||
<i-button
|
||||
ref="ok"
|
||||
:tabindex="0"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click.native="handleSuccess"
|
||||
@keydown.native.tab="handleLastTab"
|
||||
@keydown.enter="handleSuccess"
|
||||
@keydown.native.esc="closer"
|
||||
>{{t('i.datepicker.ok')}}</i-button>
|
||||
</div>
|
||||
<div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']">
|
||||
<Alpha v-model="saturationColors" @change="childChange"></Alpha>
|
||||
</div>
|
||||
<recommend-colors
|
||||
v-if="colors.length"
|
||||
:list="colors"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
<recommend-colors
|
||||
v-if="!colors.length && recommend"
|
||||
:list="recommendedColor"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="[prefixCls + '-confirm-color']">{{ formatColor }}</span>
|
||||
<Confirm @on-pick-success="handleSuccess" @on-pick-clear="handleClear"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Drop>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tinycolor from 'tinycolor2';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import Drop from '../../components/select/dropdown.vue';
|
||||
import RecommendColors from './recommend-colors.vue';
|
||||
import Saturation from './saturation.vue';
|
||||
import Hue from './hue.vue';
|
||||
import Alpha from './alpha.vue';
|
||||
import Locale from '../../mixins/locale';
|
||||
import {oneOf} from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {changeColor, toRGBAString} from './utils';
|
||||
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
|
||||
import Drop from '../../components/select/dropdown.vue';
|
||||
import RecommendColors from './recommend-colors.vue';
|
||||
import Confirm from '../date-picker/base/confirm.vue';
|
||||
import Saturation from './saturation.vue';
|
||||
import Hue from './hue.vue';
|
||||
import Alpha from './alpha.vue';
|
||||
components: {Drop, RecommendColors, Saturation, Hue, Alpha},
|
||||
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
directives: {clickOutside, TransferDom},
|
||||
|
||||
const prefixCls = 'ivu-color-picker';
|
||||
const inputPrefixCls = 'ivu-input';
|
||||
mixins: [Emitter, Locale, Prefixes],
|
||||
|
||||
function _colorChange (data, oldHue) {
|
||||
data = data === '' ? '#2d8cf0' : data;
|
||||
const alpha = data && data.a;
|
||||
let color;
|
||||
|
||||
// hsl is better than hex between conversions
|
||||
if (data && data.hsl) {
|
||||
color = tinycolor(data.hsl);
|
||||
} else if (data && data.hex && data.hex.length > 0) {
|
||||
color = tinycolor(data.hex);
|
||||
} else {
|
||||
color = tinycolor(data);
|
||||
}
|
||||
|
||||
if (color && (color._a === undefined || color._a === null)) {
|
||||
color.setAlpha(alpha || 1);
|
||||
}
|
||||
|
||||
const hsl = color.toHsl();
|
||||
const hsv = color.toHsv();
|
||||
|
||||
if (hsl.s === 0) {
|
||||
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
|
||||
}
|
||||
|
||||
// when the hsv.v is less than 0.0164 (base on test)
|
||||
// because of possible loss of precision
|
||||
// the result of hue and saturation would be miscalculated
|
||||
if (hsv.v < 0.0164) {
|
||||
hsv.h = data.h || (data.hsv && data.hsv.h) || 0;
|
||||
hsv.s = data.s || (data.hsv && data.hsv.s) || 0;
|
||||
}
|
||||
|
||||
if (hsl.l < 0.01) {
|
||||
hsl.h = data.h || (data.hsl && data.hsl.h) || 0;
|
||||
hsl.s = data.s || (data.hsl && data.hsl.s) || 0;
|
||||
}
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
hue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
recommend: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']);
|
||||
},
|
||||
default: undefined,
|
||||
},
|
||||
colors: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default: 'default',
|
||||
},
|
||||
hideDropDown: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return oneOf(value, [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-end',
|
||||
'left',
|
||||
'left-start',
|
||||
'left-end',
|
||||
'right',
|
||||
'right-start',
|
||||
'right-end',
|
||||
]);
|
||||
},
|
||||
default: 'bottom',
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
hsl: hsl,
|
||||
hex: color.toHexString().toUpperCase(),
|
||||
rgba: color.toRgb(),
|
||||
hsv: hsv,
|
||||
oldHue: data.h || oldHue || hsl.h,
|
||||
source: data.source,
|
||||
a: data.a || color.getAlpha()
|
||||
val: changeColor(this.value),
|
||||
currentValue: this.value,
|
||||
dragging: false,
|
||||
visible: false,
|
||||
recommendedColor: [
|
||||
'#2d8cf0',
|
||||
'#19be6b',
|
||||
'#ff9900',
|
||||
'#ed3f14',
|
||||
'#00b5ff',
|
||||
'#19c919',
|
||||
'#f9e31c',
|
||||
'#ea1a1a',
|
||||
'#9b1dea',
|
||||
'#00c2b1',
|
||||
'#ac7a33',
|
||||
'#1d35ea',
|
||||
'#8bc34a',
|
||||
'#f16b62',
|
||||
'#ea4ca3',
|
||||
'#0d94aa',
|
||||
'#febd79',
|
||||
'#5d4037',
|
||||
'#00bcd4',
|
||||
'#f06292',
|
||||
'#cddc39',
|
||||
'#607d8b',
|
||||
'#000000',
|
||||
'#ffffff',
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
mixins: [ Emitter ],
|
||||
components: { Drop, Confirm, RecommendColors, Saturation, Hue, Alpha },
|
||||
directives: { clickoutside, TransferDom },
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
computed: {
|
||||
arrowClasses() {
|
||||
return [
|
||||
this.iconPrefixCls,
|
||||
`${this.iconPrefixCls}-arrow-down-b`,
|
||||
`${this.inputPrefixCls}-icon`,
|
||||
`${this.inputPrefixCls}-icon-normal`,
|
||||
];
|
||||
},
|
||||
transition() {
|
||||
return oneOf(this.placement, ['bottom-start', 'bottom', 'bottom-end']) ? 'slide-up' : 'fade';
|
||||
},
|
||||
saturationColors: {
|
||||
get() {
|
||||
return this.val;
|
||||
},
|
||||
hue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
set(newVal) {
|
||||
this.val = newVal;
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
},
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
recommend: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
format: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']);
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
classes() {
|
||||
return [
|
||||
`${this.prefixCls}`,
|
||||
{
|
||||
[`${this.prefixCls}-transfer`]: this.transfer,
|
||||
},
|
||||
default: 'default'
|
||||
},
|
||||
placement: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']);
|
||||
];
|
||||
},
|
||||
wrapClasses() {
|
||||
return [
|
||||
`${this.prefixCls}-rel`,
|
||||
`${this.prefixCls}-${this.size}`,
|
||||
`${this.inputPrefixCls}-wrapper`,
|
||||
`${this.inputPrefixCls}-wrapper-${this.size}`,
|
||||
{
|
||||
[`${this.prefixCls}-disabled`]: this.disabled,
|
||||
},
|
||||
default: 'bottom'
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
name: {
|
||||
type: String
|
||||
}
|
||||
];
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
val: _colorChange(this.value),
|
||||
currentValue: this.value,
|
||||
prefixCls: prefixCls,
|
||||
visible: false,
|
||||
disableCloseUnderTransfer: false, // transfer 模式下,点击Drop也会触发关闭
|
||||
recommendedColor: [
|
||||
'#2d8cf0',
|
||||
'#19be6b',
|
||||
'#ff9900',
|
||||
'#ed3f14',
|
||||
'#00b5ff',
|
||||
'#19c919',
|
||||
'#f9e31c',
|
||||
'#ea1a1a',
|
||||
'#9b1dea',
|
||||
'#00c2b1',
|
||||
'#ac7a33',
|
||||
'#1d35ea',
|
||||
'#8bc34a',
|
||||
'#f16b62',
|
||||
'#ea4ca3',
|
||||
'#0d94aa',
|
||||
'#febd79',
|
||||
'#5d4037',
|
||||
'#00bcd4',
|
||||
'#f06292',
|
||||
'#cddc39',
|
||||
'#607d8b',
|
||||
'#000000',
|
||||
'#ffffff'
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
transition () {
|
||||
if (this.placement === 'bottom-start' || this.placement === 'bottom' || this.placement === 'bottom-end') {
|
||||
return 'slide-up';
|
||||
} else {
|
||||
return 'fade';
|
||||
}
|
||||
},
|
||||
saturationColors: {
|
||||
get () {
|
||||
return this.val;
|
||||
inputClasses() {
|
||||
return [
|
||||
`${this.prefixCls}-input`,
|
||||
`${this.inputPrefixCls}`,
|
||||
`${this.inputPrefixCls}-${this.size}`,
|
||||
{
|
||||
[`${this.prefixCls}-focused`]: this.visible,
|
||||
[`${this.prefixCls}-disabled`]: this.disabled,
|
||||
},
|
||||
set (newVal) {
|
||||
this.val = newVal;
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
}
|
||||
},
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-transfer`]: this.transfer
|
||||
}
|
||||
];
|
||||
},
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-rel`,
|
||||
`${prefixCls}-${this.size}`,
|
||||
`${inputPrefixCls}-wrapper`,
|
||||
`${inputPrefixCls}-wrapper-${this.size}`
|
||||
];
|
||||
},
|
||||
inputClasses () {
|
||||
return [
|
||||
`${prefixCls}-input`,
|
||||
`${inputPrefixCls}`,
|
||||
`${inputPrefixCls}-${this.size}`,
|
||||
{
|
||||
[`${inputPrefixCls}-disabled`]: this.disabled
|
||||
}
|
||||
];
|
||||
},
|
||||
displayedColor () {
|
||||
let color;
|
||||
if (this.visible) {
|
||||
const rgba = this.saturationColors.rgba;
|
||||
color = {
|
||||
r: rgba.r,
|
||||
g: rgba.g,
|
||||
b: rgba.b,
|
||||
a: rgba.a
|
||||
};
|
||||
} else {
|
||||
color = tinycolor(this.value).toRgb();
|
||||
}
|
||||
return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
|
||||
},
|
||||
formatColor () {
|
||||
const value = this.saturationColors;
|
||||
const format = this.format;
|
||||
let color;
|
||||
];
|
||||
},
|
||||
dropClasses() {
|
||||
return [
|
||||
`${this.transferPrefixCls}-no-max-height`,
|
||||
{
|
||||
[`${this.prefixCls}-transfer`]: this.transfer,
|
||||
[`${this.prefixCls}-hide-drop`]: this.hideDropDown,
|
||||
},
|
||||
];
|
||||
},
|
||||
displayedColorStyle() {
|
||||
return {backgroundColor: toRGBAString(this.visible ? this.saturationColors.rgba : tinycolor(this.value).toRgb())};
|
||||
},
|
||||
formatColor() {
|
||||
const {format, saturationColors} = this;
|
||||
|
||||
const rgba = `rgba(${value.rgba.r}, ${value.rgba.g}, ${value.rgba.b}, ${value.rgba.a})`;
|
||||
if (format) {
|
||||
if (format === 'hsl') {
|
||||
color = tinycolor(value.hsl).toHslString();
|
||||
} else if (format === 'hsv') {
|
||||
color = tinycolor(value.hsv).toHsvString();
|
||||
} else if (format === 'hex') {
|
||||
color = value.hex;
|
||||
} else if (format === 'rgb') {
|
||||
color = rgba;
|
||||
}
|
||||
} else if (this.alpha) {
|
||||
color = rgba;
|
||||
} else {
|
||||
color = value.hex;
|
||||
if (format) {
|
||||
if (format === 'hsl') {
|
||||
return tinycolor(saturationColors.hsl).toHslString();
|
||||
}
|
||||
return color;
|
||||
|
||||
if (format === 'hsv') {
|
||||
return tinycolor(saturationColors.hsv).toHsvString();
|
||||
}
|
||||
|
||||
if (format === 'hex') {
|
||||
return saturationColors.hex;
|
||||
}
|
||||
|
||||
if (format === 'rgb') {
|
||||
return toRGBAString(saturationColors.rgba);
|
||||
}
|
||||
} else if (this.alpha) {
|
||||
return toRGBAString(saturationColors.rgba);
|
||||
}
|
||||
|
||||
return saturationColors.hex;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.val = changeColor(newVal);
|
||||
},
|
||||
visible(val) {
|
||||
this.val = changeColor(this.value);
|
||||
this.$refs.drop[val ? 'update' : 'destroy']();
|
||||
this.$emit('on-open-change', Boolean(val));
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$on('on-escape-keydown', this.closer);
|
||||
this.$on('on-dragging', this.setDragging);
|
||||
},
|
||||
|
||||
methods: {
|
||||
setDragging(value) {
|
||||
this.dragging = value;
|
||||
},
|
||||
handleClose(event) {
|
||||
if (this.visible) {
|
||||
if (this.dragging || event.type === 'mousedown') {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.transfer) {
|
||||
const {$el} = this.$refs.drop;
|
||||
if ($el === event.target || $el.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.closer(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
},
|
||||
toggleVisible() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = !this.visible;
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
childChange(data) {
|
||||
this.colorChange(data);
|
||||
},
|
||||
colorChange(data, oldHue) {
|
||||
this.oldHue = this.saturationColors.hsl.h;
|
||||
this.saturationColors = changeColor(data, oldHue || this.oldHue);
|
||||
},
|
||||
closer(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
handleButtons(event, value) {
|
||||
this.currentValue = value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('on-change', value);
|
||||
this.dispatch('FormItem', 'on-form-change', value);
|
||||
this.closer(event);
|
||||
},
|
||||
handleSuccess(event) {
|
||||
this.handleButtons(event, this.formatColor);
|
||||
this.$emit('on-pick-success');
|
||||
},
|
||||
handleClear(event) {
|
||||
this.handleButtons(event, '');
|
||||
this.$emit('on-pick-clear');
|
||||
},
|
||||
handleSelectColor(color) {
|
||||
this.val = changeColor(color);
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
},
|
||||
handleFirstTab(event) {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.$refs.ok.$el.focus();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
this.val = _colorChange(newVal);
|
||||
},
|
||||
visible (val) {
|
||||
this.val = _colorChange(this.value);
|
||||
if (val) {
|
||||
this.$refs.drop.update();
|
||||
} else {
|
||||
this.$refs.drop.destroy();
|
||||
}
|
||||
handleLastTab(event) {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.$refs.saturation.$el.focus();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 开启 transfer 时,点击 Drop 即会关闭,这里不让其关闭
|
||||
handleTransferClick () {
|
||||
if (this.transfer) this.disableCloseUnderTransfer = true;
|
||||
},
|
||||
handleClose () {
|
||||
if (this.disableCloseUnderTransfer) {
|
||||
this.disableCloseUnderTransfer = false;
|
||||
return false;
|
||||
}
|
||||
this.visible = false;
|
||||
},
|
||||
toggleVisible () {
|
||||
this.visible = !this.visible;
|
||||
},
|
||||
childChange (data) {
|
||||
this.colorChange(data);
|
||||
},
|
||||
colorChange (data, oldHue) {
|
||||
this.oldHue = this.saturationColors.hsl.h;
|
||||
this.saturationColors = _colorChange(data, oldHue || this.oldHue);
|
||||
},
|
||||
isValidHex (hex) {
|
||||
return tinycolor(hex).isValid();
|
||||
},
|
||||
simpleCheckForValidColor (data) {
|
||||
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||
let checked = 0;
|
||||
let passed = 0;
|
||||
|
||||
for (let i = 0; i < keysToCheck.length; i++) {
|
||||
const letter = keysToCheck[i];
|
||||
if (data[letter]) {
|
||||
checked++;
|
||||
if (!isNaN(data[letter])) {
|
||||
passed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checked === passed) {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
handleSuccess () {
|
||||
const color = this.formatColor;
|
||||
this.currentValue = color;
|
||||
this.$emit('input', color);
|
||||
this.$emit('on-change', color);
|
||||
this.dispatch('FormItem', 'on-form-change', color);
|
||||
this.handleClose();
|
||||
},
|
||||
handleClear () {
|
||||
this.currentValue = '';
|
||||
this.$emit('input', '');
|
||||
this.$emit('on-change', '');
|
||||
this.dispatch('FormItem', 'on-form-change', '');
|
||||
this.handleClose();
|
||||
},
|
||||
handleSelectColor (color) {
|
||||
this.val = _colorChange(color);
|
||||
onTab(event) {
|
||||
if (this.visible) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
onEscape(event) {
|
||||
if (this.visible) {
|
||||
this.closer(event);
|
||||
}
|
||||
},
|
||||
onArrow(event) {
|
||||
if (!this.visible) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.visible = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
7
src/components/color-picker/handleEscapeMixin.js
Normal file
7
src/components/color-picker/handleEscapeMixin.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
methods: {
|
||||
handleEscape(e) {
|
||||
this.dispatch('ColorPicker', 'on-escape-keydown', e);
|
||||
},
|
||||
},
|
||||
};
|
78
src/components/color-picker/hsaMixin.js
Normal file
78
src/components/color-picker/hsaMixin.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import Emitter from '../../mixins/emitter';
|
||||
import handleEscapeMixin from './handleEscapeMixin';
|
||||
import {getTouches} from './utils';
|
||||
import { on, off } from '../../utils/dom';
|
||||
|
||||
export default {
|
||||
mixins: [Emitter, handleEscapeMixin],
|
||||
|
||||
props: {
|
||||
focused: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.focused) {
|
||||
setTimeout(() => this.$el.focus(), 1);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleLeft(e) {
|
||||
this.handleSlide(e, this.left, 'left');
|
||||
},
|
||||
handleRight(e) {
|
||||
this.handleSlide(e, this.right, 'right');
|
||||
},
|
||||
handleUp(e) {
|
||||
this.handleSlide(e, this.up, 'up');
|
||||
},
|
||||
handleDown(e) {
|
||||
this.handleSlide(e, this.down, 'down');
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
this.dispatch('ColorPicker', 'on-dragging', true);
|
||||
this.handleChange(e, true);
|
||||
// window.addEventListener('mousemove', this.handleChange, false);
|
||||
// window.addEventListener('mouseup', this.handleMouseUp, false);
|
||||
on(window, 'mousemove', this.handleChange);
|
||||
on(window, 'mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners() {
|
||||
// window.removeEventListener('mousemove', this.handleChange);
|
||||
// window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
off(window, 'mousemove', this.handleChange);
|
||||
off(window, 'mouseup', this.handleMouseUp);
|
||||
// This timeout is required so that the click handler for click-outside
|
||||
// has the chance to run before the mouseup removes the dragging flag.
|
||||
setTimeout(() => this.dispatch('ColorPicker', 'on-dragging', false), 1);
|
||||
},
|
||||
getLeft(e) {
|
||||
const {container} = this.$refs;
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || getTouches(e, 'PageX');
|
||||
|
||||
return pageX - xOffset;
|
||||
},
|
||||
getTop(e) {
|
||||
const {container} = this.$refs;
|
||||
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
const pageY = e.pageY || getTouches(e, 'PageY');
|
||||
|
||||
return pageY - yOffset;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,86 +1,101 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-hue">
|
||||
<div class="ivu-color-picker-hue-container" ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div class="ivu-color-picker-hue-pointer" :style="{top: 0, left: pointerLeft}">
|
||||
<div class="ivu-color-picker-hue-picker"></div>
|
||||
<div
|
||||
:class="[prefixCls + '-hue']"
|
||||
tabindex="0"
|
||||
@click="$el.focus()"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div
|
||||
ref="container"
|
||||
:class="[prefixCls + '-hue-container']"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div
|
||||
:style="{top: 0, left: `${percent}%`}"
|
||||
:class="[prefixCls + '-hue-pointer']">
|
||||
<div :class="[prefixCls + '-hue-picker']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hue',
|
||||
props: {
|
||||
value: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
oldHue: 0,
|
||||
pullDirection: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
const h = this.value.hsl.h;
|
||||
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
|
||||
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
|
||||
this.oldHue = h;
|
||||
import HASMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp} from './utils';
|
||||
|
||||
return this.value;
|
||||
},
|
||||
pointerLeft () {
|
||||
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
|
||||
return (this.colors.hsl.h * 100) / 360 + '%';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
export default {
|
||||
name: 'Hue',
|
||||
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
mixins: [HASMixin, Prefixes],
|
||||
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const left = pageX - xOffset;
|
||||
data() {
|
||||
const normalStep = 1 / 360 * 25;
|
||||
const jumpStep = 20 * normalStep;
|
||||
|
||||
let h;
|
||||
let percent;
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: jumpStep,
|
||||
down: -jumpStep,
|
||||
powerKey: 'shiftKey',
|
||||
percent: clamp(this.value.hsl.h * 100 / 360, 0, 100),
|
||||
};
|
||||
},
|
||||
|
||||
if (left < 0) {
|
||||
h = 0;
|
||||
} else if (left > containerWidth) {
|
||||
h = 360;
|
||||
} else {
|
||||
percent = left * 100 / containerWidth;
|
||||
h = (360 * percent / 100);
|
||||
}
|
||||
|
||||
if (this.colors.hsl.h !== h) {
|
||||
this.$emit('change', {
|
||||
h: h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: this.colors.hsl.a,
|
||||
source: 'hsl'
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseDown (e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
watch: {
|
||||
value () {
|
||||
this.percent = clamp(this.value.hsl.h * 100 / 360, 0, 100);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(percent) {
|
||||
this.percent = clamp(percent, 0, 100);
|
||||
|
||||
const {h, s, l, a} = this.value.hsl;
|
||||
const newHue = clamp(percent / 100 * 360, 0, 360);
|
||||
|
||||
if (h !== newHue) {
|
||||
this.$emit('change', {h: newHue, s, l, a, source: 'hsl'});
|
||||
}
|
||||
},
|
||||
handleSlide(e, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (e[this.powerKey]) {
|
||||
this.change(direction < 0 ? 0 : 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(this.percent + direction);
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const left = this.getLeft(e);
|
||||
|
||||
if (left < 0) {
|
||||
this.change(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const {clientWidth} = this.$refs.container;
|
||||
|
||||
if (left > clientWidth) {
|
||||
this.change(100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(left * 100 / clientWidth);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import ColorPicker from './color-picker.vue';
|
||||
export default ColorPicker;
|
||||
|
||||
export default ColorPicker;
|
||||
|
|
10
src/components/color-picker/prefixMixin.js
Normal file
10
src/components/color-picker/prefixMixin.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'ivu-color-picker',
|
||||
inputPrefixCls: 'ivu-input',
|
||||
iconPrefixCls: 'ivu-icon',
|
||||
transferPrefixCls: 'ivu-transfer',
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,20 +1,153 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
ref="reference"
|
||||
tabindex="0"
|
||||
@click="handleClick"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.enter="handleEnter"
|
||||
@keydown.left="handleArrow($event, 'x', left)"
|
||||
@keydown.right="handleArrow($event, 'x', right)"
|
||||
@keydown.up="handleArrow($event, 'y', up)"
|
||||
@keydown.down="handleArrow($event, 'y', down)"
|
||||
@blur="blurColor"
|
||||
@focus="focusColor"
|
||||
>
|
||||
<template v-for="(item, index) in list">
|
||||
<span @click="handleClick(index)"><em :style="{'background': item}"></em></span>
|
||||
<br v-if="(index + 1) % 12 === 0 && index !== 0 && (index + 1) !== list.length">
|
||||
<div
|
||||
:key="item + ':' + index"
|
||||
:class="[prefixCls + '-picker-colors-wrapper']">
|
||||
<div :data-color-id="index">
|
||||
<div
|
||||
:style="{background: item}"
|
||||
:class="[prefixCls + '-picker-colors-wrapper-color']"
|
||||
></div>
|
||||
<div
|
||||
:ref="'color-circle-' + index"
|
||||
:class="[prefixCls + '-picker-colors-wrapper-circle', hideClass]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<br v-if="lineBreak(list, index)">
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
list: Array
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import HandleEscapeMixin from './handleEscapeMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp} from './utils';
|
||||
|
||||
export default {
|
||||
name: 'RecommendedColors',
|
||||
|
||||
mixins: [Emitter, HandleEscapeMixin, Prefixes],
|
||||
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: undefined,
|
||||
},
|
||||
methods: {
|
||||
handleClick (index) {
|
||||
this.$emit('picker-color', this.list[index]);
|
||||
},
|
||||
|
||||
data() {
|
||||
const columns = 12;
|
||||
const rows = Math.ceil(this.list.length / columns);
|
||||
const normalStep = 1;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: -normalStep,
|
||||
down: normalStep,
|
||||
powerKey: 'shiftKey',
|
||||
grid: {x: 1, y: 1},
|
||||
rows,
|
||||
columns,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hideClass() {
|
||||
return `${this.prefixCls}-hide`;
|
||||
},
|
||||
linearIndex() {
|
||||
return this.getLinearIndex(this.grid);
|
||||
},
|
||||
currentCircle() {
|
||||
return this.$refs[`color-circle-${this.linearIndex}`][0];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getLinearIndex(grid) {
|
||||
return this.columns * (grid.y - 1) + grid.x - 1;
|
||||
},
|
||||
getMaxLimit(axis) {
|
||||
return axis === 'x' ? this.columns : this.rows;
|
||||
},
|
||||
handleArrow(e, axis, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.blurColor();
|
||||
|
||||
const grid = {...this.grid};
|
||||
|
||||
if (e[this.powerKey]) {
|
||||
if (direction < 0) {
|
||||
grid[axis] = 1;
|
||||
} else {
|
||||
grid[axis] = this.getMaxLimit(axis);
|
||||
}
|
||||
} else {
|
||||
grid[axis] += direction;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
const index = this.getLinearIndex(grid);
|
||||
|
||||
if (index >= 0 && index < this.list.length) {
|
||||
this.grid[axis] = clamp(grid[axis], 1, this.getMaxLimit(axis));
|
||||
}
|
||||
|
||||
this.focusColor();
|
||||
},
|
||||
blurColor() {
|
||||
this.currentCircle.classList.add(this.hideClass);
|
||||
},
|
||||
focusColor() {
|
||||
this.currentCircle.classList.remove(this.hideClass);
|
||||
},
|
||||
handleEnter(e) {
|
||||
this.handleClick(e, this.currentCircle);
|
||||
},
|
||||
handleClick(e, circle) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.$refs.reference.focus();
|
||||
|
||||
const target = circle || e.target;
|
||||
const colorId = target.dataset.colorId || target.parentElement.dataset.colorId;
|
||||
|
||||
if (colorId) {
|
||||
this.blurColor();
|
||||
const id = Number(colorId) + 1;
|
||||
this.grid.x = id % this.columns || this.columns;
|
||||
this.grid.y = Math.ceil(id / this.columns);
|
||||
this.focusColor();
|
||||
this.$emit('picker-color', this.list[colorId]);
|
||||
this.$emit('change', {hex: this.list[colorId], source: 'hex'});
|
||||
}
|
||||
},
|
||||
lineBreak(list, index) {
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextIndex = index + 1;
|
||||
|
||||
return nextIndex < list.length && nextIndex % this.columns === 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,98 +1,101 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-saturation-wrapper">
|
||||
<div
|
||||
:class="[prefixCls + '-saturation-wrapper']"
|
||||
tabindex="0"
|
||||
@keydown.esc="handleEscape"
|
||||
@click="$el.focus()"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div
|
||||
class="ivu-color-picker-saturation"
|
||||
:style="{background: bgColor}"
|
||||
ref="container"
|
||||
:style="bgColorStyle"
|
||||
:class="[prefixCls + '-saturation']"
|
||||
@mousedown="handleMouseDown">
|
||||
<div class="ivu-color-picker-saturation--white"></div>
|
||||
<div class="ivu-color-picker-saturation--black"></div>
|
||||
<div class="ivu-color-picker-saturation-pointer" :style="{top: pointerTop, left: pointerLeft}">
|
||||
<div class="ivu-color-picker-saturation-circle"></div>
|
||||
<div :class="[prefixCls + '-saturation--white']"></div>
|
||||
<div :class="[prefixCls + '-saturation--black']"></div>
|
||||
<div
|
||||
:style="pointerStyle"
|
||||
:class="[prefixCls + '-saturation-pointer']">
|
||||
<div :class="[prefixCls + '-saturation-circle']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
export default {
|
||||
name: 'Saturation',
|
||||
props: {
|
||||
value: Object
|
||||
<script>
|
||||
import HSAMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp, getIncrement} from './utils';
|
||||
import { on, off } from '../../utils/dom';
|
||||
|
||||
export default {
|
||||
name: 'Saturation',
|
||||
|
||||
mixins: [HSAMixin, Prefixes],
|
||||
|
||||
data() {
|
||||
const normalStep = 0.01;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: normalStep,
|
||||
down: -normalStep,
|
||||
multiplier: 10,
|
||||
powerKey: 'shiftKey',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
bgColorStyle() {
|
||||
return {background: `hsl(${this.value.hsv.h}, 100%, 50%)`};
|
||||
},
|
||||
data () {
|
||||
return {};
|
||||
pointerStyle() {
|
||||
return {top: `${-(this.value.hsv.v * 100) + 1 + 100}%`, left: `${this.value.hsv.s * 100}%`};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
return this.value;
|
||||
},
|
||||
bgColor () {
|
||||
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
|
||||
},
|
||||
pointerTop () {
|
||||
return (-(this.colors.hsv.v * 100) + 1) + 100 + '%';
|
||||
},
|
||||
pointerLeft () {
|
||||
return this.colors.hsv.s * 100 + '%';
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(h, s, v, a) {
|
||||
this.$emit('change', {h, s, v, a, source: 'hsva'});
|
||||
},
|
||||
methods: {
|
||||
throttle: throttle((fn, data) => {fn(data);}, 20,
|
||||
{
|
||||
'leading': true,
|
||||
'trailing': false
|
||||
}),
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
|
||||
let left = pageX - xOffset;
|
||||
let top = pageY - yOffset;
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left > containerWidth) {
|
||||
left = containerWidth;
|
||||
} else if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top > containerHeight) {
|
||||
top = containerHeight;
|
||||
}
|
||||
const saturation = left / containerWidth;
|
||||
let bright = -(top / containerHeight) + 1;
|
||||
bright = bright > 0 ? bright : 0;
|
||||
bright = bright > 1 ? 1 : bright;
|
||||
this.throttle(this.onChange, {
|
||||
h: this.colors.hsv.h,
|
||||
s: saturation,
|
||||
v: bright,
|
||||
a: this.colors.hsv.a,
|
||||
source: 'hsva'
|
||||
});
|
||||
},
|
||||
onChange (param) {
|
||||
this.$emit('change', param);
|
||||
},
|
||||
handleMouseDown () {
|
||||
// this.handleChange(e, true)
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
handleSlide(e, direction, key) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const isPowerKey = e[this.powerKey];
|
||||
const increment = isPowerKey ? direction * this.multiplier : direction;
|
||||
const {h, s, v, a} = this.value.hsv;
|
||||
const saturation = clamp(s + getIncrement(key, ['left', 'right'], increment), 0, 1);
|
||||
const bright = clamp(v + getIncrement(key, ['up', 'down'], increment), 0, 1);
|
||||
|
||||
this.change(h, saturation, bright, a);
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const {clientWidth, clientHeight} = this.$refs.container;
|
||||
const left = clamp(this.getLeft(e), 0, clientWidth);
|
||||
const top = clamp(this.getTop(e), 0, clientHeight);
|
||||
const saturation = left / clientWidth;
|
||||
const bright = clamp(1 - top / clientHeight, 0, 1);
|
||||
|
||||
this.change(this.value.hsv.h, saturation, bright, this.value.hsv.a);
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
HSAMixin.methods.handleMouseDown.call(this, e);
|
||||
// window.addEventListener('mouseup', this.handleChange, false);
|
||||
on(window, 'mouseup', this.handleChange);
|
||||
},
|
||||
unbindEventListeners(e) {
|
||||
HSAMixin.methods.unbindEventListeners.call(this, e);
|
||||
// window.removeEventListener('mouseup', this.handleChange);
|
||||
off(window, 'mouseup', this.handleChange);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
118
src/components/color-picker/utils.js
Normal file
118
src/components/color-picker/utils.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
import tinycolor from 'tinycolor2';
|
||||
import {oneOf} from '../../utils/assist';
|
||||
|
||||
function setAlpha(data, alpha) {
|
||||
const color = tinycolor(data);
|
||||
const {_a} = color;
|
||||
|
||||
if (_a === undefined || _a === null) {
|
||||
color.setAlpha(alpha || 1);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function getColor(data, colorData) {
|
||||
const alpha = colorData && colorData.a;
|
||||
|
||||
if (colorData) {
|
||||
// hsl is better than hex between conversions
|
||||
if (colorData.hsl) {
|
||||
return setAlpha(colorData.hsl, alpha);
|
||||
}
|
||||
|
||||
if (colorData.hex && colorData.hex.length > 0) {
|
||||
return setAlpha(colorData.hex, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
return setAlpha(colorData, alpha);
|
||||
}
|
||||
|
||||
export function changeColor(data, oldHue) {
|
||||
const colorData = data === '' ? '#2d8cf0' : data;
|
||||
const color = getColor(data, colorData);
|
||||
const hsl = color.toHsl();
|
||||
const hsv = color.toHsv();
|
||||
|
||||
if (hsl.s === 0) {
|
||||
hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || oldHue || 0;
|
||||
hsv.h = hsl.h;
|
||||
}
|
||||
|
||||
// when the hsv.v is less than 0.0164 (base on test)
|
||||
// because of possible loss of precision
|
||||
// the result of hue and saturation would be miscalculated
|
||||
if (hsv.v < 0.0164) {
|
||||
hsv.h = colorData.h || (colorData.hsv && colorData.hsv.h) || 0;
|
||||
hsv.s = colorData.s || (colorData.hsv && colorData.hsv.s) || 0;
|
||||
}
|
||||
|
||||
if (hsl.l < 0.01) {
|
||||
hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || 0;
|
||||
hsl.s = colorData.s || (colorData.hsl && colorData.hsl.s) || 0;
|
||||
}
|
||||
|
||||
return {
|
||||
hsl,
|
||||
hex: color.toHexString().toUpperCase(),
|
||||
rgba: color.toRgb(),
|
||||
hsv,
|
||||
oldHue: colorData.h || oldHue || hsl.h,
|
||||
source: colorData.source,
|
||||
a: colorData.a || color.getAlpha(),
|
||||
};
|
||||
}
|
||||
|
||||
export function clamp(value, min, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getIncrement(key, keys, increment) {
|
||||
return oneOf(key, keys) ? increment : 0;
|
||||
}
|
||||
|
||||
export function getTouches(e, prop) {
|
||||
return e.touches ? e.touches[0][prop] : 0;
|
||||
}
|
||||
|
||||
export function toRGBAString(rgba) {
|
||||
const {r, g, b, a} = rgba;
|
||||
|
||||
return `rgba(${[r, g, b, a].join(',')})`;
|
||||
}
|
||||
|
||||
export function isValidHex(hex) {
|
||||
return tinycolor(hex).isValid();
|
||||
}
|
||||
|
||||
function checkIteratee(data, counts, letter) {
|
||||
let {checked, passed} = counts;
|
||||
const value = data[letter];
|
||||
|
||||
if (value) {
|
||||
checked += 1;
|
||||
|
||||
if (Number.isFinite(value)) {
|
||||
passed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {checked, passed};
|
||||
}
|
||||
|
||||
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||
|
||||
export function simpleCheckForValidColor(data) {
|
||||
const results = keysToCheck.reduce(checkIteratee.bind(null, data), {checked: 0, passed: 0});
|
||||
|
||||
return results.checked === results.passed ? data : undefined;
|
||||
}
|
|
@ -1,37 +1,47 @@
|
|||
<template>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="timeClasses" v-if="showTime" @click="handleToggleTime">
|
||||
<template v-if="isTime">{{ t('i.datepicker.selectDate') }}</template>
|
||||
<template v-else>{{ t('i.datepicker.selectTime') }}</template>
|
||||
</span>
|
||||
<i-button size="small" type="text" @click.native="handleClear">{{ t('i.datepicker.clear') }}</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess">{{ t('i.datepicker.ok') }}</i-button>
|
||||
<div :class="[prefixCls + '-confirm']" @keydown.tab.capture="handleTab">
|
||||
<i-button :class="timeClasses" size="small" type="text" :disabled="timeDisabled" v-if="showTime" @click="handleToggleTime">
|
||||
{{labels.time}}
|
||||
</i-button>
|
||||
<i-button size="small" type="ghost" @click.native="handleClear" @keydown.enter.native="handleClear">
|
||||
{{labels.clear}}
|
||||
</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess" @keydown.enter.native="handleSuccess">
|
||||
{{labels.ok}}
|
||||
</i-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import iButton from '../../button/button.vue';
|
||||
import Locale from '../../../mixins/locale';
|
||||
import Emitter from '../../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-picker';
|
||||
|
||||
export default {
|
||||
mixins: [ Locale ],
|
||||
components: { iButton },
|
||||
mixins: [Locale, Emitter],
|
||||
components: {iButton},
|
||||
props: {
|
||||
showTime: false,
|
||||
isTime: false,
|
||||
timeDisabled: false
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeClasses () {
|
||||
return {
|
||||
[`${prefixCls}-confirm-time-disabled`]: this.timeDisabled
|
||||
};
|
||||
return `${prefixCls}-confirm-time`;
|
||||
},
|
||||
labels(){
|
||||
const labels = ['time', 'clear', 'ok'];
|
||||
const values = [(this.isTime ? 'selectDate' : 'selectTime'), 'clear', 'ok'];
|
||||
return labels.reduce((obj, key, i) => {
|
||||
obj[key] = this.t('i.datepicker.' + values[i]);
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -44,6 +54,17 @@
|
|||
handleToggleTime () {
|
||||
if (this.timeDisabled) return;
|
||||
this.$emit('on-pick-toggle-time');
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
},
|
||||
handleTab(e) {
|
||||
const tabbables = [...this.$el.children];
|
||||
const expectedFocus = tabbables[e.shiftKey ? 'shift' : 'pop']();
|
||||
|
||||
if (document.activeElement === expectedFocus) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
</div>
|
||||
<span
|
||||
:class="getCellCls(cell)"
|
||||
v-for="(cell, i) in readCells"
|
||||
v-for="(cell, i) in cells"
|
||||
:key="String(cell.date) + i"
|
||||
@click="handleClick(cell)"
|
||||
@click="handleClick(cell, $event)"
|
||||
@mouseenter="handleMouseMove(cell)"
|
||||
>
|
||||
<em>{{ cell.desc }}</em>
|
||||
|
@ -36,10 +36,8 @@
|
|||
},
|
||||
},
|
||||
data () {
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
calendar: new jsCalendar.Generator({onlyDays: !this.showWeekNumbers, weekStart: weekStartDay})
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -51,6 +49,10 @@
|
|||
}
|
||||
];
|
||||
},
|
||||
calendar(){
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
return new jsCalendar.Generator({onlyDays: !this.showWeekNumbers, weekStart: weekStartDay});
|
||||
},
|
||||
headerDays () {
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
const translatedDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].map(item => {
|
||||
|
@ -59,7 +61,7 @@
|
|||
const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay));
|
||||
return this.showWeekNumbers ? [''].concat(weekDays) : weekDays;
|
||||
},
|
||||
readCells () {
|
||||
cells () {
|
||||
const tableYear = this.tableDate.getFullYear();
|
||||
const tableMonth = this.tableDate.getMonth();
|
||||
const today = clearHours(new Date()); // timestamp of today
|
||||
|
@ -97,7 +99,9 @@
|
|||
[`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth',
|
||||
[`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth',
|
||||
[`${prefixCls}-cell-week-label`]: cell.type === 'weekLabel',
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end,
|
||||
[`${prefixCls}-focused`]: clearHours(cell.date) === clearHours(this.focusedDate)
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import {clearHours} from '../util';
|
||||
|
||||
export default {
|
||||
name: 'PanelTable',
|
||||
props: {
|
||||
tableDate: {
|
||||
type: Date,
|
||||
|
@ -26,7 +27,10 @@ export default {
|
|||
selecting: false
|
||||
})
|
||||
},
|
||||
|
||||
focusedDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dates(){
|
||||
|
|
|
@ -38,14 +38,16 @@
|
|||
|
||||
const tableYear = this.tableDate.getFullYear();
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), this.focusedDate.getMonth(), 1));
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.date = new Date(tableYear, i, 1);
|
||||
cell.text = this.tCell(i + 1);
|
||||
const time = clearHours(cell.date);
|
||||
const day = clearHours(cell.date);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month';
|
||||
cell.selected = selectedDays.includes(time);
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,7 @@
|
|||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
|
|
|
@ -22,8 +22,10 @@
|
|||
import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-time-picker-cells';
|
||||
const timeParts = ['hours', 'minutes', 'seconds'];
|
||||
|
||||
export default {
|
||||
name: 'TimeSpinner',
|
||||
mixins: [Options],
|
||||
props: {
|
||||
hours: {
|
||||
|
@ -51,7 +53,9 @@
|
|||
return {
|
||||
spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one),
|
||||
prefixCls: prefixCls,
|
||||
compiled: false
|
||||
compiled: false,
|
||||
focusedColumn: -1, // which column inside the picker
|
||||
focusedTime: [0, 0, 0] // the values array into [hh, mm, ss]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -66,6 +70,7 @@
|
|||
hoursList () {
|
||||
let hours = [];
|
||||
const step = this.spinerSteps[0];
|
||||
const focusedHour = this.focusedColumn === 0 && this.focusedTime[0];
|
||||
const hour_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -76,6 +81,7 @@
|
|||
for (let i = 0; i < 24; i += step) {
|
||||
const hour = deepCopy(hour_tmpl);
|
||||
hour.text = i;
|
||||
hour.focused = i === focusedHour;
|
||||
|
||||
if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) {
|
||||
hour.disabled = true;
|
||||
|
@ -90,6 +96,7 @@
|
|||
minutesList () {
|
||||
let minutes = [];
|
||||
const step = this.spinerSteps[1];
|
||||
const focusedMinute = this.focusedColumn === 1 && this.focusedTime[1];
|
||||
const minute_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -100,6 +107,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const minute = deepCopy(minute_tmpl);
|
||||
minute.text = i;
|
||||
minute.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) {
|
||||
minute.disabled = true;
|
||||
|
@ -113,6 +121,7 @@
|
|||
secondsList () {
|
||||
let seconds = [];
|
||||
const step = this.spinerSteps[2];
|
||||
const focusedMinute = this.focusedColumn === 2 && this.focusedTime[2];
|
||||
const second_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -123,6 +132,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const second = deepCopy(second_tmpl);
|
||||
second.text = i;
|
||||
second.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) {
|
||||
second.disabled = true;
|
||||
|
@ -141,15 +151,32 @@
|
|||
`${prefixCls}-cell`,
|
||||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
chooseValue(values){
|
||||
const changes = timeParts.reduce((obj, part, i) => {
|
||||
const value = values[i];
|
||||
if (this[part] === value) return obj;
|
||||
return {
|
||||
...obj,
|
||||
[part]: value
|
||||
};
|
||||
}, {});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
this.emitChange(changes);
|
||||
}
|
||||
},
|
||||
handleClick (type, cell) {
|
||||
if (cell.disabled) return;
|
||||
const data = {};
|
||||
data[type] = cell.text;
|
||||
this.$emit('on-change', data);
|
||||
const data = {[type]: cell.text};
|
||||
this.emitChange(data);
|
||||
},
|
||||
emitChange(changes){
|
||||
this.$emit('on-change', changes);
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
scroll (type, index) {
|
||||
|
@ -168,15 +195,19 @@
|
|||
return index;
|
||||
},
|
||||
updateScroll () {
|
||||
const times = ['hours', 'minutes', 'seconds'];
|
||||
this.$nextTick(() => {
|
||||
times.forEach(type => {
|
||||
timeParts.forEach(type => {
|
||||
this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]);
|
||||
});
|
||||
});
|
||||
},
|
||||
formatTime (text) {
|
||||
return text < 10 ? '0' + text : text;
|
||||
},
|
||||
updateFocusedTime(col, time) {
|
||||
this.focusedColumn = col;
|
||||
this.focusedTime = time.slice();
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -191,6 +222,13 @@
|
|||
seconds (val) {
|
||||
if (!this.compiled) return;
|
||||
this.scroll('seconds', this.secondsList.findIndex(obj => obj.text == val));
|
||||
},
|
||||
focusedTime(updated, old){
|
||||
timeParts.forEach((part, i) => {
|
||||
if (updated[i] === old[i] || typeof updated[i] === 'undefined') return;
|
||||
const valueIndex = this[`${part}List`].findIndex(obj => obj.text === updated[i]);
|
||||
this.scroll(part, valueIndex);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -39,13 +39,15 @@
|
|||
};
|
||||
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), 0, 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), 0, 1));
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.date = new Date(this.startYear + i, 0, 1);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'year';
|
||||
const time = clearHours(cell.date);
|
||||
cell.selected = selectedDays.includes(time);
|
||||
const day = clearHours(cell.date);
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,7 @@
|
|||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
|
|
|
@ -46,6 +46,10 @@ export default {
|
|||
pickerType: {
|
||||
type: String,
|
||||
require: true
|
||||
},
|
||||
focusedDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
v-for="shortcut in shortcuts"
|
||||
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="panelBodyClasses">
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-left']" v-show="!isTime">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'">
|
||||
<span
|
||||
|
@ -41,6 +41,8 @@
|
|||
:range-state="rangeState"
|
||||
:show-week-numbers="showWeekNumbers"
|
||||
:value="preSelecting.left ? [dates[0]] : dates"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-change-range="handleChangeRange"
|
||||
@on-pick="panelPickerHandlers.left"
|
||||
@on-pick-click="handlePickClick"
|
||||
|
@ -80,6 +82,8 @@
|
|||
:disabled-date="disabledDate"
|
||||
:show-week-numbers="showWeekNumbers"
|
||||
:value="preSelecting.right ? [dates[dates.length - 1]] : dates"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-change-range="handleChangeRange"
|
||||
@on-pick="panelPickerHandlers.right"
|
||||
@on-pick-click="handlePickClick"></component>
|
||||
|
@ -157,7 +161,7 @@
|
|||
leftPickerTable: `${this.selectionMode}-table`,
|
||||
rightPickerTable: `${this.selectionMode}-table`,
|
||||
leftPanelDate: leftPanelDate,
|
||||
rightPanelDate: new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate())
|
||||
rightPanelDate: new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, 1)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -171,6 +175,15 @@
|
|||
}
|
||||
];
|
||||
},
|
||||
panelBodyClasses(){
|
||||
return [
|
||||
prefixCls + '-body',
|
||||
{
|
||||
[prefixCls + '-body-time']: this.showTime,
|
||||
[prefixCls + '-body-date']: !this.showTime,
|
||||
}
|
||||
];
|
||||
},
|
||||
leftDatePanelLabel(){
|
||||
return this.panelLabelConfig('left');
|
||||
},
|
||||
|
@ -215,10 +228,7 @@
|
|||
|
||||
|
||||
// set panels positioning
|
||||
const leftPanelDate = this.startDate || this.dates[0] || new Date();
|
||||
this.leftPanelDate = leftPanelDate;
|
||||
const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate());
|
||||
this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate;
|
||||
this.setPanelDates(this.startDate || this.dates[0] || new Date());
|
||||
},
|
||||
currentView(currentView){
|
||||
const leftMonth = this.leftPanelDate.getMonth();
|
||||
|
@ -237,6 +247,9 @@
|
|||
},
|
||||
selectionMode(type){
|
||||
this.currentView = type || 'range';
|
||||
},
|
||||
focusedDate(date){
|
||||
this.setPanelDates(date || new Date());
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -245,6 +258,11 @@
|
|||
this.leftPickerTable = `${this.currentView}-table`;
|
||||
this.rightPickerTable = `${this.currentView}-table`;
|
||||
},
|
||||
setPanelDates(leftPanelDate){
|
||||
this.leftPanelDate = leftPanelDate;
|
||||
const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, leftPanelDate.getDate());
|
||||
this.rightPanelDate = this.splitPanels ? new Date(Math.max(this.dates[1], rightPanelDate)) : rightPanelDate;
|
||||
},
|
||||
panelLabelConfig (direction) {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
|
@ -275,11 +293,12 @@
|
|||
nextMonth(panel){
|
||||
this.changePanelDate(panel, 'Month', 1);
|
||||
},
|
||||
changePanelDate(panel, type, increment){
|
||||
changePanelDate(panel, type, increment, updateOtherPanel = true){
|
||||
const current = new Date(this[`${panel}PanelDate`]);
|
||||
current[`set${type}`](current[`get${type}`]() + increment);
|
||||
this[`${panel}PanelDate`] = current;
|
||||
|
||||
if (!updateOtherPanel) return;
|
||||
|
||||
if (this.splitPanels){
|
||||
// change other panel if dates overlap
|
||||
|
@ -312,12 +331,11 @@
|
|||
|
||||
if (!this.splitPanels){
|
||||
const otherPanel = panel === 'left' ? 'right' : 'left';
|
||||
const type = currentViewType === 'year-table' ? 'FullYear' : 'Month';
|
||||
this[`${otherPanel}PanelDate`] = value;
|
||||
this.changePanelDate(otherPanel, type, 1);
|
||||
this.changePanelDate(otherPanel, 'Month', 1, false);
|
||||
}
|
||||
},
|
||||
handleRangePick (val) {
|
||||
handleRangePick (val, type) {
|
||||
if (this.rangeState.selecting || this.currentView === 'time'){
|
||||
if (this.currentView === 'time'){
|
||||
this.dates = val;
|
||||
|
@ -330,7 +348,7 @@
|
|||
selecting: false
|
||||
};
|
||||
}
|
||||
this.handleConfirm(false);
|
||||
this.handleConfirm(false, type || 'date');
|
||||
} else {
|
||||
this.rangeState = {
|
||||
from: val,
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
:value="dates"
|
||||
:selection-mode="selectionMode"
|
||||
:disabled-date="disabledDate"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-pick="panelPickerHandlers"
|
||||
@on-pick-click="handlePickClick"
|
||||
></component>
|
||||
|
@ -50,6 +52,9 @@
|
|||
:value="dates"
|
||||
:format="format"
|
||||
:time-disabled="timeDisabled"
|
||||
:disabled-date="disabledDate"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
v-bind="timePickerOptions"
|
||||
@on-pick="handlePick"
|
||||
@on-pick-click="handlePickClick"
|
||||
|
@ -149,11 +154,24 @@
|
|||
},
|
||||
currentView (currentView) {
|
||||
this.$emit('on-selection-mode-change', currentView);
|
||||
this.pickertable = this.getTableType(currentView);
|
||||
|
||||
if (this.currentView === 'time') {
|
||||
this.$nextTick(() => {
|
||||
const spinner = this.$refs.timePicker.$refs.timeSpinner;
|
||||
spinner.updateScroll();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectionMode(type){
|
||||
this.currentView = type;
|
||||
this.pickerTable = this.getTableType(type);
|
||||
},
|
||||
focusedDate(date){
|
||||
const isDifferentYear = date.getFullYear() !== this.panelDate.getFullYear();
|
||||
const isDifferentMonth = isDifferentYear || date.getMonth() !== this.panelDate.getMonth();
|
||||
if (isDifferentYear || isDifferentMonth){
|
||||
this.panelDate = date;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -180,13 +198,14 @@
|
|||
else this.pickerTable = this.getTableType(this.currentView);
|
||||
|
||||
},
|
||||
handlePick (value) {
|
||||
handlePick (value, type) {
|
||||
const {selectionMode, panelDate} = this;
|
||||
if (selectionMode === 'year') value = new Date(value.getFullYear(), 0, 1);
|
||||
else if (selectionMode === 'month') value = new Date(panelDate.getFullYear(), value.getMonth(), 1);
|
||||
else value = new Date(value);
|
||||
|
||||
this.$emit('on-pick', value);
|
||||
this.dates = [value];
|
||||
this.$emit('on-pick', value, false, type || selectionMode);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
// judge endTime > startTime?
|
||||
if (dateEnd < dateStart) dateEnd = dateStart;
|
||||
|
||||
if (emit) this.$emit('on-pick', [dateStart, dateEnd], true);
|
||||
if (emit) this.$emit('on-pick', [dateStart, dateEnd], 'time');
|
||||
},
|
||||
handleStartChange (date) {
|
||||
this.handleChange(date, {});
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
ref="timeSpinner"
|
||||
:show-seconds="showSeconds"
|
||||
:steps="steps"
|
||||
:hours="value[0] && date.getHours()"
|
||||
:minutes="value[0] && date.getMinutes()"
|
||||
:seconds="value[0] && date.getSeconds()"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hours="timeSlots[0]"
|
||||
:minutes="timeSlots[1]"
|
||||
:seconds="timeSlots[2]"
|
||||
:disabled-hours="disabledHMS.disabledHours"
|
||||
:disabled-minutes="disabledHMS.disabledMinutes"
|
||||
:disabled-seconds="disabledHMS.disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
|
@ -39,12 +39,25 @@
|
|||
const timePrefixCls = 'ivu-time-picker';
|
||||
|
||||
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
|
||||
const mergeDateHMS = (date, hours, minutes, seconds) => {
|
||||
const newDate = new Date(date.getTime());
|
||||
newDate.setHours(hours);
|
||||
newDate.setMinutes(minutes);
|
||||
newDate.setSeconds(seconds);
|
||||
return newDate;
|
||||
};
|
||||
const unique = (el, i, arr) => arr.indexOf(el) === i;
|
||||
const returnFalse = () => false;
|
||||
|
||||
export default {
|
||||
name: 'TimePickerPanel',
|
||||
mixins: [ Mixin, Locale, Options ],
|
||||
components: { TimeSpinner, Confirm },
|
||||
props: {
|
||||
disabledDate: {
|
||||
type: Function,
|
||||
default: returnFalse
|
||||
},
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
|
@ -76,6 +89,35 @@
|
|||
const tYear = this.t('i.datepicker.year');
|
||||
const tMonth = this.t(`i.datepicker.month${month}`);
|
||||
return `${date.getFullYear()}${tYear} ${tMonth}`;
|
||||
},
|
||||
timeSlots(){
|
||||
if (!this.value[0]) return [];
|
||||
return ['getHours', 'getMinutes', 'getSeconds'].map(slot => this.date[slot]());
|
||||
},
|
||||
disabledHMS(){
|
||||
const disabledTypes = ['disabledHours', 'disabledMinutes', 'disabledSeconds'];
|
||||
if (this.disabledDate === returnFalse || !this.value[0]) {
|
||||
const disabled = disabledTypes.reduce(
|
||||
(obj, type) => (obj[type] = this[type], obj), {}
|
||||
);
|
||||
return disabled;
|
||||
} else {
|
||||
const slots = [24, 60, 60];
|
||||
const disabled = ['Hours', 'Minutes', 'Seconds'].map(type => this[`disabled${type}`]);
|
||||
const disabledHMS = disabled.map((preDisabled, j) => {
|
||||
const slot = slots[j];
|
||||
const toDisable = preDisabled;
|
||||
for (let i = 0; i < slot; i+= (this.steps[j] || 1)){
|
||||
const hms = this.timeSlots.map((slot, x) => x === j ? i : slot);
|
||||
const testDateTime = mergeDateHMS(this.date, ...hms);
|
||||
if (this.disabledDate(testDateTime, true)) toDisable.push(i);
|
||||
}
|
||||
return toDisable.filter(unique);
|
||||
});
|
||||
return disabledTypes.reduce(
|
||||
(obj, type, i) => (obj[type] = disabledHMS[i], obj), {}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -93,7 +135,7 @@
|
|||
type => newDate[`set${capitalize(type)}`](date[type])
|
||||
);
|
||||
|
||||
if (emit) this.$emit('on-pick', newDate, true);
|
||||
if (emit) this.$emit('on-pick', newDate, 'time');
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -44,8 +44,8 @@ export default {
|
|||
this.handleConfirm();
|
||||
// if (this.showTime) this.$refs.timePicker.handleClear();
|
||||
},
|
||||
handleConfirm(visible) {
|
||||
this.$emit('on-pick', this.dates, visible);
|
||||
handleConfirm(visible, type) {
|
||||
this.$emit('on-pick', this.dates, visible, type || this.type);
|
||||
},
|
||||
onToggleVisibility(open){
|
||||
const {timeSpinner, timeSpinnerEnd} = this.$refs;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div :class="[prefixCls]" v-clickoutside="handleClose">
|
||||
<div
|
||||
:class="wrapperClasses"
|
||||
v-click-outside:mousedown.capture="handleClose"
|
||||
v-click-outside.capture="handleClose"
|
||||
>
|
||||
<div ref="reference" :class="[prefixCls + '-rel']">
|
||||
<slot>
|
||||
<i-input
|
||||
|
@ -12,10 +16,14 @@
|
|||
:placeholder="placeholder"
|
||||
:value="visualValue"
|
||||
:name="name"
|
||||
ref="input"
|
||||
|
||||
@on-input-change="handleInputChange"
|
||||
@on-focus="handleFocus"
|
||||
@on-blur="handleBlur"
|
||||
@on-click="handleIconClick"
|
||||
@click.native="handleFocus"
|
||||
@keydown.native="handleKeydown"
|
||||
@mouseenter.native="handleInputMouseenter"
|
||||
@mouseleave.native="handleInputMouseleave"
|
||||
|
||||
|
@ -48,6 +56,7 @@
|
|||
:show-week-numbers="showWeekNumbers"
|
||||
:picker-type="type"
|
||||
:multiple="multiple"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
:time-picker-options="timePickerOptions"
|
||||
|
||||
|
@ -69,21 +78,49 @@
|
|||
|
||||
import iInput from '../../components/input/input.vue';
|
||||
import Drop from '../../components/select/dropdown.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import { DEFAULT_FORMATS, TYPE_VALUE_RESOLVER_MAP } from './util';
|
||||
import { DEFAULT_FORMATS, RANGE_SEPARATOR, TYPE_VALUE_RESOLVER_MAP, getDayCountOfMonth } from './util';
|
||||
import {findComponentsDownward} from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-date-picker';
|
||||
const pickerPrefixCls = 'ivu-picker';
|
||||
|
||||
const isEmptyArray = val => val.reduce((isEmpty, str) => isEmpty && !str || (typeof str === 'string' && str.trim() === ''), true);
|
||||
const keyValueMapper = {
|
||||
40: 'up',
|
||||
39: 'right',
|
||||
38: 'down',
|
||||
37: 'left',
|
||||
};
|
||||
|
||||
const mapPossibleValues = (key, horizontal, vertical) => {
|
||||
if (key === 'left') return horizontal * -1;
|
||||
if (key === 'right') return horizontal * 1;
|
||||
if (key === 'up') return vertical * 1;
|
||||
if (key === 'down') return vertical * -1;
|
||||
};
|
||||
|
||||
const pulseElement = (el) => {
|
||||
const pulseClass = 'ivu-date-picker-btn-pulse';
|
||||
el.classList.add(pulseClass);
|
||||
setTimeout(() => el.classList.remove(pulseClass), 200);
|
||||
};
|
||||
|
||||
const extractTime = date => {
|
||||
if (!date) return [0, 0, 0];
|
||||
return [
|
||||
date.getHours(), date.getMinutes(), date.getSeconds()
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
name: 'CalendarPicker',
|
||||
mixins: [ Emitter ],
|
||||
components: { iInput, Drop },
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
props: {
|
||||
format: {
|
||||
type: String
|
||||
|
@ -172,6 +209,7 @@
|
|||
const isRange = this.type.includes('range');
|
||||
const emptyArray = isRange ? [null, null] : [null];
|
||||
const initialValue = isEmptyArray((isRange ? this.value : [this.value]) || []) ? emptyArray : this.parseDate(this.value);
|
||||
const focusedTime = initialValue.map(extractTime);
|
||||
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
|
@ -181,10 +219,24 @@
|
|||
disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
|
||||
disableCloseUnderTransfer: false, // transfer 模式下,点击Drop也会触发关闭,
|
||||
selectionMode: this.onSelectionModeChange(this.type),
|
||||
forceInputRerender: 1
|
||||
forceInputRerender: 1,
|
||||
isFocused: false,
|
||||
focusedDate: initialValue[0] || this.startDate || new Date(),
|
||||
focusedTime: {
|
||||
column: 0, // which column inside the picker
|
||||
picker: 0, // which picker
|
||||
time: focusedTime, // the values array into [hh, mm, ss],
|
||||
active: false
|
||||
},
|
||||
internalFocus: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapperClasses(){
|
||||
return [prefixCls, {
|
||||
[prefixCls + '-focused']: this.isFocused
|
||||
}];
|
||||
},
|
||||
publicVModelValue(){
|
||||
if (this.multiple){
|
||||
return this.internalValue.slice();
|
||||
|
@ -232,32 +284,254 @@
|
|||
handleTransferClick () {
|
||||
if (this.transfer) this.disableCloseUnderTransfer = true;
|
||||
},
|
||||
handleClose () {
|
||||
handleClose (e) {
|
||||
if (this.disableCloseUnderTransfer) {
|
||||
this.disableCloseUnderTransfer = false;
|
||||
return false;
|
||||
}
|
||||
if (this.open !== null) return;
|
||||
|
||||
this.visible = false;
|
||||
if (e && e.type === 'mousedown' && this.visible) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.visible) {
|
||||
const pickerPanel = this.$refs.pickerPanel && this.$refs.pickerPanel.$el;
|
||||
if (e && pickerPanel && pickerPanel.contains(e.target)) return; // its a click inside own component, lets ignore it.
|
||||
|
||||
this.visible = false;
|
||||
e && e.preventDefault();
|
||||
e && e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isFocused = false;
|
||||
this.disableClickOutSide = false;
|
||||
},
|
||||
handleFocus () {
|
||||
handleFocus (e) {
|
||||
if (this.readonly) return;
|
||||
this.isFocused = true;
|
||||
if (e && e.type === 'focus') return; // just focus, don't open yet
|
||||
this.visible = true;
|
||||
this.$refs.pickerPanel.onToggleVisibility(true);
|
||||
},
|
||||
handleBlur () {
|
||||
this.visible = false;
|
||||
handleBlur (e) {
|
||||
if (this.internalFocus){
|
||||
this.internalFocus = false;
|
||||
return;
|
||||
}
|
||||
if (this.visible) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isFocused = false;
|
||||
this.onSelectionModeChange(this.type);
|
||||
this.internalValue = this.internalValue.slice(); // trigger panel watchers to reset views
|
||||
this.reset();
|
||||
this.$refs.pickerPanel.onToggleVisibility(false);
|
||||
|
||||
},
|
||||
handleKeydown(e){
|
||||
const keyCode = e.keyCode;
|
||||
|
||||
// handle "tab" key
|
||||
if (keyCode === 9){
|
||||
if (this.visible){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (this.isConfirm){
|
||||
const selector = `.${pickerPrefixCls}-confirm > *`;
|
||||
const tabbable = this.$refs.drop.$el.querySelectorAll(selector);
|
||||
this.internalFocus = true;
|
||||
const element = [...tabbable][e.shiftKey ? 'pop' : 'shift']();
|
||||
element.focus();
|
||||
} else {
|
||||
this.handleClose();
|
||||
}
|
||||
} else {
|
||||
this.focused = false;
|
||||
}
|
||||
}
|
||||
|
||||
// open the panel
|
||||
const arrows = [37, 38, 39, 40];
|
||||
if (!this.visible && arrows.includes(keyCode)){
|
||||
this.visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// close on "esc" key
|
||||
if (keyCode === 27){
|
||||
if (this.visible) {
|
||||
e.stopPropagation();
|
||||
this.handleClose();
|
||||
}
|
||||
}
|
||||
|
||||
// select date, "Enter" key
|
||||
if (keyCode === 13){
|
||||
const timePickers = findComponentsDownward(this, 'TimeSpinner');
|
||||
if (timePickers.length > 0){
|
||||
const columnsPerPicker = timePickers[0].showSeconds ? 3 : 2;
|
||||
const pickerIndex = Math.floor(this.focusedTime.column / columnsPerPicker);
|
||||
const value = this.focusedTime.time[pickerIndex];
|
||||
|
||||
timePickers[pickerIndex].chooseValue(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.type.match(/range/)){
|
||||
this.$refs.pickerPanel.handleRangePick(this.focusedDate, 'date');
|
||||
} else {
|
||||
const panels = findComponentsDownward(this, 'PanelTable');
|
||||
const compareDate = (d) => {
|
||||
const sliceIndex = ['year', 'month', 'date'].indexOf((this.type)) + 1;
|
||||
return [d.getFullYear(), d.getMonth(), d.getDate()].slice(0, sliceIndex).join('-');
|
||||
};
|
||||
const dateIsValid = panels.find(({cells}) => {
|
||||
return cells.find(({date, disabled}) => compareDate(date) === compareDate(this.focusedDate) && !disabled);
|
||||
});
|
||||
if (dateIsValid) this.onPick(this.focusedDate, false, 'date');
|
||||
}
|
||||
}
|
||||
|
||||
if (!arrows.includes(keyCode)) return; // ignore rest of keys
|
||||
|
||||
// navigate times and dates
|
||||
if (this.focusedTime.active) e.preventDefault(); // to prevent cursor from moving
|
||||
this.navigateDatePanel(keyValueMapper[keyCode], e.shiftKey);
|
||||
},
|
||||
reset(){
|
||||
this.$refs.pickerPanel.reset && this.$refs.pickerPanel.reset();
|
||||
},
|
||||
navigateTimePanel(direction){
|
||||
|
||||
this.focusedTime.active = true;
|
||||
const horizontal = direction.match(/left|right/);
|
||||
const vertical = direction.match(/up|down/);
|
||||
const timePickers = findComponentsDownward(this, 'TimeSpinner');
|
||||
|
||||
const maxNrOfColumns = (timePickers[0].showSeconds ? 3 : 2) * timePickers.length;
|
||||
const column = (currentColumn => {
|
||||
const incremented = currentColumn + (horizontal ? (direction === 'left' ? -1 : 1) : 0);
|
||||
return (incremented + maxNrOfColumns) % maxNrOfColumns;
|
||||
})(this.focusedTime.column);
|
||||
|
||||
const columnsPerPicker = maxNrOfColumns / timePickers.length;
|
||||
const pickerIndex = Math.floor(column / columnsPerPicker);
|
||||
const col = column % columnsPerPicker;
|
||||
|
||||
|
||||
if (horizontal){
|
||||
const time = this.internalValue.map(extractTime);
|
||||
|
||||
this.focusedTime = {
|
||||
...this.focusedTime,
|
||||
column: column,
|
||||
time: time
|
||||
};
|
||||
timePickers.forEach((instance, i) => {
|
||||
if (i === pickerIndex) instance.updateFocusedTime(col, time[pickerIndex]);
|
||||
else instance.updateFocusedTime(-1, instance.focusedTime);
|
||||
});
|
||||
}
|
||||
|
||||
if (vertical){
|
||||
const increment = direction === 'up' ? 1 : -1;
|
||||
const timeParts = ['hours', 'minutes', 'seconds'];
|
||||
|
||||
|
||||
const pickerPossibleValues = timePickers[pickerIndex][`${timeParts[col]}List`];
|
||||
const nextIndex = pickerPossibleValues.findIndex(({text}) => this.focusedTime.time[pickerIndex][col] === text) + increment;
|
||||
const nextValue = pickerPossibleValues[nextIndex % pickerPossibleValues.length].text;
|
||||
const times = this.focusedTime.time.map((time, i) => {
|
||||
if (i !== pickerIndex) return time;
|
||||
time[col] = nextValue;
|
||||
return time;
|
||||
});
|
||||
this.focusedTime = {
|
||||
...this.focusedTime,
|
||||
time: times
|
||||
};
|
||||
|
||||
timePickers.forEach((instance, i) => {
|
||||
if (i === pickerIndex) instance.updateFocusedTime(col, times[i]);
|
||||
else instance.updateFocusedTime(-1, instance.focusedTime);
|
||||
});
|
||||
}
|
||||
},
|
||||
navigateDatePanel(direction, shift){
|
||||
|
||||
const timePickers = findComponentsDownward(this, 'TimeSpinner');
|
||||
if (timePickers.length > 0) {
|
||||
// we are in TimePicker mode
|
||||
this.navigateTimePanel(direction, shift, timePickers);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shift){
|
||||
if (this.type === 'year'){
|
||||
this.focusedDate = new Date(
|
||||
this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 10),
|
||||
this.focusedDate.getMonth(),
|
||||
this.focusedDate.getDate()
|
||||
);
|
||||
} else {
|
||||
this.focusedDate = new Date(
|
||||
this.focusedDate.getFullYear() + mapPossibleValues(direction, 0, 1),
|
||||
this.focusedDate.getMonth() + mapPossibleValues(direction, 1, 0),
|
||||
this.focusedDate.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
const position = direction.match(/left|down/) ? 'prev' : 'next';
|
||||
const double = direction.match(/up|down/) ? '-double' : '';
|
||||
|
||||
// pulse button
|
||||
const button = this.$refs.drop.$el.querySelector(`.ivu-date-picker-${position}-btn-arrow${double}`);
|
||||
if (button) pulseElement(button);
|
||||
return;
|
||||
}
|
||||
|
||||
const initialDate = this.focusedDate || (this.internalValue && this.internalValue[0]) || new Date();
|
||||
const focusedDate = new Date(initialDate);
|
||||
|
||||
if (this.type.match(/^date/)){
|
||||
const lastOfMonth = getDayCountOfMonth(initialDate.getFullYear(), initialDate.getMonth());
|
||||
const startDay = initialDate.getDate();
|
||||
const nextDay = focusedDate.getDate() + mapPossibleValues(direction, 1, 7);
|
||||
|
||||
if (nextDay < 1) {
|
||||
if (direction.match(/left|right/)) {
|
||||
focusedDate.setMonth(focusedDate.getMonth() + 1);
|
||||
focusedDate.setDate(nextDay);
|
||||
} else {
|
||||
focusedDate.setDate(startDay + Math.floor((lastOfMonth - startDay) / 7) * 7);
|
||||
}
|
||||
} else if (nextDay > lastOfMonth){
|
||||
if (direction.match(/left|right/)) {
|
||||
focusedDate.setMonth(focusedDate.getMonth() - 1);
|
||||
focusedDate.setDate(nextDay);
|
||||
} else {
|
||||
focusedDate.setDate(startDay % 7);
|
||||
}
|
||||
} else {
|
||||
focusedDate.setDate(nextDay);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.type.match(/^month/)) {
|
||||
focusedDate.setMonth(focusedDate.getMonth() + mapPossibleValues(direction, 1, 3));
|
||||
}
|
||||
|
||||
if (this.type.match(/^year/)) {
|
||||
focusedDate.setFullYear(focusedDate.getFullYear() + mapPossibleValues(direction, 1, 3));
|
||||
}
|
||||
|
||||
this.focusedDate = focusedDate;
|
||||
},
|
||||
handleInputChange (event) {
|
||||
const isArrayValue = this.type.includes('range') || this.multiple;
|
||||
const oldValue = this.visualValue;
|
||||
|
@ -272,7 +546,7 @@
|
|||
const isValidDate = newDate.reduce((valid, date) => valid && date instanceof Date, true);
|
||||
|
||||
if (newValue !== oldValue && !isDisabled && isValidDate) {
|
||||
this.emitChange();
|
||||
this.emitChange(this.type);
|
||||
this.internalValue = newDate;
|
||||
} else {
|
||||
this.forceInputRerender++;
|
||||
|
@ -299,7 +573,7 @@
|
|||
this.internalValue = this.internalValue.map(() => null);
|
||||
this.$emit('on-clear');
|
||||
this.dispatch('FormItem', 'on-form-change', '');
|
||||
this.emitChange();
|
||||
this.emitChange(this.type);
|
||||
this.reset();
|
||||
|
||||
setTimeout(
|
||||
|
@ -307,9 +581,9 @@
|
|||
500 // delay to improve dropdown close visual effect
|
||||
);
|
||||
},
|
||||
emitChange () {
|
||||
emitChange (type) {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('on-change', this.publicStringValue);
|
||||
this.$emit('on-change', this.publicStringValue, type);
|
||||
this.dispatch('FormItem', 'on-form-change', this.publicStringValue);
|
||||
});
|
||||
},
|
||||
|
@ -336,8 +610,14 @@
|
|||
} else if (type === 'timerange') {
|
||||
val = parser(val, format).map(v => v || '');
|
||||
} else {
|
||||
val = val.map(date => new Date(date)); // try to parse
|
||||
val = val.map(date => isNaN(date.getTime()) ? null : date); // check if parse passed
|
||||
const [start, end] = val;
|
||||
if (start instanceof Date && end instanceof Date){
|
||||
val = val.map(date => new Date(date));
|
||||
} else if (typeof start === 'string' && typeof end === 'string'){
|
||||
val = parser(val.join(RANGE_SEPARATOR), format);
|
||||
} else if (!start || !end){
|
||||
val = [null, null];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof val === 'string' && type.indexOf('time') !== 0){
|
||||
|
@ -360,7 +640,7 @@
|
|||
return formatter(value, this.format || format);
|
||||
}
|
||||
},
|
||||
onPick(dates, visible = false) {
|
||||
onPick(dates, visible = false, type) {
|
||||
if (this.multiple){
|
||||
const pickedTimeStamp = dates.getTime();
|
||||
const indexOfPickedDate = this.internalValue.findIndex(date => date && date.getTime() === pickedTimeStamp);
|
||||
|
@ -371,29 +651,36 @@
|
|||
this.internalValue = Array.isArray(dates) ? dates : [dates];
|
||||
}
|
||||
|
||||
if (this.internalValue[0]) this.focusedDate = this.internalValue[0];
|
||||
this.focusedTime = {
|
||||
...this.focusedTime,
|
||||
time: this.internalValue.map(extractTime)
|
||||
};
|
||||
|
||||
if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
|
||||
if (!this.isConfirm) this.visible = visible;
|
||||
this.emitChange();
|
||||
this.emitChange(type);
|
||||
},
|
||||
onPickSuccess(){
|
||||
this.visible = false;
|
||||
this.$emit('on-ok');
|
||||
this.focus();
|
||||
this.reset();
|
||||
},
|
||||
focus() {
|
||||
this.$refs.input && this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (state) {
|
||||
if (state === false){
|
||||
this.$refs.drop.destroy();
|
||||
const input = this.$el.querySelector('input');
|
||||
if (input) input.blur();
|
||||
}
|
||||
this.$refs.drop.update();
|
||||
this.$emit('on-open-change', state);
|
||||
},
|
||||
value(val) {
|
||||
this.internalValue = this.parseDate(val);
|
||||
|
||||
},
|
||||
open (val) {
|
||||
this.visible = val === true;
|
||||
|
@ -415,6 +702,9 @@
|
|||
this.$emit('input', this.publicVModelValue); // to update v-model
|
||||
}
|
||||
if (this.open !== null) this.visible = this.open;
|
||||
|
||||
// to handle focus from confirm buttons
|
||||
this.$on('focus-input', () => this.focus());
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -5,6 +5,7 @@ import RangeDatePickerPanel from '../panel/Date/date-range.vue';
|
|||
import { oneOf } from '../../../utils/assist';
|
||||
|
||||
export default {
|
||||
name: 'CalendarPicker',
|
||||
mixins: [Picker],
|
||||
props: {
|
||||
type: {
|
||||
|
|
|
@ -3,7 +3,7 @@ import TimePickerPanel from '../panel/Time/time.vue';
|
|||
import RangeTimePickerPanel from '../panel/Time/time-range.vue';
|
||||
import Options from '../time-mixins';
|
||||
|
||||
import { oneOf } from '../../../utils/assist';
|
||||
import { findComponentsDownward, oneOf } from '../../../utils/assist';
|
||||
|
||||
export default {
|
||||
mixins: [Picker, Options],
|
||||
|
@ -30,4 +30,14 @@ export default {
|
|||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(visible){
|
||||
if (visible) {
|
||||
this.$nextTick(() => {
|
||||
const spinners = findComponentsDownward(this, 'TimeSpinner');
|
||||
spinners.forEach(instance => instance.updateScroll());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -147,7 +147,7 @@ export const DEFAULT_FORMATS = {
|
|||
datetimerange: 'yyyy-MM-dd HH:mm:ss'
|
||||
};
|
||||
|
||||
const RANGE_SEPARATOR = ' - ';
|
||||
export const RANGE_SEPARATOR = ' - ';
|
||||
|
||||
const DATE_FORMATTER = function(value, format) {
|
||||
return formatDate(value, format);
|
||||
|
@ -226,7 +226,15 @@ export const TYPE_VALUE_RESOLVER_MAP = {
|
|||
formatter: (value, format) => {
|
||||
return value.filter(Boolean).map(date => formatDate(date, format)).join(',');
|
||||
},
|
||||
parser: (text, format) => text.split(',').map(string => parseDate(string.trim(), format))
|
||||
parser: (value, format) => {
|
||||
const values = typeof value === 'string' ? value.split(',') : value;
|
||||
return values.map(value => {
|
||||
if (value instanceof Date) return value;
|
||||
if (typeof value === 'string') value = value.trim();
|
||||
else if (typeof value !== 'number' && !value) value = '';
|
||||
return parseDate(value, format);
|
||||
});
|
||||
}
|
||||
},
|
||||
number: {
|
||||
formatter(value) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-dropdown-item';
|
||||
|
||||
import { findComponentUpward } from '../../utils/assist';
|
||||
export default {
|
||||
name: 'DropdownItem',
|
||||
props: {
|
||||
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
const $parent = this.$parent.$parent.$parent;
|
||||
const $parent = findComponentUpward(this, 'Dropdown');
|
||||
const hasChildren = this.$parent && this.$parent.$options.name === 'Dropdown';
|
||||
|
||||
if (this.disabled) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
:class="[prefixCls]"
|
||||
v-clickoutside="onClickoutside"
|
||||
v-click-outside="onClickoutside"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave">
|
||||
<div :class="[prefixCls + '-rel']" ref="reference" @click="handleClick"><slot></slot></div>
|
||||
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
<script>
|
||||
import Drop from '../select/dropdown.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf, findComponentUpward } from '../../utils/assist';
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
|||
|
||||
export default {
|
||||
name: 'Dropdown',
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
components: { Drop },
|
||||
props: {
|
||||
trigger: {
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
this.validateState = val;
|
||||
}
|
||||
},
|
||||
inject: ['form'],
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
|
@ -103,13 +104,13 @@
|
|||
}
|
||||
];
|
||||
},
|
||||
form() {
|
||||
let parent = this.$parent;
|
||||
while (parent.$options.name !== 'iForm') {
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
// form() {
|
||||
// let parent = this.$parent;
|
||||
// while (parent.$options.name !== 'iForm') {
|
||||
// parent = parent.$parent;
|
||||
// }
|
||||
// return parent;
|
||||
// },
|
||||
fieldValue: {
|
||||
cache: false,
|
||||
get() {
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
default: 'off'
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return { form : this };
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
fields: []
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf, findComponentsDownward } from '../../utils/assist';
|
||||
import { oneOf, findComponentDownward, findBrothersComponents } from '../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-row';
|
||||
|
||||
|
@ -58,7 +58,10 @@
|
|||
},
|
||||
methods: {
|
||||
updateGutter (val) {
|
||||
const Cols = findComponentsDownward(this, 'iCol');
|
||||
// 这里会嵌套寻找,把 Col 里的 Row 里的 Col 也找到,所以用 兄弟找
|
||||
// const Cols = findComponentsDownward(this, 'iCol');
|
||||
const Col = findComponentDownward(this, 'iCol');
|
||||
const Cols = findBrothersComponents(Col, 'iCol', false);
|
||||
if (Cols.length) {
|
||||
Cols.forEach((child) => {
|
||||
if (val !== 0) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<i :class="classes" :style="styles"></i>
|
||||
<i :class="classes" :style="styles" @click="handleClick"></i>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-icon';
|
||||
|
@ -28,6 +28,11 @@
|
|||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (event) {
|
||||
this.$emit('click', event);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -26,10 +26,12 @@
|
|||
@blur="blur"
|
||||
@keydown.stop="keyDown"
|
||||
@input="change"
|
||||
@mouseup="preventDefault"
|
||||
@change="change"
|
||||
:readonly="readonly || !editable"
|
||||
:name="name"
|
||||
:value="formatterValue">
|
||||
:value="formatterValue"
|
||||
:placeholder="placeholder">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -119,7 +121,11 @@
|
|||
},
|
||||
parser: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -175,6 +181,7 @@
|
|||
},
|
||||
precisionValue () {
|
||||
// can not display 1.0
|
||||
if(!this.currentValue) return this.currentValue;
|
||||
return this.precision ? this.currentValue.toFixed(this.precision) : this.currentValue;
|
||||
},
|
||||
formatterValue () {
|
||||
|
@ -241,7 +248,7 @@
|
|||
},
|
||||
setValue (val) {
|
||||
// 如果 step 是小数,且没有设置 precision,是有问题的
|
||||
if (!isNaN(this.precision)) val = Number(Number(val).toFixed(this.precision));
|
||||
if (val && !isNaN(this.precision)) val = Number(Number(val).toFixed(this.precision));
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.currentValue = val;
|
||||
|
@ -250,9 +257,9 @@
|
|||
this.dispatch('FormItem', 'on-form-change', val);
|
||||
});
|
||||
},
|
||||
focus () {
|
||||
focus (event) {
|
||||
this.focused = true;
|
||||
this.$emit('on-focus');
|
||||
this.$emit('on-focus', event);
|
||||
},
|
||||
blur () {
|
||||
this.focused = false;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div :class="wrapClasses">
|
||||
<template v-if="type !== 'textarea'">
|
||||
<div :class="[prefixCls + '-group-prepend']" v-if="prepend" v-show="slotReady"><slot name="prepend"></slot></div>
|
||||
<i class="ivu-icon" :class="['ivu-icon-ios-close', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable" @click="handleClear"></i>
|
||||
<i class="ivu-icon" :class="['ivu-icon-ios-close', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable && currentValue" @click="handleClear"></i>
|
||||
<i class="ivu-icon" :class="['ivu-icon-' + icon, prefixCls + '-icon', prefixCls + '-icon-normal']" v-else-if="icon" @click="handleIconClick"></i>
|
||||
<transition name="fade">
|
||||
<i class="ivu-icon ivu-icon-load-c ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ul :class="classes" :style="styles"><slot></slot></ul>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf, findComponentsDownward, findBrothersComponents } from '../../utils/assist';
|
||||
import { oneOf, findComponentsDownward, findComponentsUpward } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-menu';
|
||||
|
@ -43,7 +43,8 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
currentActiveName: this.activeName
|
||||
currentActiveName: this.activeName,
|
||||
openedNames: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -76,36 +77,61 @@
|
|||
this.broadcast('MenuItem', 'on-update-active-name', this.currentActiveName);
|
||||
},
|
||||
updateOpenKeys (name) {
|
||||
const index = this.openNames.indexOf(name);
|
||||
if (index > -1) {
|
||||
this.openNames.splice(index, 1);
|
||||
let names = [...this.openedNames];
|
||||
const index = names.indexOf(name);
|
||||
if (this.accordion) findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
item.opened = false;
|
||||
});
|
||||
if (index >= 0) {
|
||||
let currentSubmenu = null;
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) {
|
||||
currentSubmenu = item;
|
||||
item.opened = false;
|
||||
}
|
||||
});
|
||||
findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = true;
|
||||
});
|
||||
findComponentsDownward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = false;
|
||||
});
|
||||
} else {
|
||||
this.openNames.push(name);
|
||||
if (this.accordion) {
|
||||
let currentSubmenu = {};
|
||||
let currentSubmenu = null;
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) currentSubmenu = item;
|
||||
if (item.name === name) {
|
||||
currentSubmenu = item;
|
||||
item.opened = true;
|
||||
}
|
||||
});
|
||||
findBrothersComponents(currentSubmenu, 'Submenu').forEach(item => {
|
||||
let index = this.openNames.indexOf(item.name);
|
||||
this.openNames.splice(index, index >= 0 ? 1 : 0);
|
||||
findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = true;
|
||||
});
|
||||
} else {
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) item.opened = true;
|
||||
});
|
||||
this.openNames.push(name);
|
||||
}
|
||||
}
|
||||
let openedNames = findComponentsDownward(this, 'Submenu').filter(item => item.opened).map(item => item.name);
|
||||
this.openedNames = [...openedNames];
|
||||
this.$emit('on-open-change', openedNames);
|
||||
},
|
||||
updateOpened () {
|
||||
const items = findComponentsDownward(this, 'Submenu');
|
||||
|
||||
if (items.length) {
|
||||
items.forEach(item => {
|
||||
if (this.openNames.indexOf(item.name) > -1) item.opened = true;
|
||||
if (this.openedNames.indexOf(item.name) > -1) item.opened = true;
|
||||
else item.opened = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateActiveName();
|
||||
this.openedNames = [...this.openNames];
|
||||
this.updateOpened();
|
||||
this.$on('on-menu-item-select', (name) => {
|
||||
this.currentActiveName = name;
|
||||
|
@ -113,8 +139,8 @@
|
|||
});
|
||||
},
|
||||
watch: {
|
||||
openNames () {
|
||||
this.$emit('on-open-change', this.openNames);
|
||||
openNames (names) {
|
||||
this.openedNames = names;
|
||||
},
|
||||
activeName (val) {
|
||||
this.currentActiveName = val;
|
||||
|
|
|
@ -16,4 +16,4 @@ export default {
|
|||
return this.menu.mode;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,6 +13,10 @@ export default {
|
|||
this.scrollBarWidth = getScrollBarSize();
|
||||
}
|
||||
},
|
||||
checkMaskInVisible () {
|
||||
let masks = document.getElementsByClassName('ivu-modal-mask') || [];
|
||||
return Array.from(masks).every(m => m.style.display === 'none' || m.classList.contains('fade-leave-to'));
|
||||
},
|
||||
setScrollBar () {
|
||||
if (this.bodyIsOverflowing && this.scrollBarWidth !== undefined) {
|
||||
document.body.style.paddingRight = `${this.scrollBarWidth}px`;
|
||||
|
@ -27,8 +31,10 @@ export default {
|
|||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
removeScrollEffect() {
|
||||
document.body.style.overflow = '';
|
||||
this.resetScrollBar();
|
||||
if (this.checkMaskInVisible()) {
|
||||
document.body.style.overflow = '';
|
||||
this.resetScrollBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ function notice (type, options) {
|
|||
if (type == 'normal') {
|
||||
withIcon = false;
|
||||
content = `
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-normal${with_desc}">
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-normal ${with_desc}">
|
||||
<div class="${prefixCls}-title">${title}</div>
|
||||
<div class="${prefixCls}-desc">${desc}</div>
|
||||
</div>
|
||||
|
@ -59,7 +59,7 @@ function notice (type, options) {
|
|||
const iconType = iconTypes[type];
|
||||
withIcon = true;
|
||||
content = `
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-icon ${prefixCls}-with-${type}${with_desc}">
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-icon ${prefixCls}-with-${type} ${with_desc}">
|
||||
<span class="${prefixCls}-icon ${prefixCls}-icon-${type}">
|
||||
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}"></i>
|
||||
</span>
|
||||
|
@ -122,4 +122,4 @@ export default {
|
|||
noticeInstance = null;
|
||||
instance.destroy('ivu-notice');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
:class="classes"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave"
|
||||
v-clickoutside="handleClose">
|
||||
v-click-outside="handleClose">
|
||||
<div
|
||||
:class="[prefixCls + '-rel']"
|
||||
ref="reference"
|
||||
|
@ -49,7 +49,7 @@
|
|||
<script>
|
||||
import Popper from '../base/popper';
|
||||
import iButton from '../button/button.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
@ -59,7 +59,7 @@
|
|||
export default {
|
||||
name: 'Poptip',
|
||||
mixins: [ Popper, Locale ],
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
components: { iButton },
|
||||
props: {
|
||||
trigger: {
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
this.childrens = findComponentsDownward(this, 'Radio');
|
||||
if (this.childrens) {
|
||||
this.childrens.forEach(child => {
|
||||
child.currentValue = this.value === child.label;
|
||||
child.currentValue = this.currentValue === child.label;
|
||||
child.group = true;
|
||||
});
|
||||
}
|
||||
|
@ -82,8 +82,10 @@
|
|||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.currentValue = this.value;
|
||||
this.updateValue();
|
||||
if(this.currentValue !== this.value){
|
||||
this.currentValue = this.value;
|
||||
this.updateValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
@change="change"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur">
|
||||
</span>
|
||||
<slot>{{ label }}</slot>
|
||||
</span><slot>{{ label }}</slot>
|
||||
</label>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
},
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -123,8 +127,13 @@
|
|||
},
|
||||
handleClick (value) {
|
||||
if (this.disabled) return;
|
||||
// value++;
|
||||
//value++;
|
||||
if (this.isHalf) value -= 0.5;
|
||||
|
||||
if(this.clearable && Math.abs(value - this.currentValue) < 0.01) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
this.currentValue = value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('on-change', value);
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
data () {
|
||||
return {
|
||||
popper: null,
|
||||
width: ''
|
||||
width: '',
|
||||
popperStatus: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -37,6 +38,7 @@
|
|||
if (this.popper) {
|
||||
this.$nextTick(() => {
|
||||
this.popper.update();
|
||||
this.popperStatus = true;
|
||||
});
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
|
@ -44,7 +46,10 @@
|
|||
placement: this.placement,
|
||||
modifiers: {
|
||||
computeStyle:{
|
||||
gpuAcceleration: false,
|
||||
gpuAcceleration: false
|
||||
},
|
||||
preventOverflow :{
|
||||
boundariesElement: 'window'
|
||||
}
|
||||
},
|
||||
onCreate:()=>{
|
||||
|
@ -65,16 +70,25 @@
|
|||
destroy () {
|
||||
if (this.popper) {
|
||||
setTimeout(() => {
|
||||
if (this.popper) {
|
||||
if (this.popper && !this.popperStatus) {
|
||||
this.popper.destroy();
|
||||
this.popper = null;
|
||||
}
|
||||
this.popperStatus = false;
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
resetTransformOrigin() {
|
||||
let placement = this.popper.popper.getAttribute('x-placement').split('-')[0];
|
||||
this.popper.popper.style.transformOrigin = placement==='bottom'?'center top':'center bottom';
|
||||
// 不判断,Select 会报错,不知道为什么
|
||||
if (!this.popper) return;
|
||||
|
||||
let x_placement = this.popper.popper.getAttribute('x-placement');
|
||||
let placementStart = x_placement.split('-')[0];
|
||||
let placementEnd = x_placement.split('-')[1];
|
||||
const leftOrRight = x_placement === 'left' || x_placement === 'right';
|
||||
if(!leftOrRight){
|
||||
this.popper.popper.style.transformOrigin = placementStart==='bottom' || ( placementStart !== 'top' && placementEnd === 'start') ? 'center top' : 'center bottom';
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
29
src/components/select/functional-options.vue
Normal file
29
src/components/select/functional-options.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
<script>
|
||||
const returnArrayFn = () => [];
|
||||
|
||||
export default {
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default: returnArrayFn
|
||||
},
|
||||
slotOptions: {
|
||||
type: Array,
|
||||
default: returnArrayFn
|
||||
},
|
||||
slotUpdateHook: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
functional: true,
|
||||
render(h, {props, parent}){
|
||||
// to detect changes in the $slot children/options we do this hack
|
||||
// so we can trigger the parents computed properties and have everything reactive
|
||||
// although $slot.default is not
|
||||
if (props.slotOptions !== parent.$slots.default) props.slotUpdateHook();
|
||||
return props.options;
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<li :class="classes" @click.stop="select" @mouseout.stop="blur" v-show="!hidden"><slot>{{ showLabel }}</slot></li>
|
||||
<li
|
||||
:class="classes"
|
||||
@click.stop="select"
|
||||
@touchend.stop="select"
|
||||
@mousedown.prevent
|
||||
@touchstart.prevent
|
||||
><slot>{{ showLabel }}</slot></li>
|
||||
</template>
|
||||
<script>
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
@ -22,15 +28,19 @@
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isFocused: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selected: false,
|
||||
index: 0, // for up and down to focus
|
||||
isFocus: false,
|
||||
hidden: false, // for search
|
||||
searchLabel: '', // the value is slot,only for search
|
||||
searchLabel: '', // the slot value (textContent)
|
||||
autoComplete: false
|
||||
};
|
||||
},
|
||||
|
@ -41,53 +51,34 @@
|
|||
{
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-selected`]: this.selected && !this.autoComplete,
|
||||
[`${prefixCls}-focus`]: this.isFocus
|
||||
[`${prefixCls}-focus`]: this.isFocused
|
||||
}
|
||||
];
|
||||
},
|
||||
showLabel () {
|
||||
return (this.label) ? this.label : this.value;
|
||||
},
|
||||
optionLabel(){
|
||||
return this.label || (this.$el && this.$el.textContent);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select () {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (this.disabled) return false;
|
||||
|
||||
this.dispatch('iSelect', 'on-select-selected', this.value);
|
||||
this.dispatch('iSelect', 'on-select-selected', {
|
||||
value: this.value,
|
||||
label: this.optionLabel,
|
||||
});
|
||||
this.$emit('on-select-selected', {
|
||||
value: this.value,
|
||||
label: this.optionLabel,
|
||||
});
|
||||
},
|
||||
blur () {
|
||||
this.isFocus = false;
|
||||
},
|
||||
queryChange (val) {
|
||||
const parsedQuery = val.replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, '\\$1');
|
||||
this.hidden = !new RegExp(parsedQuery, 'i').test(this.searchLabel);
|
||||
},
|
||||
// 在使用函数防抖后,设置 key 后,不更新组件了,导致SearchLabel 不更新 #1865
|
||||
updateSearchLabel () {
|
||||
this.searchLabel = this.$el.textContent;
|
||||
},
|
||||
onSelectClose(){
|
||||
this.isFocus = false;
|
||||
},
|
||||
onQueryChange(val){
|
||||
this.queryChange(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateSearchLabel();
|
||||
this.dispatch('iSelect', 'append');
|
||||
this.$on('on-select-close', this.onSelectClose);
|
||||
this.$on('on-query-change',this.onQueryChange);
|
||||
|
||||
const Select = findComponentUpward(this, 'iSelect');
|
||||
if (Select) this.autoComplete = Select.autoComplete;
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.dispatch('iSelect', 'remove');
|
||||
this.$off('on-select-close', this.onSelectClose);
|
||||
this.$off('on-query-change',this.onQueryChange);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
200
src/components/select/select-head.vue
Normal file
200
src/components/select/select-head.vue
Normal file
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<div @click="onHeaderClick">
|
||||
<div class="ivu-tag ivu-tag-checked" v-for="item in selectedMultiple">
|
||||
<span class="ivu-tag-text">{{ item.label }}</span>
|
||||
<Icon type="ios-close-empty" @click.native.stop="removeTag(item)"></Icon>
|
||||
</div>
|
||||
<span
|
||||
:class="singleDisplayClasses"
|
||||
v-show="singleDisplayValue"
|
||||
>{{ singleDisplayValue }}</span>
|
||||
<input
|
||||
:id="inputElementId"
|
||||
type="text"
|
||||
v-if="filterable"
|
||||
v-model="query"
|
||||
:disabled="disabled"
|
||||
:class="[prefixCls + '-input']"
|
||||
:placeholder="showPlaceholder ? localePlaceholder : ''"
|
||||
:style="inputStyle"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@keydown="resetInputState"
|
||||
@keydown.delete="handleInputDelete"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputFocus"
|
||||
|
||||
ref="input">
|
||||
<Icon type="ios-close" :class="[prefixCls + '-arrow']" v-if="resetSelect" @click.native.stop="onClear"></Icon>
|
||||
<Icon type="arrow-down-b" :class="[prefixCls + '-arrow']" v-if="!resetSelect && !remote && !disabled"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../icon';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-select';
|
||||
|
||||
export default {
|
||||
name: 'iSelectHead',
|
||||
mixins: [ Emitter, Locale ],
|
||||
components: { Icon },
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialLabel: {
|
||||
type: [String, Number, Array],
|
||||
},
|
||||
values: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
clearable: {
|
||||
type: [Function, Boolean],
|
||||
default: false,
|
||||
},
|
||||
inputElementId: {
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
type: String
|
||||
},
|
||||
queryProp: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
query: '',
|
||||
inputLength: 20,
|
||||
remoteInitialLabel: this.initialLabel,
|
||||
preventRemoteCall: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
singleDisplayClasses(){
|
||||
const {filterable, multiple, showPlaceholder} = this;
|
||||
return [{
|
||||
[prefixCls + '-placeholder']: showPlaceholder && !filterable,
|
||||
[prefixCls + '-selected-value']: !showPlaceholder && !multiple && !filterable,
|
||||
}];
|
||||
},
|
||||
singleDisplayValue(){
|
||||
if ((this.multiple && this.values.length > 0) || this.filterable) return '';
|
||||
return `${this.selectedSingle}` || this.localePlaceholder;
|
||||
},
|
||||
showPlaceholder () {
|
||||
let status = false;
|
||||
if (!this.multiple) {
|
||||
const value = this.values[0];
|
||||
if (typeof value === 'undefined' || String(value).trim() === ''){
|
||||
status = !this.remoteInitialLabel;
|
||||
}
|
||||
} else {
|
||||
if (!this.values.length > 0) {
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
},
|
||||
resetSelect(){
|
||||
return !this.showPlaceholder && this.clearable;
|
||||
},
|
||||
inputStyle () {
|
||||
let style = {};
|
||||
|
||||
if (this.multiple) {
|
||||
if (this.showPlaceholder) {
|
||||
style.width = '100%';
|
||||
} else {
|
||||
style.width = `${this.inputLength}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
localePlaceholder () {
|
||||
if (this.placeholder === undefined) {
|
||||
return this.t('i.select.placeholder');
|
||||
} else {
|
||||
return this.placeholder;
|
||||
}
|
||||
},
|
||||
selectedSingle(){
|
||||
const selected = this.values[0];
|
||||
return selected ? selected.label : (this.remoteInitialLabel || '');
|
||||
},
|
||||
selectedMultiple(){
|
||||
return this.multiple ? this.values : [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInputFocus(e){
|
||||
this.$emit(e.type === 'focus' ? 'on-input-focus' : 'on-input-blur');
|
||||
},
|
||||
removeTag (value) {
|
||||
if (this.disabled) return false;
|
||||
this.dispatch('iSelect', 'on-select-selected', value);
|
||||
},
|
||||
resetInputState () {
|
||||
this.inputLength = this.$refs.input.value.length * 12 + 20;
|
||||
},
|
||||
handleInputDelete () {
|
||||
if (this.multiple && this.selectedMultiple.length && this.query === '') {
|
||||
this.removeTag(this.selectedMultiple[this.selectedMultiple.length - 1]);
|
||||
}
|
||||
},
|
||||
onHeaderClick(e){
|
||||
if (this.filterable && e.target === this.$el){
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
onClear(){
|
||||
this.$emit('on-clear');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
values ([value]) {
|
||||
if (!this.filterable) return;
|
||||
this.preventRemoteCall = true;
|
||||
if (this.multiple){
|
||||
this.query = '';
|
||||
this.preventRemoteCall = false; // this should be after the query change setter above
|
||||
return;
|
||||
}
|
||||
// #982
|
||||
if (typeof value === 'undefined' || value === '' || value === null) this.query = '';
|
||||
else this.query = value.label;
|
||||
this.$nextTick(() => this.preventRemoteCall = false); // this should be after the query change setter above
|
||||
},
|
||||
query (val) {
|
||||
if (this.preventRemoteCall) {
|
||||
this.preventRemoteCall = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('on-query-change', val);
|
||||
},
|
||||
queryProp(query){
|
||||
if (query !== this.query) this.query = query;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,7 @@
|
|||
<Input-number
|
||||
v-if="!range && showInput"
|
||||
:min="min"
|
||||
:size="inputSize"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:value="exportValue[0]"
|
||||
|
@ -120,6 +121,13 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
inputSize: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
}
|
||||
},
|
||||
showStops: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -151,7 +159,7 @@
|
|||
startX: 0,
|
||||
currentX: 0,
|
||||
startPos: 0,
|
||||
oldValue: val,
|
||||
oldValue: [...val],
|
||||
valueIndex: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
|
@ -324,10 +332,10 @@
|
|||
changeButtonPosition (newPos, forceType) {
|
||||
const type = forceType || this.pointerDown;
|
||||
const index = type === 'min' ? 0 : 1;
|
||||
if (type === 'min') newPos = this.checkLimits([newPos, this.maxPosition])[0];
|
||||
else newPos = this.checkLimits([this.minPosition, newPos])[1];
|
||||
if (type === 'min') newPos = this.checkLimits([newPos, this.max])[0];
|
||||
else newPos = this.checkLimits([this.min, newPos])[1];
|
||||
|
||||
const modulus = newPos % this.step;
|
||||
const modulus = this.handleDecimal(newPos,this.step);
|
||||
const value = this.currentValue;
|
||||
value[index] = newPos - modulus;
|
||||
this.currentValue = [...value];
|
||||
|
@ -339,7 +347,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleDecimal(pos,step){
|
||||
if(step<1){
|
||||
let sl = step.toString(),
|
||||
multiple = 1,
|
||||
m;
|
||||
try {
|
||||
m = sl.split('.')[1].length;
|
||||
} catch (e){
|
||||
m = 0;
|
||||
}
|
||||
multiple = Math.pow(10,m);
|
||||
return (pos * multiple) % (step * multiple) / multiple;
|
||||
}else return pos % step;
|
||||
},
|
||||
emitChange(){
|
||||
const value = this.range ? this.exportValue : this.exportValue[0];
|
||||
this.$emit('on-change', value);
|
||||
|
|
|
@ -44,7 +44,9 @@ Spin.newInstance = properties => {
|
|||
spin.visible = false;
|
||||
setTimeout(function() {
|
||||
spin.$parent.$destroy();
|
||||
document.body.removeChild(document.getElementsByClassName('ivu-spin-fullscreen')[0]);
|
||||
if (document.getElementsByClassName('ivu-spin-fullscreen')[0] !== undefined) {
|
||||
document.body.removeChild(document.getElementsByClassName('ivu-spin-fullscreen')[0]);
|
||||
}
|
||||
cb();
|
||||
}, 500);
|
||||
},
|
||||
|
@ -52,4 +54,4 @@ Spin.newInstance = properties => {
|
|||
};
|
||||
};
|
||||
|
||||
export default Spin;
|
||||
export default Spin;
|
||||
|
|
2
src/components/split/index.js
Normal file
2
src/components/split/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Split from './split.vue'
|
||||
export default Split
|
155
src/components/split/split.vue
Normal file
155
src/components/split/split.vue
Normal file
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<div ref="outerWrapper" :class="wrapperClasses">
|
||||
<div v-if="isHorizontal" :class="`${prefix}-horizontal`">
|
||||
<div :style="{right: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'left-pane']"><slot name="left"/></div>
|
||||
<div :class="`${prefix}-trigger-con`" :style="{left: `${offset}%`}" @mousedown="handleMousedown">
|
||||
<slot name="trigger">
|
||||
<trigger mode="vertical"/>
|
||||
</slot>
|
||||
</div>
|
||||
<div :style="{left: `${offset}%`}" :class="[`${prefix}-pane`, 'right-pane']"><slot name="right"/></div>
|
||||
</div>
|
||||
<div v-else :class="`${prefix}-vertical`">
|
||||
<div :style="{bottom: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'top-pane']"><slot name="top"/></div>
|
||||
<div :class="`${prefix}-trigger-con`" :style="{top: `${offset}%`}" @mousedown="handleMousedown">
|
||||
<slot name="trigger">
|
||||
<trigger mode="horizontal"/>
|
||||
</slot>
|
||||
</div>
|
||||
<div :style="{top: `${offset}%`}" :class="[`${prefix}-pane`, 'bottom-pane']"><slot name="bottom"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import { on, off } from '../../utils/dom';
|
||||
import Trigger from './trigger.vue'
|
||||
export default {
|
||||
name: 'SplitPane',
|
||||
components: {
|
||||
Trigger
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 0.5
|
||||
},
|
||||
mode: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['horizontal', 'vertical'])
|
||||
},
|
||||
default: 'horizontal'
|
||||
},
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: '40px'
|
||||
},
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: '40px'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Events
|
||||
* @on-move-start
|
||||
* @on-moving 返回值:事件对象,但是在事件对象中加入了两个参数:atMin(当前是否在最小值处), atMax(当前是否在最大值处)
|
||||
* @on-move-end
|
||||
*/
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-split',
|
||||
offset: 0,
|
||||
oldOffset: 0,
|
||||
isMoving: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapperClasses () {
|
||||
return [
|
||||
`${this.prefix}-wrapper`,
|
||||
this.isMoving ? 'no-select' : ''
|
||||
]
|
||||
},
|
||||
isHorizontal () {
|
||||
return this.mode === 'horizontal'
|
||||
},
|
||||
anotherOffset () {
|
||||
return 100 - this.offset
|
||||
},
|
||||
valueIsPx () {
|
||||
return typeof this.value === 'string'
|
||||
},
|
||||
offsetSize () {
|
||||
return this.isHorizontal ? 'offsetWidth' : 'offsetHeight'
|
||||
},
|
||||
computedMin () {
|
||||
return this.getComputedThresholdValue('min')
|
||||
},
|
||||
computedMax () {
|
||||
return this.getComputedThresholdValue('max')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
px2percent (numerator, denominator) {
|
||||
return parseFloat(numerator) / parseFloat(denominator)
|
||||
},
|
||||
getComputedThresholdValue (type) {
|
||||
let size = this.$refs.outerWrapper[this.offsetSize]
|
||||
if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type]
|
||||
else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type]
|
||||
},
|
||||
getMin (value1, value2) {
|
||||
if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`
|
||||
else return Math.min(value1, value2)
|
||||
},
|
||||
getMax (value1, value2) {
|
||||
if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`
|
||||
else return Math.max(value1, value2)
|
||||
},
|
||||
getAnotherOffset (value) {
|
||||
let res = 0
|
||||
if (this.valueIsPx) res = `${this.$refs.outerWrapper[this.offsetSize] - parseFloat(value)}px`
|
||||
else res = 1 - value
|
||||
return res
|
||||
},
|
||||
handleMove (e) {
|
||||
let pageOffset = this.isHorizontal ? e.pageX : e.pageY
|
||||
let offset = pageOffset - this.initOffset
|
||||
let outerWidth = this.$refs.outerWrapper[this.offsetSize]
|
||||
let value = this.valueIsPx ? `${parseFloat(this.oldOffset) + offset}px` : (this.px2percent(outerWidth * this.oldOffset + offset, outerWidth))
|
||||
let anotherValue = this.getAnotherOffset(value)
|
||||
if (parseFloat(value) <= parseFloat(this.computedMin)) value = this.getMax(value, this.computedMin)
|
||||
if (parseFloat(anotherValue) <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax))
|
||||
e.atMin = this.value === this.computedMin
|
||||
e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : this.getAnotherOffset(this.value).toFixed(5) === this.computedMax.toFixed(5)
|
||||
this.$emit('input', value)
|
||||
this.$emit('on-moving', e)
|
||||
},
|
||||
handleUp () {
|
||||
this.isMoving = false
|
||||
off(document, 'mousemove', this.handleMove)
|
||||
off(document, 'mouseup', this.handleUp)
|
||||
this.$emit('on-move-end')
|
||||
},
|
||||
handleMousedown (e) {
|
||||
this.initOffset = this.isHorizontal ? e.pageX : e.pageY
|
||||
this.oldOffset = this.value
|
||||
this.isMoving = true
|
||||
on(document, 'mousemove', this.handleMove)
|
||||
on(document, 'mouseup', this.handleUp)
|
||||
this.$emit('on-move-start')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
39
src/components/split/trigger.vue
Normal file
39
src/components/split/trigger.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="barConClasses">
|
||||
<i :class="`${prefix}-bar`" v-once v-for="i in 8" :key="`trigger-${i}`"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Trigger',
|
||||
props: {
|
||||
mode: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-split-trigger',
|
||||
initOffset: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isVertical () {
|
||||
return this.mode === 'vertical'
|
||||
},
|
||||
classes () {
|
||||
return [
|
||||
this.prefix,
|
||||
this.isVertical ? `${this.prefix}-vertical` : `${this.prefix}-horizontal`
|
||||
]
|
||||
},
|
||||
barConClasses () {
|
||||
return [
|
||||
`${this.prefix}-bar-con`,
|
||||
this.isVertical ? 'vertical' : 'horizontal'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -34,6 +34,7 @@
|
|||
<span :class="[prefixCls + '-filter']">
|
||||
<i class="ivu-icon ivu-icon-funnel" :class="{on: getColumn(rowIndex, index)._isFiltered}"></i>
|
||||
</span>
|
||||
|
||||
<div slot="content" :class="[prefixCls + '-filter-list']" v-if="getColumn(rowIndex, index)._filterMultiple">
|
||||
<div :class="[prefixCls + '-filter-list-item']">
|
||||
<checkbox-group v-model="getColumn(rowIndex, index)._filterChecked">
|
||||
|
@ -133,8 +134,8 @@
|
|||
},
|
||||
scrollBarCellClass(){
|
||||
let hasRightFixed = false;
|
||||
for(var i in this.headRows){
|
||||
for(var j in this.headRows[i]){
|
||||
for(let i in this.headRows){
|
||||
for(let j in this.headRows[i]){
|
||||
if(this.headRows[i][j].fixed === 'right') {
|
||||
hasRightFixed=true;
|
||||
break;
|
||||
|
@ -205,7 +206,13 @@
|
|||
// 因为表头嵌套不是深拷贝,所以没有 _ 开头的方法,在 isGroup 下用此列
|
||||
getColumn (rowIndex, index) {
|
||||
const isGroup = this.columnRows.length > 1;
|
||||
return isGroup ? this.columns[rowIndex] : this.headRows[rowIndex][index];
|
||||
|
||||
if (isGroup) {
|
||||
const id = this.headRows[rowIndex][index].__id;
|
||||
return this.columns.filter(item => item.__id === id)[0];
|
||||
} else {
|
||||
return this.headRows[rowIndex][index];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
import ExportCsv from './export-csv';
|
||||
import Locale from '../../mixins/locale';
|
||||
import elementResizeDetectorMaker from 'element-resize-detector';
|
||||
import { getAllColumns, convertToRows, convertColumnOrder } from './util';
|
||||
import { getAllColumns, convertToRows, convertColumnOrder, getRandomStr } from './util';
|
||||
|
||||
const prefixCls = 'ivu-table';
|
||||
|
||||
|
@ -178,6 +178,7 @@
|
|||
}
|
||||
},
|
||||
data () {
|
||||
const colsWithId = this.makeColumnsId(this.columns);
|
||||
return {
|
||||
ready: false,
|
||||
tableWidth: 0,
|
||||
|
@ -186,15 +187,14 @@
|
|||
compiledUids: [],
|
||||
objData: this.makeObjData(), // checkbox or highlight-row
|
||||
rebuildData: [], // for sort or filter
|
||||
cloneColumns: this.makeColumns(),
|
||||
columnRows: this.makeColumnRows(false),
|
||||
leftFixedColumnRows: this.makeColumnRows('left'),
|
||||
rightFixedColumnRows: this.makeColumnRows('right'),
|
||||
allColumns: getAllColumns(this.columns), // for multiple table-head, get columns that have no children
|
||||
cloneColumns: this.makeColumns(colsWithId),
|
||||
columnRows: this.makeColumnRows(false, colsWithId),
|
||||
leftFixedColumnRows: this.makeColumnRows('left', colsWithId),
|
||||
rightFixedColumnRows: this.makeColumnRows('right', colsWithId),
|
||||
allColumns: getAllColumns(colsWithId), // for multiple table-head, get columns that have no children
|
||||
showSlotHeader: true,
|
||||
showSlotFooter: true,
|
||||
bodyHeight: 0,
|
||||
bodyRealHeight: 0,
|
||||
scrollBarWidth: getScrollBarSize(),
|
||||
currentContext: this.context,
|
||||
cloneData: deepCopy(this.data), // when Cell has a button to delete row data, clickCurrentRow will throw an error, so clone a data
|
||||
|
@ -251,7 +251,7 @@
|
|||
styles () {
|
||||
let style = {};
|
||||
if (this.height) {
|
||||
const height = (this.isLeftFixed || this.isRightFixed) ? parseInt(this.height) + this.scrollBarWidth : parseInt(this.height);
|
||||
const height = parseInt(this.height);
|
||||
style.height = `${height}px`;
|
||||
}
|
||||
if (this.width) style.width = `${this.width}px`;
|
||||
|
@ -264,11 +264,7 @@
|
|||
if (this.bodyHeight === 0) {
|
||||
width = this.tableWidth;
|
||||
} else {
|
||||
if (this.bodyHeight > this.bodyRealHeight) {
|
||||
width = this.tableWidth;
|
||||
} else {
|
||||
width = this.tableWidth - (this.showVerticalScrollBar?this.scrollBarWidth:0);
|
||||
}
|
||||
width = this.tableWidth - (this.showVerticalScrollBar?this.scrollBarWidth:0);
|
||||
}
|
||||
// const width = this.bodyHeight === 0 ? this.tableWidth : this.tableWidth - this.scrollBarWidth;
|
||||
style.width = `${width}px`;
|
||||
|
@ -318,8 +314,7 @@
|
|||
bodyStyle () {
|
||||
let style = {};
|
||||
if (this.bodyHeight !== 0) {
|
||||
// add a height to resolve scroll bug when browser has a scrollBar in fixed type and height prop
|
||||
const height = (this.isLeftFixed || this.isRightFixed) ? this.bodyHeight + this.scrollBarWidth : this.bodyHeight;
|
||||
const height = this.bodyHeight;
|
||||
style.height = `${height}px`;
|
||||
}
|
||||
return style;
|
||||
|
@ -327,15 +322,8 @@
|
|||
fixedBodyStyle () {
|
||||
let style = {};
|
||||
if (this.bodyHeight !== 0) {
|
||||
let height = this.bodyHeight + (!this.showHorizontalScrollBar?this.scrollBarWidth:0) - 1;
|
||||
|
||||
// #2102 里,如果 Table 没有设置 width,而是集成父级的 width,固定列也应该不包含滚动条高度,所以这里直接计算表格宽度
|
||||
const tableWidth = parseInt(getStyle(this.$el, 'width')) - 1;
|
||||
if ((this.width && this.width < this.tableWidth) || tableWidth < this.tableWidth+(this.showVerticalScrollBar?this.scrollBarWidth:0)){
|
||||
height = this.bodyHeight;
|
||||
}
|
||||
// style.height = this.scrollBarWidth > 0 ? `${this.bodyHeight}px` : `${this.bodyHeight - 1}px`;
|
||||
style.height = this.scrollBarWidth > 0 ? `${height}px` : `${height - 1}px`;
|
||||
let height = this.bodyHeight - (this.showHorizontalScrollBar?this.scrollBarWidth:0);
|
||||
style.height = this.showHorizontalScrollBar ? `${height}px` : `${height - 1}px`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
@ -357,82 +345,111 @@
|
|||
return this.rowClassName(this.data[index], index);
|
||||
},
|
||||
handleResize () {
|
||||
this.$nextTick(() => {
|
||||
const allWidth = !this.allColumns.some(cell => !cell.width); // each column set a width
|
||||
if (allWidth) {
|
||||
this.tableWidth = this.allColumns.map(cell => cell.width).reduce((a, b) => a + b, 0);
|
||||
} else {
|
||||
this.tableWidth = parseInt(getStyle(this.$el, 'width')) - 1;
|
||||
//let tableWidth = parseInt(getStyle(this.$el, 'width')) - 1;
|
||||
let tableWidth = this.$el.offsetWidth - 1;
|
||||
let columnsWidth = {};
|
||||
let sumMinWidth = 0;
|
||||
let hasWidthColumns = [];
|
||||
let noWidthColumns = [];
|
||||
let maxWidthColumns = [];
|
||||
let noMaxWidthColumns = [];
|
||||
this.cloneColumns.forEach((col) => {
|
||||
if (col.width) {
|
||||
hasWidthColumns.push(col);
|
||||
}
|
||||
this.columnsWidth = {};
|
||||
this.$nextTick(()=>{
|
||||
this.headerWidth = this.$refs.header.childNodes[0].offsetWidth;
|
||||
this.headerHeight = this.$refs.header.childNodes[0].offsetHeight;
|
||||
if (!this.$refs.tbody) {
|
||||
this.showVerticalScrollBar = false;
|
||||
return;
|
||||
else{
|
||||
noWidthColumns.push(col);
|
||||
if (col.minWidth) {
|
||||
sumMinWidth += col.minWidth;
|
||||
}
|
||||
});
|
||||
this.$nextTick(() => {
|
||||
let columnsWidth = {};
|
||||
let autoWidthIndex = -1;
|
||||
if (allWidth) autoWidthIndex = this.cloneColumns.findIndex(cell => !cell.width);//todo 这行可能有问题
|
||||
|
||||
if (this.data.length) {
|
||||
const $tr = this.$refs.tbody.$el.querySelectorAll('tbody tr');
|
||||
if ($tr.length === 0) return;
|
||||
const $td = $tr[0].children;
|
||||
for (let i = 0; i < $td.length; i++) { // can not use forEach in Firefox
|
||||
const column = this.cloneColumns[i];
|
||||
|
||||
let width = parseInt(getStyle($td[i], 'width'));
|
||||
if (i === autoWidthIndex) {
|
||||
width = parseInt(getStyle($td[i], 'width')) - 1;
|
||||
}
|
||||
if (column.width) width = column.width;
|
||||
|
||||
this.cloneColumns[i]._width = width;
|
||||
|
||||
columnsWidth[column._index] = {
|
||||
width: width
|
||||
};
|
||||
}
|
||||
this.columnsWidth = columnsWidth;
|
||||
this.$nextTick(()=>{
|
||||
this.fixedHeader();
|
||||
if (this.$refs.tbody) {
|
||||
let bodyContentEl = this.$refs.tbody.$el;
|
||||
let bodyEl = bodyContentEl.parentElement;
|
||||
let bodyContentHeight = bodyContentEl.offsetHeight;
|
||||
let bodyContentWidth = bodyContentEl.offsetWidth;
|
||||
let bodyWidth = bodyEl.offsetWidth;
|
||||
let bodyHeight = bodyEl.offsetHeight;
|
||||
let scrollBarWidth = 0;
|
||||
if (bodyWidth < bodyContentWidth + (bodyHeight<bodyContentHeight?this.scrollBarWidth : 0)) {
|
||||
scrollBarWidth = this.scrollBarWidth;
|
||||
}
|
||||
|
||||
this.showVerticalScrollBar = this.bodyHeight? bodyHeight - scrollBarWidth < bodyContentHeight : false;
|
||||
this.showHorizontalScrollBar = bodyWidth < bodyContentWidth + (this.showVerticalScrollBar?this.scrollBarWidth:0);
|
||||
|
||||
if(this.showVerticalScrollBar){
|
||||
bodyEl.classList.add(this.prefixCls +'-overflowY');
|
||||
}else{
|
||||
bodyEl.classList.remove(this.prefixCls +'-overflowY');
|
||||
}
|
||||
if(this.showHorizontalScrollBar){
|
||||
bodyEl.classList.add(this.prefixCls +'-overflowX');
|
||||
}else{
|
||||
bodyEl.classList.remove(this.prefixCls +'-overflowX');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
if (col.maxWidth) {
|
||||
maxWidthColumns.push(col);
|
||||
}
|
||||
});
|
||||
// get table real height,for fixed when set height prop,but height < table's height,show scrollBarWidth
|
||||
this.bodyRealHeight = parseInt(getStyle(this.$refs.tbody.$el, 'height'));
|
||||
else {
|
||||
noMaxWidthColumns.push(col);
|
||||
}
|
||||
}
|
||||
col._width = null;
|
||||
});
|
||||
|
||||
|
||||
let unUsableWidth = hasWidthColumns.map(cell => cell.width).reduce((a, b) => a + b, 0);
|
||||
let usableWidth = tableWidth - unUsableWidth - sumMinWidth - (this.showVerticalScrollBar?this.scrollBarWidth:0) - 1;
|
||||
let usableLength = noWidthColumns.length;
|
||||
let columnWidth = 0;
|
||||
if(usableWidth > 0 && usableLength > 0){
|
||||
columnWidth = parseInt(usableWidth / usableLength);
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < this.cloneColumns.length; i++) {
|
||||
const column = this.cloneColumns[i];
|
||||
let width = columnWidth + (column.minWidth?column.minWidth:0);
|
||||
if(column.width){
|
||||
width = column.width;
|
||||
}
|
||||
else{
|
||||
if (column._width) {
|
||||
width = column._width;
|
||||
}
|
||||
else {
|
||||
if (column.minWidth > width){
|
||||
width = column.minWidth;
|
||||
}
|
||||
else if (column.maxWidth < width){
|
||||
width = column.maxWidth;
|
||||
}
|
||||
|
||||
if (usableWidth>0) {
|
||||
usableWidth -= width - (column.minWidth?column.minWidth:0);
|
||||
usableLength--;
|
||||
if (usableLength > 0) {
|
||||
columnWidth = parseInt(usableWidth / usableLength);
|
||||
}
|
||||
else {
|
||||
columnWidth = 0;
|
||||
}
|
||||
}
|
||||
else{
|
||||
columnWidth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
column._width = width;
|
||||
|
||||
columnsWidth[column._index] = {
|
||||
width: width
|
||||
};
|
||||
|
||||
}
|
||||
if(usableWidth>0) {
|
||||
usableLength = noMaxWidthColumns.length;
|
||||
columnWidth = parseInt(usableWidth / usableLength);
|
||||
for (let i = 0; i < noMaxWidthColumns.length; i++) {
|
||||
const column = noMaxWidthColumns[i];
|
||||
let width = column._width + columnWidth;
|
||||
if (usableLength > 1) {
|
||||
usableLength--;
|
||||
usableWidth -= columnWidth;
|
||||
columnWidth = parseInt(usableWidth / usableLength);
|
||||
}
|
||||
else {
|
||||
columnWidth = 0;
|
||||
}
|
||||
|
||||
column._width = width;
|
||||
|
||||
columnsWidth[column._index] = {
|
||||
width: width
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
this.tableWidth = this.cloneColumns.map(cell => cell._width).reduce((a, b) => a + b, 0) + (this.showVerticalScrollBar?this.scrollBarWidth:0) + 1;
|
||||
this.columnsWidth = columnsWidth;
|
||||
this.fixedHeader();
|
||||
},
|
||||
handleMouseIn (_index) {
|
||||
if (this.disabledHover) return;
|
||||
|
@ -540,11 +557,45 @@
|
|||
const headerHeight = parseInt(getStyle(this.$refs.header, 'height')) || 0;
|
||||
const footerHeight = parseInt(getStyle(this.$refs.footer, 'height')) || 0;
|
||||
this.bodyHeight = this.height - titleHeight - headerHeight - footerHeight;
|
||||
this.$nextTick(()=>this.fixedBody());
|
||||
});
|
||||
} else {
|
||||
this.bodyHeight = 0;
|
||||
this.$nextTick(()=>this.fixedBody());
|
||||
}
|
||||
},
|
||||
fixedBody (){
|
||||
if (this.$refs.header) {
|
||||
this.headerWidth = this.$refs.header.children[0].offsetWidth;
|
||||
this.headerHeight = this.$refs.header.children[0].offsetHeight;
|
||||
//this.showHorizontalScrollBar = this.headerWidth>this.$refs.header.offsetWidth;
|
||||
}
|
||||
|
||||
if (!this.$refs.tbody || !this.data || this.data.length === 0) {
|
||||
this.showVerticalScrollBar = false;
|
||||
}
|
||||
else{
|
||||
let bodyContentEl = this.$refs.tbody.$el;
|
||||
let bodyEl = bodyContentEl.parentElement;
|
||||
let bodyContentHeight = bodyContentEl.offsetHeight;
|
||||
let bodyHeight = bodyEl.offsetHeight;
|
||||
|
||||
this.showHorizontalScrollBar = bodyEl.offsetWidth < bodyContentEl.offsetWidth + (this.showVerticalScrollBar?this.scrollBarWidth:0);
|
||||
this.showVerticalScrollBar = this.bodyHeight? bodyHeight - (this.showHorizontalScrollBar?this.scrollBarWidth:0) < bodyContentHeight : false;
|
||||
|
||||
if(this.showVerticalScrollBar){
|
||||
bodyEl.classList.add(this.prefixCls +'-overflowY');
|
||||
}else{
|
||||
bodyEl.classList.remove(this.prefixCls +'-overflowY');
|
||||
}
|
||||
if(this.showHorizontalScrollBar){
|
||||
bodyEl.classList.add(this.prefixCls +'-overflowX');
|
||||
}else{
|
||||
bodyEl.classList.remove(this.prefixCls +'-overflowX');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hideColumnFilter () {
|
||||
this.cloneColumns.forEach((col) => col._filterVisible = false);
|
||||
},
|
||||
|
@ -765,9 +816,17 @@
|
|||
});
|
||||
return data;
|
||||
},
|
||||
makeColumns () {
|
||||
// 修改列,设置一个隐藏的 id,便于后面的多级表头寻找对应的列,否则找不到
|
||||
makeColumnsId (columns) {
|
||||
return columns.map(item => {
|
||||
if ('children' in item) item.children = this.makeColumnsId(item.children);
|
||||
item.__id = getRandomStr(6);
|
||||
return item;
|
||||
});
|
||||
},
|
||||
makeColumns (cols) {
|
||||
// 在 data 时,this.allColumns 暂时为 undefined
|
||||
let columns = deepCopy(getAllColumns(this.columns));
|
||||
let columns = deepCopy(getAllColumns(cols));
|
||||
let left = [];
|
||||
let right = [];
|
||||
let center = [];
|
||||
|
@ -806,8 +865,8 @@
|
|||
return left.concat(center).concat(right);
|
||||
},
|
||||
// create a multiple table-head
|
||||
makeColumnRows (fixedType) {
|
||||
return convertToRows(this.columns, fixedType);
|
||||
makeColumnRows (fixedType, cols) {
|
||||
return convertToRows(cols, fixedType);
|
||||
},
|
||||
exportCsv (params) {
|
||||
if (params.filename) {
|
||||
|
@ -845,7 +904,6 @@
|
|||
},
|
||||
mounted () {
|
||||
this.handleResize();
|
||||
this.fixedHeader();
|
||||
this.$nextTick(() => this.ready = true);
|
||||
|
||||
on(window, 'resize', this.handleResize);
|
||||
|
@ -855,7 +913,6 @@
|
|||
this.$on('on-visible-change', (val) => {
|
||||
if (val) {
|
||||
this.handleResize();
|
||||
this.fixedHeader();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -883,11 +940,13 @@
|
|||
columns: {
|
||||
handler () {
|
||||
// todo 这里有性能问题,可能是左右固定计算属性影响的
|
||||
this.allColumns = getAllColumns(this.columns);
|
||||
this.cloneColumns = this.makeColumns();
|
||||
this.columnRows = this.makeColumnRows(false);
|
||||
this.leftFixedColumnRows = this.makeColumnRows('left');
|
||||
this.rightFixedColumnRows = this.makeColumnRows('right');
|
||||
const colsWithId = this.makeColumnsId(this.columns);
|
||||
this.allColumns = getAllColumns(colsWithId);
|
||||
this.cloneColumns = this.makeColumns(colsWithId);
|
||||
|
||||
this.columnRows = this.makeColumnRows(false, colsWithId);
|
||||
this.leftFixedColumnRows = this.makeColumnRows('left', colsWithId);
|
||||
this.rightFixedColumnRows = this.makeColumnRows('right', colsWithId);
|
||||
this.rebuildData = this.makeDataWithSortAndFilter();
|
||||
this.handleResize();
|
||||
},
|
||||
|
@ -895,11 +954,12 @@
|
|||
},
|
||||
height () {
|
||||
this.handleResize();
|
||||
this.fixedHeader();
|
||||
},
|
||||
showHorizontalScrollBar () {
|
||||
this.handleResize();
|
||||
this.fixedHeader();
|
||||
},
|
||||
showVerticalScrollBar () {
|
||||
this.handleResize();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -78,4 +78,16 @@ const convertToRows = (columns, fixedType = false) => {
|
|||
return rows;
|
||||
};
|
||||
|
||||
export {convertToRows};
|
||||
export {convertToRows};
|
||||
|
||||
const getRandomStr = function (len = 32) {
|
||||
const $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
const maxPos = $chars.length;
|
||||
let str = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
str += $chars.charAt(Math.floor(Math.random() * maxPos));
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
export {getRandomStr};
|
|
@ -2,7 +2,13 @@
|
|||
<div :class="classes">
|
||||
<div :class="[prefixCls + '-bar']">
|
||||
<div :class="[prefixCls + '-nav-right']" v-if="showSlot"><slot name="extra"></slot></div>
|
||||
<div :class="[prefixCls + '-nav-container']">
|
||||
<div
|
||||
:class="[prefixCls + '-nav-container']"
|
||||
tabindex="0"
|
||||
ref="navContainer"
|
||||
@keydown="handleTabKeyNavigation"
|
||||
@keydown.space.prevent="handleTabKeyboardSelect(false)"
|
||||
>
|
||||
<div ref="navWrap" :class="[prefixCls + '-nav-wrap', scrollable ? prefixCls + '-nav-scrollable' : '']">
|
||||
<span :class="[prefixCls + '-nav-prev', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollPrev"><Icon type="chevron-left"></Icon></span>
|
||||
<span :class="[prefixCls + '-nav-next', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollNext"><Icon type="chevron-right"></Icon></span>
|
||||
|
@ -20,7 +26,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="contentClasses" :style="contentStyle"><slot></slot></div>
|
||||
<div :class="contentClasses" :style="contentStyle" ref="panes"><slot></slot></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -31,6 +37,28 @@
|
|||
import elementResizeDetectorMaker from 'element-resize-detector';
|
||||
|
||||
const prefixCls = 'ivu-tabs';
|
||||
const transitionTime = 300; // from CSS
|
||||
|
||||
const getNextTab = (list, activeKey, direction, countDisabledAlso) => {
|
||||
const currentIndex = list.findIndex(tab => tab.name === activeKey);
|
||||
const nextIndex = (currentIndex + direction + list.length) % list.length;
|
||||
const nextTab = list[nextIndex];
|
||||
if (nextTab.disabled) return getNextTab(list, nextTab.name, direction, countDisabledAlso);
|
||||
else return nextTab;
|
||||
};
|
||||
|
||||
const focusFirst = (element, root) => {
|
||||
try {element.focus();}
|
||||
catch(err) {} // eslint-disable-line no-empty
|
||||
|
||||
if (document.activeElement == element && element !== root) return true;
|
||||
|
||||
const candidates = element.children;
|
||||
for (let candidate of candidates) {
|
||||
if (focusFirst(candidate, root)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'Tabs',
|
||||
|
@ -56,6 +84,10 @@
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
captureFocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -68,11 +100,13 @@
|
|||
barWidth: 0,
|
||||
barOffset: 0,
|
||||
activeKey: this.value,
|
||||
focusedKey: this.value,
|
||||
showSlot: false,
|
||||
navStyle: {
|
||||
transform: ''
|
||||
},
|
||||
scrollable: false
|
||||
scrollable: false,
|
||||
transitioning: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -103,7 +137,7 @@
|
|||
];
|
||||
},
|
||||
contentStyle () {
|
||||
const x = this.navList.findIndex((nav) => nav.name === this.activeKey);
|
||||
const x = this.getTabIndex(this.activeKey);
|
||||
const p = x === 0 ? '0%' : `-${x}00%`;
|
||||
|
||||
let style = {};
|
||||
|
@ -116,10 +150,10 @@
|
|||
},
|
||||
barStyle () {
|
||||
let style = {
|
||||
display: 'none',
|
||||
visibility: 'hidden',
|
||||
width: `${this.barWidth}px`
|
||||
};
|
||||
if (this.type === 'line') style.display = 'block';
|
||||
if (this.type === 'line') style.visibility = 'visible';
|
||||
if (this.animated) {
|
||||
style.transform = `translate3d(${this.barOffset}px, 0px, 0px)`;
|
||||
} else {
|
||||
|
@ -154,7 +188,7 @@
|
|||
},
|
||||
updateBar () {
|
||||
this.$nextTick(() => {
|
||||
const index = this.navList.findIndex((nav) => nav.name === this.activeKey);
|
||||
const index = this.getTabIndex(this.activeKey);
|
||||
if (!this.$refs.nav) return; // 页面销毁时,这里会报错,为了解决 #2100
|
||||
const prevTabs = this.$refs.nav.querySelectorAll(`.${prefixCls}-tab`);
|
||||
const tab = prevTabs[index];
|
||||
|
@ -183,17 +217,35 @@
|
|||
`${prefixCls}-tab`,
|
||||
{
|
||||
[`${prefixCls}-tab-disabled`]: item.disabled,
|
||||
[`${prefixCls}-tab-active`]: item.name === this.activeKey
|
||||
[`${prefixCls}-tab-active`]: item.name === this.activeKey,
|
||||
[`${prefixCls}-tab-focused`]: item.name === this.focusedKey,
|
||||
}
|
||||
];
|
||||
},
|
||||
handleChange (index) {
|
||||
if (this.transitioning) return;
|
||||
|
||||
this.transitioning = true;
|
||||
setTimeout(() => this.transitioning = false, transitionTime);
|
||||
|
||||
const nav = this.navList[index];
|
||||
if (nav.disabled) return;
|
||||
this.activeKey = nav.name;
|
||||
this.$emit('input', nav.name);
|
||||
this.$emit('on-click', nav.name);
|
||||
},
|
||||
handleTabKeyNavigation(e){
|
||||
if (e.keyCode !== 37 && e.keyCode !== 39) return;
|
||||
const direction = e.keyCode === 39 ? 1 : -1;
|
||||
const nextTab = getNextTab(this.navList, this.focusedKey, direction);
|
||||
this.focusedKey = nextTab.name;
|
||||
},
|
||||
handleTabKeyboardSelect(init = false){
|
||||
if (init) return;
|
||||
const focused = this.focusedKey || 0;
|
||||
const index = this.getTabIndex(focused);
|
||||
this.handleChange(index);
|
||||
},
|
||||
handleRemove (index) {
|
||||
const tabs = this.getTabs();
|
||||
const tab = tabs[index];
|
||||
|
@ -262,6 +314,9 @@
|
|||
? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
|
||||
: 0;
|
||||
},
|
||||
getTabIndex(name){
|
||||
return this.navList.findIndex(nav => nav.name === name);
|
||||
},
|
||||
setOffset(value) {
|
||||
this.navStyle.transform = `translateX(-${value}px)`;
|
||||
},
|
||||
|
@ -320,19 +375,37 @@
|
|||
parentNode = parentNode.parentNode;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
updateVisibility(index){
|
||||
[...this.$refs.panes.children].forEach((el, i) => {
|
||||
if (index === i) {
|
||||
[...el.children].forEach(child => child.style.visibility = 'visible');
|
||||
if (this.captureFocus) setTimeout(() => focusFirst(el, el), transitionTime);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
[...el.children].forEach(child => child.style.visibility = 'hidden');
|
||||
}, transitionTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.activeKey = val;
|
||||
this.focusedKey = val;
|
||||
},
|
||||
activeKey () {
|
||||
activeKey (val) {
|
||||
this.focusedKey = val;
|
||||
this.updateBar();
|
||||
this.updateStatus();
|
||||
this.broadcast('Table', 'on-visible-change', true);
|
||||
this.$nextTick(() => {
|
||||
this.scrollToActiveTab();
|
||||
});
|
||||
|
||||
// update visibility
|
||||
const nextIndex = Math.max(this.getTabIndex(this.focusedKey), 0);
|
||||
this.updateVisibility(nextIndex);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -351,6 +424,9 @@
|
|||
|
||||
this.mutationObserver.observe(hiddenParentNode, { attributes: true, childList: true, characterData: true, attributeFilter: ['style'] });
|
||||
}
|
||||
|
||||
this.handleTabKeyboardSelect(true);
|
||||
this.updateVisibility(this.getTabIndex(this.activeKey));
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.observer.removeListener(this.$refs.navWrap, this.handleResize);
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
<template>
|
||||
<transition name="fade">
|
||||
<transition name="fade" v-if="fade">
|
||||
<div :class="classes" @click.stop="check" :style="wraperStyles">
|
||||
<span :class="dotClasses" v-if="showDot" :style="bgColorStyle"></span>
|
||||
<span :class="textClasses" :style="textColorStyle"><slot></slot></span>
|
||||
<Icon v-if="closable" :class="iconClass" :color="lineColor" type="ios-close-empty" @click.native.stop="close"></Icon>
|
||||
</div>
|
||||
</transition>
|
||||
<div v-else :class="classes" @click.stop="check" :style="wraperStyles">
|
||||
<span :class="dotClasses" v-if="showDot" :style="bgColorStyle"></span>
|
||||
<span :class="textClasses" :style="textColorStyle"><slot></slot></span>
|
||||
<Icon v-if="closable" :class="iconClass" :color="lineColor" type="ios-close-empty" @click.native.stop="close"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../icon';
|
||||
|
@ -39,6 +44,10 @@
|
|||
},
|
||||
name: {
|
||||
type: [String, Number]
|
||||
},
|
||||
fade: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
|
2
src/components/time/index.js
Normal file
2
src/components/time/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Time from './time.vue';
|
||||
export default Time;
|
83
src/components/time/time.js
Normal file
83
src/components/time/time.js
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @param {Number} timeStamp 判断时间戳格式是否是毫秒
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
// const isMillisecond = timeStamp => {
|
||||
// const timeStr = String(timeStamp)
|
||||
// return timeStr.length > 10
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param {Number} timeStamp 传入的时间戳
|
||||
* @param {Number} currentTime 当前时间时间戳
|
||||
* @returns {Boolean} 传入的时间戳是否早于当前时间戳
|
||||
*/
|
||||
const isEarly = (timeStamp, currentTime) => {
|
||||
return timeStamp < currentTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Number} num 数值
|
||||
* @returns {String} 处理后的字符串
|
||||
* @description 如果传入的数值小于10,即位数只有1位,则在前面补充0
|
||||
*/
|
||||
const getHandledValue = num => {
|
||||
return num < 10 ? '0' + num : num;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Number} timeStamp 传入的时间戳
|
||||
* @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间
|
||||
*/
|
||||
const getDate = (timeStamp, startType) => {
|
||||
const d = new Date(timeStamp * 1000);
|
||||
const year = d.getFullYear();
|
||||
const month = getHandledValue(d.getMonth() + 1);
|
||||
const date = getHandledValue(d.getDate());
|
||||
const hours = getHandledValue(d.getHours());
|
||||
const minutes = getHandledValue(d.getMinutes());
|
||||
const second = getHandledValue(d.getSeconds());
|
||||
let resStr = '';
|
||||
if (startType === 'year') resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second;
|
||||
else resStr = month + '-' + date + ' ' + hours + ':' + minutes;
|
||||
return resStr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String|Number} timeStamp 时间戳
|
||||
* @returns {String} 相对时间字符串
|
||||
*/
|
||||
export const getRelativeTime = timeStamp => {
|
||||
// 判断当前传入的时间戳是秒格式还是毫秒
|
||||
const IS_MILLISECOND = true;
|
||||
// 如果是毫秒格式则转为秒格式
|
||||
if (IS_MILLISECOND) Math.floor(timeStamp /= 1000);
|
||||
// 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型
|
||||
timeStamp = Number(timeStamp);
|
||||
// 获取当前时间时间戳
|
||||
const currentTime = Math.floor(Date.parse(new Date()) / 1000);
|
||||
// 判断传入时间戳是否早于当前时间戳
|
||||
const IS_EARLY = isEarly(timeStamp, currentTime);
|
||||
// 获取两个时间戳差值
|
||||
let diff = currentTime - timeStamp;
|
||||
// 如果IS_EARLY为false则差值取反
|
||||
if (!IS_EARLY) diff = -diff;
|
||||
let resStr = '';
|
||||
const dirStr = IS_EARLY ? '前' : '后';
|
||||
// 少于等于59秒
|
||||
if (diff <= 59) resStr = diff + '秒' + dirStr;
|
||||
// 多于59秒,少于等于59分钟59秒
|
||||
else if (diff > 59 && diff <= 3599) resStr = Math.floor(diff / 60) + '分钟' + dirStr;
|
||||
// 多于59分钟59秒,少于等于23小时59分钟59秒
|
||||
else if (diff > 3599 && diff <= 86399) resStr = Math.floor(diff / 3600) + '小时' + dirStr;
|
||||
// 多于23小时59分钟59秒,少于等于29天59分钟59秒
|
||||
else if (diff > 86399 && diff <= 2623859) resStr = Math.floor(diff / 86400) + '天' + dirStr;
|
||||
// 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前
|
||||
else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) resStr = getDate(timeStamp);
|
||||
else resStr = getDate(timeStamp, 'year');
|
||||
return resStr;
|
||||
};
|
||||
|
||||
export default function (timestamp) {
|
||||
return getRelativeTime(timestamp);
|
||||
}
|
95
src/components/time/time.vue
Normal file
95
src/components/time/time.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<span :class="classes" @click="handleClick">{{ date }}</span>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue';
|
||||
const isServer = Vue.prototype.$isServer;
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Time from './time';
|
||||
|
||||
const prefixCls = 'ivu-time';
|
||||
|
||||
export default {
|
||||
name: 'Time',
|
||||
props: {
|
||||
time: {
|
||||
type: [Number, Date],
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
validator (value) {
|
||||
return oneOf(value, ['relative', 'date', 'datetime']);
|
||||
},
|
||||
default: 'relative'
|
||||
},
|
||||
hash: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 60
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
date: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-with-hash`]: this.hash
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
if (this.hash !== '') window.location.hash = this.hash;
|
||||
},
|
||||
setTime () {
|
||||
const type = typeof this.time;
|
||||
let time;
|
||||
|
||||
if (type === 'number') {
|
||||
const timestamp = this.time.toString().length > 10 ? this.time : this.time * 1000;
|
||||
time = (new Date(timestamp)).getTime();
|
||||
} else if (type === 'object') {
|
||||
time = this.time.getTime();
|
||||
}
|
||||
|
||||
if (this.type === 'relative') {
|
||||
this.date = Time(time);
|
||||
} else {
|
||||
const date = new Date(this.time);
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1);
|
||||
const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
|
||||
const hour = date.getHours() < 10 ? '0' + date.getHours() : date.getHours();
|
||||
const minute = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes();
|
||||
const second = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
|
||||
|
||||
if (this.type === 'datetime') {
|
||||
this.date = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||||
} else if (this.type === 'date') {
|
||||
this.date = `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.setTime();
|
||||
if (isServer) return;
|
||||
this.timer = setInterval(() => {
|
||||
this.setTime();
|
||||
}, 1000 * this.interval);
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.timer) clearInterval(this.timer);
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -17,11 +17,12 @@
|
|||
<span v-else :class="titleClasses" @click="handleSelect">{{ data.title }}</span>
|
||||
<Tree-node
|
||||
v-if="data.expand"
|
||||
v-for="(item, i) in data.children"
|
||||
v-for="(item, i) in children"
|
||||
:key="i"
|
||||
:data="item"
|
||||
:multiple="multiple"
|
||||
:show-checkbox="showCheckbox">
|
||||
:show-checkbox="showCheckbox"
|
||||
:children-key="childrenKey">
|
||||
</Tree-node>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -52,6 +53,10 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
childrenKey: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
showCheckbox: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -93,7 +98,7 @@
|
|||
];
|
||||
},
|
||||
showArrow () {
|
||||
return (this.data.children && this.data.children.length) || ('loading' in this.data && !this.data.loading);
|
||||
return (this.data[this.childrenKey] && this.data[this.childrenKey].length) || ('loading' in this.data && !this.data.loading);
|
||||
},
|
||||
showLoading () {
|
||||
return 'loading' in this.data && this.data.loading;
|
||||
|
@ -118,6 +123,9 @@
|
|||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
children () {
|
||||
return this.data[this.childrenKey];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -126,14 +134,14 @@
|
|||
if (item.disabled) return;
|
||||
|
||||
// async loading
|
||||
if (item.children.length === 0) {
|
||||
if (item[this.childrenKey].length === 0) {
|
||||
const tree = findComponentUpward(this, 'Tree');
|
||||
if (tree && tree.loadData) {
|
||||
this.$set(this.data, 'loading', true);
|
||||
tree.loadData(item, children => {
|
||||
this.$set(this.data, 'loading', false);
|
||||
if (children.length) {
|
||||
this.$set(this.data, 'children', children);
|
||||
this.$set(this.data, this.childrenKey, children);
|
||||
this.$nextTick(() => this.handleExpand());
|
||||
}
|
||||
});
|
||||
|
@ -141,7 +149,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (item.children && item.children.length) {
|
||||
if (item[this.childrenKey] && item[this.childrenKey].length) {
|
||||
this.$set(this.data, 'expand', !this.data.expand);
|
||||
this.dispatch('Tree', 'toggle-expand', this.data);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
:data="item"
|
||||
visible
|
||||
:multiple="multiple"
|
||||
:show-checkbox="showCheckbox">
|
||||
:show-checkbox="showCheckbox"
|
||||
:children-key="childrenKey">
|
||||
</Tree-node>
|
||||
<div :class="[prefixCls + '-empty']" v-if="!stateTree.length">{{ localeEmptyText }}</div>
|
||||
</div>
|
||||
|
@ -40,6 +41,10 @@
|
|||
emptyText: {
|
||||
type: String
|
||||
},
|
||||
childrenKey: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
loadData: {
|
||||
type: Function
|
||||
},
|
||||
|
@ -76,18 +81,19 @@
|
|||
methods: {
|
||||
compileFlatState () { // so we have always a relation parent/children of each node
|
||||
let keyCounter = 0;
|
||||
let childrenKey = this.childrenKey;
|
||||
const flatTree = [];
|
||||
function flattenChildren(node, parent) {
|
||||
node.nodeKey = keyCounter++;
|
||||
flatTree[node.nodeKey] = { node: node, nodeKey: node.nodeKey };
|
||||
if (typeof parent != 'undefined') {
|
||||
flatTree[node.nodeKey].parent = parent.nodeKey;
|
||||
flatTree[parent.nodeKey].children.push(node.nodeKey);
|
||||
flatTree[parent.nodeKey][childrenKey].push(node.nodeKey);
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
flatTree[node.nodeKey].children = [];
|
||||
node.children.forEach(child => flattenChildren(child, node));
|
||||
if (node[childrenKey]) {
|
||||
flatTree[node.nodeKey][childrenKey] = [];
|
||||
node[childrenKey].forEach(child => flattenChildren(child, node));
|
||||
}
|
||||
}
|
||||
this.stateTree.forEach(rootNode => {
|
||||
|
@ -104,11 +110,11 @@
|
|||
if (node.checked == parent.checked && node.indeterminate == parent.indeterminate) return; // no need to update upwards
|
||||
|
||||
if (node.checked == true) {
|
||||
this.$set(parent, 'checked', parent.children.every(node => node.checked));
|
||||
this.$set(parent, 'checked', parent[this.childrenKey].every(node => node.checked));
|
||||
this.$set(parent, 'indeterminate', !parent.checked);
|
||||
} else {
|
||||
this.$set(parent, 'checked', false);
|
||||
this.$set(parent, 'indeterminate', parent.children.some(node => node.checked || node.indeterminate));
|
||||
this.$set(parent, 'indeterminate', parent[this.childrenKey].some(node => node.checked || node.indeterminate));
|
||||
}
|
||||
this.updateTreeUp(parentKey);
|
||||
},
|
||||
|
@ -139,8 +145,8 @@
|
|||
for (let key in changes) {
|
||||
this.$set(node, key, changes[key]);
|
||||
}
|
||||
if (node.children) {
|
||||
node.children.forEach(child => {
|
||||
if (node[this.childrenKey]) {
|
||||
node[this.childrenKey].forEach(child => {
|
||||
this.updateTreeDown(child, changes);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue