@phuong-tran-redoc/document-engine-angular 0.0.36 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md CHANGED
@@ -1,7 +1,19 @@
1
- Copyright 2025 Realestatedoc (Redoc).
1
+ Copyright (c) 2025 Realestatedoc (Redoc).
2
2
 
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
4
9
 
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
6
12
 
7
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -21,7 +21,7 @@ import { TextAlign } from '@tiptap/extension-text-align';
21
21
  import { TextStyleKit } from '@tiptap/extension-text-style';
22
22
  import Underline from '@tiptap/extension-underline';
23
23
  import { Dropcursor, Gapcursor, TrailingNode, UndoRedo, CharacterCount, Placeholder } from '@tiptap/extensions';
24
- import { ResetOnEnter, EditableRegion, RestrictedEditing, CustomOrderedList, StyledTableKit, PageBreak, ResetFormat, Indent, ClearContent, TextCase, NotumHeading, DynamicField, Color, normalizeColor, getActiveMarkRange, getSelectedCells, getCombinedCellAttributeValue, TableDefaultAttributes, getCursorCellInfo, getClosestDomElement } from '@phuong-tran-redoc/document-engine-core';
24
+ import { ResetOnEnter, EditableRegion, RestrictedEditing, CustomOrderedList, StyledTableKit, ImageRef, PageBreak, ResetFormat, Indent, ClearContent, TextCase, NotumHeading, DynamicField, Color, normalizeColor, getActiveMarkRange, getSelectedCells, getCombinedCellAttributeValue, TableDefaultAttributes, getCursorCellInfo, getClosestDomElement } from '@phuong-tran-redoc/document-engine-core';
25
25
  import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
26
26
  import * as i1 from '@angular/common';
27
27
  import { DOCUMENT, CommonModule } from '@angular/common';
@@ -104,6 +104,7 @@ const DocumentEngineKit = Extension.create({
104
104
  add(options.superscript, Superscript);
105
105
  add(options.textAlign, TextAlign, DEFAULT_TEXT_ALIGN);
106
106
  add(options.image, Image);
107
+ add(options.imageRef, ImageRef);
107
108
  add(options.placeholder, Placeholder, DEFAULT_PLACEHOLDER);
108
109
  add(options.pageBreak, PageBreak);
109
110
  add(options.resetFormat, ResetFormat);
@@ -1920,6 +1921,39 @@ const DynamicFieldsBubbleConfig = {
1920
1921
  ],
1921
1922
  };
1922
1923
 
1924
+ /**
1925
+ * Whether a picker result is worth inserting. A `null`/`undefined` result (or one
1926
+ * with neither an `id` nor a `url`) is treated as a cancel — insert nothing.
1927
+ */
1928
+ function isInsertableResult(result) {
1929
+ return !!result && (!!result.id || !!result.url);
1930
+ }
1931
+ /** Read the consumer's `onPick` media hook off the `image` extension options, if any. */
1932
+ function getImagePickHook(editor) {
1933
+ const ext = editor.extensionManager.extensions.find((e) => e.name === 'image');
1934
+ return ext?.options?.onPick;
1935
+ }
1936
+ /**
1937
+ * Insert a picked media result: an `image-ref` node when that node is enabled
1938
+ * (preferred — URL-free), otherwise a plain image from the resolved URL. Returns
1939
+ * `true` when something was actually inserted, `false` when the result had
1940
+ * nothing usable (id-only with no `imageRef` node, and no url) — so the caller can
1941
+ * avoid closing the view as if an insert had happened.
1942
+ */
1943
+ function insertMediaResult(editor, result) {
1944
+ const hasImageRef = editor.extensionManager.extensions.some((e) => e.name === 'imageRef');
1945
+ const chain = editor.chain().focus();
1946
+ if (hasImageRef && result.id) {
1947
+ chain.insertImageRef({ imageId: result.id, caption: result.alt ?? null }).run();
1948
+ return true;
1949
+ }
1950
+ if (result.url) {
1951
+ chain.setImage({ src: result.url, alt: result.alt ?? '' }).run();
1952
+ return true;
1953
+ }
1954
+ return false;
1955
+ }
1956
+
1923
1957
  const urlValidator = (control) => {
1924
1958
  const value = (control.value ?? '').trim();
1925
1959
  if (!value)
@@ -1968,9 +2002,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
1968
2002
  }] } });
1969
2003
 
1970
2004
  /**
1971
- * Temporary image insert view for bubble menu
1972
- * Provides URL input for inserting images via link
1973
- * This is a temporary solution until full upload flow is implemented
2005
+ * Image insert view for the bubble menu.
2006
+ *
2007
+ * Two modes:
2008
+ * - When the editor's `image` config supplies an async `onPick` hook, the view
2009
+ * delegates selection to the consumer's media picker and inserts the result
2010
+ * (an `image-ref` node when that node is enabled, else a plain image).
2011
+ * - Otherwise it falls back to the built-in raw-URL input form.
1974
2012
  */
1975
2013
  class ImageInsertViewComponent {
1976
2014
  fb = inject(FormBuilder);
@@ -1978,11 +2016,22 @@ class ImageInsertViewComponent {
1978
2016
  editor;
1979
2017
  close;
1980
2018
  goBack;
2019
+ /** True while the consumer's `onPick` promise is in flight. */
2020
+ loading = false;
1981
2021
  imageForm = this.fb.nonNullable.group({
1982
2022
  url: this.fb.control('', [Validators.required, urlValidator]),
1983
2023
  alt: this.fb.control(''),
1984
2024
  });
2025
+ /** The consumer's media picker, read off the `image` extension options (if any). */
2026
+ get onPick() {
2027
+ return this.editor ? getImagePickHook(this.editor) : undefined;
2028
+ }
2029
+ /** Whether to show the picker mode (vs the raw-URL fallback form). */
2030
+ get hasPicker() {
2031
+ return typeof this.onPick === 'function';
2032
+ }
1985
2033
  onActivate() {
2034
+ this.loading = false;
1986
2035
  this.imageForm.reset();
1987
2036
  this.imageForm.controls.url.markAsUntouched();
1988
2037
  this.cdr.markForCheck();
@@ -1999,6 +2048,30 @@ class ImageInsertViewComponent {
1999
2048
  this.editor.chain().focus().setImage({ src: url, alt }).run();
2000
2049
  this.close?.();
2001
2050
  }
2051
+ /** Delegate selection to the consumer's media picker, then insert the result. */
2052
+ async pickImage() {
2053
+ const onPick = this.onPick;
2054
+ if (!onPick || !this.editor || this.loading)
2055
+ return;
2056
+ this.loading = true;
2057
+ this.cdr.markForCheck();
2058
+ try {
2059
+ const result = await onPick();
2060
+ // Reject / resolve-nothing is a deliberate no-op (consumer cancelled). Only
2061
+ // close when something was actually inserted — an id-only result with no
2062
+ // `image-ref` node inserts nothing and must leave the view open.
2063
+ if (this.editor && isInsertableResult(result) && insertMediaResult(this.editor, result)) {
2064
+ this.close?.();
2065
+ }
2066
+ }
2067
+ catch {
2068
+ // Swallow: a rejected picker (e.g. dialog dismissed) must not crash the editor.
2069
+ }
2070
+ finally {
2071
+ this.loading = false;
2072
+ this.cdr.markForCheck();
2073
+ }
2074
+ }
2002
2075
  cancel() {
2003
2076
  this.close?.();
2004
2077
  }
@@ -2008,7 +2081,23 @@ class ImageInsertViewComponent {
2008
2081
  <!-- Title -->
2009
2082
  <div class="image-insert-view__header">Insert Image</div>
2010
2083
 
2011
- <!-- Content -->
2084
+ <!-- Picker mode: delegate selection to the consumer's media library -->
2085
+ <ng-container *ngIf="hasPicker; else urlForm">
2086
+ <div class="image-insert-view__content">
2087
+ <p class="image-insert-view__hint">Choose an image from your media library.</p>
2088
+ </div>
2089
+ <div class="image-insert-view__actions">
2090
+ <button type="button" documentEngineButton variant="ghost" [disabled]="loading" (click)="cancel()">
2091
+ Cancel
2092
+ </button>
2093
+ <button type="button" documentEngineButton variant="default" [disabled]="loading" (click)="pickImage()">
2094
+ {{ loading ? 'Loading…' : 'Choose from library' }}
2095
+ </button>
2096
+ </div>
2097
+ </ng-container>
2098
+
2099
+ <!-- Fallback: built-in raw-URL input form -->
2100
+ <ng-template #urlForm>
2012
2101
  <form [formGroup]="imageForm" (ngSubmit)="insertImage()">
2013
2102
  <div class="image-insert-view__content">
2014
2103
  <!-- Image URL input -->
@@ -2048,8 +2137,9 @@ class ImageInsertViewComponent {
2048
2137
  <button type="submit" documentEngineButton variant="default" [disabled]="!canInsert()">Insert Image</button>
2049
2138
  </div>
2050
2139
  </form>
2140
+ </ng-template>
2051
2141
  </div>
2052
- `, isInline: true, styles: [".image-insert-view{display:flex;flex-direction:column;min-width:320px}.image-insert-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500;font-size:.875rem}.image-insert-view__content{display:flex;flex-direction:column;gap:.75rem;padding:1rem .5rem}.image-insert-view__field{display:flex;flex-direction:column;gap:.25rem;width:100%}.image-insert-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "directive", type: LabelDirective, selector: "label[documentEngineLabel]", inputs: ["class"] }, { kind: "component", type: ErrorMessageComponent, selector: "document-engine-error-message", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2142
+ `, isInline: true, styles: [".image-insert-view{display:flex;flex-direction:column;min-width:320px}.image-insert-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500;font-size:.875rem}.image-insert-view__content{display:flex;flex-direction:column;gap:.75rem;padding:1rem .5rem}.image-insert-view__hint{margin:0;font-size:.8125rem;color:var(--muted-foreground)}.image-insert-view__field{display:flex;flex-direction:column;gap:.25rem;width:100%}.image-insert-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "directive", type: LabelDirective, selector: "label[documentEngineLabel]", inputs: ["class"] }, { kind: "component", type: ErrorMessageComponent, selector: "document-engine-error-message", inputs: ["class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2053
2143
  }
2054
2144
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ImageInsertViewComponent, decorators: [{
2055
2145
  type: Component,
@@ -2058,7 +2148,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2058
2148
  <!-- Title -->
2059
2149
  <div class="image-insert-view__header">Insert Image</div>
2060
2150
 
2061
- <!-- Content -->
2151
+ <!-- Picker mode: delegate selection to the consumer's media library -->
2152
+ <ng-container *ngIf="hasPicker; else urlForm">
2153
+ <div class="image-insert-view__content">
2154
+ <p class="image-insert-view__hint">Choose an image from your media library.</p>
2155
+ </div>
2156
+ <div class="image-insert-view__actions">
2157
+ <button type="button" documentEngineButton variant="ghost" [disabled]="loading" (click)="cancel()">
2158
+ Cancel
2159
+ </button>
2160
+ <button type="button" documentEngineButton variant="default" [disabled]="loading" (click)="pickImage()">
2161
+ {{ loading ? 'Loading…' : 'Choose from library' }}
2162
+ </button>
2163
+ </div>
2164
+ </ng-container>
2165
+
2166
+ <!-- Fallback: built-in raw-URL input form -->
2167
+ <ng-template #urlForm>
2062
2168
  <form [formGroup]="imageForm" (ngSubmit)="insertImage()">
2063
2169
  <div class="image-insert-view__content">
2064
2170
  <!-- Image URL input -->
@@ -2098,8 +2204,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2098
2204
  <button type="submit" documentEngineButton variant="default" [disabled]="!canInsert()">Insert Image</button>
2099
2205
  </div>
2100
2206
  </form>
2207
+ </ng-template>
2101
2208
  </div>
2102
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".image-insert-view{display:flex;flex-direction:column;min-width:320px}.image-insert-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500;font-size:.875rem}.image-insert-view__content{display:flex;flex-direction:column;gap:.75rem;padding:1rem .5rem}.image-insert-view__field{display:flex;flex-direction:column;gap:.25rem;width:100%}.image-insert-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border)}\n"] }]
2209
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".image-insert-view{display:flex;flex-direction:column;min-width:320px}.image-insert-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500;font-size:.875rem}.image-insert-view__content{display:flex;flex-direction:column;gap:.75rem;padding:1rem .5rem}.image-insert-view__hint{margin:0;font-size:.8125rem;color:var(--muted-foreground)}.image-insert-view__field{display:flex;flex-direction:column;gap:.25rem;width:100%}.image-insert-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border)}\n"] }]
2103
2210
  }] });
2104
2211
 
2105
2212
  const ImageBubbleConfig = {
@@ -5340,7 +5447,7 @@ class EditorBubbleMenuComponent {
5340
5447
  // Activate new view
5341
5448
  this.activateView(this.currentViewRef);
5342
5449
  // Trigger change detection for the child component to render immediately
5343
- // This is especially important for Angular v14 compatibility
5450
+ // (the view is created imperatively, so it is outside Angular's normal CD pass)
5344
5451
  this.cdr.markForCheck();
5345
5452
  // Mark bubble as visible for transition handling
5346
5453
  const bubbleElement = this.bubbleElement?.nativeElement;
@@ -6054,7 +6161,7 @@ class ToolbarComponent {
6054
6161
  this.toolbarService.emit('printValue');
6055
6162
  }
6056
6163
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6057
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: ToolbarComponent, isStandalone: true, selector: "document-engine-toolbar", inputs: { editor: "editor", capabilities: "capabilities", config: "config" }, outputs: { action: "action" }, viewQueries: [{ propertyName: "colorBubbleMenu", first: true, predicate: ["colorBubbleMenu"], descendants: true }, { propertyName: "linkBubbleWrapper", first: true, predicate: ["linkBubbleWrapper"], descendants: true }, { propertyName: "imageBubbleMenu", first: true, predicate: ["imageBubbleMenu"], descendants: true }, { propertyName: "specialCharsBubbleMenu", first: true, predicate: ["specialCharsBubbleMenu"], descendants: true }, { propertyName: "dynamicFieldsBubbleMenu", first: true, predicate: ["dynamicFieldsBubbleMenu"], descendants: true }, { propertyName: "templateBubbleMenu", first: true, predicate: ["templateBubbleMenu"], descendants: true }, { propertyName: "tableCreateBubbleMenu", first: true, predicate: ["tableCreateBubbleMenu"], descendants: true }], ngImport: i0, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectLabelDirective, selector: "[documentEngineSelectLabel]" }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "component", type: ToolbarBubbleMenuComponent, selector: "document-engine-toolbar-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: EditorBubbleMenuComponent, selector: "document-engine-editor-bubble-menu", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: ToolbarComponent, isStandalone: true, selector: "document-engine-toolbar", inputs: { editor: "editor", capabilities: "capabilities", config: "config" }, outputs: { action: "action" }, viewQueries: [{ propertyName: "colorBubbleMenu", first: true, predicate: ["colorBubbleMenu"], descendants: true }, { propertyName: "linkBubbleWrapper", first: true, predicate: ["linkBubbleWrapper"], descendants: true }, { propertyName: "imageBubbleMenu", first: true, predicate: ["imageBubbleMenu"], descendants: true }, { propertyName: "specialCharsBubbleMenu", first: true, predicate: ["specialCharsBubbleMenu"], descendants: true }, { propertyName: "dynamicFieldsBubbleMenu", first: true, predicate: ["dynamicFieldsBubbleMenu"], descendants: true }, { propertyName: "templateBubbleMenu", first: true, predicate: ["templateBubbleMenu"], descendants: true }, { propertyName: "tableCreateBubbleMenu", first: true, predicate: ["tableCreateBubbleMenu"], descendants: true }], ngImport: i0, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n [class.active]=\"editor.isActive('pageBreak')\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectLabelDirective, selector: "[documentEngineSelectLabel]" }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "component", type: ToolbarBubbleMenuComponent, selector: "document-engine-toolbar-bubble-menu", inputs: ["editor", "config"] }, { kind: "component", type: EditorBubbleMenuComponent, selector: "document-engine-editor-bubble-menu", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6058
6165
  }
6059
6166
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarComponent, decorators: [{
6060
6167
  type: Component,
@@ -6067,7 +6174,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
6067
6174
  SelectOptionDirective,
6068
6175
  ToolbarBubbleMenuComponent,
6069
6176
  EditorBubbleMenuComponent,
6070
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"] }]
6177
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"document-engine-document-editor__toolbar\">\n <div class=\"document-engine-document-editor__toolbar-content\">\n <div class=\"document-engine-document-editor__toolbar-left\">\n <!-- History: Undo/Redo -->\n <div *ngIf=\"capabilities.hasHistory\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"history\">\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canUndo\"\n (click)=\"runAndEmit('undo', editor.chain().focus().undo().run)\"\n title=\"Undo (Ctrl+Z)\"\n >\n <document-engine-icon name=\"undo\"></document-engine-icon>\n </button>\n\n <button\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canRedo\"\n (click)=\"runAndEmit('redo', editor.chain().focus().redo().run)\"\n title=\"Redo\"\n >\n <document-engine-icon name=\"redo\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Formatting: Font Size, Line Height, Text Case, Heading -->\n <div\n *ngIf=\"capabilities.hasTextProperty\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"text-property\"\n >\n <!-- Font Size -->\n <document-engine-select\n *ngIf=\"capabilities.hasFontSize\"\n [value]=\"activeFontSize\"\n placeholder=\"Font Size\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Font Size\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_size\"></document-engine-icon>\n\n <button documentEngineSelectOption [value]=\"null\">Default</button>\n\n <button\n *ngFor=\"let option of fontSizeOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setFontSize(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Line Height -->\n <document-engine-select\n *ngIf=\"capabilities.hasLineHeight\"\n [value]=\"activeLineHeight\"\n placeholder=\"Line Height\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Line Height\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_line_spacing\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of lineHeightOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setLineHeight(option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Text Case -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextCase\"\n placeholder=\"Text Case\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Case\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"match_case\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textCaseOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"runAndEmit('setTextCase', editor.chain().focus().textCase(option.value).run)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n\n <!-- Heading -->\n <document-engine-select\n *ngIf=\"capabilities.hasHeading\"\n [value]=\"activeHeading?.toString() || null\"\n placeholder=\"Normal text\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Heading\"\n >\n <button\n *ngFor=\"let option of headingOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value?.toString() || null\"\n [class]=\"option.class\"\n (click)=\"setHeading(option.value?.toString() || null)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </div>\n\n <!-- Format Utilities: Reset Formatting & Colors -->\n <div\n *ngIf=\"capabilities.hasFormatUtilities\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"format-utilities\"\n >\n <!-- Reset Formatting -->\n <button\n *ngIf=\"capabilities.hasResetFormat\"\n documentEngineButton\n size=\"icon\"\n (click)=\"runAndEmit('resetFormat', editor.chain().focus().resetFormat().run)\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canResetFormat\"\n title=\"Clear Formatting\"\n >\n <document-engine-icon name=\"format_clear\"></document-engine-icon>\n </button>\n\n <!-- Colors: Text/Background -->\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Text Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'text')\"\n >\n <document-engine-icon name=\"format_color_text\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasColors\"\n documentEngineButton\n size=\"icon\"\n title=\"Background Color\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openColorBubble($event, 'fill')\"\n >\n <document-engine-icon name=\"format_color_fill\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Bold/Italic/Underline/Strike -->\n <div\n *ngIf=\"capabilities.basicInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"basic-inline\"\n >\n <button\n *ngIf=\"capabilities.hasBold\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBold\"\n (click)=\"runAndEmit('toggleBold', editor.chain().focus().toggleBold().run)\"\n [class.active]=\"editor.isActive('bold')\"\n title=\"Bold\"\n >\n <document-engine-icon name=\"format_bold\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasItalic\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleItalic\"\n (click)=\"runAndEmit('toggleItalic', editor.chain().focus().toggleItalic().run)\"\n [class.active]=\"editor.isActive('italic')\"\n title=\"Italic\"\n >\n <document-engine-icon name=\"format_italic\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasUnderline\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleUnderline\"\n (click)=\"runAndEmit('toggleUnderline', editor.chain().focus().toggleUnderline().run)\"\n [class.active]=\"editor.isActive('underline')\"\n title=\"Underline\"\n >\n <document-engine-icon name=\"format_underline\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasStrike\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleStrike\"\n (click)=\"runAndEmit('toggleStrike', editor.chain().focus().toggleStrike().run)\"\n [class.active]=\"editor.isActive('strike')\"\n title=\"Strikethrough\"\n >\n <document-engine-icon name=\"format_strikethrough\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Text Styles: Sub/Super/Code/Link -->\n <div\n *ngIf=\"capabilities.advancedInline\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"advanced-inline\"\n >\n <button\n *ngIf=\"capabilities.hasSubscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSubscript\"\n (click)=\"runAndEmit('toggleSubscript', editor.chain().focus().toggleSubscript().run)\"\n [class.active]=\"editor.isActive('subscript')\"\n title=\"Subscript\"\n >\n <document-engine-icon name=\"subscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSuperscript\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleSuperscript\"\n (click)=\"runAndEmit('toggleSuperscript', editor.chain().focus().toggleSuperscript().run)\"\n [class.active]=\"editor.isActive('superscript')\"\n title=\"Superscript\"\n >\n <document-engine-icon name=\"superscript\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasCode\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleCode\"\n (click)=\"runAndEmit('toggleCode', editor.chain().focus().toggleCode().run)\"\n [class.active]=\"editor.isActive('code')\"\n title=\"Code\"\n >\n <document-engine-icon name=\"code\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasLink\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleLink\"\n (click)=\"toggleLink()\"\n [class.active]=\"editor.isActive('link')\"\n [title]=\"editor.isActive('link') ? 'Unlink' : 'Link'\"\n >\n <document-engine-icon [name]=\"editor.isActive('link') ? 'link_off' : 'link'\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Alignment & Layout: Text Align, Indent -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Text Alignment -->\n <document-engine-select\n *ngIf=\"capabilities.hasTextAlign\"\n [value]=\"activeTextAlign\"\n (valueChange)=\"setTextAlign($event)\"\n placeholder=\"Text Align\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Text Align\"\n >\n <document-engine-icon documentEngineSelectLabel [name]=\"activeTextAlignIcon\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of textAlignOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n >\n <document-engine-icon [name]=\"option.icon\"></document-engine-icon>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n\n <!-- Indent -->\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canDecreaseIndent\"\n (click)=\"runAndEmit('decreaseIndent', editor.chain().focus().decreaseIndent().run)\"\n title=\"Decrease Indent\"\n >\n <document-engine-icon name=\"format_indent_decrease\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasIndent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canIncreaseIndent\"\n (click)=\"runAndEmit('increaseIndent', editor.chain().focus().increaseIndent().run)\"\n title=\"Increase Indent\"\n >\n <document-engine-icon name=\"format_indent_increase\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Lists -->\n <div\n *ngIf=\"capabilities.hasAlignmentLayout\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"alignment-layout\"\n >\n <!-- Lists: Bullet, Numbered (with style) -->\n <button\n *ngIf=\"capabilities.hasLists\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBulletList\"\n (click)=\"runAndEmit('toggleBulletList', editor.chain().focus().toggleBulletList().run)\"\n [class.active]=\"editor.isActive('bulletList')\"\n title=\"Bullet List\"\n >\n <document-engine-icon name=\"format_list_bulleted\"></document-engine-icon>\n </button>\n\n <!-- Numbered List with Style -->\n <document-engine-select\n *ngIf=\"capabilities.hasLists\"\n placeholder=\"Numbered List\"\n labelMode=\"static\"\n [value]=\"\n editor.isActive('customOrderedList')\n ? editor.getAttributes('customOrderedList')['data-list-style-type'] || 'decimal'\n : null\n \"\n [disabled]=\"toolbarState.isReadOnly\"\n title=\"Numbered List\"\n >\n <document-engine-icon documentEngineSelectLabel name=\"format_list_numbered\"></document-engine-icon>\n\n <button\n *ngFor=\"let option of listStyleOptions; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"setListStyle(option.value)\"\n [class.active]=\"\n editor.isActive('customOrderedList') &&\n editor.getAttributes('customOrderedList')['data-list-style-type'] === option.value\n \"\n >\n <span>{{ option.example }}</span>\n <span>{{ option.label }}</span>\n </button>\n </document-engine-select>\n </div>\n\n <!-- Block Types: Code Block, Blockquote -->\n <div\n *ngIf=\"capabilities.hasBlockTypes\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"block-types\"\n >\n <!-- Blockquote -->\n <button\n *ngIf=\"capabilities.hasBlockquote\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canToggleBlockquote\"\n (click)=\"runAndEmit('toggleBlockquote', editor.chain().focus().toggleBlockquote().run)\"\n [class.active]=\"editor.isActive('blockquote')\"\n title=\"Quote\"\n >\n <document-engine-icon name=\"format_quote\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Insert Object: Table, Images, Special Chars, Page Break -->\n <div\n *ngIf=\"capabilities.hasInsertObject\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"insert-object\"\n >\n <button\n *ngIf=\"capabilities.hasTable\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openCreateTableBubble($event)\"\n [class.active]=\"editor.isActive('table')\"\n title=\"Table\"\n >\n <document-engine-icon name=\"table_rows\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasImages\"\n documentEngineButton\n size=\"icon\"\n title=\"Insert Image\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"insertImage($event)\"\n >\n <document-engine-icon name=\"photo\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasSpecialChars\"\n documentEngineButton\n size=\"icon\"\n title=\"Special Character\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openSpecialCharsBubble($event)\"\n >\n <document-engine-icon name=\"special_character\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasPageBreak\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canInsertPageBreak\"\n (click)=\"runAndEmit('insertPageBreak', editor.chain().focus().insertPageBreak().run)\"\n [class.active]=\"editor.isActive('pageBreak')\"\n title=\"Insert page break\"\n >\n <document-engine-icon name=\"insert_page_break\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Business Features: Templates, Dynamic Fields, Editable Region -->\n <div\n *ngIf=\"capabilities.hasBusinessFeatures\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"business-features\"\n >\n <button\n *ngIf=\"capabilities.hasDynamicFields\"\n documentEngineButton\n size=\"icon\"\n title=\"Dynamic Field\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openDynamicFieldsBubble($event)\"\n >\n <document-engine-icon name=\"dynamic_form\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasTemplates\"\n documentEngineButton\n size=\"icon\"\n title=\"Template\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"openTemplateBubble($event)\"\n >\n <document-engine-icon name=\"description\"></document-engine-icon>\n </button>\n\n <button\n *ngIf=\"capabilities.hasEditableRegion\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"!toolbarState.canToggleEditableRegion || toolbarState.isReadOnly\"\n (click)=\"runAndEmit('toggleEditableRegion', editor.chain().focus().toggleEditableRegion().run)\"\n [class.active]=\"editor.isActive('editableRegion')\"\n title=\"Toggle editable region\"\n >\n <document-engine-icon name=\"restrict_edit\"></document-engine-icon>\n </button>\n </div>\n\n <!-- Actions: Clear Content, Markdown -->\n <div *ngIf=\"capabilities.hasActions\" class=\"document-engine-document-editor__toolbar-group\" data-group=\"actions\">\n <button\n *ngIf=\"capabilities.hasClearContent\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly || !toolbarState.canClearContent\"\n (click)=\"runAndEmit('clear', editor.chain().focus().clear().run)\"\n title=\"Clear all content\"\n >\n <document-engine-icon name=\"clear_all\"></document-engine-icon>\n </button>\n\n <!-- <button\n *ngIf=\"capabilities.hasMarkdown\"\n documentEngineButton\n size=\"icon\"\n (click)=\"downloadAsMarkdown()\"\n title=\"Download as Markdown\"\n >\n <document-engine-icon name=\"markdown\"></document-engine-icon>\n </button> -->\n </div>\n\n <!-- Custom Toolbar Buttons -->\n <div\n *ngIf=\"customToolbarButtons.length > 0\"\n class=\"document-engine-document-editor__toolbar-group\"\n data-group=\"custom-buttons\"\n >\n <ng-container *ngFor=\"let button of customToolbarButtons; trackBy: trackByIndex\">\n <!-- Icon Button -->\n <button\n *ngIf=\"button.type === 'icon-button'\"\n documentEngineButton\n size=\"icon\"\n [disabled]=\"toolbarState.isReadOnly\"\n (click)=\"handleCustomIconButton(button)\"\n [title]=\"button.label || ''\"\n >\n <document-engine-icon [name]=\"button.icon\"></document-engine-icon>\n </button>\n\n <!-- Select Button -->\n <document-engine-select\n *ngIf=\"button.type === 'select-button'\"\n [value]=\"null\"\n labelMode=\"static\"\n [disabled]=\"toolbarState.isReadOnly\"\n [title]=\"button.label\"\n >\n <span documentEngineSelectLabel>{{ button.label }}</span>\n\n <button\n *ngFor=\"let option of button.options; trackBy: trackByValue\"\n documentEngineSelectOption\n [value]=\"option.value\"\n (click)=\"handleCustomSelectButton(button, option.value)\"\n >\n {{ option.label }}\n </button>\n </document-engine-select>\n </ng-container>\n </div>\n </div>\n\n <!-- Right side actions -->\n <div class=\"document-engine-document-editor__toolbar-right\">\n <button\n *ngIf=\"capabilities.hasPrintButton\"\n documentEngineButton\n size=\"icon\"\n (click)=\"printValue()\"\n title=\"Print Value\"\n >\n <document-engine-icon name=\"print\"></document-engine-icon>\n </button>\n </div>\n </div>\n\n <!-- Color Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n #colorBubbleMenu\n [editor]=\"editor\"\n [config]=\"colorBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Link Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasLink\"\n #linkBubbleWrapper\n [editor]=\"editor\"\n [config]=\"linkBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Table Bubble Menu -->\n <document-engine-editor-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableBubbleWrapper\n [editor]=\"editor\"\n [config]=\"tableBubbleConfig\"\n ></document-engine-editor-bubble-menu>\n\n <!-- Image Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasImages\"\n #imageBubbleMenu\n [editor]=\"editor\"\n [config]=\"imageBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Special Characters Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasSpecialChars\"\n #specialCharsBubbleMenu\n [editor]=\"editor\"\n [config]=\"specialCharsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Dynamic Fields Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasDynamicFields\"\n #dynamicFieldsBubbleMenu\n [editor]=\"editor\"\n [config]=\"dynamicFieldsBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Template Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTemplates\"\n #templateBubbleMenu\n [editor]=\"editor\"\n [config]=\"templateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n\n <!-- Table Create Bubble Menu -->\n <document-engine-toolbar-bubble-menu\n *ngIf=\"capabilities.hasTable\"\n #tableCreateBubbleMenu\n [editor]=\"editor\"\n [config]=\"tableCreateBubbleConfig\"\n ></document-engine-toolbar-bubble-menu>\n</div>\n", styles: [":host{position:sticky;top:0;z-index:20}\n"] }]
6071
6178
  }], propDecorators: { editor: [{
6072
6179
  type: Input
6073
6180
  }], capabilities: [{