@libs-ui/components-inputs-mention 0.1.1-1

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.
@@ -0,0 +1,1084 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, model, viewChild, viewChildren, ChangeDetectionStrategy, Component, inject, ElementRef, input, output, effect, untracked, Directive } from '@angular/core';
3
+ import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
4
+ import { deleteUnicode, get, set, isNil, UtilsKeyCodeConstant, uuid } from '@libs-ui/utils';
5
+ import { Subject, fromEvent, takeUntil } from 'rxjs';
6
+ import { tap, takeUntil as takeUntil$1 } from 'rxjs/operators';
7
+ import { LibsUiComponentsAvatarComponent } from '@libs-ui/components-avatar';
8
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
9
+
10
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
+ const PATTERN_INSERT = '\ufeff';
12
+ const buildTemplate = (value, id, feId) => {
13
+ return `<span class="libs-ui-components-mention-item" style="font-weight: 600;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased; color: #7239EA" contenteditable="false" id="${id}" feId="${feId}" value="${value}">${value}</span>&nbsp;`;
14
+ };
15
+ const DEFAULT_CONFIG = {
16
+ items: [],
17
+ triggerChar: '@',
18
+ labelKey: 'email',
19
+ allowSpace: true,
20
+ returnTrigger: false,
21
+ limitSpaceSearchQuery: 3,
22
+ mentionEventName: 'click',
23
+ mentionActionByEvent: (item, trigger) => console.log(item, trigger),
24
+ mentionSelect: (item) => `@${get(item, 'email')}`,
25
+ mentionFilter: (search, items) => {
26
+ const searchString = deleteUnicode(search.toLowerCase());
27
+ return items?.filter((item) => {
28
+ const name = deleteUnicode(get(item, 'name').toLowerCase());
29
+ const userName = deleteUnicode(get(item, 'username').toLowerCase());
30
+ return name.includes(searchString) || userName.includes(searchString);
31
+ });
32
+ },
33
+ };
34
+
35
+ const setValue = (el, value) => {
36
+ if (isInputOrTextAreaElement(el)) {
37
+ el.value = value;
38
+ return;
39
+ }
40
+ el.textContent = value;
41
+ };
42
+ const getValue = (el) => {
43
+ return isInputOrTextAreaElement(el) ? el.value : el.textContent;
44
+ };
45
+ const insertValue = (el, start, end, text, iframe, noRecursion = false) => {
46
+ if (isTextElement(el)) {
47
+ const val = getValue(el);
48
+ text = noRecursion ? PATTERN_INSERT : text;
49
+ setValue(el, val?.substring(0, start) + text + val?.substring(end, val.length));
50
+ setCaretPosition(el, start + text.length, iframe);
51
+ return;
52
+ }
53
+ if (noRecursion) {
54
+ return;
55
+ }
56
+ return insertValueToContentEditable(start, end, text);
57
+ };
58
+ const insertValueToContentEditable = (start, end, text) => {
59
+ if (!window.getSelection) {
60
+ return;
61
+ }
62
+ const sel = window.getSelection();
63
+ if (!sel || !sel.anchorNode || !sel.getRangeAt || !sel.rangeCount) {
64
+ return;
65
+ }
66
+ let range = sel.getRangeAt(0);
67
+ const anchorNode = sel.anchorNode;
68
+ const numberTextBeforeCursor = (getTextBeforeCursor() || '').length;
69
+ range.setStart(anchorNode, numberTextBeforeCursor - (end - start));
70
+ range.setEnd(anchorNode, numberTextBeforeCursor);
71
+ range.deleteContents();
72
+ // Range.createContextualFragment() would be useful here but is
73
+ // only relatively recently standardized and is not supported in
74
+ // some browsers (IE9, for one)
75
+ const el = document.createElement('SPAN');
76
+ el.innerHTML = text;
77
+ const frag = document.createDocumentFragment();
78
+ let node, lastNode, nodeHandlerEvent;
79
+ while ((node = el.firstChild)) {
80
+ if (get(node, 'getAttribute')) {
81
+ nodeHandlerEvent = node;
82
+ }
83
+ lastNode = frag.appendChild(node);
84
+ }
85
+ range.insertNode(frag);
86
+ // Preserve the selection
87
+ if (lastNode) {
88
+ range = range.cloneRange();
89
+ range.setStartAfter(lastNode);
90
+ range.collapse(true);
91
+ sel.removeAllRanges();
92
+ sel.addRange(range);
93
+ }
94
+ return nodeHandlerEvent;
95
+ };
96
+ const getTextBeforeCursor = () => {
97
+ const sel = window.getSelection();
98
+ if (!sel || !sel.anchorNode) {
99
+ return undefined;
100
+ }
101
+ const range = sel.getRangeAt(0);
102
+ const anchorNode = sel.anchorNode;
103
+ const rangeNew = document.createRange();
104
+ rangeNew.selectNodeContents(anchorNode);
105
+ rangeNew.setEnd(range.startContainer, range.startOffset);
106
+ const fragNew = rangeNew.cloneContents();
107
+ const elmPrev = document.createElement('SPAN');
108
+ elmPrev.appendChild(fragNew);
109
+ return elmPrev.innerText;
110
+ };
111
+ const isInputOrTextAreaElement = (el) => {
112
+ return el !== null && (el.nodeName === 'INPUT' || el.nodeName === 'TEXTAREA');
113
+ };
114
+ const isTextElement = (el) => {
115
+ return el !== null && (el.nodeName === 'INPUT' || el.nodeName === 'TEXTAREA' || el.nodeName === '#text');
116
+ };
117
+ const setCaretPosition = (el, pos, iframe) => {
118
+ if (isInputOrTextAreaElement(el) && el.selectionStart) {
119
+ el.focus();
120
+ el.setSelectionRange(pos, pos);
121
+ return;
122
+ }
123
+ const range = getDocument(iframe)?.createRange();
124
+ range?.setStart(el, pos);
125
+ range?.collapse(true);
126
+ const sel = getWindowSelection(iframe);
127
+ if (!sel) {
128
+ return;
129
+ }
130
+ sel.removeAllRanges();
131
+ if (range) {
132
+ sel.addRange(range);
133
+ }
134
+ };
135
+ const getCaretPosition = (el, iframe) => {
136
+ if (isInputOrTextAreaElement(el)) {
137
+ const val = el.value;
138
+ return val.slice(0, el.selectionStart || undefined).length;
139
+ }
140
+ const selObj = getWindowSelection(iframe); //window.getSelection();
141
+ if (selObj && selObj.rangeCount > 0) {
142
+ const selRange = selObj.getRangeAt(0);
143
+ const preCaretRange = selRange.cloneRange();
144
+ preCaretRange.selectNodeContents(el);
145
+ preCaretRange.setEnd(selRange.endContainer, selRange.endOffset);
146
+ const position = preCaretRange.toString().length;
147
+ return position;
148
+ }
149
+ return 0;
150
+ };
151
+ // Based on ment.io functions...
152
+ //
153
+ const getDocument = (iframe) => {
154
+ if (!iframe) {
155
+ return document;
156
+ }
157
+ return iframe.contentWindow?.document;
158
+ };
159
+ const getWindowSelection = (iframe) => {
160
+ if (!iframe) {
161
+ return window.getSelection();
162
+ }
163
+ return iframe.contentWindow?.getSelection() || null;
164
+ };
165
+ const getContentEditableCaretCoords = (ctx) => {
166
+ const markerTextChar = '\ufeff';
167
+ const markerId = 'sel_' + new Date().getTime() + '_' + Math.random().toString().substring(2);
168
+ const doc = getDocument(ctx.iframe);
169
+ const sel = getWindowSelection(ctx.iframe);
170
+ if (!sel) {
171
+ return {
172
+ left: 0,
173
+ top: 0,
174
+ bottom: 0,
175
+ };
176
+ }
177
+ const prevRange = sel.getRangeAt(0);
178
+ // create new range and set postion using prevRange
179
+ const range = doc.createRange();
180
+ const anchorNode = sel.anchorNode;
181
+ range.setStart(anchorNode, prevRange.startOffset);
182
+ range.setEnd(anchorNode, prevRange.startOffset);
183
+ range.collapse(false);
184
+ // Create the marker element containing a single invisible character
185
+ // using DOM methods and insert it at the position in the range
186
+ const markerEl = doc.createElement('span');
187
+ markerEl.id = markerId;
188
+ markerEl.appendChild(doc.createTextNode(markerTextChar));
189
+ range.insertNode(markerEl);
190
+ sel.removeAllRanges();
191
+ sel.addRange(prevRange);
192
+ const coordinates = {
193
+ left: 0,
194
+ top: markerEl.offsetHeight,
195
+ bottom: -1,
196
+ };
197
+ localToRelativeCoordinates(ctx, markerEl, coordinates);
198
+ markerEl.parentNode?.removeChild(markerEl);
199
+ const widthScreen = (ctx.windowParent ? window.parent.innerWidth : window.innerWidth) || document.documentElement.clientWidth || document.body.clientWidth;
200
+ if (coordinates.left + 315 > widthScreen) {
201
+ coordinates.left = coordinates.left - 300;
202
+ }
203
+ const heightScreen = (ctx.windowParent ? window.parent.innerHeight : window.innerHeight) || document.documentElement.clientHeight || document.body.clientHeight;
204
+ if (coordinates.top + 190 > heightScreen) {
205
+ coordinates.bottom = heightScreen - coordinates.top + 15;
206
+ coordinates.top = -1;
207
+ }
208
+ return coordinates;
209
+ };
210
+ const localToRelativeCoordinates = (ctx, element, coordinates) => {
211
+ let obj = element;
212
+ let iframe = ctx ? ctx.iframe : null;
213
+ while (obj) {
214
+ if (ctx.parent !== null && ctx.parent === obj) {
215
+ break;
216
+ }
217
+ coordinates.left += obj.offsetLeft + obj.clientLeft;
218
+ coordinates.top += obj.offsetTop + obj.clientTop;
219
+ obj = obj.offsetParent;
220
+ if (!obj && iframe) {
221
+ obj = iframe;
222
+ iframe = null;
223
+ }
224
+ }
225
+ obj = element;
226
+ iframe = ctx ? ctx.iframe : null;
227
+ while ((obj !== getDocument(undefined)?.body || ctx.windowParent) && obj !== null) {
228
+ if (ctx.parent !== null && ctx.parent === obj) {
229
+ break;
230
+ }
231
+ if (obj.scrollTop && obj.scrollTop > 0) {
232
+ coordinates.top -= obj.scrollTop;
233
+ }
234
+ if (obj.scrollLeft && obj.scrollLeft > 0) {
235
+ coordinates.left -= obj.scrollLeft;
236
+ }
237
+ obj = ctx.windowParent ? obj.offsetParent : obj.parentNode;
238
+ if (!obj && iframe) {
239
+ obj = iframe;
240
+ iframe = null;
241
+ }
242
+ }
243
+ };
244
+ const setEndOfContentEditable = (contentEditableElement) => {
245
+ if (document.createRange) {
246
+ const range = document.createRange(); //Create a range (a range is a like the selection but invisible)
247
+ const selection = window.getSelection(); //get the selection object (allows you to change selection)
248
+ range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
249
+ range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
250
+ selection?.removeAllRanges(); //remove any selections already made
251
+ selection?.addRange(range); //make the range you have just created the visible selection
252
+ }
253
+ return;
254
+ };
255
+ const insertTextAtCaret = (element, text) => {
256
+ if (window.getSelection) {
257
+ if (!elementContainsSelection(element)) {
258
+ setEndOfContentEditable(element);
259
+ insertTextAtCaret(element, text);
260
+ return;
261
+ }
262
+ const sel = window.getSelection();
263
+ if (sel?.getRangeAt && sel.rangeCount) {
264
+ let range = sel.getRangeAt(0);
265
+ const el = document.createElement('SPAN');
266
+ range.deleteContents();
267
+ el.innerHTML = text;
268
+ const frag = document.createDocumentFragment();
269
+ let node;
270
+ let lastNode;
271
+ while ((node = el.firstChild)) {
272
+ lastNode = frag.appendChild(node);
273
+ }
274
+ range.insertNode(frag);
275
+ if (lastNode) {
276
+ range = range.cloneRange();
277
+ range.setStartAfter(lastNode);
278
+ range.collapse(true);
279
+ sel.removeAllRanges();
280
+ sel.addRange(range);
281
+ }
282
+ }
283
+ }
284
+ };
285
+ const elementContainsSelection = (el) => {
286
+ if (!window.getSelection) {
287
+ return false;
288
+ }
289
+ const sel = window.getSelection();
290
+ if (sel && sel.rangeCount > 0) {
291
+ for (let i = 0; i < sel.rangeCount; ++i) {
292
+ if (!isOrContains(sel.getRangeAt(i).commonAncestorContainer, el)) {
293
+ return false;
294
+ }
295
+ }
296
+ return true;
297
+ }
298
+ return false;
299
+ };
300
+ const isOrContains = (node, container) => {
301
+ while (node) {
302
+ if (node === container) {
303
+ return true;
304
+ }
305
+ if (node.parentNode) {
306
+ node = node.parentNode;
307
+ }
308
+ }
309
+ return false;
310
+ };
311
+
312
+ /* From: https://github.com/component/textarea-caret-position */
313
+ // We'll copy the properties below into the mirror div.
314
+ // Note that some browsers, such as Firefox, do not concatenate properties
315
+ // into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
316
+ // so we have to list every single property explicitly.
317
+ const properties = [
318
+ 'direction', // RTL support
319
+ 'boxSizing',
320
+ 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
321
+ 'height',
322
+ 'overflowX',
323
+ 'overflowY', // copy the scrollbar for IE
324
+ 'borderTopWidth',
325
+ 'borderRightWidth',
326
+ 'borderBottomWidth',
327
+ 'borderLeftWidth',
328
+ 'borderStyle',
329
+ 'paddingTop',
330
+ 'paddingRight',
331
+ 'paddingBottom',
332
+ 'paddingLeft',
333
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/font
334
+ 'fontStyle',
335
+ 'fontVariant',
336
+ 'fontWeight',
337
+ 'fontStretch',
338
+ 'fontSize',
339
+ 'fontSizeAdjust',
340
+ 'lineHeight',
341
+ 'fontFamily',
342
+ 'textAlign',
343
+ 'textTransform',
344
+ 'textIndent',
345
+ 'textDecoration', // might not make a difference, but better be safe
346
+ 'letterSpacing',
347
+ 'wordSpacing',
348
+ 'tabSize',
349
+ 'MozTabSize',
350
+ ];
351
+ const isBrowser = typeof window !== 'undefined';
352
+ const isFirefox = isBrowser && get(window, 'mozInnerScreenX') !== null;
353
+ const getCaretCoordinates = (element, position) => {
354
+ if (!isBrowser) {
355
+ throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
356
+ }
357
+ // The mirror div will replicate the textarea's style
358
+ const div = document.createElement('div');
359
+ div.id = 'input-textarea-caret-position-mirror-div';
360
+ document.body.appendChild(div);
361
+ const style = div.style;
362
+ const computed = window.getComputedStyle ? window.getComputedStyle(element) : get(element, 'currentStyle'); // currentStyle for IE < 9
363
+ const isInput = element.nodeName === 'INPUT';
364
+ // Default textarea styles
365
+ style.whiteSpace = 'pre-wrap';
366
+ if (!isInput) {
367
+ style.wordBreak = 'break-word';
368
+ } // only for textarea-s
369
+ // Position off-screen
370
+ style.position = 'absolute'; // required to return coordinates properly
371
+ // Transfer the element's properties to the div
372
+ properties.forEach((prop) => {
373
+ if (isInput && prop === 'lineHeight') {
374
+ // Special case for <input>s because text is rendered centered and line height may be != height
375
+ if (computed.boxSizing !== 'border-box') {
376
+ style.lineHeight = computed.height;
377
+ return;
378
+ }
379
+ const height = parseInt(computed.height);
380
+ const outerHeight = parseInt(computed.paddingTop) + parseInt(computed.paddingBottom) + parseInt(computed.borderTopWidth) + parseInt(computed.borderBottomWidth);
381
+ const targetHeight = outerHeight + parseInt(computed.lineHeight);
382
+ if (height > targetHeight) {
383
+ style.lineHeight = height - outerHeight + 'px';
384
+ return;
385
+ }
386
+ if (height === targetHeight) {
387
+ style.lineHeight = computed.lineHeight;
388
+ return;
389
+ }
390
+ style.lineHeight = '0';
391
+ return;
392
+ }
393
+ set(style, prop, computed[prop]);
394
+ });
395
+ if (!isFirefox) {
396
+ style.overflow = 'hidden';
397
+ // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
398
+ }
399
+ const value = get(element, 'value');
400
+ if (isFirefox && element.scrollHeight > parseInt(computed.height)) {
401
+ style.overflowY = 'scroll';
402
+ }
403
+ div.textContent = value.substring(0, position);
404
+ // The second special handling for input type="text" vs textarea:
405
+ // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
406
+ if (isInput && !isNil(div.textContent)) {
407
+ div.textContent = div.textContent.replace(/\s/g, '\u00a0');
408
+ }
409
+ const span = document.createElement('span');
410
+ // Wrapping must be replicated *exactly*, including when a long word gets
411
+ // onto the next line, with whitespace at the end of the line before (#7).
412
+ // The *only* reliable way to do that is to copy the *entire* rest of the
413
+ // textarea's content into the <span> created at the caret position.
414
+ // For inputs, just '.' would be enough, but no need to bother.
415
+ span.textContent = value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
416
+ div.appendChild(span);
417
+ const coordinates = {
418
+ top: span.offsetTop + parseInt(computed['borderTopWidth']),
419
+ left: span.offsetLeft + parseInt(computed['borderLeftWidth']),
420
+ height: parseInt(computed['lineHeight']),
421
+ };
422
+ document.body.removeChild(div);
423
+ return coordinates;
424
+ };
425
+
426
+ class LibsUiComponentsInputsMentionListComponent {
427
+ // #region PROPERTY
428
+ items = signal([]);
429
+ activeIndex = signal(0);
430
+ hidden = signal(false);
431
+ styleOff = signal(false);
432
+ parentHandlerKeyDown = signal(undefined);
433
+ dropUp = signal(false);
434
+ coords = signal({ top: 0, left: 0, bottom: 0 });
435
+ offset = signal(0);
436
+ nativeParentElement = signal(undefined);
437
+ onDestroy = new Subject();
438
+ // #region INPUT
439
+ labelKey = model('label');
440
+ zIndex = model(1000);
441
+ isIframe = model.required();
442
+ defaultAvatar = model();
443
+ /* VIEW CHILD */
444
+ elementEl = viewChild('element');
445
+ itemsEl = viewChildren('item');
446
+ ngOnInit() {
447
+ if (this.isIframe()) {
448
+ this.initEvent(window.parent, 'wheel');
449
+ this.initEvent(window.parent, 'resize');
450
+ return;
451
+ }
452
+ this.initEvent(window, 'wheel');
453
+ this.initEvent(window, 'resize');
454
+ }
455
+ /* FUNCTIONS */
456
+ initEvent(element, eventName) {
457
+ fromEvent(element, eventName)
458
+ .pipe(takeUntil(this.onDestroy))
459
+ .subscribe((event) => {
460
+ if (eventName === 'resize' || !this.elementEl()?.nativeElement.contains(event.target)) {
461
+ this.handlerListenClose(event);
462
+ }
463
+ });
464
+ }
465
+ handlerListenClose(event) {
466
+ if (event.target !== this.elementEl()?.nativeElement) {
467
+ this.hidden.set(true);
468
+ }
469
+ }
470
+ // lots of confusion here between relative coordinates and containers
471
+ position(nativeParentElement, iframe, leftDiv) {
472
+ this.nativeParentElement.set(nativeParentElement);
473
+ if (isInputOrTextAreaElement(nativeParentElement)) {
474
+ // parent elements need to have postition:relative for this to work correctly?
475
+ this.coords.set(getCaretCoordinates(nativeParentElement, nativeParentElement.selectionStart));
476
+ this.coords.update((item) => ({
477
+ ...item,
478
+ top: nativeParentElement.offsetTop + item.top - nativeParentElement.scrollTop,
479
+ left: nativeParentElement.offsetLeft + item.left - nativeParentElement.scrollLeft + nativeParentElement.getBoundingClientRect().left,
480
+ }));
481
+ // getCretCoordinates() for text/input elements needs an additional offset to position the list correctly
482
+ this.offset.set(this.getBlockCursorDimensions(nativeParentElement).height);
483
+ this.positionElement();
484
+ return;
485
+ }
486
+ if (iframe) {
487
+ const context = { iframe: iframe, parent: window.parent.document.body, windowParent: true };
488
+ // const rect = iframe.getBoundingClientRect();
489
+ const caretRelativeToView = getContentEditableCaretCoords(context);
490
+ const doc = document.documentElement;
491
+ const scrollLeft = (window.scrollX || doc.scrollLeft) - (doc.clientLeft || 0);
492
+ this.coords.update((item) => ({
493
+ ...item,
494
+ left: caretRelativeToView.left - scrollLeft - (leftDiv || 0),
495
+ bottom: window.parent.innerHeight - caretRelativeToView.top + 18,
496
+ }));
497
+ if (caretRelativeToView.bottom && caretRelativeToView.bottom !== -1) {
498
+ this.coords.update((item) => ({
499
+ ...item,
500
+ bottom: caretRelativeToView.bottom,
501
+ }));
502
+ }
503
+ this.positionElement();
504
+ return;
505
+ }
506
+ const doc = document.documentElement;
507
+ const scrollLeft = (window.scrollX || doc.scrollLeft) - (doc.clientLeft || 0);
508
+ const scrollTop = (window.scrollX || doc.scrollTop) - (doc.clientTop || 0);
509
+ // bounding rectangles are relative to view, offsets are relative to container?
510
+ const caretRelativeToView = getContentEditableCaretCoords({ iframe, parent: null });
511
+ this.coords.update((item) => ({
512
+ ...item,
513
+ top: caretRelativeToView.top - scrollTop + 10,
514
+ left: caretRelativeToView.left - scrollLeft - (leftDiv || 0),
515
+ bottom: caretRelativeToView.bottom,
516
+ }));
517
+ this.positionElement();
518
+ }
519
+ get ActiveItem() {
520
+ return this.items()[this.activeIndex()];
521
+ }
522
+ handlerClick(event, index) {
523
+ event.stopPropagation();
524
+ this.activeIndex.set(index);
525
+ set(event, 'keyCode', UtilsKeyCodeConstant.ENTER);
526
+ this.parentHandlerKeyDown()?.(event, this.nativeParentElement());
527
+ }
528
+ activateNextItem() {
529
+ this.activeIndex.set(this.items().length - 1 > this.activeIndex() ? this.activeIndex() + 1 : this.activeIndex());
530
+ this.scrollToActive();
531
+ }
532
+ activatePreviousItem() {
533
+ this.activeIndex.set(this.activeIndex() > 0 ? this.activeIndex() - 1 : this.activeIndex());
534
+ this.scrollToActive();
535
+ }
536
+ positionElement(left = this.coords().left, top = this.coords().top, dropUp = this.dropUp(), bottom = this.coords().bottom || 0) {
537
+ const el = this.elementEl()?.nativeElement;
538
+ top += dropUp ? 0 : this.offset(); // top of list is next line
539
+ el.style.position = 'absolute';
540
+ el.style.left = left + 'px';
541
+ el.style.top = bottom !== -1 ? 'auto' : top + 'px';
542
+ el.style.bottom = bottom === -1 ? 'auto' : bottom + 'px';
543
+ this.nativeParentElement()?.normalize();
544
+ }
545
+ getBlockCursorDimensions(nativeParentElement) {
546
+ const parentStyles = window.getComputedStyle(nativeParentElement);
547
+ return {
548
+ height: parseFloat(parentStyles.lineHeight),
549
+ width: parseFloat(parentStyles.fontSize),
550
+ };
551
+ }
552
+ handleActiveItem(event, index) {
553
+ event.stopPropagation();
554
+ this.activeIndex.set(index);
555
+ }
556
+ scrollToActive() {
557
+ if (!this.itemsEl() || this.itemsEl().length <= this.activeIndex()) {
558
+ return;
559
+ }
560
+ this.itemsEl()[this.activeIndex()].nativeElement.scrollIntoView();
561
+ }
562
+ scrollContent() {
563
+ setTimeout(() => {
564
+ const element = this.elementEl();
565
+ if (element && element.nativeElement) {
566
+ element.nativeElement.scrollTop = 0;
567
+ }
568
+ });
569
+ }
570
+ ngOnDestroy() {
571
+ this.onDestroy.next();
572
+ this.onDestroy.complete();
573
+ }
574
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsMentionListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
575
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LibsUiComponentsInputsMentionListComponent, isStandalone: true, selector: "libs_ui-components-inputs-mention-list", inputs: { labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, isIframe: { classPropertyName: "isIframe", publicName: "isIframe", isSignal: true, isRequired: true, transformFunction: null }, defaultAvatar: { classPropertyName: "defaultAvatar", publicName: "defaultAvatar", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { labelKey: "labelKeyChange", zIndex: "zIndexChange", isIframe: "isIframeChange", defaultAvatar: "defaultAvatarChange" }, viewQueries: [{ propertyName: "elementEl", first: true, predicate: ["element"], descendants: true, isSignal: true }, { propertyName: "itemsEl", predicate: ["item"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #element\n class=\"libs-ui-mention-list\"\n [style.zIndex]=\"zIndex()\"\n [class.hidden]=\"hidden()\">\n @for (item of items(); track item) {\n <div\n #item\n class=\"libs-ui-mention-list-item\"\n [class.libs-ui-mention-list-item-active]=\"$index === activeIndex()\"\n (mousedown)=\"handlerClick($event, $index)\"\n (mouseenter)=\"handleActiveItem($event, $index)\">\n <div class=\"px-[12px] py-[6px] flex items-center\">\n <libs_ui-components-avatar\n [size]=\"24\"\n [linkAvatar]=\"item.avatar\"\n [linkAvatarError]=\"defaultAvatar()\"\n [getLastTextAfterSpace]=\"true\"\n [textAvatar]=\"item.name\"\n [idGenColor]=\"item.id\" />\n <libs_ui-components-popover\n [classInclude]=\"'libs-ui-font-h5r'\"\n [type]=\"'text'\"\n [ignoreShowPopover]=\"true\"\n [config]=\"{ width: 250 }\">\n <!-- // ta.m \u1EA9n show tooltip v\u00EC ch\u01B0a x\u1EED l\u00FD \u0111c v\u1ECB tr\u00ED -->\n {{ item.name + ' (' + item.username + ')' }}\n </libs_ui-components-popover>\n </div>\n </div>\n }\n</div>\n", styles: [".libs-ui-mention-list{position:absolute;top:-999px;left:-9999px;z-index:1202;float:left;width:315px;padding:4px 0;background-color:#fff;border:solid 1px #e6e8ed;border-radius:5px;max-height:190px;overflow:auto;box-shadow:0 2px 10px 1px #3333331a}.libs-ui-mention-list .libs-ui-mention-list-item{background:#fff;cursor:pointer}.libs-ui-mention-list .libs-ui-mention-list-item:hover,.libs-ui-mention-list .libs-ui-mention-list-item-active{background-color:var(--libs-ui-color-light-3, #f4f8ff)}\n"], dependencies: [{ kind: "component", type: LibsUiComponentsAvatarComponent, selector: "libs_ui-components-avatar", inputs: ["typeShape", "classInclude", "size", "linkAvatar", "linkAvatarError", "classImageInclude", "zIndexPreviewImage", "clickPreviewImage", "idGenColor", "getLastTextAfterSpace", "textAvatar", "textAvatarClassInclude", "containertextAvatarClassInclude"], outputs: ["outAvatarError", "outEventPreviewImage"] }, { kind: "component", type: LibsUiComponentsPopoverComponent, selector: "libs_ui-components-popover,[LibsUiComponentsPopoverDirective]", inputs: ["debugId", "flagMouse", "type", "mode", "config", "ignoreShowPopover", "elementRefCustom", "initEventInElementRefCustom", "classInclude", "ignoreHiddenPopoverContentWhenMouseLeave", "ignoreStopPropagationEvent", "ignoreCursorPointerModeLikeClick", "isAddContentToParentDocument", "ignoreClickOutside"], outputs: ["outEvent", "outChangStageFlagMouse", "outEventPopoverContent", "outFunctionsControl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
576
+ }
577
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsMentionListComponent, decorators: [{
578
+ type: Component,
579
+ args: [{ selector: 'libs_ui-components-inputs-mention-list', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LibsUiComponentsAvatarComponent, LibsUiComponentsPopoverComponent], template: "<div\n #element\n class=\"libs-ui-mention-list\"\n [style.zIndex]=\"zIndex()\"\n [class.hidden]=\"hidden()\">\n @for (item of items(); track item) {\n <div\n #item\n class=\"libs-ui-mention-list-item\"\n [class.libs-ui-mention-list-item-active]=\"$index === activeIndex()\"\n (mousedown)=\"handlerClick($event, $index)\"\n (mouseenter)=\"handleActiveItem($event, $index)\">\n <div class=\"px-[12px] py-[6px] flex items-center\">\n <libs_ui-components-avatar\n [size]=\"24\"\n [linkAvatar]=\"item.avatar\"\n [linkAvatarError]=\"defaultAvatar()\"\n [getLastTextAfterSpace]=\"true\"\n [textAvatar]=\"item.name\"\n [idGenColor]=\"item.id\" />\n <libs_ui-components-popover\n [classInclude]=\"'libs-ui-font-h5r'\"\n [type]=\"'text'\"\n [ignoreShowPopover]=\"true\"\n [config]=\"{ width: 250 }\">\n <!-- // ta.m \u1EA9n show tooltip v\u00EC ch\u01B0a x\u1EED l\u00FD \u0111c v\u1ECB tr\u00ED -->\n {{ item.name + ' (' + item.username + ')' }}\n </libs_ui-components-popover>\n </div>\n </div>\n }\n</div>\n", styles: [".libs-ui-mention-list{position:absolute;top:-999px;left:-9999px;z-index:1202;float:left;width:315px;padding:4px 0;background-color:#fff;border:solid 1px #e6e8ed;border-radius:5px;max-height:190px;overflow:auto;box-shadow:0 2px 10px 1px #3333331a}.libs-ui-mention-list .libs-ui-mention-list-item{background:#fff;cursor:pointer}.libs-ui-mention-list .libs-ui-mention-list-item:hover,.libs-ui-mention-list .libs-ui-mention-list-item-active{background-color:var(--libs-ui-color-light-3, #f4f8ff)}\n"] }]
580
+ }] });
581
+
582
+ /* eslint-disable @typescript-eslint/no-explicit-any */
583
+ class LibsUiComponentsInputsMentionDirective {
584
+ // #region PROPERTY
585
+ listComponentRef = signal(undefined);
586
+ nodesInsert = signal(new Set());
587
+ activeConfig = signal(undefined);
588
+ triggerChars = signal({});
589
+ searchString = signal('');
590
+ startPos = signal(undefined);
591
+ startNode = signal(undefined);
592
+ searchList = signal(undefined);
593
+ searching = signal(false);
594
+ iframe = signal(undefined);
595
+ lastKeyCode = signal(undefined);
596
+ isEditor = signal(false);
597
+ iframeId = signal(undefined);
598
+ element = inject(ElementRef);
599
+ dynamicComponentService = inject(LibsUiDynamicComponentService);
600
+ onDestroy = new Subject();
601
+ // #region INPUT
602
+ timeDelayInit = input(0, { transform: (val) => val ?? 0 });
603
+ mentionConfig = input();
604
+ mentionListTemplate = input();
605
+ // #region OUTPUT
606
+ outSearchTerm = output();
607
+ outItemSelected = output();
608
+ outToggle = output();
609
+ outInsertMention = output();
610
+ outFunctionControl = output();
611
+ constructor() {
612
+ effect(() => {
613
+ this.mentionConfig();
614
+ untracked(() => this.updateConfig());
615
+ });
616
+ }
617
+ ngAfterViewInit() {
618
+ setTimeout(() => {
619
+ if (!this.mentionConfig()) {
620
+ return;
621
+ }
622
+ const isContentEditable = this.element.nativeElement.getAttribute('contenteditable');
623
+ this.isEditor.set(!isContentEditable);
624
+ const element = !this.isEditor() ? this.element.nativeElement : this.element.nativeElement.querySelector('.ql-editor[contenteditable="true"]');
625
+ if (element) {
626
+ element.addEventListener('keydown', (event) => {
627
+ this.handlerKeydown.bind(this)(event, element);
628
+ });
629
+ element.addEventListener('input', (event) => {
630
+ this.handlerInput.bind(this)(event, element);
631
+ });
632
+ element.addEventListener('blur', (event) => {
633
+ this.handlerBlur.bind(this)(event);
634
+ });
635
+ element.addEventListener('click', (event) => {
636
+ this.handlerFocus.bind(this)(event, element);
637
+ });
638
+ }
639
+ this.outFunctionControl.emit({
640
+ addMention: () => {
641
+ insertTextAtCaret(element, `${this.mentionConfig()?.triggerChar}`);
642
+ setTimeout(() => {
643
+ this.handlerKeydown({ key: this.mentionConfig()?.triggerChar, keyCode: UtilsKeyCodeConstant.BACKSPACE, shiftKey: true, inputEvent: true }, element);
644
+ }, 500);
645
+ },
646
+ });
647
+ }, this.timeDelayInit());
648
+ }
649
+ /* FUNCTIONS */
650
+ updateConfig() {
651
+ const mentionConfig = this.mentionConfig();
652
+ if (!mentionConfig) {
653
+ return;
654
+ }
655
+ this.triggerChars.set({});
656
+ this.addConfig(mentionConfig);
657
+ if (mentionConfig.mention) {
658
+ mentionConfig.mention.forEach((config) => this.addConfig(config));
659
+ }
660
+ }
661
+ addConfig(config) {
662
+ const defaults = Object.assign({}, DEFAULT_CONFIG);
663
+ const labelKey = config.labelKey || 'label';
664
+ config = Object.assign(defaults, config);
665
+ if (config.items && config.items.length > 0) {
666
+ if (typeof config.items[0] === 'string') {
667
+ config.items.forEach((label) => {
668
+ const object = {};
669
+ object[labelKey] = label;
670
+ return object;
671
+ });
672
+ }
673
+ if (labelKey) {
674
+ // remove items without an labelKey (as it's required to filter the list)
675
+ config.items = config.items.filter((e) => e[labelKey]);
676
+ if (!config.disableSort) {
677
+ config.items.sort((a, b) => (a[labelKey] || '').localeCompare(b[labelKey] || ''));
678
+ }
679
+ }
680
+ }
681
+ this.triggerChars.update((item) => ({
682
+ ...item,
683
+ [config.triggerChar || '@']: config,
684
+ }));
685
+ if (this.activeConfig() && this.activeConfig()?.triggerChar === config.triggerChar) {
686
+ this.activeConfig.set(config);
687
+ this.updateSearchList();
688
+ }
689
+ }
690
+ setIframe(iframe) {
691
+ this.iframe.set(iframe);
692
+ }
693
+ stopEvent(event) {
694
+ if (get(event, 'wasClick')) {
695
+ return;
696
+ }
697
+ event.preventDefault();
698
+ event.stopPropagation();
699
+ event.stopImmediatePropagation();
700
+ }
701
+ handlerBlur(event) {
702
+ setTimeout(() => {
703
+ this.stopEvent(event);
704
+ this.stopSearch();
705
+ }, 100);
706
+ }
707
+ handlerInput(event, nativeElement = this.element.nativeElement) {
708
+ const data = get(event, 'data');
709
+ if (this.lastKeyCode() === UtilsKeyCodeConstant.BUFFERED && data) {
710
+ const keyCode = data.charCodeAt(0);
711
+ this.handlerKeydown({ keyCode, inputEvent: true }, nativeElement);
712
+ }
713
+ }
714
+ handlerKeydown(event, nativeElement = this.element.nativeElement) {
715
+ this.lastKeyCode.set(event.keyCode);
716
+ if (event.isComposing || event.keyCode === UtilsKeyCodeConstant.BUFFERED) {
717
+ return;
718
+ }
719
+ const val = getValue(nativeElement);
720
+ let pos = getCaretPosition(nativeElement, this.iframe());
721
+ let charPressed = event.key;
722
+ if (!this.isEditor()) {
723
+ if (event.keyCode === UtilsKeyCodeConstant.BACKSPACE) {
724
+ this.appendEmptyToLastNode(pos, val.length, nativeElement);
725
+ }
726
+ setTimeout(() => this.addNodeZeroWidthSpace(nativeElement), 0);
727
+ }
728
+ if (!charPressed) {
729
+ const charCode = event.which || event.keyCode;
730
+ charPressed = String.fromCharCode(event.which || event.keyCode);
731
+ if (!event.shiftKey && charCode >= 65 && charCode <= 90) {
732
+ charPressed = String.fromCharCode(charCode + 32);
733
+ }
734
+ }
735
+ if (event.keyCode === UtilsKeyCodeConstant.ENTER && event.wasClick && pos < (this.startPos() || 0)) {
736
+ pos = this.startNode().length;
737
+ setCaretPosition(this.startNode(), pos, this.iframe());
738
+ }
739
+ const config = this.triggerChars()[charPressed];
740
+ if (config) {
741
+ this.activeConfig.set(config);
742
+ this.startPos.set(event.inputEvent ? pos - 1 : pos);
743
+ this.startNode.set(getWindowSelection(this.iframe())?.anchorNode);
744
+ this.searching.set(true);
745
+ this.searchString.set('');
746
+ this.showSearchList(nativeElement);
747
+ this.updateSearchList();
748
+ if (config.returnTrigger) {
749
+ this.outSearchTerm.emit(config.triggerChar);
750
+ }
751
+ return;
752
+ }
753
+ setTimeout(() => {
754
+ for (const itemSet of this.nodesInsert()) {
755
+ if (!nativeElement.contains(itemSet.elementSpan)) {
756
+ itemSet.subscription?.unsubscribe();
757
+ this.nodesInsert().delete(itemSet);
758
+ }
759
+ }
760
+ });
761
+ const startPos = this.startPos();
762
+ if (isNil(startPos) || startPos < 0 || !this.searching()) {
763
+ return;
764
+ }
765
+ const searchList = this.searchList();
766
+ if (pos <= startPos) {
767
+ if (searchList) {
768
+ searchList.hidden.set(true);
769
+ }
770
+ return;
771
+ }
772
+ if (event.keyCode === UtilsKeyCodeConstant.SHIFT || event.metaKey || event.altKey || event.ctrlKey || pos <= startPos) {
773
+ return;
774
+ }
775
+ if (!this.activeConfig()?.allowSpace && event.keyCode === UtilsKeyCodeConstant.SPACE) {
776
+ this.startPos.set(-1);
777
+ }
778
+ else if (event.keyCode === UtilsKeyCodeConstant.BACKSPACE && pos > 0) {
779
+ pos--;
780
+ if (pos === this.startPos()) {
781
+ this.stopSearch();
782
+ }
783
+ }
784
+ else if (searchList?.hidden()) {
785
+ if (event.keyCode === UtilsKeyCodeConstant.TAB || event.keyCode === UtilsKeyCodeConstant.ENTER) {
786
+ this.stopSearch();
787
+ return;
788
+ }
789
+ }
790
+ else if (!searchList?.hidden() && this.activeConfig()) {
791
+ if (event.keyCode === UtilsKeyCodeConstant.TAB || event.keyCode === UtilsKeyCodeConstant.ENTER) {
792
+ this.stopEvent(event);
793
+ // emit the selected list item
794
+ this.outItemSelected.emit(searchList?.ActiveItem);
795
+ // optional function to format the selected item before inserting the text
796
+ this.insertMention(nativeElement, pos);
797
+ // fire input event so angular bindings are updated
798
+ if ('Event' in window) {
799
+ // this seems backwards, but fire the event from this elements nativeElement (not the
800
+ // one provided that may be in an iframe, as it won't be propogate)
801
+ // Create the event using the modern Event constructor
802
+ const evt = new Event(this.iframe() ? 'change' : 'input', {
803
+ bubbles: true,
804
+ cancelable: false,
805
+ });
806
+ // Dispatch the event on the native element
807
+ nativeElement.dispatchEvent(evt);
808
+ }
809
+ this.startPos.set(-1);
810
+ this.stopSearch();
811
+ return false;
812
+ }
813
+ if (event.keyCode === UtilsKeyCodeConstant.ESCAPE) {
814
+ this.stopEvent(event);
815
+ this.stopSearch();
816
+ return false;
817
+ }
818
+ if (event.keyCode === UtilsKeyCodeConstant.DOWN_ARROW) {
819
+ this.stopEvent(event);
820
+ searchList?.activateNextItem();
821
+ return false;
822
+ }
823
+ if (event.keyCode === UtilsKeyCodeConstant.UP_ARROW) {
824
+ this.stopEvent(event);
825
+ searchList?.activatePreviousItem();
826
+ return false;
827
+ }
828
+ }
829
+ if (charPressed.length !== 1 && event.keyCode !== UtilsKeyCodeConstant.BACKSPACE) {
830
+ this.stopEvent(event);
831
+ return false;
832
+ }
833
+ if (!this.searching()) {
834
+ return;
835
+ }
836
+ let mention = val.substring((this.startPos() || 0) + 1, pos);
837
+ if (event.keyCode !== UtilsKeyCodeConstant.BACKSPACE && !event.inputEvent) {
838
+ mention += charPressed;
839
+ }
840
+ const searchString = mention.replace(String.fromCharCode(160), ' ').replace(String.fromCharCode(0), '');
841
+ this.searchString.set(searchString.trim());
842
+ const limitSpaceSearchQuery = this.activeConfig()?.limitSpaceSearchQuery;
843
+ if (limitSpaceSearchQuery && searchString.split(' ').length - 1 > limitSpaceSearchQuery) {
844
+ this.searchString.set('');
845
+ }
846
+ if (this.activeConfig()?.returnTrigger) {
847
+ const triggerChar = this.searchString() || event.keyCode === UtilsKeyCodeConstant.BACKSPACE ? val.substring(this.startPos(), (this.startPos() || 0) + 1) : '';
848
+ this.outSearchTerm.emit(triggerChar + this.searchString());
849
+ this.updateSearchList();
850
+ return;
851
+ }
852
+ this.outSearchTerm.emit(this.searchString());
853
+ this.updateSearchList();
854
+ }
855
+ handlerFocus(e, nativeElement = this.element.nativeElement) {
856
+ e?.stopPropagation();
857
+ setTimeout(() => {
858
+ const node = getWindowSelection(this.iframe())?.anchorNode;
859
+ if (node?.nodeType !== 3) {
860
+ this.stopSearch();
861
+ return;
862
+ }
863
+ const textBeforeCursor = getTextBeforeCursor() || '';
864
+ const triggerChars = Object.keys(this.triggerChars()).map((key) => {
865
+ return {
866
+ key: key,
867
+ config: this.triggerChars()[key],
868
+ index: textBeforeCursor.lastIndexOf(key),
869
+ };
870
+ });
871
+ const matchingCharacters = triggerChars.reduce((pre, current) => (pre && current && current.index > pre.index ? current : pre));
872
+ if (matchingCharacters.index < 0) {
873
+ this.stopSearch();
874
+ return;
875
+ }
876
+ const search = textBeforeCursor?.substring(matchingCharacters.index, textBeforeCursor.length);
877
+ if (!search) {
878
+ this.stopSearch();
879
+ return;
880
+ }
881
+ const pos = getCaretPosition(nativeElement, this.iframe());
882
+ this.activeConfig.set(matchingCharacters.config);
883
+ this.startPos.set(pos - (textBeforeCursor.length - matchingCharacters.index));
884
+ this.searchString.set(search.replace(matchingCharacters.key, ''));
885
+ this.searching.set(true);
886
+ this.handlerKeydown({ keyCode: ''.charCodeAt(0), inputEvent: false }, nativeElement);
887
+ const divElement = document.createElement('div');
888
+ divElement.style.width = 'max-content';
889
+ divElement.style.position = 'absolute';
890
+ divElement.innerText = search;
891
+ document.body.appendChild(divElement);
892
+ let iframe = undefined;
893
+ try {
894
+ iframe = this.mentionConfig()?.iframe ? window.parent.document.getElementById(this.mentionConfig()?.iframe) : undefined;
895
+ }
896
+ catch (error) {
897
+ console.log(error);
898
+ }
899
+ const searchList = this.searchList();
900
+ if (searchList) {
901
+ searchList.position(nativeElement, iframe, divElement.getBoundingClientRect().width);
902
+ }
903
+ divElement.remove();
904
+ nativeElement.normalize();
905
+ });
906
+ }
907
+ stopSearch() {
908
+ const searchList = this.searchList();
909
+ if (searchList && !searchList.hidden()) {
910
+ searchList.hidden.set(true);
911
+ this.outToggle.emit(false);
912
+ }
913
+ this.activeConfig.set(undefined);
914
+ this.searching.set(false);
915
+ }
916
+ updateSearchList() {
917
+ const matches = [];
918
+ const activeConfig = this.activeConfig();
919
+ const searchString = this.searchString();
920
+ const searchList = this.searchList();
921
+ // disabling the search relies on the async operation to do the filtering
922
+ if (activeConfig && activeConfig.items && !activeConfig.disableSearch && !isNil(searchString) && activeConfig.labelKey && activeConfig.mentionFilter) {
923
+ matches.push(...activeConfig.mentionFilter(searchString, activeConfig.items));
924
+ }
925
+ // update the search list
926
+ if (!searchList) {
927
+ return;
928
+ }
929
+ searchList.items.set(matches);
930
+ searchList.hidden.set(!matches.length);
931
+ searchList.activeIndex.set(0);
932
+ searchList.scrollContent();
933
+ this.outToggle.emit(matches.length ? true : false);
934
+ }
935
+ showSearchList(nativeElement) {
936
+ let iframe = undefined;
937
+ const mentionConfig = this.mentionConfig();
938
+ try {
939
+ iframe = mentionConfig?.iframe ? window.parent.document.getElementById(mentionConfig?.iframe) : undefined;
940
+ }
941
+ catch (error) {
942
+ console.log(error);
943
+ }
944
+ if (!mentionConfig) {
945
+ return;
946
+ }
947
+ this.outToggle.emit(true);
948
+ let firstTime = false;
949
+ if (!this.listComponentRef()) {
950
+ firstTime = true;
951
+ this.listComponentRef.set(this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsInputsMentionListComponent));
952
+ this.searchList.set(this.listComponentRef()?.instance);
953
+ }
954
+ this.searchList()?.labelKey.set(this.activeConfig()?.labelKey || '');
955
+ this.searchList()?.styleOff.set(this.mentionConfig()?.disableStyle || false);
956
+ this.searchList()?.activeIndex.set(0);
957
+ this.searchList()?.zIndex.set(this.mentionConfig()?.zIndex || 2500);
958
+ this.searchList()?.isIframe.set(iframe ? true : false);
959
+ this.searchList()?.parentHandlerKeyDown.set(this.handlerKeydown.bind(this));
960
+ this.searchList()?.position(nativeElement, iframe);
961
+ if (firstTime && this.listComponentRef()) {
962
+ this.iframeId.set(this.dynamicComponentService.addToBody(this.listComponentRef(), iframe ? true : false));
963
+ }
964
+ }
965
+ destroyListSearch() {
966
+ this.dynamicComponentService.remove(this.listComponentRef, this.iframeId());
967
+ this.listComponentRef.set(undefined);
968
+ }
969
+ appendEmptyToLastNode(position, length, nativeElement = this.element.nativeElement) {
970
+ const empty = document.createTextNode('\u00A0');
971
+ const selection = getWindowSelection(this.iframe());
972
+ const anchorNode = selection?.anchorNode;
973
+ if (this.isEditor()) {
974
+ const parentNode = anchorNode?.parentNode;
975
+ if (parentNode && parentNode.getAttribute && parentNode.getAttribute('id')) {
976
+ if (!parentNode.nextSibling || !parentNode.nextSibling.textContent?.length) {
977
+ parentNode?.parentNode?.insertBefore(empty, parentNode.nextSibling);
978
+ }
979
+ const range = document.createRange();
980
+ const sel = window.getSelection();
981
+ range.setStart(parentNode.nextSibling, 0);
982
+ range.collapse(true);
983
+ sel?.removeAllRanges();
984
+ sel?.addRange(range);
985
+ }
986
+ return;
987
+ }
988
+ let preNode = null;
989
+ const anchorOffset = selection?.anchorOffset;
990
+ if (!anchorNode || !anchorOffset) {
991
+ return;
992
+ }
993
+ if (anchorNode.nodeName.toLowerCase() !== '#text') {
994
+ nativeElement.appendChild(empty);
995
+ return;
996
+ }
997
+ for (let i = 0; i < nativeElement.childNodes.length; i++) {
998
+ const node = nativeElement.childNodes[i];
999
+ if (anchorNode.nodeType === 3 && anchorNode === node && preNode && preNode.getAttribute && preNode.getAttribute('id') && (!anchorNode.textContent?.length || anchorNode.textContent.length === 1) && position === length) {
1000
+ nativeElement.appendChild(empty);
1001
+ break;
1002
+ }
1003
+ preNode = node;
1004
+ }
1005
+ }
1006
+ addNodeZeroWidthSpace(nativeElement = this.element.nativeElement) {
1007
+ const empty = document.createTextNode('\u200b');
1008
+ let preNode = null;
1009
+ for (let i = 0; i < nativeElement.childNodes.length; i++) {
1010
+ const node = nativeElement.childNodes[i];
1011
+ if (preNode && preNode.getAttribute && preNode.getAttribute('id') && node && node.getAttribute && node.getAttribute('id')) {
1012
+ nativeElement.insertBefore(empty, preNode.nextSibling);
1013
+ }
1014
+ preNode = node;
1015
+ }
1016
+ }
1017
+ insertMention(nativeElement, pos) {
1018
+ const activeConfig = this.activeConfig();
1019
+ if (!activeConfig) {
1020
+ return;
1021
+ }
1022
+ const searchList = this.searchList();
1023
+ let text = '';
1024
+ const feId = uuid();
1025
+ const id = searchList?.ActiveItem.id || uuid();
1026
+ const itemActive = searchList?.ActiveItem;
1027
+ const { triggerChar, mentionEventName, mentionActionByEvent } = activeConfig;
1028
+ if (activeConfig.mentionSelect) {
1029
+ text = activeConfig.mentionSelect(searchList?.ActiveItem, activeConfig.triggerChar);
1030
+ }
1031
+ if (!this.isEditor()) {
1032
+ let span = document.createElement('TEXTAREA');
1033
+ span.innerText = text;
1034
+ text = span.innerHTML;
1035
+ span.remove();
1036
+ text = buildTemplate(text, id, feId);
1037
+ span = insertValue(nativeElement, this.startPos() || 0, pos, text, this.iframe());
1038
+ if (span) {
1039
+ const data = {
1040
+ elementSpan: span,
1041
+ subscription: undefined,
1042
+ item: itemActive,
1043
+ triggerChar: triggerChar,
1044
+ manualAdd: true,
1045
+ };
1046
+ this.nodesInsert().add(data);
1047
+ if (mentionEventName) {
1048
+ data.subscription = fromEvent(span, mentionEventName)
1049
+ .pipe(tap((e) => e.stopPropagation()), takeUntil$1(this.onDestroy))
1050
+ .subscribe(() => {
1051
+ if (mentionActionByEvent) {
1052
+ mentionActionByEvent(itemActive, triggerChar);
1053
+ }
1054
+ });
1055
+ }
1056
+ }
1057
+ }
1058
+ else {
1059
+ this.outInsertMention.emit({ data: { id, value: text, feId }, lengthKey: pos - (this.startPos() || 0) });
1060
+ }
1061
+ }
1062
+ ngOnDestroy() {
1063
+ this.destroyListSearch();
1064
+ this.onDestroy.next();
1065
+ this.onDestroy.complete();
1066
+ }
1067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsMentionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1068
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiComponentsInputsMentionDirective, isStandalone: true, selector: "[LibsUiComponentsInputsMentionDirective]", inputs: { timeDelayInit: { classPropertyName: "timeDelayInit", publicName: "timeDelayInit", isSignal: true, isRequired: false, transformFunction: null }, mentionConfig: { classPropertyName: "mentionConfig", publicName: "mentionConfig", isSignal: true, isRequired: false, transformFunction: null }, mentionListTemplate: { classPropertyName: "mentionListTemplate", publicName: "mentionListTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outSearchTerm: "outSearchTerm", outItemSelected: "outItemSelected", outToggle: "outToggle", outInsertMention: "outInsertMention", outFunctionControl: "outFunctionControl" }, ngImport: i0 });
1069
+ }
1070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsMentionDirective, decorators: [{
1071
+ type: Directive,
1072
+ args: [{
1073
+ // eslint-disable-next-line @angular-eslint/directive-selector
1074
+ selector: '[LibsUiComponentsInputsMentionDirective]',
1075
+ standalone: true,
1076
+ }]
1077
+ }], ctorParameters: () => [] });
1078
+
1079
+ /**
1080
+ * Generated bundle index. Do not edit.
1081
+ */
1082
+
1083
+ export { LibsUiComponentsInputsMentionDirective };
1084
+ //# sourceMappingURL=libs-ui-components-inputs-mention.mjs.map