UNPKG

chrome-devtools-frontend

Version:
107 lines (93 loc) 3.67 kB
// 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 i18n from '../../core/i18n/i18n.js'; import type * as Protocol from '../../generated/protocol.js'; const UIStrings = { /** * @description Summary line in a tooltip explaining a CSS selector specificity. * @example {1} PH1 * @example {2} PH2 * @example {3} PH3 */ specificity: 'Specificity: ({PH1},{PH2},{PH3})', /** * @description Tooltip line listing the selector parts contributing to the ID-like specificity bucket. * @example {#main} PH1 */ idLikeSpecificity: '(a) ID-like: {PH1}', /** * @description Tooltip line listing the selector parts contributing to the class-like specificity bucket. * @example {.active, :hover} PH1 */ classLikeSpecificity: '(b) Class-like: {PH1}', /** * @description Tooltip line listing the selector parts contributing to the type-like specificity bucket. * @example {div} PH1 */ typeLikeSpecificity: '(c) Type-like: {PH1}', } as const; const str_ = i18n.i18n.registerUIStrings('panels/elements/CSSSpecificityBreakdown.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface SpecificityBreakdown { ids: string[]; classes: string[]; types: string[]; } function formatContribution(text: string, contribution: number): string { return contribution > 1 ? `${text} x${contribution}` : text; } function formatComponentList(components: string[]): string { const formatter = new Intl.ListFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, { style: 'long', type: 'conjunction', }); return formatter.format(components); } export function getSpecificityBreakdown(specificity: Protocol.CSS.Specificity): SpecificityBreakdown { const ids: string[] = []; const classes: string[] = []; const types: string[] = []; for (const component of specificity.components ?? []) { if (component.a > 0) { ids.push(formatContribution(component.text, component.a)); } if (component.b > 0) { classes.push(formatContribution(component.text, component.b)); } if (component.c > 0) { types.push(formatContribution(component.text, component.c)); } } return {ids, classes, types}; } /** * Formats the specificity breakdown into a human-readable multi-line string * suitable for displaying in a tooltip. * * Example output for selector "div#main .active:hover": * Specificity: (1,2,1) * (a) ID-like: #main * (b) Class-like: .active, :hover * (c) Type-like: div */ export function formatSpecificitySummary(specificity: Protocol.CSS.Specificity): string { return i18nString(UIStrings.specificity, {PH1: specificity.a, PH2: specificity.b, PH3: specificity.c}); } export function getSpecificityBreakdownLines(specificity: Protocol.CSS.Specificity): string[] { const breakdown = getSpecificityBreakdown(specificity); const lines: string[] = []; if (breakdown.ids.length > 0) { lines.push(i18nString(UIStrings.idLikeSpecificity, {PH1: formatComponentList(breakdown.ids)})); } if (breakdown.classes.length > 0) { lines.push(i18nString(UIStrings.classLikeSpecificity, {PH1: formatComponentList(breakdown.classes)})); } if (breakdown.types.length > 0) { lines.push(i18nString(UIStrings.typeLikeSpecificity, {PH1: formatComponentList(breakdown.types)})); } return lines; } export function formatSpecificityTooltip(specificity: Protocol.CSS.Specificity): string { return [formatSpecificitySummary(specificity), ...getSpecificityBreakdownLines(specificity)].join('\n'); }