| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- /* ***** BEGIN LICENSE BLOCK *****
- * Distributed under the BSD license:
- *
- * Copyright (c) 2010, Ajax.org B.V.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Ajax.org B.V. nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * ***** END LICENSE BLOCK ***** */
- define(function(require, exports, module) {
- "use strict";
- var event = require("../lib/event");
- var useragent = require("../lib/useragent");
- var dom = require("../lib/dom");
- var lang = require("../lib/lang");
- var BROKEN_SETDATA = useragent.isChrome < 18;
- var USE_IE_MIME_TYPE = useragent.isIE;
- var TextInput = function(parentNode, host) {
- var text = dom.createElement("textarea");
- text.className = "ace_text-input";
- if (useragent.isTouchPad)
- text.setAttribute("x-palm-disable-auto-cap", true);
- text.setAttribute("wrap", "off");
- text.setAttribute("autocorrect", "off");
- text.setAttribute("autocapitalize", "off");
- text.setAttribute("spellcheck", false);
- text.style.opacity = "0";
- if (useragent.isOldIE) text.style.top = "-1000px";
- parentNode.insertBefore(text, parentNode.firstChild);
- var PLACEHOLDER = "\u2028\u2028";
- var copied = false;
- var pasted = false;
- var inComposition = false;
- var tempStyle = '';
- var isSelectionEmpty = true;
- // FOCUS
- // ie9 throws error if document.activeElement is accessed too soon
- try { var isFocused = document.activeElement === text; } catch(e) {}
-
- event.addListener(text, "blur", function(e) {
- host.onBlur(e);
- isFocused = false;
- });
- event.addListener(text, "focus", function(e) {
- isFocused = true;
- host.onFocus(e);
- resetSelection();
- });
- this.focus = function() {
- if (tempStyle) return text.focus();
- var top = text.style.top;
- text.style.position = "fixed";
- text.style.top = "-1000px";
- text.focus();
- setTimeout(function() {
- text.style.position = "";
- if (text.style.top == "-1000px")
- text.style.top = top;
- }, 0);
- };
- this.blur = function() {
- text.blur();
- };
- this.isFocused = function() {
- return isFocused;
- };
- // modifying selection of blured textarea can focus it (chrome mac/linux)
- var syncSelection = lang.delayedCall(function() {
- isFocused && resetSelection(isSelectionEmpty);
- });
- var syncValue = lang.delayedCall(function() {
- if (!inComposition) {
- text.value = PLACEHOLDER;
- isFocused && resetSelection();
- }
- });
- function resetSelection(isEmpty) {
- if (inComposition)
- return;
-
- // this prevents infinite recursion on safari 8
- // see https://github.com/ajaxorg/ace/issues/2114
- inComposition = true;
-
- if (inputHandler) {
- selectionStart = 0;
- selectionEnd = isEmpty ? 0 : text.value.length - 1;
- } else {
- var selectionStart = isEmpty ? 2 : 1;
- var selectionEnd = 2;
- }
- // on firefox this throws if textarea is hidden
- try {
- text.setSelectionRange(selectionStart, selectionEnd);
- } catch(e){}
-
- inComposition = false;
- }
- function resetValue() {
- if (inComposition)
- return;
- text.value = PLACEHOLDER;
- //http://code.google.com/p/chromium/issues/detail?id=76516
- if (useragent.isWebKit)
- syncValue.schedule();
- }
- useragent.isWebKit || host.addEventListener('changeSelection', function() {
- if (host.selection.isEmpty() != isSelectionEmpty) {
- isSelectionEmpty = !isSelectionEmpty;
- syncSelection.schedule();
- }
- });
- resetValue();
- if (isFocused)
- host.onFocus();
- var isAllSelected = function(text) {
- return text.selectionStart === 0 && text.selectionEnd === text.value.length;
- };
- // IE8 does not support setSelectionRange
- if (!text.setSelectionRange && text.createTextRange) {
- text.setSelectionRange = function(selectionStart, selectionEnd) {
- var range = this.createTextRange();
- range.collapse(true);
- range.moveStart('character', selectionStart);
- range.moveEnd('character', selectionEnd);
- range.select();
- };
- isAllSelected = function(text) {
- try {
- var range = text.ownerDocument.selection.createRange();
- }catch(e) {}
- if (!range || range.parentElement() != text) return false;
- return range.text == text.value;
- }
- }
- if (useragent.isOldIE) {
- var inPropertyChange = false;
- var onPropertyChange = function(e){
- if (inPropertyChange)
- return;
- var data = text.value;
- if (inComposition || !data || data == PLACEHOLDER)
- return;
- // can happen either after delete or during insert operation
- if (e && data == PLACEHOLDER[0])
- return syncProperty.schedule();
- sendText(data);
- // ie8 calls propertychange handlers synchronously!
- inPropertyChange = true;
- resetValue();
- inPropertyChange = false;
- };
- var syncProperty = lang.delayedCall(onPropertyChange);
- event.addListener(text, "propertychange", onPropertyChange);
- var keytable = { 13:1, 27:1 };
- event.addListener(text, "keyup", function (e) {
- if (inComposition && (!text.value || keytable[e.keyCode]))
- setTimeout(onCompositionEnd, 0);
- if ((text.value.charCodeAt(0)||0) < 129) {
- return syncProperty.call();
- }
- inComposition ? onCompositionUpdate() : onCompositionStart();
- });
- // when user presses backspace after focusing the editor
- // propertychange isn't called for the next character
- event.addListener(text, "keydown", function (e) {
- syncProperty.schedule(50);
- });
- }
- var onSelect = function(e) {
- if (copied) {
- copied = false;
- } else if (isAllSelected(text)) {
- host.selectAll();
- resetSelection();
- } else if (inputHandler) {
- resetSelection(host.selection.isEmpty());
- }
- };
- var inputHandler = null;
- this.setInputHandler = function(cb) {inputHandler = cb};
- this.getInputHandler = function() {return inputHandler};
- var afterContextMenu = false;
-
- var sendText = function(data) {
- if (inputHandler) {
- data = inputHandler(data);
- inputHandler = null;
- }
- if (pasted) {
- resetSelection();
- if (data)
- host.onPaste(data);
- pasted = false;
- } else if (data == PLACEHOLDER.charAt(0)) {
- if (afterContextMenu)
- host.execCommand("del", {source: "ace"});
- else // some versions of android do not fire keydown when pressing backspace
- host.execCommand("backspace", {source: "ace"});
- } else {
- if (data.substring(0, 2) == PLACEHOLDER)
- data = data.substr(2);
- else if (data.charAt(0) == PLACEHOLDER.charAt(0))
- data = data.substr(1);
- else if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
- data = data.slice(0, -1);
- // can happen if undo in textarea isn't stopped
- if (data.charAt(data.length - 1) == PLACEHOLDER.charAt(0))
- data = data.slice(0, -1);
-
- if (data)
- host.onTextInput(data);
- }
- if (afterContextMenu)
- afterContextMenu = false;
- };
- var onInput = function(e) {
- // console.log("onInput", inComposition)
- if (inComposition)
- return;
- var data = text.value;
- sendText(data);
- resetValue();
- };
-
- var handleClipboardData = function(e, data) {
- if (typeof host.middleClick != "undefined" && host.middleClick){
- return host.getSelectedText() || " ";
- }
- var clipboardData = e.clipboardData || window.clipboardData;
- if (!clipboardData || BROKEN_SETDATA)
- return;
- // using "Text" doesn't work on old webkit but ie needs it
- // TODO are there other browsers that require "Text"?
- var mime = USE_IE_MIME_TYPE ? "Text" : "text/plain";
- if (data) {
- // Safari 5 has clipboardData object, but does not handle setData()
- return clipboardData.setData(mime, data) !== false;
- } else {
- return clipboardData.getData(mime);
- }
- };
- var doCopy = function(e, isCut) {
- var data = host.getCopyText();
- if (!data)
- return event.preventDefault(e);
- if (handleClipboardData(e, data)) {
- isCut ? host.onCut() : host.onCopy();
- event.preventDefault(e);
- } else {
- copied = true;
- text.value = data;
- text.select();
- setTimeout(function(){
- copied = false;
- resetValue();
- resetSelection();
- isCut ? host.onCut() : host.onCopy();
- });
- }
- };
-
- var onCut = function(e) {
- doCopy(e, true);
- };
-
- var onCopy = function(e) {
- doCopy(e, false);
- };
-
- var onPaste = function(e) {
- var data = handleClipboardData(e);
- if (typeof data == "string") {
- if (data)
- host.onPaste(data, e);
- if (useragent.isIE)
- setTimeout(resetSelection);
- event.preventDefault(e);
- }
- else {
- text.value = "";
- pasted = true;
- }
- };
- event.addCommandKeyListener(text, host.onCommandKey.bind(host));
- event.addListener(text, "select", onSelect);
- event.addListener(text, "input", onInput);
- event.addListener(text, "cut", onCut);
- event.addListener(text, "copy", onCopy);
- event.addListener(text, "paste", onPaste);
- // Opera has no clipboard events
- if (!('oncut' in text) || !('oncopy' in text) || !('onpaste' in text)){
- event.addListener(parentNode, "keydown", function(e) {
- if ((useragent.isMac && !e.metaKey) || !e.ctrlKey)
- return;
- switch (e.keyCode) {
- case 67:
- onCopy(e);
- break;
- case 86:
- onPaste(e);
- break;
- case 88:
- onCut(e);
- break;
- }
- });
- }
- // COMPOSITION
- var onCompositionStart = function(e) {
- if (inComposition || !host.onCompositionStart || host.$readOnly)
- return;
- // console.log("onCompositionStart", inComposition)
- inComposition = {};
- host.onCompositionStart();
- setTimeout(onCompositionUpdate, 0);
- host.on("mousedown", onCompositionEnd);
- if (!host.selection.isEmpty()) {
- host.insert("");
- host.session.markUndoGroup();
- host.selection.clearSelection();
- }
- host.session.markUndoGroup();
- };
- var onCompositionUpdate = function() {
- // console.log("onCompositionUpdate", inComposition && JSON.stringify(text.value))
- if (!inComposition || !host.onCompositionUpdate || host.$readOnly)
- return;
- var val = text.value.replace(/\u2028/g, "");
- if (inComposition.lastValue === val) return;
-
- host.onCompositionUpdate(val);
- if (inComposition.lastValue)
- host.undo();
- inComposition.lastValue = val;
- if (inComposition.lastValue) {
- var r = host.selection.getRange();
- host.insert(inComposition.lastValue);
- host.session.markUndoGroup();
- inComposition.range = host.selection.getRange();
- host.selection.setRange(r);
- host.selection.clearSelection();
- }
- };
- var onCompositionEnd = function(e) {
- if (!host.onCompositionEnd || host.$readOnly) return;
- // console.log("onCompositionEnd", inComposition &&inComposition.lastValue)
- var c = inComposition;
- inComposition = false;
- var timer = setTimeout(function() {
- timer = null;
- var str = text.value.replace(/\u2028/g, "");
- // console.log(str, c.lastValue)
- if (inComposition)
- return;
- else if (str == c.lastValue)
- resetValue();
- else if (!c.lastValue && str) {
- resetValue();
- sendText(str);
- }
- });
- inputHandler = function compositionInputHandler(str) {
- // console.log("onCompositionEnd", str, c.lastValue)
- if (timer)
- clearTimeout(timer);
- str = str.replace(/\u2028/g, "");
- if (str == c.lastValue)
- return "";
- if (c.lastValue && timer)
- host.undo();
- return str;
- };
- host.onCompositionEnd();
- host.removeListener("mousedown", onCompositionEnd);
- if (e.type == "compositionend" && c.range) {
- host.selection.setRange(c.range);
- }
- // Workaround for #3027, #3045, #3097, #3100, #3249
- var needsOnInput =
- (!!useragent.isChrome && useragent.isChrome >= 53) ||
- (!!useragent.isWebKit && useragent.isWebKit >= 603);
- if (needsOnInput) {
- onInput();
- }
- };
-
-
- var syncComposition = lang.delayedCall(onCompositionUpdate, 50);
- event.addListener(text, "compositionstart", onCompositionStart);
- if (useragent.isGecko) {
- event.addListener(text, "text", function(){syncComposition.schedule()});
- } else {
- event.addListener(text, "keyup", function(){syncComposition.schedule()});
- event.addListener(text, "keydown", function(){syncComposition.schedule()});
- }
- event.addListener(text, "compositionend", onCompositionEnd);
- this.getElement = function() {
- return text;
- };
- this.setReadOnly = function(readOnly) {
- text.readOnly = readOnly;
- };
- this.onContextMenu = function(e) {
- afterContextMenu = true;
- resetSelection(host.selection.isEmpty());
- host._emit("nativecontextmenu", {target: host, domEvent: e});
- this.moveToMouse(e, true);
- };
-
- this.moveToMouse = function(e, bringToFront) {
- if (!bringToFront && useragent.isOldIE)
- return;
- if (!tempStyle)
- tempStyle = text.style.cssText;
- text.style.cssText = (bringToFront ? "z-index:100000;" : "")
- + "height:" + text.style.height + ";"
- + (useragent.isIE ? "opacity:0.1;" : "");
- var rect = host.container.getBoundingClientRect();
- var style = dom.computedStyle(host.container);
- var top = rect.top + (parseInt(style.borderTopWidth) || 0);
- var left = rect.left + (parseInt(rect.borderLeftWidth) || 0);
- var maxTop = rect.bottom - top - text.clientHeight -2;
- var move = function(e) {
- text.style.left = e.clientX - left - 2 + "px";
- text.style.top = Math.min(e.clientY - top - 2, maxTop) + "px";
- };
- move(e);
- if (e.type != "mousedown")
- return;
- if (host.renderer.$keepTextAreaAtCursor)
- host.renderer.$keepTextAreaAtCursor = null;
- // on windows context menu is opened after mouseup
- if (useragent.isWin && !useragent.isOldIE)
- event.capture(host.container, move, onContextMenuClose);
- };
- this.onContextMenuClose = onContextMenuClose;
- var closeTimeout;
- function onContextMenuClose() {
- clearTimeout(closeTimeout);
- closeTimeout = setTimeout(function () {
- if (tempStyle) {
- text.style.cssText = tempStyle;
- tempStyle = '';
- }
- if (host.renderer.$keepTextAreaAtCursor == null) {
- host.renderer.$keepTextAreaAtCursor = true;
- host.renderer.$moveTextAreaToCursor();
- }
- }, useragent.isOldIE ? 200 : 0);
- }
- var onContextMenu = function(e) {
- host.textInput.onContextMenu(e);
- onContextMenuClose();
- };
- event.addListener(host.renderer.scroller, "contextmenu", onContextMenu);
- event.addListener(text, "contextmenu", onContextMenu);
- };
- exports.TextInput = TextInput;
- });
|