From c2e9578ba69c8c4f568cb078a9f236760d5e2ef5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 3 Feb 2025 19:27:14 +0100 Subject: [PATCH 01/69] feat(client): implement basic BCD lit --- client/src/document/index.tsx | 23 +- .../browser-compatibility-table/index.tsx | 2 +- client/src/lit/compat/bcd-table.js | 702 ++++++++++++++++++ client/src/lit/compat/feature-row.ts | 77 ++ client/src/lit/compat/index-desktop-md.scss | 13 + client/src/lit/compat/index-desktop-xl.scss | 12 + client/src/lit/compat/index-desktop.scss | 99 +++ client/src/lit/compat/index-mobile.scss | 33 + client/src/lit/compat/index.scss | 453 +++++++++++ client/src/lit/compat/lazy-bcd-table.js | 104 +++ client/src/lit/compat/legend.ts | 106 +++ client/src/lit/compat/utils.ts | 206 +++++ 12 files changed, 1823 insertions(+), 7 deletions(-) create mode 100644 client/src/lit/compat/bcd-table.js create mode 100644 client/src/lit/compat/feature-row.ts create mode 100644 client/src/lit/compat/index-desktop-md.scss create mode 100644 client/src/lit/compat/index-desktop-xl.scss create mode 100644 client/src/lit/compat/index-desktop.scss create mode 100644 client/src/lit/compat/index-mobile.scss create mode 100644 client/src/lit/compat/index.scss create mode 100644 client/src/lit/compat/lazy-bcd-table.js create mode 100644 client/src/lit/compat/legend.ts create mode 100644 client/src/lit/compat/utils.ts diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx index 1a395b7abb2e..10d8fbc53edb 100644 --- a/client/src/document/index.tsx +++ b/client/src/document/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Suspense } from "react"; import { useNavigate } from "react-router-dom"; import useSWR, { mutate } from "swr"; @@ -9,7 +9,6 @@ import { useDocumentURL, useDecorateCodeExamples, useRunSample } from "./hooks"; import { Doc } from "../../../libs/types/document"; // Ingredients import { Prose } from "./ingredients/prose"; -import { LazyBrowserCompatibilityTable } from "./lazy-bcd-table"; import { SpecificationSection } from "./ingredients/spec-section"; // Misc @@ -43,9 +42,13 @@ import { BaselineIndicator } from "./baseline-indicator"; import { PlayQueue } from "../playground/queue"; import { useGleanClick } from "../telemetry/glean-context"; import { CLIENT_SIDE_NAVIGATION } from "../telemetry/constants"; +import { Spinner } from "../ui/atoms/spinner"; // import { useUIStatus } from "../ui-context"; // Lazy sub-components +const LazyBrowserCompatibilityTable = React.lazy( + () => import("../lit/compat/lazy-bcd-table") +); const Toolbar = React.lazy(() => import("./toolbar")); const MathMLPolyfillMaybe = React.lazy(() => import("./mathml-polyfill")); @@ -261,15 +264,23 @@ export function Document(props /* TODO: define a TS interface for this */) { } export function RenderDocumentBody({ doc }) { + const locale = useLocale(); + return doc.body.map((section, i) => { if (section.type === "prose") { return ; } else if (section.type === "browser_compatibility") { + const { id, title, isH3, query } = section.value; return ( - + } key={`browser_compatibility${i}`}> + + ); } else if (section.type === "specifications") { return ( diff --git a/client/src/document/ingredients/browser-compatibility-table/index.tsx b/client/src/document/ingredients/browser-compatibility-table/index.tsx index 3b7b23940c91..69ee3605a49d 100644 --- a/client/src/document/ingredients/browser-compatibility-table/index.tsx +++ b/client/src/document/ingredients/browser-compatibility-table/index.tsx @@ -46,7 +46,7 @@ export const HIDDEN_BROWSERS = ["ie"]; * shown. In all other categories, if compat data has info about Deno / Node.js * those are also shown. Deno is always shown if Node.js is shown. */ -function gatherPlatformsAndBrowsers( +export function gatherPlatformsAndBrowsers( category: string, data: BCD.Identifier, browserInfo: BCD.Browsers diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js new file mode 100644 index 000000000000..e55fead862b8 --- /dev/null +++ b/client/src/lit/compat/bcd-table.js @@ -0,0 +1,702 @@ +import { html, LitElement } from "lit"; +import { createComponent } from "@lit/react"; +import React from "react"; +import { getActiveLegendItems } from "./legend.ts"; +import { + asList, + bugURLToString, + getCurrentSupport, + hasMore, + hasNoteworthyNotes, + HIDDEN_BROWSERS, + isFullySupportedWithoutLimitation, + isNotSupportedAtAll, + isTruthy, + listFeatures, + versionIsPreview, +} from "./utils.ts"; + +import styles from "./index.scss?css" with { type: "css" }; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import { DEFAULT_LOCALE } from "../../../../libs/constants/index.js"; +import { BCD_TABLE } from "../../telemetry/constants.ts"; +import { + getSupportBrowserReleaseDate, + getSupportClassName, + labelFromString, + versionLabelFromSupport, +} from "./feature-row.ts"; + +/** + * @typedef {import("lit").TemplateResult} TemplateResult + * @typedef {import("@mdn/browser-compat-data/types").BrowserName} BrowserName + * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement + * @typedef {import("@mdn/browser-compat-data/types").SupportStatement} SupportStatement + * @typedef {import("@mdn/browser-compat-data/types").Browsers} Browsers + * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier + * @typedef {import("@mdn/browser-compat-data/types").StatusBlock} StatusBlock + * + */ + +const ISSUE_METADATA_TEMPLATE = ` + +
+MDN page report details + +* Query: \`$QUERY_ID\` +* Report started: $DATE + +
+`; + +/** + * @param {BrowserName} browser + * @returns {string} + */ +function browserToIconName(browser) { + if (browser.startsWith("firefox")) { + return "simple-firefox"; + } else if (browser === "webview_android") { + return "webview"; + } else if (browser === "webview_ios") { + return "safari"; + } else { + return browser.split("_")[0] ?? ""; + } +} + +// Also specifies the order in which the legend appears +export const LEGEND_LABELS = { + yes: "Full support", + partial: "Partial support", + preview: "In development. Supported in a pre-release version.", + no: "No support", + unknown: "Compatibility unknown", + experimental: "Experimental. Expect behavior to change in the future.", + nonstandard: "Non-standard. Check cross-browser support before using.", + deprecated: "Deprecated. Not for use in new websites.", + footnote: "See implementation notes.", + disabled: "User must explicitly enable this feature.", + altname: "Uses a non-standard name.", + prefix: "Requires a vendor prefix or different name for use.", + more: "Has more compatibility info.", +}; + +class BcdTable extends LitElement { + static properties = { + query: {}, + compat: { type: Object }, + pathname: {}, + locale: {}, + platforms: { state: true }, + browsers: { state: true }, + }; + + static styles = styles; + + constructor() { + super(); + this.query = ""; + this.compat = {}; + this.locale = ""; // TODO + this.pathname = ""; + /** @type string[] */ + this.platforms = []; + /** @type BrowserName[] */ + this.browsers = []; + } + + get breadcrumbs() { + return this.query.split("."); + } + + get data() { + // @ts-ignore + return this.compat.data; + } + + get browserInfo() { + // @ts-ignore + return this.compat.browsers; + } + + get category() { + return this.breadcrumbs[0] ?? ""; + } + + get name() { + return this.breadcrumbs.at(-1) ?? ""; + } + + connectedCallback() { + super.connectedCallback(); + this.pathname = window.location.pathname; + [this.platforms, this.browsers] = gatherPlatformsAndBrowsers( + this.category, + this.data, + this.browserInfo + ); + } + + get issueUrl() { + const url = "https://github.com/mdn/browser-compat-data/issues/new"; + const sp = new URLSearchParams(); + const metadata = ISSUE_METADATA_TEMPLATE.replace( + /\$DATE/g, + new Date().toISOString() + ) + .replace(/\$QUERY_ID/g, this.query) + .trim(); + sp.set("mdn-url", `https://developer.mozilla.org${this.pathname}`); + sp.set("metadata", metadata); + sp.set("title", `${this.query} - `); + sp.set("template", "data-problem.yml"); + return `${url}?${sp.toString()}`; + } + + renderIssueLink() { + return html` + Report problems with this compatibility data on GitHub + `; + } + + renderTable() { + return html`
+
+ + ${this.renderTableHeader()} ${this.renderTableBody()} +
+
+
`; + } + + renderTableHeader() { + return html` + ${this.renderPlatformHeaders()} ${this.renderBrowserHeaders()} + `; + } + + renderPlatformHeaders() { + return html` + + ${this.platforms.map((platform) => { + // Get the intersection of browsers in the `browsers` array and the + // `PLATFORM_BROWSERS[platform]`. + const browsersInPlatform = this.browsers.filter( + (browser) => this.browserInfo[browser].type === platform + ); + const browserCount = browsersInPlatform.length; + const cellClass = `bc-platform bc-platform-${platform}`; + const iconClass = `icon icon-${platform}`; + return html` + + ${platform} + `; + })} + `; + } + + renderBrowserHeaders() { + // + return html` + + ${this.browsers.map( + (browser) => + html` +
+ ${this.browserInfo[browser]?.name} +
+
+ ` + )} + `; + } + + renderTableBody() { + // + const { data, browsers, browserInfo, locale } = this; + const features = listFeatures(data, "", this.name); + + return html` + ${features.map((feature) => { + // + const { name, compat, depth } = feature; + + const title = compat.description + ? html`${unsafeHTML(compat.description)}` + : html`${name}`; + + let titleNode; + const titleContent = html` ${title} + ${this.renderStatusIcons(compat.status)}`; + if (compat.mdn_url && depth > 0) { + const href = compat.mdn_url.replace( + `/${DEFAULT_LOCALE}/docs`, + `/${locale}/docs` + ); + titleNode = html` ${href}`} + > + ${titleContent} + `; + } else { + titleNode = html`
+ ${titleContent} +
`; + } + + return html` + + ${titleNode} + + ${browsers.map((browserName) => { + // + const browser = browserInfo[browserName]; + const support = compat.support[browserName]; + const supportClassName = getSupportClassName(support, browser); + const notes = support && this.renderNotes(browser, support); + + const currentSupport = getCurrentSupport(support); + + const added = currentSupport?.version_added ?? null; + const lastVersion = currentSupport?.version_last ?? null; + + const browserReleaseDate = getSupportBrowserReleaseDate(support); + const timeline = false; // TODO + const showNotes = timeline; // TODO + + let status; + switch (added) { + case null: + status = { isSupported: "unknown" }; + break; + case true: + status = { isSupported: lastVersion ? "no" : "yes" }; + break; + case false: + status = { isSupported: "no" }; + break; + case "preview": + status = { isSupported: "preview" }; + break; + default: + status = { + isSupported: supportClassName, + label: versionLabelFromSupport(added, lastVersion, browser), + }; + break; + } + + let label; + let title = ""; + switch (status.isSupported) { + case "yes": + title = "Full support"; + label = status.label || "Yes"; + break; + + case "partial": + title = "Partial support"; + label = status.label || "Partial"; + break; + + case "removed-partial": + if (timeline) { + title = "Partial support"; + label = status.label || "Partial"; + } else { + title = "No support"; + label = status.label || "No"; + } + break; + + case "no": + title = "No support"; + label = status.label || "No"; + break; + + case "preview": + title = "Preview browser support"; + label = status.label || browser.preview_name; + break; + + case "unknown": + title = "Compatibility unknown; please update this."; + label = "?"; + break; + } + + const content = html`
+
+ + + ${title} + + +
+
+ ${browser.name} + + ${label} + ${browserReleaseDate && timeline + ? ` (Released ${browserReleaseDate})` + : ""} + +
+ ${support && this.renderCellIcons(support)} +
`; + + return html` + + `; + })} + `; + })} + `; + } + + /** + * @param {SupportStatement} support + */ + renderCellIcons(support) { + const supportItem = getCurrentSupport(support); + if (!supportItem) { + return null; + } + + const icons = [ + supportItem.prefix && this.renderIcon("prefix"), + hasNoteworthyNotes(supportItem) && this.renderIcon("footnote"), + supportItem.alternative_name && this.renderIcon("altname"), + supportItem.flags && this.renderIcon("disabled"), + hasMore(support) && this.renderIcon("more"), + ].filter(Boolean); + + return icons.length ? html`
${icons}
` : null; + } + + /** + * @param {keyof LEGEND_LABELS} name + * @returns {TemplateResult} + */ + renderIcon(name) { + const title = LEGEND_LABELS[name] ?? name; + + return html` + ${name} + + `; + } + + /** + * @param {StatusBlock | undefined} icons + */ + renderStatusIcons(icons) { + if (icons) { + return; + } + } + + /** + * + * @param {BrowserStatement} browser + * @param {SupportStatement} support + */ + renderNotes(browser, support) { + return asList(support) + .slice() + .reverse() + .flatMap((item, i) => { + /** + * @type {Array<{ iconName: any, label: string | TemplateResult<1>}>} + */ + const supportNotes = [ + item.version_removed && + !asList(support).some( + (otherItem) => otherItem.version_added === item.version_removed + ) + ? { + iconName: "footnote", + label: `Removed in ${labelFromString(item.version_removed, browser)} and later`, + } + : null, + item.partial_implementation + ? { + iconName: "footnote", + label: "Partial support", + } + : null, + item.prefix + ? { + iconName: "prefix", + label: `Implemented with the vendor prefix: ${item.prefix}`, + } + : null, + item.alternative_name + ? { + iconName: "altname", + label: `Alternate name: ${item.alternative_name}`, + } + : null, + item.flags + ? { + iconName: "disabled", + label: (() => { + const hasAddedVersion = + typeof item.version_added === "string"; + const hasRemovedVersion = + typeof item.version_removed === "string"; + const flags = item.flags || []; + return html` + ${hasAddedVersion && `From version ${item.version_added}`} + ${hasRemovedVersion && ( + <> + ${hasAddedVersion ? " until" : "Until"} version $ + {item.version_removed} (exclusive) + + )} + ${hasAddedVersion || hasRemovedVersion ? ": this" : "This"} + feature is behind the{" "} + ${flags.map((flag, i) => { + const valueToSet = + flag.value_to_set && + html` (needs to be set to + ${flag.value_to_set}`; + return html`${flag.name} ${flag.type === + "preference" && <> preference${valueToSet}} + ${flag.type === "runtime_flag" && ( + <> runtime flag${valueToSet} + )} + ${i < flags.length - 1 && " and the "}`; + })} + ${browser.pref_url && + flags.some((flag) => flag.type === "preference") && + ` To change preferences in ${browser.name}, visit ${browser.pref_url}.`} + `; + })(), + } + : null, + item.notes + ? (Array.isArray(item.notes) ? item.notes : [item.notes]).map( + (note) => ({ iconName: "footnote", label: note }) + ) + : null, + item.impl_url + ? (Array.isArray(item.impl_url) + ? item.impl_url + : [item.impl_url] + ).map((impl_url) => ({ + iconName: "footnote", + label: html`See + ${bugURLToString(impl_url)}.`, + })) + : null, + versionIsPreview(item.version_added, browser) + ? { + iconName: "footnote", + label: "Preview browser support", + } + : null, + // If we encounter nothing else than the required `version_added` and + // `release_date` properties, assume full support. + // EDIT 1-5-21: if item.version_added doesn't exist, assume no support. + isFullySupportedWithoutLimitation(item) && + !versionIsPreview(item.version_added, browser) + ? { + iconName: "footnote", + label: "Full support", + } + : isNotSupportedAtAll(item) + ? { + iconName: "footnote", + label: "No support", + } + : null, + ] + .flat() + .filter(isTruthy); + + const hasNotes = supportNotes.length > 0; + return ( + (i === 0 || hasNotes) && + html`
+
+ +
+ ${supportNotes.map(({ iconName, label }, i) => { + return html`
+ ${this.renderIcon(iconName)}{" "} + ${typeof label === "string" + ? html`${unsafeHTML(label)}` + : label} +
`; + })} + ${!hasNotes &&
} +
` + ); + }) + .filter(isTruthy); + } + + renderTableLegend() { + const { browserInfo } = this; + + if (!browserInfo) { + throw new Error("Missing browser info"); + } + + return html`
+

Legend

+

+ Tip: you can click/tap on a cell for more information. +

+
+ ${getActiveLegendItems(this.data, this.name, browserInfo).map( + ([key, label]) => + ["yes", "partial", "no", "unknown", "preview"].includes(key) + ? html`
+
+ + + ${label} + + +
+
${label}
+
` + : html`
+
+ +
+
${label}
+
` + )} +
+
`; + } + + render() { + return html` + ${this.renderIssueLink()} ${this.renderTable()} + ${this.renderTableLegend()} + `; + } +} + +customElements.define("bcd-table", BcdTable); + +export default createComponent({ + tagName: "bcd-table", + elementClass: BcdTable, + react: React, +}); + +/** + * Return a list of platforms and browsers that are relevant for this category & + * data. + * + * If the category is "webextensions", only those are shown. In all other cases + * at least the entirety of the "desktop" and "mobile" platforms are shown. If + * the category is JavaScript, the entirety of the "server" category is also + * shown. In all other categories, if compat data has info about Deno / Node.js + * those are also shown. Deno is always shown if Node.js is shown. + * + * @param {string} category + * @param {import("@mdn/browser-compat-data/types").Identifier} data + * @param {import("@mdn/browser-compat-data/types").Browsers} browserInfo + * @returns {[string[], BrowserName[]]} + */ +export function gatherPlatformsAndBrowsers(category, data, browserInfo) { + const hasNodeJSData = data.__compat && "nodejs" in data.__compat.support; + const hasDenoData = data.__compat && "deno" in data.__compat.support; + + let platforms = ["desktop", "mobile"]; + if (category === "javascript" || hasNodeJSData || hasDenoData) { + platforms.push("server"); + } + + /** @type BrowserName[] */ + let browsers = []; + + // Add browsers in platform order to align table cells + for (const platform of platforms) { + /** + * @type {BrowserName[]} + */ + // @ts-ignore + const platformBrowsers = Object.keys(browserInfo); + browsers.push( + ...platformBrowsers.filter( + (browser) => browserInfo[browser].type === platform + ) + ); + } + + // Filter WebExtension browsers in corresponding tables. + if (category === "webextensions") { + browsers = browsers.filter( + (browser) => browserInfo[browser].accepts_webextensions + ); + } + + // If there is no Node.js data for a category outside of "javascript", don't + // show it. It ended up in the browser list because there is data for Deno. + if (category !== "javascript" && !hasNodeJSData) { + browsers = browsers.filter((browser) => browser !== "nodejs"); + } + + // Hide Internet Explorer compatibility data + browsers = browsers.filter((browser) => !HIDDEN_BROWSERS.includes(browser)); + + return [platforms, [...browsers]]; +} diff --git a/client/src/lit/compat/feature-row.ts b/client/src/lit/compat/feature-row.ts new file mode 100644 index 000000000000..cb019c511446 --- /dev/null +++ b/client/src/lit/compat/feature-row.ts @@ -0,0 +1,77 @@ +import { + getCurrentSupport, + SupportStatementExtended, + versionIsPreview, +} from "./utils.ts"; +import type BCD from "@mdn/browser-compat-data/types"; +import { html } from "lit"; + +export function getSupportClassName( + support: SupportStatementExtended | undefined, + browser: BCD.BrowserStatement +): "no" | "yes" | "partial" | "preview" | "removed-partial" | "unknown" { + if (!support) { + return "unknown"; + } + + let { flags, version_added, version_removed, partial_implementation } = + getCurrentSupport(support)!; + + let className; + if (version_added === null) { + className = "unknown"; + } else if (versionIsPreview(version_added, browser)) { + className = "preview"; + } else if (version_added) { + className = "yes"; + if (version_removed || (flags && flags.length)) { + className = "no"; + } + } else { + className = "no"; + } + if (partial_implementation) { + className = version_removed ? "removed-partial" : "partial"; + } + + return className; +} + +export function labelFromString( + version: string | boolean | null | undefined, + browser: BCD.BrowserStatement +) { + if (typeof version !== "string") { + return "?"; + } + // Treat BCD ranges as exact versions to avoid confusion for the reader + // See https://github.com/mdn/yari/issues/3238 + if (version.startsWith("≤")) { + return version.slice(1); + } + if (version === "preview") { + return browser.preview_name; + } + return version; +} + +export function versionLabelFromSupport( + added: string | boolean | null | undefined, + removed: string | boolean | null | undefined, + browser: BCD.BrowserStatement +) { + if (typeof removed !== "string") { + return html`${labelFromString(added, browser)}`; + } + return html`${labelFromString(added, browser)} –  + ${labelFromString(removed, browser)}`; +} + +export function getSupportBrowserReleaseDate( + support: SupportStatementExtended | undefined +): string | undefined { + if (!support) { + return undefined; + } + return getCurrentSupport(support)!.release_date; +} diff --git a/client/src/lit/compat/index-desktop-md.scss b/client/src/lit/compat/index-desktop-md.scss new file mode 100644 index 000000000000..b13745a5cd4c --- /dev/null +++ b/client/src/lit/compat/index-desktop-md.scss @@ -0,0 +1,13 @@ +@use "../../ui/vars" as *; + +@media (min-width: $screen-md) { + .table-container { + width: calc(100% + 6rem); + } + + .bc-table { + tbody th { + width: 20%; + } + } +} diff --git a/client/src/lit/compat/index-desktop-xl.scss b/client/src/lit/compat/index-desktop-xl.scss new file mode 100644 index 000000000000..9b045a32b105 --- /dev/null +++ b/client/src/lit/compat/index-desktop-xl.scss @@ -0,0 +1,12 @@ +@use "../../ui/vars" as *; + +@media (min-width: $screen-xl) { + .table-container { + margin: 0; + width: 100%; + } + + .table-container-inner { + padding: 0; + } +} diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss new file mode 100644 index 000000000000..74c4260f7153 --- /dev/null +++ b/client/src/lit/compat/index-desktop.scss @@ -0,0 +1,99 @@ +@use "../../ui/vars" as *; + +// Style for desktop. + +@media (min-width: $screen-sm) { + .bc-table { + thead { + display: table-header-group; + + .bc-platforms { + th { + vertical-align: revert; + } + } + } + + td, + th { + background: inherit; + padding: 0.25rem; + width: 2rem; + } + + td.bc-support { + padding: 0; + + > button { + padding: 0.25rem; + } + } + + tr.bc-history-desktop { + display: table-row; + } + } + + .table-container { + margin: 0 -3rem; + overflow: auto; + width: 100vw; + } + + .table-container-inner { + min-width: max-content; + padding: 0 3rem; + position: relative; + + &:after { + bottom: 0; + content: ""; + height: 10px; + position: absolute; + right: 0; + width: 10px; + } + } + + .bc-support-level, + .bc-browser-name { + display: none; + } + + .bc-notes-list { + margin-left: 20%; + width: auto; + } + + .bc-support { + .bc-support-level { + display: none; + } + + &[aria-expanded="true"] { + position: relative; + + &:after { + background: var(--text-primary); + bottom: -1px; + content: ""; + height: 2px; + left: 0; + position: absolute; + width: 100%; + } + + .bc-history-mobile { + display: none; + } + } + } + + .bc-has-history { + cursor: pointer; + + &:hover { + background: var(--background-secondary); + } + } +} diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss new file mode 100644 index 000000000000..1b4cbc847060 --- /dev/null +++ b/client/src/lit/compat/index-mobile.scss @@ -0,0 +1,33 @@ +@use "../../ui/vars" as *; + +// Style for mobile. + +@media (max-width: $screen-sm - 1px) { + .bc-table { + thead { + display: none; + } + + td.bc-support { + border-left-width: 0; + display: block; + } + + .bc-feature, + .bc-support > button, + .bc-history > td { + align-content: center; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } + + .bc-history-desktop { + display: none; + } + } + + .table-container { + overflow-x: auto; + } +} diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss new file mode 100644 index 000000000000..453509755a49 --- /dev/null +++ b/client/src/lit/compat/index.scss @@ -0,0 +1,453 @@ +@use "sass:meta"; +@use "~@mdn/minimalist/sass/mixins/utils" as *; +@use "../../ui/vars" as *; + +// Style for mobile *and* desktop. + +.bc-table { + border: 1px solid var(--border-primary); + border-collapse: separate; + border-radius: var(--elem-radius); + border-spacing: 0; + margin: 1rem 0; + width: 100%; + + td, + th { + border: 1px solid var(--border-secondary); + border-width: 0 0 1px 1px; + font-weight: 500; + padding: 0; + + @media (min-width: $screen-md) { + font-size: var(--type-smaller-font-size); + padding: 0.4rem; + + code { + font-size: var(--type-smaller-font-size); + } + } + } + + th { + background: var(--background-primary); + padding: 0.4rem; + vertical-align: bottom; + } + + // these props allow us to add border-radius to the table. + // border-collapse: separate gets in the way of this + // being easy. + tbody { + tr { + height: 3rem; + + @media (min-width: $screen-md) { + &:last-child { + th, + td { + border-bottom-width: 0; + } + } + } + + th { + border-left-width: 0; + vertical-align: middle; + } + } + + .bc-support { + vertical-align: top; + + button { + cursor: pointer; + width: 100%; + } + + &.bc-supports-no > button > span { + color: var(--text-primary-red); + } + + &.bc-supports-partial > button > span { + color: var(--text-primary-yellow); + } + + &.bc-supports-preview > button > span { + color: var(--text-primary-blue); + } + + &.bc-supports-yes > button > span { + color: var(--text-primary-green); + } + } + + .bc-history { + td { + border-left-width: 0; + } + + .icon.icon-removed-partial { + // override icon + mask-image: url("../../assets/icons/partial.svg"); + } + } + } + + .bc-supports { + margin-bottom: 1rem; + + .icon-wrap { + background: var(--background-primary); + } + } + + .bc-supports.bc-supports-removed-partial { + .bcd-cell-text-copy { + color: var(--text-primary-yellow); + } + } + + .icon-wrap { + .bc-support-level { + @include visually-hidden; + } + } + + .bc-support { + > button > .icon-wrap { + display: block; + } + + .icon.icon-removed-partial { + background-color: var(--icon-critical); + // override icon + mask-image: url("../../assets/icons/no.svg"); + } + } + + .bc-support.bc-supports-removed-partial { + .bcd-cell-text-copy { + color: var(--text-primary-red); + } + } + + .bc-feature-depth-2 { + border-left-width: 8px; + } + + .bc-feature-depth-3 { + border-left-width: 16px; + } +} + +.bc-head-txt-label { + left: calc(50% - 0.5rem); + line-height: 1; + padding-top: 0.5rem; + position: relative; + text-orientation: sideways; + transform: rotate(180deg); + white-space: nowrap; + -ms-writing-mode: tb-rl; + -webkit-writing-mode: vertical-rl; + writing-mode: vertical-rl; +} + +.bc-head-icon-symbol { + margin-bottom: 0.3rem; +} + +.bc-support { + text-align: center; + vertical-align: middle; +} + +.bc-level-no { + background-color: var(--icon-critical); +} + +.bc-level-preview { + background-color: var(--icon-information); +} + +.bc-legend-items-container { + display: flex; + flex-wrap: wrap; + font-size: var(--type-smaller-font-size); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.bc-legend-tip { + font-size: var(--type-smaller-font-size); + font-style: italic; + font-variation-settings: "slnt" -10; + margin-bottom: 1rem; + margin-top: 0; +} + +.bc-legend-item { + align-items: center; + display: flex; + gap: 0.5rem; +} + +.bc-legend-item-dt { + display: flex; + + .icon { + background-color: var(--icon-primary); + } +} + +// Row with desktop / mobile icons. +.bc-platforms { + height: 2rem; + + th { + text-align: center; + } + + td { + border: none; + } +} + +// Row with browser names. +.bc-browsers { + th { + text-align: center; + } + + td { + border-width: 0 0 1px; + } +} + +.bc-notes-list { + margin: 0.5rem 0; + position: relative; + text-align: left; + width: 100%; + + &:before { + background: var(--border-primary); + content: ""; + height: calc(100% - 0.25rem); + left: 7px; + margin-top: 0.25rem; + position: absolute; + width: 2px; + z-index: -1; + } + + // complicated selector to cover the last bit of the grey line above. + .bc-notes-wrapper:last-child dd:last-child { + position: relative; + + &:before { + background: var(--background-primary); + bottom: 0; + content: ""; + height: calc(100% - 6px); + left: 7px; + position: absolute; + width: 2px; + z-index: -1; + } + } + + .bc-level-yes.icon.icon-yes { + // override icon + background-color: var(--icon-success); + mask-image: url("../../assets/icons/yes-circle.svg"); + } + + .bc-supports-dd { + .icon { + background: var(--border-primary); + border: 3px solid var(--background-primary); + border-radius: 50%; + mask-image: none; + } + } + + .bc-version-label { + display: inline; + } + + abbr { + margin-right: 4px; + } + + dd { + margin-bottom: 1rem; + padding-left: 1.5rem; + text-indent: -1.5rem; + + &:last-child { + margin-bottom: 2rem; + } + } +} + +.bc-notes-wrapper { + color: var(--text-primary); + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } +} + +dl.bc-notes-list { + dt.bc-supports { + margin-top: 1rem; + + &:first-child { + margin-top: 0; + } + } + + dd.bc-supports-dd { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } + } +} + +.offscreen, +.only-icon span { + @include visually-hidden(); +} + +.bc-table-row-header { + align-items: baseline; + display: inline-flex; + width: 100%; + + code { + overflow: hidden; + } + + .left-side, + .right-side { + overflow: hidden; + white-space: pre; + } + + /* Can only flex-shrink and not flex-grow + ie the "slider" in a sliding glass door */ + .left-side { + flex: 0 1 auto; + text-overflow: ellipsis; + } + /* Can flex-grow and not flex-shrink as + its the stationary portion */ + .right-side { + flex: 1 0 auto; + } + + .bc-icons { + display: flex; + gap: 0.5rem; + margin-top: 0.25rem; + + .icon { + background-color: var(--icon-secondary); + + &:hover { + background-color: var(--icon-primary); + } + } + } +} + +.bc-github-link { + font: var(--type-smaller-font-size); +} + +.main-page-content { + .bc-legend { + dd, + dt { + margin-bottom: 0; + margin-left: 0; + margin-top: 0; + } + } + + .bc-supports-dd { + margin: 0; + } +} + +@include meta.load-css("index-mobile"); +@include meta.load-css("index-desktop"); +@include meta.load-css("index-desktop-md"); +@include meta.load-css("index-desktop-xl"); + +.bcd-cell-text-wrapper { + display: flex; + flex-direction: row; + gap: 0.5rem; + + @media (min-width: $screen-md) { + align-items: center; + flex-direction: column; + } +} + +.bcd-timeline-cell-text-wrapper { + display: flex; + flex-direction: row; + gap: 0.5rem; +} + +.bcd-cell-text-copy { + color: var(--text-primary); + display: flex; + gap: 0.5rem; +} + +.bc-supports-yes { + .bcd-cell-text-copy { + color: var(--text-primary-green); + } +} + +.bc-supports-no { + .bcd-cell-text-copy { + color: var(--text-primary-red); + } +} + +.bc-supports-partial { + .bcd-cell-text-copy { + color: var(--text-primary-yellow); + } +} + +.bcd-cell-icons { + display: flex; + gap: 0.5rem; + + @media (min-width: $screen-md) { + display: block; + } +} + +@media (min-width: $screen-md) { + .bc-table { + td { + height: 2rem; + } + + td.bc-support > button { + padding: 0.5rem 0.25rem; + } + } +} diff --git a/client/src/lit/compat/lazy-bcd-table.js b/client/src/lit/compat/lazy-bcd-table.js new file mode 100644 index 000000000000..52ebb30209c6 --- /dev/null +++ b/client/src/lit/compat/lazy-bcd-table.js @@ -0,0 +1,104 @@ +import { LitElement, html } from "lit"; +import { createComponent } from "@lit/react"; +import React from "react"; +import { BCD_BASE_URL } from "../../env.ts"; +import "./bcd-table.js"; + +class LazyBcdTable extends LitElement { + static properties = { + _id: {}, + _title: {}, + ish3: {}, + query: {}, + locale: {}, + data: { state: true }, + error: { state: true }, + loading: { state: true }, + }; + + constructor() { + super(); + this._id = ""; + this._title = ""; + this.ish3 = ""; + this.query = ""; + this.locale = ""; + this.data = null; + this.error = null; + this.loading = false; + } + + connectedCallback() { + super.connectedCallback(); + this.loading = true; + } + + /** + * @param {Map} changedProperties + * @returns {Promise} + */ + async update(changedProperties) { + super.update(changedProperties); + if (changedProperties.has("query")) { + await this.fetchData(this.query); + } + } + + /** + * @param {string} query + * @returns {Promise} + */ + async fetchData(query) { + try { + const res = await fetch( + `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json` + ); + this.data = await res.json(); + } catch (error) { + this.error = error; + } finally { + this.loading = false; + } + } + + renderTitle() { + const { _id, _title, ish3 } = this; + + if (!_title) { + return ""; + } else if (ish3) { + return html`

${_title}

`; + } else { + return html`

${_title}

`; + } + } + + renderContent() { + if (this.loading) { + return html`

Loading...

`; + } + if (this.error) { + return html`

Error loading data

`; + } + if (!this.data) { + return html`

No compatibility data found

`; + } + return html``; + } + + render() { + return html`${this.renderTitle()} ${this.renderContent()} `; + } +} + +customElements.define("lazy-bcd-table", LazyBcdTable); + +export default createComponent({ + tagName: "lazy-bcd-table", + elementClass: LazyBcdTable, + react: React, +}); diff --git a/client/src/lit/compat/legend.ts b/client/src/lit/compat/legend.ts new file mode 100644 index 000000000000..c6b5a7951a84 --- /dev/null +++ b/client/src/lit/compat/legend.ts @@ -0,0 +1,106 @@ +import type BCD from "@mdn/browser-compat-data/types"; +import { + HIDDEN_BROWSERS, + asList, + getFirst, + hasMore, + hasNoteworthyNotes, + listFeatures, + versionIsPreview, +} from "./utils"; + +// Also specifies the order in which the legend appears +export const LEGEND_LABELS = { + yes: "Full support", + partial: "Partial support", + preview: "In development. Supported in a pre-release version.", + no: "No support", + unknown: "Compatibility unknown", + experimental: "Experimental. Expect behavior to change in the future.", + nonstandard: "Non-standard. Check cross-browser support before using.", + deprecated: "Deprecated. Not for use in new websites.", + footnote: "See implementation notes.", + disabled: "User must explicitly enable this feature.", + altname: "Uses a non-standard name.", + prefix: "Requires a vendor prefix or different name for use.", + more: "Has more compatibility info.", +}; +type LEGEND_KEY = keyof typeof LEGEND_LABELS; + +export function getActiveLegendItems( + compat: BCD.Identifier, + name: string, + browserInfo: BCD.Browsers +): Array<[LEGEND_KEY, string]> { + const legendItems = new Set(); + + for (const feature of listFeatures(compat, "", name)) { + const { status } = feature.compat; + + if (status) { + if (status.experimental) { + legendItems.add("experimental"); + } + if (status.deprecated) { + legendItems.add("deprecated"); + } + if (!status.standard_track) { + legendItems.add("nonstandard"); + } + } + + for (const [browser, browserSupport] of Object.entries( + feature.compat.support + ) as Array<[BCD.BrowserName, any]>) { + if (HIDDEN_BROWSERS.includes(browser)) { + continue; + } + if (!browserSupport) { + legendItems.add("no"); + continue; + } + const firstSupportItem = getFirst(browserSupport); + if (hasNoteworthyNotes(firstSupportItem)) { + legendItems.add("footnote"); + } + + for (const versionSupport of asList(browserSupport)) { + if (versionSupport.version_added) { + if (versionSupport.flags && versionSupport.flags.length) { + legendItems.add("no"); + } else if ( + versionIsPreview(versionSupport.version_added, browserInfo[browser]) + ) { + legendItems.add("preview"); + } else { + legendItems.add("yes"); + } + } else if (versionSupport.version_added == null) { + legendItems.add("unknown"); + } else { + legendItems.add("no"); + } + + if (versionSupport.partial_implementation) { + legendItems.add("partial"); + } + if (versionSupport.prefix) { + legendItems.add("prefix"); + } + if (versionSupport.alternative_name) { + legendItems.add("altname"); + } + if (versionSupport.flags) { + legendItems.add("disabled"); + } + } + + if (hasMore(browserSupport)) { + legendItems.add("more"); + } + } + } + return (Object.keys(LEGEND_LABELS) as LEGEND_KEY[]) + .filter((key) => legendItems.has(key)) + .map((key) => [key, LEGEND_LABELS[key]]); +} diff --git a/client/src/lit/compat/utils.ts b/client/src/lit/compat/utils.ts new file mode 100644 index 000000000000..e3e73bd351fd --- /dev/null +++ b/client/src/lit/compat/utils.ts @@ -0,0 +1,206 @@ +import type BCD from "@mdn/browser-compat-data/types"; + +export const HIDDEN_BROWSERS = ["ie"]; + +// Extended for the fields, beyond the bcd types, that are extra-added +// exclusively in Yari. +export interface SimpleSupportStatementExtended + extends BCD.SimpleSupportStatement { + // Known for some support statements where the browser *version* is known, + // as opposed to just "true" and if the version release date is known. + release_date?: string; + // The version before the version_removed if the *version* removed is known, + // as opposed to just "true". Otherwise the version_removed. + version_last?: BCD.VersionValue; +} + +export type SupportStatementExtended = + | SimpleSupportStatementExtended + | SimpleSupportStatementExtended[]; + +export function getFirst(a: T | T[]): T; +export function getFirst(a: T | T[] | undefined): T | undefined { + return Array.isArray(a) ? a[0] : a; +} + +export function asList(a: T | T[]): T[] { + return Array.isArray(a) ? a : [a]; +} + +export function isTruthy(t: T | false | undefined | null): t is T { + return Boolean(t); +} + +interface Feature { + name: string; + compat: BCD.CompatStatement; + depth: number; +} + +function findFirstCompatDepth(identifier: BCD.Identifier) { + const entries = [["", identifier]]; + + while (entries.length) { + const [path, value] = entries.shift() as [string, BCD.Identifier]; + if (value.__compat) { + // Following entries have at least this depth. + return path.split(".").length; + } + + for (const key of Object.keys(value)) { + const subpath = path ? `${path}.${key}` : key; + entries.push([subpath, value[key] as BCD.Identifier]); + } + } + + // Fallback. + return 0; +} + +export function listFeatures( + identifier: BCD.Identifier, + parentName: string = "", + rootName: string = "", + depth: number = 0, + firstCompatDepth: number = 0 +): Feature[] { + const features: Feature[] = []; + if (rootName && identifier.__compat) { + features.push({ + name: rootName, + compat: identifier.__compat, + depth, + }); + } + if (rootName) { + firstCompatDepth = findFirstCompatDepth(identifier); + } + for (const subName of Object.keys(identifier)) { + if (subName === "__compat") { + continue; + } + const subIdentifier = identifier[subName] as BCD.Identifier; + if (subIdentifier.__compat) { + features.push({ + name: parentName ? `${parentName}.${subName}` : subName, + compat: subIdentifier.__compat, + depth: depth + 1, + }); + } + if (subIdentifier.__compat || depth + 1 < firstCompatDepth) { + features.push( + ...listFeatures(subIdentifier, subName, "", depth + 1, firstCompatDepth) + ); + } + } + return features; +} + +export function hasMore(support: BCD.SupportStatement | undefined) { + return Array.isArray(support) && support.length > 1; +} + +export function versionIsPreview( + version: BCD.VersionValue | string | undefined, + browser: BCD.BrowserStatement +): boolean { + if (version === "preview") { + return true; + } + + if (browser && typeof version === "string" && browser.releases[version]) { + return ["beta", "nightly", "planned"].includes( + browser.releases[version].status + ); + } + + return false; +} + +export function hasNoteworthyNotes(support: BCD.SimpleSupportStatement) { + return ( + !!(support.notes?.length || support.impl_url?.length) && + !support.version_removed && + !support.partial_implementation + ); +} + +export function bugURLToString(url: string) { + const bugNumber = url.match( + /^https:\/\/(?:crbug\.com|webkit\.org\/b|bugzil\.la)\/([0-9]+)/i + )?.[1]; + return bugNumber ? `bug ${bugNumber}` : url; +} + +function hasLimitation(support: BCD.SimpleSupportStatement) { + return hasMajorLimitation(support) || support.notes || support.impl_url; +} + +function hasMajorLimitation(support: BCD.SimpleSupportStatement) { + return ( + support.partial_implementation || + support.alternative_name || + support.flags || + support.prefix || + support.version_removed + ); +} +export function isFullySupportedWithoutLimitation( + support: BCD.SimpleSupportStatement +) { + return support.version_added && !hasLimitation(support); +} + +export function isNotSupportedAtAll(support: BCD.SimpleSupportStatement) { + return !support.version_added && !hasLimitation(support); +} + +function isFullySupportedWithoutMajorLimitation( + support: BCD.SimpleSupportStatement +) { + return support.version_added && !hasMajorLimitation(support); +} + +// Prioritizes support items +export function getCurrentSupport( + support: SupportStatementExtended | undefined +): SimpleSupportStatementExtended | undefined { + if (!support) return undefined; + + // Full support without limitation + const noLimitationSupportItem = asList(support).find((item) => + isFullySupportedWithoutLimitation(item) + ); + if (noLimitationSupportItem) return noLimitationSupportItem; + + // Full support with only notes and version_added + const minorLimitationSupportItem = asList(support).find((item) => + isFullySupportedWithoutMajorLimitation(item) + ); + if (minorLimitationSupportItem) return minorLimitationSupportItem; + + // Full support with altname/prefix + const altnamePrefixSupportItem = asList(support).find( + (item) => !item.version_removed && (item.prefix || item.alternative_name) + ); + if (altnamePrefixSupportItem) return altnamePrefixSupportItem; + + // Partial support + const partialSupportItem = asList(support).find( + (item) => !item.version_removed && item.partial_implementation + ); + if (partialSupportItem) return partialSupportItem; + + // Support with flags only + const flagSupportItem = asList(support).find( + (item) => !item.version_removed && item.flags + ); + if (flagSupportItem) return flagSupportItem; + + // No/Inactive support + const noSupportItem = asList(support).find((item) => item.version_removed); + if (noSupportItem) return noSupportItem; + + // Default (likely never reached) + return getFirst(support); +} From ed9d21ed235aeb2ffdf4e0e418820c8c4ee319da Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 5 Feb 2025 19:14:50 +0100 Subject: [PATCH 02/69] feat(compat): implement BCD table with notes --- client/src/lit/compat/bcd-table.js | 318 +++++++++++++---------- client/src/lit/compat/index-desktop.scss | 2 +- client/src/lit/compat/index.scss | 42 +++ 3 files changed, 217 insertions(+), 145 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index e55fead862b8..80f71adef1f5 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -1,6 +1,4 @@ import { html, LitElement } from "lit"; -import { createComponent } from "@lit/react"; -import React from "react"; import { getActiveLegendItems } from "./legend.ts"; import { asList, @@ -26,6 +24,7 @@ import { labelFromString, versionLabelFromSupport, } from "./feature-row.ts"; +import { ifDefined } from "lit/directives/if-defined.js"; /** * @typedef {import("lit").TemplateResult} TemplateResult @@ -35,7 +34,6 @@ import { * @typedef {import("@mdn/browser-compat-data/types").Browsers} Browsers * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier * @typedef {import("@mdn/browser-compat-data/types").StatusBlock} StatusBlock - * */ const ISSUE_METADATA_TEMPLATE = ` @@ -65,7 +63,10 @@ function browserToIconName(browser) { } } -// Also specifies the order in which the legend appears +// Also specifies the order in which the legend +/** + * @type {Record} + */ export const LEGEND_LABELS = { yes: "Full support", partial: "Partial support", @@ -100,9 +101,9 @@ class BcdTable extends LitElement { this.compat = {}; this.locale = ""; // TODO this.pathname = ""; - /** @type string[] */ + /** @type {string[]} */ this.platforms = []; - /** @type BrowserName[] */ + /** @type {BrowserName[]} */ this.browsers = []; } @@ -151,6 +152,7 @@ class BcdTable extends LitElement { sp.set("metadata", metadata); sp.set("title", `${this.query} - `); sp.set("template", "data-problem.yml"); + return `${url}?${sp.toString()}`; } @@ -242,7 +244,7 @@ class BcdTable extends LitElement { let titleNode; const titleContent = html` ${title} - ${this.renderStatusIcons(compat.status)}`; + ${compat.status && this.renderStatusIcons(compat.status)}`; if (compat.mdn_url && depth > 0) { const href = compat.mdn_url.replace( `/${DEFAULT_LOCALE}/docs`, @@ -269,124 +271,19 @@ class BcdTable extends LitElement { // const browser = browserInfo[browserName]; const support = compat.support[browserName]; + const supportClassName = getSupportClassName(support, browser); const notes = support && this.renderNotes(browser, support); - const currentSupport = getCurrentSupport(support); - - const added = currentSupport?.version_added ?? null; - const lastVersion = currentSupport?.version_last ?? null; - - const browserReleaseDate = getSupportBrowserReleaseDate(support); - const timeline = false; // TODO - const showNotes = timeline; // TODO - - let status; - switch (added) { - case null: - status = { isSupported: "unknown" }; - break; - case true: - status = { isSupported: lastVersion ? "no" : "yes" }; - break; - case false: - status = { isSupported: "no" }; - break; - case "preview": - status = { isSupported: "preview" }; - break; - default: - status = { - isSupported: supportClassName, - label: versionLabelFromSupport(added, lastVersion, browser), - }; - break; - } - - let label; - let title = ""; - switch (status.isSupported) { - case "yes": - title = "Full support"; - label = status.label || "Yes"; - break; - - case "partial": - title = "Partial support"; - label = status.label || "Partial"; - break; - - case "removed-partial": - if (timeline) { - title = "Partial support"; - label = status.label || "Partial"; - } else { - title = "No support"; - label = status.label || "No"; - } - break; - - case "no": - title = "No support"; - label = status.label || "No"; - break; - - case "preview": - title = "Preview browser support"; - label = status.label || browser.preview_name; - break; - - case "unknown": - title = "Compatibility unknown; please update this."; - label = "?"; - break; - } - - const content = html`
-
- - - ${title} - - -
-
- ${browser.name} - - ${label} - ${browserReleaseDate && timeline - ? ` (Released ${browserReleaseDate})` - : ""} - -
- ${support && this.renderCellIcons(support)} -
`; - return html` - +
${notes}
`; })} `; @@ -411,29 +308,58 @@ class BcdTable extends LitElement { hasMore(support) && this.renderIcon("more"), ].filter(Boolean); - return icons.length ? html`
${icons}
` : null; + return icons.length ? html`
${icons}
` : null; } /** - * @param {keyof LEGEND_LABELS} name + * @param {string} name * @returns {TemplateResult} */ renderIcon(name) { - const title = LEGEND_LABELS[name] ?? name; + const title = name in LEGEND_LABELS ? LEGEND_LABELS[name] : name; - return html` + return html` ${name} - + `; } /** - * @param {StatusBlock | undefined} icons + * @param {StatusBlock} status */ - renderStatusIcons(icons) { - if (icons) { - return; - } + renderStatusIcons(status) { + // + const icons = [ + status.experimental && { + title: "Experimental. Expect behavior to change in the future.", + text: "Experimental", + iconClassName: "icon-experimental", + }, + status.deprecated && { + title: "Deprecated. Not for use in new websites.", + text: "Deprecated", + iconClassName: "icon-deprecated", + }, + !status.standard_track && { + title: "Non-standard. Expect poor cross-browser support.", + text: "Non-standard", + iconClassName: "icon-nonstandard", + }, + ].filter(isTruthy); + + return icons.length === 0 + ? null + : html`
+ ${icons.map( + (icon) => + html` + ${icon.text} + ` + )} +
`; } /** @@ -446,9 +372,6 @@ class BcdTable extends LitElement { .slice() .reverse() .flatMap((item, i) => { - /** - * @type {Array<{ iconName: any, label: string | TemplateResult<1>}>} - */ const supportNotes = [ item.version_removed && !asList(support).some( @@ -495,7 +418,7 @@ class BcdTable extends LitElement { )} ${hasAddedVersion || hasRemovedVersion ? ": this" : "This"} - feature is behind the{" "} + feature is behind the ${flags.map((flag, i) => { const valueToSet = flag.value_to_set && @@ -565,23 +488,135 @@ class BcdTable extends LitElement { browser )} bc-supports`} > - + ${this.renderCellText(item, browser, true)} - ${supportNotes.map(({ iconName, label }, i) => { - return html`
- ${this.renderIcon(iconName)}{" "} + ${supportNotes.map(({ iconName, label }) => { + return html`
+ ${this.renderIcon(iconName)} ${typeof label === "string" ? html`${unsafeHTML(label)}` : label}
`; })} - ${!hasNotes &&
} + ${!hasNotes ? html`
` : null} ` ); }) .filter(isTruthy); } + /** + * + * @param {SupportStatement | undefined} support + * @param {BrowserStatement} browser + * @param {boolean} [timeline] + */ + renderCellText(support, browser, timeline = false) { + const currentSupport = getCurrentSupport(support); + + const added = currentSupport?.version_added ?? null; + const lastVersion = currentSupport?.version_last ?? null; + + const browserReleaseDate = getSupportBrowserReleaseDate(support); + const supportClassName = getSupportClassName(support, browser); + + let status; + switch (added) { + case null: + status = { isSupported: "unknown" }; + break; + case true: + status = { isSupported: lastVersion ? "no" : "yes" }; + break; + case false: + status = { isSupported: "no" }; + break; + case "preview": + status = { isSupported: "preview" }; + break; + default: + status = { + isSupported: supportClassName, + label: versionLabelFromSupport(added, lastVersion, browser), + }; + break; + } + + let label; + let title = ""; + switch (status.isSupported) { + case "yes": + title = "Full support"; + label = status.label || "Yes"; + break; + + case "partial": + title = "Partial support"; + label = status.label || "Partial"; + break; + + case "removed-partial": + if (timeline) { + title = "Partial support"; + label = status.label || "Partial"; + } else { + title = "No support"; + label = status.label || "No"; + } + break; + + case "no": + title = "No support"; + label = status.label || "No"; + break; + + case "preview": + title = "Preview browser support"; + label = status.label || browser.preview_name; + break; + + case "unknown": + title = "Compatibility unknown; please update this."; + label = "?"; + break; + } + + return html`
+
+ + + ${title} + + +
+
+ ${browser.name} + + ${label} + ${browserReleaseDate && timeline + ? ` (Released ${browserReleaseDate})` + : ""} + +
+ ${support && this.renderCellIcons(support)} +
`; + } + renderTableLegend() { const { browserInfo } = this; @@ -635,12 +670,6 @@ class BcdTable extends LitElement { customElements.define("bcd-table", BcdTable); -export default createComponent({ - tagName: "bcd-table", - elementClass: BcdTable, - react: React, -}); - /** * Return a list of platforms and browsers that are relevant for this category & * data. @@ -677,7 +706,8 @@ export function gatherPlatformsAndBrowsers(category, data, browserInfo) { const platformBrowsers = Object.keys(browserInfo); browsers.push( ...platformBrowsers.filter( - (browser) => browserInfo[browser].type === platform + (browser) => + browser in browserInfo && browserInfo[browser].type === platform ) ); } @@ -689,7 +719,7 @@ export function gatherPlatformsAndBrowsers(category, data, browserInfo) { ); } - // If there is no Node.js data for a category outside of "javascript", don't + // If there is no Node.js data for a category outside "javascript", don't // show it. It ended up in the browser list because there is data for Deno. if (category !== "javascript" && !hasNodeJSData) { browsers = browsers.filter((browser) => browser !== "nodejs"); diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index 74c4260f7153..f948378e3aed 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -61,7 +61,7 @@ } .bc-notes-list { - margin-left: 20%; + //margin-left: 20%; width: auto; } diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 453509755a49..e896b1de8b54 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -1,6 +1,42 @@ @use "sass:meta"; @use "~@mdn/minimalist/sass/mixins/utils" as *; @use "../../ui/vars" as *; +@use "../../ui/atoms/button"; +@use "../../ui/atoms/icon"; + +// Global. + +* { + box-sizing: border-box; +} + +/* Remove default margin */ +body, +h1, +h2, +h3, +h4, +p, +figure, +blockquote, +dl, +dd { + margin: 0; +} + +.visually-hidden { + border: 0 !important; + clip: rect(1px, 1px, 1px, 1px) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + white-space: nowrap !important; + width: 1px !important; +} // Style for mobile *and* desktop. @@ -225,6 +261,12 @@ } } +.bc-support:not(:focus-within) { + .bc-notes-list { + display: none; + } +} + .bc-notes-list { margin: 0.5rem 0; position: relative; From cc0ba8ac2f885c500865a4da8aa5cb3b13d9782a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 5 Feb 2025 19:38:17 +0100 Subject: [PATCH 03/69] refactor(compat): pass down {compat,browserInfo} --- client/src/lit/compat/bcd-table.js | 20 +++++++------------- client/src/lit/compat/lazy-bcd-table.js | 11 ++++++----- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 80f71adef1f5..814218cb226f 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -86,9 +86,10 @@ export const LEGEND_LABELS = { class BcdTable extends LitElement { static properties = { query: {}, - compat: { type: Object }, - pathname: {}, locale: {}, + data: {}, + browserInfo: { attribute: "browserinfo" }, + pathname: { state: true }, platforms: { state: true }, browsers: { state: true }, }; @@ -98,7 +99,10 @@ class BcdTable extends LitElement { constructor() { super(); this.query = ""; - this.compat = {}; + this.data = {}; + /** @type {Browsers} */ + // @ts-ignore + this.browserInfo = {}; this.locale = ""; // TODO this.pathname = ""; /** @type {string[]} */ @@ -111,16 +115,6 @@ class BcdTable extends LitElement { return this.query.split("."); } - get data() { - // @ts-ignore - return this.compat.data; - } - - get browserInfo() { - // @ts-ignore - return this.compat.browsers; - } - get category() { return this.breadcrumbs[0] ?? ""; } diff --git a/client/src/lit/compat/lazy-bcd-table.js b/client/src/lit/compat/lazy-bcd-table.js index 52ebb30209c6..8f1790a23d64 100644 --- a/client/src/lit/compat/lazy-bcd-table.js +++ b/client/src/lit/compat/lazy-bcd-table.js @@ -11,7 +11,7 @@ class LazyBcdTable extends LitElement { ish3: {}, query: {}, locale: {}, - data: { state: true }, + compat: { state: true }, error: { state: true }, loading: { state: true }, }; @@ -23,7 +23,7 @@ class LazyBcdTable extends LitElement { this.ish3 = ""; this.query = ""; this.locale = ""; - this.data = null; + this.compat = null; this.error = null; this.loading = false; } @@ -53,7 +53,7 @@ class LazyBcdTable extends LitElement { const res = await fetch( `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json` ); - this.data = await res.json(); + this.compat = await res.json(); } catch (error) { this.error = error; } finally { @@ -80,13 +80,14 @@ class LazyBcdTable extends LitElement { if (this.error) { return html`

Error loading data

`; } - if (!this.data) { + if (!this.compat) { return html`

No compatibility data found

`; } return html``; } From 2fa2c32c58284c9a4793dcd62a5ff8d32b4d48cb Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 6 Feb 2025 00:05:40 +0100 Subject: [PATCH 04/69] feat(compat): use popover --- client/src/lit/compat/bcd-table.js | 10 +++++++--- client/src/lit/compat/index.scss | 6 ------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 814218cb226f..0ccb47bb7498 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -228,7 +228,7 @@ class BcdTable extends LitElement { const features = listFeatures(data, "", this.name); return html` - ${features.map((feature) => { + ${features.map((feature, featureIndex) => { // const { name, compat, depth } = feature; @@ -269,15 +269,19 @@ class BcdTable extends LitElement { const supportClassName = getSupportClassName(support, browser); const notes = support && this.renderNotes(browser, support); + const id = `${featureIndex}-${browserName}`; + return html` - -
${notes}
+
+
${notes}
+
`; })} `; diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index e896b1de8b54..3fd3db1bce36 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -261,12 +261,6 @@ dd { } } -.bc-support:not(:focus-within) { - .bc-notes-list { - display: none; - } -} - .bc-notes-list { margin: 0.5rem 0; position: relative; From aafc2a6ca503fe373811862b8a0a0b8571e31b79 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 6 Feb 2025 00:06:01 +0100 Subject: [PATCH 05/69] fix(compat): improve popover --- client/src/lit/compat/bcd-table.js | 3 ++- client/src/lit/compat/feature-row.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 0ccb47bb7498..7f7fd739fe87 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -605,7 +605,8 @@ class BcdTable extends LitElement { ? `Released ${browserReleaseDate}` : ""} > - ${label} + ${timeline ? browser.name : null} + ${!timeline || browserReleaseDate ? label : null} ${browserReleaseDate && timeline ? ` (Released ${browserReleaseDate})` : ""} diff --git a/client/src/lit/compat/feature-row.ts b/client/src/lit/compat/feature-row.ts index cb019c511446..ddd64ccb5f18 100644 --- a/client/src/lit/compat/feature-row.ts +++ b/client/src/lit/compat/feature-row.ts @@ -63,8 +63,10 @@ export function versionLabelFromSupport( if (typeof removed !== "string") { return html`${labelFromString(added, browser)}`; } - return html`${labelFromString(added, browser)} –  - ${labelFromString(removed, browser)}`; + return html`${labelFromString( + added, + browser + )} – ${labelFromString(removed, browser)}`; } export function getSupportBrowserReleaseDate( From 29e5b422c4a0d0dc906eecda17a69320da49d1cd Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 10 Mar 2025 13:51:04 +0100 Subject: [PATCH 06/69] fix(compat): replace React syntax --- client/src/lit/compat/bcd-table.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 7f7fd739fe87..171442a6eeb1 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -409,12 +409,8 @@ class BcdTable extends LitElement { const flags = item.flags || []; return html` ${hasAddedVersion && `From version ${item.version_added}`} - ${hasRemovedVersion && ( - <> - ${hasAddedVersion ? " until" : "Until"} version $ - {item.version_removed} (exclusive) - - )} + ${hasRemovedVersion && + `${hasAddedVersion ? " until" : "Until"} version ${item.version_removed} (exclusive)`} ${hasAddedVersion || hasRemovedVersion ? ": this" : "This"} feature is behind the ${flags.map((flag, i) => { @@ -423,10 +419,9 @@ class BcdTable extends LitElement { html` (needs to be set to ${flag.value_to_set}`; return html`${flag.name} ${flag.type === - "preference" && <> preference${valueToSet}} - ${flag.type === "runtime_flag" && ( - <> runtime flag${valueToSet} - )} + "preference" && ` preference${valueToSet}`} + ${flag.type === "runtime_flag" && + ` runtime flag${valueToSet}`} ${i < flags.length - 1 && " and the "}`; })} ${browser.pref_url && From 9f1ddc1c8df574b3446e2431e5deb10521f37134 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 10 Mar 2025 13:59:50 +0100 Subject: [PATCH 07/69] chore(compat): pull BCD heading out --- client/src/document/index.tsx | 5 +++-- client/src/lit/compat/lazy-bcd-table.js | 22 +--------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx index 10d8fbc53edb..49a536f62c96 100644 --- a/client/src/document/index.tsx +++ b/client/src/document/index.tsx @@ -43,6 +43,7 @@ import { PlayQueue } from "../playground/queue"; import { useGleanClick } from "../telemetry/glean-context"; import { CLIENT_SIDE_NAVIGATION } from "../telemetry/constants"; import { Spinner } from "../ui/atoms/spinner"; +import { DisplayH2, DisplayH3 } from "./ingredients/utils"; // import { useUIStatus } from "../ui-context"; // Lazy sub-components @@ -273,10 +274,10 @@ export function RenderDocumentBody({ doc }) { const { id, title, isH3, query } = section.value; return ( } key={`browser_compatibility${i}`}> + {title && !isH3 && } + {title && isH3 && } diff --git a/client/src/lit/compat/lazy-bcd-table.js b/client/src/lit/compat/lazy-bcd-table.js index 8f1790a23d64..0628fbf35ff5 100644 --- a/client/src/lit/compat/lazy-bcd-table.js +++ b/client/src/lit/compat/lazy-bcd-table.js @@ -7,8 +7,6 @@ import "./bcd-table.js"; class LazyBcdTable extends LitElement { static properties = { _id: {}, - _title: {}, - ish3: {}, query: {}, locale: {}, compat: { state: true }, @@ -19,8 +17,6 @@ class LazyBcdTable extends LitElement { constructor() { super(); this._id = ""; - this._title = ""; - this.ish3 = ""; this.query = ""; this.locale = ""; this.compat = null; @@ -61,19 +57,7 @@ class LazyBcdTable extends LitElement { } } - renderTitle() { - const { _id, _title, ish3 } = this; - - if (!_title) { - return ""; - } else if (ish3) { - return html`

${_title}

`; - } else { - return html`

${_title}

`; - } - } - - renderContent() { + render() { if (this.loading) { return html`

Loading...

`; } @@ -90,10 +74,6 @@ class LazyBcdTable extends LitElement { .browserInfo=${this.compat.browsers} >`; } - - render() { - return html`${this.renderTitle()} ${this.renderContent()} `; - } } customElements.define("lazy-bcd-table", LazyBcdTable); From 813778c9bfaf6d4834940b6551a7493ad7af5666 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 10 Mar 2025 18:33:24 +0100 Subject: [PATCH 08/69] refactor(compat): convert TS to JS with JSDoc Used GPT-o3-mini-high and refined manually. --- client/src/lit/compat/bcd-table.js | 50 ++- client/src/lit/compat/feature-row.js | 102 ++++++ client/src/lit/compat/feature-row.ts | 79 ----- .../src/lit/compat/{legend.ts => legend.js} | 50 ++- client/src/lit/compat/utils.js | 317 ++++++++++++++++++ client/src/lit/compat/utils.ts | 206 ------------ 6 files changed, 489 insertions(+), 315 deletions(-) create mode 100644 client/src/lit/compat/feature-row.js delete mode 100644 client/src/lit/compat/feature-row.ts rename client/src/lit/compat/{legend.ts => legend.js} (67%) create mode 100644 client/src/lit/compat/utils.js delete mode 100644 client/src/lit/compat/utils.ts diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 171442a6eeb1..bc42ffa81745 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -1,5 +1,5 @@ import { html, LitElement } from "lit"; -import { getActiveLegendItems } from "./legend.ts"; +import { getActiveLegendItems } from "./legend.js"; import { asList, bugURLToString, @@ -9,10 +9,9 @@ import { HIDDEN_BROWSERS, isFullySupportedWithoutLimitation, isNotSupportedAtAll, - isTruthy, listFeatures, versionIsPreview, -} from "./utils.ts"; +} from "./utils.js"; import styles from "./index.scss?css" with { type: "css" }; import { unsafeHTML } from "lit/directives/unsafe-html.js"; @@ -23,7 +22,7 @@ import { getSupportClassName, labelFromString, versionLabelFromSupport, -} from "./feature-row.ts"; +} from "./feature-row.js"; import { ifDefined } from "lit/directives/if-defined.js"; /** @@ -34,6 +33,7 @@ import { ifDefined } from "lit/directives/if-defined.js"; * @typedef {import("@mdn/browser-compat-data/types").Browsers} Browsers * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier * @typedef {import("@mdn/browser-compat-data/types").StatusBlock} StatusBlock + * @typedef {{ title: string, text: string, iconClassName: string }} StatusIcon */ const ISSUE_METADATA_TEMPLATE = ` @@ -327,23 +327,33 @@ class BcdTable extends LitElement { */ renderStatusIcons(status) { // - const icons = [ - status.experimental && { + /** + * @type {StatusIcon[]} + */ + const icons = []; + if (status.experimental) { + icons.push({ title: "Experimental. Expect behavior to change in the future.", text: "Experimental", iconClassName: "icon-experimental", - }, - status.deprecated && { + }); + } + + if (status.deprecated) { + icons.push({ title: "Deprecated. Not for use in new websites.", text: "Deprecated", iconClassName: "icon-deprecated", - }, - !status.standard_track && { + }); + } + + if (!status.standard_track) { + icons.push({ title: "Non-standard. Expect poor cross-browser support.", text: "Non-standard", iconClassName: "icon-nonstandard", - }, - ].filter(isTruthy); + }); + } return icons.length === 0 ? null @@ -370,6 +380,9 @@ class BcdTable extends LitElement { .slice() .reverse() .flatMap((item, i) => { + /** + * @type {Array<{iconName: string; label: string | TemplateResult } | null>} + */ const supportNotes = [ item.version_removed && !asList(support).some( @@ -467,9 +480,12 @@ class BcdTable extends LitElement { label: "No support", } : null, - ] - .flat() - .filter(isTruthy); + ].flat(); + + /** + * @type {Array<{iconName: string; label: string | TemplateResult }>} + */ + const filteredSupportNotes = supportNotes.filter((v) => v !== null); const hasNotes = supportNotes.length > 0; return ( @@ -483,7 +499,7 @@ class BcdTable extends LitElement { > ${this.renderCellText(item, browser, true)} - ${supportNotes.map(({ iconName, label }) => { + ${filteredSupportNotes.map(({ iconName, label }) => { return html`
${this.renderIcon(iconName)} ${typeof label === "string" @@ -495,7 +511,7 @@ class BcdTable extends LitElement { ` ); }) - .filter(isTruthy); + .filter(Boolean); } /** diff --git a/client/src/lit/compat/feature-row.js b/client/src/lit/compat/feature-row.js new file mode 100644 index 000000000000..1127c60ec246 --- /dev/null +++ b/client/src/lit/compat/feature-row.js @@ -0,0 +1,102 @@ +import { getCurrentSupport, versionIsPreview } from "./utils.js"; +import { html } from "lit"; + +/** + * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement + * @typedef {import("./utils.js").SupportStatementExtended} SupportStatementExtended + * @typedef {"no"|"yes"|"partial"|"preview"|"removed-partial"|"unknown"} SupportClassName + */ + +/** + * Returns a CSS class name based on support data and the browser. + * + * @param {SupportStatementExtended|undefined} support - The extended support statement. + * @param {BrowserStatement} browser - The browser statement. + * @returns {SupportClassName} + */ +export function getSupportClassName(support, browser) { + if (!support) { + return "unknown"; + } + + const currentSupport = getCurrentSupport(support); + if (!currentSupport) { + return "unknown"; + } + const { flags, version_added, version_removed, partial_implementation } = + currentSupport; + + /** @type {SupportClassName} */ + let className; + if (version_added === null) { + className = "unknown"; + } else if (versionIsPreview(version_added, browser)) { + className = "preview"; + } else if (version_added) { + className = "yes"; + if (version_removed || (flags && flags.length)) { + className = "no"; + } + } else { + className = "no"; + } + if (partial_implementation) { + className = version_removed ? "removed-partial" : "partial"; + } + + return className; +} + +/** + * Returns a label string derived from a version value. + * + * @param {string|boolean|null|undefined} version - The version value. + * @param {BrowserStatement} browser - The browser statement. + * @returns {string} The resulting label. + */ +export function labelFromString(version, browser) { + if (typeof version !== "string") { + return "?"; + } + // Treat BCD ranges as exact versions to avoid confusion for the reader + // See https://github.com/mdn/yari/issues/3238 + if (version.startsWith("≤")) { + return version.slice(1); + } + if (version === "preview") { + return browser.preview_name ?? "Preview"; + } + return version; +} + +/** + * Generates a version label from added and removed support data. + * + * @param {string|boolean|null|undefined} added - The added version. + * @param {string|boolean|null|undefined} removed - The removed version. + * @param {BrowserStatement} browser - The browser statement. + * @returns {import("lit").TemplateResult} A lit-html template result representing the version label. + */ +export function versionLabelFromSupport(added, removed, browser) { + if (typeof removed !== "string") { + return html`${labelFromString(added, browser)}`; + } + return html`${labelFromString( + added, + browser + )} – ${labelFromString(removed, browser)}`; +} + +/** + * Retrieves the browser release date from a support statement. + * + * @param {SupportStatementExtended|undefined} support - The extended support statement. + * @returns {string|undefined} The release date if available. + */ +export function getSupportBrowserReleaseDate(support) { + if (!support) { + return undefined; + } + + return getCurrentSupport(support)?.release_date; +} diff --git a/client/src/lit/compat/feature-row.ts b/client/src/lit/compat/feature-row.ts deleted file mode 100644 index ddd64ccb5f18..000000000000 --- a/client/src/lit/compat/feature-row.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - getCurrentSupport, - SupportStatementExtended, - versionIsPreview, -} from "./utils.ts"; -import type BCD from "@mdn/browser-compat-data/types"; -import { html } from "lit"; - -export function getSupportClassName( - support: SupportStatementExtended | undefined, - browser: BCD.BrowserStatement -): "no" | "yes" | "partial" | "preview" | "removed-partial" | "unknown" { - if (!support) { - return "unknown"; - } - - let { flags, version_added, version_removed, partial_implementation } = - getCurrentSupport(support)!; - - let className; - if (version_added === null) { - className = "unknown"; - } else if (versionIsPreview(version_added, browser)) { - className = "preview"; - } else if (version_added) { - className = "yes"; - if (version_removed || (flags && flags.length)) { - className = "no"; - } - } else { - className = "no"; - } - if (partial_implementation) { - className = version_removed ? "removed-partial" : "partial"; - } - - return className; -} - -export function labelFromString( - version: string | boolean | null | undefined, - browser: BCD.BrowserStatement -) { - if (typeof version !== "string") { - return "?"; - } - // Treat BCD ranges as exact versions to avoid confusion for the reader - // See https://github.com/mdn/yari/issues/3238 - if (version.startsWith("≤")) { - return version.slice(1); - } - if (version === "preview") { - return browser.preview_name; - } - return version; -} - -export function versionLabelFromSupport( - added: string | boolean | null | undefined, - removed: string | boolean | null | undefined, - browser: BCD.BrowserStatement -) { - if (typeof removed !== "string") { - return html`${labelFromString(added, browser)}`; - } - return html`${labelFromString( - added, - browser - )} – ${labelFromString(removed, browser)}`; -} - -export function getSupportBrowserReleaseDate( - support: SupportStatementExtended | undefined -): string | undefined { - if (!support) { - return undefined; - } - return getCurrentSupport(support)!.release_date; -} diff --git a/client/src/lit/compat/legend.ts b/client/src/lit/compat/legend.js similarity index 67% rename from client/src/lit/compat/legend.ts rename to client/src/lit/compat/legend.js index c6b5a7951a84..8f377c45698e 100644 --- a/client/src/lit/compat/legend.ts +++ b/client/src/lit/compat/legend.js @@ -1,4 +1,3 @@ -import type BCD from "@mdn/browser-compat-data/types"; import { HIDDEN_BROWSERS, asList, @@ -7,9 +6,19 @@ import { hasNoteworthyNotes, listFeatures, versionIsPreview, -} from "./utils"; +} from "./utils.js"; -// Also specifies the order in which the legend appears +/** + * @typedef {import("@mdn/browser-compat-data").Identifier} Identifier + * @typedef {import("@mdn/browser-compat-data").Browsers} Browsers + * @typedef {import("@mdn/browser-compat-data").BrowserName} BrowserName + * @typedef {"yes" | "partial" | "preview" | "no" | "unknown" | "experimental" | "nonstandard" | "deprecated" | "footnote" | "disabled" | "altname" | "prefix" | "more"} LegendKey + */ + +/** + * Legend labels which also specifies the order in which the legend appears. + * @type {Record} + */ export const LEGEND_LABELS = { yes: "Full support", partial: "Partial support", @@ -25,14 +34,18 @@ export const LEGEND_LABELS = { prefix: "Requires a vendor prefix or different name for use.", more: "Has more compatibility info.", }; -type LEGEND_KEY = keyof typeof LEGEND_LABELS; -export function getActiveLegendItems( - compat: BCD.Identifier, - name: string, - browserInfo: BCD.Browsers -): Array<[LEGEND_KEY, string]> { - const legendItems = new Set(); +/** + * Gets the active legend items based on browser compatibility data. + * + * @param {Identifier} compat - The compatibility data identifier. + * @param {string} name - The name of the feature. + * @param {Browsers} browserInfo - Information about browsers. + * @returns {Array<[LegendKey, string]>} An array of legend item entries, where each entry is a tuple of the legend key and its label. + */ +export function getActiveLegendItems(compat, name, browserInfo) { + /** @type {Set} */ + const legendItems = new Set(); for (const feature of listFeatures(compat, "", name)) { const { status } = feature.compat; @@ -51,7 +64,7 @@ export function getActiveLegendItems( for (const [browser, browserSupport] of Object.entries( feature.compat.support - ) as Array<[BCD.BrowserName, any]>) { + )) { if (HIDDEN_BROWSERS.includes(browser)) { continue; } @@ -100,7 +113,18 @@ export function getActiveLegendItems( } } } - return (Object.keys(LEGEND_LABELS) as LEGEND_KEY[]) + + /** + * @type {any[]} + */ + const keys = Object.keys(LEGEND_LABELS); + + return keys .filter((key) => legendItems.has(key)) - .map((key) => [key, LEGEND_LABELS[key]]); + .map( + /** + * @param {LegendKey} key + */ + (key) => [key, LEGEND_LABELS[key]] + ); } diff --git a/client/src/lit/compat/utils.js b/client/src/lit/compat/utils.js new file mode 100644 index 000000000000..75948fe293c7 --- /dev/null +++ b/client/src/lit/compat/utils.js @@ -0,0 +1,317 @@ +/** + * @typedef {import("@mdn/browser-compat-data/types").SimpleSupportStatement} SimpleSupportStatement + * @typedef {import("@mdn/browser-compat-data/types").VersionValue} VersionValue + * @typedef {import("@mdn/browser-compat-data/types").CompatStatement} CompatStatement + * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier + * @typedef {import("@mdn/browser-compat-data/types").SupportStatement} SupportStatement + * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement + * @typedef {SimpleSupportStatementExtended | SimpleSupportStatementExtended[]} SupportStatementExtended + */ + +/** + * Extended for the fields, beyond the bcd types, that are extra-added exclusively in Yari. + * + * Inherits all properties from SimpleSupportStatement. + * + * @typedef {SimpleSupportStatement} SimpleSupportStatementExtended + * @property {string} [release_date] - Known for some support statements where the browser version is known. + * @property {VersionValue} [version_last] - The version before the version_removed if the version removed is known. + */ + +/** + */ + +/** + * @typedef {Object} Feature + * @property {string} name + * @property {CompatStatement} compat + * @property {number} depth + */ + +/** + * A list of browsers to be hidden. + * @constant {string[]} + */ +export const HIDDEN_BROWSERS = ["ie"]; + +/** + * Gets the first element of an array or returns the value itself. + * + * @template T + * @param {T | [T, ...any[]]} a + * @returns {T} + */ +export function getFirst(a) { + return Array.isArray(a) ? a[0] : a; +} + +/** + * Ensures the input is returned as an array. + * + * @template T + * @param {T | T[]} a + * @returns {T[]} + */ +export function asList(a) { + return Array.isArray(a) ? a : [a]; +} + +/** + * Finds the first compatibility depth in a BCD Identifier. + * + * @param {Identifier} identifier + * @returns {number} + */ +function findFirstCompatDepth(identifier) { + /** @type {Array<[string, Identifier]>} */ + const entries = [["", identifier]]; + + do { + const entry = entries.shift(); + if (!entry) { + break; + } + const [path, value] = entry; + if (value.__compat) { + // The depth is the number of segments in the path. + return path.split(".").length; + } + + for (const [key, subvalue] of Object.entries(value)) { + const subpath = path ? `${path}.${key}` : key; + if ("__compat" in subvalue) { + entries.push([subpath, subvalue]); + } + } + } while (entries.length); + + // Fallback. + return 0; +} + +/** + * Recursively lists features from a BCD Identifier. + * + * @param {Identifier} identifier + * @param {string} [parentName=""] + * @param {string} [rootName=""] + * @param {number} [depth=0] + * @param {number} [firstCompatDepth=0] + * @returns {Feature[]} + */ +export function listFeatures( + identifier, + parentName = "", + rootName = "", + depth = 0, + firstCompatDepth = 0 +) { + /** @type {Feature[]} */ + const features = []; + + if (rootName && identifier.__compat) { + features.push({ + name: rootName, + compat: identifier.__compat, + depth, + }); + } + + if (rootName) { + firstCompatDepth = findFirstCompatDepth(identifier); + } + + for (const [subName, subIdentifier] of Object.entries(identifier)) { + if (subName === "__compat") { + continue; + } + + if ("__compat" in subIdentifier && subIdentifier.__compat) { + features.push({ + name: parentName ? `${parentName}.${subName}` : subName, + compat: subIdentifier.__compat, + depth: depth + 1, + }); + } + + if ("__compat" in subIdentifier /* || depth + 1 < firstCompatDepth*/) { + features.push( + ...listFeatures(subIdentifier, subName, "", depth + 1, firstCompatDepth) + ); + } + } + return features; +} + +/** + * Checks if the support statement is an array with more than one item. + * + * @param {SupportStatement | undefined} support + * @returns {boolean} + */ +export function hasMore(support) { + return Array.isArray(support) && support.length > 1; +} + +/** + * Determines if a version is a preview version. + * + * @param {string | VersionValue | undefined} version + * @param {BrowserStatement} browser + * @returns {boolean} + */ +export function versionIsPreview(version, browser) { + if (version === "preview") { + return true; + } + + if (browser && typeof version === "string" && browser.releases[version]) { + return ["beta", "nightly", "planned"].includes( + browser.releases[version].status + ); + } + + return false; +} + +/** + * Checks if the support statement has noteworthy notes. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +export function hasNoteworthyNotes(support) { + return ( + !!( + (support.notes && support.notes.length) || + (support.impl_url && support.impl_url.length) + ) && + !support.version_removed && + !support.partial_implementation + ); +} + +/** + * Converts a bug URL to a simplified string. + * + * @param {string} url + * @returns {string} + */ +export function bugURLToString(url) { + const match = url.match( + /^https:\/\/(?:crbug\.com|webkit\.org\/b|bugzil\.la)\/([0-9]+)/i + ); + const bugNumber = match ? match[1] : null; + return bugNumber ? `bug ${bugNumber}` : url; +} + +/** + * Checks if a support statement has any limitation. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +function hasLimitation(support) { + return hasMajorLimitation(support) || !!support.notes || !!support.impl_url; +} + +/** + * Checks if a support statement has major limitations. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +function hasMajorLimitation(support) { + return ( + support.partial_implementation || + !!support.alternative_name || + !!support.flags || + !!support.prefix || + !!support.version_removed + ); +} + +/** + * Checks if a support statement is fully supported without any limitation. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +export function isFullySupportedWithoutLimitation(support) { + return !!support.version_added && !hasLimitation(support); +} + +/** + * Checks if a support statement is not supported at all. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +export function isNotSupportedAtAll(support) { + return !support.version_added && !hasLimitation(support); +} + +/** + * Checks if a support statement is fully supported without major limitations. + * + * @param {SimpleSupportStatement} support + * @returns {boolean} + */ +function isFullySupportedWithoutMajorLimitation(support) { + return !!support.version_added && !hasMajorLimitation(support); +} + +/** + * Gets the current support statement from a support statement extended. + * + * Prioritizes support items in the following order: + * 1. Full support without limitation. + * 2. Full support with only notes and version_added. + * 3. Full support with alternative name or prefix. + * 4. Partial support. + * 5. Support with flags only. + * 6. No/Inactive support. + * + * @param {SupportStatementExtended | undefined} support + * @returns {SimpleSupportStatementExtended | undefined} + */ +export function getCurrentSupport(support) { + if (!support) return undefined; + + // Full support without limitation. + const noLimitationSupportItem = asList(support).find((item) => + isFullySupportedWithoutLimitation(item) + ); + if (noLimitationSupportItem) return noLimitationSupportItem; + + // Full support with only notes and version_added. + const minorLimitationSupportItem = asList(support).find((item) => + isFullySupportedWithoutMajorLimitation(item) + ); + if (minorLimitationSupportItem) return minorLimitationSupportItem; + + // Full support with alternative name/prefix. + const altnamePrefixSupportItem = asList(support).find( + (item) => !item.version_removed && (item.prefix || item.alternative_name) + ); + if (altnamePrefixSupportItem) return altnamePrefixSupportItem; + + // Partial support. + const partialSupportItem = asList(support).find( + (item) => !item.version_removed && item.partial_implementation + ); + if (partialSupportItem) return partialSupportItem; + + // Support with flags only. + const flagSupportItem = asList(support).find( + (item) => !item.version_removed && item.flags + ); + if (flagSupportItem) return flagSupportItem; + + // No/Inactive support. + const noSupportItem = asList(support).find((item) => item.version_removed); + if (noSupportItem) return noSupportItem; + + // Default (likely never reached). + return getFirst(support); +} diff --git a/client/src/lit/compat/utils.ts b/client/src/lit/compat/utils.ts deleted file mode 100644 index e3e73bd351fd..000000000000 --- a/client/src/lit/compat/utils.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type BCD from "@mdn/browser-compat-data/types"; - -export const HIDDEN_BROWSERS = ["ie"]; - -// Extended for the fields, beyond the bcd types, that are extra-added -// exclusively in Yari. -export interface SimpleSupportStatementExtended - extends BCD.SimpleSupportStatement { - // Known for some support statements where the browser *version* is known, - // as opposed to just "true" and if the version release date is known. - release_date?: string; - // The version before the version_removed if the *version* removed is known, - // as opposed to just "true". Otherwise the version_removed. - version_last?: BCD.VersionValue; -} - -export type SupportStatementExtended = - | SimpleSupportStatementExtended - | SimpleSupportStatementExtended[]; - -export function getFirst(a: T | T[]): T; -export function getFirst(a: T | T[] | undefined): T | undefined { - return Array.isArray(a) ? a[0] : a; -} - -export function asList(a: T | T[]): T[] { - return Array.isArray(a) ? a : [a]; -} - -export function isTruthy(t: T | false | undefined | null): t is T { - return Boolean(t); -} - -interface Feature { - name: string; - compat: BCD.CompatStatement; - depth: number; -} - -function findFirstCompatDepth(identifier: BCD.Identifier) { - const entries = [["", identifier]]; - - while (entries.length) { - const [path, value] = entries.shift() as [string, BCD.Identifier]; - if (value.__compat) { - // Following entries have at least this depth. - return path.split(".").length; - } - - for (const key of Object.keys(value)) { - const subpath = path ? `${path}.${key}` : key; - entries.push([subpath, value[key] as BCD.Identifier]); - } - } - - // Fallback. - return 0; -} - -export function listFeatures( - identifier: BCD.Identifier, - parentName: string = "", - rootName: string = "", - depth: number = 0, - firstCompatDepth: number = 0 -): Feature[] { - const features: Feature[] = []; - if (rootName && identifier.__compat) { - features.push({ - name: rootName, - compat: identifier.__compat, - depth, - }); - } - if (rootName) { - firstCompatDepth = findFirstCompatDepth(identifier); - } - for (const subName of Object.keys(identifier)) { - if (subName === "__compat") { - continue; - } - const subIdentifier = identifier[subName] as BCD.Identifier; - if (subIdentifier.__compat) { - features.push({ - name: parentName ? `${parentName}.${subName}` : subName, - compat: subIdentifier.__compat, - depth: depth + 1, - }); - } - if (subIdentifier.__compat || depth + 1 < firstCompatDepth) { - features.push( - ...listFeatures(subIdentifier, subName, "", depth + 1, firstCompatDepth) - ); - } - } - return features; -} - -export function hasMore(support: BCD.SupportStatement | undefined) { - return Array.isArray(support) && support.length > 1; -} - -export function versionIsPreview( - version: BCD.VersionValue | string | undefined, - browser: BCD.BrowserStatement -): boolean { - if (version === "preview") { - return true; - } - - if (browser && typeof version === "string" && browser.releases[version]) { - return ["beta", "nightly", "planned"].includes( - browser.releases[version].status - ); - } - - return false; -} - -export function hasNoteworthyNotes(support: BCD.SimpleSupportStatement) { - return ( - !!(support.notes?.length || support.impl_url?.length) && - !support.version_removed && - !support.partial_implementation - ); -} - -export function bugURLToString(url: string) { - const bugNumber = url.match( - /^https:\/\/(?:crbug\.com|webkit\.org\/b|bugzil\.la)\/([0-9]+)/i - )?.[1]; - return bugNumber ? `bug ${bugNumber}` : url; -} - -function hasLimitation(support: BCD.SimpleSupportStatement) { - return hasMajorLimitation(support) || support.notes || support.impl_url; -} - -function hasMajorLimitation(support: BCD.SimpleSupportStatement) { - return ( - support.partial_implementation || - support.alternative_name || - support.flags || - support.prefix || - support.version_removed - ); -} -export function isFullySupportedWithoutLimitation( - support: BCD.SimpleSupportStatement -) { - return support.version_added && !hasLimitation(support); -} - -export function isNotSupportedAtAll(support: BCD.SimpleSupportStatement) { - return !support.version_added && !hasLimitation(support); -} - -function isFullySupportedWithoutMajorLimitation( - support: BCD.SimpleSupportStatement -) { - return support.version_added && !hasMajorLimitation(support); -} - -// Prioritizes support items -export function getCurrentSupport( - support: SupportStatementExtended | undefined -): SimpleSupportStatementExtended | undefined { - if (!support) return undefined; - - // Full support without limitation - const noLimitationSupportItem = asList(support).find((item) => - isFullySupportedWithoutLimitation(item) - ); - if (noLimitationSupportItem) return noLimitationSupportItem; - - // Full support with only notes and version_added - const minorLimitationSupportItem = asList(support).find((item) => - isFullySupportedWithoutMajorLimitation(item) - ); - if (minorLimitationSupportItem) return minorLimitationSupportItem; - - // Full support with altname/prefix - const altnamePrefixSupportItem = asList(support).find( - (item) => !item.version_removed && (item.prefix || item.alternative_name) - ); - if (altnamePrefixSupportItem) return altnamePrefixSupportItem; - - // Partial support - const partialSupportItem = asList(support).find( - (item) => !item.version_removed && item.partial_implementation - ); - if (partialSupportItem) return partialSupportItem; - - // Support with flags only - const flagSupportItem = asList(support).find( - (item) => !item.version_removed && item.flags - ); - if (flagSupportItem) return flagSupportItem; - - // No/Inactive support - const noSupportItem = asList(support).find((item) => item.version_removed); - if (noSupportItem) return noSupportItem; - - // Default (likely never reached) - return getFirst(support); -} From 77a3e7c47bef48ee25a9a7c314527cfd3688d785 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 15:24:40 +0100 Subject: [PATCH 09/69] fix(compat): refine look&feel --- client/src/lit/compat/bcd-table.js | 36 ++++++--- client/src/lit/compat/global.scss | 37 +++++++++ client/src/lit/compat/grid.scss | 98 ++++++++++++++++++++++++ client/src/lit/compat/index-desktop.scss | 11 ++- client/src/lit/compat/index.scss | 66 +++++----------- 5 files changed, 182 insertions(+), 66 deletions(-) create mode 100644 client/src/lit/compat/global.scss create mode 100644 client/src/lit/compat/grid.scss diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index bc42ffa81745..46bf166abaac 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -165,7 +165,10 @@ class BcdTable extends LitElement { renderTable() { return html`
- +
${this.renderTableHeader()} ${this.renderTableBody()}
@@ -179,21 +182,31 @@ class BcdTable extends LitElement { } renderPlatformHeaders() { + const platformsWithBrowsers = this.platforms.map((platform) => ({ + platform, + browsers: this.browsers.filter( + (browser) => this.browserInfo[browser].type === platform + ), + })); + + const grid = platformsWithBrowsers.map(({ browsers }) => browsers.length); return html` - ${this.platforms.map((platform) => { + ${platformsWithBrowsers.map(({ platform, browsers }, index) => { // Get the intersection of browsers in the `browsers` array and the // `PLATFORM_BROWSERS[platform]`. - const browsersInPlatform = this.browsers.filter( - (browser) => this.browserInfo[browser].type === platform - ); - const browserCount = browsersInPlatform.length; + const browserCount = browsers.length; const cellClass = `bc-platform bc-platform-${platform}`; const iconClass = `icon icon-${platform}`; + + const columnStart = + 2 + grid.slice(0, index).reduce((acc, x) => acc + x, 0); + const columnEnd = columnStart + browserCount; return html` ${platform} @@ -228,7 +241,7 @@ class BcdTable extends LitElement { const features = listFeatures(data, "", this.name); return html` - ${features.map((feature, featureIndex) => { + ${features.map((feature) => { // const { name, compat, depth } = feature; @@ -269,19 +282,18 @@ class BcdTable extends LitElement { const supportClassName = getSupportClassName(support, browser); const notes = support && this.renderNotes(browser, support); - const id = `${featureIndex}-${browserName}`; - return html` - -
+ ${notes && + html`
${notes}
-
+
`} `; })} `; diff --git a/client/src/lit/compat/global.scss b/client/src/lit/compat/global.scss new file mode 100644 index 000000000000..eba29705770d --- /dev/null +++ b/client/src/lit/compat/global.scss @@ -0,0 +1,37 @@ +* { + box-sizing: border-box; +} + +/* Remove default margin */ +body, +h1, +h2, +h3, +h4, +p, +figure, +blockquote, +dl, +dd { + margin: 0; +} + +.visually-hidden { + border: 0 !important; + clip: rect(1px, 1px, 1px, 1px) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + white-space: nowrap !important; + width: 1px !important; +} + +code { + background: var(--code-background-inline); + padding: 0.125rem 0.25rem; + width: fit-content; +} diff --git a/client/src/lit/compat/grid.scss b/client/src/lit/compat/grid.scss new file mode 100644 index 000000000000..7679c90ebfca --- /dev/null +++ b/client/src/lit/compat/grid.scss @@ -0,0 +1,98 @@ +.more { + display: none; +} + +.bc-has-history:focus-within { + > button { + --padding-bottom-offset: -2px; + border-bottom: 2px solid var(--text-primary); + + ~ .more { + border-left: none; + border-top: var(--border); + display: initial; + } + } +} + +table { + display: grid; + grid-auto-flow: row dense; + grid-template-columns: minmax(1fr, 2rem) repeat(auto-fit, minmax(1fr, 2rem)); + width: 100%; + + thead { + display: contents; + line-height: 1; + text-orientation: sideways; + white-space: nowrap; + writing-mode: sideways-lr; + + tr { + display: contents; + + th, + td { + display: grid; + grid-template-columns: subgrid; + } + } + } + + tbody { + --border: 1px solid var(--border-secondary); + display: contents; + + tr { + display: contents; + + th, + td { + display: contents; + + button { + display: grid; + grid-template-columns: subgrid; + } + + .more { + grid-column: 1 / calc(var(--browser-count) + 2); + } + } + + // Border. + &:not(:first-child) { + th, + td { + > * { + border-top: var(--border); + } + } + + .bc-feature { + border-top: var(--border); + } + } + + th:not(:first-child), + td:not(:first-child) { + > * { + border-left: var(--border); + } + } + } + + .bc-feature { + align-items: center; + border: none; + display: flex; + text-align: left; + width: 100%; + + > * { + border: none !important; + flex-basis: max-content; + } + } + } +} diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index f948378e3aed..feb3301d015a 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -5,7 +5,7 @@ @media (min-width: $screen-sm) { .bc-table { thead { - display: table-header-group; + //display: table-header-group; .bc-platforms { th { @@ -18,7 +18,6 @@ th { background: inherit; padding: 0.25rem; - width: 2rem; } td.bc-support { @@ -30,7 +29,7 @@ } tr.bc-history-desktop { - display: table-row; + //display: table-row; } } @@ -41,7 +40,7 @@ } .table-container-inner { - min-width: max-content; + //min-width: max-content; padding: 0 3rem; position: relative; @@ -61,7 +60,7 @@ } .bc-notes-list { - //margin-left: 20%; + margin-left: 20%; width: auto; } @@ -92,7 +91,7 @@ .bc-has-history { cursor: pointer; - &:hover { + > button:hover { background: var(--background-secondary); } } diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 3fd3db1bce36..69c75650dfdf 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -4,39 +4,7 @@ @use "../../ui/atoms/button"; @use "../../ui/atoms/icon"; -// Global. - -* { - box-sizing: border-box; -} - -/* Remove default margin */ -body, -h1, -h2, -h3, -h4, -p, -figure, -blockquote, -dl, -dd { - margin: 0; -} - -.visually-hidden { - border: 0 !important; - clip: rect(1px, 1px, 1px, 1px) !important; - -webkit-clip-path: inset(50%) !important; - clip-path: inset(50%) !important; - height: 1px !important; - margin: -1px !important; - overflow: hidden !important; - padding: 0 !important; - position: absolute !important; - white-space: nowrap !important; - width: 1px !important; -} +@include meta.load-css("global"); // Style for mobile *and* desktop. @@ -178,22 +146,13 @@ dd { } .bc-head-txt-label { - left: calc(50% - 0.5rem); line-height: 1; - padding-top: 0.5rem; - position: relative; text-orientation: sideways; transform: rotate(180deg); white-space: nowrap; - -ms-writing-mode: tb-rl; - -webkit-writing-mode: vertical-rl; writing-mode: vertical-rl; } -.bc-head-icon-symbol { - margin-bottom: 0.3rem; -} - .bc-support { text-align: center; vertical-align: middle; @@ -242,7 +201,7 @@ dd { height: 2rem; th { - text-align: center; + align-content: center; } td { @@ -253,7 +212,12 @@ dd { // Row with browser names. .bc-browsers { th { - text-align: center; + align-items: center; + display: flex; + flex-direction: row-reverse; + gap: 0.25rem; + justify-content: start; + vertical-align: bottom; } td { @@ -315,6 +279,7 @@ dd { abbr { margin-right: 4px; + text-decoration: none; } dd { @@ -361,8 +326,8 @@ dl.bc-notes-list { } .bc-table-row-header { - align-items: baseline; - display: inline-flex; + padding: 0.25em; + text-align: left; width: 100%; code { @@ -388,7 +353,7 @@ dl.bc-notes-list { } .bc-icons { - display: flex; + display: inline-flex; gap: 0.5rem; margin-top: 0.25rem; @@ -483,7 +448,12 @@ dl.bc-notes-list { } td.bc-support > button { - padding: 0.5rem 0.25rem; + padding-bottom: calc(0.5rem + var(--padding-bottom-offset, 0px)); + padding-left: 0.25rem; + padding-right: 0.25rem; + padding-top: 0.5rem; } } } + +@include meta.load-css("grid"); From c8fe15fa1861b2b5c8f2fd14725b22bf6f855627 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 15:48:27 +0100 Subject: [PATCH 10/69] fix(compat): improve grid-template-columns --- client/src/lit/compat/grid.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/src/lit/compat/grid.scss b/client/src/lit/compat/grid.scss index 7679c90ebfca..bc683a3bac7b 100644 --- a/client/src/lit/compat/grid.scss +++ b/client/src/lit/compat/grid.scss @@ -18,7 +18,10 @@ table { display: grid; grid-auto-flow: row dense; - grid-template-columns: minmax(1fr, 2rem) repeat(auto-fit, minmax(1fr, 2rem)); + grid-template-columns: minmax(25%, max-content) repeat( + var(--browser-count), + calc(75% / var(--browser-count)) + ); width: 100%; thead { From 52e5b39287d765ef9eb72d4345544c98a10de2e5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 17:18:07 +0100 Subject: [PATCH 11/69] chore(compat): improve/consolidate mobile style --- client/src/lit/compat/bcd-table.js | 2 +- client/src/lit/compat/grid.scss | 43 ------------------- client/src/lit/compat/index-desktop-md.scss | 12 ++++-- client/src/lit/compat/index-desktop.scss | 46 +++++++++++++++++++-- client/src/lit/compat/index-mobile.scss | 20 +++++++-- client/src/lit/compat/index.scss | 32 +++++++------- 6 files changed, 83 insertions(+), 72 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 46bf166abaac..04ec106d81d8 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -628,7 +628,7 @@ class BcdTable extends LitElement { ? `Released ${browserReleaseDate}` : ""} > - ${timeline ? browser.name : null} + ${timeline && false ? browser.name : null} ${!timeline || browserReleaseDate ? label : null} ${browserReleaseDate && timeline ? ` (Released ${browserReleaseDate})` diff --git a/client/src/lit/compat/grid.scss b/client/src/lit/compat/grid.scss index bc683a3bac7b..1a791276cd9e 100644 --- a/client/src/lit/compat/grid.scss +++ b/client/src/lit/compat/grid.scss @@ -1,31 +1,9 @@ -.more { - display: none; -} - -.bc-has-history:focus-within { - > button { - --padding-bottom-offset: -2px; - border-bottom: 2px solid var(--text-primary); - - ~ .more { - border-left: none; - border-top: var(--border); - display: initial; - } - } -} - table { display: grid; grid-auto-flow: row dense; - grid-template-columns: minmax(25%, max-content) repeat( - var(--browser-count), - calc(75% / var(--browser-count)) - ); width: 100%; thead { - display: contents; line-height: 1; text-orientation: sideways; white-space: nowrap; @@ -62,27 +40,6 @@ table { grid-column: 1 / calc(var(--browser-count) + 2); } } - - // Border. - &:not(:first-child) { - th, - td { - > * { - border-top: var(--border); - } - } - - .bc-feature { - border-top: var(--border); - } - } - - th:not(:first-child), - td:not(:first-child) { - > * { - border-left: var(--border); - } - } } .bc-feature { diff --git a/client/src/lit/compat/index-desktop-md.scss b/client/src/lit/compat/index-desktop-md.scss index b13745a5cd4c..97f9f525d962 100644 --- a/client/src/lit/compat/index-desktop-md.scss +++ b/client/src/lit/compat/index-desktop-md.scss @@ -5,9 +5,15 @@ width: calc(100% + 6rem); } + .table-container-inner { + min-width: initial; + } + .bc-table { - tbody th { - width: 20%; - } + // 25% for feature, 75% for browser columns. + grid-template-columns: minmax(25%, max-content) repeat( + var(--browser-count), + calc(75% / var(--browser-count)) + ); } } diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index feb3301d015a..5f39a998ee6e 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -4,8 +4,14 @@ @media (min-width: $screen-sm) { .bc-table { + // Expand all columns. + grid-template-columns: minmax(20vw, min-content) repeat( + var(--browser-count), + auto + ); + thead { - //display: table-header-group; + display: contents; .bc-platforms { th { @@ -14,6 +20,31 @@ } } + tbody { + // Border. + tr { + &:not(:first-child) { + th, + td { + > * { + border-top: var(--border); + } + } + + .bc-feature { + border-top: var(--border); + } + } + + th:not(:first-child), + td:not(:first-child) { + > * { + border-left: var(--border); + } + } + } + } + td, th { background: inherit; @@ -28,8 +59,15 @@ } } - tr.bc-history-desktop { - //display: table-row; + .more { + border-left: none; + border-top: var(--border); + } + + .bc-has-history:focus-within > button { + // Highlight expanded item. + --padding-bottom-offset: -2px; + border-bottom: 2px solid var(--text-primary); } } @@ -40,7 +78,7 @@ } .table-container-inner { - //min-width: max-content; + min-width: max-content; padding: 0 3rem; position: relative; diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index 1b4cbc847060..dfb21eb2c139 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -4,13 +4,27 @@ @media (max-width: $screen-sm - 1px) { .bc-table { + grid-template-columns: auto; + thead { display: none; } - td.bc-support { - border-left-width: 0; - display: block; + tr { + td.bc-support { + border: none; + border-top: 1px solid var(--border-primary); + display: block; + + &:last-child { + border-bottom: 1px solid var(--border-primary); + } + } + } + + .more { + // Align timeline icons with summary icon. + margin-left: 0.25rem; } .bc-feature, diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 69c75650dfdf..89dc5208b5e4 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -20,16 +20,14 @@ th { border: 1px solid var(--border-secondary); border-width: 0 0 1px 1px; + + font-size: var(--type-smaller-font-size); font-weight: 500; padding: 0; + padding: 0.4rem; - @media (min-width: $screen-md) { + code { font-size: var(--type-smaller-font-size); - padding: 0.4rem; - - code { - font-size: var(--type-smaller-font-size); - } } } @@ -46,7 +44,7 @@ tr { height: 3rem; - @media (min-width: $screen-md) { + @media (min-width: $screen-sm) { &:last-child { th, td { @@ -143,6 +141,10 @@ .bc-feature-depth-3 { border-left-width: 16px; } + + .bc-has-history:not(:focus-within) .more { + display: none; + } } .bc-head-txt-label { @@ -198,8 +200,6 @@ // Row with desktop / mobile icons. .bc-platforms { - height: 2rem; - th { align-content: center; } @@ -396,7 +396,7 @@ dl.bc-notes-list { flex-direction: row; gap: 0.5rem; - @media (min-width: $screen-md) { + @media (min-width: $screen-sm) { align-items: center; flex-direction: column; } @@ -405,13 +405,13 @@ dl.bc-notes-list { .bcd-timeline-cell-text-wrapper { display: flex; flex-direction: row; - gap: 0.5rem; + gap: 0.25rem; } .bcd-cell-text-copy { color: var(--text-primary); display: flex; - gap: 0.5rem; + gap: 0.5ch; } .bc-supports-yes { @@ -436,17 +436,13 @@ dl.bc-notes-list { display: flex; gap: 0.5rem; - @media (min-width: $screen-md) { + @media (min-width: $screen-sm) { display: block; } } -@media (min-width: $screen-md) { +@media (min-width: $screen-sm) { .bc-table { - td { - height: 2rem; - } - td.bc-support > button { padding-bottom: calc(0.5rem + var(--padding-bottom-offset, 0px)); padding-left: 0.25rem; From d4c5fe250126fec4e35915c8180789a60e78208a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:16:57 +0100 Subject: [PATCH 12/69] fix(compat): add tabindex on more/timeline --- client/src/lit/compat/bcd-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 04ec106d81d8..191d3ef93137 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -291,7 +291,7 @@ class BcdTable extends LitElement { ${this.renderCellText(support, browser)} ${notes && - html`
+ html`
${notes}
`} `; From bb358a0cca6ca8ffdcc044d74c864d234bbfc260 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:35:30 +0100 Subject: [PATCH 13/69] chore(compat): simplify grid-column --- client/src/lit/compat/grid.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/grid.scss b/client/src/lit/compat/grid.scss index 1a791276cd9e..4227678b1eb6 100644 --- a/client/src/lit/compat/grid.scss +++ b/client/src/lit/compat/grid.scss @@ -37,7 +37,7 @@ table { } .more { - grid-column: 1 / calc(var(--browser-count) + 2); + grid-column: 1 / -1; } } } From c2b8c551571f0817a1cee8a67fc24a964f5a81a1 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:30:47 +0100 Subject: [PATCH 14/69] refactor(compat): merge grid.scss --- client/src/lit/compat/grid.scss | 58 ---------------------- client/src/lit/compat/index-desktop.scss | 2 + client/src/lit/compat/index.scss | 61 +++++++++++++++++++++++- 3 files changed, 61 insertions(+), 60 deletions(-) delete mode 100644 client/src/lit/compat/grid.scss diff --git a/client/src/lit/compat/grid.scss b/client/src/lit/compat/grid.scss deleted file mode 100644 index 4227678b1eb6..000000000000 --- a/client/src/lit/compat/grid.scss +++ /dev/null @@ -1,58 +0,0 @@ -table { - display: grid; - grid-auto-flow: row dense; - width: 100%; - - thead { - line-height: 1; - text-orientation: sideways; - white-space: nowrap; - writing-mode: sideways-lr; - - tr { - display: contents; - - th, - td { - display: grid; - grid-template-columns: subgrid; - } - } - } - - tbody { - --border: 1px solid var(--border-secondary); - display: contents; - - tr { - display: contents; - - th, - td { - display: contents; - - button { - display: grid; - grid-template-columns: subgrid; - } - - .more { - grid-column: 1 / -1; - } - } - } - - .bc-feature { - align-items: center; - border: none; - display: flex; - text-align: left; - width: 100%; - - > * { - border: none !important; - flex-basis: max-content; - } - } - } -} diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index 5f39a998ee6e..2d79d712da00 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -21,6 +21,8 @@ } tbody { + --border: 1px solid var(--border-secondary); + // Border. tr { &:not(:first-child) { diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 89dc5208b5e4..3e960f3d5348 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -8,6 +8,45 @@ // Style for mobile *and* desktop. +table { + display: grid; + grid-auto-flow: row dense; + + thead { + tr { + display: contents; + + th, + td { + display: grid; + grid-template-columns: subgrid; + } + } + } + + tbody { + display: contents; + + tr { + display: contents; + + th, + td { + display: contents; + + button { + display: grid; + grid-template-columns: subgrid; + } + + .more { + grid-column: 1 / -1; + } + } + } + } +} + .bc-table { border: 1px solid var(--border-primary); border-collapse: separate; @@ -37,6 +76,13 @@ vertical-align: bottom; } + thead { + line-height: 1; + text-orientation: sideways; + white-space: nowrap; + writing-mode: sideways-lr; + } + // these props allow us to add border-radius to the table. // border-collapse: separate gets in the way of this // being easy. @@ -134,6 +180,19 @@ } } + .bc-feature { + align-items: center; + border: none; + display: flex; + text-align: left; + width: 100%; + + > * { + border: none !important; + flex-basis: max-content; + } + } + .bc-feature-depth-2 { border-left-width: 8px; } @@ -451,5 +510,3 @@ dl.bc-notes-list { } } } - -@include meta.load-css("grid"); From b199d014467e2c9ce3a1034251706de4bf65f12f Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:40:49 +0100 Subject: [PATCH 15/69] fix(compat): restore depth border --- client/src/lit/compat/index.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 3e960f3d5348..2cdbe203a380 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -194,11 +194,11 @@ table { } .bc-feature-depth-2 { - border-left-width: 8px; + border-left: 7px solid var(--border-primary); } .bc-feature-depth-3 { - border-left-width: 16px; + border-left: 15px solid var(--border-primary); } .bc-has-history:not(:focus-within) .more { From a74b0ed97e5fb9198211d47630bde6ac5bde856e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:50:26 +0100 Subject: [PATCH 16/69] chore(compat): port https://github.com/mdn/yari/pull/12303 --- client/src/lit/compat/bcd-table.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 191d3ef93137..a935649223c4 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -592,16 +592,18 @@ class BcdTable extends LitElement { break; case "preview": - title = "Preview browser support"; + title = "Preview support"; label = status.label || browser.preview_name; break; case "unknown": - title = "Compatibility unknown; please update this."; + title = "Support unknown"; label = "?"; break; } + title = `${browser.name} – ${title}`; + return html`
${timeline && false ? browser.name : null} From 59d0b84f8bbd2328bcba9ee856e546557723faa2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 18:56:57 +0100 Subject: [PATCH 17/69] enhance(compat): omit trailing ".0" (esp. Node.js) --- client/src/lit/compat/feature-row.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/lit/compat/feature-row.js b/client/src/lit/compat/feature-row.js index 1127c60ec246..8419d580eb43 100644 --- a/client/src/lit/compat/feature-row.js +++ b/client/src/lit/compat/feature-row.js @@ -58,14 +58,17 @@ export function labelFromString(version, browser) { if (typeof version !== "string") { return "?"; } + if (version === "preview") { + return browser.preview_name ?? "Preview"; + } // Treat BCD ranges as exact versions to avoid confusion for the reader // See https://github.com/mdn/yari/issues/3238 if (version.startsWith("≤")) { - return version.slice(1); - } - if (version === "preview") { - return browser.preview_name ?? "Preview"; + version = version.slice(1); } + // New: Omit trailing ".0". + version = version.replace(/(\.0)+$/g, ""); + return version; } From 4d558f81005ae6a398ddbda36f38e665aa1dac91 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 19:01:32 +0100 Subject: [PATCH 18/69] chore(compat): remove whitespace before support note --- client/src/lit/compat/bcd-table.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index a935649223c4..0e6858c5408b 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -513,8 +513,7 @@ class BcdTable extends LitElement { ${filteredSupportNotes.map(({ iconName, label }) => { return html`
- ${this.renderIcon(iconName)} - ${typeof label === "string" + ${this.renderIcon(iconName)}${typeof label === "string" ? html`${unsafeHTML(label)}` : label}
`; From f188d888a366e81fec337c131b7c26ce8983cca5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 19:03:56 +0100 Subject: [PATCH 19/69] enhance(compat): show browser name in timeline --- client/src/lit/compat/index-desktop.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index 2d79d712da00..b98e04176f22 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -94,9 +94,11 @@ } } - .bc-support-level, - .bc-browser-name { - display: none; + .bcd-cell-text-wrapper { + .bc-support-level, + .bc-browser-name { + display: none; + } } .bc-notes-list { From 314d1424a06d7057abacad6aae74e3aa915ab65e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 19:05:11 +0100 Subject: [PATCH 20/69] refactor(compat): rename .{more => timeline} --- client/src/lit/compat/bcd-table.js | 2 +- client/src/lit/compat/index-desktop.scss | 2 +- client/src/lit/compat/index-mobile.scss | 2 +- client/src/lit/compat/index.scss | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 0e6858c5408b..7b309740484f 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -291,7 +291,7 @@ class BcdTable extends LitElement { ${this.renderCellText(support, browser)} ${notes && - html`
+ html`
${notes}
`} `; diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index b98e04176f22..13c9a5c52a3e 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -61,7 +61,7 @@ } } - .more { + .timeline { border-left: none; border-top: var(--border); } diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index dfb21eb2c139..a0088d3c2c2e 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -22,7 +22,7 @@ } } - .more { + .timeline { // Align timeline icons with summary icon. margin-left: 0.25rem; } diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 2cdbe203a380..afe6a7372120 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -39,7 +39,7 @@ table { grid-template-columns: subgrid; } - .more { + .timeline { grid-column: 1 / -1; } } @@ -201,7 +201,7 @@ table { border-left: 15px solid var(--border-primary); } - .bc-has-history:not(:focus-within) .more { + .bc-has-history:not(:focus-within) .timeline { display: none; } } From e6733ea96fafb5f6c0904ae8b74dc16b21ce4b53 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 19:10:31 +0100 Subject: [PATCH 21/69] enhance(compat): open issue form via window.open() to reduce spam --- client/src/lit/compat/bcd-table.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 7b309740484f..8fde42029a7f 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -151,9 +151,14 @@ class BcdTable extends LitElement { } renderIssueLink() { + const onClick = (/** @type {MouseEvent} */ event) => { + event.preventDefault(); + window.open(this.issueUrl, "_blank", "noopener,noreferrer"); + }; return html` Date: Tue, 11 Mar 2025 19:42:20 +0100 Subject: [PATCH 22/69] chore(compat): ignore default-case --- client/src/lit/compat/bcd-table.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 8fde42029a7f..8eb452db9365 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -569,6 +569,7 @@ class BcdTable extends LitElement { let label; let title = ""; + // eslint-disable-next-line default-case switch (status.isSupported) { case "yes": title = "Full support"; From 065efa34b6bed09d41292ab4a0aa29161a3b8508 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 21:08:45 +0100 Subject: [PATCH 23/69] enhance(compat): add "Show data on GitHub" link --- client/src/lit/compat/bcd-table.js | 35 +++++++++++++++++++++--------- client/src/lit/compat/index.scss | 7 +++++- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 8eb452db9365..eecadf9e20f7 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -99,6 +99,7 @@ class BcdTable extends LitElement { constructor() { super(); this.query = ""; + /** @type {Identifier} */ this.data = {}; /** @type {Browsers} */ // @ts-ignore @@ -155,16 +156,30 @@ class BcdTable extends LitElement { event.preventDefault(); window.open(this.issueUrl, "_blank", "noopener,noreferrer"); }; - return html` - Report problems with this compatibility data on GitHub - `; + const source_file = this.data.__compat?.source_file; + return html``; } renderTable() { diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index afe6a7372120..e3754dc4b7e7 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -47,12 +47,17 @@ table { } } +.bc-on-github { + font-size: var(--type-smaller-font-size); + text-align: right; +} + .bc-table { border: 1px solid var(--border-primary); border-collapse: separate; border-radius: var(--elem-radius); border-spacing: 0; - margin: 1rem 0; + margin: 0; width: 100%; td, From ae04022d3a4a882c578ac4c4f205f2d8fbee801e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 21:15:50 +0100 Subject: [PATCH 24/69] fix(compat): support collapsing timeline --- client/src/lit/compat/bcd-table.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index eecadf9e20f7..b2fbee4c7601 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -290,6 +290,27 @@ class BcdTable extends LitElement {
`; } + const handleMousedown = (/** @type {MouseEvent} */ event) => { + // Blur active element if already focused. + const activeElement = this.shadowRoot?.activeElement; + const { currentTarget } = event; + + if ( + !(activeElement instanceof HTMLElement) || + !(currentTarget instanceof HTMLElement) + ) { + return; + } + + const activeCell = activeElement.closest("td"); + const currentCell = currentTarget.closest("td"); + + if (activeCell === currentCell) { + activeElement.blur(); + event.preventDefault(); + } + }; + return html` ${titleNode} @@ -307,7 +328,11 @@ class BcdTable extends LitElement { notes ? "bc-has-history" : "" }`} > - ${notes && From c61bdc4716bdcf66a81f7f457758ca58bcdf97b5 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 11 Mar 2025 21:58:16 +0100 Subject: [PATCH 25/69] chore(compat): ignore some TS errors These stem from limitations of the BCD types. --- client/src/lit/compat/feature-row.js | 1 + client/src/lit/compat/legend.js | 2 ++ client/src/lit/compat/utils.js | 1 + 3 files changed, 4 insertions(+) diff --git a/client/src/lit/compat/feature-row.js b/client/src/lit/compat/feature-row.js index 8419d580eb43..ba37a99da10c 100644 --- a/client/src/lit/compat/feature-row.js +++ b/client/src/lit/compat/feature-row.js @@ -101,5 +101,6 @@ export function getSupportBrowserReleaseDate(support) { return undefined; } + // @ts-ignore return getCurrentSupport(support)?.release_date; } diff --git a/client/src/lit/compat/legend.js b/client/src/lit/compat/legend.js index 8f377c45698e..bf024e7b0e0b 100644 --- a/client/src/lit/compat/legend.js +++ b/client/src/lit/compat/legend.js @@ -72,6 +72,7 @@ export function getActiveLegendItems(compat, name, browserInfo) { legendItems.add("no"); continue; } + // @ts-ignore const firstSupportItem = getFirst(browserSupport); if (hasNoteworthyNotes(firstSupportItem)) { legendItems.add("footnote"); @@ -82,6 +83,7 @@ export function getActiveLegendItems(compat, name, browserInfo) { if (versionSupport.flags && versionSupport.flags.length) { legendItems.add("no"); } else if ( + // @ts-ignore versionIsPreview(versionSupport.version_added, browserInfo[browser]) ) { legendItems.add("preview"); diff --git a/client/src/lit/compat/utils.js b/client/src/lit/compat/utils.js index 75948fe293c7..95cafbd37a9c 100644 --- a/client/src/lit/compat/utils.js +++ b/client/src/lit/compat/utils.js @@ -313,5 +313,6 @@ export function getCurrentSupport(support) { if (noSupportItem) return noSupportItem; // Default (likely never reached). + // @ts-ignore return getFirst(support); } From 9df6e2a0ab31d2e3f044e13a177f4daed0fd1a5d Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 13 Mar 2025 17:35:35 +0100 Subject: [PATCH 26/69] refactor(compat): replace several @typedef with @import Co-authored-by: Leo McArdle --- client/src/lit/compat/bcd-table.js | 9 ++------- client/src/lit/compat/feature-row.js | 4 ++-- client/src/lit/compat/legend.js | 4 +--- client/src/lit/compat/utils.js | 7 +------ 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index b2fbee4c7601..4826d1d60666 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -26,13 +26,8 @@ import { import { ifDefined } from "lit/directives/if-defined.js"; /** - * @typedef {import("lit").TemplateResult} TemplateResult - * @typedef {import("@mdn/browser-compat-data/types").BrowserName} BrowserName - * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement - * @typedef {import("@mdn/browser-compat-data/types").SupportStatement} SupportStatement - * @typedef {import("@mdn/browser-compat-data/types").Browsers} Browsers - * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier - * @typedef {import("@mdn/browser-compat-data/types").StatusBlock} StatusBlock + * @import { TemplateResult } from "lit" + * @import { BrowserName, BrowserStatement, SupportStatement, Browsers, Identifier, StatusBlock } from "@mdn/browser-compat-data/types" * @typedef {{ title: string, text: string, iconClassName: string }} StatusIcon */ diff --git a/client/src/lit/compat/feature-row.js b/client/src/lit/compat/feature-row.js index ba37a99da10c..5f812af2ac18 100644 --- a/client/src/lit/compat/feature-row.js +++ b/client/src/lit/compat/feature-row.js @@ -2,8 +2,8 @@ import { getCurrentSupport, versionIsPreview } from "./utils.js"; import { html } from "lit"; /** - * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement - * @typedef {import("./utils.js").SupportStatementExtended} SupportStatementExtended + * @import { BrowserStatement } from "@mdn/browser-compat-data/types" + * @import { SupportStatementExtended } from "./utils.js" * @typedef {"no"|"yes"|"partial"|"preview"|"removed-partial"|"unknown"} SupportClassName */ diff --git a/client/src/lit/compat/legend.js b/client/src/lit/compat/legend.js index bf024e7b0e0b..ee9a72b14816 100644 --- a/client/src/lit/compat/legend.js +++ b/client/src/lit/compat/legend.js @@ -9,9 +9,7 @@ import { } from "./utils.js"; /** - * @typedef {import("@mdn/browser-compat-data").Identifier} Identifier - * @typedef {import("@mdn/browser-compat-data").Browsers} Browsers - * @typedef {import("@mdn/browser-compat-data").BrowserName} BrowserName + * @import { Browsers, Identifier } from "@mdn/browser-compat-data/types" * @typedef {"yes" | "partial" | "preview" | "no" | "unknown" | "experimental" | "nonstandard" | "deprecated" | "footnote" | "disabled" | "altname" | "prefix" | "more"} LegendKey */ diff --git a/client/src/lit/compat/utils.js b/client/src/lit/compat/utils.js index 95cafbd37a9c..46dfacacafce 100644 --- a/client/src/lit/compat/utils.js +++ b/client/src/lit/compat/utils.js @@ -1,10 +1,5 @@ /** - * @typedef {import("@mdn/browser-compat-data/types").SimpleSupportStatement} SimpleSupportStatement - * @typedef {import("@mdn/browser-compat-data/types").VersionValue} VersionValue - * @typedef {import("@mdn/browser-compat-data/types").CompatStatement} CompatStatement - * @typedef {import("@mdn/browser-compat-data/types").Identifier} Identifier - * @typedef {import("@mdn/browser-compat-data/types").SupportStatement} SupportStatement - * @typedef {import("@mdn/browser-compat-data/types").BrowserStatement} BrowserStatement + * @import { SimpleSupportStatement, VersionValue, CompatStatement, Identifier, SupportStatement, BrowserStatement } from "@mdn/browser-compat-data/types" * @typedef {SimpleSupportStatementExtended | SimpleSupportStatementExtended[]} SupportStatementExtended */ From 600e391a5dc8b1f97916175ff907c0d0bcf8d418 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 13 Mar 2025 17:41:40 +0100 Subject: [PATCH 27/69] refactor(compat): remove unnecessary inline imports --- client/src/lit/compat/bcd-table.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/bcd-table.js index 4826d1d60666..0c1bb2d503c1 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/bcd-table.js @@ -745,8 +745,8 @@ customElements.define("bcd-table", BcdTable); * those are also shown. Deno is always shown if Node.js is shown. * * @param {string} category - * @param {import("@mdn/browser-compat-data/types").Identifier} data - * @param {import("@mdn/browser-compat-data/types").Browsers} browserInfo + * @param {Identifier} data + * @param {Browsers} browserInfo * @returns {[string[], BrowserName[]]} */ export function gatherPlatformsAndBrowsers(category, data, browserInfo) { From b9b56738fb5430a59d9741e6f715d498b280fe6b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 13 Mar 2025 17:44:58 +0100 Subject: [PATCH 28/69] refactor(compat): rename [Lazy]{Bcd => Compat}Table --- client/src/document/index.tsx | 10 +++------- .../lit/compat/{bcd-table.js => compat-table.js} | 4 ++-- .../{lazy-bcd-table.js => lazy-compat-table.js} | 14 +++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) rename client/src/lit/compat/{bcd-table.js => compat-table.js} (99%) rename client/src/lit/compat/{lazy-bcd-table.js => lazy-compat-table.js} (86%) diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx index 49a536f62c96..e4dc98c6eac0 100644 --- a/client/src/document/index.tsx +++ b/client/src/document/index.tsx @@ -47,8 +47,8 @@ import { DisplayH2, DisplayH3 } from "./ingredients/utils"; // import { useUIStatus } from "../ui-context"; // Lazy sub-components -const LazyBrowserCompatibilityTable = React.lazy( - () => import("../lit/compat/lazy-bcd-table") +const LazyCompatTable = React.lazy( + () => import("../lit/compat/lazy-compat-table.js") ); const Toolbar = React.lazy(() => import("./toolbar")); const MathMLPolyfillMaybe = React.lazy(() => import("./mathml-polyfill")); @@ -276,11 +276,7 @@ export function RenderDocumentBody({ doc }) { } key={`browser_compatibility${i}`}> {title && !isH3 && } {title && isH3 && } - + ); } else if (section.type === "specifications") { diff --git a/client/src/lit/compat/bcd-table.js b/client/src/lit/compat/compat-table.js similarity index 99% rename from client/src/lit/compat/bcd-table.js rename to client/src/lit/compat/compat-table.js index 0c1bb2d503c1..ce676778cce9 100644 --- a/client/src/lit/compat/bcd-table.js +++ b/client/src/lit/compat/compat-table.js @@ -78,7 +78,7 @@ export const LEGEND_LABELS = { more: "Has more compatibility info.", }; -class BcdTable extends LitElement { +class CompatTable extends LitElement { static properties = { query: {}, locale: {}, @@ -732,7 +732,7 @@ class BcdTable extends LitElement { } } -customElements.define("bcd-table", BcdTable); +customElements.define("compat-table", CompatTable); /** * Return a list of platforms and browsers that are relevant for this category & diff --git a/client/src/lit/compat/lazy-bcd-table.js b/client/src/lit/compat/lazy-compat-table.js similarity index 86% rename from client/src/lit/compat/lazy-bcd-table.js rename to client/src/lit/compat/lazy-compat-table.js index 0628fbf35ff5..a0bbc09ed140 100644 --- a/client/src/lit/compat/lazy-bcd-table.js +++ b/client/src/lit/compat/lazy-compat-table.js @@ -2,9 +2,9 @@ import { LitElement, html } from "lit"; import { createComponent } from "@lit/react"; import React from "react"; import { BCD_BASE_URL } from "../../env.ts"; -import "./bcd-table.js"; +import "./compat-table.js"; -class LazyBcdTable extends LitElement { +class LazyCompatTable extends LitElement { static properties = { _id: {}, query: {}, @@ -67,19 +67,19 @@ class LazyBcdTable extends LitElement { if (!this.compat) { return html`

No compatibility data found

`; } - return html``; + >`; } } -customElements.define("lazy-bcd-table", LazyBcdTable); +customElements.define("lazy-compat-table", LazyCompatTable); export default createComponent({ - tagName: "lazy-bcd-table", - elementClass: LazyBcdTable, + tagName: "lazy-compat-table", + elementClass: LazyCompatTable, react: React, }); From b8b032804270a6adf11fe1742dd3565a311f5b78 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 13 Mar 2025 17:53:29 +0100 Subject: [PATCH 29/69] fix(compat): prefix internal state with `_` --- client/src/lit/compat/compat-table.js | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index ce676778cce9..c59fb4f7ce00 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -84,9 +84,9 @@ class CompatTable extends LitElement { locale: {}, data: {}, browserInfo: { attribute: "browserinfo" }, - pathname: { state: true }, - platforms: { state: true }, - browsers: { state: true }, + _pathname: { state: true }, + _platforms: { state: true }, + _browsers: { state: true }, }; static styles = styles; @@ -100,11 +100,11 @@ class CompatTable extends LitElement { // @ts-ignore this.browserInfo = {}; this.locale = ""; // TODO - this.pathname = ""; + this._pathname = ""; /** @type {string[]} */ - this.platforms = []; + this._platforms = []; /** @type {BrowserName[]} */ - this.browsers = []; + this._browsers = []; } get breadcrumbs() { @@ -121,8 +121,8 @@ class CompatTable extends LitElement { connectedCallback() { super.connectedCallback(); - this.pathname = window.location.pathname; - [this.platforms, this.browsers] = gatherPlatformsAndBrowsers( + this._pathname = window.location.pathname; + [this._platforms, this._browsers] = gatherPlatformsAndBrowsers( this.category, this.data, this.browserInfo @@ -138,7 +138,7 @@ class CompatTable extends LitElement { ) .replace(/\$QUERY_ID/g, this.query) .trim(); - sp.set("mdn-url", `https://developer.mozilla.org${this.pathname}`); + sp.set("mdn-url", `https://developer.mozilla.org${this._pathname}`); sp.set("metadata", metadata); sp.set("title", `${this.query} - `); sp.set("template", "data-problem.yml"); @@ -182,7 +182,7 @@ class CompatTable extends LitElement {
${this.renderTableHeader()} ${this.renderTableBody()}
@@ -197,9 +197,9 @@ class CompatTable extends LitElement { } renderPlatformHeaders() { - const platformsWithBrowsers = this.platforms.map((platform) => ({ + const platformsWithBrowsers = this._platforms.map((platform) => ({ platform, - browsers: this.browsers.filter( + browsers: this._browsers.filter( (browser) => this.browserInfo[browser].type === platform ), })); @@ -234,7 +234,7 @@ class CompatTable extends LitElement { // return html` - ${this.browsers.map( + ${this._browsers.map( (browser) => html`
@@ -252,7 +252,7 @@ class CompatTable extends LitElement { renderTableBody() { // - const { data, browsers, browserInfo, locale } = this; + const { data, _browsers: browsers, browserInfo, locale } = this; const features = listFeatures(data, "", this.name); return html` From 5b67a10eab24a5ec0a478669bffa2563f474c229 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Thu, 13 Mar 2025 18:03:03 +0100 Subject: [PATCH 30/69] fix(compat): migrate to @lit/task --- client/src/lit/compat/lazy-compat-table.js | 76 ++++++++-------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/client/src/lit/compat/lazy-compat-table.js b/client/src/lit/compat/lazy-compat-table.js index a0bbc09ed140..3a921eacbbc5 100644 --- a/client/src/lit/compat/lazy-compat-table.js +++ b/client/src/lit/compat/lazy-compat-table.js @@ -1,6 +1,8 @@ import { LitElement, html } from "lit"; import { createComponent } from "@lit/react"; +import { Task } from "@lit/task"; import React from "react"; + import { BCD_BASE_URL } from "../../env.ts"; import "./compat-table.js"; @@ -9,9 +11,6 @@ class LazyCompatTable extends LitElement { _id: {}, query: {}, locale: {}, - compat: { state: true }, - error: { state: true }, - loading: { state: true }, }; constructor() { @@ -19,60 +18,41 @@ class LazyCompatTable extends LitElement { this._id = ""; this.query = ""; this.locale = ""; - this.compat = null; - this.error = null; - this.loading = false; } connectedCallback() { super.connectedCallback(); - this.loading = true; - } - - /** - * @param {Map} changedProperties - * @returns {Promise} - */ - async update(changedProperties) { - super.update(changedProperties); - if (changedProperties.has("query")) { - await this.fetchData(this.query); - } } - /** - * @param {string} query - * @returns {Promise} - */ - async fetchData(query) { - try { - const res = await fetch( - `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json` + _dataTask = new Task(this, { + args: () => [this.query], + task: async ([query], { signal }) => { + const response = await fetch( + `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json`, + { signal } ); - this.compat = await res.json(); - } catch (error) { - this.error = error; - } finally { - this.loading = false; - } - } + if (!response.ok) { + console.error("Failed to fetch BCD data:", response); + throw new Error(response.statusText); + } + return response.json(); + }, + }); render() { - if (this.loading) { - return html`

Loading...

`; - } - if (this.error) { - return html`

Error loading data

`; - } - if (!this.compat) { - return html`

No compatibility data found

`; - } - return html``; + return this._dataTask.render({ + pending: () => html`

Loading...

`, + complete: (compat) => + compat + ? html`` + : html`

No compatibility data found

`, + error: (error) => html`

Error loading data: ${error}

`, + }); } } From b720443844792cb0123d0387ff72658219215a4e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 14 Mar 2025 13:45:22 +0100 Subject: [PATCH 31/69] fix(compat): apply global link style --- client/src/lit/compat/global.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/lit/compat/global.scss b/client/src/lit/compat/global.scss index eba29705770d..8bed324f3803 100644 --- a/client/src/lit/compat/global.scss +++ b/client/src/lit/compat/global.scss @@ -35,3 +35,7 @@ code { padding: 0.125rem 0.25rem; width: fit-content; } + +a:not(.button) { + color: var(--text-link); +} From 1881485dd36ec71eed88cee9e821e79f64472e63 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 14 Mar 2025 14:01:51 +0100 Subject: [PATCH 32/69] chore(updates): reuse lit compat table --- client/src/plus/updates/index.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/client/src/plus/updates/index.tsx b/client/src/plus/updates/index.tsx index acab9a9ab6ae..3765a1f6a06a 100644 --- a/client/src/plus/updates/index.tsx +++ b/client/src/plus/updates/index.tsx @@ -3,7 +3,6 @@ import Container from "../../ui/atoms/container"; import useSWR from "swr"; import { DocMetadata } from "../../../../libs/types/document"; import { FeatureId, MDN_PLUS_TITLE } from "../../constants"; -import BrowserCompatibilityTable from "../../document/ingredients/browser-compatibility-table"; import { browserToIconName } from "../../document/ingredients/browser-compatibility-table/headers"; import { useLocale, useScrollToTop, useViewedState } from "../../hooks"; import { Button } from "../../ui/atoms/button"; @@ -14,7 +13,7 @@ import { Paginator } from "../../ui/molecules/paginator"; import BookmarkMenu from "../../ui/organisms/article-actions/bookmark-menu"; import { useUserData } from "../../user-context"; import { camelWrap, range } from "../../utils"; -import { Event, Group, useBCD, useUpdates } from "./api"; +import { Event, Group, useUpdates } from "./api"; import "./index.scss"; import { useGleanClick } from "../../telemetry/glean-context"; import { PLUS_UPDATES } from "../../telemetry/constants"; @@ -24,6 +23,11 @@ import { useSearchParams } from "react-router-dom"; import { DataError } from "../common"; import { useCollections } from "../collections/api"; import { PlusLoginBanner } from "../common/login-banner"; +import React from "react"; + +const LazyCompatTable = React.lazy( + () => import("../../lit/compat/lazy-compat-table.js") +); type EventWithStatus = Event & { status: Status }; type Status = "added" | "removed"; @@ -351,18 +355,10 @@ function EventInnerComponent({ event: Event; }) { const locale = useLocale(); - const { data } = useBCD(path); return (
- {data && ( - - )} +
); } From 7a881a0c3f1993a772c61b008cf98993776bc543 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 14 Mar 2025 14:49:10 +0100 Subject: [PATCH 33/69] refactor(updates): inline browserToIconName() --- client/src/plus/updates/index.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/src/plus/updates/index.tsx b/client/src/plus/updates/index.tsx index 3765a1f6a06a..f5b52d8ee20d 100644 --- a/client/src/plus/updates/index.tsx +++ b/client/src/plus/updates/index.tsx @@ -3,7 +3,6 @@ import Container from "../../ui/atoms/container"; import useSWR from "swr"; import { DocMetadata } from "../../../../libs/types/document"; import { FeatureId, MDN_PLUS_TITLE } from "../../constants"; -import { browserToIconName } from "../../document/ingredients/browser-compatibility-table/headers"; import { useLocale, useScrollToTop, useViewedState } from "../../hooks"; import { Button } from "../../ui/atoms/button"; import { Icon } from "../../ui/atoms/icon"; @@ -408,3 +407,16 @@ function ArticleActions({ path, mdn_url }: { path: string; mdn_url?: string }) { ); } + +function browserToIconName(browser: string) { + if (browser.startsWith("firefox")) { + return "simple-firefox"; + } else if (browser === "webview_android") { + return "webview"; + } else if (browser === "webview_ios") { + return "safari"; + } else { + const browserStart = browser.split("_")[0]; + return browserStart; + } +} From e0be82879a47be32598baf364355673ea85f540c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Fri, 14 Mar 2025 14:49:26 +0100 Subject: [PATCH 34/69] chore(client): remove old browser-compatibility-table --- .../browser-info.tsx | 14 - .../error-boundary.test.tsx | 55 -- .../error-boundary.tsx | 37 -- .../feature-row.tsx | 593 ------------------ .../browser-compatibility-table/headers.tsx | 93 --- .../index-desktop-md.scss | 13 - .../index-desktop-xl.scss | 12 - .../index-desktop.scss | 99 --- .../index-mobile.scss | 33 - .../browser-compatibility-table/index.scss | 453 ------------- .../browser-compatibility-table/index.tsx | 329 ---------- .../browser-compatibility-table/legend.tsx | 162 ----- .../browser-compatibility-table/utils.ts | 204 ------ client/src/document/lazy-bcd-table.tsx | 177 ------ 14 files changed, 2274 deletions(-) delete mode 100644 client/src/document/ingredients/browser-compatibility-table/browser-info.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/error-boundary.test.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/error-boundary.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/feature-row.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/headers.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index-desktop-md.scss delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index-desktop-xl.scss delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index-desktop.scss delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index-mobile.scss delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index.scss delete mode 100644 client/src/document/ingredients/browser-compatibility-table/index.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/legend.tsx delete mode 100644 client/src/document/ingredients/browser-compatibility-table/utils.ts delete mode 100644 client/src/document/lazy-bcd-table.tsx diff --git a/client/src/document/ingredients/browser-compatibility-table/browser-info.tsx b/client/src/document/ingredients/browser-compatibility-table/browser-info.tsx deleted file mode 100644 index e6f6390e77c9..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/browser-info.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { useContext } from "react"; -import type BCD from "@mdn/browser-compat-data/types"; - -export const BrowserInfoContext = React.createContext( - null -); - -export function BrowserName({ id }: { id: BCD.BrowserName }) { - const browserInfo = useContext(BrowserInfoContext); - if (!browserInfo) { - throw new Error("Missing browser info"); - } - return <>{browserInfo[id].name}; -} diff --git a/client/src/document/ingredients/browser-compatibility-table/error-boundary.test.tsx b/client/src/document/ingredients/browser-compatibility-table/error-boundary.test.tsx deleted file mode 100644 index c689dc6cab5a..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/error-boundary.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import { render, fireEvent } from "@testing-library/react"; -import { MemoryRouter } from "react-router-dom"; - -import { BrowserCompatibilityErrorBoundary } from "./error-boundary"; - -function renderWithRouter(component) { - return render({component}); -} - -it("renders without crashing", () => { - const { container } = renderWithRouter( - -
- - ); - expect(container).toBeDefined(); -}); - -it("renders crashing mock component", () => { - function CrashingComponent() { - const [crashing, setCrashing] = React.useState(false); - - if (crashing) { - throw new Error("42"); - } - return ( -
{ - setCrashing(true); - }} - /> - ); - } - - const consoleError = jest - .spyOn(console, "error") - .mockImplementation(() => {}); - - const { container } = renderWithRouter( - - - - ); - expect(container.querySelector(".bc-table-error-boundary")).toBeNull(); - const div = container.querySelector("div"); - div && fireEvent.click(div); - - expect(consoleError).toHaveBeenCalledWith( - expect.stringMatching("The above error occurred") - ); - - // TODO: When `BrowserCompatibilityErrorBoundary` reports to Sentry, spy on the report function so that we can assert the error stack - expect(container.querySelector(".bc-table-error-boundary")).toBeDefined(); -}); diff --git a/client/src/document/ingredients/browser-compatibility-table/error-boundary.tsx b/client/src/document/ingredients/browser-compatibility-table/error-boundary.tsx deleted file mode 100644 index c149af562896..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/error-boundary.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; - -/** - * The error boundary for BrowserCompatibilityTable. - * - * When the whole BrowserCompatibilityTable crashes, for whatever reason, - * this component will show a friendly message - * to replace that crashed component - */ -export class BrowserCompatibilityErrorBoundary extends React.Component< - any, - any -> { - state = { - error: null, - }; - componentDidCatch(error, _errorInfo) { - this.setState({ - error, - }); - // TODO: Report this error to Sentry, https://github.com/mdn/yari/issues/99 - } - render() { - if (this.state.error) { - return ( - <> -
- Unfortunately, this table has encountered unhandled error and the - content cannot be shown. - {/* TODO: When error reporting is set up, the message should include "We have been notified of this error" or something similar */} -
- - ); - } - return this.props.children; - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/feature-row.tsx b/client/src/document/ingredients/browser-compatibility-table/feature-row.tsx deleted file mode 100644 index 0ed42fd36302..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/feature-row.tsx +++ /dev/null @@ -1,593 +0,0 @@ -import React, { useContext } from "react"; -import type BCD from "@mdn/browser-compat-data/types"; -import { BrowserInfoContext } from "./browser-info"; -import { - asList, - getCurrentSupport, - hasMore, - hasNoteworthyNotes, - isFullySupportedWithoutLimitation, - isNotSupportedAtAll, - isTruthy, - versionIsPreview, - SupportStatementExtended, - bugURLToString, -} from "./utils"; -import { LEGEND_LABELS } from "./legend"; -import { DEFAULT_LOCALE } from "../../../../../libs/constants"; -import { BCD_TABLE } from "../../../telemetry/constants"; - -function getSupportClassName( - support: SupportStatementExtended | undefined, - browser: BCD.BrowserStatement -): "no" | "yes" | "partial" | "preview" | "removed-partial" | "unknown" { - if (!support) { - return "unknown"; - } - - let { flags, version_added, version_removed, partial_implementation } = - getCurrentSupport(support)!; - - let className; - if (version_added === null) { - className = "unknown"; - } else if (versionIsPreview(version_added, browser)) { - className = "preview"; - } else if (version_added) { - className = "yes"; - if (version_removed || (flags && flags.length)) { - className = "no"; - } - } else { - className = "no"; - } - if (partial_implementation) { - className = version_removed ? "removed-partial" : "partial"; - } - - return className; -} - -function StatusIcons({ status }: { status: BCD.StatusBlock }) { - const icons = [ - status.experimental && { - title: "Experimental. Expect behavior to change in the future.", - text: "Experimental", - iconClassName: "icon-experimental", - }, - status.deprecated && { - title: "Deprecated. Not for use in new websites.", - text: "Deprecated", - iconClassName: "icon-deprecated", - }, - !status.standard_track && { - title: "Non-standard. Expect poor cross-browser support.", - text: "Non-standard", - iconClassName: "icon-nonstandard", - }, - ].filter(isTruthy); - return icons.length === 0 ? null : ( -
- {icons.map((icon) => ( - - {icon.text} - - ))} -
- ); -} - -function labelFromString( - version: string | boolean | null | undefined, - browser: BCD.BrowserStatement -) { - if (typeof version !== "string") { - return <>{"?"}; - } - // Treat BCD ranges as exact versions to avoid confusion for the reader - // See https://github.com/mdn/yari/issues/3238 - if (version.startsWith("≤")) { - return <>{version.slice(1)}; - } - if (version === "preview") { - return browser.preview_name; - } - return <>{version}; -} - -function versionLabelFromSupport( - added: string | boolean | null | undefined, - removed: string | boolean | null | undefined, - browser: BCD.BrowserStatement -) { - if (typeof removed !== "string") { - return <>{labelFromString(added, browser)}; - } - return ( - <> - {labelFromString(added, browser)} –  - {labelFromString(removed, browser)} - - ); -} - -const CellText = React.memo( - ({ - support, - browser, - timeline = false, - }: { - support: BCD.SupportStatement | undefined; - browser: BCD.BrowserStatement; - timeline?: boolean; - }) => { - const currentSupport = getCurrentSupport(support); - - const added = currentSupport?.version_added ?? null; - const lastVersion = currentSupport?.version_last ?? null; - - const browserReleaseDate = currentSupport?.release_date; - const supportClassName = getSupportClassName(support, browser); - - let status: - | { isSupported: "unknown" } - | { - isSupported: "no" | "yes" | "partial" | "preview" | "removed-partial"; - label?: React.ReactNode; - }; - switch (added) { - case null: - status = { isSupported: "unknown" }; - break; - case true: - status = { isSupported: lastVersion ? "no" : "yes" }; - break; - case false: - status = { isSupported: "no" }; - break; - case "preview": - status = { isSupported: "preview" }; - break; - default: - status = { - isSupported: supportClassName, - label: versionLabelFromSupport(added, lastVersion, browser), - }; - break; - } - - let label: string | React.ReactNode; - let title = ""; - switch (status.isSupported) { - case "yes": - title = "Full support"; - label = status.label || "Yes"; - break; - - case "partial": - title = "Partial support"; - label = status.label || "Partial"; - break; - - case "removed-partial": - if (timeline) { - title = "Partial support"; - label = status.label || "Partial"; - } else { - title = "No support"; - label = status.label || "No"; - } - break; - - case "no": - title = "No support"; - label = status.label || "No"; - break; - - case "preview": - title = "Preview support"; - label = status.label || browser.preview_name; - break; - - case "unknown": - title = "Support unknown"; - label = "?"; - break; - } - - title = `${browser.name} – ${title}`; - - return ( -
-
- - - {title} - - -
-
- {browser.name} - - {label} - {browserReleaseDate && timeline - ? ` (Released ${browserReleaseDate})` - : ""} - -
- -
- ); - } -); - -function Icon({ name }: { name: string }) { - const title = LEGEND_LABELS[name] ?? name; - - return ( - - {name} - - - ); -} - -function CellIcons({ support }: { support: BCD.SupportStatement | undefined }) { - const supportItem = getCurrentSupport(support); - if (!supportItem) { - return null; - } - - const icons = [ - supportItem.prefix && , - hasNoteworthyNotes(supportItem) && , - supportItem.alternative_name && , - supportItem.flags && , - hasMore(support) && , - ].filter(Boolean); - - return icons.length ?
{icons}
: null; -} - -function FlagsNote({ - supportItem, - browser, -}: { - supportItem: BCD.SimpleSupportStatement; - browser: BCD.BrowserStatement; -}) { - const hasAddedVersion = typeof supportItem.version_added === "string"; - const hasRemovedVersion = typeof supportItem.version_removed === "string"; - const flags = supportItem.flags || []; - return ( - <> - {hasAddedVersion && `From version ${supportItem.version_added}`} - {hasRemovedVersion && ( - <> - {hasAddedVersion ? " until" : "Until"} version{" "} - {supportItem.version_removed} (exclusive) - - )} - {hasAddedVersion || hasRemovedVersion ? ": this" : "This"} feature is - behind the{" "} - {flags.map((flag, i) => { - const valueToSet = flag.value_to_set && ( - <> - {" "} - (needs to be set to {flag.value_to_set}) - - ); - return ( - - {flag.name} - {flag.type === "preference" && <> preference{valueToSet}} - {flag.type === "runtime_flag" && <> runtime flag{valueToSet}} - {i < flags.length - 1 && " and the "} - - ); - })} - . - {browser.pref_url && - flags.some((flag) => flag.type === "preference") && - ` To change preferences in ${browser.name}, visit ${browser.pref_url}.`} - - ); -} - -function getNotes( - browser: BCD.BrowserStatement, - support: BCD.SupportStatement -) { - if (support) { - return asList(support) - .slice() - .reverse() - .flatMap((item, i) => { - const supportNotes = [ - item.version_removed && - !asList(support).some( - (otherItem) => otherItem.version_added === item.version_removed - ) - ? { - iconName: "footnote", - label: ( - <> - Removed in {labelFromString(item.version_removed, browser)}{" "} - and later - - ), - } - : null, - item.partial_implementation - ? { - iconName: "footnote", - label: "Partial support", - } - : null, - item.prefix - ? { - iconName: "prefix", - label: `Implemented with the vendor prefix: ${item.prefix}`, - } - : null, - item.alternative_name - ? { - iconName: "altname", - label: `Alternate name: ${item.alternative_name}`, - } - : null, - item.flags - ? { - iconName: "disabled", - label: , - } - : null, - item.notes - ? (Array.isArray(item.notes) ? item.notes : [item.notes]).map( - (note) => ({ iconName: "footnote", label: note }) - ) - : null, - item.impl_url - ? (Array.isArray(item.impl_url) - ? item.impl_url - : [item.impl_url] - ).map((impl_url) => ({ - iconName: "footnote", - label: ( - <> - See {bugURLToString(impl_url)}. - - ), - })) - : null, - versionIsPreview(item.version_added, browser) - ? { - iconName: "footnote", - label: "Preview browser support", - } - : null, - // If we encounter nothing else than the required `version_added` and - // `release_date` properties, assume full support. - // EDIT 1-5-21: if item.version_added doesn't exist, assume no support. - isFullySupportedWithoutLimitation(item) && - !versionIsPreview(item.version_added, browser) - ? { - iconName: "footnote", - label: "Full support", - } - : isNotSupportedAtAll(item) - ? { - iconName: "footnote", - label: "No support", - } - : null, - ] - .flat() - .filter(isTruthy); - - const hasNotes = supportNotes.length > 0; - return ( - (i === 0 || hasNotes) && ( - -
-
- -
- {supportNotes.map(({ iconName, label }, i) => { - return ( -
- {" "} - {typeof label === "string" ? ( - - ) : ( - label - )} -
- ); - })} - {!hasNotes &&
} -
-
- ) - ); - }) - .filter(isTruthy); - } -} - -function CompatCell({ - browserId, - browserInfo, - support, - showNotes, - onToggle, - locale, -}: { - browserId: BCD.BrowserName; - browserInfo: BCD.BrowserStatement; - support: BCD.SupportStatement | undefined; - showNotes: boolean; - onToggle: () => void; - locale: string; -}) { - const supportClassName = getSupportClassName(support, browserInfo); - // NOTE: 1-5-21, I've forced hasNotes to return true, in order to - // make the details view open all the time. - // Whenever the support statement is complex (array with more than one entry) - // or if a single entry is complex (prefix, notes, etc.), - // we need to render support details in `bc-history` - // const hasNotes = - // support && - // (asList(support).length > 1 || - // asList(support).some( - // (item) => - // item.prefix || item.notes || item.alternative_name || item.flags - // )); - const notes = getNotes(browserInfo, support!); - const content = ( - <> - - {showNotes && ( -
{notes}
- )} - - ); - - return ( - <> - onToggle() : undefined} - > - - - - ); -} - -export const FeatureRow = React.memo( - ({ - index, - feature, - browsers, - activeCell, - onToggleCell, - locale, - }: { - index: number; - feature: { - name: string; - compat: BCD.CompatStatement; - depth: number; - }; - browsers: BCD.BrowserName[]; - activeCell: number | null; - onToggleCell: ([row, column]: [number, number]) => void; - locale: string; - }) => { - const browserInfo = useContext(BrowserInfoContext); - - if (!browserInfo) { - throw new Error("Missing browser info"); - } - - const { name, compat, depth } = feature; - const title = compat.description ? ( - - ) : ( - {name} - ); - const activeBrowser = activeCell !== null ? browsers[activeCell] : null; - - let titleNode: string | React.ReactNode; - - if (compat.mdn_url && depth > 0) { - const href = compat.mdn_url.replace( - `/${DEFAULT_LOCALE}/docs`, - `/${locale}/docs` - ); - titleNode = ( - ${href}`} - > - {title} - {compat.status && } - - ); - } else { - titleNode = ( -
- {title} - {compat.status && } -
- ); - } - - return ( - <> - - - {titleNode} - - {browsers.map((browser, i) => ( - onToggleCell([index, i])} - locale={locale} - /> - ))} - - {activeBrowser && ( - - -
- {getNotes( - browserInfo[activeBrowser], - compat.support[activeBrowser]! - )} -
- - - )} - - ); - } -); diff --git a/client/src/document/ingredients/browser-compatibility-table/headers.tsx b/client/src/document/ingredients/browser-compatibility-table/headers.tsx deleted file mode 100644 index 5783a3984370..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/headers.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type BCD from "@mdn/browser-compat-data/types"; -import { BrowserName } from "./browser-info"; - -function PlatformHeaders({ - platforms, - browsers, - browserInfo, -}: { - platforms: string[]; - browsers: BCD.BrowserName[]; - browserInfo: BCD.Browsers; -}) { - return ( - - - {platforms.map((platform) => { - // Get the intersection of browsers in the `browsers` array and the - // `PLATFORM_BROWSERS[platform]`. - const browsersInPlatform = browsers.filter( - (browser) => browserInfo[browser].type === platform - ); - const browserCount = browsersInPlatform.length; - return ( - - - {platform} - - ); - })} - - ); -} - -function BrowserHeaders({ browsers }: { browsers: BCD.BrowserName[] }) { - return ( - - - {browsers.map((browser) => { - return ( - -
- -
-
- - ); - })} - - ); -} - -export function browserToIconName(browser: string) { - if (browser.startsWith("firefox")) { - return "simple-firefox"; - } else if (browser === "webview_android") { - return "webview"; - } else if (browser === "webview_ios") { - return "safari"; - } else { - const browserStart = browser.split("_")[0]; - return browserStart; - } -} - -export function Headers({ - platforms, - browsers, - browserInfo, -}: { - platforms: string[]; - browsers: BCD.BrowserName[]; - browserInfo: BCD.Browsers; -}) { - return ( - - - - - ); -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index-desktop-md.scss b/client/src/document/ingredients/browser-compatibility-table/index-desktop-md.scss deleted file mode 100644 index 048daa0ca061..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index-desktop-md.scss +++ /dev/null @@ -1,13 +0,0 @@ -@use "../../../ui/vars" as *; - -@media (min-width: $screen-md) { - .table-container { - width: calc(100% + 6rem); - } - - .bc-table { - tbody th { - width: 20%; - } - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index-desktop-xl.scss b/client/src/document/ingredients/browser-compatibility-table/index-desktop-xl.scss deleted file mode 100644 index 901f96fe73e1..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index-desktop-xl.scss +++ /dev/null @@ -1,12 +0,0 @@ -@use "../../../ui/vars" as *; - -@media (min-width: $screen-xl) { - .table-container { - margin: 0; - width: 100%; - } - - .table-container-inner { - padding: 0; - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index-desktop.scss b/client/src/document/ingredients/browser-compatibility-table/index-desktop.scss deleted file mode 100644 index bfdec05111d1..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index-desktop.scss +++ /dev/null @@ -1,99 +0,0 @@ -@use "../../../ui/vars" as *; - -// Style for desktop. - -@media (min-width: $screen-sm) { - .bc-table { - thead { - display: table-header-group; - - .bc-platforms { - th { - vertical-align: revert; - } - } - } - - td, - th { - background: inherit; - padding: 0.25rem; - width: 2rem; - } - - td.bc-support { - padding: 0; - - > button { - padding: 0.25rem; - } - } - - tr.bc-history-desktop { - display: table-row; - } - } - - .table-container { - margin: 0 -3rem; - overflow: auto; - width: 100vw; - } - - .table-container-inner { - min-width: max-content; - padding: 0 3rem; - position: relative; - - &:after { - bottom: 0; - content: ""; - height: 10px; - position: absolute; - right: 0; - width: 10px; - } - } - - .bc-support-level, - .bc-browser-name { - display: none; - } - - .bc-notes-list { - margin-left: 20%; - width: auto; - } - - .bc-support { - .bc-support-level { - display: none; - } - - &[aria-expanded="true"] { - position: relative; - - &:after { - background: var(--text-primary); - bottom: -1px; - content: ""; - height: 2px; - left: 0; - position: absolute; - width: 100%; - } - - .bc-history-mobile { - display: none; - } - } - } - - .bc-has-history { - cursor: pointer; - - &:hover { - background: var(--background-secondary); - } - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index-mobile.scss b/client/src/document/ingredients/browser-compatibility-table/index-mobile.scss deleted file mode 100644 index e7da25d0789d..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index-mobile.scss +++ /dev/null @@ -1,33 +0,0 @@ -@use "../../../ui/vars" as *; - -// Style for mobile. - -@media (max-width: $screen-sm - 1px) { - .bc-table { - thead { - display: none; - } - - td.bc-support { - border-left-width: 0; - display: block; - } - - .bc-feature, - .bc-support > button, - .bc-history > td { - align-content: center; - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - } - - .bc-history-desktop { - display: none; - } - } - - .table-container { - overflow-x: auto; - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index.scss b/client/src/document/ingredients/browser-compatibility-table/index.scss deleted file mode 100644 index fef506359f7b..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index.scss +++ /dev/null @@ -1,453 +0,0 @@ -@use "sass:meta"; -@use "~@mdn/minimalist/sass/mixins/utils" as *; -@use "../../../ui/vars" as *; - -// Style for mobile *and* desktop. - -.bc-table { - border: 1px solid var(--border-primary); - border-collapse: separate; - border-radius: var(--elem-radius); - border-spacing: 0; - margin: 1rem 0; - width: 100%; - - td, - th { - border: 1px solid var(--border-secondary); - border-width: 0 0 1px 1px; - font-weight: 500; - padding: 0; - - @media (min-width: $screen-md) { - font-size: var(--type-smaller-font-size); - padding: 0.4rem; - - code { - font-size: var(--type-smaller-font-size); - } - } - } - - th { - background: var(--background-primary); - padding: 0.4rem; - vertical-align: bottom; - } - - // these props allow us to add border-radius to the table. - // border-collapse: separate gets in the way of this - // being easy. - tbody { - tr { - height: 3rem; - - @media (min-width: $screen-md) { - &:last-child { - th, - td { - border-bottom-width: 0; - } - } - } - - th { - border-left-width: 0; - vertical-align: middle; - } - } - - .bc-support { - vertical-align: top; - - button { - cursor: pointer; - width: 100%; - } - - &.bc-supports-no > button > span { - color: var(--text-primary-red); - } - - &.bc-supports-partial > button > span { - color: var(--text-primary-yellow); - } - - &.bc-supports-preview > button > span { - color: var(--text-primary-blue); - } - - &.bc-supports-yes > button > span { - color: var(--text-primary-green); - } - } - - .bc-history { - td { - border-left-width: 0; - } - - .icon.icon-removed-partial { - // override icon - mask-image: url("../../../assets/icons/partial.svg"); - } - } - } - - .bc-supports { - margin-bottom: 1rem; - - .icon-wrap { - background: var(--background-primary); - } - } - - .bc-supports.bc-supports-removed-partial { - .bcd-cell-text-copy { - color: var(--text-primary-yellow); - } - } - - .icon-wrap { - .bc-support-level { - @include visually-hidden; - } - } - - .bc-support { - > button > .icon-wrap { - display: block; - } - - .icon.icon-removed-partial { - background-color: var(--icon-critical); - // override icon - mask-image: url("../../../assets/icons/no.svg"); - } - } - - .bc-support.bc-supports-removed-partial { - .bcd-cell-text-copy { - color: var(--text-primary-red); - } - } - - .bc-feature-depth-2 { - border-left-width: 8px; - } - - .bc-feature-depth-3 { - border-left-width: 16px; - } -} - -.bc-head-txt-label { - left: calc(50% - 0.5rem); - line-height: 1; - padding-top: 0.5rem; - position: relative; - text-orientation: sideways; - transform: rotate(180deg); - white-space: nowrap; - -ms-writing-mode: tb-rl; - -webkit-writing-mode: vertical-rl; - writing-mode: vertical-rl; -} - -.bc-head-icon-symbol { - margin-bottom: 0.3rem; -} - -.bc-support { - text-align: center; - vertical-align: middle; -} - -.bc-level-no { - background-color: var(--icon-critical); -} - -.bc-level-preview { - background-color: var(--icon-information); -} - -.bc-legend-items-container { - display: flex; - flex-wrap: wrap; - font-size: var(--type-smaller-font-size); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.bc-legend-tip { - font-size: var(--type-smaller-font-size); - font-style: italic; - font-variation-settings: "slnt" -10; - margin-bottom: 1rem; - margin-top: 0; -} - -.bc-legend-item { - align-items: center; - display: flex; - gap: 0.5rem; -} - -.bc-legend-item-dt { - display: flex; - - .icon { - background-color: var(--icon-primary); - } -} - -// Row with desktop / mobile icons. -.bc-platforms { - height: 2rem; - - th { - text-align: center; - } - - td { - border: none; - } -} - -// Row with browser names. -.bc-browsers { - th { - text-align: center; - } - - td { - border-width: 0 0 1px; - } -} - -.bc-notes-list { - margin: 0.5rem 0; - position: relative; - text-align: left; - width: 100%; - - &:before { - background: var(--border-primary); - content: ""; - height: calc(100% - 0.25rem); - left: 7px; - margin-top: 0.25rem; - position: absolute; - width: 2px; - z-index: -1; - } - - // complicated selector to cover the last bit of the grey line above. - .bc-notes-wrapper:last-child dd:last-child { - position: relative; - - &:before { - background: var(--background-primary); - bottom: 0; - content: ""; - height: calc(100% - 6px); - left: 7px; - position: absolute; - width: 2px; - z-index: -1; - } - } - - .bc-level-yes.icon.icon-yes { - // override icon - background-color: var(--icon-success); - mask-image: url("../../../assets/icons/yes-circle.svg"); - } - - .bc-supports-dd { - .icon { - background: var(--border-primary); - border: 3px solid var(--background-primary); - border-radius: 50%; - mask-image: none; - } - } - - .bc-version-label { - display: inline; - } - - abbr { - margin-right: 4px; - } - - dd { - margin-bottom: 1rem; - padding-left: 1.5rem; - text-indent: -1.5rem; - - &:last-child { - margin-bottom: 2rem; - } - } -} - -.bc-notes-wrapper { - color: var(--text-primary); - margin-bottom: 1rem; - - &:last-child { - margin-bottom: 0; - } -} - -dl.bc-notes-list { - dt.bc-supports { - margin-top: 1rem; - - &:first-child { - margin-top: 0; - } - } - - dd.bc-supports-dd { - margin-bottom: 1rem; - - &:last-child { - margin-bottom: 0; - } - } -} - -.offscreen, -.only-icon span { - @include visually-hidden(); -} - -.bc-table-row-header { - align-items: baseline; - display: inline-flex; - width: 100%; - - code { - overflow: hidden; - } - - .left-side, - .right-side { - overflow: hidden; - white-space: pre; - } - - /* Can only flex-shrink and not flex-grow - ie the "slider" in a sliding glass door */ - .left-side { - flex: 0 1 auto; - text-overflow: ellipsis; - } - /* Can flex-grow and not flex-shrink as - its the stationary portion */ - .right-side { - flex: 1 0 auto; - } - - .bc-icons { - display: flex; - gap: 0.5rem; - margin-top: 0.25rem; - - .icon { - background-color: var(--icon-secondary); - - &:hover { - background-color: var(--icon-primary); - } - } - } -} - -.bc-github-link { - font: var(--type-smaller-font-size); -} - -.main-page-content { - .bc-legend { - dd, - dt { - margin-bottom: 0; - margin-left: 0; - margin-top: 0; - } - } - - .bc-supports-dd { - margin: 0; - } -} - -@include meta.load-css("index-mobile"); -@include meta.load-css("index-desktop"); -@include meta.load-css("index-desktop-md"); -@include meta.load-css("index-desktop-xl"); - -.bcd-cell-text-wrapper { - display: flex; - flex-direction: row; - gap: 0.5rem; - - @media (min-width: $screen-md) { - align-items: center; - flex-direction: column; - } -} - -.bcd-timeline-cell-text-wrapper { - display: flex; - flex-direction: row; - gap: 0.5rem; -} - -.bcd-cell-text-copy { - color: var(--text-primary); - display: flex; - gap: 0.5rem; -} - -.bc-supports-yes { - .bcd-cell-text-copy { - color: var(--text-primary-green); - } -} - -.bc-supports-no { - .bcd-cell-text-copy { - color: var(--text-primary-red); - } -} - -.bc-supports-partial { - .bcd-cell-text-copy { - color: var(--text-primary-yellow); - } -} - -.bcd-cell-icons { - display: flex; - gap: 0.5rem; - - @media (min-width: $screen-md) { - display: block; - } -} - -@media (min-width: $screen-md) { - .bc-table { - td { - height: 2rem; - } - - td.bc-support > button { - padding: 0.5rem 0.25rem; - } - } -} diff --git a/client/src/document/ingredients/browser-compatibility-table/index.tsx b/client/src/document/ingredients/browser-compatibility-table/index.tsx deleted file mode 100644 index 69ee3605a49d..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/index.tsx +++ /dev/null @@ -1,329 +0,0 @@ -import React, { useReducer, useRef } from "react"; -import { useLocation } from "react-router-dom"; -import type BCD from "@mdn/browser-compat-data/types"; -import { BrowserInfoContext } from "./browser-info"; -import { BrowserCompatibilityErrorBoundary } from "./error-boundary"; -import { FeatureRow } from "./feature-row"; -import { Headers } from "./headers"; -import { Legend } from "./legend"; -import { - getCurrentSupport, - hasMore, - hasNoteworthyNotes, - listFeatures, - SupportStatementExtended, - versionIsPreview, -} from "./utils"; -import { useViewed } from "../../../hooks"; -import { BCD_TABLE } from "../../../telemetry/constants"; -import { useGleanClick } from "../../../telemetry/glean-context"; - -// Note! Don't import any SCSS here inside *this* component. -// It's done in the component that lazy-loads this component. - -// This string is used to prefill the body when clicking to file a new BCD -// issue over on github.com/mdn/browser-compat-data -const ISSUE_METADATA_TEMPLATE = ` - -
-MDN page report details - -* Query: \`$QUERY_ID\` -* Report started: $DATE - -
-`; - -export const HIDDEN_BROWSERS = ["ie"]; - -/** - * Return a list of platforms and browsers that are relevant for this category & - * data. - * - * If the category is "webextensions", only those are shown. In all other cases - * at least the entirety of the "desktop" and "mobile" platforms are shown. If - * the category is JavaScript, the entirety of the "server" category is also - * shown. In all other categories, if compat data has info about Deno / Node.js - * those are also shown. Deno is always shown if Node.js is shown. - */ -export function gatherPlatformsAndBrowsers( - category: string, - data: BCD.Identifier, - browserInfo: BCD.Browsers -): [string[], BCD.BrowserName[]] { - const hasNodeJSData = data.__compat && "nodejs" in data.__compat.support; - const hasDenoData = data.__compat && "deno" in data.__compat.support; - - let platforms = ["desktop", "mobile"]; - if (category === "javascript" || hasNodeJSData || hasDenoData) { - platforms.push("server"); - } - - let browsers: BCD.BrowserName[] = []; - - // Add browsers in platform order to align table cells - for (const platform of platforms) { - browsers.push( - ...(Object.keys(browserInfo).filter( - (browser) => browserInfo[browser].type === platform - ) as BCD.BrowserName[]) - ); - } - - // Filter WebExtension browsers in corresponding tables. - if (category === "webextensions") { - browsers = browsers.filter( - (browser) => browserInfo[browser].accepts_webextensions - ); - } - - // If there is no Node.js data for a category outside of "javascript", don't - // show it. It ended up in the browser list because there is data for Deno. - if (category !== "javascript" && !hasNodeJSData) { - browsers = browsers.filter((browser) => browser !== "nodejs"); - } - - // Hide Internet Explorer compatibility data - browsers = browsers.filter((browser) => !HIDDEN_BROWSERS.includes(browser)); - - return [platforms, [...browsers]]; -} - -type CellIndex = [number, number]; - -function FeatureListAccordion({ - features, - browsers, - browserInfo, - locale, - query, -}: { - features: ReturnType; - browsers: BCD.BrowserName[]; - browserInfo: BCD.Browsers; - locale: string; - query: string; -}) { - const [[activeRow, activeColumn], dispatchCellToggle] = useReducer< - React.Reducer - >( - ([activeRow, activeColumn], [row, column]) => - activeRow === row && activeColumn === column - ? [null, null] - : [row, column], - [null, null] - ); - - const gleanClick = useGleanClick(); - const clickedCells = useRef(new Set()); - - return ( - <> - {features.map((feature, i) => ( - { - dispatchCellToggle([row, column]); - - const cell = `${column}:${row}`; - if (clickedCells.current.has(cell)) { - return; - } else { - clickedCells.current.add(cell); - } - - const feature = features[row]; - const browser = browsers[column]; - const support = feature.compat.support[browser]; - - function getCurrentSupportType( - support: SupportStatementExtended | undefined, - browser: BCD.BrowserStatement - ): - | "no" - | "yes" - | "partial" - | "preview" - | "removed" - | "removed-partial" - | "unknown" { - if (!support) { - return "unknown"; - } - - const currentSupport = getCurrentSupport(support)!; - - const { - flags, - version_added, - version_removed, - partial_implementation, - } = currentSupport; - - if (version_added === null) { - return "unknown"; - } else if (versionIsPreview(version_added, browser)) { - return "preview"; - } else if (version_added) { - if (version_removed) { - if (partial_implementation) { - return "removed-partial"; - } else { - return "removed"; - } - } else if (flags && flags.length) { - return "no"; - } else if (partial_implementation) { - return "partial"; - } else { - return "yes"; - } - } else { - return "no"; - } - } - - function getCurrentSupportAttributes( - support: SupportStatementExtended | undefined - ): string[] { - const supportItem = getCurrentSupport(support); - - if (!supportItem) { - return []; - } - - return [ - !!supportItem.prefix && "pre", - hasNoteworthyNotes(supportItem) && "note", - !!supportItem.alternative_name && "alt", - !!supportItem.flags && "flag", - hasMore(support) && "more", - ].filter((value) => typeof value === "string"); - } - - const supportType = getCurrentSupportType( - support, - browserInfo[browser] - ); - const attrs = getCurrentSupportAttributes(support); - - gleanClick( - `${BCD_TABLE}: click ${browser} ${query} -> ${feature.name} = ${supportType} [${attrs.join(",")}]` - ); - }} - locale={locale} - /> - ))} - - ); -} - -export default function BrowserCompatibilityTable({ - query, - data, - browsers: browserInfo, - locale, -}: { - query: string; - data: BCD.Identifier; - browsers: BCD.Browsers; - locale: string; -}) { - const location = useLocation(); - const gleanClick = useGleanClick(); - - const observedNode = useViewed( - () => { - gleanClick(`${BCD_TABLE}: view -> ${query}`); - }, - { - threshold: 0, - } - ); - - if (!data || !Object.keys(data).length) { - throw new Error( - "BrowserCompatibilityTable component called with empty data" - ); - } - - const breadcrumbs = query.split("."); - const category = breadcrumbs[0]; - const name = breadcrumbs[breadcrumbs.length - 1]; - - const [platforms, browsers] = gatherPlatformsAndBrowsers( - category, - data, - browserInfo - ); - - function getNewIssueURL() { - const url = "https://github.com/mdn/browser-compat-data/issues/new"; - const sp = new URLSearchParams(); - const metadata = ISSUE_METADATA_TEMPLATE.replace( - /\$DATE/g, - new Date().toISOString() - ) - .replace(/\$QUERY_ID/g, query) - .trim(); - sp.set("mdn-url", `https://developer.mozilla.org${location.pathname}`); - sp.set("metadata", metadata); - sp.set("title", `${query} - `); - sp.set("template", "data-problem.yml"); - return `${url}?${sp.toString()}`; - } - - return ( - - - - Report problems with this compatibility data on GitHub - -
-
- - - - - -
-
-
- - - {/* https://github.com/mdn/yari/issues/1191 */} -
- The compatibility table on this page is generated from structured - data. If you'd like to contribute to the data, please check out{" "} - - https://github.com/mdn/browser-compat-data - {" "} - and send us a pull request. -
-
-
- ); -} diff --git a/client/src/document/ingredients/browser-compatibility-table/legend.tsx b/client/src/document/ingredients/browser-compatibility-table/legend.tsx deleted file mode 100644 index e59398f91e8a..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/legend.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { useContext } from "react"; -import type BCD from "@mdn/browser-compat-data/types"; -import { BrowserInfoContext } from "./browser-info"; -import { HIDDEN_BROWSERS } from "./index"; -import { - asList, - getFirst, - hasMore, - hasNoteworthyNotes, - listFeatures, - versionIsPreview, -} from "./utils"; - -// Also specifies the order in which the legend appears -export const LEGEND_LABELS = { - yes: "Full support", - partial: "Partial support", - preview: "In development. Supported in a pre-release version.", - no: "No support", - unknown: "Compatibility unknown", - experimental: "Experimental. Expect behavior to change in the future.", - nonstandard: "Non-standard. Check cross-browser support before using.", - deprecated: "Deprecated. Not for use in new websites.", - footnote: "See implementation notes.", - disabled: "User must explicitly enable this feature.", - altname: "Uses a non-standard name.", - prefix: "Requires a vendor prefix or different name for use.", - more: "Has more compatibility info.", -}; -type LEGEND_KEY = keyof typeof LEGEND_LABELS; - -function getActiveLegendItems( - compat: BCD.Identifier, - name: string, - browserInfo: BCD.Browsers -) { - const legendItems = new Set(); - - for (const feature of listFeatures(compat, "", name)) { - const { status } = feature.compat; - - if (status) { - if (status.experimental) { - legendItems.add("experimental"); - } - if (status.deprecated) { - legendItems.add("deprecated"); - } - if (!status.standard_track) { - legendItems.add("nonstandard"); - } - } - - for (const [browser, browserSupport] of Object.entries( - feature.compat.support - )) { - if (HIDDEN_BROWSERS.includes(browser)) { - continue; - } - if (!browserSupport) { - legendItems.add("no"); - continue; - } - const firstSupportItem = getFirst(browserSupport); - if (hasNoteworthyNotes(firstSupportItem)) { - legendItems.add("footnote"); - } - - for (const versionSupport of asList(browserSupport)) { - if (versionSupport.version_added) { - if (versionSupport.flags && versionSupport.flags.length) { - legendItems.add("no"); - } else if ( - versionIsPreview(versionSupport.version_added, browserInfo[browser]) - ) { - legendItems.add("preview"); - } else { - legendItems.add("yes"); - } - } else if (versionSupport.version_added == null) { - legendItems.add("unknown"); - } else { - legendItems.add("no"); - } - - if (versionSupport.partial_implementation) { - legendItems.add("partial"); - } - if (versionSupport.prefix) { - legendItems.add("prefix"); - } - if (versionSupport.alternative_name) { - legendItems.add("altname"); - } - if (versionSupport.flags) { - legendItems.add("disabled"); - } - } - - if (hasMore(browserSupport)) { - legendItems.add("more"); - } - } - } - return Object.keys(LEGEND_LABELS) - .filter((key) => legendItems.has(key as LEGEND_KEY)) - .map((key) => [key, LEGEND_LABELS[key]]); -} - -export function Legend({ - compat, - name, -}: { - compat: BCD.Identifier; - name: string; -}) { - const browserInfo = useContext(BrowserInfoContext); - - if (!browserInfo) { - throw new Error("Missing browser info"); - } - - return ( -
-

- Legend -

-

- Tip: you can click/tap on a cell for more information. -

-
- {getActiveLegendItems(compat, name, browserInfo).map(([key, label]) => - ["yes", "partial", "no", "unknown", "preview"].includes(key) ? ( -
-
- - - {label} - - -
-
{label}
-
- ) : ( -
-
- -
-
{label}
-
- ) - )} -
-
- ); -} diff --git a/client/src/document/ingredients/browser-compatibility-table/utils.ts b/client/src/document/ingredients/browser-compatibility-table/utils.ts deleted file mode 100644 index ceb307c925b2..000000000000 --- a/client/src/document/ingredients/browser-compatibility-table/utils.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type BCD from "@mdn/browser-compat-data/types"; - -// Extended for the fields, beyond the bcd types, that are extra-added -// exclusively in Yari. -export interface SimpleSupportStatementExtended - extends BCD.SimpleSupportStatement { - // Known for some support statements where the browser *version* is known, - // as opposed to just "true" and if the version release date is known. - release_date?: string; - // The version before the version_removed if the *version* removed is known, - // as opposed to just "true". Otherwise the version_removed. - version_last?: BCD.VersionValue; -} - -export type SupportStatementExtended = - | SimpleSupportStatementExtended - | SimpleSupportStatementExtended[]; - -export function getFirst(a: T | T[]): T; -export function getFirst(a: T | T[] | undefined): T | undefined { - return Array.isArray(a) ? a[0] : a; -} - -export function asList(a: T | T[]): T[] { - return Array.isArray(a) ? a : [a]; -} - -export function isTruthy(t: T | false | undefined | null): t is T { - return Boolean(t); -} - -interface Feature { - name: string; - compat: BCD.CompatStatement; - depth: number; -} - -function findFirstCompatDepth(identifier: BCD.Identifier) { - const entries = [["", identifier]]; - - while (entries.length) { - const [path, value] = entries.shift() as [string, BCD.Identifier]; - if (value.__compat) { - // Following entries have at least this depth. - return path.split(".").length; - } - - for (const key of Object.keys(value)) { - const subpath = path ? `${path}.${key}` : key; - entries.push([subpath, value[key]]); - } - } - - // Fallback. - return 0; -} - -export function listFeatures( - identifier: BCD.Identifier, - parentName: string = "", - rootName: string = "", - depth: number = 0, - firstCompatDepth: number = 0 -): Feature[] { - const features: Feature[] = []; - if (rootName && identifier.__compat) { - features.push({ - name: rootName, - compat: identifier.__compat, - depth, - }); - } - if (rootName) { - firstCompatDepth = findFirstCompatDepth(identifier); - } - for (const subName of Object.keys(identifier)) { - if (subName === "__compat") { - continue; - } - const subIdentifier = identifier[subName]; - if (subIdentifier.__compat) { - features.push({ - name: parentName ? `${parentName}.${subName}` : subName, - compat: subIdentifier.__compat, - depth: depth + 1, - }); - } - if (subIdentifier.__compat || depth + 1 < firstCompatDepth) { - features.push( - ...listFeatures(subIdentifier, subName, "", depth + 1, firstCompatDepth) - ); - } - } - return features; -} - -export function hasMore(support: BCD.SupportStatement | undefined) { - return Array.isArray(support) && support.length > 1; -} - -export function versionIsPreview( - version: BCD.VersionValue | string | undefined, - browser: BCD.BrowserStatement -): boolean { - if (version === "preview") { - return true; - } - - if (browser && typeof version === "string" && browser.releases[version]) { - return ["beta", "nightly", "planned"].includes( - browser.releases[version].status - ); - } - - return false; -} - -export function hasNoteworthyNotes(support: BCD.SimpleSupportStatement) { - return ( - !!(support.notes?.length || support.impl_url?.length) && - !support.version_removed && - !support.partial_implementation - ); -} - -export function bugURLToString(url: string) { - const bugNumber = url.match( - /^https:\/\/(?:crbug\.com|webkit\.org\/b|bugzil\.la)\/([0-9]+)/i - )?.[1]; - return bugNumber ? `bug ${bugNumber}` : url; -} - -function hasLimitation(support: BCD.SimpleSupportStatement) { - return hasMajorLimitation(support) || support.notes || support.impl_url; -} - -function hasMajorLimitation(support: BCD.SimpleSupportStatement) { - return ( - support.partial_implementation || - support.alternative_name || - support.flags || - support.prefix || - support.version_removed - ); -} -export function isFullySupportedWithoutLimitation( - support: BCD.SimpleSupportStatement -) { - return support.version_added && !hasLimitation(support); -} - -export function isNotSupportedAtAll(support: BCD.SimpleSupportStatement) { - return !support.version_added && !hasLimitation(support); -} - -function isFullySupportedWithoutMajorLimitation( - support: BCD.SimpleSupportStatement -) { - return support.version_added && !hasMajorLimitation(support); -} - -// Prioritizes support items -export function getCurrentSupport( - support: SupportStatementExtended | undefined -): SimpleSupportStatementExtended | undefined { - if (!support) return undefined; - - // Full support without limitation - const noLimitationSupportItem = asList(support).find((item) => - isFullySupportedWithoutLimitation(item) - ); - if (noLimitationSupportItem) return noLimitationSupportItem; - - // Full support with only notes and version_added - const minorLimitationSupportItem = asList(support).find((item) => - isFullySupportedWithoutMajorLimitation(item) - ); - if (minorLimitationSupportItem) return minorLimitationSupportItem; - - // Full support with altname/prefix - const altnamePrefixSupportItem = asList(support).find( - (item) => !item.version_removed && (item.prefix || item.alternative_name) - ); - if (altnamePrefixSupportItem) return altnamePrefixSupportItem; - - // Partial support - const partialSupportItem = asList(support).find( - (item) => !item.version_removed && item.partial_implementation - ); - if (partialSupportItem) return partialSupportItem; - - // Support with flags only - const flagSupportItem = asList(support).find( - (item) => !item.version_removed && item.flags - ); - if (flagSupportItem) return flagSupportItem; - - // No/Inactive support - const noSupportItem = asList(support).find((item) => item.version_removed); - if (noSupportItem) return noSupportItem; - - // Default (likely never reached) - return getFirst(support); -} diff --git a/client/src/document/lazy-bcd-table.tsx b/client/src/document/lazy-bcd-table.tsx deleted file mode 100644 index 067f201c0b0a..000000000000 --- a/client/src/document/lazy-bcd-table.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React, { lazy, Suspense } from "react"; -import useSWR from "swr"; - -import { DisplayH2, DisplayH3 } from "./ingredients/utils"; -import { Loading } from "../ui/atoms/loading"; -// Because it's bad for web performance to lazy-load CSS during the initial render -// (because the page is saying "Wait! Stop rendering, now that I've downloaded -// some JS I decided I need more CSSOM to block the rendering.") -// Therefore, we import all the necessary CSS here in this file so that -// the BCD table CSS becomes part of the core bundle. -// That means that when the lazy-loading happens, it only needs to lazy-load -// the JS (and the JSON XHR fetch of course) -import "./ingredients/browser-compatibility-table/index.scss"; -import { useLocale, useIsServer } from "../hooks"; -import NoteCard from "../ui/molecules/notecards"; -import type BCD from "@mdn/browser-compat-data/types"; -import { BCD_BASE_URL } from "../env"; - -interface QueryJson { - query: string; - data: BCD.Identifier; - browsers: BCD.Browsers; -} - -const BrowserCompatibilityTable = lazy( - () => - import( - /* webpackChunkName: "browser-compatibility-table" */ "./ingredients/browser-compatibility-table" - ) -); - -export function LazyBrowserCompatibilityTable({ - id, - title, - isH3, - query, -}: { - id: string; - title: string; - isH3: boolean; - query: string; -}) { - return ( - <> - {title && !isH3 && } - {title && isH3 && } - - - ); -} - -function LazyBrowserCompatibilityTableInner({ query }: { query: string }) { - const locale = useLocale(); - const isServer = useIsServer(); - - const { error, data } = useSWR( - query, - async (query) => { - const response = await fetch( - `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json` - ); - if (!response.ok) { - throw new Error(response.status.toString()); - } - return (await response.json()) as QueryJson; - }, - { revalidateOnFocus: false } - ); - - if (isServer) { - return ( -

- BCD tables only load in the browser - -

- ); - } - if (error) { - if (error.message === "404") { - return ( - -

- No compatibility data found for {query}.
- Check for problems with this page or - contribute missing data to{" "} - - mdn/browser-compat-data - - . -

-
- ); - } - return

Error loading BCD data

; - } - if (!data) { - return ; - } - - return ( - - }> - - - - ); -} - -type ErrorBoundaryProps = { children?: React.ReactNode }; -type ErrorBoundaryState = { - error: Error | null; -}; - -class ErrorBoundary extends React.Component< - ErrorBoundaryProps, - ErrorBoundaryState -> { - constructor(props: ErrorBoundaryProps) { - super(props); - this.state = { error: null }; - } - - static getDerivedStateFromError(error: Error) { - return { error }; - } - - // componentDidCatch(error: Error, errorInfo) { - // console.log({ error, errorInfo }); - // } - - render() { - if (this.state.error) { - return ( - -

- Error loading browser compatibility table -

-

- This can happen if the JavaScript, which is loaded later, didn't - successfully load. -

-

- { - event.preventDefault(); - window.location.reload(); - }} - > - Try reloading the page - -

-
-

- If you're curious, this was the error: -
- - {this.state.error.toString()} - -

-
- ); - } - - return this.props.children; - } -} From 031837985bb42240e58db6e56f60ea0ccfb2ec3c Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 11:20:12 +0100 Subject: [PATCH 35/69] chore(compat): remove data-test attribute --- client/src/lit/compat/compat-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index c59fb4f7ce00..a009159314f3 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -409,7 +409,7 @@ class CompatTable extends LitElement { return icons.length === 0 ? null - : html`
+ : html`
${icons.map( (icon) => html` Date: Mon, 17 Mar 2025 11:29:31 +0100 Subject: [PATCH 36/69] chore(compat): refine icon positioning Also avoids underlined white space. --- client/src/lit/compat/compat-table.js | 4 ++-- client/src/lit/compat/index.scss | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index a009159314f3..f09d75ccd471 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -265,8 +265,8 @@ class CompatTable extends LitElement { : html`${name}`; let titleNode; - const titleContent = html` ${title} - ${compat.status && this.renderStatusIcons(compat.status)}`; + const titleContent = html`${title}${compat.status && + this.renderStatusIcons(compat.status)}`; if (compat.mdn_url && depth > 0) { const href = compat.mdn_url.replace( `/${DEFAULT_LOCALE}/docs`, diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index e3754dc4b7e7..b43bafc04e00 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -418,8 +418,9 @@ dl.bc-notes-list { .bc-icons { display: inline-flex; - gap: 0.5rem; - margin-top: 0.25rem; + gap: 0.5ch; + margin-left: 0.5ch; + vertical-align: text-top; .icon { background-color: var(--icon-secondary); From 917228046151da53186637d52c6f5d3df0a02574 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 12:11:21 +0100 Subject: [PATCH 37/69] fix(compat): use partial icon in timeline --- client/src/lit/compat/index.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index b43bafc04e00..5c8ed70b374f 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -177,6 +177,11 @@ table { // override icon mask-image: url("../../assets/icons/no.svg"); } + + .timeline .icon.icon-removed-partial { + background-color: var(--icon-primary); + mask-image: url("../../assets/icons/partial.svg"); + } } .bc-support.bc-supports-removed-partial { From 726b87e4324061c66e59c9a48ba76c78152c04c8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 13:50:37 +0100 Subject: [PATCH 38/69] chore(compat): remove duplicate browser.name --- client/src/lit/compat/compat-table.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index f09d75ccd471..e480c79b77a0 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -670,7 +670,6 @@ class CompatTable extends LitElement { ? `${browser.name} ${added} – Released ${browserReleaseDate}` : ""} > - ${timeline && false ? browser.name : null} ${!timeline || browserReleaseDate ? label : null} ${browserReleaseDate && timeline ? ` (Released ${browserReleaseDate})` From 3450ea28e11052e5bbf7e1e88ed3736bd34274b8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 13:59:31 +0100 Subject: [PATCH 39/69] fix(compat): show preview version in timeline without release date --- client/src/lit/compat/compat-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index e480c79b77a0..e0aa7e9c8a8f 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -670,7 +670,7 @@ class CompatTable extends LitElement { ? `${browser.name} ${added} – Released ${browserReleaseDate}` : ""} > - ${!timeline || browserReleaseDate ? label : null} + ${!timeline || added ? label : null} ${browserReleaseDate && timeline ? ` (Released ${browserReleaseDate})` : ""} From 1206cde1b40754d98cada77aad614eac61f94da0 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 14:19:45 +0100 Subject: [PATCH 40/69] enhance(compat): render timeline for unknown support --- client/src/lit/compat/compat-table.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index e0aa7e9c8a8f..0e4dc5390f3a 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -313,10 +313,12 @@ class CompatTable extends LitElement { ${browsers.map((browserName) => { // const browser = browserInfo[browserName]; - const support = compat.support[browserName]; + const support = compat.support[browserName] ?? { + version_added: null, + }; const supportClassName = getSupportClassName(support, browser); - const notes = support && this.renderNotes(browser, support); + const notes = this.renderNotes(browser, support); return html` Date: Mon, 17 Mar 2025 14:20:12 +0100 Subject: [PATCH 41/69] fix(compat): show unknown in legend --- client/src/lit/compat/compat-table.js | 4 ++-- client/src/lit/compat/legend.js | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 0e4dc5390f3a..3477fcf84ef4 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -683,7 +683,7 @@ class CompatTable extends LitElement { } renderTableLegend() { - const { browserInfo } = this; + const { _browsers: browsers, browserInfo } = this; if (!browserInfo) { throw new Error("Missing browser info"); @@ -695,7 +695,7 @@ class CompatTable extends LitElement { Tip: you can click/tap on a cell for more information.

- ${getActiveLegendItems(this.data, this.name, browserInfo).map( + ${getActiveLegendItems(this.data, this.name, browserInfo, browsers).map( ([key, label]) => ["yes", "partial", "no", "unknown", "preview"].includes(key) ? html`
diff --git a/client/src/lit/compat/legend.js b/client/src/lit/compat/legend.js index ee9a72b14816..dfc74998bebe 100644 --- a/client/src/lit/compat/legend.js +++ b/client/src/lit/compat/legend.js @@ -9,7 +9,7 @@ import { } from "./utils.js"; /** - * @import { Browsers, Identifier } from "@mdn/browser-compat-data/types" + * @import { BrowserName, Browsers, Identifier } from "@mdn/browser-compat-data/types" * @typedef {"yes" | "partial" | "preview" | "no" | "unknown" | "experimental" | "nonstandard" | "deprecated" | "footnote" | "disabled" | "altname" | "prefix" | "more"} LegendKey */ @@ -39,9 +39,10 @@ export const LEGEND_LABELS = { * @param {Identifier} compat - The compatibility data identifier. * @param {string} name - The name of the feature. * @param {Browsers} browserInfo - Information about browsers. + * @param {BrowserName[]} browsers - The list of displayed browsers. * @returns {Array<[LegendKey, string]>} An array of legend item entries, where each entry is a tuple of the legend key and its label. */ -export function getActiveLegendItems(compat, name, browserInfo) { +export function getActiveLegendItems(compat, name, browserInfo, browsers) { /** @type {Set} */ const legendItems = new Set(); @@ -60,16 +61,16 @@ export function getActiveLegendItems(compat, name, browserInfo) { } } - for (const [browser, browserSupport] of Object.entries( - feature.compat.support - )) { + for (const browser of browsers) { + // @ts-ignore + const browserSupport = feature.compat.support[browser] ?? { + version_added: null, + }; + if (HIDDEN_BROWSERS.includes(browser)) { continue; } - if (!browserSupport) { - legendItems.add("no"); - continue; - } + // @ts-ignore const firstSupportItem = getFirst(browserSupport); if (hasNoteworthyNotes(firstSupportItem)) { From 25fd4dc1f6cf441db79452605a362f0c1e806212 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 14:25:03 +0100 Subject: [PATCH 42/69] enhance(compat): move GitHub links in scroll container Also only right-aligns on medium+ screens. --- client/src/lit/compat/compat-table.js | 6 ++---- client/src/lit/compat/index-desktop-md.scss | 4 ++++ client/src/lit/compat/index.scss | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 3477fcf84ef4..1a15acf8021e 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -180,6 +180,7 @@ class CompatTable extends LitElement { renderTable() { return html`
+ ${this.renderIssueLink()} Date: Mon, 17 Mar 2025 14:33:39 +0100 Subject: [PATCH 43/69] chore(compat): make feature column wider on xl screens --- client/src/lit/compat/index-desktop-xl.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/lit/compat/index-desktop-xl.scss b/client/src/lit/compat/index-desktop-xl.scss index 9b045a32b105..f02434a4a3fd 100644 --- a/client/src/lit/compat/index-desktop-xl.scss +++ b/client/src/lit/compat/index-desktop-xl.scss @@ -9,4 +9,12 @@ .table-container-inner { padding: 0; } + + .bc-table { + // 33% for feature, 67% for browser columns. + grid-template-columns: minmax(33%, max-content) repeat( + var(--browser-count), + calc(67% / var(--browser-count)) + ); + } } From 85437eb057ace10384de9ff8be193573b72e2be7 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 14:37:18 +0100 Subject: [PATCH 44/69] chore(compat): remove unused .bc-history styles --- client/src/lit/compat/index-desktop.scss | 4 ---- client/src/lit/compat/index-mobile.scss | 7 +------ client/src/lit/compat/index.scss | 11 ----------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index 13c9a5c52a3e..6d28370b6cdc 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -123,10 +123,6 @@ position: absolute; width: 100%; } - - .bc-history-mobile { - display: none; - } } } diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index a0088d3c2c2e..502aa26c46cb 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -28,17 +28,12 @@ } .bc-feature, - .bc-support > button, - .bc-history > td { + .bc-support > button { align-content: center; display: flex; flex-wrap: wrap; gap: 0.5rem; } - - .bc-history-desktop { - display: none; - } } .table-container { diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 7026054891e9..6f212a8ab71a 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -133,17 +133,6 @@ table { color: var(--text-primary-green); } } - - .bc-history { - td { - border-left-width: 0; - } - - .icon.icon-removed-partial { - // override icon - mask-image: url("../../assets/icons/partial.svg"); - } - } } .bc-supports { From 39c5ca6f092e83a8b5df43785fc57663cd7ec2ce Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 14:51:54 +0100 Subject: [PATCH 45/69] fix(compat): fix timeline entry for flags --- client/src/lit/compat/compat-table.js | 47 ++++++++++++++++----------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 1a15acf8021e..727609781939 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -476,25 +476,34 @@ class CompatTable extends LitElement { typeof item.version_removed === "string"; const flags = item.flags || []; return html` - ${hasAddedVersion && `From version ${item.version_added}`} - ${hasRemovedVersion && - `${hasAddedVersion ? " until" : "Until"} version ${item.version_removed} (exclusive)`} - ${hasAddedVersion || hasRemovedVersion ? ": this" : "This"} - feature is behind the - ${flags.map((flag, i) => { - const valueToSet = - flag.value_to_set && - html` (needs to be set to - ${flag.value_to_set}`; - return html`${flag.name} ${flag.type === - "preference" && ` preference${valueToSet}`} - ${flag.type === "runtime_flag" && - ` runtime flag${valueToSet}`} - ${i < flags.length - 1 && " and the "}`; - })} - ${browser.pref_url && - flags.some((flag) => flag.type === "preference") && - ` To change preferences in ${browser.name}, visit ${browser.pref_url}.`} + ${[ + hasAddedVersion && `From version ${item.version_added}`, + hasRemovedVersion && + `${hasAddedVersion ? " until" : "Until"} ${item.version_removed} (exclusive)`, + hasAddedVersion || hasRemovedVersion ? ": this" : "This", + " feature is behind the", + ...flags.map((flag, i) => { + const valueToSet = flag.value_to_set + ? html` (needs to be set to + ${flag.value_to_set})` + : ""; + + return [ + html`${flag.name}`, + flag.type === "preference" && + html` preference${valueToSet}`, + flag.type === "runtime_flag" && + html` runtime flag${valueToSet}`, + i < flags.length - 1 && " and the ", + ].filter(Boolean); + }), + ".", + browser.pref_url && + flags.some((flag) => flag.type === "preference") && + ` To change preferences in ${browser.name}, visit ${browser.pref_url}.`, + ] + .filter(Boolean) + .map((value) => html`${value}`)} `; })(), } From 32dbc512d1c7087adaa9aab7c5229031dec5a808 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:01:19 +0100 Subject: [PATCH 46/69] chore(compat): remove unused aria-expanded style --- client/src/lit/compat/index-desktop.scss | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index 6d28370b6cdc..c2dc98d9fae4 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -110,20 +110,6 @@ .bc-support-level { display: none; } - - &[aria-expanded="true"] { - position: relative; - - &:after { - background: var(--text-primary); - bottom: -1px; - content: ""; - height: 2px; - left: 0; - position: absolute; - width: 100%; - } - } } .bc-has-history { From 868d44137e748148adbd85d5ac2a25dfc6f5d7b2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:03:40 +0100 Subject: [PATCH 47/69] fix(compat): work around cut icon at the top --- client/src/lit/compat/index-desktop-md.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/lit/compat/index-desktop-md.scss b/client/src/lit/compat/index-desktop-md.scss index 6250590c8b66..37ea54eb75d6 100644 --- a/client/src/lit/compat/index-desktop-md.scss +++ b/client/src/lit/compat/index-desktop-md.scss @@ -20,4 +20,9 @@ calc(75% / var(--browser-count)) ); } + + .icon { + // Workaround for Icons being cut by 1px at the top. + --size: calc(1rem + 1px); + } } From 51ae4544cbff62d1cf6555126fbf9d68a2961aaa Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:12:02 +0100 Subject: [PATCH 48/69] fix(compat): merge .bc-supports styles Fixes an issue where the CSS class of the outer cell influenced the text color of the timeline cell. --- client/src/lit/compat/index.scss | 64 +++++++++++++++----------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 6f212a8ab71a..a2760d521c33 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -135,20 +135,6 @@ table { } } - .bc-supports { - margin-bottom: 1rem; - - .icon-wrap { - background: var(--background-primary); - } - } - - .bc-supports.bc-supports-removed-partial { - .bcd-cell-text-copy { - color: var(--text-primary-yellow); - } - } - .icon-wrap { .bc-support-level { @include visually-hidden; @@ -172,12 +158,6 @@ table { } } - .bc-support.bc-supports-removed-partial { - .bcd-cell-text-copy { - color: var(--text-primary-red); - } - } - .bc-feature { align-items: center; border: none; @@ -466,27 +446,41 @@ dl.bc-notes-list { gap: 0.25rem; } -.bcd-cell-text-copy { - color: var(--text-primary); - display: flex; - gap: 0.5ch; -} +.bc-supports { + margin-bottom: 1rem; -.bc-supports-yes { - .bcd-cell-text-copy { - color: var(--text-primary-green); + .icon-wrap { + background: var(--background-primary); } -} -.bc-supports-no { .bcd-cell-text-copy { - color: var(--text-primary-red); + color: var(--text-primary); + display: flex; + gap: 0.5ch; } -} -.bc-supports-partial { - .bcd-cell-text-copy { - color: var(--text-primary-yellow); + &.bc-supports-removed-partial { + .bcd-cell-text-copy { + color: var(--text-primary-red); + } + } + + &.bc-supports-yes { + .bcd-cell-text-copy { + color: var(--text-primary-green); + } + } + + &.bc-supports-no { + .bcd-cell-text-copy { + color: var(--text-primary-red); + } + } + + &.bc-supports-partial { + .bcd-cell-text-copy { + color: var(--text-primary-yellow); + } } } From 8bb9870287b2f786334bece185f4d0aef7fc5535 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:13:50 +0100 Subject: [PATCH 49/69] chore(compat): remove unused style --- client/src/lit/compat/index.scss | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index a2760d521c33..5ed40652e566 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -357,7 +357,6 @@ dl.bc-notes-list { } } -.offscreen, .only-icon span { @include visually-hidden(); } @@ -371,24 +370,6 @@ dl.bc-notes-list { overflow: hidden; } - .left-side, - .right-side { - overflow: hidden; - white-space: pre; - } - - /* Can only flex-shrink and not flex-grow - ie the "slider" in a sliding glass door */ - .left-side { - flex: 0 1 auto; - text-overflow: ellipsis; - } - /* Can flex-grow and not flex-shrink as - its the stationary portion */ - .right-side { - flex: 1 0 auto; - } - .bc-icons { display: inline-flex; gap: 0.5ch; From 445a7f41a632ccd71578f6bef32f38a06b9add80 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:16:07 +0100 Subject: [PATCH 50/69] enhance(atoms/icon): introduce --icon-color variable --- client/src/ui/atoms/icon/index.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/ui/atoms/icon/index.scss b/client/src/ui/atoms/icon/index.scss index ca6c172bebeb..0ed6ddec80e6 100644 --- a/client/src/ui/atoms/icon/index.scss +++ b/client/src/ui/atoms/icon/index.scss @@ -15,8 +15,9 @@ $icons: "twitter-x", "unknown", "warning", "webview", "yes", "yes-circle"; .icon { + --color: var(--icon-color, var(--icon-primary)); --size: var(--icon-size, 1rem); - background-color: var(--icon-primary); + background-color: var(--color); display: inline-block; flex-shrink: 0; height: var(--size); @@ -36,7 +37,7 @@ $icons: // override un-breaks those. svg.icon { background: transparent; - color: var(--icon-primary); + color: var(--color); } .icons-highlighted { From a8a9f4ff8c07b688735077ae3efeb1e0605994d8 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:20:09 +0100 Subject: [PATCH 51/69] Revert "enhance(atoms/icon): introduce --icon-color variable" This reverts commit 445a7f41a632ccd71578f6bef32f38a06b9add80. --- client/src/ui/atoms/icon/index.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/ui/atoms/icon/index.scss b/client/src/ui/atoms/icon/index.scss index 0ed6ddec80e6..ca6c172bebeb 100644 --- a/client/src/ui/atoms/icon/index.scss +++ b/client/src/ui/atoms/icon/index.scss @@ -15,9 +15,8 @@ $icons: "twitter-x", "unknown", "warning", "webview", "yes", "yes-circle"; .icon { - --color: var(--icon-color, var(--icon-primary)); --size: var(--icon-size, 1rem); - background-color: var(--color); + background-color: var(--icon-primary); display: inline-block; flex-shrink: 0; height: var(--size); @@ -37,7 +36,7 @@ $icons: // override un-breaks those. svg.icon { background: transparent; - color: var(--color); + color: var(--icon-primary); } .icons-highlighted { From dfd73af2ad970896362e26e9fef3d73fe732a67b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:20:24 +0100 Subject: [PATCH 52/69] Revert "fix(compat): merge .bc-supports styles" This reverts commit 51ae4544cbff62d1cf6555126fbf9d68a2961aaa. --- client/src/lit/compat/index.scss | 64 +++++++++++++++++--------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 5ed40652e566..10a5c0d1815b 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -135,6 +135,20 @@ table { } } + .bc-supports { + margin-bottom: 1rem; + + .icon-wrap { + background: var(--background-primary); + } + } + + .bc-supports.bc-supports-removed-partial { + .bcd-cell-text-copy { + color: var(--text-primary-yellow); + } + } + .icon-wrap { .bc-support-level { @include visually-hidden; @@ -158,6 +172,12 @@ table { } } + .bc-support.bc-supports-removed-partial { + .bcd-cell-text-copy { + color: var(--text-primary-red); + } + } + .bc-feature { align-items: center; border: none; @@ -427,41 +447,27 @@ dl.bc-notes-list { gap: 0.25rem; } -.bc-supports { - margin-bottom: 1rem; - - .icon-wrap { - background: var(--background-primary); - } +.bcd-cell-text-copy { + color: var(--text-primary); + display: flex; + gap: 0.5ch; +} +.bc-supports-yes { .bcd-cell-text-copy { - color: var(--text-primary); - display: flex; - gap: 0.5ch; - } - - &.bc-supports-removed-partial { - .bcd-cell-text-copy { - color: var(--text-primary-red); - } - } - - &.bc-supports-yes { - .bcd-cell-text-copy { - color: var(--text-primary-green); - } + color: var(--text-primary-green); } +} - &.bc-supports-no { - .bcd-cell-text-copy { - color: var(--text-primary-red); - } +.bc-supports-no { + .bcd-cell-text-copy { + color: var(--text-primary-red); } +} - &.bc-supports-partial { - .bcd-cell-text-copy { - color: var(--text-primary-yellow); - } +.bc-supports-partial { + .bcd-cell-text-copy { + color: var(--text-primary-yellow); } } From 857de5f6af1a6c7bf489b754cdf36a955a924b61 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:26:02 +0100 Subject: [PATCH 53/69] fix(compat): move .bc-supports-no below .bc-supports-partial --- client/src/lit/compat/index.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index 10a5c0d1815b..cc0075049a20 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -459,15 +459,15 @@ dl.bc-notes-list { } } -.bc-supports-no { +.bc-supports-partial { .bcd-cell-text-copy { - color: var(--text-primary-red); + color: var(--text-primary-yellow); } } -.bc-supports-partial { +.bc-supports-no { .bcd-cell-text-copy { - color: var(--text-primary-yellow); + color: var(--text-primary-red); } } From 1536f1b6676344471f942928b7dea48cf8b5f419 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:34:28 +0100 Subject: [PATCH 54/69] chore(compat): separate features in xs view --- client/src/lit/compat/index-mobile.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index 502aa26c46cb..7b9ace531ae9 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -27,6 +27,11 @@ margin-left: 0.25rem; } + tr:not(:first-of-type) .bc-feature { + // Feature separator. + border-top: 2px solid var(--border-primary); + } + .bc-feature, .bc-support > button { align-content: center; From 1a54bae1a405a1dedb0a04f9696363476d5693f2 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:38:18 +0100 Subject: [PATCH 55/69] chore(compat): improve links in xs view --- client/src/lit/compat/index-mobile.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index 7b9ace531ae9..3b28d88f93a8 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -41,6 +41,10 @@ } } + .bc-on-github a { + white-space: nowrap; + } + .table-container { overflow-x: auto; } From 6a15c3fa2a0f950e4af2212e88624d3bd07df86a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 15:40:05 +0100 Subject: [PATCH 56/69] fix(compat): calculate breakpoint with SCSS --- client/src/lit/compat/index-mobile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/index-mobile.scss b/client/src/lit/compat/index-mobile.scss index 3b28d88f93a8..165cbdbec70f 100644 --- a/client/src/lit/compat/index-mobile.scss +++ b/client/src/lit/compat/index-mobile.scss @@ -2,7 +2,7 @@ // Style for mobile. -@media (max-width: $screen-sm - 1px) { +@media (max-width: #{$screen-sm - 0.02}) { .bc-table { grid-template-columns: auto; From 707e502bd5d20b08bee090fbdd436c2577ce30bb Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Mon, 17 Mar 2025 18:12:07 +0100 Subject: [PATCH 57/69] fix(compat): show "Support unknown" in timeline --- client/src/lit/compat/compat-table.js | 5 ++++- client/src/lit/compat/utils.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 727609781939..5a133d31e446 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -543,7 +543,10 @@ class CompatTable extends LitElement { iconName: "footnote", label: "No support", } - : null, + : { + iconName: "unknown", + label: "Support unknown", + }, ].flat(); /** diff --git a/client/src/lit/compat/utils.js b/client/src/lit/compat/utils.js index 46dfacacafce..bbce1a2e8a4b 100644 --- a/client/src/lit/compat/utils.js +++ b/client/src/lit/compat/utils.js @@ -243,7 +243,7 @@ export function isFullySupportedWithoutLimitation(support) { * @returns {boolean} */ export function isNotSupportedAtAll(support) { - return !support.version_added && !hasLimitation(support); + return support.version_added === false && !hasLimitation(support); } /** From dbc84b6dca3219b4917edc8707c007095578c8fe Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 18 Mar 2025 14:58:44 +0100 Subject: [PATCH 58/69] fix(compat): focus explicitly on click --- client/src/lit/compat/compat-table.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 5a133d31e446..c95a140aa5b6 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -292,18 +292,26 @@ class CompatTable extends LitElement { const { currentTarget } = event; if ( - !(activeElement instanceof HTMLElement) || - !(currentTarget instanceof HTMLElement) + activeElement instanceof HTMLElement && + currentTarget instanceof HTMLElement ) { - return; + const activeCell = activeElement.closest("td"); + const currentCell = currentTarget.closest("td"); + + if (activeCell === currentCell) { + activeElement.blur(); + event.preventDefault(); + return; + } } - const activeCell = activeElement.closest("td"); - const currentCell = currentTarget.closest("td"); - - if (activeCell === currentCell) { - activeElement.blur(); - event.preventDefault(); + if (currentTarget instanceof HTMLElement) { + // Workaround for Safari, which doesn't focus implicitly. + currentTarget.addEventListener( + "click", + () => currentTarget.focus(), + { once: true } + ); } }; From 09025b6157e148cb98baed9cef2efe451842a193 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 18 Mar 2025 18:47:36 +0100 Subject: [PATCH 59/69] fix(compat): fix header layout --- client/src/lit/compat/index.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/lit/compat/index.scss b/client/src/lit/compat/index.scss index cc0075049a20..e5629ec393e3 100644 --- a/client/src/lit/compat/index.scss +++ b/client/src/lit/compat/index.scss @@ -82,9 +82,7 @@ table { thead { line-height: 1; - text-orientation: sideways; white-space: nowrap; - writing-mode: sideways-lr; } // these props allow us to add border-radius to the table. @@ -258,7 +256,9 @@ table { // Row with desktop / mobile icons. .bc-platforms { th { - align-content: center; + align-items: center; + display: flex; + justify-content: center; } td { @@ -271,9 +271,9 @@ table { th { align-items: center; display: flex; - flex-direction: row-reverse; + flex-direction: column; gap: 0.25rem; - justify-content: start; + justify-content: end; vertical-align: bottom; } From 81de805a2ff0e33522efd0f07a011ccd3cad30fb Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 18 Mar 2025 18:47:54 +0100 Subject: [PATCH 60/69] fix(compat): avoid left border in timeline cell --- client/src/lit/compat/index-desktop.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/index-desktop.scss b/client/src/lit/compat/index-desktop.scss index c2dc98d9fae4..ab3141f8ded7 100644 --- a/client/src/lit/compat/index-desktop.scss +++ b/client/src/lit/compat/index-desktop.scss @@ -62,7 +62,7 @@ } .timeline { - border-left: none; + border-left: none !important; border-top: var(--border); } From f73e2e2540dffe7b1b6476eb4e11e8e3aece249e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Tue, 18 Mar 2025 18:59:41 +0100 Subject: [PATCH 61/69] fixup! fix(compat): focus explicitly on click --- client/src/lit/compat/compat-table.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index c95a140aa5b6..73dadd57b48f 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -307,11 +307,7 @@ class CompatTable extends LitElement { if (currentTarget instanceof HTMLElement) { // Workaround for Safari, which doesn't focus implicitly. - currentTarget.addEventListener( - "click", - () => currentTarget.focus(), - { once: true } - ); + setTimeout(() => currentTarget.focus(), 0); } }; From 759eb44b1f1685c8df9955663c4ea3479fd19f19 Mon Sep 17 00:00:00 2001 From: Leo McArdle Date: Wed, 19 Mar 2025 12:07:54 +0000 Subject: [PATCH 62/69] fix(compat): fix types, removing unnecessary @ts-ignore --- client/src/lit/compat/feature-row.js | 3 +-- client/src/lit/compat/legend.js | 10 ++-------- client/src/lit/compat/types.ts | 17 +++++++++++++++++ client/src/lit/compat/utils.js | 17 +++-------------- 4 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 client/src/lit/compat/types.ts diff --git a/client/src/lit/compat/feature-row.js b/client/src/lit/compat/feature-row.js index 5f812af2ac18..9850479d27ed 100644 --- a/client/src/lit/compat/feature-row.js +++ b/client/src/lit/compat/feature-row.js @@ -3,7 +3,7 @@ import { html } from "lit"; /** * @import { BrowserStatement } from "@mdn/browser-compat-data/types" - * @import { SupportStatementExtended } from "./utils.js" + * @import { SupportStatementExtended } from "./types" * @typedef {"no"|"yes"|"partial"|"preview"|"removed-partial"|"unknown"} SupportClassName */ @@ -101,6 +101,5 @@ export function getSupportBrowserReleaseDate(support) { return undefined; } - // @ts-ignore return getCurrentSupport(support)?.release_date; } diff --git a/client/src/lit/compat/legend.js b/client/src/lit/compat/legend.js index dfc74998bebe..0e838b8cc1b0 100644 --- a/client/src/lit/compat/legend.js +++ b/client/src/lit/compat/legend.js @@ -62,7 +62,6 @@ export function getActiveLegendItems(compat, name, browserInfo, browsers) { } for (const browser of browsers) { - // @ts-ignore const browserSupport = feature.compat.support[browser] ?? { version_added: null, }; @@ -71,9 +70,8 @@ export function getActiveLegendItems(compat, name, browserInfo, browsers) { continue; } - // @ts-ignore const firstSupportItem = getFirst(browserSupport); - if (hasNoteworthyNotes(firstSupportItem)) { + if (firstSupportItem && hasNoteworthyNotes(firstSupportItem)) { legendItems.add("footnote"); } @@ -82,7 +80,6 @@ export function getActiveLegendItems(compat, name, browserInfo, browsers) { if (versionSupport.flags && versionSupport.flags.length) { legendItems.add("no"); } else if ( - // @ts-ignore versionIsPreview(versionSupport.version_added, browserInfo[browser]) ) { legendItems.add("preview"); @@ -115,10 +112,7 @@ export function getActiveLegendItems(compat, name, browserInfo, browsers) { } } - /** - * @type {any[]} - */ - const keys = Object.keys(LEGEND_LABELS); + const keys = /** @type {LegendKey[]} */ (Object.keys(LEGEND_LABELS)); return keys .filter((key) => legendItems.has(key)) diff --git a/client/src/lit/compat/types.ts b/client/src/lit/compat/types.ts new file mode 100644 index 000000000000..ae8a4a0ba7a7 --- /dev/null +++ b/client/src/lit/compat/types.ts @@ -0,0 +1,17 @@ +import type BCD from "@mdn/browser-compat-data/types"; + +// Extended for the fields, beyond the bcd types, that are extra-added +// exclusively in Yari. +export interface SimpleSupportStatementExtended + extends BCD.SimpleSupportStatement { + // Known for some support statements where the browser *version* is known, + // as opposed to just "true" and if the version release date is known. + release_date?: string; + // The version before the version_removed if the *version* removed is known, + // as opposed to just "true". Otherwise the version_removed. + version_last?: BCD.VersionValue; +} + +export type SupportStatementExtended = + | SimpleSupportStatementExtended + | SimpleSupportStatementExtended[]; diff --git a/client/src/lit/compat/utils.js b/client/src/lit/compat/utils.js index bbce1a2e8a4b..0e40a8a2e670 100644 --- a/client/src/lit/compat/utils.js +++ b/client/src/lit/compat/utils.js @@ -1,16 +1,6 @@ /** * @import { SimpleSupportStatement, VersionValue, CompatStatement, Identifier, SupportStatement, BrowserStatement } from "@mdn/browser-compat-data/types" - * @typedef {SimpleSupportStatementExtended | SimpleSupportStatementExtended[]} SupportStatementExtended - */ - -/** - * Extended for the fields, beyond the bcd types, that are extra-added exclusively in Yari. - * - * Inherits all properties from SimpleSupportStatement. - * - * @typedef {SimpleSupportStatement} SimpleSupportStatementExtended - * @property {string} [release_date] - Known for some support statements where the browser version is known. - * @property {VersionValue} [version_last] - The version before the version_removed if the version removed is known. + * @import { SupportStatementExtended, SimpleSupportStatementExtended } from "./types" */ /** @@ -33,8 +23,8 @@ export const HIDDEN_BROWSERS = ["ie"]; * Gets the first element of an array or returns the value itself. * * @template T - * @param {T | [T, ...any[]]} a - * @returns {T} + * @param {T | [T?, ...any[]]} a + * @returns {T | undefined} */ export function getFirst(a) { return Array.isArray(a) ? a[0] : a; @@ -308,6 +298,5 @@ export function getCurrentSupport(support) { if (noSupportItem) return noSupportItem; // Default (likely never reached). - // @ts-ignore return getFirst(support); } From 8d25034c5c9ce5900cdf220d88692b6522a0a90a Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 19 Mar 2025 13:55:13 +0100 Subject: [PATCH 63/69] chore(compat): remove unused _id --- client/src/document/index.tsx | 2 +- client/src/lit/compat/lazy-compat-table.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/document/index.tsx b/client/src/document/index.tsx index e4dc98c6eac0..58a4f125cba1 100644 --- a/client/src/document/index.tsx +++ b/client/src/document/index.tsx @@ -276,7 +276,7 @@ export function RenderDocumentBody({ doc }) { } key={`browser_compatibility${i}`}> {title && !isH3 && } {title && isH3 && } - + ); } else if (section.type === "specifications") { diff --git a/client/src/lit/compat/lazy-compat-table.js b/client/src/lit/compat/lazy-compat-table.js index 3a921eacbbc5..9b3d5c258770 100644 --- a/client/src/lit/compat/lazy-compat-table.js +++ b/client/src/lit/compat/lazy-compat-table.js @@ -8,14 +8,12 @@ import "./compat-table.js"; class LazyCompatTable extends LitElement { static properties = { - _id: {}, query: {}, locale: {}, }; constructor() { super(); - this._id = ""; this.query = ""; this.locale = ""; } From a2765efe79e5e43beea150eac64f0ab67b76aa78 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 19 Mar 2025 14:02:31 +0100 Subject: [PATCH 64/69] chore(compat): type BCD response.json() --- client/src/lit/compat/lazy-compat-table.js | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/client/src/lit/compat/lazy-compat-table.js b/client/src/lit/compat/lazy-compat-table.js index 9b3d5c258770..3bbf62b36c25 100644 --- a/client/src/lit/compat/lazy-compat-table.js +++ b/client/src/lit/compat/lazy-compat-table.js @@ -6,6 +6,11 @@ import React from "react"; import { BCD_BASE_URL } from "../../env.ts"; import "./compat-table.js"; +/** + * @import { Browsers, Identifier } from "@mdn/browser-compat-data/types" + * @typedef {{data: Identifier, browsers: Browsers}} Compat + */ + class LazyCompatTable extends LitElement { static properties = { query: {}, @@ -33,22 +38,27 @@ class LazyCompatTable extends LitElement { console.error("Failed to fetch BCD data:", response); throw new Error(response.statusText); } - return response.json(); + return /** @type {Promise} */ response.json(); }, }); render() { return this._dataTask.render({ pending: () => html`

Loading...

`, - complete: (compat) => - compat - ? html`` - : html`

No compatibility data found

`, + + complete: + /** + * @param {Compat} compat + */ + (compat) => + compat + ? html`` + : html`

No compatibility data found

`, error: (error) => html`

Error loading data: ${error}

`, }); } From 33a795890ef20382a4063eb32e9f5e020e75b667 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 19 Mar 2025 14:04:41 +0100 Subject: [PATCH 65/69] chore(compat): remove TODO --- client/src/lit/compat/compat-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 73dadd57b48f..0d89a52d0d1d 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -99,7 +99,7 @@ class CompatTable extends LitElement { /** @type {Browsers} */ // @ts-ignore this.browserInfo = {}; - this.locale = ""; // TODO + this.locale = ""; this._pathname = ""; /** @type {string[]} */ this._platforms = []; From 503c2c66fd341a0f54870df00c25b3cf0238152b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 19 Mar 2025 14:19:11 +0100 Subject: [PATCH 66/69] refactor(compat): prefix internal getters/methods --- client/src/lit/compat/compat-table.js | 122 +++++++++++++------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 0d89a52d0d1d..ff1a8ddf79eb 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -107,29 +107,29 @@ class CompatTable extends LitElement { this._browsers = []; } - get breadcrumbs() { + get _breadcrumbs() { return this.query.split("."); } - get category() { - return this.breadcrumbs[0] ?? ""; + get _category() { + return this._breadcrumbs[0] ?? ""; } - get name() { - return this.breadcrumbs.at(-1) ?? ""; + get _name() { + return this._breadcrumbs.at(-1) ?? ""; } connectedCallback() { super.connectedCallback(); this._pathname = window.location.pathname; [this._platforms, this._browsers] = gatherPlatformsAndBrowsers( - this.category, + this._category, this.data, this.browserInfo ); } - get issueUrl() { + get _issueUrl() { const url = "https://github.com/mdn/browser-compat-data/issues/new"; const sp = new URLSearchParams(); const metadata = ISSUE_METADATA_TEMPLATE.replace( @@ -146,10 +146,10 @@ class CompatTable extends LitElement { return `${url}?${sp.toString()}`; } - renderIssueLink() { + _renderIssueLink() { const onClick = (/** @type {MouseEvent} */ event) => { event.preventDefault(); - window.open(this.issueUrl, "_blank", "noopener,noreferrer"); + window.open(this._issueUrl, "_blank", "noopener,noreferrer"); }; const source_file = this.data.__compat?.source_file; return html`
@@ -177,27 +177,27 @@ class CompatTable extends LitElement {
`; } - renderTable() { + _renderTable() { return html`
- ${this.renderIssueLink()} + ${this._renderIssueLink()}
- ${this.renderTableHeader()} ${this.renderTableBody()} + ${this._renderTableHeader()} ${this._renderTableBody()}
`; } - renderTableHeader() { + _renderTableHeader() { return html` - ${this.renderPlatformHeaders()} ${this.renderBrowserHeaders()} + ${this._renderPlatformHeaders()} ${this._renderBrowserHeaders()} `; } - renderPlatformHeaders() { + _renderPlatformHeaders() { const platformsWithBrowsers = this._platforms.map((platform) => ({ platform, browsers: this._browsers.filter( @@ -231,7 +231,7 @@ class CompatTable extends LitElement { `; } - renderBrowserHeaders() { + _renderBrowserHeaders() { // return html` @@ -251,10 +251,10 @@ class CompatTable extends LitElement { `; } - renderTableBody() { + _renderTableBody() { // const { data, _browsers: browsers, browserInfo, locale } = this; - const features = listFeatures(data, "", this.name); + const features = listFeatures(data, "", this._name); return html` ${features.map((feature) => { @@ -267,7 +267,7 @@ class CompatTable extends LitElement { let titleNode; const titleContent = html`${title}${compat.status && - this.renderStatusIcons(compat.status)}`; + this._renderStatusIcons(compat.status)}`; if (compat.mdn_url && depth > 0) { const href = compat.mdn_url.replace( `/${DEFAULT_LOCALE}/docs`, @@ -323,7 +323,7 @@ class CompatTable extends LitElement { }; const supportClassName = getSupportClassName(support, browser); - const notes = this.renderNotes(browser, support); + const notes = this._renderNotes(browser, support); return html` - ${this.renderCellText(support, browser)} + ${this._renderCellText(support, browser)} ${notes && html`
@@ -351,18 +351,18 @@ class CompatTable extends LitElement { /** * @param {SupportStatement} support */ - renderCellIcons(support) { + _renderCellIcons(support) { const supportItem = getCurrentSupport(support); if (!supportItem) { return null; } const icons = [ - supportItem.prefix && this.renderIcon("prefix"), - hasNoteworthyNotes(supportItem) && this.renderIcon("footnote"), - supportItem.alternative_name && this.renderIcon("altname"), - supportItem.flags && this.renderIcon("disabled"), - hasMore(support) && this.renderIcon("more"), + supportItem.prefix && this._renderIcon("prefix"), + hasNoteworthyNotes(supportItem) && this._renderIcon("footnote"), + supportItem.alternative_name && this._renderIcon("altname"), + supportItem.flags && this._renderIcon("disabled"), + hasMore(support) && this._renderIcon("more"), ].filter(Boolean); return icons.length ? html`
${icons}
` : null; @@ -372,7 +372,7 @@ class CompatTable extends LitElement { * @param {string} name * @returns {TemplateResult} */ - renderIcon(name) { + _renderIcon(name) { const title = name in LEGEND_LABELS ? LEGEND_LABELS[name] : name; return html` @@ -384,7 +384,7 @@ class CompatTable extends LitElement { /** * @param {StatusBlock} status */ - renderStatusIcons(status) { + _renderStatusIcons(status) { // /** * @type {StatusIcon[]} @@ -434,7 +434,7 @@ class CompatTable extends LitElement { * @param {BrowserStatement} browser * @param {SupportStatement} support */ - renderNotes(browser, support) { + _renderNotes(browser, support) { return asList(support) .slice() .reverse() @@ -568,11 +568,11 @@ class CompatTable extends LitElement { browser )} bc-supports`} > - ${this.renderCellText(item, browser, true)} + ${this._renderCellText(item, browser, true)} ${filteredSupportNotes.map(({ iconName, label }) => { return html`
- ${this.renderIcon(iconName)}${typeof label === "string" + ${this._renderIcon(iconName)}${typeof label === "string" ? html`${unsafeHTML(label)}` : label}
`; @@ -590,7 +590,7 @@ class CompatTable extends LitElement { * @param {BrowserStatement} browser * @param {boolean} [timeline] */ - renderCellText(support, browser, timeline = false) { + _renderCellText(support, browser, timeline = false) { const currentSupport = getCurrentSupport(support); const added = currentSupport?.version_added ?? null; @@ -695,11 +695,11 @@ class CompatTable extends LitElement { : ""}
- ${support && this.renderCellIcons(support)} + ${support && this._renderCellIcons(support)}
`; } - renderTableLegend() { + _renderTableLegend() { const { _browsers: browsers, browserInfo } = this; if (!browserInfo) { @@ -712,38 +712,42 @@ class CompatTable extends LitElement { Tip: you can click/tap on a cell for more information.

- ${getActiveLegendItems(this.data, this.name, browserInfo, browsers).map( - ([key, label]) => - ["yes", "partial", "no", "unknown", "preview"].includes(key) - ? html`
-
- - - ${label} - - -
-
${label}
-
` - : html`
-
+ ${getActiveLegendItems( + this.data, + this._name, + browserInfo, + browsers + ).map(([key, label]) => + ["yes", "partial", "no", "unknown", "preview"].includes(key) + ? html`
+
+ -
-
${label}
-
` + > + ${label} + + + +
${label}
+
` + : html`
+
+ +
+
${label}
+
` )} `; } render() { - return html` ${this.renderTable()} ${this.renderTableLegend()} `; + return html` ${this._renderTable()} ${this._renderTableLegend()} `; } } From 2f659abd5eb68f0c91b7a11f03a765ef7816681b Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 19 Mar 2025 14:22:17 +0100 Subject: [PATCH 67/69] fix(compat): replace className => class --- client/src/lit/compat/compat-table.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index ff1a8ddf79eb..5f2f0450f98a 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -165,7 +165,7 @@ class CompatTable extends LitElement { >${source_file ? html` • Date: Wed, 19 Mar 2025 14:22:33 +0100 Subject: [PATCH 68/69] chore(compat): use ifDefined directive --- client/src/lit/compat/compat-table.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/lit/compat/compat-table.js b/client/src/lit/compat/compat-table.js index 5f2f0450f98a..f4886f8e59e8 100644 --- a/client/src/lit/compat/compat-table.js +++ b/client/src/lit/compat/compat-table.js @@ -1,4 +1,7 @@ import { html, LitElement } from "lit"; +import { ifDefined } from "lit/directives/if-defined.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; + import { getActiveLegendItems } from "./legend.js"; import { asList, @@ -14,7 +17,6 @@ import { } from "./utils.js"; import styles from "./index.scss?css" with { type: "css" }; -import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { DEFAULT_LOCALE } from "../../../../libs/constants/index.js"; import { BCD_TABLE } from "../../telemetry/constants.ts"; import { @@ -23,7 +25,6 @@ import { labelFromString, versionLabelFromSupport, } from "./feature-row.js"; -import { ifDefined } from "lit/directives/if-defined.js"; /** * @import { TemplateResult } from "lit" @@ -332,7 +333,7 @@ class CompatTable extends LitElement { >