@bagelink/vue 1.4.141 → 1.4.147

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.
Files changed (70) hide show
  1. package/dist/components/Btn.vue.d.ts.map +1 -1
  2. package/dist/components/Carousel.vue.d.ts +1 -1
  3. package/dist/components/Modal.vue.d.ts +3 -0
  4. package/dist/components/Modal.vue.d.ts.map +1 -1
  5. package/dist/components/Slider.vue.d.ts +1 -1
  6. package/dist/components/Slider.vue.d.ts.map +1 -1
  7. package/dist/components/analytics/BarChart.vue.d.ts +11 -3
  8. package/dist/components/analytics/BarChart.vue.d.ts.map +1 -1
  9. package/dist/components/analytics/LineChart.vue.d.ts +9 -0
  10. package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
  11. package/dist/components/analytics/PieChart.vue.d.ts +30 -2
  12. package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
  13. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +8 -0
  14. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
  15. package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts +9 -0
  16. package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -0
  17. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  18. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +0 -14
  19. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  20. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  21. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
  22. package/dist/components/form/inputs/RichText/index.vue.d.ts +15 -15
  23. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  24. package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -3
  25. package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
  26. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  27. package/dist/components/form/inputs/RichText/utils/media-clean.d.ts +2 -0
  28. package/dist/components/form/inputs/RichText/utils/media-clean.d.ts.map +1 -0
  29. package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -4
  30. package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
  31. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  32. package/dist/components/form/inputs/RichText/utils/table.d.ts +1 -1
  33. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  34. package/dist/components/index.d.ts +1 -0
  35. package/dist/components/index.d.ts.map +1 -1
  36. package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
  37. package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
  38. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  39. package/dist/index.cjs +123 -22
  40. package/dist/index.mjs +123 -22
  41. package/dist/style.css +1 -1
  42. package/package.json +1 -1
  43. package/src/components/Btn.vue +50 -42
  44. package/src/components/Modal.vue +49 -50
  45. package/src/components/analytics/BarChart.vue +118 -7
  46. package/src/components/analytics/KpiCard.vue +2 -2
  47. package/src/components/analytics/LineChart.vue +189 -105
  48. package/src/components/analytics/PieChart.vue +392 -49
  49. package/src/components/form/inputs/RichText/CheckList.md +23 -0
  50. package/src/components/form/inputs/RichText/components/EditorToolbar.vue +243 -38
  51. package/src/components/form/inputs/RichText/components/TableGridSelector.vue +94 -0
  52. package/src/components/form/inputs/RichText/composables/useCommands.ts +4 -1
  53. package/src/components/form/inputs/RichText/composables/useEditor.ts +6 -6
  54. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +1 -0
  55. package/src/components/form/inputs/RichText/config.ts +23 -11
  56. package/src/components/form/inputs/RichText/editor.css +300 -33
  57. package/src/components/form/inputs/RichText/index.vue +3014 -75
  58. package/src/components/form/inputs/RichText/richTextTypes.ts +2 -3
  59. package/src/components/form/inputs/RichText/utils/commands.ts +279 -50
  60. package/src/components/form/inputs/RichText/utils/media-clean.ts +0 -0
  61. package/src/components/form/inputs/RichText/utils/media.ts +133 -67
  62. package/src/components/form/inputs/RichText/utils/selection.ts +10 -2
  63. package/src/components/form/inputs/RichText/utils/table.ts +1 -1
  64. package/src/components/index.ts +1 -0
  65. package/src/components/layout/AppContent.vue +26 -26
  66. package/src/components/layout/AppLayout.vue +21 -3
  67. package/src/components/layout/AppSidebar.vue +5 -2
  68. package/src/styles/layout.css +267 -0
  69. package/src/styles/mobilLayout.css +266 -0
  70. package/src/styles/modal.css +3 -17
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.4.141",
4
+ "version": "1.4.147",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -57,8 +57,6 @@ const emit = defineEmits<{
57
57
  confirmed: [event: MouseEvent]
58
58
  }>()
59
59
 
60
- const { confirmModal } = useModal()
61
-
62
60
  const isMobileScreen = ref(false)
63
61
 
64
62
  function checkMobile() {
@@ -76,6 +74,7 @@ onUnmounted(() => {
76
74
 
77
75
  async function handleClick(event: MouseEvent) {
78
76
  if (props.confirm) {
77
+ const { confirmModal } = useModal()
79
78
  const message = typeof props.confirm === 'string' ? props.confirm : 'Are you sure?'
80
79
  const confirmed = await confirmModal({
81
80
  title: 'Confirm',
@@ -96,15 +95,15 @@ const iconSizeComputed = $computed(() => {
96
95
 
97
96
  // Default icon sizes based on button size
98
97
  const sizeMap = {
99
- xs: 0.7,
98
+ 'xs': 0.7,
100
99
  'extra-small': 0.7,
101
- s: 0.9,
102
- small: 0.9,
103
- m: 1,
104
- medium: 1,
105
- l: 1.3,
106
- large: 1.3,
107
- xl: 1.6,
100
+ 's': 0.9,
101
+ 'small': 0.9,
102
+ 'm': 1,
103
+ 'medium': 1,
104
+ 'l': 1.3,
105
+ 'large': 1.3,
106
+ 'xl': 1.6,
108
107
  'extra-large': 1.6
109
108
  }
110
109
 
@@ -138,36 +137,44 @@ const slots: SetupContext['slots'] = useSlots()
138
137
  </script>
139
138
 
140
139
  <template>
141
- <component :is="isComponent" v-ripple="ripple" v-bind="bind" :disabled="disabled" class="bgl_btn" :class="{
142
- 'bgl_btn-icon': icon && !slots.default && !value,
143
- thin,
144
- 'bgl_btn_xsSize': size === 'xs' || size === 'extra-small',
145
- 'bgl_btn_sSize': size === 's' || size === 'small',
146
- 'bgl_btn_mSize': size === 'm' || size === 'medium',
147
- 'bgl_btn_lSize': size === 'l' || size === 'large',
148
- 'bgl_btn_xlSize': size === 'xl' || size === 'extra-large',
149
- 'bgl_btn_fullWidth': fullWidth,
150
- 'bgl_btn_fullWidthMobile': fullWidthMobile,
151
- 'bgl_btn_alignStart': alignTxt === 'start',
152
- 'bgl_btn_alignEnd': alignTxt === 'end',
153
- 'bgl_btn_alignStartMobile': alignTxtMobile === 'start',
154
- 'bgl_btn_alignEndMobile': alignTxtMobile === 'end',
155
- round,
156
- 'bgl_btn_flat': flat,
157
- 'bgl_btn-border': border || outline,
158
- [`bgl_btn-${color}`]: color,
159
- [`bgl_btn-${theme}`]: theme,
160
- }" :tabindex="disabled ? -1 : 0" @click.stop="handleClick" @keydown.enter="handleClick" @keydown.space="handleClick">
161
- <Loading v-if="loading" class="h-100p" size="15" />
162
- <div v-else class="bgl_btn-flex">
163
- <Icon v-if="icon" :icon="icon" class="transition-400" :size="iconSizeComputed" :mobile-size="iconMobileSize" />
164
- <slot />
165
- <template v-if="!slots.default && value">
166
- {{ value }}
167
- </template>
168
- <Icon v-if="iconEnd" :icon="iconEnd" class="transition-400" :size="iconSizeComputed" :mobile-size="iconMobileSize" />
169
- </div>
170
- </component>
140
+ <component
141
+ :is="isComponent" v-ripple="ripple" v-bind="bind" :disabled="disabled" class="bgl_btn" :class="{
142
+ 'bgl_btn-icon': icon && !slots.default && !value,
143
+ thin,
144
+ 'bgl_btn_xsSize': size === 'xs' || size === 'extra-small',
145
+ 'bgl_btn_sSize': size === 's' || size === 'small',
146
+ 'bgl_btn_mSize': size === 'm' || size === 'medium',
147
+ 'bgl_btn_lSize': size === 'l' || size === 'large',
148
+ 'bgl_btn_xlSize': size === 'xl' || size === 'extra-large',
149
+ 'bgl_btn_fullWidth': fullWidth,
150
+ 'bgl_btn_fullWidthMobile': fullWidthMobile,
151
+ 'bgl_btn_alignStart': alignTxt === 'start',
152
+ 'bgl_btn_alignEnd': alignTxt === 'end',
153
+ 'bgl_btn_alignStartMobile': alignTxtMobile === 'start',
154
+ 'bgl_btn_alignEndMobile': alignTxtMobile === 'end',
155
+ round,
156
+ 'bgl_btn_flat': flat,
157
+ 'bgl_btn-border': border || outline,
158
+ [`bgl_btn-${color}`]: color,
159
+ [`bgl_btn-${theme}`]: theme,
160
+ }" :tabindex="disabled ? -1 : 0" @click.stop="handleClick" @keydown.enter="handleClick" @keydown.space="handleClick"
161
+ >
162
+ <Loading v-if="loading" class="h-100p" size="15" />
163
+ <div v-else class="bgl_btn-flex">
164
+ <Icon
165
+ v-if="icon" :icon="icon" class="transition-400" :size="iconSizeComputed"
166
+ :mobile-size="iconMobileSize"
167
+ />
168
+ <slot />
169
+ <template v-if="!slots.default && value">
170
+ {{ value }}
171
+ </template>
172
+ <Icon
173
+ v-if="iconEnd" :icon="iconEnd" class="transition-400" :size="iconSizeComputed"
174
+ :mobile-size="iconMobileSize"
175
+ />
176
+ </div>
177
+ </component>
171
178
  </template>
172
179
 
173
180
  <style scoped>
@@ -232,6 +239,7 @@ a {
232
239
  .bgl_btn-icon.bgl_btn_flat:active:not(:disabled) {
233
240
  background: var(--bgl-gray-tint-dark);
234
241
  }
242
+
235
243
  .bgl_btn.round {
236
244
  border-radius: 1000px !important;
237
245
  }
@@ -265,6 +273,7 @@ a {
265
273
  filter: grayscale(0.3);
266
274
  cursor: not-allowed;
267
275
  }
276
+
268
277
  .bgl_btn-icon .bgl_btn-flex {
269
278
  height: 100%;
270
279
  }
@@ -282,7 +291,6 @@ a {
282
291
  line-height: normal;
283
292
  }
284
293
 
285
-
286
294
  .bgl_btn_xsSize {
287
295
  height: calc(var(--btn-height) / 2);
288
296
  line-height: calc(var(--btn-height) / 2);
@@ -350,6 +358,7 @@ a {
350
358
  height: calc(var(--btn-height) * 1.5);
351
359
  width: calc(var(--btn-height) * 1.5);
352
360
  }
361
+
353
362
  .bgl_btn_fullWidth {
354
363
  width: 100%;
355
364
  }
@@ -358,7 +367,6 @@ a {
358
367
  width: auto;
359
368
  }
360
369
 
361
-
362
370
  /* Content alignment styles */
363
371
  .bgl_btn_alignStart .bgl_btn-flex {
364
372
  justify-content: flex-start;
@@ -4,8 +4,7 @@ import type { SetupContext } from 'vue'
4
4
  import {
5
5
  Btn,
6
6
  Card,
7
- Title,
8
- useEscape
7
+ Title
9
8
  } from '@bagelink/vue'
10
9
  import {
11
10
  onMounted,
@@ -24,6 +23,7 @@ interface ModalProps {
24
23
  actions?: BtnOptions[]
25
24
  visible?: boolean
26
25
  zIndex?: number
26
+ closePlacement?: 'header' | 'header-end' | 'overlay' | 'overlay-end' | 'none' | 'footer'
27
27
  }
28
28
  const props = withDefaults(defineProps<ModalProps>(), {
29
29
  thin: false,
@@ -31,6 +31,7 @@ const props = withDefaults(defineProps<ModalProps>(), {
31
31
  dismissable: true,
32
32
  visible: false,
33
33
  zIndex: 99,
34
+ closePlacement: 'header'
34
35
  })
35
36
 
36
37
  const emit = defineEmits(['update:visible'])
@@ -56,6 +57,17 @@ const maxWidth = $computed(() => {
56
57
  return { 'max-width': '720px' }
57
58
  })
58
59
 
60
+ // Computed properties for close button placement
61
+ const isOverlay = $computed(() => props.closePlacement === 'overlay' || props.closePlacement === 'overlay-end')
62
+ const isHeader = $computed(() => props.closePlacement === 'header' || props.closePlacement === 'header-end')
63
+ const isFooter = $computed(() => props.closePlacement === 'footer')
64
+ const isNone = $computed(() => props.closePlacement === 'none')
65
+
66
+ const overlayCloseClass = $computed(() => {
67
+ if (props.closePlacement === 'overlay-end') return 'top-1 end-1'
68
+ return 'top-1 start-1'
69
+ })
70
+
59
71
  function closeModal() {
60
72
  isVisible = false
61
73
  setTimeout(() => { emit('update:visible', false) }, 200)
@@ -63,7 +75,11 @@ function closeModal() {
63
75
 
64
76
  defineExpose({ closeModal })
65
77
 
66
- const escapeKeyClose = (e: KeyboardEvent) => props.dismissable && useEscape(e, () => { closeModal() })
78
+ const escapeKeyClose = (e: KeyboardEvent) => {
79
+ if (props.dismissable && e.key === 'Escape') {
80
+ closeModal()
81
+ }
82
+ }
67
83
 
68
84
  function openModal() {
69
85
  setTimeout(() => (isVisible = true), 1)
@@ -77,64 +93,43 @@ onUnmounted(() => {
77
93
  </script>
78
94
 
79
95
  <template>
80
- <div
81
- class="bg-dark" :style="{ zIndex }" :class="{ 'is-side': side, 'is-active': isVisible, 'bg-lignt': false }"
82
- @click="() => (dismissable ? closeModal() : '')" @keydown.esc="closeModal"
83
- >
84
- <Card class="modal" :style="{ ...maxWidth }" :thin="thin" @click.stop>
85
- <header v-if="slots.toolbar || title" class="tool-bar">
96
+ <div class="bg-dark" :style="{ zIndex }" :class="{ 'is-side': side, 'is-active': isVisible, 'bg-lignt': false }" @click="() => (dismissable ? closeModal() : '')" @keydown.esc="closeModal">
97
+ <!-- Overlay close button -->
98
+ <Btn v-if="dismissable && isOverlay" icon="close" icon-mobile-size="1.4" class="fixed" :class="overlayCloseClass" @click="closeModal" />
99
+
100
+ <Card class="modal m_pt-0" :style="{ ...maxWidth }" :thin="thin" @click.stop :class="{ 'pt-0': thin, 'pt-1': !thin, 'm_mt-5': isOverlay, 'display-flex column': side }">
101
+ <header v-if="slots.toolbar || title" class="tool-bar w-100p flex space-between sticky z-3 py-1">
102
+ <!-- Header close button -->
103
+ <Btn v-if="dismissable && isHeader && closePlacement === 'header'" :style="{ float: side ? '' : '' }" flat icon="close" thin icon-mobile-size="1.4" @click="closeModal" />
104
+ <slot name="toolbar" />
105
+ <Title v-if="title" class="modal-title txt-center txt20 medium my-0 w-100p ellipsis-1" tag="h3" :label="title"
106
+ :class="{ 'me-1-5': isHeader && dismissable && closePlacement === 'header', 'ms-1-5': isHeader && dismissable && closePlacement === 'header-end' }" />
107
+ <!-- Header-end close button -->
108
+ <Btn v-if="dismissable && isHeader && closePlacement === 'header-end'" :style="{ float: side ? '' : '' }" flat icon="close" thin icon-mobile-size="1.4" @click="closeModal" />
109
+ </header>
110
+
111
+ <header v-else class="tool-bar w-100p flex space-between sticky z-3" :class="{ 'py-1': !isOverlay, 'pt-1': isOverlay }">
112
+ <!-- Header close button (no title) -->
113
+ <Btn v-if="dismissable && isHeader && closePlacement === 'header'" :style="{ float: side ? '' : '' }" flat icon="close" thin icon-mobile-size="1.4" @click="closeModal" />
86
114
  <slot name="toolbar" />
87
- <Btn
88
- v-if="dismissable" :style="{ float: side ? '' : '' }" flat icon="close" thin icon-mobile-size="1.4"
89
- @click="closeModal"
90
- />
91
- <Title v-if="title" class="modal-title" tag="h3" :label="title" />
115
+ <!-- Header-end close button (no title) -->
116
+ <Btn v-if="dismissable && isHeader && closePlacement === 'header-end'" :style="{ float: side ? '' : '' }" flat icon="close" thin icon-mobile-size="1.4" class="ms-auto" @click="closeModal" />
92
117
  </header>
93
-
94
- <div v-else class="sticky z-index-999 -mt-1 -ms-1 px-025 h-30px pt-025 modal-no-title">
95
- <Btn
96
- v-if="dismissable" class="position-start" icon="close" thin round color="white"
97
- icon-mobile-size="1.4" @click="closeModal"
98
- />
99
- </div>
118
+
100
119
  <slot />
101
- <footer v-if="slots.footer || actions?.length" class="modal-footer mt-1">
120
+ <footer v-if="slots.footer || actions?.length" class="modal-footer gap-1 flex space-between" :class="{ 'mt-1': !side, 'mt-auto': side }">
102
121
  <Btn v-for="(action, i) in actions" :key="i" color="gray" v-bind="action" @click="closeModal" />
103
122
  <slot name="footer" />
123
+ <!-- Footer close button -->
104
124
  </footer>
105
- </Card>
106
- </div>
125
+ <Btn v-if="dismissable && isFooter" icon="close" label="Close" class="mx-auto absolute start-0 end-0 modalFooterBtn" @click="closeModal" />
126
+ </Card>
127
+ </div>
107
128
  </template>
108
129
 
109
130
  <style>
110
131
  .modal {
111
132
  color: var(--bgl-popup-text);
112
- /* display: flex;
113
- flex-direction: column; */
114
- }
115
-
116
- .modal-title {
117
- text-align: center;
118
- font-weight: 600;
119
- font-size: 20px;
120
- margin-inline-end: 2rem;
121
- margin-top: 0.5rem;
122
- margin-bottom: 0 !important;
123
- width: 100%;
124
- line-height: 2;
125
- display: -webkit-box;
126
- max-width: 100%;
127
- -webkit-line-clamp: 1;
128
- -webkit-box-orient: vertical;
129
- overflow: hidden;
130
- text-overflow: ellipsis;
131
- }
132
-
133
- .modal-footer {
134
- gap: 1rem;
135
- display: flex;
136
- justify-content: space-between;
137
- align-items: center;
138
133
  }
139
134
 
140
135
  .modal-footer>div {
@@ -147,6 +142,10 @@ onUnmounted(() => {
147
142
  .modal-no-title {
148
143
  width: calc(100% + 2rem);
149
144
  border-radius: var(--card-border-radius);
145
+ }
146
+
147
+ .modalFooterBtn {
148
+ bottom: calc(var(--btn-height) / 2 * -1);
150
149
 
151
150
  }
152
151
  </style>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { Icon, Loading } from '@bagelink/vue'
3
- import { computed } from 'vue'
3
+ import { computed, ref, onMounted, onUnmounted } from 'vue'
4
4
 
5
5
  interface SecondaryValue {
6
6
  label: string
@@ -29,6 +29,9 @@ interface Props {
29
29
  maxBars?: number
30
30
  loading?: boolean
31
31
  rtl?: boolean
32
+ animated?: boolean
33
+ animationDuration?: number
34
+ animationStartDelay?: number
32
35
  }
33
36
 
34
37
  const props = withDefaults(defineProps<Props>(), {
@@ -41,8 +44,18 @@ const props = withDefaults(defineProps<Props>(), {
41
44
  maxBars: 30,
42
45
  loading: false,
43
46
  rtl: false,
47
+ animated: true,
48
+ animationDuration: 1500,
49
+ animationStartDelay: 0,
44
50
  })
45
51
 
52
+ // Animation state
53
+ const animatedProgress = ref(0)
54
+ const isAnimating = ref(false)
55
+ const isInView = ref(false)
56
+ const observer = ref<IntersectionObserver | null>(null)
57
+ const chartRef = ref<HTMLElement | null>(null)
58
+
46
59
  const chartData = computed(() => {
47
60
  if (!props.data || props.data.length === 0) return []
48
61
 
@@ -56,6 +69,94 @@ const chartData = computed(() => {
56
69
  }))
57
70
  })
58
71
 
72
+ // Animation computed properties
73
+ const getBarOpacity = computed(() => {
74
+ return (index: number) => {
75
+ if (!props.animated) return 1
76
+ if (!isInView.value) return 0
77
+
78
+ const totalBars = chartData.value.length
79
+
80
+ // Each bar appears with a delay based on its index
81
+ const barDelay = index / totalBars
82
+ const progress = Math.max(0, Math.min(1, (animatedProgress.value - barDelay) * totalBars))
83
+
84
+ return progress
85
+ }
86
+ })
87
+
88
+ // Animation functions
89
+ function easeOutCubic(t: number): number {
90
+ return 1 - Math.pow(1 - t, 3)
91
+ }
92
+
93
+ function startAnimation() {
94
+ if (isAnimating.value || !props.animated) return
95
+
96
+ console.log(`🎯 TrendChart: Starting animation with ${props.animationDuration}ms duration`)
97
+ isAnimating.value = true
98
+ animatedProgress.value = 0
99
+
100
+ const startTime = performance.now()
101
+
102
+ function animate(currentTime: number) {
103
+ const elapsed = currentTime - startTime
104
+ const progress = Math.min(elapsed / props.animationDuration, 1)
105
+ const easedProgress = easeOutCubic(progress)
106
+
107
+ animatedProgress.value = easedProgress
108
+
109
+ if (progress < 1) {
110
+ requestAnimationFrame(animate)
111
+ } else {
112
+ isAnimating.value = false
113
+ console.log(`✅ TrendChart: Animation completed`)
114
+ }
115
+ }
116
+
117
+ requestAnimationFrame(animate)
118
+ }
119
+
120
+ function setupIntersectionObserver() {
121
+ if (!chartRef.value || observer.value) return
122
+
123
+ observer.value = new IntersectionObserver(
124
+ (entries) => {
125
+ entries.forEach(entry => {
126
+ if (entry.isIntersecting && !isInView.value) {
127
+ console.log(`👀 TrendChart: Entered viewport, starting animation in ${props.animationStartDelay}ms`)
128
+ isInView.value = true
129
+ setTimeout(() => {
130
+ startAnimation()
131
+ }, props.animationStartDelay)
132
+ }
133
+ })
134
+ },
135
+ {
136
+ threshold: 0.3,
137
+ rootMargin: '50px'
138
+ }
139
+ )
140
+
141
+ observer.value.observe(chartRef.value)
142
+ }
143
+
144
+ onMounted(() => {
145
+ if (props.animated) {
146
+ setupIntersectionObserver()
147
+ } else {
148
+ // If not animated, show all bars immediately
149
+ isInView.value = true
150
+ animatedProgress.value = 1
151
+ }
152
+ })
153
+
154
+ onUnmounted(() => {
155
+ if (observer.value) {
156
+ observer.value.disconnect()
157
+ }
158
+ })
159
+
59
160
  function formatDate(dateStr: string): string {
60
161
  const date = new Date(dateStr)
61
162
  return date.toLocaleDateString('he-IL', {
@@ -79,7 +180,7 @@ function formatTooltip(item: any): string {
79
180
  const primaryValue = formatValue(item.value, props.currency)
80
181
  const primaryText = `${props.prefix}${primaryValue}${props.suffix}`
81
182
 
82
- let tooltipLines = [`${item.displayLabel}`, `הכנסות: <b>${primaryText}</b>`]
183
+ let tooltipLines = [`${item.displayLabel}`, `<b>${primaryText}</b>`]
83
184
 
84
185
  if (item.secondaryValues && Array.isArray(item.secondaryValues)) {
85
186
  item.secondaryValues.forEach((secondary: SecondaryValue) => {
@@ -94,14 +195,20 @@ function formatTooltip(item: any): string {
94
195
  </script>
95
196
 
96
197
  <template>
97
- <div class="h-100p flex column flex-stretch">
98
- <div class="flex space-between">
99
- <div class="flex align-center gap-05 pb-1">
100
- <Icon :name="icon" size="1.2" :color="color" class="line-height-08" />
198
+ <div ref="chartRef" class="h-100p flex column flex-stretch">
199
+ <div class="flex space-between pb-1">
200
+ <div class="flex align-center gap-05">
201
+ <Icon :name="icon" size="1.2" :color="color" class="line-height-0" />
101
202
  <p class="white-space light m_txt14">
102
203
  {{ title }}
103
204
  </p>
104
205
  </div>
206
+ <div v-if="percentageChange !== 0" class="flex align-center gap-025">
207
+ <Icon :name="percentageChange > 0 ? 'trending_up' : 'trending_down'" size="1" :class="percentageChange > 0 ? 'color-success' : 'color-danger'" />
208
+ <span class="txt12 bold" :class="percentageChange > 0 ? 'color-success' : 'color-danger'">
209
+ {{ Math.abs(percentageChange) }}%
210
+ </span>
211
+ </div>
105
212
  </div>
106
213
  <div class="flex w-100p align-items-end mt-auto gap-075 ltr overflow justify-content-start">
107
214
  <div
@@ -109,7 +216,11 @@ function formatTooltip(item: any): string {
109
216
  :key="index"
110
217
  v-tooltip="{ content: formatTooltip(bar), html: true }"
111
218
  class="flex-grow txt-center hover transition-400 relative barWrap mb-1"
112
- :style="{ width: `max(2rem, ${100 / chartData.length}%)` }"
219
+ :style="{
220
+ width: `max(2rem, ${100 / chartData.length}%)`,
221
+ opacity: getBarOpacity(index),
222
+ transition: animated ? 'opacity 0.3s ease-out' : 'none'
223
+ }"
113
224
  >
114
225
  <div
115
226
  class="bar radius-05 transition-400 "
@@ -50,8 +50,8 @@ const trendColor = computed(() => isIncreasing.value ? 'var(--bgl-green)' : 'var
50
50
  <template>
51
51
  <Card class=" flex column space-between align-items-start py-1 px-1-5 m_p-1 relative ">
52
52
  <div class="mb-1 flex space-between align-items-start m_mb-05 w-100p">
53
- <div class="flex gap-025 align-items-start">
54
- <Icon :name="icon" size="1" :color="color" class="line-height-08" weight="300" />
53
+ <div class="flex gap-025">
54
+ <Icon :name="icon" size="1" :color="color" class="line-height-0" weight="300" />
55
55
  <div>
56
56
  <h3 class="txt14 m-0 line-height-12 light opacity-6">
57
57
  {{ title }}