| /* |
| * Copyright (C) 2015 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.VisualStyleNumberInputBox = class VisualStyleNumberInputBox extends WebInspector.VisualStylePropertyEditor |
| { |
| constructor(propertyNames, text, possibleValues, possibleUnits, allowNegativeValues, layoutReversed) |
| { |
| let unitlessNumberUnit = WebInspector.UIString("Number"); |
| |
| super(propertyNames, text, possibleValues, possibleUnits || [unitlessNumberUnit], "number-input-box", layoutReversed); |
| |
| this._unitlessNumberUnit = unitlessNumberUnit; |
| |
| this._hasUnits = this._possibleUnits.basic.some((unit) => unit !== unitlessNumberUnit); |
| this._allowNegativeValues = !!allowNegativeValues || false; |
| |
| this.contentElement.classList.toggle("no-values", !possibleValues || !possibleValues.length); |
| this.contentElement.classList.toggle("no-units", !this._hasUnits); |
| |
| let focusRingElement = document.createElement("div"); |
| focusRingElement.classList.add("focus-ring"); |
| this.contentElement.appendChild(focusRingElement); |
| |
| this._keywordSelectElement = document.createElement("select"); |
| this._keywordSelectElement.classList.add("number-input-keyword-select"); |
| if (this._possibleUnits.advanced) |
| this._keywordSelectElement.title = WebInspector.UIString("Option-click to show all units"); |
| |
| this._unchangedOptionElement = document.createElement("option"); |
| this._unchangedOptionElement.value = ""; |
| this._unchangedOptionElement.text = WebInspector.UIString("Unchanged"); |
| this._keywordSelectElement.appendChild(this._unchangedOptionElement); |
| |
| this._keywordSelectElement.appendChild(document.createElement("hr")); |
| |
| if (this._possibleValues) { |
| this._createValueOptions(this._possibleValues.basic); |
| this._keywordSelectElement.appendChild(document.createElement("hr")); |
| } |
| |
| if (this._possibleUnits) |
| this._createUnitOptions(this._possibleUnits.basic); |
| |
| this._advancedUnitsElements = null; |
| |
| this._keywordSelectElement.addEventListener("focus", this._focusContentElement.bind(this)); |
| this._keywordSelectElement.addEventListener("mousedown", this._keywordSelectMouseDown.bind(this)); |
| this._keywordSelectElement.addEventListener("change", this._keywordChanged.bind(this)); |
| this._keywordSelectElement.addEventListener("blur", this._blurContentElement.bind(this)); |
| this.contentElement.appendChild(this._keywordSelectElement); |
| |
| this._numberUnitsContainer = document.createElement("div"); |
| this._numberUnitsContainer.classList.add("number-input-container"); |
| |
| this._valueNumberInputElement = document.createElement("input"); |
| this._valueNumberInputElement.classList.add("number-input-value"); |
| this._valueNumberInputElement.spellcheck = false; |
| this._valueNumberInputElement.addEventListener("focus", this._focusContentElement.bind(this)); |
| this._valueNumberInputElement.addEventListener("keydown", this._valueNumberInputKeyDown.bind(this)); |
| this._valueNumberInputElement.addEventListener("keyup", this.debounce(250)._valueNumberInputKeyUp); |
| this._valueNumberInputElement.addEventListener("blur", this._blurContentElement.bind(this)); |
| this._valueNumberInputElement.addEventListener("change", this.debounce(250)._valueNumberInputChanged); |
| this._numberUnitsContainer.appendChild(this._valueNumberInputElement); |
| |
| this._unitsElement = document.createElement("span"); |
| this._numberUnitsContainer.appendChild(this._unitsElement); |
| |
| this.contentElement.appendChild(this._numberUnitsContainer); |
| |
| this._setNumberInputIsEditable(true); |
| this._valueNumberInputElement.value = null; |
| this._valueNumberInputElement.setAttribute("placeholder", 0); |
| this._unitsElementTextContent = this._keywordSelectElement.value = this.valueIsSupportedUnit("px") ? "px" : this._possibleUnits.basic[0]; |
| } |
| |
| // Public |
| |
| get value() |
| { |
| if (this._numberInputIsEditable) |
| return parseFloat(this._valueNumberInputElement.value); |
| return this._keywordSelectElement.value || null; |
| } |
| |
| set value(value) |
| { |
| if (value && value === this.value) |
| return; |
| |
| if (this._propertyMissing) { |
| if (value || this._updatedValues.placeholder) |
| this.specialPropertyPlaceholderElement.textContent = (value || this._updatedValues.placeholder) + (this._updatedValues.units || ""); |
| |
| if (isNaN(value)) { |
| this._unchangedOptionElement.selected = true; |
| this._setNumberInputIsEditable(); |
| this.specialPropertyPlaceholderElement.hidden = false; |
| return; |
| } |
| } |
| |
| this.specialPropertyPlaceholderElement.hidden = true; |
| |
| if (!value) { |
| this._valueNumberInputElement.value = null; |
| this._markUnitsContainerIfInputHasValue(); |
| return; |
| } |
| |
| if (!isNaN(value)) { |
| this._setNumberInputIsEditable(true); |
| this._valueNumberInputElement.value = Math.round(value * 100) / 100; |
| this._markUnitsContainerIfInputHasValue(); |
| return; |
| } |
| |
| if (this.valueIsSupportedKeyword(value)) { |
| this._setNumberInputIsEditable(); |
| this._keywordSelectElement.value = value; |
| return; |
| } |
| } |
| |
| get units() |
| { |
| if (this._unchangedOptionElement.selected) |
| return null; |
| |
| let keyword = this._keywordSelectElement.value; |
| if (!this.valueIsSupportedUnit(keyword)) |
| return null; |
| |
| return keyword; |
| } |
| |
| set units(unit) |
| { |
| if (this._unchangedOptionElement.selected || unit === this.units) |
| return; |
| |
| if (!unit && !this._possibleUnits.basic.includes(this._unitlessNumberUnit) && !this.valueIsSupportedUnit(unit)) |
| return; |
| |
| if (this._valueIsSupportedAdvancedUnit(unit)) |
| this._addAdvancedUnits(); |
| |
| this._setNumberInputIsEditable(true); |
| this._keywordSelectElement.value = unit || this._unitlessNumberUnit; |
| this._unitsElementTextContent = unit; |
| } |
| |
| get placeholder() |
| { |
| return this._valueNumberInputElement.getAttribute("placeholder"); |
| } |
| |
| set placeholder(text) |
| { |
| if (text === this.placeholder) |
| return; |
| |
| let onlyNumericalText = text && !isNaN(text) && (Math.round(text * 100) / 100); |
| this._valueNumberInputElement.setAttribute("placeholder", onlyNumericalText || 0); |
| |
| if (!onlyNumericalText) |
| this.specialPropertyPlaceholderElement.textContent = this._canonicalizedKeywordForKey(text) || text; |
| } |
| |
| get synthesizedValue() |
| { |
| if (this._unchangedOptionElement.selected) |
| return null; |
| |
| let value = this._valueNumberInputElement.value; |
| if (this._numberInputIsEditable && !value) |
| return null; |
| |
| let keyword = this._keywordSelectElement.value; |
| return this.valueIsSupportedUnit(keyword) ? value + (keyword === this._unitlessNumberUnit ? "" : keyword) : keyword; |
| } |
| |
| updateValueFromText(text, value) |
| { |
| let match = this.parseValue(value); |
| this.value = match ? match[1] : value; |
| this.units = match ? match[2] : null; |
| return this.modifyPropertyText(text, value); |
| } |
| |
| // Protected |
| |
| set specialPropertyPlaceholderElementText(text) |
| { |
| this._unchangedOptionElement.selected = true; |
| |
| super.specialPropertyPlaceholderElementText = text; |
| } |
| |
| parseValue(text) |
| { |
| return /^(-?[\d.]+)([^\s\d]{0,4})(?:\s*;?)$/.exec(text); |
| } |
| |
| // Private |
| |
| set _unitsElementTextContent(text) |
| { |
| if (!this._hasUnits) |
| return; |
| |
| this._unitsElement.textContent = text === this._unitlessNumberUnit ? "" : text; |
| this._markUnitsContainerIfInputHasValue(); |
| } |
| |
| _setNumberInputIsEditable(flag) |
| { |
| this._numberInputIsEditable = flag || false; |
| this.contentElement.classList.toggle("number-input-editable", this._numberInputIsEditable); |
| } |
| |
| _markUnitsContainerIfInputHasValue() |
| { |
| let numberInputValue = this._valueNumberInputElement.value; |
| this._numberUnitsContainer.classList.toggle("has-value", numberInputValue && numberInputValue.length); |
| } |
| |
| _keywordChanged() |
| { |
| let unchangedOptionSelected = this._unchangedOptionElement.selected; |
| if (!unchangedOptionSelected) { |
| let selectedKeywordIsUnit = this.valueIsSupportedUnit(this._keywordSelectElement.value); |
| if (!this._numberInputIsEditable && selectedKeywordIsUnit) |
| this._valueNumberInputElement.value = null; |
| |
| this._unitsElementTextContent = this._keywordSelectElement.value; |
| this._setNumberInputIsEditable(selectedKeywordIsUnit); |
| } else |
| this._setNumberInputIsEditable(false); |
| |
| this._valueDidChange(); |
| this.specialPropertyPlaceholderElement.hidden = !unchangedOptionSelected; |
| } |
| |
| _valueNumberInputKeyDown(event) |
| { |
| if (!this._numberInputIsEditable) |
| return; |
| |
| function adjustValue(delta) |
| { |
| let newValue; |
| let value = this.value; |
| if (!value && isNaN(value)) { |
| let placeholderValue = this.placeholder && !isNaN(this.placeholder) ? parseFloat(this.placeholder) : 0; |
| newValue = placeholderValue + delta; |
| } else |
| newValue = value + delta; |
| |
| if (!this._allowNegativeValues && newValue < 0) |
| newValue = 0; |
| |
| this._propertyMissing = false; |
| this.value = Math.round(newValue * 100) / 100; |
| this._valueDidChange(); |
| } |
| |
| let shift = 1; |
| if (event.ctrlKey) |
| shift /= 10; |
| else if (event.shiftKey) |
| shift *= 10; |
| |
| let key = event.keyIdentifier; |
| if (key.startsWith("Page")) |
| shift *= 10; |
| |
| if (key === "Up" || key === "PageUp") { |
| event.preventDefault(); |
| adjustValue.call(this, shift); |
| return; |
| } |
| |
| if (key === "Down" || key === "PageDown") { |
| event.preventDefault(); |
| adjustValue.call(this, -shift); |
| return; |
| } |
| |
| this._markUnitsContainerIfInputHasValue(); |
| this.debounce(250)._valueDidChange(); |
| } |
| |
| _valueNumberInputKeyUp(event) |
| { |
| if (!this._numberInputIsEditable) |
| return; |
| |
| this._markUnitsContainerIfInputHasValue(); |
| this._valueDidChange(); |
| } |
| |
| _keywordSelectMouseDown(event) |
| { |
| if (event.altKey) |
| this._addAdvancedUnits(); |
| else if (!this._valueIsSupportedAdvancedUnit(this._keywordSelectElement.value)) |
| this._removeAdvancedUnits(); |
| } |
| |
| _createValueOptions(values) |
| { |
| let addedElements = []; |
| for (let key in values) { |
| let option = document.createElement("option"); |
| option.value = key; |
| option.text = values[key]; |
| this._keywordSelectElement.appendChild(option); |
| addedElements.push(option); |
| } |
| |
| return addedElements; |
| } |
| |
| _createUnitOptions(units) |
| { |
| let addedElements = []; |
| for (let unit of units) { |
| let option = document.createElement("option"); |
| option.text = unit; |
| this._keywordSelectElement.appendChild(option); |
| addedElements.push(option); |
| } |
| |
| return addedElements; |
| } |
| |
| _addAdvancedUnits() |
| { |
| if (this._advancedUnitsElements) |
| return; |
| |
| this._keywordSelectElement.appendChild(document.createElement("hr")); |
| this._advancedUnitsElements = this._createUnitOptions(this._possibleUnits.advanced); |
| } |
| |
| _removeAdvancedUnits() |
| { |
| if (!this._advancedUnitsElements) |
| return; |
| |
| this._keywordSelectElement.removeChild(this._advancedUnitsElements[0].previousSibling); |
| for (let element of this._advancedUnitsElements) |
| this._keywordSelectElement.removeChild(element); |
| |
| this._advancedUnitsElements = null; |
| } |
| |
| _focusContentElement(event) |
| { |
| this.contentElement.classList.add("focused"); |
| } |
| |
| _blurContentElement(event) |
| { |
| this.contentElement.classList.remove("focused"); |
| } |
| |
| _valueNumberInputChanged(event) |
| { |
| let newValue = this.value; |
| if (!newValue && isNaN(newValue)) |
| newValue = this.placeholder && !isNaN(this.placeholder) ? parseFloat(this.placeholder) : 0; |
| |
| if (!this._allowNegativeValues && newValue < 0) |
| newValue = 0; |
| |
| this.value = Math.round(newValue * 100) / 100; |
| this._valueDidChange(); |
| } |
| |
| _toggleTabbingOfSelectableElements(disabled) |
| { |
| this._keywordSelectElement.tabIndex = disabled ? "-1" : null; |
| this._valueNumberInputElement.tabIndex = disabled ? "-1" : null; |
| } |
| }; |