@bagelink/vue 1.15.69 → 1.15.73

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.69",
4
+ "version": "1.15.73",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -1,7 +1,9 @@
1
1
  <script lang="ts" setup>
2
2
  import type { IconType, ThemeType } from '@bagelink/vue'
3
3
  import { Avatar, Icon } from '@bagelink/vue'
4
- import { computed } from 'vue'
4
+ import { computed, useAttrs } from 'vue'
5
+
6
+ defineOptions({ inheritAttrs: false })
5
7
 
6
8
  const props = withDefaults(
7
9
  defineProps<{
@@ -31,9 +33,8 @@ const props = withDefaults(
31
33
  /** Visually mark as selected (for non-router selection lists). */
32
34
  active?: boolean
33
35
  /** Render as an interactive row (cursor + hover) without needing a handler.
34
- Implied when `to`, `href`, or `onClick` is set. */
36
+ Implied when `to`, `href`, or a `@click` listener is set. */
35
37
  clickable?: boolean
36
- onClick?: () => void
37
38
  }>(),
38
39
  {
39
40
  ellipsis: true,
@@ -41,9 +42,34 @@ const props = withDefaults(
41
42
  }
42
43
  )
43
44
 
45
+ const attrs = useAttrs()
46
+
47
+ // Split inherited attrs: event listeners (onClick, onClickStop, …) belong on the
48
+ // clickable element so handlers + modifiers like `.stop` fire on the actual
49
+ // button/link row; everything else (class, style, data-*, …) stays on the root
50
+ // wrapper to preserve existing behavior.
51
+ const listenerAttrs = computed(() => {
52
+ const out: Record<string, any> = {}
53
+ for (const k of Object.keys(attrs)) {
54
+ if (/^on[A-Z]/.test(k)) { out[k] = (attrs as any)[k] }
55
+ }
56
+ return out
57
+ })
58
+ const rootAttrs = computed(() => {
59
+ const out: Record<string, any> = {}
60
+ for (const k of Object.keys(attrs)) {
61
+ if (!/^on[A-Z]/.test(k)) { out[k] = (attrs as any)[k] }
62
+ }
63
+ return out
64
+ })
65
+
44
66
  const hasTo = computed(() => props.to !== undefined && props.to !== '')
45
67
  const hasHref = computed(() => props.href !== undefined && props.href !== '')
46
- const isClickable = computed(() => hasTo.value || hasHref.value || props.onClick !== undefined || props.clickable === true)
68
+ // A `@click` listener (with or without modifiers like `.stop`) shows up in
69
+ // $attrs as `onClick` / `onClickStop` / etc — detect any of them so a clickable
70
+ // row is recognized even when the handler uses modifiers.
71
+ const hasClickListener = computed(() => Object.keys(attrs).some(k => /^onClick/i.test(k)))
72
+ const isClickable = computed(() => hasTo.value || hasHref.value || hasClickListener.value || props.clickable === true)
47
73
 
48
74
  const isComponent = computed(() => {
49
75
  if (hasTo.value) { return 'router-link' }
@@ -53,7 +79,10 @@ const isComponent = computed(() => {
53
79
  })
54
80
 
55
81
  const bind = computed(() => {
56
- const obj: { [key: string]: any } = {}
82
+ // Spread the inherited event listeners (including `@click`/`@click.stop`) onto
83
+ // the clickable element itself, so the handler fires on the actual button/link
84
+ // row — not the outer wrapper — and modifiers like `.stop` keep working.
85
+ const obj: { [key: string]: any } = { ...listenerAttrs.value }
57
86
  if (props.to !== undefined && props.to !== '') { obj.to = props.to }
58
87
  else if (props.href !== undefined && props.href !== '') { obj.href = props.href }
59
88
  if (props.target !== undefined && props.target !== undefined && ((props.to !== undefined && props.to !== '') || (props.href !== undefined && props.href !== ''))) { obj.target = props.target }
@@ -69,6 +98,7 @@ const bind = computed(() => {
69
98
 
70
99
  <template>
71
100
  <div
101
+ v-bind="rootAttrs"
72
102
  class="flex space-between list-item-row"
73
103
  :class="{ 'no-border-list': props.flat || rounded, 'list-item-flush': props.fullWidth, 'list-item-fullrow': isClickable, 'list-item-rounded': rounded, 'list-item-active': active }"
74
104
  :style="color ? { '--bgl-list-item-accent': `var(--bgl-${color})` } : undefined"
@@ -86,7 +116,7 @@ const bind = computed(() => {
86
116
  'py-05': props.thin,
87
117
  'px-1': !props.fullWidth,
88
118
  'px-0': props.fullWidth,
89
- }" @click="typeof onClick === 'function' && onClick()"
119
+ }"
90
120
  >
91
121
  <!-- Leading visual INSIDE the clickable area. Use #media for custom art
92
122
  (covers, thumbnails); `src`/`showAvatar` and `icon` are shortcuts. -->
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { Btn } from '@bagelink/vue'
2
+ import type { Option } from '@bagelink/vue'
3
+ import { Btn, SelectInput, useI18n } from '@bagelink/vue'
3
4
  import { computed, ref, watch, nextTick } from 'vue'
5
+ import { getOptionValue } from '../utils/options'
4
6
 
5
7
  interface Range {
6
8
  start: number
@@ -9,25 +11,38 @@ interface Range {
9
11
 
10
12
  interface PaginationProps {
11
13
  totalItems: number
12
- perPage?: number
13
14
  totalPages?: number
14
15
  variant?: 'default' | 'simple'
15
16
  rtl?: boolean
16
17
  maxVisiblePages?: number
18
+ /** When set, renders a "per page" selector with these choices. */
19
+ perPageOptions?: number[]
17
20
  }
18
21
 
19
22
  const props = withDefaults(defineProps<PaginationProps>(), {
20
23
  totalItems: 0,
21
- perPage: 25,
22
24
  totalPages: undefined,
23
25
  variant: 'default',
24
26
  rtl: false,
25
- maxVisiblePages: 3
27
+ maxVisiblePages: 3,
28
+ perPageOptions: undefined,
26
29
  })
27
30
 
31
+ const { $t } = useI18n()
32
+
28
33
  const page = defineModel<number>('page', { default: 1 })
34
+ const perPage = defineModel<number>('perPage', { default: 25 })
29
35
  const range = defineModel<Range>('range')
30
36
 
37
+ // Update how many items show per page, then jump back to the first page.
38
+ function onPerPageChange(opt: Option | Option[] | undefined) {
39
+ const v = Number(getOptionValue(Array.isArray(opt) ? opt[0] : opt))
40
+ if (!Number.isNaN(v) && v > 0) {
41
+ perPage.value = v
42
+ page.value = 1
43
+ }
44
+ }
45
+
31
46
  const paginationContainer = ref<HTMLElement>()
32
47
  const indicatorPosition = ref(0)
33
48
  const indicatorWidth = ref(0)
@@ -35,18 +50,16 @@ const indicatorWidth = ref(0)
35
50
  // Calculate totalPages from totalItems and perPage if not provided directly
36
51
  const computedTotalPages = computed(() => {
37
52
  if (props.totalPages !== undefined) { return props.totalPages }
38
- const { perPage } = props
39
- return Math.max(1, Math.ceil(props.totalItems / perPage))
53
+ return Math.max(1, Math.ceil(props.totalItems / perPage.value))
40
54
  })
41
55
 
42
56
  watch(
43
- [() => page.value, () => props.perPage, () => props.totalItems],
57
+ [() => page.value, () => perPage.value, () => props.totalItems],
44
58
  () => {
45
59
  if (range.value) {
46
- const { perPage } = props
47
60
  // Calculate zero-based indices
48
- const start = (page.value - 1) * perPage
49
- const end = Math.min(start + perPage - 1, props.totalItems - 1)
61
+ const start = (page.value - 1) * perPage.value
62
+ const end = Math.min(start + perPage.value - 1, props.totalItems - 1)
50
63
  range.value = { start, end }
51
64
  }
52
65
  // Update indicator position when page changes
@@ -187,41 +200,52 @@ const renderPageButtons = computed(() => {
187
200
  </script>
188
201
 
189
202
  <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
- }"
203
+ <div v-if="computedTotalPages > 1 || perPageOptions" class="flex gap-1 align-items-center justify-content-between">
204
+ <!-- Per-page selector -->
205
+ <label v-if="perPageOptions" class="flex gap-05 align-items-center txt14 nowrap">
206
+ <span class="color-gray">{{ $t('pagination.perPage') }}</span>
207
+ <SelectInput
208
+ thin border :options="perPageOptions" :model-value="perPage"
209
+ @update:model-value="onPerPageChange"
199
210
  />
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"
211
+ </label>
212
+
213
+ <div v-if="computedTotalPages > 1" ref="paginationContainer" class="relative flex gap-1 justify-content">
214
+ <!-- Default pagination with page numbers -->
215
+ <template v-if="variant !== 'simple'">
216
+ <div
217
+ class="indicator radius-1"
218
+ :style="{
219
+ [rtl ? 'right' : 'left']: `${indicatorPosition}px`,
220
+ width: `${indicatorWidth}px`,
221
+ }"
208
222
  />
223
+ <!-- Render the page buttons and ellipses in order -->
224
+ <template v-for="item in renderPageButtons" :key="item.key">
225
+ <!-- Page button -->
226
+ <Btn
227
+ v-if="item.type === 'page'" flat thin
228
+ :class="{ selected: item.number === page }"
229
+ :value="item.number ? item.number.toString() : ''"
230
+ @click="item.number ? handleClick(item.number) : null"
231
+ />
232
+
233
+ <!-- Ellipsis -->
234
+ <div v-else-if="item.type === 'ellipsis'" class="pagination-ellipsis">
235
+ ...
236
+ </div>
237
+ </template>
238
+ </template>
209
239
 
210
- <!-- Ellipsis -->
211
- <div v-else-if="item.type === 'ellipsis'" class="pagination-ellipsis">
212
- ...
213
- </div>
240
+ <!-- Simple pagination with prev/next buttons -->
241
+ <template v-else>
242
+ <Btn flat thin :disabled="page <= 1" icon="chevron_left" @click="prev" />
243
+ <span class="pagination-info">
244
+ {{ displayIndex(range?.start) }}-{{ displayIndex(range?.end) }} / {{ props.totalItems }}
245
+ </span>
246
+ <Btn flat thin :disabled="page >= computedTotalPages" icon="chevron_right" @click="next" />
214
247
  </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>
248
+ </div>
225
249
  </div>
226
250
  </template>
227
251
 
@@ -17,7 +17,8 @@
17
17
  "pagination": {
18
18
  "of": "of",
19
19
  "prev": "Previous",
20
- "next": "Next"
20
+ "next": "Next",
21
+ "perPage": "Per page"
21
22
  },
22
23
  "form": {
23
24
  "required": "This field is required",
@@ -17,7 +17,8 @@
17
17
  "pagination": {
18
18
  "of": "מתוך",
19
19
  "prev": "הקודם",
20
- "next": "הבא"
20
+ "next": "הבא",
21
+ "perPage": "פריטים לעמוד"
21
22
  },
22
23
  "form": {
23
24
  "required": "שדה זה נדרש",