blob: 6988428b37e763c71a294746189231778aba8d51 [file] [log] [blame]
/*
* 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._showPanel(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));
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
_showPanel()
{
this.contentView.addSubview(this._panel);
this.contentView.element.classList.toggle("has-filter-bar", this._filterBar);
if (this._filterBar)
this.contentView.element.classList.toggle(WI.GeneralStyleDetailsSidebarPanel.FilterInProgressClassName, this._filterBar.hasActiveFilters());
}
_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()
{
this.needsLayout();
}
};
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";