From bad3f230ab32e9254331489e71a33381aff292c0 Mon Sep 17 00:00:00 2001 From: nightwing Date: Wed, 28 Nov 2012 22:39:38 +0400 Subject: [PATCH 1/9] Clipboard API support, context menu Delete command support --- lib/ace/keyboard/textinput.js | 276 ++++++++++++++++------------------ 1 file changed, 126 insertions(+), 150 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 44632ef3..7c9c29d7 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -54,69 +54,46 @@ var TextInput = function(parentNode, host) { parentNode.insertBefore(text, parentNode.firstChild); var PLACEHOLDER = useragent.isIE ? "\x01" : "\x00"; - reset(true); if (isFocused()) host.onFocus(); - var inCompostion = false; - var copied = false; var pasted = false; + var inCompostion = false; + var isSelectionEmpty = true; var tempStyle = ''; - function reset(full) { - try { - if (full) { - text.value = PLACEHOLDER; - text.selectionStart = 0; - text.selectionEnd = 1; - } else - text.select(); - } catch (e) {} - } - - function sendText(valueToSend) { - if (!copied) { - var value = valueToSend || text.value; - if (value) { - if (value.length > 1) { - if (value.charAt(0) == PLACEHOLDER) - value = value.substr(1); - else if (value.charAt(value.length - 1) == PLACEHOLDER) - value = value.slice(0, -1); - } - - if (value && value != PLACEHOLDER) { - if (pasted) - host.onPaste(value); - else - host.onTextInput(value); - } - } + host.addEventListener('changeSelection', function(){ + if (host.selection.isEmpty() != isSelectionEmpty) { + isSelectionEmpty = !isSelectionEmpty; + text.value = isSelectionEmpty ? '' : PLACEHOLDER; + text.select(); } + }); - copied = false; - pasted = false; + var onInput = function(e) { + if (inCompostion) + return; - // Safari doesn't fire copy events if no text is selected - reset(true); - } + if (pasted) { + var data = text.value; + if (data) + host.onPaste(data); + pasted = false; + } else { + var data = text.value; + if (data) + host.onTextInput(data); + else + host.onDelete(); + } + text.value = ""; - var onTextInput = function(e) { - if (!inCompostion) - sendText(e.data); - setTimeout(function () { - if (!inCompostion) - reset(true); - }, 0); - }; - - var onPropertyChange = function(e) { - setTimeout(function() { - if (!inCompostion) - if(text.value != "") { - sendText(); - } - }, 0); + //http://code.google.com/p/chromium/issues/detail?id=76516 + if (useragent.isWebKit) + setTimeout(function(){ + text.blur(); + text.focus(); + }); }; var onCompositionStart = function(e) { @@ -135,37 +112,106 @@ var TextInput = function(parentNode, host) { host.onCompositionEnd(); }; - var onCopy = function(e) { - copied = true; - var copyText = host.getCopyText(); - if(copyText) - text.value = copyText; - else - e.preventDefault(); - reset(); - setTimeout(function () { - sendText(); - }, 0); + var onCut = function(e) { + var data = host.getCopyText(); + if (!data) { + event.preventDefault(e); + return; + } + + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + // Safari 5 has clipboardData object, but does not handle setData() + var supported = e.clipboardData.setData("Text", data); + if (supported) { + host.onCut(); + event.preventDefault(e); + } + } + + if (!supported) { + text.value = data; + text.select(); + setTimeout(function(){ host.onCut() }); + } }; - var onCut = function(e) { - copied = true; - var copyText = host.getCopyText(); - if(copyText) { - text.value = copyText; - host.onCut(); - } else - e.preventDefault(); - reset(); - setTimeout(function () { - sendText(); - }, 0); + var onCopy = function(e) { + var data = host.getCopyText(); + if (!data) { + event.preventDefault(e); + return; + } + + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + // Safari 5 has clipboardData object, but does not handle setData() + var supported = e.clipboardData.setData("Text", data); + if (supported) { + host.onCopy(); + event.preventDefault(e); + } + } + if (!supported) { + text.value = data; + text.select(); + setTimeout(function(){ host.onCopy() }); + } + + + }; + + var onPaste = function(e) { + e.clipboardData = e.clipboardData || window.clipboardData; + + if (e.clipboardData) { + var data = e.clipboardData.getData("Text"); + if (data) + host.onPaste(data); + event.preventDefault(e); + } + else { + pasted = true; + } }; event.addCommandKeyListener(text, host.onCommandKey.bind(host)); - event.addListener(text, "input", onTextInput); - + + 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; + } + }); + } + if (useragent.isOldIE) { + event.addListener(text, "propertychange", function(e){ + if (text.value != "" && text.value != PLACEHOLDER) + onInput(e); + }); + var keytable = { 13:1, 27:1 }; event.addListener(text, "keyup", function (e) { if (inCompostion && (!text.value || keytable[e.keyCode])) @@ -175,70 +221,6 @@ var TextInput = function(parentNode, host) { } inCompostion ? onCompositionUpdate() : onCompositionStart(); }); - - event.addListener(text, "propertychange", function() { - if (text.value != PLACEHOLDER) - setTimeout(sendText, 0); - }); - } - - event.addListener(text, "paste", function(e) { - // Mark that the next input text comes from past. - pasted = true; - // Some browsers support the event.clipboardData API. Use this to get - // the pasted content which increases speed if pasting a lot of lines. - if (e.clipboardData && e.clipboardData.getData) { - sendText(e.clipboardData.getData("text/plain")); - e.preventDefault(); - } - else { - // If a browser doesn't support any of the things above, use the regular - // method to detect the pasted input. - onPropertyChange(); - } - }); - - if ("onbeforecopy" in text && typeof clipboardData !== "undefined") { - event.addListener(text, "beforecopy", function(e) { - if (tempStyle) - return; // without this text is copied when contextmenu is shown - var copyText = host.getCopyText(); - if (copyText) - clipboardData.setData("Text", copyText); - else - e.preventDefault(); - }); - event.addListener(parentNode, "keydown", function(e) { - if (e.ctrlKey && e.keyCode == 88) { - var copyText = host.getCopyText(); - if (copyText) { - clipboardData.setData("Text", copyText); - host.onCut(); - } - event.preventDefault(e); - } - }); - event.addListener(text, "cut", onCut); // for ie9 context menu - } - else if (useragent.isOpera && !("KeyboardEvent" in window)) { - event.addListener(parentNode, "keydown", function(e) { - if ((useragent.isMac && !e.metaKey) || !e.ctrlKey) - return; - - if ((e.keyCode == 88 || e.keyCode == 67)) { - var copyText = host.getCopyText(); - if (copyText) { - text.value = copyText; - text.select(); - if (e.keyCode == 88) - host.onCut(); - } - } - }); - } - else { - event.addListener(text, "copy", onCopy); - event.addListener(text, "cut", onCut); } event.addListener(text, "compositionstart", onCompositionStart); @@ -256,11 +238,11 @@ var TextInput = function(parentNode, host) { event.addListener(text, "focus", function() { host.onFocus(); - reset(); + text.select(); }); this.focus = function() { - reset(); + text.select(); text.focus(); }; @@ -286,11 +268,6 @@ var TextInput = function(parentNode, host) { (useragent.isIE ? "background:rgba(0, 0, 0, 0.03); opacity:0.1;" : "") + //"background:rgba(250, 0, 0, 0.3); opacity:1;" + "left:" + (e.clientX - 2) + "px; top:" + (e.clientY - 2) + "px;"; - if (host.selection.isEmpty()) - text.value = ""; - else - reset(true); - if (e.type != "mousedown") return; @@ -311,7 +288,6 @@ var TextInput = function(parentNode, host) { text.style.cssText = tempStyle; tempStyle = ''; } - sendText(); if (host.renderer.$keepTextAreaAtCursor == null) { host.renderer.$keepTextAreaAtCursor = true; host.renderer.$moveTextAreaToCursor(); From a061dfd0da1cd31f27282870cb7ca2427c0cb8dc Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sat, 20 Oct 2012 19:21:32 +1100 Subject: [PATCH 2/9] change selection instead of text.value --- lib/ace/keyboard/textinput.js | 81 +++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 23 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 7c9c29d7..d8142136 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -53,22 +53,54 @@ var TextInput = function(parentNode, host) { text.style.top = "-2em"; parentNode.insertBefore(text, parentNode.firstChild); - var PLACEHOLDER = useragent.isIE ? "\x01" : "\x00"; + var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01" : "\x00"; + + resetValue(); + if (isFocused()) host.onFocus(); + // Somehow fixes problem with firing onpropertychange on first typed char + if (useragent.isOldIE) { + resetSelection(); + resetValue(); + setTimeout(resetSelection); + } + var pasted = false; + var inCompostion = false; + var isSelectionEmpty = true; + var tempStyle = ''; - host.addEventListener('changeSelection', function(){ - if (host.selection.isEmpty() != isSelectionEmpty) { - isSelectionEmpty = !isSelectionEmpty; - text.value = isSelectionEmpty ? '' : PLACEHOLDER; - text.select(); + function resetValue() { + //http://code.google.com/p/chromium/issues/detail?id=76516 + if (!useragent.isWebKit) + text.value = PLACEHOLDER; + else + setTimeout(function(){ + text.value = PLACEHOLDER; + }); + }; + + function resetSelection() { + var selectionStart = isSelectionEmpty ? 1 : 0; + var selectionEnd = 1; + + if (text.setSelectionRange) { + text.setSelectionRange(selectionStart, selectionEnd); } - }); + // IE8 does not support setSelectionRange + else if (text.createTextRange) { + var range = text.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } + }; var onInput = function(e) { if (inCompostion) @@ -76,24 +108,19 @@ var TextInput = function(parentNode, host) { if (pasted) { var data = text.value; + resetValue(); if (data) host.onPaste(data); pasted = false; - } else { - var data = text.value; - if (data) - host.onTextInput(data); - else - host.onDelete(); + return; } - text.value = ""; - //http://code.google.com/p/chromium/issues/detail?id=76516 - if (useragent.isWebKit) - setTimeout(function(){ - text.blur(); - text.focus(); - }); + var data = text.value.substring(isSelectionEmpty ? 1 : 0); + resetValue(); + if (data) + host.onTextInput(data); + else + host.onDelete(); }; var onCompositionStart = function(e) { @@ -173,6 +200,7 @@ var TextInput = function(parentNode, host) { event.preventDefault(e); } else { + text.value = ""; pasted = true; } }; @@ -238,11 +266,10 @@ var TextInput = function(parentNode, host) { event.addListener(text, "focus", function() { host.onFocus(); - text.select(); + resetSelection(); }); this.focus = function() { - text.select(); text.focus(); }; @@ -300,8 +327,16 @@ var TextInput = function(parentNode, host) { if (!useragent.isGecko) event.addListener(text, "contextmenu", function(e) { host.textInput.onContextMenu(e); - onContextMenuClose() + onContextMenuClose(); }); + + + host.addEventListener('changeSelection', function(){ + if (host.selection.isEmpty() != isSelectionEmpty) { + isSelectionEmpty = !isSelectionEmpty; + resetSelection(); + } + }); }; exports.TextInput = TextInput; From 29f581c61ad1712aa350abc67598d43fc2f461da Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sat, 20 Oct 2012 19:22:34 +1100 Subject: [PATCH 3/9] Add Select All context menu command support --- lib/ace/keyboard/textinput.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index d8142136..31335a79 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -53,7 +53,7 @@ var TextInput = function(parentNode, host) { text.style.top = "-2em"; parentNode.insertBefore(text, parentNode.firstChild); - var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01" : "\x00"; + var PLACEHOLDER = useragent.isIE || useragent.isOpera ? "\x01\x01" : "\x00\x00"; resetValue(); @@ -67,6 +67,8 @@ var TextInput = function(parentNode, host) { setTimeout(resetSelection); } + var cut = false + var copied = false; var pasted = false; var inCompostion = false; @@ -86,8 +88,8 @@ var TextInput = function(parentNode, host) { }; function resetSelection() { - var selectionStart = isSelectionEmpty ? 1 : 0; - var selectionEnd = 1; + var selectionStart = isSelectionEmpty ? 2 : 1; + var selectionEnd = 2; if (text.setSelectionRange) { text.setSelectionRange(selectionStart, selectionEnd); @@ -102,6 +104,21 @@ var TextInput = function(parentNode, host) { } }; + var onSelect = function(e) { + if (cut) { + cut = false; + return; + } + if (copied) { + copied = false; + return; + } + if (text.selectionStart === 0 && text.selectionEnd === text.value.length) { + host.selectAll(); + resetSelection(); + } + }; + var onInput = function(e) { if (inCompostion) return; @@ -115,7 +132,7 @@ var TextInput = function(parentNode, host) { return; } - var data = text.value.substring(isSelectionEmpty ? 1 : 0); + var data = text.value.substring(isSelectionEmpty ? 2 : 1); resetValue(); if (data) host.onTextInput(data); @@ -160,6 +177,7 @@ var TextInput = function(parentNode, host) { if (!supported) { text.value = data; text.select(); + cut = true; setTimeout(function(){ host.onCut() }); } }; @@ -184,6 +202,7 @@ var TextInput = function(parentNode, host) { if (!supported) { text.value = data; text.select(); + copided = true; setTimeout(function(){ host.onCopy() }); } @@ -207,6 +226,8 @@ var TextInput = function(parentNode, host) { event.addCommandKeyListener(text, host.onCommandKey.bind(host)); + event.addListener(text, "select", onSelect); + event.addListener(text, "input", onInput); event.addListener(text, "cut", onCut); From 46170cd78188855995b9a37c3fa974d4baba3f12 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 03:40:07 +1100 Subject: [PATCH 4/9] Fix mistype and opera copy/cut selectAll triggering --- lib/ace/keyboard/textinput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 31335a79..9a221e3c 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -175,9 +175,9 @@ var TextInput = function(parentNode, host) { } if (!supported) { + cut = true; text.value = data; text.select(); - cut = true; setTimeout(function(){ host.onCut() }); } }; @@ -200,9 +200,9 @@ var TextInput = function(parentNode, host) { } } if (!supported) { + copied = true; text.value = data; text.select(); - copided = true; setTimeout(function(){ host.onCopy() }); } From 7cc950253956a051f2926dd636e6fee7c9332e08 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 04:48:12 +1100 Subject: [PATCH 5/9] Fix webkit text.oninput value changing workaround --- lib/ace/keyboard/textinput.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 9a221e3c..8e7cbdab 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -78,12 +78,12 @@ var TextInput = function(parentNode, host) { var tempStyle = ''; function resetValue() { + text.value = PLACEHOLDER; //http://code.google.com/p/chromium/issues/detail?id=76516 - if (!useragent.isWebKit) - text.value = PLACEHOLDER; - else + if (useragent.isWebKit) setTimeout(function(){ text.value = PLACEHOLDER; + resetSelection(); }); }; From 1459f3639108784fc0080111ebe91e288d4ea469 Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 21 Oct 2012 12:59:25 +1100 Subject: [PATCH 6/9] fix clipboardData reference --- lib/ace/keyboard/textinput.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 8e7cbdab..a6a8d05f 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -163,11 +163,11 @@ var TextInput = function(parentNode, host) { return; } - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { + if (clipboardData) { // Safari 5 has clipboardData object, but does not handle setData() - var supported = e.clipboardData.setData("Text", data); + var supported = clipboardData.setData("Text", data); if (supported) { host.onCut(); event.preventDefault(e); @@ -189,11 +189,11 @@ var TextInput = function(parentNode, host) { return; } - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { + if (clipboardData) { // Safari 5 has clipboardData object, but does not handle setData() - var supported = e.clipboardData.setData("Text", data); + var supported = clipboardData.setData("Text", data); if (supported) { host.onCopy(); event.preventDefault(e); @@ -210,10 +210,10 @@ var TextInput = function(parentNode, host) { }; var onPaste = function(e) { - e.clipboardData = e.clipboardData || window.clipboardData; + var clipboardData = e.clipboardData || window.clipboardData; - if (e.clipboardData) { - var data = e.clipboardData.getData("Text"); + if (clipboardData) { + var data = clipboardData.getData("Text"); if (data) host.onPaste(data); event.preventDefault(e); From 67a4a3329cb0fa640fed3cd0a8002ed2d148db4e Mon Sep 17 00:00:00 2001 From: DanyaPostfactum Date: Sun, 4 Nov 2012 18:54:10 +1100 Subject: [PATCH 7/9] reset text on old way cut/copy --- lib/ace/keyboard/textinput.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index a6a8d05f..03820794 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -178,7 +178,12 @@ var TextInput = function(parentNode, host) { cut = true; text.value = data; text.select(); - setTimeout(function(){ host.onCut() }); + setTimeout(function(){ + cut = false; + resetValue(); + resetSelection(); + host.onCut(); + }); } }; @@ -203,7 +208,12 @@ var TextInput = function(parentNode, host) { copied = true; text.value = data; text.select(); - setTimeout(function(){ host.onCopy() }); + setTimeout(function(){ + copied = false; + resetValue(); + resetSelection(); + host.onCopy(); + }); } From 3c57b1b41519cb805eb662a006f0c1d51c84d20c Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 26 Nov 2012 23:02:32 +0400 Subject: [PATCH 8/9] fix editor loosing focus on input --- lib/ace/keyboard/textinput.js | 47 ++++++++++++++++------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 03820794..bda80b35 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -73,35 +73,38 @@ var TextInput = function(parentNode, host) { var inCompostion = false; - var isSelectionEmpty = true; + var resetTimeout = true; var tempStyle = ''; function resetValue() { text.value = PLACEHOLDER; //http://code.google.com/p/chromium/issues/detail?id=76516 - if (useragent.isWebKit) - setTimeout(function(){ + if (useragent.isWebKit && !resetTimeout) + resetTimeout = setTimeout(function(){ text.value = PLACEHOLDER; resetSelection(); + resetTimeout = null; }); }; - function resetSelection() { - var selectionStart = isSelectionEmpty ? 2 : 1; + function resetSelection(isEmpty) { + var selectionStart = isEmpty ? 2 : 1; var selectionEnd = 2; - - if (text.setSelectionRange) { - text.setSelectionRange(selectionStart, selectionEnd); - } - // IE8 does not support setSelectionRange - else if (text.createTextRange) { - var range = text.createTextRange(); - range.collapse(true); - range.moveEnd('character', selectionEnd); - range.moveStart('character', selectionStart); - range.select(); - } + // on firefox this throws if textarea is hidden + try { + if (text.setSelectionRange) { + text.setSelectionRange(selectionStart, selectionEnd); + } + // IE8 does not support setSelectionRange + else if (text.createTextRange) { + var range = text.createTextRange(); + range.collapse(true); + range.moveEnd('character', selectionEnd); + range.moveStart('character', selectionStart); + range.select(); + } + } catch(e){} }; var onSelect = function(e) { @@ -326,6 +329,8 @@ var TextInput = function(parentNode, host) { (useragent.isIE ? "background:rgba(0, 0, 0, 0.03); opacity:0.1;" : "") + //"background:rgba(250, 0, 0, 0.3); opacity:1;" + "left:" + (e.clientX - 2) + "px; top:" + (e.clientY - 2) + "px;"; + resetSelection(host.selection.isEmpty()); + if (e.type != "mousedown") return; @@ -360,14 +365,6 @@ var TextInput = function(parentNode, host) { host.textInput.onContextMenu(e); onContextMenuClose(); }); - - - host.addEventListener('changeSelection', function(){ - if (host.selection.isEmpty() != isSelectionEmpty) { - isSelectionEmpty = !isSelectionEmpty; - resetSelection(); - } - }); }; exports.TextInput = TextInput; From 6772e90df3ccdc4ada0993b2870a73787fb9804e Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 26 Nov 2012 23:04:05 +0400 Subject: [PATCH 9/9] be more cautios to not insert parts of PLACEHOLDER --- lib/ace/keyboard/textinput.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index bda80b35..dfad806b 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -73,7 +73,7 @@ var TextInput = function(parentNode, host) { var inCompostion = false; - var resetTimeout = true; + var resetTimeout = null; var tempStyle = ''; @@ -135,11 +135,20 @@ var TextInput = function(parentNode, host) { return; } - var data = text.value.substring(isSelectionEmpty ? 2 : 1); + var data = text.value; + if (data.substring(0, 2) == PLACEHOLDER) + data = data.substr(2); + else + data = data.substr(1); + resetValue(); + if (data) { + // can happen if undo in textarea isn't stopped + if (data[data.length - 1] == PLACEHOLDER[0]) + data = data.slice(0, -1); if (data) host.onTextInput(data); - else + } else host.onDelete(); };