@flogeez/angular-tiptap-editor 2.0.2 → 2.1.0

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
@@ -1,744 +1,805 @@
1
- # Angular Tiptap Editor
2
-
3
- A modern, customizable rich-text editor for Angular applications, built with Tiptap and featuring complete internationalization support.
4
-
5
- [![Demo](https://img.shields.io/badge/Demo-Live-brightgreen?style=for-the-badge&logo=google-chrome)](https://flogeez.github.io/angular-tiptap-editor/) [![Try it on StackBlitz](https://img.shields.io/badge/Try%20it-StackBlitz-blue?style=for-the-badge&logo=stackblitz)](https://stackblitz.com/edit/angular-tiptap-editor)
6
-
7
- ## 🚀 Features
8
-
9
- - **Modern Angular**: Built with Angular 18+ using Signals and modern patterns for peak performance.
10
- - **Full Rich Text Power**: Powered by Tiptap v2 with extensive formatting and block capabilities.
11
- - **Modern UX (Notion-like)**: Intuitive slash commands and bubble menus for a keyboard-first experience.
12
- - **Highly Customizable**: Easily configure toolbars, bubble menus, and slash command items.
13
- - **Signal-Based Reactivity**: Pure Signal architecture natively compatible with `ChangeDetectionStrategy.OnPush`.
14
- - **Advanced Table Support**: Full table management with cell selection and context-aware bubble menus.
15
- - **Professional Media**: Advanced image handling with resizing, auto-compression, and custom uploaders.
16
- - **Built-in i18n**: English & French support with a reactive, extensible locale system.
17
- - **Word/Character Count**: Real-time statistics with proper pluralization support.
18
- - **Office-Ready**: Cleaned-up pasting from Microsoft Word and Excel to maintain layout integrity.
19
- - **Service Driven**: Deep programmatic control via `EditorCommandsService` and isolated instances.
20
- - **A11y First**: Built with accessibility best practices and full keyboard navigation.
21
-
22
- ## 💎 Why this editor?
23
-
24
- Most Angular wrappers for Tiptap provide a basic component but leave the heavy lifting to you. **Angular Tiptap Editor** is built to solve common production hurdles:
25
-
26
- - **True Scalability**: Thanks to **isolated services** provided at the component level, you can host multiple independent editors with different configurations and languages on the same page without a single state leak.
27
- - **OnPush by Default**: The entire UI (toolbar, menus) is powered by **Angular Signals**. The `editorState` snapshot logic ensures that your components only re-render when necessary, even in complex `OnPush` applications.
28
- - **Deep i18n & Extensibility**: Not just English/French — you can inject **custom translations** and **custom Tiptap extensions**. Our `DiscoveryCalculator` automatically tracks any new mark or node you add, making them reactive without extra code.
29
- - **Clean Office UX**: Professional-grade pasting from **Word and Excel** plus smart image handling (auto-compression, resizing handles) ensures a polished experience for end-users.
30
-
31
- ## 🛠️ Extensions included
32
-
33
- The library comes with a pre-configured set of standard and custom extensions:
34
-
35
- - **Nodes**: `StarterKit`, `Heading`, `Table`, `Image`, `HorizontalRule`, `CodeBlock`.
36
- - **Marks**: `Bold`, `Italic`, `Underline`, `Strike`, `Code`, `Link`, `Highlight`, `TextStyle`, `Color`, `Superscript`, `Subscript`.
37
- - **Utilities**: `Placeholder`, `CharacterCount`, `Typography`, `Focus`, `BubbleMenu`, `Gapcursor`, `Dropcursor`, `ResizableImage` (Custom).
38
-
39
- ## 📦 Installation
40
-
41
- ```bash
42
- npm install @flogeez/angular-tiptap-editor
43
- ```
44
-
45
- ### CSS Styles
46
-
47
- Add the required CSS to your `angular.json` file in the `styles` array:
48
-
49
- ```json
50
- {
51
- "styles": [
52
- ...
53
- "node_modules/@fontsource/material-symbols-outlined/index.css",
54
- "node_modules/@flogeez/angular-tiptap-editor/src/lib/styles/index.css",
55
- ...
56
- ]
57
- }
58
- ```
59
-
60
- ## 🎯 Quick Start
61
-
62
- ### 1. Basic Usage
63
-
64
- ```typescript
65
- import { Component } from "@angular/core";
66
- import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
67
-
68
- @Component({
69
- selector: "app-example",
70
- standalone: true,
71
- imports: [AngularTiptapEditorComponent],
72
- template: `
73
- <angular-tiptap-editor
74
- [content]="content"
75
- (contentChange)="onContentChange($event)"
76
- />
77
- `,
78
- })
79
- export class ExampleComponent {
80
- content = "<p>Hello <strong>World</strong>!</p>";
81
-
82
- onContentChange(newContent: string) {
83
- this.content = newContent;
84
- console.log("Content updated:", newContent);
85
- }
86
- }
87
- ```
88
-
89
- ### 2. With Custom Configuration
90
-
91
- ```typescript
92
- import { Component } from "@angular/core";
93
- import {
94
- AngularTiptapEditorComponent,
95
- DEFAULT_TOOLBAR_CONFIG,
96
- DEFAULT_BUBBLE_MENU_CONFIG,
97
- } from "@flogeez/angular-tiptap-editor";
98
-
99
- @Component({
100
- selector: "app-advanced",
101
- standalone: true,
102
- imports: [AngularTiptapEditorComponent],
103
- template: `
104
- <angular-tiptap-editor
105
- [content]="content"
106
- [toolbar]="toolbarConfig"
107
- [bubbleMenu]="bubbleMenuConfig"
108
- [slashCommands]="slashCommandsConfig"
109
- [locale]="'en'"
110
- [height]="400"
111
- [showCharacterCount]="true"
112
- [showWordCount]="true"
113
- [maxCharacters]="500"
114
- (contentChange)="onContentChange($event)"
115
- />
116
- `,
117
- })
118
- export class AdvancedComponent {
119
- content = "<h1>Welcome!</h1><p>Start editing...</p>";
120
-
121
- // Use default configurations as base
122
- toolbarConfig = {
123
- ...DEFAULT_TOOLBAR_CONFIG,
124
- clear: true, // Add clear button
125
- };
126
-
127
- bubbleMenuConfig = {
128
- ...DEFAULT_BUBBLE_MENU_CONFIG,
129
- table: true, // Enable table bubble menu
130
- };
131
-
132
- // No config needed if you want all commands enabled
133
- slashCommandsConfig = {
134
- image: true,
135
- table: true,
136
- heading1: true
137
- };
138
-
139
- // Available keys: "heading1", "heading2", "heading3", "bulletList",
140
- // "orderedList", "blockquote", "code", "image", "horizontalRule", "table"
141
-
142
- onContentChange(newContent: string) {
143
- this.content = newContent;
144
- }
145
- }
146
- ```
147
-
148
- ### 3. Registering Custom Extensions
149
-
150
- Easily extend the editor with any standard Tiptap extension or your own custom marks/nodes via the `tiptapExtensions` input.
151
-
152
- ```typescript
153
- import { Component } from "@angular/core";
154
- import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
155
-
156
- @Component({
157
- selector: "app-custom-extensions",
158
- standalone: true,
159
- imports: [AngularTiptapEditorComponent],
160
- template: `
161
- <angular-tiptap-editor
162
- [content]="content"
163
- [tiptapExtensions]="extensions"
164
- (contentChange)="content = $event"
165
- />
166
- `,
167
- })
168
- export class CustomExtensionsComponent {
169
- content = "<p>Custom extensions example</p>";
170
-
171
- extensions = [
172
- // Add your custom TipTap extensions here
173
- // Example: Custom extension configuration
174
- // MyCustomExtension.configure({ /* options */ })
175
- ];
176
- }
177
- ```
178
-
179
- ### 4. With Form Integration
180
-
181
- ```typescript
182
- import { Component } from "@angular/core";
183
- import { FormControl, ReactiveFormsModule } from "@angular/forms";
184
- import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
185
-
186
- @Component({
187
- selector: "app-form",
188
- standalone: true,
189
- imports: [AngularTiptapEditorComponent, ReactiveFormsModule],
190
- template: `
191
- <form>
192
- <angular-tiptap-editor
193
- [formControl]="contentControl"
194
- placeholder="Enter your content here..."
195
- [showCharacterCount]="true"
196
- [showWordCount]="true"
197
- />
198
- <button type="submit">Submit</button>
199
- </form>
200
- `,
201
- })
202
- export class FormComponent {
203
- contentControl = new FormControl("<p>Initial content</p>");
204
- }
205
- ```
206
-
207
- ### 5. Using EditorCommandsService
208
-
209
- ```typescript
210
- import { Component, inject } from "@angular/core";
211
- import { EditorCommandsService } from "@flogeez/angular-tiptap-editor";
212
-
213
- @Component({
214
- selector: "app-commands",
215
- standalone: true,
216
- template: `
217
- <div>
218
- <div class="controls">
219
- <button (click)="clearContent()">Clear Content</button>
220
- <button (click)="focusEditor()">Focus Editor</button>
221
- <button (click)="setContent()">Set Content</button>
222
- </div>
223
-
224
- <angular-tiptap-editor
225
- (editorCreated)="onEditorCreated($event)"
226
- />
227
- </div>
228
- `,
229
- })
230
- export class CommandsComponent {
231
- private editorCommandsService = inject(EditorCommandsService);
232
- private editor: Editor | null = null;
233
-
234
- onEditorCreated(editor: Editor) {
235
- this.editor = editor;
236
- }
237
-
238
- clearContent() {
239
- if (this.editor) {
240
- this.editorCommandsService.clearContent(this.editor);
241
- }
242
- }
243
-
244
- focusEditor() {
245
- if (this.editor) {
246
- this.editorCommandsService.focus(this.editor);
247
- }
248
- }
249
-
250
- setContent() {
251
- if (this.editor) {
252
- this.editorCommandsService.setContent(
253
- this.editor,
254
- "<h1>New Content</h1>"
255
- );
256
- }
257
- }
258
- }
259
- ```
260
-
261
- ### 6. Extending Reactive Editor State
262
-
263
- The editor features a dual-layer state architecture: **Automatic Tracking** for simple extensions and **Custom Calculators** for advanced needs.
264
-
265
- #### A. Automatic Extension Tracking (Zero Config)
266
-
267
- Any TipTap **Mark** or **Node** you add to `tiptapExtensions` is automatically tracked by our `DiscoveryCalculator`. You don't need to write any extra code to make them reactive.
268
-
269
- * **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
270
- * **For Nodes**: `state().nodes.yourExtensionName` (boolean).
271
-
272
- #### B. Custom State Calculators (Advanced)
273
-
274
- If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
275
-
276
- 1. **Define a Calculator**:
277
- ```typescript
278
- import { StateCalculator } from "@flogeez/angular-tiptap-editor";
279
-
280
- // This function will be called on every editor update
281
- export const MyCustomCalculator: StateCalculator = (editor) => {
282
- return {
283
- custom: {
284
- hasHighPriority: editor.isActive('priority'),
285
- selectionDepth: editor.state.selection.$from.depth,
286
- // Any data you need...
287
- }
288
- };
289
- };
290
- ```
291
-
292
- 2. **Register it in the Template**:
293
- ```html
294
- <angular-tiptap-editor
295
- [stateCalculators]="[MyCustomCalculator]"
296
- />
297
- ```
298
-
299
- 3. **Consume it anywhere**:
300
- ```typescript
301
- @Component({ ... })
302
- export class MyToolbarComponent {
303
- private editorCommands = inject(EditorCommandsService);
304
-
305
- // Access your custom data reactively!
306
- isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
307
- }
308
- ```
309
-
310
- ## Key Features
311
-
312
- ### 📊 Table Management
313
-
314
- Full table support with intuitive bubble menus:
315
-
316
- - **Table Creation**: Insert tables via slash commands (`/table`)
317
- - **Cell Selection**: Click and drag to select multiple cells
318
- - **Bubble Menus**: Context-aware menus for table operations
319
- - **Row/Column Management**: Add, remove, and merge cells
320
- - **Styling**: Custom table styling with proper borders
321
-
322
- ### Slash Commands
323
-
324
- Quick content insertion with slash commands:
325
-
326
- - **Headings**: `/h1`, `/h2`, `/h3`
327
- - **Lists**: `/bullet`, `/numbered`
328
- - **Blocks**: `/quote`, `/code`, `/line`
329
- - **Media**: `/image`, `/table`
330
- - **Fully Internationalized**: All commands translated
331
-
332
- #### Custom Slash Commands
333
-
334
- The `slashCommands` object also allows you to add completely custom command items:
335
-
336
- ```typescript
337
- import { SlashCommandsConfig } from "@flogeez/angular-tiptap-editor";
338
-
339
- slashCommands: SlashCommandsConfig = {
340
- // Toggle native commands
341
- heading1: true,
342
- image: false,
343
- // Add custom ones
344
- custom: [
345
- {
346
- title: 'Magic Action',
347
- description: 'Insert some AI magic',
348
- icon: 'auto_fix',
349
- keywords: ['magic', 'ai'],
350
- command: (editor) => editor.commands.insertContent('✨ Magic happened!')
351
- }
352
- ]
353
- };
354
- ```
355
-
356
- ### 🖼️ Advanced Image Handling
357
-
358
- Professional image management:
359
-
360
- - **Drag & Drop**: Drag images directly into the editor
361
- - **File Selection**: Click to select images from device
362
- - **Auto-Compression**: Images automatically compressed (max 1920x1080)
363
- - **Resizable**: Images can be resized with handles
364
- - **Bubble Menu**: Context menu for image operations
365
- - **Custom Upload Handler**: Upload images to your own server instead of base64
366
-
367
- #### Custom Image Upload Handler
368
-
369
- By default, images are converted to base64 and embedded directly in the HTML content. You can provide a custom upload handler to upload images to your own server (S3, Cloudinary, custom API, etc.) and use the returned URL instead.
370
-
371
- The handler can return either an **Observable** or a **Promise**.
372
-
373
- #### Using Observable (recommended for Angular)
374
-
375
- ```typescript
376
- import { Component, inject } from '@angular/core';
377
- import { HttpClient } from '@angular/common/http';
378
- import { map } from 'rxjs/operators';
379
- import {
380
- AngularTiptapEditorComponent,
381
- ImageUploadHandler
382
- } from '@flogeez/angular-tiptap-editor';
383
-
384
- @Component({
385
- selector: 'app-custom-upload',
386
- standalone: true,
387
- imports: [AngularTiptapEditorComponent],
388
- template: `
389
- <angular-tiptap-editor
390
- [content]="content"
391
- [imageUploadHandler]="uploadHandler"
392
- (contentChange)="onContentChange($event)"
393
- />
394
- `
395
- })
396
- export class CustomUploadComponent {
397
- private http = inject(HttpClient);
398
- content = '';
399
-
400
- uploadHandler: ImageUploadHandler = (ctx) => {
401
- const formData = new FormData();
402
- formData.append('image', ctx.file);
403
-
404
- return this.http.post<{ url: string }>('/api/upload', formData).pipe(
405
- map(result => ({ src: result.url }))
406
- );
407
- };
408
-
409
- onContentChange(newContent: string) {
410
- this.content = newContent;
411
- }
412
- }
413
- ```
414
-
415
- #### Using Promise (async/await)
416
-
417
- ```typescript
418
- uploadHandler: ImageUploadHandler = async (ctx) => {
419
- const formData = new FormData();
420
- formData.append('image', ctx.file);
421
-
422
- const result = await firstValueFrom(
423
- this.http.post<{ url: string }>('/api/upload', formData)
424
- );
425
-
426
- return { src: result.url };
427
- };
428
- ```
429
-
430
- The `ImageUploadContext` provides:
431
- - `file`: The original File object
432
- - `width`: Processed image width
433
- - `height`: Processed image height
434
- - `type`: MIME type (e.g., 'image/jpeg')
435
- - `base64`: Base64 data URL of the processed image (fallback)
436
-
437
- The handler must return an `ImageUploadHandlerResult` with at least a `src` property containing the image URL.
438
-
439
- ---
440
-
441
-
442
- ### 📝 Word & Character Counting
443
-
444
- Real-time content statistics:
445
-
446
- - **Live Updates**: Counters update as you type
447
- - **Proper Pluralization**: "1 word" vs "2 words"
448
- - **Separate Counts**: Independent word and character counts
449
- - **Configurable**: Show/hide individual counters
450
-
451
- ## 🎨 Demo
452
-
453
- ### 🌐 Live Demo
454
-
455
- Try the interactive demo online: **[https://flogeez.github.io/angular-tiptap-editor/](https://flogeez.github.io/angular-tiptap-editor/)**
456
-
457
- ### 🖥️ Run Locally
458
-
459
- ```bash
460
- git clone https://github.com/FloGeez/angular-tiptap-editor.git
461
- cd angular-tiptap-editor
462
- npm install
463
- npm start
464
- ```
465
-
466
- Open [http://localhost:4200](http://localhost:4200) to view the demo.
467
-
468
- ## 📖 Documentation
469
-
470
- ### API Reference
471
-
472
- #### Inputs
473
-
474
- | Input | Type | Default | Description |
475
- | -------------------- | -------------------------------------- | ------------------- | -------------------------------- |
476
- | `content` | `string` | `""` | Initial HTML content |
477
- | `placeholder` | `string` | `"Start typing..."` | Placeholder text |
478
- | `locale` | `'en' \| 'fr'` | Auto-detect | Editor language |
479
- | `editable` | `boolean` | `true` | Whether editor is editable |
480
- | `height` | `number` | `undefined` | Fixed height in pixels |
481
- | `maxHeight` | `number` | `undefined` | Maximum height in pixels |
482
- | `minHeight` | `number` | `200` | Minimum height in pixels |
483
- | `maxCharacters` | `number` | `undefined` | Character limit |
484
- | `fillContainer` | `boolean` | `false` | Fill parent container height |
485
- | `autofocus` | `boolean \| 'start' \| 'end' \| 'all'` | `false` | Auto-focus behavior |
486
- | `showToolbar` | `boolean` | `true` | Show toolbar |
487
- | `showBubbleMenu` | `boolean` | `true` | Show text bubble menu |
488
- | `showImageBubbleMenu`| `boolean` | `true` | Show image bubble menu |
489
- | `showTableBubbleMenu`| `boolean` | `true` | Show table bubble menu |
490
- | `showCellBubbleMenu` | `boolean` | `true` | Show cell bubble menu |
491
- | `enableSlashCommands`| `boolean` | `true` | Enable slash commands functionality|
492
- | `enableOfficePaste` | `boolean` | `true` | Enable smart Office pasting |
493
- | `showCharacterCount` | `boolean` | `true` | Show character counter |
494
- | `showWordCount` | `boolean` | `true` | Show word counter |
495
- | `toolbar` | `ToolbarConfig` | All enabled | Toolbar configuration |
496
- | `bubbleMenu` | `BubbleMenuConfig` | All enabled | Bubble menu configuration |
497
- | `imageBubbleMenu` | `ImageBubbleMenuConfig` | All enabled | Image bubble menu config |
498
- | `tableBubbleMenu` | `TableBubbleMenuConfig` | All enabled | Table bubble menu config |
499
- | `cellBubbleMenu` | `CellBubbleMenuConfig` | All enabled | Cell bubble menu config |
500
- | `slashCommands` | `SlashCommandsConfig` | All enabled | Slash commands configuration |
501
- | `imageUploadHandler` | `ImageUploadHandler` | `undefined` | Custom image upload function |
502
- | `stateCalculators` | `StateCalculator[]` | `[]` | Custom reactive state logic |
503
- | `tiptapExtensions` | `(Extension \| Node \| Mark)[]` | `[]` | Additional Tiptap extensions |
504
- | `tiptapOptions` | `Partial<EditorOptions>` | `{}` | Additional Tiptap editor options |
505
-
506
-
507
- #### Outputs
508
-
509
- | Output | Type | Description |
510
- | --------------- | ----------------- | ------------------------------- |
511
- | `contentChange` | `string` | Emitted when content changes |
512
- | `editorCreated` | `Editor` | Emitted when editor is created |
513
- | `editorUpdate` | `{editor, trans}` | Emitted on every editor update |
514
- | `editorFocus` | `{editor, event}` | Emitted when editor gains focus |
515
- | `editorBlur` | `{editor, event}` | Emitted when editor loses focus |
516
-
517
-
518
- ## 🌍 Internationalization
519
-
520
- The editor comes with built-in support for **English (en)** and **French (fr)**, featuring automatic browser language detection.
521
-
522
- ### Basic Usage
523
-
524
- ```typescript
525
- // Force a specific language
526
- <angular-tiptap-editor [locale]="'fr'" />
527
-
528
- // Auto-detect (default)
529
- <angular-tiptap-editor />
530
- ```
531
-
532
- ### Adding Custom Languages
533
-
534
- You can easily extend the editor with new languages or override existing labels using the `TiptapI18nService`:
535
-
536
- ```typescript
537
- import { TiptapI18nService } from "@flogeez/angular-tiptap-editor";
538
-
539
- @Component({ ... })
540
- export class MyComponent {
541
- constructor(private i18nService: TiptapI18nService) {
542
- // Add Spanish support
543
- this.i18nService.addTranslations('es', {
544
- toolbar: { bold: 'Negrita', italic: 'Cursiva', ... },
545
- editor: { placeholder: 'Empieza a escribir...' }
546
- });
547
-
548
- // Switch to Spanish
549
- this.i18nService.setLocale('es');
550
- }
551
- }
552
- ```
553
-
554
-
555
- ### 🎨 CSS Custom Properties
556
-
557
- Customize the editor appearance using CSS variables with the `--ate-` prefix:
558
-
559
- ```css
560
- /* In your global styles or component styles */
561
- angular-tiptap-editor {
562
- --ate-primary: #2563eb;
563
- --ate-primary-contrast: #ffffff;
564
- --ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);
565
- --ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);
566
- --ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);
567
-
568
- --ate-surface: #ffffff;
569
- --ate-surface-secondary: #f8f9fa;
570
- --ate-surface-tertiary: #f1f5f9;
571
-
572
- --ate-text: #2d3748;
573
- --ate-text-secondary: #64748b;
574
- --ate-text-muted: #a0aec0;
575
-
576
- --ate-border: #e2e8f0;
577
-
578
- /* And More... */
579
- }
580
- ```
581
-
582
- #### Dark Mode Support
583
-
584
- The editor supports dark mode in two ways:
585
-
586
- **1. With CSS Class**
587
-
588
- ```html
589
- <angular-tiptap-editor [class.dark]="isDarkMode" />
590
- ```
591
-
592
- **2. With Data Attribute**
593
-
594
- ```html
595
- <angular-tiptap-editor [attr.data-theme]="isDarkMode ? 'dark' : null" />
596
- ```
597
-
598
- #### Example: Custom Dark Theme
599
-
600
- ```css
601
- angular-tiptap-editor.dark {
602
- --ate-background: #1a1a2e;
603
- --ate-border-color: #3d3d5c;
604
- --ate-focus-color: #6366f1;
605
- --ate-text-color: #e2e8f0;
606
- --ate-placeholder-color: #64748b;
607
- --ate-counter-background: #2d2d44;
608
- --ate-counter-color: #94a3b8;
609
- --ate-blockquote-background: #2d2d44;
610
- --ate-code-background: #2d2d44;
611
- }
612
- ```
613
-
614
- #### Example: Custom Brand Colors
615
-
616
- ```css
617
- angular-tiptap-editor {
618
- --ate-focus-color: #8b5cf6;
619
- --ate-image-selected-color: #8b5cf6;
620
- --ate-border-radius: 12px;
621
- }
622
- ```
623
-
624
-
625
- ### ⚡ Reactive State & OnPush
626
-
627
- The library exposes a reactive `editorState` signal via the `EditorCommandsService`. This signal contains everything you need to build custom UIs around the editor:
628
-
629
- - **Active State**: Check if `bold`, `italic`, or custom marks are active.
630
- - **Commands Availability**: Check if `undo`, `redo`, or custom commands can be executed.
631
- - **Structural Data**: Access table status, image attributes, or selection details.
632
-
633
- Since it's built with Signals, your custom toolbar items or UI overlays will only re-render when the specific data they consume changes, making it extremely efficient for `OnPush` applications.
634
-
635
- ---
636
-
637
- ### 🧩 Custom Tiptap Extensions
638
-
639
- You are not limited to the built-in extensions. Pass any Tiptap extension, mark, or node:
640
-
641
- ```html
642
- <angular-tiptap-editor [tiptapExtensions]="[MyCustomExtension]" />
643
- ```
644
-
645
- Any custom extension is automatically detected and its state (active/can) is added to the reactive `editorState` snapshot.
646
-
647
- ---
648
-
649
- ## 🏗️ Architecture
650
-
651
- ### Reactive State Management
652
-
653
- The library uses a **Snapshot & Signal** pattern to bridge Tiptap and Angular.
654
-
655
- 1. **State Snapshot**: Every editor transaction triggers a set of "Calculators" that produce a single immutable state object.
656
- 2. **Specialized Calculators**: Logic is modularized into specialized functions (Marks, Table, Image, etc.) and a **Discovery Calculator** for automatic extension detection.
657
- 3. **Signals Integration**: This snapshot is stored in a single Angular Signal. Sub-components (toolbar, menus) consume this signal only where needed.
658
- 4. **Change Detection Optimization**: A custom equality check on the signal prevents unnecessary re-renders when the visual state of the editor hasn't changed.
659
-
660
- ### Core Services
661
-
662
- - **`EditorCommandsService`**: Exposes the `editorState` signal and provides a centralized API for executing Tiptap commands.
663
- - **`ImageService`**: Manages the image processing pipeline (selection, compression, and server-side upload handling).
664
- - **`TiptapI18nService`**: Reactive translation service with support for browser locale auto-detection.
665
-
666
- ### Isolated Instances
667
-
668
- Each component instance provides its own set of services (`EditorCommandsService`, `ImageService`, etc.) at the component level. This ensures that multiple editors on the same page maintain independent states and configurations without interference.
669
-
670
- ### Modern Angular Integration
671
-
672
- - **Signals**: Native reactivity for efficient UI updates.
673
- - **OnPush**: Designed for `ChangeDetectionStrategy.OnPush` throughout.
674
- - **Typed State**: Fully typed interfaces for the editor state and configurations.
675
-
676
- ### Default Configurations
677
-
678
- The library provides default configurations that can be imported and customized:
679
-
680
- ```typescript
681
- import {
682
- DEFAULT_TOOLBAR_CONFIG,
683
- DEFAULT_BUBBLE_MENU_CONFIG,
684
- DEFAULT_IMAGE_BUBBLE_MENU_CONFIG,
685
- DEFAULT_TABLE_MENU_CONFIG,
686
- SLASH_COMMAND_KEYS,
687
- } from "@flogeez/angular-tiptap-editor";
688
- ```
689
-
690
- ## 🔧 Development
691
-
692
- ### Build Library
693
-
694
- ```bash
695
- npm run build:lib
696
- ```
697
-
698
- ### Watch Mode (Development)
699
-
700
- ```bash
701
- npm run dev
702
- ```
703
-
704
- This runs the library in watch mode and starts the demo application.
705
-
706
- ### Available Scripts
707
-
708
- - `npm start` - Start demo application
709
- - `npm run build` - Build demo application
710
- - `npm run build:lib` - Build library
711
- - `npm run watch:lib` - Watch library changes
712
- - `npm run dev` - Development mode (watch + serve)
713
-
714
-
715
- ## 📝 License
716
-
717
- MIT License - see [LICENSE](LICENSE) file for details.
718
-
719
- ## 🤝 Contributing
720
-
721
- Contributions are welcome! Please feel free to submit a Pull Request.
722
-
723
- ## 🔗 Links
724
-
725
- - 📖 [Tiptap Documentation](https://tiptap.dev/)
726
- - 🅰️ [Angular Documentation](https://angular.dev/)
727
- - 📦 [NPM Package](https://www.npmjs.com/package/@flogeez/angular-tiptap-editor)
728
- - 📖 [Live Demo](https://flogeez.github.io/angular-tiptap-editor/)
729
- - 🐛 [Report Issues](https://github.com/FloGeez/angular-tiptap-editor/issues)
730
- - 💡 [Feature Requests](https://github.com/FloGeez/angular-tiptap-editor/issues)
731
-
732
- ## 🆕 What's New
733
-
734
- ### Latest Updates
735
-
736
- - **Reactive State & Signals**: Optimized state management for a faster, smoother experience.
737
- - **Zero-Config Extensions**: Custom Tiptap Marks and Nodes are tracked automatically.
738
- - **Multi-Instance Support**: Use multiple independent editors on the same page without state leaks.
739
- - ✅ **Clean Service Architecture**: Decoupled configurations and isolated services for better stability.
740
- - **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
741
-
742
- ---
743
-
744
- Made with ❤️ by [FloGeez](https://github.com/FloGeez)
1
+ # Angular Tiptap Editor
2
+
3
+ A modern, customizable rich-text editor for Angular applications, built with Tiptap and featuring complete internationalization support.
4
+
5
+ [![Demo](https://img.shields.io/badge/Demo-Live-brightgreen?style=for-the-badge&logo=google-chrome)](https://flogeez.github.io/angular-tiptap-editor/) [![Try it on StackBlitz](https://img.shields.io/badge/Try%20it-StackBlitz-blue?style=for-the-badge&logo=stackblitz)](https://stackblitz.com/edit/angular-tiptap-editor)
6
+
7
+ ## 🚀 Features
8
+
9
+ - **Modern Angular**: Built with Angular 18+ using Signals and modern patterns for peak performance.
10
+ - **Full Rich Text Power**: Powered by Tiptap v2 with extensive formatting and block capabilities.
11
+ - **Modern UX (Notion-like)**: Intuitive slash commands and bubble menus for a keyboard-first experience.
12
+ - **Highly Customizable**: Easily configure toolbars, bubble menus, and slash command items.
13
+ - **Signal-Based Reactivity**: Pure Signal architecture natively compatible with `ChangeDetectionStrategy.OnPush`.
14
+ - **Advanced Table Support**: Full table management with cell selection and context-aware bubble menus.
15
+ - **Professional Media**: Advanced image handling with resizing, auto-compression, and custom uploaders.
16
+ - **Built-in i18n**: English & French support with a reactive, extensible locale system.
17
+ - **Word/Character Count**: Real-time statistics with proper pluralization support.
18
+ - **Office-Ready**: Cleaned-up pasting from Microsoft Word and Excel to maintain layout integrity.
19
+ - **Service Driven**: Deep programmatic control via `EditorCommandsService` and isolated instances.
20
+ - **A11y First**: Built with accessibility best practices and full keyboard navigation.
21
+
22
+ ## 💎 Why this editor?
23
+
24
+ Most Angular wrappers for Tiptap provide a basic component but leave the heavy lifting to you. **Angular Tiptap Editor** is built to solve common production hurdles:
25
+
26
+ - **True Scalability**: Thanks to **isolated services** provided at the component level, you can host multiple independent editors with different configurations and languages on the same page without a single state leak.
27
+ - **OnPush by Default**: The entire UI (toolbar, menus) is powered by **Angular Signals**. The `editorState` snapshot logic ensures that your components only re-render when necessary, even in complex `OnPush` applications.
28
+ - **Deep i18n & Extensibility**: Not just English/French — you can inject **custom translations** and **custom Tiptap extensions**. Our `DiscoveryCalculator` automatically tracks any new mark or node you add, making them reactive without extra code.
29
+ - **Clean Office UX**: Professional-grade pasting from **Word and Excel** plus smart image handling (auto-compression, resizing handles) ensures a polished experience for end-users.
30
+
31
+ ## 🛠️ Extensions included
32
+
33
+ The library comes with a pre-configured set of standard and custom extensions:
34
+
35
+ - **Nodes**: `StarterKit`, `Heading`, `Table`, `Image`, `HorizontalRule`, `CodeBlock`.
36
+ - **Marks**: `Bold`, `Italic`, `Underline`, `Strike`, `Code`, `Link`, `Highlight`, `TextStyle`, `Color`, `Superscript`, `Subscript`.
37
+ - **Utilities**: `Placeholder`, `CharacterCount`, `Typography`, `Focus`, `BubbleMenu`, `Gapcursor`, `Dropcursor`, `ResizableImage` (Custom).
38
+
39
+ ## 📦 Installation
40
+
41
+ ```bash
42
+ npm install @flogeez/angular-tiptap-editor
43
+ ```
44
+
45
+ ### CSS Styles
46
+
47
+ Add the required CSS to your `angular.json` file in the `styles` array:
48
+
49
+ ```json
50
+ {
51
+ "styles": [
52
+ ...
53
+ "node_modules/@fontsource/material-symbols-outlined/index.css",
54
+ "node_modules/@flogeez/angular-tiptap-editor/src/lib/styles/index.css",
55
+ ...
56
+ ]
57
+ }
58
+ ```
59
+
60
+ ## 🎯 Quick Start
61
+
62
+ ### 1. Basic Usage
63
+
64
+ ```typescript
65
+ import { Component } from "@angular/core";
66
+ import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
67
+
68
+ @Component({
69
+ selector: "app-example",
70
+ standalone: true,
71
+ imports: [AngularTiptapEditorComponent],
72
+ template: `
73
+ <angular-tiptap-editor
74
+ [content]="content"
75
+ (contentChange)="onContentChange($event)"
76
+ />
77
+ `,
78
+ })
79
+ export class ExampleComponent {
80
+ content = "<p>Hello <strong>World</strong>!</p>";
81
+
82
+ onContentChange(newContent: string) {
83
+ this.content = newContent;
84
+ console.log("Content updated:", newContent);
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### 2. With Custom Configuration
90
+
91
+ The editor can be fully configured using a single `[config]` object, which provides a clean and type-safe way to manage all settings.
92
+
93
+ ```typescript
94
+ import { Component } from "@angular/core";
95
+ import {
96
+ AngularTiptapEditorComponent,
97
+ AteEditorConfig,
98
+ DEFAULT_TOOLBAR_CONFIG,
99
+ } from "@flogeez/angular-tiptap-editor";
100
+
101
+ @Component({
102
+ selector: "app-advanced",
103
+ standalone: true,
104
+ imports: [AngularTiptapEditorComponent],
105
+ template: `
106
+ <angular-tiptap-editor
107
+ [content]="content"
108
+ [config]="editorConfig"
109
+ (contentChange)="onContentChange($event)"
110
+ />
111
+ `,
112
+ })
113
+ export class AdvancedComponent {
114
+ content = "<h1>Welcome!</h1><p>Start editing...</p>";
115
+
116
+ editorConfig: AteEditorConfig = {
117
+ locale: "fr", // Force French (default is auto-detect)
118
+ height: "400px", // Set a fixed height
119
+ placeholder: "Commencez à rédiger...",
120
+ showWordCount: false, // Hide the word counter (default is true)
121
+ showEditToggle: true, // Show the button to toggle read-only mode (default is false)
122
+
123
+ // Customize the toolbar by enabling specific features
124
+ toolbar: {
125
+ ...DEFAULT_TOOLBAR_CONFIG,
126
+ clear: true, // Enable the 'Clear' button
127
+ highlight: false, // Disable the highlight button
128
+ },
129
+
130
+ // Only enable specific slash commands
131
+ slashCommands: {
132
+ heading1: true,
133
+ heading2: true,
134
+ image: true,
135
+ table: true,
136
+ },
137
+ };
138
+
139
+ onContentChange(newContent: string) {
140
+ this.content = newContent;
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### 3. Registering Custom Extensions
146
+
147
+ Easily extend the editor with any standard Tiptap extension or your own custom marks/nodes via the `tiptapExtensions` input.
148
+
149
+ ```typescript
150
+ import { Component } from "@angular/core";
151
+ import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
152
+
153
+ @Component({
154
+ selector: "app-custom-extensions",
155
+ standalone: true,
156
+ imports: [AngularTiptapEditorComponent],
157
+ template: `
158
+ <angular-tiptap-editor
159
+ [content]="content"
160
+ [tiptapExtensions]="extensions"
161
+ (contentChange)="content = $event"
162
+ />
163
+ `,
164
+ })
165
+ export class CustomExtensionsComponent {
166
+ content = "<p>Custom extensions example</p>";
167
+
168
+ extensions = [
169
+ // Add your custom TipTap extensions here
170
+ // Example: Custom extension configuration
171
+ // MyCustomExtension.configure({ /* options */ })
172
+ ];
173
+ }
174
+ ```
175
+
176
+ ### 4. With Form Integration
177
+
178
+ ```typescript
179
+ import { Component } from "@angular/core";
180
+ import { FormControl, ReactiveFormsModule } from "@angular/forms";
181
+ import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
182
+
183
+ @Component({
184
+ selector: "app-form",
185
+ standalone: true,
186
+ imports: [AngularTiptapEditorComponent, ReactiveFormsModule],
187
+ template: `
188
+ <form>
189
+ <angular-tiptap-editor
190
+ [formControl]="contentControl"
191
+ placeholder="Enter your content here..."
192
+ [showCharacterCount]="true"
193
+ [showWordCount]="true"
194
+ />
195
+ <button type="submit">Submit</button>
196
+ </form>
197
+ `,
198
+ })
199
+ export class FormComponent {
200
+ contentControl = new FormControl("<p>Initial content</p>");
201
+ }
202
+ ```
203
+
204
+ ### 5. Using EditorCommandsService
205
+
206
+ ```typescript
207
+ import { Component, inject } from "@angular/core";
208
+ import { EditorCommandsService } from "@flogeez/angular-tiptap-editor";
209
+
210
+ @Component({
211
+ selector: "app-commands",
212
+ standalone: true,
213
+ template: `
214
+ <div>
215
+ <div class="controls">
216
+ <button (click)="clearContent()">Clear Content</button>
217
+ <button (click)="focusEditor()">Focus Editor</button>
218
+ <button (click)="setContent()">Set Content</button>
219
+ </div>
220
+
221
+ <angular-tiptap-editor (editorCreated)="onEditorCreated($event)" />
222
+ </div>
223
+ `,
224
+ })
225
+ export class CommandsComponent {
226
+ private editorCommandsService = inject(EditorCommandsService);
227
+ private editor: Editor | null = null;
228
+
229
+ onEditorCreated(editor: Editor) {
230
+ this.editor = editor;
231
+ }
232
+
233
+ clearContent() {
234
+ if (this.editor) {
235
+ this.editorCommandsService.clearContent(this.editor);
236
+ }
237
+ }
238
+
239
+ focusEditor() {
240
+ if (this.editor) {
241
+ this.editorCommandsService.focus(this.editor);
242
+ }
243
+ }
244
+
245
+ setContent() {
246
+ if (this.editor) {
247
+ this.editorCommandsService.setContent(
248
+ this.editor,
249
+ "<h1>New Content</h1>",
250
+ );
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ ### 6. Extending Reactive Editor State
257
+
258
+ The editor features a dual-layer state architecture: **Automatic Tracking** for simple extensions and **Custom Calculators** for advanced needs.
259
+
260
+ #### A. Automatic Extension Tracking (Zero Config)
261
+
262
+ Any TipTap **Mark** or **Node** you add to `tiptapExtensions` is automatically tracked by our `DiscoveryCalculator`. You don't need to write any extra code to make them reactive.
263
+
264
+ - **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
265
+ - **For Nodes**: `state().nodes.yourExtensionName` (boolean).
266
+
267
+ #### B. Custom State Calculators (Advanced)
268
+
269
+ If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
270
+
271
+ 1. **Define a Calculator**:
272
+
273
+ ```typescript
274
+ import { StateCalculator } from "@flogeez/angular-tiptap-editor";
275
+
276
+ // This function will be called on every editor update
277
+ export const MyCustomCalculator: StateCalculator = (editor) => {
278
+ return {
279
+ custom: {
280
+ hasHighPriority: editor.isActive("priority"),
281
+ selectionDepth: editor.state.selection.$from.depth,
282
+ // Any data you need...
283
+ },
284
+ };
285
+ };
286
+ ```
287
+
288
+ 2. **Register it in the Template**:
289
+
290
+ ```html
291
+ <angular-tiptap-editor [stateCalculators]="[MyCustomCalculator]" />
292
+ ```
293
+
294
+ 3. **Consume it anywhere**:
295
+
296
+ ```typescript
297
+ @Component({ ... })
298
+ export class MyToolbarComponent {
299
+ private editorCommands = inject(EditorCommandsService);
300
+
301
+ // Access your custom data reactively!
302
+ isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
303
+ }
304
+ ```
305
+
306
+ ## Key Features
307
+
308
+ ### 📊 Table Management
309
+
310
+ Full table support with intuitive bubble menus:
311
+
312
+ - **Table Creation**: Insert tables via slash commands (`/table`)
313
+ - **Cell Selection**: Click and drag to select multiple cells
314
+ - **Bubble Menus**: Context-aware menus for table operations
315
+ - **Row/Column Management**: Add, remove, and merge cells
316
+ - **Styling**: Custom table styling with proper borders
317
+
318
+ ### Slash Commands
319
+
320
+ Quick content insertion with slash commands:
321
+
322
+ - **Headings**: `/h1`, `/h2`, `/h3`
323
+ - **Lists**: `/bullet`, `/numbered`
324
+ - **Blocks**: `/quote`, `/code`, `/line`
325
+ - **Media**: `/image`, `/table`
326
+ - **Fully Internationalized**: All commands translated
327
+
328
+ #### Custom Slash Commands
329
+
330
+ The `slashCommands` object also allows you to add completely custom command items:
331
+
332
+ ```typescript
333
+ import { SlashCommandsConfig } from "@flogeez/angular-tiptap-editor";
334
+
335
+ slashCommands: SlashCommandsConfig = {
336
+ // Toggle native commands
337
+ heading1: true,
338
+ image: false,
339
+ // Add custom ones
340
+ custom: [
341
+ {
342
+ title: "Magic Action",
343
+ description: "Insert some AI magic",
344
+ icon: "auto_fix",
345
+ keywords: ["magic", "ai"],
346
+ command: (editor) => editor.commands.insertContent("✨ Magic happened!"),
347
+ },
348
+ ],
349
+ };
350
+ ```
351
+
352
+ ### 🖼️ Advanced Image Handling
353
+
354
+ Professional image management:
355
+
356
+ - **Drag & Drop**: Drag images directly into the editor
357
+ - **File Selection**: Click to select images from device
358
+ - **Auto-Compression**: Images automatically compressed (max 1920x1080)
359
+ - **Resizable**: Images can be resized with handles
360
+ - **Bubble Menu**: Context menu for image operations
361
+ - **Custom Upload Handler**: Upload images to your own server instead of base64
362
+
363
+ #### Custom Image Upload Handler
364
+
365
+ By default, images are converted to base64 and embedded directly in the HTML content. You can provide a custom upload handler to upload images to your own server (S3, Cloudinary, custom API, etc.) and use the returned URL instead.
366
+
367
+ The handler can return either an **Observable** or a **Promise**.
368
+
369
+ #### Using Observable (recommended for Angular)
370
+
371
+ ```typescript
372
+ import { Component, inject } from "@angular/core";
373
+ import { HttpClient } from "@angular/common/http";
374
+ import { map } from "rxjs/operators";
375
+ import {
376
+ AngularTiptapEditorComponent,
377
+ ImageUploadHandler,
378
+ } from "@flogeez/angular-tiptap-editor";
379
+
380
+ @Component({
381
+ selector: "app-custom-upload",
382
+ standalone: true,
383
+ imports: [AngularTiptapEditorComponent],
384
+ template: `
385
+ <angular-tiptap-editor
386
+ [content]="content"
387
+ [imageUploadHandler]="uploadHandler"
388
+ (contentChange)="onContentChange($event)"
389
+ />
390
+ `,
391
+ })
392
+ export class CustomUploadComponent {
393
+ private http = inject(HttpClient);
394
+ content = "";
395
+
396
+ uploadHandler: ImageUploadHandler = (ctx) => {
397
+ const formData = new FormData();
398
+ formData.append("image", ctx.file);
399
+
400
+ return this.http
401
+ .post<{ url: string }>("/api/upload", formData)
402
+ .pipe(map((result) => ({ src: result.url })));
403
+ };
404
+
405
+ onContentChange(newContent: string) {
406
+ this.content = newContent;
407
+ }
408
+ }
409
+ ```
410
+
411
+ #### Using Promise (async/await)
412
+
413
+ ```typescript
414
+ uploadHandler: ImageUploadHandler = async (ctx) => {
415
+ const formData = new FormData();
416
+ formData.append("image", ctx.file);
417
+
418
+ const result = await firstValueFrom(
419
+ this.http.post<{ url: string }>("/api/upload", formData),
420
+ );
421
+
422
+ return { src: result.url };
423
+ };
424
+ ```
425
+
426
+ The `ImageUploadContext` provides:
427
+
428
+ - `file`: The original File object
429
+ - `width`: Processed image width
430
+ - `height`: Processed image height
431
+ - `type`: MIME type (e.g., 'image/jpeg')
432
+ - `base64`: Base64 data URL of the processed image (fallback)
433
+
434
+ The handler must return an `ImageUploadHandlerResult` with at least a `src` property containing the image URL.
435
+
436
+ ---
437
+
438
+ ### 📝 Word & Character Counting
439
+
440
+ Real-time content statistics:
441
+
442
+ - **Live Updates**: Counters update as you type
443
+ - **Proper Pluralization**: "1 word" vs "2 words"
444
+ - **Separate Counts**: Independent word and character counts
445
+ - **Configurable**: Show/hide individual counters
446
+
447
+ ## 🎨 Demo
448
+
449
+ ### 🌐 Live Demo
450
+
451
+ Try the interactive demo online: **[https://flogeez.github.io/angular-tiptap-editor/](https://flogeez.github.io/angular-tiptap-editor/)**
452
+
453
+ ### 🖥️ Run Locally
454
+
455
+ ```bash
456
+ git clone https://github.com/FloGeez/angular-tiptap-editor.git
457
+ cd angular-tiptap-editor
458
+ npm install
459
+ npm start
460
+ ```
461
+
462
+ Open [http://localhost:4200](http://localhost:4200) to view the demo.
463
+
464
+ ## 📖 Documentation
465
+
466
+ ### API Reference
467
+
468
+ #### Inputs
469
+
470
+ | Input | Type | Default | Description |
471
+ | --------------------- | ------------------------------------------------ | ------------------- | --------------------------------------------- |
472
+ | `config` | `AteEditorConfig` | `{}` | **Global configuration object** (Recommended) |
473
+ | `content` | `string` | `""` | Initial HTML content |
474
+ | `placeholder` | `string` | `"Start typing..."` | Placeholder text (overrides config) |
475
+ | `locale` | `'en' \| 'fr'` | Auto-detect | Editor language (overrides config) |
476
+ | `editable` | `boolean` | `true` | Whether editor is editable |
477
+ | `height` | `string` | `undefined` | Editor height (e.g. '400px', 'auto') |
478
+ | `maxHeight` | `string` | `undefined` | Maximum height (e.g. '80vh') |
479
+ | `minHeight` | `string` | `undefined` | Minimum height |
480
+ | `maxCharacters` | `number` | `undefined` | Character limit |
481
+ | `fillContainer` | `boolean` | `false` | Fill parent container height |
482
+ | `autofocus` | `boolean \| 'start' \| 'end' \| 'all' \| number` | `false` | Auto-focus behavior |
483
+ | `disabled` | `boolean` | `false` | Disabled state (for forms) |
484
+ | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
485
+ | `showToolbar` | `boolean` | `true` | Show toolbar |
486
+ | `showFooter` | `boolean` | `true` | Show footer (counters) |
487
+ | `showBubbleMenu` | `boolean` | `true` | Show text bubble menu |
488
+ | `showImageBubbleMenu` | `boolean` | `true` | Show image bubble menu |
489
+ | `showTableMenu` | `boolean` | `true` | Show table bubble menu |
490
+ | `showCellMenu` | `boolean` | `true` | Show cell bubble menu |
491
+ | `enableSlashCommands` | `boolean` | `true` | Enable slash commands functionality |
492
+ | `enableOfficePaste` | `boolean` | `true` | Enable smart Office pasting |
493
+ | `showCharacterCount` | `boolean` | `true` | Show character counter |
494
+ | `showWordCount` | `boolean` | `true` | Show word counter |
495
+ | `toolbar` | `ToolbarConfig` | All enabled | Detailed toolbar configuration |
496
+ | `bubbleMenu` | `BubbleMenuConfig` | All enabled | Detailed bubble menu configuration |
497
+ | `slashCommands` | `SlashCommandsConfig` | All enabled | Detailed slash commands config |
498
+ | `imageUploadHandler` | `ImageUploadHandler` | `undefined` | Custom image upload function |
499
+ | `stateCalculators` | `StateCalculator[]` | `[]` | Custom reactive state logic |
500
+ | `tiptapExtensions` | `(Extension \| Node \| Mark)[]` | `[]` | Additional Tiptap extensions |
501
+ | `tiptapOptions` | `Partial<EditorOptions>` | `{}` | Additional Tiptap editor options |
502
+
503
+ > **Note on Precedence**: Values provided in individual inputs (e.g., `[editable]="false"`) always take precedence over values defined inside the `[config]` object.
504
+
505
+ #### AteEditorConfig Reference
506
+
507
+ The `AteEditorConfig` nested structure allows for complex configurations while remaining flat for core settings:
508
+
509
+ ```typescript
510
+ export interface AteEditorConfig {
511
+ // Core Settings
512
+ theme?: "light" | "dark" | "auto";
513
+ height?: string;
514
+ minHeight?: string;
515
+ maxHeight?: string;
516
+ fillContainer?: boolean;
517
+ autofocus?: "start" | "end" | "all" | boolean | number;
518
+ placeholder?: string;
519
+ editable?: boolean;
520
+ disabled?: boolean;
521
+ locale?: string;
522
+ spellcheck?: boolean;
523
+ enableOfficePaste?: boolean;
524
+
525
+ // Visibility Options
526
+ showToolbar?: boolean;
527
+ showFooter?: boolean;
528
+ showCharacterCount?: boolean;
529
+ showWordCount?: boolean;
530
+ showEditToggle?: boolean;
531
+ showBubbleMenu?: boolean;
532
+ showImageBubbleMenu?: boolean;
533
+ showTableMenu?: boolean;
534
+ showCellMenu?: boolean;
535
+ enableSlashCommands?: boolean;
536
+ maxCharacters?: number;
537
+
538
+ // Complex Modules
539
+ toolbar?: ToolbarConfig;
540
+ bubbleMenu?: BubbleMenuConfig;
541
+ imageBubbleMenu?: ImageBubbleMenuConfig;
542
+ tableBubbleMenu?: TableBubbleMenuConfig;
543
+ cellBubbleMenu?: CellBubbleMenuConfig;
544
+ slashCommands?: SlashCommandsConfig;
545
+ imageUpload?: AteImageUploadConfig;
546
+ }
547
+ ```
548
+
549
+ #### Image Upload Configuration
550
+
551
+ The `imageUpload` property in `AteEditorConfig` provides fine-grained control over the image processing pipeline:
552
+
553
+ ```typescript
554
+ export interface AteImageUploadConfig {
555
+ /** Custom handler to upload files to a server */
556
+ handler?: ImageUploadHandler;
557
+ /** Maximum file size in bytes (default: 10MB) */
558
+ maxFileSize?: number;
559
+ /** Accepted file types (default: 'image/*') */
560
+ accept?: string;
561
+ /** Whether to automatically compress images before upload (default: true) */
562
+ autoCompress?: boolean;
563
+ }
564
+ ```
565
+
566
+ #### Outputs
567
+
568
+ | Output | Type | Description |
569
+ | --------------- | ----------------- | ------------------------------- |
570
+ | `contentChange` | `string` | Emitted when content changes |
571
+ | `editorCreated` | `Editor` | Emitted when editor is created |
572
+ | `editorUpdate` | `{editor, trans}` | Emitted on every editor update |
573
+ | `editorFocus` | `{editor, event}` | Emitted when editor gains focus |
574
+ | `editorBlur` | `{editor, event}` | Emitted when editor loses focus |
575
+
576
+ ## 🌍 Internationalization
577
+
578
+ The editor comes with built-in support for **English (en)** and **French (fr)**, featuring automatic browser language detection.
579
+
580
+ ### Basic Usage
581
+
582
+ ```typescript
583
+ // Force a specific language
584
+ <angular-tiptap-editor [locale]="'fr'" />
585
+
586
+ // Auto-detect (default)
587
+ <angular-tiptap-editor />
588
+ ```
589
+
590
+ ### Adding Custom Languages
591
+
592
+ You can easily extend the editor with new languages or override existing labels using the `TiptapI18nService`:
593
+
594
+ ```typescript
595
+ import { TiptapI18nService } from "@flogeez/angular-tiptap-editor";
596
+
597
+ @Component({ ... })
598
+ export class MyComponent {
599
+ constructor(private i18nService: TiptapI18nService) {
600
+ // Add Spanish support
601
+ this.i18nService.addTranslations('es', {
602
+ toolbar: { bold: 'Negrita', italic: 'Cursiva', ... },
603
+ editor: { placeholder: 'Empieza a escribir...' }
604
+ });
605
+
606
+ // Switch to Spanish
607
+ this.i18nService.setLocale('es');
608
+ }
609
+ }
610
+ ```
611
+
612
+ ### 🎨 CSS Custom Properties
613
+
614
+ Customize the editor appearance using CSS variables with the `--ate-` prefix:
615
+
616
+ ```css
617
+ /* In your global styles or component styles */
618
+ angular-tiptap-editor {
619
+ --ate-primary: #2563eb;
620
+ --ate-primary-contrast: #ffffff;
621
+ --ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);
622
+ --ate-primary-lighter: color-mix(
623
+ in srgb,
624
+ var(--ate-primary),
625
+ transparent 95%
626
+ );
627
+ --ate-primary-light-alpha: color-mix(
628
+ in srgb,
629
+ var(--ate-primary),
630
+ transparent 85%
631
+ );
632
+
633
+ --ate-surface: #ffffff;
634
+ --ate-surface-secondary: #f8f9fa;
635
+ --ate-surface-tertiary: #f1f5f9;
636
+
637
+ --ate-text: #2d3748;
638
+ --ate-text-secondary: #64748b;
639
+ --ate-text-muted: #a0aec0;
640
+
641
+ --ate-border: #e2e8f0;
642
+
643
+ /* And More... */
644
+ }
645
+ ```
646
+
647
+ #### Dark Mode Support
648
+
649
+ The editor supports dark mode in two ways:
650
+
651
+ **1. With CSS Class**
652
+
653
+ ```html
654
+ <angular-tiptap-editor [class.dark]="isDarkMode" />
655
+ ```
656
+
657
+ **2. With Data Attribute**
658
+
659
+ ```html
660
+ <angular-tiptap-editor [attr.data-theme]="isDarkMode ? 'dark' : null" />
661
+ ```
662
+
663
+ #### Example: Custom Dark Theme
664
+
665
+ ```css
666
+ angular-tiptap-editor.dark {
667
+ --ate-background: #1a1a2e;
668
+ --ate-border-color: #3d3d5c;
669
+ --ate-focus-color: #6366f1;
670
+ --ate-text-color: #e2e8f0;
671
+ --ate-placeholder-color: #64748b;
672
+ --ate-counter-background: #2d2d44;
673
+ --ate-counter-color: #94a3b8;
674
+ --ate-blockquote-background: #2d2d44;
675
+ --ate-code-background: #2d2d44;
676
+ }
677
+ ```
678
+
679
+ #### Example: Custom Brand Colors
680
+
681
+ ```css
682
+ angular-tiptap-editor {
683
+ --ate-focus-color: #8b5cf6;
684
+ --ate-image-selected-color: #8b5cf6;
685
+ --ate-border-radius: 12px;
686
+ }
687
+ ```
688
+
689
+ ### ⚡ Reactive State & OnPush
690
+
691
+ The library exposes a reactive `editorState` signal via the `EditorCommandsService`. This signal contains everything you need to build custom UIs around the editor:
692
+
693
+ - **Active State**: Check if `bold`, `italic`, or custom marks are active.
694
+ - **Commands Availability**: Check if `undo`, `redo`, or custom commands can be executed.
695
+ - **Structural Data**: Access table status, image attributes, or selection details.
696
+
697
+ Since it's built with Signals, your custom toolbar items or UI overlays will only re-render when the specific data they consume changes, making it extremely efficient for `OnPush` applications.
698
+
699
+ ---
700
+
701
+ ### 🧩 Custom Tiptap Extensions
702
+
703
+ You are not limited to the built-in extensions. Pass any Tiptap extension, mark, or node:
704
+
705
+ ```html
706
+ <angular-tiptap-editor [tiptapExtensions]="[MyCustomExtension]" />
707
+ ```
708
+
709
+ Any custom extension is automatically detected and its state (active/can) is added to the reactive `editorState` snapshot.
710
+
711
+ ---
712
+
713
+ ## 🏗️ Architecture
714
+
715
+ ### Reactive State Management
716
+
717
+ The library uses a **Snapshot & Signal** pattern to bridge Tiptap and Angular.
718
+
719
+ 1. **State Snapshot**: Every editor transaction triggers a set of "Calculators" that produce a single immutable state object.
720
+ 2. **Specialized Calculators**: Logic is modularized into specialized functions (Marks, Table, Image, etc.) and a **Discovery Calculator** for automatic extension detection.
721
+ 3. **Signals Integration**: This snapshot is stored in a single Angular Signal. Sub-components (toolbar, menus) consume this signal only where needed.
722
+ 4. **Change Detection Optimization**: A custom equality check on the signal prevents unnecessary re-renders when the visual state of the editor hasn't changed.
723
+
724
+ ### Core Services
725
+
726
+ - **`EditorCommandsService`**: Exposes the `editorState` signal and provides a centralized API for executing Tiptap commands.
727
+ - **`ImageService`**: Manages the image processing pipeline (selection, compression, and server-side upload handling).
728
+ - **`TiptapI18nService`**: Reactive translation service with support for browser locale auto-detection.
729
+
730
+ ### Isolated Instances
731
+
732
+ Each component instance provides its own set of services (`EditorCommandsService`, `ImageService`, etc.) at the component level. This ensures that multiple editors on the same page maintain independent states and configurations without interference.
733
+
734
+ ### Modern Angular Integration
735
+
736
+ - **Signals**: Native reactivity for efficient UI updates.
737
+ - **OnPush**: Designed for `ChangeDetectionStrategy.OnPush` throughout.
738
+ - **Typed State**: Fully typed interfaces for the editor state and configurations.
739
+
740
+ ### Default Configurations
741
+
742
+ The library provides default configurations that can be imported and customized:
743
+
744
+ ```typescript
745
+ import {
746
+ DEFAULT_TOOLBAR_CONFIG,
747
+ DEFAULT_BUBBLE_MENU_CONFIG,
748
+ DEFAULT_IMAGE_BUBBLE_MENU_CONFIG,
749
+ DEFAULT_TABLE_MENU_CONFIG,
750
+ SLASH_COMMAND_KEYS,
751
+ } from "@flogeez/angular-tiptap-editor";
752
+ ```
753
+
754
+ ## 🔧 Development
755
+
756
+ ### Build Library
757
+
758
+ ```bash
759
+ npm run build:lib
760
+ ```
761
+
762
+ ### Watch Mode (Development)
763
+
764
+ ```bash
765
+ npm run dev
766
+ ```
767
+
768
+ This runs the library in watch mode and starts the demo application.
769
+
770
+ ### Available Scripts
771
+
772
+ - `npm start` - Start demo application
773
+ - `npm run build` - Build demo application
774
+ - `npm run build:lib` - Build library
775
+ - `npm run watch:lib` - Watch library changes
776
+ - `npm run dev` - Development mode (watch + serve)
777
+
778
+ ## 📝 License
779
+
780
+ MIT License - see [LICENSE](LICENSE) file for details.
781
+
782
+ ## 🤝 Contributing
783
+
784
+ Contributions are welcome! Please feel free to submit a Pull Request.
785
+
786
+ ## 🔗 Links
787
+
788
+ - 📖 [Tiptap Documentation](https://tiptap.dev/)
789
+ - 🅰️ [Angular Documentation](https://angular.dev/)
790
+ - 📦 [NPM Package](https://www.npmjs.com/package/@flogeez/angular-tiptap-editor)
791
+ - 📖 [Live Demo](https://flogeez.github.io/angular-tiptap-editor/)
792
+ - 🐛 [Report Issues](https://github.com/FloGeez/angular-tiptap-editor/issues)
793
+ - 💡 [Feature Requests](https://github.com/FloGeez/angular-tiptap-editor/issues)
794
+
795
+ ## 🆕 What's New
796
+
797
+ ### Latest Updates
798
+
799
+ - ✅ **Unified Configuration**: New unified `AteEditorConfig` system for cleaner, type-safe editor setup.
800
+ - ✅ **Enhanced Image Upload**: Advanced image handling with custom upload handlers and auto-compression.
801
+ - ✅ **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
802
+
803
+ ---
804
+
805
+ Made with ❤️ by [FloGeez](https://github.com/FloGeez)