Merge branch '2.0' into pr004

This commit is contained in:
Aresn 2018-06-20 11:23:55 +08:00 committed by GitHub
commit e9dc3c4289
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
177 changed files with 62359 additions and 18930 deletions

View file

@ -0,0 +1,2 @@
import AnchorLink from '../anchor/anchor-link.vue';
export default AnchorLink;

View 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>

View 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>

View file

@ -0,0 +1,2 @@
import Anchor from './anchor.vue';
export default Anchor;

View file

@ -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>

View file

@ -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) {

View file

@ -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>

View file

@ -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,

View file

@ -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;
});
},

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,7 @@
export default {
methods: {
handleEscape(e) {
this.dispatch('ColorPicker', 'on-escape-keydown', e);
},
},
};

View 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;
},
},
};

View file

@ -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>

View file

@ -1,2 +1,3 @@
import ColorPicker from './color-picker.vue';
export default ColorPicker;
export default ColorPicker;

View file

@ -0,0 +1,10 @@
export default {
data() {
return {
prefixCls: 'ivu-color-picker',
inputPrefixCls: 'ivu-input',
iconPrefixCls: 'ivu-icon',
transferPrefixCls: 'ivu-transfer',
};
},
};

View file

@ -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>

View file

@ -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>

View 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;
}

View file

@ -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');
}
}
}
};

View file

@ -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)
}
];
},

View file

@ -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(){

View file

@ -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
}
];

View file

@ -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 () {

View file

@ -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
}
];

View file

@ -46,6 +46,10 @@ export default {
pickerType: {
type: String,
require: true
},
focusedDate: {
type: Date,
required: true,
}
},
computed: {

View file

@ -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,

View file

@ -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);
},
},
};

View file

@ -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, {});

View file

@ -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 () {

View file

@ -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;

View file

@ -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>

View file

@ -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: {

View file

@ -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());
});
}
}
}
};

View file

@ -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) {

View file

@ -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) {

View file

@ -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: {

View file

@ -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() {

View file

@ -40,6 +40,9 @@
default: 'off'
}
},
provide() {
return { form : this };
},
data () {
return {
fields: []

View file

@ -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) {

View file

@ -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>

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -16,4 +16,4 @@ export default {
return this.menu.mode;
}
}
};
};

View file

@ -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();
}
}
}
};
};

View file

@ -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');
}
};
};

View file

@ -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: {

View file

@ -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();
}
}
}
};

View file

@ -11,8 +11,7 @@
@change="change"
@focus="onFocus"
@blur="onBlur">
</span>
<slot>{{ label }}</slot>
</span><slot>{{ label }}</slot>
</label>
</template>
<script>

View file

@ -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);

View file

@ -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 () {

View 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>

View file

@ -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>

View 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

View file

@ -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);

View file

@ -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;

View file

@ -0,0 +1,2 @@
import Split from './split.vue'
export default Split

View 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>

View 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>

View file

@ -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];
}
}
}
};

View file

@ -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();
}
}
};

View file

@ -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};

View file

@ -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);

View file

@ -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 () {

View file

@ -0,0 +1,2 @@
import Time from './time.vue';
export default Time;

View 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);
}

View 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>

View file

@ -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);
}

View file

@ -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);
});
}