summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/EditingSupport.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/EditingSupport.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/EditingSupport.js271
1 files changed, 271 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/EditingSupport.js b/Source/WebInspectorUI/UserInterface/EditingSupport.js
new file mode 100644
index 000000000..c2fe47107
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/EditingSupport.js
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2013 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS CONTRIBUTORS
+ * 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.
+ */
+
+WebInspector.isBeingEdited = function(element)
+{
+ while (element) {
+ if (element.__editing)
+ return true;
+ element = element.parentNode;
+ }
+
+ return false;
+}
+
+WebInspector.markBeingEdited = function(element, value)
+{
+ if (value) {
+ if (element.__editing)
+ return false;
+ element.__editing = true;
+ WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
+ } else {
+ if (!element.__editing)
+ return false;
+ delete element.__editing;
+ --WebInspector.__editingCount;
+ }
+ return true;
+}
+
+WebInspector.isEditingAnyField = function()
+{
+ return !!WebInspector.__editingCount;
+}
+
+WebInspector.isEventTargetAnEditableField = function(event)
+{
+ const textInputTypes = {"text": true, "search": true, "tel": true, "url": true, "email": true, "password": true};
+ if (event.target instanceof HTMLInputElement)
+ return event.target.type in textInputTypes;
+
+ var codeMirrorEditorElement = event.target.enclosingNodeOrSelfWithClass("CodeMirror");
+ if (codeMirrorEditorElement && codeMirrorEditorElement.CodeMirror)
+ return !codeMirrorEditorElement.CodeMirror.getOption("readOnly");
+
+ if (event.target instanceof HTMLTextAreaElement)
+ return true;
+
+ if (event.target.enclosingNodeOrSelfWithClass("text-prompt"))
+ return true;
+
+ return false;
+}
+
+/**
+ * @constructor
+ * @param {function(Element,string,string,*,string)} commitHandler
+ * @param {function(Element,*)} cancelHandler
+ * @param {*=} context
+ */
+WebInspector.EditingConfig = function(commitHandler, cancelHandler, context)
+{
+ this.commitHandler = commitHandler;
+ this.cancelHandler = cancelHandler
+ this.context = context;
+
+ /**
+ * Handles the "paste" event, return values are the same as those for customFinishHandler
+ * @type {function(Element)|undefined}
+ */
+ this.pasteHandler;
+
+ /**
+ * Whether the edited element is multiline
+ * @type {boolean|undefined}
+ */
+ this.multiline;
+
+ /**
+ * Custom finish handler for the editing session (invoked on keydown)
+ * @type {function(Element,*)|undefined}
+ */
+ this.customFinishHandler;
+
+ /**
+ * Whether or not spellcheck is enabled.
+ * @type {boolean}
+ */
+ this.spellcheck = false;
+}
+
+WebInspector.EditingConfig.prototype = {
+ setPasteHandler: function(pasteHandler)
+ {
+ this.pasteHandler = pasteHandler;
+ },
+
+ setMultiline: function(multiline)
+ {
+ this.multiline = multiline;
+ },
+
+ setCustomFinishHandler: function(customFinishHandler)
+ {
+ this.customFinishHandler = customFinishHandler;
+ }
+}
+
+/**
+ * @param {Element} element
+ * @param {WebInspector.EditingConfig=} config
+ */
+WebInspector.startEditing = function(element, config)
+{
+ if (!WebInspector.markBeingEdited(element, true))
+ return;
+
+ config = config || new WebInspector.EditingConfig(function() {}, function() {});
+ var committedCallback = config.commitHandler;
+ var cancelledCallback = config.cancelHandler;
+ var pasteCallback = config.pasteHandler;
+ var context = config.context;
+ var oldText = getContent(element);
+ var moveDirection = "";
+
+ element.classList.add("editing");
+
+ var oldSpellCheck = element.hasAttribute("spellcheck") ? element.spellcheck : undefined;
+ element.spellcheck = config.spellcheck;
+
+ if (config.multiline)
+ element.classList.add("multiline");
+
+ var oldTabIndex = element.tabIndex;
+ if (element.tabIndex < 0)
+ element.tabIndex = 0;
+
+ function blurEventListener() {
+ editingCommitted.call(element);
+ }
+
+ function getContent(element) {
+ if (element.tagName === "INPUT" && element.type === "text")
+ return element.value;
+ else
+ return element.textContent;
+ }
+
+ /** @this {Element} */
+ function cleanUpAfterEditing()
+ {
+ WebInspector.markBeingEdited(element, false);
+
+ this.classList.remove("editing");
+ this.scrollTop = 0;
+ this.scrollLeft = 0;
+
+ if (oldSpellCheck === undefined)
+ element.removeAttribute("spellcheck");
+ else
+ element.spellcheck = oldSpellCheck;
+
+ if (oldTabIndex === -1)
+ this.removeAttribute("tabindex");
+ else
+ this.tabIndex = oldTabIndex;
+
+ element.removeEventListener("blur", blurEventListener, false);
+ element.removeEventListener("keydown", keyDownEventListener, true);
+ if (pasteCallback)
+ element.removeEventListener("paste", pasteEventListener, true);
+
+ WebInspector.restoreFocusFromElement(element);
+ }
+
+ /** @this {Element} */
+ function editingCancelled()
+ {
+ if (this.tagName === "INPUT" && this.type === "text")
+ this.value = oldText;
+ else
+ this.textContent = oldText;
+
+ cleanUpAfterEditing.call(this);
+
+ cancelledCallback(this, context);
+ }
+
+ /** @this {Element} */
+ function editingCommitted()
+ {
+ cleanUpAfterEditing.call(this);
+
+ committedCallback(this, getContent(this), oldText, context, moveDirection);
+ }
+
+ function defaultFinishHandler(event)
+ {
+ var hasOnlyMetaModifierKey = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
+ if (isEnterKey(event) && (!config.multiline || hasOnlyMetaModifierKey))
+ return "commit";
+ else if (event.keyCode === WebInspector.KeyboardShortcut.Key.Escape.keyCode || event.keyIdentifier === "U+001B")
+ return "cancel";
+ else if (event.keyIdentifier === "U+0009") // Tab key
+ return "move-" + (event.shiftKey ? "backward" : "forward");
+ }
+
+ function handleEditingResult(result, event)
+ {
+ if (result === "commit") {
+ editingCommitted.call(element);
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (result === "cancel") {
+ editingCancelled.call(element);
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (result && result.startsWith("move-")) {
+ moveDirection = result.substring(5);
+ if (event.keyIdentifier !== "U+0009")
+ blurEventListener();
+ }
+ }
+
+ function pasteEventListener(event)
+ {
+ var result = pasteCallback(event);
+ handleEditingResult(result, event);
+ }
+
+ function keyDownEventListener(event)
+ {
+ var handler = config.customFinishHandler || defaultFinishHandler;
+ var result = handler(event);
+ handleEditingResult(result, event);
+ }
+
+ element.addEventListener("blur", blurEventListener, false);
+ element.addEventListener("keydown", keyDownEventListener, true);
+ if (pasteCallback)
+ element.addEventListener("paste", pasteEventListener, true);
+
+ element.focus();
+
+ return {
+ cancel: editingCancelled.bind(element),
+ commit: editingCommitted.bind(element)
+ };
+}