@bagelink/vue 0.0.1117 → 0.0.1121

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
@@ -1244,14 +1244,14 @@ pre code.hljs{
1244
1244
  direction: ltr;
1245
1245
  }
1246
1246
 
1247
- .txtInputIconStart .iconStart[data-v-f265687e] {
1247
+ .txtInputIconStart .iconStart[data-v-4a70ce50] {
1248
1248
  color: var(--input-color);
1249
1249
  position: absolute;
1250
1250
  inset-inline-start:calc(var(--input-height) / 3 - 0.25rem);
1251
1251
  margin-top: calc(var(--input-height) / 2 );
1252
1252
  line-height: 0;
1253
1253
  }
1254
- .textInputSpinnerWrap .spinner[data-v-f265687e] {
1254
+ .textInputSpinnerWrap .spinner[data-v-4a70ce50] {
1255
1255
  color: var(--input-color);
1256
1256
  position: absolute;
1257
1257
  inset-inline-end: 0;
@@ -1261,18 +1261,18 @@ pre code.hljs{
1261
1261
  flex-direction: column;
1262
1262
  gap: 0;
1263
1263
  }
1264
- .top-bgl-ctrl-num-btn[data-v-f265687e]{
1264
+ .top-bgl-ctrl-num-btn[data-v-4a70ce50]{
1265
1265
  margin-top: calc(var(--input-height) / 10) !important;
1266
1266
  }
1267
- .bgl-ctrl-num-btn[data-v-f265687e]{
1267
+ .bgl-ctrl-num-btn[data-v-4a70ce50]{
1268
1268
  height: calc(var(--input-height) / 2.5) !important;
1269
1269
  isolation: isolate;
1270
1270
  }
1271
- .bgl-big-ctrl-num-btn[data-v-f265687e]{
1271
+ .bgl-big-ctrl-num-btn[data-v-4a70ce50]{
1272
1272
  width: 100% !important;
1273
1273
  isolation: isolate;
1274
1274
  }
1275
- .bgl-number-input[data-v-f265687e]{
1275
+ .bgl-number-input[data-v-4a70ce50]{
1276
1276
  padding-inline-end: 1.75rem !important;
1277
1277
  }
1278
1278
 
@@ -1435,7 +1435,7 @@ input[type="range"][data-v-25d991e5]:active::-webkit-slider-thumb {
1435
1435
  background: var(--bgl-primary-light);
1436
1436
  }
1437
1437
 
1438
- .toolbar[data-v-33dff8fa] .active {
1438
+ .toolbar[data-v-bcd681b9] .active {
1439
1439
  background: var(--bgl-primary);
1440
1440
  color: white;
1441
1441
  }
@@ -2123,12 +2123,15 @@ line-height: 1.65;
2123
2123
  color: var(--bgl-green);
2124
2124
  }
2125
2125
 
2126
- .img-web-kit[data-v-1b7f9bb3] {
2126
+ .img-web-kit[data-v-6f277b5b] {
2127
2127
  max-width: 100%;
2128
2128
  vertical-align: middle;
2129
2129
  border: 0;
2130
2130
  width: 100%;
2131
2131
  }
2132
+ .error-image[data-v-6f277b5b] {
2133
+ background-color: var(--skeleton-bg);
2134
+ }
2132
2135
 
2133
2136
  .bgl_bottombar .bgl_btn-flex{
2134
2137
  flex-direction: column;
@@ -3336,11 +3339,11 @@ body:has(.bg-dark.is-active) {
3336
3339
  border-radius: var(--card-border-radius);
3337
3340
  }
3338
3341
 
3339
- .modal-title[data-v-7361477b] {
3342
+ .modal-title[data-v-f796c518] {
3340
3343
  margin-top: 0.5rem;
3341
3344
  }
3342
3345
  @media screen and (max-width: 910px) {
3343
- .modal-title[data-v-7361477b] {
3346
+ .modal-title[data-v-f796c518] {
3344
3347
  margin-top: 1rem;
3345
3348
  }
3346
3349
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "0.0.1117",
4
+ "version": "0.0.1121",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -1,83 +1,112 @@
1
1
  <script setup lang="ts">
2
- import { Skeleton, normalizeDimension, appendScript, normalizeURL } from '@bagelink/vue'
3
- import { watch } from 'vue'
2
+ import { Skeleton, normalizeDimension, appendScript, normalizeURL, Icon } from '@bagelink/vue'
3
+ import { watch, computed } from 'vue'
4
4
 
5
- declare global {
6
- interface Window {
7
- heic2any: any
8
- }
9
- }
10
-
11
- const { height, width, alt = '', src, pathKey } = defineProps<{
5
+ interface ImageProps {
12
6
  src?: string
13
7
  pathKey?: string
14
8
  alt?: string
15
9
  width?: string | number
16
10
  height?: string | number
17
11
  caption?: string
18
- }>()
12
+ }
13
+
14
+ declare global {
15
+ interface Window {
16
+ heic2any: any
17
+ }
18
+ }
19
+
20
+ const props = defineProps<ImageProps>()
19
21
 
20
22
  let imageSrc = $ref<string | null>(null)
23
+ let loadingError = $ref(false)
21
24
 
22
- const fileBaseUrl = $computed(() => ('https://files.bagel.design').replace(/\/$/, ''))
23
- function pathToUrl() {
24
- if (pathKey?.startsWith('static/')) return `${import.meta.env.VITE_BAGEL_BASE_URL}/${pathKey}`
25
+ const fileBaseUrl = computed(() => 'https://files.bagel.design')
25
26
 
26
- return `${fileBaseUrl}/${pathKey}`
27
+ function getImageUrl(): string | null {
28
+ if (!props.src && !props.pathKey) return null
29
+ if (props.src) return props.src
30
+ if (props.pathKey?.startsWith('static/')) {
31
+ return `${import.meta.env.VITE_BAGEL_BASE_URL}/${props.pathKey}`
32
+ }
33
+ return `${fileBaseUrl.value}/${props.pathKey}`
34
+ }
35
+
36
+ async function getCachedImage(url: string): Promise<string | null> {
37
+ if (!('caches' in window)) return null
38
+ try {
39
+ const imgCache = await window.caches.open('img-cache')
40
+ const cachedResponse = await imgCache.match(url)
41
+ if (cachedResponse) {
42
+ return URL.createObjectURL(await cachedResponse.blob())
43
+ }
44
+ } catch (error) {
45
+ console.warn('Cache access error:', error)
46
+ }
47
+ return null
48
+ }
49
+
50
+ async function cacheImage(url: string, blob: Blob) {
51
+ if (!('caches' in window)) return
52
+ try {
53
+ const imgCache = await window.caches.open('img-cache')
54
+ await imgCache.put(url, new Response(blob))
55
+ } catch (error) {
56
+ console.warn('Cache write error:', error)
57
+ }
58
+ }
59
+
60
+ async function convertHeicImage(url: string): Promise<string> {
61
+ await appendScript('https://cdnjs.cloudflare.com/ajax/libs/heic2any/0.0.1/index.min.js')
62
+ const response = await fetch(normalizeURL(url))
63
+ const blob = await response.blob()
64
+ const convertedBlob = await window.heic2any({ blob }) as Blob
65
+ await cacheImage(url, convertedBlob)
66
+ return URL.createObjectURL(convertedBlob)
27
67
  }
28
68
 
29
69
  async function loadImage() {
30
- const url = src || pathToUrl()
31
- console.log(url)
70
+ loadingError = false
71
+ const url = getImageUrl()
32
72
  if (!url) {
33
73
  imageSrc = null
34
74
  return
35
75
  }
36
- const ext = url.split('.').pop()?.toLowerCase().split('?').shift()
37
76
 
38
- if (ext === 'heic') {
39
- if (!('caches' in window)) {
40
- console.warn('Caching is not available. Proceeding without cache.')
41
- } else {
42
- try {
43
- const imgCache = await window.caches.open('img-cache')
44
- const cachedResponse = await imgCache.match(url)
45
- if (cachedResponse) {
46
- imageSrc = URL.createObjectURL(await cachedResponse.blob())
47
- return
48
- }
49
- } catch (error) {
50
- console.warn('Error accessing cache:', error)
51
- }
52
- }
53
- try {
54
- await appendScript('https://cdnjs.cloudflare.com/ajax/libs/heic2any/0.0.1/index.min.js')
55
- const response = await fetch(normalizeURL(url))
56
- const blob = await response.blob()
57
- const convertedBlob = await window.heic2any({ blob }) as Blob
58
- imageSrc = URL.createObjectURL(convertedBlob)
59
- // Only attempt to cache if the cache API is available
60
- if ('caches' in window) {
61
- try {
62
- const imgCache = await window.caches.open('img-cache')
63
- imgCache.put(url, new Response(convertedBlob))
64
- } catch (cacheError) {
65
- console.warn('Failed to cache the image:', cacheError)
66
- }
77
+ try {
78
+ const ext = url.split('.').pop()?.toLowerCase().split('?')[0]
79
+
80
+ if (ext === 'heic') {
81
+ const cachedSrc = await getCachedImage(url)
82
+ if (cachedSrc) {
83
+ imageSrc = cachedSrc
84
+ return
67
85
  }
68
- } catch (error) {
69
- console.error('Error converting HEIC file:', error)
86
+ imageSrc = await convertHeicImage(url)
87
+ } else {
88
+ imageSrc = url
70
89
  }
71
- } else {
72
- imageSrc = url
90
+ } catch (error) {
91
+ console.error('Image loading error:', error)
92
+ loadingError = true
93
+ imageSrc = null
73
94
  }
74
95
  }
75
- watch(() => [src, pathKey], loadImage, { immediate: true })
96
+
97
+ watch(() => [props.src, props.pathKey], loadImage, { immediate: true })
76
98
  </script>
77
99
 
78
100
  <template>
79
101
  <figcaption v-if="caption">
80
- <img v-if="imageSrc" :src="imageSrc" v-bind="$attrs" :alt="alt" :width="normalizeDimension(width)" :height="normalizeDimension(height)">
102
+ <img
103
+ v-if="imageSrc"
104
+ :src="imageSrc"
105
+ v-bind="$attrs"
106
+ :alt="alt"
107
+ :width="normalizeDimension(width)"
108
+ :height="normalizeDimension(height)"
109
+ >
81
110
  <Skeleton
82
111
  v-else
83
112
  class="img-web-kit"
@@ -85,7 +114,25 @@ watch(() => [src, pathKey], loadImage, { immediate: true })
85
114
  :height="normalizeDimension(height)"
86
115
  />
87
116
  </figcaption>
88
- <img v-else-if="imageSrc" :src="imageSrc" v-bind="$attrs" :alt="alt" :width="normalizeDimension(width)" :height="normalizeDimension(height)">
117
+
118
+ <img
119
+ v-else-if="imageSrc"
120
+ :src="imageSrc"
121
+ v-bind="$attrs"
122
+ :alt="alt"
123
+ :width="normalizeDimension(width)"
124
+ :height="normalizeDimension(height)"
125
+ >
126
+ <div
127
+ v-else-if="loadingError"
128
+ class="flex-center error-image"
129
+ :style="{
130
+ width: normalizeDimension(width),
131
+ height: normalizeDimension(height),
132
+ }"
133
+ >
134
+ <Icon name="broken_image" />
135
+ </div>
89
136
  <Skeleton
90
137
  v-else
91
138
  class="img-web-kit"
@@ -101,4 +148,7 @@ watch(() => [src, pathKey], loadImage, { immediate: true })
101
148
  border: 0;
102
149
  width: 100%;
103
150
  }
151
+ .error-image {
152
+ background-color: var(--skeleton-bg);
153
+ }
104
154
  </style>
@@ -1,12 +1,6 @@
1
1
  <script lang="ts" setup>
2
- import {
3
- BagelForm,
4
- type BglFormSchemaFnT,
5
- Btn,
6
- type BtnOptions,
7
- Modal,
8
- useBagel,
9
- } from '@bagelink/vue'
2
+ import type { BglFormSchemaFnT, BtnOptions, BagelForm } from '@bagelink/vue'
3
+ import { Btn, Modal, useBagel, BagelForm2 } from '@bagelink/vue'
10
4
 
11
5
  const props = defineProps<{
12
6
  side?: boolean
@@ -80,15 +74,16 @@ defineExpose({ setFormValues })
80
74
  :visible="visible"
81
75
  :dismissable
82
76
  :title
83
- @update:visible="($event) => emit('update:visible', $event)"
77
+ @update:visible="($event: boolean) => emit('update:visible', $event)"
84
78
  >
85
- <BagelForm
79
+ <BagelForm2 v-model="formData" :schema="computedFormSchema" @submit="runSubmit" />
80
+ <!-- <BagelForm
86
81
  v-if="visible"
87
82
  ref="form"
88
83
  v-model="formData"
89
84
  :schema="computedFormSchema"
90
85
  @submit="runSubmit"
91
- />
86
+ /> -->
92
87
  <template v-if="onDelete || onSubmit" #footer>
93
88
  <div>
94
89
  <Btn thin flat value="Cancel" @click="closeModal" />
@@ -0,0 +1,150 @@
1
+ <script setup lang="ts">
2
+ import type { BglFormSchemaT, BglFormSchemaFnT, Field } from '@bagelink/vue'
3
+ import type { VNode } from 'vue'
4
+ import { BglForm, CheckInput, DateInput, FieldArray, FileUpload, NumberInput, RichText, SelectInput, TabsNav, TextInput, ToggleInput, UploadInput } from '@bagelink/vue'
5
+ import { h, watch } from 'vue'
6
+
7
+ interface Props {
8
+ modelValue: Record<string, any>
9
+ schema?: BglFormSchemaFnT
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ modelValue: () => ({}),
14
+ schema: undefined
15
+ })
16
+
17
+ const emit = defineEmits<{
18
+ (e: 'update:modelValue', value: Record<string, any>): void
19
+ (e: 'submit', value: Record<string, any>): void
20
+ }>()
21
+
22
+ const form = $ref<HTMLFormElement>()
23
+ let formData = $ref({ ...props.modelValue })
24
+ let initialFormData = $ref({ ...props.modelValue })
25
+
26
+ // Keep formData in sync with modelValue prop
27
+ watch(() => props.modelValue, (newValue) => {
28
+ formData = { ...newValue }
29
+ }, { immediate: true, deep: true })
30
+
31
+ const resolvedSchema = $computed<BglFormSchemaT | undefined>(() => {
32
+ if (!props.schema) return undefined
33
+ return typeof props.schema === 'function' ? props.schema() : props.schema
34
+ })
35
+
36
+ const isDirty = $computed(() => {
37
+ const current = JSON.stringify(formData)
38
+ const initial = JSON.stringify(initialFormData)
39
+ return current !== initial
40
+ })
41
+
42
+ function getComponent(field: Field) {
43
+ const componentMap = {
44
+ text: TextInput,
45
+ textarea: TextInput,
46
+ number: NumberInput,
47
+ array: FieldArray,
48
+ select: SelectInput,
49
+ toggle: ToggleInput,
50
+ check: CheckInput,
51
+ richtext: RichText,
52
+ upload: UploadInput,
53
+ file: FileUpload,
54
+ date: DateInput,
55
+ tabs: TabsNav,
56
+ bglform: BglForm
57
+ }
58
+
59
+ if (field.$el === 'textarea' && !field.attrs?.multiline) {
60
+ field.attrs = { ...field.attrs, multiline: true }
61
+ }
62
+
63
+ return componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
64
+ }
65
+
66
+ function processFieldValue(fieldType: any, value: any): any {
67
+ if (value == null) return value
68
+ if (fieldType === 'select' && typeof value === 'object') return value.value
69
+ if (typeof value === 'object' && 'value' in value) return value.value
70
+ return value
71
+ }
72
+
73
+ function updateFormData(fieldId: string, value: any, fieldType?: any) {
74
+ formData = {
75
+ ...formData,
76
+ [fieldId]: processFieldValue(fieldType, value)
77
+ }
78
+ emit('update:modelValue', formData)
79
+ }
80
+
81
+ function handleSubmit() {
82
+ emit('submit', formData)
83
+ initialFormData = { ...formData }
84
+ }
85
+
86
+ function renderSchemaField(field: Field): VNode | null {
87
+ const Component = getComponent(field)
88
+ if (!Component) return null
89
+
90
+ const { $el, vIf, 'v-if': vIf2, children, ...fieldProps } = field
91
+ const currentValue = field.id ? formData[field.id] : undefined
92
+
93
+ const props: Record<string, any> = {
94
+ ...fieldProps,
95
+ 'modelValue': processFieldValue(field.$el, currentValue),
96
+ 'onUpdate:modelValue': (value: any) => {
97
+ if (!field.id) return
98
+ updateFormData(field.id, value, field.$el)
99
+ field.onUpdate?.(value, formData)
100
+ }
101
+ }
102
+
103
+ // Handle dynamic props
104
+ if (field.attrs) {
105
+ Object.entries(field.attrs).forEach(([key, value]) => {
106
+ props[key] = typeof value === 'function' ? value(field, formData) : value
107
+ })
108
+ }
109
+
110
+ if (field.class) {
111
+ props.class = typeof field.class === 'function'
112
+ ? field.class(field, formData)
113
+ : field.class
114
+ }
115
+
116
+ const slots = field.children?.length
117
+ ? {
118
+ default: () => field.children!.map(child => typeof child === 'string' ? child : renderSchemaField(child)
119
+ )
120
+ }
121
+ : undefined
122
+
123
+ return h(Component as any, props, slots)
124
+ }
125
+
126
+ function shouldRenderField(field: Field): boolean {
127
+ const condition = field.vIf ?? field['v-if']
128
+ if (condition === undefined) return true
129
+ return typeof condition === 'function'
130
+ ? condition(formData[field.id as string], formData)
131
+ : !!condition
132
+ }
133
+
134
+ defineExpose({ form, isDirty })
135
+ </script>
136
+
137
+ <template>
138
+ <form ref="form" @submit.prevent="handleSubmit">
139
+ <template v-if="resolvedSchema">
140
+ <template v-for="(field, index) in resolvedSchema" :key="field.id || index">
141
+ <component
142
+ :is="renderSchemaField(field)"
143
+ v-if="shouldRenderField(field)"
144
+ />
145
+ </template>
146
+ </template>
147
+ <slot v-else />
148
+ <slot name="submit" :submit="handleSubmit" :isDirty="isDirty" />
149
+ </form>
150
+ </template>
@@ -1,6 +1,6 @@
1
+ export { default as BagelForm2 } from './BagelForm.vue'
1
2
  export { default as BglField } from './BglField.vue'
2
3
  export { default as BglForm } from './BglForm.vue'
3
4
  export { default as BagelForm } from './BglForm.vue'
4
- export { default as BglMultiStepForm } from './BglMultiStepForm.vue'
5
5
  export { default as FieldArray } from './FieldArray.vue'
6
6
  export * from './inputs'
@@ -5,7 +5,7 @@ import { watch } from 'vue'
5
5
  type NumberLayout = 'default' | 'vertical' | 'horizontal'
6
6
 
7
7
  interface NumberInputProps {
8
- modelValue?: number
8
+ modelValue?: number | string
9
9
  min?: number
10
10
  max?: number
11
11
  step?: number
@@ -43,7 +43,7 @@ const {
43
43
 
44
44
  const emit = defineEmits(['update:modelValue'])
45
45
 
46
- let numberValue = $ref(modelValue || 0)
46
+ let numberValue = $ref(Number.parseFloat(`${modelValue}`) || 0)
47
47
 
48
48
  const btnLayouts: NumberLayout[] = ['horizontal', 'vertical']
49
49
 
@@ -88,7 +88,7 @@ watch(() => numberValue, () => {
88
88
 
89
89
  watch(() => modelValue, (newVal) => {
90
90
  if (newVal !== numberValue) {
91
- numberValue = newVal || 0
91
+ numberValue = Number.parseFloat(`${newVal}`) || 0
92
92
  }
93
93
  }, { immediate: true })
94
94
  </script>
@@ -35,6 +35,7 @@ function runAction(name: ToolbarConfigOption, value?: string) {
35
35
  v-else-if="action.name !== 'separator'" v-tooltip="action.label" :icon="action.icon" thin flat
36
36
  :aria-label="action.name" :class="[action.class, { active: selectedStyles.has(action.name) }]"
37
37
  class=""
38
+ tabindex="-1"
38
39
  @click="runAction(action.name)"
39
40
  />
40
41
  <span v-else-if="action.name === 'separator'" :key="`separator-${index}`" class="opacity-2 mb-025">|</span>