nexheal-lib 0.0.55 → 0.0.56

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.
@@ -3974,6 +3974,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3974
3974
  args: ['switchContainer']
3975
3975
  }] } });
3976
3976
 
3977
+ const RICH_TEXT_EDITOR_DEFAULT_FEATURES = {
3978
+ bold: true,
3979
+ italic: true,
3980
+ underline: true,
3981
+ h1: true,
3982
+ h2: true,
3983
+ textColor: true,
3984
+ backgroundColor: true,
3985
+ alignLeft: true,
3986
+ alignCenter: true,
3987
+ alignRight: true,
3988
+ justify: true,
3989
+ indent: true,
3990
+ outdent: true,
3991
+ unorderedList: true,
3992
+ orderedList: true,
3993
+ link: true,
3994
+ image: true,
3995
+ undo: true,
3996
+ redo: true,
3997
+ clearFormatting: true,
3998
+ };
3999
+
3977
4000
  /**
3978
4001
  * Lightweight rich-text editor built on a `contenteditable` element and the
3979
4002
  * browser's `document.execCommand` rich-editing API (the de-facto standard for
@@ -3984,9 +4007,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
3984
4007
  * inner HTML string.
3985
4008
  */
3986
4009
  class RichTextEditor {
3987
- header = true;
3988
- media = true;
3989
- link = true;
4010
+ /** Per-item toolbar visibility. Omitted keys default to visible. */
4011
+ features = {};
3990
4012
  placeholder = 'Type here...';
3991
4013
  readonly = false;
3992
4014
  /** Disabled state, set by Reactive Forms through `setDisabledState`. */
@@ -4159,6 +4181,14 @@ class RichTextEditor {
4159
4181
  trackByIndex(index) {
4160
4182
  return index;
4161
4183
  }
4184
+ /** Whether a toolbar item is visible (defaults to true when not configured). */
4185
+ isVisible(feature) {
4186
+ return this.features[feature] ?? RICH_TEXT_EDITOR_DEFAULT_FEATURES[feature];
4187
+ }
4188
+ /** Whether any of the given toolbar items are visible (for group separators). */
4189
+ hasAny(...features) {
4190
+ return features.some((feature) => this.isVisible(feature));
4191
+ }
4162
4192
  // ---- Undo / redo / clear ----
4163
4193
  undo() {
4164
4194
  this.exec('undo');
@@ -4193,13 +4223,13 @@ class RichTextEditor {
4193
4223
  selection.addRange(this.savedRange);
4194
4224
  }
4195
4225
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RichTextEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
4196
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RichTextEditor, isStandalone: true, selector: "rich-text-editor", inputs: { header: "header", media: "media", link: "link", placeholder: "placeholder", readonly: "readonly" }, host: { listeners: { "document:selectionchange": "saveSelection()", "document:mousedown": "onDocumentMouseDown($event)" } }, providers: [
4226
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.25", type: RichTextEditor, isStandalone: true, selector: "rich-text-editor", inputs: { features: "features", placeholder: "placeholder", readonly: "readonly" }, host: { listeners: { "document:selectionchange": "saveSelection()", "document:mousedown": "onDocumentMouseDown($event)" } }, providers: [
4197
4227
  {
4198
4228
  provide: NG_VALUE_ACCESSOR,
4199
4229
  useExisting: forwardRef(() => RichTextEditor),
4200
4230
  multi: true,
4201
4231
  },
4202
- ], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly || disabled}\">\n <div class=\"toolbar\" *ngIf=\"!readonly && !disabled\">\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('bold')\" title=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('italic')\" title=\"Italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('underline')\" title=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"header\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h1')\" title=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h2')\" title=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\">\n <div class=\"color-tool\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('text')\" title=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'text'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('text', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('text', $event)\" />\n </label>\n </div>\n </div>\n <div class=\"color-tool\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('background')\" title=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'background'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('background', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('background', $event)\" />\n </label>\n </div>\n </div>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('left')\" title=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('center')\" title=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('right')\" title=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('justify')\" title=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"indent()\" title=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"outdent()\" title=\"Outdent\">\n <i class=\"he he-text-indent-right\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"media\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ul')\" title=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ol')\" title=\"Ordered list\">\n <i class=\"he he-ordered-list\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"link\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"createLink()\" title=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"insertImage()\" title=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"undo()\" title=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"redo()\" title=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"clearFormatting()\" title=\"Clear Formatting\">\n <i class=\"he he-text-clear-format\"></i>\n </button>\n </div>\n </div>\n\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"(readonly || disabled) ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\n [attr.data-placeholder]=\"placeholder\">\n </div>\n\n <!-- Custom Modal for Link/Image URL -->\n <div class=\"editor-modal-overlay\" *ngIf=\"showModal\" (click)=\"cancelModal()\">\n <div class=\"editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"editor-modal-header\">\n <span>{{ modalType === 'link' ? 'Insert Link' : 'Insert Image' }}</span>\n <button type=\"button\" class=\"editor-modal-close\" (click)=\"cancelModal()\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n <div class=\"editor-modal-body\">\n <div class=\"url-list\">\n <div class=\"url-row\" *ngFor=\"let url of modalUrls; let i = index; trackBy: trackByIndex\">\n <div class=\"url-field\">\n <input type=\"text\" class=\"url-input\" [class.invalid]=\"isRowInvalid(modalUrls[i])\"\n [ngModel]=\"modalUrls[i]\"\n (ngModelChange)=\"modalUrls[i] = $event\"\n [placeholder]=\"modalType === 'link' ? 'https://example.com' : 'https://example.com/image.png'\" />\n <span class=\"url-error\" *ngIf=\"isRowInvalid(modalUrls[i])\">Enter a valid URL</span>\n </div>\n <button type=\"button\" class=\"url-remove-btn\" *ngIf=\"modalUrls.length > 1\" (click)=\"removeUrlRow(i)\" title=\"Remove\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n </div>\n <button type=\"button\" class=\"url-add-btn\" (click)=\"addUrlRow()\">\n + Add more\n </button>\n </div>\n <div class=\"editor-modal-footer\">\n <button type=\"button\" class=\"modal-btn cancel-btn\" (click)=\"cancelModal()\">Cancel</button>\n <button type=\"button\" class=\"modal-btn confirm-btn\" [disabled]=\"!hasValidUrl\" (click)=\"confirmModal()\">Insert</button>\n </div>\n </div>\n </div>\n</div>\n", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button{cursor:pointer;min-width:25px;min-height:20px;border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items button:hover i{color:#000}.text-editor .toolbar .toolbar-items .color-tool{position:relative}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .color-popover{top:calc(100% + 6px);left:0;z-index:30;padding:8px;position:absolute;background:#fff;border-radius:6px;border:1px solid #e0e0e0;box-shadow:0 4px 16px #00000026}.text-editor .color-popover .color-grid{gap:4px;display:grid;grid-template-columns:repeat(8,18px)}.text-editor .color-popover .color-swatch{width:18px;height:18px;padding:0;cursor:pointer;border-radius:3px;border:1px solid rgba(0,0,0,.15);transition:transform .1s ease}.text-editor .color-popover .color-swatch:hover{transform:scale(1.15)}.text-editor .color-popover .color-custom{gap:8px;display:flex;margin-top:8px;font-size:12px;color:#555;cursor:pointer;align-items:center;justify-content:space-between}.text-editor .color-popover .color-custom input[type=color]{width:28px;height:20px;padding:0;cursor:pointer;background:none;border:1px solid #cccccc}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}.text-editor .editor-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000}.text-editor .editor-modal{background:#fff;border-radius:8px;box-shadow:0 8px 32px #0003;width:420px;max-width:90vw;animation:modalFadeIn .2s ease}@keyframes modalFadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.text-editor .editor-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e8e8e8}.text-editor .editor-modal-header span{font-size:15px;font-weight:600;color:#1a1a1a}.text-editor .editor-modal-close{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;display:grid;place-items:center}.text-editor .editor-modal-close i{font-size:14px;color:#666}.text-editor .editor-modal-close:hover{background:#f0f0f0}.text-editor .editor-modal-close:hover i{color:#333}.text-editor .editor-modal-body{padding:16px 18px}.text-editor .editor-modal-body .url-list{max-height:200px;overflow-y:auto;margin-bottom:10px;padding-right:2px}.text-editor .editor-modal-body .url-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:10px}.text-editor .editor-modal-body .url-field{flex:1;display:flex;flex-direction:column;gap:2px}.text-editor .editor-modal-body .url-input{width:100%;padding:8px 12px;border:1px solid #d0d0d0;border-radius:6px;font-size:13px;color:#333;outline:none;transition:border-color .2s}.text-editor .editor-modal-body .url-input:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff26}.text-editor .editor-modal-body .url-input::placeholder{color:#aaa}.text-editor .editor-modal-body .url-input.invalid{border-color:#e04040}.text-editor .editor-modal-body .url-input.invalid:focus{border-color:#e04040;box-shadow:0 0 0 2px #e0404026}.text-editor .editor-modal-body .url-error{font-size:11px;color:#e04040}.text-editor .editor-modal-body .url-remove-btn{background:none;border:1px solid #e0e0e0;border-radius:6px;cursor:pointer;padding:8px 9px;display:grid;place-items:center;transition:all .15s}.text-editor .editor-modal-body .url-remove-btn i{font-size:11px;color:#999}.text-editor .editor-modal-body .url-remove-btn:hover{background:#fff0f0;border-color:#faa}.text-editor .editor-modal-body .url-remove-btn:hover i{color:#e04040}.text-editor .editor-modal-body .url-add-btn{background:none;border:1px dashed #c0c0c0;border-radius:6px;cursor:pointer;padding:6px 14px;font-size:12px;color:#007bff;transition:all .15s;width:100%}.text-editor .editor-modal-body .url-add-btn:hover{background:#f0f8ff;border-color:#007bff}.text-editor .editor-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:12px 18px;border-top:1px solid #e8e8e8}.text-editor .editor-modal-footer .modal-btn{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s;border:1px solid transparent}.text-editor .editor-modal-footer .cancel-btn{background:#f5f5f5;color:#555;border-color:#ddd}.text-editor .editor-modal-footer .cancel-btn:hover{background:#eaeaea}.text-editor .editor-modal-footer .confirm-btn{background:#007bff;color:#fff}.text-editor .editor-modal-footer .confirm-btn:hover{background:#0069d9}.text-editor .editor-modal-footer .confirm-btn:disabled{opacity:.5;cursor:not-allowed}.text-editor .editor-modal-footer .confirm-btn:disabled:hover{background:#007bff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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"] }] });
4232
+ ], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly || disabled}\">\n <div class=\"toolbar\" *ngIf=\"!readonly && !disabled\">\n <div class=\"toolbar-items\" *ngIf=\"hasAny('bold', 'italic', 'underline')\">\n <button type=\"button\" *ngIf=\"isVisible('bold')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('bold')\" title=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('italic')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('italic')\" title=\"Italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('underline')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('underline')\" title=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('h1', 'h2')\">\n <button type=\"button\" *ngIf=\"isVisible('h1')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h1')\" title=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('h2')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h2')\" title=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\" *ngIf=\"hasAny('textColor', 'backgroundColor')\">\n <div class=\"color-tool\" *ngIf=\"isVisible('textColor')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('text')\" title=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'text'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('text', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('text', $event)\" />\n </label>\n </div>\n </div>\n <div class=\"color-tool\" *ngIf=\"isVisible('backgroundColor')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('background')\" title=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'background'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('background', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('background', $event)\" />\n </label>\n </div>\n </div>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('alignLeft', 'alignCenter', 'alignRight', 'justify')\">\n <button type=\"button\" *ngIf=\"isVisible('alignLeft')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('left')\" title=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('alignCenter')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('center')\" title=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('alignRight')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('right')\" title=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('justify')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('justify')\" title=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('indent', 'outdent')\">\n <button type=\"button\" *ngIf=\"isVisible('indent')\" (mousedown)=\"$event.preventDefault()\" (click)=\"indent()\" title=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('outdent')\" (mousedown)=\"$event.preventDefault()\" (click)=\"outdent()\" title=\"Outdent\">\n <i class=\"he he-text-indent-right\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('unorderedList', 'orderedList')\">\n <button type=\"button\" *ngIf=\"isVisible('unorderedList')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ul')\" title=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('orderedList')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ol')\" title=\"Ordered list\">\n <i class=\"he he-ordered-list\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('link', 'image')\">\n <button type=\"button\" *ngIf=\"isVisible('link')\" (mousedown)=\"$event.preventDefault()\" (click)=\"createLink()\" title=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('image')\" (mousedown)=\"$event.preventDefault()\" (click)=\"insertImage()\" title=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('undo', 'redo')\">\n <button type=\"button\" *ngIf=\"isVisible('undo')\" (mousedown)=\"$event.preventDefault()\" (click)=\"undo()\" title=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('redo')\" (mousedown)=\"$event.preventDefault()\" (click)=\"redo()\" title=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"isVisible('clearFormatting')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"clearFormatting()\" title=\"Clear Formatting\">\n <i class=\"he he-text-clear-format\"></i>\n </button>\n </div>\n </div>\n\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"(readonly || disabled) ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\n [attr.data-placeholder]=\"placeholder\">\n </div>\n\n <!-- Custom Modal for Link/Image URL -->\n <div class=\"editor-modal-overlay\" *ngIf=\"showModal\" (click)=\"cancelModal()\">\n <div class=\"editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"editor-modal-header\">\n <span>{{ modalType === 'link' ? 'Insert Link' : 'Insert Image' }}</span>\n <button type=\"button\" class=\"editor-modal-close\" (click)=\"cancelModal()\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n <div class=\"editor-modal-body\">\n <div class=\"url-list\">\n <div class=\"url-row\" *ngFor=\"let url of modalUrls; let i = index; trackBy: trackByIndex\">\n <div class=\"url-field\">\n <input type=\"text\" class=\"url-input\" [class.invalid]=\"isRowInvalid(modalUrls[i])\"\n [ngModel]=\"modalUrls[i]\"\n (ngModelChange)=\"modalUrls[i] = $event\"\n [placeholder]=\"modalType === 'link' ? 'https://example.com' : 'https://example.com/image.png'\" />\n <span class=\"url-error\" *ngIf=\"isRowInvalid(modalUrls[i])\">Enter a valid URL</span>\n </div>\n <button type=\"button\" class=\"url-remove-btn\" *ngIf=\"modalUrls.length > 1\" (click)=\"removeUrlRow(i)\" title=\"Remove\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n </div>\n <button type=\"button\" class=\"url-add-btn\" (click)=\"addUrlRow()\">\n + Add more\n </button>\n </div>\n <div class=\"editor-modal-footer\">\n <button type=\"button\" class=\"modal-btn cancel-btn\" (click)=\"cancelModal()\">Cancel</button>\n <button type=\"button\" class=\"modal-btn confirm-btn\" [disabled]=\"!hasValidUrl\" (click)=\"confirmModal()\">Insert</button>\n </div>\n </div>\n </div>\n</div>\n", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button{cursor:pointer;min-width:25px;min-height:20px;border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items button:hover i{color:#000}.text-editor .toolbar .toolbar-items .color-tool{position:relative}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .color-popover{top:calc(100% + 6px);left:0;z-index:30;padding:8px;position:absolute;background:#fff;border-radius:6px;border:1px solid #e0e0e0;box-shadow:0 4px 16px #00000026}.text-editor .color-popover .color-grid{gap:4px;display:grid;grid-template-columns:repeat(8,18px)}.text-editor .color-popover .color-swatch{width:18px;height:18px;padding:0;cursor:pointer;border-radius:3px;border:1px solid rgba(0,0,0,.15);transition:transform .1s ease}.text-editor .color-popover .color-swatch:hover{transform:scale(1.15)}.text-editor .color-popover .color-custom{gap:8px;display:flex;margin-top:8px;font-size:12px;color:#555;cursor:pointer;align-items:center;justify-content:space-between}.text-editor .color-popover .color-custom input[type=color]{width:28px;height:20px;padding:0;cursor:pointer;background:none;border:1px solid #cccccc}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}.text-editor .editor-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000}.text-editor .editor-modal{background:#fff;border-radius:8px;box-shadow:0 8px 32px #0003;width:420px;max-width:90vw;animation:modalFadeIn .2s ease}@keyframes modalFadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.text-editor .editor-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e8e8e8}.text-editor .editor-modal-header span{font-size:15px;font-weight:600;color:#1a1a1a}.text-editor .editor-modal-close{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;display:grid;place-items:center}.text-editor .editor-modal-close i{font-size:14px;color:#666}.text-editor .editor-modal-close:hover{background:#f0f0f0}.text-editor .editor-modal-close:hover i{color:#333}.text-editor .editor-modal-body{padding:16px 18px}.text-editor .editor-modal-body .url-list{max-height:200px;overflow-y:auto;margin-bottom:10px;padding-right:2px}.text-editor .editor-modal-body .url-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:10px}.text-editor .editor-modal-body .url-field{flex:1;display:flex;flex-direction:column;gap:2px}.text-editor .editor-modal-body .url-input{width:100%;padding:8px 12px;border:1px solid #d0d0d0;border-radius:6px;font-size:13px;color:#333;outline:none;transition:border-color .2s}.text-editor .editor-modal-body .url-input:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff26}.text-editor .editor-modal-body .url-input::placeholder{color:#aaa}.text-editor .editor-modal-body .url-input.invalid{border-color:#e04040}.text-editor .editor-modal-body .url-input.invalid:focus{border-color:#e04040;box-shadow:0 0 0 2px #e0404026}.text-editor .editor-modal-body .url-error{font-size:11px;color:#e04040}.text-editor .editor-modal-body .url-remove-btn{background:none;border:1px solid #e0e0e0;border-radius:6px;cursor:pointer;padding:8px 9px;display:grid;place-items:center;transition:all .15s}.text-editor .editor-modal-body .url-remove-btn i{font-size:11px;color:#999}.text-editor .editor-modal-body .url-remove-btn:hover{background:#fff0f0;border-color:#faa}.text-editor .editor-modal-body .url-remove-btn:hover i{color:#e04040}.text-editor .editor-modal-body .url-add-btn{background:none;border:1px dashed #c0c0c0;border-radius:6px;cursor:pointer;padding:6px 14px;font-size:12px;color:#007bff;transition:all .15s;width:100%}.text-editor .editor-modal-body .url-add-btn:hover{background:#f0f8ff;border-color:#007bff}.text-editor .editor-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:12px 18px;border-top:1px solid #e8e8e8}.text-editor .editor-modal-footer .modal-btn{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s;border:1px solid transparent}.text-editor .editor-modal-footer .cancel-btn{background:#f5f5f5;color:#555;border-color:#ddd}.text-editor .editor-modal-footer .cancel-btn:hover{background:#eaeaea}.text-editor .editor-modal-footer .confirm-btn{background:#007bff;color:#fff}.text-editor .editor-modal-footer .confirm-btn:hover{background:#0069d9}.text-editor .editor-modal-footer .confirm-btn:disabled{opacity:.5;cursor:not-allowed}.text-editor .editor-modal-footer .confirm-btn:disabled:hover{background:#007bff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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"] }] });
4203
4233
  }
4204
4234
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: RichTextEditor, decorators: [{
4205
4235
  type: Component,
@@ -4209,12 +4239,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4209
4239
  useExisting: forwardRef(() => RichTextEditor),
4210
4240
  multi: true,
4211
4241
  },
4212
- ], template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly || disabled}\">\n <div class=\"toolbar\" *ngIf=\"!readonly && !disabled\">\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('bold')\" title=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('italic')\" title=\"Italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('underline')\" title=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"header\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h1')\" title=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h2')\" title=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\">\n <div class=\"color-tool\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('text')\" title=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'text'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('text', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('text', $event)\" />\n </label>\n </div>\n </div>\n <div class=\"color-tool\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('background')\" title=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'background'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('background', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('background', $event)\" />\n </label>\n </div>\n </div>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('left')\" title=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('center')\" title=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('right')\" title=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('justify')\" title=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"indent()\" title=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"outdent()\" title=\"Outdent\">\n <i class=\"he he-text-indent-right\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"media\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ul')\" title=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ol')\" title=\"Ordered list\">\n <i class=\"he he-ordered-list\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"link\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"createLink()\" title=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"insertImage()\" title=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"undo()\" title=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"redo()\" title=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"clearFormatting()\" title=\"Clear Formatting\">\n <i class=\"he he-text-clear-format\"></i>\n </button>\n </div>\n </div>\n\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"(readonly || disabled) ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\n [attr.data-placeholder]=\"placeholder\">\n </div>\n\n <!-- Custom Modal for Link/Image URL -->\n <div class=\"editor-modal-overlay\" *ngIf=\"showModal\" (click)=\"cancelModal()\">\n <div class=\"editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"editor-modal-header\">\n <span>{{ modalType === 'link' ? 'Insert Link' : 'Insert Image' }}</span>\n <button type=\"button\" class=\"editor-modal-close\" (click)=\"cancelModal()\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n <div class=\"editor-modal-body\">\n <div class=\"url-list\">\n <div class=\"url-row\" *ngFor=\"let url of modalUrls; let i = index; trackBy: trackByIndex\">\n <div class=\"url-field\">\n <input type=\"text\" class=\"url-input\" [class.invalid]=\"isRowInvalid(modalUrls[i])\"\n [ngModel]=\"modalUrls[i]\"\n (ngModelChange)=\"modalUrls[i] = $event\"\n [placeholder]=\"modalType === 'link' ? 'https://example.com' : 'https://example.com/image.png'\" />\n <span class=\"url-error\" *ngIf=\"isRowInvalid(modalUrls[i])\">Enter a valid URL</span>\n </div>\n <button type=\"button\" class=\"url-remove-btn\" *ngIf=\"modalUrls.length > 1\" (click)=\"removeUrlRow(i)\" title=\"Remove\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n </div>\n <button type=\"button\" class=\"url-add-btn\" (click)=\"addUrlRow()\">\n + Add more\n </button>\n </div>\n <div class=\"editor-modal-footer\">\n <button type=\"button\" class=\"modal-btn cancel-btn\" (click)=\"cancelModal()\">Cancel</button>\n <button type=\"button\" class=\"modal-btn confirm-btn\" [disabled]=\"!hasValidUrl\" (click)=\"confirmModal()\">Insert</button>\n </div>\n </div>\n </div>\n</div>\n", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button{cursor:pointer;min-width:25px;min-height:20px;border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items button:hover i{color:#000}.text-editor .toolbar .toolbar-items .color-tool{position:relative}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .color-popover{top:calc(100% + 6px);left:0;z-index:30;padding:8px;position:absolute;background:#fff;border-radius:6px;border:1px solid #e0e0e0;box-shadow:0 4px 16px #00000026}.text-editor .color-popover .color-grid{gap:4px;display:grid;grid-template-columns:repeat(8,18px)}.text-editor .color-popover .color-swatch{width:18px;height:18px;padding:0;cursor:pointer;border-radius:3px;border:1px solid rgba(0,0,0,.15);transition:transform .1s ease}.text-editor .color-popover .color-swatch:hover{transform:scale(1.15)}.text-editor .color-popover .color-custom{gap:8px;display:flex;margin-top:8px;font-size:12px;color:#555;cursor:pointer;align-items:center;justify-content:space-between}.text-editor .color-popover .color-custom input[type=color]{width:28px;height:20px;padding:0;cursor:pointer;background:none;border:1px solid #cccccc}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}.text-editor .editor-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000}.text-editor .editor-modal{background:#fff;border-radius:8px;box-shadow:0 8px 32px #0003;width:420px;max-width:90vw;animation:modalFadeIn .2s ease}@keyframes modalFadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.text-editor .editor-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e8e8e8}.text-editor .editor-modal-header span{font-size:15px;font-weight:600;color:#1a1a1a}.text-editor .editor-modal-close{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;display:grid;place-items:center}.text-editor .editor-modal-close i{font-size:14px;color:#666}.text-editor .editor-modal-close:hover{background:#f0f0f0}.text-editor .editor-modal-close:hover i{color:#333}.text-editor .editor-modal-body{padding:16px 18px}.text-editor .editor-modal-body .url-list{max-height:200px;overflow-y:auto;margin-bottom:10px;padding-right:2px}.text-editor .editor-modal-body .url-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:10px}.text-editor .editor-modal-body .url-field{flex:1;display:flex;flex-direction:column;gap:2px}.text-editor .editor-modal-body .url-input{width:100%;padding:8px 12px;border:1px solid #d0d0d0;border-radius:6px;font-size:13px;color:#333;outline:none;transition:border-color .2s}.text-editor .editor-modal-body .url-input:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff26}.text-editor .editor-modal-body .url-input::placeholder{color:#aaa}.text-editor .editor-modal-body .url-input.invalid{border-color:#e04040}.text-editor .editor-modal-body .url-input.invalid:focus{border-color:#e04040;box-shadow:0 0 0 2px #e0404026}.text-editor .editor-modal-body .url-error{font-size:11px;color:#e04040}.text-editor .editor-modal-body .url-remove-btn{background:none;border:1px solid #e0e0e0;border-radius:6px;cursor:pointer;padding:8px 9px;display:grid;place-items:center;transition:all .15s}.text-editor .editor-modal-body .url-remove-btn i{font-size:11px;color:#999}.text-editor .editor-modal-body .url-remove-btn:hover{background:#fff0f0;border-color:#faa}.text-editor .editor-modal-body .url-remove-btn:hover i{color:#e04040}.text-editor .editor-modal-body .url-add-btn{background:none;border:1px dashed #c0c0c0;border-radius:6px;cursor:pointer;padding:6px 14px;font-size:12px;color:#007bff;transition:all .15s;width:100%}.text-editor .editor-modal-body .url-add-btn:hover{background:#f0f8ff;border-color:#007bff}.text-editor .editor-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:12px 18px;border-top:1px solid #e8e8e8}.text-editor .editor-modal-footer .modal-btn{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s;border:1px solid transparent}.text-editor .editor-modal-footer .cancel-btn{background:#f5f5f5;color:#555;border-color:#ddd}.text-editor .editor-modal-footer .cancel-btn:hover{background:#eaeaea}.text-editor .editor-modal-footer .confirm-btn{background:#007bff;color:#fff}.text-editor .editor-modal-footer .confirm-btn:hover{background:#0069d9}.text-editor .editor-modal-footer .confirm-btn:disabled{opacity:.5;cursor:not-allowed}.text-editor .editor-modal-footer .confirm-btn:disabled:hover{background:#007bff}\n"] }]
4213
- }], propDecorators: { header: [{
4214
- type: Input
4215
- }], media: [{
4216
- type: Input
4217
- }], link: [{
4242
+ ], template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly || disabled}\">\n <div class=\"toolbar\" *ngIf=\"!readonly && !disabled\">\n <div class=\"toolbar-items\" *ngIf=\"hasAny('bold', 'italic', 'underline')\">\n <button type=\"button\" *ngIf=\"isVisible('bold')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('bold')\" title=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('italic')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('italic')\" title=\"Italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('underline')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyInlineStyle('underline')\" title=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('h1', 'h2')\">\n <button type=\"button\" *ngIf=\"isVisible('h1')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h1')\" title=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('h2')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyBlockFormat('h2')\" title=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\" *ngIf=\"hasAny('textColor', 'backgroundColor')\">\n <div class=\"color-tool\" *ngIf=\"isVisible('textColor')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('text')\" title=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'text'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('text', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('text', $event)\" />\n </label>\n </div>\n </div>\n <div class=\"color-tool\" *ngIf=\"isVisible('backgroundColor')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"toggleColorMenu('background')\" title=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n </button>\n <div class=\"color-popover\" *ngIf=\"colorMenu === 'background'\">\n <div class=\"color-grid\">\n <button type=\"button\" class=\"color-swatch\" *ngFor=\"let c of palette\" [style.backgroundColor]=\"c\"\n [title]=\"c\" (mousedown)=\"$event.preventDefault()\" (click)=\"pickColor('background', c)\"></button>\n </div>\n <label class=\"color-custom\">\n <span>Custom</span>\n <input type=\"color\" (change)=\"pickCustomColor('background', $event)\" />\n </label>\n </div>\n </div>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('alignLeft', 'alignCenter', 'alignRight', 'justify')\">\n <button type=\"button\" *ngIf=\"isVisible('alignLeft')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('left')\" title=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('alignCenter')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('center')\" title=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('alignRight')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('right')\" title=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('justify')\" (mousedown)=\"$event.preventDefault()\" (click)=\"setAlignment('justify')\" title=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('indent', 'outdent')\">\n <button type=\"button\" *ngIf=\"isVisible('indent')\" (mousedown)=\"$event.preventDefault()\" (click)=\"indent()\" title=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('outdent')\" (mousedown)=\"$event.preventDefault()\" (click)=\"outdent()\" title=\"Outdent\">\n <i class=\"he he-text-indent-right\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('unorderedList', 'orderedList')\">\n <button type=\"button\" *ngIf=\"isVisible('unorderedList')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ul')\" title=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('orderedList')\" (mousedown)=\"$event.preventDefault()\" (click)=\"applyList('ol')\" title=\"Ordered list\">\n <i class=\"he he-ordered-list\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('link', 'image')\">\n <button type=\"button\" *ngIf=\"isVisible('link')\" (mousedown)=\"$event.preventDefault()\" (click)=\"createLink()\" title=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('image')\" (mousedown)=\"$event.preventDefault()\" (click)=\"insertImage()\" title=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"hasAny('undo', 'redo')\">\n <button type=\"button\" *ngIf=\"isVisible('undo')\" (mousedown)=\"$event.preventDefault()\" (click)=\"undo()\" title=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" *ngIf=\"isVisible('redo')\" (mousedown)=\"$event.preventDefault()\" (click)=\"redo()\" title=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"isVisible('clearFormatting')\">\n <button type=\"button\" (mousedown)=\"$event.preventDefault()\" (click)=\"clearFormatting()\" title=\"Clear Formatting\">\n <i class=\"he he-text-clear-format\"></i>\n </button>\n </div>\n </div>\n\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"(readonly || disabled) ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\n [attr.data-placeholder]=\"placeholder\">\n </div>\n\n <!-- Custom Modal for Link/Image URL -->\n <div class=\"editor-modal-overlay\" *ngIf=\"showModal\" (click)=\"cancelModal()\">\n <div class=\"editor-modal\" (click)=\"$event.stopPropagation()\">\n <div class=\"editor-modal-header\">\n <span>{{ modalType === 'link' ? 'Insert Link' : 'Insert Image' }}</span>\n <button type=\"button\" class=\"editor-modal-close\" (click)=\"cancelModal()\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n <div class=\"editor-modal-body\">\n <div class=\"url-list\">\n <div class=\"url-row\" *ngFor=\"let url of modalUrls; let i = index; trackBy: trackByIndex\">\n <div class=\"url-field\">\n <input type=\"text\" class=\"url-input\" [class.invalid]=\"isRowInvalid(modalUrls[i])\"\n [ngModel]=\"modalUrls[i]\"\n (ngModelChange)=\"modalUrls[i] = $event\"\n [placeholder]=\"modalType === 'link' ? 'https://example.com' : 'https://example.com/image.png'\" />\n <span class=\"url-error\" *ngIf=\"isRowInvalid(modalUrls[i])\">Enter a valid URL</span>\n </div>\n <button type=\"button\" class=\"url-remove-btn\" *ngIf=\"modalUrls.length > 1\" (click)=\"removeUrlRow(i)\" title=\"Remove\">\n <i class=\"he he-close\"></i>\n </button>\n </div>\n </div>\n <button type=\"button\" class=\"url-add-btn\" (click)=\"addUrlRow()\">\n + Add more\n </button>\n </div>\n <div class=\"editor-modal-footer\">\n <button type=\"button\" class=\"modal-btn cancel-btn\" (click)=\"cancelModal()\">Cancel</button>\n <button type=\"button\" class=\"modal-btn confirm-btn\" [disabled]=\"!hasValidUrl\" (click)=\"confirmModal()\">Insert</button>\n </div>\n </div>\n </div>\n</div>\n", styles: [".text-editor{border:1px solid #ccc}.text-editor .toolbar{gap:.75rem;display:flex;flex-wrap:wrap;background:#f8f8f8;padding:.75rem;border-bottom:1px solid #cccccc}.text-editor .toolbar .toolbar-items{height:18px;display:flex;flex-wrap:wrap;align-items:center;padding-right:.75rem;border-right:1px solid #969090}.text-editor .toolbar .toolbar-items button{cursor:pointer;min-width:25px;min-height:20px;border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items button:hover i{color:#000}.text-editor .toolbar .toolbar-items .color-tool{position:relative}.text-editor .toolbar .toolbar-items i{font-size:13px;color:#3c4148}.text-editor .toolbar .toolbar-items i.he-bold{font-weight:600}.text-editor .toolbar .toolbar-items i.he-underline{font-size:14px}.text-editor .toolbar .toolbar-items i.he-text-drop{font-size:12px}.text-editor .toolbar .toolbar-items i.he-background-drop{top:-1px;font-size:12px}.text-editor .toolbar .toolbar-items i.he-heading-1,.text-editor .toolbar .toolbar-items i.he-link,.text-editor .toolbar .toolbar-items i.he-left-align,.text-editor .toolbar .toolbar-items i.he-center-align,.text-editor .toolbar .toolbar-items i.he-right-align,.text-editor .toolbar .toolbar-items i.he-justify{font-size:16px}.text-editor .toolbar .toolbar-items i.he-text-indent-left,.text-editor .toolbar .toolbar-items i.he-text-indent-right{font-size:17px}.text-editor .toolbar .toolbar-items i.he-heading-2{top:1px;font-size:16px}.text-editor .toolbar .toolbar-items i.he-unordered-list,.text-editor .toolbar .toolbar-items i.he-ordered-list{top:-1px;font-size:20px}.text-editor .toolbar .toolbar-items i.he-image{font-size:15px;font-weight:600}.text-editor .toolbar .toolbar-items i.he-redo,.text-editor .toolbar .toolbar-items i.he-undo{top:-1px;font-size:14px}.text-editor .toolbar .toolbar-items.more-gap{gap:5px}.text-editor .toolbar .toolbar-items:last-child{border-right:0}.text-editor .color-popover{top:calc(100% + 6px);left:0;z-index:30;padding:8px;position:absolute;background:#fff;border-radius:6px;border:1px solid #e0e0e0;box-shadow:0 4px 16px #00000026}.text-editor .color-popover .color-grid{gap:4px;display:grid;grid-template-columns:repeat(8,18px)}.text-editor .color-popover .color-swatch{width:18px;height:18px;padding:0;cursor:pointer;border-radius:3px;border:1px solid rgba(0,0,0,.15);transition:transform .1s ease}.text-editor .color-popover .color-swatch:hover{transform:scale(1.15)}.text-editor .color-popover .color-custom{gap:8px;display:flex;margin-top:8px;font-size:12px;color:#555;cursor:pointer;align-items:center;justify-content:space-between}.text-editor .color-popover .color-custom input[type=color]{width:28px;height:20px;padding:0;cursor:pointer;background:none;border:1px solid #cccccc}.text-editor .editor-content{outline:none;min-height:200px;position:relative;background:#fff;padding:.75rem}.text-editor .editor-content[data-placeholder]:empty:before{content:attr(data-placeholder);color:#aaa;pointer-events:none;position:absolute;left:.75rem;top:.75rem}.text-editor .editor-modal-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:#00000073;display:flex;align-items:center;justify-content:center;z-index:10000}.text-editor .editor-modal{background:#fff;border-radius:8px;box-shadow:0 8px 32px #0003;width:420px;max-width:90vw;animation:modalFadeIn .2s ease}@keyframes modalFadeIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.text-editor .editor-modal-header{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e8e8e8}.text-editor .editor-modal-header span{font-size:15px;font-weight:600;color:#1a1a1a}.text-editor .editor-modal-close{background:none;border:none;cursor:pointer;padding:4px;border-radius:4px;display:grid;place-items:center}.text-editor .editor-modal-close i{font-size:14px;color:#666}.text-editor .editor-modal-close:hover{background:#f0f0f0}.text-editor .editor-modal-close:hover i{color:#333}.text-editor .editor-modal-body{padding:16px 18px}.text-editor .editor-modal-body .url-list{max-height:200px;overflow-y:auto;margin-bottom:10px;padding-right:2px}.text-editor .editor-modal-body .url-row{display:flex;align-items:flex-start;gap:8px;margin-bottom:10px}.text-editor .editor-modal-body .url-field{flex:1;display:flex;flex-direction:column;gap:2px}.text-editor .editor-modal-body .url-input{width:100%;padding:8px 12px;border:1px solid #d0d0d0;border-radius:6px;font-size:13px;color:#333;outline:none;transition:border-color .2s}.text-editor .editor-modal-body .url-input:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff26}.text-editor .editor-modal-body .url-input::placeholder{color:#aaa}.text-editor .editor-modal-body .url-input.invalid{border-color:#e04040}.text-editor .editor-modal-body .url-input.invalid:focus{border-color:#e04040;box-shadow:0 0 0 2px #e0404026}.text-editor .editor-modal-body .url-error{font-size:11px;color:#e04040}.text-editor .editor-modal-body .url-remove-btn{background:none;border:1px solid #e0e0e0;border-radius:6px;cursor:pointer;padding:8px 9px;display:grid;place-items:center;transition:all .15s}.text-editor .editor-modal-body .url-remove-btn i{font-size:11px;color:#999}.text-editor .editor-modal-body .url-remove-btn:hover{background:#fff0f0;border-color:#faa}.text-editor .editor-modal-body .url-remove-btn:hover i{color:#e04040}.text-editor .editor-modal-body .url-add-btn{background:none;border:1px dashed #c0c0c0;border-radius:6px;cursor:pointer;padding:6px 14px;font-size:12px;color:#007bff;transition:all .15s;width:100%}.text-editor .editor-modal-body .url-add-btn:hover{background:#f0f8ff;border-color:#007bff}.text-editor .editor-modal-footer{display:flex;justify-content:flex-end;gap:10px;padding:12px 18px;border-top:1px solid #e8e8e8}.text-editor .editor-modal-footer .modal-btn{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;cursor:pointer;transition:all .15s;border:1px solid transparent}.text-editor .editor-modal-footer .cancel-btn{background:#f5f5f5;color:#555;border-color:#ddd}.text-editor .editor-modal-footer .cancel-btn:hover{background:#eaeaea}.text-editor .editor-modal-footer .confirm-btn{background:#007bff;color:#fff}.text-editor .editor-modal-footer .confirm-btn:hover{background:#0069d9}.text-editor .editor-modal-footer .confirm-btn:disabled{opacity:.5;cursor:not-allowed}.text-editor .editor-modal-footer .confirm-btn:disabled:hover{background:#007bff}\n"] }]
4243
+ }], propDecorators: { features: [{
4218
4244
  type: Input
4219
4245
  }], placeholder: [{
4220
4246
  type: Input
@@ -4343,5 +4369,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImpo
4343
4369
  * Generated bundle index. Do not edit.
4344
4370
  */
4345
4371
 
4346
- export { AutocompleteControl, CalendarControl, CheckboxControl, ColorPicker, InputControl, MultiselectControl, PaginatedAutocompleteControl, RadioControl, RichTextEditor, SelectControl, SwitchControl, TextareaControl };
4372
+ export { AutocompleteControl, CalendarControl, CheckboxControl, ColorPicker, InputControl, MultiselectControl, PaginatedAutocompleteControl, RICH_TEXT_EDITOR_DEFAULT_FEATURES, RadioControl, RichTextEditor, SelectControl, SwitchControl, TextareaControl };
4347
4373
  //# sourceMappingURL=nexheal-lib.mjs.map