@bagelink/vue 1.16.0 → 1.17.6

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 (128) hide show
  1. package/dist/assetTransport.d.ts +28 -0
  2. package/dist/assetTransport.d.ts.map +1 -0
  3. package/dist/assetTransport.test.d.ts +2 -0
  4. package/dist/assetTransport.test.d.ts.map +1 -0
  5. package/dist/assets.d.ts +65 -0
  6. package/dist/assets.d.ts.map +1 -0
  7. package/dist/assets.test.d.ts +2 -0
  8. package/dist/assets.test.d.ts.map +1 -0
  9. package/dist/components/AddressSearch.vue.d.ts +40 -36
  10. package/dist/components/AddressSearch.vue.d.ts.map +1 -1
  11. package/dist/components/ContextMenu.vue.d.ts +518 -0
  12. package/dist/components/ContextMenu.vue.d.ts.map +1 -0
  13. package/dist/components/Dropdown.types.d.ts +25 -0
  14. package/dist/components/Dropdown.types.d.ts.map +1 -0
  15. package/dist/components/Dropdown.vue.d.ts +20 -15
  16. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  17. package/dist/components/FilterQuery.vue.d.ts +1 -1
  18. package/dist/components/FilterQuery.vue.d.ts.map +1 -1
  19. package/dist/components/Icon/Icon.vue.d.ts +0 -1
  20. package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
  21. package/dist/components/Icon/constants.d.ts +0 -1
  22. package/dist/components/Icon/constants.d.ts.map +1 -1
  23. package/dist/components/Icon/types.d.ts +2 -3
  24. package/dist/components/Icon/types.d.ts.map +1 -1
  25. package/dist/components/Image.vue.d.ts +7 -1
  26. package/dist/components/Image.vue.d.ts.map +1 -1
  27. package/dist/components/Swiper.vue.d.ts +1 -1
  28. package/dist/components/draggable/useDraggable.d.ts.map +1 -1
  29. package/dist/components/form/inputs/IconPickerInput.vue.d.ts +48 -37
  30. package/dist/components/form/inputs/IconPickerInput.vue.d.ts.map +1 -1
  31. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
  32. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
  33. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +3 -0
  34. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  35. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +1 -1
  36. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  37. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  38. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
  39. package/dist/components/form/inputs/RichText/utils/selection.d.ts +2 -0
  40. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  41. package/dist/components/form/inputs/RichText/utils/table.d.ts +34 -8
  42. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  43. package/dist/components/form/inputs/SelectInput.vue.d.ts +71 -63
  44. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  45. package/dist/components/form/inputs/TelInput.vue.d.ts +2 -2
  46. package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts +11 -0
  47. package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts.map +1 -0
  48. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +2 -2
  49. package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
  50. package/dist/components/index.d.ts +3 -1
  51. package/dist/components/index.d.ts.map +1 -1
  52. package/dist/components/lightbox/AssetPreview.vue.d.ts +9 -0
  53. package/dist/components/lightbox/AssetPreview.vue.d.ts.map +1 -0
  54. package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
  55. package/dist/components/lightbox/LightboxImage.vue.d.ts +2 -1
  56. package/dist/components/lightbox/LightboxImage.vue.d.ts.map +1 -1
  57. package/dist/components/lightbox/index.d.ts.map +1 -1
  58. package/dist/components/lightbox/lightbox.types.d.ts +7 -0
  59. package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
  60. package/dist/composables/index.d.ts +1 -1
  61. package/dist/composables/index.d.ts.map +1 -1
  62. package/dist/composables/useBoundedRectDrag.d.ts.map +1 -1
  63. package/dist/composables/useImageSrc.d.ts +8 -4
  64. package/dist/composables/useImageSrc.d.ts.map +1 -1
  65. package/dist/composables/usePointerSession.d.ts +14 -3
  66. package/dist/composables/usePointerSession.d.ts.map +1 -1
  67. package/dist/core.css +3 -3
  68. package/dist/directives/vResize.d.ts.map +1 -1
  69. package/dist/form-flow/FormFlowPlayer.vue.d.ts +3 -3
  70. package/dist/form-flow/FormFlowPlayer.vue.d.ts.map +1 -1
  71. package/dist/form-flow/form-flow.d.ts +13 -6
  72. package/dist/form-flow/form-flow.d.ts.map +1 -1
  73. package/dist/form-flow/schema-fields.d.ts +6 -0
  74. package/dist/form-flow/schema-fields.d.ts.map +1 -1
  75. package/dist/index.cjs +240 -153
  76. package/dist/index.css +1 -1
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.mjs +24167 -23417
  80. package/dist/style.css +3 -3
  81. package/dist/types/BagelForm.d.ts +5 -1
  82. package/dist/types/BagelForm.d.ts.map +1 -1
  83. package/package.json +1 -1
  84. package/src/assetTransport.test.ts +75 -0
  85. package/src/assetTransport.ts +98 -0
  86. package/src/assets.test.ts +82 -0
  87. package/src/assets.ts +162 -0
  88. package/src/components/ContextMenu.vue +113 -0
  89. package/src/components/Dropdown.types.ts +27 -0
  90. package/src/components/Dropdown.vue +129 -64
  91. package/src/components/FilterQuery.vue +1 -1
  92. package/src/components/Icon/Icon.vue +8 -30
  93. package/src/components/Icon/constants.ts +0 -166
  94. package/src/components/Icon/types.ts +2 -3
  95. package/src/components/Image.vue +19 -3
  96. package/src/components/draggable/useDraggable.ts +67 -100
  97. package/src/components/form/inputs/IconPickerInput.vue +14 -9
  98. package/src/components/form/inputs/RichText/components/EditorToolbar.vue +43 -111
  99. package/src/components/form/inputs/RichText/composables/useCommands.ts +32 -54
  100. package/src/components/form/inputs/RichText/composables/useEditor.ts +45 -171
  101. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +90 -14
  102. package/src/components/form/inputs/RichText/index.vue +1195 -1210
  103. package/src/components/form/inputs/RichText/utils/commands.ts +689 -1028
  104. package/src/components/form/inputs/RichText/utils/selection.ts +66 -41
  105. package/src/components/form/inputs/RichText/utils/table.ts +798 -136
  106. package/src/components/form/inputs/SelectInput.vue +1 -1
  107. package/src/components/form/inputs/Upload/AssetUploadInput.vue +67 -0
  108. package/src/components/form/inputs/Upload/UploadInput.vue +5 -1
  109. package/src/components/index.ts +3 -1
  110. package/src/components/lightbox/AssetPreview.vue +16 -0
  111. package/src/components/lightbox/Lightbox.vue +8 -3
  112. package/src/components/lightbox/LightboxImage.vue +4 -3
  113. package/src/components/lightbox/index.ts +29 -4
  114. package/src/components/lightbox/lightbox.types.ts +5 -1
  115. package/src/composables/index.ts +1 -1
  116. package/src/composables/useBoundedRectDrag.ts +9 -12
  117. package/src/composables/useImageSrc.ts +47 -37
  118. package/src/composables/usePointerSession.ts +57 -12
  119. package/src/directives/vResize.ts +6 -7
  120. package/src/form-flow/form-flow.ts +31 -7
  121. package/src/form-flow/schema-fields.ts +28 -1
  122. package/src/index.ts +3 -0
  123. package/src/styles/core.css +1 -18
  124. package/src/types/BagelForm.ts +5 -1
  125. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +0 -10
  126. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +0 -1
  127. package/src/components/form/inputs/RichText/components/gridBox.vue +0 -58
  128. package/src/components/form/inputs/RichText/utils/formatting.ts +0 -331
@@ -1,7 +1,6 @@
1
- import type { FONT_AWESOME_ICONS, MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'
1
+ import type { MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'
2
2
 
3
- export type FontAwesomeIcons = (typeof FONT_AWESOME_ICONS)[number]
4
3
  export type FontAwesomeBrandsIcons = (typeof FONT_AWESOME_BRANDS_ICONS)[number]
5
4
  export type MaterialIcons = (typeof MATERIAL_ICONS)[number]
6
5
 
7
- export type IconType = MaterialIcons | FontAwesomeIcons | FontAwesomeBrandsIcons
6
+ export type IconType = MaterialIcons | FontAwesomeBrandsIcons
@@ -1,10 +1,16 @@
1
1
  <script setup lang="ts">
2
2
  defineOptions({ name: 'BglImage', inheritAttrs: false })
3
3
  import { Skeleton, normalizeDimension, Icon, useImageSrc } from '../index'
4
+ import type { AssetDeliveryRequest, AssetPurpose } from '../assets'
4
5
  import { Comment, computed, ref, useSlots, watch, type StyleValue, type VNode } from 'vue'
5
6
 
6
7
  interface ImageProps {
7
- src?: string
8
+ src?: string | AssetDeliveryRequest
9
+ /** Stable asset UUID; requires an injected asset client. */
10
+ assetId?: string
11
+ versionId?: string
12
+ /** Server preset; width/height below control layout only. */
13
+ purpose?: AssetPurpose
8
14
  pathKey?: string
9
15
  alt?: string
10
16
  width?: string | number
@@ -25,10 +31,17 @@ interface ImageProps {
25
31
 
26
32
  const props = defineProps<ImageProps>()
27
33
 
28
- const { imageSrc, loadingError } = useImageSrc(() => props.src ?? props.pathKey ?? props.modelValue)
34
+ const { imageSrc, loadingError, onError } = useImageSrc(() => props.assetId
35
+ ? { asset_id: props.assetId, version_id: props.versionId, representation: props.purpose }
36
+ : typeof props.src === 'object'
37
+ ? { ...props.src, representation: props.purpose ?? props.src.representation }
38
+ : props.src ?? props.pathKey ?? props.modelValue)
29
39
  const imageLoaded = ref(false)
30
40
 
31
- watch(imageSrc, () => {
41
+ watch(imageSrc, value => {
42
+ if (!value) imageLoaded.value = false
43
+ })
44
+ watch(() => [props.assetId, props.versionId, props.purpose, props.src, props.pathKey, props.modelValue], () => {
32
45
  imageLoaded.value = false
33
46
  })
34
47
 
@@ -101,6 +114,7 @@ const imgFit = computed(() => props.fit ?? 'cover')
101
114
  :class="[frameHasHeight ? 'absolute-fill' : 'bgl-image-frame-img--flow', { 'opacity-0': !imageLoaded }]"
102
115
  :style="{ objectFit: imgFit, mixBlendMode: blend } as StyleValue"
103
116
  @load="onImageLoad"
117
+ @error="onError"
104
118
  >
105
119
  <Skeleton v-if="!imageLoaded && !loadingError" class="absolute-fill" />
106
120
  <div v-else-if="loadingError && !gradientCss" class="flex-center absolute-fill error-image">
@@ -120,6 +134,7 @@ const imgFit = computed(() => props.fit ?? 'cover')
120
134
  :height="normalizeDimension(height)"
121
135
  :class="{ 'opacity-0': !imageLoaded }"
122
136
  @load="onImageLoad"
137
+ @error="onError"
123
138
  >
124
139
  <Skeleton
125
140
  v-if="!imageLoaded && !loadingError"
@@ -144,6 +159,7 @@ const imgFit = computed(() => props.fit ?? 'cover')
144
159
  :class="{ 'opacity-0': !imageLoaded }"
145
160
  :style="{ objectFit: imgFit }"
146
161
  @load="onImageLoad"
162
+ @error="onError"
147
163
  >
148
164
  <div v-if="loadingError" class="flex-center absolute-fill error-image">
149
165
  <Icon name="broken_image" />
@@ -1,4 +1,5 @@
1
1
  import { ref, computed, toValue, getCurrentInstance, onUnmounted, type MaybeRefOrGetter } from 'vue'
2
+ import { createPointerSession } from '../../composables/usePointerSession'
2
3
  import { insertionIndexToFinalIndex } from './reorder'
3
4
 
4
5
  export interface DraggableEvent extends MouseEvent {
@@ -37,10 +38,9 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
37
38
  const dropIndicator = ref<HTMLElement | null>(null)
38
39
  const dropTarget = ref<DraggableElement | null>(null)
39
40
 
40
- // Only run in browser environment
41
41
  const isBrowser = typeof window !== 'undefined'
42
+ const pointerSession = createPointerSession()
42
43
 
43
- // Move style injection into setup
44
44
  if (isBrowser) {
45
45
  const existingStyle = document.getElementById('draggable-style')
46
46
  if (!existingStyle) {
@@ -83,8 +83,6 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
83
83
  function appendToTopLayer(element: HTMLElement) {
84
84
  document.body.appendChild(element)
85
85
  element.setAttribute('popover', 'manual')
86
- // The browser's popover UA styles use `inset: 0; margin: auto`, which
87
- // recentres fixed-position drag helpers and offsets them from the cursor.
88
86
  element.style.inset = 'auto'
89
87
  element.style.margin = '0'
90
88
  element.style.padding = '0'
@@ -128,9 +126,8 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
128
126
  line.style.background = 'var(--primary-color, #0066ff)'
129
127
  line.style.boxShadow = '0 0 4px rgba(0, 102, 255, 0.5)'
130
128
  line.style.transition = 'all 0.15s ease'
131
- line.style.display = 'none' // Start hidden
129
+ line.style.display = 'none'
132
130
 
133
- // Add the dot indicator
134
131
  const dot = document.createElement('div')
135
132
  dot.style.position = 'absolute'
136
133
  dot.style.left = '-4px'
@@ -153,7 +150,6 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
153
150
  dropIndicator.value.style.width = `${rect.width}px`
154
151
  dropIndicator.value.style.left = `${rect.left}px`
155
152
 
156
- // Move indicator to match the actual drop position
157
153
  if (isAfter) {
158
154
  dropIndicator.value.style.top = `${rect.bottom}px`
159
155
  } else {
@@ -180,15 +176,6 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
180
176
  return 2
181
177
  }
182
178
 
183
- // function resetDragStyles(dragEl: DraggableElement) {
184
- // if (defaultOptions.value.mode === 'ghost') {
185
- // dragEl.style.opacity = ''
186
- // dragEl.style.transform = ''
187
- // } else {
188
- // dragEl.style.opacity = ''
189
- // }
190
- // }
191
-
192
179
  function cacheElementPositions(draggedList: DraggableElement[]) {
193
180
  POSITION_CACHE.clear()
194
181
  draggedList.forEach((el, index) => {
@@ -203,11 +190,9 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
203
190
  function calculateDropIndex(e: MouseEvent, draggedList: DraggableElement[], dragIndex: number, newDropTarget: DraggableElement) {
204
191
  const now = Date.now()
205
192
  if (now - lastUpdateTime < UPDATE_THRESHOLD) {
206
- // Return last calculated index if we're within threshold
207
193
  return lastDropIndex !== -1 ? lastDropIndex : dragIndex
208
194
  }
209
195
 
210
- // Use cached positions if available, otherwise calculate new ones
211
196
  if (POSITION_CACHE.size === 0) {
212
197
  cacheElementPositions(draggedList)
213
198
  }
@@ -221,18 +206,14 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
221
206
  const elementHeight = targetPos.bottom - targetPos.top
222
207
  const relativeY = mouseY - targetPos.top
223
208
 
224
- // Calculate sticky zones
225
209
  const upperSticky = elementHeight * 0.3
226
210
  const lowerSticky = elementHeight * 0.7
227
211
 
228
212
  let dropIndex = targetIndex
229
213
 
230
- // Different behavior based on context
231
214
  if (targetIndex === dragIndex) {
232
- // On original element - require significant movement
233
215
  if (relativeY > lowerSticky) { dropIndex += 1 }
234
216
  } else if (targetIndex === lastDropIndex) {
235
- // On previously targeted element - use sticky zones
236
217
  if (targetIndex > dragIndex) {
237
218
  if (relativeY < upperSticky) { dropIndex = targetIndex }
238
219
  else { dropIndex = targetIndex + 1 }
@@ -241,18 +222,16 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
241
222
  else { dropIndex = targetIndex }
242
223
  }
243
224
  } else {
244
- // On new element - use 60/40 split
245
225
  if (relativeY > elementHeight * 0.6) { dropIndex += 1 }
246
226
  }
247
227
 
248
- // Update state
249
228
  lastDropIndex = dropIndex
250
229
  lastUpdateTime = now
251
230
 
252
231
  return dropIndex
253
232
  }
254
233
 
255
- function cleanup() {
234
+ function resetVisualState() {
256
235
  if (!isBrowser) { return }
257
236
 
258
237
  document.removeEventListener('mousemove', onDragMove)
@@ -268,7 +247,6 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
268
247
  ghostElement.value = null
269
248
  dropIndicator.value = null
270
249
 
271
- // Reset all elements
272
250
  if (dragElement.value) {
273
251
  const list = dragElement.value.__drag_list
274
252
  list?.forEach((el) => {
@@ -299,58 +277,85 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
299
277
  POSITION_CACHE.clear()
300
278
  }
301
279
 
280
+ function cleanup() {
281
+ pointerSession.cancel()
282
+ resetVisualState()
283
+ }
284
+
302
285
  function onKeyDown(e: KeyboardEvent) {
303
286
  if (e.key === 'Escape' && isDragging.value) {
304
287
  cleanup()
305
288
  }
306
289
  }
307
290
 
308
- function onDragStart(e: MouseEvent) {
309
- if (!isBrowser || isDragging.value || defaultOptions.value.disabled) { return }
310
- if (e.button !== 0) { return } // Only left mouse button
311
- if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) { return }
312
-
313
- const dragEl = findDraggableParent(e.target as Element)
314
- if (!dragEl || dragEl.dataset.draggable === 'false' || e.currentTarget !== dragEl) { return }
315
-
316
- // If handle is specified, check if clicked element matches
317
- if (defaultOptions.value.handle) {
318
- const handleEl = (e.target as Element).closest(defaultOptions.value.handle)
319
- if (!handleEl || !dragEl.contains(handleEl)) { return }
320
- }
321
-
322
- e.preventDefault()
291
+ function beginDrag(dragEl: DraggableElement, clientX: number, clientY: number, rawEvent: MouseEvent) {
323
292
  isDragging.value = true
324
293
  dragElement.value = dragEl
325
294
 
326
- // Store initial positions
327
- startX = e.clientX
328
- startY = e.clientY
295
+ startX = clientX
296
+ startY = clientY
329
297
  const rect = dragEl.getBoundingClientRect()
330
298
  initialX = rect.left
331
299
  initialY = rect.top
332
300
 
333
- // Create ghost element that follows mouse
334
301
  ghostElement.value = createGhost(dragEl)
335
302
  if (ghostElement.value) {
336
303
  ghostElement.value.style.left = `${initialX}px`
337
304
  ghostElement.value.style.top = `${initialY}px`
338
305
  }
339
306
 
340
- // Only create drop indicator in line mode
341
307
  if (defaultOptions.value.mode === 'line') {
342
308
  dropIndicator.value = createDropIndicator()
343
309
  }
344
310
 
345
- // Apply mode-specific styles and get shift amount
346
311
  dragEl.classList.add(defaultOptions.value.dragClass!)
347
312
  shiftAmount = defaultOptions.value.mode === 'ghost'
348
313
  ? handleGhostMode(dragEl)
349
314
  : handleLineMode(dragEl)
350
315
 
351
- defaultOptions.value.onStart?.(e)
316
+ defaultOptions.value.onStart?.(rawEvent)
317
+ }
318
+
319
+ function onPointerStart(e: PointerEvent) {
320
+ if (!isBrowser || isDragging.value || defaultOptions.value.disabled) { return }
321
+ if (e.button !== 0 || e.isPrimary === false) { return }
322
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) { return }
323
+
324
+ const dragEl = findDraggableParent(e.target as Element)
325
+ if (!dragEl || dragEl.dataset.draggable === 'false' || e.currentTarget !== dragEl) { return }
326
+
327
+ if (defaultOptions.value.handle) {
328
+ const handleEl = (e.target as Element).closest(defaultOptions.value.handle)
329
+ if (!handleEl || !dragEl.contains(handleEl)) { return }
330
+ }
331
+
332
+ beginDrag(dragEl, e.clientX, e.clientY, e)
333
+
334
+ pointerSession.start(e, {
335
+ cursor: 'grabbing',
336
+ target: dragEl,
337
+ onMove: next => onDragMove(next),
338
+ onEnd: next => onDragEnd(next),
339
+ onCancel: () => resetVisualState(),
340
+ })
341
+ }
342
+
343
+ function onDragStart(e: MouseEvent) {
344
+ if (!isBrowser || isDragging.value || defaultOptions.value.disabled) { return }
345
+ if (e.button !== 0) { return }
346
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) { return }
347
+
348
+ const dragEl = findDraggableParent(e.target as Element)
349
+ if (!dragEl || dragEl.dataset.draggable === 'false' || e.currentTarget !== dragEl) { return }
350
+
351
+ if (defaultOptions.value.handle) {
352
+ const handleEl = (e.target as Element).closest(defaultOptions.value.handle)
353
+ if (!handleEl || !dragEl.contains(handleEl)) { return }
354
+ }
355
+
356
+ e.preventDefault()
357
+ beginDrag(dragEl, e.clientX, e.clientY, e)
352
358
 
353
- // Prevent text selection during drag
354
359
  if (isBrowser) {
355
360
  document.body.style.userSelect = 'none'
356
361
  document.body.style.webkitUserSelect = 'none'
@@ -383,18 +388,15 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
383
388
  const scrollParent = findScrollParent(dragElement.value!) as HTMLElement
384
389
  const rect = scrollParent.getBoundingClientRect()
385
390
  const margin = 50
386
- const speed = Math.min(20, rect.height * 0.1) // Adjust speed based on container height
391
+ const speed = Math.min(20, rect.height * 0.1)
387
392
 
388
393
  const { scrollTop } = scrollParent
389
394
  const maxScroll = scrollParent.scrollHeight - scrollParent.clientHeight
390
395
 
391
- // Use requestAnimationFrame for smoother scrolling
392
396
  if (e.clientY - rect.top < margin) {
393
- const newScroll = Math.max(0, scrollTop - speed)
394
- scrollParent.scrollTop = newScroll
397
+ scrollParent.scrollTop = Math.max(0, scrollTop - speed)
395
398
  } else if (rect.bottom - e.clientY < margin) {
396
- const newScroll = Math.min(maxScroll, scrollTop + speed)
397
- scrollParent.scrollTop = newScroll
399
+ scrollParent.scrollTop = Math.min(maxScroll, scrollTop + speed)
398
400
  }
399
401
  })
400
402
  }
@@ -402,23 +404,19 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
402
404
  function onDragMove(e: MouseEvent) {
403
405
  if (!isDragging.value || !dragElement.value) { return }
404
406
 
405
- // Move ghost element with cursor
406
407
  if (ghostElement.value) {
407
408
  const dx = e.clientX - startX
408
409
  const dy = e.clientY - startY
409
410
  ghostElement.value.style.transform = `translate3d(${dx}px, ${dy}px, 0)`
410
411
  }
411
412
 
412
- // Get the draggable list and current positions
413
413
  const list = dragElement.value.__drag_list!
414
414
  const draggedList = list.filter(el => el.dataset.draggable !== 'false')
415
415
  const dragIndex = draggedList.indexOf(dragElement.value)
416
416
 
417
- // Find the element at cursor position
418
417
  const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY)
419
418
  const newDropTarget = findDraggableParent(elementAtPoint)
420
419
 
421
- // Validate drop target
422
420
  if (!newDropTarget || !draggedList.includes(newDropTarget)) {
423
421
  dropTarget.value = null
424
422
  if (dropIndicator.value) {
@@ -429,23 +427,19 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
429
427
 
430
428
  const dropIndex = calculateDropIndex(e, draggedList, dragIndex, newDropTarget)
431
429
 
432
- // Only update if position actually changed
433
430
  if (dropTarget.value !== newDropTarget || dropTarget.value.__intended_index !== dropIndex) {
434
431
  dropTarget.value = newDropTarget
435
432
  dropTarget.value.__intended_index = dropIndex
436
433
 
437
- // Update line indicator
438
434
  if (dropIndicator.value && defaultOptions.value.mode === 'line') {
439
435
  const targetIndex = draggedList.indexOf(newDropTarget)
440
436
  const isAfter = dropIndex > targetIndex
441
437
  updateDropIndicator(newDropTarget, isAfter)
442
438
  }
443
439
 
444
- // Update element positions
445
440
  const finalIndex = insertionIndexToFinalIndex(dragIndex, dropIndex, draggedList.length)
446
441
  const shift = shiftAmount
447
442
  draggedList.forEach((el) => {
448
- // Use transform3d for better performance
449
443
  el.style.transition = `transform ${defaultOptions.value.animation}ms cubic-bezier(0.2, 0, 0, 1)`
450
444
  el.style.transform = 'translate3d(0, 0, 0)'
451
445
 
@@ -473,19 +467,19 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
473
467
  }
474
468
 
475
469
  function onDragEnd(e: MouseEvent) {
476
- if (defaultOptions.value.disabled) { cleanup(); return }
470
+ if (defaultOptions.value.disabled) { resetVisualState(); return }
477
471
  if (isDragging.value) {
478
472
  lastUpdateTime = 0
479
473
  onDragMove(e)
480
474
  }
481
475
  if (!isDragging.value || !dragElement.value || !dropTarget.value) {
482
- cleanup()
476
+ resetVisualState()
483
477
  return
484
478
  }
485
479
 
486
480
  const list = dragElement.value.__drag_list
487
481
  if (!list || !list.length) {
488
- cleanup()
482
+ resetVisualState()
489
483
  return
490
484
  }
491
485
 
@@ -506,12 +500,12 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
506
500
  dragEvent.newIndex = actualDropIndex
507
501
  dragEvent.item = defaultOptions.value.items?.[actualDragIndex] ?? dragElement.value.__drag_data
508
502
 
509
- cleanup()
503
+ resetVisualState()
510
504
  defaultOptions.value.onEnd?.(dragEvent)
511
505
  return
512
506
  }
513
507
 
514
- cleanup()
508
+ resetVisualState()
515
509
  }
516
510
 
517
511
  function onTouchStart(e: TouchEvent) {
@@ -528,38 +522,10 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
528
522
  const { handle } = defaultOptions.value
529
523
  if (handle && !target.closest(handle)) { return }
530
524
 
531
- e.preventDefault() // Prevent scrolling
532
- isDragging.value = true
533
- dragElement.value = draggableParent
534
-
535
- startX = touch.clientX
536
- startY = touch.clientY
537
- const rect = draggableParent.getBoundingClientRect()
538
- initialX = rect.left
539
- initialY = rect.top
540
-
541
- // Rest of the setup same as onDragStart...
542
- ghostElement.value = createGhost(draggableParent)
543
- if (ghostElement.value) {
544
- ghostElement.value.style.left = `${initialX}px`
545
- ghostElement.value.style.top = `${initialY}px`
546
- }
547
-
548
- // Only create drop indicator in line mode
549
- if (defaultOptions.value.mode === 'line') {
550
- dropIndicator.value = createDropIndicator()
551
- }
552
-
553
- // Apply mode-specific styles and get shift amount
554
- dragElement.value.classList.add(defaultOptions.value.dragClass!)
555
- shiftAmount = defaultOptions.value.mode === 'ghost'
556
- ? handleGhostMode(dragElement.value)
557
- : handleLineMode(dragElement.value)
558
-
559
- defaultOptions.value.onStart?.(e as unknown as MouseEvent)
525
+ e.preventDefault()
526
+ beginDrag(draggableParent, touch.clientX, touch.clientY, e as unknown as MouseEvent)
560
527
 
561
528
  if (isBrowser) {
562
- // Prevent text selection during drag
563
529
  document.body.style.userSelect = 'none'
564
530
  document.body.style.webkitUserSelect = 'none'
565
531
  document.body.style.cursor = 'grabbing'
@@ -573,7 +539,7 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
573
539
  }
574
540
 
575
541
  function onTouchMove(e: TouchEvent) {
576
- e.preventDefault() // Prevent scrolling
542
+ e.preventDefault()
577
543
  const touch = e.touches[0]
578
544
  if (!touch) { return }
579
545
 
@@ -607,7 +573,6 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
607
573
  if (!isBrowser) { return }
608
574
  cleanupFn?.()
609
575
 
610
- // Reset state
611
576
  isDragging.value = false
612
577
  dragElement.value = null
613
578
  ghostElement.value = null
@@ -633,6 +598,7 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
633
598
 
634
599
  el.__drag_data = defaultOptions.value.items?.[el.__drag_index]
635
600
 
601
+ el.addEventListener('pointerdown', onPointerStart as EventListener)
636
602
  el.addEventListener('mousedown', onDragStart)
637
603
  el.addEventListener('touchstart', onTouchStart)
638
604
  }
@@ -641,6 +607,7 @@ export function useDraggable(options: MaybeRefOrGetter<DraggableOptions> = {}) {
641
607
  cleanupFn = () => {
642
608
  cleanup()
643
609
  elements.forEach((el) => {
610
+ el.removeEventListener('pointerdown', onPointerStart as EventListener)
644
611
  el.removeEventListener('mousedown', onDragStart)
645
612
  el.removeEventListener('touchstart', onTouchStart)
646
613
  delete el.__drag_group
@@ -1,13 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onBeforeUnmount, ref, watch } from 'vue'
3
3
  import { Dropdown, EmptyState, Icon, TabsNav, TextInput } from '../../../index'
4
- import { FONT_AWESOME_BRANDS_ICONS, FONT_AWESOME_ICONS, MATERIAL_ICONS } from '../../Icon/constants'
4
+ import { FONT_AWESOME_BRANDS_ICONS, MATERIAL_ICONS } from '../../Icon/constants'
5
5
 
6
- defineProps<{
6
+ withDefaults(defineProps<{
7
7
  modelValue?: string
8
8
  label?: string
9
9
  placeholder?: string
10
- }>()
10
+ emptyLabel?: string
11
+ searchPlaceholder?: string
12
+ noResultsText?: string
13
+ }>(), {
14
+ emptyLabel: 'Choose an icon',
15
+ searchPlaceholder: 'Search icons',
16
+ noResultsText: 'No icons found',
17
+ })
11
18
 
12
19
  const emit = defineEmits<{
13
20
  'update:modelValue': [value: string | undefined]
@@ -15,20 +22,18 @@ const emit = defineEmits<{
15
22
 
16
23
  const dropdown = ref<InstanceType<typeof Dropdown> | null>(null)
17
24
  const search = ref('')
18
- const activeTab = ref<'material' | 'fa' | 'brands'>('material')
25
+ const activeTab = ref<'material' | 'brands'>('material')
19
26
  const visibleCount = ref(80)
20
27
  const tabsGroup = `icon-picker-${Math.random().toString(36).slice(2)}`
21
28
  let batchTimer: ReturnType<typeof setTimeout> | undefined
22
29
 
23
30
  const tabs = [
24
31
  { label: 'Material', value: 'material' },
25
- { label: 'FA', value: 'fa' },
26
32
  { label: 'Brands', value: 'brands' },
27
33
  ]
28
34
 
29
35
  const iconSets = {
30
36
  material: MATERIAL_ICONS,
31
- fa: FONT_AWESOME_ICONS,
32
37
  brands: FONT_AWESOME_BRANDS_ICONS,
33
38
  }
34
39
 
@@ -86,7 +91,7 @@ onBeforeUnmount(() => {
86
91
  <Icon v-if="modelValue" :icon="modelValue" :size="1.1" />
87
92
  <span v-else class="txt11 opacity-4">—</span>
88
93
  </span>
89
- <span class="txt12 flex-grow opacity-6 ellipsis-1">{{ modelValue || placeholder || 'Choose an icon' }}</span>
94
+ <span class="txt12 flex-grow opacity-6 ellipsis-1">{{ modelValue || placeholder || emptyLabel }}</span>
90
95
  <Icon v-if="modelValue" icon="close" :size="0.9" class="opacity-5 hover-opacity-10" @click.stop="clear" />
91
96
  <Icon :icon="shown ? 'expand_less' : 'expand_more'" :size="1" class="opacity-4" />
92
97
  </div>
@@ -94,7 +99,7 @@ onBeforeUnmount(() => {
94
99
 
95
100
  <div class="w-300px overflow-hidden">
96
101
  <TabsNav v-model="activeTab" :tabs="tabs" :group="tabsGroup" flat class="round-none w-100p mx-0 txt-center" />
97
- <TextInput v-model="search" icon-start="search" placeholder="Search icons" class="mb-0" />
102
+ <TextInput v-model="search" icon-start="search" :placeholder="searchPlaceholder" class="mb-0" />
98
103
  <div class="grid grid-wrap-7 gap-025 p-05 overflow max-h-300px">
99
104
  <div
100
105
  v-for="icon in visibleIcons"
@@ -107,7 +112,7 @@ onBeforeUnmount(() => {
107
112
  <Icon :icon="icon" :size="1.2" />
108
113
  </div>
109
114
  </div>
110
- <EmptyState v-if="visibleIcons.length === 0" compact icon="none" description="No icons found" />
115
+ <EmptyState v-if="visibleIcons.length === 0" compact icon="none" :description="noResultsText" />
111
116
  </div>
112
117
  </Dropdown>
113
118
  </div>