mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
3.4 KiB
143 lines
3.4 KiB
<template>
|
|
<div
|
|
class="group/contenteditable relative overflow-visible"
|
|
:class="{ 'flex items-center': !iconInline }"
|
|
>
|
|
<span
|
|
ref="contenteditable"
|
|
dir="auto"
|
|
:contenteditable="editable"
|
|
style="min-width: 2px"
|
|
:class="[iconInline ? 'inline' : 'block', hideIcon ? 'focus:block' : '']"
|
|
class="peer outline-none"
|
|
@paste.prevent="onPaste"
|
|
@keydown.enter.prevent="blurContenteditable"
|
|
@focus="$emit('focus', $event)"
|
|
@blur="onBlur"
|
|
>
|
|
{{ value }}
|
|
</span>
|
|
<span
|
|
class="relative inline"
|
|
:class="{ 'peer-focus:hidden': hideIcon, 'peer-focus:invisible': !hideIcon }"
|
|
>
|
|
<IconPencil
|
|
class="cursor-pointer flex-none opacity-0 group-hover/contenteditable-container:opacity-100 group-hover/contenteditable:opacity-100 align-middle pl-1"
|
|
:style="iconInline ? {} : { right: -(1.1 * iconWidth) + 'px' }"
|
|
:title="t('edit')"
|
|
:class="{ invisible: !editable, 'absolute top-1/2 -translate-y-1/2': !iconInline || floatIcon, 'inline align-bottom': iconInline, 'left-0': floatIcon }"
|
|
:width="iconWidth + 4"
|
|
:stroke-width="iconStrokeWidth"
|
|
@click="[focusContenteditable(), selectOnEditClick && selectContent()]"
|
|
/>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { IconPencil } from '@tabler/icons-vue'
|
|
|
|
export default {
|
|
name: 'ContenteditableField',
|
|
components: {
|
|
IconPencil
|
|
},
|
|
inject: ['t'],
|
|
props: {
|
|
modelValue: {
|
|
type: String,
|
|
required: false,
|
|
default: ''
|
|
},
|
|
iconInline: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false
|
|
},
|
|
iconWidth: {
|
|
type: Number,
|
|
required: false,
|
|
default: 30
|
|
},
|
|
hideIcon: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
},
|
|
floatIcon: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false
|
|
},
|
|
selectOnEditClick: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false
|
|
},
|
|
editable: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: true
|
|
},
|
|
iconStrokeWidth: {
|
|
type: Number,
|
|
required: false,
|
|
default: 2
|
|
}
|
|
},
|
|
emits: ['update:model-value', 'focus', 'blur'],
|
|
data () {
|
|
return {
|
|
value: ''
|
|
}
|
|
},
|
|
watch: {
|
|
modelValue: {
|
|
handler (value) {
|
|
this.value = value
|
|
},
|
|
immediate: true
|
|
}
|
|
},
|
|
methods: {
|
|
onPaste (e) {
|
|
const text = (e.clipboardData || window.clipboardData).getData('text/plain')
|
|
|
|
const selection = this.$el.getRootNode().getSelection()
|
|
|
|
if (selection.rangeCount) {
|
|
selection.deleteFromDocument()
|
|
selection.getRangeAt(0).insertNode(document.createTextNode(text))
|
|
selection.collapseToEnd()
|
|
}
|
|
},
|
|
selectContent () {
|
|
const el = this.$refs.contenteditable
|
|
|
|
const range = document.createRange()
|
|
|
|
range.selectNodeContents(el)
|
|
|
|
const sel = window.getSelection()
|
|
|
|
sel.removeAllRanges()
|
|
|
|
sel.addRange(range)
|
|
},
|
|
onBlur (e) {
|
|
setTimeout(() => {
|
|
this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue
|
|
this.$emit('update:model-value', this.value)
|
|
this.$emit('blur', e)
|
|
}, 1)
|
|
},
|
|
focusContenteditable () {
|
|
this.$refs.contenteditable.focus()
|
|
},
|
|
blurContenteditable () {
|
|
this.$refs.contenteditable.blur()
|
|
}
|
|
}
|
|
}
|
|
</script>
|