eco-vue-js 0.11.18 → 0.11.19
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/dist/components/Input/WInput.vue.d.ts.map +1 -1
- package/dist/components/Input/WInput.vue.js +10 -9
- package/dist/components/Input/components/ContentEditable.vue.d.ts +5 -1
- package/dist/components/Input/components/ContentEditable.vue.d.ts.map +1 -1
- package/dist/components/Input/components/ContentEditable.vue.js +261 -2
- package/dist/components/Input/components/ContentEditable.vue2.js +2 -260
- package/dist/components/Input/models/utils.d.ts +3 -1
- package/dist/components/Input/models/utils.d.ts.map +1 -1
- package/dist/components/Input/models/utils.js +12 -6
- package/package.json +1 -1
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"WInput.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/Input/WInput.vue"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"WInput.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/Input/WInput.vue"],"names":[],"mappings":"AAoPA;AAqiBA,OAAO,KAAK,EAAC,UAAU,EAAE,aAAa,EAAC,MAAM,SAAS,CAAA;AActD,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,gBAAgB,CAAA;yBAE9B,IAAI,SAAS,SAAS,GAAG,MAAM,EAC/C,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,eAAe,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EACjE;WAmyBO,mBAAmB,CAAC,oCAAkE,CAAC,4BAA2B;oBACzG,OAAO,KAAK,EAAE,gBAAgB;qBAzmB7B,IAAI;oBAOL,IAAI;+BAkCS,aAAa;0BA1KlB,MAAM,QAAQ,MAAM,KAAG,IAAI;wBAN/B,WAAW;;;oBAyBf,IAAI;oBAgBJ,IAAI;MAusBgD,GAAG,IAAI;WACpE,GAAG;;uBAhEgB,GAAG;0BACA,GAAG;wBACJ,GAAG;;;YAEH,GAAG;;mCArgBF,aAAa;YAogBb,GAAG;;;YAEJ,GAAG;uBACJ,GAAG;wBACF,GAAG;uBACJ,GAAG;uBACH,GAAG;wBACF,GAAG;;;YAttB1B,oBAAoB,SAAS,4CAAa,SAAS,GAAG,IAAI;YAC1D,gBAAgB,SAAS,aAAa,GAAG,IAAI;YAC7C,aAAa,SAAS,aAAa,GAAG,IAAI;YAC1C,eAAe,SAAS,aAAa,GAAG,IAAI;YAC5C,iBAAiB,SAAS,aAAa,GAAG,IAAI;YAC9C,oBAAoB,SAAS,aAAa,GAAG,IAAI;YACjD,aAAa,GAAG,IAAI;YACpB,OAAO,SAAS,UAAU,GAAG,SAAS,GAAG,IAAI;YAC7C,MAAM,SAAS,UAAU,GAAG,IAAI;YAChC,OAAO,SAAS,UAAU,GAAG,IAAI;YACjC,WAAW,SAAS,UAAU,GAAG,IAAI;YACrC,cAAc,SAAS,UAAU,GAAG,IAAI;YACxC,cAAc,SAAS,KAAK,GAAG,IAAI;YACnC,OAAO,GAAG,IAAI;;EAmwBhB,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,WAAW,CAAC,CAAA;CAAE;AA9yBzE,wBA8yB4E;AAC5E,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG,EAAE,CAAC"}
|
@@ -5,7 +5,8 @@ import { Notify } from '../../utils/Notify.js';
|
|
5
5
|
import { useComponentStates } from '../../utils/useComponentStates.js';
|
6
6
|
import { checkPermissionPaste } from '../../utils/useCopy.js';
|
7
7
|
import { debounce } from '../../utils/utils.js';
|
8
|
-
import _sfc_main$2 from './components/
|
8
|
+
import _sfc_main$2 from './components/ContentEditable.vue.js';
|
9
|
+
import _sfc_main$3 from './components/InputActions.vue.js';
|
9
10
|
|
10
11
|
const _hoisted_1 = { class: "relative flex min-h-full flex-1" };
|
11
12
|
const _sfc_main = /* @__PURE__ */ defineComponent({
|
@@ -67,7 +68,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
67
68
|
emits: ["update:model-value", "keypress:enter", "keypress:up", "keypress:down", "keypress:delete", "keypress:backspace", "click:clear", "focus", "blur", "click", "mousedown", "click:suffix", "select:input", "paste"],
|
68
69
|
setup(__props, { expose: __expose, emit: __emit }) {
|
69
70
|
const InputToolbar = defineAsyncComponent(() => import('./components/InputToolbar.vue.js'));
|
70
|
-
const ContentEditable = defineAsyncComponent(() => import('./components/ContentEditable.vue.js'));
|
71
71
|
const props = __props;
|
72
72
|
const emit = __emit;
|
73
73
|
const { isReadonly, isDisabled } = useComponentStates(props);
|
@@ -289,7 +289,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
289
289
|
name: "default",
|
290
290
|
fn: withCtx(() => [
|
291
291
|
createElementVNode("div", {
|
292
|
-
class: normalizeClass(["flex gap-1", {
|
292
|
+
class: normalizeClass(["flex max-w-full gap-1", {
|
293
293
|
"flex-wrap": !_ctx.seamless,
|
294
294
|
"overflow-hidden": _ctx.seamless
|
295
295
|
}])
|
@@ -297,10 +297,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
297
297
|
renderSlot(_ctx.$slots, "prefix"),
|
298
298
|
!_ctx.hideInput ? (openBlock(), createElementBlock("div", {
|
299
299
|
key: 0,
|
300
|
-
class: normalizeClass({
|
300
|
+
class: normalizeClass([{
|
301
301
|
"font-mono": _ctx.mono,
|
302
|
-
"text-secure": _ctx.textSecure && !isSecureVisible.value && _ctx.modelValue
|
303
|
-
|
302
|
+
"text-secure": _ctx.textSecure && !isSecureVisible.value && _ctx.modelValue,
|
303
|
+
"whitespace-pre": _ctx.textarea
|
304
|
+
}, "overflow-x-auto overscroll-x-contain"])
|
304
305
|
}, toDisplayString(_ctx.modelValue || _ctx.emptyValue), 3)) : createCommentVNode("", true)
|
305
306
|
], 2)
|
306
307
|
]),
|
@@ -320,7 +321,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
320
321
|
}]),
|
321
322
|
onClick: focus
|
322
323
|
}, [
|
323
|
-
_ctx.textarea && (_ctx.rich || _ctx.toolbarActions || _ctx.$slots.toolbar) ? (openBlock(), createBlock(unref(InputToolbar), {
|
324
|
+
!unref(isDisabled) && !unref(isReadonly) && _ctx.textarea && (_ctx.rich || _ctx.toolbarActions || _ctx.$slots.toolbar) ? (openBlock(), createBlock(unref(InputToolbar), {
|
324
325
|
key: 0,
|
325
326
|
list: _ctx.toolbarActions,
|
326
327
|
rich: _ctx.rich === true,
|
@@ -377,7 +378,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
377
378
|
}, [
|
378
379
|
createElementVNode("div", _hoisted_1, [
|
379
380
|
renderSlot(_ctx.$slots, "before", normalizeProps(guardReactiveProps({ modelValue: _ctx.modelValue }))),
|
380
|
-
(openBlock(), createBlock(resolveDynamicComponent(_ctx.textarea ?
|
381
|
+
(openBlock(), createBlock(resolveDynamicComponent(_ctx.textarea ? _sfc_main$2 : "input"), {
|
381
382
|
id,
|
382
383
|
ref: "input",
|
383
384
|
class: normalizeClass(["w-input min-h-full flex-1 basis-auto appearance-none border-none bg-[inherit] outline-0 placeholder:text-gray-400 disabled:cursor-not-allowed dark:placeholder:text-gray-500", {
|
@@ -431,7 +432,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
431
432
|
], 2)
|
432
433
|
], 2)
|
433
434
|
], 2),
|
434
|
-
!_ctx.seamless || focused ? (openBlock(), createBlock(_sfc_main$
|
435
|
+
!_ctx.seamless || focused ? (openBlock(), createBlock(_sfc_main$3, {
|
435
436
|
key: 2,
|
436
437
|
"model-value": _ctx.modelValue,
|
437
438
|
loading: _ctx.loading,
|
@@ -4,13 +4,17 @@ type __VLS_Props = {
|
|
4
4
|
placeholder: string;
|
5
5
|
maxLength: number;
|
6
6
|
textParts: TextPart[] | undefined;
|
7
|
+
readonly: boolean | undefined;
|
8
|
+
disabled: boolean | undefined;
|
7
9
|
};
|
8
10
|
declare const _default: import('vue').DefineComponent<__VLS_Props, {
|
9
11
|
focus: () => void;
|
10
12
|
blur: () => void;
|
11
13
|
wrapSelection: (value: WrapSelection) => void;
|
12
14
|
setCaret: (indexStart: number, indexEnd?: number) => void;
|
13
|
-
getCaret: () => import('../models/utils').CaretOffset
|
15
|
+
getCaret: () => import('../models/utils').CaretOffset & {
|
16
|
+
trail: number;
|
17
|
+
};
|
14
18
|
offsetWidth: number;
|
15
19
|
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
|
16
20
|
blur: (value: FocusEvent) => any;
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"ContentEditable.vue.d.ts","sourceRoot":"","sources":["../../../../../src/components/Input/components/ContentEditable.vue"],"names":[],"mappings":"AAiBA;
|
1
|
+
{"version":3,"file":"ContentEditable.vue.d.ts","sourceRoot":"","sources":["../../../../../src/components/Input/components/ContentEditable.vue"],"names":[],"mappings":"AAiBA;AA0UA,OAAO,KAAK,EAAC,QAAQ,EAAG,aAAa,EAAC,MAAM,UAAU,CAAA;AAStD,KAAK,WAAW,GAAG;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAA;IACjC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;CAC9B,CAAC;;;;2BAqK4B,aAAa,KAAG,IAAI;2BATpB,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;;;AAsNvD,wBAUG"}
|
@@ -1,5 +1,264 @@
|
|
1
|
-
import
|
2
|
-
|
1
|
+
import { defineComponent, useTemplateRef, ref, watch, onMounted, createElementBlock, openBlock, nextTick } from 'vue';
|
2
|
+
import { WrapSelectionType } from '../../../utils/utils.js';
|
3
|
+
import { preserveIndentation } from '../models/toolbarActions.js';
|
4
|
+
import { getCaretOffset, setCaretOffset } from '../models/utils.js';
|
3
5
|
|
6
|
+
const _hoisted_1 = ["contenteditable", "placeholder"];
|
7
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
8
|
+
__name: "ContentEditable",
|
9
|
+
props: {
|
10
|
+
value: {},
|
11
|
+
placeholder: {},
|
12
|
+
maxLength: {},
|
13
|
+
textParts: {},
|
14
|
+
readonly: { type: Boolean },
|
15
|
+
disabled: { type: Boolean }
|
16
|
+
},
|
17
|
+
emits: ["update:model-value", "focus", "blur", "keydown"],
|
18
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
19
|
+
const props = __props;
|
20
|
+
const emit = __emit;
|
21
|
+
const elementRef = useTemplateRef("element");
|
22
|
+
const focused = ref(false);
|
23
|
+
const updateTextParts = () => {
|
24
|
+
if (!elementRef.value || !props.textParts) return;
|
25
|
+
const offsets = getCaret();
|
26
|
+
let nodeIndex = 0;
|
27
|
+
for (const item of props.textParts) {
|
28
|
+
const existingNode = elementRef.value.childNodes[nodeIndex] ?? null;
|
29
|
+
if (typeof item === "string") {
|
30
|
+
const displayText = item.replace(/\n$/g, "\n ");
|
31
|
+
if (existingNode instanceof Text) {
|
32
|
+
if (existingNode.textContent !== displayText) existingNode.textContent = displayText;
|
33
|
+
} else {
|
34
|
+
const textNode = document.createTextNode(displayText);
|
35
|
+
elementRef.value.insertBefore(textNode, existingNode);
|
36
|
+
}
|
37
|
+
} else {
|
38
|
+
if (existingNode instanceof HTMLElement && existingNode.tagName.toLowerCase() === item.tag.toLowerCase()) {
|
39
|
+
if (existingNode.textContent !== item.value) existingNode.textContent = item.value;
|
40
|
+
if (existingNode.className !== (item.class || "")) existingNode.className = item.class || "";
|
41
|
+
if (item.edit === false && existingNode.getAttribute("contenteditable") !== "false") {
|
42
|
+
existingNode.setAttribute("contenteditable", "false");
|
43
|
+
}
|
44
|
+
} else {
|
45
|
+
const element = document.createElement(item.tag);
|
46
|
+
element.textContent = item.value;
|
47
|
+
element.setAttribute("contenteditable", item.edit ? "plaintext-only" : "false");
|
48
|
+
if (item.class) element.className = item.class;
|
49
|
+
elementRef.value.insertBefore(element, existingNode);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
nodeIndex++;
|
53
|
+
}
|
54
|
+
while (elementRef.value.childNodes.length > props.textParts.length) {
|
55
|
+
const nodeToRemove = elementRef.value.childNodes[props.textParts.length];
|
56
|
+
elementRef.value.removeChild(nodeToRemove);
|
57
|
+
}
|
58
|
+
if (focused.value && !isSetCaretNext) setCaret(offsets.start, offsets.end !== offsets.start ? void 0 : offsets.end);
|
59
|
+
};
|
60
|
+
watch(() => props.textParts, updateTextParts, { immediate: true });
|
61
|
+
const updateTextValue = (value) => {
|
62
|
+
if (props.textParts || !elementRef.value) return;
|
63
|
+
if (elementRef.value.textContent !== value) {
|
64
|
+
const offsets = getCaret();
|
65
|
+
elementRef.value.textContent = value;
|
66
|
+
if (focused.value) setCaret(offsets.start, offsets.end !== offsets.start ? void 0 : offsets.end);
|
67
|
+
}
|
68
|
+
};
|
69
|
+
watch(() => props.value, updateTextValue, { immediate: true });
|
70
|
+
const textPartsToText = (parts) => {
|
71
|
+
return parts.map((part) => typeof part === "string" ? part : part.value).join("");
|
72
|
+
};
|
73
|
+
const getCurrentText = () => {
|
74
|
+
return props.textParts ? textPartsToText(props.textParts) : props.value;
|
75
|
+
};
|
76
|
+
const lineBreakEvents = ["insertParagraph", "insertLineBreak"];
|
77
|
+
const insertParagraph = (e) => {
|
78
|
+
if (lineBreakEvents.includes(e.inputType)) {
|
79
|
+
e.preventDefault();
|
80
|
+
insertPlain("\n");
|
81
|
+
}
|
82
|
+
};
|
83
|
+
const regexDifferentEnding = /\r\n?/g;
|
84
|
+
const regexSpaces = /\n \n/g;
|
85
|
+
const regexEnding = / +$/gm;
|
86
|
+
const normalizeText = (text) => {
|
87
|
+
return text.replace(regexDifferentEnding, "\n").replace(regexSpaces, "\n\n").replace(regexEnding, "");
|
88
|
+
};
|
89
|
+
const onInput = (e) => {
|
90
|
+
e.stopImmediatePropagation();
|
91
|
+
if (!(e.target instanceof HTMLDivElement)) return;
|
92
|
+
const rawText = e.target.textContent ?? "";
|
93
|
+
const text = normalizeText(rawText);
|
94
|
+
const currentText = getCurrentText();
|
95
|
+
if (text === currentText) return;
|
96
|
+
if (props.maxLength && typeof text === "string" && text.length > props.maxLength) {
|
97
|
+
e.preventDefault();
|
98
|
+
const substring = text.substring(0, props.maxLength);
|
99
|
+
if (!props.textParts) updateTextValue(substring);
|
100
|
+
else updateTextParts();
|
101
|
+
emit("update:model-value", substring);
|
102
|
+
} else {
|
103
|
+
emit("update:model-value", text);
|
104
|
+
}
|
105
|
+
};
|
106
|
+
const onPaste = async (e) => {
|
107
|
+
e.preventDefault();
|
108
|
+
navigator.clipboard.readText();
|
109
|
+
const text = (e.clipboardData?.getData("text/plain") || await navigator.clipboard.readText()).replace(/\r\n?/g, "\n");
|
110
|
+
insertPlain(text);
|
111
|
+
};
|
112
|
+
const insertPlain = (text) => {
|
113
|
+
const root = elementRef.value;
|
114
|
+
if (!root) return;
|
115
|
+
const { start, end, trail } = getCaret();
|
116
|
+
const currentText = getCurrentText();
|
117
|
+
const next = (currentText ?? "").slice(0, start) + " ".repeat(trail) + text + ((currentText ?? "").slice(end) || " ");
|
118
|
+
const caretAfter = start + text.length + trail;
|
119
|
+
emit("update:model-value", props.maxLength && next.length > props.maxLength ? next.substring(0, props.maxLength) : next);
|
120
|
+
nextTick(() => setCaret(props.maxLength ? Math.min(caretAfter, props.maxLength) : caretAfter));
|
121
|
+
};
|
122
|
+
const getCaret = () => getCaretOffset(elementRef.value);
|
123
|
+
let isSetCaretNext = false;
|
124
|
+
const setCaret = (indexStart, indexEnd) => {
|
125
|
+
isSetCaretNext = false;
|
126
|
+
setCaretOffset(elementRef.value, indexStart, indexEnd);
|
127
|
+
};
|
128
|
+
const collapseList = [" ", "\n"];
|
129
|
+
let offsetsOld = null;
|
130
|
+
const wrapSelection = (value) => {
|
131
|
+
if (focused.value || !offsetsOld) offsetsOld = getCaret();
|
132
|
+
const offsets = offsetsOld;
|
133
|
+
const currentText = getCurrentText() ?? "";
|
134
|
+
let newText = "";
|
135
|
+
let newCursorStart = offsets.start;
|
136
|
+
let newCursorEnd = void 0;
|
137
|
+
switch (value.type) {
|
138
|
+
case WrapSelectionType.TOGGLE:
|
139
|
+
let startLen = value.start.length;
|
140
|
+
const endLen = value.end.length;
|
141
|
+
const textWithContext = currentText.slice(
|
142
|
+
Math.max(0, offsets.start - startLen),
|
143
|
+
Math.min(currentText.length, offsets.end + endLen)
|
144
|
+
);
|
145
|
+
if ((!value.start || textWithContext.startsWith(value.start)) && (!value.end || textWithContext.endsWith(value.end))) {
|
146
|
+
let expandedStart = Math.max(0, offsets.start - startLen);
|
147
|
+
let start = currentText.slice(0, expandedStart);
|
148
|
+
const middle = currentText.slice(offsets.start, offsets.end);
|
149
|
+
let end = currentText.slice(Math.min(currentText.length, offsets.end + endLen));
|
150
|
+
for (const item of collapseList) {
|
151
|
+
if (value.start.startsWith(item) && !start.endsWith(item) && !middle.startsWith(item)) {
|
152
|
+
start += item;
|
153
|
+
expandedStart += item.length;
|
154
|
+
}
|
155
|
+
if (value.end.endsWith(item) && !end.startsWith(item) && !middle.endsWith(item)) end = item + end;
|
156
|
+
}
|
157
|
+
newText = start + middle + end;
|
158
|
+
if (value.prepare) newText = value.prepare(newText, 0);
|
159
|
+
newCursorStart = expandedStart;
|
160
|
+
newCursorEnd = expandedStart + offsets.end - offsets.start;
|
161
|
+
} else {
|
162
|
+
if (!value.start || !value.end) {
|
163
|
+
const offset = value.start ? offsets.start : offsets.end;
|
164
|
+
let start = currentText.slice(0, offset);
|
165
|
+
let end = currentText.slice(offset);
|
166
|
+
for (const item of collapseList) {
|
167
|
+
if (value.start.startsWith(item) && start.endsWith(item)) {
|
168
|
+
start = start.slice(0, offsets.start - item.length);
|
169
|
+
startLen -= item.length;
|
170
|
+
}
|
171
|
+
if (value.end.endsWith(item) && end.startsWith(item)) end = end.slice(item.length);
|
172
|
+
}
|
173
|
+
newText = (value.prepare?.(start, 0) ?? start) + (value.start || value.end) + (value.prepare?.(end, offset) ?? end);
|
174
|
+
} else {
|
175
|
+
let start = currentText.slice(0, offsets.start);
|
176
|
+
const middle = currentText.slice(offsets.start, offsets.end);
|
177
|
+
let end = currentText.slice(offsets.end);
|
178
|
+
for (const item of collapseList) {
|
179
|
+
if (value.start.startsWith(item) && start.endsWith(item)) {
|
180
|
+
start = start.slice(0, offsets.start - item.length);
|
181
|
+
startLen -= item.length;
|
182
|
+
}
|
183
|
+
if (value.end.endsWith(item) && end.startsWith(item)) end = end.slice(item.length);
|
184
|
+
}
|
185
|
+
newText = (value.prepare?.(start, 0) ?? start) + value.start + (value.prepare?.(middle, offsets.start) ?? middle) + value.end + (value.prepare?.(end, offsets.end) ?? end);
|
186
|
+
}
|
187
|
+
newCursorStart = offsets.start + startLen;
|
188
|
+
if (offsets.start !== offsets.end) newCursorEnd = newCursorStart + offsets.end - offsets.start;
|
189
|
+
}
|
190
|
+
break;
|
191
|
+
case WrapSelectionType.LINE_PREFIX:
|
192
|
+
const lineStart = currentText.lastIndexOf("\n", offsets.start - 1) + 1;
|
193
|
+
const lineEnd = currentText.indexOf("\n", offsets.end);
|
194
|
+
const actualLineEnd = lineEnd === -1 ? currentText.length : lineEnd;
|
195
|
+
const linesText = currentText.slice(lineStart, actualLineEnd);
|
196
|
+
const beforeLines = currentText.slice(0, lineStart);
|
197
|
+
const afterLines = currentText.slice(actualLineEnd);
|
198
|
+
const lines = linesText.split("\n");
|
199
|
+
if (lines.length === 0) lines.push("");
|
200
|
+
const allLinesHavePrefix = value.detectPattern ? lines.every((line) => !line.trim() || value.detectPattern.test(line)) : value.linePrefix ? lines.every((line) => !line.trim() || line.startsWith(value.linePrefix)) : false;
|
201
|
+
if (allLinesHavePrefix) {
|
202
|
+
const cleanText = lines.map((line) => preserveIndentation(line, "")).join("\n");
|
203
|
+
newText = beforeLines + cleanText + afterLines;
|
204
|
+
newCursorStart = lineStart;
|
205
|
+
newCursorEnd = lineStart + cleanText.length;
|
206
|
+
} else {
|
207
|
+
const processedText = value.lineTransformAll ? value.lineTransformAll(lines) : value.lineTransform ? lines.map(value.lineTransform).join("\n") : lines.map((line) => line.trim() ? preserveIndentation(line, value.linePrefix) : line).join("\n");
|
208
|
+
newText = beforeLines + processedText + afterLines;
|
209
|
+
newCursorStart = lineStart;
|
210
|
+
newCursorEnd = lineStart + processedText.length;
|
211
|
+
}
|
212
|
+
break;
|
213
|
+
}
|
214
|
+
isSetCaretNext = true;
|
215
|
+
emit("update:model-value", newText);
|
216
|
+
requestAnimationFrame(() => setCaret(newCursorStart, newCursorEnd));
|
217
|
+
};
|
218
|
+
const focus = () => {
|
219
|
+
elementRef.value?.focus();
|
220
|
+
};
|
221
|
+
const blur = () => {
|
222
|
+
elementRef.value?.blur();
|
223
|
+
};
|
224
|
+
onMounted(() => {
|
225
|
+
updateTextValue(props.value);
|
226
|
+
updateTextParts();
|
227
|
+
});
|
228
|
+
__expose({
|
229
|
+
focus,
|
230
|
+
blur,
|
231
|
+
wrapSelection,
|
232
|
+
setCaret,
|
233
|
+
getCaret,
|
234
|
+
get offsetWidth() {
|
235
|
+
return elementRef.value?.offsetWidth ?? 0;
|
236
|
+
}
|
237
|
+
});
|
238
|
+
return (_ctx, _cache) => {
|
239
|
+
return openBlock(), createElementBlock("div", {
|
240
|
+
ref: "element",
|
241
|
+
contenteditable: _ctx.readonly || _ctx.disabled ? "false" : "plaintext-only",
|
242
|
+
role: "textbox",
|
243
|
+
"aria-multiline": "true",
|
244
|
+
spellcheck: "false",
|
245
|
+
placeholder: _ctx.placeholder,
|
246
|
+
class: "relative whitespace-pre",
|
247
|
+
onInput,
|
248
|
+
onBeforeinput: _cache[0] || (_cache[0] = ($event) => insertParagraph($event)),
|
249
|
+
onPaste,
|
250
|
+
onKeydown: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("keydown", $event)),
|
251
|
+
onFocus: _cache[2] || (_cache[2] = ($event) => {
|
252
|
+
_ctx.$emit("focus", $event);
|
253
|
+
focused.value = true;
|
254
|
+
}),
|
255
|
+
onBlur: _cache[3] || (_cache[3] = ($event) => {
|
256
|
+
_ctx.$emit("blur", $event);
|
257
|
+
focused.value = false;
|
258
|
+
})
|
259
|
+
}, null, 40, _hoisted_1);
|
260
|
+
};
|
261
|
+
}
|
262
|
+
});
|
4
263
|
|
5
264
|
export { _sfc_main as default };
|
@@ -1,263 +1,5 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
import { preserveIndentation } from '../models/toolbarActions.js';
|
4
|
-
import { getCaretOffset, setCaretOffset } from '../models/utils.js';
|
1
|
+
import _sfc_main from './ContentEditable.vue.js';
|
2
|
+
|
5
3
|
|
6
|
-
const _hoisted_1 = ["placeholder"];
|
7
|
-
const _sfc_main = /* @__PURE__ */ defineComponent({
|
8
|
-
__name: "ContentEditable",
|
9
|
-
props: {
|
10
|
-
value: {},
|
11
|
-
placeholder: {},
|
12
|
-
maxLength: {},
|
13
|
-
textParts: {}
|
14
|
-
},
|
15
|
-
emits: ["update:model-value", "focus", "blur", "keydown"],
|
16
|
-
setup(__props, { expose: __expose, emit: __emit }) {
|
17
|
-
const props = __props;
|
18
|
-
const emit = __emit;
|
19
|
-
const elementRef = useTemplateRef("element");
|
20
|
-
const focused = ref(false);
|
21
|
-
const updateTextParts = () => {
|
22
|
-
if (!elementRef.value || !props.textParts) return;
|
23
|
-
const offsets = getCaret();
|
24
|
-
let nodeIndex = 0;
|
25
|
-
for (const item of props.textParts) {
|
26
|
-
const existingNode = elementRef.value.childNodes[nodeIndex] ?? null;
|
27
|
-
if (typeof item === "string") {
|
28
|
-
const displayText = item.replace(/\n$/g, "\n ");
|
29
|
-
if (existingNode instanceof Text) {
|
30
|
-
if (existingNode.textContent !== displayText) existingNode.textContent = displayText;
|
31
|
-
} else {
|
32
|
-
const textNode = document.createTextNode(displayText);
|
33
|
-
elementRef.value.insertBefore(textNode, existingNode);
|
34
|
-
}
|
35
|
-
} else {
|
36
|
-
if (existingNode instanceof HTMLElement && existingNode.tagName.toLowerCase() === item.tag.toLowerCase()) {
|
37
|
-
if (existingNode.textContent !== item.value) existingNode.textContent = item.value;
|
38
|
-
if (existingNode.className !== (item.class || "")) existingNode.className = item.class || "";
|
39
|
-
const contentEditable = item.edit ? "plaintext-only" : "false";
|
40
|
-
if (existingNode.getAttribute("contenteditable") !== contentEditable) {
|
41
|
-
existingNode.setAttribute("contenteditable", contentEditable);
|
42
|
-
}
|
43
|
-
} else {
|
44
|
-
const element = document.createElement(item.tag);
|
45
|
-
element.textContent = item.value;
|
46
|
-
element.setAttribute("contenteditable", item.edit ? "plaintext-only" : "false");
|
47
|
-
if (item.class) element.className = item.class;
|
48
|
-
elementRef.value.insertBefore(element, existingNode);
|
49
|
-
}
|
50
|
-
}
|
51
|
-
nodeIndex++;
|
52
|
-
}
|
53
|
-
while (elementRef.value.childNodes.length > props.textParts.length) {
|
54
|
-
const nodeToRemove = elementRef.value.childNodes[props.textParts.length];
|
55
|
-
elementRef.value.removeChild(nodeToRemove);
|
56
|
-
}
|
57
|
-
if (focused.value && !isSetCaretNext) setCaret(offsets.start, offsets.end !== offsets.start ? void 0 : offsets.end);
|
58
|
-
};
|
59
|
-
watch(() => props.textParts, updateTextParts, { immediate: true });
|
60
|
-
const updateTextValue = (value) => {
|
61
|
-
if (props.textParts || !elementRef.value) return;
|
62
|
-
if (elementRef.value.textContent !== value) {
|
63
|
-
const offsets = getCaret();
|
64
|
-
elementRef.value.textContent = value;
|
65
|
-
if (focused.value) setCaret(offsets.start, offsets.end !== offsets.start ? void 0 : offsets.end);
|
66
|
-
}
|
67
|
-
};
|
68
|
-
watch(() => props.value, updateTextValue, { immediate: true });
|
69
|
-
const textPartsToText = (parts) => {
|
70
|
-
return parts.map((part) => typeof part === "string" ? part : part.value).join("");
|
71
|
-
};
|
72
|
-
const getCurrentText = () => {
|
73
|
-
return props.textParts ? textPartsToText(props.textParts) : props.value;
|
74
|
-
};
|
75
|
-
const lineBreakEvents = ["insertParagraph", "insertLineBreak"];
|
76
|
-
const insertParagraph = (e) => {
|
77
|
-
if (lineBreakEvents.includes(e.inputType)) {
|
78
|
-
e.preventDefault();
|
79
|
-
insertPlain("\n");
|
80
|
-
}
|
81
|
-
};
|
82
|
-
const regexDifferentEnding = /\r\n?/g;
|
83
|
-
const regexSpaces = /\n \n/g;
|
84
|
-
const regexEnding = / +$/gm;
|
85
|
-
const normalizeText = (text) => {
|
86
|
-
return text.replace(regexDifferentEnding, "\n").replace(regexSpaces, "\n\n").replace(regexEnding, "");
|
87
|
-
};
|
88
|
-
const onInput = (e) => {
|
89
|
-
e.stopImmediatePropagation();
|
90
|
-
if (!(e.target instanceof HTMLDivElement)) return;
|
91
|
-
const rawText = e.target.textContent ?? "";
|
92
|
-
const text = normalizeText(rawText);
|
93
|
-
const currentText = getCurrentText();
|
94
|
-
if (text === currentText) return;
|
95
|
-
if (props.maxLength && typeof text === "string" && text.length > props.maxLength) {
|
96
|
-
e.preventDefault();
|
97
|
-
const substring = text.substring(0, props.maxLength);
|
98
|
-
if (!props.textParts) updateTextValue(substring);
|
99
|
-
else updateTextParts();
|
100
|
-
emit("update:model-value", substring);
|
101
|
-
} else {
|
102
|
-
emit("update:model-value", text);
|
103
|
-
}
|
104
|
-
};
|
105
|
-
const onPaste = async (e) => {
|
106
|
-
e.preventDefault();
|
107
|
-
navigator.clipboard.readText();
|
108
|
-
const text = (e.clipboardData?.getData("text/plain") || await navigator.clipboard.readText()).replace(/\r\n?/g, "\n");
|
109
|
-
insertPlain(text);
|
110
|
-
};
|
111
|
-
const insertPlain = (text) => {
|
112
|
-
const root = elementRef.value;
|
113
|
-
if (!root) return;
|
114
|
-
const { start, end } = getCaret();
|
115
|
-
const currentText = getCurrentText();
|
116
|
-
const next = (currentText ?? "").slice(0, start) + text + ((currentText ?? "").slice(end) || " ");
|
117
|
-
const caretAfter = start + text.length;
|
118
|
-
emit("update:model-value", props.maxLength && next.length > props.maxLength ? next.substring(0, props.maxLength) : next);
|
119
|
-
nextTick(() => setCaret(props.maxLength ? Math.min(caretAfter, props.maxLength) : caretAfter));
|
120
|
-
};
|
121
|
-
const getCaret = () => getCaretOffset(elementRef.value);
|
122
|
-
let isSetCaretNext = false;
|
123
|
-
const setCaret = (indexStart, indexEnd) => {
|
124
|
-
isSetCaretNext = false;
|
125
|
-
setCaretOffset(elementRef.value, indexStart, indexEnd);
|
126
|
-
};
|
127
|
-
const collapseList = [" ", "\n"];
|
128
|
-
let offsetsOld = null;
|
129
|
-
const wrapSelection = (value) => {
|
130
|
-
if (focused.value || !offsetsOld) offsetsOld = getCaret();
|
131
|
-
const offsets = offsetsOld;
|
132
|
-
const currentText = getCurrentText() ?? "";
|
133
|
-
let newText = "";
|
134
|
-
let newCursorStart = offsets.start;
|
135
|
-
let newCursorEnd = void 0;
|
136
|
-
switch (value.type) {
|
137
|
-
case WrapSelectionType.TOGGLE:
|
138
|
-
let startLen = value.start.length;
|
139
|
-
const endLen = value.end.length;
|
140
|
-
const textWithContext = currentText.slice(
|
141
|
-
Math.max(0, offsets.start - startLen),
|
142
|
-
Math.min(currentText.length, offsets.end + endLen)
|
143
|
-
);
|
144
|
-
if ((!value.start || textWithContext.startsWith(value.start)) && (!value.end || textWithContext.endsWith(value.end))) {
|
145
|
-
let expandedStart = Math.max(0, offsets.start - startLen);
|
146
|
-
let start = currentText.slice(0, expandedStart);
|
147
|
-
const middle = currentText.slice(offsets.start, offsets.end);
|
148
|
-
let end = currentText.slice(Math.min(currentText.length, offsets.end + endLen));
|
149
|
-
for (const item of collapseList) {
|
150
|
-
if (value.start.startsWith(item) && !start.endsWith(item) && !middle.startsWith(item)) {
|
151
|
-
start += item;
|
152
|
-
expandedStart += item.length;
|
153
|
-
}
|
154
|
-
if (value.end.endsWith(item) && !end.startsWith(item) && !middle.endsWith(item)) end = item + end;
|
155
|
-
}
|
156
|
-
newText = start + middle + end;
|
157
|
-
if (value.prepare) newText = value.prepare(newText, 0);
|
158
|
-
newCursorStart = expandedStart;
|
159
|
-
newCursorEnd = expandedStart + offsets.end - offsets.start;
|
160
|
-
} else {
|
161
|
-
if (!value.start || !value.end) {
|
162
|
-
const offset = value.start ? offsets.start : offsets.end;
|
163
|
-
let start = currentText.slice(0, offset);
|
164
|
-
let end = currentText.slice(offset);
|
165
|
-
for (const item of collapseList) {
|
166
|
-
if (value.start.startsWith(item) && start.endsWith(item)) {
|
167
|
-
start = start.slice(0, offsets.start - item.length);
|
168
|
-
startLen -= item.length;
|
169
|
-
}
|
170
|
-
if (value.end.endsWith(item) && end.startsWith(item)) end = end.slice(item.length);
|
171
|
-
}
|
172
|
-
newText = (value.prepare?.(start, 0) ?? start) + (value.start || value.end) + (value.prepare?.(end, offset) ?? end);
|
173
|
-
} else {
|
174
|
-
let start = currentText.slice(0, offsets.start);
|
175
|
-
const middle = currentText.slice(offsets.start, offsets.end);
|
176
|
-
let end = currentText.slice(offsets.end);
|
177
|
-
for (const item of collapseList) {
|
178
|
-
if (value.start.startsWith(item) && start.endsWith(item)) {
|
179
|
-
start = start.slice(0, offsets.start - item.length);
|
180
|
-
startLen -= item.length;
|
181
|
-
}
|
182
|
-
if (value.end.endsWith(item) && end.startsWith(item)) end = end.slice(item.length);
|
183
|
-
}
|
184
|
-
newText = (value.prepare?.(start, 0) ?? start) + value.start + (value.prepare?.(middle, offsets.start) ?? middle) + value.end + (value.prepare?.(end, offsets.end) ?? end);
|
185
|
-
}
|
186
|
-
newCursorStart = offsets.start + startLen;
|
187
|
-
if (offsets.start !== offsets.end) newCursorEnd = newCursorStart + offsets.end - offsets.start;
|
188
|
-
}
|
189
|
-
break;
|
190
|
-
case WrapSelectionType.LINE_PREFIX:
|
191
|
-
const lineStart = currentText.lastIndexOf("\n", offsets.start - 1) + 1;
|
192
|
-
const lineEnd = currentText.indexOf("\n", offsets.end);
|
193
|
-
const actualLineEnd = lineEnd === -1 ? currentText.length : lineEnd;
|
194
|
-
const linesText = currentText.slice(lineStart, actualLineEnd);
|
195
|
-
const beforeLines = currentText.slice(0, lineStart);
|
196
|
-
const afterLines = currentText.slice(actualLineEnd);
|
197
|
-
const lines = linesText.split("\n");
|
198
|
-
if (lines.length === 0) lines.push("");
|
199
|
-
const allLinesHavePrefix = value.detectPattern ? lines.every((line) => !line.trim() || value.detectPattern.test(line)) : value.linePrefix ? lines.every((line) => !line.trim() || line.startsWith(value.linePrefix)) : false;
|
200
|
-
if (allLinesHavePrefix) {
|
201
|
-
const cleanText = lines.map((line) => preserveIndentation(line, "")).join("\n");
|
202
|
-
newText = beforeLines + cleanText + afterLines;
|
203
|
-
newCursorStart = lineStart;
|
204
|
-
newCursorEnd = lineStart + cleanText.length;
|
205
|
-
} else {
|
206
|
-
const processedText = value.lineTransformAll ? value.lineTransformAll(lines) : value.lineTransform ? lines.map(value.lineTransform).join("\n") : lines.map((line) => line.trim() ? preserveIndentation(line, value.linePrefix) : line).join("\n");
|
207
|
-
newText = beforeLines + processedText + afterLines;
|
208
|
-
newCursorStart = lineStart;
|
209
|
-
newCursorEnd = lineStart + processedText.length;
|
210
|
-
}
|
211
|
-
break;
|
212
|
-
}
|
213
|
-
isSetCaretNext = true;
|
214
|
-
emit("update:model-value", newText);
|
215
|
-
requestAnimationFrame(() => setCaret(newCursorStart, newCursorEnd));
|
216
|
-
};
|
217
|
-
const focus = () => {
|
218
|
-
elementRef.value?.focus();
|
219
|
-
};
|
220
|
-
const blur = () => {
|
221
|
-
elementRef.value?.blur();
|
222
|
-
};
|
223
|
-
onMounted(() => {
|
224
|
-
updateTextValue(props.value);
|
225
|
-
updateTextParts();
|
226
|
-
});
|
227
|
-
__expose({
|
228
|
-
focus,
|
229
|
-
blur,
|
230
|
-
wrapSelection,
|
231
|
-
setCaret,
|
232
|
-
getCaret,
|
233
|
-
get offsetWidth() {
|
234
|
-
return elementRef.value?.offsetWidth ?? 0;
|
235
|
-
}
|
236
|
-
});
|
237
|
-
return (_ctx, _cache) => {
|
238
|
-
return openBlock(), createElementBlock("div", {
|
239
|
-
ref: "element",
|
240
|
-
contenteditable: "plaintext-only",
|
241
|
-
role: "textbox",
|
242
|
-
"aria-multiline": "true",
|
243
|
-
spellcheck: "false",
|
244
|
-
placeholder: _ctx.placeholder,
|
245
|
-
class: "relative whitespace-pre",
|
246
|
-
onInput,
|
247
|
-
onBeforeinput: _cache[0] || (_cache[0] = ($event) => insertParagraph($event)),
|
248
|
-
onPaste,
|
249
|
-
onKeydown: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("keydown", $event)),
|
250
|
-
onFocus: _cache[2] || (_cache[2] = ($event) => {
|
251
|
-
_ctx.$emit("focus", $event);
|
252
|
-
focused.value = true;
|
253
|
-
}),
|
254
|
-
onBlur: _cache[3] || (_cache[3] = ($event) => {
|
255
|
-
_ctx.$emit("blur", $event);
|
256
|
-
focused.value = false;
|
257
|
-
})
|
258
|
-
}, null, 40, _hoisted_1);
|
259
|
-
};
|
260
|
-
}
|
261
|
-
});
|
262
4
|
|
263
5
|
export { _sfc_main as default };
|
@@ -3,5 +3,7 @@ export type CaretOffset = {
|
|
3
3
|
end: number;
|
4
4
|
};
|
5
5
|
export declare const setCaretOffset: (parent: Element | null | undefined, indexStart: number, indexEnd: number | undefined) => void;
|
6
|
-
export declare const getCaretOffset: (parent: Element | null | undefined) => CaretOffset
|
6
|
+
export declare const getCaretOffset: (parent: Element | null | undefined) => CaretOffset & {
|
7
|
+
trail: number;
|
8
|
+
};
|
7
9
|
//# sourceMappingURL=utils.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/components/Input/models/utils.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,WAAW,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAAA;AAEtD,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS,SAelH,CAAA;
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/components/Input/models/utils.ts"],"names":[],"mappings":"AAwBA,MAAM,MAAM,WAAW,GAAG;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,CAAA;AAEtD,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS,SAelH,CAAA;AA6BD,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,GAAG,IAAI,GAAG,SAAS,KAAG,WAAW,GAAG;IAAC,KAAK,EAAE,MAAM,CAAA;CAU/F,CAAA"}
|
@@ -29,28 +29,34 @@ const setCaretOffset = (parent, indexStart, indexEnd) => {
|
|
29
29
|
selection?.removeAllRanges();
|
30
30
|
selection?.addRange(range);
|
31
31
|
};
|
32
|
+
const endSpacesRegex = /\s+$/;
|
32
33
|
const getOffsetFromNode = (parent, targetNode, targetOffset, isStart) => {
|
33
34
|
if (!parent.firstChild) parent.appendChild(document.createTextNode(""));
|
34
35
|
const walker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT, null);
|
35
36
|
let node, offset = 0;
|
36
37
|
while (node = walker.nextNode()) {
|
37
38
|
if (node.parentElement?.contentEditable === "false") {
|
38
|
-
if (isStart && node === targetNode) return offset;
|
39
|
+
if (isStart && node === targetNode) return { offset, trail: 0 };
|
39
40
|
} else if (node === targetNode) {
|
40
|
-
|
41
|
+
const text = node.textContent;
|
42
|
+
if (text?.[targetOffset] === "\n") {
|
43
|
+
const trail = text.slice(0, targetOffset).match(endSpacesRegex)?.[0]?.length ?? 0;
|
44
|
+
return { offset: offset + targetOffset - trail, trail };
|
45
|
+
}
|
46
|
+
return { offset: offset + targetOffset, trail: 0 };
|
41
47
|
}
|
42
48
|
offset += node.nodeValue?.length ?? 0;
|
43
49
|
}
|
44
|
-
return offset;
|
50
|
+
return { offset, trail: 0 };
|
45
51
|
};
|
46
52
|
const getCaretOffset = (parent) => {
|
47
|
-
if (!parent) return { start: 0, end: 0 };
|
53
|
+
if (!parent) return { start: 0, end: 0, trail: 0 };
|
48
54
|
const selection = window.getSelection();
|
49
|
-
if (!selection || selection.rangeCount === 0) return { start: 0, end: 0 };
|
55
|
+
if (!selection || selection.rangeCount === 0) return { start: 0, end: 0, trail: 0 };
|
50
56
|
const range = selection.getRangeAt(0);
|
51
57
|
const start = getOffsetFromNode(parent, range.startContainer, range.startOffset, true);
|
52
58
|
const end = range.startOffset !== range.endOffset ? getOffsetFromNode(parent, range.endContainer, range.endOffset, false) : start;
|
53
|
-
return { start, end };
|
59
|
+
return { start: start.offset, end: end.offset, trail: end.trail };
|
54
60
|
};
|
55
61
|
|
56
62
|
export { getCaretOffset, setCaretOffset };
|