PageSourceSearch

https://vestnikkui.ru/bundles/asutemplate/Base/js/editorum-ui/edit…?v=20260929-truncation-tooltips

js vestnikkui.ru collected 2026-10-02 20:03:07 UTC 29,657 bytes, 907 lines download raw bytes

1(function (window, document) {
2    var namespace = window.EditorumUi = window.EditorumUi || {};
3    var internals = namespace.__internals = namespace.__internals || {};
4    var selectpickerTitlePatchInitialized = false;
5    var tooltipCompatInitialized = false;
6    var activeTooltipTrigger = null;
7    var activeTooltipElement = null;
8    var activeTooltipIsTruncation = false;
9    var suppressedTitleElement = null;
10    var suppressedTitle = null;
11    var textMeasureContext = null;
12    // Longer text is a whole section or message preview; it opens in full through its own link or expander.
13    var truncationTooltipMaxLength = 600;
14    var measurableFieldTypes = ['', 'text', 'search', 'email', 'url', 'tel', 'number'];
15    var tooltipCompatSelector = [
16        '[data-toggle="tooltip"]',
17        '[data-tooltip-compat="true"]',
18        '[data-toggle][title]',
19        '[data-toggle][data-original-title]'
20    ].join(', ');
21    var tooltipToggleHelpSelector = [
22        '.ds-help-icon',
23        '.for-request',
24        '.tooltip-here',
25        '.glyphicon-question-sign',
26        '.glyphicon-info-sign',
27        '[data-tooltip-action="toggle"]'
28    ].join(', ');
29    var tooltipActionSelector = [
30        'a[href]',
31        'button',
32        'input',
33        'select',
34        'textarea',
35        'label',
36        'summary',
37        '[data-toggle="modal"]',
38        '[data-toggle="dropdown"]',
39        '[data-toggle="collapse"]',
40        '[data-toggle="tab"]'
41    ].join(', ');
42
43    function hasVisibleTextNode(node) {
44        var childNodes;
45        var index;
46
47        if (!node || !node.childNodes) {
48            return false;
49        }
50
51        childNodes = node.childNodes;
52
53        for (index = 0; index < childNodes.length; index += 1) {
54            if (childNodes[index].nodeType === 3 && childNodes[index].textContent.trim()) {
55                return true;
56            }
57        }
58
59        return false;
60    }
61
62    function isIconOnlyTrigger(trigger) {
63        var text = (trigger.textContent || '').replace(/\s+/g, ' ').trim();
64
65        if (!trigger) {
66            return false;
67        }
68
69        if (trigger.getAttribute('data-tooltip-mode') === 'icon') {
70            return true;
71        }
72
73        if (trigger.querySelector && trigger.querySelector('.ds-icon, svg, i') && !hasVisibleTextNode(trigger)) {
74            return !text;
75        }
76
77        return !text;
78    }
79
80    function isElementTruncated(element) {
81        if (!element || element.nodeType !== 1) {
82            return false;
83        }
84
85        return element.scrollWidth > element.clientWidth + 1
86            || element.scrollHeight > element.clientHeight + 1;
87    }
88
89    // Text that CSS cuts short (text-overflow: ellipsis or -webkit-line-clamp) shows the white tooltip with
90    // its full value, without markup opting in. Elements marked data-tooltip-truncation="off" are skipped.
91    function getTruncationStyle(element) {
92        var style = window.getComputedStyle ? window.getComputedStyle(element) : null;
93        var lineClamp;
94
95        if (!style) {
96            return '';
97        }
98
99        if (style.textOverflow === 'ellipsis' && style.overflowX !== 'visible') {
100            return 'ellipsis';
101        }
102
103        lineClamp = style.webkitLineClamp || style.getPropertyValue('-webkit-line-clamp');
104
105        return lineClamp && lineClamp !== 'none' ? 'line-clamp' : '';
106    }
107
108    function isFormField(element) {
109        return element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA';
110    }
111
112    function measureTextWidth(text, style) {
113        if (!textMeasureContext) {
114            textMeasureContext = document.createElement('canvas').getContext('2d');
115        }
116
117        textMeasureContext.font = style.fontStyle + ' ' + style.fontWeight + ' ' + style.fontSize + ' ' + style.fontFamily;
118
119        return textMeasureContext.measureText(text).width;
120    }
121
122    // A field draws its text outside the DOM tree, so scrollWidth does not show the overflow; measure the text instead.
123    function isFieldTruncated(element) {
124        var style = window.getComputedStyle(element);
125        var available;
126
127        if (style.textOverflow !== 'ellipsis' || element.tagName === 'TEXTAREA' || element.multiple) {
128            return false;
129        }
130
131        if (element.tagName === 'INPUT' && measurableFieldTypes.indexOf((element.getAttribute('type') || '').toLowerCase()) === -1) {
132            return false;
133        }
134
135        available = element.clientWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
136
137        // A natively drawn select arrow sits inside the content box.
138        if (element.tagName === 'SELECT' && (style.appearance || style.webkitAppearance) !== 'none') {
139            available -= 20;
140        }
141
142        return measureTextWidth(readFullText(element), style) > available + 1;
143    }
144
145    function findTruncatedElement(target) {
146        var element = target && target.nodeType === 1 ? target : (target ? target.parentElement : null);
147        var depth;
148        var truncation;
149
150        for (depth = 0; element && element !== document.body && depth < 8; depth += 1) {
151            if (element.getAttribute('data-tooltip-truncation') === 'off') {
152                return null;
153            }
154
155            if (isFormField(element)) {
156                return isFieldTruncated(element) ? element : null;
157            }
158
159            truncation = getTruncationStyle(element);
160
161            if (truncation === 'ellipsis') {
162                return element.scrollWidth > element.clientWidth + 1 ? element : null;
163            }
164
165            // A clamped block also clips a single word that is wider than the box.
166            if (truncation === 'line-clamp') {
167                return element.scrollHeight > element.clientHeight + 1 || element.scrollWidth > element.clientWidth + 1
168                    ? element
169                    : null;
170            }
171
172            element = element.parentElement;
173        }
174
175        return null;
176    }
177
178    function readFullText(element) {
179        if (element.tagName === 'SELECT') {
180            return element.selectedIndex >= 0 ? element.options[element.selectedIndex].text.trim() : '';
181        }
182
183        if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
184            return (element.value || element.getAttribute('placeholder') || '').trim();
185        }
186
187        return (element.innerText || element.textContent || '').replace(/\s+/g, ' ').trim();
188    }
189
190    function suppressNativeTitle(target) {
191        var element = target && target.closest ? target.closest('[title]') : null;
192
193        if (!element || !element.getAttribute('title')) {
194            return;
195        }
196
197        suppressedTitleElement = element;
198        suppressedTitle = element.getAttribute('title');
199        element.removeAttribute('title');
200    }
201
202    function restoreNativeTitle() {
203        if (suppressedTitleElement && !suppressedTitleElement.hasAttribute('title')) {
204            suppressedTitleElement.setAttribute('title', suppressedTitle);
205        }
206
207        suppressedTitleElement = null;
208        suppressedTitle = null;
209    }
210
211    function showTruncationTooltip(target) {
212        var element = findTruncatedElement(target);
213        var text;
214
215        if (!element) {
216            return false;
217        }
218
219        if (element === activeTooltipTrigger) {
220            return true;
221        }
222
223        // A field being edited shows its own text; a tooltip over it would cover the caret line.
224        if ((element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') && element === document.activeElement) {
225            return false;
226        }
227
228        text = readFullText(element);
229
230        if (!text || text.length > truncationTooltipMaxLength) {
231            return false;
232        }
233
234        showTooltip(element, text, 'top', 'tooltip--full-text');
235        activeTooltipIsTruncation = true;
236        // The native title would show a second, dark tooltip over the white one.
237        suppressNativeTitle(target);
238
239        return true;
240    }
241
242    function getTooltipMeasurementTargets(trigger) {
243        var targets = [];
244        var selector = trigger && trigger.getAttribute ? trigger.getAttribute('data-tooltip-target') : '';
245        var matched;
246
247        if (!trigger) {
248            return targets;
249        }
250
251        targets.push(trigger);
252
253        if (selector && trigger.querySelectorAll) {
254            matched = trigger.querySelectorAll(selector);
255            Array.prototype.forEach.call(matched, function (node) {
256                targets.push(node);
257            });
258        } else if (trigger.querySelectorAll) {
259            matched = trigger.querySelectorAll('.admin-list-multiselect-option-label, .td-cell-content, .ds-text-truncate, .text-truncate, .truncate, .ellipsis');
260            Array.prototype.forEach.call(matched, function (node) {
261                targets.push(node);
262            });
263        }
264
265        return targets;
266    }
267
268    function isIgnoredTooltipTrigger(trigger) {
269        return !!(
270            trigger
271            && trigger.closest
272            && trigger.closest('.bootstrap-select')
273        );
274    }
275
276    function isTooltipCandidate(trigger) {
277        return !!(
278            trigger
279            && internals.matches
280            && internals.matches(trigger, tooltipCompatSelector)
281        );
282    }
283
284    function prepareTooltipTrigger(trigger) {
285        var title;
286
287        if (!isTooltipCandidate(trigger)) {
288            return;
289        }
290
291        if (isIgnoredTooltipTrigger(trigger)) {
292            trigger.removeAttribute('title');
293            trigger.removeAttribute('data-original-title');
294            return;
295        }
296
297        if (trigger.hasAttribute('data-toggle') && trigger.getAttribute('data-tooltip-compat') !== 'true') {
298            trigger.setAttribute('data-tooltip-compat', 'true');
299        }
300
301        title = trigger.getAttribute('title');
302
303        if (title !== null) {
304            if (title && !trigger.getAttribute('data-original-title')) {
305                trigger.setAttribute('data-original-title', title);
306            }
307
308            if (title && !trigger.getAttribute('aria-label')) {
309                trigger.setAttribute('aria-label', title);
310            }
311
312            trigger.removeAttribute('title');
313            return;
314        }
315
316        title = trigger.getAttribute('data-original-title');
317
318        if (title && !trigger.getAttribute('aria-label')) {
319            trigger.setAttribute('aria-label', title);
320        }
321    }
322
323    function prepareTooltipTriggers(scope) {
324        var root = scope && scope.nodeType === 1 ? scope : document;
325        var triggers = [];
326
327        if (isTooltipCandidate(root)) {
328            triggers.push(root);
329        }
330
331        triggers = triggers.concat(internals.queryAll
332            ? internals.queryAll(tooltipCompatSelector, root)
333            : Array.prototype.slice.call(root.querySelectorAll(tooltipCompatSelector)));
334
335        Array.prototype.forEach.call(triggers, prepareTooltipTrigger);
336    }
337
338    function shouldRenderTooltip(trigger) {
339        var targets;
340        var index;
341
342        if (!trigger || isIgnoredTooltipTrigger(trigger)) {
343            return false;
344        }
345
346        if (trigger.getAttribute('data-tooltip-mode') === 'overflow') {
347            targets = getTooltipMeasurementTargets(trigger);
348
349            for (index = 0; index < targets.length; index += 1) {
350                if (isElementTruncated(targets[index])) {
351                    return true;
352                }
353            }
354
355            // An inline label measures 0x0; the ellipsis is drawn by the block around it.
356            return !!findTruncatedElement(trigger);
357        }
358
359        return isTooltipCandidate(trigger) || isIconOnlyTrigger(trigger);
360    }
361
362    function readTooltipText(trigger) {
363        var title = trigger.getAttribute('data-original-title');
364
365        if (!title) {
366            title = trigger.getAttribute('data-tooltip-title');
367        }
368
369        if (!title) {
370            title = trigger.getAttribute('title');
371            if (title) {
372                trigger.setAttribute('data-original-title', title);
373                trigger.removeAttribute('title');
374            }
375        }
376
377        if (!title) {
378            title = trigger.getAttribute('aria-label');
379        }
380
381        return title;
382    }
383
384    function getTooltipPlacement(trigger) {
385        var placement = trigger.getAttribute('data-placement');
386        var parts;
387        var index;
388
389        if (placement === 'top' || placement === 'right' || placement === 'bottom' || placement === 'left') {
390            return placement;
391        }
392
393        if (placement) {
394            parts = placement.split(/\s+/);
395
396            for (index = 0; index < parts.length; index += 1) {
397                if (parts[index] === 'top' || parts[index] === 'right' || parts[index] === 'bottom' || parts[index] === 'left') {
398                    return parts[index];
399                }
400            }
401        }
402
403        return 'top';
404    }
405
406    function matchesMedia(query) {
407        if (!window.matchMedia) {
408            return false;
409        }
410
411        try {
412            return window.matchMedia(query).matches;
413        } catch (error) {
414            return false;
415        }
416    }
417
418    function hasTouchInput() {
419        return ('ontouchstart' in window)
420            || !!(window.navigator && window.navigator.maxTouchPoints > 0);
421    }
422
423    function canUseHoverTooltips() {
424        if (matchesMedia('(hover: hover) and (pointer: fine)')) {
425            return true;
426        }
427
428        if (matchesMedia('(any-hover: hover) and (any-pointer: fine)')) {
429            return true;
430        }
431
432        if (window.matchMedia) {
433            return false;
434        }
435
436        return !hasTouchInput();
437    }
438
439    function isCoarsePointer() {
440        if (window.matchMedia) {
441            return matchesMedia('(hover: none)')
442                || matchesMedia('(pointer: coarse)')
443                || matchesMedia('(any-pointer: coarse)');
444        }
445
446        return hasTouchInput();
447    }
448
449    function isTooltipHelpTrigger(trigger) {
450        return !!trigger
451            && !!internals.matches
452            && internals.matches(trigger, tooltipToggleHelpSelector);
453    }
454
455    function isActionTooltipTrigger(trigger) {
456        return !!trigger
457            && !!internals.matches
458            && internals.matches(trigger, tooltipActionSelector);
459    }
460
461    function hasDedicatedClickHelp(trigger) {
462        return !!trigger
463            && !!internals.matches
464            && internals.matches(trigger, '.compose-info');
465    }
466
467    function shouldUseClickTooltip(trigger) {
468        if (!trigger || !isCoarsePointer() || hasDedicatedClickHelp(trigger)) {
469            return false;
470        }
471
472        // Full text of a truncated label is a hover aid; on touch a tap must still select the option or follow the link.
473        if (trigger.getAttribute('data-tooltip-mode') === 'overflow') {
474            return false;
475        }
476
477        if (isTooltipHelpTrigger(trigger)) {
478            return true;
479        }
480
481        return !isActionTooltipTrigger(trigger);
482    }
483
484    function hasInteractiveAncestor(trigger) {
485        var current = trigger ? trigger.parentNode : null;
486
487        while (current && current !== document) {
488            if (internals.matches && internals.matches(current, tooltipActionSelector)) {
489                return true;
490            }
491
492            current = current.parentNode;
493        }
494
495        return false;
496    }
497
498    function prepareClickableTooltipTrigger(trigger) {
499        var label;
500
501        if (!trigger || !shouldUseClickTooltip(trigger)) {
502            return;
503        }
504
505        label = trigger.getAttribute('aria-label')
506            || trigger.getAttribute('data-original-title')
507            || trigger.getAttribute('title');
508
509        if (label && !trigger.getAttribute('aria-label')) {
510            trigger.setAttribute('aria-label', label);
511        }
512
513        if (!isActionTooltipTrigger(trigger) && !trigger.hasAttribute('tabindex')) {
514            trigger.setAttribute('tabindex', '0');
515        }
516
517        if (!trigger.getAttribute('role') && !isActionTooltipTrigger(trigger)) {
518            trigger.setAttribute('role', 'button');
519        }
520
521        if (trigger.getAttribute('aria-hidden') === 'true') {
522            trigger.removeAttribute('aria-hidden');
523        }
524
525        trigger.setAttribute('data-editorum-tooltip-clickable', 'true');
526
527        if (!trigger.hasAttribute('aria-expanded')) {
528            trigger.setAttribute('aria-expanded', 'false');
529        }
530    }
531
532    function prepareClickableTooltipTriggers(scope) {
533        var triggers;
534
535        if (!isCoarsePointer()) {
536            return;
537        }
538
539        triggers = internals.queryAll
540            ? internals.queryAll(tooltipCompatSelector, scope || document)
541            : Array.prototype.slice.call((scope || document).querySelectorAll(tooltipCompatSelector));
542
543        Array.prototype.forEach.call(triggers, prepareClickableTooltipTrigger);
544    }
545
546    function removeActiveTooltip() {
547        if (activeTooltipElement && activeTooltipElement.parentNode) {
548            activeTooltipElement.parentNode.removeChild(activeTooltipElement);
549        }
550
551        if (activeTooltipTrigger) {
552            activeTooltipTrigger.removeAttribute('aria-describedby');
553
554            if (activeTooltipTrigger.getAttribute('data-editorum-tooltip-clickable') === 'true') {
555                activeTooltipTrigger.setAttribute('aria-expanded', 'false');
556            }
557        }
558
559        activeTooltipElement = null;
560        activeTooltipTrigger = null;
561        activeTooltipIsTruncation = false;
562        restoreNativeTitle();
563    }
564
565    function positionTooltip(trigger, tooltip, placement) {
566        var triggerRect = trigger.getBoundingClientRect();
567        var tooltipRect = tooltip.getBoundingClientRect();
568        var scrollX = window.pageXOffset || document.documentElement.scrollLeft || 0;
569        var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
570        var viewportW = document.documentElement.clientWidth;
571        var viewportH = document.documentElement.clientHeight;
572        var edgePad = 8;
573        var gap = 0;
574        var top = 0;
575        var left = 0;
576        var idealLeft;
577        var arrow;
578        var flipped = false;
579        var maxLeft;
580        var maxTop;
581        var triggerCenter;
582        var arrowPos;
583        var requestedPlacement = placement;
584
585        if (placement === 'top' && triggerRect.top < tooltipRect.height + edgePad
586            && triggerRect.bottom + tooltipRect.height + edgePad <= viewportH) {
587            placement = 'bottom';
588            flipped = true;
589        } else if (placement === 'bottom' && triggerRect.bottom + tooltipRect.height + edgePad > viewportH
590            && triggerRect.top >= tooltipRect.height + edgePad) {
591            placement = 'top';
592            flipped = true;
593        } else if (placement === 'left' && triggerRect.left < tooltipRect.width + edgePad
594            && triggerRect.right + tooltipRect.width + edgePad <= viewportW) {
595            placement = 'right';
596            flipped = true;
597        } else if (placement === 'right' && triggerRect.right + tooltipRect.width + edgePad > viewportW
598            && triggerRect.left >= tooltipRect.width + edgePad) {
599            placement = 'left';
600            flipped = true;
601        }
602
603        if (flipped) {
604            tooltip.classList.remove(requestedPlacement);
605            tooltip.classList.add(placement);
606        }
607
608        if (placement === 'bottom') {
609            top = triggerRect.bottom + scrollY + gap;
610            left = triggerRect.left + scrollX + (triggerRect.width / 2) - (tooltipRect.width / 2);
611        } else if (placement === 'left') {
612            top = triggerRect.top + scrollY + (triggerRect.height / 2) - (tooltipRect.height / 2);
613            left = triggerRect.left + scrollX - tooltipRect.width - gap;
614        } else if (placement === 'right') {
615            top = triggerRect.top + scrollY + (triggerRect.height / 2) - (tooltipRect.height / 2);
616            left = triggerRect.right + scrollX + gap;
617        } else {
618            top = triggerRect.top + scrollY - tooltipRect.height - gap;
619            left = triggerRect.left + scrollX + (triggerRect.width / 2) - (tooltipRect.width / 2);
620        }
621
622        idealLeft = left;
623        maxLeft = scrollX + viewportW - tooltipRect.width - edgePad;
624        left = Math.max(left, edgePad);
625        left = Math.min(left, maxLeft);
626
627        maxTop = scrollY + viewportH - tooltipRect.height - edgePad;
628        top = Math.max(top, scrollY + edgePad);
629        top = Math.min(top, maxTop);
630
631        tooltip.style.top = top + 'px';
632        tooltip.style.left = left + 'px';
633
634        if (left !== idealLeft && (placement === 'top' || placement === 'bottom')) {
635            arrow = tooltip.querySelector('.tooltip-arrow');
636            if (arrow) {
637                triggerCenter = triggerRect.left + scrollX + (triggerRect.width / 2);
638                arrowPos = triggerCenter - left;
639                arrowPos = Math.max(12, Math.min(arrowPos, tooltipRect.width - 12));
640                arrow.style.left = arrowPos + 'px';
641                arrow.style.marginLeft = '0';
642            }
643        }
644    }
645
646    function renderTooltip(trigger) {
647        var tooltipText;
648
649        prepareTooltipTrigger(trigger);
650        tooltipText = readTooltipText(trigger);
651
652        if (!tooltipText || !shouldRenderTooltip(trigger)) {
653            return null;
654        }
655
656        return showTooltip(trigger, tooltipText, getTooltipPlacement(trigger));
657    }
658
659    function showTooltip(trigger, tooltipText, placement, modifierClass) {
660        var tooltip;
661        var arrow;
662        var inner;
663        var tooltipId;
664
665        removeActiveTooltip();
666
667        tooltip = document.createElement('div');
668        tooltip.className = 'tooltip ' + placement + ' in';
669
670        if (modifierClass) {
671            tooltip.classList.add(modifierClass);
672        }
673
674        tooltip.setAttribute('role', 'tooltip');
675
676        tooltipId = 'editorum-tooltip-' + Date.now();
677        tooltip.id = tooltipId;
678
679        arrow = document.createElement('div');
680        arrow.className = 'tooltip-arrow';
681
682        inner = document.createElement('div');
683        inner.className = 'tooltip-inner';
684        inner.textContent = tooltipText;
685
686        tooltip.appendChild(arrow);
687        tooltip.appendChild(inner);
688        document.body.appendChild(tooltip);
689
690        trigger.setAttribute('aria-describedby', tooltipId);
691        if (trigger.getAttribute('data-editorum-tooltip-clickable') === 'true') {
692            trigger.setAttribute('aria-expanded', 'true');
693        }
694        positionTooltip(trigger, tooltip, placement);
695
696        activeTooltipTrigger = trigger;
697        activeTooltipElement = tooltip;
698
699        return tooltip;
700    }
701
702    function toggleClickTooltip(trigger) {
703        prepareClickableTooltipTrigger(trigger);
704
705        if (activeTooltipTrigger === trigger) {
706            removeActiveTooltip();
707            return;
708        }
709
710        renderTooltip(trigger);
711    }
712
713    function isSameTooltipTrigger(first, second) {
714        return !!first && !!second && first === second;
715    }
716
717    function installTooltipJQueryCompat() {
718        return false;
719    }
720
721    function prepareTooltipMutationNode(node) {
722        if (!node || node.nodeType !== 1) {
723            return;
724        }
725
726        if (isTooltipCandidate(node)) {
727            prepareTooltipTrigger(node);
728            prepareClickableTooltipTrigger(node);
729        }
730
731        if (node.querySelectorAll) {
732            prepareTooltipTriggers(node);
733            prepareClickableTooltipTriggers(node);
734        }
735    }
736
737    function initTooltipCompat(root) {
738        var scope = root || document;
739
740        prepareTooltipTriggers(scope);
741        prepareClickableTooltipTriggers(scope);
742
743        if (tooltipCompatInitialized) {
744            return true;
745        }
746
747        tooltipCompatInitialized = true;
748
749        document.addEventListener('mouseover', function (event) {
750            var trigger = internals.closest(event.target, tooltipCompatSelector);
751            var fromTrigger = internals.closest(event.relatedTarget, tooltipCompatSelector);
752
753            if (!canUseHoverTooltips()) {
754                return;
755            }
756
757            if (!trigger) {
758                showTruncationTooltip(event.target);
759                return;
760            }
761
762            if (isSameTooltipTrigger(trigger, fromTrigger)) {
763                return;
764            }
765
766            renderTooltip(trigger);
767        });
768
769        document.addEventListener('mouseout', function (event) {
770            var trigger = internals.closest(event.target, tooltipCompatSelector);
771            var toTrigger = internals.closest(event.relatedTarget, tooltipCompatSelector);
772
773            if (activeTooltipIsTruncation) {
774                if (!event.relatedTarget || !activeTooltipTrigger.contains(event.relatedTarget)) {
775                    removeActiveTooltip();
776                }
777                return;
778            }
779
780            if (!canUseHoverTooltips() || !trigger || isSameTooltipTrigger(trigger, toTrigger)) {
781                return;
782            }
783
784            if (activeTooltipTrigger === trigger) {
785                removeActiveTooltip();
786            }
787        });
788
789        document.addEventListener('focusin', function (event) {
790            var trigger = internals.closest(event.target, tooltipCompatSelector);
791
792            if (!trigger) {
793                showTruncationTooltip(event.target);
794                return;
795            }
796
797            if (!shouldUseClickTooltip(trigger) && !hasDedicatedClickHelp(trigger)) {
798                renderTooltip(trigger);
799            }
800        });
801
802        document.addEventListener('focusout', function (event) {
803            var trigger = internals.closest(event.target, tooltipCompatSelector);
804
805            if (activeTooltipIsTruncation) {
806                if (activeTooltipTrigger.contains(event.target)) {
807                    removeActiveTooltip();
808                }
809                return;
810            }
811
812            if (trigger
813                && activeTooltipTrigger === trigger
814                && trigger.getAttribute('data-editorum-tooltip-clickable') !== 'true') {
815                removeActiveTooltip();
816            }
817        });
818
819        document.addEventListener('click', function (event) {
820            var trigger = internals.closest(event.target, tooltipCompatSelector);
821
822            if (!shouldUseClickTooltip(trigger) || !hasInteractiveAncestor(trigger)) {
823                return;
824            }
825
826            event.preventDefault();
827            event.stopPropagation();
828            toggleClickTooltip(trigger);
829        }, true);
830
831        document.addEventListener('click', function (event) {
832            var trigger = internals.closest(event.target, tooltipCompatSelector);
833
834            if (shouldUseClickTooltip(trigger)) {
835                event.preventDefault();
836                toggleClickTooltip(trigger);
837                return;
838            }
839
840            if (activeTooltipTrigger
841                && event.target !== activeTooltipTrigger
842                && !activeTooltipTrigger.contains(event.target)
843                && (!activeTooltipElement || !activeTooltipElement.contains(event.target))) {
844                removeActiveTooltip();
845            }
846        });
847
848        document.addEventListener('keydown', function (event) {
849            var trigger = internals.closest(event.target, tooltipCompatSelector);
850
851            if (!shouldUseClickTooltip(trigger) || (event.key !== 'Enter' && event.key !== ' ')) {
852                return;
853            }
854
855            event.preventDefault();
856            toggleClickTooltip(trigger);
857        });
858
859        if (window.MutationObserver) {
860            new window.MutationObserver(function (mutations) {
861                Array.prototype.forEach.call(mutations, function (mutation) {
862                    if (mutation.type === 'attributes') {
863                        prepareTooltipMutationNode(mutation.target);
864                        return;
865                    }
866
867                    Array.prototype.forEach.call(mutation.addedNodes || [], prepareTooltipMutationNode);
868                });
869            }).observe(document.documentElement, {
870                attributeFilter: ['data-original-title', 'data-toggle', 'data-tooltip-compat', 'title'],
871                attributes: true,
872                childList: true,
873                subtree: true
874            });
875        }
876
877        window.addEventListener('scroll', removeActiveTooltip, { passive: true, capture: true });
878        window.addEventListener('resize', removeActiveTooltip, { passive: true });
879
880        return true;
881    }
882
883    function patchSelectpickerTitles() {
884        if (selectpickerTitlePatchInitialized) {
885            return true;
886        }
887        selectpickerTitlePatchInitialized = true;
888
889        Array.prototype.forEach.call(document.querySelectorAll('.bootstrap-select .dropdown-toggle'), function (button) {
890            button.removeAttribute('title');
891            button.removeAttribute('data-original-title');
892        });
893
894        return true;
895    }
896
897    internals.findTruncatedElement = findTruncatedElement;
898    internals.readTooltipText = readTooltipText;
899    internals.getTooltipPlacement = getTooltipPlacement;
900    internals.removeActiveTooltip = removeActiveTooltip;
901    internals.renderTooltip = renderTooltip;
902    internals.prepareTooltipTrigger = prepareTooltipTrigger;
903    internals.prepareTooltipTriggers = prepareTooltipTriggers;
904    internals.installTooltipJQueryCompat = installTooltipJQueryCompat;
905    internals.initTooltipCompat = initTooltipCompat;
906    internals.patchSelectpickerTitles = patchSelectpickerTitles;
907})(window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.