nexheal-lib 0.0.10 → 0.0.12

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.
@@ -386,7 +386,21 @@ class CalendarControl {
386
386
  timeOnly = false;
387
387
  dateFormat = "dd/MM/yyyy";
388
388
  placeholder = "dd-mm-yyyy";
389
- disabled = false;
389
+ _disabled = false;
390
+ get disabled() {
391
+ return this._disabled;
392
+ }
393
+ set disabled(value) {
394
+ this._disabled = value;
395
+ if (this.inputControl) {
396
+ if (value) {
397
+ this.inputControl.disable({ emitEvent: false });
398
+ }
399
+ else {
400
+ this.inputControl.enable({ emitEvent: false });
401
+ }
402
+ }
403
+ }
390
404
  readonly = false;
391
405
  submitted = false;
392
406
  inputPlaceholder = false;
@@ -2458,24 +2472,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImpor
2458
2472
  args: ['switchContainer']
2459
2473
  }] } });
2460
2474
 
2475
+ /**
2476
+ * Lightweight rich-text editor built on a `contenteditable` element and the
2477
+ * browser's `document.execCommand` rich-editing API (the de-facto standard for
2478
+ * simple in-browser editors).
2479
+ *
2480
+ * Implemented as a `ControlValueAccessor`, so it works with `[(ngModel)]`,
2481
+ * `formControlName` and `[formControl]`. The emitted value is the editor's
2482
+ * inner HTML string.
2483
+ */
2461
2484
  class TextEditor {
2462
2485
  header = true;
2463
2486
  media = true;
2464
2487
  link = true;
2465
2488
  placeholder = 'Type here...';
2466
2489
  readonly = false;
2490
+ /** Disabled state, set by Reactive Forms through `setDisabledState`. */
2491
+ disabled = false;
2467
2492
  editorRef;
2468
2493
  onChange = () => { };
2469
2494
  onTouch = () => { };
2470
- undoStack = [];
2471
- redoStack = [];
2472
- ngOnInit() { }
2495
+ /** HTML received via `writeValue` before the view (and `editorRef`) is ready. */
2496
+ pendingValue = '';
2497
+ viewInitialized = false;
2498
+ /** Last selection range observed inside the editor (restored before commands). */
2499
+ savedRange = null;
2473
2500
  ngAfterViewInit() {
2474
- this.saveState();
2501
+ this.viewInitialized = true;
2502
+ this.editorRef.nativeElement.innerHTML = this.pendingValue;
2475
2503
  }
2504
+ // ---- ControlValueAccessor ----
2476
2505
  writeValue(value) {
2477
- this.editorRef.nativeElement.innerHTML = value || '';
2478
- this.saveState();
2506
+ this.pendingValue = value ?? '';
2507
+ if (this.viewInitialized && this.editorRef) {
2508
+ this.editorRef.nativeElement.innerHTML = this.pendingValue;
2509
+ }
2479
2510
  }
2480
2511
  registerOnChange(fn) {
2481
2512
  this.onChange = fn;
@@ -2483,256 +2514,126 @@ class TextEditor {
2483
2514
  registerOnTouched(fn) {
2484
2515
  this.onTouch = fn;
2485
2516
  }
2517
+ setDisabledState(isDisabled) {
2518
+ this.disabled = isDisabled;
2519
+ }
2520
+ // ---- Editor events (wired from the template) ----
2486
2521
  onInput() {
2487
- const html = this.editorRef.nativeElement.innerHTML;
2488
- this.onChange(html);
2489
- this.saveState();
2522
+ this.onChange(this.editorRef.nativeElement.innerHTML);
2490
2523
  }
2491
- // events
2492
2524
  onTouched() {
2493
2525
  this.onTouch();
2494
2526
  }
2495
- setDisabledState(isDisabled) {
2496
- this.editorRef.nativeElement.contentEditable = (!isDisabled).toString();
2497
- }
2498
- // ---- Selection & Range Utilities ----
2499
- getSelectionRange() {
2527
+ /** Remember the caret/selection whenever it sits inside the editor. */
2528
+ saveSelection() {
2500
2529
  const selection = window.getSelection();
2501
- if (selection && selection.rangeCount > 0) {
2502
- return selection.getRangeAt(0);
2503
- }
2504
- return null;
2505
- }
2506
- wrapSelection(htmlTag, style) {
2507
- const range = this.getSelectionRange();
2508
- if (!range || range.collapsed)
2509
- return; // no selection
2510
- const selectedText = range.extractContents();
2511
- const el = document.createElement(htmlTag);
2512
- if (style) {
2513
- el.setAttribute('style', style);
2514
- }
2515
- el.appendChild(selectedText);
2516
- range.insertNode(el);
2517
- this.mergeAdjacentSimilarElements(el);
2518
- this.onInput();
2519
- }
2520
- // Merges adjacent spans with the same style to avoid clutter
2521
- mergeAdjacentSimilarElements(el) {
2522
- const parent = el.parentElement;
2523
- if (!parent)
2530
+ if (!selection || selection.rangeCount === 0 || !this.editorRef)
2524
2531
  return;
2525
- const siblings = Array.from(parent.childNodes);
2526
- for (let i = 0; i < siblings.length - 1; i++) {
2527
- const current = siblings[i];
2528
- const next = siblings[i + 1];
2529
- if (current.nodeType === 1 && next?.nodeType === 1) {
2530
- if (current.tagName === next.tagName && current.getAttribute('style') === next.getAttribute('style')) {
2531
- // merge them
2532
- while (next.firstChild) {
2533
- current.appendChild(next.firstChild);
2534
- }
2535
- next.remove();
2536
- }
2537
- }
2532
+ const range = selection.getRangeAt(0);
2533
+ if (this.editorRef.nativeElement.contains(range.commonAncestorContainer)) {
2534
+ this.savedRange = range.cloneRange();
2538
2535
  }
2539
2536
  }
2540
- // ---- Inline Formatting ----
2541
- // applyInlineStyle() toggles inline styles by wrapping selection in a styled span
2542
- applyInlineStyle(styleType) {
2543
- let style = '';
2544
- if (styleType === 'bold')
2545
- style = 'font-weight:bold;';
2546
- else if (styleType === 'italic')
2547
- style = 'font-style:italic;';
2548
- else if (styleType === 'underline')
2549
- style = 'text-decoration:underline;';
2550
- this.wrapSelection('span', style);
2537
+ // ---- Inline formatting ----
2538
+ applyInlineStyle(style) {
2539
+ this.exec(style);
2551
2540
  }
2552
- applyColor(event) {
2553
- const input = event.target;
2554
- const color = input.value;
2555
- // Now `color` is a string and `input` is never null
2556
- this.wrapSelection('span', `color:${color};`);
2541
+ // ---- Block formatting (headings / paragraph) ----
2542
+ applyBlockFormat(block) {
2543
+ this.exec('formatBlock', `<${block}>`);
2557
2544
  }
2558
- applyHighlight(event) {
2559
- const input = event.target;
2560
- const color = input.value;
2561
- this.wrapSelection('span', `background-color:${color};`);
2562
- }
2563
- // ---- Block Formatting (Headings, Paragraph) ----
2564
- // Replaces the parent block element containing selection with a new block type
2565
- applyBlockFormat(blockTag) {
2566
- const range = this.getSelectionRange();
2567
- if (!range)
2568
- return;
2569
- // Find the nearest block element
2570
- let block = this.findBlockAncestor(range.commonAncestorContainer);
2571
- if (!block) {
2572
- // If no block found, wrap the selection in a block
2573
- const wrapper = document.createElement(blockTag);
2574
- wrapper.appendChild(range.extractContents());
2575
- range.insertNode(wrapper);
2576
- }
2577
- else {
2578
- // Replace the block with a new block type
2579
- const newBlock = document.createElement(blockTag);
2580
- // Move children
2581
- while (block.firstChild) {
2582
- newBlock.appendChild(block.firstChild);
2583
- }
2584
- block.replaceWith(newBlock);
2585
- }
2586
- this.onInput();
2545
+ // ---- Lists ----
2546
+ applyList(listType) {
2547
+ this.exec(listType === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');
2587
2548
  }
2588
- findBlockAncestor(node) {
2589
- while (node && node !== this.editorRef.nativeElement) {
2590
- if (this.isBlockElement(node)) {
2591
- return node;
2592
- }
2593
- node = node.parentNode;
2594
- }
2595
- return null;
2549
+ // ---- Indentation ----
2550
+ indent() {
2551
+ this.exec('indent');
2596
2552
  }
2597
- isBlockElement(node) {
2598
- if (node.nodeType !== 1)
2599
- return false;
2600
- const display = window.getComputedStyle(node).display;
2601
- return display === 'block' || display === 'list-item';
2553
+ outdent() {
2554
+ this.exec('outdent');
2602
2555
  }
2603
- // ---- Lists ----
2604
- // Convert selected lines into a list
2605
- applyList(listType) {
2606
- const range = this.getSelectionRange();
2607
- if (!range)
2608
- return;
2609
- const commonBlock = this.findBlockAncestor(range.commonAncestorContainer);
2610
- // Get the text in the selection
2611
- const content = range.extractContents();
2612
- const lines = this.splitContentByLine(content);
2613
- const listEl = document.createElement(listType);
2614
- for (const line of lines) {
2615
- const li = document.createElement('li');
2616
- li.appendChild(line);
2617
- listEl.appendChild(li);
2618
- }
2619
- if (commonBlock) {
2620
- // Insert the list right where the block was or at the selection
2621
- range.insertNode(listEl);
2622
- }
2623
- else {
2624
- // If no common block, just insert at current position
2625
- range.insertNode(listEl);
2626
- }
2627
- this.onInput();
2556
+ // ---- Alignment ----
2557
+ setAlignment(alignment) {
2558
+ const command = {
2559
+ left: 'justifyLeft',
2560
+ center: 'justifyCenter',
2561
+ right: 'justifyRight',
2562
+ justify: 'justifyFull',
2563
+ }[alignment];
2564
+ this.exec(command);
2565
+ }
2566
+ // ---- Color / highlight ----
2567
+ applyColor(event) {
2568
+ this.exec('foreColor', event.target.value, true);
2628
2569
  }
2629
- splitContentByLine(fragment) {
2630
- // A very naive approach: split by <br> or block elements.
2631
- // For advanced logic, detect line breaks more thoroughly.
2632
- const lines = [];
2633
- let currentFrag = document.createDocumentFragment();
2634
- Array.from(fragment.childNodes).forEach((node) => {
2635
- if (node.nodeName === 'BR' || this.isBlockElement(node)) {
2636
- // This node signifies a new line
2637
- lines.push(currentFrag);
2638
- currentFrag = document.createDocumentFragment();
2639
- if (this.isBlockElement(node)) {
2640
- while (node.firstChild) {
2641
- currentFrag.appendChild(node.firstChild);
2642
- }
2643
- lines.push(currentFrag);
2644
- currentFrag = document.createDocumentFragment();
2645
- }
2646
- }
2647
- else {
2648
- currentFrag.appendChild(node);
2649
- }
2650
- });
2651
- if (currentFrag.childNodes.length > 0) {
2652
- lines.push(currentFrag);
2653
- }
2654
- return lines;
2570
+ applyHighlight(event) {
2571
+ this.exec('hiliteColor', event.target.value, true);
2655
2572
  }
2656
- // ---- Links ----
2573
+ // ---- Links / images ----
2657
2574
  createLink() {
2658
2575
  const url = prompt('Enter URL', 'https://');
2659
2576
  if (!url)
2660
2577
  return;
2661
- const range = this.getSelectionRange();
2662
- if (!range || range.collapsed)
2663
- return;
2664
- const selectedContent = range.extractContents();
2665
- const a = document.createElement('a');
2666
- a.href = url;
2667
- a.appendChild(selectedContent);
2668
- range.insertNode(a);
2669
- this.onInput();
2578
+ this.exec('createLink', url);
2670
2579
  }
2671
- // ---- Images ----
2672
2580
  insertImage() {
2673
2581
  const url = prompt('Enter image URL:');
2674
2582
  if (!url)
2675
2583
  return;
2676
- const range = this.getSelectionRange();
2677
- if (!range)
2678
- return;
2679
- const img = document.createElement('img');
2680
- img.src = url;
2681
- range.insertNode(img);
2682
- this.onInput();
2584
+ this.exec('insertImage', url);
2683
2585
  }
2684
- // ---- Alignment ----
2685
- setAlignment(alignment) {
2686
- // Find the block and set text-align
2687
- const range = this.getSelectionRange();
2688
- if (!range)
2689
- return;
2690
- const block = this.findBlockAncestor(range.commonAncestorContainer);
2691
- if (block) {
2692
- block.style.textAlign = alignment;
2693
- this.onInput();
2694
- }
2586
+ // ---- Undo / redo / clear ----
2587
+ undo() {
2588
+ this.exec('undo');
2695
2589
  }
2696
- // ---- Clear Formatting ----
2697
- clearFormatting() {
2698
- const html = this.editorRef.nativeElement.innerText;
2699
- // Convert innerText to a plain <p> block for simplicity
2700
- this.editorRef.nativeElement.innerHTML = `<p>${html}</p>`;
2701
- this.onInput();
2590
+ redo() {
2591
+ this.exec('redo');
2702
2592
  }
2703
- // ---- Undo/Redo ----
2704
- saveState() {
2705
- const currentHtml = this.editorRef.nativeElement.innerHTML;
2706
- if (this.undoStack.length === 0 || this.undoStack[this.undoStack.length - 1].html !== currentHtml) {
2707
- this.undoStack.push({ html: currentHtml });
2708
- this.redoStack = []; // clear redo on new input
2709
- }
2593
+ clearFormatting() {
2594
+ this.exec('removeFormat');
2710
2595
  }
2711
- // clear options
2712
- undo() {
2713
- if (this.undoStack.length > 1) {
2714
- const current = this.undoStack.pop();
2715
- this.redoStack.push(current);
2716
- const previous = this.undoStack[this.undoStack.length - 1];
2717
- this.editorRef.nativeElement.innerHTML = previous.html;
2718
- this.onChange(previous.html);
2719
- }
2596
+ /**
2597
+ * Focus the editor, restore the last selection, run a rich-text command and
2598
+ * propagate the resulting HTML. No-op while readonly/disabled.
2599
+ */
2600
+ exec(command, value, useCss = false) {
2601
+ if (this.disabled || this.readonly)
2602
+ return;
2603
+ this.editorRef.nativeElement.focus();
2604
+ this.restoreSelection();
2605
+ // `useCss` keeps colors as inline styles; semantic tags (<b>, <i>…) otherwise.
2606
+ document.execCommand('styleWithCSS', false, String(useCss));
2607
+ document.execCommand(command, false, value);
2608
+ this.onInput();
2720
2609
  }
2721
- redo() {
2722
- if (this.redoStack.length > 0) {
2723
- const state = this.redoStack.pop();
2724
- this.undoStack.push(state);
2725
- this.editorRef.nativeElement.innerHTML = state.html;
2726
- this.onChange(state.html);
2727
- }
2610
+ restoreSelection() {
2611
+ if (!this.savedRange)
2612
+ return;
2613
+ const selection = window.getSelection();
2614
+ if (!selection)
2615
+ return;
2616
+ selection.removeAllRanges();
2617
+ selection.addRange(this.savedRange);
2728
2618
  }
2729
2619
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
2730
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: TextEditor, isStandalone: true, selector: "app-text-editor", inputs: { header: "header", media: "media", link: "link", placeholder: "placeholder", readonly: "readonly" }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly}\">\r\n <div class=\"toolbar\" *ngIf=\"!readonly\">\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\r\n <i class=\"he he-bold\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\r\n <i class=\"he he-italics\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\r\n <i class=\"he he-underline\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"header\">\r\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\r\n <i class=\"he he-heading-1\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\r\n <i class=\"he he-heading-2\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items more-gap\">\r\n <div class=\"input-wrap\" tooltip=\"Text Color\">\r\n <i class=\"he he-text-drop\"></i>\r\n <input type=\"color\" (change)=\"applyColor($event)\" />\r\n </div>\r\n <div class=\"input-wrap\" tooltip=\"Background Color\">\r\n <i class=\"he he-background-drop\"></i>\r\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\r\n </div>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\r\n <i class=\"he he-left-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\r\n <i class=\"he he-center-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\r\n <i class=\"he he-right-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\r\n <i class=\"he he-justify\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" tooltip=\"Indent\">\r\n <i class=\"he he-text-indent-left\"></i>\r\n </button>\r\n <button type=\"button\" tooltip=\"Outdent\">\r\n <i class=\"he he-text-indent-right\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"media\">\r\n <button type=\"button\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\r\n <i class=\"he he-unordered-list\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"Ordered list\">\r\n <i class=\"he he-ordered-list\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"link\">\r\n <button type=\"button\" (click)=\"createLink()\" tooltip=\"Link\">\r\n <i class=\"he he-link\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\r\n <i class=\"he he-image\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\r\n <i class=\"he he-undo\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\r\n <i class=\"he he-redo\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"Clear Formatting\">\r\n <i class=\"he he-text-clear-format\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"readonly ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\r\n [attr.data-placeholder]=\"placeholder\">\r\n </div>\r\n</div>", 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,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.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 .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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2620
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.3", type: TextEditor, isStandalone: true, selector: "app-text-editor", inputs: { header: "header", media: "media", link: "link", placeholder: "placeholder", readonly: "readonly" }, host: { listeners: { "document:selectionchange": "saveSelection()" } }, providers: [
2621
+ {
2622
+ provide: NG_VALUE_ACCESSOR,
2623
+ useExisting: forwardRef(() => TextEditor),
2624
+ multi: true,
2625
+ },
2626
+ ], 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\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"header\">\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\">\n <div class=\"input-wrap\" tooltip=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n <input type=\"color\" (change)=\"applyColor($event)\" />\n </div>\n <div class=\"input-wrap\" tooltip=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\n </div>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"indent()\" tooltip=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" (click)=\"outdent()\" tooltip=\"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\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"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\" (click)=\"createLink()\" tooltip=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"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</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,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.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 .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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
2731
2627
  }
2732
2628
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, decorators: [{
2733
2629
  type: Component,
2734
- args: [{ selector: 'app-text-editor', imports: [CommonModule,
2735
- ], template: "<div class=\"text-editor\" [ngClass]=\"{'readonly': readonly}\">\r\n <div class=\"toolbar\" *ngIf=\"!readonly\">\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\r\n <i class=\"he he-bold\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\r\n <i class=\"he he-italics\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\r\n <i class=\"he he-underline\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"header\">\r\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\r\n <i class=\"he he-heading-1\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\r\n <i class=\"he he-heading-2\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items more-gap\">\r\n <div class=\"input-wrap\" tooltip=\"Text Color\">\r\n <i class=\"he he-text-drop\"></i>\r\n <input type=\"color\" (change)=\"applyColor($event)\" />\r\n </div>\r\n <div class=\"input-wrap\" tooltip=\"Background Color\">\r\n <i class=\"he he-background-drop\"></i>\r\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\r\n </div>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\r\n <i class=\"he he-left-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\r\n <i class=\"he he-center-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\r\n <i class=\"he he-right-align\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\r\n <i class=\"he he-justify\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" tooltip=\"Indent\">\r\n <i class=\"he he-text-indent-left\"></i>\r\n </button>\r\n <button type=\"button\" tooltip=\"Outdent\">\r\n <i class=\"he he-text-indent-right\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"media\">\r\n <button type=\"button\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\r\n <i class=\"he he-unordered-list\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"Ordered list\">\r\n <i class=\"he he-ordered-list\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\" *ngIf=\"link\">\r\n <button type=\"button\" (click)=\"createLink()\" tooltip=\"Link\">\r\n <i class=\"he he-link\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\r\n <i class=\"he he-image\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\r\n <i class=\"he he-undo\"></i>\r\n </button>\r\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\r\n <i class=\"he he-redo\"></i>\r\n </button>\r\n </div>\r\n <div class=\"toolbar-items\">\r\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"Clear Formatting\">\r\n <i class=\"he he-text-clear-format\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <div #editor class=\"editor-content\" [attr.contenteditable]=\"readonly ? 'false' : 'true'\" (input)=\"onInput()\" (blur)=\"onTouched()\"\r\n [attr.data-placeholder]=\"placeholder\">\r\n </div>\r\n</div>", 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,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.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 .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}\n"] }]
2630
+ args: [{ selector: 'app-text-editor', standalone: true, imports: [CommonModule], providers: [
2631
+ {
2632
+ provide: NG_VALUE_ACCESSOR,
2633
+ useExisting: forwardRef(() => TextEditor),
2634
+ multi: true,
2635
+ },
2636
+ ], 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\" (click)=\"applyInlineStyle('bold')\" tooltip=\"Bold\">\n <i class=\"he he-bold\"></i>\n </button>\n <button type=\"button\" (click)=\"applyInlineStyle('italic')\" tooltip=\"italic\">\n <i class=\"he he-italics\"></i>\n </button>\n <button type=\"button\" (click)=\"applyInlineStyle('underline')\" tooltip=\"Underline\">\n <i class=\"he he-underline\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\" *ngIf=\"header\">\n <button type=\"button\" (click)=\"applyBlockFormat('h1')\" tooltip=\"Header 1\">\n <i class=\"he he-heading-1\"></i>\n </button>\n <button type=\"button\" (click)=\"applyBlockFormat('h2')\" tooltip=\"Header 2\">\n <i class=\"he he-heading-2\"></i>\n </button>\n </div>\n <div class=\"toolbar-items more-gap\">\n <div class=\"input-wrap\" tooltip=\"Text Color\">\n <i class=\"he he-text-drop\"></i>\n <input type=\"color\" (change)=\"applyColor($event)\" />\n </div>\n <div class=\"input-wrap\" tooltip=\"Background Color\">\n <i class=\"he he-background-drop\"></i>\n <input type=\"color\" (change)=\"applyHighlight($event)\" />\n </div>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"setAlignment('left')\" tooltip=\"Justify Left\">\n <i class=\"he he-left-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('center')\" tooltip=\"Justify Center\">\n <i class=\"he he-center-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('right')\" tooltip=\"Justify Right\">\n <i class=\"he he-right-align\"></i>\n </button>\n <button type=\"button\" (click)=\"setAlignment('justify')\" tooltip=\"Justify Full\">\n <i class=\"he he-justify\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"indent()\" tooltip=\"Indent\">\n <i class=\"he he-text-indent-left\"></i>\n </button>\n <button type=\"button\" (click)=\"outdent()\" tooltip=\"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\" (click)=\"applyList('ul')\" tooltip=\"Unordered list\">\n <i class=\"he he-unordered-list\"></i>\n </button>\n <button type=\"button\" (click)=\"applyList('ol')\" tooltip=\"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\" (click)=\"createLink()\" tooltip=\"Link\">\n <i class=\"he he-link\"></i>\n </button>\n <button type=\"button\" (click)=\"insertImage()\" tooltip=\"Image\">\n <i class=\"he he-image\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"undo()\" tooltip=\"Undo\">\n <i class=\"he he-undo\"></i>\n </button>\n <button type=\"button\" (click)=\"redo()\" tooltip=\"Redo\">\n <i class=\"he he-redo\"></i>\n </button>\n </div>\n <div class=\"toolbar-items\">\n <button type=\"button\" (click)=\"clearFormatting()\" tooltip=\"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</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,.text-editor .toolbar .toolbar-items .input-wrap{cursor:pointer;min-width:25px;min-height:20px}.text-editor .toolbar .toolbar-items button:hover i,.text-editor .toolbar .toolbar-items .input-wrap:hover i{color:#000}.text-editor .toolbar .toolbar-items button{border:0;display:grid;place-items:center;background:transparent}.text-editor .toolbar .toolbar-items button:hover{background:#eee}.text-editor .toolbar .toolbar-items .input-wrap{gap:2px;display:flex;cursor:pointer;align-items:center;flex-direction:column;justify-content:center}.text-editor .toolbar .toolbar-items .input-wrap input[type=color]{width:80%;padding:0;height:2px;border:none;cursor:inherit}.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 .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}\n"] }]
2736
2637
  }], propDecorators: { header: [{
2737
2638
  type: Input
2738
2639
  }], media: [{
@@ -2746,6 +2647,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImpor
2746
2647
  }], editorRef: [{
2747
2648
  type: ViewChild,
2748
2649
  args: ['editor']
2650
+ }], saveSelection: [{
2651
+ type: HostListener,
2652
+ args: ['document:selectionchange']
2749
2653
  }] } });
2750
2654
 
2751
2655
  class TextareaControl {