vuewrite 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -11,26 +11,33 @@ You can watch the demo [here](https://vuewrite.easix.ru)
11
11
 
12
12
  ```vue
13
13
  <template>
14
- <TextEditor
15
- :store="textEditorStore"
16
- class="text-editor"
17
- :decorator="decorator"
18
- @keydown="onKeyDown"
19
- />
14
+ <TextEditor
15
+ ref="textEditorRef"
16
+ v-model="modelValue"
17
+ single
18
+ class="text-editor"
19
+ :decorator="decorator"
20
+ @keydown="onKeyDown"
21
+ />
20
22
  </template>
21
23
 
22
24
  <script lang="ts">
25
+ import { TextEditor, TextEditorRef } from 'vuewrite'
26
+
27
+ const textEditorRef = shallowRef<TextEditorRef>()
28
+ const modelValue = ref("")
23
29
 
24
30
  const onKeyDown = (e: KeyboardEvent) => {
31
+ if (!textEditorRef.value) return
25
32
  if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
26
33
  if (e.code === "KeyB") {
27
- textEditorStore.toggleStyle("bold")
34
+ textEditorRef.value.toggleStyle("bold")
28
35
  }
29
36
  if (e.code === "KeyI") {
30
- textEditorStore.toggleStyle("italic")
37
+ textEditorRef.value.toggleStyle("italic")
31
38
  }
32
39
  if (e.code === "KeyU") {
33
- textEditorStore.toggleStyle("underline")
40
+ textEditorRef.value.toggleStyle("underline")
34
41
  }
35
42
  }
36
43
  }
@@ -1,9 +1,11 @@
1
1
  import { ComponentOptionsMixin } from 'vue';
2
+ import { ComputedRef } from 'vue';
2
3
  import { DefineComponent } from 'vue';
3
4
  import { ExtractPropTypes } from 'vue';
4
5
  import { HTMLAttributes } from 'vue';
5
6
  import { PropType } from 'vue';
6
7
  import { PublicProps } from 'vue';
8
+ import { Ref } from 'vue';
7
9
 
8
10
  declare type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
9
11
 
@@ -39,24 +41,64 @@ export declare type Style = {
39
41
  };
40
42
 
41
43
  export declare const TextEditor: __VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<{
42
- store: TextEditorStore;
43
- placeholder?: string | undefined;
44
44
  decorator?: Decorator | undefined;
45
+ single?: boolean | undefined;
46
+ modelValue?: string | string[] | undefined;
47
+ styles?: Style[] | Style[][] | undefined;
48
+ autofocus?: boolean | undefined;
45
49
  }>, {
46
- store: TextEditorStore;
50
+ currentStyles: ComputedRef<Map<string, Style>>;
51
+ currentBlock: ComputedRef< {
52
+ id: string;
53
+ text: string;
54
+ type?: string | undefined;
55
+ styles: {
56
+ start: number;
57
+ end: number;
58
+ style: string;
59
+ meta?: any;
60
+ }[];
61
+ } | null>;
62
+ isCollapsed: ComputedRef<boolean>;
63
+ selection: {
64
+ anchor: {
65
+ blockId: string;
66
+ offset: number;
67
+ };
68
+ focus: {
69
+ blockId: string;
70
+ offset: number;
71
+ };
72
+ };
73
+ isFocused: Ref<boolean>;
74
+ toggleStyle: (style: string) => void;
75
+ applyStyle: (_style: string, meta?: any) => void;
76
+ removeStyle: (_style: string) => void;
77
+ insertText: (data: string) => void;
78
+ selectAll: () => void;
47
79
  }, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
48
80
  keydown: (...args: any[]) => void;
81
+ "update:modelValue": (...args: any[]) => void;
82
+ "update:styles": (...args: any[]) => void;
49
83
  }, string, PublicProps, Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
50
- store: TextEditorStore;
51
- placeholder?: string | undefined;
52
84
  decorator?: Decorator | undefined;
85
+ single?: boolean | undefined;
86
+ modelValue?: string | string[] | undefined;
87
+ styles?: Style[] | Style[][] | undefined;
88
+ autofocus?: boolean | undefined;
53
89
  }>>> & {
54
90
  onKeydown?: ((...args: any[]) => any) | undefined;
91
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
92
+ "onUpdate:styles"?: ((...args: any[]) => any) | undefined;
55
93
  }, {}, {}>, {
56
94
  placeholder?(_: {}): any;
57
95
  }>;
58
96
 
59
- export declare class TextEditorStore {
97
+ export declare type TextEditorRef = Pick<TextEditorStore, "currentStyles" | "currentBlock" | "isCollapsed" | "selection" | "toggleStyle" | "applyStyle" | "removeStyle" | "insertText" | "selectAll"> & {
98
+ isFocused: boolean;
99
+ };
100
+
101
+ declare class TextEditorStore {
60
102
  blocks: {
61
103
  id: string;
62
104
  text: string;
@@ -78,9 +120,20 @@ export declare class TextEditorStore {
78
120
  offset: number;
79
121
  };
80
122
  };
81
- private _isCollapsed;
123
+ _isCollapsed: ComputedRef<boolean>;
82
124
  get isCollapsed(): boolean;
83
- private _currentBlock;
125
+ isFocused: Ref<boolean>;
126
+ _currentBlock: ComputedRef< {
127
+ id: string;
128
+ text: string;
129
+ type?: string | undefined;
130
+ styles: {
131
+ start: number;
132
+ end: number;
133
+ style: string;
134
+ meta?: any;
135
+ }[];
136
+ } | null>;
84
137
  get currentBlock(): {
85
138
  id: string;
86
139
  text: string;
@@ -105,13 +158,14 @@ export declare class TextEditorStore {
105
158
  }, number, number];
106
159
  private moveStyles;
107
160
  deleteSelected(): void;
108
- private _currentStyles;
161
+ _currentStyles: ComputedRef<Map<string, Style>>;
109
162
  get currentStyles(): Map<string, Style>;
110
163
  applyStyle(_style: string, meta?: any): void;
111
164
  removeStyleAt(block: Block, _start: number, _end: number, _style?: string): void;
112
165
  removeStyle(_style: string): void;
113
166
  removeAllStyles(): void;
114
167
  toggleStyle(style: string): void;
168
+ selectAll(): void;
115
169
  }
116
170
 
117
171
  export { }
package/dist/vuewrite.js CHANGED
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => {
4
4
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  return value;
6
6
  };
7
- import { getCurrentScope, onScopeDispose, unref, watch, reactive, computed, defineComponent, getCurrentInstance, openBlock, createBlock, resolveDynamicComponent, createElementBlock, normalizeProps, mergeProps, h, nextTick, useSlots, ref, Fragment, renderList, renderSlot, createCommentVNode } from "vue";
7
+ import { getCurrentScope, onScopeDispose, unref, watch, reactive, computed, ref, defineComponent, getCurrentInstance, openBlock, createBlock, resolveDynamicComponent, createElementBlock, normalizeProps, mergeProps, h, nextTick, useSlots, onMounted, Fragment, renderList, renderSlot, createCommentVNode } from "vue";
8
8
  function tryOnScopeDispose(fn) {
9
9
  if (getCurrentScope()) {
10
10
  onScopeDispose(fn);
@@ -150,6 +150,7 @@ class TextEditorStore {
150
150
  __publicField(this, "_isCollapsed", computed(() => {
151
151
  return this.selection.anchor.blockId === this.selection.focus.blockId && this.selection.anchor.offset === this.selection.focus.offset;
152
152
  }));
153
+ __publicField(this, "isFocused", ref(false));
153
154
  __publicField(this, "_currentBlock", computed(() => {
154
155
  if (this.selection.anchor.blockId !== this.selection.focus.blockId)
155
156
  return null;
@@ -158,6 +159,8 @@ class TextEditorStore {
158
159
  __publicField(this, "_currentStyles", computed(() => {
159
160
  const [start, end, startIndex, endIndex] = this.startAndEnd;
160
161
  const styles = /* @__PURE__ */ new Map();
162
+ if (startIndex < 0)
163
+ return styles;
161
164
  for (let i = startIndex; i <= endIndex; i++) {
162
165
  for (let style of this.blocks[i].styles) {
163
166
  if (i === startIndex && start.offset < style.start)
@@ -372,6 +375,11 @@ class TextEditorStore {
372
375
  this.applyStyle(style);
373
376
  }
374
377
  }
378
+ selectAll() {
379
+ this.selection.anchor = { blockId: this.blocks[0].id, offset: 0 };
380
+ const lastBlock = this.blocks[this.blocks.length - 1];
381
+ this.selection.focus = { blockId: lastBlock.id, offset: lastBlock.text.length };
382
+ }
375
383
  }
376
384
  let uidCounter = 0;
377
385
  const uid = () => (uidCounter++).toString();
@@ -516,24 +524,65 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
516
524
  const _sfc_main = /* @__PURE__ */ defineComponent({
517
525
  __name: "TextEditor",
518
526
  props: {
519
- store: {},
520
- placeholder: {},
521
- decorator: { type: Function }
527
+ decorator: { type: Function },
528
+ single: { type: Boolean },
529
+ modelValue: {},
530
+ styles: {},
531
+ autofocus: { type: Boolean }
522
532
  },
523
- emits: ["keydown"],
533
+ emits: ["keydown", "update:modelValue", "update:styles"],
524
534
  setup(__props, { expose: __expose, emit: __emit }) {
525
535
  const props = __props;
526
536
  const emit = __emit;
527
537
  const slots = useSlots();
528
538
  const textEditorRef = ref();
529
- const store = props.store ?? new TextEditorStore();
539
+ const store = new TextEditorStore();
540
+ let modelValue = "";
541
+ watch(() => props.modelValue, (newValue) => {
542
+ if (newValue === void 0 || newValue === null || newValue === modelValue)
543
+ return;
544
+ if (!Array.isArray(newValue)) {
545
+ store.blocks[0].text = newValue;
546
+ return;
547
+ } else {
548
+ for (let i = store.blocks.length; i < newValue.length; i++) {
549
+ store.blocks.push({ id: uid(), text: "", styles: [] });
550
+ }
551
+ store.blocks.length = newValue.length;
552
+ for (let i = 0; i < newValue.length; i++) {
553
+ store.blocks[i].text = newValue[i];
554
+ }
555
+ }
556
+ }, { immediate: true });
557
+ let styles;
558
+ watch(() => props.styles, (newStyles) => {
559
+ if (!newStyles || newStyles.length === 0 || newStyles === styles)
560
+ return;
561
+ for (let i = 0; i < store.blocks.length; i++) {
562
+ if (newStyles.length <= i)
563
+ break;
564
+ const style = props.single ? newStyles : newStyles[i];
565
+ store.blocks[i].styles = style;
566
+ }
567
+ }, { immediate: true });
568
+ watch(() => store.blocks, () => {
569
+ if (props.single) {
570
+ modelValue = store.blocks[0].text;
571
+ styles = store.blocks[0].styles;
572
+ } else {
573
+ modelValue = store.blocks.map((item) => item.text);
574
+ styles = store.blocks.map((item) => item.styles);
575
+ }
576
+ emit("update:modelValue", modelValue);
577
+ emit("update:styles", styles);
578
+ }, { deep: true });
530
579
  const onKeyDown = (e) => {
531
580
  emit("keydown", e);
532
581
  if (e.defaultPrevented)
533
582
  return;
534
583
  if (e.code === "Enter") {
535
584
  e.preventDefault();
536
- if (e.shiftKey) {
585
+ if (e.shiftKey || props.single) {
537
586
  store.insertText("\n");
538
587
  } else {
539
588
  store.addNewLine();
@@ -563,7 +612,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
563
612
  const offset = anchor === sel.focusNode ? 0 : calcOffsetToNode(focus, sel.focusNode) + sel.focusOffset;
564
613
  store.selection.focus = { blockId: focus.getAttribute("data-block-id"), offset };
565
614
  }
566
- cachedSelection = JSON.parse(JSON.stringify(store.selection));
615
+ if (store.isFocused.value !== !!focus || !!anchor) {
616
+ store.isFocused.value = !!focus || !!anchor;
617
+ }
618
+ if (!anchor && !focus)
619
+ cachedSelection = JSON.parse(JSON.stringify(store.selection));
567
620
  });
568
621
  let postRendered = false;
569
622
  const onPostRender = () => {
@@ -577,6 +630,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
577
630
  });
578
631
  };
579
632
  const applySelection = () => {
633
+ if (!store.isFocused.value) {
634
+ cachedSelection = JSON.parse(JSON.stringify(store.selection));
635
+ return;
636
+ }
580
637
  if (isEqual(store.selection, cachedSelection))
581
638
  return;
582
639
  const nativeSelection = window.getSelection();
@@ -599,8 +656,24 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
599
656
  cachedSelection = JSON.parse(JSON.stringify(store.selection));
600
657
  };
601
658
  watch(() => store.selection, applySelection, { deep: true, flush: "post" });
659
+ onMounted(() => {
660
+ var _a;
661
+ if (props.autofocus) {
662
+ (_a = textEditorRef.value) == null ? void 0 : _a.focus();
663
+ applySelection();
664
+ }
665
+ });
602
666
  __expose({
603
- store
667
+ currentStyles: store._currentStyles,
668
+ currentBlock: store._currentBlock,
669
+ isCollapsed: store._isCollapsed,
670
+ selection: store.selection,
671
+ isFocused: store.isFocused,
672
+ toggleStyle: store.toggleStyle.bind(store),
673
+ applyStyle: store.applyStyle.bind(store),
674
+ removeStyle: store.removeStyle.bind(store),
675
+ insertText: store.insertText.bind(store),
676
+ selectAll: store.selectAll.bind(store)
604
677
  });
605
678
  return (_ctx, _cache) => {
606
679
  return openBlock(), createElementBlock("div", {
@@ -616,7 +689,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
616
689
  key: block.id,
617
690
  block,
618
691
  slots: unref(slots),
619
- decorator: _ctx.decorator,
692
+ decorator: props.decorator,
620
693
  onPostrender: onPostRender
621
694
  }, null, 8, ["block", "slots", "decorator"]);
622
695
  }), 128)),
@@ -626,6 +699,5 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
626
699
  }
627
700
  });
628
701
  export {
629
- _sfc_main as TextEditor,
630
- TextEditorStore
702
+ _sfc_main as TextEditor
631
703
  };
package/package.json CHANGED
@@ -2,8 +2,9 @@
2
2
  "name": "vuewrite",
3
3
  "description": "Rich Text Editor based on Vue3 reactivity",
4
4
  "private": false,
5
- "version": "0.0.1",
5
+ "version": "0.0.3",
6
6
  "type": "module",
7
+ "license": "MIT",
7
8
  "author": "den59k",
8
9
  "repository": {
9
10
  "type": "git",