@flogeez/angular-tiptap-editor 2.3.0 → 2.4.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,5 +1,5 @@
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';
@@ -20,7 +20,7 @@ import Table from '@tiptap/extension-table';
20
20
  import TableRow from '@tiptap/extension-table-row';
21
21
  import TableCell from '@tiptap/extension-table-cell';
22
22
  import TableHeader from '@tiptap/extension-table-header';
23
- import { isObservable, firstValueFrom, concat, defer, of, tap, Subscription } from 'rxjs';
23
+ import { isObservable, firstValueFrom, Subscription, concat, defer, of, tap } from 'rxjs';
24
24
  import { CommonModule } from '@angular/common';
25
25
  import tippy, { sticky } from 'tippy.js';
26
26
  import * as i1 from '@angular/forms';
@@ -5391,407 +5391,851 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5391
5391
  }]
5392
5392
  }] });
5393
5393
 
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";
5394
+ /**
5395
+ * Base abstract class for custom 'Angular Nodes'.
5396
+ *
5397
+ * Extend this class in your custom components to automatically receive TipTap editor
5398
+ * properties (node attributes, selection state, etc.) as reactive Signals.
5399
+ *
5400
+ * @example
5401
+ * ```typescript
5402
+ * @Component({
5403
+ * selector: 'app-counter',
5404
+ * template: `
5405
+ * <div>
5406
+ * <button (click)="increment()">Count: {{ node().attrs['count'] }}</button>
5407
+ * </div>
5408
+ * `
5409
+ * })
5410
+ * export class CounterComponent extends AteAngularNodeView {
5411
+ * increment() {
5412
+ * const count = this.node().attrs['count'] || 0;
5413
+ * this.updateAttributes({ count: count + 1 });
5414
+ * }
5415
+ * }
5416
+ * ```
5417
+ */
5418
+ class AteAngularNodeView {
5419
+ /**
5420
+ * Internal method to initialize the component with NodeView props.
5421
+ * This is called by the AngularNodeViewRenderer.
5422
+ *
5423
+ * @internal
5424
+ */
5425
+ _initNodeView(props) {
5426
+ // Create signals from the props
5427
+ const editorSignal = signal(props.editor, ...(ngDevMode ? [{ debugName: "editorSignal" }] : []));
5428
+ const nodeSignal = signal(props.node, ...(ngDevMode ? [{ debugName: "nodeSignal" }] : []));
5429
+ const decorationsSignal = signal(props.decorations, ...(ngDevMode ? [{ debugName: "decorationsSignal" }] : []));
5430
+ const selectedSignal = signal(props.selected, ...(ngDevMode ? [{ debugName: "selectedSignal" }] : []));
5431
+ const extensionSignal = signal(props.extension, ...(ngDevMode ? [{ debugName: "extensionSignal" }] : []));
5432
+ // Assign to the component
5433
+ this.editor = editorSignal.asReadonly();
5434
+ this.node = nodeSignal.asReadonly();
5435
+ this.decorations = decorationsSignal.asReadonly();
5436
+ this.selected = selectedSignal.asReadonly();
5437
+ this.extension = extensionSignal.asReadonly();
5438
+ this.getPos = props.getPos;
5439
+ this.updateAttributes = props.updateAttributes;
5440
+ this.deleteNode = props.deleteNode;
5441
+ // Store writable signals for updates
5442
+ this._writableSignals = {
5443
+ node: nodeSignal,
5444
+ decorations: decorationsSignal,
5445
+ selected: selectedSignal,
5446
+ };
5400
5447
  }
5401
- else if (selection instanceof NodeSelection) {
5402
- selectionType = "node";
5448
+ /**
5449
+ * Internal method to update the component when the node changes.
5450
+ * This is called by the AngularNodeViewRenderer.
5451
+ *
5452
+ * @internal
5453
+ */
5454
+ _updateNodeView(node, decorations) {
5455
+ if (this._writableSignals) {
5456
+ this._writableSignals.node.set(node);
5457
+ this._writableSignals.decorations.set(decorations);
5458
+ }
5403
5459
  }
5404
- else if (selection instanceof CellSelection) {
5405
- selectionType = "cell";
5460
+ /**
5461
+ * Internal method to update the selection state.
5462
+ * This is called by the AngularNodeViewRenderer.
5463
+ *
5464
+ * @internal
5465
+ */
5466
+ _selectNodeView() {
5467
+ if (this._writableSignals) {
5468
+ this._writableSignals.selected.set(true);
5469
+ }
5406
5470
  }
5407
- let isSingleCell = false;
5408
- if (selection instanceof CellSelection) {
5409
- isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
5471
+ /**
5472
+ * Internal method to update the deselection state.
5473
+ * This is called by the AngularNodeViewRenderer.
5474
+ *
5475
+ * @internal
5476
+ */
5477
+ _deselectNodeView() {
5478
+ if (this._writableSignals) {
5479
+ this._writableSignals.selected.set(false);
5480
+ }
5410
5481
  }
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
- };
5482
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
5483
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: AteAngularNodeView, isStandalone: true, ngImport: i0 }); }
5484
+ }
5485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, decorators: [{
5486
+ type: Directive
5487
+ }] });
5426
5488
 
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;
5489
+ /**
5490
+ * Universal Renderer for Angular Components as TipTap NodeViews.
5491
+ *
5492
+ * Supports:
5493
+ * - TipTap-Aware components (extending AteAngularNodeView)
5494
+ * - Standard library components (automatic @Input() sync)
5495
+ * - Signal-based inputs and outputs (Angular 18+)
5496
+ * - Content projection (editableContent)
5497
+ * - Unified lifecycle and event management
5498
+ */
5499
+ function AteNodeViewRenderer(component, options) {
5500
+ return props => {
5501
+ const { node, view: _view, getPos, decorations, editor, extension } = props;
5502
+ const { injector, inputs = {}, wrapperTag = "div", wrapperClass, editableContent = false, contentSelector, onOutput, ignoreMutation = true, } = options;
5503
+ const dom = document.createElement(wrapperTag);
5504
+ if (wrapperClass) {
5505
+ dom.className = wrapperClass;
5440
5506
  }
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;
5507
+ const applicationRef = injector.get(ApplicationRef);
5508
+ const environmentInjector = injector.get(EnvironmentInjector);
5509
+ // 1. Setup Content Projection (ng-content)
5510
+ let initialNodes = [];
5511
+ let contentDOM = null;
5512
+ if (editableContent && !contentSelector) {
5513
+ contentDOM = document.createElement("div");
5514
+ contentDOM.setAttribute("data-node-view-content", "");
5515
+ initialNodes = [[contentDOM]];
5445
5516
  }
5446
- catch (_e) {
5447
- return null;
5517
+ // 2. Create the Angular Component
5518
+ const componentRef = createComponent(component, {
5519
+ environmentInjector,
5520
+ elementInjector: injector,
5521
+ projectableNodes: initialNodes,
5522
+ });
5523
+ const instance = componentRef.instance;
5524
+ const subscriptions = [];
5525
+ // 3. Initialize TipTap-Aware instances
5526
+ if (instance instanceof AteAngularNodeView) {
5527
+ instance._initNodeView({
5528
+ editor,
5529
+ node,
5530
+ decorations,
5531
+ selected: false,
5532
+ extension,
5533
+ getPos,
5534
+ updateAttributes: attrs => editor.commands.updateAttributes(node.type.name, attrs),
5535
+ deleteNode: () => {
5536
+ const pos = getPos();
5537
+ if (pos !== undefined) {
5538
+ editor.commands.deleteRange({ from: pos, to: pos + node.nodeSize });
5539
+ }
5540
+ },
5541
+ });
5448
5542
  }
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;
5543
+ // 4. Synchronize Inputs (Handles standard @Input and Signal-based inputs)
5544
+ const syncInputs = (nodeToSync) => {
5545
+ // Combine base inputs from options with dynamic node attributes
5546
+ const mergedInputs = { ...inputs, ...nodeToSync.attrs };
5547
+ Object.entries(mergedInputs).forEach(([key, value]) => {
5548
+ if (key !== "id" && value !== null && value !== undefined) {
5549
+ try {
5550
+ componentRef.setInput(key, value);
5551
+ }
5552
+ catch {
5553
+ // Silently ignore inputs that don't exist on the component
5554
+ }
5555
+ }
5556
+ });
5557
+ };
5558
+ syncInputs(node);
5559
+ // 5. Setup Outputs (Handles EventEmitter and OutputRef)
5560
+ if (onOutput) {
5561
+ Object.entries(instance).forEach(([key, potentialOutput]) => {
5562
+ if (potentialOutput &&
5563
+ typeof potentialOutput
5564
+ .subscribe === "function") {
5565
+ const sub = potentialOutput.subscribe((value) => {
5566
+ onOutput(key, value);
5567
+ });
5568
+ if (sub instanceof Subscription) {
5569
+ subscriptions.push(sub);
5570
+ }
5571
+ }
5572
+ });
5456
5573
  }
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) {
5574
+ // 6. Attach to DOM and ApplicationRef
5575
+ applicationRef.attachView(componentRef.hostView);
5576
+ const componentElement = componentRef.location.nativeElement;
5577
+ // Target specific element for content if selector provided
5578
+ if (editableContent && contentSelector) {
5579
+ contentDOM = componentElement.querySelector(contentSelector);
5580
+ }
5581
+ dom.appendChild(componentElement);
5582
+ // Initial detection to ensure the component is rendered
5583
+ componentRef.changeDetectorRef.detectChanges();
5584
+ // 7. Return the TipTap NodeView Interface
5504
5585
  return {
5505
- nodes: {
5506
- isTable: false,
5507
- isTableCell: false,
5508
- isTableHeaderRow: false,
5509
- isTableHeaderColumn: false,
5586
+ dom,
5587
+ contentDOM,
5588
+ update: (updatedNode, updatedDecorations) => {
5589
+ if (updatedNode.type !== node.type) {
5590
+ return false;
5591
+ }
5592
+ // Update Aware component signals
5593
+ if (instance instanceof AteAngularNodeView) {
5594
+ instance._updateNodeView(updatedNode, updatedDecorations);
5595
+ }
5596
+ // Update inputs
5597
+ syncInputs(updatedNode);
5598
+ // Notify and Detect changes
5599
+ componentRef.changeDetectorRef.detectChanges();
5600
+ if (options.onUpdate) {
5601
+ options.onUpdate(updatedNode);
5602
+ }
5603
+ return true;
5604
+ },
5605
+ selectNode: () => {
5606
+ if (instance instanceof AteAngularNodeView) {
5607
+ instance._selectNodeView();
5608
+ }
5609
+ dom.classList.add("ProseMirror-selectednode");
5610
+ },
5611
+ deselectNode: () => {
5612
+ if (instance instanceof AteAngularNodeView) {
5613
+ instance._deselectNodeView();
5614
+ }
5615
+ dom.classList.remove("ProseMirror-selectednode");
5616
+ },
5617
+ destroy: () => {
5618
+ if (options.onDestroy) {
5619
+ options.onDestroy();
5620
+ }
5621
+ subscriptions.forEach(s => s.unsubscribe());
5622
+ applicationRef.detachView(componentRef.hostView);
5623
+ componentRef.destroy();
5624
+ },
5625
+ stopEvent: event => {
5626
+ const target = event.target;
5627
+ const isEditable = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
5628
+ return isEditable;
5629
+ },
5630
+ ignoreMutation: mutation => {
5631
+ if (typeof ignoreMutation === "function") {
5632
+ return ignoreMutation(mutation);
5633
+ }
5634
+ return ignoreMutation;
5510
5635
  },
5511
5636
  };
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
5637
  };
5536
- };
5638
+ }
5537
5639
 
5538
- const AteImageCalculator = editor => {
5539
- return {
5540
- nodes: {
5541
- isImage: editor.isActive("image") || editor.isActive("resizableImage"),
5640
+ const RESERVED_NAMES = [
5641
+ "doc",
5642
+ "paragraph",
5643
+ "text",
5644
+ "hardBreak",
5645
+ "bulletList",
5646
+ "orderedList",
5647
+ "listItem",
5648
+ "blockquote",
5649
+ "codeBlock",
5650
+ "heading",
5651
+ "horizontalRule",
5652
+ ];
5653
+ /**
5654
+ * Derives the TipTap node name and HTML tag from a component class.
5655
+ */
5656
+ function deriveMetadata(component, customName) {
5657
+ let nodeName = customName;
5658
+ if (!nodeName) {
5659
+ nodeName = component.name
5660
+ .replace(/Component$/, "")
5661
+ .replace(/Node$/, "")
5662
+ .replace(/^([A-Z])/, m => m.toLowerCase());
5663
+ console.warn(`[ATE] Auto-deriving node name '${nodeName}' for component ${component.name}. ` +
5664
+ `Provide an explicit 'name' in options to avoid potential naming collisions.`);
5665
+ }
5666
+ if (RESERVED_NAMES.includes(nodeName)) {
5667
+ throw new Error(`[ATE] The name '${nodeName}' is a reserved TipTap node name. ` +
5668
+ `Please provide a unique name for your custom component.`);
5669
+ }
5670
+ const tag = nodeName.toLowerCase().replace(/([A-Z])/g, "-$1");
5671
+ return { nodeName, tag };
5672
+ }
5673
+ /**
5674
+ * Creates TipTap attributes from component inputs (Standard Mode).
5675
+ */
5676
+ function createStandardAttributes(defaultInputs = {}) {
5677
+ const tiptapAttributes = {};
5678
+ Object.entries(defaultInputs).forEach(([key, defaultValue]) => {
5679
+ tiptapAttributes[key] = {
5680
+ default: defaultValue,
5681
+ parseHTML: (element) => {
5682
+ const attr = element.getAttribute(`data-${key}`);
5683
+ if (attr === null) {
5684
+ return defaultValue;
5685
+ }
5686
+ try {
5687
+ return JSON.parse(attr);
5688
+ }
5689
+ catch {
5690
+ return attr;
5691
+ }
5692
+ },
5693
+ renderHTML: (attrs) => {
5694
+ const value = attrs[key];
5695
+ if (value === undefined || value === null) {
5696
+ return {};
5697
+ }
5698
+ const serialized = typeof value === "object" ? JSON.stringify(value) : String(value);
5699
+ return { [`data-${key}`]: serialized };
5700
+ },
5701
+ };
5702
+ });
5703
+ return tiptapAttributes;
5704
+ }
5705
+ /**
5706
+ * Factory to transform an Angular component into a TipTap Node extension.
5707
+ * Supports both "TipTap-Aware" and "Standard" modes.
5708
+ *
5709
+ * @internal
5710
+ */
5711
+ function createAngularComponentExtension(injector, options) {
5712
+ const { component, name: customName, attributes, defaultInputs, contentSelector, contentMode = "block", editableContent = false, group = "block", draggable = true, ignoreMutation = true, onOutput, HTMLAttributes = {}, parseHTML: customParseHTML, renderHTML: customRenderHTML, } = options;
5713
+ const { nodeName, tag } = deriveMetadata(component, customName);
5714
+ const isTipTapAware = Object.prototype.isPrototypeOf.call(AteAngularNodeView, component);
5715
+ const atom = !editableContent;
5716
+ // 1. Prepare Attributes
5717
+ const tiptapAttributes = isTipTapAware
5718
+ ? attributes || {}
5719
+ : createStandardAttributes(defaultInputs);
5720
+ // 2. Create Node Extension
5721
+ return Node$1.create({
5722
+ name: nodeName,
5723
+ group,
5724
+ inline: group === "inline",
5725
+ atom,
5726
+ draggable,
5727
+ content: editableContent ? (contentMode === "inline" ? "inline*" : "block*") : undefined,
5728
+ addAttributes() {
5729
+ return tiptapAttributes;
5542
5730
  },
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" }),
5731
+ parseHTML() {
5732
+ if (customParseHTML) {
5733
+ return customParseHTML.call(this);
5734
+ }
5735
+ return [{ tag }, { tag: `div[data-component="${nodeName}"]` }];
5560
5736
  },
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: "" }),
5737
+ renderHTML({ node, HTMLAttributes: attrs }) {
5738
+ if (customRenderHTML) {
5739
+ return customRenderHTML.call(this, { node, HTMLAttributes: attrs });
5740
+ }
5741
+ return [tag, mergeAttributes(HTMLAttributes, attrs, { "data-component": nodeName })];
5742
+ },
5743
+ addNodeView() {
5744
+ return AteNodeViewRenderer(component, {
5745
+ injector,
5746
+ inputs: defaultInputs,
5747
+ wrapperTag: group === "inline" ? "span" : "div",
5748
+ wrapperClass: isTipTapAware
5749
+ ? `ate-node-${nodeName}`
5750
+ : `embedded-component embedded-${nodeName}`,
5751
+ atom,
5752
+ editableContent,
5753
+ contentSelector,
5754
+ contentMode,
5755
+ onOutput,
5756
+ ignoreMutation,
5757
+ });
5576
5758
  },
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
- };
5590
- // We skip core extensions that are already handled by specialized calculators
5591
- // to avoid redundant calculations and maintain precise attribute tracking.
5592
- const handled = [
5593
- "bold",
5594
- "italic",
5595
- "underline",
5596
- "strike",
5597
- "code",
5598
- "link",
5599
- "highlight",
5600
- "superscript",
5601
- "subscript",
5602
- "table",
5603
- "image",
5604
- "resizableImage",
5605
- "heading",
5606
- "bulletList",
5607
- "orderedList",
5608
- "blockquote",
5609
- "textAlign",
5610
- "textStyle",
5611
- "color",
5612
- ];
5613
- editor.extensionManager.extensions.forEach(extension => {
5614
- const name = extension.name;
5615
- const type = extension.type;
5616
- // Skip internal/core extensions or already handled ones
5617
- if ([
5618
- "selection",
5619
- "editable",
5620
- "focus",
5621
- "undo",
5622
- "redo",
5623
- "history",
5624
- "placeholder",
5625
- "characterCount",
5626
- ].includes(name)) {
5627
- return;
5628
- }
5629
- if (handled.includes(name)) {
5630
- return;
5631
- }
5632
- if (type === "mark") {
5633
- state.marks[name] = editor.isActive(name);
5634
- }
5635
- else if (type === "node") {
5636
- state.nodes[name] = editor.isActive(name);
5637
- }
5638
5759
  });
5639
- return state;
5640
- };
5760
+ }
5641
5761
 
5642
- const AteLinkClickBehavior = Extension.create({
5643
- name: "linkClickBehavior",
5644
- addProseMirrorPlugins() {
5645
- return [
5646
- new Plugin({
5647
- key: new PluginKey("linkClickBehavior"),
5648
- props: {
5649
- handleClick(view, _pos, event) {
5650
- // handleClick only runs in the browser, but we guard it for absolute SSR safety
5651
- if (typeof window === "undefined") {
5652
- return false;
5653
- }
5654
- const isModKey = event.ctrlKey || event.metaKey;
5655
- // If editor is editable, only proceed if Ctrl/Cmd is pressed
5656
- if (view.editable && !isModKey) {
5657
- return false;
5658
- }
5659
- const attrs = getAttributes(view.state, "link");
5660
- if (attrs["href"]) {
5661
- window.open(attrs["href"], "_blank", "noopener,noreferrer");
5662
- return true;
5663
- }
5664
- return false;
5665
- },
5666
- },
5667
- }),
5668
- ];
5669
- },
5670
- });
5762
+ /**
5763
+ * Internal registry to store the root injector for the editor.
5764
+ * This allows registerAngularComponent to work without explicitly passing the injector.
5765
+ * @internal
5766
+ */
5767
+ let globalInjector = null;
5768
+ function setGlobalInjector(injector) {
5769
+ globalInjector = injector;
5770
+ }
5771
+ function getGlobalInjector() {
5772
+ if (!globalInjector) {
5773
+ throw new Error("[ATE] Global Injector not found. Make sure to call provideAteEditor() in your app.config.ts or main.ts.");
5774
+ }
5775
+ return globalInjector;
5776
+ }
5671
5777
 
5672
- // Default toolbar configuration
5673
- const ATE_DEFAULT_TOOLBAR_CONFIG = {
5674
- bold: true,
5675
- italic: true,
5676
- underline: true,
5677
- strike: true,
5678
- code: true,
5679
- codeBlock: true,
5680
- superscript: false, // Disabled by default (opt-in)
5681
- subscript: false, // Disabled by default (opt-in)
5682
- highlight: false, // Disabled by default (opt-in)
5683
- highlightPicker: true,
5684
- heading1: true,
5685
- heading2: true,
5686
- heading3: true,
5687
- bulletList: true,
5688
- orderedList: true,
5689
- blockquote: true,
5690
- alignLeft: false, // Disabled by default (opt-in)
5691
- alignCenter: false, // Disabled by default (opt-in)
5692
- alignRight: false, // Disabled by default (opt-in)
5693
- alignJustify: false, // Disabled by default (opt-in)
5694
- link: true,
5695
- image: true,
5696
- horizontalRule: false, // Disabled by default (opt-in)
5697
- table: true,
5698
- undo: true,
5699
- redo: true,
5700
- clear: false, // Disabled by default (opt-in)
5701
- textColor: true,
5702
- separator: true,
5703
- };
5704
- // Default bubble menu configuration
5705
- const ATE_DEFAULT_BUBBLE_MENU_CONFIG = {
5706
- bold: true,
5707
- italic: true,
5708
- underline: true,
5709
- strike: true,
5710
- code: true,
5711
- superscript: false,
5712
- subscript: false,
5713
- highlight: false,
5714
- highlightPicker: true,
5715
- textColor: true,
5716
- link: true,
5717
- separator: true,
5718
- };
5719
- // Default image bubble menu configuration
5720
- const ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG = {
5721
- changeImage: true,
5722
- resizeSmall: true,
5723
- resizeMedium: true,
5724
- resizeLarge: true,
5725
- resizeOriginal: true,
5726
- deleteImage: true,
5727
- separator: true,
5728
- };
5729
- // Default table bubble menu configuration
5730
- const ATE_DEFAULT_TABLE_MENU_CONFIG = {
5731
- addRowBefore: true,
5732
- addRowAfter: true,
5733
- deleteRow: true,
5734
- addColumnBefore: true,
5735
- addColumnAfter: true,
5736
- deleteColumn: true,
5737
- toggleHeaderRow: true,
5738
- toggleHeaderColumn: true,
5739
- deleteTable: true,
5740
- separator: true,
5741
- };
5742
- // Default cell bubble menu configuration
5743
- const ATE_DEFAULT_CELL_MENU_CONFIG = {
5744
- mergeCells: true,
5745
- splitCell: true,
5746
- };
5778
+ /**
5779
+ * Registers ANY Angular component as a TipTap node extension.
5780
+ *
5781
+ * This function is the single public entry point for adding custom components.
5782
+ * It supports two distinct modes:
5783
+ *
5784
+ * 1. **TipTap-Aware Mode** (the component extends `AteAngularNodeView`):
5785
+ * Grants direct access to the TipTap API (`editor`, `node`, `updateAttributes`, etc.) within the component.
5786
+ *
5787
+ * 2. **Standard Mode** (library or legacy components):
5788
+ * Allows using any existing Angular component. Its `@Input()` properties are automatically
5789
+ * synchronized with TipTap node attributes.
5790
+ *
5791
+ * @param injectorOrOptions - The Angular Injector OR the configuration options
5792
+ * @param maybeOptions - Configuration options (if the first param was an injector)
5793
+ * @returns A TipTap Node extension ready to be used
5794
+ *
5795
+ * @example
5796
+ * ```typescript
5797
+ * // Modern way (requires provideAteEditor())
5798
+ * registerAngularComponent({
5799
+ * component: MyComponent,
5800
+ * name: 'myComponent'
5801
+ * });
5802
+ *
5803
+ * // Classic way
5804
+ * registerAngularComponent(injector, {
5805
+ * component: MyComponent
5806
+ * });
5807
+ * ```
5808
+ */
5809
+ function registerAngularComponent(injectorOrOptions, maybeOptions) {
5810
+ let injector;
5811
+ let options;
5812
+ // Duck-typing check: Injectors have a 'get' method
5813
+ const isInjector = (obj) => obj !== null && typeof obj === "object" && typeof obj.get === "function";
5814
+ if (isInjector(injectorOrOptions)) {
5815
+ injector = injectorOrOptions;
5816
+ options = maybeOptions;
5817
+ }
5818
+ else {
5819
+ injector = getGlobalInjector();
5820
+ options = injectorOrOptions;
5821
+ }
5822
+ return createAngularComponentExtension(injector, options);
5823
+ }
5747
5824
 
5748
- // Slash commands configuration is handled dynamically via slashCommandsConfigComputed
5749
- class AngularTiptapEditorComponent {
5750
- // ============================================
5751
- // Toolbar / Bubble Menu Coordination
5752
- // ============================================
5753
- hideBubbleMenus() {
5754
- this.editorCommandsService.setToolbarInteracting(true);
5825
+ /**
5826
+ * Injection Token for global editor configuration.
5827
+ */
5828
+ const ATE_GLOBAL_CONFIG = new InjectionToken("ATE_GLOBAL_CONFIG");
5829
+
5830
+ const AteSelectionCalculator = editor => {
5831
+ const { selection } = editor.state;
5832
+ const { from, to } = selection;
5833
+ let selectionType = "none";
5834
+ if (selection instanceof TextSelection) {
5835
+ selectionType = "text";
5755
5836
  }
5756
- showBubbleMenus() {
5757
- this.editorCommandsService.setToolbarInteracting(false);
5837
+ else if (selection instanceof NodeSelection) {
5838
+ selectionType = "node";
5758
5839
  }
5759
- constructor() {
5760
- /** Configuration globale de l'éditeur */
5761
- this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
5762
- 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" }] : []));
5767
- this.height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : []));
5768
- 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" }] : []));
5774
- 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" }] : []));
5778
- this.customSlashCommands = input(undefined, ...(ngDevMode ? [{ debugName: "customSlashCommands" }] : []));
5779
- 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" }] : []));
5787
- // 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" }] : []));
5792
- // Configuration de la toolbar
5793
- this.toolbar = input({}, ...(ngDevMode ? [{ debugName: "toolbar" }] : []));
5794
- // Configuration des menus de table
5840
+ else if (selection instanceof CellSelection) {
5841
+ selectionType = "cell";
5842
+ }
5843
+ let isSingleCell = false;
5844
+ if (selection instanceof CellSelection) {
5845
+ isSingleCell = selection.$anchorCell.pos === selection.$headCell.pos;
5846
+ }
5847
+ return {
5848
+ isFocused: editor.view.hasFocus(),
5849
+ isEditable: editor.isEditable,
5850
+ selection: {
5851
+ type: selectionType,
5852
+ from,
5853
+ to,
5854
+ empty: selection.empty || (selectionType === "text" && from === to),
5855
+ isSingleCell: isSingleCell,
5856
+ },
5857
+ nodes: {
5858
+ activeNodeName: editor.state.doc.nodeAt(editor.state.selection.$head.pos)?.type.name || null,
5859
+ },
5860
+ };
5861
+ };
5862
+
5863
+ const AteMarksCalculator = editor => {
5864
+ const isCodeBlock = editor.isActive("codeBlock");
5865
+ const isCode = editor.isActive("code"); // Inline code mark
5866
+ const isImage = editor.isActive("image") || editor.isActive("resizableImage");
5867
+ // Check if marks are generally allowed based on context
5868
+ const marksAllowed = !isCodeBlock && !isImage;
5869
+ // For inline code specifically, we don't allow nesting OTHER marks inside it,
5870
+ // but the code mark ITSELF must be togglable to be removed.
5871
+ const isInsideInlineCode = isCode;
5872
+ // 1. Resolve target element once for this calculation
5873
+ const getTargetElement = () => {
5874
+ if (typeof window === "undefined" || !editor.view?.dom) {
5875
+ return null;
5876
+ }
5877
+ try {
5878
+ const { from } = editor.state.selection;
5879
+ const { node } = editor.view.domAtPos(from);
5880
+ return node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
5881
+ }
5882
+ catch (_e) {
5883
+ return null;
5884
+ }
5885
+ };
5886
+ const targetEl = getTargetElement() || (typeof window !== "undefined" ? editor.view?.dom : null);
5887
+ const computedStyle = targetEl && typeof window !== "undefined" ? window.getComputedStyle(targetEl) : null;
5888
+ // 2. Lightweight helper to extract properties from the pre-calculated style object
5889
+ const getStyle = (prop) => {
5890
+ if (!computedStyle) {
5891
+ return null;
5892
+ }
5893
+ const val = computedStyle.getPropertyValue(prop);
5894
+ return normalizeColor(val);
5895
+ };
5896
+ const colorMark = editor.getAttributes("textStyle")["color"] || null;
5897
+ const backgroundMark = editor.getAttributes("highlight")["color"] || null;
5898
+ return {
5899
+ marks: {
5900
+ bold: editor.isActive("bold"),
5901
+ italic: editor.isActive("italic"),
5902
+ underline: editor.isActive("underline"),
5903
+ strike: editor.isActive("strike"),
5904
+ code: isCode,
5905
+ superscript: editor.isActive("superscript"),
5906
+ subscript: editor.isActive("subscript"),
5907
+ highlight: editor.isActive("highlight"),
5908
+ link: editor.isActive("link"),
5909
+ linkHref: editor.getAttributes("link")["href"] || null,
5910
+ color: colorMark,
5911
+ computedColor: colorMark || getStyle("color"),
5912
+ background: backgroundMark,
5913
+ computedBackground: backgroundMark || getStyle("background-color"),
5914
+ linkOpenOnClick: editor.extensionManager.extensions.find(ext => ext.name === "link")?.options?.openOnClick ??
5915
+ false,
5916
+ },
5917
+ can: {
5918
+ toggleBold: marksAllowed && !isInsideInlineCode && editor.can().toggleBold(),
5919
+ toggleItalic: marksAllowed && !isInsideInlineCode && editor.can().toggleItalic(),
5920
+ toggleUnderline: marksAllowed && !isInsideInlineCode && editor.can().toggleUnderline(),
5921
+ toggleStrike: marksAllowed && !isInsideInlineCode && editor.can().toggleStrike(),
5922
+ toggleCode: marksAllowed && editor.can().toggleCode(),
5923
+ toggleHighlight: marksAllowed && !isInsideInlineCode && editor.can().toggleHighlight(),
5924
+ toggleLink: marksAllowed &&
5925
+ !isInsideInlineCode &&
5926
+ (editor.can().setLink({ href: "" }) || editor.can().unsetLink()),
5927
+ toggleSuperscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSuperscript(),
5928
+ toggleSubscript: marksAllowed && !isInsideInlineCode && editor.can().toggleSubscript(),
5929
+ setColor: marksAllowed && !isInsideInlineCode && editor.can().setColor(""),
5930
+ setHighlight: marksAllowed && !isInsideInlineCode && editor.can().setHighlight(),
5931
+ undo: editor.can().undo(),
5932
+ redo: editor.can().redo(),
5933
+ },
5934
+ };
5935
+ };
5936
+
5937
+ const AteTableCalculator = editor => {
5938
+ const isTable = editor.isActive("table");
5939
+ if (!isTable) {
5940
+ return {
5941
+ nodes: {
5942
+ isTable: false,
5943
+ isTableCell: false,
5944
+ isTableHeaderRow: false,
5945
+ isTableHeaderColumn: false,
5946
+ },
5947
+ };
5948
+ }
5949
+ const { selection } = editor.state;
5950
+ return {
5951
+ nodes: {
5952
+ isTable: true,
5953
+ isTableNodeSelected: selection instanceof NodeSelection && selection.node.type.name === "table",
5954
+ isTableCell: editor.isActive("tableCell") || editor.isActive("tableHeader"),
5955
+ isTableHeaderRow: editor.isActive("tableHeader", { row: true }),
5956
+ isTableHeaderColumn: editor.isActive("tableHeader", { column: true }),
5957
+ },
5958
+ can: {
5959
+ addRowBefore: editor.can().addRowBefore(),
5960
+ addRowAfter: editor.can().addRowAfter(),
5961
+ deleteRow: editor.can().deleteRow(),
5962
+ addColumnBefore: editor.can().addColumnBefore(),
5963
+ addColumnAfter: editor.can().addColumnAfter(),
5964
+ deleteColumn: editor.can().deleteColumn(),
5965
+ deleteTable: editor.can().deleteTable(),
5966
+ mergeCells: editor.can().mergeCells(),
5967
+ splitCell: editor.can().splitCell(),
5968
+ toggleHeaderRow: editor.can().toggleHeaderRow(),
5969
+ toggleHeaderColumn: editor.can().toggleHeaderColumn(),
5970
+ },
5971
+ };
5972
+ };
5973
+
5974
+ const AteImageCalculator = editor => {
5975
+ return {
5976
+ nodes: {
5977
+ isImage: editor.isActive("image") || editor.isActive("resizableImage"),
5978
+ },
5979
+ };
5980
+ };
5981
+
5982
+ const AteStructureCalculator = editor => {
5983
+ return {
5984
+ nodes: {
5985
+ isBlockquote: editor.isActive("blockquote"),
5986
+ isCodeBlock: editor.isActive("codeBlock"),
5987
+ isBulletList: editor.isActive("bulletList"),
5988
+ isOrderedList: editor.isActive("orderedList"),
5989
+ h1: editor.isActive("heading", { level: 1 }),
5990
+ h2: editor.isActive("heading", { level: 2 }),
5991
+ h3: editor.isActive("heading", { level: 3 }),
5992
+ alignLeft: editor.isActive({ textAlign: "left" }),
5993
+ alignCenter: editor.isActive({ textAlign: "center" }),
5994
+ alignRight: editor.isActive({ textAlign: "right" }),
5995
+ alignJustify: editor.isActive({ textAlign: "justify" }),
5996
+ },
5997
+ can: {
5998
+ toggleHeading1: editor.can().toggleHeading({ level: 1 }),
5999
+ toggleHeading2: editor.can().toggleHeading({ level: 2 }),
6000
+ toggleHeading3: editor.can().toggleHeading({ level: 3 }),
6001
+ toggleBulletList: editor.can().toggleBulletList(),
6002
+ toggleOrderedList: editor.can().toggleOrderedList(),
6003
+ toggleBlockquote: editor.can().toggleBlockquote(),
6004
+ toggleCodeBlock: editor.can().toggleCodeBlock(),
6005
+ setTextAlignLeft: editor.can().setTextAlign("left"),
6006
+ setTextAlignCenter: editor.can().setTextAlign("center"),
6007
+ setTextAlignRight: editor.can().setTextAlign("right"),
6008
+ setTextAlignJustify: editor.can().setTextAlign("justify"),
6009
+ insertHorizontalRule: editor.can().setHorizontalRule(),
6010
+ insertTable: editor.can().insertTable(),
6011
+ insertImage: editor.can().setResizableImage({ src: "" }),
6012
+ },
6013
+ };
6014
+ };
6015
+
6016
+ /**
6017
+ * DiscoveryCalculator automatically detects and tracks the state of any TipTap extension.
6018
+ * It provides a "fallback" reactive state for any mark or node not explicitly handled
6019
+ * by specialized calculators.
6020
+ */
6021
+ const AteDiscoveryCalculator = (editor) => {
6022
+ const state = {
6023
+ marks: {},
6024
+ nodes: {},
6025
+ };
6026
+ // We skip core extensions that are already handled by specialized calculators
6027
+ // to avoid redundant calculations and maintain precise attribute tracking.
6028
+ const handled = [
6029
+ "bold",
6030
+ "italic",
6031
+ "underline",
6032
+ "strike",
6033
+ "code",
6034
+ "link",
6035
+ "highlight",
6036
+ "superscript",
6037
+ "subscript",
6038
+ "table",
6039
+ "image",
6040
+ "resizableImage",
6041
+ "heading",
6042
+ "bulletList",
6043
+ "orderedList",
6044
+ "blockquote",
6045
+ "textAlign",
6046
+ "textStyle",
6047
+ "color",
6048
+ ];
6049
+ editor.extensionManager.extensions.forEach(extension => {
6050
+ const name = extension.name;
6051
+ const type = extension.type;
6052
+ // Skip internal/core extensions or already handled ones
6053
+ if ([
6054
+ "selection",
6055
+ "editable",
6056
+ "focus",
6057
+ "undo",
6058
+ "redo",
6059
+ "history",
6060
+ "placeholder",
6061
+ "characterCount",
6062
+ ].includes(name)) {
6063
+ return;
6064
+ }
6065
+ if (handled.includes(name)) {
6066
+ return;
6067
+ }
6068
+ if (type === "mark") {
6069
+ state.marks[name] = editor.isActive(name);
6070
+ }
6071
+ else if (type === "node") {
6072
+ state.nodes[name] = editor.isActive(name);
6073
+ }
6074
+ });
6075
+ return state;
6076
+ };
6077
+
6078
+ const AteLinkClickBehavior = Extension.create({
6079
+ name: "linkClickBehavior",
6080
+ addProseMirrorPlugins() {
6081
+ return [
6082
+ new Plugin({
6083
+ key: new PluginKey("linkClickBehavior"),
6084
+ props: {
6085
+ handleClick(view, _pos, event) {
6086
+ // handleClick only runs in the browser, but we guard it for absolute SSR safety
6087
+ if (typeof window === "undefined") {
6088
+ return false;
6089
+ }
6090
+ const isModKey = event.ctrlKey || event.metaKey;
6091
+ // If editor is editable, only proceed if Ctrl/Cmd is pressed
6092
+ if (view.editable && !isModKey) {
6093
+ return false;
6094
+ }
6095
+ const attrs = getAttributes(view.state, "link");
6096
+ if (attrs["href"]) {
6097
+ window.open(attrs["href"], "_blank", "noopener,noreferrer");
6098
+ return true;
6099
+ }
6100
+ return false;
6101
+ },
6102
+ },
6103
+ }),
6104
+ ];
6105
+ },
6106
+ });
6107
+
6108
+ // Default toolbar configuration
6109
+ const ATE_DEFAULT_TOOLBAR_CONFIG = {
6110
+ bold: true,
6111
+ italic: true,
6112
+ underline: true,
6113
+ strike: true,
6114
+ code: true,
6115
+ codeBlock: true,
6116
+ superscript: false, // Disabled by default (opt-in)
6117
+ subscript: false, // Disabled by default (opt-in)
6118
+ highlight: false, // Disabled by default (opt-in)
6119
+ highlightPicker: true,
6120
+ heading1: true,
6121
+ heading2: true,
6122
+ heading3: true,
6123
+ bulletList: true,
6124
+ orderedList: true,
6125
+ blockquote: true,
6126
+ alignLeft: false, // Disabled by default (opt-in)
6127
+ alignCenter: false, // Disabled by default (opt-in)
6128
+ alignRight: false, // Disabled by default (opt-in)
6129
+ alignJustify: false, // Disabled by default (opt-in)
6130
+ link: true,
6131
+ image: true,
6132
+ horizontalRule: false, // Disabled by default (opt-in)
6133
+ table: true,
6134
+ undo: true,
6135
+ redo: true,
6136
+ clear: false, // Disabled by default (opt-in)
6137
+ textColor: true,
6138
+ separator: true,
6139
+ };
6140
+ // Default bubble menu configuration
6141
+ const ATE_DEFAULT_BUBBLE_MENU_CONFIG = {
6142
+ bold: true,
6143
+ italic: true,
6144
+ underline: true,
6145
+ strike: true,
6146
+ code: true,
6147
+ superscript: false,
6148
+ subscript: false,
6149
+ highlight: false,
6150
+ highlightPicker: true,
6151
+ textColor: true,
6152
+ link: true,
6153
+ separator: true,
6154
+ };
6155
+ // Default image bubble menu configuration
6156
+ const ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG = {
6157
+ changeImage: true,
6158
+ resizeSmall: true,
6159
+ resizeMedium: true,
6160
+ resizeLarge: true,
6161
+ resizeOriginal: true,
6162
+ deleteImage: true,
6163
+ separator: true,
6164
+ };
6165
+ // Default table bubble menu configuration
6166
+ const ATE_DEFAULT_TABLE_MENU_CONFIG = {
6167
+ addRowBefore: true,
6168
+ addRowAfter: true,
6169
+ deleteRow: true,
6170
+ addColumnBefore: true,
6171
+ addColumnAfter: true,
6172
+ deleteColumn: true,
6173
+ toggleHeaderRow: true,
6174
+ toggleHeaderColumn: true,
6175
+ deleteTable: true,
6176
+ separator: true,
6177
+ };
6178
+ // Default cell bubble menu configuration
6179
+ const ATE_DEFAULT_CELL_MENU_CONFIG = {
6180
+ mergeCells: true,
6181
+ splitCell: true,
6182
+ };
6183
+
6184
+ // Slash commands configuration is handled dynamically via slashCommandsConfigComputed
6185
+ /**
6186
+ * The main rich-text editor component for Angular.
6187
+ *
6188
+ * Powered by Tiptap and built with a native Signal-based architecture, it provides
6189
+ * a seamless, high-performance editing experience. Supports automatic registration
6190
+ * of Angular components as interactive nodes ('Angular Nodes'), full Reactive Forms
6191
+ * integration, and extensive customization via the AteEditorConfig.
6192
+ */
6193
+ class AngularTiptapEditorComponent {
6194
+ // ============================================
6195
+ // Toolbar / Bubble Menu Coordination
6196
+ // ============================================
6197
+ hideBubbleMenus() {
6198
+ this.editorCommandsService.setToolbarInteracting(true);
6199
+ }
6200
+ showBubbleMenus() {
6201
+ this.editorCommandsService.setToolbarInteracting(false);
6202
+ }
6203
+ constructor() {
6204
+ /** Configuration globale de l'éditeur */
6205
+ this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : []));
6206
+ this.content = input("", ...(ngDevMode ? [{ debugName: "content" }] : []));
6207
+ this.placeholder = input("", ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
6208
+ this.editable = input(true, ...(ngDevMode ? [{ debugName: "editable" }] : []));
6209
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
6210
+ this.minHeight = input(200, ...(ngDevMode ? [{ debugName: "minHeight" }] : []));
6211
+ this.height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : []));
6212
+ this.maxHeight = input(undefined, ...(ngDevMode ? [{ debugName: "maxHeight" }] : []));
6213
+ this.fillContainer = input(false, ...(ngDevMode ? [{ debugName: "fillContainer" }] : []));
6214
+ this.showToolbar = input(true, ...(ngDevMode ? [{ debugName: "showToolbar" }] : []));
6215
+ this.showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
6216
+ this.showCharacterCount = input(true, ...(ngDevMode ? [{ debugName: "showCharacterCount" }] : []));
6217
+ this.showWordCount = input(true, ...(ngDevMode ? [{ debugName: "showWordCount" }] : []));
6218
+ this.maxCharacters = input(undefined, ...(ngDevMode ? [{ debugName: "maxCharacters" }] : []));
6219
+ this.enableOfficePaste = input(true, ...(ngDevMode ? [{ debugName: "enableOfficePaste" }] : []));
6220
+ this.enableSlashCommands = input(true, ...(ngDevMode ? [{ debugName: "enableSlashCommands" }] : []));
6221
+ this.slashCommands = input({}, ...(ngDevMode ? [{ debugName: "slashCommands" }] : []));
6222
+ this.customSlashCommands = input(undefined, ...(ngDevMode ? [{ debugName: "customSlashCommands" }] : []));
6223
+ this.locale = input(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : []));
6224
+ this.autofocus = input(false, ...(ngDevMode ? [{ debugName: "autofocus" }] : []));
6225
+ this.seamless = input(false, ...(ngDevMode ? [{ debugName: "seamless" }] : []));
6226
+ this.floatingToolbar = input(false, ...(ngDevMode ? [{ debugName: "floatingToolbar" }] : []));
6227
+ this.showEditToggle = input(false, ...(ngDevMode ? [{ debugName: "showEditToggle" }] : []));
6228
+ this.spellcheck = input(true, ...(ngDevMode ? [{ debugName: "spellcheck" }] : []));
6229
+ this.tiptapExtensions = input([], ...(ngDevMode ? [{ debugName: "tiptapExtensions" }] : []));
6230
+ this.tiptapOptions = input({}, ...(ngDevMode ? [{ debugName: "tiptapOptions" }] : []));
6231
+ // Nouveaux inputs pour les bubble menus
6232
+ this.showBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showBubbleMenu" }] : []));
6233
+ this.bubbleMenu = input(ATE_DEFAULT_BUBBLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "bubbleMenu" }] : []));
6234
+ this.showImageBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showImageBubbleMenu" }] : []));
6235
+ this.imageBubbleMenu = input(ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "imageBubbleMenu" }] : []));
6236
+ // Configuration de la toolbar
6237
+ this.toolbar = input({}, ...(ngDevMode ? [{ debugName: "toolbar" }] : []));
6238
+ // Configuration des menus de table
5795
6239
  this.showTableBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showTableBubbleMenu" }] : []));
5796
6240
  this.tableBubbleMenu = input(ATE_DEFAULT_TABLE_MENU_CONFIG, ...(ngDevMode ? [{ debugName: "tableBubbleMenu" }] : []));
5797
6241
  this.showCellBubbleMenu = input(true, ...(ngDevMode ? [{ debugName: "showCellBubbleMenu" }] : []));
@@ -5848,7 +6292,7 @@ class AngularTiptapEditorComponent {
5848
6292
  this._isFormControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_isFormControlDisabled" }] : []));
5849
6293
  this.isFormControlDisabled = this._isFormControlDisabled.asReadonly();
5850
6294
  // Combined disabled state (Input + FormControl)
5851
- this.mergedDisabled = computed(() => (this.config().disabled ?? this.disabled()) || this.isFormControlDisabled(), ...(ngDevMode ? [{ debugName: "mergedDisabled" }] : []));
6295
+ this.mergedDisabled = computed(() => (this.effectiveConfig().disabled ?? this.disabled()) || this.isFormControlDisabled(), ...(ngDevMode ? [{ debugName: "mergedDisabled" }] : []));
5852
6296
  // Computed for editor states
5853
6297
  this.isEditorReady = computed(() => this.editor() !== null, ...(ngDevMode ? [{ debugName: "isEditorReady" }] : []));
5854
6298
  // ============================================
@@ -5856,27 +6300,27 @@ class AngularTiptapEditorComponent {
5856
6300
  // ============================================
5857
6301
  // Appearance & Fundamentals
5858
6302
  this.finalSeamless = computed(() => {
5859
- const fromConfig = this.config().mode;
6303
+ const fromConfig = this.effectiveConfig().mode;
5860
6304
  if (fromConfig !== undefined) {
5861
6305
  return fromConfig === "seamless";
5862
6306
  }
5863
6307
  return this.seamless();
5864
6308
  }, ...(ngDevMode ? [{ debugName: "finalSeamless" }] : []));
5865
- this.finalEditable = computed(() => this.config().editable ?? this.editable(), ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
5866
- this.finalPlaceholder = computed(() => this.config().placeholder ??
6309
+ this.finalEditable = computed(() => this.effectiveConfig().editable ?? this.editable(), ...(ngDevMode ? [{ debugName: "finalEditable" }] : []));
6310
+ this.finalPlaceholder = computed(() => this.effectiveConfig().placeholder ??
5867
6311
  (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" }] : []));
6312
+ this.finalFillContainer = computed(() => this.effectiveConfig().fillContainer ?? this.fillContainer(), ...(ngDevMode ? [{ debugName: "finalFillContainer" }] : []));
6313
+ this.finalShowFooter = computed(() => this.effectiveConfig().showFooter ?? this.showFooter(), ...(ngDevMode ? [{ debugName: "finalShowFooter" }] : []));
6314
+ this.finalShowEditToggle = computed(() => this.effectiveConfig().showEditToggle ?? this.showEditToggle(), ...(ngDevMode ? [{ debugName: "finalShowEditToggle" }] : []));
6315
+ this.finalHeight = computed(() => this.effectiveConfig().height ?? (this.height() ? `${this.height()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalHeight" }] : []));
6316
+ this.finalMinHeight = computed(() => this.effectiveConfig().minHeight ?? (this.minHeight() ? `${this.minHeight()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalMinHeight" }] : []));
6317
+ this.finalMaxHeight = computed(() => this.effectiveConfig().maxHeight ?? (this.maxHeight() ? `${this.maxHeight()}px` : undefined), ...(ngDevMode ? [{ debugName: "finalMaxHeight" }] : []));
6318
+ this.finalSpellcheck = computed(() => this.effectiveConfig().spellcheck ?? this.spellcheck(), ...(ngDevMode ? [{ debugName: "finalSpellcheck" }] : []));
6319
+ this.finalEnableOfficePaste = computed(() => this.effectiveConfig().enableOfficePaste ?? this.enableOfficePaste(), ...(ngDevMode ? [{ debugName: "finalEnableOfficePaste" }] : []));
5876
6320
  // Features
5877
- this.finalShowToolbar = computed(() => this.config().showToolbar ?? this.showToolbar(), ...(ngDevMode ? [{ debugName: "finalShowToolbar" }] : []));
6321
+ this.finalShowToolbar = computed(() => this.effectiveConfig().showToolbar ?? this.showToolbar(), ...(ngDevMode ? [{ debugName: "finalShowToolbar" }] : []));
5878
6322
  this.finalToolbarConfig = computed(() => {
5879
- const fromConfig = this.config().toolbar;
6323
+ const fromConfig = this.effectiveConfig().toolbar;
5880
6324
  const base = ATE_DEFAULT_TOOLBAR_CONFIG;
5881
6325
  if (fromConfig) {
5882
6326
  return { ...base, ...fromConfig };
@@ -5884,19 +6328,19 @@ class AngularTiptapEditorComponent {
5884
6328
  const fromInput = this.toolbar();
5885
6329
  return Object.keys(fromInput).length === 0 ? base : { ...base, ...fromInput };
5886
6330
  }, ...(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" }] : []));
6331
+ this.finalFloatingToolbar = computed(() => this.effectiveConfig().floatingToolbar ?? this.floatingToolbar(), ...(ngDevMode ? [{ debugName: "finalFloatingToolbar" }] : []));
6332
+ this.finalShowBubbleMenu = computed(() => this.effectiveConfig().showBubbleMenu ?? this.showBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowBubbleMenu" }] : []));
5889
6333
  this.finalBubbleMenuConfig = computed(() => {
5890
- const fromConfig = this.config().bubbleMenu;
6334
+ const fromConfig = this.effectiveConfig().bubbleMenu;
5891
6335
  const base = ATE_DEFAULT_BUBBLE_MENU_CONFIG;
5892
6336
  if (fromConfig) {
5893
6337
  return { ...base, ...fromConfig };
5894
6338
  }
5895
6339
  return Object.keys(this.bubbleMenu()).length === 0 ? base : { ...base, ...this.bubbleMenu() };
5896
6340
  }, ...(ngDevMode ? [{ debugName: "finalBubbleMenuConfig" }] : []));
5897
- this.finalShowImageBubbleMenu = computed(() => this.config().showImageBubbleMenu ?? this.showImageBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
6341
+ this.finalShowImageBubbleMenu = computed(() => this.effectiveConfig().showImageBubbleMenu ?? this.showImageBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowImageBubbleMenu" }] : []));
5898
6342
  this.finalImageBubbleMenuConfig = computed(() => {
5899
- const fromConfig = this.config().imageBubbleMenu;
6343
+ const fromConfig = this.effectiveConfig().imageBubbleMenu;
5900
6344
  const base = ATE_DEFAULT_IMAGE_BUBBLE_MENU_CONFIG;
5901
6345
  if (fromConfig) {
5902
6346
  return { ...base, ...fromConfig };
@@ -5905,9 +6349,9 @@ class AngularTiptapEditorComponent {
5905
6349
  ? base
5906
6350
  : { ...base, ...this.imageBubbleMenu() };
5907
6351
  }, ...(ngDevMode ? [{ debugName: "finalImageBubbleMenuConfig" }] : []));
5908
- this.finalShowTableBubbleMenu = computed(() => this.config().showTableMenu ?? this.showTableBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
6352
+ this.finalShowTableBubbleMenu = computed(() => this.effectiveConfig().showTableMenu ?? this.showTableBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowTableBubbleMenu" }] : []));
5909
6353
  this.finalTableBubbleMenuConfig = computed(() => {
5910
- const fromConfig = this.config().tableBubbleMenu;
6354
+ const fromConfig = this.effectiveConfig().tableBubbleMenu;
5911
6355
  const base = ATE_DEFAULT_TABLE_MENU_CONFIG;
5912
6356
  if (fromConfig) {
5913
6357
  return { ...base, ...fromConfig };
@@ -5916,9 +6360,9 @@ class AngularTiptapEditorComponent {
5916
6360
  ? base
5917
6361
  : { ...base, ...this.tableBubbleMenu() };
5918
6362
  }, ...(ngDevMode ? [{ debugName: "finalTableBubbleMenuConfig" }] : []));
5919
- this.finalShowCellBubbleMenu = computed(() => this.config().showCellMenu ?? this.showCellBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
6363
+ this.finalShowCellBubbleMenu = computed(() => this.effectiveConfig().showCellMenu ?? this.showCellBubbleMenu(), ...(ngDevMode ? [{ debugName: "finalShowCellBubbleMenu" }] : []));
5920
6364
  this.finalCellBubbleMenuConfig = computed(() => {
5921
- const fromConfig = this.config().cellBubbleMenu;
6365
+ const fromConfig = this.effectiveConfig().cellBubbleMenu;
5922
6366
  const base = ATE_DEFAULT_CELL_MENU_CONFIG;
5923
6367
  if (fromConfig) {
5924
6368
  return { ...base, ...fromConfig };
@@ -5927,10 +6371,12 @@ class AngularTiptapEditorComponent {
5927
6371
  ? base
5928
6372
  : { ...base, ...this.cellBubbleMenu() };
5929
6373
  }, ...(ngDevMode ? [{ debugName: "finalCellBubbleMenuConfig" }] : []));
5930
- this.finalEnableSlashCommands = computed(() => this.config().enableSlashCommands ?? this.enableSlashCommands(), ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
6374
+ this.finalEnableSlashCommands = computed(() => this.effectiveConfig().enableSlashCommands ?? this.enableSlashCommands(), ...(ngDevMode ? [{ debugName: "finalEnableSlashCommands" }] : []));
5931
6375
  this.finalSlashCommandsConfig = computed(() => {
5932
- const fromConfig = this.config().slashCommands;
5933
- const customConfig = this.customSlashCommands();
6376
+ const fromConfig = this.effectiveConfig().slashCommands;
6377
+ const fromGlobalCustom = this.effectiveConfig().customSlashCommands;
6378
+ const fromInputCustom = this.customSlashCommands();
6379
+ const customConfig = fromInputCustom ?? fromGlobalCustom;
5934
6380
  if (customConfig) {
5935
6381
  return customConfig;
5936
6382
  }
@@ -5943,14 +6389,19 @@ class AngularTiptapEditorComponent {
5943
6389
  };
5944
6390
  }, ...(ngDevMode ? [{ debugName: "finalSlashCommandsConfig" }] : []));
5945
6391
  // 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" }] : []));
6392
+ this.finalAutofocus = computed(() => this.effectiveConfig().autofocus ?? this.autofocus(), ...(ngDevMode ? [{ debugName: "finalAutofocus" }] : []));
6393
+ this.finalMaxCharacters = computed(() => this.effectiveConfig().maxCharacters ?? this.maxCharacters(), ...(ngDevMode ? [{ debugName: "finalMaxCharacters" }] : []));
6394
+ this.finalShowCharacterCount = computed(() => this.effectiveConfig().showCharacterCount ?? this.showCharacterCount(), ...(ngDevMode ? [{ debugName: "finalShowCharacterCount" }] : []));
6395
+ this.finalShowWordCount = computed(() => this.effectiveConfig().showWordCount ?? this.showWordCount(), ...(ngDevMode ? [{ debugName: "finalShowWordCount" }] : []));
6396
+ this.finalLocale = computed(() => this.effectiveConfig().locale ?? this.locale(), ...(ngDevMode ? [{ debugName: "finalLocale" }] : []));
6397
+ // Extensions & Options
6398
+ this.finalTiptapExtensions = computed(() => this.effectiveConfig().tiptapExtensions ?? this.tiptapExtensions() ?? [], ...(ngDevMode ? [{ debugName: "finalTiptapExtensions" }] : []));
6399
+ this.finalTiptapOptions = computed(() => this.effectiveConfig().tiptapOptions ?? this.tiptapOptions() ?? {}, ...(ngDevMode ? [{ debugName: "finalTiptapOptions" }] : []));
6400
+ this.finalStateCalculators = computed(() => this.effectiveConfig().stateCalculators ?? this.stateCalculators() ?? [], ...(ngDevMode ? [{ debugName: "finalStateCalculators" }] : []));
6401
+ this.finalAngularNodesConfig = computed(() => this.effectiveConfig().angularNodes ?? [], ...(ngDevMode ? [{ debugName: "finalAngularNodesConfig" }] : []));
5951
6402
  // Image Upload
5952
6403
  this.finalImageUploadConfig = computed(() => {
5953
- const fromConfig = this.config().imageUpload;
6404
+ const fromConfig = this.effectiveConfig().imageUpload;
5954
6405
  const fromInput = this.imageUpload();
5955
6406
  const merged = {
5956
6407
  maxSize: 5, // Default 5MB
@@ -5970,7 +6421,7 @@ class AngularTiptapEditorComponent {
5970
6421
  maxSize: merged.maxSize * 1024 * 1024, // Convert MB to bytes for internal service
5971
6422
  };
5972
6423
  }, ...(ngDevMode ? [{ debugName: "finalImageUploadConfig" }] : []));
5973
- this.finalImageUploadHandler = computed(() => this.config().imageUpload?.handler ?? this.imageUploadHandler(), ...(ngDevMode ? [{ debugName: "finalImageUploadHandler" }] : []));
6424
+ this.finalImageUploadHandler = computed(() => this.effectiveConfig().imageUpload?.handler ?? this.imageUploadHandler(), ...(ngDevMode ? [{ debugName: "finalImageUploadHandler" }] : []));
5974
6425
  // Computed for current translations (allows per-instance override via config or input)
5975
6426
  this.currentTranslations = computed(() => {
5976
6427
  const localeOverride = this.finalLocale();
@@ -5987,6 +6438,17 @@ class AngularTiptapEditorComponent {
5987
6438
  this.editorCommandsService = inject(AteEditorCommandsService);
5988
6439
  // Access editor state via service
5989
6440
  this.editorState = this.editorCommandsService.editorState;
6441
+ this.injector = inject(Injector);
6442
+ this.globalConfig = inject(ATE_GLOBAL_CONFIG, { optional: true });
6443
+ /**
6444
+ * Final merged configuration.
6445
+ * Priority: Input [config] > Global config via provideAteEditor()
6446
+ */
6447
+ this.effectiveConfig = computed(() => {
6448
+ const fromInput = this.config();
6449
+ const fromGlobal = this.globalConfig || {};
6450
+ return { ...fromGlobal, ...fromInput };
6451
+ }, ...(ngDevMode ? [{ debugName: "effectiveConfig" }] : []));
5990
6452
  // Effect to update editor content (with anti-echo)
5991
6453
  effect(() => {
5992
6454
  const content = this.content(); // Sole reactive dependency
@@ -6053,11 +6515,12 @@ class AngularTiptapEditorComponent {
6053
6515
  }
6054
6516
  }
6055
6517
  });
6056
- // Effect to re-initialize editor when extensions or options change
6518
+ // Effect to re-initialize editor when technical configuration changes
6057
6519
  effect(() => {
6058
- // Monitor extensions and options
6059
- this.tiptapExtensions();
6060
- this.tiptapOptions();
6520
+ // Monitor technical dependencies
6521
+ this.finalTiptapExtensions();
6522
+ this.finalTiptapOptions();
6523
+ this.finalAngularNodesConfig();
6061
6524
  untracked(() => {
6062
6525
  // Only if already initialized (post AfterViewInit)
6063
6526
  if (this.editorFullyInitialized()) {
@@ -6136,7 +6599,7 @@ class AngularTiptapEditorComponent {
6136
6599
  AteImageCalculator,
6137
6600
  AteStructureCalculator,
6138
6601
  AteDiscoveryCalculator,
6139
- ...this.stateCalculators(),
6602
+ ...this.finalStateCalculators(),
6140
6603
  ],
6141
6604
  }),
6142
6605
  ];
@@ -6153,8 +6616,22 @@ class AngularTiptapEditorComponent {
6153
6616
  limit: this.finalMaxCharacters(),
6154
6617
  }));
6155
6618
  }
6619
+ // Register automatic node views from config
6620
+ const autoNodeViews = this.finalAngularNodesConfig();
6621
+ autoNodeViews.forEach(reg => {
6622
+ const options = typeof reg === "function"
6623
+ ? { component: reg }
6624
+ : reg;
6625
+ try {
6626
+ const extension = registerAngularComponent(this.injector, options);
6627
+ extensions.push(extension);
6628
+ }
6629
+ catch (e) {
6630
+ console.error("[ATE] Failed to auto-register node view:", e);
6631
+ }
6632
+ });
6156
6633
  // Allow addition of custom extensions, but avoid duplicates by filtering by name
6157
- const customExtensions = this.tiptapExtensions();
6634
+ const customExtensions = this.finalTiptapExtensions();
6158
6635
  if (customExtensions.length > 0) {
6159
6636
  const existingNames = new Set(extensions
6160
6637
  .map(ext => ext?.name)
@@ -6166,7 +6643,7 @@ class AngularTiptapEditorComponent {
6166
6643
  extensions.push(...toAdd);
6167
6644
  }
6168
6645
  // Also allow any tiptap user options
6169
- const userOptions = this.tiptapOptions();
6646
+ const userOptions = this.finalTiptapOptions();
6170
6647
  const newEditor = new Editor({
6171
6648
  ...userOptions,
6172
6649
  element: this.editorElement().nativeElement,
@@ -6265,228 +6742,84 @@ class AngularTiptapEditorComponent {
6265
6742
  }
6266
6743
  // Public methods
6267
6744
  getHTML() {
6268
- return this.editor()?.getHTML() || "";
6269
- }
6270
- getJSON() {
6271
- return this.editor()?.getJSON();
6272
- }
6273
- getText() {
6274
- return this.editor()?.getText() || "";
6275
- }
6276
- setContent(content, emitUpdate = true) {
6277
- const editor = this.editor();
6278
- if (editor) {
6279
- this.editorCommandsService.setContent(editor, content, emitUpdate);
6280
- }
6281
- }
6282
- focus() {
6283
- const editor = this.editor();
6284
- if (editor) {
6285
- this.editorCommandsService.focus(editor);
6286
- }
6287
- }
6288
- blur() {
6289
- const editor = this.editor();
6290
- if (editor) {
6291
- this.editorCommandsService.blur(editor);
6292
- }
6293
- }
6294
- clearContent() {
6295
- const editor = this.editor();
6296
- if (editor) {
6297
- this.editorCommandsService.clearContent(editor);
6298
- }
6299
- }
6300
- // Méthode publique pour obtenir l'éditeur
6301
- getEditor() {
6302
- return this.editor();
6303
- }
6304
- setupFormControlSubscription() {
6305
- const control = this.ngControl?.control;
6306
- if (control) {
6307
- // Synchronize form control value with editor content
6308
- const formValue$ = concat(defer(() => of(control.value)), control.valueChanges);
6309
- formValue$
6310
- .pipe(tap((value) => {
6311
- const editor = this.editor();
6312
- if (editor) {
6313
- this.setContent(value, false);
6314
- }
6315
- }), takeUntilDestroyed(this._destroyRef))
6316
- .subscribe();
6317
- // Synchronize form control status with editor disabled state
6318
- const formStatus$ = concat(defer(() => of(control.status)), control.statusChanges);
6319
- formStatus$
6320
- .pipe(tap((status) => {
6321
- this._isFormControlDisabled.set(status === "DISABLED");
6322
- }), takeUntilDestroyed(this._destroyRef))
6323
- .subscribe();
6324
- }
6325
- }
6326
- onEditorClick(event) {
6327
- const editor = this.editor();
6328
- if (!editor || !this.finalEditable()) {
6329
- return;
6330
- }
6331
- // Verify if interaction is on the container element and not on the content
6332
- const target = event.target;
6333
- const editorElement = this.editorElement()?.nativeElement;
6334
- if (target === editorElement || target.classList.contains("ate-content")) {
6335
- // Interaction in the empty space, position the cursor at the end
6336
- setTimeout(() => {
6337
- const { doc } = editor.state;
6338
- const endPos = doc.content.size;
6339
- editor.commands.setTextSelection(endPos);
6340
- editor.commands.focus();
6341
- }, 0);
6342
- }
6343
- }
6344
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6345
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AngularTiptapEditorComponent, isStandalone: true, selector: "angular-tiptap-editor", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, fillContainer: { classPropertyName: "fillContainer", publicName: "fillContainer", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, showCharacterCount: { classPropertyName: "showCharacterCount", publicName: "showCharacterCount", isSignal: true, isRequired: false, transformFunction: null }, showWordCount: { classPropertyName: "showWordCount", publicName: "showWordCount", isSignal: true, isRequired: false, transformFunction: null }, maxCharacters: { classPropertyName: "maxCharacters", publicName: "maxCharacters", isSignal: true, isRequired: false, transformFunction: null }, enableOfficePaste: { classPropertyName: "enableOfficePaste", publicName: "enableOfficePaste", isSignal: true, isRequired: false, transformFunction: null }, enableSlashCommands: { classPropertyName: "enableSlashCommands", publicName: "enableSlashCommands", isSignal: true, isRequired: false, transformFunction: null }, slashCommands: { classPropertyName: "slashCommands", publicName: "slashCommands", isSignal: true, isRequired: false, transformFunction: null }, customSlashCommands: { classPropertyName: "customSlashCommands", publicName: "customSlashCommands", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, seamless: { classPropertyName: "seamless", publicName: "seamless", isSignal: true, isRequired: false, transformFunction: null }, floatingToolbar: { classPropertyName: "floatingToolbar", publicName: "floatingToolbar", isSignal: true, isRequired: false, transformFunction: null }, showEditToggle: { classPropertyName: "showEditToggle", publicName: "showEditToggle", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, tiptapExtensions: { classPropertyName: "tiptapExtensions", publicName: "tiptapExtensions", isSignal: true, isRequired: false, transformFunction: null }, tiptapOptions: { classPropertyName: "tiptapOptions", publicName: "tiptapOptions", isSignal: true, isRequired: false, transformFunction: null }, showBubbleMenu: { classPropertyName: "showBubbleMenu", publicName: "showBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, bubbleMenu: { classPropertyName: "bubbleMenu", publicName: "bubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, showImageBubbleMenu: { classPropertyName: "showImageBubbleMenu", publicName: "showImageBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, imageBubbleMenu: { classPropertyName: "imageBubbleMenu", publicName: "imageBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, showTableBubbleMenu: { classPropertyName: "showTableBubbleMenu", publicName: "showTableBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, tableBubbleMenu: { classPropertyName: "tableBubbleMenu", publicName: "tableBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, showCellBubbleMenu: { classPropertyName: "showCellBubbleMenu", publicName: "showCellBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, cellBubbleMenu: { classPropertyName: "cellBubbleMenu", publicName: "cellBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, stateCalculators: { classPropertyName: "stateCalculators", publicName: "stateCalculators", isSignal: true, isRequired: false, transformFunction: null }, imageUpload: { classPropertyName: "imageUpload", publicName: "imageUpload", isSignal: true, isRequired: false, transformFunction: null }, imageUploadHandler: { classPropertyName: "imageUploadHandler", publicName: "imageUploadHandler", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { contentChange: "contentChange", editorCreated: "editorCreated", editorUpdate: "editorUpdate", editorFocus: "editorFocus", editorBlur: "editorBlur", editableChange: "editableChange" }, host: { properties: { "class.fill-container": "finalFillContainer()", "class.floating-toolbar": "finalFloatingToolbar()", "class.is-readonly": "!finalEditable() && !mergedDisabled()", "class.is-disabled": "mergedDisabled()", "style.--ate-border-width": "finalSeamless() || mergedDisabled() ? '0' : null", "style.--ate-background": "finalSeamless() ? 'transparent' : (mergedDisabled() ? 'var(--ate-surface-tertiary)' : null)", "style.--ate-toolbar-border-color": "finalSeamless() ? 'transparent' : null", "style.--ate-counter-background": "finalSeamless() ? 'transparent' : null", "style.--ate-counter-border-color": "finalSeamless() ? 'transparent' : null", "class.dark": "config().theme === 'dark'", "attr.data-theme": "config().theme" } }, providers: [AteEditorCommandsService, AteImageService, AteColorPickerService, AteLinkService], viewQueries: [{ propertyName: "editorElement", first: true, predicate: ["editorElement"], descendants: true, isSignal: true }], hostDirectives: [{ directive: AteNoopValueAccessorDirective }], ngImport: i0, template: `
6346
- <div class="ate-editor">
6347
- <!-- Toolbar -->
6348
- @if (finalEditable() && !mergedDisabled() && finalShowToolbar() && editor()) {
6349
- <ate-toolbar
6350
- [editor]="editor()!"
6351
- [config]="finalToolbarConfig()"
6352
- [imageUpload]="finalImageUploadConfig()"
6353
- [floating]="finalFloatingToolbar()"
6354
- (mouseenter)="hideBubbleMenus()"
6355
- (mouseleave)="showBubbleMenus()" />
6356
- }
6357
-
6358
- @if (finalShowEditToggle() && !mergedDisabled()) {
6359
- <ate-edit-toggle
6360
- [editable]="finalEditable()"
6361
- [translations]="currentTranslations()"
6362
- (editToggle)="toggleEditMode($event)" />
6363
- }
6364
-
6365
- <!-- Editor Content -->
6366
- <div
6367
- #editorElement
6368
- class="ate-content"
6369
- [class.drag-over]="isDragOver()"
6370
- (dragover)="onDragOver($event)"
6371
- (drop)="onDrop($event)"
6372
- (click)="onEditorClick($event)"
6373
- (keydown.enter)="onEditorClick($event)"
6374
- (keydown.space)="onEditorClick($event)"
6375
- tabindex="0"
6376
- role="application"
6377
- [attr.aria-label]="currentTranslations().editor.placeholder"></div>
6378
-
6379
- <!-- Text Bubble Menu -->
6380
- @if (finalEditable() && finalShowBubbleMenu() && editor()) {
6381
- <ate-bubble-menu
6382
- [editor]="editor()!"
6383
- [config]="finalBubbleMenuConfig()"
6384
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-bubble-menu>
6385
- }
6386
-
6387
- <!-- Image Bubble Menu -->
6388
- @if (finalEditable() && finalShowImageBubbleMenu() && editor()) {
6389
- <ate-image-bubble-menu
6390
- [editor]="editor()!"
6391
- [config]="finalImageBubbleMenuConfig()"
6392
- [imageUpload]="finalImageUploadConfig()"
6393
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-image-bubble-menu>
6394
- }
6395
-
6396
- <!-- Link Bubble Menu -->
6397
- @if (finalEditable() && editor()) {
6398
- <ate-link-bubble-menu
6399
- [editor]="editor()!"
6400
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-link-bubble-menu>
6401
- }
6402
-
6403
- <!-- Color Bubble Menu -->
6404
- @if (finalEditable() && editor()) {
6405
- <ate-color-bubble-menu
6406
- [editor]="editor()!"
6407
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-color-bubble-menu>
6408
- }
6409
-
6410
- <!-- Slash Commands -->
6411
- @if (finalEditable() && finalEnableSlashCommands() && editor()) {
6412
- <ate-slash-commands
6413
- [editor]="editor()!"
6414
- [config]="finalSlashCommandsConfig()"
6415
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-slash-commands>
6416
- }
6417
-
6418
- <!-- Table Menu -->
6419
- @if (finalEditable() && finalShowTableBubbleMenu() && editor()) {
6420
- <ate-table-bubble-menu
6421
- [editor]="editor()!"
6422
- [config]="finalTableBubbleMenuConfig()"
6423
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-table-bubble-menu>
6424
- }
6425
-
6426
- <!-- Cell Menu -->
6427
- @if (finalEditable() && finalShowCellBubbleMenu() && editor()) {
6428
- <ate-cell-bubble-menu
6429
- [editor]="editor()!"
6430
- [config]="finalCellBubbleMenuConfig()"
6431
- [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-cell-bubble-menu>
6432
- }
6433
-
6434
- <!-- Counters -->
6435
- @if (
6436
- finalEditable() &&
6437
- !mergedDisabled() &&
6438
- finalShowFooter() &&
6439
- (finalShowCharacterCount() || finalShowWordCount())
6440
- ) {
6441
- <div
6442
- class="character-count"
6443
- [class.limit-reached]="finalMaxCharacters() && characterCount() >= finalMaxCharacters()!">
6444
- @if (finalShowCharacterCount()) {
6445
- {{ characterCount() }}
6446
- {{ currentTranslations().editor.character }}{{ characterCount() > 1 ? "s" : "" }}
6447
- @if (finalMaxCharacters()) {
6448
- / {{ finalMaxCharacters() }}
6449
- }
6450
- }
6451
-
6452
- @if (finalShowCharacterCount() && finalShowWordCount()) {
6453
- ,
6454
- }
6455
-
6456
- @if (finalShowWordCount()) {
6457
- {{ wordCount() }}
6458
- {{ currentTranslations().editor.word }}{{ wordCount() > 1 ? "s" : "" }}
6459
- }
6460
- </div>
6461
- }
6462
- </div>
6463
- `, isInline: true, 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"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu" }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu" }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6464
- }
6465
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
6466
- type: Component,
6467
- args: [{ selector: "angular-tiptap-editor", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [AteNoopValueAccessorDirective], host: {
6468
- "[class.fill-container]": "finalFillContainer()",
6469
- "[class.floating-toolbar]": "finalFloatingToolbar()",
6470
- "[class.is-readonly]": "!finalEditable() && !mergedDisabled()",
6471
- "[class.is-disabled]": "mergedDisabled()",
6472
- "[style.--ate-border-width]": "finalSeamless() || mergedDisabled() ? '0' : null",
6473
- "[style.--ate-background]": "finalSeamless() ? 'transparent' : (mergedDisabled() ? 'var(--ate-surface-tertiary)' : null)",
6474
- "[style.--ate-toolbar-border-color]": "finalSeamless() ? 'transparent' : null",
6475
- "[style.--ate-counter-background]": "finalSeamless() ? 'transparent' : null",
6476
- "[style.--ate-counter-border-color]": "finalSeamless() ? 'transparent' : null",
6477
- "[class.dark]": "config().theme === 'dark'",
6478
- "[attr.data-theme]": "config().theme",
6479
- }, imports: [
6480
- AteToolbarComponent,
6481
- AteBubbleMenuComponent,
6482
- AteImageBubbleMenuComponent,
6483
- AteTableBubbleMenuComponent,
6484
- AteCellBubbleMenuComponent,
6485
- AteSlashCommandsComponent,
6486
- AteLinkBubbleMenuComponent,
6487
- AteColorBubbleMenuComponent,
6488
- AteEditToggleComponent,
6489
- ], providers: [AteEditorCommandsService, AteImageService, AteColorPickerService, AteLinkService], template: `
6745
+ return this.editor()?.getHTML() || "";
6746
+ }
6747
+ getJSON() {
6748
+ return this.editor()?.getJSON();
6749
+ }
6750
+ getText() {
6751
+ return this.editor()?.getText() || "";
6752
+ }
6753
+ setContent(content, emitUpdate = true) {
6754
+ const editor = this.editor();
6755
+ if (editor) {
6756
+ this.editorCommandsService.setContent(editor, content, emitUpdate);
6757
+ }
6758
+ }
6759
+ focus() {
6760
+ const editor = this.editor();
6761
+ if (editor) {
6762
+ this.editorCommandsService.focus(editor);
6763
+ }
6764
+ }
6765
+ blur() {
6766
+ const editor = this.editor();
6767
+ if (editor) {
6768
+ this.editorCommandsService.blur(editor);
6769
+ }
6770
+ }
6771
+ clearContent() {
6772
+ const editor = this.editor();
6773
+ if (editor) {
6774
+ this.editorCommandsService.clearContent(editor);
6775
+ }
6776
+ }
6777
+ // Méthode publique pour obtenir l'éditeur
6778
+ getEditor() {
6779
+ return this.editor();
6780
+ }
6781
+ setupFormControlSubscription() {
6782
+ const control = this.ngControl?.control;
6783
+ if (control) {
6784
+ // Synchronize form control value with editor content
6785
+ const formValue$ = concat(defer(() => of(control.value)), control.valueChanges);
6786
+ formValue$
6787
+ .pipe(tap((value) => {
6788
+ const editor = this.editor();
6789
+ if (editor) {
6790
+ this.setContent(value, false);
6791
+ }
6792
+ }), takeUntilDestroyed(this._destroyRef))
6793
+ .subscribe();
6794
+ // Synchronize form control status with editor disabled state
6795
+ const formStatus$ = concat(defer(() => of(control.status)), control.statusChanges);
6796
+ formStatus$
6797
+ .pipe(tap((status) => {
6798
+ this._isFormControlDisabled.set(status === "DISABLED");
6799
+ }), takeUntilDestroyed(this._destroyRef))
6800
+ .subscribe();
6801
+ }
6802
+ }
6803
+ onEditorClick(event) {
6804
+ const editor = this.editor();
6805
+ if (!editor || !this.finalEditable()) {
6806
+ return;
6807
+ }
6808
+ // Verify if interaction is on the container element and not on the content
6809
+ const target = event.target;
6810
+ const editorElement = this.editorElement()?.nativeElement;
6811
+ if (target === editorElement || target.classList.contains("ate-content")) {
6812
+ // Interaction in the empty space, position the cursor at the end
6813
+ setTimeout(() => {
6814
+ const { doc } = editor.state;
6815
+ const endPos = doc.content.size;
6816
+ editor.commands.setTextSelection(endPos);
6817
+ editor.commands.focus();
6818
+ }, 0);
6819
+ }
6820
+ }
6821
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6822
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: AngularTiptapEditorComponent, isStandalone: true, selector: "angular-tiptap-editor", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, fillContainer: { classPropertyName: "fillContainer", publicName: "fillContainer", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, showCharacterCount: { classPropertyName: "showCharacterCount", publicName: "showCharacterCount", isSignal: true, isRequired: false, transformFunction: null }, showWordCount: { classPropertyName: "showWordCount", publicName: "showWordCount", isSignal: true, isRequired: false, transformFunction: null }, maxCharacters: { classPropertyName: "maxCharacters", publicName: "maxCharacters", isSignal: true, isRequired: false, transformFunction: null }, enableOfficePaste: { classPropertyName: "enableOfficePaste", publicName: "enableOfficePaste", isSignal: true, isRequired: false, transformFunction: null }, enableSlashCommands: { classPropertyName: "enableSlashCommands", publicName: "enableSlashCommands", isSignal: true, isRequired: false, transformFunction: null }, slashCommands: { classPropertyName: "slashCommands", publicName: "slashCommands", isSignal: true, isRequired: false, transformFunction: null }, customSlashCommands: { classPropertyName: "customSlashCommands", publicName: "customSlashCommands", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, seamless: { classPropertyName: "seamless", publicName: "seamless", isSignal: true, isRequired: false, transformFunction: null }, floatingToolbar: { classPropertyName: "floatingToolbar", publicName: "floatingToolbar", isSignal: true, isRequired: false, transformFunction: null }, showEditToggle: { classPropertyName: "showEditToggle", publicName: "showEditToggle", isSignal: true, isRequired: false, transformFunction: null }, spellcheck: { classPropertyName: "spellcheck", publicName: "spellcheck", isSignal: true, isRequired: false, transformFunction: null }, tiptapExtensions: { classPropertyName: "tiptapExtensions", publicName: "tiptapExtensions", isSignal: true, isRequired: false, transformFunction: null }, tiptapOptions: { classPropertyName: "tiptapOptions", publicName: "tiptapOptions", isSignal: true, isRequired: false, transformFunction: null }, showBubbleMenu: { classPropertyName: "showBubbleMenu", publicName: "showBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, bubbleMenu: { classPropertyName: "bubbleMenu", publicName: "bubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, showImageBubbleMenu: { classPropertyName: "showImageBubbleMenu", publicName: "showImageBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, imageBubbleMenu: { classPropertyName: "imageBubbleMenu", publicName: "imageBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, toolbar: { classPropertyName: "toolbar", publicName: "toolbar", isSignal: true, isRequired: false, transformFunction: null }, showTableBubbleMenu: { classPropertyName: "showTableBubbleMenu", publicName: "showTableBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, tableBubbleMenu: { classPropertyName: "tableBubbleMenu", publicName: "tableBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, showCellBubbleMenu: { classPropertyName: "showCellBubbleMenu", publicName: "showCellBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, cellBubbleMenu: { classPropertyName: "cellBubbleMenu", publicName: "cellBubbleMenu", isSignal: true, isRequired: false, transformFunction: null }, stateCalculators: { classPropertyName: "stateCalculators", publicName: "stateCalculators", isSignal: true, isRequired: false, transformFunction: null }, imageUpload: { classPropertyName: "imageUpload", publicName: "imageUpload", isSignal: true, isRequired: false, transformFunction: null }, imageUploadHandler: { classPropertyName: "imageUploadHandler", publicName: "imageUploadHandler", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { contentChange: "contentChange", editorCreated: "editorCreated", editorUpdate: "editorUpdate", editorFocus: "editorFocus", editorBlur: "editorBlur", editableChange: "editableChange" }, host: { properties: { "class.fill-container": "finalFillContainer()", "class.floating-toolbar": "finalFloatingToolbar()", "class.is-readonly": "!finalEditable() && !mergedDisabled()", "class.is-disabled": "mergedDisabled()", "style.--ate-border-width": "finalSeamless() || mergedDisabled() ? '0' : null", "style.--ate-background": "finalSeamless() ? 'transparent' : (mergedDisabled() ? 'var(--ate-surface-tertiary)' : null)", "style.--ate-toolbar-border-color": "finalSeamless() ? 'transparent' : null", "style.--ate-counter-background": "finalSeamless() ? 'transparent' : null", "style.--ate-counter-border-color": "finalSeamless() ? 'transparent' : null", "class.dark": "config().theme === 'dark'", "attr.data-theme": "config().theme" } }, providers: [AteEditorCommandsService, AteImageService, AteColorPickerService, AteLinkService], viewQueries: [{ propertyName: "editorElement", first: true, predicate: ["editorElement"], descendants: true, isSignal: true }], hostDirectives: [{ directive: AteNoopValueAccessorDirective }], ngImport: i0, template: `
6490
6823
  <div class="ate-editor">
6491
6824
  <!-- Toolbar -->
6492
6825
  @if (finalEditable() && !mergedDisabled() && finalShowToolbar() && editor()) {
@@ -6600,498 +6933,276 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6600
6933
  @if (finalShowWordCount()) {
6601
6934
  {{ wordCount() }}
6602
6935
  {{ 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 }); }
6936
+ }
6937
+ </div>
6938
+ }
6939
+ </div>
6940
+ `, isInline: true, 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"], dependencies: [{ kind: "component", type: AteToolbarComponent, selector: "ate-toolbar", inputs: ["editor", "config", "imageUpload", "floating"] }, { kind: "component", type: AteBubbleMenuComponent, selector: "ate-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteImageBubbleMenuComponent, selector: "ate-image-bubble-menu", inputs: ["config", "imageUpload"] }, { kind: "component", type: AteTableBubbleMenuComponent, selector: "ate-table-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteCellBubbleMenuComponent, selector: "ate-cell-bubble-menu", inputs: ["config"] }, { kind: "component", type: AteSlashCommandsComponent, selector: "ate-slash-commands", inputs: ["editor", "config"] }, { kind: "component", type: AteLinkBubbleMenuComponent, selector: "ate-link-bubble-menu" }, { kind: "component", type: AteColorBubbleMenuComponent, selector: "ate-color-bubble-menu" }, { kind: "component", type: AteEditToggleComponent, selector: "ate-edit-toggle", inputs: ["editable", "translations"], outputs: ["editToggle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6783
6941
  }
6784
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AteAngularNodeView, decorators: [{
6785
- type: Directive
6786
- }] });
6942
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AngularTiptapEditorComponent, decorators: [{
6943
+ type: Component,
6944
+ args: [{ selector: "angular-tiptap-editor", standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [AteNoopValueAccessorDirective], host: {
6945
+ "[class.fill-container]": "finalFillContainer()",
6946
+ "[class.floating-toolbar]": "finalFloatingToolbar()",
6947
+ "[class.is-readonly]": "!finalEditable() && !mergedDisabled()",
6948
+ "[class.is-disabled]": "mergedDisabled()",
6949
+ "[style.--ate-border-width]": "finalSeamless() || mergedDisabled() ? '0' : null",
6950
+ "[style.--ate-background]": "finalSeamless() ? 'transparent' : (mergedDisabled() ? 'var(--ate-surface-tertiary)' : null)",
6951
+ "[style.--ate-toolbar-border-color]": "finalSeamless() ? 'transparent' : null",
6952
+ "[style.--ate-counter-background]": "finalSeamless() ? 'transparent' : null",
6953
+ "[style.--ate-counter-border-color]": "finalSeamless() ? 'transparent' : null",
6954
+ "[class.dark]": "config().theme === 'dark'",
6955
+ "[attr.data-theme]": "config().theme",
6956
+ }, imports: [
6957
+ AteToolbarComponent,
6958
+ AteBubbleMenuComponent,
6959
+ AteImageBubbleMenuComponent,
6960
+ AteTableBubbleMenuComponent,
6961
+ AteCellBubbleMenuComponent,
6962
+ AteSlashCommandsComponent,
6963
+ AteLinkBubbleMenuComponent,
6964
+ AteColorBubbleMenuComponent,
6965
+ AteEditToggleComponent,
6966
+ ], providers: [AteEditorCommandsService, AteImageService, AteColorPickerService, AteLinkService], template: `
6967
+ <div class="ate-editor">
6968
+ <!-- Toolbar -->
6969
+ @if (finalEditable() && !mergedDisabled() && finalShowToolbar() && editor()) {
6970
+ <ate-toolbar
6971
+ [editor]="editor()!"
6972
+ [config]="finalToolbarConfig()"
6973
+ [imageUpload]="finalImageUploadConfig()"
6974
+ [floating]="finalFloatingToolbar()"
6975
+ (mouseenter)="hideBubbleMenus()"
6976
+ (mouseleave)="showBubbleMenus()" />
6977
+ }
6787
6978
 
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
- };
6979
+ @if (finalShowEditToggle() && !mergedDisabled()) {
6980
+ <ate-edit-toggle
6981
+ [editable]="finalEditable()"
6982
+ [translations]="currentTranslations()"
6983
+ (editToggle)="toggleEditMode($event)" />
6984
+ }
6985
+
6986
+ <!-- Editor Content -->
6987
+ <div
6988
+ #editorElement
6989
+ class="ate-content"
6990
+ [class.drag-over]="isDragOver()"
6991
+ (dragover)="onDragOver($event)"
6992
+ (drop)="onDrop($event)"
6993
+ (click)="onEditorClick($event)"
6994
+ (keydown.enter)="onEditorClick($event)"
6995
+ (keydown.space)="onEditorClick($event)"
6996
+ tabindex="0"
6997
+ role="application"
6998
+ [attr.aria-label]="currentTranslations().editor.placeholder"></div>
6999
+
7000
+ <!-- Text Bubble Menu -->
7001
+ @if (finalEditable() && finalShowBubbleMenu() && editor()) {
7002
+ <ate-bubble-menu
7003
+ [editor]="editor()!"
7004
+ [config]="finalBubbleMenuConfig()"
7005
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-bubble-menu>
7006
+ }
7007
+
7008
+ <!-- Image Bubble Menu -->
7009
+ @if (finalEditable() && finalShowImageBubbleMenu() && editor()) {
7010
+ <ate-image-bubble-menu
7011
+ [editor]="editor()!"
7012
+ [config]="finalImageBubbleMenuConfig()"
7013
+ [imageUpload]="finalImageUploadConfig()"
7014
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-image-bubble-menu>
7015
+ }
7016
+
7017
+ <!-- Link Bubble Menu -->
7018
+ @if (finalEditable() && editor()) {
7019
+ <ate-link-bubble-menu
7020
+ [editor]="editor()!"
7021
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-link-bubble-menu>
7022
+ }
7023
+
7024
+ <!-- Color Bubble Menu -->
7025
+ @if (finalEditable() && editor()) {
7026
+ <ate-color-bubble-menu
7027
+ [editor]="editor()!"
7028
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-color-bubble-menu>
7029
+ }
7030
+
7031
+ <!-- Slash Commands -->
7032
+ @if (finalEditable() && finalEnableSlashCommands() && editor()) {
7033
+ <ate-slash-commands
7034
+ [editor]="editor()!"
7035
+ [config]="finalSlashCommandsConfig()"
7036
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-slash-commands>
7037
+ }
7038
+
7039
+ <!-- Table Menu -->
7040
+ @if (finalEditable() && finalShowTableBubbleMenu() && editor()) {
7041
+ <ate-table-bubble-menu
7042
+ [editor]="editor()!"
7043
+ [config]="finalTableBubbleMenuConfig()"
7044
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-table-bubble-menu>
7045
+ }
7046
+
7047
+ <!-- Cell Menu -->
7048
+ @if (finalEditable() && finalShowCellBubbleMenu() && editor()) {
7049
+ <ate-cell-bubble-menu
7050
+ [editor]="editor()!"
7051
+ [config]="finalCellBubbleMenuConfig()"
7052
+ [style.display]="editorFullyInitialized() ? 'block' : 'none'"></ate-cell-bubble-menu>
7053
+ }
7054
+
7055
+ <!-- Counters -->
7056
+ @if (
7057
+ finalEditable() &&
7058
+ !mergedDisabled() &&
7059
+ finalShowFooter() &&
7060
+ (finalShowCharacterCount() || finalShowWordCount())
7061
+ ) {
7062
+ <div
7063
+ class="character-count"
7064
+ [class.limit-reached]="finalMaxCharacters() && characterCount() >= finalMaxCharacters()!">
7065
+ @if (finalShowCharacterCount()) {
7066
+ {{ characterCount() }}
7067
+ {{ currentTranslations().editor.character }}{{ characterCount() > 1 ? "s" : "" }}
7068
+ @if (finalMaxCharacters()) {
7069
+ / {{ finalMaxCharacters() }}
7070
+ }
7071
+ }
7072
+
7073
+ @if (finalShowCharacterCount() && finalShowWordCount()) {
7074
+ ,
7075
+ }
7076
+
7077
+ @if (finalShowWordCount()) {
7078
+ {{ wordCount() }}
7079
+ {{ currentTranslations().editor.word }}{{ wordCount() > 1 ? "s" : "" }}
7080
+ }
7081
+ </div>
7082
+ }
7083
+ </div>
7084
+ `, 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"] }]
7085
+ }], 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 }] }] } });
7086
+
7087
+ /**
7088
+ * Provides the necessary configuration and initialization for the Angular TipTap Editor.
7089
+ * This should be called in your app.config.ts (for standalone) or main.ts.
7090
+ *
7091
+ * This provider is essential for 'Angular Nodes' to work correctly, as it captures the
7092
+ * root Injector required to instantiate custom Angular components within the editor.
7093
+ * @example
7094
+ * ```ts
7095
+ * bootstrapApplication(AppComponent, {
7096
+ * providers: [
7097
+ * provideAteEditor({
7098
+ * theme: 'dark',
7099
+ * mode: 'seamless'
7100
+ * })
7101
+ * ]
7102
+ * });
7103
+ * ```
7104
+ */
7105
+ function provideAteEditor(config) {
7106
+ return makeEnvironmentProviders([
7107
+ {
7108
+ provide: ATE_GLOBAL_CONFIG,
7109
+ useValue: config || {},
7110
+ },
7111
+ provideEnvironmentInitializer(() => {
7112
+ const injector = inject(Injector);
7113
+ setGlobalInjector(injector);
7114
+ }),
7115
+ ]);
6937
7116
  }
6938
7117
 
6939
- const RESERVED_NAMES = [
6940
- "doc",
6941
- "paragraph",
6942
- "text",
6943
- "hardBreak",
7118
+ /**
7119
+ * Clés des boutons de la barre d'outils native
7120
+ */
7121
+ const ATE_TOOLBAR_KEYS = [
7122
+ "bold",
7123
+ "italic",
7124
+ "underline",
7125
+ "strike",
7126
+ "code",
7127
+ "codeBlock",
7128
+ "superscript",
7129
+ "subscript",
7130
+ "highlight",
7131
+ "highlightPicker",
7132
+ "heading1",
7133
+ "heading2",
7134
+ "heading3",
6944
7135
  "bulletList",
6945
7136
  "orderedList",
6946
- "listItem",
6947
7137
  "blockquote",
6948
- "codeBlock",
6949
- "heading",
7138
+ "alignLeft",
7139
+ "alignCenter",
7140
+ "alignRight",
7141
+ "alignJustify",
7142
+ "link",
7143
+ "image",
6950
7144
  "horizontalRule",
7145
+ "table",
7146
+ "undo",
7147
+ "redo",
7148
+ "clear",
7149
+ "textColor",
7150
+ "separator",
6951
7151
  ];
7152
+
6952
7153
  /**
6953
- * Derives the TipTap node name and HTML tag from a component class.
7154
+ * Clés des options du menu bulle de texte
6954
7155
  */
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
- }
7156
+ const ATE_BUBBLE_MENU_KEYS = [
7157
+ "bold",
7158
+ "italic",
7159
+ "underline",
7160
+ "strike",
7161
+ "code",
7162
+ "superscript",
7163
+ "subscript",
7164
+ "highlight",
7165
+ "highlightPicker",
7166
+ "textColor",
7167
+ "link",
7168
+ "separator",
7169
+ ];
6972
7170
  /**
6973
- * Creates TipTap attributes from component inputs (Standard Mode).
7171
+ * Clés des options du menu bulle d'image
6974
7172
  */
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
- }
7173
+ const ATE_IMAGE_BUBBLE_MENU_KEYS = [
7174
+ "changeImage",
7175
+ "resizeSmall",
7176
+ "resizeMedium",
7177
+ "resizeLarge",
7178
+ "resizeOriginal",
7179
+ "deleteImage",
7180
+ "separator",
7181
+ ];
7004
7182
  /**
7005
- * Factory to transform an Angular component into a TipTap Node extension.
7006
- * Supports both "TipTap-Aware" and "Standard" modes.
7007
- *
7008
- * @internal
7183
+ * Clés des options du menu de table
7009
7184
  */
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
-
7185
+ const ATE_TABLE_BUBBLE_MENU_KEYS = [
7186
+ "addRowBefore",
7187
+ "addRowAfter",
7188
+ "deleteRow",
7189
+ "addColumnBefore",
7190
+ "addColumnAfter",
7191
+ "deleteColumn",
7192
+ "deleteTable",
7193
+ "toggleHeaderRow",
7194
+ "toggleHeaderColumn",
7195
+ "separator",
7196
+ ];
7061
7197
  /**
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
- * ```
7198
+ * Clés des options du menu de cellule
7086
7199
  */
7087
- function registerAngularComponent(injector, options) {
7088
- return createAngularComponentExtension(injector, options);
7089
- }
7200
+ const ATE_CELL_BUBBLE_MENU_KEYS = ["mergeCells", "splitCell"];
7090
7201
 
7091
7202
  /*
7092
7203
  * Public API Surface of tiptap-editor
7093
7204
  */
7094
- // Main component
7205
+ // Main component & Provider
7095
7206
  /** @deprecated Renamed to `ATE_INITIAL_EDITOR_STATE`. This alias will be removed in v3.0.0. */
7096
7207
  const INITIAL_EDITOR_STATE = ATE_INITIAL_EDITOR_STATE;
7097
7208
  /** @deprecated Renamed to `ATE_SLASH_COMMAND_KEYS`. This alias will be removed in v3.0.0. */
@@ -7125,5 +7236,5 @@ const DEFAULT_CELL_MENU_CONFIG = ATE_DEFAULT_CELL_MENU_CONFIG;
7125
7236
  * Generated bundle index. Do not edit.
7126
7237
  */
7127
7238
 
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 };
7239
+ 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_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, 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, provideAteEditor, registerAngularComponent };
7129
7240
  //# sourceMappingURL=flogeez-angular-tiptap-editor.mjs.map