@brickflow/ui 0.0.56 → 0.0.58

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 (47) hide show
  1. package/dist/module.d.mts +4 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +35 -7
  4. package/dist/runtime/brickflow-ui.d.ts +1 -0
  5. package/dist/runtime/components/Button/index.vue +5 -3
  6. package/dist/runtime/components/Dialog/DemoContent.vue +33 -0
  7. package/dist/runtime/components/Dialog/DemoForm.vue +54 -0
  8. package/dist/runtime/components/Dialog/DemoSimple.vue +23 -0
  9. package/dist/runtime/components/Dialog/DemoStack.vue +41 -0
  10. package/dist/runtime/components/Dialog/Host.vue +654 -0
  11. package/dist/runtime/components/Dialog/basic.demo.vue +27 -0
  12. package/dist/runtime/components/Dialog/form.demo.vue +34 -0
  13. package/dist/runtime/components/Dialog/index.vue +53 -0
  14. package/dist/runtime/components/Dialog/scroll.demo.vue +27 -0
  15. package/dist/runtime/components/Dialog/stack.demo.vue +19 -0
  16. package/dist/runtime/components/Form/index.vue +46 -1
  17. package/dist/runtime/components/Form/login.demo.vue +12 -1
  18. package/dist/runtime/components/Form/rules.demo.vue +108 -0
  19. package/dist/runtime/components/Icon/gallery.demo.vue +1 -1
  20. package/dist/runtime/components/Icon/index.vue +3 -1
  21. package/dist/runtime/components/Input/index.vue +151 -27
  22. package/dist/runtime/components/Input/label.demo.vue +15 -3
  23. package/dist/runtime/components/Input/mask.demo.vue +73 -0
  24. package/dist/runtime/components/Input/variants.demo.vue +16 -0
  25. package/dist/runtime/components/InputNumber/controls.demo.vue +145 -0
  26. package/dist/runtime/components/InputNumber/denominator.demo.vue +1 -1
  27. package/dist/runtime/components/InputNumber/index.vue +29 -8
  28. package/dist/runtime/components/TransitionSize/duration.demo.vue +88 -0
  29. package/dist/runtime/components/TransitionSize/flex.demo.vue +112 -0
  30. package/dist/runtime/components/TransitionSize/height.demo.vue +88 -0
  31. package/dist/runtime/components/TransitionSize/index.vue +69 -27
  32. package/dist/runtime/components/TransitionSize/width.demo.vue +88 -0
  33. package/dist/runtime/composables/useBrickflow.d.ts +2 -0
  34. package/dist/runtime/composables/useDialog.d.ts +38 -0
  35. package/dist/runtime/composables/useDialog.js +40 -0
  36. package/dist/runtime/composables/useFieldRules.d.ts +16 -0
  37. package/dist/runtime/composables/useFieldRules.js +42 -0
  38. package/dist/runtime/composables/useScreen.d.ts +8 -0
  39. package/dist/runtime/plugins/dialog.d.ts +3 -0
  40. package/dist/runtime/plugins/dialog.js +22 -0
  41. package/dist/runtime/tailwind.d.ts +1 -1
  42. package/dist/runtime/tailwind.js +2 -2
  43. package/dist/runtime/utils/rules.d.ts +34 -0
  44. package/dist/runtime/utils/rules.js +68 -0
  45. package/dist/types.d.mts +4 -0
  46. package/package.json +7 -6
  47. package/dist/runtime/icon-font.d.ts +0 -4
@@ -0,0 +1,654 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
3
+
4
+ import type {
5
+ DialogController,
6
+ DialogEntry,
7
+ DialogPlacement,
8
+ DialogPlacementConfig,
9
+ } from '../../composables/useDialog'
10
+
11
+ import { useScreen } from '../../composables/useScreen'
12
+ import Icon from '../Icon/index.vue'
13
+ import Dialog, { dialogConfig } from './index.vue'
14
+
15
+ const props = defineProps<{ dialog: DialogController }>()
16
+ const screen = useScreen()
17
+ const placementClasses = {
18
+ bottom: UI_STYLE.layerPlacementClasses.bottom,
19
+ center: UI_STYLE.layerPlacementClasses.center,
20
+ fullscreen: UI_STYLE.layerPlacementClasses.fullscreen,
21
+ }
22
+ const screenKeys = ['ms', 'mb', 'tb', 'lp', 'dk'] as const
23
+ function resolvePlacement(config: DialogPlacementConfig): DialogPlacement {
24
+ if (typeof config === 'string') {
25
+ return config
26
+ }
27
+ const key = screenKeys.find((candidate) => screen[candidate])
28
+ return key ? config[key] : 'center'
29
+ }
30
+ const dragY = ref(0)
31
+ const renderedDialogs = ref<
32
+ {
33
+ entry: DialogEntry
34
+ loadingVisible: boolean
35
+ ready: boolean
36
+ visible: boolean
37
+ }[]
38
+ >([])
39
+ const loadingTimers = new Map<number, number>()
40
+ const draggedId = ref<number>()
41
+ const dismissDistance = ref(320)
42
+ const isDragging = ref(false)
43
+ let startY = 0
44
+ let dragFrame: number | undefined
45
+ let pendingDragY = 0
46
+ let pointerId: number | undefined
47
+ let pointerDialogId: number | undefined
48
+ let pointerStartX = 0
49
+ let pointerStartY = 0
50
+ let settleTimer: number | undefined
51
+ let suppressBackdropClickUntil = 0
52
+ let dragMoved = false
53
+ let touchId: number | undefined
54
+ let touchDialogId: number | undefined
55
+ let touchLastY = 0
56
+ let touchStartX = 0
57
+ let touchStartY = 0
58
+ let touchTarget: HTMLElement | undefined
59
+ let previousOverflow = ''
60
+ let focusObserver: MutationObserver | undefined
61
+ let deniedAnimation: Animation | undefined
62
+ const completedLeaves = new Map<number, Set<'backdrop' | 'panel'>>()
63
+ const focusOrigins = new Map<number, HTMLElement>()
64
+
65
+ const backdropOpacity = computed(() => Math.max(0, 1 - Math.max(0, dragY.value) / dismissDistance.value))
66
+ const topId = (): number | undefined => props.dialog.stack.at(-1)?.id
67
+
68
+ function beginDrag(id: number, clientY: number, target: HTMLElement): void {
69
+ draggedId.value = id
70
+ isDragging.value = true
71
+ window.getSelection()?.removeAllRanges()
72
+ const layer = target.parentElement
73
+ const panel = layer?.querySelector('[data-dialog-panel]')
74
+ const panelTop = panel?.getBoundingClientRect().top
75
+ dismissDistance.value =
76
+ panelTop === undefined ? 320 : Math.max(1, Math.ceil(window.innerHeight - panelTop) + 1)
77
+ startY = clientY
78
+ dragMoved = false
79
+ pendingDragY = 0
80
+ dragY.value = 0
81
+ }
82
+
83
+ function clearGesture(): void {
84
+ if (dragFrame !== undefined) {
85
+ window.cancelAnimationFrame(dragFrame)
86
+ dragFrame = undefined
87
+ }
88
+ pendingDragY = 0
89
+ if (settleTimer !== undefined) {
90
+ window.clearTimeout(settleTimer)
91
+ settleTimer = undefined
92
+ }
93
+ draggedId.value = undefined
94
+ dragY.value = 0
95
+ isDragging.value = false
96
+ pointerId = undefined
97
+ pointerDialogId = undefined
98
+ touchId = undefined
99
+ touchDialogId = undefined
100
+ touchTarget = undefined
101
+ }
102
+
103
+ function denyClose(id: number): void {
104
+ if (id !== topId() || !props.dialog.stack.at(-1)?.options.preventClose) {
105
+ return
106
+ }
107
+ const panel = topPanel()
108
+ if (!panel || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
109
+ return
110
+ }
111
+ deniedAnimation?.cancel()
112
+ deniedAnimation = panel.animate(
113
+ [
114
+ { transform: 'translateX(0)' },
115
+ { transform: 'translateX(-12px)' },
116
+ { transform: 'translateX(10px)' },
117
+ { transform: 'translateX(-7px)' },
118
+ { transform: 'translateX(4px)' },
119
+ { transform: 'translateX(0)' },
120
+ ],
121
+ { duration: 380, easing: 'ease-in-out' },
122
+ )
123
+ }
124
+
125
+ function endDrag(event: PointerEvent): void {
126
+ if (event.pointerId !== pointerId) {
127
+ return
128
+ }
129
+ pointerId = undefined
130
+ pointerDialogId = undefined
131
+ if (draggedId.value !== undefined) {
132
+ finishDrag(event.type === 'pointercancel', event.currentTarget as HTMLElement)
133
+ }
134
+ }
135
+
136
+ function endTouch(event: TouchEvent): void {
137
+ const touch = Array.from(event.changedTouches).find((item) => item.identifier === touchId)
138
+ if (!touch) {
139
+ return
140
+ }
141
+ touchId = undefined
142
+ touchDialogId = undefined
143
+ touchTarget = undefined
144
+ if (draggedId.value !== undefined) {
145
+ finishDrag(event.type === 'touchcancel', event.currentTarget as HTMLElement)
146
+ }
147
+ }
148
+
149
+ function finishDrag(cancelled: boolean, target: HTMLElement): void {
150
+ flushDrag()
151
+ if (dragMoved) {
152
+ suppressBackdropClickUntil = performance.now() + 400
153
+ }
154
+ const id = draggedId.value
155
+ const layer = target.parentElement
156
+ const shouldDismiss = !cancelled && dragY.value > 100 && id !== undefined
157
+ const prevented = shouldDismiss && props.dialog.stack.at(-1)?.options.preventClose
158
+ isDragging.value = false
159
+ nextTick(() => {
160
+ if (draggedId.value !== id) {
161
+ return
162
+ }
163
+ layer?.querySelector('[data-dialog-panel]')?.getBoundingClientRect()
164
+ dragY.value = shouldDismiss && !prevented ? Math.max(dragY.value, dismissDistance.value) : 0
165
+ settleTimer = window.setTimeout(() => {
166
+ if (shouldDismiss && id !== undefined) {
167
+ if (prevented) {
168
+ denyClose(id)
169
+ } else {
170
+ props.dialog.close(id)
171
+ return
172
+ }
173
+ }
174
+ clearGesture()
175
+ }, 320)
176
+ })
177
+ }
178
+
179
+ function flushDrag(): void {
180
+ if (dragFrame !== undefined) {
181
+ window.cancelAnimationFrame(dragFrame)
182
+ dragFrame = undefined
183
+ dragY.value = pendingDragY
184
+ }
185
+ }
186
+
187
+ function focusableElements(panel: HTMLElement): HTMLElement[] {
188
+ return Array.from(
189
+ panel.querySelectorAll<HTMLElement>(
190
+ 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
191
+ ),
192
+ ).filter((element) => element.getClientRects().length > 0)
193
+ }
194
+
195
+ function focusPanel(): void {
196
+ const panel = topPanel()
197
+ if (panel) {
198
+ const first = focusableElements(panel)[0]
199
+ if (first) {
200
+ first.focus()
201
+ return
202
+ }
203
+ panel.focus()
204
+ focusObserver = new MutationObserver(() => {
205
+ if (topPanel() !== panel || document.activeElement !== panel) {
206
+ focusObserver?.disconnect()
207
+ return
208
+ }
209
+ const next = focusableElements(panel)[0]
210
+ if (next) {
211
+ next.focus()
212
+ focusObserver?.disconnect()
213
+ }
214
+ })
215
+ focusObserver.observe(panel, { childList: true, subtree: true })
216
+ }
217
+ }
218
+
219
+ function moveDrag(event: PointerEvent): void {
220
+ if (event.pointerId !== pointerId) {
221
+ return
222
+ }
223
+ if (draggedId.value === undefined && pointerDialogId !== undefined) {
224
+ const distanceY = event.clientY - pointerStartY
225
+ if (Math.abs(distanceY) <= 4 || Math.abs(event.clientX - pointerStartX) > Math.abs(distanceY)) {
226
+ return
227
+ }
228
+ beginDrag(pointerDialogId, pointerStartY, event.currentTarget as HTMLElement)
229
+ ;(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId)
230
+ }
231
+ if (draggedId.value !== undefined) {
232
+ event.preventDefault()
233
+ }
234
+ updateDrag(event.clientY)
235
+ }
236
+
237
+ function moveTouch(event: TouchEvent): void {
238
+ const touch = Array.from(event.changedTouches).find((item) => item.identifier === touchId)
239
+ if (!touch || touchDialogId === undefined) {
240
+ return
241
+ }
242
+ const deltaY = touch.clientY - touchLastY
243
+ touchLastY = touch.clientY
244
+ if (draggedId.value === touchDialogId) {
245
+ event.preventDefault()
246
+ updateDrag(touch.clientY)
247
+ return
248
+ }
249
+ const totalY = touch.clientY - touchStartY
250
+ if (Math.abs(totalY) <= 4 || Math.abs(touch.clientX - touchStartX) > Math.abs(totalY) || deltaY === 0) {
251
+ return
252
+ }
253
+ const panel = (event.currentTarget as HTMLElement).parentElement?.querySelector('[data-dialog-panel]')
254
+ if (!panel || !touchTarget) {
255
+ return
256
+ }
257
+ const scrollables = scrollableAncestors(touchTarget, panel)
258
+ const remaining = scrollables.reduce(
259
+ (total, element) =>
260
+ total + (deltaY > 0 ? element.scrollTop : element.scrollHeight - element.clientHeight - element.scrollTop),
261
+ 0,
262
+ )
263
+ if (remaining > 1) {
264
+ return
265
+ }
266
+ event.preventDefault()
267
+ beginDrag(touchDialogId, touch.clientY - deltaY, event.currentTarget as HTMLElement)
268
+ updateDrag(touch.clientY)
269
+ }
270
+
271
+ function onAfterLeave(id: number, part: 'backdrop' | 'panel'): void {
272
+ const completed = completedLeaves.get(id) ?? new Set<'backdrop' | 'panel'>()
273
+ completed.add(part)
274
+ if (completed.size < 2) {
275
+ completedLeaves.set(id, completed)
276
+ return
277
+ }
278
+ completedLeaves.delete(id)
279
+ renderedDialogs.value = renderedDialogs.value.filter(({ entry }) => entry.id !== id)
280
+ if (id === draggedId.value) {
281
+ clearGesture()
282
+ }
283
+ }
284
+
285
+ function onBackdropClick(id: number): void {
286
+ if (performance.now() < suppressBackdropClickUntil) {
287
+ return
288
+ }
289
+ if (id === topId()) {
290
+ if (props.dialog.stack.at(-1)?.options.preventClose) {
291
+ denyClose(id)
292
+ } else {
293
+ props.dialog.close(id)
294
+ }
295
+ }
296
+ }
297
+
298
+ function onBeforeBackdropLeave(element: Element): void {
299
+ const backdrop = element as HTMLElement
300
+ const id = Number(backdrop.parentElement?.dataset.dialogId)
301
+ if (id === draggedId.value && backdropOpacity.value < 1) {
302
+ backdrop.style.opacity = String(backdropOpacity.value)
303
+ } else {
304
+ backdrop.style.removeProperty('opacity')
305
+ }
306
+ removeStyleClasses(backdrop, UI_STYLE.animation.settleBackdrop)
307
+ }
308
+
309
+ function onBeforePanelLeave(element: Element): void {
310
+ removeStyleClasses(element, UI_STYLE.animation.settlePanel)
311
+ }
312
+
313
+ function onContentResolved(id: number): void {
314
+ const rendered = renderedDialogs.value.find(({ entry }) => entry.id === id)
315
+ if (rendered) {
316
+ rendered.ready = true
317
+ }
318
+ stopLoading(id)
319
+ }
320
+
321
+ function onKeydown(event: KeyboardEvent): void {
322
+ if (event.key === 'Tab') {
323
+ const panel = topPanel()
324
+ if (panel) {
325
+ const focusables = focusableElements(panel)
326
+ const first = focusables[0] ?? panel
327
+ const last = focusables.at(-1) ?? panel
328
+ if (!panel.contains(document.activeElement) || document.activeElement === panel) {
329
+ event.preventDefault()
330
+ ;(event.shiftKey ? last : first).focus()
331
+ } else if (event.shiftKey && document.activeElement === first) {
332
+ event.preventDefault()
333
+ last.focus()
334
+ } else if (!event.shiftKey && document.activeElement === last) {
335
+ event.preventDefault()
336
+ first.focus()
337
+ }
338
+ }
339
+ }
340
+ const topEntry = props.dialog.stack.at(-1)
341
+ if (event.key === 'Escape' && topEntry) {
342
+ event.preventDefault()
343
+ if (topEntry.options.preventClose) {
344
+ denyClose(topEntry.id)
345
+ } else {
346
+ props.dialog.close()
347
+ }
348
+ }
349
+ }
350
+
351
+ function panelHiddenClass(placement: DialogPlacement): string {
352
+ return placement === 'bottom' ? UI_STYLE.animation.bottomPanelHidden : UI_STYLE.animation.panelHidden
353
+ }
354
+
355
+ function removeStyleClasses(element: Element, classes: string): void {
356
+ element.classList.remove(...classes.split(' '))
357
+ }
358
+
359
+ function scrollableAncestors(target: HTMLElement, panel: Element): HTMLElement[] {
360
+ const ancestors: HTMLElement[] = []
361
+ let current: HTMLElement | null = target
362
+ while (current && panel.contains(current)) {
363
+ const overflowY = window.getComputedStyle(current).overflowY
364
+ if (/auto|scroll|overlay/u.test(overflowY) && current.scrollHeight > current.clientHeight + 1) {
365
+ ancestors.push(current)
366
+ }
367
+ current = current.parentElement
368
+ }
369
+ return ancestors
370
+ }
371
+
372
+ function startDrag(event: PointerEvent, id: number): void {
373
+ if (
374
+ event.pointerType === 'touch' ||
375
+ !event.isPrimary ||
376
+ event.button !== 0 ||
377
+ (event.target instanceof Element && event.target.closest('input, textarea, [contenteditable]')) ||
378
+ id !== topId() ||
379
+ draggedId.value !== undefined ||
380
+ resolvePlacement(props.dialog.stack.at(-1)?.options.placement ?? 'center') !== 'bottom'
381
+ ) {
382
+ return
383
+ }
384
+ pointerId = event.pointerId
385
+ pointerDialogId = id
386
+ pointerStartX = event.clientX
387
+ pointerStartY = event.clientY
388
+ }
389
+
390
+ function startLoadingTimer(id: number): void {
391
+ if (typeof window === 'undefined') {
392
+ return
393
+ }
394
+ const timer = window.setTimeout(() => {
395
+ loadingTimers.delete(id)
396
+ const rendered = renderedDialogs.value.find(({ entry }) => entry.id === id)
397
+ if (rendered?.visible && !rendered.ready) {
398
+ rendered.loadingVisible = true
399
+ }
400
+ }, dialogConfig.loadingDelay)
401
+ loadingTimers.set(id, timer)
402
+ }
403
+
404
+ function startTouch(event: TouchEvent, id: number): void {
405
+ if (
406
+ touchId !== undefined ||
407
+ draggedId.value !== undefined ||
408
+ id !== topId() ||
409
+ resolvePlacement(props.dialog.stack.at(-1)?.options.placement ?? 'center') !== 'bottom'
410
+ ) {
411
+ return
412
+ }
413
+ const touch = event.changedTouches[0]
414
+ if (!touch) {
415
+ return
416
+ }
417
+ touchId = touch.identifier
418
+ touchDialogId = id
419
+ touchStartX = touch.clientX
420
+ touchStartY = touch.clientY
421
+ touchLastY = touch.clientY
422
+ touchTarget = event.target as HTMLElement
423
+ if (
424
+ touchTarget.closest('[data-dialog-handle]') ||
425
+ (event.currentTarget as HTMLElement).hasAttribute('data-dialog-backdrop')
426
+ ) {
427
+ beginDrag(id, touch.clientY, event.currentTarget as HTMLElement)
428
+ }
429
+ }
430
+
431
+ function stopLoading(id: number): void {
432
+ const timer = loadingTimers.get(id)
433
+ if (timer !== undefined) {
434
+ clearTimeout(timer)
435
+ loadingTimers.delete(id)
436
+ }
437
+ const rendered = renderedDialogs.value.find(({ entry }) => entry.id === id)
438
+ if (rendered) {
439
+ rendered.loadingVisible = false
440
+ }
441
+ }
442
+
443
+ function topPanel(): HTMLElement | null {
444
+ const id = topId()
445
+ return id === undefined ? null : document.querySelector(`[data-dialog-id="${id}"] [data-dialog-panel]`)
446
+ }
447
+
448
+ function updateDrag(clientY: number): void {
449
+ const distance = clientY - startY
450
+ dragMoved ||= Math.abs(distance) > 4
451
+ pendingDragY = distance < 0 ? -72 * (1 - Math.exp(distance / 120)) : distance
452
+ if (dragFrame === undefined) {
453
+ dragFrame = window.requestAnimationFrame(() => {
454
+ dragFrame = undefined
455
+ dragY.value = pendingDragY
456
+ })
457
+ }
458
+ }
459
+
460
+ watch(
461
+ () => props.dialog.stack.length,
462
+ (length) => {
463
+ if (length) {
464
+ if (!previousOverflow) {
465
+ previousOverflow = document.body.style.overflow || 'auto'
466
+ }
467
+ document.body.style.overflow = 'hidden'
468
+ } else if (previousOverflow) {
469
+ document.body.style.overflow = previousOverflow === 'auto' ? '' : previousOverflow
470
+ previousOverflow = ''
471
+ }
472
+ },
473
+ )
474
+
475
+ watch(
476
+ () => props.dialog.stack.map((entry) => entry.id),
477
+ (ids, previousIds) => {
478
+ focusObserver?.disconnect()
479
+ const added = ids.find((id) => !previousIds.includes(id))
480
+ if (added !== undefined && document.activeElement instanceof HTMLElement) {
481
+ focusOrigins.set(added, document.activeElement)
482
+ }
483
+ const removed = previousIds.filter((id) => !ids.includes(id))
484
+ const restoreId = ids.length ? previousIds.at(-1) : previousIds[0]
485
+ const restore = restoreId === undefined ? undefined : focusOrigins.get(restoreId)
486
+ for (const id of removed) {
487
+ focusOrigins.delete(id)
488
+ }
489
+ nextTick(() => {
490
+ if (ids.length) {
491
+ if (removed.length && restore?.isConnected && topPanel()?.contains(restore)) {
492
+ restore.focus()
493
+ } else {
494
+ focusPanel()
495
+ }
496
+ } else if (restore?.isConnected) {
497
+ restore.focus()
498
+ }
499
+ })
500
+ },
501
+ )
502
+
503
+ watch(
504
+ () => props.dialog.stack.map((entry) => entry.id),
505
+ (ids) => {
506
+ for (const rendered of renderedDialogs.value) {
507
+ if (!ids.includes(rendered.entry.id)) {
508
+ rendered.visible = false
509
+ stopLoading(rendered.entry.id)
510
+ if (!rendered.ready) {
511
+ onAfterLeave(rendered.entry.id, 'panel')
512
+ }
513
+ }
514
+ }
515
+ for (const entry of props.dialog.stack) {
516
+ if (!renderedDialogs.value.some((rendered) => rendered.entry.id === entry.id)) {
517
+ renderedDialogs.value.push({ entry, loadingVisible: false, ready: false, visible: true })
518
+ startLoadingTimer(entry.id)
519
+ }
520
+ }
521
+ },
522
+ { immediate: true },
523
+ )
524
+
525
+ onMounted(() => {
526
+ document.addEventListener('keydown', onKeydown)
527
+ for (const rendered of renderedDialogs.value) {
528
+ if (!rendered.ready && !loadingTimers.has(rendered.entry.id)) {
529
+ startLoadingTimer(rendered.entry.id)
530
+ }
531
+ }
532
+ })
533
+ onBeforeUnmount(() => {
534
+ document.removeEventListener('keydown', onKeydown)
535
+ for (const timer of loadingTimers.values()) {
536
+ clearTimeout(timer)
537
+ }
538
+ loadingTimers.clear()
539
+ deniedAnimation?.cancel()
540
+ focusObserver?.disconnect()
541
+ clearGesture()
542
+ if (previousOverflow) {
543
+ document.body.style.overflow = previousOverflow === 'auto' ? '' : previousOverflow
544
+ }
545
+ })
546
+ </script>
547
+
548
+ <template>
549
+ <Teleport to="body">
550
+ <template
551
+ v-for="({ entry, loadingVisible, visible }, index) in renderedDialogs"
552
+ :key="entry.id"
553
+ >
554
+ <div
555
+ :data-dialog-id="entry.id"
556
+ :class="[
557
+ UI_STYLE.layer,
558
+ placementClasses[resolvePlacement(entry.options.placement)],
559
+ entry.id === draggedId && isDragging && 'select-none',
560
+ ]"
561
+ :data-placement="resolvePlacement(entry.options.placement)"
562
+ :style="{ zIndex: 1000 + index * 2 }"
563
+ >
564
+ <Transition
565
+ appear
566
+ :enterActiveClass="UI_STYLE.animation.backdropEnterActive"
567
+ :enterFromClass="UI_STYLE.animation.backdropHidden"
568
+ :leaveActiveClass="UI_STYLE.animation.backdropLeaveActive"
569
+ :leaveToClass="UI_STYLE.animation.backdropHidden"
570
+ @beforeLeave="onBeforeBackdropLeave"
571
+ @afterLeave="onAfterLeave(entry.id, 'backdrop')"
572
+ >
573
+ <div
574
+ v-if="visible"
575
+ :class="[
576
+ UI_STYLE.backdrop,
577
+ entry.id === draggedId && !isDragging && UI_STYLE.animation.settleBackdrop,
578
+ ]"
579
+ data-dialog-backdrop
580
+ aria-hidden="true"
581
+ :style="entry.id === draggedId ? { opacity: backdropOpacity } : undefined"
582
+ @click="onBackdropClick(entry.id)"
583
+ @pointerdown="startDrag($event, entry.id)"
584
+ @pointermove="moveDrag"
585
+ @pointerup="endDrag"
586
+ @pointercancel="endDrag"
587
+ @touchstart="startTouch($event, entry.id)"
588
+ @touchmove="moveTouch"
589
+ @touchend="endTouch"
590
+ @touchcancel="endTouch"
591
+ />
592
+ </Transition>
593
+ <Suspense @resolve="onContentResolved(entry.id)">
594
+ <template #default>
595
+ <Transition
596
+ appear
597
+ :enterActiveClass="UI_STYLE.animation.panelEnterActive"
598
+ :enterFromClass="panelHiddenClass(resolvePlacement(entry.options.placement))"
599
+ :leaveActiveClass="UI_STYLE.animation.panelLeaveActive"
600
+ :leaveToClass="panelHiddenClass(resolvePlacement(entry.options.placement))"
601
+ @beforeLeave="onBeforePanelLeave"
602
+ @afterLeave="onAfterLeave(entry.id, 'panel')"
603
+ >
604
+ <Dialog
605
+ v-if="visible"
606
+ :placement="resolvePlacement(entry.options.placement)"
607
+ :elasticHeight="entry.options.elasticHeight"
608
+ :dragY="entry.id === draggedId ? dragY : 0"
609
+ :class="entry.id === draggedId && !isDragging ? UI_STYLE.animation.settlePanel : undefined"
610
+ @pointerdown="startDrag($event, entry.id)"
611
+ @pointermove="moveDrag"
612
+ @pointerup="endDrag"
613
+ @pointercancel="endDrag"
614
+ @touchstart="startTouch($event, entry.id)"
615
+ @touchmove="moveTouch"
616
+ @touchend="endTouch"
617
+ @touchcancel="endTouch"
618
+ >
619
+ <component
620
+ :is="entry.component"
621
+ v-bind="entry.props"
622
+ :class="[
623
+ UI_STYLE.content,
624
+ resolvePlacement(entry.options.placement) === 'bottom' &&
625
+ entry.options.elasticHeight &&
626
+ UI_STYLE.elasticHeightStretch,
627
+ entry.id === draggedId &&
628
+ !isDragging &&
629
+ entry.options.elasticHeight &&
630
+ UI_STYLE.animation.settleElasticHeight,
631
+ ]"
632
+ />
633
+ </Dialog>
634
+ </Transition>
635
+ </template>
636
+ </Suspense>
637
+ <Transition :name="dialogConfig.loadingIconTransition">
638
+ <div
639
+ v-if="loadingVisible"
640
+ :class="UI_STYLE.loader"
641
+ role="status"
642
+ >
643
+ <Icon
644
+ :name="dialogConfig.loadingIconName"
645
+ :class="UI_STYLE.loadingIcon"
646
+ aria-hidden="true"
647
+ />
648
+ <span class="sr-only">Loading dialog</span>
649
+ </div>
650
+ </Transition>
651
+ </div>
652
+ </template>
653
+ </Teleport>
654
+ </template>
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Placement and motion',
4
+ }
5
+ </script>
6
+
7
+ <script setup lang="ts">
8
+ import { defineAsyncComponent } from 'vue'
9
+
10
+ import { useDialog } from '../../composables/useDialog'
11
+ import Button from '../Button/index.vue'
12
+
13
+ const dialog = useDialog()
14
+ const LazyDemoDialog = defineAsyncComponent(() => import('./DemoSimple.vue'))
15
+ </script>
16
+
17
+ <template>
18
+ <div class="flex flex-wrap gap-2">
19
+ <Button @click="dialog.open(LazyDemoDialog, { title: 'Responsive dialog' })">Default placement</Button>
20
+ <Button @click="dialog.open(LazyDemoDialog, { title: 'Bottom sheet' }, { placement: 'bottom' })">
21
+ Bottom sheet
22
+ </Button>
23
+ <Button @click="dialog.open(LazyDemoDialog, { title: 'Fullscreen dialog' }, { placement: 'fullscreen' })">
24
+ Fullscreen
25
+ </Button>
26
+ </div>
27
+ </template>