OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | |
5 /** | 4 /** |
6 * @constructor | 5 * @unrestricted |
7 */ | 6 */ |
8 WebInspector.InplaceEditor = function() | 7 WebInspector.InplaceEditor = class { |
9 { | 8 /** |
| 9 * @param {!Element} element |
| 10 * @param {!WebInspector.InplaceEditor.Config=} config |
| 11 * @return {?WebInspector.InplaceEditor.Controller} |
| 12 */ |
| 13 static startEditing(element, config) { |
| 14 if (!WebInspector.InplaceEditor._defaultInstance) |
| 15 WebInspector.InplaceEditor._defaultInstance = new WebInspector.InplaceEdit
or(); |
| 16 return WebInspector.InplaceEditor._defaultInstance.startEditing(element, con
fig); |
| 17 } |
| 18 |
| 19 /** |
| 20 * @param {!Element} element |
| 21 * @param {!WebInspector.InplaceEditor.Config=} config |
| 22 * @return {!Promise.<!WebInspector.InplaceEditor.Controller>} |
| 23 */ |
| 24 static startMultilineEditing(element, config) { |
| 25 return self.runtime.extension(WebInspector.InplaceEditor).instance().then(st
artEditing); |
| 26 |
| 27 /** |
| 28 * @param {!Object} inplaceEditor |
| 29 * @return {!WebInspector.InplaceEditor.Controller|!Promise.<!WebInspector.I
nplaceEditor.Controller>} |
| 30 */ |
| 31 function startEditing(inplaceEditor) { |
| 32 var controller = /** @type {!WebInspector.InplaceEditor} */ (inplaceEditor
).startEditing(element, config); |
| 33 if (!controller) |
| 34 return Promise.reject(new Error('Editing is already in progress')); |
| 35 return controller; |
| 36 } |
| 37 } |
| 38 |
| 39 /** |
| 40 * @return {string} |
| 41 */ |
| 42 editorContent(editingContext) { |
| 43 var element = editingContext.element; |
| 44 if (element.tagName === 'INPUT' && element.type === 'text') |
| 45 return element.value; |
| 46 |
| 47 return element.textContent; |
| 48 } |
| 49 |
| 50 setUpEditor(editingContext) { |
| 51 var element = editingContext.element; |
| 52 element.classList.add('editing'); |
| 53 |
| 54 var oldTabIndex = element.getAttribute('tabIndex'); |
| 55 if (typeof oldTabIndex !== 'number' || oldTabIndex < 0) |
| 56 element.tabIndex = 0; |
| 57 this._focusRestorer = new WebInspector.ElementFocusRestorer(element); |
| 58 editingContext.oldTabIndex = oldTabIndex; |
| 59 } |
| 60 |
| 61 closeEditor(editingContext) { |
| 62 var element = editingContext.element; |
| 63 element.classList.remove('editing'); |
| 64 |
| 65 if (typeof editingContext.oldTabIndex !== 'number') |
| 66 element.removeAttribute('tabIndex'); |
| 67 else |
| 68 element.tabIndex = editingContext.oldTabIndex; |
| 69 element.scrollTop = 0; |
| 70 element.scrollLeft = 0; |
| 71 } |
| 72 |
| 73 cancelEditing(editingContext) { |
| 74 var element = editingContext.element; |
| 75 if (element.tagName === 'INPUT' && element.type === 'text') |
| 76 element.value = editingContext.oldText; |
| 77 else |
| 78 element.textContent = editingContext.oldText; |
| 79 } |
| 80 |
| 81 augmentEditingHandle(editingContext, handle) { |
| 82 } |
| 83 |
| 84 /** |
| 85 * @param {!Element} element |
| 86 * @param {!WebInspector.InplaceEditor.Config=} config |
| 87 * @return {?WebInspector.InplaceEditor.Controller} |
| 88 */ |
| 89 startEditing(element, config) { |
| 90 if (!WebInspector.markBeingEdited(element, true)) |
| 91 return null; |
| 92 |
| 93 config = config || new WebInspector.InplaceEditor.Config(function() {}, func
tion() {}); |
| 94 var editingContext = {element: element, config: config}; |
| 95 var committedCallback = config.commitHandler; |
| 96 var cancelledCallback = config.cancelHandler; |
| 97 var pasteCallback = config.pasteHandler; |
| 98 var context = config.context; |
| 99 var isMultiline = config.multiline || false; |
| 100 var moveDirection = ''; |
| 101 var self = this; |
| 102 |
| 103 /** |
| 104 * @param {!Event} e |
| 105 */ |
| 106 function consumeCopy(e) { |
| 107 e.consume(); |
| 108 } |
| 109 |
| 110 this.setUpEditor(editingContext); |
| 111 |
| 112 editingContext.oldText = isMultiline ? config.initialValue : this.editorCont
ent(editingContext); |
| 113 |
| 114 /** |
| 115 * @param {!Event=} e |
| 116 */ |
| 117 function blurEventListener(e) { |
| 118 if (config.blurHandler && !config.blurHandler(element, e)) |
| 119 return; |
| 120 if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSelfOrDes
cendant(element)) |
| 121 editingCommitted.call(element); |
| 122 } |
| 123 |
| 124 function cleanUpAfterEditing() { |
| 125 WebInspector.markBeingEdited(element, false); |
| 126 |
| 127 element.removeEventListener('blur', blurEventListener, isMultiline); |
| 128 element.removeEventListener('keydown', keyDownEventListener, true); |
| 129 if (pasteCallback) |
| 130 element.removeEventListener('paste', pasteEventListener, true); |
| 131 |
| 132 if (self._focusRestorer) |
| 133 self._focusRestorer.restore(); |
| 134 self.closeEditor(editingContext); |
| 135 } |
| 136 |
| 137 /** @this {Element} */ |
| 138 function editingCancelled() { |
| 139 self.cancelEditing(editingContext); |
| 140 cleanUpAfterEditing(); |
| 141 cancelledCallback(this, context); |
| 142 } |
| 143 |
| 144 /** @this {Element} */ |
| 145 function editingCommitted() { |
| 146 cleanUpAfterEditing(); |
| 147 |
| 148 committedCallback(this, self.editorContent(editingContext), editingContext
.oldText, context, moveDirection); |
| 149 } |
| 150 |
| 151 /** |
| 152 * @param {!Event} event |
| 153 * @return {string} |
| 154 */ |
| 155 function defaultFinishHandler(event) { |
| 156 var isMetaOrCtrl = WebInspector.isMac() ? event.metaKey && !event.shiftKey
&& !event.ctrlKey && !event.altKey : |
| 157 event.ctrlKey && !event.shiftKey
&& !event.metaKey && !event.altKey; |
| 158 if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline || isM
etaOrCtrl)) |
| 159 return 'commit'; |
| 160 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code ||
event.key === 'Escape') |
| 161 return 'cancel'; |
| 162 else if (!isMultiline && event.key === 'Tab') |
| 163 return 'move-' + (event.shiftKey ? 'backward' : 'forward'); |
| 164 return ''; |
| 165 } |
| 166 |
| 167 function handleEditingResult(result, event) { |
| 168 if (result === 'commit') { |
| 169 editingCommitted.call(element); |
| 170 event.consume(true); |
| 171 } else if (result === 'cancel') { |
| 172 editingCancelled.call(element); |
| 173 event.consume(true); |
| 174 } else if (result && result.startsWith('move-')) { |
| 175 moveDirection = result.substring(5); |
| 176 if (event.key !== 'Tab') |
| 177 blurEventListener(); |
| 178 } |
| 179 } |
| 180 |
| 181 /** |
| 182 * @param {!Event} event |
| 183 */ |
| 184 function pasteEventListener(event) { |
| 185 var result = pasteCallback(event); |
| 186 handleEditingResult(result, event); |
| 187 } |
| 188 |
| 189 /** |
| 190 * @param {!Event} event |
| 191 */ |
| 192 function keyDownEventListener(event) { |
| 193 var result = defaultFinishHandler(event); |
| 194 if (!result && config.postKeydownFinishHandler) |
| 195 result = config.postKeydownFinishHandler(event); |
| 196 handleEditingResult(result, event); |
| 197 } |
| 198 |
| 199 element.addEventListener('blur', blurEventListener, isMultiline); |
| 200 element.addEventListener('keydown', keyDownEventListener, true); |
| 201 if (pasteCallback) |
| 202 element.addEventListener('paste', pasteEventListener, true); |
| 203 |
| 204 var handle = {cancel: editingCancelled.bind(element), commit: editingCommitt
ed.bind(element), setWidth() {}}; |
| 205 this.augmentEditingHandle(editingContext, handle); |
| 206 return handle; |
| 207 } |
10 }; | 208 }; |
11 | 209 |
12 /** | 210 /** |
13 * @typedef {{cancel: function(), commit: function(), setWidth: function(number)
}} | 211 * @typedef {{cancel: function(), commit: function(), setWidth: function(number)
}} |
14 */ | 212 */ |
15 WebInspector.InplaceEditor.Controller; | 213 WebInspector.InplaceEditor.Controller; |
16 | 214 |
| 215 |
17 /** | 216 /** |
18 * @param {!Element} element | 217 * @template T |
19 * @param {!WebInspector.InplaceEditor.Config=} config | 218 * @unrestricted |
20 * @return {?WebInspector.InplaceEditor.Controller} | |
21 */ | 219 */ |
22 WebInspector.InplaceEditor.startEditing = function(element, config) | 220 WebInspector.InplaceEditor.Config = class { |
23 { | 221 /** |
24 if (!WebInspector.InplaceEditor._defaultInstance) | 222 * @param {function(!Element,string,string,T,string)} commitHandler |
25 WebInspector.InplaceEditor._defaultInstance = new WebInspector.InplaceEd
itor(); | 223 * @param {function(!Element,T)} cancelHandler |
26 return WebInspector.InplaceEditor._defaultInstance.startEditing(element, con
fig); | 224 * @param {T=} context |
27 }; | 225 * @param {function(!Element,!Event):boolean=} blurHandler |
28 | 226 */ |
29 /** | 227 constructor(commitHandler, cancelHandler, context, blurHandler) { |
30 * @param {!Element} element | |
31 * @param {!WebInspector.InplaceEditor.Config=} config | |
32 * @return {!Promise.<!WebInspector.InplaceEditor.Controller>} | |
33 */ | |
34 WebInspector.InplaceEditor.startMultilineEditing = function(element, config) | |
35 { | |
36 return self.runtime.extension(WebInspector.InplaceEditor).instance().then(st
artEditing); | |
37 | |
38 /** | |
39 * @param {!Object} inplaceEditor | |
40 * @return {!WebInspector.InplaceEditor.Controller|!Promise.<!WebInspector.I
nplaceEditor.Controller>} | |
41 */ | |
42 function startEditing(inplaceEditor) | |
43 { | |
44 var controller = /** @type {!WebInspector.InplaceEditor} */ (inplaceEdit
or).startEditing(element, config); | |
45 if (!controller) | |
46 return Promise.reject(new Error("Editing is already in progress")); | |
47 return controller; | |
48 } | |
49 }; | |
50 | |
51 WebInspector.InplaceEditor.prototype = { | |
52 /** | |
53 * @return {string} | |
54 */ | |
55 editorContent: function(editingContext) { | |
56 var element = editingContext.element; | |
57 if (element.tagName === "INPUT" && element.type === "text") | |
58 return element.value; | |
59 | |
60 return element.textContent; | |
61 }, | |
62 | |
63 setUpEditor: function(editingContext) | |
64 { | |
65 var element = editingContext.element; | |
66 element.classList.add("editing"); | |
67 | |
68 var oldTabIndex = element.getAttribute("tabIndex"); | |
69 if (typeof oldTabIndex !== "number" || oldTabIndex < 0) | |
70 element.tabIndex = 0; | |
71 this._focusRestorer = new WebInspector.ElementFocusRestorer(element); | |
72 editingContext.oldTabIndex = oldTabIndex; | |
73 }, | |
74 | |
75 closeEditor: function(editingContext) | |
76 { | |
77 var element = editingContext.element; | |
78 element.classList.remove("editing"); | |
79 | |
80 if (typeof editingContext.oldTabIndex !== "number") | |
81 element.removeAttribute("tabIndex"); | |
82 else | |
83 element.tabIndex = editingContext.oldTabIndex; | |
84 element.scrollTop = 0; | |
85 element.scrollLeft = 0; | |
86 }, | |
87 | |
88 cancelEditing: function(editingContext) | |
89 { | |
90 var element = editingContext.element; | |
91 if (element.tagName === "INPUT" && element.type === "text") | |
92 element.value = editingContext.oldText; | |
93 else | |
94 element.textContent = editingContext.oldText; | |
95 }, | |
96 | |
97 augmentEditingHandle: function(editingContext, handle) | |
98 { | |
99 }, | |
100 | |
101 /** | |
102 * @param {!Element} element | |
103 * @param {!WebInspector.InplaceEditor.Config=} config | |
104 * @return {?WebInspector.InplaceEditor.Controller} | |
105 */ | |
106 startEditing: function(element, config) | |
107 { | |
108 if (!WebInspector.markBeingEdited(element, true)) | |
109 return null; | |
110 | |
111 config = config || new WebInspector.InplaceEditor.Config(function() {},
function() {}); | |
112 var editingContext = { element: element, config: config }; | |
113 var committedCallback = config.commitHandler; | |
114 var cancelledCallback = config.cancelHandler; | |
115 var pasteCallback = config.pasteHandler; | |
116 var context = config.context; | |
117 var isMultiline = config.multiline || false; | |
118 var moveDirection = ""; | |
119 var self = this; | |
120 | |
121 /** | |
122 * @param {!Event} e | |
123 */ | |
124 function consumeCopy(e) | |
125 { | |
126 e.consume(); | |
127 } | |
128 | |
129 this.setUpEditor(editingContext); | |
130 | |
131 editingContext.oldText = isMultiline ? config.initialValue : this.editor
Content(editingContext); | |
132 | |
133 /** | |
134 * @param {!Event=} e | |
135 */ | |
136 function blurEventListener(e) { | |
137 if (config.blurHandler && !config.blurHandler(element, e)) | |
138 return; | |
139 if (!isMultiline || !e || !e.relatedTarget || !e.relatedTarget.isSel
fOrDescendant(element)) | |
140 editingCommitted.call(element); | |
141 } | |
142 | |
143 function cleanUpAfterEditing() | |
144 { | |
145 WebInspector.markBeingEdited(element, false); | |
146 | |
147 element.removeEventListener("blur", blurEventListener, isMultiline); | |
148 element.removeEventListener("keydown", keyDownEventListener, true); | |
149 if (pasteCallback) | |
150 element.removeEventListener("paste", pasteEventListener, true); | |
151 | |
152 if (self._focusRestorer) | |
153 self._focusRestorer.restore(); | |
154 self.closeEditor(editingContext); | |
155 } | |
156 | |
157 /** @this {Element} */ | |
158 function editingCancelled() | |
159 { | |
160 self.cancelEditing(editingContext); | |
161 cleanUpAfterEditing(); | |
162 cancelledCallback(this, context); | |
163 } | |
164 | |
165 /** @this {Element} */ | |
166 function editingCommitted() | |
167 { | |
168 cleanUpAfterEditing(); | |
169 | |
170 committedCallback(this, self.editorContent(editingContext), editingC
ontext.oldText, context, moveDirection); | |
171 } | |
172 | |
173 /** | |
174 * @param {!Event} event | |
175 * @return {string} | |
176 */ | |
177 function defaultFinishHandler(event) | |
178 { | |
179 var isMetaOrCtrl = WebInspector.isMac() ? | |
180 event.metaKey && !event.shiftKey && !event.ctrlKey && !event.alt
Key : | |
181 event.ctrlKey && !event.shiftKey && !event.metaKey && !event.alt
Key; | |
182 if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !isMultiline
|| isMetaOrCtrl)) | |
183 return "commit"; | |
184 else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.co
de || event.key === "Escape") | |
185 return "cancel"; | |
186 else if (!isMultiline && event.key === "Tab") | |
187 return "move-" + (event.shiftKey ? "backward" : "forward"); | |
188 return ""; | |
189 } | |
190 | |
191 function handleEditingResult(result, event) | |
192 { | |
193 if (result === "commit") { | |
194 editingCommitted.call(element); | |
195 event.consume(true); | |
196 } else if (result === "cancel") { | |
197 editingCancelled.call(element); | |
198 event.consume(true); | |
199 } else if (result && result.startsWith("move-")) { | |
200 moveDirection = result.substring(5); | |
201 if (event.key !== "Tab") | |
202 blurEventListener(); | |
203 } | |
204 } | |
205 | |
206 /** | |
207 * @param {!Event} event | |
208 */ | |
209 function pasteEventListener(event) | |
210 { | |
211 var result = pasteCallback(event); | |
212 handleEditingResult(result, event); | |
213 } | |
214 | |
215 /** | |
216 * @param {!Event} event | |
217 */ | |
218 function keyDownEventListener(event) | |
219 { | |
220 var result = defaultFinishHandler(event); | |
221 if (!result && config.postKeydownFinishHandler) | |
222 result = config.postKeydownFinishHandler(event); | |
223 handleEditingResult(result, event); | |
224 } | |
225 | |
226 element.addEventListener("blur", blurEventListener, isMultiline); | |
227 element.addEventListener("keydown", keyDownEventListener, true); | |
228 if (pasteCallback) | |
229 element.addEventListener("paste", pasteEventListener, true); | |
230 | |
231 var handle = { | |
232 cancel: editingCancelled.bind(element), | |
233 commit: editingCommitted.bind(element), | |
234 setWidth: function() {} | |
235 }; | |
236 this.augmentEditingHandle(editingContext, handle); | |
237 return handle; | |
238 } | |
239 }; | |
240 | |
241 /** | |
242 * @constructor | |
243 * @param {function(!Element,string,string,T,string)} commitHandler | |
244 * @param {function(!Element,T)} cancelHandler | |
245 * @param {T=} context | |
246 * @param {function(!Element,!Event):boolean=} blurHandler | |
247 * @template T | |
248 */ | |
249 WebInspector.InplaceEditor.Config = function(commitHandler, cancelHandler, conte
xt, blurHandler) | |
250 { | |
251 this.commitHandler = commitHandler; | 228 this.commitHandler = commitHandler; |
252 this.cancelHandler = cancelHandler; | 229 this.cancelHandler = cancelHandler; |
253 this.context = context; | 230 this.context = context; |
254 this.blurHandler = blurHandler; | 231 this.blurHandler = blurHandler; |
255 | 232 |
256 /** | 233 /** |
257 * @type {function(!Event):string|undefined} | 234 * @type {function(!Event):string|undefined} |
258 */ | 235 */ |
259 this.pasteHandler; | 236 this.pasteHandler; |
260 | 237 |
261 /** | 238 /** |
262 * @type {boolean|undefined} | 239 * @type {boolean|undefined} |
263 */ | 240 */ |
264 this.multiline; | 241 this.multiline; |
265 | 242 |
266 /** | 243 /** |
267 * @type {function(!Event):string|undefined} | 244 * @type {function(!Event):string|undefined} |
268 */ | 245 */ |
269 this.postKeydownFinishHandler; | 246 this.postKeydownFinishHandler; |
| 247 } |
| 248 |
| 249 setPasteHandler(pasteHandler) { |
| 250 this.pasteHandler = pasteHandler; |
| 251 } |
| 252 |
| 253 /** |
| 254 * @param {string} initialValue |
| 255 * @param {!Object} mode |
| 256 * @param {string} theme |
| 257 * @param {boolean=} lineWrapping |
| 258 * @param {boolean=} smartIndent |
| 259 */ |
| 260 setMultilineOptions(initialValue, mode, theme, lineWrapping, smartIndent) { |
| 261 this.multiline = true; |
| 262 this.initialValue = initialValue; |
| 263 this.mode = mode; |
| 264 this.theme = theme; |
| 265 this.lineWrapping = lineWrapping; |
| 266 this.smartIndent = smartIndent; |
| 267 } |
| 268 |
| 269 /** |
| 270 * @param {function(!Event):string} postKeydownFinishHandler |
| 271 */ |
| 272 setPostKeydownFinishHandler(postKeydownFinishHandler) { |
| 273 this.postKeydownFinishHandler = postKeydownFinishHandler; |
| 274 } |
270 }; | 275 }; |
271 | |
272 WebInspector.InplaceEditor.Config.prototype = { | |
273 setPasteHandler: function(pasteHandler) | |
274 { | |
275 this.pasteHandler = pasteHandler; | |
276 }, | |
277 | |
278 /** | |
279 * @param {string} initialValue | |
280 * @param {!Object} mode | |
281 * @param {string} theme | |
282 * @param {boolean=} lineWrapping | |
283 * @param {boolean=} smartIndent | |
284 */ | |
285 setMultilineOptions: function(initialValue, mode, theme, lineWrapping, smart
Indent) | |
286 { | |
287 this.multiline = true; | |
288 this.initialValue = initialValue; | |
289 this.mode = mode; | |
290 this.theme = theme; | |
291 this.lineWrapping = lineWrapping; | |
292 this.smartIndent = smartIndent; | |
293 }, | |
294 | |
295 /** | |
296 * @param {function(!Event):string} postKeydownFinishHandler | |
297 */ | |
298 setPostKeydownFinishHandler: function(postKeydownFinishHandler) | |
299 { | |
300 this.postKeydownFinishHandler = postKeydownFinishHandler; | |
301 } | |
302 }; | |
OLD | NEW |