innoboxrr-vue-datatable 2.2.0 → 2.3.0
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innoboxrr-vue-datatable",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Datatable para vue3",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,11 +40,12 @@
|
|
|
40
40
|
"*.vue"
|
|
41
41
|
],
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"innoboxrr-form-core": "^2.
|
|
43
|
+
"innoboxrr-form-core": "^2.3.0",
|
|
44
44
|
"vue": "^3.5.0",
|
|
45
45
|
"vue-router": "^4.5.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"@floating-ui/dom": "^1.8.0",
|
|
48
49
|
"@iconify/vue": "^5.0.0",
|
|
49
50
|
"axios": "^1.7.0"
|
|
50
51
|
},
|
package/src/DataTable.vue
CHANGED
|
@@ -59,9 +59,10 @@
|
|
|
59
59
|
<button
|
|
60
60
|
type="button"
|
|
61
61
|
class="fe-text-right pointer"
|
|
62
|
-
uk-toggle="target: .filter-form; animation: uk-animation-scale-up;"
|
|
63
62
|
data-tooltip="Buscar"
|
|
64
|
-
aria-label="Buscar"
|
|
63
|
+
aria-label="Buscar"
|
|
64
|
+
:aria-expanded="filtersOpen"
|
|
65
|
+
@click="filtersOpen = ! filtersOpen">
|
|
65
66
|
<svg class="w-6 h-6 text-slate-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 18">
|
|
66
67
|
<path d="M18.85 1.1A1.99 1.99 0 0 0 17.063 0H2.937a2 2 0 0 0-1.566 3.242L6.99 9.868 7 14a1 1 0 0 0 .4.8l4 3A1 1 0 0 0 13 17l.01-7.134 5.66-6.676a1.99 1.99 0 0 0 .18-2.09Z"/>
|
|
67
68
|
</svg>
|
|
@@ -74,7 +75,7 @@
|
|
|
74
75
|
<div
|
|
75
76
|
v-if="hasFilter"
|
|
76
77
|
class="filter-form fe-card fe-card-body fe-pt-0"
|
|
77
|
-
hidden>
|
|
78
|
+
:hidden="! filtersOpen">
|
|
78
79
|
<slot name="filterForm"></slot>
|
|
79
80
|
</div>
|
|
80
81
|
</div>
|
|
@@ -183,6 +184,11 @@
|
|
|
183
184
|
* Se leia de la global `csrf_token`, que la aplicacion anfitriona tenia
|
|
184
185
|
* que definir en window: el componente no se podia montar fuera de ella.
|
|
185
186
|
*/
|
|
187
|
+
// Abrir un panel es estado de componente, no una instruccion al DOM. Antes
|
|
188
|
+
// lo hacia uk-toggle buscando `.filter-form` por selector, que es como la
|
|
189
|
+
// rama Vue y la de React acabaron comportandose distinto.
|
|
190
|
+
const filtersOpen = ref(false)
|
|
191
|
+
|
|
186
192
|
const csrfToken = () => globalThis.csrf_token
|
|
187
193
|
?? document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
188
194
|
?? ''
|
|
@@ -392,14 +398,9 @@
|
|
|
392
398
|
|
|
393
399
|
}
|
|
394
400
|
|
|
401
|
+
// Un popover se cierra solo: no hace falta preguntarle nada a nadie.
|
|
395
402
|
const closeDropdown = (event) => {
|
|
396
|
-
|
|
397
|
-
const dropdown = event.target.closest('.uk-dropdown')
|
|
398
|
-
|
|
399
|
-
if (dropdown) {
|
|
400
|
-
globalThis.UIkit?.dropdown(dropdown)?.hide(false)
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
+
event.target.closest('[popover]')?.hidePopover()
|
|
403
404
|
}
|
|
404
405
|
|
|
405
406
|
watch(() => props.formFilters, () => {
|
|
@@ -46,8 +46,10 @@
|
|
|
46
46
|
<td v-if="actions" class="fe-text-right">
|
|
47
47
|
<button
|
|
48
48
|
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-2 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
|
49
|
+
:popovertarget="`dropdown_${body.id}`"
|
|
50
|
+
:aria-label="'Actions'"
|
|
49
51
|
@click="emit('actionButtonClicked', body.actions)">
|
|
50
|
-
<
|
|
52
|
+
<Icon :icon="iconFor('actions')" aria-hidden="true" />
|
|
51
53
|
</button>
|
|
52
54
|
<NavDropdownComponent :id="`dropdown_${body.id}`" pos="left">
|
|
53
55
|
<li
|
|
@@ -98,6 +100,8 @@
|
|
|
98
100
|
<script setup>
|
|
99
101
|
|
|
100
102
|
import { computed } from 'vue'
|
|
103
|
+
import { Icon } from '@iconify/vue'
|
|
104
|
+
import { iconFor } from 'innoboxrr-form-core'
|
|
101
105
|
|
|
102
106
|
import NavDropdownComponent from './NavDropdownComponent.vue'
|
|
103
107
|
import IconRouteComponent from './IconRouteComponent.vue'
|
|
@@ -162,15 +166,9 @@
|
|
|
162
166
|
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
// Un popover se cierra solo: no hace falta preguntarle nada a nadie.
|
|
165
170
|
const closeDropdown = (event) => {
|
|
166
|
-
|
|
167
|
-
const dropdown = event.target.closest('.uk-dropdown')
|
|
168
|
-
|
|
169
|
-
// UIkit lo aporta la aplicacion anfitriona.
|
|
170
|
-
if (dropdown) {
|
|
171
|
-
globalThis.UIkit?.dropdown(dropdown)?.hide(false)
|
|
172
|
-
}
|
|
173
|
-
|
|
171
|
+
event.target.closest('[popover]')?.hidePopover()
|
|
174
172
|
}
|
|
175
173
|
|
|
176
174
|
</script>
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
3
|
<div
|
|
4
|
-
:
|
|
5
|
-
|
|
4
|
+
:id="id"
|
|
5
|
+
ref="panel"
|
|
6
|
+
popover
|
|
7
|
+
:class="classFor('menu')"
|
|
8
|
+
@beforetoggle="onBeforeToggle">
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
<ul class="fe-menu-list">
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
<slot></slot>
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
</ul>
|
|
12
15
|
|
|
13
16
|
</div>
|
|
14
17
|
|
|
@@ -16,40 +19,100 @@
|
|
|
16
19
|
|
|
17
20
|
<script setup>
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* El menu de acciones de una fila.
|
|
24
|
+
*
|
|
25
|
+
* Antes lo movia `uk-dropdown`, que ademas de la clase traia el JavaScript
|
|
26
|
+
* de UIkit: abrir, cerrar al pulsar fuera, cerrar con Escape, capa
|
|
27
|
+
* superior y posicionamiento, todo dentro de un framework de 12 MB que
|
|
28
|
+
* ningun package.json declaraba.
|
|
29
|
+
*
|
|
30
|
+
* La mitad de eso ya lo hace el navegador. La **Popover API** —el atributo
|
|
31
|
+
* `popover` y el `popovertarget` del boton— aporta de fabrica la capa
|
|
32
|
+
* superior, el cierre al pulsar fuera y el cierre con Escape, sin una
|
|
33
|
+
* linea de JavaScript. Lo unico que falta es colocarlo, y de eso se ocupa
|
|
34
|
+
* Floating UI, que son unos 6 kB y es la pieza que usan por debajo Reka
|
|
35
|
+
* UI, Radix y el propio Flowbite.
|
|
36
|
+
*
|
|
37
|
+
* El boton que lo abre solo necesita `popovertarget` con este mismo id:
|
|
38
|
+
*
|
|
39
|
+
* <button :popovertarget="`dropdown_${row.id}`">…</button>
|
|
40
|
+
* <NavDropdownComponent :id="`dropdown_${row.id}`" pos="left" />
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { computed, ref } from 'vue'
|
|
44
|
+
import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'
|
|
45
|
+
import { classFor } from 'innoboxrr-form-core'
|
|
20
46
|
|
|
21
47
|
const props = defineProps({
|
|
22
|
-
|
|
48
|
+
id: {
|
|
23
49
|
type: String,
|
|
24
|
-
|
|
50
|
+
required: true,
|
|
25
51
|
},
|
|
26
|
-
|
|
52
|
+
// Se conservan los nombres que usaba UIkit para no romper a quien ya
|
|
53
|
+
// los pasa; `bottom-left` y compania se traducen a los de Floating UI.
|
|
54
|
+
pos: {
|
|
27
55
|
type: String,
|
|
28
|
-
default:
|
|
56
|
+
default: 'bottom-left',
|
|
29
57
|
},
|
|
30
58
|
offset: {
|
|
31
59
|
type: Number,
|
|
32
|
-
default:
|
|
33
|
-
},
|
|
34
|
-
animation: {
|
|
35
|
-
type: String,
|
|
36
|
-
// Es el nombre de una animacion de UIkit, no una clase nuestra: el
|
|
37
|
-
// dropdown sigue siendo de UIkit hasta que lo sustituya un componente
|
|
38
|
-
// headless propio.
|
|
39
|
-
default: "uk-animation-slide-top-small"
|
|
60
|
+
default: 4,
|
|
40
61
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const panel = ref(null)
|
|
65
|
+
|
|
66
|
+
/** Deja de seguir al disparador cuando el menu se cierra. */
|
|
67
|
+
let stopFollowing = null
|
|
68
|
+
|
|
69
|
+
const placement = computed(() => {
|
|
70
|
+
const [side, align] = props.pos.split('-')
|
|
71
|
+
|
|
72
|
+
if (! align) {
|
|
73
|
+
return side
|
|
44
74
|
}
|
|
75
|
+
|
|
76
|
+
// UIkit dice `bottom-left` para «debajo, alineado a la izquierda»;
|
|
77
|
+
// Floating UI lo llama `bottom-start`.
|
|
78
|
+
return `${side}-${align === 'left' ? 'start' : 'end'}`
|
|
45
79
|
})
|
|
46
80
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
const onBeforeToggle = (event) => {
|
|
82
|
+
if (event.newState !== 'open') {
|
|
83
|
+
stopFollowing?.()
|
|
84
|
+
stopFollowing = null
|
|
85
|
+
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// El boton es quien apunta a este panel, asi que se encuentra por el
|
|
90
|
+
// atributo y no hace falta que nadie lo pase como prop.
|
|
91
|
+
const trigger = document.querySelector(`[popovertarget="${props.id}"]`)
|
|
92
|
+
|
|
93
|
+
if (! trigger || ! panel.value) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// autoUpdate recoloca al hacer scroll o redimensionar: un menu abierto
|
|
98
|
+
// que se queda flotando donde estaba es peor que uno mal colocado.
|
|
99
|
+
stopFollowing = autoUpdate(trigger, panel.value, () => {
|
|
100
|
+
computePosition(trigger, panel.value, {
|
|
101
|
+
placement: placement.value,
|
|
102
|
+
middleware: [
|
|
103
|
+
offset(props.offset),
|
|
104
|
+
// Si no cabe abajo, se va arriba; si se sale por un lado,
|
|
105
|
+
// se desplaza para caber.
|
|
106
|
+
flip(),
|
|
107
|
+
shift({ padding: 8 }),
|
|
108
|
+
],
|
|
109
|
+
}).then(({ x, y }) => {
|
|
110
|
+
Object.assign(panel.value.style, {
|
|
111
|
+
left: `${x}px`,
|
|
112
|
+
top: `${y}px`,
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
}
|
|
54
117
|
|
|
55
118
|
</script>
|