• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atinc / ngx-tethys / d9ae709b-3c27-4b69-b125-b8b80b54f90b

pending completion
d9ae709b-3c27-4b69-b125-b8b80b54f90b

Pull #2757

circleci

mengshuicmq
fix: fix code review
Pull Request #2757: feat(color-picker): color-picker support disabled (#INFR-8645)

98 of 6315 branches covered (1.55%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

2392 of 13661 relevant lines covered (17.51%)

83.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

3.91
/src/mention/caret-positioner.ts
1
import { Injectable } from '@angular/core';
2
import { getElementOffset } from 'ngx-tethys/util';
3

4
export interface CaretCoordinates {
5
    top: number;
6
    left: number;
1✔
7
    height: number;
8
}
9

10
export interface CaretOptions {
11
    debug: boolean;
12
}
13

14
// We'll copy the properties below into the mirror div.
15
// Note that some browsers, such as Firefox, do not concatenate properties
16
// into their shorthand (e.g. padding-top, padding-bottom etc. -> padding),
17
// so we have to list every single property explicitly.
18
const properties = [
19
    'direction', // RTL support
20
    'boxSizing',
21
    'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
22
    'height',
23
    'overflowX',
24
    'overflowY', // copy the scrollbar for IE
25

26
    'borderTopWidth',
27
    'borderRightWidth',
28
    'borderBottomWidth',
29
    'borderLeftWidth',
30
    'borderStyle',
31

32
    'paddingTop',
33
    'paddingRight',
34
    'paddingBottom',
35
    'paddingLeft',
36

37
    // https://developer.mozilla.org/en-US/docs/Web/CSS/font
38
    'fontStyle',
39
    'fontVariant',
40
    'fontWeight',
1✔
41
    'fontStretch',
1✔
42
    'fontSize',
43
    'fontSizeAdjust',
44
    'lineHeight',
45
    'fontFamily',
46

×
47
    'textAlign',
×
48
    'textTransform',
49
    'textIndent',
×
50
    'textDecoration', // might not make a difference, but better be safe
×
51

×
52
    'letterSpacing',
×
53
    'wordSpacing',
×
54

55
    'tabSize',
56
    'MozTabSize'
57
];
×
58

×
59
const isBrowser = typeof window !== 'undefined';
×
60
const isFirefox = isBrowser && window['mozInnerScreenX'] != null;
×
61

×
62
export type InputOrTextAreaElement = HTMLInputElement | HTMLTextAreaElement;
×
63
export type AllElement = InputOrTextAreaElement | HTMLElement;
64

×
65
export class CaretPositioner {
×
66
    // get caret coordinates in input or textarea
×
67
    // copy from repo: https://github.com/component/textarea-caret-position
68
    static getTextareaCaretCoordinates(element: InputOrTextAreaElement, position: number, options?: CaretOptions): CaretCoordinates {
69
        if ((typeof ngDevMode === 'undefined' || ngDevMode) && !isBrowser) {
×
70
            throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
×
71
        }
×
72

73
        const debug = (options && options.debug) || false;
74
        if (debug) {
×
75
            const el = document.querySelector('#input-textarea-caret-position-mirror-div');
×
76
            if (el) {
77
                el.parentNode.removeChild(el);
×
78
            }
×
79
        }
×
80

81
        // The mirror div will replicate the textarea's style
82
        const div = document.createElement('div');
83
        div.id = 'input-textarea-caret-position-mirror-div';
×
84
        document.body.appendChild(div);
×
85

×
86
        const style = div.style;
87
        const computed = window.getComputedStyle ? window.getComputedStyle(element) : element['currentStyle']; // currentStyle for IE < 9
×
88
        const isInput = element.nodeName === 'INPUT';
×
89

90
        // Default textarea styles
91
        style.whiteSpace = 'pre-wrap';
×
92
        if (!isInput) {
93
            style.wordWrap = 'break-word'; // only for textarea-s
94
        }
95

×
96
        // Position off-screen
97
        style.position = 'absolute'; // required to return coordinates properly
98
        if (!debug) {
99
            style.visibility = 'hidden'; // not 'display: none' because we want rendering
×
100
        }
101
        // Transfer the element's properties to the div
102
        properties.forEach(function (prop) {
×
103
            if (isInput && prop === 'lineHeight') {
104
                // Special case for <input>s because text is rendered centered and line height may be != height
×
105
                if (computed.boxSizing === 'border-box') {
×
106
                    const height = parseInt(computed.height, 10);
107
                    const outerHeight =
108
                        parseInt(computed.paddingTop, 10) +
109
                        parseInt(computed.paddingBottom, 10) +
×
110
                        parseInt(computed.borderTopWidth, 10) +
111
                        parseInt(computed.borderBottomWidth, 10);
×
112
                    const targetHeight = outerHeight + parseInt(computed.lineHeight, 10);
113
                    if (height > targetHeight) {
114
                        style.lineHeight = height - outerHeight + 'px';
×
115
                    } else if (height === targetHeight) {
×
116
                        style.lineHeight = computed.lineHeight;
117
                    } else {
×
118
                        style.lineHeight = '0';
119
                    }
120
                } else {
121
                    style.lineHeight = computed.height;
122
                }
123
            } else {
×
124
                style[prop] = computed[prop];
×
125
            }
×
126
        });
127

128
        if (isFirefox) {
129
            // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
130
            if (element.scrollHeight > parseInt(computed.height, 10)) {
×
131
                style.overflowY = 'scroll';
×
132
            }
133
        } else {
134
            style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
×
135
        }
136

×
137
        div.textContent = element.value.substring(0, position);
138
        // The second special handling for input type="text" vs textarea:
139
        // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
×
140
        if (isInput) {
×
141
            div.textContent = div.textContent.replace(/\s/g, '\u00a0');
×
142
        }
143

×
144
        const span = document.createElement('span');
145
        // Wrapping must be replicated *exactly*, including when a long word gets
146
        // onto the next line, with whitespace at the end of the line before (#7).
×
147
        // The  *only* reliable way to do that is to copy the *entire* rest of the
×
148
        // textarea's content into the <span> created at the caret position.
×
149
        // For inputs, just '.' would be enough, but no need to bother.
×
150
        span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
151
        div.appendChild(span);
×
152

×
153
        const coordinates = {
×
154
            top: span.offsetTop + parseInt(computed['borderTopWidth'], 10),
×
155
            left: span.offsetLeft + parseInt(computed['borderLeftWidth'], 10),
×
156
            height: parseInt(computed['lineHeight'], 10)
157
        };
×
158

159
        if (debug) {
160
            span.style.backgroundColor = '#aaa';
×
161
        } else {
×
162
            document.body.removeChild(div);
163
        }
164

165
        return coordinates;
166
    }
167

168
    static getEditableCaretCoordinates(element: HTMLElement): CaretCoordinates {
×
169
        if (window.getSelection().rangeCount) {
170
            const range = window.getSelection().getRangeAt(0);
171
            const rect = range.getBoundingClientRect();
172
            // using the start or endcontainer is... uhm yeah... difficult...? :D
173
            let height: string | number =
174
                range.startContainer.nodeType === 1
175
                    ? getComputedStyle(range.startContainer as Element).lineHeight
176
                    : getComputedStyle(range.startContainer.parentNode as Element).lineHeight;
×
177
            if (isNaN(height as any)) {
×
178
                let node = range.startContainer as HTMLElement;
×
179
                if (range.startContainer.nodeType !== 1) {
180
                    node = node.parentNode as HTMLElement;
181
                }
×
182
                const current = node.style.lineHeight;
183
                node.style.lineHeight = '1em';
184
                height = parseInt(getComputedStyle(node).lineHeight, 10);
185
                node.style.lineHeight = current != null ? current : '';
186
                if (!node.getAttribute('style').length) {
×
187
                    // clean up if empty
×
188
                    node.removeAttribute('style');
×
189
                }
190
            }
191
            const editableRect = element.getBoundingClientRect();
192
            return {
193
                top: rect.top - editableRect.top,
194
                left: rect.left - editableRect.left,
195
                height: height as number
196
            };
197
        } else {
198
            return {
199
                top: 0,
200
                left: 0,
201
                height: 0
202
            };
203
        }
204
    }
205

206
    static getCaretCoordinates(element: AllElement, position: number, options?: CaretOptions) {
207
        const isInput = ['INPUT', 'TEXTAREA'].indexOf(element.nodeName) >= 0;
208
        if (isInput) {
209
            return this.getTextareaCaretCoordinates(element as InputOrTextAreaElement, position, options);
210
        } else {
211
            return this.getEditableCaretCoordinates(element);
212
        }
213
    }
214

215
    // get caret position in view window
216
    static getCaretPosition(element: AllElement, position: number, options?: CaretOptions) {
217
        const coordinates = CaretPositioner.getCaretCoordinates(element, position, options);
218
        const elementOffset = getElementOffset(element);
219
        return {
220
            top: coordinates.top + elementOffset.top,
221
            left: coordinates.left + elementOffset.left
222
        };
223
    }
224
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc