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.
@@ -1 +1 @@
1
- {"version":3,"file":"WInput.vue.d.ts","sourceRoot":"","sources":["../../../../src/components/Input/WInput.vue"],"names":[],"mappings":"AAkPA;AAoiBA,OAAO,KAAK,EAAC,UAAU,EAAE,aAAa,EAAC,MAAM,SAAS,CAAA;AAatD,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;qBAvmB7B,IAAI;oBAOL,IAAI;+BAkCS,aAAa;0BA1KlB,MAAM,QAAQ,MAAM,KAAG,IAAI;wBAN/B,WAAW;;;oBAyBf,IAAI;oBAgBJ,IAAI;MAqsBgD,GAAG,IAAI;WACpE,GAAG;;uBAhEgB,GAAG;0BACA,GAAG;wBACJ,GAAG;;;YAEH,GAAG;;mCAngBF,aAAa;YAkgBb,GAAG;;;YAEJ,GAAG;uBACJ,GAAG;wBACF,GAAG;uBACJ,GAAG;uBACH,GAAG;wBACF,GAAG;;;YAptB1B,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;;EAiwBhB,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"}
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/InputActions.vue.js';
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 ? unref(ContentEditable) : "input"), {
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$2, {
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;AAyUA,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;CAClC,CAAC;;;;2BAsK4B,aAAa,KAAG,IAAI;2BATpB,MAAM,aAAa,MAAM;;;;;;;;;;;;;;;;AAsNvD,wBAUG"}
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 _sfc_main from './ContentEditable.vue2.js';
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 { 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';
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;AAqBD,eAAO,MAAM,cAAc,GAAI,QAAQ,OAAO,GAAG,IAAI,GAAG,SAAS,KAAG,WAUnE,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
- return offset + targetOffset;
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 };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/rsmple/eco-vue-js.git"
6
6
  },
7
- "version": "0.11.18",
7
+ "version": "0.11.19",
8
8
  "dependencies": {
9
9
  "@stylistic/eslint-plugin": "5.2.3",
10
10
  "@tanstack/eslint-plugin-query": "5.83.1",