@bagelink/vue 1.2.89 → 1.2.93

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/dist/style.css CHANGED
@@ -4368,6 +4368,27 @@ body:has(.bg-dark.is-active) {
4368
4368
  }
4369
4369
  }
4370
4370
 
4371
+ .indicator[data-v-0443aea2] {
4372
+ position: absolute;
4373
+ height: 30px;
4374
+ background-color: var(--bgl-primary);
4375
+ transition: all 0.3s ease;
4376
+ z-index: -1;
4377
+ }
4378
+ .selected[data-v-0443aea2] {
4379
+ color: white !important;
4380
+ }
4381
+ .pagination-info[data-v-0443aea2] {
4382
+ min-width: 60px;
4383
+ text-align: center;
4384
+ font-size: 14px;
4385
+ }
4386
+ .pagination-ellipsis[data-v-0443aea2] {
4387
+ display: flex;
4388
+ align-items: center;
4389
+ padding: 0 4px;
4390
+ }
4391
+
4371
4392
  .bgl_pill-btn[data-v-764b6b8b]{
4372
4393
  color: var(--pill-btn-color);
4373
4394
  background: var(--pill-btn-bg);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.2.89",
4
+ "version": "1.2.93",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -0,0 +1,252 @@
1
+ <script setup lang="ts">
2
+ import { Btn } from '@bagelink/vue'
3
+ import { watch, nextTick } from 'vue'
4
+
5
+ interface Range {
6
+ start: number
7
+ end: number
8
+ }
9
+
10
+ interface PaginationProps {
11
+ totalItems: number
12
+ perPage?: number
13
+ totalPages?: number
14
+ variant?: 'default' | 'simple'
15
+ rtl?: boolean
16
+ maxVisiblePages?: number
17
+ }
18
+
19
+ const props = withDefaults(defineProps<PaginationProps>(), {
20
+ totalItems: 0,
21
+ perPage: 25,
22
+ totalPages: undefined,
23
+ variant: 'default',
24
+ rtl: false,
25
+ maxVisiblePages: 3
26
+ })
27
+
28
+ const page = defineModel<number>('page', { default: 1 })
29
+ const range = defineModel<Range>('range')
30
+
31
+ const paginationContainer = $ref<HTMLElement>()
32
+ let indicatorPosition = $ref(0)
33
+ let indicatorWidth = $ref(0)
34
+
35
+ // Calculate totalPages from totalItems and perPage if not provided directly
36
+ const computedTotalPages = $computed(() => {
37
+ if (props.totalPages !== undefined) return props.totalPages
38
+ const { perPage } = props
39
+ return Math.max(1, Math.ceil(props.totalItems / perPage))
40
+ })
41
+
42
+ watch(
43
+ [() => page.value, () => props.perPage, () => props.totalItems],
44
+ () => {
45
+ if (range.value) {
46
+ const { perPage } = props
47
+ // Calculate zero-based indices
48
+ const start = (page.value - 1) * perPage
49
+ const end = Math.min(start + perPage - 1, props.totalItems - 1)
50
+ range.value = { start, end }
51
+ }
52
+ // Update indicator position when page changes
53
+ nextTick(() => { updateIndicatorPosition() })
54
+ },
55
+ { immediate: true }
56
+ )
57
+
58
+ // Also watch for RTL changes
59
+ watch(() => props.rtl, updateIndicatorPosition)
60
+
61
+ // Calculate which page numbers to show
62
+ const visiblePages = $computed(() => {
63
+ const { maxVisiblePages } = props
64
+
65
+ // If we have few enough pages, show all of them
66
+ if (computedTotalPages <= maxVisiblePages * 2) {
67
+ return Array.from({ length: computedTotalPages }, (_, i) => i + 1)
68
+ }
69
+
70
+ // Always include current page and adjacent pages for navigation
71
+ const mustInclude = new Set<number>()
72
+
73
+ // Current page is always included
74
+ mustInclude.add(page.value)
75
+
76
+ // Ensure adjacent pages are included for navigation
77
+ if (page.value > 1) mustInclude.add(page.value - 1)
78
+ if (page.value < computedTotalPages) mustInclude.add(page.value + 1)
79
+
80
+ // Always include first and last pages
81
+ mustInclude.add(1)
82
+ mustInclude.add(computedTotalPages)
83
+
84
+ // Start with explicitly required pages
85
+ const pageArray = Array.from(mustInclude).sort((a, b) => a - b)
86
+
87
+ // Now fill remaining slots if there's room within maxVisiblePages
88
+ if (pageArray.length < maxVisiblePages + 2) {
89
+ // Try to add pages between existing ones
90
+ for (let i = 0; i < pageArray.length - 1; i++) {
91
+ const current = pageArray[i]
92
+ const next = pageArray[i + 1]
93
+
94
+ if (next - current > 1) {
95
+ // There's a gap - fill in additional pages
96
+ const pagesToAdd = Math.min(next - current - 1, maxVisiblePages + 2 - pageArray.length)
97
+
98
+ const newPages = Array.from(
99
+ { length: pagesToAdd },
100
+ (_, idx) => current + idx + 1
101
+ )
102
+
103
+ pageArray.splice(i + 1, 0, ...newPages)
104
+ i += newPages.length // Skip past inserted items
105
+ }
106
+ }
107
+ }
108
+
109
+ return pageArray
110
+ })
111
+
112
+ function updateIndicatorPosition() {
113
+ if (!paginationContainer) return
114
+
115
+ const selectedButton = paginationContainer.querySelector('.selected')
116
+ if (!selectedButton) return
117
+
118
+ const containerRect = paginationContainer.getBoundingClientRect()
119
+ const buttonRect = selectedButton.getBoundingClientRect()
120
+
121
+ // Get position and dimensions
122
+ indicatorWidth = buttonRect.width
123
+
124
+ if (props.rtl) {
125
+ // RTL positioning - align to right edge
126
+ const rightOffset = containerRect.right - buttonRect.right
127
+ indicatorPosition = rightOffset
128
+ } else {
129
+ // LTR positioning - align to left edge
130
+ const leftOffset = buttonRect.left - containerRect.left
131
+ indicatorPosition = leftOffset
132
+ }
133
+ }
134
+
135
+ function handleClick(p: number) {
136
+ if (p < 1 || p > computedTotalPages) return
137
+ page.value = p
138
+ }
139
+
140
+ function next() {
141
+ handleClick(page.value + 1)
142
+ }
143
+
144
+ function prev() {
145
+ handleClick(page.value - 1)
146
+ }
147
+
148
+ // Convert zero-based index to one-based for display
149
+ function displayIndex(index: number | undefined): string {
150
+ return index === undefined ? '-' : (index + 1).toString()
151
+ }
152
+
153
+ interface PageItem {
154
+ type: 'page' | 'ellipsis'
155
+ key: string | number
156
+ number?: number
157
+ }
158
+
159
+ // Enhanced rendering - creates UI with ellipses in the right positions
160
+ const renderPageButtons = $computed(() => {
161
+ const items: PageItem[] = []
162
+
163
+ for (let i = 0; i < visiblePages.length; i++) {
164
+ const pageNum = visiblePages[i]
165
+
166
+ // Insert ellipsis before this page if needed
167
+ if (i > 0) {
168
+ const prevPage = visiblePages[i - 1]
169
+ if (pageNum - prevPage > 1) {
170
+ items.push({
171
+ type: 'ellipsis',
172
+ key: `ellipsis-${i}`
173
+ })
174
+ }
175
+ }
176
+
177
+ // Add the page button
178
+ items.push({
179
+ type: 'page',
180
+ number: pageNum,
181
+ key: pageNum
182
+ })
183
+ }
184
+
185
+ return items
186
+ })
187
+ </script>
188
+
189
+ <template>
190
+ <div v-if="computedTotalPages > 1" ref="paginationContainer" class="relative flex gap-1 justify-content">
191
+ <!-- Default pagination with page numbers -->
192
+ <template v-if="variant !== 'simple'">
193
+ <div
194
+ class="indicator radius-1"
195
+ :style="{
196
+ [rtl ? 'right' : 'left']: `${indicatorPosition}px`,
197
+ width: `${indicatorWidth}px`,
198
+ }"
199
+ />
200
+ <!-- Render the page buttons and ellipses in order -->
201
+ <template v-for="item in renderPageButtons" :key="item.key">
202
+ <!-- Page button -->
203
+ <Btn
204
+ v-if="item.type === 'page'" flat thin
205
+ :class="{ selected: item.number === page }"
206
+ :value="item.number ? item.number.toString() : ''"
207
+ @click="item.number ? handleClick(item.number) : null"
208
+ />
209
+
210
+ <!-- Ellipsis -->
211
+ <div v-else-if="item.type === 'ellipsis'" class="pagination-ellipsis">
212
+ ...
213
+ </div>
214
+ </template>
215
+ </template>
216
+
217
+ <!-- Simple pagination with prev/next buttons -->
218
+ <template v-else>
219
+ <Btn flat thin :disabled="page <= 1" icon="chevron_left" @click="prev" />
220
+ <span class="pagination-info">
221
+ {{ displayIndex(range?.start) }}-{{ displayIndex(range?.end) }} / {{ props.totalItems }}
222
+ </span>
223
+ <Btn flat thin :disabled="page >= computedTotalPages" icon="chevron_right" @click="next" />
224
+ </template>
225
+ </div>
226
+ </template>
227
+
228
+ <style scoped>
229
+ .indicator {
230
+ position: absolute;
231
+ height: 30px;
232
+ background-color: var(--bgl-primary);
233
+ transition: all 0.3s ease;
234
+ z-index: -1;
235
+ }
236
+
237
+ .selected {
238
+ color: white !important;
239
+ }
240
+
241
+ .pagination-info {
242
+ min-width: 60px;
243
+ text-align: center;
244
+ font-size: 14px;
245
+ }
246
+
247
+ .pagination-ellipsis {
248
+ display: flex;
249
+ align-items: center;
250
+ padding: 0 4px;
251
+ }
252
+ </style>
@@ -32,6 +32,7 @@ export { default as ModalConfirm } from './ModalConfirm.vue'
32
32
  export { default as ModalForm } from './ModalForm.vue'
33
33
  export { default as NavBar } from './NavBar.vue'
34
34
  export { default as PageTitle } from './PageTitle.vue'
35
+ export { default as Pagination } from './Pagination.vue'
35
36
  export { default as Pill } from './Pill.vue'
36
37
  export { default as RouterWrapper } from './RouterWrapper.vue'
37
38
  export { default as Slider } from './Slider.vue'