@bagelink/vue 1.6.43 → 1.6.49
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/bin/experimentalGenTypedRoutes.ts +18 -19
- package/bin/utils.ts +4 -4
- package/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/BglVideo.vue.d.ts.map +1 -1
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +2 -2
- package/dist/components/Carousel.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/Flag.vue.d.ts.map +1 -1
- package/dist/components/IframeVue.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/Loading.vue.d.ts.map +1 -1
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/ModalForm.vue.d.ts.map +1 -1
- package/dist/components/NavBar.vue.d.ts +1 -1
- package/dist/components/Pill.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +12 -4
- package/dist/components/Swiper.vue.d.ts.map +1 -1
- package/dist/components/Zoomer.vue.d.ts.map +1 -1
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts +2 -1
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/index.d.ts +1 -1
- package/dist/components/analytics/index.d.ts.map +1 -1
- package/dist/components/calendar/CalendarPopover.vue.d.ts.map +1 -1
- package/dist/components/form/BglMultiStepForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/ColorInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/PasswordInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RangeInput.vue.d.ts +11 -11
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts +1 -0
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/components/layout/Layout.vue.d.ts.map +1 -1
- package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
- package/dist/components/layout/index.d.ts +3 -3
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/index.cjs +24 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1530 -1404
- package/dist/plugins/modalTypes.d.ts +1 -8
- package/dist/plugins/modalTypes.d.ts.map +1 -1
- package/dist/plugins/useModal.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -2
- package/src/components/AccordionItem.vue +13 -13
- package/src/components/AddToCalendar.vue +1 -1
- package/src/components/AddressSearch.vue +9 -8
- package/src/components/Alert.vue +2 -1
- package/src/components/Badge.vue +5 -5
- package/src/components/BglVideo.vue +44 -45
- package/src/components/Btn.vue +15 -15
- package/src/components/Card.vue +10 -8
- package/src/components/Carousel.vue +159 -162
- package/src/components/DataPreview.vue +1 -1
- package/src/components/DragOver.vue +6 -6
- package/src/components/Dropdown.vue +39 -38
- package/src/components/Flag.vue +7 -6
- package/src/components/Icon/Icon.vue +22 -22
- package/src/components/IframeVue.vue +5 -5
- package/src/components/Image.vue +17 -17
- package/src/components/ImportData.vue +79 -79
- package/src/components/ListItem.vue +12 -11
- package/src/components/Loading.vue +10 -9
- package/src/components/MapEmbed/Index.vue +24 -24
- package/src/components/Modal.vue +11 -9
- package/src/components/ModalForm.vue +15 -11
- package/src/components/NavBar.vue +6 -6
- package/src/components/Pagination.vue +27 -27
- package/src/components/Pill.vue +11 -12
- package/src/components/Rating.vue +2 -2
- package/src/components/Slider.vue +75 -75
- package/src/components/Spreadsheet/Index.vue +34 -34
- package/src/components/Spreadsheet/SpreadsheetTable.vue +3 -3
- package/src/components/Swiper.vue +4 -4
- package/src/components/Zoomer.vue +282 -182
- package/src/components/analytics/BarChart.vue +6 -6
- package/src/components/analytics/KpiCard.vue +2 -2
- package/src/components/analytics/LineChart.vue +63 -61
- package/src/components/analytics/PieChart.vue +104 -90
- package/src/components/analytics/index.ts +2 -2
- package/src/components/calendar/CalendarPopover.vue +1 -1
- package/src/components/calendar/Index.vue +1 -1
- package/src/components/calendar/views/AgendaView.vue +3 -3
- package/src/components/calendar/views/DayView.vue +6 -6
- package/src/components/calendar/views/MonthView.vue +2 -2
- package/src/components/calendar/views/WeekView.vue +18 -18
- package/src/components/dataTable/DataTable.vue +4 -4
- package/src/components/dataTable/useSorting.ts +1 -1
- package/src/components/dataTable/useTableData.ts +15 -15
- package/src/components/dataTable/useTableSelection.ts +15 -15
- package/src/components/dataTable/useTableVirtualization.ts +1 -1
- package/src/components/draggable/useDraggable.ts +42 -42
- package/src/components/form/BagelForm.vue +15 -15
- package/src/components/form/BglFieldSet.vue +5 -3
- package/src/components/form/BglMultiStepForm.vue +20 -21
- package/src/components/form/inputs/CheckInput.vue +2 -2
- package/src/components/form/inputs/CodeEditor/format.ts +7 -7
- package/src/components/form/inputs/CodeEditor/useHighlight.ts +6 -6
- package/src/components/form/inputs/ColorInput.vue +5 -4
- package/src/components/form/inputs/DateInput.vue +8 -9
- package/src/components/form/inputs/DatePicker.vue +24 -24
- package/src/components/form/inputs/EmailInput.vue +24 -24
- package/src/components/form/inputs/NumberInput.vue +26 -26
- package/src/components/form/inputs/OTP.vue +7 -7
- package/src/components/form/inputs/PasswordInput.vue +3 -2
- package/src/components/form/inputs/RadioGroup.vue +28 -25
- package/src/components/form/inputs/RadioPillsInput.vue +12 -12
- package/src/components/form/inputs/RangeInput.vue +21 -21
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +107 -92
- package/src/components/form/inputs/RichText/components/TableGridSelector.vue +64 -64
- package/src/components/form/inputs/RichText/components/gridBox.vue +10 -8
- package/src/components/form/inputs/RichText/composables/useCommands.ts +1 -1
- package/src/components/form/inputs/RichText/composables/useEditor.ts +12 -12
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +1 -1
- package/src/components/form/inputs/RichText/index.vue +138 -138
- package/src/components/form/inputs/RichText/utils/commands.ts +84 -85
- package/src/components/form/inputs/RichText/utils/debug.ts +1 -1
- package/src/components/form/inputs/RichText/utils/formatting.ts +39 -39
- package/src/components/form/inputs/RichText/utils/media.ts +7 -7
- package/src/components/form/inputs/RichText/utils/selection.ts +28 -28
- package/src/components/form/inputs/RichText/utils/table.ts +19 -19
- package/src/components/form/inputs/SelectBtn.vue +1 -1
- package/src/components/form/inputs/SelectInput.vue +54 -54
- package/src/components/form/inputs/SignaturePad.vue +40 -40
- package/src/components/form/inputs/TableField.vue +1 -1
- package/src/components/form/inputs/TelInput.vue +54 -53
- package/src/components/form/inputs/TextInput.vue +19 -19
- package/src/components/form/inputs/ToggleInput.vue +2 -2
- package/src/components/form/inputs/Upload/useFileUpload.ts +6 -6
- package/src/components/form/useBagelFormState.ts +5 -5
- package/src/components/layout/AppContent.vue +6 -3
- package/src/components/layout/AppLayout.vue +2 -2
- package/src/components/layout/AppSidebar.vue +83 -16
- package/src/components/layout/Layout.vue +12 -10
- package/src/components/layout/SidebarMenu.vue +4 -4
- package/src/components/layout/TabbedLayout.vue +17 -17
- package/src/components/layout/Tabs.vue +4 -5
- package/src/components/layout/TabsNav.vue +14 -14
- package/src/components/layout/index.ts +3 -5
- package/src/components/lightbox/Lightbox.vue +276 -126
- package/src/components/lightbox/index.ts +8 -8
- package/src/composables/index.ts +8 -8
- package/src/composables/useAddToCalendar.ts +13 -13
- package/src/composables/useDevice.ts +2 -2
- package/src/composables/useFormField.ts +4 -4
- package/src/composables/usePolling.ts +8 -8
- package/src/composables/useSchemaField.ts +38 -38
- package/src/composables/useTheme.ts +9 -9
- package/src/composables/useValidateFieldValue.ts +2 -2
- package/src/directives/pattern.ts +25 -25
- package/src/directives/ripple.ts +4 -4
- package/src/directives/vResize.ts +6 -6
- package/src/index.ts +1 -0
- package/src/plugins/bagel.ts +4 -4
- package/src/plugins/modalTypes.ts +1 -8
- package/src/plugins/useModal.ts +43 -18
- package/src/styles/layout.css +1 -1
- package/src/types/index.ts +1 -1
- package/src/utils/BagelFormUtils.ts +7 -7
- package/src/utils/calendar/Helpers.ts +8 -8
- package/src/utils/calendar/dateUtils.ts +22 -22
- package/src/utils/calendar/time.ts +25 -25
- package/src/utils/calendar/week.ts +25 -25
- package/src/utils/elementUtils.ts +27 -27
- package/src/utils/sizeParsing.ts +2 -2
- package/src/utils/strings.ts +5 -5
- package/src/utils/tapDetector.ts +11 -11
- package/src/utils/useSearch.ts +29 -29
- package/vite.config.ts +0 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { useDebounceFn } from '@bagelink/vue'
|
|
3
|
-
import { onMounted, onUnmounted, watch } from 'vue'
|
|
3
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
4
4
|
import TapDetector from '../utils/tapDetector'
|
|
5
5
|
|
|
6
6
|
// Props interface
|
|
@@ -34,278 +34,364 @@ const {
|
|
|
34
34
|
} = defineProps<Props>()
|
|
35
35
|
|
|
36
36
|
const emit = defineEmits(['update:zoom'])
|
|
37
|
-
// Reactive state using
|
|
38
|
-
const zoomElement =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
get: () => zoom
|
|
37
|
+
// Reactive state using standard Vue composition API
|
|
38
|
+
const zoomElement = ref<HTMLElement | undefined>()
|
|
39
|
+
const containerWidth = ref(1)
|
|
40
|
+
const containerHeight = ref(1)
|
|
41
|
+
const containerLeft = ref(0)
|
|
42
|
+
const containerTop = ref(0)
|
|
43
|
+
const translateX = ref(0)
|
|
44
|
+
const animTranslateX = ref(0)
|
|
45
|
+
const translateY = ref(0)
|
|
46
|
+
const animTranslateY = ref(0)
|
|
47
|
+
const _zoom = ref(zoom ?? 1)
|
|
48
|
+
const scale = computed({
|
|
49
|
+
get: () => zoom ?? _zoom.value,
|
|
50
50
|
set: (val) => {
|
|
51
|
-
_zoom = val
|
|
52
|
-
emit('update:zoom', _zoom)
|
|
51
|
+
_zoom.value = val
|
|
52
|
+
emit('update:zoom', _zoom.value)
|
|
53
53
|
},
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
const animScale = ref(1)
|
|
57
|
+
const lastFullWheelTime = ref(0)
|
|
58
|
+
const lastWheelTime = ref(0)
|
|
59
|
+
const lastWheelDirection = ref<'x' | 'y'>('y')
|
|
60
|
+
const isPointerDown = ref(false)
|
|
61
|
+
const pointerPosX = ref(-1)
|
|
62
|
+
const pointerPosY = ref(-1)
|
|
63
|
+
const twoFingerInitDist = ref(0)
|
|
64
|
+
const panLocked = ref(true)
|
|
65
|
+
const raf = ref<number | undefined>()
|
|
66
|
+
const tapDetector = ref<TapDetector | undefined>()
|
|
67
|
+
|
|
68
|
+
// Momentum/inertia tracking
|
|
69
|
+
const velocityX = ref(0)
|
|
70
|
+
const velocityY = ref(0)
|
|
71
|
+
const lastMoveTime = ref(0)
|
|
72
|
+
const momentumRaf = ref<number | undefined>()
|
|
73
|
+
|
|
74
|
+
const wrapperStyle = computed(() => {
|
|
75
|
+
// Translation is in normalized coordinates (0.5 = half container)
|
|
76
|
+
// Convert to pixels
|
|
77
|
+
const translateXValue = containerWidth.value * animTranslateX.value
|
|
78
|
+
const translateYValue = containerHeight.value * animTranslateY.value
|
|
74
79
|
return {
|
|
75
80
|
transform: [
|
|
76
81
|
`translate(${translateXValue}px, ${translateYValue}px)`,
|
|
77
|
-
`scale(${animScale})`,
|
|
82
|
+
`scale(${animScale.value})`,
|
|
78
83
|
].join(' '),
|
|
84
|
+
transformOrigin: '50% 50%',
|
|
79
85
|
}
|
|
80
86
|
})
|
|
81
87
|
|
|
82
|
-
watch(() => scale, (newScale) => {
|
|
88
|
+
watch(() => scale.value, (newScale) => {
|
|
83
89
|
const { x, y } = calcTranslateLimit()
|
|
84
|
-
translateX = Math.max(-x, Math.min(x, translateX))
|
|
85
|
-
translateY = Math.max(-y, Math.min(y, translateY))
|
|
86
|
-
if (
|
|
87
|
-
panLocked = false
|
|
90
|
+
translateX.value = Math.max(-x, Math.min(x, translateX.value))
|
|
91
|
+
translateY.value = Math.max(-y, Math.min(y, translateY.value))
|
|
92
|
+
if (newScale !== 1) {
|
|
93
|
+
panLocked.value = false
|
|
88
94
|
}
|
|
89
95
|
})
|
|
90
96
|
|
|
91
97
|
function reset() {
|
|
92
|
-
scale = 1
|
|
93
|
-
panLocked = true
|
|
94
|
-
translateX = 0
|
|
95
|
-
translateY = 0
|
|
98
|
+
scale.value = 1
|
|
99
|
+
panLocked.value = true
|
|
100
|
+
translateX.value = 0
|
|
101
|
+
translateY.value = 0
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
defineExpose({ reset })
|
|
99
105
|
|
|
100
106
|
function tryToScale(scaleDelta: number) {
|
|
101
|
-
if (disabled) {return}
|
|
102
|
-
let newScale = scale * scaleDelta
|
|
107
|
+
if (disabled) { return }
|
|
108
|
+
let newScale = scale.value * scaleDelta
|
|
103
109
|
if (zoomingElastic) {
|
|
104
|
-
if (newScale < minScale
|
|
110
|
+
if (newScale < minScale || newScale > maxScale) {
|
|
105
111
|
let log = Math.log2(scaleDelta)
|
|
106
112
|
log *= 0.2
|
|
107
113
|
scaleDelta = 2 ** log
|
|
108
|
-
newScale = scale * scaleDelta
|
|
114
|
+
newScale = scale.value * scaleDelta
|
|
109
115
|
}
|
|
110
116
|
} else {
|
|
111
|
-
if (newScale < minScale
|
|
112
|
-
|
|
117
|
+
if (newScale < minScale) {
|
|
118
|
+
newScale = minScale
|
|
119
|
+
} else if (newScale > maxScale) {
|
|
120
|
+
newScale = maxScale
|
|
121
|
+
}
|
|
113
122
|
}
|
|
114
|
-
scaleDelta = newScale / scale
|
|
115
|
-
scale = newScale
|
|
116
|
-
if ('image-center'
|
|
117
|
-
const normMousePosX = (pointerPosX - containerLeft) / containerWidth
|
|
118
|
-
const normMousePosY = (pointerPosY - containerTop) / containerHeight
|
|
119
|
-
translateX = (0.5 + translateX - normMousePosX) * scaleDelta + normMousePosX - 0.5
|
|
120
|
-
translateY = (0.5 + translateY - normMousePosY) * scaleDelta + normMousePosY - 0.5
|
|
123
|
+
scaleDelta = newScale / scale.value
|
|
124
|
+
scale.value = newScale
|
|
125
|
+
if (pivot !== 'image-center') {
|
|
126
|
+
const normMousePosX = (pointerPosX.value - containerLeft.value) / containerWidth.value
|
|
127
|
+
const normMousePosY = (pointerPosY.value - containerTop.value) / containerHeight.value
|
|
128
|
+
translateX.value = (0.5 + translateX.value - normMousePosX) * scaleDelta + normMousePosX - 0.5
|
|
129
|
+
translateY.value = (0.5 + translateY.value - normMousePosY) * scaleDelta + normMousePosY - 0.5
|
|
121
130
|
}
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
function setPointerPosCenter() {
|
|
125
|
-
pointerPosX = containerLeft + containerWidth / 2
|
|
126
|
-
pointerPosY = containerTop + containerHeight / 2
|
|
134
|
+
pointerPosX.value = containerLeft.value + containerWidth.value / 2
|
|
135
|
+
pointerPosY.value = containerTop.value + containerHeight.value / 2
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
function onPointerMove(newMousePosX: number, newMousePosY: number) {
|
|
130
|
-
if (isPointerDown) {
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
139
|
+
if (isPointerDown.value) {
|
|
140
|
+
const currentTime = Date.now()
|
|
141
|
+
const pixelDeltaX = newMousePosX - pointerPosX.value
|
|
142
|
+
const pixelDeltaY = newMousePosY - pointerPosY.value
|
|
143
|
+
|
|
144
|
+
if (!panLocked.value) {
|
|
145
|
+
// Calculate velocity for momentum
|
|
146
|
+
const timeDelta = currentTime - lastMoveTime.value
|
|
147
|
+
if (timeDelta > 0) {
|
|
148
|
+
velocityX.value = pixelDeltaX / timeDelta
|
|
149
|
+
velocityY.value = pixelDeltaY / timeDelta
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
const translateLimit = calcTranslateLimit()
|
|
135
153
|
const maxTranslateX = translateLimit.x
|
|
136
154
|
const maxTranslateY = translateLimit.y
|
|
137
|
-
const newTranslateX = translateX + pixelDeltaX / containerWidth
|
|
138
|
-
const newTranslateY = translateY + pixelDeltaY / containerHeight
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
const newTranslateX = translateX.value + pixelDeltaX / containerWidth.value
|
|
156
|
+
const newTranslateY = translateY.value + pixelDeltaY / containerHeight.value
|
|
157
|
+
|
|
158
|
+
translateX.value = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
|
|
159
|
+
translateY.value = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
|
|
160
|
+
|
|
161
|
+
lastMoveTime.value = currentTime
|
|
141
162
|
}
|
|
142
163
|
}
|
|
143
|
-
pointerPosX = newMousePosX
|
|
144
|
-
pointerPosY = newMousePosY
|
|
164
|
+
pointerPosX.value = newMousePosX
|
|
165
|
+
pointerPosY.value = newMousePosY
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function applyMomentum() {
|
|
169
|
+
if (Math.abs(velocityX.value) < 0.01 && Math.abs(velocityY.value) < 0.01) {
|
|
170
|
+
velocityX.value = 0
|
|
171
|
+
velocityY.value = 0
|
|
172
|
+
if (momentumRaf.value) {
|
|
173
|
+
cancelAnimationFrame(momentumRaf.value)
|
|
174
|
+
momentumRaf.value = undefined
|
|
175
|
+
}
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const translateLimit = calcTranslateLimit()
|
|
180
|
+
const newTranslateX = translateX.value + velocityX.value * 16 / containerWidth.value
|
|
181
|
+
const newTranslateY = translateY.value + velocityY.value * 16 / containerHeight.value
|
|
182
|
+
|
|
183
|
+
translateX.value = Math.max(-translateLimit.x, Math.min(translateLimit.x, newTranslateX))
|
|
184
|
+
translateY.value = Math.max(-translateLimit.y, Math.min(translateLimit.y, newTranslateY))
|
|
185
|
+
|
|
186
|
+
// Apply friction
|
|
187
|
+
velocityX.value *= 0.95
|
|
188
|
+
velocityY.value *= 0.95
|
|
189
|
+
|
|
190
|
+
momentumRaf.value = requestAnimationFrame(applyMomentum)
|
|
145
191
|
}
|
|
146
192
|
|
|
147
193
|
const onInteractionEnd = useDebounceFn(() => {
|
|
148
194
|
limit()
|
|
149
|
-
panLocked =
|
|
150
|
-
|
|
195
|
+
panLocked.value = scale.value === 1
|
|
196
|
+
|
|
197
|
+
// Start momentum animation if there's significant velocity
|
|
198
|
+
if (!panLocked.value && (Math.abs(velocityX.value) > 0.1 || Math.abs(velocityY.value) > 0.1)) {
|
|
199
|
+
if (momentumRaf.value) cancelAnimationFrame(momentumRaf.value)
|
|
200
|
+
applyMomentum()
|
|
201
|
+
} else {
|
|
202
|
+
velocityX.value = 0
|
|
203
|
+
velocityY.value = 0
|
|
204
|
+
}
|
|
205
|
+
}, 50)
|
|
151
206
|
|
|
152
207
|
function limit() {
|
|
153
|
-
if (scale < minScale
|
|
154
|
-
scale = minScale
|
|
155
|
-
} else if (scale > maxScale
|
|
156
|
-
tryToScale(maxScale
|
|
208
|
+
if (scale.value < minScale) {
|
|
209
|
+
scale.value = minScale
|
|
210
|
+
} else if (scale.value > maxScale) {
|
|
211
|
+
tryToScale(maxScale / scale.value)
|
|
157
212
|
}
|
|
158
213
|
|
|
159
214
|
if (limitTranslation) {
|
|
160
215
|
const translateLimit = calcTranslateLimit()
|
|
161
|
-
if (Math.abs(translateX) > translateLimit.x) {
|
|
162
|
-
translateX *= translateLimit.x / Math.abs(translateX)
|
|
216
|
+
if (Math.abs(translateX.value) > translateLimit.x) {
|
|
217
|
+
translateX.value *= translateLimit.x / Math.abs(translateX.value)
|
|
163
218
|
}
|
|
164
|
-
if (Math.abs(translateY) > translateLimit.y) {
|
|
165
|
-
translateY *= translateLimit.y / Math.abs(translateY)
|
|
219
|
+
if (Math.abs(translateY.value) > translateLimit.y) {
|
|
220
|
+
translateY.value *= translateLimit.y / Math.abs(translateY.value)
|
|
166
221
|
}
|
|
167
222
|
}
|
|
168
223
|
}
|
|
169
224
|
|
|
170
225
|
function calcTranslateLimit() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
226
|
+
// When scale = 1, no panning needed (image fits in container)
|
|
227
|
+
// When scale > 1, we can pan to see parts of the image outside the viewport
|
|
228
|
+
|
|
229
|
+
// The image is scaled from center, so:
|
|
230
|
+
// - At scale 2, the image is 2x larger
|
|
231
|
+
// - The amount that extends beyond the container on EACH side is: (scale - 1) * containerSize / 2
|
|
232
|
+
// - In normalized coordinates (where 1.0 = full container), this is: (scale - 1) / 2
|
|
233
|
+
|
|
234
|
+
// Simple and correct approach:
|
|
235
|
+
// Max pan distance in normalized coords = (scale - 1) / 2
|
|
236
|
+
// This works for both X and Y directions regardless of aspect ratio
|
|
237
|
+
|
|
238
|
+
const basePanLimit = Math.max(0, (scale.value - 1) / 2)
|
|
239
|
+
|
|
183
240
|
return {
|
|
184
|
-
x:
|
|
185
|
-
y:
|
|
241
|
+
x: basePanLimit,
|
|
242
|
+
y: basePanLimit,
|
|
186
243
|
}
|
|
187
244
|
}
|
|
188
245
|
|
|
189
|
-
function getMarginDirection() {
|
|
190
|
-
const containerRatio = containerWidth / containerHeight
|
|
191
|
-
return containerRatio > aspectRatio ? 'x' : 'y'
|
|
192
|
-
}
|
|
193
|
-
|
|
194
246
|
function onMouseWheel(ev: WheelEvent) {
|
|
195
|
-
if (!mouseWheelToZoom) {return}
|
|
247
|
+
if (!mouseWheelToZoom) { return }
|
|
196
248
|
ev.preventDefault()
|
|
197
249
|
const currTime = Date.now()
|
|
198
|
-
if (
|
|
199
|
-
if (
|
|
250
|
+
if (Math.abs(ev.deltaY) === 120) {
|
|
251
|
+
if (currTime - lastFullWheelTime.value > 50) {
|
|
200
252
|
onMouseWheelDo(ev.deltaY)
|
|
201
|
-
lastFullWheelTime = currTime
|
|
253
|
+
lastFullWheelTime.value = currTime
|
|
202
254
|
}
|
|
203
255
|
} else {
|
|
204
|
-
if (
|
|
205
|
-
lastWheelDirection = ev.deltaX > ev.deltaY ? 'x' : 'y'
|
|
206
|
-
if ('y'
|
|
256
|
+
if (currTime - lastWheelTime.value > 50) {
|
|
257
|
+
lastWheelDirection.value = ev.deltaX > ev.deltaY ? 'x' : 'y'
|
|
258
|
+
if (lastWheelDirection.value === 'y') {
|
|
207
259
|
onMouseWheelDo(ev.deltaY)
|
|
208
260
|
}
|
|
209
261
|
}
|
|
210
|
-
lastWheelTime = currTime
|
|
262
|
+
lastWheelTime.value = currTime
|
|
211
263
|
}
|
|
212
264
|
}
|
|
213
265
|
|
|
214
266
|
function onMouseWheelDo(wheelDelta: number) {
|
|
215
|
-
|
|
267
|
+
// More responsive zoom speed
|
|
268
|
+
const scaleDelta = 1.15 ** (wheelDelta / 120)
|
|
216
269
|
tryToScale(scaleDelta)
|
|
217
270
|
onInteractionEnd()
|
|
218
271
|
}
|
|
219
272
|
|
|
220
273
|
function onMouseDown(ev: MouseEvent) {
|
|
221
274
|
refreshContainerPos()
|
|
222
|
-
isPointerDown = true
|
|
223
|
-
pointerPosX = ev.clientX
|
|
224
|
-
pointerPosY = ev.clientY
|
|
275
|
+
isPointerDown.value = true
|
|
276
|
+
pointerPosX.value = ev.clientX
|
|
277
|
+
pointerPosY.value = ev.clientY
|
|
278
|
+
lastMoveTime.value = Date.now()
|
|
279
|
+
velocityX.value = 0
|
|
280
|
+
velocityY.value = 0
|
|
281
|
+
if (momentumRaf.value) {
|
|
282
|
+
cancelAnimationFrame(momentumRaf.value)
|
|
283
|
+
momentumRaf.value = undefined
|
|
284
|
+
}
|
|
225
285
|
document.addEventListener('mousemove', onMouseMove)
|
|
226
286
|
document.addEventListener('mouseup', onMouseUp)
|
|
227
287
|
}
|
|
228
288
|
|
|
229
289
|
function onMouseUp() {
|
|
230
|
-
isPointerDown = false
|
|
290
|
+
isPointerDown.value = false
|
|
231
291
|
onInteractionEnd()
|
|
232
292
|
document.removeEventListener('mouseup', onMouseUp)
|
|
233
293
|
document.removeEventListener('mousemove', onMouseMove)
|
|
234
294
|
}
|
|
235
295
|
|
|
236
296
|
function onMouseMove(ev: MouseEvent) {
|
|
297
|
+
// Prevent swiper from handling mouse drag when panning zoomed image
|
|
298
|
+
if (!panLocked.value && scale.value > 1) {
|
|
299
|
+
ev.stopPropagation()
|
|
300
|
+
}
|
|
237
301
|
onPointerMove(ev.clientX, ev.clientY)
|
|
238
302
|
}
|
|
239
303
|
|
|
240
304
|
function onTouchStart(ev: TouchEvent) {
|
|
241
|
-
if (
|
|
305
|
+
if (ev.touches.length === 1) {
|
|
242
306
|
refreshContainerPos()
|
|
243
|
-
pointerPosX = ev.touches[0].clientX
|
|
244
|
-
pointerPosY = ev.touches[0].clientY
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
307
|
+
pointerPosX.value = ev.touches[0].clientX
|
|
308
|
+
pointerPosY.value = ev.touches[0].clientY
|
|
309
|
+
lastMoveTime.value = Date.now()
|
|
310
|
+
velocityX.value = 0
|
|
311
|
+
velocityY.value = 0
|
|
312
|
+
if (momentumRaf.value) {
|
|
313
|
+
cancelAnimationFrame(momentumRaf.value)
|
|
314
|
+
momentumRaf.value = undefined
|
|
315
|
+
}
|
|
316
|
+
isPointerDown.value = true
|
|
317
|
+
} else if (ev.touches.length === 2) {
|
|
318
|
+
if (momentumRaf.value) {
|
|
319
|
+
cancelAnimationFrame(momentumRaf.value)
|
|
320
|
+
momentumRaf.value = undefined
|
|
321
|
+
}
|
|
322
|
+
velocityX.value = 0
|
|
323
|
+
velocityY.value = 0
|
|
324
|
+
isPointerDown.value = true
|
|
325
|
+
pointerPosX.value = (ev.touches[0].clientX + ev.touches[1].clientX) / 2
|
|
326
|
+
pointerPosY.value = (ev.touches[0].clientY + ev.touches[1].clientY) / 2
|
|
250
327
|
const distX = ev.touches[0].clientX - ev.touches[1].clientX
|
|
251
328
|
const distY = ev.touches[0].clientY - ev.touches[1].clientY
|
|
252
|
-
twoFingerInitDist = Math.sqrt(distX * distX + distY * distY)
|
|
329
|
+
twoFingerInitDist.value = Math.sqrt(distX * distX + distY * distY)
|
|
253
330
|
}
|
|
254
331
|
document.addEventListener('touchend', onTouchEnd)
|
|
255
332
|
}
|
|
256
333
|
|
|
257
334
|
function onTouchEnd(ev: TouchEvent) {
|
|
258
|
-
if (
|
|
259
|
-
isPointerDown = false
|
|
260
|
-
if (
|
|
335
|
+
if (ev.touches.length === 0) {
|
|
336
|
+
isPointerDown.value = false
|
|
337
|
+
if (Math.abs(scale.value - 1) < 0.1) { scale.value = 1 }
|
|
261
338
|
onInteractionEnd()
|
|
262
|
-
} else if (
|
|
263
|
-
pointerPosX = ev.touches[0].clientX
|
|
264
|
-
pointerPosY = ev.touches[0].clientY
|
|
339
|
+
} else if (ev.touches.length === 1) {
|
|
340
|
+
pointerPosX.value = ev.touches[0].clientX
|
|
341
|
+
pointerPosY.value = ev.touches[0].clientY
|
|
265
342
|
}
|
|
266
343
|
document.removeEventListener('touchend', onTouchEnd)
|
|
267
344
|
}
|
|
268
345
|
|
|
269
346
|
function onTouchMove(ev: TouchEvent) {
|
|
270
|
-
if (
|
|
347
|
+
if (ev.touches.length === 1) {
|
|
348
|
+
// Prevent swiper from handling touch when panning zoomed image
|
|
349
|
+
if (!panLocked.value && scale.value > 1) {
|
|
350
|
+
ev.stopPropagation()
|
|
351
|
+
}
|
|
271
352
|
onPointerMove(ev.touches[0].clientX, ev.touches[0].clientY)
|
|
272
|
-
} else if (2
|
|
273
|
-
|
|
274
|
-
|
|
353
|
+
} else if (ev.touches.length === 2) {
|
|
354
|
+
// Always prevent default during pinch zoom
|
|
355
|
+
ev.stopPropagation()
|
|
275
356
|
const distX = ev.touches[0].clientX - ev.touches[1].clientX
|
|
276
357
|
const distY = ev.touches[0].clientY - ev.touches[1].clientY
|
|
277
358
|
const newTwoFingerDist = Math.sqrt(distX * distX + distY * distY)
|
|
278
|
-
tryToScale(newTwoFingerDist / twoFingerInitDist)
|
|
279
|
-
twoFingerInitDist = newTwoFingerDist
|
|
359
|
+
tryToScale(newTwoFingerDist / twoFingerInitDist.value)
|
|
360
|
+
twoFingerInitDist.value = newTwoFingerDist
|
|
280
361
|
}
|
|
281
362
|
}
|
|
282
363
|
|
|
283
364
|
function refreshContainerPos() {
|
|
284
|
-
if (zoomElement) {
|
|
285
|
-
const rect = zoomElement.getBoundingClientRect()
|
|
286
|
-
containerLeft = rect.left
|
|
287
|
-
containerTop = rect.top
|
|
365
|
+
if (zoomElement.value) {
|
|
366
|
+
const rect = zoomElement.value.getBoundingClientRect()
|
|
367
|
+
containerLeft.value = rect.left
|
|
368
|
+
containerTop.value = rect.top
|
|
288
369
|
}
|
|
289
370
|
}
|
|
290
371
|
|
|
291
372
|
function loop() {
|
|
292
|
-
animScale = gainOn(animScale, scale)
|
|
293
|
-
animTranslateX = gainOn(animTranslateX, translateX)
|
|
294
|
-
animTranslateY = gainOn(animTranslateY, translateY)
|
|
295
|
-
raf = window.requestAnimationFrame(loop)
|
|
373
|
+
animScale.value = gainOn(animScale.value, scale.value)
|
|
374
|
+
animTranslateX.value = gainOn(animTranslateX.value, translateX.value)
|
|
375
|
+
animTranslateY.value = gainOn(animTranslateY.value, translateY.value)
|
|
376
|
+
raf.value = window.requestAnimationFrame(loop)
|
|
296
377
|
}
|
|
297
378
|
|
|
298
379
|
function gainOn(from: number, to: number) {
|
|
299
|
-
|
|
300
|
-
|
|
380
|
+
// Smoother easing with better responsiveness
|
|
381
|
+
const delta = (to - from) * 0.4
|
|
382
|
+
return Math.abs(delta) > 1e-5 ? from + delta : to
|
|
301
383
|
}
|
|
302
384
|
|
|
303
385
|
// Lifecycle hooks
|
|
304
386
|
onMounted(() => {
|
|
305
|
-
tapDetector = new TapDetector()
|
|
306
|
-
if (zoomElement) {tapDetector.attach(zoomElement)}
|
|
387
|
+
tapDetector.value = new TapDetector()
|
|
388
|
+
if (zoomElement.value) { tapDetector.value.attach(zoomElement.value) }
|
|
307
389
|
if (doubleClickToZoom) {
|
|
308
|
-
tapDetector.onDoubleTap(onDoubleTap as any)
|
|
390
|
+
tapDetector.value.onDoubleTap(onDoubleTap as any)
|
|
391
|
+
// Also add native dblclick event for mouse
|
|
392
|
+
if (zoomElement.value) {
|
|
393
|
+
zoomElement.value.addEventListener('dblclick', onDoubleTap as any)
|
|
394
|
+
}
|
|
309
395
|
}
|
|
310
396
|
window.addEventListener('resize', onWindowResize)
|
|
311
397
|
onWindowResize()
|
|
@@ -314,28 +400,46 @@ onMounted(() => {
|
|
|
314
400
|
})
|
|
315
401
|
|
|
316
402
|
onUnmounted(() => {
|
|
317
|
-
if (zoomElement) {
|
|
403
|
+
if (zoomElement.value) {
|
|
404
|
+
tapDetector.value?.detach(zoomElement.value)
|
|
405
|
+
if (doubleClickToZoom) {
|
|
406
|
+
zoomElement.value.removeEventListener('dblclick', onDoubleTap as any)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
318
409
|
window.removeEventListener('resize', onWindowResize)
|
|
319
|
-
if (raf) {window.cancelAnimationFrame(raf)}
|
|
410
|
+
if (raf.value) { window.cancelAnimationFrame(raf.value) }
|
|
411
|
+
if (momentumRaf.value) { window.cancelAnimationFrame(momentumRaf.value) }
|
|
320
412
|
})
|
|
321
413
|
|
|
322
414
|
function onDoubleTap(ev: MouseEvent) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
415
|
+
// Update pointer position if available
|
|
416
|
+
if (ev.clientX > 0) {
|
|
417
|
+
pointerPosX.value = ev.clientX
|
|
418
|
+
pointerPosY.value = ev.clientY
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Calculate zoom levels
|
|
422
|
+
const mediumZoom = Math.min(2.5, maxScale / 2 + 0.5)
|
|
423
|
+
const highZoom = maxScale
|
|
424
|
+
|
|
425
|
+
// Cycle through three states: 1x -> medium -> max -> 1x
|
|
426
|
+
if (scale.value === 1) {
|
|
427
|
+
// From no zoom -> medium zoom
|
|
428
|
+
tryToScale(mediumZoom)
|
|
429
|
+
} else if (scale.value < highZoom - 0.1) {
|
|
430
|
+
// From medium zoom -> max zoom
|
|
431
|
+
tryToScale(highZoom / scale.value)
|
|
329
432
|
} else {
|
|
433
|
+
// From max zoom -> no zoom (reset)
|
|
330
434
|
reset()
|
|
331
435
|
}
|
|
332
436
|
}
|
|
333
437
|
|
|
334
438
|
function onWindowResize() {
|
|
335
|
-
if (zoomElement) {
|
|
336
|
-
const styles = window.getComputedStyle(zoomElement)
|
|
337
|
-
containerWidth = Number.parseFloat(styles.width)
|
|
338
|
-
containerHeight = Number.parseFloat(styles.height)
|
|
439
|
+
if (zoomElement.value) {
|
|
440
|
+
const styles = window.getComputedStyle(zoomElement.value)
|
|
441
|
+
containerWidth.value = Number.parseFloat(styles.width)
|
|
442
|
+
containerHeight.value = Number.parseFloat(styles.height)
|
|
339
443
|
setPointerPosCenter()
|
|
340
444
|
limit()
|
|
341
445
|
}
|
|
@@ -344,16 +448,9 @@ function onWindowResize() {
|
|
|
344
448
|
|
|
345
449
|
<template>
|
|
346
450
|
<div
|
|
347
|
-
ref="zoomElement"
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
@mousewheel="onMouseWheel"
|
|
351
|
-
@DOMMouseScroll="onMouseWheel"
|
|
352
|
-
@mousedown="onMouseDown"
|
|
353
|
-
@mouseout="setPointerPosCenter"
|
|
354
|
-
@focusout="setPointerPosCenter"
|
|
355
|
-
@touchstart="onTouchStart"
|
|
356
|
-
@touchmove="onTouchMove"
|
|
451
|
+
ref="zoomElement" class="vue-zoomer" :style="{ backgroundColor }" @mousewheel="onMouseWheel"
|
|
452
|
+
@DOMMouseScroll="onMouseWheel" @mousedown="onMouseDown" @mouseout="setPointerPosCenter"
|
|
453
|
+
@focusout="setPointerPosCenter" @touchstart="onTouchStart" @touchmove="onTouchMove"
|
|
357
454
|
>
|
|
358
455
|
<div class="zoomer" :style="wrapperStyle">
|
|
359
456
|
<slot />
|
|
@@ -371,24 +468,27 @@ function onWindowResize() {
|
|
|
371
468
|
z-index: 1000;
|
|
372
469
|
height: 100px
|
|
373
470
|
}
|
|
471
|
+
|
|
374
472
|
.vue-zoomer {
|
|
375
|
-
|
|
473
|
+
overflow: hidden;
|
|
376
474
|
}
|
|
475
|
+
|
|
377
476
|
.zoomer {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
477
|
+
transform-origin: 50% 50%;
|
|
478
|
+
width: 100%;
|
|
479
|
+
height: 100%;
|
|
480
|
+
max-width: 100vw;
|
|
481
|
+
max-height: 100vh;
|
|
383
482
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
483
|
+
|
|
484
|
+
.zoomer>img {
|
|
485
|
+
vertical-align: top;
|
|
486
|
+
user-select: none;
|
|
487
|
+
-moz-user-drag: none;
|
|
488
|
+
-webkit-user-select: none;
|
|
489
|
+
-moz-user-select: none;
|
|
490
|
+
-ms-user-select: none;
|
|
491
|
+
-webkit-user-drag: none;
|
|
492
|
+
-moz-user-drag: none;
|
|
393
493
|
}
|
|
394
494
|
</style>
|