vuewrite 0.0.6 → 0.0.8

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.
@@ -29,6 +29,7 @@ export declare type Block = {
29
29
  text: string;
30
30
  type?: string;
31
31
  styles: Style[];
32
+ editable?: boolean;
32
33
  };
33
34
 
34
35
  export declare type Decorator = (style: Style) => HTMLAttributes | undefined;
@@ -63,6 +64,7 @@ end: number;
63
64
  style: string;
64
65
  meta?: any;
65
66
  }[];
67
+ editable?: boolean | undefined;
66
68
  } | null>;
67
69
  isCollapsed: ComputedRef<boolean>;
68
70
  selection: {
@@ -80,7 +82,11 @@ toggleStyle: (style: string) => void;
80
82
  applyStyle: (_style: string, meta?: any) => void;
81
83
  removeStyle: (_style: string) => void;
82
84
  insertText: (data: string) => void;
85
+ insertBlock: (blockData: Partial<Block>) => void;
86
+ addNewLine: () => void;
87
+ removeNewLine: () => void;
83
88
  selectAll: () => void;
89
+ getClientRects: (selection: TextEditorSelection) => DOMRectList;
84
90
  }, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
85
91
  keydown: (...args: any[]) => void;
86
92
  "update:modelValue": (...args: any[]) => void;
@@ -104,8 +110,20 @@ onKeydown?: ((...args: any[]) => any) | undefined;
104
110
  placeholder?(_: {}): any;
105
111
  }>;
106
112
 
107
- export declare type TextEditorRef = Pick<TextEditorStore, "currentStyles" | "currentBlock" | "isCollapsed" | "selection" | "toggleStyle" | "applyStyle" | "removeStyle" | "insertText" | "selectAll"> & {
113
+ export declare type TextEditorRef = Pick<TextEditorStore, "currentStyles" | "currentBlock" | "isCollapsed" | "selection" | "toggleStyle" | "applyStyle" | "removeStyle" | "insertText" | "insertBlock" | "addNewLine" | "removeNewLine" | "selectAll"> & {
108
114
  isFocused: boolean;
115
+ getClientRects: (selection: TextEditorSelection) => DOMRectList;
116
+ };
117
+
118
+ declare type TextEditorSelection = {
119
+ anchor: {
120
+ blockId: string;
121
+ offset: number;
122
+ };
123
+ focus: {
124
+ blockId: string;
125
+ offset: number;
126
+ };
109
127
  };
110
128
 
111
129
  declare class TextEditorStore {
@@ -119,6 +137,7 @@ declare class TextEditorStore {
119
137
  style: string;
120
138
  meta?: any;
121
139
  }[];
140
+ editable?: boolean | undefined;
122
141
  }[];
123
142
  selection: {
124
143
  anchor: {
@@ -143,6 +162,7 @@ declare class TextEditorStore {
143
162
  style: string;
144
163
  meta?: any;
145
164
  }[];
165
+ editable?: boolean | undefined;
146
166
  } | null>;
147
167
  get currentBlock(): {
148
168
  id: string;
@@ -154,13 +174,17 @@ declare class TextEditorStore {
154
174
  style: string;
155
175
  meta?: any;
156
176
  }[];
177
+ editable?: boolean | undefined;
157
178
  } | null;
158
179
  _selectedText: ComputedRef<string>;
159
180
  get selectedText(): string;
160
181
  moveOffset(newOffset: number): void;
182
+ concatBlocks(start: Block, end: Block): void;
183
+ removeNewLine(): void;
161
184
  onInput(_e: Event): void;
162
185
  addNewLine(): void;
163
186
  insertText(data: string): void;
187
+ insertBlock(blockData: Partial<Block>): void;
164
188
  get startAndEnd(): [{
165
189
  blockId: string;
166
190
  offset: number;
package/dist/vuewrite.js CHANGED
@@ -202,8 +202,33 @@ class TextEditorStore {
202
202
  this.selection.anchor.offset = newOffset;
203
203
  this.selection.focus.offset = newOffset;
204
204
  }
205
+ concatBlocks(start, end) {
206
+ for (let style of end.styles) {
207
+ style.start += start.text.length;
208
+ style.end += start.text.length;
209
+ start.styles.push(style);
210
+ }
211
+ start.text = start.text + end.text;
212
+ }
213
+ removeNewLine() {
214
+ const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
215
+ if (blockIndex < 1)
216
+ return;
217
+ if (this.blocks[blockIndex - 1].editable === false) {
218
+ this.blocks.splice(blockIndex - 1, 1);
219
+ } else {
220
+ this.concatBlocks(this.blocks[blockIndex - 1], this.blocks[blockIndex]);
221
+ this.blocks.splice(blockIndex, 1);
222
+ }
223
+ this.selection.anchor.blockId = this.blocks[blockIndex - 1].id;
224
+ this.selection.focus.blockId = this.blocks[blockIndex - 1].id;
225
+ this.selection.anchor.offset = this.blocks[blockIndex - 1].text.length;
226
+ this.selection.focus.offset = this.blocks[blockIndex - 1].text.length;
227
+ }
205
228
  onInput(_e) {
206
229
  const ev = _e;
230
+ if (ev.defaultPrevented)
231
+ return;
207
232
  ev.preventDefault();
208
233
  const collapsed = this.isCollapsed;
209
234
  if (!collapsed)
@@ -212,15 +237,12 @@ class TextEditorStore {
212
237
  if (!block)
213
238
  return;
214
239
  if ((ev.inputType === "deleteContentBackward" || ev.inputType === "deleteContentForward") && collapsed) {
215
- const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
216
240
  if (ev.inputType === "deleteContentBackward") {
217
241
  if (this.selection.anchor.offset === 0) {
218
- if (blockIndex > 0) {
219
- this.selection.anchor.blockId = this.blocks[blockIndex - 1].id;
220
- this.selection.focus.blockId = this.blocks[blockIndex - 1].id;
221
- this.selection.anchor.offset = this.blocks[blockIndex - 1].text.length;
222
- this.selection.focus.offset = this.blocks[blockIndex - 1].text.length;
223
- this.blocks.splice(blockIndex, 1);
242
+ if (block.type) {
243
+ delete block.type;
244
+ } else {
245
+ this.removeNewLine();
224
246
  }
225
247
  } else {
226
248
  const offset = Math.max(0, this.selection.focus.offset - 1);
@@ -229,8 +251,13 @@ class TextEditorStore {
229
251
  }
230
252
  }
231
253
  if (ev.inputType === "deleteContentForward") {
254
+ const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
232
255
  if (this.selection.anchor.offset === this.blocks[blockIndex].text.length) {
233
- this.blocks.splice(blockIndex + 1, 1);
256
+ const nextBlock = this.blocks[blockIndex + 1];
257
+ if (nextBlock) {
258
+ this.blocks.splice(blockIndex + 1, 1);
259
+ this.concatBlocks(this.blocks[blockIndex - 1], this.blocks[blockIndex]);
260
+ }
234
261
  } else {
235
262
  block.text = block.text.slice(0, this.selection.focus.offset) + block.text.slice(this.selection.focus.offset + 1);
236
263
  this.moveStyles(block, this.selection.focus.offset + 1, 1);
@@ -260,6 +287,23 @@ class TextEditorStore {
260
287
  block.text = block.text.slice(0, this.selection.focus.offset) + data + block.text.slice(this.selection.focus.offset);
261
288
  this.moveOffset(clamp(this.selection.focus.offset + data.length, 0, block.text.length));
262
289
  }
290
+ insertBlock(blockData) {
291
+ if (!this.currentBlock)
292
+ return;
293
+ this.deleteSelected();
294
+ if (this.currentBlock.text !== "") {
295
+ this.addNewLine();
296
+ }
297
+ if (this.currentBlock.text !== "") {
298
+ const selection = JSON.parse(JSON.stringify(this.selection));
299
+ this.addNewLine();
300
+ Object.assign(this.selection, selection);
301
+ }
302
+ Object.assign(this.currentBlock, blockData);
303
+ if (blockData.editable === false && this.currentBlock === this.blocks[this.blocks.length - 1]) {
304
+ this.addNewLine();
305
+ }
306
+ }
263
307
  get startAndEnd() {
264
308
  const a = this.blocks.findIndex((block) => block.id === this.selection.anchor.blockId);
265
309
  const f = this.blocks.findIndex((block) => block.id === this.selection.focus.blockId);
@@ -487,6 +531,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
487
531
  };
488
532
  const content = () => {
489
533
  const block = props.block;
534
+ if (block.editable === false)
535
+ return null;
490
536
  if (block.text.length === 0) {
491
537
  cleanTree(1);
492
538
  return [h("br")];
@@ -609,11 +655,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
609
655
  store.addNewLine();
610
656
  }
611
657
  }
612
- if (e.code === "Backspace") {
613
- if (store.isCollapsed && store.currentBlock === store.blocks[0] && store.selection.anchor.offset === 0) {
614
- e.preventDefault();
615
- }
616
- }
617
658
  if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
618
659
  if (e.code === "KeyB" || e.code === "KeyI" || e.code === "KeyU") {
619
660
  e.preventDefault();
@@ -623,21 +664,22 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
623
664
  let cachedSelection = {};
624
665
  useEventListener(document, "selectionchange", () => {
625
666
  const sel = window.getSelection();
626
- const anchor = findParent(sel.anchorNode, (el) => el.hasAttribute("data-vw-block-id"));
667
+ const anchor = findParent(sel.anchorNode, (el) => el.hasAttribute("data-vw-block-id") && el.parentElement === textEditorRef.value);
627
668
  if (anchor) {
628
669
  const offset = anchor === sel.anchorNode ? 0 : calcOffsetToNode(anchor, sel.anchorNode) + sel.anchorOffset;
629
670
  store.selection.anchor = { blockId: anchor.getAttribute("data-vw-block-id"), offset };
630
671
  }
631
- const focus = findParent(sel.focusNode, (el) => el.hasAttribute("data-vw-block-id"));
672
+ const focus = findParent(sel.focusNode, (el) => el.hasAttribute("data-vw-block-id") && el.parentElement === textEditorRef.value);
632
673
  if (focus) {
633
674
  const offset = anchor === sel.focusNode ? 0 : calcOffsetToNode(focus, sel.focusNode) + sel.focusOffset;
634
675
  store.selection.focus = { blockId: focus.getAttribute("data-vw-block-id"), offset };
635
676
  }
636
- if (store.isFocused.value !== !!focus || !!anchor) {
677
+ if (store.isFocused.value !== (!!focus || !!anchor)) {
637
678
  store.isFocused.value = !!focus || !!anchor;
638
679
  }
639
- if (!anchor && !focus)
680
+ if (!anchor && !focus) {
640
681
  cachedSelection = JSON.parse(JSON.stringify(store.selection));
682
+ }
641
683
  });
642
684
  let postRendered = false;
643
685
  const onPostRender = () => {
@@ -650,6 +692,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
650
692
  postRendered = false;
651
693
  });
652
694
  };
695
+ const getNode = (blockId) => {
696
+ for (let item of textEditorRef.value.children) {
697
+ if (item.getAttribute("data-vw-block-id") === blockId) {
698
+ return item;
699
+ }
700
+ }
701
+ return null;
702
+ };
653
703
  const applySelection = () => {
654
704
  if (!store.isFocused.value) {
655
705
  cachedSelection = JSON.parse(JSON.stringify(store.selection));
@@ -657,17 +707,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
657
707
  }
658
708
  if (isEqual(store.selection, cachedSelection))
659
709
  return;
660
- const nativeSelection = window.getSelection();
661
- let anchor = null;
662
- let focus = null;
663
- for (let item of textEditorRef.value.children) {
664
- if (item.getAttribute("data-vw-block-id") === store.selection.anchor.blockId) {
665
- anchor = item;
666
- }
667
- if (item.getAttribute("data-vw-block-id") === store.selection.focus.blockId) {
668
- focus = item;
669
- }
710
+ if (store.selection.anchor.blockId === store.selection.focus.blockId && store.currentBlock && store.currentBlock.editable === false) {
711
+ return;
670
712
  }
713
+ const anchor = getNode(store.selection.anchor.blockId);
714
+ const focus = getNode(store.selection.focus.blockId);
715
+ const nativeSelection = window.getSelection();
671
716
  if (anchor && focus) {
672
717
  nativeSelection.setBaseAndExtent(
673
718
  ...calcNodeByOffset(anchor, store.selection.anchor.offset),
@@ -705,6 +750,17 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
705
750
  return;
706
751
  store.insertText(text);
707
752
  };
753
+ const getClientRects = (selection) => {
754
+ const anchor = getNode(selection.anchor.blockId);
755
+ const focus = getNode(selection.focus.blockId);
756
+ if (anchor && focus) {
757
+ const range = new Range();
758
+ range.setStart(...calcNodeByOffset(anchor, selection.anchor.offset));
759
+ range.setEnd(...calcNodeByOffset(focus, selection.focus.offset));
760
+ return range.getClientRects();
761
+ }
762
+ return new DOMRectList();
763
+ };
708
764
  __expose({
709
765
  currentStyles: store._currentStyles,
710
766
  currentBlock: store._currentBlock,
@@ -715,14 +771,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
715
771
  applyStyle: store.applyStyle.bind(store),
716
772
  removeStyle: store.removeStyle.bind(store),
717
773
  insertText: store.insertText.bind(store),
718
- selectAll: store.selectAll.bind(store)
774
+ insertBlock: store.insertBlock.bind(store),
775
+ addNewLine: store.addNewLine.bind(store),
776
+ removeNewLine: store.removeNewLine.bind(store),
777
+ selectAll: store.selectAll.bind(store),
778
+ getClientRects
719
779
  });
720
780
  return (_ctx, _cache) => {
721
781
  return openBlock(), createElementBlock("div", {
722
782
  ref_key: "textEditorRef",
723
783
  ref: textEditorRef,
724
784
  contenteditable: "",
725
- onInput: _cache[0] || (_cache[0] = //@ts-ignore
785
+ onBeforeinput: _cache[0] || (_cache[0] = //@ts-ignore
726
786
  (...args) => unref(store).onInput && unref(store).onInput(...args)),
727
787
  onKeydown: onKeyDown,
728
788
  onCopy,
@@ -738,7 +798,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
738
798
  onPostrender: onPostRender
739
799
  }, null, 8, ["block", "slots", "decorator"]);
740
800
  }), 128)),
741
- unref(store).blocks.length === 1 && unref(store).blocks[0].text === "" ? renderSlot(_ctx.$slots, "placeholder", { key: 0 }) : createCommentVNode("", true)
801
+ unref(store).blocks.length === 1 && unref(store).blocks[0].text === "" && !unref(store).blocks[0].type ? renderSlot(_ctx.$slots, "placeholder", { key: 0 }) : createCommentVNode("", true)
742
802
  ], 544);
743
803
  };
744
804
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "vuewrite",
3
3
  "description": "Rich Text Editor based on Vue3 reactivity",
4
4
  "private": false,
5
- "version": "0.0.6",
5
+ "version": "0.0.8",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "author": "den59k",