20260721173401
This commit is contained in:
1 parent
fd161dd302
commit
bab8e2972f
32 files changed
+1207
-3171
No files matched your search
@@ -0,0 +1,171 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
|
||||
defineOptions({ inheritAttrs: false });
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: String, default: "" },
|
||||
disabled: { type: Boolean, default: false },
|
||||
readonly: { type: Boolean, default: false },
|
||||
allowClear: { type: Boolean, default: true },
|
||||
maxlength: { type: Number, default: undefined },
|
||||
popupWidth: { type: [Number, String], default: 420 },
|
||||
popupHeight: { type: [Number, String], default: 180 },
|
||||
placement: { type: String, default: "bottomCenter" },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update:modelValue", "change", "focus", "blur", "openChange"]);
|
||||
|
||||
const open = ref(false);
|
||||
const inputRef = ref(null);
|
||||
const textareaRef = ref(null);
|
||||
const editorRef = ref(null);
|
||||
const triggerWidth = ref(0);
|
||||
|
||||
const popoverPlacement = computed(() =>
|
||||
props.placement === "bottomCenter" ? "bottom" : props.placement,
|
||||
);
|
||||
const popupWidth = computed(() =>
|
||||
typeof props.popupWidth === "number" ? `${props.popupWidth}px` : props.popupWidth,
|
||||
);
|
||||
const popupHeight = computed(() =>
|
||||
typeof props.popupHeight === "number" ? `${props.popupHeight}px` : props.popupHeight,
|
||||
);
|
||||
const popupStyles = computed(() => ({
|
||||
container: {
|
||||
width: popupWidth.value,
|
||||
minWidth: triggerWidth.value ? `${triggerWidth.value}px` : undefined,
|
||||
maxWidth: "calc(100vw - 24px)",
|
||||
padding: "8px",
|
||||
},
|
||||
}));
|
||||
|
||||
function updateValue(value) {
|
||||
emit("update:modelValue", value ?? "");
|
||||
}
|
||||
|
||||
function onInputChange(event) {
|
||||
emit("change", event);
|
||||
}
|
||||
|
||||
async function showEditor() {
|
||||
if (props.disabled || open.value) return;
|
||||
|
||||
const inputElement = inputRef.value?.$el ?? inputRef.value;
|
||||
triggerWidth.value = inputElement?.getBoundingClientRect?.().width ?? 0;
|
||||
open.value = true;
|
||||
emit("openChange", true);
|
||||
await nextTick();
|
||||
textareaRef.value?.focus?.({ cursor: "end" });
|
||||
}
|
||||
|
||||
function onInputFocus(event) {
|
||||
emit("focus", event);
|
||||
showEditor();
|
||||
}
|
||||
|
||||
function onOpenChange(value) {
|
||||
if (value && props.disabled) return;
|
||||
open.value = value;
|
||||
emit("openChange", value);
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
if (!open.value) return;
|
||||
open.value = false;
|
||||
emit("openChange", false);
|
||||
}
|
||||
|
||||
function onDocumentPointerDown(event) {
|
||||
if (!open.value) return;
|
||||
|
||||
const inputElement = inputRef.value?.$el ?? inputRef.value;
|
||||
const path = event.composedPath?.() ?? [];
|
||||
if (path.includes(inputElement) || path.includes(editorRef.value)) return;
|
||||
closeEditor();
|
||||
}
|
||||
|
||||
function onTextareaKeydown(event) {
|
||||
event.stopPropagation();
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeEditor();
|
||||
}
|
||||
}
|
||||
|
||||
function onInputBlur(event) {
|
||||
if (!open.value) emit("blur", event);
|
||||
}
|
||||
|
||||
function onTextareaBlur(event) {
|
||||
emit("blur", event);
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener("pointerdown", onDocumentPointerDown, true));
|
||||
onBeforeUnmount(() => document.removeEventListener("pointerdown", onDocumentPointerDown, true));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-popover
|
||||
:open="open"
|
||||
:trigger="[]"
|
||||
:placement="popoverPlacement"
|
||||
:arrow="false"
|
||||
:styles="popupStyles"
|
||||
@open-change="onOpenChange"
|
||||
>
|
||||
<template #content>
|
||||
<div ref="editorRef" class="fms-memo-editor" @mousedown.stop @click.stop>
|
||||
<a-textarea
|
||||
ref="textareaRef"
|
||||
:value="modelValue"
|
||||
:maxlength="maxlength"
|
||||
:placeholder="$attrs.placeholder"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:style="{ height: popupHeight, resize: 'both' }"
|
||||
@update:value="updateValue"
|
||||
@change="onInputChange"
|
||||
@keydown="onTextareaKeydown"
|
||||
@blur="onTextareaBlur"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<a-input
|
||||
ref="inputRef"
|
||||
v-bind="$attrs"
|
||||
class="fms-memo-input"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
:readonly="readonly"
|
||||
:allow-clear="allowClear"
|
||||
:maxlength="maxlength"
|
||||
@update:value="updateValue"
|
||||
@change="onInputChange"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputBlur"
|
||||
@click.stop="showEditor"
|
||||
@keydown.stop
|
||||
>
|
||||
<template v-for="(_, name) in $slots" :key="name" #[name]="slotProps">
|
||||
<slot :name="name" v-bind="slotProps || {}" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fms-memo-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fms-memo-editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fms-memo-editor :deep(textarea) {
|
||||
min-width: 180px;
|
||||
min-height: 96px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user