vue2-excel-grid 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:class="dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'"
|
|
4
4
|
:style="dropdownOnly ? overlayStyle : null"
|
|
5
5
|
@mousedown="onRootMouseDown"
|
|
6
|
+
@scroll.passive="dropdownOnly && _hideTooltip()"
|
|
6
7
|
>
|
|
7
8
|
<!-- Standalone mode: trigger button -->
|
|
8
9
|
<div
|
|
@@ -12,7 +13,12 @@
|
|
|
12
13
|
@click.stop="toggle"
|
|
13
14
|
>
|
|
14
15
|
<span v-if="selectedOpt && selectedOpt.color" class="eg-select-dot" :style="{ background: selectedOpt.color }"></span>
|
|
15
|
-
<span
|
|
16
|
+
<span
|
|
17
|
+
class="eg-select-trigger-label"
|
|
18
|
+
:class="{ 'eg-select-placeholder': !selectedOpt }"
|
|
19
|
+
@mouseenter="_showTooltip(selectedOpt && selectedOpt.tooltip, $event.currentTarget)"
|
|
20
|
+
@mouseleave="_hideTooltip"
|
|
21
|
+
>
|
|
16
22
|
{{ selectedOpt ? selectedOpt.label : placeholder }}
|
|
17
23
|
</span>
|
|
18
24
|
<span class="eg-select-caret">▾</span>
|
|
@@ -44,6 +50,8 @@
|
|
|
44
50
|
}"
|
|
45
51
|
@mousedown.prevent="pickOption(opt)"
|
|
46
52
|
@mouseover="highlight = idx"
|
|
53
|
+
@mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
|
|
54
|
+
@mouseleave="_hideTooltip"
|
|
47
55
|
>
|
|
48
56
|
<span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>
|
|
49
57
|
{{ opt.label }}
|
|
@@ -56,6 +64,7 @@
|
|
|
56
64
|
v-else-if="open"
|
|
57
65
|
class="eg-select-dropdown eg-select-dropdown--floating"
|
|
58
66
|
@mousedown.stop.prevent
|
|
67
|
+
@scroll.passive="_hideTooltip"
|
|
59
68
|
>
|
|
60
69
|
<div v-if="enableSearch" class="eg-select-search" @mousedown.stop>
|
|
61
70
|
<input
|
|
@@ -81,12 +90,16 @@
|
|
|
81
90
|
}"
|
|
82
91
|
@mousedown.prevent="pickOption(opt)"
|
|
83
92
|
@mouseover="highlight = idx"
|
|
93
|
+
@mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
|
|
94
|
+
@mouseleave="_hideTooltip"
|
|
84
95
|
>
|
|
85
96
|
<span v-if="opt.color" class="eg-select-dot" :style="{ background: opt.color }"></span>
|
|
86
97
|
{{ opt.label }}
|
|
87
98
|
</div>
|
|
88
99
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
89
100
|
</div>
|
|
101
|
+
|
|
102
|
+
<div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
|
|
90
103
|
</div>
|
|
91
104
|
</template>
|
|
92
105
|
|
|
@@ -126,7 +139,12 @@ export default {
|
|
|
126
139
|
// True right after Arrow/Page keyboard navigation — lets Space commit the
|
|
127
140
|
// highlighted option instead of typing a literal space into the search box.
|
|
128
141
|
// Reset on every text edit so normal multi-word search typing is unaffected.
|
|
129
|
-
_navigatedByKeyboard: false
|
|
142
|
+
_navigatedByKeyboard: false,
|
|
143
|
+
// { text, rect } while a themed option tooltip should be visible — see
|
|
144
|
+
// _showTooltip/_hideTooltip. rect is a snapshot from getBoundingClientRect()
|
|
145
|
+
// at the moment the hover delay elapsed.
|
|
146
|
+
hoverTooltip: null,
|
|
147
|
+
_tooltipTimer: null
|
|
130
148
|
}
|
|
131
149
|
},
|
|
132
150
|
computed: {
|
|
@@ -134,11 +152,11 @@ export default {
|
|
|
134
152
|
normalizedOptions() {
|
|
135
153
|
const vk = this.valueKey
|
|
136
154
|
const lk = this.labelKey
|
|
137
|
-
return (this.options || []).map(o =>
|
|
138
|
-
typeof o === 'string'
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
155
|
+
return (this.options || []).map(o => {
|
|
156
|
+
if (typeof o === 'string') return { value: o, label: o, tooltip: o }
|
|
157
|
+
const label = o[lk] != null ? o[lk] : String(o[vk])
|
|
158
|
+
return { value: o[vk], label, color: o.color, tooltip: o.tooltip != null ? o.tooltip : label }
|
|
159
|
+
})
|
|
142
160
|
},
|
|
143
161
|
filteredOptions() {
|
|
144
162
|
if (!this.enableSearch || !this.searchQuery.trim()) return this.normalizedOptions
|
|
@@ -165,6 +183,15 @@ export default {
|
|
|
165
183
|
maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',
|
|
166
184
|
zIndex: 9999
|
|
167
185
|
}
|
|
186
|
+
},
|
|
187
|
+
hoverTooltipStyle() {
|
|
188
|
+
if (!this.hoverTooltip) return {}
|
|
189
|
+
const { rect, text } = this.hoverTooltip
|
|
190
|
+
const estH = 20 + Math.ceil(text.length / 40) * 16
|
|
191
|
+
const spaceBelow = window.innerHeight - rect.bottom
|
|
192
|
+
const top = spaceBelow >= estH + 8 ? rect.bottom + 6 : rect.top - estH - 6
|
|
193
|
+
const left = Math.min(rect.left, window.innerWidth - 280 - 8)
|
|
194
|
+
return { position: 'fixed', top: top + 'px', left: Math.max(8, left) + 'px', zIndex: 10000 }
|
|
168
195
|
}
|
|
169
196
|
},
|
|
170
197
|
watch: {
|
|
@@ -188,6 +215,7 @@ export default {
|
|
|
188
215
|
}
|
|
189
216
|
},
|
|
190
217
|
beforeDestroy() {
|
|
218
|
+
this._clearTooltipTimer()
|
|
191
219
|
if (!this.dropdownOnly) {
|
|
192
220
|
document.removeEventListener('click', this._onDocClick)
|
|
193
221
|
}
|
|
@@ -212,6 +240,7 @@ export default {
|
|
|
212
240
|
}
|
|
213
241
|
},
|
|
214
242
|
onSearch(e) {
|
|
243
|
+
this._hideTooltip()
|
|
215
244
|
this.searchQuery = e.target.value
|
|
216
245
|
this.highlight = 0
|
|
217
246
|
this._navigatedByKeyboard = false
|
|
@@ -326,6 +355,27 @@ export default {
|
|
|
326
355
|
if (!this.open) return
|
|
327
356
|
if (this.$el && this.$el.contains(e.target)) return
|
|
328
357
|
this.open = false
|
|
358
|
+
},
|
|
359
|
+
// Shared by option rows and the closed trigger label — shows a themed
|
|
360
|
+
// popover (see .eg-option-tooltip) after a short hover delay, instead of
|
|
361
|
+
// the browser's native title tooltip.
|
|
362
|
+
_showTooltip(text, el) {
|
|
363
|
+
this._clearTooltipTimer()
|
|
364
|
+
if (!text) return
|
|
365
|
+
const rect = el.getBoundingClientRect()
|
|
366
|
+
this._tooltipTimer = setTimeout(() => {
|
|
367
|
+
this.hoverTooltip = { text, rect }
|
|
368
|
+
}, 400)
|
|
369
|
+
},
|
|
370
|
+
_hideTooltip() {
|
|
371
|
+
this._clearTooltipTimer()
|
|
372
|
+
this.hoverTooltip = null
|
|
373
|
+
},
|
|
374
|
+
_clearTooltipTimer() {
|
|
375
|
+
if (this._tooltipTimer) {
|
|
376
|
+
clearTimeout(this._tooltipTimer)
|
|
377
|
+
this._tooltipTimer = null
|
|
378
|
+
}
|
|
329
379
|
}
|
|
330
380
|
}
|
|
331
381
|
}
|
|
@@ -484,4 +534,24 @@ export default {
|
|
|
484
534
|
display: inline-block;
|
|
485
535
|
margin-top: 3px;
|
|
486
536
|
}
|
|
537
|
+
|
|
538
|
+
/* Themed option/trigger tooltip — same recipe as .eg-select-dropdown
|
|
539
|
+
(cell-bg/border/radius) plus .eg-validation-tooltip's shadow, so it reads
|
|
540
|
+
as part of the grid's own chrome in every built-in theme instead of a
|
|
541
|
+
generic OS tooltip box. */
|
|
542
|
+
.eg-option-tooltip {
|
|
543
|
+
position: fixed;
|
|
544
|
+
max-width: 280px;
|
|
545
|
+
background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
|
|
546
|
+
border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
547
|
+
border-radius: var(--eg-radius, var(--eg-radius-default));
|
|
548
|
+
box-shadow: var(--eg-shadow-tooltip, var(--eg-shadow-tooltip-default));
|
|
549
|
+
color: var(--eg-color-text, var(--eg-color-text-default));
|
|
550
|
+
font-size: var(--eg-font-size, var(--eg-font-size-default));
|
|
551
|
+
padding: 6px 10px;
|
|
552
|
+
pointer-events: none;
|
|
553
|
+
white-space: normal;
|
|
554
|
+
word-break: break-word;
|
|
555
|
+
z-index: 10000;
|
|
556
|
+
}
|
|
487
557
|
</style>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:class="dropdownOnly ? 'eg-select-dropdown' : 'eg-select-wrapper'"
|
|
4
4
|
:style="dropdownOnly ? overlayStyle : null"
|
|
5
5
|
@mousedown="onRootMouseDown"
|
|
6
|
+
@scroll.passive="dropdownOnly && _hideTooltip()"
|
|
6
7
|
>
|
|
7
8
|
<!-- Standalone mode: trigger button -->
|
|
8
9
|
<div
|
|
@@ -11,7 +12,12 @@
|
|
|
11
12
|
:class="{ 'eg-select-trigger--disabled': disabled, 'eg-select-trigger--open': open }"
|
|
12
13
|
@click.stop="toggle"
|
|
13
14
|
>
|
|
14
|
-
<span
|
|
15
|
+
<span
|
|
16
|
+
class="eg-select-trigger-label"
|
|
17
|
+
:class="{ 'eg-select-placeholder': selectedLabel == null }"
|
|
18
|
+
@mouseenter="_showTooltip(selectedTooltip, $event.currentTarget)"
|
|
19
|
+
@mouseleave="_hideTooltip"
|
|
20
|
+
>
|
|
15
21
|
{{ selectedLabel != null ? selectedLabel : placeholder }}
|
|
16
22
|
</span>
|
|
17
23
|
<span class="eg-select-caret">▾</span>
|
|
@@ -44,6 +50,8 @@
|
|
|
44
50
|
:style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null"
|
|
45
51
|
@mousedown.prevent="pickOption(opt)"
|
|
46
52
|
@mouseover="highlight = idx"
|
|
53
|
+
@mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
|
|
54
|
+
@mouseleave="_hideTooltip"
|
|
47
55
|
>
|
|
48
56
|
<span
|
|
49
57
|
v-if="!searchQuery"
|
|
@@ -62,6 +70,7 @@
|
|
|
62
70
|
v-else-if="open"
|
|
63
71
|
class="eg-select-dropdown eg-select-dropdown--floating"
|
|
64
72
|
@mousedown.stop.prevent
|
|
73
|
+
@scroll.passive="_hideTooltip"
|
|
65
74
|
>
|
|
66
75
|
<div v-if="enableSearch" class="eg-select-search" @mousedown.stop>
|
|
67
76
|
<input
|
|
@@ -88,6 +97,8 @@
|
|
|
88
97
|
:style="!searchQuery ? { paddingLeft: (12 + opt.depth * 16) + 'px' } : null"
|
|
89
98
|
@mousedown.prevent="pickOption(opt)"
|
|
90
99
|
@mouseover="highlight = idx"
|
|
100
|
+
@mouseenter="_showTooltip(opt.tooltip, $event.currentTarget)"
|
|
101
|
+
@mouseleave="_hideTooltip"
|
|
91
102
|
>
|
|
92
103
|
<span
|
|
93
104
|
v-if="!searchQuery"
|
|
@@ -100,6 +111,8 @@
|
|
|
100
111
|
</div>
|
|
101
112
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
102
113
|
</div>
|
|
114
|
+
|
|
115
|
+
<div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
|
|
103
116
|
</div>
|
|
104
117
|
</template>
|
|
105
118
|
|
|
@@ -138,7 +151,12 @@ export default {
|
|
|
138
151
|
// True right after Arrow/Page keyboard navigation — lets Space commit the
|
|
139
152
|
// highlighted option instead of typing a literal space into the search box.
|
|
140
153
|
// Reset on every text edit so normal multi-word search typing is unaffected.
|
|
141
|
-
_navigatedByKeyboard: false
|
|
154
|
+
_navigatedByKeyboard: false,
|
|
155
|
+
// { text, rect } while a themed option tooltip should be visible — see
|
|
156
|
+
// _showTooltip/_hideTooltip. rect is a snapshot from getBoundingClientRect()
|
|
157
|
+
// at the moment the hover delay elapsed.
|
|
158
|
+
hoverTooltip: null,
|
|
159
|
+
_tooltipTimer: null
|
|
142
160
|
}
|
|
143
161
|
},
|
|
144
162
|
computed: {
|
|
@@ -165,6 +183,14 @@ export default {
|
|
|
165
183
|
const found = this._treeFindLabel(this.options || [], this.keys, v)
|
|
166
184
|
return found != null ? found : null
|
|
167
185
|
},
|
|
186
|
+
selectedTooltip() {
|
|
187
|
+
const v = String(this.value == null ? '' : this.value)
|
|
188
|
+
if (!v) return null
|
|
189
|
+
const node = this._treeFindNode(this.options || [], this.keys, v)
|
|
190
|
+
if (!node) return null
|
|
191
|
+
const label = node[this.keys.labelKey] != null ? node[this.keys.labelKey] : String(node[this.keys.valueKey])
|
|
192
|
+
return node.tooltip != null ? node.tooltip : label
|
|
193
|
+
},
|
|
168
194
|
overlayStyle() {
|
|
169
195
|
if (!this.dropdownOnly || !this.triggerRect) return { position: 'fixed', zIndex: 9999 }
|
|
170
196
|
const rect = this.triggerRect
|
|
@@ -181,6 +207,15 @@ export default {
|
|
|
181
207
|
maxWidth: Math.min(360, window.innerWidth - rect.left - 16) + 'px',
|
|
182
208
|
zIndex: 9999
|
|
183
209
|
}
|
|
210
|
+
},
|
|
211
|
+
hoverTooltipStyle() {
|
|
212
|
+
if (!this.hoverTooltip) return {}
|
|
213
|
+
const { rect, text } = this.hoverTooltip
|
|
214
|
+
const estH = 20 + Math.ceil(text.length / 40) * 16
|
|
215
|
+
const spaceBelow = window.innerHeight - rect.bottom
|
|
216
|
+
const top = spaceBelow >= estH + 8 ? rect.bottom + 6 : rect.top - estH - 6
|
|
217
|
+
const left = Math.min(rect.left, window.innerWidth - 280 - 8)
|
|
218
|
+
return { position: 'fixed', top: top + 'px', left: Math.max(8, left) + 'px', zIndex: 10000 }
|
|
184
219
|
}
|
|
185
220
|
},
|
|
186
221
|
watch: {
|
|
@@ -202,6 +237,7 @@ export default {
|
|
|
202
237
|
}
|
|
203
238
|
},
|
|
204
239
|
beforeDestroy() {
|
|
240
|
+
this._clearTooltipTimer()
|
|
205
241
|
if (!this.dropdownOnly) {
|
|
206
242
|
document.removeEventListener('click', this._onDocClick)
|
|
207
243
|
}
|
|
@@ -245,6 +281,7 @@ export default {
|
|
|
245
281
|
this.highlight = newIdx >= 0 ? newIdx : this.highlight
|
|
246
282
|
},
|
|
247
283
|
onSearch(e) {
|
|
284
|
+
this._hideTooltip()
|
|
248
285
|
this.searchQuery = e.target.value
|
|
249
286
|
this.highlight = 0
|
|
250
287
|
this._navigatedByKeyboard = false
|
|
@@ -375,7 +412,8 @@ export default {
|
|
|
375
412
|
const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []
|
|
376
413
|
const hasChildren = children.length > 0
|
|
377
414
|
const expanded = hasChildren && !!expandedMap[id]
|
|
378
|
-
|
|
415
|
+
const label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])
|
|
416
|
+
out.push({ value: node[keys.valueKey], label, color: node.color, tooltip: node.tooltip != null ? node.tooltip : label, id, depth, hasChildren, expanded })
|
|
379
417
|
if (expanded) out = out.concat(this._treeFlatten(children, keys, expandedMap, depth + 1, path))
|
|
380
418
|
})
|
|
381
419
|
return out
|
|
@@ -385,7 +423,8 @@ export default {
|
|
|
385
423
|
const walk = (list) => {
|
|
386
424
|
;(list || []).forEach(node => {
|
|
387
425
|
const children = Array.isArray(node[keys.childrenKey]) ? node[keys.childrenKey] : []
|
|
388
|
-
|
|
426
|
+
const label = node[keys.labelKey] != null ? node[keys.labelKey] : String(node[keys.valueKey])
|
|
427
|
+
result.push({ value: node[keys.valueKey], label, color: node.color, tooltip: node.tooltip != null ? node.tooltip : label, id: String(node[keys.valueKey]), depth: 0, hasChildren: false, expanded: false })
|
|
389
428
|
if (children.length) walk(children)
|
|
390
429
|
})
|
|
391
430
|
}
|
|
@@ -402,6 +441,14 @@ export default {
|
|
|
402
441
|
}
|
|
403
442
|
return null
|
|
404
443
|
},
|
|
444
|
+
_treeFindNode(nodes, keys, value) {
|
|
445
|
+
for (const node of (nodes || [])) {
|
|
446
|
+
if (String(node[keys.valueKey]) === value) return node
|
|
447
|
+
const found = this._treeFindNode(node[keys.childrenKey], keys, value)
|
|
448
|
+
if (found != null) return found
|
|
449
|
+
}
|
|
450
|
+
return null
|
|
451
|
+
},
|
|
405
452
|
_treeFindPath(nodes, keys, value, pathArr) {
|
|
406
453
|
for (let i = 0; i < (nodes || []).length; i++) {
|
|
407
454
|
const node = nodes[i]
|
|
@@ -414,6 +461,27 @@ export default {
|
|
|
414
461
|
}
|
|
415
462
|
}
|
|
416
463
|
return null
|
|
464
|
+
},
|
|
465
|
+
// Shared by option rows and the closed trigger label — shows a themed
|
|
466
|
+
// popover (see .eg-option-tooltip) after a short hover delay, instead of
|
|
467
|
+
// the browser's native title tooltip.
|
|
468
|
+
_showTooltip(text, el) {
|
|
469
|
+
this._clearTooltipTimer()
|
|
470
|
+
if (!text) return
|
|
471
|
+
const rect = el.getBoundingClientRect()
|
|
472
|
+
this._tooltipTimer = setTimeout(() => {
|
|
473
|
+
this.hoverTooltip = { text, rect }
|
|
474
|
+
}, 400)
|
|
475
|
+
},
|
|
476
|
+
_hideTooltip() {
|
|
477
|
+
this._clearTooltipTimer()
|
|
478
|
+
this.hoverTooltip = null
|
|
479
|
+
},
|
|
480
|
+
_clearTooltipTimer() {
|
|
481
|
+
if (this._tooltipTimer) {
|
|
482
|
+
clearTimeout(this._tooltipTimer)
|
|
483
|
+
this._tooltipTimer = null
|
|
484
|
+
}
|
|
417
485
|
}
|
|
418
486
|
}
|
|
419
487
|
}
|
|
@@ -7661,6 +7661,21 @@ export default {
|
|
|
7661
7661
|
color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
|
|
7662
7662
|
user-select: none;
|
|
7663
7663
|
}
|
|
7664
|
+
.eg-option-tooltip {
|
|
7665
|
+
position: fixed;
|
|
7666
|
+
max-width: 280px;
|
|
7667
|
+
background: var(--eg-color-cell-bg, var(--eg-color-cell-bg-default));
|
|
7668
|
+
border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
7669
|
+
border-radius: var(--eg-radius, var(--eg-radius-default));
|
|
7670
|
+
box-shadow: var(--eg-shadow-tooltip, var(--eg-shadow-tooltip-default));
|
|
7671
|
+
color: var(--eg-color-text, var(--eg-color-text-default));
|
|
7672
|
+
font-size: var(--eg-font-size, var(--eg-font-size-default));
|
|
7673
|
+
padding: 6px 10px;
|
|
7674
|
+
pointer-events: none;
|
|
7675
|
+
white-space: normal;
|
|
7676
|
+
word-break: break-word;
|
|
7677
|
+
z-index: 10000;
|
|
7678
|
+
}
|
|
7664
7679
|
/* Grid `size` overrides — must live in this *unscoped* style block, not the
|
|
7665
7680
|
scoped one above: .eg-select-option is rendered by the separate EgSelect /
|
|
7666
7681
|
EgTreeSelect components, so Vue's scoped-CSS attribute (added only to this
|