@flogeez/angular-tiptap-editor 2.2.3 → 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 } 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 } 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,6 +5391,442 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5391
5391
  }]
5392
5392
  }] });
5393
5393
 
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
+ };
5447
+ }
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
+ }
5459
+ }
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
+ }
5470
+ }
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
+ }
5481
+ }
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
+ }] });
5488
+
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;
5506
+ }
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]];
5516
+ }
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
+ });
5542
+ }
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
+ });
5573
+ }
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
5585
+ return {
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;
5635
+ },
5636
+ };
5637
+ };
5638
+ }
5639
+
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;
5730
+ },
5731
+ parseHTML() {
5732
+ if (customParseHTML) {
5733
+ return customParseHTML.call(this);
5734
+ }
5735
+ return [{ tag }, { tag: `div[data-component="${nodeName}"]` }];
5736
+ },
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
+ });
5758
+ },
5759
+ });
5760
+ }
5761
+
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
+ }
5777
+
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
+ }
5824
+
5825
+ /**
5826
+ * Injection Token for global editor configuration.
5827
+ */
5828
+ const ATE_GLOBAL_CONFIG = new InjectionToken("ATE_GLOBAL_CONFIG");
5829
+
5394
5830
  const AteSelectionCalculator = editor => {
5395
5831
  const { selection } = editor.state;
5396
5832
  const { from, to } = selection;
@@ -5746,6 +6182,14 @@ const ATE_DEFAULT_CELL_MENU_CONFIG = {
5746
6182
  };
5747
6183
 
5748
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
+ */
5749
6193
  class AngularTiptapEditorComponent {
5750
6194
  // ============================================
5751
6195
  // Toolbar / Bubble Menu Coordination
@@ -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,6 +6515,24 @@ class AngularTiptapEditorComponent {
6053
6515
  }
6054
6516
  }
6055
6517
  });
6518
+ // Effect to re-initialize editor when technical configuration changes
6519
+ effect(() => {
6520
+ // Monitor technical dependencies
6521
+ this.finalTiptapExtensions();
6522
+ this.finalTiptapOptions();
6523
+ this.finalAngularNodesConfig();
6524
+ untracked(() => {
6525
+ // Only if already initialized (post AfterViewInit)
6526
+ if (this.editorFullyInitialized()) {
6527
+ const currentEditor = this.editor();
6528
+ if (currentEditor) {
6529
+ currentEditor.destroy();
6530
+ this._editorFullyInitialized.set(false);
6531
+ this.initEditor();
6532
+ }
6533
+ }
6534
+ });
6535
+ });
6056
6536
  }
6057
6537
  ngAfterViewInit() {
6058
6538
  // La vue est déjà complètement initialisée dans ngAfterViewInit
@@ -6119,7 +6599,7 @@ class AngularTiptapEditorComponent {
6119
6599
  AteImageCalculator,
6120
6600
  AteStructureCalculator,
6121
6601
  AteDiscoveryCalculator,
6122
- ...this.stateCalculators(),
6602
+ ...this.finalStateCalculators(),
6123
6603
  ],
6124
6604
  }),
6125
6605
  ];
@@ -6136,8 +6616,22 @@ class AngularTiptapEditorComponent {
6136
6616
  limit: this.finalMaxCharacters(),
6137
6617
  }));
6138
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
+ });
6139
6633
  // Allow addition of custom extensions, but avoid duplicates by filtering by name
6140
- const customExtensions = this.tiptapExtensions();
6634
+ const customExtensions = this.finalTiptapExtensions();
6141
6635
  if (customExtensions.length > 0) {
6142
6636
  const existingNames = new Set(extensions
6143
6637
  .map(ext => ext?.name)
@@ -6149,7 +6643,7 @@ class AngularTiptapEditorComponent {
6149
6643
  extensions.push(...toAdd);
6150
6644
  }
6151
6645
  // Also allow any tiptap user options
6152
- const userOptions = this.tiptapOptions();
6646
+ const userOptions = this.finalTiptapOptions();
6153
6647
  const newEditor = new Editor({
6154
6648
  ...userOptions,
6155
6649
  element: this.editorElement().nativeElement,
@@ -6590,6 +7084,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
6590
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"] }]
6591
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 }] }] } });
6592
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
+ ]);
7116
+ }
7117
+
6593
7118
  /**
6594
7119
  * Clés des boutons de la barre d'outils native
6595
7120
  */
@@ -6677,7 +7202,7 @@ const ATE_CELL_BUBBLE_MENU_KEYS = ["mergeCells", "splitCell"];
6677
7202
  /*
6678
7203
  * Public API Surface of tiptap-editor
6679
7204
  */
6680
- // Main component
7205
+ // Main component & Provider
6681
7206
  /** @deprecated Renamed to `ATE_INITIAL_EDITOR_STATE`. This alias will be removed in v3.0.0. */
6682
7207
  const INITIAL_EDITOR_STATE = ATE_INITIAL_EDITOR_STATE;
6683
7208
  /** @deprecated Renamed to `ATE_SLASH_COMMAND_KEYS`. This alias will be removed in v3.0.0. */
@@ -6711,5 +7236,5 @@ const DEFAULT_CELL_MENU_CONFIG = ATE_DEFAULT_CELL_MENU_CONFIG;
6711
7236
  * Generated bundle index. Do not edit.
6712
7237
  */
6713
7238
 
6714
- 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, AteColorPickerService, AteDiscoveryCalculator, AteEditorCommandsService, AteI18nService, AteImageCalculator, AteImageService, AteLinkService, AteMarksCalculator, 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, createDefaultSlashCommands, filterSlashCommands };
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 };
6715
7240
  //# sourceMappingURL=flogeez-angular-tiptap-editor.mjs.map