/********************************************************************** * * * **********************************************************************/ Element.prototype.visibleInViewport = function(partial=false){ var { top, left, bottom, right } = this.getBoundingClientRect() var { innerHeight, innerWidth } = window return partial ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) : (top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth) } //--------------------------------------------------------------------- // XXX should these be here??? HTMLElement.encode = function(str){ var span = document.createElement('span') // XXX return str .replace(/&/g, '&') .replace(//g, '>') } /*/ span.innerText = str return span.innerHTML } //*/ // XXX this does not convert
back to \n... HTMLElement.decode = function(str){ var span = document.createElement('span') // XXX return str .replace(/</g, '<') .replace(/>/g, '>') .replace(/&/g, '&') } /*/ span.innerHTML = str return span.innerText } //*/ //--------------------------------------------------------------------- HTMLTextAreaElement.prototype.updateSize = function(){ // NOTE: this is set to 0px to negate the effects of external/inherited // height settings... this.style.height = '0px' this.style.height = this.scrollHeight + 'px' return this } HTMLTextAreaElement.prototype.autoUpdateSize = function(){ var that = this this.addEventListener('input', function(evt){ that.updateSize() }) return this } var cloneAsOffscreenSpan = function(elem){ var style = getComputedStyle(elem) var s = {} for(var i=0; i < style.length; i++){ var k = style[i] if(k.startsWith('font') || k.startsWith('line') || k.startsWith('white-space')){ s[k] = style[k] } } var span = document.createElement('span') Object.assign(span.style, { ...s, position: 'fixed', display: 'block', /* DEBUG... top: '0px', left: '0px', /*/ top: '-100%', left: '-100%', //*/ width: style.width, height: style.height, padding: style.padding, boxSizing: style.boxSizing, whiteSpace: style.whiteSpace, outline: 'solid 1px red', pointerEvents: 'none', }) return span } HTMLTextAreaElement.prototype.getTextGeometry = function(func){ var offset = this.selectionStart var text = this.value // get the relevant styles... var style = getComputedStyle(this) var paddingV = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom) var caret = document.createElement('span') caret.innerText = '|' caret.style.margin = '0px' caret.style.padding = '0px' var span = cloneAsOffscreenSpan(this) span.append( text.slice(0, offset), caret, // NOTE: wee need the rest of the text for the caret to be typeset // to the correct line... text.slice(offset)) document.body.append(span) var res = { length: text.length, lines: Math.floor( (this.offsetHeight - paddingV) / caret.offsetHeight), line: Math.floor(caret.offsetTop / caret.offsetHeight), caretHeight: caret.offsetHeight, offset: offset, offsetLeft: caret.offsetLeft, offsetTop: caret.offsetTop, } if(typeof(func) == 'function'){ res = func(res, span) } span.remove() return res } HTMLTextAreaElement.prototype.getTextOffsetAt = function(x, y){ var that = this var text = this.value // cleanup cached span... this.__getTextOffsetAt_timeout && clearTimeout(this.__getTextOffsetAt_timeout) this.__getTextOffsetAt_timeout = setTimeout(function(){ delete that.__getTextOffsetAt_timeout that.__getTextOffsetAt_clone.remove() delete that.__getTextOffsetAt_clone }, 50) // create/get clone span... if(this.__getTextOffsetAt_clone == null){ var span = this.__getTextOffsetAt_clone = cloneAsOffscreenSpan(this) span.append(text) document.body.append(span) } else { var span = this.__getTextOffsetAt_clone } var r = document.createRange() var t = span.firstChild var clone = span.getBoundingClientRect() var target = this.getBoundingClientRect() var ox = x - target.x var oy = y - target.y var rect, prev var cursor_line var col for(var i=0; i <= t.length; i++){ r.setStart(t, i) r.setEnd(t, i) prev = rect rect = r.getBoundingClientRect() // line change... if(prev && prev.y != rect.y){ // went off the cursor line if(cursor_line // cursor above block || oy <= prev.y - clone.y){ // end of prev line... return col ?? i - 1 } // reset col col = undefined } // cursor line... cursor_line = oy >= rect.y - clone.y && oy <= rect.bottom - clone.y // cursor col -- set once per line... if(col == null && ox <= rect.x - clone.x){ col = (ox > 0 || i == 0) ? i : i - 1 if(cursor_line){ return col } } } // below or right of the block -> return last col or last char... return col ?? i } // calculate number of lines in text area (both wrapped and actual lines) Object.defineProperty(HTMLTextAreaElement.prototype, 'heightLines', { enumerable: false, get: function(){ var style = getComputedStyle(this) return Math.floor( (this.scrollHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom)) / (parseFloat(style.lineHeight) || parseFloat(style.fontSize))) }, }) Object.defineProperty(HTMLTextAreaElement.prototype, 'lines', { enumerable: false, get: function(){ return this.value .split(/\n/g) .length }, }) // XXX this does not account for wrapping... Object.defineProperty(HTMLTextAreaElement.prototype, 'caretLine', { enumerable: false, get: function(){ var offset = this.selectionStart return offset != null ? this.value .slice(0, offset) .split(/\n/g) .length : undefined }, }) Object.defineProperty(HTMLTextAreaElement.prototype, 'caretOffset', { enumerable: false, get: function(){ var offset = this.selectionStart var r = document.createRange() r.setStart(this, offset) r.setEnd(this, offset) var rect = r.getBoundingClientRect() return { top: rect.top, left: rect.left, } }, }) /********************************************************************** * vim:set ts=4 sw=4 : */