nexheal-lib 0.0.11 → 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.
- package/fesm2022/nexheal-lib.mjs +130 -267
- package/fesm2022/nexheal-lib.mjs.map +1 -1
- package/index.d.ts +39 -22
- package/package.json +1 -1
package/fesm2022/nexheal-lib.mjs
CHANGED
|
@@ -28,28 +28,12 @@ class AutocompleteControl {
|
|
|
28
28
|
addNewItemClicked = new EventEmitter();
|
|
29
29
|
blurEvent = new EventEmitter();
|
|
30
30
|
optionPatched = new EventEmitter();
|
|
31
|
-
loadAllOnOpen = false;
|
|
32
31
|
readonly = false;
|
|
33
32
|
_disabled = false;
|
|
34
33
|
_options = [];
|
|
35
34
|
set options(value) {
|
|
36
35
|
this._options = value || [];
|
|
37
36
|
this.processValueBuffer();
|
|
38
|
-
if (this.hasFocus && this.isDropdownOpen) {
|
|
39
|
-
const val = this.inputControl.value || "";
|
|
40
|
-
if (val === "" && this.loadAllOnOpen) {
|
|
41
|
-
this.filteredSuggestions = [...this._options];
|
|
42
|
-
}
|
|
43
|
-
else if (val !== "") {
|
|
44
|
-
const filterValue = val.toString().toLowerCase();
|
|
45
|
-
this.filteredSuggestions = this._options.filter((suggestion) => suggestion[this.optionDisplayProperty]?.toLowerCase().includes(filterValue));
|
|
46
|
-
}
|
|
47
|
-
this.highlightedIndex = this.filteredSuggestions.length > 0 ? 0 : null;
|
|
48
|
-
setTimeout(() => {
|
|
49
|
-
if (this.popperInstance)
|
|
50
|
-
this.popperInstance.update();
|
|
51
|
-
}, 0);
|
|
52
|
-
}
|
|
53
37
|
}
|
|
54
38
|
get options() {
|
|
55
39
|
return this._options;
|
|
@@ -96,13 +80,7 @@ class AutocompleteControl {
|
|
|
96
80
|
this.search.emit(filterValue);
|
|
97
81
|
}
|
|
98
82
|
else {
|
|
99
|
-
|
|
100
|
-
this.filterSuggestions("");
|
|
101
|
-
this.search.emit("");
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
this.isDropdownOpen = false;
|
|
105
|
-
}
|
|
83
|
+
this.isDropdownOpen = false;
|
|
106
84
|
}
|
|
107
85
|
});
|
|
108
86
|
}
|
|
@@ -175,18 +153,8 @@ class AutocompleteControl {
|
|
|
175
153
|
return;
|
|
176
154
|
}
|
|
177
155
|
if (value === "") {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.highlightedIndex = this.filteredSuggestions.length > 0 ? 0 : null;
|
|
181
|
-
this.isDropdownOpen = true;
|
|
182
|
-
setTimeout(() => {
|
|
183
|
-
this.createPopperInstance();
|
|
184
|
-
}, 0);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
this.filteredSuggestions = [];
|
|
188
|
-
this.isDropdownOpen = false;
|
|
189
|
-
}
|
|
156
|
+
this.filteredSuggestions = [];
|
|
157
|
+
this.isDropdownOpen = false;
|
|
190
158
|
return;
|
|
191
159
|
}
|
|
192
160
|
const filterValue = value.toString().toLowerCase();
|
|
@@ -216,13 +184,6 @@ class AutocompleteControl {
|
|
|
216
184
|
// events
|
|
217
185
|
onFocus() {
|
|
218
186
|
this.hasFocus = true;
|
|
219
|
-
if (this.readonly)
|
|
220
|
-
return;
|
|
221
|
-
if (this.loadAllOnOpen) {
|
|
222
|
-
const val = this.inputControl.value || "";
|
|
223
|
-
this.filterSuggestions(val);
|
|
224
|
-
this.search.emit(val);
|
|
225
|
-
}
|
|
226
187
|
}
|
|
227
188
|
onBlur() {
|
|
228
189
|
this.blurEvent.emit();
|
|
@@ -343,7 +304,7 @@ class AutocompleteControl {
|
|
|
343
304
|
this.optionSelected.emit(this.selectedItems);
|
|
344
305
|
}
|
|
345
306
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: AutocompleteControl, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
346
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: AutocompleteControl, isStandalone: true, selector: "autocomplete-control", inputs: { title: "title", required: "required", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", field: "field", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", inputLoader: "inputLoader", isAddNewItem: "isAddNewItem", optionDisplayProperty: "optionDisplayProperty",
|
|
307
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: AutocompleteControl, isStandalone: true, selector: "autocomplete-control", inputs: { title: "title", required: "required", placeholder: "placeholder", customClass: "customClass", clearVal: "clearVal", field: "field", error: "error", errorMessage: "errorMessage", autocomplete: "autocomplete", inputLoader: "inputLoader", isAddNewItem: "isAddNewItem", optionDisplayProperty: "optionDisplayProperty", readonly: "readonly", options: "options", disabled: "disabled" }, outputs: { optionSelected: "optionSelected", search: "search", selectionCleared: "selectionCleared", addNewItemClicked: "addNewItemClicked", blurEvent: "blurEvent", optionPatched: "optionPatched" }, providers: [
|
|
347
308
|
{
|
|
348
309
|
provide: NG_VALUE_ACCESSOR,
|
|
349
310
|
useExisting: AutocompleteControl,
|
|
@@ -396,8 +357,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImpor
|
|
|
396
357
|
type: Output
|
|
397
358
|
}], optionPatched: [{
|
|
398
359
|
type: Output
|
|
399
|
-
}], loadAllOnOpen: [{
|
|
400
|
-
type: Input
|
|
401
360
|
}], readonly: [{
|
|
402
361
|
type: Input
|
|
403
362
|
}], options: [{
|
|
@@ -427,7 +386,21 @@ class CalendarControl {
|
|
|
427
386
|
timeOnly = false;
|
|
428
387
|
dateFormat = "dd/MM/yyyy";
|
|
429
388
|
placeholder = "dd-mm-yyyy";
|
|
430
|
-
|
|
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
|
+
}
|
|
431
404
|
readonly = false;
|
|
432
405
|
submitted = false;
|
|
433
406
|
inputPlaceholder = false;
|
|
@@ -2499,24 +2472,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImpor
|
|
|
2499
2472
|
args: ['switchContainer']
|
|
2500
2473
|
}] } });
|
|
2501
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
|
+
*/
|
|
2502
2484
|
class TextEditor {
|
|
2503
2485
|
header = true;
|
|
2504
2486
|
media = true;
|
|
2505
2487
|
link = true;
|
|
2506
2488
|
placeholder = 'Type here...';
|
|
2507
2489
|
readonly = false;
|
|
2490
|
+
/** Disabled state, set by Reactive Forms through `setDisabledState`. */
|
|
2491
|
+
disabled = false;
|
|
2508
2492
|
editorRef;
|
|
2509
2493
|
onChange = () => { };
|
|
2510
2494
|
onTouch = () => { };
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
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;
|
|
2514
2500
|
ngAfterViewInit() {
|
|
2515
|
-
this.
|
|
2501
|
+
this.viewInitialized = true;
|
|
2502
|
+
this.editorRef.nativeElement.innerHTML = this.pendingValue;
|
|
2516
2503
|
}
|
|
2504
|
+
// ---- ControlValueAccessor ----
|
|
2517
2505
|
writeValue(value) {
|
|
2518
|
-
this.
|
|
2519
|
-
this.
|
|
2506
|
+
this.pendingValue = value ?? '';
|
|
2507
|
+
if (this.viewInitialized && this.editorRef) {
|
|
2508
|
+
this.editorRef.nativeElement.innerHTML = this.pendingValue;
|
|
2509
|
+
}
|
|
2520
2510
|
}
|
|
2521
2511
|
registerOnChange(fn) {
|
|
2522
2512
|
this.onChange = fn;
|
|
@@ -2524,256 +2514,126 @@ class TextEditor {
|
|
|
2524
2514
|
registerOnTouched(fn) {
|
|
2525
2515
|
this.onTouch = fn;
|
|
2526
2516
|
}
|
|
2517
|
+
setDisabledState(isDisabled) {
|
|
2518
|
+
this.disabled = isDisabled;
|
|
2519
|
+
}
|
|
2520
|
+
// ---- Editor events (wired from the template) ----
|
|
2527
2521
|
onInput() {
|
|
2528
|
-
|
|
2529
|
-
this.onChange(html);
|
|
2530
|
-
this.saveState();
|
|
2522
|
+
this.onChange(this.editorRef.nativeElement.innerHTML);
|
|
2531
2523
|
}
|
|
2532
|
-
// events
|
|
2533
2524
|
onTouched() {
|
|
2534
2525
|
this.onTouch();
|
|
2535
2526
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
}
|
|
2539
|
-
// ---- Selection & Range Utilities ----
|
|
2540
|
-
getSelectionRange() {
|
|
2527
|
+
/** Remember the caret/selection whenever it sits inside the editor. */
|
|
2528
|
+
saveSelection() {
|
|
2541
2529
|
const selection = window.getSelection();
|
|
2542
|
-
if (selection
|
|
2543
|
-
return selection.getRangeAt(0);
|
|
2544
|
-
}
|
|
2545
|
-
return null;
|
|
2546
|
-
}
|
|
2547
|
-
wrapSelection(htmlTag, style) {
|
|
2548
|
-
const range = this.getSelectionRange();
|
|
2549
|
-
if (!range || range.collapsed)
|
|
2550
|
-
return; // no selection
|
|
2551
|
-
const selectedText = range.extractContents();
|
|
2552
|
-
const el = document.createElement(htmlTag);
|
|
2553
|
-
if (style) {
|
|
2554
|
-
el.setAttribute('style', style);
|
|
2555
|
-
}
|
|
2556
|
-
el.appendChild(selectedText);
|
|
2557
|
-
range.insertNode(el);
|
|
2558
|
-
this.mergeAdjacentSimilarElements(el);
|
|
2559
|
-
this.onInput();
|
|
2560
|
-
}
|
|
2561
|
-
// Merges adjacent spans with the same style to avoid clutter
|
|
2562
|
-
mergeAdjacentSimilarElements(el) {
|
|
2563
|
-
const parent = el.parentElement;
|
|
2564
|
-
if (!parent)
|
|
2530
|
+
if (!selection || selection.rangeCount === 0 || !this.editorRef)
|
|
2565
2531
|
return;
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
const next = siblings[i + 1];
|
|
2570
|
-
if (current.nodeType === 1 && next?.nodeType === 1) {
|
|
2571
|
-
if (current.tagName === next.tagName && current.getAttribute('style') === next.getAttribute('style')) {
|
|
2572
|
-
// merge them
|
|
2573
|
-
while (next.firstChild) {
|
|
2574
|
-
current.appendChild(next.firstChild);
|
|
2575
|
-
}
|
|
2576
|
-
next.remove();
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2532
|
+
const range = selection.getRangeAt(0);
|
|
2533
|
+
if (this.editorRef.nativeElement.contains(range.commonAncestorContainer)) {
|
|
2534
|
+
this.savedRange = range.cloneRange();
|
|
2579
2535
|
}
|
|
2580
2536
|
}
|
|
2581
|
-
// ---- Inline
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
let style = '';
|
|
2585
|
-
if (styleType === 'bold')
|
|
2586
|
-
style = 'font-weight:bold;';
|
|
2587
|
-
else if (styleType === 'italic')
|
|
2588
|
-
style = 'font-style:italic;';
|
|
2589
|
-
else if (styleType === 'underline')
|
|
2590
|
-
style = 'text-decoration:underline;';
|
|
2591
|
-
this.wrapSelection('span', style);
|
|
2537
|
+
// ---- Inline formatting ----
|
|
2538
|
+
applyInlineStyle(style) {
|
|
2539
|
+
this.exec(style);
|
|
2592
2540
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
// Now `color` is a string and `input` is never null
|
|
2597
|
-
this.wrapSelection('span', `color:${color};`);
|
|
2541
|
+
// ---- Block formatting (headings / paragraph) ----
|
|
2542
|
+
applyBlockFormat(block) {
|
|
2543
|
+
this.exec('formatBlock', `<${block}>`);
|
|
2598
2544
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
this.wrapSelection('span', `background-color:${color};`);
|
|
2603
|
-
}
|
|
2604
|
-
// ---- Block Formatting (Headings, Paragraph) ----
|
|
2605
|
-
// Replaces the parent block element containing selection with a new block type
|
|
2606
|
-
applyBlockFormat(blockTag) {
|
|
2607
|
-
const range = this.getSelectionRange();
|
|
2608
|
-
if (!range)
|
|
2609
|
-
return;
|
|
2610
|
-
// Find the nearest block element
|
|
2611
|
-
let block = this.findBlockAncestor(range.commonAncestorContainer);
|
|
2612
|
-
if (!block) {
|
|
2613
|
-
// If no block found, wrap the selection in a block
|
|
2614
|
-
const wrapper = document.createElement(blockTag);
|
|
2615
|
-
wrapper.appendChild(range.extractContents());
|
|
2616
|
-
range.insertNode(wrapper);
|
|
2617
|
-
}
|
|
2618
|
-
else {
|
|
2619
|
-
// Replace the block with a new block type
|
|
2620
|
-
const newBlock = document.createElement(blockTag);
|
|
2621
|
-
// Move children
|
|
2622
|
-
while (block.firstChild) {
|
|
2623
|
-
newBlock.appendChild(block.firstChild);
|
|
2624
|
-
}
|
|
2625
|
-
block.replaceWith(newBlock);
|
|
2626
|
-
}
|
|
2627
|
-
this.onInput();
|
|
2545
|
+
// ---- Lists ----
|
|
2546
|
+
applyList(listType) {
|
|
2547
|
+
this.exec(listType === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');
|
|
2628
2548
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
return node;
|
|
2633
|
-
}
|
|
2634
|
-
node = node.parentNode;
|
|
2635
|
-
}
|
|
2636
|
-
return null;
|
|
2549
|
+
// ---- Indentation ----
|
|
2550
|
+
indent() {
|
|
2551
|
+
this.exec('indent');
|
|
2637
2552
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
return false;
|
|
2641
|
-
const display = window.getComputedStyle(node).display;
|
|
2642
|
-
return display === 'block' || display === 'list-item';
|
|
2553
|
+
outdent() {
|
|
2554
|
+
this.exec('outdent');
|
|
2643
2555
|
}
|
|
2644
|
-
// ----
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
li.appendChild(line);
|
|
2658
|
-
listEl.appendChild(li);
|
|
2659
|
-
}
|
|
2660
|
-
if (commonBlock) {
|
|
2661
|
-
// Insert the list right where the block was or at the selection
|
|
2662
|
-
range.insertNode(listEl);
|
|
2663
|
-
}
|
|
2664
|
-
else {
|
|
2665
|
-
// If no common block, just insert at current position
|
|
2666
|
-
range.insertNode(listEl);
|
|
2667
|
-
}
|
|
2668
|
-
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);
|
|
2669
2569
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
// For advanced logic, detect line breaks more thoroughly.
|
|
2673
|
-
const lines = [];
|
|
2674
|
-
let currentFrag = document.createDocumentFragment();
|
|
2675
|
-
Array.from(fragment.childNodes).forEach((node) => {
|
|
2676
|
-
if (node.nodeName === 'BR' || this.isBlockElement(node)) {
|
|
2677
|
-
// This node signifies a new line
|
|
2678
|
-
lines.push(currentFrag);
|
|
2679
|
-
currentFrag = document.createDocumentFragment();
|
|
2680
|
-
if (this.isBlockElement(node)) {
|
|
2681
|
-
while (node.firstChild) {
|
|
2682
|
-
currentFrag.appendChild(node.firstChild);
|
|
2683
|
-
}
|
|
2684
|
-
lines.push(currentFrag);
|
|
2685
|
-
currentFrag = document.createDocumentFragment();
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
else {
|
|
2689
|
-
currentFrag.appendChild(node);
|
|
2690
|
-
}
|
|
2691
|
-
});
|
|
2692
|
-
if (currentFrag.childNodes.length > 0) {
|
|
2693
|
-
lines.push(currentFrag);
|
|
2694
|
-
}
|
|
2695
|
-
return lines;
|
|
2570
|
+
applyHighlight(event) {
|
|
2571
|
+
this.exec('hiliteColor', event.target.value, true);
|
|
2696
2572
|
}
|
|
2697
|
-
// ---- Links ----
|
|
2573
|
+
// ---- Links / images ----
|
|
2698
2574
|
createLink() {
|
|
2699
2575
|
const url = prompt('Enter URL', 'https://');
|
|
2700
2576
|
if (!url)
|
|
2701
2577
|
return;
|
|
2702
|
-
|
|
2703
|
-
if (!range || range.collapsed)
|
|
2704
|
-
return;
|
|
2705
|
-
const selectedContent = range.extractContents();
|
|
2706
|
-
const a = document.createElement('a');
|
|
2707
|
-
a.href = url;
|
|
2708
|
-
a.appendChild(selectedContent);
|
|
2709
|
-
range.insertNode(a);
|
|
2710
|
-
this.onInput();
|
|
2578
|
+
this.exec('createLink', url);
|
|
2711
2579
|
}
|
|
2712
|
-
// ---- Images ----
|
|
2713
2580
|
insertImage() {
|
|
2714
2581
|
const url = prompt('Enter image URL:');
|
|
2715
2582
|
if (!url)
|
|
2716
2583
|
return;
|
|
2717
|
-
|
|
2718
|
-
if (!range)
|
|
2719
|
-
return;
|
|
2720
|
-
const img = document.createElement('img');
|
|
2721
|
-
img.src = url;
|
|
2722
|
-
range.insertNode(img);
|
|
2723
|
-
this.onInput();
|
|
2584
|
+
this.exec('insertImage', url);
|
|
2724
2585
|
}
|
|
2725
|
-
// ----
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
const range = this.getSelectionRange();
|
|
2729
|
-
if (!range)
|
|
2730
|
-
return;
|
|
2731
|
-
const block = this.findBlockAncestor(range.commonAncestorContainer);
|
|
2732
|
-
if (block) {
|
|
2733
|
-
block.style.textAlign = alignment;
|
|
2734
|
-
this.onInput();
|
|
2735
|
-
}
|
|
2586
|
+
// ---- Undo / redo / clear ----
|
|
2587
|
+
undo() {
|
|
2588
|
+
this.exec('undo');
|
|
2736
2589
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
const html = this.editorRef.nativeElement.innerText;
|
|
2740
|
-
// Convert innerText to a plain <p> block for simplicity
|
|
2741
|
-
this.editorRef.nativeElement.innerHTML = `<p>${html}</p>`;
|
|
2742
|
-
this.onInput();
|
|
2590
|
+
redo() {
|
|
2591
|
+
this.exec('redo');
|
|
2743
2592
|
}
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
const currentHtml = this.editorRef.nativeElement.innerHTML;
|
|
2747
|
-
if (this.undoStack.length === 0 || this.undoStack[this.undoStack.length - 1].html !== currentHtml) {
|
|
2748
|
-
this.undoStack.push({ html: currentHtml });
|
|
2749
|
-
this.redoStack = []; // clear redo on new input
|
|
2750
|
-
}
|
|
2593
|
+
clearFormatting() {
|
|
2594
|
+
this.exec('removeFormat');
|
|
2751
2595
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
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();
|
|
2761
2609
|
}
|
|
2762
|
-
|
|
2763
|
-
if (this.
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
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);
|
|
2769
2618
|
}
|
|
2770
2619
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2771
|
-
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" },
|
|
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"] }] });
|
|
2772
2627
|
}
|
|
2773
2628
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: TextEditor, decorators: [{
|
|
2774
2629
|
type: Component,
|
|
2775
|
-
args: [{ selector: 'app-text-editor', imports: [CommonModule,
|
|
2776
|
-
|
|
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"] }]
|
|
2777
2637
|
}], propDecorators: { header: [{
|
|
2778
2638
|
type: Input
|
|
2779
2639
|
}], media: [{
|
|
@@ -2787,6 +2647,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImpor
|
|
|
2787
2647
|
}], editorRef: [{
|
|
2788
2648
|
type: ViewChild,
|
|
2789
2649
|
args: ['editor']
|
|
2650
|
+
}], saveSelection: [{
|
|
2651
|
+
type: HostListener,
|
|
2652
|
+
args: ['document:selectionchange']
|
|
2790
2653
|
}] } });
|
|
2791
2654
|
|
|
2792
2655
|
class TextareaControl {
|