@libs-ui/components-inputs-mention 0.2.78

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