// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import type * as SDK from '../../core/sdk/sdk.js'; import * as ComputedStyle from '../../models/computed_style/computed_style.js'; import type * as TextUtils from '../../models/text_utils/text_utils.js'; import * as InlineEditor from '../../ui/legacy/components/inline_editor/inline_editor.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import {html, render} from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import * as ElementsComponents from './components/components.js'; import {StylePropertiesSection} from './StylePropertiesSection.js'; import type {StylePropertyTreeElement} from './StylePropertyTreeElement.js'; import type {StylesContainer} from './StylesContainer.js'; import stylesSidebarPaneStyles from './stylesSidebarPane.css.js'; import {WebCustomData} from './WebCustomData.js'; interface ViewInput { sections: StylePropertiesSection[]; } type View = (input: ViewInput, output_: undefined, target: HTMLElement) => void; export const DEFAULT_VIEW: View = (input, _output, target) => { render( html`
${input.sections.map(section => section.element)}
`, target); }; export class StandaloneStylesContainer extends UI.Widget.VBox implements StylesContainer { activeCSSAngle: InlineEditor.CSSAngle.CSSAngle|null = null; isEditingStyle = false; readonly sectionByElement = new WeakMap(); // TODO: Reference the MAX_LINK_LENGTH from StylesSidebarPane at a later stage, when we have a reference to it. readonly linkifier: Components.Linkifier.Linkifier = new Components.Linkifier.Linkifier(23, /* useLinkDecorator */ true); #webCustomData: WebCustomData|undefined; #userOperation = false; #sections: StylePropertiesSection[] = []; #swatchPopoverHelper = new InlineEditor.SwatchPopoverHelper.SwatchPopoverHelper(); #computedStyleModelInternal = new ComputedStyle.ComputedStyleModel.ComputedStyleModel(); #view: View; constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) { super(element, {useShadowDom: true}); this.#view = view; } get userOperation(): boolean { return this.#userOperation; } get webCustomData(): WebCustomData|undefined { if (!this.#webCustomData && Common.Settings.Settings.instance().moduleSetting('show-css-property-documentation-on-hover').get()) { this.#webCustomData = WebCustomData.create(); } return this.#webCustomData; } async #updateSections(): Promise { const node = this.node(); if (!node) { this.#sections = []; this.requestUpdate(); return; } const cssModel = node.domModel().cssModel(); const matchedStyles = await cssModel.cachedMatchedCascadeForNode(node); const parentNodeId = matchedStyles?.getParentLayoutNodeId(); const [parentStyles, computedStyles, extraStyles] = await Promise.all([ parentNodeId ? cssModel.getComputedStyle(parentNodeId) : null, cssModel.getComputedStyle(node.id), cssModel.getComputedStyleExtraFields(node.id) ]); if (!matchedStyles) { return; } const newSections: StylePropertiesSection[] = []; let sectionIdx = 0; for (const style of matchedStyles.nodeStyles()) { const section = new StylePropertiesSection( this, matchedStyles, style, sectionIdx++, computedStyles, parentStyles, extraStyles); newSections.push(section); this.sectionByElement.set(section.element, section); } this.#sections = newSections; this.swatchPopoverHelper().reposition(); } override async performUpdate(): Promise { if (this.isEditingStyle || this.#userOperation) { return; } await this.#updateSections(); const viewInput: ViewInput = { sections: this.#sections, }; this.#view(viewInput, undefined, this.contentElement); } swatchPopoverHelper(): InlineEditor.SwatchPopoverHelper.SwatchPopoverHelper { return this.#swatchPopoverHelper; } // TODO: Refactor StylesContainer to use getter for node(), so that we can have a `node` setter here: set node(). set domNode(node: SDK.DOMModel.DOMNode|null) { if (this.#computedStyleModelInternal.node === node) { return; } this.#computedStyleModelInternal.node = node; this.requestUpdate(); } node(): SDK.DOMModel.DOMNode|null { return this.#computedStyleModelInternal.node; } cssModel(): SDK.CSSModel.CSSModel|null { return this.#computedStyleModelInternal.cssModel(); } computedStyleModel(): ComputedStyle.ComputedStyleModel.ComputedStyleModel { return this.#computedStyleModelInternal; } setActiveProperty(_treeElement: StylePropertyTreeElement|null): void { } refreshUpdate(editedSection: StylePropertiesSection, editedTreeElement?: StylePropertyTreeElement): void { if (editedTreeElement) { for (const section of this.#sections) { section.updateVarFunctions(editedTreeElement); } } if (this.isEditingStyle) { return; } for (const section of this.#sections) { section.update(section === editedSection); } this.swatchPopoverHelper().reposition(); } filterRegex(): RegExp|null { return null; } setEditingStyle(editing: boolean): void { this.isEditingStyle = editing; } setUserOperation(userOperation: boolean): void { this.#userOperation = userOperation; } forceUpdate(): void { this.hideAllPopovers(); this.requestUpdate(); } hideAllPopovers(): void { this.#swatchPopoverHelper.hide(); } allSections(): StylePropertiesSection[] { return this.#sections; } getVariablePopoverContents( matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, variableName: string, computedValue: string|null): ElementsComponents.CSSVariableValueView.CSSVariableValueView { const registration = matchedStyles.getRegisteredProperty(variableName); return new ElementsComponents.CSSVariableValueView.CSSVariableValueView({ variableName, value: computedValue ?? undefined, // TODO: provide a goToDefinition to jump to the StylesSidebarPane details: registration ? {registration, goToDefinition: () => {}} : undefined, }); } getVariableParserError(_matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles, _variableName: string): ElementsComponents.CSSVariableValueView.CSSVariableParserError|null { return null; } jumpToFunctionDefinition(_functionName: string): void { } continueEditingElement(_sectionIndex: number, _propertyIndex: number): void { } revealProperty(_cssProperty: SDK.CSSProperty.CSSProperty): void { } resetFocus(): void { const firstVisibleSection = this.#sections[0]?.findCurrentOrNextVisible(true); if (firstVisibleSection) { firstVisibleSection.element.tabIndex = this.hasFocus() ? -1 : 0; } } removeSection(_section: StylePropertiesSection): void { } focusedSectionIndex(): number { return this.#sections.findIndex(section => section.element.hasFocus()); } addBlankSection( _insertAfterSection: StylePropertiesSection, _styleSheetHeader: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader, _ruleLocation: TextUtils.TextRange.TextRange): void { } jumpToProperty(_propertyName: string, _sectionName?: string, _blockName?: string): boolean { return false; } jumpToSectionBlock(_section: string): void { } jumpToFontPaletteDefinition(_paletteName: string): void { } jumpToDeclaration(_valueSource: SDK.CSSMatchedStyles.CSSValueSource): void { } addStyleUpdateListener(_listener: () => void): void { } removeStyleUpdateListener(_listener: () => void): void { } }