mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 06:42:32 -05:00
add pinch zoom and fix touch editing
This commit is contained in:
1 parent
57c8be33ce
commit
aa7d6e9aaa
7 files changed
+185
-43
No files matched your search
@@ -587,9 +587,14 @@
|
|||||||
class="w-full mt-0.5 pt-0.5"
|
class="w-full mt-0.5 pt-0.5"
|
||||||
:class="[
|
:class="[
|
||||||
isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto',
|
isMobile ? 'overflow-y-auto' : 'overflow-y-hidden md:overflow-y-auto',
|
||||||
zoomLevel > 1 ? 'overflow-x-auto' : 'overflow-x-hidden'
|
zoomLevel > 1 ? 'overflow-x-auto' : 'overflow-x-hidden',
|
||||||
|
{ 'touch-pan-x touch-pan-y': hasDynamicDocuments && !isVisualViewportZoomed }
|
||||||
]"
|
]"
|
||||||
@wheel="onPagesWheel"
|
@wheel="onPagesWheel"
|
||||||
|
@touchstart="onPagesTouchStart"
|
||||||
|
@touchmove="onPagesTouchMove"
|
||||||
|
@touchend="onPagesTouchEnd"
|
||||||
|
@touchcancel="onPagesTouchEnd"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="documents"
|
ref="documents"
|
||||||
@@ -893,20 +898,20 @@
|
|||||||
leave-to-class="translate-y-4 opacity-0"
|
leave-to-class="translate-y-4 opacity-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="zoomLevel > 1"
|
v-if="(pinchZoomLevel || zoomLevel) > 1"
|
||||||
class="sticky bottom-0 z-10 pointer-events-none"
|
class="sticky bottom-0 z-10 pointer-events-none"
|
||||||
>
|
>
|
||||||
<div class="absolute left-0 right-0 bottom-6 md:bottom-4 flex justify-center">
|
<div class="absolute left-0 right-0 bottom-6 md:bottom-4 flex justify-center">
|
||||||
<div class="join shadow pointer-events-auto">
|
<div class="join shadow pointer-events-auto">
|
||||||
<span class="join-item bg-base-content text-white pl-2 pr-2.5 h-9 items-center text-sm font-medium cursor-default w-16 flex justify-end">
|
<span class="join-item bg-base-content text-white pl-2 pr-2.5 h-9 items-center text-sm font-medium cursor-default w-16 flex justify-end">
|
||||||
<span>
|
<span>
|
||||||
{{ Math.round(zoomLevel * 100) }}%
|
{{ Math.round((pinchZoomLevel || zoomLevel) * 100) }}%
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="join-item bg-base-content text-white h-9 pl-2 pr-3 inline-flex items-center justify-center cursor-pointer hover:opacity-90 border-l border-white/20"
|
class="join-item bg-base-content text-white h-9 pl-2 pr-3 inline-flex items-center justify-center cursor-pointer hover:opacity-90 border-l border-white/20"
|
||||||
@click="zoomLevel = 1"
|
@click="resetZoomLevel"
|
||||||
>
|
>
|
||||||
<IconX class="w-4 h-4 stroke-2" />
|
<IconX class="w-4 h-4 stroke-2" />
|
||||||
</button>
|
</button>
|
||||||
@@ -1394,10 +1399,15 @@ export default {
|
|||||||
editModalDocumentUuid: null,
|
editModalDocumentUuid: null,
|
||||||
revisions: [],
|
revisions: [],
|
||||||
beforeRevisionSnapshot: null,
|
beforeRevisionSnapshot: null,
|
||||||
zoomLevel: 1
|
zoomLevel: 1,
|
||||||
|
pinchZoomLevel: null,
|
||||||
|
isVisualViewportZoomed: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
hasDynamicDocuments () {
|
||||||
|
return this.template.schema.some((item) => item.dynamic)
|
||||||
|
},
|
||||||
submitterDefaultNames: FieldSubmitter.computed.names,
|
submitterDefaultNames: FieldSubmitter.computed.names,
|
||||||
isSelectModeRef: () => ref(false),
|
isSelectModeRef: () => ref(false),
|
||||||
isCmdKeyRef: () => ref(false),
|
isCmdKeyRef: () => ref(false),
|
||||||
@@ -1653,6 +1663,7 @@ export default {
|
|||||||
window.addEventListener('keydown', this.onKeyDown)
|
window.addEventListener('keydown', this.onKeyDown)
|
||||||
|
|
||||||
window.addEventListener('dragleave', this.onWindowDragLeave)
|
window.addEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
window.visualViewport?.addEventListener('resize', this.onVisualViewportResize)
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (document.location.search?.includes('stripe_connect_success')) {
|
if (document.location.search?.includes('stripe_connect_success')) {
|
||||||
@@ -1677,6 +1688,7 @@ export default {
|
|||||||
|
|
||||||
this.resizeObserver.disconnect()
|
this.resizeObserver.disconnect()
|
||||||
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
window.removeEventListener('dragleave', this.onWindowDragLeave)
|
||||||
|
window.visualViewport?.removeEventListener('resize', this.onVisualViewportResize)
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
this.documentRefs = []
|
this.documentRefs = []
|
||||||
@@ -2464,23 +2476,73 @@ export default {
|
|||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const oldZoom = this.zoomLevel
|
this.setZoomLevel(this.zoomLevel - event.deltaY * 0.006, event.clientX, event.clientY)
|
||||||
const nextZoom = Math.max(1, Math.min(3, oldZoom - event.deltaY * 0.006))
|
},
|
||||||
|
onVisualViewportResize () {
|
||||||
|
if (!this.hasDynamicDocuments) return
|
||||||
|
|
||||||
if (nextZoom === oldZoom) return
|
this.isVisualViewportZoomed = window.visualViewport.scale > 1.01
|
||||||
|
},
|
||||||
|
onPagesTouchStart (event) {
|
||||||
|
if (event.touches.length === 2 && this.hasDynamicDocuments && !this.isVisualViewportZoomed) {
|
||||||
|
const [a, b] = event.touches
|
||||||
|
const rect = this.$refs.documents.getBoundingClientRect()
|
||||||
|
const x = (a.clientX + b.clientX) / 2
|
||||||
|
const y = (a.clientY + b.clientY) / 2
|
||||||
|
|
||||||
|
this.pinch = { distance: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY), zoomLevel: this.zoomLevel, x, y }
|
||||||
|
this.pinchZoomLevel = this.zoomLevel
|
||||||
|
|
||||||
|
this.$refs.documents.style.transformOrigin = `${x - rect.left}px ${y - rect.top}px`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPagesTouchMove (event) {
|
||||||
|
if (!this.pinch || event.touches.length !== 2) return
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
const [a, b] = event.touches
|
||||||
|
|
||||||
|
this.pinchZoomLevel = Math.max(1, Math.min(3, this.pinch.zoomLevel * Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY) / this.pinch.distance))
|
||||||
|
|
||||||
|
this.$refs.documents.style.transform = `scale(${this.pinchZoomLevel / this.pinch.zoomLevel})`
|
||||||
|
},
|
||||||
|
onPagesTouchEnd (event) {
|
||||||
|
if (!this.pinch || event.touches.length === 2) return
|
||||||
|
|
||||||
|
this.$refs.documents.style.transform = ''
|
||||||
|
this.$refs.documents.style.transformOrigin = ''
|
||||||
|
|
||||||
|
this.setZoomLevel(this.pinchZoomLevel, this.pinch.x, this.pinch.y)
|
||||||
|
|
||||||
|
this.pinch = null
|
||||||
|
this.pinchZoomLevel = null
|
||||||
|
},
|
||||||
|
resetZoomLevel () {
|
||||||
const rect = this.$refs.pagesContainer.getBoundingClientRect()
|
const rect = this.$refs.pagesContainer.getBoundingClientRect()
|
||||||
const cursorX = event.clientX - rect.left
|
|
||||||
const cursorY = event.clientY - rect.top
|
this.setZoomLevel(1, rect.left + rect.width / 2, (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2)
|
||||||
const ratio = nextZoom / oldZoom
|
},
|
||||||
const nextScrollLeft = (this.$refs.pagesContainer.scrollLeft + cursorX) * ratio - cursorX
|
setZoomLevel (zoomLevel, clientX, clientY) {
|
||||||
const nextScrollTop = (this.$refs.pagesContainer.scrollTop + cursorY) * ratio - cursorY
|
const nextZoom = Math.max(1, Math.min(3, zoomLevel))
|
||||||
|
|
||||||
|
if (nextZoom === this.zoomLevel) return
|
||||||
|
|
||||||
|
const container = this.$refs.pagesContainer
|
||||||
|
const scrollContainer = container.scrollHeight > container.clientHeight ? container : document.scrollingElement
|
||||||
|
const rect = this.$refs.documents.getBoundingClientRect()
|
||||||
|
|
||||||
this.zoomLevel = nextZoom
|
this.zoomLevel = nextZoom
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.pagesContainer.scrollLeft = nextScrollLeft
|
this.documentRefs.filter((ref) => ref.isDynamic).forEach((ref) => ref.updateContainerWidth())
|
||||||
this.$refs.pagesContainer.scrollTop = nextScrollTop
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const nextRect = this.$refs.documents.getBoundingClientRect()
|
||||||
|
|
||||||
|
container.scrollLeft += nextRect.left - clientX + (clientX - rect.left) * nextRect.width / rect.width
|
||||||
|
scrollContainer.scrollTop += nextRect.top - clientY + (clientY - rect.top) * nextRect.height / rect.height
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setDocumentRefs (el) {
|
setDocumentRefs (el) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="coords.type === 'table'"
|
v-if="coords.type === 'table'"
|
||||||
class="absolute w-3 h-3 bg-white border-2 border-blue-500 rounded-sm pointer-events-auto cursor-nwse-resize touch-none before:absolute before:-inset-3 before:content-['']"
|
class="absolute w-3 h-3 bg-white border-2 border-blue-500 rounded-sm pointer-events-auto cursor-nwse-resize touch-none before:absolute before:-inset-3"
|
||||||
:style="{ left: (size?.width ?? coords.width) - 6 + 'px', top: (size?.height ?? coords.height) - 6 + 'px' }"
|
:style="{ left: (size?.width ?? coords.width) - 6 + 'px', top: (size?.height ?? coords.height) - 6 + 'px' }"
|
||||||
:title="t('resize')"
|
:title="t('resize')"
|
||||||
@pointerdown.prevent.stop="startResize"
|
@pointerdown.prevent.stop="startResize"
|
||||||
@@ -87,7 +87,7 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
size: null,
|
size: null,
|
||||||
toolbarHeight: 38
|
toolbarHeight: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -150,7 +150,7 @@ export default {
|
|||||||
onResizeMove (event) {
|
onResizeMove (event) {
|
||||||
const { x, y, width, height } = this.resizeStart
|
const { x, y, width, height } = this.resizeStart
|
||||||
this.size = {
|
this.size = {
|
||||||
width: Math.max(width + event.clientX - x, 20),
|
width: Math.min(Math.max(width + event.clientX - x, 20), this.coords.maxWidth),
|
||||||
height: Math.max(height + event.clientY - y, this.coords.minHeight)
|
height: Math.max(height + event.clientY - y, this.coords.minHeight)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
DynamicSection
|
DynamicSection
|
||||||
},
|
},
|
||||||
inject: ['baseFetch', 'template'],
|
inject: ['baseFetch', 'template', 'nativePlatform'],
|
||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
documentFonts: computed(() => this.documentFonts)
|
documentFonts: computed(() => this.documentFonts)
|
||||||
@@ -197,21 +197,29 @@ export default {
|
|||||||
|
|
||||||
this.ensureListsStyle(this.bodyDom)
|
this.ensureListsStyle(this.bodyDom)
|
||||||
|
|
||||||
this.containerWidth = this.$refs.container.clientWidth
|
this.updateContainerWidth()
|
||||||
|
|
||||||
this.resizeObserver = new ResizeObserver(() => {
|
this.resizeObserver = new ResizeObserver(() => {
|
||||||
if (this.$refs.container) {
|
if (this.$refs.container) {
|
||||||
this.containerWidth = this.$refs.container.clientWidth
|
this.updateContainerWidth()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.resizeObserver.observe(this.$refs.container)
|
this.resizeObserver.observe(this.$refs.container)
|
||||||
|
|
||||||
window.addEventListener('beforeunload', this.onBeforeUnload)
|
window.addEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
|
||||||
|
if (this.nativePlatform === 'ios') {
|
||||||
|
this.viewportMeta = document.querySelector('meta[name="viewport"]')
|
||||||
|
this.viewportContent = this.viewportMeta.getAttribute('content')
|
||||||
|
this.viewportMeta.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
window.removeEventListener('beforeunload', this.onBeforeUnload)
|
window.removeEventListener('beforeunload', this.onBeforeUnload)
|
||||||
|
|
||||||
|
this.viewportMeta?.setAttribute('content', this.viewportContent)
|
||||||
|
|
||||||
this.resizeObserver.unobserve(this.$refs.container)
|
this.resizeObserver.unobserve(this.$refs.container)
|
||||||
},
|
},
|
||||||
beforeUpdate () {
|
beforeUpdate () {
|
||||||
@@ -219,6 +227,9 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
mergeSchemaProperties,
|
mergeSchemaProperties,
|
||||||
|
updateContainerWidth () {
|
||||||
|
this.containerWidth = this.$refs.container.clientWidth
|
||||||
|
},
|
||||||
removeArea (area) {
|
removeArea (area) {
|
||||||
this.sectionRefs.forEach((sectionRef) => {
|
this.sectionRefs.forEach((sectionRef) => {
|
||||||
const pos = sectionRef.findAreaNodePos(area.uuid)
|
const pos = sectionRef.findAreaNodePos(area.uuid)
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ img.ProseMirror-separator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-resize-handle] {
|
[data-resize-handle] {
|
||||||
--handle-size: calc(10px / var(--zoom, 1));
|
--handle-size: calc(10px / var(--zoom));
|
||||||
display: none;
|
display: none;
|
||||||
width: var(--handle-size);
|
width: var(--handle-size);
|
||||||
height: var(--handle-size);
|
height: var(--handle-size);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: calc(2px / var(--zoom, 1)) solid #3b82f6;
|
border: calc(2px / var(--zoom)) solid #3b82f6;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ img.ProseMirror-separator {
|
|||||||
[data-resize-handle]::after {
|
[data-resize-handle]::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: calc(-8px / var(--zoom, 1));
|
inset: calc(-8px / var(--zoom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +135,12 @@ img.ProseMirror-separator {
|
|||||||
[data-resize-handle="bottom-left"] { margin: 0 0 calc(var(--handle-size) / -2) calc(var(--handle-size) / -2); cursor: nesw-resize; }
|
[data-resize-handle="bottom-left"] { margin: 0 0 calc(var(--handle-size) / -2) calc(var(--handle-size) / -2); cursor: nesw-resize; }
|
||||||
[data-resize-handle="bottom-right"] { margin: 0 calc(var(--handle-size) / -2) calc(var(--handle-size) / -2) 0; cursor: nwse-resize; }
|
[data-resize-handle="bottom-right"] { margin: 0 calc(var(--handle-size) / -2) calc(var(--handle-size) / -2) 0; cursor: nwse-resize; }
|
||||||
|
|
||||||
|
/* iOS 27 WebKit drops thin collapsed borders when zoomed out */
|
||||||
|
[data-zoomed-out] .ProseMirror table {
|
||||||
|
border-collapse: separate !important;
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ProseMirror .selectedCell {
|
.ProseMirror .selectedCell {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -1586,7 +1592,9 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
|
|||||||
widths[columnIndex] += change
|
widths[columnIndex] += change
|
||||||
widths[columnIndex + 1] -= change
|
widths[columnIndex + 1] -= change
|
||||||
} else {
|
} else {
|
||||||
widths[columnIndex] = Math.max(widths[columnIndex] + delta, MIN_SIZE_PX)
|
const maxDelta = (view.dom.parentElement.getBoundingClientRect().right - tableDom.getBoundingClientRect().right) / zoom
|
||||||
|
|
||||||
|
widths[columnIndex] = Math.max(widths[columnIndex] + Math.min(delta, Math.max(maxDelta, 0)), MIN_SIZE_PX)
|
||||||
}
|
}
|
||||||
|
|
||||||
setTableColumnWidths(view, tableDom, widths)
|
setTableColumnWidths(view, tableDom, widths)
|
||||||
@@ -1680,6 +1688,25 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
|
|||||||
})
|
})
|
||||||
|
|
||||||
class ZoomedResizableNodeView extends ResizableNodeView {
|
class ZoomedResizableNodeView extends ResizableNodeView {
|
||||||
|
constructor (options) {
|
||||||
|
super(options)
|
||||||
|
|
||||||
|
this.wrapper.addEventListener('touchstart', (event) => {
|
||||||
|
if (event.target.closest('[data-resize-handle]')) {
|
||||||
|
document.addEventListener('touchend', this.handleTouchEnd)
|
||||||
|
document.addEventListener('touchcancel', this.handleTouchEnd)
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchEnd = () => {
|
||||||
|
document.removeEventListener('touchend', this.handleTouchEnd)
|
||||||
|
document.removeEventListener('touchcancel', this.handleTouchEnd)
|
||||||
|
document.removeEventListener('touchmove', this.handleTouchMove)
|
||||||
|
|
||||||
|
this.handleMouseUp()
|
||||||
|
}
|
||||||
|
|
||||||
handleResize (deltaX, deltaY) {
|
handleResize (deltaX, deltaY) {
|
||||||
const zoom = dynamicAreaProps.getZoom()
|
const zoom = dynamicAreaProps.getZoom()
|
||||||
|
|
||||||
@@ -1688,6 +1715,15 @@ export function buildEditor ({ dynamicAreaProps, attachmentsIndex, renderHtmlFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DynamicImageNode = ImageNode.extend({
|
const DynamicImageNode = ImageNode.extend({
|
||||||
|
addProseMirrorPlugins () {
|
||||||
|
return [
|
||||||
|
new Plugin({
|
||||||
|
props: {
|
||||||
|
attributes: (state) => state.selection.node?.type.name === this.name ? { inputmode: 'none', style: 'caret-color: transparent' } : {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
renderHTML ({ node }) {
|
renderHTML ({ node }) {
|
||||||
const { loading, ...attrs } = node.attrs.htmlAttrs
|
const { loading, ...attrs } = node.attrs.htmlAttrs
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="relative"
|
class="relative"
|
||||||
@mouseenter="submenu = 'field'"
|
@pointerenter="$event.pointerType === 'mouse' ? submenu = 'field' : null"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 w-full px-2 py-1 rounded-md"
|
class="flex items-center gap-2 w-full px-2 py-1 rounded-md"
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="relative"
|
class="relative"
|
||||||
@mouseenter="submenu = 'table'"
|
@pointerenter="$event.pointerType === 'mouse' ? submenu = 'table' : null"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 w-full px-2 py-1 rounded-md"
|
class="flex items-center gap-2 w-full px-2 py-1 rounded-md"
|
||||||
@@ -90,9 +90,11 @@
|
|||||||
<div
|
<div
|
||||||
v-if="submenu === 'table'"
|
v-if="submenu === 'table'"
|
||||||
class="absolute top-0 left-full w-max p-2 bg-white border border-neutral-200 rounded-lg shadow-lg"
|
class="absolute top-0 left-full w-max p-2 bg-white border border-neutral-200 rounded-lg shadow-lg"
|
||||||
@mouseleave="tableSize = { rows: 0, cols: 0 }"
|
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-8 gap-0.5">
|
<div
|
||||||
|
v-if="!isMobile"
|
||||||
|
class="grid grid-cols-8 gap-0.5 mb-2"
|
||||||
|
>
|
||||||
<template
|
<template
|
||||||
v-for="row in 8"
|
v-for="row in 8"
|
||||||
:key="row"
|
:key="row"
|
||||||
@@ -103,14 +105,47 @@
|
|||||||
class="w-4 h-4 border rounded-sm"
|
class="w-4 h-4 border rounded-sm"
|
||||||
:class="row <= tableSize.rows && col <= tableSize.cols ? 'bg-neutral-200 border-neutral-400' : 'bg-white border-neutral-200'"
|
:class="row <= tableSize.rows && col <= tableSize.cols ? 'bg-neutral-200 border-neutral-400' : 'bg-white border-neutral-200'"
|
||||||
:aria-label="`${row} × ${col}`"
|
:aria-label="`${row} × ${col}`"
|
||||||
@mouseenter="tableSize = { rows: row, cols: col }"
|
@pointerenter="$event.pointerType === 'mouse' ? tableSize = { rows: row, cols: col } : null"
|
||||||
@click="[$emit('insert-table', { rows: row, cols: col }), isOpen = false]"
|
@click="[$emit('insert-table', { rows: row, cols: col }), isOpen = false]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 text-xs text-center text-base-content/60">
|
<div class="flex items-center justify-center gap-1 text-xs">
|
||||||
{{ tableSize.rows ? `${tableSize.rows} × ${tableSize.cols}` : t('table_size') }}
|
<template
|
||||||
|
v-for="key in ['rows', 'cols']"
|
||||||
|
:key="key"
|
||||||
|
>
|
||||||
|
<span v-if="key === 'cols'">×</span>
|
||||||
|
<div class="flex items-center border border-neutral-200 rounded-md">
|
||||||
|
<button
|
||||||
|
class="inline-flex items-center justify-center w-6 h-6 rounded-md hover:bg-neutral-100"
|
||||||
|
@click="tableSize[key] = Math.max(tableSize[key] - 1, 1)"
|
||||||
|
>
|
||||||
|
<IconMinus
|
||||||
|
:width="12"
|
||||||
|
:height="12"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span class="w-5 text-center">{{ tableSize[key] }}</span>
|
||||||
|
<button
|
||||||
|
class="inline-flex items-center justify-center w-6 h-6 rounded-md hover:bg-neutral-100"
|
||||||
|
@click="tableSize[key] = Math.min(tableSize[key] + 1, 20)"
|
||||||
|
>
|
||||||
|
<IconPlus
|
||||||
|
:width="12"
|
||||||
|
:height="12"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="isMobile || tableSize.rows > 8 || tableSize.cols > 8"
|
||||||
|
class="w-full mt-2 px-2 py-1 rounded-md bg-neutral-100 hover:bg-neutral-200"
|
||||||
|
@click="[$emit('insert-table', { ...tableSize }), isOpen = false]"
|
||||||
|
>
|
||||||
|
{{ t('insert') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -142,13 +177,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconPlus, IconX, IconForms, IconTable, IconPhoto, IconChevronRight, IconBracketsContain } from '@tabler/icons-vue'
|
import { IconPlus, IconMinus, IconX, IconForms, IconTable, IconPhoto, IconChevronRight, IconBracketsContain } from '@tabler/icons-vue'
|
||||||
import FieldType from './field_type.vue'
|
import FieldType from './field_type.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DynamicInsertMenu',
|
name: 'DynamicInsertMenu',
|
||||||
components: {
|
components: {
|
||||||
IconPlus,
|
IconPlus,
|
||||||
|
IconMinus,
|
||||||
IconX,
|
IconX,
|
||||||
IconForms,
|
IconForms,
|
||||||
IconTable,
|
IconTable,
|
||||||
@@ -156,7 +192,7 @@ export default {
|
|||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconBracketsContain
|
IconBracketsContain
|
||||||
},
|
},
|
||||||
inject: ['withPhone', 'withPayment', 'withVerification', 'withKba', 't', 'fieldTypes'],
|
inject: ['withPhone', 'withPayment', 'withVerification', 'withKba', 't', 'fieldTypes', 'isMobile'],
|
||||||
props: {
|
props: {
|
||||||
coords: {
|
coords: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -168,7 +204,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
submenu: null,
|
submenu: null,
|
||||||
tableSize: { rows: 0, cols: 0 }
|
tableSize: { rows: 3, cols: 3 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
class="absolute pointer-events-none z-10 bg-black"
|
class="absolute pointer-events-none z-10 bg-black"
|
||||||
:style="{ width: '1px', height: cursorHighlightCoords.height + 'px', left: cursorHighlightCoords.x + 'px', top: cursorHighlightCoords.y + 'px' }"
|
:style="{ width: '1px', height: cursorHighlightCoords.height + 'px', left: cursorHighlightCoords.x + 'px', top: cursorHighlightCoords.y + 'px' }"
|
||||||
/>
|
/>
|
||||||
<div :style="{ zoom, '--zoom': zoom }">
|
<div
|
||||||
|
:style="{ zoom, '--zoom': zoom }"
|
||||||
|
:data-zoomed-out="zoom < 0.75 || null"
|
||||||
|
>
|
||||||
<section
|
<section
|
||||||
:id="section.id"
|
:id="section.id"
|
||||||
ref="editorElement"
|
ref="editorElement"
|
||||||
@@ -533,6 +536,7 @@ export default {
|
|||||||
coords.canMerge = editor.can().mergeTableCells()
|
coords.canMerge = editor.can().mergeTableCells()
|
||||||
coords.canSplit = editor.can().splitTableCell()
|
coords.canSplit = editor.can().splitTableCell()
|
||||||
coords.minHeight = tableRowMinHeights(dom, this.zoom, false).reduce((acc, value) => acc + value, 0)
|
coords.minHeight = tableRowMinHeights(dom, this.zoom, false).reduce((acc, value) => acc + value, 0)
|
||||||
|
coords.maxWidth = Math.max(this.$refs.editorElement.getBoundingClientRect().right - rect.left, rect.width)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.blockMenuCoords = coords
|
this.blockMenuCoords = coords
|
||||||
|
|||||||
@@ -271,7 +271,6 @@ const en = {
|
|||||||
insert: 'Insert',
|
insert: 'Insert',
|
||||||
field: 'Field',
|
field: 'Field',
|
||||||
table: 'Table',
|
table: 'Table',
|
||||||
table_size: 'Table size',
|
|
||||||
align_center: 'Align Center',
|
align_center: 'Align Center',
|
||||||
insert_row_above: 'Insert row above',
|
insert_row_above: 'Insert row above',
|
||||||
insert_row_below: 'Insert row below',
|
insert_row_below: 'Insert row below',
|
||||||
@@ -559,7 +558,6 @@ const es = {
|
|||||||
insert: 'Insertar',
|
insert: 'Insertar',
|
||||||
field: 'Campo',
|
field: 'Campo',
|
||||||
table: 'Tabla',
|
table: 'Tabla',
|
||||||
table_size: 'Tamaño de tabla',
|
|
||||||
align_center: 'Centrar',
|
align_center: 'Centrar',
|
||||||
insert_row_above: 'Fila arriba',
|
insert_row_above: 'Fila arriba',
|
||||||
insert_row_below: 'Fila abajo',
|
insert_row_below: 'Fila abajo',
|
||||||
@@ -847,7 +845,6 @@ const it = {
|
|||||||
insert: 'Inserisci',
|
insert: 'Inserisci',
|
||||||
field: 'Campo',
|
field: 'Campo',
|
||||||
table: 'Tabella',
|
table: 'Tabella',
|
||||||
table_size: 'Dimensione tabella',
|
|
||||||
align_center: 'Centra',
|
align_center: 'Centra',
|
||||||
insert_row_above: 'Riga sopra',
|
insert_row_above: 'Riga sopra',
|
||||||
insert_row_below: 'Riga sotto',
|
insert_row_below: 'Riga sotto',
|
||||||
@@ -1135,7 +1132,6 @@ const pt = {
|
|||||||
insert: 'Inserir',
|
insert: 'Inserir',
|
||||||
field: 'Campo',
|
field: 'Campo',
|
||||||
table: 'Tabela',
|
table: 'Tabela',
|
||||||
table_size: 'Tamanho da tabela',
|
|
||||||
align_center: 'Centralizar',
|
align_center: 'Centralizar',
|
||||||
insert_row_above: 'Linha acima',
|
insert_row_above: 'Linha acima',
|
||||||
insert_row_below: 'Linha abaixo',
|
insert_row_below: 'Linha abaixo',
|
||||||
@@ -1423,7 +1419,6 @@ const fr = {
|
|||||||
insert: 'Insérer',
|
insert: 'Insérer',
|
||||||
field: 'Champ',
|
field: 'Champ',
|
||||||
table: 'Tableau',
|
table: 'Tableau',
|
||||||
table_size: 'Taille du tableau',
|
|
||||||
align_center: 'Centrer',
|
align_center: 'Centrer',
|
||||||
insert_row_above: 'Ligne au-dessus',
|
insert_row_above: 'Ligne au-dessus',
|
||||||
insert_row_below: 'Ligne en dessous',
|
insert_row_below: 'Ligne en dessous',
|
||||||
@@ -1711,7 +1706,6 @@ const de = {
|
|||||||
insert: 'Einfügen',
|
insert: 'Einfügen',
|
||||||
field: 'Feld',
|
field: 'Feld',
|
||||||
table: 'Tabelle',
|
table: 'Tabelle',
|
||||||
table_size: 'Tabellengröße',
|
|
||||||
align_center: 'Zentrieren',
|
align_center: 'Zentrieren',
|
||||||
insert_row_above: 'Zeile darüber',
|
insert_row_above: 'Zeile darüber',
|
||||||
insert_row_below: 'Zeile darunter',
|
insert_row_below: 'Zeile darunter',
|
||||||
@@ -1999,7 +1993,6 @@ const nl = {
|
|||||||
insert: 'Invoegen',
|
insert: 'Invoegen',
|
||||||
field: 'Veld',
|
field: 'Veld',
|
||||||
table: 'Tabel',
|
table: 'Tabel',
|
||||||
table_size: 'Tabelgrootte',
|
|
||||||
align_center: 'Centreren',
|
align_center: 'Centreren',
|
||||||
insert_row_above: 'Rij erboven',
|
insert_row_above: 'Rij erboven',
|
||||||
insert_row_below: 'Rij eronder',
|
insert_row_below: 'Rij eronder',
|
||||||
|
|||||||
Reference in new issue
Block a user