/* * Copyright (C) 2013, 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. */ WI.GeneralStyleDetailsSidebarPanel = class GeneralStyleDetailsSidebarPanel extends WI.DOMDetailsSidebarPanel { constructor(identifier, displayName, panelConstructor) { super(identifier, displayName); this.element.classList.add("css-style"); console.assert(panelConstructor.prototype instanceof WI.StyleDetailsPanel); this._panel = new panelConstructor(this); this._panel.addEventListener(WI.StyleDetailsPanel.Event.NodeChanged, this._handleNodeChanged, this); if (this._panel.supportsToggleCSSClassList && InspectorBackend.hasCommand("DOM.resolveNode")) this._classListContainerToggledSetting = new WI.Setting(identifier + "-class-list-container-toggled", !!WI.Setting.migrateValue("class-list-container-toggled")); if (this._panel.supportsToggleCSSForcedPseudoClass && WI.cssManager.canForcePseudoClass()) { this._forcedPseudoClassContainerToggledSetting = new WI.Setting(identifier + "-forced-pseudo-class-container-toggled", this._panel.initialToggleCSSForcedPseudoClassState); this._checkboxForForcedPseudoClass = new Map; } } // Public get panel() { return this._panel; } get minimumWidth() { return Math.max(super.minimumWidth, this._panel.minimumWidth || 0); } supportsDOMNode(nodeToInspect) { return nodeToInspect.nodeType() === Node.ELEMENT_NODE; } attached() { super.attached(); if (!this._panel) return; console.assert(this.visible, `Shown panel ${this._identifier} must be visible.`); this._panel.markAsNeedsRefresh(this.domNode); } // StyleDetailsPanel delegate styleDetailsPanelFocusFilterBar(styleDetailsPanel) { if (this._filterBar) this._filterBar.inputField.focus(); } // Protected layout() { let domNode = this.domNode; if (!domNode || domNode.destroyed) return; this.contentView.element.scrollTop = 0; this._panel.markAsNeedsRefresh(domNode); if (this._forcedPseudoClassContainerToggledSetting) this._updatePseudoClassCheckboxes(); if (this._classListContainerToggledSetting) this._populateClassToggles(); } addEventListeners() { let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; if (this._forcedPseudoClassContainerToggledSetting) effectiveDOMNode.addEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this); if (this._classListContainerToggledSetting) { effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this); effectiveDOMNode.addEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this); } } removeEventListeners() { let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; if (this._forcedPseudoClassContainerToggledSetting) effectiveDOMNode.removeEventListener(WI.DOMNode.Event.EnabledPseudoClassesChanged, this._updatePseudoClassCheckboxes, this); if (this._classListContainerToggledSetting) { effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeModified, this._handleNodeAttributeModified, this); effectiveDOMNode.removeEventListener(WI.DOMNode.Event.AttributeRemoved, this._handleNodeAttributeRemoved, this); } } initialLayout() { this.contentView.addSubview(this._panel); if (this._classListContainerToggledSetting) { this._classListContainer = this.element.createChild("div", "class-list-container"); this._addClassContainer = this._classListContainer.createChild("div", "new-class"); this._addClassContainer.title = WI.UIString("Add a Class"); this._addClassContainer.addEventListener("click", this._addClassContainerClicked.bind(this)); this._addClassInput = this._addClassContainer.createChild("input", "class-name-input"); this._addClassInput.spellcheck = false; this._addClassInput.setAttribute("placeholder", WI.UIString("Add New Class")); this._addClassInput.addEventListener("keypress", this._addClassInputKeyPressed.bind(this)); this._addClassInput.addEventListener("blur", this._addClassInputBlur.bind(this)); } if (this._forcedPseudoClassContainerToggledSetting) { this._forcedPseudoClassContainer = this.element.appendChild(document.createElement("div")); this._forcedPseudoClassContainer.className = "forced-pseudo-class-container"; for (let pseudoClass of Object.values(WI.CSSManager.ForceablePseudoClass)) { if (!WI.cssManager.canForcePseudoClass(pseudoClass)) continue; let labelElement = this._forcedPseudoClassContainer.appendChild(document.createElement("label")); let checkboxElement = labelElement.appendChild(document.createElement("input")); checkboxElement.addEventListener("change", this._forcedPseudoClassCheckboxChanged.bind(this, pseudoClass)); checkboxElement.type = "checkbox"; this._checkboxForForcedPseudoClass.set(pseudoClass, checkboxElement); labelElement.append(WI.CSSManager.displayNameForForceablePseudoClass(pseudoClass)); } } let optionsContainer = this.element.createChild("div", "options-container"); let newRuleButton = optionsContainer.createChild("img", "new-rule"); newRuleButton.title = WI.UIString("Add new rule"); newRuleButton.addEventListener("click", this._newRuleButtonClicked.bind(this)); newRuleButton.addEventListener("contextmenu", this._newRuleButtonContextMenu.bind(this)); if (typeof this._panel.filterDidChange === "function") { this._filterBar = new WI.FilterBar; this._filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._filterDidChange, this); this._filterBar.inputField.addEventListener("keydown", this._handleFilterBarInputFieldKeyDown.bind(this)); this.contentView.element.classList.add("has-filter-bar"); optionsContainer.appendChild(this._filterBar.element); } if (this._classListContainerToggledSetting) { this._classListToggleButton = optionsContainer.createChild("button", "toggle class-list"); this._classListToggleButton.textContent = WI.UIString("Classes"); this._classListToggleButton.title = WI.UIString("Toggle Classes"); this._classListToggleButton.addEventListener("click", this._classListToggleButtonClicked.bind(this)); this._updateClassListContainer(); } if (this._forcedPseudoClassContainerToggledSetting) { this._forcedPseudoClassToggleButton = optionsContainer.appendChild(document.createElement("button")); this._forcedPseudoClassToggleButton.className = "toggle forced-pseudo-class"; this._forcedPseudoClassToggleButton.textContent = WI.UIString("Pseudo", "Pseudo @ Styles details sidebar panel", "Label for button that shows controls for toggling CSS pseudo-classes on the selected element."); this._forcedPseudoClassToggleButton.title = WI.UIString("Toggle Pseudo Classes"); this._forcedPseudoClassToggleButton.addEventListener("click", this._forcedPseudoClassToggleButtonClicked.bind(this)); this._updateForcedPseudoClassContainer(); } WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetAdded, this._styleSheetAddedOrRemoved, this); WI.cssManager.addEventListener(WI.CSSManager.Event.StyleSheetRemoved, this._styleSheetAddedOrRemoved, this); } sizeDidChange() { super.sizeDidChange(); if (this._panel) this._panel.sizeDidChange(); } // Private _updateClassListContainer() { let hidden = !this._classListContainerToggledSetting.value; this._classListToggleButton.classList.toggle("selected", !hidden); this._classListContainer.hidden = hidden; this._populateClassToggles(); } _updateForcedPseudoClassContainer() { let hidden = !this._forcedPseudoClassContainerToggledSetting.value; this._forcedPseudoClassToggleButton.classList.toggle("selected", !hidden); this._forcedPseudoClassContainer.hidden = hidden; } _handleNodeChanged(event) { this.contentView.element.classList.toggle("supports-new-rule", this._panel.supportsNewRule); this.contentView.element.classList.toggle("supports-toggle-class-list", this._panel.supportsToggleCSSClassList); this.contentView.element.classList.toggle("supports-toggle-forced-pseudo-class", this._panel.supportsToggleCSSForcedPseudoClass); } _forcedPseudoClassCheckboxChanged(pseudoClass, event) { if (!this.domNode) return; let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; effectiveDOMNode.setPseudoClassEnabled(pseudoClass, event.target.checked); this._checkboxForForcedPseudoClass.get(pseudoClass).focus(); } _updatePseudoClassCheckboxes() { if (!this.domNode) return; let effectiveDOMNode = this.domNode.isPseudoElement() ? this.domNode.parentNode : this.domNode; if (!effectiveDOMNode) return; let enabledPseudoClasses = effectiveDOMNode.enabledPseudoClasses; for (let [pseudoClass, checkboxElement] of this._checkboxForForcedPseudoClass) checkboxElement.checked = enabledPseudoClasses.includes(pseudoClass); } _handleNodeAttributeModified(event) { if (event && event.data && event.data.name === "class") this._populateClassToggles(); } _handleNodeAttributeRemoved(event) { if (event && event.data && event.data.name === "class") this._populateClassToggles(); } _newRuleButtonClicked() { if (this._panel && typeof this._panel.newRuleButtonClicked === "function") this._panel.newRuleButtonClicked(); } _newRuleButtonContextMenu(event) { if (this._panel && typeof this._panel.newRuleButtonContextMenu === "function") this._panel.newRuleButtonContextMenu(event); } _classListToggleButtonClicked(event) { if (this._forcedPseudoClassContainerToggledSetting) { this._forcedPseudoClassContainerToggledSetting.value = false; this._updateForcedPseudoClassContainer(); } this._classListContainerToggledSetting.value = !this._classListContainerToggledSetting.value; this._updateClassListContainer(); } _forcedPseudoClassToggleButtonClicked(event) { if (this._classListContainerToggledSetting) { this._classListContainerToggledSetting.value = false; this._updateClassListContainer(); } this._forcedPseudoClassContainerToggledSetting.value = !this._forcedPseudoClassContainerToggledSetting.value; this._updateForcedPseudoClassContainer(); } _addClassContainerClicked(event) { this._addClassContainer.classList.add("active"); this._addClassInput.focus(); } _addClassInputKeyPressed(event) { if (event.keyCode !== WI.KeyboardShortcut.Key.Enter.keyCode) return; this._addClassFromInput(); } _addClassInputBlur(event) { this._addClassFromInput(); this._addClassContainer.classList.remove("active"); } _addClassFromInput() { this.domNode.toggleClass(this._addClassInput.value, true); this._addClassInput.value = null; } _populateClassToggles() { if (!this._classListContainer || this._classListContainer.hidden) return; // Ensure that _addClassContainer is the first child of _classListContainer. while (this._classListContainer.children.length > 1) this._classListContainer.children[1].remove(); let classes = this.domNode.getAttribute("class") || []; let classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol]; if (!classToggledMap) classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol] = new Map; if (classes && classes.length) { for (let className of classes.split(/\s+/)) classToggledMap.set(className, true); } for (let [className, toggled] of classToggledMap) { if ((toggled && !classes.includes(className)) || (!toggled && classes.includes(className))) { toggled = !toggled; classToggledMap.set(className, toggled); } this._createToggleForClassName(className); } } _createToggleForClassName(className) { if (!className || !className.length) return; let classToggledMap = this.domNode[WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol]; if (!classToggledMap) return; if (!classToggledMap.has(className)) classToggledMap.set(className, true); let toggled = classToggledMap.get(className); let classNameContainer = document.createElement("div"); classNameContainer.classList.add("class-toggle"); let classNameToggle = classNameContainer.createChild("input"); classNameToggle.type = "checkbox"; classNameToggle.checked = toggled; let classNameTitle = classNameContainer.createChild("span"); classNameTitle.textContent = className; classNameTitle.draggable = true; classNameTitle.addEventListener("dragstart", (event) => { event.dataTransfer.setData(WI.GeneralStyleDetailsSidebarPanel.ToggledClassesDragType, className); event.dataTransfer.effectAllowed = "copy"; }); let classNameToggleChanged = (event) => { this.domNode.toggleClass(className, classNameToggle.checked); classToggledMap.set(className, classNameToggle.checked); }; classNameToggle.addEventListener("click", classNameToggleChanged); classNameTitle.addEventListener("click", (event) => { classNameToggle.checked = !classNameToggle.checked; classNameToggleChanged(); }); this._classListContainer.appendChild(classNameContainer); } _filterDidChange() { if (!this._filterBar) return; this.contentView.element.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName, this._filterBar.hasActiveFilters()); this._panel.filterDidChange(this._filterBar); } _handleFilterBarInputFieldKeyDown(event) { if (event.key !== "Tab" || !event.shiftKey) return; if (this._panel.focusLastSection) { this._panel.focusLastSection(); event.preventDefault(); } } _styleSheetAddedOrRemoved() { let domNode = this.domNode; if (!domNode || domNode.destroyed) return; this._panel.markAsNeedsRefresh(domNode); } }; WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName = "filter-in-progress"; WI.GeneralStyleDetailsSidebarPanel.FilterMatchingSectionHasLabelClassName = "filter-section-has-label"; WI.GeneralStyleDetailsSidebarPanel.FilterMatchSectionClassName = "filter-matching"; WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInSectionClassName = "filter-section-non-matching"; WI.GeneralStyleDetailsSidebarPanel.NoFilterMatchInPropertyClassName = "filter-property-non-matching"; WI.GeneralStyleDetailsSidebarPanel.ToggledClassesSymbol = Symbol("css-style-details-sidebar-panel-toggled-classes-symbol"); WI.GeneralStyleDetailsSidebarPanel.ToggledClassesDragType = "web-inspector/css-class";