@flogeez/angular-tiptap-editor 2.3.0 → 3.0.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.
@@ -1,26 +1,22 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject, viewChild, effect, Directive, DestroyRef, untracked, ApplicationRef, EnvironmentInjector, createComponent } from '@angular/core';
2
+ import { input, output, ChangeDetectionStrategy, Component, signal, computed, Injectable, inject, viewChild, effect, Directive, ApplicationRef, EnvironmentInjector, createComponent, InjectionToken, DestroyRef, Injector, untracked, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { Node as Node$1, nodeInputRule, mergeAttributes, Extension, getAttributes, Editor } from '@tiptap/core';
5
5
  import StarterKit from '@tiptap/starter-kit';
6
- import Placeholder from '@tiptap/extension-placeholder';
7
- import CharacterCount from '@tiptap/extension-character-count';
8
- import Underline from '@tiptap/extension-underline';
9
- import Superscript from '@tiptap/extension-superscript';
10
- import Subscript from '@tiptap/extension-subscript';
11
- import TextAlign from '@tiptap/extension-text-align';
12
- import Link from '@tiptap/extension-link';
13
- import Highlight from '@tiptap/extension-highlight';
14
- import TextStyle from '@tiptap/extension-text-style';
15
- import Color from '@tiptap/extension-color';
6
+ import { Placeholder, CharacterCount } from '@tiptap/extensions';
7
+ import { Underline } from '@tiptap/extension-underline';
8
+ import { Superscript } from '@tiptap/extension-superscript';
9
+ import { Subscript } from '@tiptap/extension-subscript';
10
+ import { TextAlign } from '@tiptap/extension-text-align';
11
+ import { Link } from '@tiptap/extension-link';
12
+ import { Highlight } from '@tiptap/extension-highlight';
13
+ import { TextStyle } from '@tiptap/extension-text-style';
14
+ import { Color } from '@tiptap/extension-color';
16
15
  import OfficePaste from '@intevation/tiptap-extension-office-paste';
17
16
  import { Plugin, PluginKey, TextSelection, NodeSelection } from '@tiptap/pm/state';
18
17
  import { DecorationSet, Decoration } from '@tiptap/pm/view';
19
- import Table from '@tiptap/extension-table';
20
- import TableRow from '@tiptap/extension-table-row';
21
- import TableCell from '@tiptap/extension-table-cell';
22
- import TableHeader from '@tiptap/extension-table-header';
23
- import { isObservable, firstValueFrom, concat, defer, of, tap, Subscription } from 'rxjs';
18
+ import { Table, TableRow, TableHeader, TableCell } from '@tiptap/extension-table';
19
+ import { isObservable, firstValueFrom, Subscription, concat, defer, of, tap } from 'rxjs';
24
20
  import { CommonModule } from '@angular/common';
25
21
  import tippy, { sticky } from 'tippy.js';
26
22
  import * as i1 from '@angular/forms';
@@ -1679,7 +1675,8 @@ class AteColorPickerService {
1679
1675
  let trigger;
1680
1676
  if (event && typeof event !== "string") {
1681
1677
  const target = event.target;
1682
- trigger = event.currentTarget || target?.closest("button") || target;
1678
+ trigger =
1679
+ event.currentTarget || target?.closest("button") || target;
1683
1680
  }
1684
1681
  this.open(mode, trigger);
1685
1682
  }
@@ -1883,7 +1880,9 @@ class AteLinkService {
1883
1880
  if (urlOrEvent && typeof urlOrEvent !== "string") {
1884
1881
  const target = urlOrEvent.target;
1885
1882
  trigger =
1886
- urlOrEvent.currentTarget || target?.closest("button") || target;
1883
+ urlOrEvent.currentTarget ||
1884
+ target?.closest("button") ||
1885
+ target;
1887
1886
  }
1888
1887
  // Open the edit menu
1889
1888
  this.open(trigger);
@@ -2375,7 +2374,7 @@ class AteEditorCommandsService {
2375
2374
  if (!editor) {
2376
2375
  return;
2377
2376
  }
2378
- editor.commands.setContent(content, emitUpdate);
2377
+ editor.commands.setContent(content, { emitUpdate });
2379
2378
  }
2380
2379
  setEditable(editor, editable) {
2381
2380
  if (!editor) {
@@ -3138,7 +3137,7 @@ class AteBaseBubbleMenu {
3138
3137
  content: this.menuRef().nativeElement,
3139
3138
  trigger: "manual",
3140
3139
  placement: "top-start",
3141
- appendTo: () => (ed ? ed.options.element : document.body),
3140
+ appendTo: () => ed?.options?.element || document.body,
3142
3141
  interactive: true,
3143
3142
  hideOnClick: false,
3144
3143
  arrow: false,
@@ -3151,7 +3150,7 @@ class AteBaseBubbleMenu {
3151
3150
  {
3152
3151
  name: "preventOverflow",
3153
3152
  options: {
3154
- boundary: this.editor().options.element,
3153
+ boundary: this.editor().options.element || document.body,
3155
3154
  padding: 8,
3156
3155
  },
3157
3156
  },
@@ -3808,7 +3807,9 @@ class AteTableBubbleMenuComponent extends AteBaseBubbleMenu {
3808
3807
  // 1. Direct ProseMirror approach: get DOM node at position
3809
3808
  const dom = ed.view.domAtPos(from).node;
3810
3809
  // Find closest table element
3811
- const tableElement = dom instanceof HTMLElement ? dom.closest("table") : dom.parentElement?.closest("table");
3810
+ const tableElement = dom instanceof HTMLElement
3811
+ ? dom.closest("table")
3812
+ : dom.parentElement?.closest("table");
3812
3813
  if (tableElement) {
3813
3814
  return tableElement.getBoundingClientRect();
3814
3815
  }
@@ -4209,7 +4210,7 @@ class AteBaseSubBubbleMenu {
4209
4210
  content: this.menuRef().nativeElement,
4210
4211
  trigger: "manual",
4211
4212
  placement: "bottom-start",
4212
- appendTo: () => ed.options.element,
4213
+ appendTo: () => ed?.options?.element || document.body,
4213
4214
  interactive: true,
4214
4215
  arrow: false,
4215
4216
  offset: [0, 8],
@@ -4221,7 +4222,10 @@ class AteBaseSubBubbleMenu {
4221
4222
  modifiers: [
4222
4223
  {
4223
4224
  name: "preventOverflow",
4224
- options: { boundary: ed.options.element, padding: 8 },
4225
+ options: {
4226
+ boundary: ed?.options?.element || document.body,
4227
+ padding: 8,
4228
+ },
4225
4229
  },
4226
4230
  {
4227
4231
  name: "flip",
@@ -5134,7 +5138,7 @@ class AteSlashCommandsComponent {
5134
5138
  {
5135
5139
  name: "preventOverflow",
5136
5140
  options: {
5137
- boundary: this.editor().options.element,
5141
+ boundary: this.editor().options.element || document.body,
5138
5142
  padding: 8,
5139
5143
  },
5140
5144
  },
@@ -5391,202 +5395,638 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5391
5395
  }]
5392
5396
  }] });
5393
5397
 
5394
- const AteSelectionCalculator = editor => {
5395
- const { selection } = editor.state;
5396
- const { from, to } = selection;
5397
- let selectionType = "none";
5398
- if (selection instanceof TextSelection) {
5399
- selectionType = "text";
5398
+ /**
5399
+ * Base abstract class for custom 'Angular Nodes'.
5400
+ *
5401
+ * Extend this class in your custom components to automatically receive TipTap editor
5402
+ * properties (node attributes, selection state, etc.) as reactive Signals.
5403
+ *
5404
+ * @example
5405
+ * ```typescript
5406
+ * @Component({
5407
+ * selector: 'app-counter',
5408
+ * template: `
5409
+ * <div>
5410
+ * <button (click)="increment()">Count: {{ node().attrs['count'] }}</button>
5411
+ * </div>
5412
+ * `
5413
+ * })
5414
+ * export class CounterComponent extends AteAngularNodeView {
5415
+ * increment() {
5416
+ * const count = this.node().attrs['count'] || 0;
5417
+ * this.updateAttributes({ count: count + 1 });
5418
+ * }
5419
+ * }
5420
+ * ```
5421
+ */
5422
+ class AteAngularNodeView {
5423
+ /**
5424
+ * Internal method to initialize the component with NodeView props.
5425
+ * This is called by the AngularNodeViewRenderer.
5426
+ *
5427
+ * @internal
5428
+ */
5429
+ _initNodeView(props) {
5430
+ // Create signals from the props
5431
+ const editorSignal = signal(props.editor, ...(ngDevMode ? [{ debugName: "editorSignal" }] : []));
5432
+ const nodeSignal = signal(props.node, ...(ngDevMode ? [{ debugName: "nodeSignal" }] : []));
5433
+ const decorationsSignal = signal(props.decorations, ...(ngDevMode ? [{ debugName: "decorationsSignal" }] : []));
5434
+ const selectedSignal = signal(props.selected, ...(ngDevMode ? [{ debugName: "selectedSignal" }] : []));
5435
+ const extensionSignal = signal(props.extension, ...(ngDevMode ? [{ debugName: "extensionSignal" }] : []));
5436
+ // Assign to the component
5437
+ this.editor = editorSignal.asReadonly();
5438
+ this.node = nodeSignal.asReadonly();
5439
+ this.decorations = decorationsSignal.asReadonly();
5440
+ this.selected = selectedSignal.asReadonly();
5441
+ this.extension = extensionSignal.asReadonly();
5442
+ this.getPos = props.getPos;
5443
+ this.updateAttributes = props.updateAttributes;
5444
+ this.deleteNode = props.deleteNode;
5445
+ // Store writable signals for updates
5446
+ this._writableSignals = {
5447
+ node: nodeSignal,
5448
+ decorations: decorationsSignal,
5449
+ selected: selectedSignal,
5450
+ };
5400
5451
  }
5401
- else if (selection instanceof NodeSelection) {
5402
- selectionType = "node";
5452
+ /**
5453
+ * Internal method to update the component when the node changes.
5454
+ * This is called by the AngularNodeViewRenderer.
5455
+ *
5456
+ * @internal
5457
+ */
5458
+ _updateNodeView(node, decorations) {
5459
+ if (this._writableSignals) {
5460
+ this._writableSignals.node.set(node);
5461
+ this._writableSignals.decorations.set(decorations);
5462
+ }
5403
5463
  }
5404
- else if (selection instanceof CellSelection) {
5405
- selectionType = "cell";
5464
+ /**
5465
+ * Internal method to update the selection state.
5466
+ * This is called by the AngularNodeViewRenderer.
5467
+ *
5468
+ * @internal
5469
+ */
5470
+ _selectNodeView() {
5471
+ if (this._writableSignals) {
5472
+ this._writableSignals.selected.set(true);
5473
+ }
5406
5474
  }
5407
- let isSingleCell = false;
5408
- if (selection instanceof CellSelection) {
5409
- isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
5475
+ /**
5476
+ * Internal method to update the deselection state.
5477
+ * This is called by the AngularNodeViewRenderer.
5478
+ *
5479
+ * @internal
5480
+ */
5481
+ _deselectNodeView() {
5482
+ if (this._writableSignals) {
5483
+ this._writableSignals.selected.set(false);
5484
+ }
5410
5485
  }
5411
- return {
5412
- isFocused: editor.view.hasFocus(),
5413
- isEditable: editor.isEditable,
5414
- selection: {
5415
- type: selectionType,
5416
- from,
5417
- to,
5418
- empty: selection.empty || (selectionType === "text" && from === to),
5419
- isSingleCell: isSingleCell,
5420
- },
5421
- nodes: {
5422
- activeNodeName: editor.state.doc.nodeAt(editor.state.selection.$head.pos)?.type.name || null,
5423
- },
5424
- };
5425
- };
5486
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
5487
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AteAngularNodeView, isStandalone: true, ngImport: i0 }); }
5488
+ }
5489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, decorators: [{
5490
+ type: Directive
5491
+ }] });
5426
5492
 
5427
- const AteMarksCalculator = editor => {
5428
- const isCodeBlock = editor.isActive("codeBlock");
5429
- const isCode = editor.isActive("code"); // Inline code mark
5430
- const isImage = editor.isActive("image") || editor.isActive("resizableImage");
5431
- // Check if marks are generally allowed based on context
5432
- const marksAllowed = !isCodeBlock && !isImage;
5433
- // For inline code specifically, we don't allow nesting OTHER marks inside it,
5434
- // but the code mark ITSELF must be togglable to be removed.
5435
- const isInsideInlineCode = isCode;
5436
- // 1. Resolve target element once for this calculation
5437
- const getTargetElement = () => {
5438
- if (typeof window === "undefined" || !editor.view?.dom) {
5439
- return null;
5493
+ /**
5494
+ * Universal Renderer for Angular Components as TipTap NodeViews.
5495
+ *
5496
+ * Supports:
5497
+ * - TipTap-Aware components (extending AteAngularNodeView)
5498
+ * - Standard library components (automatic @Input() sync)
5499
+ * - Signal-based inputs and outputs (Angular 18+)
5500
+ * - Content projection (editableContent)
5501
+ * - Unified lifecycle and event management
5502
+ */
5503
+ function AteNodeViewRenderer(component, options) {
5504
+ return props => {
5505
+ const { node, view: _view, getPos, decorations, editor, extension } = props;
5506
+ const { injector, inputs = {}, wrapperTag = "div", wrapperClass, editableContent = false, contentSelector, onOutput, ignoreMutation = true, } = options;
5507
+ const dom = document.createElement(wrapperTag);
5508
+ if (wrapperClass) {
5509
+ dom.className = wrapperClass;
5440
5510
  }
5441
- try {
5442
- const { from } = editor.state.selection;
5443
- const { node } = editor.view.domAtPos(from);
5444
- return node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
5511
+ const applicationRef = injector.get(ApplicationRef);
5512
+ const environmentInjector = injector.get(EnvironmentInjector);
5513
+ // 1. Setup Content Projection (ng-content)
5514
+ let initialNodes = [];
5515
+ let contentDOM = null;
5516
+ if (editableContent && !contentSelector) {
5517
+ contentDOM = document.createElement("div");
5518
+ contentDOM.setAttribute("data-node-view-content", "");
5519
+ initialNodes = [[contentDOM]];
5445
5520
  }
5446
- catch (_e) {
5447
- return null;
5521
+ // 2. Create the Angular Component
5522
+ const componentRef = createComponent(component, {
5523
+ environmentInjector,
5524
+ elementInjector: injector,
5525
+ projectableNodes: initialNodes,
5526
+ });
5527
+ const instance = componentRef.instance;
5528
+ const subscriptions = [];
5529
+ // 3. Initialize TipTap-Aware instances
5530
+ if (instance instanceof AteAngularNodeView) {
5531
+ instance._initNodeView({
5532
+ editor,
5533
+ node,
5534
+ decorations,
5535
+ selected: false,
5536
+ extension,
5537
+ getPos,
5538
+ updateAttributes: attrs => editor.commands.updateAttributes(node.type.name, attrs),
5539
+ deleteNode: () => {
5540
+ const pos = getPos();
5541
+ if (pos !== undefined) {
5542
+ editor.commands.deleteRange({ from: pos, to: pos + node.nodeSize });
5543
+ }
5544
+ },
5545
+ });
5448
5546
  }
5449
- };
5450
- const targetEl = getTargetElement() || (typeof window !== "undefined" ? editor.view?.dom : null);
5451
- const computedStyle = targetEl && typeof window !== "undefined" ? window.getComputedStyle(targetEl) : null;
5452
- // 2. Lightweight helper to extract properties from the pre-calculated style object
5453
- const getStyle = (prop) => {
5454
- if (!computedStyle) {
5455
- return null;
5547
+ // 4. Synchronize Inputs (Handles standard @Input and Signal-based inputs)
5548
+ const syncInputs = (nodeToSync) => {
5549
+ // Combine base inputs from options with dynamic node attributes
5550
+ const mergedInputs = { ...inputs, ...nodeToSync.attrs };
5551
+ Object.entries(mergedInputs).forEach(([key, value]) => {
5552
+ if (key !== "id" && value !== null && value !== undefined) {
5553
+ try {
5554
+ componentRef.setInput(key, value);
5555
+ }
5556
+ catch {
5557
+ // Silently ignore inputs that don't exist on the component
5558
+ }
5559
+ }
5560
+ });
5561
+ };
5562
+ syncInputs(node);
5563
+ // 5. Setup Outputs (Handles EventEmitter and OutputRef)
5564
+ if (onOutput) {
5565
+ Object.entries(instance).forEach(([key, potentialOutput]) => {
5566
+ if (potentialOutput &&
5567
+ typeof potentialOutput
5568
+ .subscribe === "function") {
5569
+ const sub = potentialOutput.subscribe((value) => {
5570
+ onOutput(key, value);
5571
+ });
5572
+ if (sub instanceof Subscription) {
5573
+ subscriptions.push(sub);
5574
+ }
5575
+ }
5576
+ });
5456
5577
  }
5457
- const val = computedStyle.getPropertyValue(prop);
5458
- return normalizeColor(val);
5459
- };
5460
- const colorMark = editor.getAttributes("textStyle")["color"] || null;
5461
- const backgroundMark = editor.getAttributes("highlight")["color"] || null;
5462
- return {
5463
- marks: {
5464
- bold: editor.isActive("bold"),
5465
- italic: editor.isActive("italic"),
5466
- underline: editor.isActive("underline"),
5467
- strike: editor.isActive("strike"),
5468
- code: isCode,
5469
- superscript: editor.isActive("superscript"),
5470
- subscript: editor.isActive("subscript"),
5471
- highlight: editor.isActive("highlight"),
5472
- link: editor.isActive("link"),
5473
- linkHref: editor.getAttributes("link")["href"] || null,
5474
- color: colorMark,
5475
- computedColor: colorMark || getStyle("color"),
5476
- background: backgroundMark,
5477
- computedBackground: backgroundMark || getStyle("background-color"),
5478
- linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
5479
- false,
5480
- },
5481
- can: {
5482
- toggleBold: marksAllowed && !isInsideInlineCode && editor.can().toggleBold(),
5483
- toggleItalic: marksAllowed && !isInsideInlineCode && editor.can().toggleItalic(),
5484
- toggleUnderline: marksAllowed && !isInsideInlineCode && editor.can().toggleUnderline(),
5485
- toggleStrike: marksAllowed && !isInsideInlineCode && editor.can().toggleStrike(),
5486
- toggleCode: marksAllowed && editor.can().toggleCode(),
5487
- toggleHighlight: marksAllowed && !isInsideInlineCode && editor.can().toggleHighlight(),
5488
- toggleLink: marksAllowed &&
5489
- !isInsideInlineCode &&
5490
- (editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
5491
- toggleSuperscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSuperscript(),
5492
- toggleSubscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSubscript(),
5493
- setColor: marksAllowed && !isInsideInlineCode && editor.can().setColor(""),
5494
- setHighlight: marksAllowed && !isInsideInlineCode && editor.can().setHighlight(),
5495
- undo: editor.can().undo(),
5496
- redo: editor.can().redo(),
5497
- },
5498
- };
5499
- };
5500
-
5501
- const AteTableCalculator = editor => {
5502
- const isTable = editor.isActive("table");
5503
- if (!isTable) {
5578
+ // 6. Attach to DOM and ApplicationRef
5579
+ applicationRef.attachView(componentRef.hostView);
5580
+ const componentElement = componentRef.location.nativeElement;
5581
+ // Target specific element for content if selector provided
5582
+ if (editableContent && contentSelector) {
5583
+ contentDOM = componentElement.querySelector(contentSelector);
5584
+ }
5585
+ dom.appendChild(componentElement);
5586
+ // Initial detection to ensure the component is rendered
5587
+ componentRef.changeDetectorRef.detectChanges();
5588
+ // 7. Return the TipTap NodeView Interface
5504
5589
  return {
5505
- nodes: {
5506
- isTable: false,
5507
- isTableCell: false,
5508
- isTableHeaderRow: false,
5509
- isTableHeaderColumn: false,
5590
+ dom,
5591
+ contentDOM,
5592
+ update: (updatedNode, updatedDecorations) => {
5593
+ if (updatedNode.type !== node.type) {
5594
+ return false;
5595
+ }
5596
+ // Update Aware component signals
5597
+ if (instance instanceof AteAngularNodeView) {
5598
+ instance._updateNodeView(updatedNode, updatedDecorations);
5599
+ }
5600
+ // Update inputs
5601
+ syncInputs(updatedNode);
5602
+ // Notify and Detect changes
5603
+ componentRef.changeDetectorRef.detectChanges();
5604
+ if (options.onUpdate) {
5605
+ options.onUpdate(updatedNode);
5606
+ }
5607
+ return true;
5608
+ },
5609
+ selectNode: () => {
5610
+ if (instance instanceof AteAngularNodeView) {
5611
+ instance._selectNodeView();
5612
+ }
5613
+ dom.classList.add("ProseMirror-selectednode");
5614
+ },
5615
+ deselectNode: () => {
5616
+ if (instance instanceof AteAngularNodeView) {
5617
+ instance._deselectNodeView();
5618
+ }
5619
+ dom.classList.remove("ProseMirror-selectednode");
5620
+ },
5621
+ destroy: () => {
5622
+ if (options.onDestroy) {
5623
+ options.onDestroy();
5624
+ }
5625
+ subscriptions.forEach(s => s.unsubscribe());
5626
+ applicationRef.detachView(componentRef.hostView);
5627
+ componentRef.destroy();
5628
+ },
5629
+ stopEvent: event => {
5630
+ const target = event.target;
5631
+ const isEditable = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
5632
+ return isEditable;
5633
+ },
5634
+ ignoreMutation: mutation => {
5635
+ if (typeof ignoreMutation === "function") {
5636
+ return ignoreMutation(mutation);
5637
+ }
5638
+ return ignoreMutation;
5510
5639
  },
5511
5640
  };
5512
- }
5513
- const { selection } = editor.state;
5514
- return {
5515
- nodes: {
5516
- isTable: true,
5517
- isTableNodeSelected: selection instanceof NodeSelection && selection.node.type.name === "table",
5518
- isTableCell: editor.isActive("tableCell") || editor.isActive("tableHeader"),
5519
- isTableHeaderRow: editor.isActive("tableHeader", { row: true }),
5520
- isTableHeaderColumn: editor.isActive("tableHeader", { column: true }),
5521
- },
5522
- can: {
5523
- addRowBefore: editor.can().addRowBefore(),
5524
- addRowAfter: editor.can().addRowAfter(),
5525
- deleteRow: editor.can().deleteRow(),
5526
- addColumnBefore: editor.can().addColumnBefore(),
5527
- addColumnAfter: editor.can().addColumnAfter(),
5528
- deleteColumn: editor.can().deleteColumn(),
5529
- deleteTable: editor.can().deleteTable(),
5530
- mergeCells: editor.can().mergeCells(),
5531
- splitCell: editor.can().splitCell(),
5532
- toggleHeaderRow: editor.can().toggleHeaderRow(),
5533
- toggleHeaderColumn: editor.can().toggleHeaderColumn(),
5534
- },
5535
5641
  };
5536
- };
5642
+ }
5537
5643
 
5538
- const AteImageCalculator = editor => {
5539
- return {
5540
- nodes: {
5541
- isImage: editor.isActive("image") || editor.isActive("resizableImage"),
5644
+ const RESERVED_NAMES = [
5645
+ "doc",
5646
+ "paragraph",
5647
+ "text",
5648
+ "hardBreak",
5649
+ "bulletList",
5650
+ "orderedList",
5651
+ "listItem",
5652
+ "blockquote",
5653
+ "codeBlock",
5654
+ "heading",
5655
+ "horizontalRule",
5656
+ ];
5657
+ /**
5658
+ * Derives the TipTap node name and HTML tag from a component class.
5659
+ */
5660
+ function deriveMetadata(component, customName) {
5661
+ let nodeName = customName;
5662
+ if (!nodeName) {
5663
+ nodeName = component.name
5664
+ .replace(/Component$/, "")
5665
+ .replace(/Node$/, "")
5666
+ .replace(/^([A-Z])/, m => m.toLowerCase());
5667
+ console.warn(`[ATE] Auto-deriving node name '${nodeName}' for component ${component.name}. ` +
5668
+ `Provide an explicit 'name' in options to avoid potential naming collisions.`);
5669
+ }
5670
+ if (RESERVED_NAMES.includes(nodeName)) {
5671
+ throw new Error(`[ATE] The name '${nodeName}' is a reserved TipTap node name. ` +
5672
+ `Please provide a unique name for your custom component.`);
5673
+ }
5674
+ const tag = nodeName.toLowerCase().replace(/([A-Z])/g, "-$1");
5675
+ return { nodeName, tag };
5676
+ }
5677
+ /**
5678
+ * Creates TipTap attributes from component inputs (Standard Mode).
5679
+ */
5680
+ function createStandardAttributes(defaultInputs = {}) {
5681
+ const tiptapAttributes = {};
5682
+ Object.entries(defaultInputs).forEach(([key, defaultValue]) => {
5683
+ tiptapAttributes[key] = {
5684
+ default: defaultValue,
5685
+ parseHTML: (element) => {
5686
+ const attr = element.getAttribute(`data-${key}`);
5687
+ if (attr === null) {
5688
+ return defaultValue;
5689
+ }
5690
+ try {
5691
+ return JSON.parse(attr);
5692
+ }
5693
+ catch {
5694
+ return attr;
5695
+ }
5696
+ },
5697
+ renderHTML: (attrs) => {
5698
+ const value = attrs[key];
5699
+ if (value === undefined || value === null) {
5700
+ return {};
5701
+ }
5702
+ const serialized = typeof value === "object" ? JSON.stringify(value) : String(value);
5703
+ return { [`data-${key}`]: serialized };
5704
+ },
5705
+ };
5706
+ });
5707
+ return tiptapAttributes;
5708
+ }
5709
+ /**
5710
+ * Factory to transform an Angular component into a TipTap Node extension.
5711
+ * Supports both "TipTap-Aware" and "Standard" modes.
5712
+ *
5713
+ * @internal
5714
+ */
5715
+ function createAngularComponentExtension(injector, options) {
5716
+ const { component, name: customName, attributes, defaultInputs, contentSelector, contentMode = "block", editableContent = false, group = "block", draggable = true, ignoreMutation = true, onOutput, HTMLAttributes = {}, parseHTML: customParseHTML, renderHTML: customRenderHTML, } = options;
5717
+ const { nodeName, tag } = deriveMetadata(component, customName);
5718
+ const isTipTapAware = Object.prototype.isPrototypeOf.call(AteAngularNodeView, component);
5719
+ const atom = !editableContent;
5720
+ // 1. Prepare Attributes
5721
+ const tiptapAttributes = isTipTapAware
5722
+ ? attributes || {}
5723
+ : createStandardAttributes(defaultInputs);
5724
+ // 2. Create Node Extension
5725
+ return Node$1.create({
5726
+ name: nodeName,
5727
+ group,
5728
+ inline: group === "inline",
5729
+ atom,
5730
+ draggable,
5731
+ content: editableContent ? (contentMode === "inline" ? "inline*" : "block*") : undefined,
5732
+ addAttributes() {
5733
+ return tiptapAttributes;
5542
5734
  },
5543
- };
5544
- };
5545
-
5546
- const AteStructureCalculator = editor => {
5547
- return {
5548
- nodes: {
5549
- isBlockquote: editor.isActive("blockquote"),
5550
- isCodeBlock: editor.isActive("codeBlock"),
5551
- isBulletList: editor.isActive("bulletList"),
5552
- isOrderedList: editor.isActive("orderedList"),
5553
- h1: editor.isActive("heading", { level: 1 }),
5554
- h2: editor.isActive("heading", { level: 2 }),
5555
- h3: editor.isActive("heading", { level: 3 }),
5556
- alignLeft: editor.isActive({ textAlign: "left" }),
5557
- alignCenter: editor.isActive({ textAlign: "center" }),
5558
- alignRight: editor.isActive({ textAlign: "right" }),
5559
- alignJustify: editor.isActive({ textAlign: "justify" }),
5735
+ parseHTML() {
5736
+ if (customParseHTML) {
5737
+ return customParseHTML.call(this);
5738
+ }
5739
+ return [{ tag }, { tag: `div[data-component="${nodeName}"]` }];
5560
5740
  },
5561
- can: {
5562
- toggleHeading1: editor.can().toggleHeading({ level: 1 }),
5563
- toggleHeading2: editor.can().toggleHeading({ level: 2 }),
5564
- toggleHeading3: editor.can().toggleHeading({ level: 3 }),
5565
- toggleBulletList: editor.can().toggleBulletList(),
5566
- toggleOrderedList: editor.can().toggleOrderedList(),
5567
- toggleBlockquote: editor.can().toggleBlockquote(),
5568
- toggleCodeBlock: editor.can().toggleCodeBlock(),
5569
- setTextAlignLeft: editor.can().setTextAlign("left"),
5570
- setTextAlignCenter: editor.can().setTextAlign("center"),
5571
- setTextAlignRight: editor.can().setTextAlign("right"),
5572
- setTextAlignJustify: editor.can().setTextAlign("justify"),
5573
- insertHorizontalRule: editor.can().setHorizontalRule(),
5574
- insertTable: editor.can().insertTable(),
5575
- insertImage: editor.can().setResizableImage({ src: "" }),
5741
+ renderHTML({ node, HTMLAttributes: attrs }) {
5742
+ if (customRenderHTML) {
5743
+ return customRenderHTML.call(this, { node, HTMLAttributes: attrs });
5744
+ }
5745
+ return [tag, mergeAttributes(HTMLAttributes, attrs, { "data-component": nodeName })];
5576
5746
  },
5577
- };
5578
- };
5579
-
5580
- /**
5581
- * DiscoveryCalculator automatically detects and tracks the state of any TipTap extension.
5582
- * It provides a "fallback" reactive state for any mark or node not explicitly handled
5583
- * by specialized calculators.
5584
- */
5585
- const AteDiscoveryCalculator = (editor) => {
5586
- const state = {
5587
- marks: {},
5588
- nodes: {},
5589
- };
5747
+ addNodeView() {
5748
+ return AteNodeViewRenderer(component, {
5749
+ injector,
5750
+ inputs: defaultInputs,
5751
+ wrapperTag: group === "inline" ? "span" : "div",
5752
+ wrapperClass: isTipTapAware
5753
+ ? `ate-node-${nodeName}`
5754
+ : `embedded-component embedded-${nodeName}`,
5755
+ atom,
5756
+ editableContent,
5757
+ contentSelector,
5758
+ contentMode,
5759
+ onOutput,
5760
+ ignoreMutation,
5761
+ });
5762
+ },
5763
+ });
5764
+ }
5765
+
5766
+ /**
5767
+ * Internal registry to store the root injector for the editor.
5768
+ * This allows registerAngularComponent to work without explicitly passing the injector.
5769
+ * @internal
5770
+ */
5771
+ let globalInjector = null;
5772
+ function setGlobalInjector(injector) {
5773
+ globalInjector = injector;
5774
+ }
5775
+ function getGlobalInjector() {
5776
+ if (!globalInjector) {
5777
+ throw new Error("[ATE] Global Injector not found. Make sure to call provideAteEditor() in your app.config.ts or main.ts.");
5778
+ }
5779
+ return globalInjector;
5780
+ }
5781
+
5782
+ /**
5783
+ * Registers ANY Angular component as a TipTap node extension.
5784
+ *
5785
+ * This function is the single public entry point for adding custom components.
5786
+ * It supports two distinct modes:
5787
+ *
5788
+ * 1. **TipTap-Aware Mode** (the component extends `AteAngularNodeView`):
5789
+ * Grants direct access to the TipTap API (`editor`, `node`, `updateAttributes`, etc.) within the component.
5790
+ *
5791
+ * 2. **Standard Mode** (library or legacy components):
5792
+ * Allows using any existing Angular component. Its `@Input()` properties are automatically
5793
+ * synchronized with TipTap node attributes.
5794
+ *
5795
+ * @param injectorOrOptions - The Angular Injector OR the configuration options
5796
+ * @param maybeOptions - Configuration options (if the first param was an injector)
5797
+ * @returns A TipTap Node extension ready to be used
5798
+ *
5799
+ * @example
5800
+ * ```typescript
5801
+ * // Modern way (requires provideAteEditor())
5802
+ * registerAngularComponent({
5803
+ * component: MyComponent,
5804
+ * name: 'myComponent'
5805
+ * });
5806
+ *
5807
+ * // Classic way
5808
+ * registerAngularComponent(injector, {
5809
+ * component: MyComponent
5810
+ * });
5811
+ * ```
5812
+ */
5813
+ function registerAngularComponent(injectorOrOptions, maybeOptions) {
5814
+ let injector;
5815
+ let options;
5816
+ // Duck-typing check: Injectors have a 'get' method
5817
+ const isInjector = (obj) => obj !== null && typeof obj === "object" && typeof obj.get === "function";
5818
+ if (isInjector(injectorOrOptions)) {
5819
+ injector = injectorOrOptions;
5820
+ options = maybeOptions;
5821
+ }
5822
+ else {
5823
+ injector = getGlobalInjector();
5824
+ options = injectorOrOptions;
5825
+ }
5826
+ return createAngularComponentExtension(injector, options);
5827
+ }
5828
+
5829
+ /**
5830
+ * Injection Token for global editor configuration.
5831
+ */
5832
+ const ATE_GLOBAL_CONFIG = new InjectionToken("ATE_GLOBAL_CONFIG");
5833
+
5834
+ const AteSelectionCalculator = editor => {
5835
+ const { selection } = editor.state;
5836
+ const { from, to } = selection;
5837
+ let selectionType = "none";
5838
+ if (selection instanceof TextSelection) {
5839
+ selectionType = "text";
5840
+ }
5841
+ else if (selection instanceof NodeSelection) {
5842
+ selectionType = "node";
5843
+ }
5844
+ else if (selection instanceof CellSelection) {
5845
+ selectionType = "cell";
5846
+ }
5847
+ let isSingleCell = false;
5848
+ if (selection instanceof CellSelection) {
5849
+ isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
5850
+ }
5851
+ return {
5852
+ isFocused: editor.view.hasFocus(),
5853
+ isEditable: editor.isEditable,
5854
+ selection: {
5855
+ type: selectionType,
5856
+ from,
5857
+ to,
5858
+ empty: selection.empty || (selectionType === "text" && from === to),
5859
+ isSingleCell: isSingleCell,
5860
+ },
5861
+ nodes: {
5862
+ activeNodeName: editor.state.doc.nodeAt(editor.state.selection.$head.pos)?.type.name || null,
5863
+ },
5864
+ };
5865
+ };
5866
+
5867
+ const AteMarksCalculator = editor => {
5868
+ const isCodeBlock = editor.isActive("codeBlock");
5869
+ const isCode = editor.isActive("code"); // Inline code mark
5870
+ const isImage = editor.isActive("image") || editor.isActive("resizableImage");
5871
+ // Check if marks are generally allowed based on context
5872
+ const marksAllowed = !isCodeBlock && !isImage;
5873
+ // For inline code specifically, we don't allow nesting OTHER marks inside it,
5874
+ // but the code mark ITSELF must be togglable to be removed.
5875
+ const isInsideInlineCode = isCode;
5876
+ // 1. Resolve target element once for this calculation
5877
+ const getTargetElement = () => {
5878
+ if (typeof window === "undefined" || !editor.view?.dom) {
5879
+ return null;
5880
+ }
5881
+ try {
5882
+ const { from } = editor.state.selection;
5883
+ const { node } = editor.view.domAtPos(from);
5884
+ return node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
5885
+ }
5886
+ catch (_e) {
5887
+ return null;
5888
+ }
5889
+ };
5890
+ const targetEl = getTargetElement() || (typeof window !== "undefined" ? editor.view?.dom : null);
5891
+ const computedStyle = targetEl && typeof window !== "undefined" ? window.getComputedStyle(targetEl) : null;
5892
+ // 2. Lightweight helper to extract properties from the pre-calculated style object
5893
+ const getStyle = (prop) => {
5894
+ if (!computedStyle) {
5895
+ return null;
5896
+ }
5897
+ const val = computedStyle.getPropertyValue(prop);
5898
+ return normalizeColor(val);
5899
+ };
5900
+ const colorMark = editor.getAttributes("textStyle")["color"] || null;
5901
+ const backgroundMark = editor.getAttributes("highlight")["color"] || null;
5902
+ return {
5903
+ marks: {
5904
+ bold: editor.isActive("bold"),
5905
+ italic: editor.isActive("italic"),
5906
+ underline: editor.isActive("underline"),
5907
+ strike: editor.isActive("strike"),
5908
+ code: isCode,
5909
+ superscript: editor.isActive("superscript"),
5910
+ subscript: editor.isActive("subscript"),
5911
+ highlight: editor.isActive("highlight"),
5912
+ link: editor.isActive("link"),
5913
+ linkHref: editor.getAttributes("link")["href"] || null,
5914
+ color: colorMark,
5915
+ computedColor: colorMark || getStyle("color"),
5916
+ background: backgroundMark,
5917
+ computedBackground: backgroundMark || getStyle("background-color"),
5918
+ linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
5919
+ false,
5920
+ },
5921
+ can: {
5922
+ toggleBold: marksAllowed && !isInsideInlineCode && editor.can().toggleBold(),
5923
+ toggleItalic: marksAllowed && !isInsideInlineCode && editor.can().toggleItalic(),
5924
+ toggleUnderline: marksAllowed && !isInsideInlineCode && editor.can().toggleUnderline(),
5925
+ toggleStrike: marksAllowed && !isInsideInlineCode && editor.can().toggleStrike(),
5926
+ toggleCode: marksAllowed && editor.can().toggleCode(),
5927
+ toggleHighlight: marksAllowed && !isInsideInlineCode && editor.can().toggleHighlight(),
5928
+ toggleLink: marksAllowed &&
5929
+ !isInsideInlineCode &&
5930
+ (editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
5931
+ toggleSuperscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSuperscript(),
5932
+ toggleSubscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSubscript(),
5933
+ setColor: marksAllowed && !isInsideInlineCode && editor.can().setColor(""),
5934
+ setHighlight: marksAllowed && !isInsideInlineCode && editor.can().setHighlight(),
5935
+ undo: editor.can().undo(),
5936
+ redo: editor.can().redo(),
5937
+ },
5938
+ };
5939
+ };
5940
+
5941
+ const AteTableCalculator = editor => {
5942
+ const isTable = editor.isActive("table");
5943
+ if (!isTable) {
5944
+ return {
5945
+ nodes: {
5946
+ isTable: false,
5947
+ isTableCell: false,
5948
+ isTableHeaderRow: false,
5949
+ isTableHeaderColumn: false,
5950
+ },
5951
+ };
5952
+ }
5953
+ const { selection } = editor.state;
5954
+ return {
5955
+ nodes: {
5956
+ isTable: true,
5957
+ isTableNodeSelected: selection instanceof NodeSelection && selection.node.type.name === "table",
5958
+ isTableCell: editor.isActive("tableCell") || editor.isActive("tableHeader"),
5959
+ isTableHeaderRow: editor.isActive("tableHeader", { row: true }),
5960
+ isTableHeaderColumn: editor.isActive("tableHeader", { column: true }),
5961
+ },
5962
+ can: {
5963
+ addRowBefore: editor.can().addRowBefore(),
5964
+ addRowAfter: editor.can().addRowAfter(),
5965
+ deleteRow: editor.can().deleteRow(),
5966
+ addColumnBefore: editor.can().addColumnBefore(),
5967
+ addColumnAfter: editor.can().addColumnAfter(),
5968
+ deleteColumn: editor.can().deleteColumn(),
5969
+ deleteTable: editor.can().deleteTable(),
5970
+ mergeCells: editor.can().mergeCells(),
5971
+ splitCell: editor.can().splitCell(),
5972
+ toggleHeaderRow: editor.can().toggleHeaderRow(),
5973
+ toggleHeaderColumn: editor.can().toggleHeaderColumn(),
5974
+ },
5975
+ };
5976
+ };
5977
+
5978
+ const AteImageCalculator = editor => {
5979
+ return {
5980
+ nodes: {
5981
+ isImage: editor.isActive("image") || editor.isActive("resizableImage"),
5982
+ },
5983
+ };
5984
+ };
5985
+
5986
+ const AteStructureCalculator = editor => {
5987
+ return {
5988
+ nodes: {
5989
+ isBlockquote: editor.isActive("blockquote"),
5990
+ isCodeBlock: editor.isActive("codeBlock"),
5991
+ isBulletList: editor.isActive("bulletList"),
5992
+ isOrderedList: editor.isActive("orderedList"),
5993
+ h1: editor.isActive("heading", { level: 1 }),
5994
+ h2: editor.isActive("heading", { level: 2 }),
5995
+ h3: editor.isActive("heading", { level: 3 }),
5996
+ alignLeft: editor.isActive({ textAlign: "left" }),
5997
+ alignCenter: editor.isActive({ textAlign: "center" }),
5998
+ alignRight: editor.isActive({ textAlign: "right" }),
5999
+ alignJustify: editor.isActive({ textAlign: "justify" }),
6000
+ },
6001
+ can: {
6002
+ toggleHeading1: editor.can().toggleHeading({ level: 1 }),
6003
+ toggleHeading2: editor.can().toggleHeading({ level: 2 }),
6004
+ toggleHeading3: editor.can().toggleHeading({ level: 3 }),
6005
+ toggleBulletList: editor.can().toggleBulletList(),
6006
+ toggleOrderedList: editor.can().toggleOrderedList(),
6007
+ toggleBlockquote: editor.can().toggleBlockquote(),
6008
+ toggleCodeBlock: editor.can().toggleCodeBlock(),
6009
+ setTextAlignLeft: editor.can().setTextAlign("left"),
6010
+ setTextAlignCenter: editor.can().setTextAlign("center"),
6011
+ setTextAlignRight: editor.can().setTextAlign("right"),
6012
+ setTextAlignJustify: editor.can().setTextAlign("justify"),
6013
+ insertHorizontalRule: editor.can().setHorizontalRule(),
6014
+ insertTable: editor.can().insertTable(),
6015
+ insertImage: editor.can().setResizableImage({ src: "" }),
6016
+ },
6017
+ };
6018
+ };
6019
+
6020
+ /**
6021
+ * DiscoveryCalculator automatically detects and tracks the state of any TipTap extension.
6022
+ * It provides a "fallback" reactive state for any mark or node not explicitly handled
6023
+ * by specialized calculators.
6024
+ */
6025
+ const AteDiscoveryCalculator = (editor) => {
6026
+ const state = {
6027
+ marks: {},
6028
+ nodes: {},
6029
+ };
5590
6030
  // We skip core extensions that are already handled by specialized calculators
5591
6031
  // to avoid redundant calculations and maintain precise attribute tracking.
5592
6032
  const handled = [
@@ -5744,8 +6184,58 @@ const ATE_DEFAULT_CELL_MENU_CONFIG = {
5744
6184
  mergeCells: true,
5745
6185
  splitCell: true,
5746
6186
  };
6187
+ /**
6188
+ * Ultimate default configuration for the Angular Tiptap Editor.
6189
+ * This serves as the 'Level 4' fallback in the configuration hierarchy:
6190
+ * 1. Component Inputs (Le Roi)
6191
+ * 2. Component [config] (Le Prince)
6192
+ * 3. Global provideAteEditor() (Le Duc)
6193
+ * 4. ATE_DEFAULT_CONFIG (Le Peuple)
6194
+ */
6195
+ const ATE_DEFAULT_CONFIG = {
6196
+ theme: "auto",
6197
+ mode: "classic",
6198
+ height: "auto",
6199
+ minHeight: "200px",
6200
+ maxHeight: "none",
6201
+ fillContainer: false,
6202
+ autofocus: false,
6203
+ editable: true,
6204
+ disabled: false,
6205
+ spellcheck: true,
6206
+ enableOfficePaste: true,
6207
+ showToolbar: true,
6208
+ showFooter: true,
6209
+ showCharacterCount: true,
6210
+ showWordCount: true,
6211
+ showEditToggle: false,
6212
+ showBubbleMenu: true,
6213
+ showImageBubbleMenu: true,
6214
+ showTableMenu: true,
6215
+ showCellMenu: true,
6216
+ enableSlashCommands: true,
6217
+ floatingToolbar: false,
6218
+ toolbar: ATE_DEFAULT_TOOLBAR_CONFIG,
6219
+ bubbleMenu: ATE_DEFAULT_BUBBLE_MENU_CONFIG,
6220
+ imageBubbleMenu: ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG,
6221
+ tableBubbleMenu: ATE_DEFAULT_TABLE_MENU_CONFIG,
6222
+ cellBubbleMenu: ATE_DEFAULT_CELL_MENU_CONFIG,
6223
+ slashCommands: {},
6224
+ tiptapExtensions: [],
6225
+ tiptapOptions: {},
6226
+ stateCalculators: [],
6227
+ angularNodes: [],
6228
+ };
5747
6229
 
5748
6230
  // Slash commands configuration is handled dynamically via slashCommandsConfigComputed
6231
+ /**
6232
+ * The main rich-text editor component for Angular.
6233
+ *
6234
+ * Powered by Tiptap and built with a native Signal-based architecture, it provides
6235
+ * a seamless, high-performance editing experience. Supports automatic registration
6236
+ * of Angular components as interactive nodes ('Angular Nodes'), full Reactive Forms
6237
+ * integration, and extensive customization via the AteEditorConfig.
6238
+ */
5749
6239
  class AngularTiptapEditorComponent {
5750
6240
  // ============================================
5751
6241
  // Toolbar / Bubble Menu Coordination
@@ -5760,48 +6250,48 @@ class AngularTiptapEditorComponent {
5760
6250
  /** Configuration globale de l'éditeur */
5761
6251
  this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
5762
6252
  this.content = input("", ...(ngDevMode ? [{ debugName: "content" }] : []));
5763
- this.placeholder = input("", ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
5764
- this.editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : []));
5765
- this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
5766
- this.minHeight = input(200, ...(ngDevMode ? [{ debugName: "minHeight" }] : []));
6253
+ this.placeholder = input(undefined, ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
6254
+ this.editable = input(undefined, ...(ngDevMode ? [{ debugName: "editable" }] : []));
6255
+ this.disabled = input(undefined, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
6256
+ this.minHeight = input(undefined, ...(ngDevMode ? [{ debugName: "minHeight" }] : []));
5767
6257
  this.height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : []));
5768
6258
  this.maxHeight = input(undefined, ...(ngDevMode ? [{ debugName: "maxHeight" }] : []));
5769
- this.fillContainer = input(false, ...(ngDevMode ? [{ debugName: "fillContainer" }] : []));
5770
- this.showToolbar = input(true, ...(ngDevMode ? [{ debugName: "showToolbar" }] : []));
5771
- this.showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
5772
- this.showCharacterCount = input(true, ...(ngDevMode ? [{ debugName: "showCharacterCount" }] : []));
5773
- this.showWordCount = input(true, ...(ngDevMode ? [{ debugName: "showWordCount" }] : []));
6259
+ this.fillContainer = input(undefined, ...(ngDevMode ? [{ debugName: "fillContainer" }] : []));
6260
+ this.showToolbar = input(undefined, ...(ngDevMode ? [{ debugName: "showToolbar" }] : []));
6261
+ this.showFooter = input(undefined, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
6262
+ this.showCharacterCount = input(undefined, ...(ngDevMode ? [{ debugName: "showCharacterCount" }] : []));
6263
+ this.showWordCount = input(undefined, ...(ngDevMode ? [{ debugName: "showWordCount" }] : []));
5774
6264
  this.maxCharacters = input(undefined, ...(ngDevMode ? [{ debugName: "maxCharacters" }] : []));
5775
- this.enableOfficePaste = input(true, ...(ngDevMode ? [{ debugName: "enableOfficePaste" }] : []));
5776
- this.enableSlashCommands = input(true, ...(ngDevMode ? [{ debugName: "enableSlashCommands" }] : []));
5777
- this.slashCommands = input({}, ...(ngDevMode ? [{ debugName: "slashCommands" }] : []));
6265
+ this.enableOfficePaste = input(undefined, ...(ngDevMode ? [{ debugName: "enableOfficePaste" }] : []));
6266
+ this.enableSlashCommands = input(undefined, ...(ngDevMode ? [{ debugName: "enableSlashCommands" }] : []));
6267
+ this.slashCommands = input(undefined, ...(ngDevMode ? [{ debugName: "slashCommands" }] : []));
5778
6268
  this.customSlashCommands = input(undefined, ...(ngDevMode ? [{ debugName: "customSlashCommands" }] : []));
5779
6269
  this.locale = input(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : []));
5780
- this.autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : []));
5781
- this.seamless = input(false, ...(ngDevMode ? [{ debugName: "seamless" }] : []));
5782
- this.floatingToolbar = input(false, ...(ngDevMode ? [{ debugName: "floatingToolbar" }] : []));
5783
- this.showEditToggle = input(false, ...(ngDevMode ? [{ debugName: "showEditToggle" }] : []));
5784
- this.spellcheck = input(true, ...(ngDevMode ? [{ debugName: "spellcheck" }] : []));
5785
- this.tiptapExtensions = input([], ...(ngDevMode ? [{ debugName: "tiptapExtensions" }] : []));
5786
- this.tiptapOptions = input({}, ...(ngDevMode ? [{ debugName: "tiptapOptions" }] : []));
6270
+ this.autofocus = input(undefined, ...(ngDevMode ? [{ debugName: "autofocus" }] : []));
6271
+ this.seamless = input(undefined, ...(ngDevMode ? [{ debugName: "seamless" }] : []));
6272
+ this.floatingToolbar = input(undefined, ...(ngDevMode ? [{ debugName: "floatingToolbar" }] : []));
6273
+ this.showEditToggle = input(undefined, ...(ngDevMode ? [{ debugName: "showEditToggle" }] : []));
6274
+ this.spellcheck = input(undefined, ...(ngDevMode ? [{ debugName: "spellcheck" }] : []));
6275
+ this.tiptapExtensions = input(undefined, ...(ngDevMode ? [{ debugName: "tiptapExtensions" }] : []));
6276
+ this.tiptapOptions = input(undefined, ...(ngDevMode ? [{ debugName: "tiptapOptions" }] : []));
5787
6277
  // Nouveaux inputs pour les bubble menus
5788
- this.showBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showBubbleMenu" }] : []));
5789
- this.bubbleMenu = input(ATE_DEFAULT_BUBBLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "bubbleMenu" }] : []));
5790
- this.showImageBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showImageBubbleMenu" }] : []));
5791
- this.imageBubbleMenu = input(ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "imageBubbleMenu" }] : []));
6278
+ this.showBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "showBubbleMenu" }] : []));
6279
+ this.bubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "bubbleMenu" }] : []));
6280
+ this.showImageBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "showImageBubbleMenu" }] : []));
6281
+ this.imageBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "imageBubbleMenu" }] : []));
5792
6282
  // Configuration de la toolbar
5793
- this.toolbar = input({}, ...(ngDevMode ? [{ debugName: "toolbar" }] : []));
6283
+ this.toolbar = input(undefined, ...(ngDevMode ? [{ debugName: "toolbar" }] : []));
5794
6284
  // Configuration des menus de table
5795
- this.showTableBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showTableBubbleMenu" }] : []));
5796
- this.tableBubbleMenu = input(ATE_DEFAULT_TABLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "tableBubbleMenu" }] : []));
5797
- this.showCellBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showCellBubbleMenu" }] : []));
5798
- this.cellBubbleMenu = input(ATE_DEFAULT_CELL_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "cellBubbleMenu" }] : []));
6285
+ this.showTableBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "showTableBubbleMenu" }] : []));
6286
+ this.tableBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "tableBubbleMenu" }] : []));
6287
+ this.showCellBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "showCellBubbleMenu" }] : []));
6288
+ this.cellBubbleMenu = input(undefined, ...(ngDevMode ? [{ debugName: "cellBubbleMenu" }] : []));
5799
6289
  /**
5800
6290
  * Additionnal state calculators to extend the reactive editor state.
5801
6291
  */
5802
- this.stateCalculators = input([], ...(ngDevMode ? [{ debugName: "stateCalculators" }] : []));
6292
+ this.stateCalculators = input(undefined, ...(ngDevMode ? [{ debugName: "stateCalculators" }] : []));
5803
6293
  // Nouveau input pour la configuration de l'upload d'images
5804
- this.imageUpload = input({}, ...(ngDevMode ? [{ debugName: "imageUpload" }] : []));
6294
+ this.imageUpload = input(undefined, ...(ngDevMode ? [{ debugName: "imageUpload" }] : []));
5805
6295
  /**
5806
6296
  * Custom handler for image uploads.
5807
6297
  * When provided, images will be processed through this handler instead of being converted to base64.
@@ -5848,7 +6338,7 @@ class AngularTiptapEditorComponent {
5848
6338
  this._isFormControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_isFormControlDisabled" }] : []));
5849
6339
  this.isFormControlDisabled = this._isFormControlDisabled.asReadonly();
5850
6340
  // Combined disabled state (Input + FormControl)
5851
- this.mergedDisabled = computed(() => (this.config().disabled ?? this.disabled()) || this.isFormControlDisabled(), ...(ngDevMode ? [{ debugName: "mergedDisabled" }] : []));
6341
+ this.mergedDisabled = computed(() => (this.disabled() ?? this.effectiveConfig().disabled) || this.isFormControlDisabled(), ...(ngDevMode ? [{ debugName: "mergedDisabled" }] : []));
5852
6342
  // Computed for editor states
5853
6343
  this.isEditorReady = computed(() => this.editor() !== null, ...(ngDevMode ? [{ debugName: "isEditorReady" }] : []));
5854
6344
  // ============================================
@@ -5856,102 +6346,132 @@ class AngularTiptapEditorComponent {
5856
6346
  // ============================================
5857
6347
  // Appearance & Fundamentals
5858
6348
  this.finalSeamless = computed(() => {
5859
- const fromConfig = this.config().mode;
5860
- if (fromConfig !== undefined) {
5861
- return fromConfig === "seamless";
5862
- }
5863
- return this.seamless();
6349
+ const inputVal = this.seamless();
6350
+ if (inputVal !== undefined)
6351
+ return inputVal;
6352
+ const fromConfig = this.effectiveConfig().mode;
6353
+ return fromConfig === "seamless";
5864
6354
  }, ...(ngDevMode ? [{ debugName: "finalSeamless" }] : []));
5865
- this.finalEditable = computed(() => this.config().editable ?? this.editable(), ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
5866
- this.finalPlaceholder = computed(() => this.config().placeholder ??
5867
- (this.placeholder() || this.currentTranslations().editor.placeholder), ...(ngDevMode ? [{ debugName: "finalPlaceholder" }] : []));
5868
- this.finalFillContainer = computed(() => this.config().fillContainer ?? this.fillContainer(), ...(ngDevMode ? [{ debugName: "finalFillContainer" }] : []));
5869
- this.finalShowFooter = computed(() => this.config().showFooter ?? this.showFooter(), ...(ngDevMode ? [{ debugName: "finalShowFooter" }] : []));
5870
- this.finalShowEditToggle = computed(() => this.config().showEditToggle ?? this.showEditToggle(), ...(ngDevMode ? [{ debugName: "finalShowEditToggle" }] : []));
5871
- this.finalHeight = computed(() => this.config().height ?? (this.height() ? `${this.height()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalHeight" }] : []));
5872
- this.finalMinHeight = computed(() => this.config().minHeight ?? (this.minHeight() ? `${this.minHeight()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalMinHeight" }] : []));
5873
- this.finalMaxHeight = computed(() => this.config().maxHeight ?? (this.maxHeight() ? `${this.maxHeight()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalMaxHeight" }] : []));
5874
- this.finalSpellcheck = computed(() => this.config().spellcheck ?? this.spellcheck(), ...(ngDevMode ? [{ debugName: "finalSpellcheck" }] : []));
5875
- this.finalEnableOfficePaste = computed(() => this.config().enableOfficePaste ?? this.enableOfficePaste(), ...(ngDevMode ? [{ debugName: "finalEnableOfficePaste" }] : []));
6355
+ this.finalEditable = computed(() => this.editable() ?? this.effectiveConfig().editable, ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
6356
+ this.finalPlaceholder = computed(() => this.placeholder() ??
6357
+ this.effectiveConfig().placeholder ??
6358
+ this.currentTranslations().editor.placeholder, ...(ngDevMode ? [{ debugName: "finalPlaceholder" }] : []));
6359
+ this.finalFillContainer = computed(() => this.fillContainer() ?? this.effectiveConfig().fillContainer, ...(ngDevMode ? [{ debugName: "finalFillContainer" }] : []));
6360
+ this.finalShowFooter = computed(() => this.showFooter() ?? this.effectiveConfig().showFooter, ...(ngDevMode ? [{ debugName: "finalShowFooter" }] : []));
6361
+ this.finalShowEditToggle = computed(() => this.showEditToggle() ?? this.effectiveConfig().showEditToggle, ...(ngDevMode ? [{ debugName: "finalShowEditToggle" }] : []));
6362
+ this.finalHeight = computed(() => {
6363
+ const h = this.height() ?? this.effectiveConfig().height;
6364
+ return typeof h === "number" ? `${h}px` : h;
6365
+ }, ...(ngDevMode ? [{ debugName: "finalHeight" }] : []));
6366
+ this.finalMinHeight = computed(() => {
6367
+ const mh = this.minHeight() ?? this.effectiveConfig().minHeight;
6368
+ return typeof mh === "number" ? `${mh}px` : mh;
6369
+ }, ...(ngDevMode ? [{ debugName: "finalMinHeight" }] : []));
6370
+ this.finalMaxHeight = computed(() => {
6371
+ const mh = this.maxHeight() ?? this.effectiveConfig().maxHeight;
6372
+ return typeof mh === "number" ? `${mh}px` : mh;
6373
+ }, ...(ngDevMode ? [{ debugName: "finalMaxHeight" }] : []));
6374
+ this.finalSpellcheck = computed(() => this.spellcheck() ?? this.effectiveConfig().spellcheck, ...(ngDevMode ? [{ debugName: "finalSpellcheck" }] : []));
6375
+ this.finalEnableOfficePaste = computed(() => this.enableOfficePaste() ?? this.effectiveConfig().enableOfficePaste, ...(ngDevMode ? [{ debugName: "finalEnableOfficePaste" }] : []));
5876
6376
  // Features
5877
- this.finalShowToolbar = computed(() => this.config().showToolbar ?? this.showToolbar(), ...(ngDevMode ? [{ debugName: "finalShowToolbar" }] : []));
6377
+ this.finalShowToolbar = computed(() => this.showToolbar() ?? this.effectiveConfig().showToolbar, ...(ngDevMode ? [{ debugName: "finalShowToolbar" }] : []));
5878
6378
  this.finalToolbarConfig = computed(() => {
5879
- const fromConfig = this.config().toolbar;
6379
+ const fromInput = this.toolbar();
6380
+ const fromConfig = this.effectiveConfig().toolbar;
5880
6381
  const base = ATE_DEFAULT_TOOLBAR_CONFIG;
6382
+ if (fromInput && Object.keys(fromInput).length > 0) {
6383
+ return { ...base, ...fromInput };
6384
+ }
5881
6385
  if (fromConfig) {
5882
6386
  return { ...base, ...fromConfig };
5883
6387
  }
5884
- const fromInput = this.toolbar();
5885
- return Object.keys(fromInput).length === 0 ? base : { ...base, ...fromInput };
6388
+ return base;
5886
6389
  }, ...(ngDevMode ? [{ debugName: "finalToolbarConfig" }] : []));
5887
- this.finalFloatingToolbar = computed(() => this.config().floatingToolbar ?? this.floatingToolbar(), ...(ngDevMode ? [{ debugName: "finalFloatingToolbar" }] : []));
5888
- this.finalShowBubbleMenu = computed(() => this.config().showBubbleMenu ?? this.showBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowBubbleMenu" }] : []));
6390
+ this.finalFloatingToolbar = computed(() => this.floatingToolbar() ?? this.effectiveConfig().floatingToolbar, ...(ngDevMode ? [{ debugName: "finalFloatingToolbar" }] : []));
6391
+ this.finalShowBubbleMenu = computed(() => this.showBubbleMenu() ?? this.effectiveConfig().showBubbleMenu, ...(ngDevMode ? [{ debugName: "finalShowBubbleMenu" }] : []));
5889
6392
  this.finalBubbleMenuConfig = computed(() => {
5890
- const fromConfig = this.config().bubbleMenu;
6393
+ const fromInput = this.bubbleMenu();
6394
+ const fromConfig = this.effectiveConfig().bubbleMenu;
5891
6395
  const base = ATE_DEFAULT_BUBBLE_MENU_CONFIG;
6396
+ if (fromInput && Object.keys(fromInput).length > 0) {
6397
+ return { ...base, ...fromInput };
6398
+ }
5892
6399
  if (fromConfig) {
5893
6400
  return { ...base, ...fromConfig };
5894
6401
  }
5895
- return Object.keys(this.bubbleMenu()).length === 0 ? base : { ...base, ...this.bubbleMenu() };
6402
+ return base;
5896
6403
  }, ...(ngDevMode ? [{ debugName: "finalBubbleMenuConfig" }] : []));
5897
- this.finalShowImageBubbleMenu = computed(() => this.config().showImageBubbleMenu ?? this.showImageBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
6404
+ this.finalShowImageBubbleMenu = computed(() => this.showImageBubbleMenu() ?? this.effectiveConfig().showImageBubbleMenu, ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
5898
6405
  this.finalImageBubbleMenuConfig = computed(() => {
5899
- const fromConfig = this.config().imageBubbleMenu;
6406
+ const fromInput = this.imageBubbleMenu();
6407
+ const fromConfig = this.effectiveConfig().imageBubbleMenu;
5900
6408
  const base = ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG;
6409
+ if (fromInput && Object.keys(fromInput).length > 0) {
6410
+ return { ...base, ...fromInput };
6411
+ }
5901
6412
  if (fromConfig) {
5902
6413
  return { ...base, ...fromConfig };
5903
6414
  }
5904
- return Object.keys(this.imageBubbleMenu()).length === 0
5905
- ? base
5906
- : { ...base, ...this.imageBubbleMenu() };
6415
+ return base;
5907
6416
  }, ...(ngDevMode ? [{ debugName: "finalImageBubbleMenuConfig" }] : []));
5908
- this.finalShowTableBubbleMenu = computed(() => this.config().showTableMenu ?? this.showTableBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
6417
+ this.finalShowTableBubbleMenu = computed(() => this.showTableBubbleMenu() ?? this.effectiveConfig().showTableMenu, ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
5909
6418
  this.finalTableBubbleMenuConfig = computed(() => {
5910
- const fromConfig = this.config().tableBubbleMenu;
6419
+ const fromInput = this.tableBubbleMenu();
6420
+ const fromConfig = this.effectiveConfig().tableBubbleMenu;
5911
6421
  const base = ATE_DEFAULT_TABLE_MENU_CONFIG;
6422
+ if (fromInput && Object.keys(fromInput).length > 0) {
6423
+ return { ...base, ...fromInput };
6424
+ }
5912
6425
  if (fromConfig) {
5913
6426
  return { ...base, ...fromConfig };
5914
6427
  }
5915
- return Object.keys(this.tableBubbleMenu()).length === 0
5916
- ? base
5917
- : { ...base, ...this.tableBubbleMenu() };
6428
+ return base;
5918
6429
  }, ...(ngDevMode ? [{ debugName: "finalTableBubbleMenuConfig" }] : []));
5919
- this.finalShowCellBubbleMenu = computed(() => this.config().showCellMenu ?? this.showCellBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
6430
+ this.finalShowCellBubbleMenu = computed(() => this.showCellBubbleMenu() ?? this.effectiveConfig().showCellMenu, ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
5920
6431
  this.finalCellBubbleMenuConfig = computed(() => {
5921
- const fromConfig = this.config().cellBubbleMenu;
6432
+ const fromInput = this.cellBubbleMenu();
6433
+ const fromConfig = this.effectiveConfig().cellBubbleMenu;
5922
6434
  const base = ATE_DEFAULT_CELL_MENU_CONFIG;
6435
+ if (fromInput && Object.keys(fromInput).length > 0) {
6436
+ return { ...base, ...fromInput };
6437
+ }
5923
6438
  if (fromConfig) {
5924
6439
  return { ...base, ...fromConfig };
5925
6440
  }
5926
- return Object.keys(this.cellBubbleMenu()).length === 0
5927
- ? base
5928
- : { ...base, ...this.cellBubbleMenu() };
6441
+ return base;
5929
6442
  }, ...(ngDevMode ? [{ debugName: "finalCellBubbleMenuConfig" }] : []));
5930
- this.finalEnableSlashCommands = computed(() => this.config().enableSlashCommands ?? this.enableSlashCommands(), ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
6443
+ this.finalEnableSlashCommands = computed(() => this.enableSlashCommands() ?? this.effectiveConfig().enableSlashCommands, ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
5931
6444
  this.finalSlashCommandsConfig = computed(() => {
5932
- const fromConfig = this.config().slashCommands;
5933
- const customConfig = this.customSlashCommands();
6445
+ const fromInputComponent = this.customSlashCommands();
6446
+ const fromConfigComponent = this.effectiveConfig().customSlashCommands;
6447
+ const customConfig = fromInputComponent ?? fromConfigComponent;
5934
6448
  if (customConfig) {
5935
6449
  return customConfig;
5936
6450
  }
5937
- let baseConfig = this.slashCommands();
5938
- if (fromConfig) {
5939
- baseConfig = fromConfig;
5940
- }
6451
+ const fromInputOptions = this.slashCommands();
6452
+ const fromConfigOptions = this.effectiveConfig().slashCommands;
6453
+ const baseConfig = fromInputOptions && Object.keys(fromInputOptions).length > 0
6454
+ ? fromInputOptions
6455
+ : fromConfigOptions;
5941
6456
  return {
5942
- commands: filterSlashCommands(baseConfig, this.i18nService, this.editorCommandsService, this.finalImageUploadConfig()),
6457
+ commands: filterSlashCommands(baseConfig || {}, this.i18nService, this.editorCommandsService, this.finalImageUploadConfig()),
5943
6458
  };
5944
6459
  }, ...(ngDevMode ? [{ debugName: "finalSlashCommandsConfig" }] : []));
5945
6460
  // Behavior
5946
- this.finalAutofocus = computed(() => this.config().autofocus ?? this.autofocus(), ...(ngDevMode ? [{ debugName: "finalAutofocus" }] : []));
5947
- this.finalMaxCharacters = computed(() => this.config().maxCharacters ?? this.maxCharacters(), ...(ngDevMode ? [{ debugName: "finalMaxCharacters" }] : []));
5948
- this.finalShowCharacterCount = computed(() => this.config().showCharacterCount ?? this.showCharacterCount(), ...(ngDevMode ? [{ debugName: "finalShowCharacterCount" }] : []));
5949
- this.finalShowWordCount = computed(() => this.config().showWordCount ?? this.showWordCount(), ...(ngDevMode ? [{ debugName: "finalShowWordCount" }] : []));
5950
- this.finalLocale = computed(() => this.config().locale ?? this.locale(), ...(ngDevMode ? [{ debugName: "finalLocale" }] : []));
6461
+ this.finalAutofocus = computed(() => this.autofocus() ?? this.effectiveConfig().autofocus, ...(ngDevMode ? [{ debugName: "finalAutofocus" }] : []));
6462
+ this.finalMaxCharacters = computed(() => this.maxCharacters() ?? this.effectiveConfig().maxCharacters, ...(ngDevMode ? [{ debugName: "finalMaxCharacters" }] : []));
6463
+ this.finalShowCharacterCount = computed(() => this.showCharacterCount() ?? this.effectiveConfig().showCharacterCount, ...(ngDevMode ? [{ debugName: "finalShowCharacterCount" }] : []));
6464
+ this.finalShowWordCount = computed(() => this.showWordCount() ?? this.effectiveConfig().showWordCount, ...(ngDevMode ? [{ debugName: "finalShowWordCount" }] : []));
6465
+ this.finalLocale = computed(() => this.locale() ?? this.effectiveConfig().locale, ...(ngDevMode ? [{ debugName: "finalLocale" }] : []));
6466
+ // Extensions & Options
6467
+ this.finalTiptapExtensions = computed(() => this.tiptapExtensions() ?? this.effectiveConfig().tiptapExtensions ?? [], ...(ngDevMode ? [{ debugName: "finalTiptapExtensions" }] : []));
6468
+ this.finalTiptapOptions = computed(() => this.tiptapOptions() ?? this.effectiveConfig().tiptapOptions ?? {}, ...(ngDevMode ? [{ debugName: "finalTiptapOptions" }] : []));
6469
+ this.finalStateCalculators = computed(() => this.stateCalculators() ?? this.effectiveConfig().stateCalculators ?? [], ...(ngDevMode ? [{ debugName: "finalStateCalculators" }] : []));
6470
+ this.finalAngularNodesConfig = computed(() => this.effectiveConfig().angularNodes ?? [], ...(ngDevMode ? [{ debugName: "finalAngularNodesConfig" }] : []));
5951
6471
  // Image Upload
5952
6472
  this.finalImageUploadConfig = computed(() => {
5953
- const fromConfig = this.config().imageUpload;
5954
6473
  const fromInput = this.imageUpload();
6474
+ const fromConfig = this.effectiveConfig().imageUpload;
5955
6475
  const merged = {
5956
6476
  maxSize: 5, // Default 5MB
5957
6477
  maxWidth: 1920,
@@ -5962,15 +6482,15 @@ class AngularTiptapEditorComponent {
5962
6482
  multiple: false,
5963
6483
  compressImages: true,
5964
6484
  quality: 0.8,
5965
- ...fromInput,
5966
6485
  ...fromConfig,
6486
+ ...fromInput,
5967
6487
  };
5968
6488
  return {
5969
6489
  ...merged,
5970
6490
  maxSize: merged.maxSize * 1024 * 1024, // Convert MB to bytes for internal service
5971
6491
  };
5972
6492
  }, ...(ngDevMode ? [{ debugName: "finalImageUploadConfig" }] : []));
5973
- this.finalImageUploadHandler = computed(() => this.config().imageUpload?.handler ?? this.imageUploadHandler(), ...(ngDevMode ? [{ debugName: "finalImageUploadHandler" }] : []));
6493
+ this.finalImageUploadHandler = computed(() => this.imageUploadHandler() ?? this.effectiveConfig().imageUpload?.handler, ...(ngDevMode ? [{ debugName: "finalImageUploadHandler" }] : []));
5974
6494
  // Computed for current translations (allows per-instance override via config or input)
5975
6495
  this.currentTranslations = computed(() => {
5976
6496
  const localeOverride = this.finalLocale();
@@ -5987,6 +6507,17 @@ class AngularTiptapEditorComponent {
5987
6507
  this.editorCommandsService = inject(AteEditorCommandsService);
5988
6508
  // Access editor state via service
5989
6509
  this.editorState = this.editorCommandsService.editorState;
6510
+ this.injector = inject(Injector);
6511
+ this.globalConfig = inject(ATE_GLOBAL_CONFIG, { optional: true });
6512
+ /**
6513
+ * Final merged configuration.
6514
+ * Priority: Input [config] > Global config via provideAteEditor()
6515
+ */
6516
+ this.effectiveConfig = computed(() => {
6517
+ const fromInput = this.config();
6518
+ const fromGlobal = this.globalConfig || {};
6519
+ return { ...ATE_DEFAULT_CONFIG, ...fromGlobal, ...fromInput };
6520
+ }, ...(ngDevMode ? [{ debugName: "effectiveConfig" }] : []));
5990
6521
  // Effect to update editor content (with anti-echo)
5991
6522
  effect(() => {
5992
6523
  const content = this.content(); // Sole reactive dependency
@@ -6008,7 +6539,7 @@ class AngularTiptapEditorComponent {
6008
6539
  if (hasFormControl && !content) {
6009
6540
  return;
6010
6541
  }
6011
- editor.commands.setContent(content, false);
6542
+ editor.commands.setContent(content, { emitUpdate: false });
6012
6543
  });
6013
6544
  });
6014
6545
  // Effect to update height properties
@@ -6053,11 +6584,12 @@ class AngularTiptapEditorComponent {
6053
6584
  }
6054
6585
  }
6055
6586
  });
6056
- // Effect to re-initialize editor when extensions or options change
6587
+ // Effect to re-initialize editor when technical configuration changes
6057
6588
  effect(() => {
6058
- // Monitor extensions and options
6059
- this.tiptapExtensions();
6060
- this.tiptapOptions();
6589
+ // Monitor technical dependencies
6590
+ this.finalTiptapExtensions();
6591
+ this.finalTiptapOptions();
6592
+ this.finalAngularNodesConfig();
6061
6593
  untracked(() => {
6062
6594
  // Only if already initialized (post AfterViewInit)
6063
6595
  if (this.editorFullyInitialized()) {
@@ -6136,7 +6668,7 @@ class AngularTiptapEditorComponent {
6136
6668
  AteImageCalculator,
6137
6669
  AteStructureCalculator,
6138
6670
  AteDiscoveryCalculator,
6139
- ...this.stateCalculators(),
6671
+ ...this.finalStateCalculators(),
6140
6672
  ],
6141
6673
  }),
6142
6674
  ];
@@ -6153,20 +6685,34 @@ class AngularTiptapEditorComponent {
6153
6685
  limit: this.finalMaxCharacters(),
6154
6686
  }));
6155
6687
  }
6688
+ // Register automatic node views from config
6689
+ const autoNodeViews = this.finalAngularNodesConfig();
6690
+ autoNodeViews.forEach((reg) => {
6691
+ const options = typeof reg === "function"
6692
+ ? { component: reg }
6693
+ : reg;
6694
+ try {
6695
+ const extension = registerAngularComponent(this.injector, options);
6696
+ extensions.push(extension);
6697
+ }
6698
+ catch (e) {
6699
+ console.error("[ATE] Failed to auto-register node view:", e);
6700
+ }
6701
+ });
6156
6702
  // Allow addition of custom extensions, but avoid duplicates by filtering by name
6157
- const customExtensions = this.tiptapExtensions();
6703
+ const customExtensions = this.finalTiptapExtensions();
6158
6704
  if (customExtensions.length > 0) {
6159
6705
  const existingNames = new Set(extensions
6160
- .map(ext => ext?.name)
6706
+ .map((ext) => ext?.name)
6161
6707
  .filter((name) => !!name));
6162
- const toAdd = customExtensions.filter(ext => {
6708
+ const toAdd = customExtensions.filter((ext) => {
6163
6709
  const name = ext?.name;
6164
6710
  return !name || !existingNames.has(name);
6165
6711
  });
6166
6712
  extensions.push(...toAdd);
6167
6713
  }
6168
6714
  // Also allow any tiptap user options
6169
- const userOptions = this.tiptapOptions();
6715
+ const userOptions = this.finalTiptapOptions();
6170
6716
  const newEditor = new Editor({
6171
6717
  ...userOptions,
6172
6718
  element: this.editorElement().nativeElement,
@@ -6562,568 +7108,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6562
7108
  <!-- Table Menu -->
6563
7109
  @if (finalEditable() && finalShowTableBubbleMenu() && editor()) {
6564
7110
  <ate-table-bubble-menu
6565
- [editor]="editor()!"
6566
- [config]="finalTableBubbleMenuConfig()"
6567
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-table-bubble-menu>
6568
- }
6569
-
6570
- <!-- Cell Menu -->
6571
- @if (finalEditable() && finalShowCellBubbleMenu() && editor()) {
6572
- <ate-cell-bubble-menu
6573
- [editor]="editor()!"
6574
- [config]="finalCellBubbleMenuConfig()"
6575
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-cell-bubble-menu>
6576
- }
6577
-
6578
- <!-- Counters -->
6579
- @if (
6580
- finalEditable() &&
6581
- !mergedDisabled() &&
6582
- finalShowFooter() &&
6583
- (finalShowCharacterCount() || finalShowWordCount())
6584
- ) {
6585
- <div
6586
- class="character-count"
6587
- [class.limit-reached]="finalMaxCharacters() && characterCount() >= finalMaxCharacters()!">
6588
- @if (finalShowCharacterCount()) {
6589
- {{ characterCount() }}
6590
- {{ currentTranslations().editor.character }}{{ characterCount() > 1 ? "s" : "" }}
6591
- @if (finalMaxCharacters()) {
6592
- / {{ finalMaxCharacters() }}
6593
- }
6594
- }
6595
-
6596
- @if (finalShowCharacterCount() && finalShowWordCount()) {
6597
- ,
6598
- }
6599
-
6600
- @if (finalShowWordCount()) {
6601
- {{ wordCount() }}
6602
- {{ currentTranslations().editor.word }}{{ wordCount() > 1 ? "s" : "" }}
6603
- }
6604
- </div>
6605
- }
6606
- </div>
6607
- `, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"] }]
6608
- }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], fillContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillContainer", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], showCharacterCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCharacterCount", required: false }] }], showWordCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWordCount", required: false }] }], maxCharacters: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxCharacters", required: false }] }], enableOfficePaste: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableOfficePaste", required: false }] }], enableSlashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableSlashCommands", required: false }] }], slashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "slashCommands", required: false }] }], customSlashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "customSlashCommands", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], seamless: [{ type: i0.Input, args: [{ isSignal: true, alias: "seamless", required: false }] }], floatingToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingToolbar", required: false }] }], showEditToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditToggle", required: false }] }], spellcheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellcheck", required: false }] }], tiptapExtensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "tiptapExtensions", required: false }] }], tiptapOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "tiptapOptions", required: false }] }], showBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBubbleMenu", required: false }] }], bubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "bubbleMenu", required: false }] }], showImageBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showImageBubbleMenu", required: false }] }], imageBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageBubbleMenu", required: false }] }], toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }], showTableBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTableBubbleMenu", required: false }] }], tableBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableBubbleMenu", required: false }] }], showCellBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCellBubbleMenu", required: false }] }], cellBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellBubbleMenu", required: false }] }], stateCalculators: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateCalculators", required: false }] }], imageUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUpload", required: false }] }], imageUploadHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploadHandler", required: false }] }], contentChange: [{ type: i0.Output, args: ["contentChange"] }], editorCreated: [{ type: i0.Output, args: ["editorCreated"] }], editorUpdate: [{ type: i0.Output, args: ["editorUpdate"] }], editorFocus: [{ type: i0.Output, args: ["editorFocus"] }], editorBlur: [{ type: i0.Output, args: ["editorBlur"] }], editableChange: [{ type: i0.Output, args: ["editableChange"] }], editorElement: [{ type: i0.ViewChild, args: ["editorElement", { isSignal: true }] }] } });
6609
-
6610
- /**
6611
- * Clés des boutons de la barre d'outils native
6612
- */
6613
- const ATE_TOOLBAR_KEYS = [
6614
- "bold",
6615
- "italic",
6616
- "underline",
6617
- "strike",
6618
- "code",
6619
- "codeBlock",
6620
- "superscript",
6621
- "subscript",
6622
- "highlight",
6623
- "highlightPicker",
6624
- "heading1",
6625
- "heading2",
6626
- "heading3",
6627
- "bulletList",
6628
- "orderedList",
6629
- "blockquote",
6630
- "alignLeft",
6631
- "alignCenter",
6632
- "alignRight",
6633
- "alignJustify",
6634
- "link",
6635
- "image",
6636
- "horizontalRule",
6637
- "table",
6638
- "undo",
6639
- "redo",
6640
- "clear",
6641
- "textColor",
6642
- "separator",
6643
- ];
6644
-
6645
- /**
6646
- * Clés des options du menu bulle de texte
6647
- */
6648
- const ATE_BUBBLE_MENU_KEYS = [
6649
- "bold",
6650
- "italic",
6651
- "underline",
6652
- "strike",
6653
- "code",
6654
- "superscript",
6655
- "subscript",
6656
- "highlight",
6657
- "highlightPicker",
6658
- "textColor",
6659
- "link",
6660
- "separator",
6661
- ];
6662
- /**
6663
- * Clés des options du menu bulle d'image
6664
- */
6665
- const ATE_IMAGE_BUBBLE_MENU_KEYS = [
6666
- "changeImage",
6667
- "resizeSmall",
6668
- "resizeMedium",
6669
- "resizeLarge",
6670
- "resizeOriginal",
6671
- "deleteImage",
6672
- "separator",
6673
- ];
6674
- /**
6675
- * Clés des options du menu de table
6676
- */
6677
- const ATE_TABLE_BUBBLE_MENU_KEYS = [
6678
- "addRowBefore",
6679
- "addRowAfter",
6680
- "deleteRow",
6681
- "addColumnBefore",
6682
- "addColumnAfter",
6683
- "deleteColumn",
6684
- "deleteTable",
6685
- "toggleHeaderRow",
6686
- "toggleHeaderColumn",
6687
- "separator",
6688
- ];
6689
- /**
6690
- * Clés des options du menu de cellule
6691
- */
6692
- const ATE_CELL_BUBBLE_MENU_KEYS = ["mergeCells", "splitCell"];
6693
-
6694
- /**
6695
- * Base abstract class for Angular components used as TipTap NodeViews.
6696
- *
6697
- * Extend this class in your custom components to get access to the TipTap node properties.
6698
- *
6699
- * @example
6700
- * ```typescript
6701
- * @Component({
6702
- * selector: 'app-counter',
6703
- * template: `
6704
- * <div>
6705
- * <button (click)="increment()">Count: {{ node().attrs['count'] }}</button>
6706
- * </div>
6707
- * `
6708
- * })
6709
- * export class CounterComponent extends AteAngularNodeView {
6710
- * increment() {
6711
- * const count = this.node().attrs['count'] || 0;
6712
- * this.updateAttributes({ count: count + 1 });
6713
- * }
6714
- * }
6715
- * ```
6716
- */
6717
- class AteAngularNodeView {
6718
- /**
6719
- * Internal method to initialize the component with NodeView props.
6720
- * This is called by the AngularNodeViewRenderer.
6721
- *
6722
- * @internal
6723
- */
6724
- _initNodeView(props) {
6725
- // Create signals from the props
6726
- const editorSignal = signal(props.editor, ...(ngDevMode ? [{ debugName: "editorSignal" }] : []));
6727
- const nodeSignal = signal(props.node, ...(ngDevMode ? [{ debugName: "nodeSignal" }] : []));
6728
- const decorationsSignal = signal(props.decorations, ...(ngDevMode ? [{ debugName: "decorationsSignal" }] : []));
6729
- const selectedSignal = signal(props.selected, ...(ngDevMode ? [{ debugName: "selectedSignal" }] : []));
6730
- const extensionSignal = signal(props.extension, ...(ngDevMode ? [{ debugName: "extensionSignal" }] : []));
6731
- // Assign to the component
6732
- this.editor = editorSignal.asReadonly();
6733
- this.node = nodeSignal.asReadonly();
6734
- this.decorations = decorationsSignal.asReadonly();
6735
- this.selected = selectedSignal.asReadonly();
6736
- this.extension = extensionSignal.asReadonly();
6737
- this.getPos = props.getPos;
6738
- this.updateAttributes = props.updateAttributes;
6739
- this.deleteNode = props.deleteNode;
6740
- // Store writable signals for updates
6741
- this._writableSignals = {
6742
- node: nodeSignal,
6743
- decorations: decorationsSignal,
6744
- selected: selectedSignal,
6745
- };
6746
- }
6747
- /**
6748
- * Internal method to update the component when the node changes.
6749
- * This is called by the AngularNodeViewRenderer.
6750
- *
6751
- * @internal
6752
- */
6753
- _updateNodeView(node, decorations) {
6754
- if (this._writableSignals) {
6755
- this._writableSignals.node.set(node);
6756
- this._writableSignals.decorations.set(decorations);
6757
- }
6758
- }
6759
- /**
6760
- * Internal method to update the selection state.
6761
- * This is called by the AngularNodeViewRenderer.
6762
- *
6763
- * @internal
6764
- */
6765
- _selectNodeView() {
6766
- if (this._writableSignals) {
6767
- this._writableSignals.selected.set(true);
6768
- }
6769
- }
6770
- /**
6771
- * Internal method to update the deselection state.
6772
- * This is called by the AngularNodeViewRenderer.
6773
- *
6774
- * @internal
6775
- */
6776
- _deselectNodeView() {
6777
- if (this._writableSignals) {
6778
- this._writableSignals.selected.set(false);
6779
- }
6780
- }
6781
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
6782
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AteAngularNodeView, isStandalone: true, ngImport: i0 }); }
6783
- }
6784
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, decorators: [{
6785
- type: Directive
6786
- }] });
6787
-
6788
- /**
6789
- * Universal Renderer for Angular Components as TipTap NodeViews.
6790
- *
6791
- * Supports:
6792
- * - TipTap-Aware components (extending AteAngularNodeView)
6793
- * - Standard library components (automatic @Input() sync)
6794
- * - Signal-based inputs and outputs (Angular 18+)
6795
- * - Content projection (editableContent)
6796
- * - Unified lifecycle and event management
6797
- */
6798
- function AteNodeViewRenderer(component, options) {
6799
- return props => {
6800
- const { node, view: _view, getPos, decorations, editor, extension } = props;
6801
- const { injector, inputs = {}, wrapperTag = "div", wrapperClass, editableContent = false, contentSelector, onOutput, ignoreMutation = true, } = options;
6802
- const dom = document.createElement(wrapperTag);
6803
- if (wrapperClass) {
6804
- dom.className = wrapperClass;
6805
- }
6806
- const applicationRef = injector.get(ApplicationRef);
6807
- const environmentInjector = injector.get(EnvironmentInjector);
6808
- // 1. Setup Content Projection (ng-content)
6809
- let initialNodes = [];
6810
- let contentDOM = null;
6811
- if (editableContent && !contentSelector) {
6812
- contentDOM = document.createElement("div");
6813
- contentDOM.setAttribute("data-node-view-content", "");
6814
- initialNodes = [[contentDOM]];
6815
- }
6816
- // 2. Create the Angular Component
6817
- const componentRef = createComponent(component, {
6818
- environmentInjector,
6819
- elementInjector: injector,
6820
- projectableNodes: initialNodes,
6821
- });
6822
- const instance = componentRef.instance;
6823
- const subscriptions = [];
6824
- // 3. Initialize TipTap-Aware instances
6825
- if (instance instanceof AteAngularNodeView) {
6826
- instance._initNodeView({
6827
- editor,
6828
- node,
6829
- decorations,
6830
- selected: false,
6831
- extension,
6832
- getPos,
6833
- updateAttributes: attrs => editor.commands.updateAttributes(node.type.name, attrs),
6834
- deleteNode: () => {
6835
- const pos = getPos();
6836
- if (pos !== undefined) {
6837
- editor.commands.deleteRange({ from: pos, to: pos + node.nodeSize });
6838
- }
6839
- },
6840
- });
6841
- }
6842
- // 4. Synchronize Inputs (Handles standard @Input and Signal-based inputs)
6843
- const syncInputs = (nodeToSync) => {
6844
- // Combine base inputs from options with dynamic node attributes
6845
- const mergedInputs = { ...inputs, ...nodeToSync.attrs };
6846
- Object.entries(mergedInputs).forEach(([key, value]) => {
6847
- if (key !== "id" && value !== null && value !== undefined) {
6848
- try {
6849
- componentRef.setInput(key, value);
6850
- }
6851
- catch {
6852
- // Silently ignore inputs that don't exist on the component
6853
- }
6854
- }
6855
- });
6856
- };
6857
- syncInputs(node);
6858
- // 5. Setup Outputs (Handles EventEmitter and OutputRef)
6859
- if (onOutput) {
6860
- Object.entries(instance).forEach(([key, potentialOutput]) => {
6861
- if (potentialOutput &&
6862
- typeof potentialOutput
6863
- .subscribe === "function") {
6864
- const sub = potentialOutput.subscribe((value) => {
6865
- onOutput(key, value);
6866
- });
6867
- if (sub instanceof Subscription) {
6868
- subscriptions.push(sub);
6869
- }
6870
- }
6871
- });
6872
- }
6873
- // 6. Attach to DOM and ApplicationRef
6874
- applicationRef.attachView(componentRef.hostView);
6875
- const componentElement = componentRef.location.nativeElement;
6876
- // Target specific element for content if selector provided
6877
- if (editableContent && contentSelector) {
6878
- contentDOM = componentElement.querySelector(contentSelector);
6879
- }
6880
- dom.appendChild(componentElement);
6881
- // Initial detection to ensure the component is rendered
6882
- componentRef.changeDetectorRef.detectChanges();
6883
- // 7. Return the TipTap NodeView Interface
6884
- return {
6885
- dom,
6886
- contentDOM,
6887
- update: (updatedNode, updatedDecorations) => {
6888
- if (updatedNode.type !== node.type) {
6889
- return false;
6890
- }
6891
- // Update Aware component signals
6892
- if (instance instanceof AteAngularNodeView) {
6893
- instance._updateNodeView(updatedNode, updatedDecorations);
6894
- }
6895
- // Update inputs
6896
- syncInputs(updatedNode);
6897
- // Notify and Detect changes
6898
- componentRef.changeDetectorRef.detectChanges();
6899
- if (options.onUpdate) {
6900
- options.onUpdate(updatedNode);
6901
- }
6902
- return true;
6903
- },
6904
- selectNode: () => {
6905
- if (instance instanceof AteAngularNodeView) {
6906
- instance._selectNodeView();
6907
- }
6908
- dom.classList.add("ProseMirror-selectednode");
6909
- },
6910
- deselectNode: () => {
6911
- if (instance instanceof AteAngularNodeView) {
6912
- instance._deselectNodeView();
6913
- }
6914
- dom.classList.remove("ProseMirror-selectednode");
6915
- },
6916
- destroy: () => {
6917
- if (options.onDestroy) {
6918
- options.onDestroy();
6919
- }
6920
- subscriptions.forEach(s => s.unsubscribe());
6921
- applicationRef.detachView(componentRef.hostView);
6922
- componentRef.destroy();
6923
- },
6924
- stopEvent: event => {
6925
- const target = event.target;
6926
- const isEditable = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
6927
- return isEditable;
6928
- },
6929
- ignoreMutation: mutation => {
6930
- if (typeof ignoreMutation === "function") {
6931
- return ignoreMutation(mutation);
6932
- }
6933
- return ignoreMutation;
6934
- },
6935
- };
6936
- };
7111
+ [editor]="editor()!"
7112
+ [config]="finalTableBubbleMenuConfig()"
7113
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-table-bubble-menu>
7114
+ }
7115
+
7116
+ <!-- Cell Menu -->
7117
+ @if (finalEditable() && finalShowCellBubbleMenu() && editor()) {
7118
+ <ate-cell-bubble-menu
7119
+ [editor]="editor()!"
7120
+ [config]="finalCellBubbleMenuConfig()"
7121
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-cell-bubble-menu>
7122
+ }
7123
+
7124
+ <!-- Counters -->
7125
+ @if (
7126
+ finalEditable() &&
7127
+ !mergedDisabled() &&
7128
+ finalShowFooter() &&
7129
+ (finalShowCharacterCount() || finalShowWordCount())
7130
+ ) {
7131
+ <div
7132
+ class="character-count"
7133
+ [class.limit-reached]="finalMaxCharacters() && characterCount() >= finalMaxCharacters()!">
7134
+ @if (finalShowCharacterCount()) {
7135
+ {{ characterCount() }}
7136
+ {{ currentTranslations().editor.character }}{{ characterCount() > 1 ? "s" : "" }}
7137
+ @if (finalMaxCharacters()) {
7138
+ / {{ finalMaxCharacters() }}
7139
+ }
7140
+ }
7141
+
7142
+ @if (finalShowCharacterCount() && finalShowWordCount()) {
7143
+ ,
7144
+ }
7145
+
7146
+ @if (finalShowWordCount()) {
7147
+ {{ wordCount() }}
7148
+ {{ currentTranslations().editor.word }}{{ wordCount() > 1 ? "s" : "" }}
7149
+ }
7150
+ </div>
7151
+ }
7152
+ </div>
7153
+ `, styles: [":host{--ate-primary: #2563eb;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-surface: #ffffff;--ate-surface-secondary: #f8f9fa;--ate-surface-tertiary: #f1f5f9;--ate-text: #2d3748;--ate-text-secondary: #64748b;--ate-text-muted: #a0aec0;--ate-border: #e2e8f0;--ate-highlight-bg: #fef08a;--ate-highlight-color: #854d0e;--ate-button-hover: #f1f5f9;--ate-button-active: #e2e8f0;--ate-error-color: #c53030;--ate-error-bg: #fed7d7;--ate-error-border: #feb2b2;--ate-border-color: var(--ate-border);--ate-border-width: 2px;--ate-border-radius: 12px;--ate-focus-color: var(--ate-primary);--ate-background: var(--ate-surface);--ate-sub-border-radius: 8px;--ate-text-color: var(--ate-text);--ate-placeholder-color: var(--ate-text-muted);--ate-line-height: 1.6;--ate-content-padding: 16px;--ate-menu-bg: var(--ate-surface);--ate-menu-border-radius: var(--ate-border-radius);--ate-menu-border: var(--ate-border);--ate-menu-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);--ate-menu-padding: 6px;--ate-toolbar-padding: var(--ate-menu-padding);--ate-toolbar-background: var(--ate-surface-secondary);--ate-toolbar-border-color: var(--ate-border);--ate-toolbar-button-color: var(--ate-text-secondary);--ate-toolbar-button-hover-background: transparent;--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-counter-color: var(--ate-text-secondary);--ate-counter-background: var(--ate-surface-secondary);--ate-counter-border-color: var(--ate-border);--ate-drag-background: #f0f8ff;--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-border);--ate-blockquote-background: var(--ate-surface-secondary);--ate-code-background: var(--ate-surface-secondary);--ate-code-color: var(--ate-code-color);--ate-code-border-color: var(--ate-border);--ate-code-block-background: #0f172a;--ate-code-block-color: #e2e8f0;--ate-code-block-border-color: var(--ate-border);--ate-image-border-radius: 16px;--ate-image-selected-color: var(--ate-primary);--ate-scrollbar-width: 10px;--ate-scrollbar-thumb: var(--ate-border);--ate-scrollbar-thumb-hover: var(--ate-text-muted);--ate-scrollbar-track: transparent;--ate-table-border-color: var(--ate-border);--ate-table-header-background: var(--ate-surface-secondary);--ate-table-header-color: var(--ate-text);--ate-table-cell-background: var(--ate-surface);--ate-table-cell-selected-background: var(--ate-primary-light);--ate-table-resize-handle-color: var(--ate-primary);--ate-table-row-hover-background: var(--ate-primary-lighter)}:host(.dark),:host([data-theme=\"dark\"]){--ate-primary: #3b82f6;--ate-primary-contrast: #ffffff;--ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 85%);--ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 92%);--ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 80%);--ate-surface: #020617;--ate-surface-secondary: #0f172a;--ate-surface-tertiary: #1e293b;--ate-text: #f8fafc;--ate-text-secondary: #94a3b8;--ate-text-muted: #64748b;--ate-border: #1e293b;--ate-highlight-bg: #854d0e;--ate-highlight-color: #fef08a;--ate-button-hover: #1e293b;--ate-button-active: #0f172a;--ate-menu-border: rgba(255, 255, 255, .1);--ate-menu-shadow: 0 20px 25px -5px rgba(0, 0, 0, .3), 0 10px 10px -5px rgba(0, 0, 0, .2);--ate-error-color: #f87171;--ate-error-bg: #450a0a;--ate-error-border: #7f1d1d;--ate-drag-background: var(--ate-surface-tertiary);--ate-drag-border-color: var(--ate-primary);--ate-blockquote-border-color: var(--ate-primary);--ate-toolbar-button-active-background: var(--ate-primary-light);--ate-toolbar-button-active-color: var(--ate-primary);--ate-button-hover: var(--ate-surface-tertiary);--ate-button-active: var(--ate-surface-secondary);--ate-scrollbar-thumb: var(--ate-surface-tertiary);--ate-scrollbar-thumb-hover: var(--ate-text-muted)}:host(.fill-container){display:block;height:100%}.ate-editor{border:var(--ate-border-width) solid var(--ate-border-color);border-radius:var(--ate-border-radius);background:var(--ate-background);overflow:visible;transition:border-color .2s ease;position:relative}:host(.floating-toolbar) .ate-editor{overflow:visible}:host(.fill-container) .ate-editor{display:flex;flex-direction:column;height:100%}:host(.fill-container) .ate-content-wrapper{flex:1;min-height:0}:host(.fill-container) .ate-content{flex:1;min-height:0;overflow-y:auto}.ate-editor:focus-within{border-color:var(--ate-focus-color)}.ate-content{min-height:var(--editor-min-height, 200px);height:var(--editor-height, auto);max-height:var(--editor-max-height, none);overflow-y:var(--editor-overflow, visible);outline:none;position:relative;scrollbar-width:thin;scrollbar-color:var(--ate-scrollbar-thumb) var(--ate-scrollbar-track)}:host(.is-disabled) .ate-content{cursor:not-allowed;opacity:.7;-webkit-user-select:none;user-select:none;pointer-events:none;background-color:var(--ate-surface-tertiary)}:host(.is-readonly) .ate-content{cursor:default;-webkit-user-select:text;user-select:text}:host(.is-readonly) .ate-content ::ng-deep .ate-link{cursor:pointer;pointer-events:auto}.ate-content::-webkit-scrollbar{width:var(--ate-scrollbar-width)}.ate-content-wrapper{position:relative;display:flex;flex-direction:column;min-height:0}.ate-content-wrapper .ate-content{flex:1}.ate-content::-webkit-scrollbar-track{background:var(--ate-scrollbar-track)}.ate-content::-webkit-scrollbar-thumb{background:var(--ate-scrollbar-thumb);border:3px solid transparent;background-clip:content-box;border-radius:10px}.ate-content::-webkit-scrollbar-thumb:hover{background:var(--ate-scrollbar-thumb-hover);background-clip:content-box}.ate-content.drag-over{background:var(--ate-drag-background);border:2px dashed var(--ate-drag-border-color)}.character-count{padding:6px 8px;font-size:12px;color:var(--ate-counter-color);text-align:right;border-top:1px solid var(--ate-counter-border-color);background:var(--ate-counter-background);transition:color .2s ease;border-bottom-left-radius:calc(var(--ate-border-radius) - var(--ate-border-width));border-bottom-right-radius:calc(var(--ate-border-radius) - var(--ate-border-width))}.character-count.limit-reached{color:var(--ate-error-color, #ef4444);font-weight:600}:host ::ng-deep .ProseMirror{padding:var(--ate-content-padding);outline:none;line-height:var(--ate-line-height);color:var(--ate-text-color);min-height:100%;height:100%;word-wrap:break-word;overflow-wrap:break-word}:host ::ng-deep .ProseMirror h1{font-size:2em;font-weight:700;margin-top:0;margin-bottom:.5em}:host ::ng-deep .ProseMirror h2{font-size:1.5em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror h3{font-size:1.25em;font-weight:700;margin-top:1em;margin-bottom:.5em}:host ::ng-deep .ProseMirror p{margin:.5em 0}:host ::ng-deep .ProseMirror ul,:host ::ng-deep .ProseMirror ol{padding-left:2em;margin:.5em 0}:host ::ng-deep .ProseMirror blockquote{border-left:4px solid var(--ate-blockquote-border-color);margin:1em 0;background:var(--ate-blockquote-background);padding:.5em 1em;border-radius:0 4px 4px 0}:host ::ng-deep .ProseMirror code{background:var(--ate-code-background);color:var(--ate-code-color);border:1px solid var(--ate-code-border-color);padding:.15em .4em;border-radius:4px;font-family:JetBrains Mono,Fira Code,Monaco,Consolas,monospace;font-size:.85em;font-weight:500}:host ::ng-deep .ProseMirror pre{background:var(--ate-code-block-background);color:var(--ate-code-block-color);border:1px solid var(--ate-code-block-border-color);padding:1em;border-radius:var(--ate-border-radius);overflow-x:auto;margin:1em 0}:host ::ng-deep .ProseMirror pre code{background:none;color:inherit;border:none;padding:0}:host ::ng-deep .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);color:var(--ate-placeholder-color);pointer-events:none;float:left;height:0}:host ::ng-deep .ProseMirror[contenteditable=false]{pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img{cursor:default;pointer-events:none}:host ::ng-deep .ProseMirror[contenteditable=false] img:hover{transform:none;box-shadow:0 2px 8px #0000001a}:host ::ng-deep .ProseMirror[contenteditable=false] img.ProseMirror-selectednode{outline:none}:host ::ng-deep .ProseMirror img{position:relative;display:inline-block;max-width:100%;height:auto;cursor:pointer;transition:all .2s ease;border:2px solid transparent;border-radius:var(--ate-image-border-radius)}:host ::ng-deep .ProseMirror img:hover{border-color:var(--ate-border-color);box-shadow:0 2px 4px #0000001a}:host ::ng-deep .ProseMirror img.ProseMirror-selectednode{border-color:var(--ate-image-selected-color);box-shadow:0 0 0 3px var(--ate-primary-light-alpha);transition:all .2s ease}:host ::ng-deep .ProseMirror .tiptap-image{max-width:100%;height:auto;border-radius:var(--ate-image-border-radius);box-shadow:0 4px 20px #00000014;margin:.5em 0;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:block;filter:brightness(1) contrast(1)}:host ::ng-deep .ProseMirror .tiptap-image:hover{box-shadow:0 8px 30px #0000001f;filter:brightness(1.02) contrast(1.02)}:host ::ng-deep .ProseMirror .tiptap-image.ProseMirror-selectednode{outline:2px solid var(--ate-primary);outline-offset:2px;border-radius:var(--ate-image-border-radius);box-shadow:0 0 0 4px var(--ate-primary-light-alpha)}:host ::ng-deep .image-container{margin:.5em 0;text-align:center;border-radius:16px;overflow:hidden;transition:all .3s cubic-bezier(.4,0,.2,1)}:host ::ng-deep .image-container.image-align-left{text-align:left}:host ::ng-deep .image-container.image-align-center{text-align:center}:host ::ng-deep .image-container.image-align-right{text-align:right}:host ::ng-deep .image-container img{display:inline-block;max-width:100%;height:auto;border-radius:16px}:host ::ng-deep .resizable-image-container{position:relative;display:inline-block;margin:.5em 0}:host ::ng-deep .resize-controls{position:absolute;inset:0;pointer-events:none;z-index:1000}:host ::ng-deep .resize-handle{position:absolute;width:12px;height:12px;background:var(--ate-primary);border:2px solid var(--ate-surface);border-radius:50%;pointer-events:all;cursor:pointer;z-index:1001;transition:all .15s ease;box-shadow:0 2px 6px #0003}:host ::ng-deep .resize-handle:hover{background:var(--ate-primary);box-shadow:0 3px 8px #0000004d}:host ::ng-deep .resize-handle:active{background:var(--ate-primary)}:host ::ng-deep .resize-handle-n:hover,:host ::ng-deep .resize-handle-s:hover{transform:translate(-50%) scale(1.2)}:host ::ng-deep .resize-handle-w:hover,:host ::ng-deep .resize-handle-e:hover{transform:translateY(-50%) scale(1.2)}:host ::ng-deep .resize-handle-n:active,:host ::ng-deep .resize-handle-s:active{transform:translate(-50%) scale(.9)}:host ::ng-deep .resize-handle-w:active,:host ::ng-deep .resize-handle-e:active{transform:translateY(-50%) scale(.9)}:host ::ng-deep .resize-handle-nw:hover,:host ::ng-deep .resize-handle-ne:hover,:host ::ng-deep .resize-handle-sw:hover,:host ::ng-deep .resize-handle-se:hover{transform:scale(1.2)}:host ::ng-deep .resize-handle-nw:active,:host ::ng-deep .resize-handle-ne:active,:host ::ng-deep .resize-handle-sw:active,:host ::ng-deep .resize-handle-se:active{transform:scale(.9)}:host ::ng-deep .resize-handle-nw{top:0;left:-6px;cursor:nw-resize}:host ::ng-deep .resize-handle-n{top:0;left:50%;transform:translate(-50%);cursor:n-resize}:host ::ng-deep .resize-handle-ne{top:0;right:-6px;cursor:ne-resize}:host ::ng-deep .resize-handle-w{top:50%;left:-6px;transform:translateY(-50%);cursor:w-resize}:host ::ng-deep .resize-handle-e{top:50%;right:-6px;transform:translateY(-50%);cursor:e-resize}:host ::ng-deep .resize-handle-sw{bottom:0;left:-6px;cursor:sw-resize}:host ::ng-deep .resize-handle-s{bottom:0;left:50%;transform:translate(-50%);cursor:s-resize}:host ::ng-deep .resize-handle-se{bottom:0;right:-6px;cursor:se-resize}:host ::ng-deep body.resizing{-webkit-user-select:none;user-select:none;cursor:crosshair}:host ::ng-deep body.resizing .ProseMirror{pointer-events:none}:host ::ng-deep body.resizing .ProseMirror .tiptap-image{pointer-events:none}:host ::ng-deep .image-size-info{position:absolute;bottom:-20px;left:50%;transform:translate(-50%);background:#000c;color:#fff;padding:2px 6px;border-radius:3px;font-size:11px;white-space:nowrap;opacity:0;transition:opacity .2s ease}:host ::ng-deep .image-container:hover .image-size-info{opacity:1}:host ::ng-deep .ProseMirror table{border-collapse:separate;border-spacing:0;margin:0;table-layout:fixed;width:100%;border-radius:8px;overflow:hidden}:host ::ng-deep .ProseMirror table td,:host ::ng-deep .ProseMirror table th{border:none;border-right:1px solid var(--ate-table-border-color);border-bottom:1px solid var(--ate-table-border-color);box-sizing:border-box;min-width:1em;padding:8px 12px;position:relative;vertical-align:top;text-align:left}:host ::ng-deep .ProseMirror table td{background:var(--ate-table-cell-background)}:host ::ng-deep .ProseMirror table td:first-child,:host ::ng-deep .ProseMirror table th:first-child{border-left:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child td,:host ::ng-deep .ProseMirror table tr:first-child th{border-top:1px solid var(--ate-table-border-color)}:host ::ng-deep .ProseMirror table tr:first-child th:first-child{border-top-left-radius:8px}:host ::ng-deep .ProseMirror table tr:first-child th:last-child{border-top-right-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:first-child{border-bottom-left-radius:8px}:host ::ng-deep .ProseMirror table tr:last-child td:last-child{border-bottom-right-radius:8px}:host ::ng-deep .ProseMirror table th{background:var(--ate-table-header-background);font-weight:600;color:var(--ate-table-header-color)}:host ::ng-deep .ProseMirror table .selectedCell:after{background:var(--ate-table-cell-selected-background);content:\"\";inset:0;pointer-events:none;position:absolute;z-index:2}:host ::ng-deep .ProseMirror table .column-resize-handle{position:absolute;right:-2px;top:0;bottom:0;width:4px;background-color:var(--ate-table-resize-handle-color);opacity:0;transition:opacity .2s ease}:host ::ng-deep .ProseMirror table:hover .column-resize-handle{opacity:1}:host ::ng-deep .ProseMirror table .column-resize-handle:hover{background-color:var(--ate-focus-color)}:host ::ng-deep .ProseMirror .tableWrapper{overflow-x:auto;margin:1em 0;border-radius:8px}:host ::ng-deep .ProseMirror .tableWrapper table{margin:0;border-radius:8px;min-width:600px;overflow:hidden}:host ::ng-deep .ProseMirror table p{margin:0}:host ::ng-deep .ProseMirror table tbody tr:hover td{background-color:var(--ate-table-row-hover-background)}\n"] }]
7154
+ }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], fillContainer: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillContainer", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], showCharacterCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCharacterCount", required: false }] }], showWordCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWordCount", required: false }] }], maxCharacters: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxCharacters", required: false }] }], enableOfficePaste: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableOfficePaste", required: false }] }], enableSlashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableSlashCommands", required: false }] }], slashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "slashCommands", required: false }] }], customSlashCommands: [{ type: i0.Input, args: [{ isSignal: true, alias: "customSlashCommands", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], seamless: [{ type: i0.Input, args: [{ isSignal: true, alias: "seamless", required: false }] }], floatingToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingToolbar", required: false }] }], showEditToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEditToggle", required: false }] }], spellcheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellcheck", required: false }] }], tiptapExtensions: [{ type: i0.Input, args: [{ isSignal: true, alias: "tiptapExtensions", required: false }] }], tiptapOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "tiptapOptions", required: false }] }], showBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBubbleMenu", required: false }] }], bubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "bubbleMenu", required: false }] }], showImageBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showImageBubbleMenu", required: false }] }], imageBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageBubbleMenu", required: false }] }], toolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbar", required: false }] }], showTableBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTableBubbleMenu", required: false }] }], tableBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableBubbleMenu", required: false }] }], showCellBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCellBubbleMenu", required: false }] }], cellBubbleMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellBubbleMenu", required: false }] }], stateCalculators: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateCalculators", required: false }] }], imageUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUpload", required: false }] }], imageUploadHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUploadHandler", required: false }] }], contentChange: [{ type: i0.Output, args: ["contentChange"] }], editorCreated: [{ type: i0.Output, args: ["editorCreated"] }], editorUpdate: [{ type: i0.Output, args: ["editorUpdate"] }], editorFocus: [{ type: i0.Output, args: ["editorFocus"] }], editorBlur: [{ type: i0.Output, args: ["editorBlur"] }], editableChange: [{ type: i0.Output, args: ["editableChange"] }], editorElement: [{ type: i0.ViewChild, args: ["editorElement", { isSignal: true }] }] } });
7155
+
7156
+ /**
7157
+ * Provides the necessary configuration and initialization for the Angular TipTap Editor.
7158
+ * This should be called in your app.config.ts (for standalone) or main.ts.
7159
+ *
7160
+ * This provider is essential for 'Angular Nodes' to work correctly, as it captures the
7161
+ * root Injector required to instantiate custom Angular components within the editor.
7162
+ * @example
7163
+ * ```ts
7164
+ * bootstrapApplication(AppComponent, {
7165
+ * providers: [
7166
+ * provideAteEditor({
7167
+ * theme: 'dark',
7168
+ * mode: 'seamless'
7169
+ * })
7170
+ * ]
7171
+ * });
7172
+ * ```
7173
+ */
7174
+ function provideAteEditor(config) {
7175
+ return makeEnvironmentProviders([
7176
+ {
7177
+ provide: ATE_GLOBAL_CONFIG,
7178
+ useValue: config || {},
7179
+ },
7180
+ provideEnvironmentInitializer(() => {
7181
+ const injector = inject(Injector);
7182
+ setGlobalInjector(injector);
7183
+ }),
7184
+ ]);
6937
7185
  }
6938
7186
 
6939
- const RESERVED_NAMES = [
6940
- "doc",
6941
- "paragraph",
6942
- "text",
6943
- "hardBreak",
7187
+ /**
7188
+ * Clés des boutons de la barre d'outils native
7189
+ */
7190
+ const ATE_TOOLBAR_KEYS = [
7191
+ "bold",
7192
+ "italic",
7193
+ "underline",
7194
+ "strike",
7195
+ "code",
7196
+ "codeBlock",
7197
+ "superscript",
7198
+ "subscript",
7199
+ "highlight",
7200
+ "highlightPicker",
7201
+ "heading1",
7202
+ "heading2",
7203
+ "heading3",
6944
7204
  "bulletList",
6945
7205
  "orderedList",
6946
- "listItem",
6947
7206
  "blockquote",
6948
- "codeBlock",
6949
- "heading",
7207
+ "alignLeft",
7208
+ "alignCenter",
7209
+ "alignRight",
7210
+ "alignJustify",
7211
+ "link",
7212
+ "image",
6950
7213
  "horizontalRule",
7214
+ "table",
7215
+ "undo",
7216
+ "redo",
7217
+ "clear",
7218
+ "textColor",
7219
+ "separator",
6951
7220
  ];
7221
+
6952
7222
  /**
6953
- * Derives the TipTap node name and HTML tag from a component class.
7223
+ * Clés des options du menu bulle de texte
6954
7224
  */
6955
- function deriveMetadata(component, customName) {
6956
- let nodeName = customName;
6957
- if (!nodeName) {
6958
- nodeName = component.name
6959
- .replace(/Component$/, "")
6960
- .replace(/Node$/, "")
6961
- .replace(/^([A-Z])/, m => m.toLowerCase());
6962
- console.warn(`[ATE] Auto-deriving node name '${nodeName}' for component ${component.name}. ` +
6963
- `Provide an explicit 'name' in options to avoid potential naming collisions.`);
6964
- }
6965
- if (RESERVED_NAMES.includes(nodeName)) {
6966
- throw new Error(`[ATE] The name '${nodeName}' is a reserved TipTap node name. ` +
6967
- `Please provide a unique name for your custom component.`);
6968
- }
6969
- const tag = nodeName.toLowerCase().replace(/([A-Z])/g, "-$1");
6970
- return { nodeName, tag };
6971
- }
7225
+ const ATE_BUBBLE_MENU_KEYS = [
7226
+ "bold",
7227
+ "italic",
7228
+ "underline",
7229
+ "strike",
7230
+ "code",
7231
+ "superscript",
7232
+ "subscript",
7233
+ "highlight",
7234
+ "highlightPicker",
7235
+ "textColor",
7236
+ "link",
7237
+ "separator",
7238
+ ];
6972
7239
  /**
6973
- * Creates TipTap attributes from component inputs (Standard Mode).
7240
+ * Clés des options du menu bulle d'image
6974
7241
  */
6975
- function createStandardAttributes(defaultInputs = {}) {
6976
- const tiptapAttributes = {};
6977
- Object.entries(defaultInputs).forEach(([key, defaultValue]) => {
6978
- tiptapAttributes[key] = {
6979
- default: defaultValue,
6980
- parseHTML: (element) => {
6981
- const attr = element.getAttribute(`data-${key}`);
6982
- if (attr === null) {
6983
- return defaultValue;
6984
- }
6985
- try {
6986
- return JSON.parse(attr);
6987
- }
6988
- catch {
6989
- return attr;
6990
- }
6991
- },
6992
- renderHTML: (attrs) => {
6993
- const value = attrs[key];
6994
- if (value === undefined || value === null) {
6995
- return {};
6996
- }
6997
- const serialized = typeof value === "object" ? JSON.stringify(value) : String(value);
6998
- return { [`data-${key}`]: serialized };
6999
- },
7000
- };
7001
- });
7002
- return tiptapAttributes;
7003
- }
7242
+ const ATE_IMAGE_BUBBLE_MENU_KEYS = [
7243
+ "changeImage",
7244
+ "resizeSmall",
7245
+ "resizeMedium",
7246
+ "resizeLarge",
7247
+ "resizeOriginal",
7248
+ "deleteImage",
7249
+ "separator",
7250
+ ];
7004
7251
  /**
7005
- * Factory to transform an Angular component into a TipTap Node extension.
7006
- * Supports both "TipTap-Aware" and "Standard" modes.
7007
- *
7008
- * @internal
7252
+ * Clés des options du menu de table
7009
7253
  */
7010
- function createAngularComponentExtension(injector, options) {
7011
- const { component, name: customName, attributes, defaultInputs, contentSelector, contentMode = "block", editableContent = false, group = "block", draggable = true, ignoreMutation = true, onOutput, HTMLAttributes = {}, parseHTML: customParseHTML, renderHTML: customRenderHTML, } = options;
7012
- const { nodeName, tag } = deriveMetadata(component, customName);
7013
- const isTipTapAware = Object.prototype.isPrototypeOf.call(AteAngularNodeView, component);
7014
- const atom = !editableContent;
7015
- // 1. Prepare Attributes
7016
- const tiptapAttributes = isTipTapAware
7017
- ? attributes || {}
7018
- : createStandardAttributes(defaultInputs);
7019
- // 2. Create Node Extension
7020
- return Node$1.create({
7021
- name: nodeName,
7022
- group,
7023
- inline: group === "inline",
7024
- atom,
7025
- draggable,
7026
- content: editableContent ? (contentMode === "inline" ? "inline*" : "block*") : undefined,
7027
- addAttributes() {
7028
- return tiptapAttributes;
7029
- },
7030
- parseHTML() {
7031
- if (customParseHTML) {
7032
- return customParseHTML.call(this);
7033
- }
7034
- return [{ tag }, { tag: `div[data-component="${nodeName}"]` }];
7035
- },
7036
- renderHTML({ node, HTMLAttributes: attrs }) {
7037
- if (customRenderHTML) {
7038
- return customRenderHTML.call(this, { node, HTMLAttributes: attrs });
7039
- }
7040
- return [tag, mergeAttributes(HTMLAttributes, attrs, { "data-component": nodeName })];
7041
- },
7042
- addNodeView() {
7043
- return AteNodeViewRenderer(component, {
7044
- injector,
7045
- inputs: defaultInputs,
7046
- wrapperTag: group === "inline" ? "span" : "div",
7047
- wrapperClass: isTipTapAware
7048
- ? `ate-node-${nodeName}`
7049
- : `embedded-component embedded-${nodeName}`,
7050
- atom,
7051
- editableContent,
7052
- contentSelector,
7053
- contentMode,
7054
- onOutput,
7055
- ignoreMutation,
7056
- });
7057
- },
7058
- });
7059
- }
7060
-
7254
+ const ATE_TABLE_BUBBLE_MENU_KEYS = [
7255
+ "addRowBefore",
7256
+ "addRowAfter",
7257
+ "deleteRow",
7258
+ "addColumnBefore",
7259
+ "addColumnAfter",
7260
+ "deleteColumn",
7261
+ "deleteTable",
7262
+ "toggleHeaderRow",
7263
+ "toggleHeaderColumn",
7264
+ "separator",
7265
+ ];
7061
7266
  /**
7062
- * Registers ANY Angular component as a TipTap node extension.
7063
- *
7064
- * This function is the single public entry point for adding custom components.
7065
- * It supports two distinct modes:
7066
- *
7067
- * 1. **TipTap-Aware Mode** (the component extends `AteAngularNodeView`):
7068
- * Grants direct access to the TipTap API (`editor`, `node`, `updateAttributes`, etc.) within the component.
7069
- *
7070
- * 2. **Standard Mode** (library or legacy components):
7071
- * Allows using any existing Angular component. Its `@Input()` properties are automatically
7072
- * synchronized with TipTap node attributes.
7073
- *
7074
- * @param injector - The Angular Injector (obtained via `inject(Injector)`)
7075
- * @param options - Configuration options for the component extension
7076
- * @returns A TipTap Node extension ready to be used
7077
- *
7078
- * @example
7079
- * ```typescript
7080
- * registerAngularComponent(injector, {
7081
- * component: MyComponent,
7082
- * name: 'myComponent',
7083
- * defaultInputs: { color: 'blue' }
7084
- * });
7085
- * ```
7267
+ * Clés des options du menu de cellule
7086
7268
  */
7087
- function registerAngularComponent(injector, options) {
7088
- return createAngularComponentExtension(injector, options);
7089
- }
7269
+ const ATE_CELL_BUBBLE_MENU_KEYS = ["mergeCells", "splitCell"];
7090
7270
 
7091
7271
  /*
7092
7272
  * Public API Surface of tiptap-editor
7093
7273
  */
7094
- // Main component
7095
- /** @deprecated Renamed to `ATE_INITIAL_EDITOR_STATE`. This alias will be removed in v3.0.0. */
7096
- const INITIAL_EDITOR_STATE = ATE_INITIAL_EDITOR_STATE;
7097
- /** @deprecated Renamed to `ATE_SLASH_COMMAND_KEYS`. This alias will be removed in v3.0.0. */
7098
- const SLASH_COMMAND_KEYS = ATE_SLASH_COMMAND_KEYS;
7099
- /** @deprecated Renamed to `ATE_DEFAULT_SLASH_COMMANDS_CONFIG`. This alias will be removed in v3.0.0. */
7100
- const DEFAULT_SLASH_COMMANDS_CONFIG = ATE_DEFAULT_SLASH_COMMANDS_CONFIG;
7101
- /** @deprecated Renamed to `AteDiscoveryCalculator`. This alias will be removed in v3.0.0. */
7102
- const DiscoveryCalculator = AteDiscoveryCalculator;
7103
- /** @deprecated Renamed to `AteImageCalculator`. This alias will be removed in v3.0.0. */
7104
- const ImageCalculator = AteImageCalculator;
7105
- /** @deprecated Renamed to `AteMarksCalculator`. This alias will be removed in v3.0.0. */
7106
- const MarksCalculator = AteMarksCalculator;
7107
- /** @deprecated Renamed to `AteSelectionCalculator`. This alias will be removed in v3.0.0. */
7108
- const SelectionCalculator = AteSelectionCalculator;
7109
- /** @deprecated Renamed to `AteStructureCalculator`. This alias will be removed in v3.0.0. */
7110
- const StructureCalculator = AteStructureCalculator;
7111
- /** @deprecated Renamed to `AteTableCalculator`. This alias will be removed in v3.0.0. */
7112
- const TableCalculator = AteTableCalculator;
7113
- /** @deprecated Renamed to `ATE_DEFAULT_TOOLBAR_CONFIG`. This alias will be removed in v3.0.0. */
7114
- const DEFAULT_TOOLBAR_CONFIG = ATE_DEFAULT_TOOLBAR_CONFIG;
7115
- /** @deprecated Renamed to `ATE_DEFAULT_BUBBLE_MENU_CONFIG`. This alias will be removed in v3.0.0. */
7116
- const DEFAULT_BUBBLE_MENU_CONFIG = ATE_DEFAULT_BUBBLE_MENU_CONFIG;
7117
- /** @deprecated Renamed to `ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG`. This alias will be removed in v3.0.0. */
7118
- const DEFAULT_IMAGE_BUBBLE_MENU_CONFIG = ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG;
7119
- /** @deprecated Renamed to `ATE_DEFAULT_TABLE_MENU_CONFIG`. This alias will be removed in v3.0.0. */
7120
- const DEFAULT_TABLE_MENU_CONFIG = ATE_DEFAULT_TABLE_MENU_CONFIG;
7121
- /** @deprecated Renamed to `ATE_DEFAULT_CELL_MENU_CONFIG`. This alias will be removed in v3.0.0. */
7122
- const DEFAULT_CELL_MENU_CONFIG = ATE_DEFAULT_CELL_MENU_CONFIG;
7274
+ // Main component & Provider
7123
7275
 
7124
7276
  /**
7125
7277
  * Generated bundle index. Do not edit.
7126
7278
  */
7127
7279
 
7128
- export { ATE_BUBBLE_MENU_KEYS, ATE_CELL_BUBBLE_MENU_KEYS, ATE_DEFAULT_BUBBLE_MENU_CONFIG, ATE_DEFAULT_CELL_MENU_CONFIG, ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ATE_DEFAULT_SLASH_COMMANDS_CONFIG, ATE_DEFAULT_TABLE_MENU_CONFIG, ATE_DEFAULT_TOOLBAR_CONFIG, ATE_IMAGE_BUBBLE_MENU_KEYS, ATE_INITIAL_EDITOR_STATE, ATE_SLASH_COMMAND_KEYS, ATE_TABLE_BUBBLE_MENU_KEYS, ATE_TOOLBAR_KEYS, AngularTiptapEditorComponent, AteAngularNodeView, AteColorPickerService, AteDiscoveryCalculator, AteEditorCommandsService, AteI18nService, AteImageCalculator, AteImageService, AteLinkService, AteMarksCalculator, AteNodeViewRenderer, AteNoopValueAccessorDirective, AteSelectionCalculator, AteStructureCalculator, AteTableCalculator, AteColorPickerService as ColorPickerService, DEFAULT_BUBBLE_MENU_CONFIG, DEFAULT_CELL_MENU_CONFIG, DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, DEFAULT_SLASH_COMMANDS_CONFIG, DEFAULT_TABLE_MENU_CONFIG, DEFAULT_TOOLBAR_CONFIG, DiscoveryCalculator, AteEditorCommandsService as EditorCommandsService, INITIAL_EDITOR_STATE, ImageCalculator, AteImageService as ImageService, AteLinkService as LinkService, MarksCalculator, SLASH_COMMAND_KEYS, SelectionCalculator, StructureCalculator, TableCalculator, AteI18nService as TiptapI18nService, createAngularComponentExtension, createDefaultSlashCommands, filterSlashCommands, registerAngularComponent };
7280
+ export { ATE_BUBBLE_MENU_KEYS, ATE_CELL_BUBBLE_MENU_KEYS, ATE_DEFAULT_BUBBLE_MENU_CONFIG, ATE_DEFAULT_CELL_MENU_CONFIG, ATE_DEFAULT_CONFIG, ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ATE_DEFAULT_SLASH_COMMANDS_CONFIG, ATE_DEFAULT_TABLE_MENU_CONFIG, ATE_DEFAULT_TOOLBAR_CONFIG, ATE_GLOBAL_CONFIG, ATE_IMAGE_BUBBLE_MENU_KEYS, ATE_INITIAL_EDITOR_STATE, ATE_SLASH_COMMAND_KEYS, ATE_TABLE_BUBBLE_MENU_KEYS, ATE_TOOLBAR_KEYS, AngularTiptapEditorComponent, AteAngularNodeView, AteColorPickerService, AteDiscoveryCalculator, AteEditorCommandsService, AteI18nService, AteImageCalculator, AteImageService, AteLinkService, AteMarksCalculator, AteNodeViewRenderer, AteNoopValueAccessorDirective, AteSelectionCalculator, AteStructureCalculator, AteTableCalculator, createAngularComponentExtension, createDefaultSlashCommands, filterSlashCommands, provideAteEditor, registerAngularComponent };
7129
7281
  //# sourceMappingURL=flogeez-angular-tiptap-editor.mjs.map