Skip to content

Commit db545c5

Browse files
committed
refactor: add renderless-select
1 parent d67086a commit db545c5

File tree

6 files changed

+225
-268
lines changed

6 files changed

+225
-268
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<script>
2+
3+
export default {
4+
inheritAttrs: false,
5+
name: 'RenderlessSelect',
6+
props: {
7+
multiple: {
8+
type: Boolean,
9+
default: false
10+
},
11+
modelValue: {
12+
type: [String, Array, Object],
13+
default (props) {
14+
return props.multiple ? [] : null
15+
}
16+
},
17+
items: {
18+
type: Array,
19+
default: () => []
20+
},
21+
returnObject: {
22+
type: Boolean,
23+
default: false
24+
},
25+
itemText: {
26+
type: String,
27+
default: 'text'
28+
},
29+
itemValue: {
30+
type: String,
31+
default: 'value'
32+
},
33+
cols: {
34+
type: Number,
35+
default: 1
36+
},
37+
selection: {
38+
type: String,
39+
default: ''
40+
},
41+
clearable: {
42+
type: Boolean,
43+
default: false
44+
}
45+
},
46+
emits: ['update:model-value'],
47+
data () {
48+
return {
49+
menu: false
50+
}
51+
},
52+
computed: {
53+
_value () {
54+
return (this.multiple) ? this.modelValue : [this.modelValue]
55+
},
56+
selectedItems () {
57+
return this.items.filter((item) => {
58+
for (const value of this._value) {
59+
if (this.returnObject) {
60+
if (value === item) return true
61+
} else {
62+
if (value === item[this.itemValue]) return true
63+
}
64+
}
65+
return false
66+
})
67+
},
68+
selectedStr () {
69+
return this.selection || this.selectedItems.map((item) => item[this.itemText]).join(',')
70+
},
71+
rows () {
72+
return Array.isArray(this.items) ? Math.ceil(this.items.length / this.cols) : 0
73+
},
74+
itemRows () {
75+
return Array.from(Array(this.rows), (_, i) => {
76+
return Array.from(Array(this.cols), (_, j) => {
77+
return this.items[this.cols * i + j]
78+
})
79+
})
80+
}
81+
},
82+
methods: {
83+
select (item) {
84+
if (!item) {
85+
return
86+
}
87+
88+
if (this.multiple) {
89+
const value = this.selectedItems.slice()
90+
const i = this.selectedItems.indexOf(item)
91+
// deselect
92+
if (i >= 0) {
93+
value.splice(i, 1)
94+
} else { // select
95+
value.push(item)
96+
}
97+
this.$emit('update:model-value', (this.returnObject) ? value : value.map((item) => item[this.itemValue]))
98+
} else {
99+
this.$emit('update:model-value', (this.returnObject) ? item : item[this.itemValue])
100+
}
101+
},
102+
isSelected (item) {
103+
return this.selectedItems.includes(item)
104+
},
105+
clear () {
106+
this.$emit('update:model-value', this.multiple ? [] : null)
107+
}
108+
},
109+
110+
render () {
111+
if (!this.$slots || !this.$slots.default) {
112+
return
113+
}
114+
115+
return this.$slots.default({
116+
selectedStr: this.selectedStr,
117+
modelValue: this.modelValue,
118+
items: this.items,
119+
select: this.select,
120+
isSelected: this.isSelected,
121+
clearable: this.clearable,
122+
clear: this.clear,
123+
cols: this.cols,
124+
rows: this.rows,
125+
itemRows: this.itemRows,
126+
multiple: this.multiple
127+
})
128+
}
129+
}
130+
</script>
131+
132+
<style>
133+
</style>

core/src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Import vue component
2+
import RenderlessSelect from './components/renderless-select.vue'
23
import component from './core.vue'
34
import * as locale from './locale'
45
import util from './util'
@@ -20,3 +21,7 @@ const plugin = {
2021

2122
// To allow use as module (npm/webpack/etc.) export component
2223
export default plugin
24+
25+
export {
26+
RenderlessSelect
27+
}

light/src/CronEditor.vue

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<CronCore v-bind="$attrs" @update:model-value="$emit('update:model-value', $event)" @error="$emit('error', $event)" v-slot="{fields, period}">
44
<span class="vcron-editor">
55
<span>{{period.prefix}}</span>
6-
<custom-select v-bind="period.attrs" v-on="period.events" :items="period.items" item-value="id" :cols="cols('period')" :width="width('period')" />
6+
<custom-select v-bind="period.attrs" v-on="period.events" :items="period.items" item-value="id" :cols="cols['period'] || 1" />
77
<span>{{period.suffix}}</span>
88

99
<template v-for="f in fields" :key="f.id">
1010
<span>{{f.prefix}}</span>
11-
<custom-select v-bind="f.attrs" v-on="f.events" :items="f.items" :cols="cols(f.id)" :width="width(f.id)" multiple>{{f.selectedStr}}</custom-select>
11+
<custom-select v-bind="f.attrs" v-on="f.events" :items="f.items" :cols="cols[f.id] || 1" :selection="f.selectedStr" multiple></custom-select>
1212
<span>{{f.suffix}}</span>
1313
</template>
1414
</span>
@@ -27,21 +27,13 @@ export default {
2727
},
2828
props: {
2929
cols: {
30-
type: Function,
31-
default: (fieldId) => {
32-
if (fieldId === 'minute') return 5
33-
else if (fieldId === 'hour') return 4
34-
else if (fieldId === 'day') return 4
35-
else return 1
36-
}
37-
},
38-
width: {
39-
type: Function,
40-
default: (fieldId) => {
41-
if (fieldId === 'minute') return '10em'
42-
else if (fieldId === 'hour') return '8em'
43-
else if (fieldId === 'day') return '8em'
44-
else return 'unset'
30+
type: Object,
31+
default: () => {
32+
return {
33+
minute: 5,
34+
hour: 4,
35+
day: 4
36+
}
4537
}
4638
}
4739
},

light/src/components/CustomSelect.vue

Lines changed: 43 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,47 @@
11
<template>
2-
<div class="vcron-select-container">
3-
<span class="vcron-select-input" @click="toggleMenu">
4-
<slot>{{selectedStr}}</slot>
5-
</span>
6-
<span class="vcron-select-list" :style="listStyle">
7-
<span v-for="item in items"
8-
:key="item[itemValue]+''"
9-
class="vcron-select-list-item"
10-
:class="{'vcron-select-list-item-selected': selectedItems.includes(item)}"
11-
:style="listItemStyle"
12-
@click="select(item)"
13-
@click.stop="multiple ? () => {} : toggleMenu()">
14-
15-
{{item[itemText]}}
2+
<renderless-select
3+
v-bind="$attrs"
4+
@update:model-value="$emit('update:model-value', $event)"
5+
#default="{ selectedStr, itemRows, select, isSelected, multiple }">
6+
7+
<div class="vcron-select-container">
8+
<span class="vcron-select-input" @click="toggleMenu">
9+
{{selectedStr}}
1610
</span>
17-
</span>
18-
</div>
11+
12+
<div class="vcron-select-list" v-if="menu">
13+
<div class="vcron-select-row" v-for="(row, i) in itemRows" :key="i">
14+
<div v-for="(item, j) in row"
15+
:key="i+'-'+j"
16+
class="vcron-select-col"
17+
:class="{'vcron-select-selected': isSelected(item)}"
18+
@click="select(item)"
19+
@click.stop="multiple ? () => {} : toggleMenu()">
20+
21+
<div v-if="item">{{item.text}}</div>
22+
</div>
23+
</div>
24+
</div>
25+
</div>
26+
</renderless-select>
1927
</template>
2028

2129
<script>
30+
import { RenderlessSelect } from '@vue-js-cron/core'
2231
2332
export default {
2433
inheritAttrs: false,
25-
name: 'CustomSelect',
26-
props: {
27-
multiple: {
28-
type: Boolean,
29-
default: false
30-
},
31-
modelValue: {
32-
type: [String, Array, Object],
33-
default (props) {
34-
return props.multiple ? [] : null
35-
}
36-
},
37-
items: {
38-
type: Array,
39-
default: () => []
40-
},
41-
returnObject: {
42-
type: Boolean,
43-
default: false
44-
},
45-
itemText: {
46-
type: String,
47-
default: 'text'
48-
},
49-
itemValue: {
50-
type: String,
51-
default: 'value'
52-
},
53-
cols: {
54-
type: Number,
55-
default: 1
56-
},
57-
width: {
58-
type: String,
59-
default: 'unset'
60-
}
34+
components: {
35+
RenderlessSelect
6136
},
37+
name: 'CustomSelect',
38+
props: {},
6239
emits: ['update:model-value'],
6340
data () {
6441
return {
6542
menu: false
6643
}
6744
},
68-
computed: {
69-
listStyle () {
70-
return {
71-
display: (this.menu) ? 'inline-block' : 'none',
72-
minWidth: '5em',
73-
width: this.width
74-
}
75-
},
76-
listItemStyle () {
77-
return {
78-
width: 100 / this.cols + '%'
79-
}
80-
},
81-
_value () {
82-
return (this.multiple) ? this.modelValue : [this.modelValue]
83-
},
84-
selectedItems () {
85-
return this.items.filter((item) => {
86-
for (const value of this._value) {
87-
if (this.returnObject) {
88-
if (value === item) return true
89-
} else {
90-
if (value === item[this.itemValue]) return true
91-
}
92-
}
93-
return false
94-
})
95-
},
96-
selectedStr () {
97-
return this.selectedItems.map((item) => item[this.itemText]).join(',')
98-
}
99-
},
10045
methods: {
10146
menuEvtListener (evt) {
10247
this.menu = false
@@ -112,21 +57,6 @@ export default {
11257
} else {
11358
document.removeEventListener('click', this.menuEvtListener)
11459
}
115-
},
116-
select (item) {
117-
if (this.multiple) {
118-
const value = this.selectedItems.slice()
119-
const i = this.selectedItems.indexOf(item)
120-
// deselect
121-
if (i >= 0) {
122-
value.splice(i, 1)
123-
} else { // select
124-
value.push(item)
125-
}
126-
this.$emit('update:model-value', (this.returnObject) ? value : value.map((item) => item[this.itemValue]))
127-
} else {
128-
this.$emit('update:model-value', (this.returnObject) ? item : item[this.itemValue])
129-
}
13060
}
13161
}
13262
}
@@ -168,22 +98,32 @@ export default {
16898
z-index: 100;
16999
}
170100
171-
.vcron-select-list-item {
101+
.vcron-select-row {
102+
display: flex;
103+
}
104+
105+
.vcron-select-col {
106+
flex-grow: 1;
107+
flex-basis: 0%;
172108
display: inline-block;
173109
box-sizing: border-box;
174110
user-select: none;
175-
width: 100%;
176111
padding: 0.2em 0.5em;
177112
text-align: center;
178113
color: black;
179114
}
180115
181-
.vcron-select-list-item:hover {
116+
.vcron-select-col:hover {
182117
background-color: rgb(52, 147, 190);
183118
color: white;
184119
}
185120
186-
.vcron-select-list-item-selected {
121+
.vcron-select-selected {
122+
background-color: rgb(43, 108, 138);
123+
color: white;
124+
}
125+
126+
.vcron-select-selected:hover {
187127
background-color: rgb(43, 108, 138);
188128
color: white;
189129
}

0 commit comments

Comments
 (0)