@bagelink/vue 1.15.668 → 1.17.2
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/LICENSE +21 -0
- package/dist/assetTransport.d.ts +50 -0
- package/dist/assetTransport.d.ts.map +1 -0
- package/dist/assetTransport.test.d.ts +2 -0
- package/dist/assetTransport.test.d.ts.map +1 -0
- package/dist/assets.d.ts +46 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.test.d.ts +2 -0
- package/dist/assets.test.d.ts.map +1 -0
- package/dist/components/Image.vue.d.ts +7 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts +11 -0
- package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/lightbox/AssetPreview.vue.d.ts +9 -0
- package/dist/components/lightbox/AssetPreview.vue.d.ts.map +1 -0
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/components/lightbox/LightboxImage.vue.d.ts +2 -1
- package/dist/components/lightbox/LightboxImage.vue.d.ts.map +1 -1
- package/dist/components/lightbox/index.d.ts.map +1 -1
- package/dist/components/lightbox/lightbox.types.d.ts +7 -0
- package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
- package/dist/composables/useImageSrc.d.ts +8 -4
- package/dist/composables/useImageSrc.d.ts.map +1 -1
- package/dist/core.css +1 -1
- package/dist/form-flow/FormFlowPlayer.vue.d.ts +3 -3
- package/dist/form-flow/FormFlowPlayer.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +11 -6
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +3 -0
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/index.cjs +62 -62
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +19554 -18982
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts +3 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/package.json +104 -105
- package/src/assetTransport.test.ts +125 -0
- package/src/assetTransport.ts +164 -0
- package/src/assets.test.ts +123 -0
- package/src/assets.ts +245 -0
- package/src/components/Image.vue +19 -3
- package/src/components/form/inputs/Upload/AssetUploadInput.vue +67 -0
- package/src/components/form/inputs/Upload/UploadInput.vue +5 -1
- package/src/components/lightbox/AssetPreview.vue +16 -0
- package/src/components/lightbox/Lightbox.vue +8 -3
- package/src/components/lightbox/LightboxImage.vue +4 -3
- package/src/components/lightbox/index.ts +29 -4
- package/src/components/lightbox/lightbox.types.ts +5 -1
- package/src/composables/useImageSrc.ts +47 -37
- package/src/form-flow/form-flow.ts +26 -7
- package/src/form-flow/schema-fields.ts +23 -1
- package/src/index.ts +3 -0
- package/src/types/BagelForm.ts +3 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type { AssetReference } from '../../assets'
|
|
2
|
+
|
|
3
|
+
export type XORSrcPath = { src: string, pathKey?: never, asset?: never }
|
|
4
|
+
| { src?: never, pathKey: string, asset?: never }
|
|
5
|
+
| { src?: never, pathKey?: never, asset: AssetReference }
|
|
2
6
|
|
|
3
7
|
export type LightboxItem = XORSrcPath & {
|
|
4
8
|
type: string
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MaybeRefOrGetter } from 'vue'
|
|
2
2
|
import { appendScript, awaitGlobal, normalizeURL, pathKeyToURL } from '../index'
|
|
3
|
-
import { ref, toValue, watch } from 'vue'
|
|
3
|
+
import { computed, onScopeDispose, ref, toValue, watch } from 'vue'
|
|
4
|
+
import { useAssetDelivery, type AssetDeliveryRequest } from '../assets'
|
|
4
5
|
|
|
5
6
|
declare global {
|
|
6
7
|
interface Window {
|
|
@@ -8,74 +9,76 @@ declare global {
|
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return URL.createObjectURL(await cachedResponse.blob())
|
|
18
|
-
}
|
|
19
|
-
} catch (error) {
|
|
20
|
-
console.warn('Cache access error:', error)
|
|
21
|
-
}
|
|
22
|
-
return undefined
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function cacheImage(url: string, blob: Blob) {
|
|
26
|
-
if (!('caches' in window)) { return }
|
|
27
|
-
try {
|
|
28
|
-
const imgCache = await window.caches.open('img-cache')
|
|
29
|
-
await imgCache.put(url, new Response(blob))
|
|
30
|
-
} catch (error) {
|
|
31
|
-
console.warn('Cache write error:', error)
|
|
32
|
-
}
|
|
12
|
+
let legacyCacheCleanup: Promise<boolean> | undefined
|
|
13
|
+
/** Remove only the retired sensitive image cache, once per browser session. */
|
|
14
|
+
export function clearLegacyImageCache() {
|
|
15
|
+
if (typeof window === 'undefined' || !('caches' in window)) return
|
|
16
|
+
legacyCacheCleanup ??= window.caches.delete('img-cache').catch(() => false)
|
|
17
|
+
return legacyCacheCleanup
|
|
33
18
|
}
|
|
34
19
|
|
|
35
|
-
async function convertHeicImage(url: string): Promise<string> {
|
|
20
|
+
async function convertHeicImage(url: string, signal: AbortSignal): Promise<string> {
|
|
36
21
|
await appendScript('https://cdnjs.cloudflare.com/ajax/libs/heic2any/0.0.1/index.min.js')
|
|
37
22
|
const heic2any = await awaitGlobal<(opts: { blob: Blob }) => Promise<Blob>>('heic2any')
|
|
38
|
-
const response = await fetch(normalizeURL(url))
|
|
23
|
+
const response = await fetch(normalizeURL(url), { signal, cache: 'no-store' })
|
|
24
|
+
if (!response.ok) throw new Error('Image request failed')
|
|
39
25
|
const blob = await response.blob()
|
|
40
26
|
const convertedBlob = await heic2any({ blob })
|
|
41
|
-
await cacheImage(url, convertedBlob)
|
|
42
27
|
return URL.createObjectURL(convertedBlob)
|
|
43
28
|
}
|
|
44
29
|
|
|
45
30
|
/**
|
|
46
31
|
* Resolve an image source for display: turns a pathKey/URL into a usable URL via
|
|
47
32
|
* `pathKeyToURL`, and transparently converts `.heic` files to a displayable blob
|
|
48
|
-
* (
|
|
33
|
+
* (in memory only) since browsers can't render HEIC in <img>. Shared by <Image>
|
|
49
34
|
* and the lightbox so both get the same resolution + HEIC handling.
|
|
50
35
|
*
|
|
51
36
|
* @param source ref/getter of the raw src or pathKey
|
|
52
37
|
* @returns `{ imageSrc, loadingError }` reactive refs
|
|
53
38
|
*/
|
|
54
|
-
export function useImageSrc(source: MaybeRefOrGetter<string | undefined>) {
|
|
39
|
+
export function useImageSrc(source: MaybeRefOrGetter<string | AssetDeliveryRequest | undefined>) {
|
|
40
|
+
void clearLegacyImageCache()
|
|
41
|
+
const asset = useAssetDelivery(() => {
|
|
42
|
+
const value = toValue(source)
|
|
43
|
+
return typeof value === 'object' ? { ...value, representation: value.representation ?? 'preview' } : undefined
|
|
44
|
+
})
|
|
55
45
|
const imageSrc = ref<string | undefined>(undefined)
|
|
56
46
|
const loadingError = ref(false)
|
|
47
|
+
let controller: AbortController | undefined
|
|
48
|
+
let generation = 0
|
|
49
|
+
let objectUrl: string | undefined
|
|
50
|
+
function cleanup() {
|
|
51
|
+
controller?.abort()
|
|
52
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl)
|
|
53
|
+
objectUrl = undefined
|
|
54
|
+
}
|
|
57
55
|
|
|
58
56
|
async function load() {
|
|
57
|
+
const current = ++generation
|
|
58
|
+
cleanup()
|
|
59
59
|
loadingError.value = false
|
|
60
|
-
|
|
60
|
+
imageSrc.value = undefined
|
|
61
|
+
const value = toValue(source)
|
|
62
|
+
if (typeof value === 'object') return
|
|
63
|
+
const url = pathKeyToURL(value)
|
|
61
64
|
if (url === undefined || url === '') {
|
|
62
65
|
imageSrc.value = undefined
|
|
63
66
|
return
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
try {
|
|
70
|
+
controller = new AbortController()
|
|
67
71
|
const ext = url.split('.').pop()?.toLowerCase().split('?')[0]
|
|
68
72
|
if (ext === 'heic') {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
imageSrc.value = await convertHeicImage(url)
|
|
73
|
+
const converted = await convertHeicImage(url, controller.signal)
|
|
74
|
+
if (current !== generation) { URL.revokeObjectURL(converted); return }
|
|
75
|
+
objectUrl = converted
|
|
76
|
+
imageSrc.value = converted
|
|
75
77
|
} else {
|
|
76
78
|
imageSrc.value = url
|
|
77
79
|
}
|
|
78
80
|
} catch (error) {
|
|
81
|
+
if (current !== generation) return
|
|
79
82
|
console.error('Image loading error:', error)
|
|
80
83
|
loadingError.value = true
|
|
81
84
|
imageSrc.value = undefined
|
|
@@ -83,6 +86,13 @@ export function useImageSrc(source: MaybeRefOrGetter<string | undefined>) {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
watch(() => toValue(source), load, { immediate: true })
|
|
86
|
-
|
|
87
|
-
return {
|
|
89
|
+
onScopeDispose(() => { generation++; cleanup() })
|
|
90
|
+
return {
|
|
91
|
+
imageSrc: computed(() => typeof toValue(source) === 'object' ? asset.url.value : imageSrc.value),
|
|
92
|
+
loadingError: computed(() => typeof toValue(source) === 'object' ? asset.error.value : loadingError.value),
|
|
93
|
+
onError: () => {
|
|
94
|
+
if (typeof toValue(source) === 'object') asset.onError()
|
|
95
|
+
else loadingError.value = true
|
|
96
|
+
},
|
|
97
|
+
}
|
|
88
98
|
}
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
isCustomFormFieldType,
|
|
18
18
|
warnMissingCustomFormField,
|
|
19
19
|
} from './custom-field-registry'
|
|
20
|
+
import type {
|
|
21
|
+
JSONSchemaObject as FormJSONSchemaObject,
|
|
22
|
+
JSONSchemaProperty as FormJSONSchemaProperty,
|
|
23
|
+
} from './schema-fields'
|
|
20
24
|
|
|
21
25
|
// ============================================================================
|
|
22
26
|
// Core Types
|
|
@@ -412,6 +416,8 @@ export const $ = {
|
|
|
412
416
|
noFilePlaceholder?: string
|
|
413
417
|
btnPlaceholder?: string
|
|
414
418
|
style?: string
|
|
419
|
+
/** Upload to the immutable assets catalog and persist a `{asset_id, version_id}` pin. */
|
|
420
|
+
assetMode?: boolean
|
|
415
421
|
}),
|
|
416
422
|
config?: BaseFieldConfig & {
|
|
417
423
|
multiple?: boolean
|
|
@@ -428,6 +434,8 @@ export const $ = {
|
|
|
428
434
|
noFilePlaceholder?: string
|
|
429
435
|
btnPlaceholder?: string
|
|
430
436
|
style?: string
|
|
437
|
+
/** Upload to the immutable assets catalog and persist a `{asset_id, version_id}` pin. */
|
|
438
|
+
assetMode?: boolean
|
|
431
439
|
}
|
|
432
440
|
): FieldBuilder<string | string[]> {
|
|
433
441
|
return new Field('upload', parseArgs(labelOrConfig, config))
|
|
@@ -806,8 +814,10 @@ export function multiStepSchema<T extends Record<string, any> = Record<string, a
|
|
|
806
814
|
// JSONSchema Import
|
|
807
815
|
// ============================================================================
|
|
808
816
|
|
|
809
|
-
|
|
810
|
-
|
|
817
|
+
type JSONSchemaProperty = Omit<
|
|
818
|
+
FormJSONSchemaProperty,
|
|
819
|
+
'items' | 'properties' | 'x-field-type'
|
|
820
|
+
> & {
|
|
811
821
|
'title'?: string
|
|
812
822
|
'description'?: string
|
|
813
823
|
'default'?: any
|
|
@@ -855,6 +865,7 @@ interface JSONSchemaProperty {
|
|
|
855
865
|
'x-placeholder'?: string
|
|
856
866
|
'x-select-multiselect'?: boolean
|
|
857
867
|
'x-select-searchable'?: boolean
|
|
868
|
+
'x-asset'?: boolean
|
|
858
869
|
'x-radio-inline'?: boolean
|
|
859
870
|
'x-radio-align'?: 'start' | 'center' | 'end'
|
|
860
871
|
'x-radio-text-align'?: 'left' | 'center' | 'right'
|
|
@@ -929,8 +940,8 @@ function normalizeOptionsSourceResponse(
|
|
|
929
940
|
})
|
|
930
941
|
}
|
|
931
942
|
|
|
932
|
-
|
|
933
|
-
'type'
|
|
943
|
+
type JSONSchemaObject = Omit<FormJSONSchemaObject, 'properties'> & {
|
|
944
|
+
'type': 'object'
|
|
934
945
|
'title'?: string
|
|
935
946
|
'properties'?: Record<string, JSONSchemaProperty>
|
|
936
947
|
'required'?: string[]
|
|
@@ -1039,8 +1050,8 @@ function resolveUnionType(prop: JSONSchemaProperty, _rootSchema: JSONSchemaObjec
|
|
|
1039
1050
|
// Prefer object > array > string > number > boolean
|
|
1040
1051
|
const typeOrder = ['object', 'array', 'string', 'number', 'integer', 'boolean']
|
|
1041
1052
|
const sorted = nonNullTypes.sort((a, b) => {
|
|
1042
|
-
const aIdx = typeOrder.indexOf(a.type
|
|
1043
|
-
const bIdx = typeOrder.indexOf(b.type
|
|
1053
|
+
const aIdx = typeOrder.indexOf(Array.isArray(a.type) ? (a.type[0] ?? 'string') : (a.type ?? 'string'))
|
|
1054
|
+
const bIdx = typeOrder.indexOf(Array.isArray(b.type) ? (b.type[0] ?? 'string') : (b.type ?? 'string'))
|
|
1044
1055
|
return aIdx - bIdx
|
|
1045
1056
|
})
|
|
1046
1057
|
|
|
@@ -1092,8 +1103,12 @@ function createFieldFromProperty(
|
|
|
1092
1103
|
const xUi = resolvedProp['x-ui'] || {}
|
|
1093
1104
|
// Field type may be declared either as `x-ui.fieldType` (this module's own
|
|
1094
1105
|
// round-trip) or the flat `x-field-type` (FormBuilder designer). Multiselect
|
|
1095
|
-
// is a `select` flagged multi.
|
|
1106
|
+
// is a `select` flagged multi. `x-asset` is an immutable-assets file field
|
|
1107
|
+
// whose JSON-schema type is `object` (the pin), not `string`.
|
|
1096
1108
|
let fieldType = xUi.fieldType || resolvedProp['x-field-type'] || inferFieldType(resolvedProp)
|
|
1109
|
+
if (resolvedProp['x-asset'] === true && fieldType !== 'file' && fieldType !== 'upload') {
|
|
1110
|
+
fieldType = 'file'
|
|
1111
|
+
}
|
|
1097
1112
|
if (fieldType === 'select' && resolvedProp['x-select-multiselect']) fieldType = 'multiselect'
|
|
1098
1113
|
|
|
1099
1114
|
// Designer-authored options: [{label,value}] pairs preserve labels (raw
|
|
@@ -1232,6 +1247,10 @@ function createFieldFromProperty(
|
|
|
1232
1247
|
break
|
|
1233
1248
|
case 'file':
|
|
1234
1249
|
case 'upload':
|
|
1250
|
+
// `x-asset` marks a field as managed by the immutable assets catalog:
|
|
1251
|
+
// the widget uploads and persists a `{asset_id, version_id}` pin, and
|
|
1252
|
+
// the JSON-schema type is `object`, not `string`.
|
|
1253
|
+
if (resolvedProp['x-asset'] === true) config.assetMode = true
|
|
1235
1254
|
field = $.upload(config)
|
|
1236
1255
|
break
|
|
1237
1256
|
case 'array': {
|
|
@@ -95,6 +95,8 @@ export interface FormField {
|
|
|
95
95
|
fileTheme?: 'dropzone' | 'basic'
|
|
96
96
|
fileShowIcon?: boolean
|
|
97
97
|
fileIcon?: string
|
|
98
|
+
/** Upload to the immutable assets catalog; persist a `{asset_id, version_id}` pin. */
|
|
99
|
+
assetMode?: boolean
|
|
98
100
|
dateEnableTime?: boolean
|
|
99
101
|
dateMin?: string
|
|
100
102
|
dateMax?: string
|
|
@@ -163,6 +165,7 @@ export interface JSONSchemaProperty {
|
|
|
163
165
|
'x-file-theme'?: 'dropzone' | 'basic'
|
|
164
166
|
'x-file-show-icon'?: boolean
|
|
165
167
|
'x-file-icon'?: string
|
|
168
|
+
'x-asset'?: boolean
|
|
166
169
|
'x-date-enable-time'?: boolean
|
|
167
170
|
'x-date-min'?: string
|
|
168
171
|
'x-date-max'?: string
|
|
@@ -351,7 +354,26 @@ export function fieldsToSchema(
|
|
|
351
354
|
prop.type = 'string'
|
|
352
355
|
break
|
|
353
356
|
case 'file':
|
|
354
|
-
if (field.
|
|
357
|
+
if (field.assetMode === true) {
|
|
358
|
+
// Immutable assets catalog: persist a `{asset_id, version_id}` pin.
|
|
359
|
+
// The JSON-schema type is `object` (the pin), and `x-asset` is the
|
|
360
|
+
// authoritative marker — never a `string` URL.
|
|
361
|
+
const assetRef = {
|
|
362
|
+
type: 'object',
|
|
363
|
+
properties: {
|
|
364
|
+
asset_id: { type: 'string', format: 'uuid' },
|
|
365
|
+
version_id: { type: 'string', format: 'uuid' },
|
|
366
|
+
},
|
|
367
|
+
required: ['asset_id', 'version_id'],
|
|
368
|
+
}
|
|
369
|
+
if (field.fileMultiple === true) {
|
|
370
|
+
prop.type = 'array'
|
|
371
|
+
prop.items = assetRef
|
|
372
|
+
} else {
|
|
373
|
+
Object.assign(prop, assetRef)
|
|
374
|
+
}
|
|
375
|
+
prop['x-asset'] = true
|
|
376
|
+
} else if (field.fileMultiple === true) {
|
|
355
377
|
prop.type = 'array'
|
|
356
378
|
prop.items = { type: 'string' }
|
|
357
379
|
} else {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export * from './components'
|
|
2
|
+
export * from './assets'
|
|
3
|
+
export * from './assetTransport'
|
|
4
|
+
export { default as AssetPreview } from './components/lightbox/AssetPreview.vue'
|
|
2
5
|
export * from './composables'
|
|
3
6
|
// Dialog (native <dialog> based)
|
|
4
7
|
export * from './dialog'
|
package/src/types/BagelForm.ts
CHANGED
|
@@ -192,6 +192,8 @@ export interface UploadInputValueObject {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
export interface UploadInputProps extends BagelInputShellProps {
|
|
195
|
+
/** Opt-in stable reference output; defaults to legacy path-key behavior. */
|
|
196
|
+
assetMode?: boolean
|
|
195
197
|
id?: string
|
|
196
198
|
name?: string
|
|
197
199
|
label?: string
|
|
@@ -200,7 +202,7 @@ export interface UploadInputProps extends BagelInputShellProps {
|
|
|
200
202
|
* Path-key string(s). Legacy/migrated records may also provide an image object
|
|
201
203
|
* (e.g. `{ imageURL, imageName, extension, altText }`); it is normalized internally.
|
|
202
204
|
*/
|
|
203
|
-
modelValue?: string | string[] | UploadInputValueObject | (string | UploadInputValueObject)[]
|
|
205
|
+
modelValue?: string | string[] | UploadInputValueObject | import('../assets').AssetReference | (string | UploadInputValueObject | import('../assets').AssetReference)[]
|
|
204
206
|
/** Optional upload handler. When omitted, the shared file-storage uploader is used. */
|
|
205
207
|
upload?: (file: File) => Promise<string>
|
|
206
208
|
width?: string
|