UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

205 lines 9.79 kB
var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; define(["require", "exports", "jquery", "../domutil", "../key-constants", "bootstrap", "typeahead"], function (require, exports, jquery_1, domutil, keyConstants) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); jquery_1 = __importDefault(jquery_1); domutil = __importStar(domutil); keyConstants = __importStar(keyConstants); /** * A typeahead popup GUI element. */ class TypeaheadPopup { /** * @param doc The DOM document for which to make this popup. * * @param x Position of popup. The popup may ignore the position if it would * overflow off the screen or not have enough space to reasonably show the * choices for typing ahead. * * @param y Position of popup. * * @param width The desired width of the popup. This value may get overridden. * * @param placeholder The placeholder text to use. * * @param options The options to pass to the underlying Twitter Typeahead * menu. * * @param dismissCallback Function to call when the popup is dismissed. */ // tslint:disable-next-line:max-func-body-length constructor(doc, x, y, width, placeholder, options, // tslint:disable-next-line:no-any dismissCallback) { this.dismissed = false; const taWrapper = domutil.htmlToElements("<div class=\"wed-typeahead-popup\">\ <input class=\"typeahead form-control\" type=\"text\">\ <span class=\"spinner\"><i class=\"fa fa-spinner fa-spin\"></i></span></div>", doc)[0]; const ta = taWrapper.firstElementChild; ta.setAttribute("placeholder", placeholder); this.taWrapper = taWrapper; this.dismissCallback = dismissCallback; this.backdrop = document.createElement("div"); this.backdrop.className = "wed-typeahead-popup-backdrop"; jquery_1.default(this.backdrop).click(() => { this.dismiss(); return false; }); taWrapper.style.width = `${width}px`; taWrapper.style.left = `${x}px`; taWrapper.style.top = `${y}px`; const $ta = this.$ta = jquery_1.default(ta); let args = [options.options]; if (options.datasets != null && options.datasets.length > 0) { args = args.concat(options.datasets); } $ta.typeahead.apply($ta, args); $ta.on("keydown", this._keydownHandler.bind(this)); $ta.on("typeahead:selected", this._selectedHandler.bind(this)); const body = doc.body; body.insertBefore(taWrapper, body.firstChild); body.insertBefore(this.backdrop, body.firstChild); // Verify if we're going to run off screen. If so, then modify our position // to be inside the screen. const actualWidth = taWrapper.offsetWidth; const winWidth = doc.defaultView.innerWidth; // The x value that would put the menu just against the side of the window // is actualWidth - winWidth. If x is less than it, then x is the value we // want, but we don't want less than 0. taWrapper.style.left = `${Math.max(0, Math.min(x, winWidth - actualWidth))}px`; taWrapper.style.maxWidth = `${winWidth}px`; const winHeight = doc.defaultView.innerHeight; const maxHeight = winHeight - y; taWrapper.style.maxHeight = `${maxHeight}px`; const dropdown = taWrapper.getElementsByClassName("tt-menu")[0]; const $dropdown = jquery_1.default(dropdown); // Yep, we forcibly display it here because the next computations depend on // the dropdown being visible. const oldDisplay = dropdown.style.display; dropdown.style.display = "block"; // We arbitrarily want to show at least five lines of information. (Which // may or may not translate to 4 choices. This is not the goal. The goal is // just to show a reasonable amount of information.) const fiveLines = Number($dropdown.css("line-height").replace("px", "")) * 5; const dropdownPos = dropdown.getBoundingClientRect(); let dropdownMaxHeight = winHeight - dropdownPos.top; if (dropdownMaxHeight < fiveLines) { // Less than 5 lines: we need to move up. y -= fiveLines - dropdownMaxHeight; dropdownMaxHeight = fiveLines; taWrapper.style.top = `${y}px`; } dropdown.style.maxHeight = `${dropdownMaxHeight}px`; // Restore it. It was probably hidden. dropdown.style.display = oldDisplay; // Work around a stupid issue with typeahead. The problem is that // **hovering** over a choice makes it so that the choice is considered to // be the one to be selected when ENTER is pressed. This can lead to // inconsistent behavior from browser to browser. (It certainly messed up // testing.) $dropdown.off("mouseenter.tt", ".tt-suggestion"); $dropdown.off("mouseleave.tt", ".tt-suggestion"); // Prevent clicks from propagating up. $dropdown.on("click", false); ta.focus(); // Typeahead will consider itself "activated" once it is focused. On most // platforms the focus above is delivered right away. However, on IE the // focus event is sent to elements asynchronously. Which means that the // typeahead could become "activated" much later than the end of this // constructor. For our purposes we want the typeahead to be activated right // away. So we unfortunately break through into private bits of the // typeahead code. const tt = jquery_1.default.data(ta, "ttTypeahead"); tt.isActivated = true; // The default implementation closes the dropdown when the input is // unfocused. This is not a particularly good behavior for // wed. Unfortunately, the only way to rectify it is to break into the // private parts of typeahead. tt.input.off("blurred"); tt._onBlurred = function _onBlurred() { this.isActivated = false; }; tt.input.onSync("blurred", tt._onBlurred, tt); } /** * Dismisses the popup. Calls the callback that was passed when the popup was * created, if any. * * @param obj This should be the object selected by the user, if any. This * will be passed to the ``dismissCallback`` that was passed when the popup * was created, if any. If you call this method directly and want a selection * to occur, take care to use an object which is from the data set passed in * the ``options`` parameter that was used when the popup was created. The * value ``undefined`` means no object was selected. */ // tslint:disable-next-line:no-any dismiss(obj) { if (this.dismissed) { return; } const taWrapper = this.taWrapper; if (taWrapper !== undefined && taWrapper.parentNode !== null) { taWrapper.parentNode.removeChild(taWrapper); } const backdrop = this.backdrop; if (backdrop !== undefined && backdrop.parentNode !== null) { backdrop.parentNode.removeChild(backdrop); } if (this.dismissCallback !== undefined) { this.dismissCallback(obj); } this.dismissed = true; } /** * Event handler for keydown events on the popup. The default implementation * is to dismiss the popup if escape is pressed. */ _keydownHandler(ev) { if (keyConstants.ESCAPE.matchesEvent(ev)) { this.dismiss(); return false; } return undefined; } /** * Event handler for typeahead:selected events. The default implementation is * to dismiss the popup. */ // tslint:disable-next-line:no-any _selectedHandler(_ev, obj) { this.dismiss(obj); } /** * Hide the spinner that was created to indicate that the data is being * loaded. */ hideSpinner() { this.taWrapper.getElementsByClassName("spinner")[0] .style.display = "none"; } /** * Set the value in the input field of the typeahead. This also updates the * suggestions. * * @param value The new value. */ setValue(value) { // tslint:disable-next-line:no-any this.$ta.typeahead("val", value); } } exports.TypeaheadPopup = TypeaheadPopup; }); // LocalWords: typeahead MPL px keydown actualWidth winWidth tt dropdown // LocalWords: dropdownMaxHeight mouseenter mouseleave ttTypeahead //# sourceMappingURL=typeahead-popup.js.map