PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 3.3.5
Pods – Custom Content Types and Fields v3.3.5
2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / ui / js / codemirror / addon / hint / show-hint.js
pods / ui / js / codemirror / addon / hint Last commit date
show-hint.css 2 years ago show-hint.js 2 years ago
show-hint.js
524 lines
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: https://codemirror.net/5/LICENSE
3
4 // declare global: DOMRect
5
6 (function(mod) {
7 if (typeof exports == "object" && typeof module == "object") // CommonJS
8 mod(require("../../lib/codemirror"));
9 else if (typeof define == "function" && define.amd) // AMD
10 define(["../../lib/codemirror"], mod);
11 else // Plain browser env
12 mod(CodeMirror);
13 })(function(CodeMirror) {
14 "use strict";
15
16 var HINT_ELEMENT_CLASS = "CodeMirror-hint";
17 var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
18
19 // This is the old interface, kept around for now to stay
20 // backwards-compatible.
21 CodeMirror.showHint = function(cm, getHints, options) {
22 if (!getHints) return cm.showHint(options);
23 if (options && options.async) getHints.async = true;
24 var newOpts = {hint: getHints};
25 if (options) for (var prop in options) newOpts[prop] = options[prop];
26 return cm.showHint(newOpts);
27 };
28
29 CodeMirror.defineExtension("showHint", function(options) {
30 options = parseOptions(this, this.getCursor("start"), options);
31 var selections = this.listSelections()
32 if (selections.length > 1) return;
33 // By default, don't allow completion when something is selected.
34 // A hint function can have a `supportsSelection` property to
35 // indicate that it can handle selections.
36 if (this.somethingSelected()) {
37 if (!options.hint.supportsSelection) return;
38 // Don't try with cross-line selections
39 for (var i = 0; i < selections.length; i++)
40 if (selections[i].head.line != selections[i].anchor.line) return;
41 }
42
43 if (this.state.completionActive) this.state.completionActive.close();
44 var completion = this.state.completionActive = new Completion(this, options);
45 if (!completion.options.hint) return;
46
47 CodeMirror.signal(this, "startCompletion", this);
48 completion.update(true);
49 });
50
51 CodeMirror.defineExtension("closeHint", function() {
52 if (this.state.completionActive) this.state.completionActive.close()
53 })
54
55 function Completion(cm, options) {
56 this.cm = cm;
57 this.options = options;
58 this.widget = null;
59 this.debounce = 0;
60 this.tick = 0;
61 this.startPos = this.cm.getCursor("start");
62 this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
63
64 if (this.options.updateOnCursorActivity) {
65 var self = this;
66 cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
67 }
68 }
69
70 var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
71 return setTimeout(fn, 1000/60);
72 };
73 var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
74
75 Completion.prototype = {
76 close: function() {
77 if (!this.active()) return;
78 this.cm.state.completionActive = null;
79 this.tick = null;
80 if (this.options.updateOnCursorActivity) {
81 this.cm.off("cursorActivity", this.activityFunc);
82 }
83
84 if (this.widget && this.data) CodeMirror.signal(this.data, "close");
85 if (this.widget) this.widget.close();
86 CodeMirror.signal(this.cm, "endCompletion", this.cm);
87 },
88
89 active: function() {
90 return this.cm.state.completionActive == this;
91 },
92
93 pick: function(data, i) {
94 var completion = data.list[i], self = this;
95 this.cm.operation(function() {
96 if (completion.hint)
97 completion.hint(self.cm, data, completion);
98 else
99 self.cm.replaceRange(getText(completion), completion.from || data.from,
100 completion.to || data.to, "complete");
101 CodeMirror.signal(data, "pick", completion);
102 self.cm.scrollIntoView();
103 });
104 if (this.options.closeOnPick) {
105 this.close();
106 }
107 },
108
109 cursorActivity: function() {
110 if (this.debounce) {
111 cancelAnimationFrame(this.debounce);
112 this.debounce = 0;
113 }
114
115 var identStart = this.startPos;
116 if(this.data) {
117 identStart = this.data.from;
118 }
119
120 var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
121 if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
122 pos.ch < identStart.ch || this.cm.somethingSelected() ||
123 (!pos.ch || this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
124 this.close();
125 } else {
126 var self = this;
127 this.debounce = requestAnimationFrame(function() {self.update();});
128 if (this.widget) this.widget.disable();
129 }
130 },
131
132 update: function(first) {
133 if (this.tick == null) return
134 var self = this, myTick = ++this.tick
135 fetchHints(this.options.hint, this.cm, this.options, function(data) {
136 if (self.tick == myTick) self.finishUpdate(data, first)
137 })
138 },
139
140 finishUpdate: function(data, first) {
141 if (this.data) CodeMirror.signal(this.data, "update");
142
143 var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
144 if (this.widget) this.widget.close();
145
146 this.data = data;
147
148 if (data && data.list.length) {
149 if (picked && data.list.length == 1) {
150 this.pick(data, 0);
151 } else {
152 this.widget = new Widget(this, data);
153 CodeMirror.signal(data, "shown");
154 }
155 }
156 }
157 };
158
159 function parseOptions(cm, pos, options) {
160 var editor = cm.options.hintOptions;
161 var out = {};
162 for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
163 if (editor) for (var prop in editor)
164 if (editor[prop] !== undefined) out[prop] = editor[prop];
165 if (options) for (var prop in options)
166 if (options[prop] !== undefined) out[prop] = options[prop];
167 if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
168 return out;
169 }
170
171 function getText(completion) {
172 if (typeof completion == "string") return completion;
173 else return completion.text;
174 }
175
176 function buildKeyMap(completion, handle) {
177 var baseMap = {
178 Up: function() {handle.moveFocus(-1);},
179 Down: function() {handle.moveFocus(1);},
180 PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
181 PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
182 Home: function() {handle.setFocus(0);},
183 End: function() {handle.setFocus(handle.length - 1);},
184 Enter: handle.pick,
185 Tab: handle.pick,
186 Esc: handle.close
187 };
188
189 var mac = /Mac/.test(navigator.platform);
190
191 if (mac) {
192 baseMap["Ctrl-P"] = function() {handle.moveFocus(-1);};
193 baseMap["Ctrl-N"] = function() {handle.moveFocus(1);};
194 }
195
196 var custom = completion.options.customKeys;
197 var ourMap = custom ? {} : baseMap;
198 function addBinding(key, val) {
199 var bound;
200 if (typeof val != "string")
201 bound = function(cm) { return val(cm, handle); };
202 // This mechanism is deprecated
203 else if (baseMap.hasOwnProperty(val))
204 bound = baseMap[val];
205 else
206 bound = val;
207 ourMap[key] = bound;
208 }
209 if (custom)
210 for (var key in custom) if (custom.hasOwnProperty(key))
211 addBinding(key, custom[key]);
212 var extra = completion.options.extraKeys;
213 if (extra)
214 for (var key in extra) if (extra.hasOwnProperty(key))
215 addBinding(key, extra[key]);
216 return ourMap;
217 }
218
219 function getHintElement(hintsElement, el) {
220 while (el && el != hintsElement) {
221 if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
222 el = el.parentNode;
223 }
224 }
225
226 function Widget(completion, data) {
227 this.id = "cm-complete-" + Math.floor(Math.random(1e6))
228 this.completion = completion;
229 this.data = data;
230 this.picked = false;
231 var widget = this, cm = completion.cm;
232 var ownerDocument = cm.getInputField().ownerDocument;
233 var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow;
234
235 var hints = this.hints = ownerDocument.createElement("ul");
236 hints.setAttribute("role", "listbox")
237 hints.setAttribute("aria-expanded", "true")
238 hints.id = this.id
239 var theme = completion.cm.options.theme;
240 hints.className = "CodeMirror-hints " + theme;
241 this.selectedHint = data.selectedHint || 0;
242
243 var completions = data.list;
244 for (var i = 0; i < completions.length; ++i) {
245 var elt = hints.appendChild(ownerDocument.createElement("li")), cur = completions[i];
246 var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
247 if (cur.className != null) className = cur.className + " " + className;
248 elt.className = className;
249 if (i == this.selectedHint) elt.setAttribute("aria-selected", "true")
250 elt.id = this.id + "-" + i
251 elt.setAttribute("role", "option")
252 if (cur.render) cur.render(elt, data, cur);
253 else elt.appendChild(ownerDocument.createTextNode(cur.displayText || getText(cur)));
254 elt.hintId = i;
255 }
256
257 var container = completion.options.container || ownerDocument.body;
258 var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
259 var left = pos.left, top = pos.bottom, below = true;
260 var offsetLeft = 0, offsetTop = 0;
261 if (container !== ownerDocument.body) {
262 // We offset the cursor position because left and top are relative to the offsetParent's top left corner.
263 var isContainerPositioned = ['absolute', 'relative', 'fixed'].indexOf(parentWindow.getComputedStyle(container).position) !== -1;
264 var offsetParent = isContainerPositioned ? container : container.offsetParent;
265 var offsetParentPosition = offsetParent.getBoundingClientRect();
266 var bodyPosition = ownerDocument.body.getBoundingClientRect();
267 offsetLeft = (offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft);
268 offsetTop = (offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop);
269 }
270 hints.style.left = (left - offsetLeft) + "px";
271 hints.style.top = (top - offsetTop) + "px";
272
273 // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
274 var winW = parentWindow.innerWidth || Math.max(ownerDocument.body.offsetWidth, ownerDocument.documentElement.offsetWidth);
275 var winH = parentWindow.innerHeight || Math.max(ownerDocument.body.offsetHeight, ownerDocument.documentElement.offsetHeight);
276 container.appendChild(hints);
277 cm.getInputField().setAttribute("aria-autocomplete", "list")
278 cm.getInputField().setAttribute("aria-owns", this.id)
279 cm.getInputField().setAttribute("aria-activedescendant", this.id + "-" + this.selectedHint)
280
281 var box = completion.options.moveOnOverlap ? hints.getBoundingClientRect() : new DOMRect();
282 var scrolls = completion.options.paddingForScrollbar ? hints.scrollHeight > hints.clientHeight + 1 : false;
283
284 // Compute in the timeout to avoid reflow on init
285 var startScroll;
286 setTimeout(function() { startScroll = cm.getScrollInfo(); });
287
288 var overlapY = box.bottom - winH;
289 if (overlapY > 0) { // Does not fit below
290 var height = box.bottom - box.top, spaceAbove = box.top - (pos.bottom - pos.top) - 2
291 if (winH - box.top < spaceAbove) { // More room at the top
292 if (height > spaceAbove) hints.style.height = (height = spaceAbove) + "px";
293 hints.style.top = ((top = pos.top - height) + offsetTop) + "px";
294 below = false;
295 } else {
296 hints.style.height = (winH - box.top - 2) + "px";
297 }
298 }
299 var overlapX = box.right - winW;
300 if (scrolls) overlapX += cm.display.nativeBarWidth;
301 if (overlapX > 0) {
302 if (box.right - box.left > winW) {
303 hints.style.width = (winW - 5) + "px";
304 overlapX -= (box.right - box.left) - winW;
305 }
306 hints.style.left = (left = Math.max(pos.left - overlapX - offsetLeft, 0)) + "px";
307 }
308 if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
309 node.style.paddingRight = cm.display.nativeBarWidth + "px"
310
311 cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
312 moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
313 setFocus: function(n) { widget.changeActive(n); },
314 menuSize: function() { return widget.screenAmount(); },
315 length: completions.length,
316 close: function() { completion.close(); },
317 pick: function() { widget.pick(); },
318 data: data
319 }));
320
321 if (completion.options.closeOnUnfocus) {
322 var closingOnBlur;
323 cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
324 cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
325 }
326
327 cm.on("scroll", this.onScroll = function() {
328 var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
329 if (!startScroll) startScroll = cm.getScrollInfo();
330 var newTop = top + startScroll.top - curScroll.top;
331 var point = newTop - (parentWindow.pageYOffset || (ownerDocument.documentElement || ownerDocument.body).scrollTop);
332 if (!below) point += hints.offsetHeight;
333 if (point <= editor.top || point >= editor.bottom) return completion.close();
334 hints.style.top = newTop + "px";
335 hints.style.left = (left + startScroll.left - curScroll.left) + "px";
336 });
337
338 CodeMirror.on(hints, "dblclick", function(e) {
339 var t = getHintElement(hints, e.target || e.srcElement);
340 if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
341 });
342
343 CodeMirror.on(hints, "click", function(e) {
344 var t = getHintElement(hints, e.target || e.srcElement);
345 if (t && t.hintId != null) {
346 widget.changeActive(t.hintId);
347 if (completion.options.completeOnSingleClick) widget.pick();
348 }
349 });
350
351 CodeMirror.on(hints, "mousedown", function() {
352 setTimeout(function(){cm.focus();}, 20);
353 });
354
355 // The first hint doesn't need to be scrolled to on init
356 var selectedHintRange = this.getSelectedHintRange();
357 if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) {
358 this.scrollToActive();
359 }
360
361 CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
362 return true;
363 }
364
365 Widget.prototype = {
366 close: function() {
367 if (this.completion.widget != this) return;
368 this.completion.widget = null;
369 if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints);
370 this.completion.cm.removeKeyMap(this.keyMap);
371 var input = this.completion.cm.getInputField()
372 input.removeAttribute("aria-activedescendant")
373 input.removeAttribute("aria-owns")
374
375 var cm = this.completion.cm;
376 if (this.completion.options.closeOnUnfocus) {
377 cm.off("blur", this.onBlur);
378 cm.off("focus", this.onFocus);
379 }
380 cm.off("scroll", this.onScroll);
381 },
382
383 disable: function() {
384 this.completion.cm.removeKeyMap(this.keyMap);
385 var widget = this;
386 this.keyMap = {Enter: function() { widget.picked = true; }};
387 this.completion.cm.addKeyMap(this.keyMap);
388 },
389
390 pick: function() {
391 this.completion.pick(this.data, this.selectedHint);
392 },
393
394 changeActive: function(i, avoidWrap) {
395 if (i >= this.data.list.length)
396 i = avoidWrap ? this.data.list.length - 1 : 0;
397 else if (i < 0)
398 i = avoidWrap ? 0 : this.data.list.length - 1;
399 if (this.selectedHint == i) return;
400 var node = this.hints.childNodes[this.selectedHint];
401 if (node) {
402 node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
403 node.removeAttribute("aria-selected")
404 }
405 node = this.hints.childNodes[this.selectedHint = i];
406 node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
407 node.setAttribute("aria-selected", "true")
408 this.completion.cm.getInputField().setAttribute("aria-activedescendant", node.id)
409 this.scrollToActive()
410 CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
411 },
412
413 scrollToActive: function() {
414 var selectedHintRange = this.getSelectedHintRange();
415 var node1 = this.hints.childNodes[selectedHintRange.from];
416 var node2 = this.hints.childNodes[selectedHintRange.to];
417 var firstNode = this.hints.firstChild;
418 if (node1.offsetTop < this.hints.scrollTop)
419 this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop;
420 else if (node2.offsetTop + node2.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
421 this.hints.scrollTop = node2.offsetTop + node2.offsetHeight - this.hints.clientHeight + firstNode.offsetTop;
422 },
423
424 screenAmount: function() {
425 return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
426 },
427
428 getSelectedHintRange: function() {
429 var margin = this.completion.options.scrollMargin || 0;
430 return {
431 from: Math.max(0, this.selectedHint - margin),
432 to: Math.min(this.data.list.length - 1, this.selectedHint + margin),
433 };
434 }
435 };
436
437 function applicableHelpers(cm, helpers) {
438 if (!cm.somethingSelected()) return helpers
439 var result = []
440 for (var i = 0; i < helpers.length; i++)
441 if (helpers[i].supportsSelection) result.push(helpers[i])
442 return result
443 }
444
445 function fetchHints(hint, cm, options, callback) {
446 if (hint.async) {
447 hint(cm, callback, options)
448 } else {
449 var result = hint(cm, options)
450 if (result && result.then) result.then(callback)
451 else callback(result)
452 }
453 }
454
455 function resolveAutoHints(cm, pos) {
456 var helpers = cm.getHelpers(pos, "hint"), words
457 if (helpers.length) {
458 var resolved = function(cm, callback, options) {
459 var app = applicableHelpers(cm, helpers);
460 function run(i) {
461 if (i == app.length) return callback(null)
462 fetchHints(app[i], cm, options, function(result) {
463 if (result && result.list.length > 0) callback(result)
464 else run(i + 1)
465 })
466 }
467 run(0)
468 }
469 resolved.async = true
470 resolved.supportsSelection = true
471 return resolved
472 } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
473 return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
474 } else if (CodeMirror.hint.anyword) {
475 return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
476 } else {
477 return function() {}
478 }
479 }
480
481 CodeMirror.registerHelper("hint", "auto", {
482 resolve: resolveAutoHints
483 });
484
485 CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
486 var cur = cm.getCursor(), token = cm.getTokenAt(cur)
487 var term, from = CodeMirror.Pos(cur.line, token.start), to = cur
488 if (token.start < cur.ch && /\w/.test(token.string.charAt(cur.ch - token.start - 1))) {
489 term = token.string.substr(0, cur.ch - token.start)
490 } else {
491 term = ""
492 from = cur
493 }
494 var found = [];
495 for (var i = 0; i < options.words.length; i++) {
496 var word = options.words[i];
497 if (word.slice(0, term.length) == term)
498 found.push(word);
499 }
500
501 if (found.length) return {list: found, from: from, to: to};
502 });
503
504 CodeMirror.commands.autocomplete = CodeMirror.showHint;
505
506 var defaultOptions = {
507 hint: CodeMirror.hint.auto,
508 completeSingle: true,
509 alignWithWord: true,
510 closeCharacters: /[\s()\[\]{};:>,]/,
511 closeOnPick: true,
512 closeOnUnfocus: true,
513 updateOnCursorActivity: true,
514 completeOnSingleClick: true,
515 container: null,
516 customKeys: null,
517 extraKeys: null,
518 paddingForScrollbar: true,
519 moveOnOverlap: true,
520 };
521
522 CodeMirror.defineOption("hintOptions", null);
523 });
524