@flexiui/svelte-rich-text 0.0.61 → 0.0.63

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
@@ -63,6 +63,18 @@ npm install @flexiui/svelte-rich-text
63
63
  <RichText showToolbar={false} />
64
64
  ```
65
65
 
66
+ ### With Content Wrapper Element
67
+
68
+ ```svelte
69
+ <RichText contentWrapperAs="h1" />
70
+ ```
71
+
72
+ ### Inline Node Mode
73
+
74
+ ```svelte
75
+ <RichText inlineNodeMode={true} />
76
+ ```
77
+
66
78
  ### With Node Limit
67
79
 
68
80
  Limita el número de nodos de primer nivel que puede tener el documento.
@@ -215,6 +227,8 @@ Automáticamente convierte encabezados H1 adicionales en párrafos, permitiendo
215
227
  | `customExtensions` | `any[]` | `[]` | Custom Tiptap extensions |
216
228
  | `editorEvents` | `EditorEvents` | `{}` | Event handlers for editor events |
217
229
  | `config` | `EditorConfig` | `{}` | Visual configuration options |
230
+ | `contentWrapperAs` | `T extends keyof SvelteHTMLElements` | `'div'` | HTML element type for content wrapper |
231
+ | `inlineNodeMode` | `boolean` | `false` | Enable inline node mode (uses inline document) |
218
232
 
219
233
  ## Editor Events
220
234
 
@@ -1,49 +1,31 @@
1
- <script lang="ts">
1
+ <script lang="ts" generics="T extends keyof SvelteHTMLElements = 'div'">
2
+ import type { SvelteHTMLElements } from 'svelte/elements';
2
3
  import "./styles.css";
3
4
  import "katex/dist/katex.min.css";
4
-
5
- import { onMount, onDestroy, setContext } from "svelte";
5
+ import { onMount, onDestroy } from "svelte";
6
6
  import type { Readable } from "svelte/store";
7
-
8
7
  import { computePosition, offset, autoUpdate } from "@floating-ui/dom";
9
-
10
8
  import {
11
9
  Mathematics,
12
10
  migrateMathStrings,
13
11
  } from "@tiptap/extension-mathematics";
14
-
15
12
  import { CharacterCount } from '@tiptap/extensions'
16
-
17
13
  import { CellSelection } from "prosemirror-tables";
18
-
19
14
  import {
20
15
  createEditor,
21
16
  Editor,
22
- EditorContent,
17
+ // EditorContent,
23
18
  BubbleMenu,
24
19
  } from "svelte-tiptap";
25
20
 
21
+ import EditorContent from "./svelte-tiptap-extends/EditorContent.svelte";
26
22
  import { getRichTextExtensions } from "./getExtensions";
27
-
28
23
  import { rgbToHex } from "./utils";
29
24
 
30
- import SpecialBox from "./Toolbar/action-buttons/marks/SpecialBox.svelte";
31
-
32
25
  import MergeCellsBtn from "./Toolbar/action-buttons/MergeCellsBtn.svelte";
33
26
  import SplitCellBtn from "./Toolbar/action-buttons/SplitCellBtn.svelte";
34
27
 
35
28
  import HeadingBtn from "./Toolbar/action-buttons/nodes/HeadingBtn.svelte";
36
- import HardBreakBtn from "./Toolbar/action-buttons/nodes/HardBreakBtn.svelte";
37
-
38
- import CodeMarkBtn from "./Toolbar/action-buttons/marks/CodeMarkBtn.svelte";
39
- import LinkBtn from "./Toolbar/action-buttons/marks/LinkBtn.svelte";
40
- import Strike from "./Toolbar/action-buttons/marks/Strike.svelte";
41
- import Underline from "./Toolbar/action-buttons/marks/Underline.svelte";
42
- import Italic from "./Toolbar/action-buttons/marks/Italic.svelte";
43
- import Bold from "./Toolbar/action-buttons/marks/Bold.svelte";
44
-
45
- import HighlightDropdownBtn from "./Toolbar/dropdown-buttons/HighlightDropdownBtn.svelte";
46
- import TextColorDropdownBtn from "./Toolbar/dropdown-buttons/TextColorDropdownBtn.svelte";
47
29
 
48
30
  import LineHeightDropdown from "./Toolbar/dropdowns/LineHeightDropdown.svelte";
49
31
  import ListBtn from "./Toolbar/action-buttons/nodes/ListBtn.svelte";
@@ -51,7 +33,11 @@
51
33
 
52
34
  import RenderToolbarButton from "./Toolbar/RenderToolbarButton.svelte";
53
35
 
54
- export interface Props {
36
+ import { Document } from "@tiptap/extension-document";
37
+
38
+ const InlineDoc = Document.extend({ content: "inline*" })
39
+
40
+ interface Props {
55
41
  id?: string;
56
42
  className?: string;
57
43
  editable?: boolean;
@@ -60,7 +46,8 @@
60
46
  charactersLimit?: number;
61
47
  limitWarningMessage?: string;
62
48
  showToolbar?: boolean;
63
- toolbarAlign?: string;
49
+ showCountersBar?: boolean;
50
+ toolbarAlign?: "left" | "center" | "right";
64
51
  semanticHeadings?: boolean;
65
52
  uniqueH1?: boolean;
66
53
  toolbarConfig?: ToolbarConfig;
@@ -104,9 +91,10 @@
104
91
  docTextColor?: string;
105
92
  buttonStyle?: "accent-soft" | "accent-solid";
106
93
  };
107
- }
108
-
109
- export type RichTextProps = Props;
94
+ contentWrapperAs?: T;
95
+ inlineNodeMode?: boolean;
96
+ trailingNode?: boolean;
97
+ }
110
98
 
111
99
  type ToolbarButton =
112
100
  | string
@@ -138,6 +126,7 @@
138
126
  charactersLimit,
139
127
  limitWarningMessage,
140
128
  showToolbar = true,
129
+ showCountersBar = true,
141
130
  toolbarAlign = "center",
142
131
  semanticHeadings = false,
143
132
  uniqueH1 = false,
@@ -159,7 +148,10 @@
159
148
  onPaste: () => {},
160
149
  },
161
150
  config,
162
- }: Props = $props();
151
+ contentWrapperAs = "div" as T,
152
+ inlineNodeMode = false,
153
+ trailingNode = true,
154
+ }: Props = $props();
163
155
 
164
156
  let editor = $state() as Readable<Editor>;
165
157
 
@@ -241,7 +233,7 @@
241
233
 
242
234
  const isAccentSoft = editorConfig.buttonStyle === "accent-soft";
243
235
  let percentage = $derived.by(() => {
244
- return $editor ? Math.round((100 / charactersLimit) * $editor.storage.characterCount.characters()) : 0
236
+ return $editor ? (100 / charactersLimit) * $editor.storage.characterCount.characters() : 0
245
237
  });
246
238
 
247
239
  let toolbarGroups = $derived(
@@ -286,14 +278,13 @@
286
278
  "rgb(255, 102, 142)",
287
279
  ];
288
280
 
289
- const extensions = getRichTextExtensions({
290
- editable: true,
291
- customExtensions: [
281
+ const extensions = getRichTextExtensions({
282
+ editable: true,
283
+ trailingNode,
284
+ customExtensions: [
292
285
  Mathematics.configure({
293
286
  inlineOptions: {
294
287
  onClick: (node, pos) => {
295
- // you can do anything on click, e.g. open a dialog to edit the math node
296
- // or just a prompt to edit the LaTeX code for a quick prototype
297
288
  const katex = prompt(
298
289
  "Update math LaTeX expression:",
299
290
  node.attrs.latex,
@@ -321,7 +312,9 @@
321
312
  },
322
313
  }),
323
314
 
324
- charactersLimit && CharacterCount.configure({
315
+ inlineNodeMode && InlineDoc,
316
+
317
+ CharacterCount.configure({
325
318
  limit: charactersLimit,
326
319
  }),
327
320
  ...customExtensions,
@@ -334,9 +327,9 @@
334
327
  activeDropdownType = type;
335
328
 
336
329
  if (tooltipVisible) {
337
- hideDropdown(); // Ocultar
330
+ hideDropdown();
338
331
  } else {
339
- hideDropdown(); // limpiar antes de abrir
332
+ hideDropdown();
340
333
  currentTriggerEl = el;
341
334
  tooltipVisible = true;
342
335
  document.body.append(tooltip);
@@ -722,7 +715,7 @@
722
715
  </header>
723
716
  {/if}
724
717
 
725
- <EditorContent editor={$editor} class="fl-rich-text-content" />
718
+ <EditorContent as={contentWrapperAs} editor={$editor} class="fl-rich-text-content" data-fl-editable="true" />
726
719
 
727
720
  <!-- Warning message for node limit -->
728
721
  {#if showLimitWarning && nodesLimit}
@@ -733,40 +726,43 @@
733
726
  {/if}
734
727
 
735
728
  <!-- Bottom bar showing node count -->
736
- {#if nodesLimit}
737
- <div class="fl-node-count-bar">
738
-
729
+ {#if showCountersBar || percentage >= 90}
730
+ <div class="fl-counters-bar">
731
+ <div class="fl-character-count" class:fl-character-count--warning={percentage >= 100}>
739
732
  {#if charactersLimit}
740
- <div class="fl-character-count" class:fl-character-count--warning={percentage >= 100}>
741
- <svg height="20" width="20" viewBox="0 0 20 20">
742
- <circle r="10" cx="10" cy="10" fill="#ffffff30" />
743
- <circle
744
- r="5"
745
- cx="10"
746
- cy="10"
747
- fill="transparent"
748
- stroke="currentColor"
749
- stroke-width="10"
750
- stroke-dasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
751
- transform="rotate(-90) translate(-20)"
752
- />
753
- <circle r="6" cx="10" cy="10" fill="var(--fl-editor-bg)" />
754
- </svg>
755
-
756
- <span>
757
- Characters count: {$editor?.storage?.characterCount?.characters()} / {charactersLimit}
758
- </span>
759
-
760
- <span>
761
- Words count: {$editor?.storage?.characterCount?.words()}
762
- </span>
763
- </div>
733
+ <svg height="20" width="20" viewBox="0 0 20 20">
734
+ <circle r="10" cx="10" cy="10" fill="#ffffff30" />
735
+ <circle
736
+ r="5"
737
+ cx="10"
738
+ cy="10"
739
+ fill="transparent"
740
+ stroke="currentColor"
741
+ stroke-width="10"
742
+ stroke-dasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
743
+ transform="rotate(-90) translate(-20)"
744
+ />
745
+ <circle r="6" cx="10" cy="10" fill="var(--fl-editor-bg)" />
746
+ </svg>
764
747
  {/if}
765
748
 
749
+ <span>
750
+ Characters: {$editor?.storage?.characterCount?.characters()}
751
+ {#if charactersLimit} / {charactersLimit}{/if}
752
+ </span>
753
+ </div>
766
754
 
767
- <span class="fl-node-count-text">
768
- Nodes count: {currentNodeCount} / {nodesLimit}
769
- </span>
755
+ {#if showCountersBar}
756
+ <span>
757
+ Words: {$editor?.storage?.characterCount?.words()}
758
+ </span>
759
+ {/if}
760
+
761
+ {#if nodesLimit}
762
+ <span class="fl-node-count-text">
763
+ Nodes: {currentNodeCount} / {nodesLimit}
764
+ </span>
765
+ {/if}
770
766
 
771
767
  <!-- <div class="fl-node-count-progress">
772
768
  <div
@@ -1,90 +1,17 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
+ import type { SvelteHTMLElements } from 'svelte/elements';
2
3
  import "./styles.css";
3
4
  import "katex/dist/katex.min.css";
4
- type ToolbarButton = string | {
5
- type?: string;
6
- tooltip?: string;
7
- icon?: string;
8
- name?: string;
9
- };
10
- type ToolbarSelect = {
11
- select: ToolbarButton[];
12
- name?: string;
13
- tooltip?: string;
14
- icon?: string;
15
- type?: string;
16
- };
17
- type ToolbarItem = ToolbarButton | ToolbarSelect;
18
- type ToolbarConfig = ToolbarItem[] | ToolbarItem[][];
19
- type BubbleMenuConfig = ToolbarItem[] | ToolbarItem[][];
20
- export interface Props {
21
- id?: string;
22
- className?: string;
23
- editable?: boolean;
24
- content?: string | {
25
- type: string;
26
- content: any[];
27
- } | null;
28
- nodesLimit?: number;
29
- charactersLimit?: number;
30
- limitWarningMessage?: string;
31
- showToolbar?: boolean;
32
- toolbarAlign?: string;
33
- semanticHeadings?: boolean;
34
- uniqueH1?: boolean;
35
- toolbarConfig?: ToolbarConfig;
36
- bubbleMenuConfig?: BubbleMenuConfig;
37
- customExtensions?: any[];
38
- editorEvents?: {
39
- onTransaction?: (params: any) => void;
40
- onBeforeCreate?: (params: any) => void;
41
- onCreate?: (params: any) => void;
42
- onUpdate: (params: any) => void;
43
- onFocus?: (params: any) => void;
44
- onBlur?: (params: any) => void;
45
- onDestroy?: (params: any) => void;
46
- onDrop?: (params: any) => void;
47
- onDelete?: (params: any) => void;
48
- onContentError?: (params: any) => void;
49
- onSelectionUpdate?: (params: any) => void;
50
- onPaste?: (params: any) => void;
51
- };
52
- config?: {
53
- editorAccentColor?: string;
54
- editorBgColor?: string;
55
- editorRadius?: string;
56
- toolbarStickyPosition?: number;
57
- toolbarZIndex?: number;
58
- toolbarBgColor?: string;
59
- toolbarTextColor?: string;
60
- toolbarPadding?: string;
61
- toolbarJustifyContent?: string;
62
- toolbarGap?: string;
63
- toolbarBtnPadding?: string;
64
- toolbarBtnRadius?: string;
65
- toolbarBtnMinHeight?: string;
66
- toolbarBtnMinWidth?: string;
67
- docMaxWidth?: string;
68
- docPadding?: string;
69
- docBg?: string;
70
- docMarginInline?: string;
71
- docMarginBlock?: string;
72
- docRadius?: string;
73
- docTextColor?: string;
74
- buttonStyle?: "accent-soft" | "accent-solid";
75
- };
76
- }
77
- export type RichTextProps = Props;
78
- declare const __propDef: {
79
- props: Record<string, never>;
80
- events: {
5
+ declare class __sveltets_Render<T extends keyof SvelteHTMLElements = 'div'> {
6
+ props(): Record<string, never>;
7
+ events(): {} & {
81
8
  [evt: string]: CustomEvent<any>;
82
9
  };
83
- slots: {};
84
- };
85
- type RichTextProps_ = typeof __propDef.props;
86
- export type RichTextEvents = typeof __propDef.events;
87
- export type RichTextSlots = typeof __propDef.slots;
88
- export default class RichText extends SvelteComponentTyped<RichTextProps_, RichTextEvents, RichTextSlots> {
10
+ slots(): {};
11
+ }
12
+ export type RichTextProps<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['props']>;
13
+ export type RichTextEvents<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['events']>;
14
+ export type RichTextSlots<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['slots']>;
15
+ export default class RichText<T extends keyof SvelteHTMLElements = 'div'> extends SvelteComponentTyped<RichTextProps<T>, RichTextEvents<T>, RichTextSlots<T>> {
89
16
  }
90
17
  export {};
@@ -7,5 +7,6 @@ declare module '@tiptap/core' {
7
7
  }
8
8
  export declare function getRichTextExtensions(options?: {
9
9
  editable?: boolean;
10
+ trailingNode?: boolean;
10
11
  customExtensions?: any[];
11
12
  }): any[];
@@ -28,7 +28,7 @@ const DocHeading = Heading.extend({
28
28
  },
29
29
  }).configure({ levels: [1] });
30
30
  export function getRichTextExtensions(options) {
31
- const { editable = false, customExtensions = [] } = options ?? {};
31
+ const { editable = false, trailingNode = true, customExtensions = [] } = options ?? {};
32
32
  return [
33
33
  DocHeading,
34
34
  Heading.configure({ levels: [2, 3, 4, 5, 6] }),
@@ -37,7 +37,7 @@ export function getRichTextExtensions(options) {
37
37
  // fontSize: false
38
38
  }),
39
39
  StarterKit.configure({
40
- trailingNode: false,
40
+ trailingNode: trailingNode ? undefined : false,
41
41
  link: false,
42
42
  bulletList: false,
43
43
  listItem: false,
package/dist/styles.css CHANGED
@@ -127,7 +127,8 @@
127
127
  display: flex;
128
128
  gap: 5px;
129
129
  position: relative;
130
- z-index: 9;
130
+ z-index: var(--fl-toolbar-sticky-position, 10);
131
+
131
132
 
132
133
  .fl-bubble-menu-mark-button {
133
134
  border-radius: 9px;
@@ -540,7 +541,7 @@ button {
540
541
  }
541
542
  }
542
543
 
543
- .fl-node-count-bar {
544
+ .fl-counters-bar {
544
545
  background: var(--fl-toolbar-bg, #242424);
545
546
  border-top: 1px solid #333;
546
547
  padding: 9px 12px;
@@ -551,12 +552,16 @@ button {
551
552
  color: var(--fl-toolbar-text-color, currentColor);
552
553
  border-bottom-left-radius: inherit;
553
554
  border-bottom-right-radius: inherit;
555
+
556
+ span {
557
+ color: #ffffffab;
558
+ }
554
559
  }
555
560
 
556
561
  .fl-character-count {
557
562
  display: flex;
558
563
  align-items: center;
559
- gap: 8px;
564
+ gap: 9px;
560
565
 
561
566
  svg {
562
567
  color: var(--fl-editor-accent-color);
@@ -565,7 +570,7 @@ button {
565
570
  &.fl-character-count--warning {
566
571
  color: #ff5c33;
567
572
 
568
- svg {
573
+ svg, span {
569
574
  color: currentColor;
570
575
  }
571
576
  }
@@ -0,0 +1,4 @@
1
+ import { Editor as CoreEditor } from '@tiptap/core';
2
+ export declare class Editor extends CoreEditor {
3
+ contentElement: HTMLElement | null;
4
+ }
@@ -0,0 +1,4 @@
1
+ import { Editor as CoreEditor } from '@tiptap/core';
2
+ export class Editor extends CoreEditor {
3
+ contentElement = null;
4
+ }
@@ -0,0 +1,62 @@
1
+ <script lang="ts" generics="T extends keyof SvelteHTMLElements = 'div'">
2
+ import { onDestroy, onMount, tick } from 'svelte';
3
+ import type { SvelteHTMLElements } from 'svelte/elements';
4
+ import type { ComponentInputProps } from './types';
5
+
6
+ type Props = ComponentInputProps<{}> & SvelteHTMLElements[T] & {
7
+ as?: T;
8
+ };
9
+
10
+ const { editor, children, class: className, as = 'div' as T, ...rest }: Props = $props();
11
+ let element: HTMLElement;
12
+
13
+ const init = async () => {
14
+ await tick();
15
+ if (!element) {
16
+ return;
17
+ }
18
+
19
+ if (!editor?.options.element) {
20
+ return;
21
+ }
22
+
23
+ if (editor.contentElement) {
24
+ return;
25
+ }
26
+
27
+ const editorElement = editor.options.element;
28
+
29
+ if (editorElement instanceof HTMLElement) {
30
+ element.append(...Array.from(editorElement.childNodes));
31
+ editor.setOptions({ element });
32
+ editor.contentElement = element;
33
+ }
34
+ };
35
+
36
+ onMount(init);
37
+
38
+ onDestroy(() => {
39
+ if (!editor) {
40
+ return;
41
+ }
42
+
43
+ editor.contentElement = null;
44
+
45
+ const editorElement = editor.options.element;
46
+
47
+ if (editorElement instanceof HTMLElement && editorElement.firstChild) {
48
+ const newElement = document.createElement('div');
49
+ newElement.append(...Array.from(editorElement.childNodes));
50
+
51
+ editor.setOptions({
52
+ element: newElement,
53
+ });
54
+ }
55
+ });
56
+ </script>
57
+
58
+ <svelte:element this={as} bind:this={element} class={className} {...rest}>
59
+ {#if children}
60
+ {@render children()}
61
+ {/if}
62
+ </svelte:element>
@@ -0,0 +1,15 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import type { SvelteHTMLElements } from 'svelte/elements';
3
+ declare class __sveltets_Render<T extends keyof SvelteHTMLElements = 'div'> {
4
+ props(): Record<string, never>;
5
+ events(): {} & {
6
+ [evt: string]: CustomEvent<any>;
7
+ };
8
+ slots(): {};
9
+ }
10
+ export type EditorContentProps<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['props']>;
11
+ export type EditorContentEvents<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['events']>;
12
+ export type EditorContentSlots<T extends keyof SvelteHTMLElements = 'div'> = ReturnType<__sveltets_Render<T>['slots']>;
13
+ export default class EditorContent<T extends keyof SvelteHTMLElements = 'div'> extends SvelteComponentTyped<EditorContentProps<T>, EditorContentEvents<T>, EditorContentSlots<T>> {
14
+ }
15
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { Editor } from './Editor';
3
+ export interface TiptapNodeViewContext {
4
+ onDragStart: (event: DragEvent) => void;
5
+ }
6
+ export type ComponentInputProps<T> = Partial<T> & {
7
+ editor: Editor;
8
+ class?: string;
9
+ children?: Snippet;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flexiui/svelte-rich-text",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",