From 1312d198a1bd13f504aafae5a2cdb67ee35bcc28 Mon Sep 17 00:00:00 2001 From: upstream Date: Sat, 17 Oct 2026 03:31:03 +0200 Subject: [PATCH] Upstream mermaid-js/mermaid state of the touched files before f69fecf786 --- .../mermaid/src/diagrams/flowchart/styles.ts | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 packages/mermaid/src/diagrams/flowchart/styles.ts diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts new file mode 100644 index 0000000..e0286b7 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/styles.ts @@ -0,0 +1,298 @@ +// import khroma from 'khroma'; +import * as khroma from 'khroma'; +import { getIconStyles } from '../globalStyles.js'; +import { colorSlotCount, hasPalette, isColorTheme, safeLook } from '../common/colorThemeGate.js'; + +/** Returns the styles given options */ +export interface FlowChartStyleOptions { + arrowheadColor: string; + border2: string; + clusterBkg: string; + clusterBorder: string; + edgeLabelBackground: string; + fontFamily: string; + lineColor: string; + mainBkg: string; + nodeBorder: string; + nodeTextColor: string; + tertiaryColor: string; + textColor: string; + titleColor: string; + strokeWidth: string; + theme?: string; + look?: string; + THEME_COLOR_LIMIT?: number; + borderColorArray?: string[]; + bkgColorArray?: string[]; +} + +/** + * Cycling per-subgraph colour. Only the containers are painted -- the nodes inside keep + * the uniform look, because a flowchart node is a step in a flow rather than a distinct + * participant, and node colour is already how `classDef` / `style` carry meaning. + * + * Emits both the `rect` (classic/neo) and `path` (handDrawn) forms since the container is + * a plain rect in one look and a roughjs path pair in the other. `.collapsed-group` is + * the same container drawn as a compact node by `collapsedGroup.ts` — it is a container, + * not one of the flow's steps, so it takes the palette too; without it a collapsed + * subgraph rendered uncoloured beside tinted siblings. + * + * Not `!important`: `clusters.js` and `collapsedGroup.ts` both put user styles in an + * inline `style` attribute, which has to keep winning over the theme palette. The + * collapsed form's own colours are presentation attributes (`fill=` / `stroke=`), which + * these rules correctly outrank while still losing to that inline style. + * + * Lanes are excluded by `:not(.swimlane)` and ruled separately below: a lane is two + * rectangles, and the generic `path` rule would paint the hachure roughjs emits for its + * unfilled body. Here, not in `swimlanes/styles.ts`, so `layout: swimlane` is covered too. + */ +const genColor = (options: FlowChartStyleOptions) => { + const { theme, bkgColorArray, borderColorArray } = options; + if (!isColorTheme(theme, borderColorArray)) { + return ''; + } + const look = safeLook(options.look); + const hasBkgColors = hasPalette(bkgColorArray); + let sections = ''; + + for (let i = 0; i < colorSlotCount(options.THEME_COLOR_LIMIT, borderColorArray); i++) { + const borderColor = borderColorArray![i % borderColorArray!.length]; + const fill = hasBkgColors ? `fill: ${bkgColorArray[i % bkgColorArray.length]};` : ''; + const slot = `[data-look="${look}"][data-color-id="color-${i}"]`; + /* A collapsed subgraph is drawn by `collapsedGroup.ts` through `getNodeClasses`, which + * returns `rough-node` instead of `node` for the handDrawn look -- so a `.node`-only + * selector leaves handDrawn collapsed containers uncoloured beside their tinted + * siblings. Clusters are unaffected: `clusters.js` sets the `cluster` class directly. + * + * Each descendant has to be appended to *both* prefixes separately. Writing + * `${slot}.node, ${slot}.rough-node .thing` would attach the descendant to the last + * item of the list only, silently matching nothing under the classic look. + */ + const collapsedRule = (suffix: string) => + `${slot}.node ${suffix}, ${slot}.rough-node ${suffix}`; + /* Both halves of a lane. Each carries the whole prefix separately, as above. */ + const laneRule = (suffix: string) => + `${slot}.swimlane.cluster .swimlane-title${suffix}, ${slot}.swimlane.cluster .swimlane-body${suffix}`; + sections += ` + + ${slot}.cluster:not(.swimlane) rect { + stroke: ${borderColor}; + ${fill} + } + + ${slot}.cluster:not(.swimlane) path { + stroke: ${borderColor}; + ${fill} + } + + /* Lane, classic and neo. */ + ${slot}.swimlane.cluster rect.swimlane-title, + ${slot}.swimlane.cluster rect.swimlane-body { + stroke: ${borderColor}; + ${fill} + } + + /* Lane, handDrawn: roughjs emits the hachure fill first, then the outline. */ + ${laneRule(' path:nth-of-type(2)')} { + stroke: ${borderColor}; + } +${ + hasBkgColors + ? ` + /* A roughjs fill is drawn as lines, so the lane fill is a stroke here. */ + ${laneRule(' path:first-of-type')} { + stroke: ${bkgColorArray[i % bkgColorArray.length]}; + } +` + : '' +} + ${collapsedRule('.collapsed-group')}, + ${collapsedRule('.collapsed-group path')} { + stroke: ${borderColor}; + ${fill} + } + + /* The ellipsis dots and the separator take clusterBorder further down, so without + these the container is palette-coloured while its own markers are not. */ + ${collapsedRule('.collapsed-indicator')} { + fill: ${borderColor}; + } + + ${collapsedRule('.collapsed-separator')} { + stroke: ${borderColor}; + } + `; + } + return sections; +}; + +const fade = (color: string, opacity: number) => { + // @ts-ignore TODO: incorrect types from khroma + const channel = khroma.channel; + + const r = channel(color, 'r'); + const g = channel(color, 'g'); + const b = channel(color, 'b'); + + // @ts-ignore incorrect types from khroma + return khroma.rgba(r, g, b, opacity); +}; + +const getStyles = (options: FlowChartStyleOptions) => + `${genColor(options)} + .label { + font-family: ${options.fontFamily}; + color: ${options.nodeTextColor || options.textColor}; + } + .cluster-label text { + fill: ${options.titleColor}; + } + .cluster-label span { + color: ${options.titleColor}; + } + .cluster-label span p { + background-color: transparent; + } + + .label text,span { + fill: ${options.nodeTextColor || options.textColor}; + color: ${options.nodeTextColor || options.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${options.mainBkg}; + stroke: ${options.nodeBorder}; + stroke-width: ${options.strokeWidth ?? 1}px; + } + .rough-node .label text , .node .label text, .image-shape .label, .icon-shape .label { + text-anchor: middle; + } + + .node .katex path { + fill: #000; + stroke: #000; + stroke-width: 1px; + } + + .rough-node .label,.node .label, .image-shape .label, .icon-shape .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + + .root .anchor path { + fill: ${options.lineColor} !important; + stroke-width: 0; + stroke: ${options.lineColor}; + } + + .arrowheadPath { + fill: ${options.arrowheadColor}; + } + + .edgePaths .path { + stroke: ${options.lineColor}; + stroke-width: ${options.strokeWidth ?? 2}px; + } + + .flowchart-link { + stroke: ${options.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${options.edgeLabelBackground}; + p { + background-color: ${options.edgeLabelBackground}; + } + rect { + opacity: 0.5; + background-color: ${options.edgeLabelBackground}; + fill: ${options.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${fade(options.edgeLabelBackground, 0.5)}; + // background-color: + } + + .cluster rect { + fill: ${options.clusterBkg}; + stroke: ${options.clusterBorder}; + stroke-width: 1px; + } + + .cluster text { + fill: ${options.titleColor}; + } + + .cluster span { + color: ${options.titleColor}; + } + /* .cluster div { + color: ${options.titleColor}; + } */ + + /* Collapsed subgraph node (@{ view: collapsed }) */ + .node .collapsed-indicator { + fill: ${options.clusterBorder}; + stroke: none; + opacity: 0.6; + } + + .node .collapsed-separator { + stroke: ${options.clusterBorder}; + stroke-width: 0.75px; + } + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${options.fontFamily}; + font-size: 12px; + background: ${options.tertiaryColor}; + border: 1px solid ${options.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${options.textColor}; + } + + rect.text { + fill: none; + stroke-width: 0; + } + + .icon-shape, .image-shape { + background-color: ${options.edgeLabelBackground}; + p { + background-color: ${options.edgeLabelBackground}; + padding: 2px; + } + .label rect { + opacity: 0.5; + background-color: ${options.edgeLabelBackground}; + fill: ${options.edgeLabelBackground}; + } + text-align: center; + } + ${getIconStyles()} +`; + +export default getStyles;