@flexiui/svelte-rich-text 0.0.61 → 0.0.62

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,10 +91,10 @@
104
91
  docTextColor?: string;
105
92
  buttonStyle?: "accent-soft" | "accent-solid";
106
93
  };
94
+ contentWrapperAs?: T;
95
+ inlineNodeMode?: boolean;
107
96
  }
108
97
 
109
- export type RichTextProps = Props;
110
-
111
98
  type ToolbarButton =
112
99
  | string
113
100
  | {
@@ -138,6 +125,7 @@
138
125
  charactersLimit,
139
126
  limitWarningMessage,
140
127
  showToolbar = true,
128
+ showCountersBar = true,
141
129
  toolbarAlign = "center",
142
130
  semanticHeadings = false,
143
131
  uniqueH1 = false,
@@ -159,6 +147,8 @@
159
147
  onPaste: () => {},
160
148
  },
161
149
  config,
150
+ contentWrapperAs = "div" as T,
151
+ inlineNodeMode = false,
162
152
  }: Props = $props();
163
153
 
164
154
  let editor = $state() as Readable<Editor>;
@@ -241,7 +231,7 @@
241
231
 
242
232
  const isAccentSoft = editorConfig.buttonStyle === "accent-soft";
243
233
  let percentage = $derived.by(() => {
244
- return $editor ? Math.round((100 / charactersLimit) * $editor.storage.characterCount.characters()) : 0
234
+ return $editor ? (100 / charactersLimit) * $editor.storage.characterCount.characters() : 0
245
235
  });
246
236
 
247
237
  let toolbarGroups = $derived(
@@ -292,8 +282,6 @@
292
282
  Mathematics.configure({
293
283
  inlineOptions: {
294
284
  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
285
  const katex = prompt(
298
286
  "Update math LaTeX expression:",
299
287
  node.attrs.latex,
@@ -321,7 +309,9 @@
321
309
  },
322
310
  }),
323
311
 
324
- charactersLimit && CharacterCount.configure({
312
+ inlineNodeMode && InlineDoc,
313
+
314
+ CharacterCount.configure({
325
315
  limit: charactersLimit,
326
316
  }),
327
317
  ...customExtensions,
@@ -334,9 +324,9 @@
334
324
  activeDropdownType = type;
335
325
 
336
326
  if (tooltipVisible) {
337
- hideDropdown(); // Ocultar
327
+ hideDropdown();
338
328
  } else {
339
- hideDropdown(); // limpiar antes de abrir
329
+ hideDropdown();
340
330
  currentTriggerEl = el;
341
331
  tooltipVisible = true;
342
332
  document.body.append(tooltip);
@@ -722,7 +712,7 @@
722
712
  </header>
723
713
  {/if}
724
714
 
725
- <EditorContent editor={$editor} class="fl-rich-text-content" />
715
+ <EditorContent as={contentWrapperAs} editor={$editor} class="fl-rich-text-content a" data-fl-editable="true" />
726
716
 
727
717
  <!-- Warning message for node limit -->
728
718
  {#if showLimitWarning && nodesLimit}
@@ -733,40 +723,43 @@
733
723
  {/if}
734
724
 
735
725
  <!-- Bottom bar showing node count -->
736
- {#if nodesLimit}
737
- <div class="fl-node-count-bar">
738
-
726
+ {#if showCountersBar || percentage >= 90}
727
+ <div class="fl-counters-bar">
728
+ <div class="fl-character-count" class:fl-character-count--warning={percentage >= 100}>
739
729
  {#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>
730
+ <svg height="20" width="20" viewBox="0 0 20 20">
731
+ <circle r="10" cx="10" cy="10" fill="#ffffff30" />
732
+ <circle
733
+ r="5"
734
+ cx="10"
735
+ cy="10"
736
+ fill="transparent"
737
+ stroke="currentColor"
738
+ stroke-width="10"
739
+ stroke-dasharray={`calc(${percentage} * 31.4 / 100) 31.4`}
740
+ transform="rotate(-90) translate(-20)"
741
+ />
742
+ <circle r="6" cx="10" cy="10" fill="var(--fl-editor-bg)" />
743
+ </svg>
764
744
  {/if}
765
745
 
746
+ <span>
747
+ Characters: {$editor?.storage?.characterCount?.characters()}
748
+ {#if charactersLimit} / {charactersLimit}{/if}
749
+ </span>
750
+ </div>
766
751
 
767
- <span class="fl-node-count-text">
768
- Nodes count: {currentNodeCount} / {nodesLimit}
769
- </span>
752
+ {#if showCountersBar}
753
+ <span>
754
+ Words: {$editor?.storage?.characterCount?.words()}
755
+ </span>
756
+ {/if}
757
+
758
+ {#if nodesLimit}
759
+ <span class="fl-node-count-text">
760
+ Nodes: {currentNodeCount} / {nodesLimit}
761
+ </span>
762
+ {/if}
770
763
 
771
764
  <!-- <div class="fl-node-count-progress">
772
765
  <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 {};
package/dist/styles.css CHANGED
@@ -540,7 +540,7 @@ button {
540
540
  }
541
541
  }
542
542
 
543
- .fl-node-count-bar {
543
+ .fl-counters-bar {
544
544
  background: var(--fl-toolbar-bg, #242424);
545
545
  border-top: 1px solid #333;
546
546
  padding: 9px 12px;
@@ -551,12 +551,16 @@ button {
551
551
  color: var(--fl-toolbar-text-color, currentColor);
552
552
  border-bottom-left-radius: inherit;
553
553
  border-bottom-right-radius: inherit;
554
+
555
+ span {
556
+ color: #ffffffab;
557
+ }
554
558
  }
555
559
 
556
560
  .fl-character-count {
557
561
  display: flex;
558
562
  align-items: center;
559
- gap: 8px;
563
+ gap: 9px;
560
564
 
561
565
  svg {
562
566
  color: var(--fl-editor-accent-color);
@@ -565,7 +569,7 @@ button {
565
569
  &.fl-character-count--warning {
566
570
  color: #ff5c33;
567
571
 
568
- svg {
572
+ svg, span {
569
573
  color: currentColor;
570
574
  }
571
575
  }
@@ -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.62",
4
4
  "description": "A lightweight and flexible rich text editor component for Svelte",
5
5
  "keywords": [
6
6
  "svelte",