Merge branch 'develop' of ***REMOVED*** into develop

This commit is contained in:
JiwenBai 2019-07-25 11:14:45 +08:00
commit 6eb8c9651c
41 changed files with 2536 additions and 431 deletions

View File

@ -24,7 +24,7 @@ Run `npm run test` to test all components.
Run `npm run test-cov` to test all components and see detailed test coverage report.
## Want to see how component works
1. Run `npm run build`
1. Run `npm run dev`
2. Open `http://localhost:8086/` in browser.
## Want to add your own component
There is no guideline for now. If you want to know how to do it, you can explore by yourself or ask `lecong.zhang@tusimple.ai`.

View File

@ -9,13 +9,13 @@
>
<!--EXAMPLE_START-->
<n-date-picker
v-model="dateTimeTimestamp"
v-model="ts1"
type="datetime"
style="margin-right: 12px;"
@change="onDateTimeChange"
/>
<n-date-picker
v-model="dateTimestamp"
v-model="ts2"
type="date"
@change="onDateChange"
/>
@ -26,18 +26,20 @@
style="flex-wrap: nowrap;"
>
<n-input
v-model="dateTimeTimestamp"
placeholder="dateTimeTimestamp"
:value="ts1"
placeholder="ts1"
type="text"
style="margin-right: 12px;"
@input="handleInputTs1"
/>
<n-input
v-model="dateTimestamp"
placeholder="dateTimestamp"
:value="ts2"
placeholder="ts2"
type="text"
@input="handleInputTs2"
/>
</div>
<pre class="n-doc-section__inspect">datetime v-model: {{ dateTimeTimestamp }}, date v-model: {{ dateTimestamp }}</pre>
<pre class="n-doc-section__inspect">datetime v-model: {{ JSON.stringify(ts1) }}, date v-model: {{ JSON.stringify(ts2) }}</pre>
<n-doc-source-block>
<!--SOURCE-->
</n-doc-source-block>
@ -48,8 +50,8 @@
export default {
data () {
return {
dateTimeTimestamp: 891360258000,
dateTimestamp: 891360258000
ts1: null,
ts2: 0
}
},
methods: {
@ -58,6 +60,13 @@ export default {
},
onDateChange (timestamp, dateString) {
this.$NMessage.success(`${timestamp}, ${dateString}`)
},
handleInputTs1 (v) {
v = Number(v)
this.ts1 = Number.isNaN(v) ? null : v
},
handleInputTs2 (v) {
this.ts2 = Number.isNaN(v) ? null : v
}
}
}

View File

@ -0,0 +1,908 @@
<template>
<div
ref="doc"
class="n-doc"
>
<div class="n-doc-header">
<n-gradient-text :font-size="20">
Form / n-form
</n-gradient-text>
</div>
<div class="n-doc-body">
<div class="n-doc-section">
<div class="n-doc-section__header">
Inline Form
</div>
<div class="n-doc-section__view">
<n-form
inline
:label-width="80"
>
<n-form-item label="name">
<n-input placeholder="Input your name" />
</n-form-item>
<n-form-item label="age">
<n-input placeholder="Input your age" />
</n-form-item>
<n-form-item label="phone">
<n-input placeholder="Input your phone number" />
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form inline
:label-width="80">
<n-form-item :model="form" label="name">
<n-input v-model="form.name" placeholder="Input your name" />
</n-form-item>
<n-form-item label="age">
<n-input v-model="form.age" placeholder="Input your age" />
</n-form-item>
<n-form-item label="phone">
<n-input v-model="form.phone" placeholder="Input your phone number" />
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
form: {
name: '',
age: '',
phone: ''
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form Item
</div>
<div class="n-doc-section__view">
<n-form :model="form">
<n-form-item label="Input">
<n-input
v-model="form.input"
placeholder="Enter sth"
/>
</n-form-item>
<n-form-item label="Select">
<n-select
v-model="form.select"
size="small"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item label="Switch">
<n-switch v-model="form.switch" />
</n-form-item>
<n-form-item label="DatePicker">
<n-date-picker
v-model="form.dateTimeTimestamp"
type="datetime"
/>
</n-form-item>
<n-form-item label="Switch">
<n-radio
v-model="form.radio"
value="Definitely Maybe"
>
Definitely Maybe
</n-radio>
<n-radio
v-model="form.radio"
value="Be Here Now"
>
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form :model="form">
<n-form-item label="Input">
<n-input v-model="form.input" placeholder="Enter sth" />
</n-form-item>
<n-form-item label="Select">
<n-select size="small" v-model="form.select"
placeholder="Please Select Type" :items="items" />
</n-form-item>
<n-form-item label="Switch">
<n-switch v-model="form.switch" />
</n-form-item>
<n-form-item label="DatePicker">
<n-date-picker v-model="form.dateTimeTimestamp"
type="datetime" />
</n-form-item>
<n-form-item label="Switch">
<n-radio v-model="form.radio" value="Definitely Maybe">
Definitely Maybe</n-radio>
<n-radio v-model="form.radio" value="Be Here Now">
Be Here Now</n-radio>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
items: [
{
label: "ArtifactoryLabel",
value: "Artifactory"
},
{
label: "Registry",
value: "Registry"
},
{
label: "Public",
value: "Public"
},
{
label: "Custom",
value: "Custom"
}
],
form: {
input: '',
select: '',
datepicker: 0,
switch: false,
radio: ''
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Label Position
</div>
<div class="n-doc-section__view">
<n-form
:label-width="200"
label-position="top"
>
<n-form-item label="Top1">
<n-input />
</n-form-item>
<n-form-item label="Top2">
<n-input />
</n-form-item>
<n-form-item
label-position="left"
label="Left"
>
<n-input />
</n-form-item>
<n-form-item
label-position="center"
label="Center"
>
<n-input />
</n-form-item>
<n-form-item
label-position="right"
label="Right"
>
<n-input />
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
<n-form-item
label="Action"
label-position="right"
>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea>
<n-form
:label-width="200"
label-position="top"
>
<n-form-item label="Top1">
<n-input/>
</n-form-item>
<n-form-item label="Top2">
<n-input/>
</n-form-item>
<n-form-item label-position="left" label="Left">
<n-input/>
</n-form-item>
<n-form-item label-position="center" label="Center">
<n-input/>
</n-form-item>
<n-form-item label-position="right" label="Right">
<n-input/>
</n-form-item>
<n-form-item>
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
<n-form-item label="Action" label-position="right">
<n-button>Submit</n-button>
<n-button>Cancel</n-button>
</n-form-item>
</n-form>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Validate Form
</div>
<div class="n-doc-section__view">
<n-form
ref="form-validate"
:model="validateForm"
:rules="validateRules"
>
<n-form-item
label="Warning"
label-position="top"
>
The key in form-model does not support the form with ' . '.<br>
And does not init the value of parameters with 'undefined'.<br>
ResetForm Method: only can reset the item with prop.<br>
ValidateForm Method: support validate specified items by the second parameter in form of array.<br>
</n-form-item>
<n-form-item
:required-logo="false"
prop="input"
label="Input"
>
<n-input
v-model="validateForm.input"
placeholder="Enter string"
/>
</n-form-item>
<n-form-item
prop="muti.deep.select"
label="Select"
>
<n-select
v-model="validateForm.muti.deep.select"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item
label="Switch"
prop="switch"
>
<n-switch v-model="validateForm.switch" />
</n-form-item>
<n-form-item
label="DatePicker"
prop="datepicker"
>
<n-date-picker
v-model="validateForm.datepicker"
type="date"
/>
</n-form-item>
<n-form-item
prop="radio"
label="Radio"
>
<n-radio
v-model="validateForm.radio"
value="Definitely Maybe"
>
Definitely Maybe
</n-radio>
<n-radio
v-model="validateForm.radio"
value="Be Here Now"
>
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('form-validate')">
Submit
</n-button>
<n-button @click="formReset('form-validate')">
Reset
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="form-validate" disabled :model="validateForm" :rules="validateRules">
<n-form-item prop="input" label="Input">
<n-input v-model="validateForm.input" placeholder="Enter string" />
</n-form-item>
<n-form-item prop="muti.deep.select" label="Select">
<n-select
size="small"
v-model="validateForm.muti.deep.select"
placeholder="Please Select Type"
:items="items"
/>
</n-form-item>
<n-form-item label="Switch" prop="switch">
<n-switch v-model="validateForm.switch" />
</n-form-item>
<n-form-item label="DatePicker" prop="datepicker">
<n-date-picker v-model="validateForm.datepicker" type="date" />
</n-form-item>
<n-form-item
prop="radio"
label="Radio">
<n-radio v-model="validateForm.radio" value="Definitely Maybe">
Definitely Maybe
</n-radio>
<n-radio v-model="validateForm.radio" value="Be Here Now">
Be Here Now
</n-radio>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('form-validate')">
Submit
</n-button>
<n-button @click="formReset('form-validate')">
Reset
</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
validateForm: {
input: "",
muti: {
deep: {
select: ""
}
},
datepicker: "",
switch: false,
radio: ""
},
validateRules: {
input: [
{ required: true, message: "input cannot be empty", trigger: "blur" }
],
"muti.deep.select": [
{
required: true,
message: "select cannot be empty",
trigger: "change"
}
],
radio: [
{
required: true,
message: "radio please choose some",
trigger: "change"
}
],
datepicker: [
{
required: true,
type: "date",
message: "Please select the date",
trigger: "change"
}
]
}
}
},
methods: {
formValidate(ref) {
// two ways to use, the first used most
// this.$refs[ref].validate((flag, res) => {
// console.log("validate all result", flag, res);
// });
new Promise((resolve, reject) => {
this.$refs[ref].validate((valid, filed) => {
if (valid) {
resolve(filed);
} else {
reject(filed);
}
});
})
.then(f => {
console.log("pass", f);
})
.catch(e => {
console.log("unpass", e);
});
},
formReset (ref) {
this.$refs[ref].resetForm();
},
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Custom validate rules
</div>
<div class="n-doc-section__view">
<n-form
ref="custom"
:model="custom"
:rules="customRules"
:label-width="80"
>
<n-form-item
prop="age"
label="Age"
>
<n-input v-model.number="custom.age" />
</n-form-item>
<n-form-item
prop="pass"
label="Password"
>
<n-input
v-model="custom.pass"
/>
</n-form-item>
<n-form-item
prop="checkPass"
label="CheckPass"
>
<n-input v-model="custom.checkPass" />
</n-form-item>
<n-form-item>
<n-button @click="formValidate('custom')">
Commit
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="custom" :model="custom" :rules="customRules" :label-width="80">
<n-form-item prop="age" label="Age">
<n-input v-model.number="custom.age"></n-input>
</n-form-item>
<n-form-item prop="pass" label="Password">
<n-input v-model="custom.pass"></n-input>
</n-form-item>
<n-form-item prop="checkPass" label="CheckPass">
<n-input v-model="custom.checkPass"></n-input>
</n-form-item>
<n-form-item>
<n-button @click="formValidate('custom')">Commit</n-button>
</n-form-item>
</n-form>
<script>
export default {
data() {
return {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error("Input age"));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error("Number required"));
} else {
if (value < 18) {
callback(new Error("Age should over 18"));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === "") {
callback(new Error("Input password"));
} else {
if (this.custom.checkPass !== "") {
this.$refs.custom.validate("", ["checkPass"]);
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === "") {
callback(new Error("Input password again"));
} else if (value !== this.custom.pass) {
callback(new Error("ent input password twice!"));
} else {
callback();
}
};
custom: {
pass: "",
checkPass: "",
age: ""
},
customRules: {
pass: [{ validator: validatePass, trigger: "blur" }],
checkPass: [{ validator: validatePass2, trigger: "blur" }],
age: [{ validator: checkAge, trigger: "blur" }]
}
}
}
}
</script>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Form of Dynamic
</div>
<div class="n-doc-section__view">
<n-form
ref="custom"
:model="dynamic"
:rules="dynamicRules"
:label-width="80"
>
<n-form-item
v-for="(item, k) in dynamic.email"
:key="k"
:prop="'email.' + k"
:label="k"
>
<n-input v-model="dynamic['email'][k]" />
</n-form-item>
<n-form-item>
<n-button @click="dynamicAddItem">
Add
</n-button>
<n-button @click="dynamicPopItem">
Delete
</n-button>
</n-form-item>
</n-form>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-form ref="dynamic" :model="dynamic" :rules="dynamicRules" :label-width="80">
<n-form-item
:key="k"
:prop="'email.' + k"
:label="k"
v-for="(item, k) in dynamic.email"
>
<n-input v-model="dynamic['email'][k]"></n-input>
</n-form-item>
<n-form-item>
<n-button @click="dynamicAddItem">Add item</n-button>
<n-button @click="dynamicPopItem">Delete</n-button>
</n-form-item>
</n-form>
<script>
export default {
data () {
return {
dynamic: {
name: "",
email: {
email0: ""
}
},
dynamicRules: {
"email.email0": [
{
required: true,
message: "Mailbox cannot be empty",
trigger: "blur"
},
{ type: "email", message: "Incorrect email format", trigger: "blur" }
]
}
}
},
methods: {
dynamicAddItem() {
let i = Object.keys(this.dynamic.email).length;
this.$set(this.dynamic.email, "email" + i, "");
// dynamicRules
this.$set(
this.dynamicRules,
"email.email" + i,
this.dynamicRules["email.email0"]
);
},
dynamicPopItem () {
let keys = Object.keys(this.dynamic.email)
let k = keys[keys.length - 1]
if (k) {
delete this.dynamic.email[k]
}
}
}
}
</script>
</textarea>
</div>
</div>
</div>
</div>
</template>
<script>
import docCodeEditorMixin from './docCodeEditorMixin'
export default {
mixins: [docCodeEditorMixin],
data () {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('Input age'))
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('Number required'))
} else {
if (value < 18) {
callback(new Error('Age should over 18'))
} else {
callback()
}
}
}, 1000)
}
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('Input password'))
} else {
if (this.custom.checkPass !== '') {
this.$refs.custom.validate('', ['checkPass'])
}
callback()
}
}
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('Input password again'))
} else if (value !== this.custom.pass) {
callback(new Error('ent input password twice!'))
} else {
callback()
}
}
return {
form: {
name: '',
age: '',
phone: '',
// second form
input: '',
select: '',
datepicker: '',
switch: false,
radio: ''
},
items: [
{
label: 'ArtifactoryLabel',
value: 'Artifactory'
},
{
label: 'Registry',
value: 'Registry'
},
{
label: 'Public',
value: 'Public'
},
{
label: 'Custom',
value: 'Custom'
}
],
validateForm: {
input: 'input',
muti: {
deep: {
select: 'Public'
}
},
datepicker: 0,
switch: false,
radio: ''
},
validateRules: {
input: [
{ required: true, message: 'input cannot be empty', trigger: 'blur' }
],
'muti.deep.select': [
{
required: true,
message: 'select cannot be empty',
trigger: 'change'
}
],
radio: [
{
required: true,
message: 'radio please choose some',
trigger: 'change'
}
],
datepicker: [
{
required: true,
type: 'date',
message: 'Please select the date',
trigger: 'change'
}
]
},
ruleValidate: {
name: [
{
required: true,
message: 'The name cannot be empty',
trigger: 'blur'
}
],
mail: [
{
required: true,
message: 'Mailbox cannot be empty',
trigger: 'blur'
},
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' }
],
city: [
{
required: true,
message: 'Please select the city',
trigger: 'change'
}
],
gender: [
{ required: true, message: 'Please select gender', trigger: 'change' }
],
interest: [
{
required: true,
type: 'array',
min: 1,
message: 'Choose at least one hobby',
trigger: 'change'
},
{
type: 'array',
max: 2,
message: 'Choose two hobbies at best',
trigger: 'change'
}
],
date: [
{
required: true,
type: 'date',
message: 'Please select the date',
trigger: 'change'
}
],
time: [
{
required: true,
type: 'string',
message: 'Please select time',
trigger: 'change'
}
],
desc: [
{
required: true,
message: 'Please enter a personal introduction',
trigger: 'blur'
},
{
type: 'string',
min: 20,
message: 'Introduce no less than 20 words',
trigger: 'blur'
}
]
},
custom: {
pass: '',
checkPass: '',
age: ''
},
customRules: {
pass: [{ validator: validatePass, trigger: 'blur' }],
checkPass: [{ validator: validatePass2, trigger: 'blur' }],
age: [{ validator: checkAge, trigger: 'blur' }]
},
dynamic: {
name: '',
email: {
email0: ''
}
},
dynamicRules: {
'email.email0': [
{
required: true,
message: 'Mailbox cannot be empty',
trigger: 'blur'
},
{ type: 'email', message: 'Incorrect email format', trigger: 'blur' }
]
}
}
},
methods: {
formValidate (ref) {
// this.$refs[ref].validate((flag, res) => {
// console.log('validate all result', flag, res);
// })
new Promise((resolve, reject) => {
this.$refs[ref].validate((valid, filed) => {
if (valid) {
resolve(filed)
} else {
reject(filed)
}
})
})
.then(f => {
console.log('pass', f)
})
.catch(e => {
console.log('unpass', e)
})
},
formReset (ref) {
this.$refs[ref].resetForm()
},
dynamicAddItem () {
let i = Object.keys(this.dynamic.email).length
this.$set(this.dynamic.email, 'email' + i, '')
// dynamicRules
this.$set(
this.dynamicRules,
'email.email' + i,
this.dynamicRules['email.email0']
)
},
dynamicPopItem () {
let keys = Object.keys(this.dynamic.email)
let k = keys.pop()
if (k) {
this.$delete(this.dynamic.email, k)
}
}
}
}
</script>

View File

@ -15,7 +15,6 @@
</div>
<div class="n-doc-section__view">
<n-nimbus-service-layout
v-model="currentFileld"
icon="md-musical-notes"
name="Oasis"
:items="[
@ -65,7 +64,6 @@
<textarea><n-nimbus-service-layout
icon="md-musical-notes"
name="Oasis"
v-model="'Hello'"
:items="[
{
name: 'Definitely Maybe',
@ -120,7 +118,7 @@ export default {
mixins: [docCodeEditorMixin],
data () {
return {
currentFileld: 'Hello'
currentField: 'Hello'
}
},
methods: {

View File

@ -0,0 +1,34 @@
<template>
<div class="n-doc-section">
<div class="n-doc-section__header">
Basic Usage
</div>
<div
class="n-doc-section__view"
style="flex-wrap: nowrap;"
>
<!--EXAMPLE_START-->
<n-time-picker
v-model="time0"
style="margin-right: 14px;"
/>
<n-time-picker v-model="time1" />
<!--EXAMPLE_END-->
</div>
<pre class="n-doc-section__inspect">time0: {{ JSON.stringify(time0) }}, time1: {{ JSON.stringify(time1) }}</pre>
<n-doc-source-block>
<!--SOURCE-->
</n-doc-source-block>
</div>
</template>
<script>
export default {
data () {
return {
time0: null,
time1: 1563937380000
}
}
}
</script>

View File

@ -0,0 +1,32 @@
<template>
<div
ref="doc"
class="n-doc"
>
<div class="n-doc-header">
<n-gradient-text :font-size="20">
TimePicker / n-time-picker
</n-gradient-text>
</div>
<div class="n-doc-body">
<basicUsage />
</div>
</div>
</template>
<script>
import basicUsage from './basicUsage.demo.vue'
export default {
components: {
basicUsage
},
data () {
return {
}
},
methods: {
}
}
</script>

View File

@ -0,0 +1,23 @@
<template>
<div style="height: 3000px;">
<div style="height: 400px;" />
<div style="margin-left: 400px;">
<n-popover trigger="click">
<template v-slot:activator>
<button>Activator</button>
</template>
<span>Out Out Out</span>
</n-popover>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@ -0,0 +1,17 @@
<template>
<div style="padding: 100px;">
<button @click="f">
router debug
</button>
</div>
</template>
<script>
export default {
methods: {
f () {
this.$router.push('/n-popover?999')
}
}
}
</script>

View File

@ -50,7 +50,6 @@ export default {
},
{
name: 'Common',
path: '/',
childItems: [
{
name: 'AdvanceTable',
@ -124,9 +123,30 @@ export default {
name: 'Table',
path: '/n-table'
},
{
name: 'TimePicker',
path: '/n-time-picker'
},
{
name: 'Tooltip',
path: '/n-tooltip'
},
{
name: 'Form',
path: '/n-form'
}
]
},
{
name: 'Debug',
childItems: [
{
name: 'PopoverDebug',
path: '/n-popover-debug'
},
{
name: 'RouterDebug',
path: '/n-router-debug'
}
]
}

View File

@ -26,6 +26,8 @@ import datePickerDemo from './components/datePickerDemo'
import inputNumberDemo from './components/inputNumberDemo'
import nimbusIconDemo from './components/nimbusIconDemo'
import radioDemo from './components/radioDemo'
import formDemo from './components/formDemo'
import timePickerDemo from './components/timePickerDemo'
import notificationDemo from './components/notificationDemo'
import nimbusConfirmCardDemo from './components/nimbusConfirmCardDemo'
@ -33,6 +35,9 @@ import paginationDemo from './components/paginationDemo'
import startPage from './components/startPage'
import demo from './demo'
import popoverDebug from './debugComponents/popoverDebug'
import routerDebug from './debugComponents/routerDebug'
Vue.use(NaiveUI)
Vue.use(VueRouter)
@ -43,6 +48,10 @@ const routes = [
path: '/home-demo',
component: homeDemo
},
{
path: '/n-popover-debug',
component: popoverDebug
},
{
path: '/start',
component: demo,
@ -71,7 +80,10 @@ const routes = [
{ path: '/n-date-picker', component: datePickerDemo },
{ path: '/n-input-number', component: inputNumberDemo },
{ path: '/n-nimbus-icon', component: nimbusIconDemo },
{ path: '/n-radio', component: radioDemo }
{ path: '/n-radio', component: radioDemo },
{ path: '/n-form', component: formDemo },
{ path: '/n-time-picker', component: timePickerDemo },
{ path: '/n-router-debug', component: routerDebug }
]
},
{

View File

@ -22,7 +22,7 @@ You <strong>MUST</strong> fix all lint warnings and errors before you push your
Run <code>npm run test-cov</code> to test all components and see detailed test coverage report.</p>
<h2 id="wanttoseehowcomponentworks">Want to see how component works</h2>
<ol>
<li>Run <code>npm run build</code></li>
<li>Run <code>npm run dev</code></li>
<li>Open <code>http://localhost:8086/</code> in browser.</li>
</ol>
<h2 id="wanttoaddyourowncomponent">Want to add your own component</h2>

View File

@ -23,7 +23,8 @@ import Alert from './packages/common/Alert'
import DatePicker from './packages/common/DatePicker'
import InputNumber from './packages/common/InputNumber'
import Radio from './packages/common/Radio'
import Form from './packages/common/Form'
import TimePicker from './packages/common/TimePicker'
import ServiceCard from './packages/nimbus/ServiceCard'
import HomeLayout from './packages/nimbus/HomeLayout'
import Navbar from './packages/nimbus/Navbar'
@ -65,6 +66,8 @@ function install (Vue) {
InputNumber.install(Vue)
NimbusIcon.install(Vue)
Radio.install(Vue)
Form.install(Vue)
TimePicker.install(Vue)
}
export default {

View File

@ -1,6 +1,6 @@
{
"name": "naive-ui",
"version": "0.2.29",
"version": "0.2.33",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -6,16 +6,15 @@
[`n-date-picker--${size}-size`]: true,
'n-date-picker--disabled': disabled
}"
@click="handleCalendarClick"
>
<input
v-model="displayDateTimeString"
class="n-date-picker__input"
:placeholder="placeholder"
:readonly="disabled ? 'disabled' : false"
@click.stop="openCalendar"
@click="handleActivatorClick"
@blur="handleDateTimeInputBlur"
@keyup.enter="handleDateTimeInputEnter"
@input="handleDateTimeInputInput"
>
<div class="n-date-picker__icon">
<n-icon
@ -29,9 +28,17 @@
>
<div ref="content">
<datetime-panel
v-model="panelValue"
v-if="type === 'datetime'"
:value="value"
:active="active"
@change="handlePanelValueChange"
@input="handlePanelInput"
@close="closeCalendar"
/>
<date-panel
v-if="type === 'date'"
:value="value"
:active="active"
@input="handlePanelInput"
@close="closeCalendar"
/>
</div>
@ -45,6 +52,7 @@ import NIcon from '../../Icon'
import detachable from '../../../mixins/detachable'
import placeable from '../../../mixins/placeable'
import DatetimePanel from './panel/datetime'
import DatePanel from './panel/date'
import clickoutside from '../../../directives/clickoutside'
const DATE_FORMAT = {
@ -69,16 +77,13 @@ export default {
},
components: {
NIcon,
DatetimePanel
DatetimePanel,
DatePanel
},
mixins: [
detachable,
placeable
],
model: {
prop: 'value',
event: 'change'
},
props: {
disabled: {
type: Boolean,
@ -89,7 +94,7 @@ export default {
default: 'bottom-start'
},
value: {
type: [Number, String],
type: Number,
required: false,
default: null
},
@ -112,21 +117,14 @@ export default {
data () {
return {
displayDateTimeString: '',
displayDateString: '',
displayTimeString: '',
rightDisplayDateTimeString: '',
rightDisplayDateString: '',
rightDisplayTimeString: '',
calendarDateTime: moment(),
rightCalendarDateTime: moment(),
currentDateTime: moment(),
active: false,
showTimeSelector: false,
showRightTimeSelector: false,
calendar: [],
rightCalendar: [],
...TIME_CONST,
panelValue: this.value
...TIME_CONST
}
},
computed: {
@ -182,84 +180,10 @@ export default {
}
},
methods: {
handlePanelValueChange (value) {
this.$emit('change', value)
handlePanelInput (value, valueString) {
this.$emit('input', value, 'unavailable for now')
this.refreshSelectedDateTimeString()
},
/**
* If new datetime is null or undefined, emit null to value.
* Else adjust new datetime by props.type and emit it to value.
*/
setValue (newSelectedDateTime) {
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
this.$emit('change', null)
return
}
if (newSelectedDateTime.isValid()) {
const adjustedDateTime = this.adjustDateTimeAccrodingToType(newSelectedDateTime)
if (this.computedSelectedDateTime === null || adjustedDateTime.valueOf() !== this.computedSelectedDateTime.valueOf()) {
this.$emit('change', adjustedDateTime.valueOf(), adjustedDateTime.format(this.format))
}
}
},
adjustDateTimeAccrodingToType (datetime) {
if (this.type === 'datetime') {
return moment(datetime).startOf('second')
} else {
return moment(datetime).startOf('date').hour(0)
}
},
justifySelectedDateTimeAfterChangeTimeString () {
if (this.computedSelectedDateTime === null) {
// case here is impossible for now, because you can't clear time for now
} else {
const newDisplayDateTimeString = this.displayDateString + ' ' + this.displayTimeString
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
}
},
handleTimeInput (e) {
const newDisplayDateTimeString = this.displayDateString + ' ' + e.target.value
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
handleDateInput (e) {
const newDisplayDateTimeString = e.target.value + ' ' + this.displayTimeString
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
handleTimeInputBlur () {
this.refreshSelectedDateTimeString()
},
handleDateInputBlur () {
this.refreshSelectedDateTimeString()
},
handleTimeConfirmClick () {
this.justifySelectedDateTimeAfterChangeTimeString()
this.refreshSelectedDateTimeString()
this.closeTimeSelector()
},
handleTimeCancelClick () {
this.closeTimeSelector()
},
openTimeSelector () {
if (this.computedSelectedDateTime === null) {
this.setValue(moment())
}
this.showTimeSelector = true
},
closeTimeSelector () {
this.showTimeSelector = false
},
/**
* To close timeSelector
*/
handleCalendarClick (e) {
if (!this.$refs.timeSelector) return
if (!this.$refs.timeSelector.contains(e.target) && this.$refs.timeSelector !== e.target) {
this.closeTimeSelector()
}
},
/**
* If not selected, display nothing,
* else update datetime related string
@ -270,16 +194,6 @@ export default {
return
}
this.displayDateTimeString = this.computedSelectedDateTime.format(this.format)
this.displayDateString = this.computedSelectedDateTime.format('YYYY-MM-DD')
this.displayTimeString = this.computedSelectedDateTime.format('HH:mm:ss')
},
/**
* If new time is invalid, do nothing.
* If valid, update.
*/
handleDateTimeInputEnter () {
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
/**
* If new SelectedDateTime is valid, update `selectedDateTime` and `calendarTime`
@ -287,16 +201,23 @@ export default {
*/
handleDateTimeInputBlur () {
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
/**
* If newSelectedDateTime is invalid, display string need to be restored
*/
this.refreshSelectedDateTimeString()
if (newSelectedDateTime.isValid()) {
this.$emit('input', newSelectedDateTime.valueOf())
} else {
this.refreshSelectedDateTimeString()
}
},
handleActivatorClick (e) {
if (this.active) {
e.stopPropagation()
} else {
this.openCalendar()
}
},
/**
* Calendar view related methods
*/
openCalendar () {
openCalendar (e) {
/**
* May leak memory here if change disabled from false to true
*/
@ -310,6 +231,12 @@ export default {
},
toggleCalendar () {
},
handleDateTimeInputInput (v) {
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
if (newSelectedDateTime.isValid()) {
this.$emit('input', newSelectedDateTime.valueOf())
}
}
}
}

View File

@ -0,0 +1,327 @@
<template>
<transition name="n-date-picker-calendar--transition">
<div
v-if="active"
v-clickoutside.lazy="handleClickOutside"
class="n-date-picker-calendar"
>
<div style="width: 100%; height: 12px" />
<div class="n-date-picker-calendar__month-modifier">
<div
class="n-date-picker-calendar__fast-prev"
@click="prevYear"
>
<n-icon
type="ios-arrow-back"
size="14"
/>
<n-icon
type="ios-arrow-back"
size="14"
/>
</div>
<div
class="n-date-picker-calendar__prev"
@click="prevMonth"
>
<n-icon
type="ios-arrow-back"
size="14"
/>
</div>
<div class="n-date-picker-calendar__month-year">
{{ calendarDateTime.format('MMMM') }} {{ calendarDateTime.year() }}
</div>
<div
class="n-date-picker-calendar__next"
@click="nextMonth"
>
<n-icon
type="ios-arrow-forward"
size="14"
/>
</div>
<div
class="n-date-picker-calendar__fast-next"
@click="nextYear"
>
<n-icon
type="ios-arrow-forward"
size="14"
/>
<n-icon
type="ios-arrow-forward"
size="14"
/>
</div>
</div>
<div class="n-date-picker-calendar__weekdays">
<div
v-for="weekday in weekdays"
:key="weekday"
class="n-date-picker-calendar__weekday"
>
{{ weekday }}
</div>
</div>
<div class="n-date-picker-calendar__divider" />
<div class="n-date-picker-calendar__dates">
<div
v-for="dateItem in dateArray(calendarDateTime, valueAsMoment, currentDateTime)"
:key="dateItem.timestamp"
class="n-date-picker-calendar__date"
:class="{
'n-date-picker-calendar__date--current': dateItem.isCurrentDate,
'n-date-picker-calendar__date--selected': dateItem.isSelectedDate,
'n-date-picker-calendar__date--in-display-month': dateItem.isDateOfDisplayMonth
}"
@click="handleDateClick(dateItem)"
>
{{ dateItem.date }}
</div>
</div>
<div class="n-date-picker-calendar__actions">
<n-button
size="tiny"
round
@click="setSelectedDateTimeToNow"
>
Now
</n-button>
<n-button
size="tiny"
round
auto-text-color
type="primary"
@click="handleConfirmClick"
>
Confirm
</n-button>
</div>
</div>
</transition>
</template>
<script>
import moment from 'moment'
import { dateArray, setDate } from '../utils'
import NIcon from '../../../Icon'
import clickoutside from '../../../../directives/clickoutside'
import NButton from '../../../Button'
const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
const DATE_FORMAT = 'YYYY-MM-DD'
const DATE_VALIDATE_FORMAT = ['YYYY-MM-DD', 'YYYY-MM-D', 'YYYY-M-D', 'YYYY-M-DD']
const PLACEHOLDER = 'Select date and time'
const TIME_CONST = {
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
minutes: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'],
seconds: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59']
}
export default {
components: {
NButton,
NIcon
},
directives: {
clickoutside
},
props: {
active: {
type: Boolean,
default: true
},
value: {
type: Number,
required: false,
default: null
},
debug: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: PLACEHOLDER
},
format: {
type: String,
default: DATETIME_FORMAT
}
},
data () {
return {
displayDateString: '',
calendarDateTime: moment(),
currentDateTime: moment(),
calendar: [],
...TIME_CONST
}
},
computed: {
computedHour () {
if (this.valueAsMoment) return this.valueAsMoment.format('HH')
else return null
},
computedMinute () {
if (this.valueAsMoment) return this.valueAsMoment.format('mm')
else return null
},
computedSecond () {
if (this.valueAsMoment) return this.valueAsMoment.format('ss')
else return null
},
/**
* If value is valid return null.
* If value is not valid, return moment(value)
*/
valueAsMoment () {
if (this.value === null || this.value === undefined) return null
const newSelectedDateTime = moment(this.value)
if (newSelectedDateTime.isValid()) {
return newSelectedDateTime
} else {
return null
}
}
},
watch: {
valueAsMoment (newValue) {
if (newValue !== null) {
this.displayDateString = newValue.format(DATE_FORMAT)
} else {
this.displayDateString = ''
}
}
},
created () {
if (this.valueAsMoment !== null) {
this.displayDateString = this.valueAsMoment.format(DATE_FORMAT)
} else {
this.displayDateString = ''
}
},
methods: {
dateArray,
handleClickOutside () {
this.closeCalendar()
},
setValue (newSelectedDateTime) {
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
this.$emit('input', null)
} else if (newSelectedDateTime.isValid()) {
const adjustedDateTime = this.adjustValue(newSelectedDateTime)
if (this.valueAsMoment === null || adjustedDateTime.valueOf() !== this.valueAsMoment.valueOf()) {
this.refreshDisplayDateString(adjustedDateTime)
this.$emit('input', adjustedDateTime.valueOf())
}
}
},
adjustValue (value) {
return moment(value).startOf('day')
},
handleDateInput (value) {
const date = moment(value, DATE_FORMAT, true)
if (date.isValid()) {
if (!this.valueAsMoment) {
const newValue = moment()
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
} else {
const newValue = this.valueAsMoment
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
}
} else {
// do nothing
}
},
handleDateInputBlur () {
const date = moment(this.displayDateString, DATE_VALIDATE_FORMAT, true)
if (date.isValid()) {
if (!this.valueAsMoment) {
const newValue = moment()
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
} else {
const newValue = this.valueAsMoment
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
}
} else {
this.refreshDisplayDateString()
}
},
clearSelectedDateTime () {
this.$emit('input', null)
this.displayDateString = ''
},
setSelectedDateTimeToNow () {
this.$emit('input', this.adjustValue(moment()).valueOf())
this.calendarDateTime = moment()
},
handleDateClick (dateItem) {
let newSelectedDateTime = moment()
if (this.valueAsMoment !== null) {
newSelectedDateTime = moment(this.valueAsMoment)
}
newSelectedDateTime = setDate(newSelectedDateTime, dateItem)
this.$emit('input', this.adjustValue(newSelectedDateTime).valueOf())
},
/**
* If not selected, display nothing,
* else update datetime related string
*/
refreshDisplayDateString (time) {
if (this.valueAsMoment === null) {
this.displayDateString = ''
return
}
if (time === undefined) {
time = this.valueAsMoment
}
this.displayDateString = time.format(DATE_FORMAT)
},
handleConfirmClick () {
this.$emit('confirm')
this.closeCalendar()
},
closeCalendar () {
if (this.active) {
this.$emit('close')
}
},
nextYear () {
this.calendarDateTime.add(1, 'year')
this.$forceUpdate()
},
prevYear () {
this.calendarDateTime.subtract(1, 'year')
this.$forceUpdate()
},
nextMonth () {
this.calendarDateTime.add(1, 'month')
this.$forceUpdate()
},
prevMonth () {
this.calendarDateTime.subtract(1, 'month')
this.$forceUpdate()
},
handleTimePickerInput (value) {
this.$emit('input', value)
}
}
}
</script>

View File

@ -2,106 +2,26 @@
<transition name="n-date-picker-calendar--transition">
<div
v-if="active"
v-clickoutside="handleClickOutside"
v-clickoutside.lazy="handleClickOutside"
class="n-date-picker-calendar"
>
<div
class="n-date-picker-calendar__date-time-input-wrapper"
>
<input
<n-input
v-model="displayDateString"
class="n-date-picker-calendar__date-input"
placeholder="Select date"
@blur="handleDateInputBlur"
@input="handleDateInput"
>
<div
ref="timeSelector"
class="n-date-picker-calendar__time-input-wrapper"
>
<input
v-model="displayTimeString"
class="n-date-picker-calendar__time-input"
placeholder="Select time"
@click="openTimeSelector"
@input="handleTimeInput"
@blur="handleTimeInputBlur"
>
<transition name="n-date-picker-time-selector--transition">
<div
v-if="showTimeSelector"
class="n-date-picker-time-selector"
>
<div class="n-date-picker-time-selector__selection-wrapper">
<div class="n-date-picker-time-selector__hour">
<div
v-for="hour in hours"
:key="hour"
class="n-date-picker-time-selector__item"
:class="{
'n-date-picker-time-selector__item--active':
hour === computedHour
}"
@click="setHour(hour)"
>
{{ hour }}
</div>
</div>
<div class="n-date-picker-time-selector__minute">
<div
v-for="minute in minutes"
:key="minute"
class="n-date-picker-time-selector__item"
:class="{
'n-date-picker-time-selector__item--active':
minute === computedMinute
}"
@click="setMinute(minute)"
>
{{ minute }}
</div>
</div>
<div class="n-date-picker-time-selector__hour">
<div
v-for="second in seconds"
:key="second"
class="n-date-picker-time-selector__item"
:class="{
'n-date-picker-time-selector__item--active':
second === computedSecond
}"
@click="setSecond(second)"
>
{{ second }}
</div>
</div>
</div>
<div class="n-date-picker-time-selector__actions">
<n-button
size="tiny"
round
@click="handleTimeCancelClick"
>
Cancel
</n-button>
<n-button
size="tiny"
round
auto-text-color
type="primary"
@click="handleTimeConfirmClick"
>
Confirm
</n-button>
</div>
</div>
</transition>
</div>
/>
<n-time-picker
class="n-date-picker-calendar__time-input"
:value="value"
stop-selector-bubble
@input="handleTimePickerInput"
/>
</div>
<!-- <div
v-else
style="width: 100%; height: 12px;"
/>-->
<div class="n-date-picker-calendar__month-modifier">
<div
class="n-date-picker-calendar__fast-prev"
@ -163,7 +83,7 @@
<div class="n-date-picker-calendar__divider" />
<div class="n-date-picker-calendar__dates">
<div
v-for="dateItem in dateArray(calendarDateTime, computedSelectedDateTime, currentDateTime)"
v-for="dateItem in dateArray(calendarDateTime, valueAsMoment, currentDateTime)"
:key="dateItem.timestamp"
class="n-date-picker-calendar__date"
:class="{
@ -189,7 +109,7 @@
round
auto-text-color
type="primary"
@click="handleDateInputAndTimeInputConfirmClick"
@click="handleConfirmClick"
>
Confirm
</n-button>
@ -205,15 +125,15 @@ import NIcon from '../../../Icon'
import clickoutside from '../../../../directives/clickoutside'
import NButton from '../../../Button'
import NTimePicker from '../../../TimePicker'
import NInput from '../../../Input'
const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
const DATE_FORMAT = 'YYYY-MM-DD'
const DATE_VALIDATE_FORMAT = ['YYYY-MM-DD', 'YYYY-MM-D', 'YYYY-M-D', 'YYYY-M-DD']
const PLACEHOLDER = 'Select date and time'
const DATE_FORMAT = {
date: 'YYYY-MM-DD',
datetime: 'YYYY-MM-DD HH:mm:ss'
}
const PLACEHOLDER = {
date: 'Select date',
datetime: 'Select date and time'
}
const TIME_CONST = {
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
@ -224,74 +144,65 @@ const TIME_CONST = {
export default {
components: {
NButton,
NIcon
NIcon,
NTimePicker,
NInput
},
directives: {
clickoutside
},
model: {
prop: 'value',
event: 'change'
},
props: {
active: {
type: Boolean,
default: true
},
value: {
type: [Number, String],
type: Number,
required: false,
default: null
},
debug: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: PLACEHOLDER
},
format: {
type: String,
default: DATETIME_FORMAT
}
},
data () {
return {
displayDateTimeString: '',
displayDateString: '',
displayTimeString: '',
rightDisplayDateTimeString: '',
rightDisplayDateString: '',
rightDisplayTimeString: '',
calendarDateTime: moment(),
rightCalendarDateTime: moment(),
currentDateTime: moment(),
showTimeSelector: false,
showRightTimeSelector: false,
calendar: [],
rightCalendar: [],
...TIME_CONST
}
},
computed: {
placeholder () {
return PLACEHOLDER[this.type]
},
format () {
return DATE_FORMAT[this.type]
},
computedHour () {
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('HH')
if (this.valueAsMoment) return this.valueAsMoment.format('HH')
else return null
},
computedMinute () {
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('mm')
if (this.valueAsMoment) return this.valueAsMoment.format('mm')
else return null
},
computedSecond () {
if (this.computedSelectedDateTime) return this.computedSelectedDateTime.format('ss')
if (this.valueAsMoment) return this.valueAsMoment.format('ss')
else return null
},
/**
* If value is valid return null.
* If value is not valid, return moment(value)
*/
computedSelectedDateTime () {
valueAsMoment () {
if (this.value === null || this.value === undefined) return null
const newSelectedDateTime = moment(Number(this.value))
const newSelectedDateTime = moment(this.value)
if (newSelectedDateTime.isValid()) {
return newSelectedDateTime
} else {
@ -299,171 +210,113 @@ export default {
}
}
},
watch: {
valueAsMoment (newValue) {
if (newValue !== null) {
this.displayDateString = newValue.format(DATE_FORMAT)
} else {
this.displayDateString = ''
}
}
},
created () {
if (this.valueAsMoment !== null) {
this.displayDateString = this.valueAsMoment.format(DATE_FORMAT)
} else {
this.displayDateString = ''
}
},
methods: {
dateArray,
handleClickOutside () {
this.closeCalendar()
console.log('clickoutside')
},
dateArray,
/**
* If new datetime is null or undefined, emit null to value.
* Else adjust new datetime by props.type and emit it to value.
*/
setValue (newSelectedDateTime) {
if (newSelectedDateTime === null || newSelectedDateTime === undefined) {
this.$emit('change', null)
return
}
if (newSelectedDateTime.isValid()) {
const adjustedDateTime = this.adjustDateTime(newSelectedDateTime)
if (this.computedSelectedDateTime === null || adjustedDateTime.valueOf() !== this.computedSelectedDateTime.valueOf()) {
this.$emit('change', adjustedDateTime.valueOf(), adjustedDateTime.format(this.format))
this.$emit('input', null)
} else if (newSelectedDateTime.isValid()) {
const adjustedDateTime = this.adjustValue(newSelectedDateTime)
if (this.valueAsMoment === null || adjustedDateTime.valueOf() !== this.valueAsMoment.valueOf()) {
this.refreshDisplayDateString(adjustedDateTime)
this.$emit('input', adjustedDateTime.valueOf())
}
}
},
adjustDateTime (datetime) {
adjustValue (datetime) {
return moment(datetime).startOf('second')
},
justifySelectedDateTimeAfterChangeTimeString () {
if (this.computedSelectedDateTime === null) {
// case here is impossible for now, because you can't clear time for now
handleDateInput (value) {
const date = moment(value, DATE_FORMAT, true)
if (date.isValid()) {
if (!this.valueAsMoment) {
const newValue = moment()
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
} else {
const newValue = this.valueAsMoment
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
}
} else {
const newDisplayDateTimeString = this.displayDateString + ' ' + this.displayTimeString
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
// do nothing
}
},
handleTimeInput (e) {
const newDisplayDateTimeString = this.displayDateString + ' ' + e.target.value
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
handleDateInput (e) {
const newDisplayDateTimeString = e.target.value + ' ' + this.displayTimeString
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
handleTimeInputBlur () {
this.refreshSelectedDateTimeString()
},
handleDateInputBlur () {
this.refreshSelectedDateTimeString()
},
handleTimeConfirmClick () {
this.justifySelectedDateTimeAfterChangeTimeString()
this.refreshSelectedDateTimeString()
this.closeTimeSelector()
},
handleTimeCancelClick () {
this.closeTimeSelector()
},
setHour (hour) {
try {
const timeArray = this.displayTimeString.split(':')
timeArray[0] = hour
this.displayTimeString = timeArray.join(':')
} catch (err) {
} finally {
this.justifySelectedDateTimeAfterChangeTimeString()
}
},
setMinute (minute) {
try {
const timeArray = this.displayTimeString.split(':')
timeArray[1] = minute
this.displayTimeString = timeArray.join(':')
} catch (err) {
} finally {
this.justifySelectedDateTimeAfterChangeTimeString()
}
},
setSecond (second) {
try {
const timeArray = this.displayTimeString.split(':')
timeArray[2] = second
this.displayTimeString = timeArray.join(':')
} catch (err) {
} finally {
this.justifySelectedDateTimeAfterChangeTimeString()
}
},
openTimeSelector () {
if (this.computedSelectedDateTime === null) {
this.setValue(moment())
}
this.showTimeSelector = true
},
closeTimeSelector () {
this.showTimeSelector = false
},
nativeCloseCalendar (e) {
if (!this.$refs.activator.contains(e.target) && !this.$refs.content.contains(e.target)) {
this.closeCalendar()
const date = moment(this.displayDateString, DATE_VALIDATE_FORMAT, true)
if (date.isValid()) {
if (!this.valueAsMoment) {
const newValue = moment()
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
} else {
const newValue = this.valueAsMoment
newValue.year(date.year())
newValue.month(date.month())
newValue.date(date.date())
this.$emit('input', this.adjustValue(newValue).valueOf())
}
} else {
this.refreshDisplayDateString()
}
},
clearSelectedDateTime () {
this.setValue(null)
this.displayDateTimeString = ''
this.$emit('input', null)
this.displayDateString = ''
this.displayTimeString = ''
},
setSelectedDateTimeToNow () {
this.setValue(moment())
this.$emit('input', this.adjustValue(moment()).valueOf())
this.calendarDateTime = moment()
},
handleDateClick (dateItem) {
let newSelectedDateTime = moment()
if (this.computedSelectedDateTime !== null) {
newSelectedDateTime = moment(this.computedSelectedDateTime)
if (this.valueAsMoment !== null) {
newSelectedDateTime = moment(this.valueAsMoment)
}
newSelectedDateTime = setDate(newSelectedDateTime, dateItem)
this.setValue(newSelectedDateTime)
this.$emit('input', this.adjustValue(newSelectedDateTime).valueOf())
},
/**
* If not selected, display nothing,
* else update datetime related string
*/
refreshSelectedDateTimeString () {
if (this.computedSelectedDateTime === null) {
this.displayDateTimeString = ''
refreshDisplayDateString (time) {
if (this.valueAsMoment === null) {
this.displayDateString = ''
return
}
this.displayDateTimeString = this.computedSelectedDateTime.format(this.format)
this.displayDateString = this.computedSelectedDateTime.format('YYYY-MM-DD')
this.displayTimeString = this.computedSelectedDateTime.format('HH:mm:ss')
},
/**
* If new time is invalid, do nothing.
* If valid, update.
*/
handleDateTimeInputEnter () {
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
},
/**
* If new SelectedDateTime is valid, update `selectedDateTime` and `calendarTime`
* Whatever happened, refresh selectedDateTime
*/
handleDateTimeInputBlur () {
const newSelectedDateTime = moment(this.displayDateTimeString, this.format, true)
this.setValue(newSelectedDateTime)
/**
* If newSelectedDateTime is invalid, display string need to be restored
*/
this.refreshSelectedDateTimeString()
},
handleDateInputAndTimeInputConfirmClick () {
const newDisplayDateTimeString = `${this.displayDateString.trim()} ${this.displayTimeString.trim()}`
const newSelectedDateTime = moment(newDisplayDateTimeString, this.format, true)
if (this.computedSelectedDateTime === null) {
this.setValue(moment())
} else {
this.setValue(newSelectedDateTime)
this.refreshSelectedDateTimeString()
if (time === undefined) {
time = this.valueAsMoment
}
this.displayDateString = time.format(DATE_FORMAT)
},
handleConfirmClick () {
this.$emit('confirm')
this.closeCalendar()
},
closeCalendar () {
@ -486,6 +339,9 @@ export default {
prevMonth () {
this.calendarDateTime.subtract(1, 'month')
this.$forceUpdate()
},
handleTimePickerInput (value) {
this.$emit('input', value)
}
}
}

View File

@ -0,0 +1,10 @@
/* istanbul ignore file */
import Form from './src/main.vue'
import FormItem from './src/form-item.vue'
Form.install = function (Vue) {
Vue.component(Form.name, Form)
Vue.component(FormItem.name, FormItem)
}
export default Form

View File

@ -0,0 +1,194 @@
<template>
<div
:class="getFormClass()"
>
<label
v-if="label !== undefined"
:class="`${prefix}__label`"
:style="style"
>
{{ label }}
</label>
<div
class="n-form-item__content"
:class="validateFlag ? `${prefix}__content--error` : `${prefix}__content--pass`"
>
<slot />
<div :class="`${prefix}__validate`">
{{ validateInfo }}
</div>
</div>
</div>
</template>
<script>
import AsyncValidator from 'async-validator'
import { getObjValue } from '../../../utils/index'
export default {
name: 'NFormItem',
props: {
label: {
type: String,
default: undefined
},
labelWidth: {
type: Number,
default: undefined
},
labelStyle: {
type: String,
default: undefined
},
labelPosition: {
type: String,
default: undefined
},
prop: {
type: String,
default: undefined
},
required: {
type: Boolean,
default: undefined
},
requiredLogo: {
type: Boolean,
default: undefined
}
},
inject: ['form'],
provide () {
return {
formItem: this
}
},
data () {
return {
prefix: 'n-form-item',
validateInfo: '',
validateFlag: false
}
},
computed: {
style () {
let s = {
width: null
}
let lW = this.getValue('labelWidth')
s.width = lW ? lW + 'px' : 'auto'
return s
},
requiredRule () {
let rule = []
if (this.required) {
rule.push({
trigger: 'blur',
required: true
})
}
return rule
}
},
watch: {
// disabled (n) {
// this.$children.forEach(i => {
// if (getObjValue(i, '$options.name.0'.split('.')) === 'N') {
// // dirctly modify the prop value is forbbiden by vue,
// // neither definePropery or proxy pass
// // we need to update every components we want to influence
// // i.disabled = n
// }
// })
// }
},
created () {
this.validateEventListener()
},
beforeDestroy () {
this.$off('on-form-blur', this.onFormBlur)
this.$off('on-form-change', this.onFormChange)
},
methods: {
getValue (key) {
return this[key] === undefined ? this.form[key] : this[key]
},
getFormClass () {
let cls = ['n-form-item']
let pre = 'n-form-item__label--'
cls.push(pre + this.getValue('labelPosition'))
if (!this.getValue('requiredLogo')) {
return cls
}
if (this.required) {
cls.push(pre + 'require')
} else if (this.form && this.form.rules && this.prop) {
let flag = (this.form.rules[this.prop] || []).some(i => {
return i.required
})
if (flag) {
cls.push(pre + 'require')
}
}
return cls
},
getRules () {
let rules = []
if (this.required) {
rules.push(this.requiredRule)
}
if (this.form && this.form.rules && this.prop) {
(this.form.rules[this.prop] || []).forEach(i => {
rules.push(i)
})
}
return rules
},
onFormBlur () {
this.validate('blur')
},
onFormChange () {
this.validate('change')
},
/**
* form item validation, can specify rules
*/
validate (trigger = '', cb = () => {}) {
if (!this.prop || typeof this.prop !== 'string') {
return
}
let rules = this.getRules()
//
let prop = this.prop
let value = getObjValue(this.form.model, this.prop.split('.'))
if (this.prop.indexOf('.') > -1) {
prop = prop.slice(prop.lastIndexOf('.') + 1)
}
let activeRule = trigger === '' ? rules : rules.filter(i => {
return i.trigger === trigger
})
if (activeRule.length === 0) return
const asyncValidator = new AsyncValidator({ [prop]: activeRule })
asyncValidator.validate({ [prop]: value }, (errors, fields) => {
if (errors) {
this.validateInfo = errors[0].message
this.validateFlag = true
} else {
this.clearValidateClass()
}
cb(errors[0].message || false, fields)
})
},
clearValidateClass () {
this.validateInfo = ''
this.validateFlag = false
},
validateEventListener () {
const rules = this.getRules()
if (rules.length >= 0) {
this.$on('on-form-blur', this.onFormBlur)
this.$on('on-form-change', this.onFormChange)
}
}
}
}
</script>

View File

@ -0,0 +1,124 @@
<template>
<form
class="n-form"
:class="{
'n-form--inline': inline
}"
>
<slot />
</form>
</template>
<script>
import { deepClone, getObjValue } from '../../../utils/index'
export default {
name: 'NForm',
provide () {
return {
form: this
}
},
props: {
inline: {
type: Boolean,
default: false
},
labelWidth: {
type: Number,
default: 80
},
labelPosition: {
type: String,
default: 'right' // ['top', 'right', 'left', 'center']
},
model: {
type: Object,
default: function () {
return {}
}
},
rules: {
type: Object,
default: function () {
return {}
}
},
requiredLogo: {
type: Boolean,
default: true
}
},
data () {
return {
initialValue: ''
}
},
created () {
this.initialValue = deepClone(this.model)
},
methods: {
getLabelPosClass (labelPosition) {
return 'n-form--lable-' + labelPosition
},
/**
* form validation, validate all prop-elements by default,
* can use specify the scope of validation by param part.
*
* @param {Funtion} cb callback
* @param {Array} scope to specify the scope of validation
* @return {Boolean} validation passed or not
*/
validate (cb, scope = []) {
let promise
let isCallback = typeof cb === 'function'
if (!isCallback && window.Promise) {
promise = new Promise((resolve, reject) => {
cb = valid => valid ? resolve(valid) : reject(valid)
})
}
let valid = true
let fields = {}
this.$children.forEach((child, i) => {
let flag = scope.length > 0 ? scope.indexOf(child.prop) > -1 : true
if (child.prop && flag) {
child.validate('', (errors, field) => {
if (errors) {
valid = false
}
fields = Object.assign({}, fields, field)
})
}
if (++i === this.$children.length && isCallback) {
cb(valid, fields)
}
})
if (promise) {
return promise
}
},
/**
* just can reset the value with prop in form-item
*/
resetForm () {
this.$children.forEach(child => {
if (child.prop) {
let keys = child.prop.split('.')
let obj = this.model
let j = 0
keys.forEach((k, i) => {
if (i !== keys.length - 1) {
obj = obj[k]
}
j = i
})
obj[keys[j]] = getObjValue(this.initialValue, keys)
if (child.validateFlag) {
child.clearValidateClass()
}
}
})
}
}
}
</script>

View File

@ -19,6 +19,7 @@
@blur="handleBlur"
@focus="handleFocus"
@input="handleInput"
@click="handleClick"
@change="handleChange"
@keyup="handleKeyUp"
@compositionstart="handleCompositionStart"
@ -48,6 +49,7 @@
@blur="handleBlur"
@focus="handleFocus"
@input="handleInput"
@click="handleClick"
@change="handleChange"
@keyup="handleKeyUp"
@compositionstart="handleCompositionStart"
@ -64,12 +66,22 @@
<script>
import nIcon from '../../Icon'
import Emitter from '../../../mixins/emitter'
export default {
name: 'NInput',
components: {
nIcon
},
mixins: [ Emitter ],
inject: {
form: {
default: null
},
formItem: {
default: null
}
},
model: {
prop: 'value',
event: 'input'
@ -117,6 +129,11 @@ export default {
isComposing: false
}
},
computed: {
validateState () {
return this.formItem ? this.formItem.validateState : ''
}
},
methods: {
handleCompositionStart () {
this.isComposing = true
@ -131,6 +148,10 @@ export default {
},
handleBlur (e) {
this.$emit('blur', e)
// ,
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-blur', e.target.value)
}
},
handleFocus (e) {
this.$emit('focus', e)
@ -140,7 +161,13 @@ export default {
this.$emit('keyup', e)
},
handleChange (e) {
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-change', e.target.value)
}
this.$emit('change', e.target.value)
},
handleClick (e) {
this.$emit('click', e)
}
}
}

View File

@ -23,6 +23,11 @@ export default {
</script>
<style lang="scss">
.n-modal-content {
&::-webkit-scrollbar {
width: 0px;
}
}
.n-modal-content--transition-enter-active {
opacity: 1;

View File

@ -20,12 +20,20 @@
</template>
<script>
import Emitter from '../../../mixins/emitter'
export default {
name: 'NRadio',
mixins: [ Emitter ],
model: {
prop: 'privateValue',
event: 'input'
},
inject: {
formItem: {
default: null
}
},
props: {
value: {
type: [Boolean, String, Number],
@ -50,6 +58,9 @@ export default {
if (this.disabled) return
if (this.privateValue !== this.value) {
this.$emit('input', this.value)
if (this.formItem) {
this.dispatch('NFormItem', 'on-form-change', this.value)
}
}
}
}

View File

@ -85,17 +85,23 @@
</template>
<script>
import Emitter from '../../../mixins/emitter'
import detachable from '../../../mixins/detachable'
import placeable from '../../../mixins/placeable'
import toggleable from '../../../mixins/toggleable'
export default {
name: 'NSingleSelect',
mixins: [detachable, toggleable, placeable],
mixins: [detachable, toggleable, placeable, Emitter],
model: {
prop: 'selectedValue',
event: 'input'
},
inject: {
formItem: {
default: null
}
},
props: {
items: {
type: Array,
@ -159,9 +165,12 @@ export default {
}
},
watch: {
selectedItem () {
selectedItem (n, o) {
if (this.selectedItem !== null) {
this.label = this.selectedItem.label
if (n !== o && this.formItem) {
this.dispatch('NFormItem', 'on-form-change', n.value)
}
} else {
this.label = ''
}

View File

@ -0,0 +1,8 @@
/* istanbul ignore file */
import Scaffold from './src/main.vue'
Scaffold.install = function (Vue) {
Vue.component(Scaffold.name, Scaffold)
}
export default Scaffold

View File

@ -0,0 +1,307 @@
<template>
<div class="n-time-picker">
<div ref="activator">
<n-input
v-model="displayTimeString"
class="n-date-picker-calendar__time-input"
placeholder="Select time"
@click="handleActivatorClick"
@input="handleTimeInput"
@blur="handleTimeInputBlur"
/>
</div>
<div
ref="contentWrapper"
class="n-content-wrapper"
@click="handleContentClick"
>
<div
ref="content"
>
<transition name="n-time-picker--transition">
<div
v-if="active"
v-clickoutside.lazy="closeTimeSelector"
class="n-time-picker-selector"
>
<div class="n-time-picker__selection-wrapper">
<div
ref="hours"
class="n-time-picker__hour"
>
<div
v-for="hour in hours"
:key="hour"
class="n-time-picker__item"
:class="{
'n-time-picker__item--active':
hour === computedHour
}"
@click="setHour(hour)"
>
{{ hour }}
</div>
</div>
<div
ref="minutes"
class="n-time-picker__minute"
>
<div
v-for="minute in minutes"
:key="minute"
class="n-time-picker__item"
:class="{
'n-time-picker__item--active':
minute === computedMinute
}"
@click="setMinute(minute)"
>
{{ minute }}
</div>
</div>
<div
ref="seconds"
class="n-time-picker__hour"
>
<div
v-for="second in seconds"
:key="second"
class="n-time-picker__item"
:class="{
'n-time-picker__item--active':
second === computedSecond,
'n-time-picker__item--disabled':
validator &&
!validator(computedHour, computedMinute, second)
}"
@click="setSecond(second)"
>
{{ second }}
</div>
</div>
</div>
<div class="n-time-picker__actions">
<n-button
size="tiny"
round
@click="handleCancelClick"
>
Cancel
</n-button>
<n-button
size="tiny"
round
auto-text-color
type="primary"
@click="handleConfirmClick"
>
Confirm
</n-button>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
<script>
import NInput from '../../Input'
import moment from 'moment'
import detachable from '../../../mixins/detachable'
import placeable from '../../../mixins/placeable'
import clickoutside from '../../../directives/clickoutside'
const DEFAULT_FORMAT = 'HH:mm:ss'
const TIME_CONST = {
weekdays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
hours: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'],
minutes: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59'],
seconds: ['00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59']
}
// function ranges () {
// }
// function validateRange (ranges) {
// const rangeReg = /((\d\d):(\d\d):(\d\d))\s*-\s*((\d\d):(\d\d):(\d\d))/
// if (typeof ranges === 'string') {
// const result = ranges.match(rangeReg)
// if (!result) return false
// else {
// const [time1, hour1, min1, sec1, time2, hour2, min2, sec2] = result.slice(1)
// if ()
// }
// } else if (Array.isArray(ranges)) {
// } else {
// return false
// }
// }
/**
* Use range to disabled time since validator will need time picker to loop all available options.
* Warning: this component shouldn't change v-model's timestamps' date
*/
export default {
name: 'NTimePicker',
components: {
NInput
},
directives: {
clickoutside
},
mixins: [detachable, placeable],
props: {
stopSelectorBubble: {
type: Boolean,
default: false
},
placement: {
type: String,
default: 'bottom-start'
},
value: {
type: Number,
default: null
},
format: {
type: String,
default: DEFAULT_FORMAT
},
validator: {
type: Function,
default: null
}
},
data () {
return {
active: false,
displayTimeString: this.value === null ? null : moment(this.value).format(this.format),
...TIME_CONST,
memorizedValue: this.value
}
},
computed: {
computedTime () {
if (this.value === null) return null
else return moment(this.value)
},
computedHour () {
if (this.computedTime) return this.computedTime.format('HH')
else return null
},
computedMinute () {
if (this.computedTime) return this.computedTime.format('mm')
else return null
},
computedSecond () {
if (this.computedTime) return this.computedTime.format('ss')
else return null
}
},
watch: {
computedTime (time) {
this.refreshTimeString(time)
this.$nextTick().then(this.scrollTimer)
}
},
methods: {
justifyValueAfterChangeDisplayTimeString () {
const time = moment(this.displayTimeString, this.format, true)
if (time.isValid()) {
if (this.computedTime !== null) {
const newTime = this.computedTime
newTime.hour(time.hour())
newTime.minute(time.minute())
newTime.second(time.second())
this.$emit('input', newTime.valueOf())
} else {
this.$emit('input', time.valueOf())
}
}
},
setHour (hour) {
if (this.value === null) {
this.$emit('input', moment().hour(hour).startOf('hour').valueOf())
} else {
this.$emit('input', moment(this.value).hour(hour).valueOf())
}
},
setMinute (minute) {
if (this.value === null) {
this.$emit('input', moment().minute(minute).startOf('minute').valueOf())
} else {
this.$emit('input', moment(this.value).minute(minute).valueOf())
}
},
setSecond (second) {
if (this.value === null) {
this.$emit('input', moment().second(second).startOf('second').valueOf())
} else {
this.$emit('input', moment(this.value).second(second).valueOf())
}
},
refreshTimeString (time) {
if (time === undefined) time = this.computedTime
if (time === null) this.displayTimeString = ''
else this.displayTimeString = time.format(this.format)
},
handleTimeInputBlur () {
this.refreshTimeString()
},
scrollTimer () {
if (this.$refs.hours) {
const hour = this.$refs.hours.querySelector('.n-time-picker__item--active')
if (hour) {
this.$refs.hours.scrollTo(0, hour.offsetTop)
}
}
if (this.$refs.minutes) {
const minute = this.$refs.minutes.querySelector('.n-time-picker__item--active')
if (minute) {
this.$refs.minutes.scrollTo(0, minute.offsetTop)
}
}
if (this.$refs.seconds) {
const second = this.$refs.seconds.querySelector('.n-time-picker__item--active')
if (second) {
this.$refs.seconds.scrollTo(0, second.offsetTop)
}
}
},
openTimeSelector () {
this.memorizedValue = this.value
this.active = true
this.$nextTick().then(this.scrollTimer)
},
handleActivatorClick (e) {
if (this.active) {
e.stopPropagation()
} else {
this.openTimeSelector()
}
},
closeTimeSelector () {
this.active = false
},
handleTimeInput () {
this.justifyValueAfterChangeDisplayTimeString()
},
handleCancelClick () {
this.$emit('input', this.memorizedValue)
this.active = false
},
handleConfirmClick () {
this.refreshTimeString()
this.active = false
},
handleContentClick (e) {
if (this.stopSelectorBubble) {
e.stopPropagation()
}
}
}
}
</script>

View File

@ -2,25 +2,41 @@ import clickoutsideDelegate from '../utils/clickoutsideDelegate'
const ctx = '@@clickoutsideContext'
function lazyHandler (handler) {
let called = false
return function () {
if (called) {
console.debug('[clickoutside] called')
handler()
} else {
console.debug('[clickoutside] lazy called')
called = true
}
}
}
const clickoutside = {
bind (el, bindings) {
console.debug('[clickoutside]: bind', el)
},
inserted (el, bindings) {
console.log('[clickoutside]: inserted')
console.debug('[clickoutside]: inserted')
if (typeof bindings.value === 'function') {
el[ctx] = {
handler: bindings.value
handler: bindings.modifiers.lazy ? lazyHandler(bindings.value) : bindings.value
}
clickoutsideDelegate.registerHandler(el, el[ctx].handler, false)
}
},
update (el, bindings) {
if (typeof bindings.value === 'function') {
clickoutsideDelegate.unregisterHandler(el, el[ctx].handler)
clickoutsideDelegate.unregisterHandler(el[ctx].handler)
el[ctx].handler = bindings.value
clickoutsideDelegate.registerHandler(el, el[ctx].handler, false)
}
},
unbind (el) {
console.log('[clickoutside]: unbind')
console.debug('[clickoutside]: unbind')
clickoutsideDelegate.unregisterHandler(el[ctx].handler)
}
}

View File

@ -0,0 +1,33 @@
function broadcast (componentName, eventName, params) {
this.$children.forEach(child => {
let name = child.$options.componentName
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params))
} else {
broadcast.apply(child, [componentName, eventName].concat([params]))
}
})
}
export default {
methods: {
dispatch (componentName, eventName, params) {
let parent = this.$parent || this.$root
let name = parent.$options.name
while (parent && (!name || name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.name
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params))
}
},
broadcast (componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params)
}
}
}

View File

@ -52,6 +52,7 @@ export default {
}
},
mounted () {
this.$refs.content.style = 'position: absolute;'
this.$nextTick().then(() => {
this.registerScrollListeners()
this.registerResizeListener()
@ -86,15 +87,14 @@ export default {
resizeDelegate.registerHandler(this.updatePosition)
},
registerScrollListeners () {
let currentElement = this.$el
let currentElement = getParentNode(this.$el)
while (true) {
currentElement = getScrollParent(currentElement)
if (currentElement === null) break
this.scrollListeners.push([currentElement, this.updatePosition])
currentElement = getParentNode(currentElement)
if (currentElement === document.body || currentElement.nodeName === 'HTML') {
break
}
}
// console.log(this.scrollListeners)
for (const [el, handler] of this.scrollListeners) {
scrollDelegate.registerHandler(el, handler)
}

View File

@ -104,10 +104,6 @@ export default {
paddingBody: {
type: Boolean,
default: true
},
value: {
type: String,
default: ''
}
},
data () {
@ -121,31 +117,31 @@ export default {
}
},
watch: {
value () {
this.activeItemName = this.value
$route (to, from) {
this.syncActiveItemWithPath(to.path)
}
},
mounted () {
this.activeItemName = this.value
const path = this.$route.path
for (const item of this.items) {
if (item.path === path) {
this.activeItemName = item.name
this.$emit('input', item.name)
return
}
if (item.childItems) {
for (const childItem of item.childItems) {
if (childItem.path === path) {
this.activeItemName = childItem.name
this.$emit('input', item.name)
return
this.syncActiveItemWithPath(path)
},
methods: {
syncActiveItemWithPath (path) {
for (const item of this.items) {
if (item.path === path) {
this.activeItemName = item.name
return
}
if (item.childItems) {
for (const childItem of item.childItems) {
if (childItem.path === path) {
this.activeItemName = childItem.name
return
}
}
}
}
}
},
methods: {
},
toggle () {
this.isCollapsed = !this.isCollapsed
},

View File

@ -45,7 +45,7 @@ class ClickOutsideDelegate {
}
if (!this.handlerCount) {
console.debug('[ClickOutsideDelegate]: remove handler from window')
window.removeEventListener('click', this.handleClickOutside, true)
window.removeEventListener('click', this.handleClickOutside)
this.handlers = new Map()
}
}
@ -57,11 +57,11 @@ class ClickOutsideDelegate {
if (!el) throw new Error('[ClickOutsideDelegate.registerHandler]: make sure `el` is an HTMLElement')
}
if (this.handlers.get(handler)) {
throw new Error('[ClickOutsideDelegate.registerHandler]: don\'t register duplicate event handler')
throw new Error('[ClickOutsideDelegate.registerHandler]: don\'t register duplicate event handler, if you want to do it, unregister this handler and reregister it.')
}
if (!this.handlerCount) {
console.debug('[ClickOutsideDelegate]: add handler to window')
window.addEventListener('click', this.handleClickOutside, true)
window.addEventListener('click', this.handleClickOutside)
}
++this.handlerCount
this.handlers.set(handler, { els, once })

View File

@ -1,11 +1,11 @@
/**
* Returns the parentNode or the host of the element
* Returns the parentNode or the host of the element until document
* @method
* @param {Element} element
* @returns {Element} parent
*/
export default function getParentNode (element) {
if (element.nodeName === 'HTML') {
if (element.nodeName === '#document') {
return element
}
return element.parentNode || element.host

View File

@ -5,20 +5,20 @@ import getParentNode from './getParentNode'
* Returns the scrolling parent of the given element
* @method
* @param {Element} element
* @returns {Element} scroll parent
* @returns {Element|null} scroll parent
*/
export default function getScrollParent (element) {
// Return body, `getScroll` will take care to get the correct `scrollTop` from it
if (!element) {
return document.body
return null
}
switch (element.nodeName) {
case 'HTML':
case 'BODY':
return element.ownerDocument.body
return document
case '#document':
return element.body
return null
}
// Firefox want us to check `-x` and `-y` variations as well

View File

@ -1,4 +1,34 @@
import getScrollParent from './getScrollParent'
export {
getScrollParent
import getScrollParent from './dom/getScrollParent'
const isObject = (o) => {
let type = Object.prototype.toString.call(o)
return type === '[object Array]' || type === '[object Object]'
}
const _isObject = (o) => {
return (typeof o === 'object' || typeof o === 'function') && o !== null
}
const deepClone = (obj) => {
if (!isObject(obj)) {
return obj
}
let isArray = Array.isArray(obj)
let cloneObj = isArray ? [] : {}
for (let key in obj) {
cloneObj[key] = _isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
}
return cloneObj
}
const getObjValue = (obj, keys) => {
return keys.reduce((res, n) => (res !== undefined && res[n] !== undefined ? res[n] : null), obj)
}
export {
getScrollParent,
deepClone,
getObjValue
}

View File

@ -38,7 +38,7 @@
color: rgba(221, 238, 247, 0.3);
height: 20px;
}
.n-date-picker__input, .n-date-picker-calendar__date-input, .n-date-picker-calendar__time-input {
.n-date-picker__input, .n-date-picker-calendar__date-input {
-webkit-appearance: none;
box-sizing: border-box;
border: none;

88
styles/Form.scss Normal file
View File

@ -0,0 +1,88 @@
@import './mixins/mixins.scss';
@import './theme/default.scss';
@include b(form) {
width: 100%;
font-size: 14px;
color: rgba(255, 255, 255, 1);
&.n-form--inline {
display: flex;
align-items: center;
align-content: space-around;
flex-wrap: nowrap;
.n-form-item:not(:last-child) {
margin-right: 10px;
}
}
.n-form-item__label--top {
display: block;
.n-form-item__label {
display: block;
overflow-wrap: unset;
align-items: center;
text-align: left;
}
}
.n-form-item__label--require .n-form-item__label::before {
float: right;
content: '*';
color:rgba(255, 146, 164, 1);
margin-left: 5px;
}
.n-form-item__label--left .n-form-item__label {
text-align: left;
}
.n-form-item__label--right .n-form-item__label {
text-align: right;
}
.n-form-item__label--center .n-form-item__label {
text-align: center;
}
.n-form--disable {
box-shadow: inset 0 0 0px 1px rgb(99, 96, 96);
background-color: rgba(255, 255, 255, 0.1);
cursor: not-allowed;
&:hover {
box-shadow: inset 0 0 0px 1px rgb(99, 96, 96);
background-color: rgba(255, 255, 255, 0.1);
}
}
}
@include b(form-item) {
display: flex;
width: 100%;
align-items: center;
margin-bottom: 24px;
.n-form-item__label {
flex-grow: 0;
padding: 10px 10px 10px 0;
overflow-wrap: break-word;
}
.n-form-item__content {
position: relative;
flex-grow: 1;
}
.n-form-item__validate {
position: absolute;
bottom: -18px;
margin-top: 6px;
color: rgba(255, 146, 164, 1);
font-size: 14px;
}
.n-form-item__content--pass {
.n-form-item__validate {
visibility: hidden;
}
}
.n-form-item__content--error {
.n-form-item__validate {
visibility: visible;
}
// 后期可以通过属性优化, 精确定位
input, select, textarea {
box-shadow: inset 0 0 0px 1px rgba(255, 146, 164, 1);
background-color: rgba(255, 255, 255, 0.1);
}
}
}

View File

@ -123,8 +123,13 @@
transition: opacity 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
opacity: 1;
}
&.n-nimbus-service-layout-drawer__item--active span{
opacity: 1;
&.n-nimbus-service-layout-drawer__item--active {
&:not(.n-nimbus-service-layout-drawer__item--is-group-item) {
color: #63E2B7;
}
span {
opacity: 1;
}
}
&:hover span {
opacity: 1;

View File

@ -14,6 +14,8 @@ $popover-distance: $popover-arrow-width - 1 + 14;
}
.n-popover__content-container {
left: 0;
top: 0;
height: 0;
width: 0;
position: fixed;

51
styles/TimePicker.scss Normal file
View File

@ -0,0 +1,51 @@
@import "./mixins/mixins.scss";
@import "./theme/default.scss";
@include b(time-picker-selector) {
background-color: rgba(97, 104, 132, 1);
box-shadow:0px 2px 20px 0px rgba(0,0,0,0.16);
font-size: 12px;
border-radius: 6px;
margin-top: 4px;
width: 180px;
overflow: hidden;
@include fade-in-transition(time-picker, top left);
.n-time-picker__selection-wrapper {
position: relative;
height: 244px;
border-bottom: 1px solid rgba(255, 255, 255, .07);
display: flex;
}
.n-time-picker__hour, .n-time-picker__minute, .n-time-picker__second {
@include scrollbar;
width: 60px;
height: 244px;
flex-direction: column;
overflow-y: scroll;
.n-time-picker__item {
cursor: pointer;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color .2s $default-cubic-bezier, color .2s $default-cubic-bezier;
background: transparent;
&:hover {
background-color: rgba(99,226,183,0.12);
}
&.n-time-picker__item--active {
background-color: rgba(99,226,183,0.12);
color: rgba(99, 226, 183, 1);
}
}
}
.n-time-picker__actions {
// margin will boom! I don't know why
padding: 10px 15px;
display: flex;
justify-content: space-between;
.n-button {
margin: 0;
}
}
}

View File

@ -7,4 +7,5 @@
@include b(tooltip__content) {
padding: 8px 14px;
white-space: nowrap;
}

View File

@ -21,6 +21,8 @@
@import './DatePicker.scss';
@import './InputNumber.scss';
@import './Radio.scss';
@import './Form.scss';
@import './TimePicker.scss';
@import "./NimbusServiceLayout.scss";
@import "./Popover.scss";

View File

@ -182,4 +182,24 @@ $default-font-family: 'Lato';
&.#{$namespace}-#{$block}--transition-leave-to {
transition: opacity .2s $slow-out-cubic-bezier, transform .2s $slow-out-cubic-bezier;
}
}
@mixin fade-in-transition($block, $origin: left top) {
&.#{$namespace}-#{$block}--transition-enter-active,
&.#{$namespace}-#{$block}--transition-leave-active {
transform: scale(1);
opacity: 1;
}
&.#{$namespace}-#{$block}--transition-enter-active {
transform-origin: $origin;
transition: opacity .2s $fast-in-cubic-bezier, transform .2s $fast-in-cubic-bezier;
}
&.#{$namespace}-#{$block}--transition-enter, &.#{$namespace}-#{$block}--transition-leave-to {
opacity: 0;
transform-origin: $origin;
transform: scale(.9);
}
&.#{$namespace}-#{$block}--transition-leave-to {
transition: opacity .2s $slow-out-cubic-bezier, transform .2s $slow-out-cubic-bezier;
}
}