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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -206,6 +206,12 @@ the contract.** It lists every token the library reads
206
206
  hardcodes a colour.
207
207
  - `--de-editor-min-height` (default `12rem`) sets the editing surface's floor; alternatively give the
208
208
  `document-engine-editor` element a real height and the surface fills it.
209
+ - **The editing surface does not scroll internally — it grows, and the page scrolls.** `0.1.5` briefly
210
+ made it a scroll container; that was reverted in `0.1.6` because the selection bubble menu is an
211
+ absolutely-positioned descendant of it, so the scroller became the panel's clip rect and a dropdown
212
+ opening upward lost its top options to the clip. If you need a fixed-height box that scrolls its
213
+ content, put `overflow: auto` on **your own** wrapper around `document-engine-editor`, never on
214
+ `.tiptap-editor`.
209
215
  - Prose and editing-affordance styles are **opt-in** subpaths (`styles/editor-content`,
210
216
  `styles/editor-interaction`), so the baseline can never impose a look on your documents.
211
217
 
@@ -239,13 +245,9 @@ nx lint @phuong-tran-redoc/document-engine-angular # lint
239
245
 
240
246
  ---
241
247
 
242
- ## 👤 Author
248
+ ## 👤 Authors
243
249
 
244
- Developed by **Duc Phuong (Jack)**
245
-
246
- - 💼 [LinkedIn](https://www.linkedin.com/in/tdp1999/)
247
- - 🐙 [GitHub](https://github.com/tdp1999)
248
- - 📧 [Email](mailto:tdp99.business@gmail.com)
250
+ See [AUTHORS.md](https://github.com/phuong-tran-redoc/document-engine/blob/main/AUTHORS.md).
249
251
 
250
252
  ---
251
253
 
@@ -3,27 +3,27 @@ import { DOCUMENT, CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, ElementRef, Input, Directive, Injectable, ApplicationRef, createComponent, HostBinding, InjectionToken, Renderer2, ChangeDetectorRef, forwardRef, ChangeDetectionStrategy, Component, ViewEncapsulation, EventEmitter, Output, HostListener, ContentChildren, ViewChild, ViewChildren, NgZone, ViewContainerRef, EnvironmentInjector, NgModule } from '@angular/core';
5
5
  import { Extension, Editor } from '@tiptap/core';
6
- import Blockquote from '@tiptap/extension-blockquote';
7
- import Bold from '@tiptap/extension-bold';
8
- import Code from '@tiptap/extension-code';
9
- import CodeBlock from '@tiptap/extension-code-block';
6
+ import { Blockquote } from '@tiptap/extension-blockquote';
7
+ import { Bold } from '@tiptap/extension-bold';
8
+ import { Code } from '@tiptap/extension-code';
9
+ import { CodeBlock } from '@tiptap/extension-code-block';
10
10
  import { Document } from '@tiptap/extension-document';
11
11
  import { HardBreak } from '@tiptap/extension-hard-break';
12
12
  import { HorizontalRule } from '@tiptap/extension-horizontal-rule';
13
13
  import { Image } from '@tiptap/extension-image';
14
- import Italic from '@tiptap/extension-italic';
15
- import Link from '@tiptap/extension-link';
14
+ import { Italic } from '@tiptap/extension-italic';
15
+ import { Link } from '@tiptap/extension-link';
16
16
  import { ListKeymap, ListItem, BulletList } from '@tiptap/extension-list';
17
17
  import { Paragraph } from '@tiptap/extension-paragraph';
18
- import Strike from '@tiptap/extension-strike';
18
+ import { Strike } from '@tiptap/extension-strike';
19
19
  import { Subscript } from '@tiptap/extension-subscript';
20
20
  import { Superscript } from '@tiptap/extension-superscript';
21
21
  import { Text } from '@tiptap/extension-text';
22
22
  import { TextAlign } from '@tiptap/extension-text-align';
23
23
  import { TextStyleKit } from '@tiptap/extension-text-style';
24
- import Underline from '@tiptap/extension-underline';
24
+ import { Underline } from '@tiptap/extension-underline';
25
25
  import { Dropcursor, Gapcursor, TrailingNode, UndoRedo, CharacterCount, Placeholder } from '@tiptap/extensions';
26
- import { ResetOnEnter, EditableRegion, RestrictedEditing, CustomOrderedList, StyledTableKit, ImageRef, PageBreak, ResetFormat, Indent, ClearContent, TextCase, NotumHeading, DynamicField, Color, normalizeColor, getActiveMarkRange, getSelectedCells, getCombinedCellAttributeValue, TableDefaultAttributes, getCursorCellInfo, getClosestDomElement } from '@phuong-tran-redoc/document-engine-core';
26
+ import { ResetOnEnter, EditableRegion, RestrictedEditing, CustomOrderedList, StyledTableKit, ImageRef, PageBreak, ResetFormat, Indent, ClearContent, TextCase, NotumHeading, DynamicField, CkCompat, Color, normalizeColor, getActiveMarkRange, getSelectedCells, getCombinedCellAttributeValue, TableDefaultAttributes, getCursorCellInfo, getClosestDomElement } from '@phuong-tran-redoc/document-engine-core';
27
27
  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';
@@ -113,6 +113,7 @@ const DocumentEngineKit = Extension.create({
113
113
  add(options.textCase, TextCase);
114
114
  add(options.heading, NotumHeading);
115
115
  add(options.dynamicField, DynamicField);
116
+ add(options.ckCompat, CkCompat);
116
117
  return extensions;
117
118
  },
118
119
  });
@@ -3733,6 +3734,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
3733
3734
  args: [ToggleOptionDirective, { descendants: true }]
3734
3735
  }] } });
3735
3736
 
3737
+ /**
3738
+ * Normalize what a user typed into the border-width field into a valid CSS length.
3739
+ *
3740
+ * The field is a free-text input, and `border-width: 3` is not a valid declaration —
3741
+ * the browser drops it silently, so typing a bare number used to do nothing at all
3742
+ * with no feedback. A bare number is the single most likely thing to type, and in a
3743
+ * length context it conventionally means pixels, so it is completed to `px`.
3744
+ *
3745
+ * Anything already carrying a unit is left alone (only the unit is lower-cased).
3746
+ *
3747
+ * The two failure modes are deliberately NOT the same value, because the command this
3748
+ * feeds reads `null` as "clear this field" and `undefined` as "leave it alone":
3749
+ *
3750
+ * - an **empty** field is a real instruction to clear the width, so it returns `null`;
3751
+ * - **unusable** input (`abc`, `-2px`, `50%`) returns `undefined`, so a typo leaves the
3752
+ * width the cell already had instead of wiping it. Collapsing the two would mean a
3753
+ * user with a working `1px` border who mistypes `3 pixels` loses it on Save with no
3754
+ * message — the field is free text, so mistyping it is the expected case, not a rare one.
3755
+ *
3756
+ * @returns a valid CSS border-width; `null` to clear it; `undefined` to leave it alone.
3757
+ */
3758
+ function normalizeBorderWidth(input) {
3759
+ const value = (input ?? '').trim();
3760
+ if (!value)
3761
+ return null;
3762
+ // `thin | medium | thick` are valid border-width keywords in their own right.
3763
+ if (/^(thin|medium|thick)$/i.test(value))
3764
+ return value.toLowerCase();
3765
+ // A bare number — the case this helper exists for. Border widths cannot be
3766
+ // negative, so a signed value is rejected rather than quietly clamped.
3767
+ if (/^\d+(\.\d+)?$/.test(value))
3768
+ return `${parseFloat(value)}px`;
3769
+ // Already a length. Units per CSS Values 4; `%` is not valid for border-width.
3770
+ const withUnit = /^(\d+(?:\.\d+)?)\s*(px|em|rem|pt|pc|in|cm|mm|q|ch|ex|vh|vw|vmin|vmax)$/i.exec(value);
3771
+ if (withUnit)
3772
+ return `${parseFloat(withUnit[1])}${withUnit[2].toLowerCase()}`;
3773
+ // Not a border-width at all — leave whatever the cell already has untouched.
3774
+ return undefined;
3775
+ }
3776
+
3736
3777
  /**
3737
3778
  * Cell style view for table bubble menu
3738
3779
  * Allows styling individual table cells (borders, background, alignment)
@@ -3834,10 +3875,14 @@ class TableCellStyleViewComponent {
3834
3875
  this.cdr.markForCheck();
3835
3876
  }
3836
3877
  onSave() {
3878
+ // See `normalizeBorderWidth`: a bare number is completed to `px` so the field does
3879
+ // not silently do nothing, and unusable input yields `undefined` so the command
3880
+ // leaves the width the cell already has rather than clearing it.
3881
+ const width = normalizeBorderWidth(this.borderWidth);
3837
3882
  this.editor
3838
3883
  ?.chain()
3839
3884
  .focus()
3840
- .setCellBorder({ style: this.borderStyle, color: this.borderColor, width: this.borderWidth })
3885
+ .setCellBorder({ style: this.borderStyle, color: this.borderColor, width })
3841
3886
  .setCellBackgroundColor(this.backgroundColor)
3842
3887
  .setCellTextAlign(this.textAlign)
3843
3888
  .setCellVerticalAlign(this.vAlign)
@@ -3902,6 +3947,7 @@ class TableCellStyleViewComponent {
3902
3947
  <button
3903
3948
  #borderColorTrigger
3904
3949
  type="button"
3950
+ data-testid="border-color-trigger"
3905
3951
  class="table-cell-style-view__color-swatch"
3906
3952
  [style.background-color]="borderColor || 'transparent'"
3907
3953
  (click)="toggleColorPicker('border')"
@@ -3955,6 +4001,7 @@ class TableCellStyleViewComponent {
3955
4001
  <button
3956
4002
  #bgColorTrigger
3957
4003
  type="button"
4004
+ data-testid="background-color-trigger"
3958
4005
  class="table-cell-style-view__color-swatch"
3959
4006
  [style.background-color]="backgroundColor || 'transparent'"
3960
4007
  (click)="toggleColorPicker('bg')"
@@ -4095,6 +4142,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4095
4142
  <button
4096
4143
  #borderColorTrigger
4097
4144
  type="button"
4145
+ data-testid="border-color-trigger"
4098
4146
  class="table-cell-style-view__color-swatch"
4099
4147
  [style.background-color]="borderColor || 'transparent'"
4100
4148
  (click)="toggleColorPicker('border')"
@@ -4148,6 +4196,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4148
4196
  <button
4149
4197
  #bgColorTrigger
4150
4198
  type="button"
4199
+ data-testid="background-color-trigger"
4151
4200
  class="table-cell-style-view__color-swatch"
4152
4201
  [style.background-color]="backgroundColor || 'transparent'"
4153
4202
  (click)="toggleColorPicker('bg')"
@@ -4631,7 +4680,7 @@ class TableMainViewComponent {
4631
4680
  </document-engine-select>
4632
4681
 
4633
4682
  <!-- Cell actions -->
4634
- <document-engine-select [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4683
+ <document-engine-select data-testid="cell-actions" [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4635
4684
  <document-engine-icon documentEngineSelectLabel name="table_merge_cell"></document-engine-icon>
4636
4685
 
4637
4686
  <button documentEngineSelectOption data-testid="merge-cells" value="merge" [disabled]="!canMerge">
@@ -4693,7 +4742,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
4693
4742
  </document-engine-select>
4694
4743
 
4695
4744
  <!-- Cell actions -->
4696
- <document-engine-select [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4745
+ <document-engine-select data-testid="cell-actions" [(value)]="cellAction" (valueChange)="handleCellAction($event)" labelMode="static">
4697
4746
  <document-engine-icon documentEngineSelectLabel name="table_merge_cell"></document-engine-icon>
4698
4747
 
4699
4748
  <button documentEngineSelectOption data-testid="merge-cells" value="merge" [disabled]="!canMerge">
@@ -4829,10 +4878,14 @@ class TableStyleViewComponent {
4829
4878
  const editor = this.editor;
4830
4879
  if (!editor)
4831
4880
  return;
4881
+ // Complete a bare number to `px` before it reaches the command; `border-width: 3`
4882
+ // is invalid CSS and would be dropped without any feedback. `undefined` here means
4883
+ // the input was not a width at all, and the command leaves the current one alone.
4884
+ const width = normalizeBorderWidth(this.borderWidth);
4832
4885
  editor
4833
4886
  .chain()
4834
4887
  .focus()
4835
- .setTableBorder({ style: this.borderStyle, color: this.borderColor, width: this.borderWidth })
4888
+ .setTableBorder({ style: this.borderStyle, color: this.borderColor, width })
4836
4889
  .setTableBackgroundColor(this.tableBg)
4837
4890
  .run();
4838
4891
  this.cancel();
@@ -4896,6 +4949,7 @@ class TableStyleViewComponent {
4896
4949
  <button
4897
4950
  #borderColorTrigger
4898
4951
  type="button"
4952
+ data-testid="border-color-trigger"
4899
4953
  class="table-style-view__color-swatch"
4900
4954
  [style.background-color]="borderColor || 'transparent'"
4901
4955
  (click)="toggleColorPicker('border')"
@@ -4949,6 +5003,7 @@ class TableStyleViewComponent {
4949
5003
  <button
4950
5004
  #bgColorTrigger
4951
5005
  type="button"
5006
+ data-testid="background-color-trigger"
4952
5007
  class="table-style-view__color-swatch"
4953
5008
  [style.background-color]="tableBg || 'transparent'"
4954
5009
  (click)="toggleColorPicker('bg')"
@@ -5053,6 +5108,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5053
5108
  <button
5054
5109
  #borderColorTrigger
5055
5110
  type="button"
5111
+ data-testid="border-color-trigger"
5056
5112
  class="table-style-view__color-swatch"
5057
5113
  [style.background-color]="borderColor || 'transparent'"
5058
5114
  (click)="toggleColorPicker('border')"
@@ -5106,6 +5162,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.10", ngImpo
5106
5162
  <button
5107
5163
  #bgColorTrigger
5108
5164
  type="button"
5165
+ data-testid="background-color-trigger"
5109
5166
  class="table-style-view__color-swatch"
5110
5167
  [style.background-color]="tableBg || 'transparent'"
5111
5168
  (click)="toggleColorPicker('bg')"