@phuong-tran-redoc/document-engine-angular 0.1.3 → 0.1.5

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.
@@ -28,7 +28,7 @@ import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
28
28
  import * as i2 from '@angular/forms';
29
29
  import { NG_VALUE_ACCESSOR, FormsModule, FormControl, ReactiveFormsModule, FormBuilder, Validators, NonNullableFormBuilder } from '@angular/forms';
30
30
  import { FloatingMenuPlugin } from '@tiptap/extension-floating-menu';
31
- import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
31
+ import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
32
32
  import { throttle } from 'lodash-es';
33
33
  import { DomSanitizer } from '@angular/platform-browser';
34
34
  import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs';
@@ -883,9 +883,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
883
883
  *
884
884
  * Usage:
885
885
  * <button #trigger>Open</button>
886
- * <div [popover]="trigger" [isOpen]="isOpen">
886
+ * <div [documentEnginePopover]="trigger" [isOpen]="isOpen">
887
887
  * Content
888
888
  * </div>
889
+ *
890
+ * The host is pinned `position: fixed`, so floating-ui is asked for viewport-
891
+ * relative coordinates (`strategy: 'fixed'`). Omitting the strategy yields
892
+ * offset-parent-relative numbers, which are off by the page's scroll offset
893
+ * once written onto a fixed element.
889
894
  */
890
895
  class PopoverDirective {
891
896
  elRef = inject(ElementRef);
@@ -893,7 +898,18 @@ class PopoverDirective {
893
898
  /**
894
899
  * The trigger element reference (ElementRef or HTMLElement)
895
900
  */
901
+ documentEnginePopover;
902
+ /**
903
+ * The trigger element reference (ElementRef or HTMLElement)
904
+ *
905
+ * @deprecated Use `documentEnginePopover`. `[popover]` collides with the
906
+ * native HTML popover attribute and will be removed in the next major.
907
+ */
896
908
  popover;
909
+ /** The trigger, whichever input supplied it. */
910
+ get trigger() {
911
+ return this.documentEnginePopover ?? this.popover;
912
+ }
897
913
  /**
898
914
  * Whether the popover is open
899
915
  */
@@ -920,7 +936,8 @@ class PopoverDirective {
920
936
  this.updateVisibility();
921
937
  }
922
938
  ngOnChanges(changes) {
923
- if (this.viewInitialized && (changes['isOpen'] || changes['popover'])) {
939
+ if (this.viewInitialized &&
940
+ (changes['isOpen'] || changes['popover'] || changes['documentEnginePopover'])) {
924
941
  this.updateVisibility();
925
942
  }
926
943
  }
@@ -930,7 +947,7 @@ class PopoverDirective {
930
947
  updateVisibility() {
931
948
  if (!this.viewInitialized)
932
949
  return;
933
- if (this.isOpen && this.popover) {
950
+ if (this.isOpen && this.trigger) {
934
951
  // Wait for next tick to ensure element is rendered
935
952
  setTimeout(() => {
936
953
  this.setupFloating();
@@ -948,15 +965,16 @@ class PopoverDirective {
948
965
  }
949
966
  _setupFloatingInternal() {
950
967
  let triggerEl;
951
- if (!this.popover) {
968
+ const trigger = this.trigger;
969
+ if (!trigger) {
952
970
  if (this.retryCount < this.maxRetries) {
953
971
  this.retryCount++;
954
972
  setTimeout(() => this._setupFloatingInternal(), 10);
955
973
  }
956
974
  return;
957
975
  }
958
- if (this.popover instanceof ElementRef) {
959
- triggerEl = this.popover.nativeElement;
976
+ if (trigger instanceof ElementRef) {
977
+ triggerEl = trigger.nativeElement;
960
978
  // If ElementRef exists but nativeElement is not yet available, retry
961
979
  if (!triggerEl) {
962
980
  if (this.retryCount < this.maxRetries) {
@@ -967,7 +985,7 @@ class PopoverDirective {
967
985
  }
968
986
  }
969
987
  else {
970
- triggerEl = this.popover;
988
+ triggerEl = trigger;
971
989
  }
972
990
  const dropdownEl = this.elRef.nativeElement;
973
991
  if (!triggerEl || !dropdownEl) {
@@ -979,13 +997,8 @@ class PopoverDirective {
979
997
  return;
980
998
  }
981
999
  // Compute initial position before showing dropdown to prevent flash
982
- computePosition(triggerEl, dropdownEl, {
983
- placement: this.placement,
984
- middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
985
- })
986
- .then(({ x, y }) => {
987
- // Set position first, then make visible
988
- Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
1000
+ this.reposition(triggerEl, dropdownEl)
1001
+ .then(() => {
989
1002
  this.isPositioned = true;
990
1003
  // Trigger change detection to update visibility binding
991
1004
  this.cdr.markForCheck();
@@ -993,14 +1006,27 @@ class PopoverDirective {
993
1006
  .catch((error) => {
994
1007
  console.error('[PopoverDirective] Error computing position', error);
995
1008
  });
996
- // Use autoUpdate to automatically reposition on scroll/resize
1009
+ // Use autoUpdate to automatically reposition on scroll/resize. It must use
1010
+ // the SAME strategy as the initial call, or the first scroll undoes the fix.
997
1011
  this.cleanupAutoUpdate = autoUpdate(triggerEl, dropdownEl, () => {
998
- computePosition(triggerEl, dropdownEl, {
999
- placement: this.placement,
1000
- middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
1001
- }).then(({ x, y }) => {
1002
- Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
1003
- });
1012
+ void this.reposition(triggerEl, dropdownEl);
1013
+ });
1014
+ }
1015
+ /**
1016
+ * Position `dropdownEl` against `triggerEl`.
1017
+ *
1018
+ * `strategy: 'fixed'` is required: the host binding pins this element to
1019
+ * `position: fixed`, so the coordinates must be viewport-relative. Without it
1020
+ * floating-ui returns offset-parent-relative numbers and the panel lands the
1021
+ * page's scroll offset away from its trigger.
1022
+ */
1023
+ reposition(triggerEl, dropdownEl) {
1024
+ return computePosition(triggerEl, dropdownEl, {
1025
+ strategy: 'fixed',
1026
+ placement: this.placement,
1027
+ middleware: [offset(this.offset), flip(), shift({ padding: this.padding })],
1028
+ }).then(({ x, y }) => {
1029
+ Object.assign(dropdownEl.style, { left: `${x}px`, top: `${y}px` });
1004
1030
  });
1005
1031
  }
1006
1032
  cleanup() {
@@ -1010,19 +1036,24 @@ class PopoverDirective {
1010
1036
  }
1011
1037
  }
1012
1038
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1013
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: PopoverDirective, isStandalone: true, selector: "[popover]", inputs: { popover: "popover", isOpen: "isOpen", placement: "placement", offset: "offset", padding: "padding" }, host: { properties: { "style.position": "\"fixed\"", "style.visibility": "isPositioned ? \"visible\" : \"hidden\"" } }, usesOnChanges: true, ngImport: i0 });
1039
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.10", type: PopoverDirective, isStandalone: true, selector: "[documentEnginePopover], [popover]", inputs: { documentEnginePopover: "documentEnginePopover", popover: "popover", isOpen: "isOpen", placement: "placement", offset: "offset", padding: "padding" }, host: { properties: { "style.position": "\"fixed\"", "style.visibility": "isPositioned ? \"visible\" : \"hidden\"" } }, usesOnChanges: true, ngImport: i0 });
1014
1040
  }
1015
1041
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: PopoverDirective, decorators: [{
1016
1042
  type: Directive,
1017
1043
  args: [{
1018
- selector: '[popover]',
1044
+ // `documentEnginePopover` is the supported selector. `[popover]` is kept as a
1045
+ // deprecated alias for consumers on <= 0.1.4; it collides with the platform's
1046
+ // native popover attribute and will be removed in the next major.
1047
+ selector: '[documentEnginePopover], [popover]',
1019
1048
  standalone: true,
1020
1049
  host: {
1021
1050
  '[style.position]': '"fixed"',
1022
1051
  '[style.visibility]': 'isPositioned ? "visible" : "hidden"',
1023
1052
  },
1024
1053
  }]
1025
- }], propDecorators: { popover: [{
1054
+ }], propDecorators: { documentEnginePopover: [{
1055
+ type: Input
1056
+ }], popover: [{
1026
1057
  type: Input
1027
1058
  }], isOpen: [{
1028
1059
  type: Input
@@ -1464,6 +1495,7 @@ class ColorPickerComponent {
1464
1495
  type="button"
1465
1496
  class="swatch"
1466
1497
  [style.background-color]="color.value"
1498
+ [attr.data-color]="color.value"
1467
1499
  (click)="colorSelected.emit(color)"
1468
1500
  [attr.title]="color.name"
1469
1501
  >
@@ -1543,6 +1575,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
1543
1575
  type="button"
1544
1576
  class="swatch"
1545
1577
  [style.background-color]="color.value"
1578
+ [attr.data-color]="color.value"
1546
1579
  (click)="colorSelected.emit(color)"
1547
1580
  [attr.title]="color.name"
1548
1581
  >
@@ -1728,7 +1761,7 @@ class ColorPickerViewComponent {
1728
1761
  <button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
1729
1762
  </div>
1730
1763
  </div>
1731
- `, isInline: true, styles: [".color-picker-view{display:flex;flex-direction:column}.color-picker-view__title{padding:.5rem 1rem;border-bottom:1px solid var(--border, #e5e7eb);font-weight:500}.color-picker-view__actions{display:flex;gap:.5rem;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, #e5e7eb)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1764
+ `, isInline: true, styles: [".color-picker-view{display:flex;flex-direction:column}.color-picker-view__title{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500}.color-picker-view__actions{display:flex;gap:.5rem;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1732
1765
  }
1733
1766
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ColorPickerViewComponent, decorators: [{
1734
1767
  type: Component,
@@ -1752,7 +1785,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
1752
1785
  <button type="button" documentEngineButton variant="secondary" (click)="close?.()">Close</button>
1753
1786
  </div>
1754
1787
  </div>
1755
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".color-picker-view{display:flex;flex-direction:column}.color-picker-view__title{padding:.5rem 1rem;border-bottom:1px solid var(--border, #e5e7eb);font-weight:500}.color-picker-view__actions{display:flex;gap:.5rem;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, #e5e7eb)}\n"] }]
1788
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".color-picker-view{display:flex;flex-direction:column}.color-picker-view__title{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500}.color-picker-view__actions{display:flex;gap:.5rem;justify-content:space-between;padding:.75rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}\n"] }]
1756
1789
  }] });
1757
1790
 
1758
1791
  const ColorBubbleConfig = {
@@ -2515,7 +2548,7 @@ class LinkEditViewComponent {
2515
2548
  </div>
2516
2549
  </form>
2517
2550
  </div>
2518
- `, isInline: true, styles: [".link-edit-view{display:flex;flex-direction:column;min-width:320px}.link-edit-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-edit-view__content{display:flex;flex-direction:column;gap:1.5rem;padding:1rem .5rem}.link-edit-view__shortcuts{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:space-between;border-top:1px solid #e5e7eb;padding:1rem .5rem}.link-edit-view__shortcuts-label{color:#6b7280;font-size:.875rem}.link-edit-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid #e5e7eb}.link-edit-view__field{display:flex;flex-direction:column;gap:.5rem;width:100%}.chip-set{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.chip{display:inline-flex;align-items:center;padding:.25rem .75rem;background-color:#f3f4f6;border:1px solid #e5e7eb;border-radius:9999px;font-size:.875rem;color:#374151;cursor:pointer;transition:background-color .2s,border-color .2s}.chip:hover{background-color:#e5e7eb;border-color:#d1d5db}.chip:active{background-color:#d1d5db}\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 });
2551
+ `, isInline: true, styles: [".link-edit-view{display:flex;flex-direction:column;min-width:320px}.link-edit-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-edit-view__content{display:flex;flex-direction:column;gap:1.5rem;padding:1rem .5rem}.link-edit-view__shortcuts{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:space-between;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));padding:1rem .5rem}.link-edit-view__shortcuts-label{color:var(--muted-foreground, hsl(215.4, 16.3%, 46.9%));font-size:.875rem}.link-edit-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}.link-edit-view__field{display:flex;flex-direction:column;gap:.5rem;width:100%}.chip-set{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.chip{display:inline-flex;align-items:center;padding:.25rem .75rem;background-color:var(--muted, hsl(210, 40%, 96.1%));border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));border-radius:9999px;font-size:.875rem;color:var(--foreground, hsl(222.2, 84%, 4.9%));cursor:pointer;transition:background-color .2s,border-color .2s}.chip:hover{background-color:var(--accent, hsl(210, 40%, 96.1%));color:var(--accent-foreground, hsl(222.2, 47.4%, 11.2%));border-color:var(--ring, hsl(222.2, 84%, 4.9%))}.chip:active{background-color:var(--secondary, hsl(210, 40%, 96.1%));color:var(--secondary-foreground, hsl(222.2, 47.4%, 11.2%))}\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 });
2519
2552
  }
2520
2553
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: LinkEditViewComponent, decorators: [{
2521
2554
  type: Component,
@@ -2585,7 +2618,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2585
2618
  </div>
2586
2619
  </form>
2587
2620
  </div>
2588
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".link-edit-view{display:flex;flex-direction:column;min-width:320px}.link-edit-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-edit-view__content{display:flex;flex-direction:column;gap:1.5rem;padding:1rem .5rem}.link-edit-view__shortcuts{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:space-between;border-top:1px solid #e5e7eb;padding:1rem .5rem}.link-edit-view__shortcuts-label{color:#6b7280;font-size:.875rem}.link-edit-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid #e5e7eb}.link-edit-view__field{display:flex;flex-direction:column;gap:.5rem;width:100%}.chip-set{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.chip{display:inline-flex;align-items:center;padding:.25rem .75rem;background-color:#f3f4f6;border:1px solid #e5e7eb;border-radius:9999px;font-size:.875rem;color:#374151;cursor:pointer;transition:background-color .2s,border-color .2s}.chip:hover{background-color:#e5e7eb;border-color:#d1d5db}.chip:active{background-color:#d1d5db}\n"] }]
2621
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".link-edit-view{display:flex;flex-direction:column;min-width:320px}.link-edit-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-edit-view__content{display:flex;flex-direction:column;gap:1.5rem;padding:1rem .5rem}.link-edit-view__shortcuts{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;justify-content:space-between;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));padding:1rem .5rem}.link-edit-view__shortcuts-label{color:var(--muted-foreground, hsl(215.4, 16.3%, 46.9%));font-size:.875rem}.link-edit-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}.link-edit-view__field{display:flex;flex-direction:column;gap:.5rem;width:100%}.chip-set{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}.chip{display:inline-flex;align-items:center;padding:.25rem .75rem;background-color:var(--muted, hsl(210, 40%, 96.1%));border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));border-radius:9999px;font-size:.875rem;color:var(--foreground, hsl(222.2, 84%, 4.9%));cursor:pointer;transition:background-color .2s,border-color .2s}.chip:hover{background-color:var(--accent, hsl(210, 40%, 96.1%));color:var(--accent-foreground, hsl(222.2, 47.4%, 11.2%));border-color:var(--ring, hsl(222.2, 84%, 4.9%))}.chip:active{background-color:var(--secondary, hsl(210, 40%, 96.1%));color:var(--secondary-foreground, hsl(222.2, 47.4%, 11.2%))}\n"] }]
2589
2622
  }] });
2590
2623
 
2591
2624
  /**
@@ -2656,7 +2689,7 @@ class LinkMainViewComponent {
2656
2689
  </button>
2657
2690
  </div>
2658
2691
  </div>
2659
- `, isInline: true, styles: [".link-main-view{display:flex;flex-direction:column}.link-main-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-main-view__content{display:flex;gap:.5rem;padding:.75rem;align-items:center}.link-main-view__url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;font-size:.875rem;color:#3b82f6;text-decoration:none;transition:color .2s}.link-main-view__url:hover{color:#2563eb;text-decoration:underline}.link-main-view__button{display:inline-flex;align-items:center;gap:.25rem;white-space:nowrap;min-width:fit-content;width:auto}.link-main-view__button document-engine-icon{width:1rem;height:1rem;display:inline-flex;align-items:center;justify-content:center}.link-main-view__button span{font-size:.875rem}.link-main-view__button-icon{display:inline-flex;align-items:center;justify-content:center}.link-main-view__button-icon document-engine-icon{width:1rem;height:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2692
+ `, isInline: true, styles: [".link-main-view{display:flex;flex-direction:column}.link-main-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-main-view__content{display:flex;gap:.5rem;padding:.75rem;align-items:center}.link-main-view__url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;font-size:.875rem;color:var(--primary, hsl(222.2, 47.4%, 11.2%));text-decoration:none;transition:color .2s}.link-main-view__url:hover{text-decoration:underline}.link-main-view__button{display:inline-flex;align-items:center;gap:.25rem;white-space:nowrap;min-width:fit-content;width:auto}.link-main-view__button document-engine-icon{width:1rem;height:1rem;display:inline-flex;align-items:center;justify-content:center}.link-main-view__button span{font-size:.875rem}.link-main-view__button-icon{display:inline-flex;align-items:center;justify-content:center}.link-main-view__button-icon document-engine-icon{width:1rem;height:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2660
2693
  }
2661
2694
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: LinkMainViewComponent, decorators: [{
2662
2695
  type: Component,
@@ -2699,9 +2732,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2699
2732
  </button>
2700
2733
  </div>
2701
2734
  </div>
2702
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".link-main-view{display:flex;flex-direction:column}.link-main-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-main-view__content{display:flex;gap:.5rem;padding:.75rem;align-items:center}.link-main-view__url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;font-size:.875rem;color:#3b82f6;text-decoration:none;transition:color .2s}.link-main-view__url:hover{color:#2563eb;text-decoration:underline}.link-main-view__button{display:inline-flex;align-items:center;gap:.25rem;white-space:nowrap;min-width:fit-content;width:auto}.link-main-view__button document-engine-icon{width:1rem;height:1rem;display:inline-flex;align-items:center;justify-content:center}.link-main-view__button span{font-size:.875rem}.link-main-view__button-icon{display:inline-flex;align-items:center;justify-content:center}.link-main-view__button-icon document-engine-icon{width:1rem;height:1rem}\n"] }]
2735
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".link-main-view{display:flex;flex-direction:column}.link-main-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-main-view__content{display:flex;gap:.5rem;padding:.75rem;align-items:center}.link-main-view__url{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px;font-size:.875rem;color:var(--primary, hsl(222.2, 47.4%, 11.2%));text-decoration:none;transition:color .2s}.link-main-view__url:hover{text-decoration:underline}.link-main-view__button{display:inline-flex;align-items:center;gap:.25rem;white-space:nowrap;min-width:fit-content;width:auto}.link-main-view__button document-engine-icon{width:1rem;height:1rem;display:inline-flex;align-items:center;justify-content:center}.link-main-view__button span{font-size:.875rem}.link-main-view__button-icon{display:inline-flex;align-items:center;justify-content:center}.link-main-view__button-icon document-engine-icon{width:1rem;height:1rem}\n"] }]
2703
2736
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
2704
2737
 
2738
+ /**
2739
+ * Checkbox component
2740
+ * Works with ngModel and reactive forms
2741
+ *
2742
+ * Usage:
2743
+ * <document-engine-checkbox [(ngModel)]="checked">Label text</document-engine-checkbox>
2744
+ */
2745
+ class CheckboxComponent {
2746
+ disabled = false;
2747
+ checked = false;
2748
+ checkedChange = new EventEmitter();
2749
+ hostClass = true;
2750
+ cdr = inject(ChangeDetectorRef);
2751
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2752
+ onChange = () => { };
2753
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2754
+ onTouched = () => { };
2755
+ onCheckboxChange(event) {
2756
+ if (this.disabled)
2757
+ return;
2758
+ const target = event.target;
2759
+ this.checked = target.checked;
2760
+ this.checkedChange.emit(this.checked);
2761
+ this.onChange(this.checked);
2762
+ this.onTouched();
2763
+ }
2764
+ // ControlValueAccessor implementation.
2765
+ //
2766
+ // The forms package calls these from outside any template binding, so on an
2767
+ // OnPush component they must mark the view dirty explicitly. Without it a
2768
+ // programmatic `setValue()` — including a bubble-menu view being re-activated
2769
+ // with different attributes on a reused instance — updates the model while the
2770
+ // rendered tick keeps showing the previous state.
2771
+ writeValue(value) {
2772
+ this.checked = value;
2773
+ this.cdr.markForCheck();
2774
+ }
2775
+ registerOnChange(fn) {
2776
+ this.onChange = fn;
2777
+ }
2778
+ registerOnTouched(fn) {
2779
+ this.onTouched = fn;
2780
+ }
2781
+ setDisabledState(isDisabled) {
2782
+ this.disabled = isDisabled;
2783
+ this.cdr.markForCheck();
2784
+ }
2785
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2786
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: CheckboxComponent, isStandalone: true, selector: "document-engine-checkbox", inputs: { disabled: "disabled", checked: "checked" }, outputs: { checkedChange: "checkedChange" }, host: { properties: { "class.document-engine-checkbox-container": "this.hostClass" } }, providers: [
2787
+ {
2788
+ provide: NG_VALUE_ACCESSOR,
2789
+ useExisting: forwardRef(() => CheckboxComponent),
2790
+ multi: true,
2791
+ },
2792
+ ], ngImport: i0, template: `
2793
+ <label class="document-engine-checkbox">
2794
+ <input
2795
+ type="checkbox"
2796
+ class="document-engine-checkbox__input"
2797
+ [checked]="checked"
2798
+ [disabled]="disabled"
2799
+ (change)="onCheckboxChange($event)"
2800
+ />
2801
+ <span class="document-engine-checkbox__box">
2802
+ <document-engine-icon
2803
+ *ngIf="checked"
2804
+ name="check"
2805
+ class="document-engine-checkbox__icon"
2806
+ ></document-engine-icon>
2807
+ </span>
2808
+ <span class="document-engine-checkbox__label">
2809
+ <ng-content></ng-content>
2810
+ </span>
2811
+ </label>
2812
+ `, isInline: true, styles: [".document-engine-checkbox{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.document-engine-checkbox__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.document-engine-checkbox__box{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1px solid var(--input);border-radius:.25rem;background-color:transparent;transition:background-color .2s,border-color .2s}.document-engine-checkbox__input:checked~.document-engine-checkbox__box{background-color:var(--primary);border-color:var(--primary)}.document-engine-checkbox__input:focus-visible~.document-engine-checkbox__box{outline:2px solid var(--ring);outline-offset:2px}.document-engine-checkbox__input:disabled~.document-engine-checkbox__box{cursor:not-allowed;opacity:.5}.document-engine-checkbox__icon{width:.75rem;height:.75rem;color:var(--primary-foreground)}.document-engine-checkbox__label{font-size:.875rem;line-height:1;font-weight:500}.document-engine-checkbox__input:disabled~.document-engine-checkbox__label{cursor:not-allowed;opacity:.5}.document-engine-checkbox-container{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2813
+ }
2814
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CheckboxComponent, decorators: [{
2815
+ type: Component,
2816
+ args: [{ selector: 'document-engine-checkbox', standalone: true, imports: [CommonModule, IconComponent], providers: [
2817
+ {
2818
+ provide: NG_VALUE_ACCESSOR,
2819
+ useExisting: forwardRef(() => CheckboxComponent),
2820
+ multi: true,
2821
+ },
2822
+ ], template: `
2823
+ <label class="document-engine-checkbox">
2824
+ <input
2825
+ type="checkbox"
2826
+ class="document-engine-checkbox__input"
2827
+ [checked]="checked"
2828
+ [disabled]="disabled"
2829
+ (change)="onCheckboxChange($event)"
2830
+ />
2831
+ <span class="document-engine-checkbox__box">
2832
+ <document-engine-icon
2833
+ *ngIf="checked"
2834
+ name="check"
2835
+ class="document-engine-checkbox__icon"
2836
+ ></document-engine-icon>
2837
+ </span>
2838
+ <span class="document-engine-checkbox__label">
2839
+ <ng-content></ng-content>
2840
+ </span>
2841
+ </label>
2842
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".document-engine-checkbox{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.document-engine-checkbox__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.document-engine-checkbox__box{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1px solid var(--input);border-radius:.25rem;background-color:transparent;transition:background-color .2s,border-color .2s}.document-engine-checkbox__input:checked~.document-engine-checkbox__box{background-color:var(--primary);border-color:var(--primary)}.document-engine-checkbox__input:focus-visible~.document-engine-checkbox__box{outline:2px solid var(--ring);outline-offset:2px}.document-engine-checkbox__input:disabled~.document-engine-checkbox__box{cursor:not-allowed;opacity:.5}.document-engine-checkbox__icon{width:.75rem;height:.75rem;color:var(--primary-foreground)}.document-engine-checkbox__label{font-size:.875rem;line-height:1;font-weight:500}.document-engine-checkbox__input:disabled~.document-engine-checkbox__label{cursor:not-allowed;opacity:.5}.document-engine-checkbox-container{display:inline-block}\n"] }]
2843
+ }], propDecorators: { disabled: [{
2844
+ type: Input
2845
+ }], checked: [{
2846
+ type: Input
2847
+ }], checkedChange: [{
2848
+ type: Output
2849
+ }], hostClass: [{
2850
+ type: HostBinding,
2851
+ args: ['class.document-engine-checkbox-container']
2852
+ }] } });
2853
+
2705
2854
  /**
2706
2855
  * Properties view for link bubble menu
2707
2856
  * Shows link properties like target and rel attributes
@@ -2765,27 +2914,30 @@ class LinkPropertiesViewComponent {
2765
2914
  <!-- Content -->
2766
2915
  <div class="link-properties-view__content">
2767
2916
  <!-- Open in new tab -->
2768
- <label class="checkbox-label">
2769
- <input type="checkbox" class="checkbox-input" [formControl]="newTabControl" />
2770
- <span class="checkbox-text">Open in new tab</span>
2771
- </label>
2917
+ <div class="link-properties-view__option">
2918
+ <document-engine-checkbox [formControl]="newTabControl">Open in new tab</document-engine-checkbox>
2919
+ </div>
2772
2920
 
2773
2921
  <!-- No follow -->
2774
- <label class="checkbox-label">
2775
- <input type="checkbox" class="checkbox-input" [formControl]="noFollowControl" />
2776
- <span class="checkbox-text"> Add <code class="inline-code">rel="nofollow"</code> </span>
2777
- </label>
2922
+ <div class="link-properties-view__option">
2923
+ <document-engine-checkbox [formControl]="noFollowControl">
2924
+ Add <code class="inline-code">rel="nofollow"</code>
2925
+ </document-engine-checkbox>
2926
+ </div>
2778
2927
 
2779
2928
  <!-- No opener -->
2780
- <label class="checkbox-label">
2781
- <input type="checkbox" class="checkbox-input" [formControl]="noOpenerControl" />
2782
- <span class="checkbox-text"> Add <code class="inline-code">rel="noopener"</code> </span>
2783
- </label>
2784
-
2785
- <label class="checkbox-label">
2786
- <input type="checkbox" class="checkbox-input" [formControl]="noReferrerControl" />
2787
- <span class="checkbox-text"> Add <code class="inline-code">rel="noreferrer"</code> </span>
2788
- </label>
2929
+ <div class="link-properties-view__option">
2930
+ <document-engine-checkbox [formControl]="noOpenerControl">
2931
+ Add <code class="inline-code">rel="noopener"</code>
2932
+ </document-engine-checkbox>
2933
+ </div>
2934
+
2935
+ <!-- No referrer -->
2936
+ <div class="link-properties-view__option">
2937
+ <document-engine-checkbox [formControl]="noReferrerControl">
2938
+ Add <code class="inline-code">rel="noreferrer"</code>
2939
+ </document-engine-checkbox>
2940
+ </div>
2789
2941
  </div>
2790
2942
 
2791
2943
  <!-- Actions -->
@@ -2795,11 +2947,11 @@ class LinkPropertiesViewComponent {
2795
2947
  <button type="submit" documentEngineButton variant="default" (click)="saveProperties()">Save</button>
2796
2948
  </div>
2797
2949
  </div>
2798
- `, isInline: true, styles: [".link-properties-view{display:flex;flex-direction:column;min-width:280px}.link-properties-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-properties-view__content{display:flex;flex-direction:column;gap:.5rem;padding:1rem .5rem}.link-properties-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid #e5e7eb}.checkbox-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none;padding:.25rem}.checkbox-input{width:1rem;height:1rem;cursor:pointer;accent-color:#3b82f6}.checkbox-text{font-size:.875rem;color:#374151}.inline-code{padding:.125rem .25rem;border-radius:.25rem;background-color:#0000000d;font-family:Courier New,monospace;font-size:.813rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2950
+ `, isInline: true, styles: ["@charset \"UTF-8\";.link-properties-view{display:flex;flex-direction:column;min-width:280px}.link-properties-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-properties-view__content{display:flex;flex-direction:column;gap:.5rem;padding:1rem .5rem}.link-properties-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}.link-properties-view__option{display:flex;align-items:center;padding:.25rem}.inline-code{padding:.125rem .25rem;border-radius:.25rem;background-color:var(--muted, hsl(210, 40%, 96.1%));color:var(--muted-foreground, hsl(215.4, 16.3%, 46.9%));font-family:Courier New,monospace;font-size:.813rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: ButtonDirective, selector: "button[documentEngineButton]", inputs: ["variant", "size"] }, { kind: "component", type: CheckboxComponent, selector: "document-engine-checkbox", inputs: ["disabled", "checked"], outputs: ["checkedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2799
2951
  }
2800
2952
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: LinkPropertiesViewComponent, decorators: [{
2801
2953
  type: Component,
2802
- args: [{ selector: 'document-engine-link-properties-view', standalone: true, imports: [CommonModule, ReactiveFormsModule, ButtonDirective], template: `
2954
+ args: [{ selector: 'document-engine-link-properties-view', standalone: true, imports: [CommonModule, ReactiveFormsModule, ButtonDirective, CheckboxComponent], template: `
2803
2955
  <div class="link-properties-view">
2804
2956
  <!-- Title -->
2805
2957
  <!-- No referrer -->
@@ -2808,27 +2960,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2808
2960
  <!-- Content -->
2809
2961
  <div class="link-properties-view__content">
2810
2962
  <!-- Open in new tab -->
2811
- <label class="checkbox-label">
2812
- <input type="checkbox" class="checkbox-input" [formControl]="newTabControl" />
2813
- <span class="checkbox-text">Open in new tab</span>
2814
- </label>
2963
+ <div class="link-properties-view__option">
2964
+ <document-engine-checkbox [formControl]="newTabControl">Open in new tab</document-engine-checkbox>
2965
+ </div>
2815
2966
 
2816
2967
  <!-- No follow -->
2817
- <label class="checkbox-label">
2818
- <input type="checkbox" class="checkbox-input" [formControl]="noFollowControl" />
2819
- <span class="checkbox-text"> Add <code class="inline-code">rel="nofollow"</code> </span>
2820
- </label>
2968
+ <div class="link-properties-view__option">
2969
+ <document-engine-checkbox [formControl]="noFollowControl">
2970
+ Add <code class="inline-code">rel="nofollow"</code>
2971
+ </document-engine-checkbox>
2972
+ </div>
2821
2973
 
2822
2974
  <!-- No opener -->
2823
- <label class="checkbox-label">
2824
- <input type="checkbox" class="checkbox-input" [formControl]="noOpenerControl" />
2825
- <span class="checkbox-text"> Add <code class="inline-code">rel="noopener"</code> </span>
2826
- </label>
2827
-
2828
- <label class="checkbox-label">
2829
- <input type="checkbox" class="checkbox-input" [formControl]="noReferrerControl" />
2830
- <span class="checkbox-text"> Add <code class="inline-code">rel="noreferrer"</code> </span>
2831
- </label>
2975
+ <div class="link-properties-view__option">
2976
+ <document-engine-checkbox [formControl]="noOpenerControl">
2977
+ Add <code class="inline-code">rel="noopener"</code>
2978
+ </document-engine-checkbox>
2979
+ </div>
2980
+
2981
+ <!-- No referrer -->
2982
+ <div class="link-properties-view__option">
2983
+ <document-engine-checkbox [formControl]="noReferrerControl">
2984
+ Add <code class="inline-code">rel="noreferrer"</code>
2985
+ </document-engine-checkbox>
2986
+ </div>
2832
2987
  </div>
2833
2988
 
2834
2989
  <!-- Actions -->
@@ -2838,7 +2993,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
2838
2993
  <button type="submit" documentEngineButton variant="default" (click)="saveProperties()">Save</button>
2839
2994
  </div>
2840
2995
  </div>
2841
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".link-properties-view{display:flex;flex-direction:column;min-width:280px}.link-properties-view__header{padding:.5rem 1rem;border-bottom:1px solid #e5e7eb;font-weight:500;font-size:.875rem}.link-properties-view__content{display:flex;flex-direction:column;gap:.5rem;padding:1rem .5rem}.link-properties-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid #e5e7eb}.checkbox-label{display:flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none;padding:.25rem}.checkbox-input{width:1rem;height:1rem;cursor:pointer;accent-color:#3b82f6}.checkbox-text{font-size:.875rem;color:#374151}.inline-code{padding:.125rem .25rem;border-radius:.25rem;background-color:#0000000d;font-family:Courier New,monospace;font-size:.813rem}\n"] }]
2996
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";.link-properties-view{display:flex;flex-direction:column;min-width:280px}.link-properties-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));font-weight:500;font-size:.875rem}.link-properties-view__content{display:flex;flex-direction:column;gap:.5rem;padding:1rem .5rem}.link-properties-view__actions{display:flex;gap:.5rem;justify-content:flex-end;padding:.5rem;border-top:1px solid var(--border, hsl(214.3, 31.8%, 91.4%))}.link-properties-view__option{display:flex;align-items:center;padding:.25rem}.inline-code{padding:.125rem .25rem;border-radius:.25rem;background-color:var(--muted, hsl(210, 40%, 96.1%));color:var(--muted-foreground, hsl(215.4, 16.3%, 46.9%));font-family:Courier New,monospace;font-size:.813rem}\n"] }]
2842
2997
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
2843
2998
 
2844
2999
  const LinkBubbleConfig = {
@@ -3707,6 +3862,7 @@ class TableCellStyleViewComponent {
3707
3862
  <div class="table-cell-style-view__field-label">Width</div>
3708
3863
  <input
3709
3864
  documentEngineInput
3865
+ data-testid="border-width"
3710
3866
  [(ngModel)]="borderWidth"
3711
3867
  (ngModelChange)="onBorderWidthChange()"
3712
3868
  placeholder="1px"
@@ -3716,7 +3872,7 @@ class TableCellStyleViewComponent {
3716
3872
  <div class="table-cell-style-view__field">
3717
3873
  <div class="table-cell-style-view__field-label">Style</div>
3718
3874
 
3719
- <document-engine-select [(value)]="borderStyle" variant="outline">
3875
+ <document-engine-select data-testid="border-style" [(value)]="borderStyle" variant="outline">
3720
3876
  <button documentEngineSelectOption value="solid">Solid</button>
3721
3877
  <button documentEngineSelectOption value="dashed">Dashed</button>
3722
3878
  <button documentEngineSelectOption value="dotted">Dotted</button>
@@ -3737,6 +3893,7 @@ class TableCellStyleViewComponent {
3737
3893
  size="icon"
3738
3894
  variant="ghost"
3739
3895
  class="table-cell-style-view__color-clear"
3896
+ data-testid="clear-border-color"
3740
3897
  (click)="borderColor = null"
3741
3898
  >
3742
3899
  <document-engine-icon name="close"></document-engine-icon>
@@ -3757,13 +3914,14 @@ class TableCellStyleViewComponent {
3757
3914
  <!-- Color picker for border -->
3758
3915
  <div
3759
3916
  *ngIf="showColorPicker === 'border'"
3760
- [popover]="borderColorTrigger"
3917
+ [documentEnginePopover]="borderColorTrigger"
3761
3918
  [isOpen]="showColorPicker === 'border'"
3762
3919
  class="table-cell-style-view__color-picker-dropdown"
3763
3920
  (click)="$event.stopPropagation()"
3764
3921
  >
3765
3922
  <document-engine-color-picker
3766
3923
  [colorPalette]="colorPalette"
3924
+ data-testid="border-color"
3767
3925
  [activeColor]="borderColorObj"
3768
3926
  (colorSelected)="onBorderColorSelected($event)"
3769
3927
  (colorRemoved)="onBorderColorRemoved()"
@@ -3788,6 +3946,7 @@ class TableCellStyleViewComponent {
3788
3946
  size="icon"
3789
3947
  variant="ghost"
3790
3948
  class="table-cell-style-view__color-clear"
3949
+ data-testid="clear-background-color"
3791
3950
  (click)="backgroundColor = null"
3792
3951
  >
3793
3952
  <document-engine-icon name="close"></document-engine-icon>
@@ -3808,13 +3967,14 @@ class TableCellStyleViewComponent {
3808
3967
  <!-- Color picker for background -->
3809
3968
  <div
3810
3969
  *ngIf="showColorPicker === 'bg'"
3811
- [popover]="bgColorTrigger"
3970
+ [documentEnginePopover]="bgColorTrigger"
3812
3971
  [isOpen]="showColorPicker === 'bg'"
3813
3972
  class="table-cell-style-view__color-picker-dropdown"
3814
3973
  (click)="$event.stopPropagation()"
3815
3974
  >
3816
3975
  <document-engine-color-picker
3817
3976
  [colorPalette]="colorPalette"
3977
+ data-testid="background-color"
3818
3978
  [activeColor]="backgroundColorObj"
3819
3979
  (colorSelected)="onBackgroundColorSelected($event)"
3820
3980
  (colorRemoved)="onBackgroundColorRemoved()"
@@ -3861,11 +4021,11 @@ class TableCellStyleViewComponent {
3861
4021
 
3862
4022
  <!-- Actions -->
3863
4023
  <div class="table-cell-style-view__actions">
3864
- <button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
3865
- <button documentEngineButton variant="default" (click)="onSave()">Save</button>
4024
+ <button documentEngineButton data-testid="cancel" variant="ghost" (click)="cancel()">Cancel</button>
4025
+ <button documentEngineButton data-testid="save" variant="default" (click)="onSave()">Save</button>
3866
4026
  </div>
3867
4027
  </div>
3868
- `, isInline: true, styles: [".table-cell-style-view{display:flex;flex-direction:column;width:32.5rem}.table-cell-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-cell-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-cell-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-cell-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-cell-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-cell-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-cell-style-view__field--color{grid-column:span 4;position:relative}.table-cell-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-cell-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-cell-style-view__color-input input{flex:1;padding-right:3rem}.table-cell-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-cell-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-cell-style-view__color-swatch:hover{transform:scale(1.1)}.table-cell-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-cell-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-cell-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }, { kind: "component", type: ToggleGroupComponent, selector: "document-engine-toggle-group", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ToggleOptionDirective, selector: "button[documentEngineToggleOption]", inputs: ["value", "disabled"] }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["popover", "isOpen", "placement", "offset", "padding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4028
+ `, isInline: true, styles: ["@charset \"UTF-8\";.table-cell-style-view{display:flex;flex-direction:column;width:32.5rem;max-inline-size:calc(100vw - 2rem)}.table-cell-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-cell-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-cell-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-cell-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-cell-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-cell-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-cell-style-view__field--color{grid-column:span 4;position:relative}.table-cell-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-cell-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-cell-style-view__color-input input{flex:1;padding-right:3rem}.table-cell-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-cell-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-cell-style-view__color-swatch:hover{transform:scale(1.1)}.table-cell-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-cell-style-view__color-picker-dropdown{box-sizing:border-box;z-index:50;border-radius:.5rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));padding:.5rem}.table-cell-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }, { kind: "component", type: ToggleGroupComponent, selector: "document-engine-toggle-group", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ToggleOptionDirective, selector: "button[documentEngineToggleOption]", inputs: ["value", "disabled"] }, { kind: "directive", type: PopoverDirective, selector: "[documentEnginePopover], [popover]", inputs: ["documentEnginePopover", "popover", "isOpen", "placement", "offset", "padding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3869
4029
  }
3870
4030
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableCellStyleViewComponent, decorators: [{
3871
4031
  type: Component,
@@ -3895,6 +4055,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3895
4055
  <div class="table-cell-style-view__field-label">Width</div>
3896
4056
  <input
3897
4057
  documentEngineInput
4058
+ data-testid="border-width"
3898
4059
  [(ngModel)]="borderWidth"
3899
4060
  (ngModelChange)="onBorderWidthChange()"
3900
4061
  placeholder="1px"
@@ -3904,7 +4065,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3904
4065
  <div class="table-cell-style-view__field">
3905
4066
  <div class="table-cell-style-view__field-label">Style</div>
3906
4067
 
3907
- <document-engine-select [(value)]="borderStyle" variant="outline">
4068
+ <document-engine-select data-testid="border-style" [(value)]="borderStyle" variant="outline">
3908
4069
  <button documentEngineSelectOption value="solid">Solid</button>
3909
4070
  <button documentEngineSelectOption value="dashed">Dashed</button>
3910
4071
  <button documentEngineSelectOption value="dotted">Dotted</button>
@@ -3925,6 +4086,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3925
4086
  size="icon"
3926
4087
  variant="ghost"
3927
4088
  class="table-cell-style-view__color-clear"
4089
+ data-testid="clear-border-color"
3928
4090
  (click)="borderColor = null"
3929
4091
  >
3930
4092
  <document-engine-icon name="close"></document-engine-icon>
@@ -3945,13 +4107,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3945
4107
  <!-- Color picker for border -->
3946
4108
  <div
3947
4109
  *ngIf="showColorPicker === 'border'"
3948
- [popover]="borderColorTrigger"
4110
+ [documentEnginePopover]="borderColorTrigger"
3949
4111
  [isOpen]="showColorPicker === 'border'"
3950
4112
  class="table-cell-style-view__color-picker-dropdown"
3951
4113
  (click)="$event.stopPropagation()"
3952
4114
  >
3953
4115
  <document-engine-color-picker
3954
4116
  [colorPalette]="colorPalette"
4117
+ data-testid="border-color"
3955
4118
  [activeColor]="borderColorObj"
3956
4119
  (colorSelected)="onBorderColorSelected($event)"
3957
4120
  (colorRemoved)="onBorderColorRemoved()"
@@ -3976,6 +4139,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3976
4139
  size="icon"
3977
4140
  variant="ghost"
3978
4141
  class="table-cell-style-view__color-clear"
4142
+ data-testid="clear-background-color"
3979
4143
  (click)="backgroundColor = null"
3980
4144
  >
3981
4145
  <document-engine-icon name="close"></document-engine-icon>
@@ -3996,13 +4160,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3996
4160
  <!-- Color picker for background -->
3997
4161
  <div
3998
4162
  *ngIf="showColorPicker === 'bg'"
3999
- [popover]="bgColorTrigger"
4163
+ [documentEnginePopover]="bgColorTrigger"
4000
4164
  [isOpen]="showColorPicker === 'bg'"
4001
4165
  class="table-cell-style-view__color-picker-dropdown"
4002
4166
  (click)="$event.stopPropagation()"
4003
4167
  >
4004
4168
  <document-engine-color-picker
4005
4169
  [colorPalette]="colorPalette"
4170
+ data-testid="background-color"
4006
4171
  [activeColor]="backgroundColorObj"
4007
4172
  (colorSelected)="onBackgroundColorSelected($event)"
4008
4173
  (colorRemoved)="onBackgroundColorRemoved()"
@@ -4049,11 +4214,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4049
4214
 
4050
4215
  <!-- Actions -->
4051
4216
  <div class="table-cell-style-view__actions">
4052
- <button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
4053
- <button documentEngineButton variant="default" (click)="onSave()">Save</button>
4217
+ <button documentEngineButton data-testid="cancel" variant="ghost" (click)="cancel()">Cancel</button>
4218
+ <button documentEngineButton data-testid="save" variant="default" (click)="onSave()">Save</button>
4054
4219
  </div>
4055
4220
  </div>
4056
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".table-cell-style-view{display:flex;flex-direction:column;width:32.5rem}.table-cell-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-cell-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-cell-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-cell-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-cell-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-cell-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-cell-style-view__field--color{grid-column:span 4;position:relative}.table-cell-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-cell-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-cell-style-view__color-input input{flex:1;padding-right:3rem}.table-cell-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-cell-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-cell-style-view__color-swatch:hover{transform:scale(1.1)}.table-cell-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-cell-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-cell-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
4221
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";.table-cell-style-view{display:flex;flex-direction:column;width:32.5rem;max-inline-size:calc(100vw - 2rem)}.table-cell-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-cell-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-cell-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-cell-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-cell-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-cell-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-cell-style-view__field--color{grid-column:span 4;position:relative}.table-cell-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-cell-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-cell-style-view__color-input input{flex:1;padding-right:3rem}.table-cell-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-cell-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-cell-style-view__color-swatch:hover{transform:scale(1.1)}.table-cell-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-cell-style-view__color-picker-dropdown{box-sizing:border-box;z-index:50;border-radius:.5rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));padding:.5rem}.table-cell-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
4057
4222
  }], propDecorators: { borderColorTrigger: [{
4058
4223
  type: ViewChild,
4059
4224
  args: ['borderColorTrigger', { static: false, read: ElementRef }]
@@ -4062,113 +4227,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4062
4227
  args: ['bgColorTrigger', { static: false, read: ElementRef }]
4063
4228
  }] } });
4064
4229
 
4065
- /**
4066
- * Checkbox component
4067
- * Works with ngModel and reactive forms
4068
- *
4069
- * Usage:
4070
- * <document-engine-checkbox [(ngModel)]="checked">Label text</document-engine-checkbox>
4071
- */
4072
- class CheckboxComponent {
4073
- disabled = false;
4074
- checked = false;
4075
- checkedChange = new EventEmitter();
4076
- hostClass = true;
4077
- // eslint-disable-next-line @typescript-eslint/no-empty-function
4078
- onChange = () => { };
4079
- // eslint-disable-next-line @typescript-eslint/no-empty-function
4080
- onTouched = () => { };
4081
- onCheckboxChange(event) {
4082
- if (this.disabled)
4083
- return;
4084
- const target = event.target;
4085
- this.checked = target.checked;
4086
- this.checkedChange.emit(this.checked);
4087
- this.onChange(this.checked);
4088
- this.onTouched();
4089
- }
4090
- // ControlValueAccessor implementation
4091
- writeValue(value) {
4092
- this.checked = value;
4093
- }
4094
- registerOnChange(fn) {
4095
- this.onChange = fn;
4096
- }
4097
- registerOnTouched(fn) {
4098
- this.onTouched = fn;
4099
- }
4100
- setDisabledState(isDisabled) {
4101
- this.disabled = isDisabled;
4102
- }
4103
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CheckboxComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4104
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: CheckboxComponent, isStandalone: true, selector: "document-engine-checkbox", inputs: { disabled: "disabled", checked: "checked" }, outputs: { checkedChange: "checkedChange" }, host: { properties: { "class.document-engine-checkbox-container": "this.hostClass" } }, providers: [
4105
- {
4106
- provide: NG_VALUE_ACCESSOR,
4107
- useExisting: forwardRef(() => CheckboxComponent),
4108
- multi: true,
4109
- },
4110
- ], ngImport: i0, template: `
4111
- <label class="document-engine-checkbox">
4112
- <input
4113
- type="checkbox"
4114
- class="document-engine-checkbox__input"
4115
- [checked]="checked"
4116
- [disabled]="disabled"
4117
- (change)="onCheckboxChange($event)"
4118
- />
4119
- <span class="document-engine-checkbox__box">
4120
- <document-engine-icon
4121
- *ngIf="checked"
4122
- name="check"
4123
- class="document-engine-checkbox__icon"
4124
- ></document-engine-icon>
4125
- </span>
4126
- <span class="document-engine-checkbox__label">
4127
- <ng-content></ng-content>
4128
- </span>
4129
- </label>
4130
- `, isInline: true, styles: [".document-engine-checkbox{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.document-engine-checkbox__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.document-engine-checkbox__box{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1px solid var(--input);border-radius:.25rem;background-color:transparent;transition:background-color .2s,border-color .2s}.document-engine-checkbox__input:checked~.document-engine-checkbox__box{background-color:var(--primary);border-color:var(--primary)}.document-engine-checkbox__input:focus-visible~.document-engine-checkbox__box{outline:2px solid var(--ring);outline-offset:2px}.document-engine-checkbox__input:disabled~.document-engine-checkbox__box{cursor:not-allowed;opacity:.5}.document-engine-checkbox__icon{width:.75rem;height:.75rem;color:var(--primary-foreground)}.document-engine-checkbox__label{font-size:.875rem;line-height:1;font-weight:500}.document-engine-checkbox__input:disabled~.document-engine-checkbox__label{cursor:not-allowed;opacity:.5}.document-engine-checkbox-container{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconComponent, selector: "document-engine-icon", inputs: ["name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4131
- }
4132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: CheckboxComponent, decorators: [{
4133
- type: Component,
4134
- args: [{ selector: 'document-engine-checkbox', standalone: true, imports: [CommonModule, IconComponent], providers: [
4135
- {
4136
- provide: NG_VALUE_ACCESSOR,
4137
- useExisting: forwardRef(() => CheckboxComponent),
4138
- multi: true,
4139
- },
4140
- ], template: `
4141
- <label class="document-engine-checkbox">
4142
- <input
4143
- type="checkbox"
4144
- class="document-engine-checkbox__input"
4145
- [checked]="checked"
4146
- [disabled]="disabled"
4147
- (change)="onCheckboxChange($event)"
4148
- />
4149
- <span class="document-engine-checkbox__box">
4150
- <document-engine-icon
4151
- *ngIf="checked"
4152
- name="check"
4153
- class="document-engine-checkbox__icon"
4154
- ></document-engine-icon>
4155
- </span>
4156
- <span class="document-engine-checkbox__label">
4157
- <ng-content></ng-content>
4158
- </span>
4159
- </label>
4160
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".document-engine-checkbox{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.document-engine-checkbox__input{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.document-engine-checkbox__box{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;border:1px solid var(--input);border-radius:.25rem;background-color:transparent;transition:background-color .2s,border-color .2s}.document-engine-checkbox__input:checked~.document-engine-checkbox__box{background-color:var(--primary);border-color:var(--primary)}.document-engine-checkbox__input:focus-visible~.document-engine-checkbox__box{outline:2px solid var(--ring);outline-offset:2px}.document-engine-checkbox__input:disabled~.document-engine-checkbox__box{cursor:not-allowed;opacity:.5}.document-engine-checkbox__icon{width:.75rem;height:.75rem;color:var(--primary-foreground)}.document-engine-checkbox__label{font-size:.875rem;line-height:1;font-weight:500}.document-engine-checkbox__input:disabled~.document-engine-checkbox__label{cursor:not-allowed;opacity:.5}.document-engine-checkbox-container{display:inline-block}\n"] }]
4161
- }], propDecorators: { disabled: [{
4162
- type: Input
4163
- }], checked: [{
4164
- type: Input
4165
- }], checkedChange: [{
4166
- type: Output
4167
- }], hostClass: [{
4168
- type: HostBinding,
4169
- args: ['class.document-engine-checkbox-container']
4170
- }] } });
4171
-
4172
4230
  /**
4173
4231
  * Table creation view for bubble menu
4174
4232
  * Allows users to configure table dimensions and header row
@@ -4553,7 +4611,7 @@ class TableMainViewComponent {
4553
4611
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: TableMainViewComponent, isStandalone: true, selector: "document-engine-table-main-view", ngImport: i0, template: `
4554
4612
  <div class="table-main-view">
4555
4613
  <!-- Column actions -->
4556
- <document-engine-select [(value)]="colAction" (valueChange)="handleColAction($event)" labelMode="static">
4614
+ <document-engine-select data-testid="column-actions" [(value)]="colAction" (valueChange)="handleColAction($event)" labelMode="static">
4557
4615
  <document-engine-icon documentEngineSelectLabel name="table_column"></document-engine-icon>
4558
4616
 
4559
4617
  <button documentEngineSelectOption value="add-before">Insert column left</button>
@@ -4563,7 +4621,7 @@ class TableMainViewComponent {
4563
4621
  </document-engine-select>
4564
4622
 
4565
4623
  <!-- Row actions -->
4566
- <document-engine-select [(value)]="rowAction" (valueChange)="handleRowAction($event)" labelMode="static">
4624
+ <document-engine-select data-testid="row-actions" [(value)]="rowAction" (valueChange)="handleRowAction($event)" labelMode="static">
4567
4625
  <document-engine-icon documentEngineSelectLabel name="table_row"></document-engine-icon>
4568
4626
 
4569
4627
  <button documentEngineSelectOption value="add-before">Insert row above</button>
@@ -4576,8 +4634,12 @@ class TableMainViewComponent {
4576
4634
  <document-engine-select [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4577
4635
  <document-engine-icon documentEngineSelectLabel name="table_merge_cell"></document-engine-icon>
4578
4636
 
4579
- <button documentEngineSelectOption value="merge" [disabled]="!canMerge">Merge cells</button>
4580
- <button documentEngineSelectOption value="split" [disabled]="!canSplit">Split cell</button>
4637
+ <button documentEngineSelectOption data-testid="merge-cells" value="merge" [disabled]="!canMerge">
4638
+ Merge cells
4639
+ </button>
4640
+ <button documentEngineSelectOption data-testid="split-cell" value="split" [disabled]="!canSplit">
4641
+ Split cell
4642
+ </button>
4581
4643
  </document-engine-select>
4582
4644
 
4583
4645
  <!-- Table style -->
@@ -4586,6 +4648,7 @@ class TableMainViewComponent {
4586
4648
  size="icon"
4587
4649
  variant="ghost"
4588
4650
  title="Table Properties"
4651
+ data-testid="table-properties"
4589
4652
  (click)="navigateTo?.('table-style')"
4590
4653
  >
4591
4654
  <document-engine-icon name="table_property"></document-engine-icon>
@@ -4597,6 +4660,7 @@ class TableMainViewComponent {
4597
4660
  size="icon"
4598
4661
  variant="ghost"
4599
4662
  title="Cell Properties"
4663
+ data-testid="cell-properties"
4600
4664
  (click)="navigateTo?.('cell-style')"
4601
4665
  >
4602
4666
  <document-engine-icon name="table_cell_property"></document-engine-icon>
@@ -4609,7 +4673,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4609
4673
  args: [{ selector: 'document-engine-table-main-view', standalone: true, imports: [CommonModule, ButtonDirective, IconComponent, SelectComponent, SelectOptionDirective, SelectLabelDirective], template: `
4610
4674
  <div class="table-main-view">
4611
4675
  <!-- Column actions -->
4612
- <document-engine-select [(value)]="colAction" (valueChange)="handleColAction($event)" labelMode="static">
4676
+ <document-engine-select data-testid="column-actions" [(value)]="colAction" (valueChange)="handleColAction($event)" labelMode="static">
4613
4677
  <document-engine-icon documentEngineSelectLabel name="table_column"></document-engine-icon>
4614
4678
 
4615
4679
  <button documentEngineSelectOption value="add-before">Insert column left</button>
@@ -4619,7 +4683,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4619
4683
  </document-engine-select>
4620
4684
 
4621
4685
  <!-- Row actions -->
4622
- <document-engine-select [(value)]="rowAction" (valueChange)="handleRowAction($event)" labelMode="static">
4686
+ <document-engine-select data-testid="row-actions" [(value)]="rowAction" (valueChange)="handleRowAction($event)" labelMode="static">
4623
4687
  <document-engine-icon documentEngineSelectLabel name="table_row"></document-engine-icon>
4624
4688
 
4625
4689
  <button documentEngineSelectOption value="add-before">Insert row above</button>
@@ -4632,8 +4696,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4632
4696
  <document-engine-select [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4633
4697
  <document-engine-icon documentEngineSelectLabel name="table_merge_cell"></document-engine-icon>
4634
4698
 
4635
- <button documentEngineSelectOption value="merge" [disabled]="!canMerge">Merge cells</button>
4636
- <button documentEngineSelectOption value="split" [disabled]="!canSplit">Split cell</button>
4699
+ <button documentEngineSelectOption data-testid="merge-cells" value="merge" [disabled]="!canMerge">
4700
+ Merge cells
4701
+ </button>
4702
+ <button documentEngineSelectOption data-testid="split-cell" value="split" [disabled]="!canSplit">
4703
+ Split cell
4704
+ </button>
4637
4705
  </document-engine-select>
4638
4706
 
4639
4707
  <!-- Table style -->
@@ -4642,6 +4710,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4642
4710
  size="icon"
4643
4711
  variant="ghost"
4644
4712
  title="Table Properties"
4713
+ data-testid="table-properties"
4645
4714
  (click)="navigateTo?.('table-style')"
4646
4715
  >
4647
4716
  <document-engine-icon name="table_property"></document-engine-icon>
@@ -4653,6 +4722,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4653
4722
  size="icon"
4654
4723
  variant="ghost"
4655
4724
  title="Cell Properties"
4725
+ data-testid="cell-properties"
4656
4726
  (click)="navigateTo?.('cell-style')"
4657
4727
  >
4658
4728
  <document-engine-icon name="table_cell_property"></document-engine-icon>
@@ -4786,6 +4856,7 @@ class TableStyleViewComponent {
4786
4856
 
4787
4857
  <input
4788
4858
  documentEngineInput
4859
+ data-testid="border-width"
4789
4860
  [(ngModel)]="borderWidth"
4790
4861
  (ngModelChange)="onBorderWidthChange()"
4791
4862
  placeholder="1px"
@@ -4795,7 +4866,7 @@ class TableStyleViewComponent {
4795
4866
  <div class="table-style-view__field">
4796
4867
  <div class="table-style-view__field-label">Style</div>
4797
4868
 
4798
- <document-engine-select [(value)]="borderStyle" variant="outline">
4869
+ <document-engine-select data-testid="border-style" [(value)]="borderStyle" variant="outline">
4799
4870
  <button documentEngineSelectOption value="solid">Solid</button>
4800
4871
  <button documentEngineSelectOption value="double">Double</button>
4801
4872
  <button documentEngineSelectOption value="dashed">Dashed</button>
@@ -4816,6 +4887,7 @@ class TableStyleViewComponent {
4816
4887
  size="icon"
4817
4888
  variant="ghost"
4818
4889
  class="table-style-view__color-clear"
4890
+ data-testid="clear-border-color"
4819
4891
  (click)="borderColor = null"
4820
4892
  >
4821
4893
  <document-engine-icon name="close"></document-engine-icon>
@@ -4836,13 +4908,14 @@ class TableStyleViewComponent {
4836
4908
  <!-- Color picker for border -->
4837
4909
  <div
4838
4910
  *ngIf="showColorPicker === 'border'"
4839
- [popover]="borderColorTrigger"
4911
+ [documentEnginePopover]="borderColorTrigger"
4840
4912
  [isOpen]="showColorPicker === 'border'"
4841
4913
  class="table-style-view__color-picker-dropdown"
4842
4914
  (click)="$event.stopPropagation()"
4843
4915
  >
4844
4916
  <document-engine-color-picker
4845
4917
  [colorPalette]="colorPalette"
4918
+ data-testid="border-color"
4846
4919
  [activeColor]="borderColorObj"
4847
4920
  (colorSelected)="onBorderColorSelected($event)"
4848
4921
  (colorRemoved)="onBorderColorRemoved()"
@@ -4867,6 +4940,7 @@ class TableStyleViewComponent {
4867
4940
  size="icon"
4868
4941
  variant="ghost"
4869
4942
  class="table-style-view__color-clear"
4943
+ data-testid="clear-background-color"
4870
4944
  (click)="tableBg = null"
4871
4945
  >
4872
4946
  <document-engine-icon name="close"></document-engine-icon>
@@ -4887,13 +4961,14 @@ class TableStyleViewComponent {
4887
4961
  <!-- Color picker for background -->
4888
4962
  <div
4889
4963
  *ngIf="showColorPicker === 'bg'"
4890
- [popover]="bgColorTrigger"
4964
+ [documentEnginePopover]="bgColorTrigger"
4891
4965
  [isOpen]="showColorPicker === 'bg'"
4892
4966
  class="table-style-view__color-picker-dropdown"
4893
4967
  (click)="$event.stopPropagation()"
4894
4968
  >
4895
4969
  <document-engine-color-picker
4896
4970
  [colorPalette]="colorPalette"
4971
+ data-testid="background-color"
4897
4972
  [activeColor]="tableBgObj"
4898
4973
  (colorSelected)="onTableBgSelected($event)"
4899
4974
  (colorRemoved)="onTableBgRemoved()"
@@ -4905,11 +4980,11 @@ class TableStyleViewComponent {
4905
4980
 
4906
4981
  <!-- Actions -->
4907
4982
  <div class="table-style-view__actions">
4908
- <button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
4909
- <button documentEngineButton variant="default" (click)="onSave()">Save</button>
4983
+ <button documentEngineButton data-testid="cancel" variant="ghost" (click)="cancel()">Cancel</button>
4984
+ <button documentEngineButton data-testid="save" variant="default" (click)="onSave()">Save</button>
4910
4985
  </div>
4911
4986
  </div>
4912
- `, isInline: true, styles: [".table-style-view{display:flex;flex-direction:column;width:32.5rem}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["popover", "isOpen", "placement", "offset", "padding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4987
+ `, isInline: true, styles: ["@charset \"UTF-8\";.table-style-view{display:flex;flex-direction:column;width:32.5rem;max-inline-size:calc(100vw - 2rem)}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{box-sizing:border-box;z-index:50;border-radius:.5rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "directive", type: InputDirective, selector: "input[documentEngineInput]", inputs: ["class"] }, { kind: "component", type: SelectComponent, selector: "document-engine-select", inputs: ["value", "disabled", "labelMode", "variant"], outputs: ["valueChange"] }, { kind: "directive", type: SelectOptionDirective, selector: "[documentEngineSelectOption]", inputs: ["value", "label", "disabled"] }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ColorPickerComponent, selector: "document-engine-color-picker", inputs: ["colorPalette", "usedColors", "activeColor"], outputs: ["colorSelected", "colorRemoved"] }, { kind: "directive", type: PopoverDirective, selector: "[documentEnginePopover], [popover]", inputs: ["documentEnginePopover", "popover", "isOpen", "placement", "offset", "padding"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4913
4988
  }
4914
4989
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: TableStyleViewComponent, decorators: [{
4915
4990
  type: Component,
@@ -4938,6 +5013,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4938
5013
 
4939
5014
  <input
4940
5015
  documentEngineInput
5016
+ data-testid="border-width"
4941
5017
  [(ngModel)]="borderWidth"
4942
5018
  (ngModelChange)="onBorderWidthChange()"
4943
5019
  placeholder="1px"
@@ -4947,7 +5023,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4947
5023
  <div class="table-style-view__field">
4948
5024
  <div class="table-style-view__field-label">Style</div>
4949
5025
 
4950
- <document-engine-select [(value)]="borderStyle" variant="outline">
5026
+ <document-engine-select data-testid="border-style" [(value)]="borderStyle" variant="outline">
4951
5027
  <button documentEngineSelectOption value="solid">Solid</button>
4952
5028
  <button documentEngineSelectOption value="double">Double</button>
4953
5029
  <button documentEngineSelectOption value="dashed">Dashed</button>
@@ -4968,6 +5044,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4968
5044
  size="icon"
4969
5045
  variant="ghost"
4970
5046
  class="table-style-view__color-clear"
5047
+ data-testid="clear-border-color"
4971
5048
  (click)="borderColor = null"
4972
5049
  >
4973
5050
  <document-engine-icon name="close"></document-engine-icon>
@@ -4988,13 +5065,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4988
5065
  <!-- Color picker for border -->
4989
5066
  <div
4990
5067
  *ngIf="showColorPicker === 'border'"
4991
- [popover]="borderColorTrigger"
5068
+ [documentEnginePopover]="borderColorTrigger"
4992
5069
  [isOpen]="showColorPicker === 'border'"
4993
5070
  class="table-style-view__color-picker-dropdown"
4994
5071
  (click)="$event.stopPropagation()"
4995
5072
  >
4996
5073
  <document-engine-color-picker
4997
5074
  [colorPalette]="colorPalette"
5075
+ data-testid="border-color"
4998
5076
  [activeColor]="borderColorObj"
4999
5077
  (colorSelected)="onBorderColorSelected($event)"
5000
5078
  (colorRemoved)="onBorderColorRemoved()"
@@ -5019,6 +5097,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5019
5097
  size="icon"
5020
5098
  variant="ghost"
5021
5099
  class="table-style-view__color-clear"
5100
+ data-testid="clear-background-color"
5022
5101
  (click)="tableBg = null"
5023
5102
  >
5024
5103
  <document-engine-icon name="close"></document-engine-icon>
@@ -5039,13 +5118,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5039
5118
  <!-- Color picker for background -->
5040
5119
  <div
5041
5120
  *ngIf="showColorPicker === 'bg'"
5042
- [popover]="bgColorTrigger"
5121
+ [documentEnginePopover]="bgColorTrigger"
5043
5122
  [isOpen]="showColorPicker === 'bg'"
5044
5123
  class="table-style-view__color-picker-dropdown"
5045
5124
  (click)="$event.stopPropagation()"
5046
5125
  >
5047
5126
  <document-engine-color-picker
5048
5127
  [colorPalette]="colorPalette"
5128
+ data-testid="background-color"
5049
5129
  [activeColor]="tableBgObj"
5050
5130
  (colorSelected)="onTableBgSelected($event)"
5051
5131
  (colorRemoved)="onTableBgRemoved()"
@@ -5057,11 +5137,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5057
5137
 
5058
5138
  <!-- Actions -->
5059
5139
  <div class="table-style-view__actions">
5060
- <button documentEngineButton variant="ghost" (click)="cancel()">Cancel</button>
5061
- <button documentEngineButton variant="default" (click)="onSave()">Save</button>
5140
+ <button documentEngineButton data-testid="cancel" variant="ghost" (click)="cancel()">Cancel</button>
5141
+ <button documentEngineButton data-testid="save" variant="default" (click)="onSave()">Save</button>
5062
5142
  </div>
5063
5143
  </div>
5064
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".table-style-view{display:flex;flex-direction:column;width:32.5rem}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{z-index:50;background:var(--popover);border:1px solid var(--border);border-radius:.5rem;box-shadow:var(--shadow-elevation-2);padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
5144
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";.table-style-view{display:flex;flex-direction:column;width:32.5rem;max-inline-size:calc(100vw - 2rem)}.table-style-view__header{padding:.5rem 1rem;border-bottom:1px solid var(--border);font-weight:500}.table-style-view__content{display:flex;flex-direction:column;gap:1rem;padding:.75rem}.table-style-view__section{display:flex;flex-direction:column;gap:.5rem}.table-style-view__section-label{font-weight:700;color:var(--muted-foreground)}.table-style-view__border-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:.5rem}.table-style-view__field{display:flex;flex-direction:column;gap:.25rem;grid-column:span 2}.table-style-view__field--color{grid-column:span 4;position:relative}.table-style-view__field-label{font-size:.75rem;color:var(--muted-foreground)}.table-style-view__color-input{position:relative;display:flex;align-items:center;gap:.25rem}.table-style-view__color-input input{flex:1;padding-right:3rem}.table-style-view__color-clear{position:absolute;right:2.5rem;z-index:1}.table-style-view__color-swatch{position:absolute;right:.5rem;width:1.75rem;height:1.75rem;border-radius:.25rem;border:1px solid var(--border);cursor:pointer;transition:transform .2s;overflow:hidden;z-index:2}.table-style-view__color-swatch:hover{transform:scale(1.1)}.table-style-view__color-checker{position:absolute;inset:0;opacity:.3;background-image:repeating-conic-gradient(#ccc 0deg 90deg,#fff 90deg 180deg);background-size:4px 4px}.table-style-view__color-picker-dropdown{box-sizing:border-box;z-index:50;border-radius:.5rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));padding:.5rem}.table-style-view__actions{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;padding:.75rem;border-top:1px solid var(--border)}\n"] }]
5065
5145
  }], propDecorators: { borderColorTrigger: [{
5066
5146
  type: ViewChild,
5067
5147
  args: ['borderColorTrigger', { static: false, read: ElementRef }]
@@ -5318,12 +5398,31 @@ const TEXT_CASE_OPTIONS = [
5318
5398
  { value: 'lowercase', label: 'lowercase' },
5319
5399
  { value: 'capitalize', label: 'Capitalize' },
5320
5400
  ];
5321
- const HEADING_OPTIONS = [
5322
- { value: null, label: 'Normal text', class: '' },
5323
- { value: 1, label: 'Heading 1', class: 'h1' },
5324
- { value: 2, label: 'Heading 2', class: 'h2' },
5325
- { value: 3, label: 'Heading 3', class: 'h3' },
5326
- ];
5401
+ /**
5402
+ * Heading levels offered when `heading` is enabled without an explicit `levels`
5403
+ * array — mirrors the Tiptap Heading extension default (1–6).
5404
+ */
5405
+ const DEFAULT_HEADING_LEVELS = [1, 2, 3, 4, 5, 6];
5406
+ /**
5407
+ * Build the block-type dropdown options from the editor's configured heading
5408
+ * levels, so the toolbar offers exactly the levels the editor can produce: no
5409
+ * dead entry for a level the config omits, and no configured level left
5410
+ * unreachable. The leading "Normal text" (paragraph) entry is always present.
5411
+ * Levels are de-duplicated and sorted ascending; an empty or omitted list falls
5412
+ * back to {@link DEFAULT_HEADING_LEVELS}.
5413
+ */
5414
+ function buildHeadingOptions(levels) {
5415
+ const source = levels && levels.length > 0 ? levels : DEFAULT_HEADING_LEVELS;
5416
+ const sorted = [...new Set(source)].sort((a, b) => a - b);
5417
+ return [
5418
+ { value: null, label: 'Normal text', class: '' },
5419
+ ...sorted.map((level) => ({
5420
+ value: level,
5421
+ label: `Heading ${level}`,
5422
+ class: `h${level}`,
5423
+ })),
5424
+ ];
5425
+ }
5327
5426
  const TEXT_ALIGN_OPTIONS = [
5328
5427
  { value: 'left', label: 'Align Left', icon: 'format_align_left' },
5329
5428
  { value: 'center', label: 'Align Center', icon: 'format_align_center' },
@@ -5661,7 +5760,7 @@ class EditorBubbleMenuComponent {
5661
5760
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: EditorBubbleMenuComponent, isStandalone: true, selector: "document-engine-editor-bubble-menu", inputs: { editor: "editor", config: "config" }, viewQueries: [{ propertyName: "viewContainer", first: true, predicate: ["viewContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "bubbleElement", first: true, predicate: ["bubbleElement"], descendants: true }], ngImport: i0, template: `
5662
5761
  <div
5663
5762
  #bubbleElement
5664
- class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-30"
5763
+ class="bubble-menu-wrapper"
5665
5764
  tiptapBubbleMenu
5666
5765
  [editor]="editor"
5667
5766
  [props]="config.props!"
@@ -5671,18 +5770,14 @@ class EditorBubbleMenuComponent {
5671
5770
  <!-- View component will be dynamically inserted here -->
5672
5771
  <ng-container #viewContainer></ng-container>
5673
5772
  </div>
5674
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TiptapBubbleMenuDirective, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: ["props", "editor", "mountTo", "shouldShow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5773
+ `, isInline: true, styles: ["@charset \"UTF-8\";.bubble-menu-wrapper{box-sizing:border-box;z-index:30;border-radius:.25rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: TiptapBubbleMenuDirective, selector: "tiptap-bubble-menu[editor], [tiptapBubbleMenu][editor]", inputs: ["props", "editor", "mountTo", "shouldShow"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5675
5774
  }
5676
5775
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: EditorBubbleMenuComponent, decorators: [{
5677
5776
  type: Component,
5678
- args: [{
5679
- selector: 'document-engine-editor-bubble-menu',
5680
- standalone: true,
5681
- imports: [CommonModule, TiptapBubbleMenuDirective],
5682
- template: `
5777
+ args: [{ selector: 'document-engine-editor-bubble-menu', standalone: true, imports: [CommonModule, TiptapBubbleMenuDirective], template: `
5683
5778
  <div
5684
5779
  #bubbleElement
5685
- class="bubble-menu-wrapper rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-30"
5780
+ class="bubble-menu-wrapper"
5686
5781
  tiptapBubbleMenu
5687
5782
  [editor]="editor"
5688
5783
  [props]="config.props!"
@@ -5692,9 +5787,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5692
5787
  <!-- View component will be dynamically inserted here -->
5693
5788
  <ng-container #viewContainer></ng-container>
5694
5789
  </div>
5695
- `,
5696
- changeDetection: ChangeDetectionStrategy.OnPush,
5697
- }]
5790
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";.bubble-menu-wrapper{box-sizing:border-box;z-index:30;border-radius:.25rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1))}\n"] }]
5698
5791
  }], propDecorators: { editor: [{
5699
5792
  type: Input
5700
5793
  }], config: [{
@@ -5723,6 +5816,8 @@ class ToolbarBubbleMenuComponent {
5723
5816
  currentViewRef = null;
5724
5817
  currentViewId = null;
5725
5818
  currentAttributes = {};
5819
+ cleanupAutoUpdate;
5820
+ positionTimeout;
5726
5821
  isVisible = false;
5727
5822
  ngAfterViewInit() {
5728
5823
  const bubbleElement = this.bubbleElement?.nativeElement;
@@ -5763,7 +5858,8 @@ class ToolbarBubbleMenuComponent {
5763
5858
  this.showView(initialView);
5764
5859
  // Position the bubble relative to the target element
5765
5860
  // TODO: It make the bubble jump when the bubble is opened
5766
- setTimeout(() => this.positionBubble(targetElement), 0);
5861
+ clearTimeout(this.positionTimeout);
5862
+ this.positionTimeout = setTimeout(() => this.positionBubble(targetElement), 0);
5767
5863
  // Attach outside click/escape listeners using the bubble element, not the toolbar button
5768
5864
  const bubbleEl = this.bubbleElement?.nativeElement;
5769
5865
  if (bubbleEl) {
@@ -5776,6 +5872,15 @@ class ToolbarBubbleMenuComponent {
5776
5872
  */
5777
5873
  hideBubble() {
5778
5874
  this.isVisible = false;
5875
+ // Cancel a positioning pass that has not run yet. `showBubble` defers it by a
5876
+ // tick, so a show-then-immediately-hide (or a teardown) would otherwise let it
5877
+ // fire afterwards and register a fresh `autoUpdate` — scroll/resize listeners
5878
+ // plus a Resize and an Intersection observer — on a hidden or detached
5879
+ // element, with the handle for disposing it already thrown away.
5880
+ clearTimeout(this.positionTimeout);
5881
+ this.positionTimeout = undefined;
5882
+ this.cleanupAutoUpdate?.();
5883
+ this.cleanupAutoUpdate = undefined;
5779
5884
  this.eventManager.detach(this.config.pluginKey);
5780
5885
  this.destroyCurrentView();
5781
5886
  this.cdr.markForCheck();
@@ -5880,59 +5985,67 @@ class ToolbarBubbleMenuComponent {
5880
5985
  findViewConfig(viewId) {
5881
5986
  return this.config.views.find((v) => v.id === viewId) || null;
5882
5987
  }
5988
+ /**
5989
+ * Anchor the bubble to its trigger with floating-ui, matching
5990
+ * `PopoverDirective`.
5991
+ *
5992
+ * The hand-rolled version this replaces measured the trigger exactly once, so
5993
+ * scrolling left the panel welded to the viewport while its target slid away.
5994
+ * It also had no third case: a panel that fit neither below nor above got a
5995
+ * negative `top`, putting its own header off the top edge of the screen.
5996
+ * `flip` handles the second placement and `shift` clamps the panel into the
5997
+ * viewport instead, so there is no unhandled case left.
5998
+ *
5999
+ * `strategy: 'fixed'` matches the `position: fixed` written below — asking for
6000
+ * the default `'absolute'` coordinates would offset the panel by the page's
6001
+ * scroll amount.
6002
+ */
5883
6003
  positionBubble(targetElement) {
5884
6004
  const bubble = this.bubbleElement?.nativeElement;
5885
6005
  if (!bubble)
5886
6006
  return;
5887
- const targetRect = targetElement.getBoundingClientRect();
5888
- const bubbleRect = bubble.getBoundingClientRect();
5889
- const viewportWidth = window.innerWidth;
5890
- const viewportHeight = window.innerHeight;
5891
- // Default position: below the target element, centered
5892
- let top = targetRect.bottom + 8;
5893
- let left = targetRect.left + targetRect.width / 2 - bubbleRect.width / 2;
5894
- // Adjust horizontal position to stay within viewport
5895
- if (left < 8) {
5896
- left = 8;
5897
- }
5898
- else if (left + bubbleRect.width > viewportWidth - 8) {
5899
- left = viewportWidth - bubbleRect.width - 8;
5900
- }
5901
- // If bubble would go below viewport, position it above the target
5902
- if (top + bubbleRect.height > viewportHeight - 8) {
5903
- top = targetRect.top - bubbleRect.height - 8;
5904
- }
5905
- // Apply position
5906
6007
  bubble.style.position = 'fixed';
5907
- bubble.style.top = `${top}px`;
5908
- bubble.style.left = `${left}px`;
6008
+ this.cleanupAutoUpdate?.();
6009
+ this.cleanupAutoUpdate = autoUpdate(targetElement, bubble, () => {
6010
+ computePosition(targetElement, bubble, {
6011
+ strategy: 'fixed',
6012
+ placement: 'bottom',
6013
+ middleware: [offset(8), flip({ padding: 8 }), shift({ padding: 8 })],
6014
+ })
6015
+ .then(({ x, y }) => {
6016
+ Object.assign(bubble.style, { left: `${x}px`, top: `${y}px` });
6017
+ })
6018
+ .catch((error) => {
6019
+ console.error('[ToolbarBubbleMenu] Error computing position', error);
6020
+ });
6021
+ });
5909
6022
  }
5910
6023
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarBubbleMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5911
6024
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: ToolbarBubbleMenuComponent, isStandalone: true, selector: "document-engine-toolbar-bubble-menu", inputs: { editor: "editor", config: "config" }, viewQueries: [{ propertyName: "bubbleElement", first: true, predicate: ["bubbleElement"], descendants: true }, { propertyName: "viewContainer", first: true, predicate: ["viewContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: `
5912
6025
  <div
5913
6026
  #bubbleElement
5914
- class="toolbar-bubble-menu rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-50"
5915
- [class.hidden]="!isVisible"
6027
+ class="toolbar-bubble-menu"
6028
+ [class.toolbar-bubble-menu--hidden]="!isVisible"
5916
6029
  [style.width]="config.width || 'auto'"
5917
6030
  [style.maxWidth]="config.maxWidth || '320px'"
5918
6031
  >
5919
6032
  <ng-container #viewContainer></ng-container>
5920
6033
  </div>
5921
- `, isInline: true, styles: [".toolbar-bubble-menu{opacity:0;transform:translateY(-10px);transition:opacity .2s ease-out,transform .2s ease-out;pointer-events:none}.toolbar-bubble-menu:not(.hidden){opacity:1;transform:translateY(0);pointer-events:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6034
+ `, isInline: true, styles: ["@charset \"UTF-8\";.toolbar-bubble-menu{box-sizing:border-box;z-index:50;border-radius:.25rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));opacity:1;pointer-events:auto;transition:opacity .2s ease-out}.toolbar-bubble-menu--hidden{display:none;opacity:0;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5922
6035
  }
5923
6036
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: ToolbarBubbleMenuComponent, decorators: [{
5924
6037
  type: Component,
5925
6038
  args: [{ selector: 'document-engine-toolbar-bubble-menu', standalone: true, imports: [CommonModule], template: `
5926
6039
  <div
5927
6040
  #bubbleElement
5928
- class="toolbar-bubble-menu rounded border bg-card text-card-foreground border-border shadow-elevation-2 z-50"
5929
- [class.hidden]="!isVisible"
6041
+ class="toolbar-bubble-menu"
6042
+ [class.toolbar-bubble-menu--hidden]="!isVisible"
5930
6043
  [style.width]="config.width || 'auto'"
5931
6044
  [style.maxWidth]="config.maxWidth || '320px'"
5932
6045
  >
5933
6046
  <ng-container #viewContainer></ng-container>
5934
6047
  </div>
5935
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".toolbar-bubble-menu{opacity:0;transform:translateY(-10px);transition:opacity .2s ease-out,transform .2s ease-out;pointer-events:none}.toolbar-bubble-menu:not(.hidden){opacity:1;transform:translateY(0);pointer-events:auto}\n"] }]
6048
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["@charset \"UTF-8\";.toolbar-bubble-menu{box-sizing:border-box;z-index:50;border-radius:.25rem;border:1px solid var(--border, hsl(214.3, 31.8%, 91.4%));background-color:var(--popover, var(--card, hsl(0, 0%, 100%)));color:var(--popover-foreground, var(--card-foreground, hsl(222.2, 84%, 4.9%)));box-shadow:var(--shadow-elevation-2, 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px -1px rgba(0, 0, 0, .1));opacity:1;pointer-events:auto;transition:opacity .2s ease-out}.toolbar-bubble-menu--hidden{display:none;opacity:0;pointer-events:none}\n"] }]
5936
6049
  }], propDecorators: { editor: [{
5937
6050
  type: Input
5938
6051
  }], config: [{
@@ -6057,7 +6170,10 @@ class ToolbarComponent {
6057
6170
  fontSizeOptions = FONT_SIZE_OPTIONS;
6058
6171
  lineHeightOptions = LINE_HEIGHT_OPTIONS;
6059
6172
  textCaseOptions = TEXT_CASE_OPTIONS;
6060
- headingOptions = HEADING_OPTIONS;
6173
+ // Derived from the editor's configured heading levels in ngOnInit so the
6174
+ // block-type dropdown lists exactly the levels the config allows (no dead
6175
+ // entry, no unreachable level). Seeded with the default set for first paint.
6176
+ headingOptions = buildHeadingOptions();
6061
6177
  textAlignOptions = TEXT_ALIGN_OPTIONS;
6062
6178
  listStyleOptions = LIST_STYLE_OPTIONS;
6063
6179
  colorBubbleMenu;
@@ -6108,6 +6224,10 @@ class ToolbarComponent {
6108
6224
  });
6109
6225
  };
6110
6226
  ngOnInit() {
6227
+ // Build the block-type dropdown from the configured heading levels (semantic
6228
+ // mode = 2/3/4, etc.) rather than a fixed 1/2/3 set. Done before the first
6229
+ // updateToolbarState() so its heading-active scan iterates the real options.
6230
+ this.headingOptions = buildHeadingOptions(this.resolveHeadingLevels());
6111
6231
  // Subscribe to selectionUpdate to keep toolbar state in sync when cursor/selection changes
6112
6232
  this.editor.on('update', this.updateToolbarState);
6113
6233
  this.editor.on('transaction', this.throttleUpdateToolbarState);
@@ -6123,6 +6243,19 @@ class ToolbarComponent {
6123
6243
  this.toolbarService.ngOnDestroy();
6124
6244
  this.editor.destroy();
6125
6245
  }
6246
+ /**
6247
+ * Read the configured heading levels off `config.heading`. It may be a boolean
6248
+ * (`true` = default levels, handled by buildHeadingOptions' fallback) or a
6249
+ * `Partial<HeadingOptions>` carrying a `levels` array. Returns undefined when no
6250
+ * explicit levels are set so the caller falls back to the default set.
6251
+ */
6252
+ resolveHeadingLevels() {
6253
+ const heading = this.config?.heading;
6254
+ if (heading && typeof heading === 'object' && Array.isArray(heading.levels)) {
6255
+ return heading.levels;
6256
+ }
6257
+ return undefined;
6258
+ }
6126
6259
  runAndEmit(actionName, chain) {
6127
6260
  chain();
6128
6261
  this.toolbarService.emit(actionName);
@@ -6415,13 +6548,13 @@ class DocumentEditorComponent {
6415
6548
  return merged;
6416
6549
  }
6417
6550
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6418
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: DocumentEditorComponent, isStandalone: true, selector: "document-engine-editor", inputs: { config: "config" }, outputs: { editorReady: "editorReady" }, host: { classAttribute: "no-twp" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ToolbarComponent, selector: "document-engine-toolbar", inputs: ["editor", "capabilities", "config"], outputs: ["action"] }, { kind: "component", type: FooterComponent, selector: "document-engine-footer", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6551
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.10", type: DocumentEditorComponent, isStandalone: true, selector: "document-engine-editor", inputs: { config: "config" }, outputs: { editorReady: "editorReady" }, host: { classAttribute: "no-twp" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ToolbarComponent, selector: "document-engine-toolbar", inputs: ["editor", "capabilities", "config"], outputs: ["action"] }, { kind: "component", type: FooterComponent, selector: "document-engine-footer", inputs: ["editor", "config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6419
6552
  }
6420
6553
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImport: i0, type: DocumentEditorComponent, decorators: [{
6421
6554
  type: Component,
6422
6555
  args: [{ selector: 'document-engine-editor', imports: [CommonModule, ToolbarComponent, FooterComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
6423
6556
  class: 'no-twp',
6424
- }, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"] }]
6557
+ }, template: "<div class=\"document-engine-document-editor\">\n <document-engine-toolbar\n *ngIf=\"mergedConfig.showToolbar\"\n [editor]=\"editor\"\n [capabilities]=\"capabilities\"\n [config]=\"mergedConfig\"\n ></document-engine-toolbar>\n\n <div class=\"document-engine-document-editor__content\">\n <ng-content></ng-content>\n </div>\n\n <document-engine-footer\n *ngIf=\"editor && mergedConfig.showFooter\"\n [editor]=\"editor\"\n [config]=\"mergedConfig\"\n ></document-engine-footer>\n</div>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:is() *,:is() *:before,:is() *:after{box-sizing:border-box}\n"] }]
6425
6558
  }], propDecorators: { config: [{
6426
6559
  type: Input
6427
6560
  }], editorReady: [{