innoboxrr-vue-datatable 2.3.0 → 3.0.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/README.md +130 -168
- package/index.js +13 -2
- package/package.json +6 -4
- package/src/DataTable.vue +249 -424
- package/src/components/DataTableComponent.vue +218 -174
- package/src/components/SelectPaginationComponent.vue +78 -93
- package/src/table.js +251 -0
- package/src/useDataTable.js +370 -0
- package/src/useTheme.js +20 -0
- package/src/components/DisabledLinkComponent.vue +0 -45
- package/src/components/IconLinkComponent.vue +0 -81
- package/src/components/IconRouteComponent.vue +0 -84
- package/src/components/NavDropdownComponent.vue +0 -118
- package/src/components/PaginationComponent.vue +0 -79
|
@@ -1,181 +1,225 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
2
|
+
|
|
3
|
+
<div :class="theme.tableContainer">
|
|
4
|
+
|
|
5
|
+
<table :class="[theme.table, theme.tableSticky]" :aria-busy="loading ? 'true' : 'false'">
|
|
6
|
+
|
|
7
|
+
<thead v-if="showTableHeader">
|
|
8
|
+
<tr>
|
|
9
|
+
<th v-if="selectable" scope="col" :class="theme.tableSelect">
|
|
10
|
+
<input
|
|
11
|
+
type="checkbox"
|
|
12
|
+
:class="theme.checkbox"
|
|
13
|
+
:aria-label="labels.selectAll"
|
|
14
|
+
:checked="allSelected"
|
|
15
|
+
:indeterminate="someSelected"
|
|
16
|
+
:disabled="rows.length === 0"
|
|
17
|
+
@change="table.toggleAllPageRowsSelected($event.target.checked)">
|
|
18
|
+
</th>
|
|
19
|
+
|
|
20
|
+
<th
|
|
21
|
+
v-for="column in head"
|
|
22
|
+
:id="`th_${column.id}`"
|
|
23
|
+
:key="column.id"
|
|
24
|
+
scope="col"
|
|
25
|
+
:class="column.numeric ? theme.tableNumeric : null"
|
|
26
|
+
:aria-sort="ariaSort(column, orderBy, sort)">
|
|
27
|
+
<button
|
|
28
|
+
v-if="column.sortable === true"
|
|
29
|
+
type="button"
|
|
30
|
+
:class="theme.tableSort"
|
|
31
|
+
@click="emit('sortColumn', column)">
|
|
32
|
+
<span>{{ column.value }}</span>
|
|
33
|
+
<IconComponent :name="sortIcon(column, orderBy, sort)" :size="12" />
|
|
34
|
+
</button>
|
|
35
|
+
<template v-else>{{ column.value }}</template>
|
|
36
|
+
</th>
|
|
37
|
+
|
|
38
|
+
<th v-if="actions" scope="col" :class="theme.tableSelect" :aria-label="labels.actions" />
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
|
|
42
|
+
<tbody>
|
|
43
|
+
|
|
44
|
+
<!-- Primera carga: la forma de las filas, mientras llegan. -->
|
|
45
|
+
<template v-if="loading && rows.length === 0">
|
|
46
|
+
<tr v-for="n in SKELETON_ROWS" :key="`skeleton-${n}`" data-skeleton="true">
|
|
47
|
+
<td v-if="selectable" :class="theme.tableSelect" />
|
|
48
|
+
<td v-for="column in head" :key="column.id">
|
|
49
|
+
<SkeletonComponent />
|
|
50
|
+
</td>
|
|
51
|
+
<td v-if="actions" :class="theme.tableSelect" />
|
|
52
|
+
</tr>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<!-- Sin filas se dice por qué: no es lo mismo vacío que prohibido. -->
|
|
56
|
+
<tr v-else-if="rows.length === 0">
|
|
57
|
+
<td :colspan="colspan" :class="theme.tableEmpty">
|
|
58
|
+
<template v-if="error">
|
|
59
|
+
<span role="alert">{{ error.message }}</span>
|
|
60
|
+
<button
|
|
61
|
+
v-if="error.retryable"
|
|
62
|
+
type="button"
|
|
63
|
+
:class="theme.buttonLink"
|
|
64
|
+
@click="emit('retry')">{{ labels.retry }}</button>
|
|
65
|
+
</template>
|
|
66
|
+
<template v-else>{{ labels.empty }}</template>
|
|
67
|
+
</td>
|
|
68
|
+
</tr>
|
|
69
|
+
|
|
70
|
+
<template v-else>
|
|
71
|
+
<tr
|
|
72
|
+
v-for="row in table.getRowModel().rows"
|
|
73
|
+
:key="row.id"
|
|
74
|
+
:data-selected="row.getIsSelected() ? 'true' : undefined">
|
|
75
|
+
|
|
76
|
+
<td v-if="selectable" :class="theme.tableSelect">
|
|
77
|
+
<input
|
|
78
|
+
type="checkbox"
|
|
79
|
+
:class="theme.checkbox"
|
|
80
|
+
:aria-label="`${labels.selectRow} ${row.original.id}`"
|
|
81
|
+
:checked="row.getIsSelected()"
|
|
82
|
+
@click="row.getToggleSelectedHandler()($event)">
|
|
83
|
+
</td>
|
|
84
|
+
|
|
85
|
+
<td
|
|
86
|
+
v-for="cell in row.getVisibleCells()"
|
|
87
|
+
:key="cell.id"
|
|
88
|
+
:class="headOf(cell).numeric ? theme.tableNumeric : null">
|
|
89
|
+
<component
|
|
90
|
+
:is="componentFor(cell)"
|
|
91
|
+
v-if="componentFor(cell)"
|
|
92
|
+
v-bind="componentProps(valueFor(cell, row))"
|
|
93
|
+
@callback="onCallback(cell, row, $event)" />
|
|
94
|
+
<span v-else-if="headOf(cell).html" v-html="valueFor(cell, row)" />
|
|
95
|
+
<template v-else>{{ valueFor(cell, row) }}</template>
|
|
96
|
+
</td>
|
|
97
|
+
|
|
98
|
+
<td v-if="actions" :class="theme.tableSelect">
|
|
99
|
+
<MenuComponent
|
|
100
|
+
:items="itemsFor(row.original)"
|
|
101
|
+
:label="`${labels.rowActions} ${row.original.id}`"
|
|
102
|
+
:before-open="() => prepareRow(row.original.id)" />
|
|
103
|
+
</td>
|
|
104
|
+
|
|
105
|
+
</tr>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
</tbody>
|
|
109
|
+
|
|
110
|
+
</table>
|
|
111
|
+
|
|
112
|
+
</div>
|
|
113
|
+
|
|
98
114
|
</template>
|
|
99
115
|
|
|
100
116
|
<script setup>
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const props = defineProps({
|
|
112
|
-
actions: {
|
|
113
|
-
type: Boolean,
|
|
114
|
-
default: false
|
|
115
|
-
},
|
|
116
|
-
dataTable: {
|
|
117
|
-
type: [Object, Boolean],
|
|
118
|
-
required: true
|
|
119
|
-
},
|
|
120
|
-
// Vue 3 exige factoria en los defaults de objeto.
|
|
121
|
-
extraParams: {
|
|
122
|
-
type: Object,
|
|
123
|
-
default: () => ({})
|
|
124
|
-
},
|
|
125
|
-
extraQuery: {
|
|
126
|
-
type: Object,
|
|
127
|
-
default: () => ({})
|
|
128
|
-
},
|
|
129
|
-
showTableHeader: {
|
|
130
|
-
type: Boolean,
|
|
131
|
-
default: true,
|
|
132
|
-
},
|
|
133
|
-
dataTableComponents: {
|
|
134
|
-
type: Object,
|
|
135
|
-
default: () => ({})
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
const emit = defineEmits(['sortColumn', 'actionButtonClicked', 'actionClicked'])
|
|
140
|
-
|
|
141
|
-
const getComponent = (componentName) => props.dataTableComponents[componentName] || null
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Copia aislada de cada fila, para que un parser del modelo no pueda
|
|
145
|
-
* mutar los datos de la tabla.
|
|
146
|
-
*
|
|
147
|
-
* Se clonaba con JSON dentro de setData(), es decir una vez por celda: con
|
|
148
|
-
* 20 filas y 8 columnas eran 160 clonados en cada repintado. Un clon por
|
|
149
|
-
* fila y repintado deja lo mismo en 20.
|
|
150
|
-
*
|
|
151
|
-
* La cache que hacia eso era un WeakMap de modulo, y eso traia dos
|
|
152
|
-
* problemas propios: la compartian todas las tablas de la pagina, y un
|
|
153
|
-
* parser que escribiera en su copia la envenenaba para el resto de la vida
|
|
154
|
-
* de la aplicacion. Una computed sobre el cuerpo da el mismo ahorro sin
|
|
155
|
-
* ninguna de las dos cosas.
|
|
156
|
-
*/
|
|
157
|
-
const rows = computed(() => (props.dataTable.body ?? []).map(
|
|
158
|
-
(row) => JSON.parse(JSON.stringify(row))
|
|
159
|
-
))
|
|
160
|
-
|
|
161
|
-
const setData = (head, body, index) => {
|
|
162
|
-
|
|
163
|
-
const data = rows.value[index] ?? body
|
|
164
|
-
|
|
165
|
-
return typeof head.parser === 'function' ? head.parser(data[head.id], data) : data[head.id]
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Un popover se cierra solo: no hace falta preguntarle nada a nadie.
|
|
170
|
-
const closeDropdown = (event) => {
|
|
171
|
-
event.target.closest('[popover]')?.hidePopover()
|
|
172
|
-
}
|
|
118
|
+
/**
|
|
119
|
+
* La tabla propiamente dicha, sobre una instancia de TanStack Table.
|
|
120
|
+
*
|
|
121
|
+
* El menú de cada fila espera a conocer los permisos antes de abrirse.
|
|
122
|
+
* Antes se abría al instante con todo deshabilitado y se habilitaba cuando
|
|
123
|
+
* llegaba la respuesta —si llegaba: un fallo se reintentaba en silencio—,
|
|
124
|
+
* y el usuario veía parpadear lo que no podía hacer.
|
|
125
|
+
*/
|
|
173
126
|
|
|
174
|
-
|
|
127
|
+
import { computed } from 'vue'
|
|
128
|
+
import IconComponent from 'innoboxrr-form-elements/src/IconComponent.vue'
|
|
129
|
+
import MenuComponent from 'innoboxrr-form-elements/src/MenuComponent.vue'
|
|
130
|
+
import SkeletonComponent from 'innoboxrr-form-elements/src/SkeletonComponent.vue'
|
|
131
|
+
|
|
132
|
+
import useTheme from '../useTheme.js'
|
|
133
|
+
import { DEFAULT_LABELS, ariaSort, cellValue, componentProps, sortIcon } from '../table.js'
|
|
134
|
+
|
|
135
|
+
const SKELETON_ROWS = 5
|
|
136
|
+
|
|
137
|
+
const props = defineProps({
|
|
138
|
+
table: {
|
|
139
|
+
type: Object,
|
|
140
|
+
required: true,
|
|
141
|
+
},
|
|
142
|
+
head: {
|
|
143
|
+
type: Array,
|
|
144
|
+
default: () => [],
|
|
145
|
+
},
|
|
146
|
+
rows: {
|
|
147
|
+
type: Array,
|
|
148
|
+
default: () => [],
|
|
149
|
+
},
|
|
150
|
+
clones: {
|
|
151
|
+
type: Array,
|
|
152
|
+
default: () => [],
|
|
153
|
+
},
|
|
154
|
+
loading: {
|
|
155
|
+
type: Boolean,
|
|
156
|
+
default: false,
|
|
157
|
+
},
|
|
158
|
+
error: {
|
|
159
|
+
type: Object,
|
|
160
|
+
default: null,
|
|
161
|
+
},
|
|
162
|
+
actions: {
|
|
163
|
+
type: Boolean,
|
|
164
|
+
default: false,
|
|
165
|
+
},
|
|
166
|
+
selectable: {
|
|
167
|
+
type: Boolean,
|
|
168
|
+
default: false,
|
|
169
|
+
},
|
|
170
|
+
showTableHeader: {
|
|
171
|
+
type: Boolean,
|
|
172
|
+
default: true,
|
|
173
|
+
},
|
|
174
|
+
dataTableComponents: {
|
|
175
|
+
type: Object,
|
|
176
|
+
default: () => ({}),
|
|
177
|
+
},
|
|
178
|
+
labels: {
|
|
179
|
+
type: Object,
|
|
180
|
+
default: () => DEFAULT_LABELS,
|
|
181
|
+
},
|
|
182
|
+
orderBy: {
|
|
183
|
+
type: String,
|
|
184
|
+
default: null,
|
|
185
|
+
},
|
|
186
|
+
sort: {
|
|
187
|
+
type: Object,
|
|
188
|
+
default: () => ({}),
|
|
189
|
+
},
|
|
190
|
+
itemsFor: {
|
|
191
|
+
type: Function,
|
|
192
|
+
default: () => [],
|
|
193
|
+
},
|
|
194
|
+
prepareRow: {
|
|
195
|
+
type: Function,
|
|
196
|
+
default: async () => {},
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const emit = defineEmits(['sortColumn', 'retry'])
|
|
201
|
+
|
|
202
|
+
const theme = useTheme()
|
|
203
|
+
|
|
204
|
+
const colspan = computed(() => props.head.length + (props.selectable ? 1 : 0) + (props.actions ? 1 : 0))
|
|
175
205
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
const allSelected = computed(() => props.rows.length > 0 && props.table.getIsAllPageRowsSelected())
|
|
207
|
+
const someSelected = computed(() => props.table.getIsSomePageRowsSelected() && ! allSelected.value)
|
|
208
|
+
|
|
209
|
+
const headOf = (cell) => cell.column.columnDef.meta?.head ?? {}
|
|
210
|
+
|
|
211
|
+
const componentFor = (cell) => {
|
|
212
|
+
const name = headOf(cell).component
|
|
213
|
+
|
|
214
|
+
return name ? (props.dataTableComponents[name] ?? null) : null
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const valueFor = (cell, row) => cellValue(headOf(cell), props.clones[row.index] ?? row.original)
|
|
218
|
+
|
|
219
|
+
const onCallback = (cell, row, payload) => {
|
|
220
|
+
const callback = headOf(cell).callback
|
|
221
|
+
|
|
222
|
+
return typeof callback === 'function' ? callback(payload, row.original) : null
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
</script>
|
|
@@ -1,110 +1,95 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div :class="theme.tableFooter">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<span>{{ summaryText }}</span>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<div v-if="last > 1" :class="theme.tablePager">
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
<button
|
|
10
|
+
type="button"
|
|
11
|
+
:class="theme.iconButton"
|
|
12
|
+
:aria-label="text.previous"
|
|
13
|
+
:disabled="current <= 1"
|
|
14
|
+
@click="go(current - 1)">
|
|
15
|
+
<IconComponent name="previous" :size="14" />
|
|
16
|
+
</button>
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
<select
|
|
19
|
+
:class="theme.select"
|
|
20
|
+
:aria-label="text.page"
|
|
21
|
+
:value="current"
|
|
22
|
+
@change="go($event.target.value)">
|
|
23
|
+
<option v-for="page in last" :key="page" :value="page">{{ page }}</option>
|
|
24
|
+
</select>
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
<span>{{ text.of }} {{ last }}</span>
|
|
14
27
|
|
|
15
|
-
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
:class="theme.iconButton"
|
|
31
|
+
:aria-label="text.next"
|
|
32
|
+
:disabled="current >= last"
|
|
33
|
+
@click="go(current + 1)">
|
|
34
|
+
<IconComponent name="next" :size="14" />
|
|
35
|
+
</button>
|
|
16
36
|
|
|
17
|
-
|
|
37
|
+
</div>
|
|
18
38
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</ul>
|
|
22
|
-
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
<div class="fe-w-expand">
|
|
26
|
-
|
|
27
|
-
<ul class="fe-pagination fe-justify-end fe-mt-md" fe-mb>
|
|
28
|
-
|
|
29
|
-
<!-- Prev -->
|
|
30
|
-
<li v-if="currentPage > 1">
|
|
31
|
-
|
|
32
|
-
<a href="#" @click="prevPage()">
|
|
33
|
-
|
|
34
|
-
<span fe-page-prev></span>
|
|
35
|
-
|
|
36
|
-
</a>
|
|
37
|
-
|
|
38
|
-
</li>
|
|
39
|
-
|
|
40
|
-
<li>
|
|
41
|
-
<select
|
|
42
|
-
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
43
|
-
v-model="currentPage">
|
|
44
|
-
|
|
45
|
-
<option
|
|
46
|
-
v-for="page in meta.last_page"
|
|
47
|
-
:key="'page_' + page"
|
|
48
|
-
:value="page">{{ page }}</option>
|
|
49
|
-
|
|
50
|
-
</select>
|
|
51
|
-
</li>
|
|
52
|
-
|
|
53
|
-
<!-- Next -->
|
|
54
|
-
<li v-if="currentPage < meta.last_page">
|
|
55
|
-
|
|
56
|
-
<a href="#" @click="nextPage()">
|
|
57
|
-
|
|
58
|
-
<span fe-page-next></span>
|
|
59
|
-
|
|
60
|
-
</a>
|
|
61
|
-
|
|
62
|
-
</li>
|
|
63
|
-
|
|
64
|
-
</ul>
|
|
65
|
-
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
</div>
|
|
39
|
+
</div>
|
|
69
40
|
|
|
70
41
|
</template>
|
|
71
42
|
|
|
72
43
|
<script setup>
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
45
|
+
/**
|
|
46
|
+
* El pie de la tabla: cuántos registros se ven y en qué página se está.
|
|
47
|
+
*
|
|
48
|
+
* No guarda la página. Antes tenía su propia copia: al reiniciar los
|
|
49
|
+
* filtros el padre volvía a la 1, el paginador se enteraba después y la
|
|
50
|
+
* devolvía, y eso era una segunda petición idéntica.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { computed } from 'vue'
|
|
54
|
+
import IconComponent from 'innoboxrr-form-elements/src/IconComponent.vue'
|
|
55
|
+
|
|
56
|
+
import useTheme from '../useTheme.js'
|
|
57
|
+
import { DEFAULT_LABELS, summary } from '../table.js'
|
|
58
|
+
|
|
59
|
+
const props = defineProps({
|
|
60
|
+
meta: {
|
|
61
|
+
type: Object,
|
|
62
|
+
default: () => ({}),
|
|
63
|
+
},
|
|
64
|
+
// Se sigue aceptando para no romper a quien lo pasa; la página sale de
|
|
65
|
+
// `meta`.
|
|
66
|
+
links: {
|
|
67
|
+
type: [Array, Object],
|
|
68
|
+
default: () => [],
|
|
69
|
+
},
|
|
70
|
+
labels: {
|
|
71
|
+
type: Object,
|
|
72
|
+
default: () => ({}),
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const emit = defineEmits(['updatePage'])
|
|
77
|
+
|
|
78
|
+
const theme = useTheme()
|
|
79
|
+
|
|
80
|
+
const text = computed(() => ({ ...DEFAULT_LABELS, ...props.labels }))
|
|
81
|
+
|
|
82
|
+
const current = computed(() => Number(props.meta?.current_page ?? 1))
|
|
83
|
+
const last = computed(() => Number(props.meta?.last_page ?? 1))
|
|
84
|
+
|
|
85
|
+
const summaryText = computed(() => summary(props.meta ?? {}, text.value))
|
|
86
|
+
|
|
87
|
+
const go = (value) => {
|
|
88
|
+
const page = Number(value)
|
|
89
|
+
|
|
90
|
+
if (page >= 1 && page <= last.value && page !== current.value) {
|
|
91
|
+
emit('updatePage', page)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
109
94
|
|
|
110
95
|
</script>
|