UNPKG

mermaid

Version:

Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.

8 lines (7 loc) 7.36 kB
{ "version": 3, "sources": ["../../../src/diagrams/common/colorThemeGate.ts"], "sourcesContent": ["/**\n * The per-item colour palette is opt-in per diagram. A shape or container stamps a\n * `data-color-id` slot on its rendered element, and the diagram's stylesheet maps that\n * slot to a border and fill. Both halves need the same answers to the same three\n * questions \u2014 is this a colour theme, does it actually carry a palette, and which slot\n * does this item get \u2014 so they live here rather than being restated per diagram.\n *\n * Before this module the `COLOR_THEMES` list existed in five copies and the stamping\n * block was duplicated verbatim between `clusters.js` and `classBox.ts`. Two idioms for\n * the same gate had already appeared: `er/styles.ts` keys off the theme name while\n * `requirement/styles.js` keys off the array being non-empty. Anything added here is\n * added once.\n */\nimport type { D3Selection } from '../../types.js';\n\n/** Themes that carry a categorical palette for per-item colouring. */\nexport const COLOR_THEMES = new Set(['redux-color', 'redux-dark-color']);\n\n/** How many palette slots to emit when the theme does not say. */\nexport const DEFAULT_COLOR_SLOTS = 12;\n\n/**\n * Upper bound on slots. Every shipped palette has 12 entries, so this is generous -- it\n * exists to bound the loop, not to express a design limit. See `colorSlotCount`.\n */\nexport const MAX_COLOR_SLOTS = 64;\n\n/**\n * A palette array is usable only if it is genuinely a non-empty array. A truthy `.length`\n * check passes for a plain string too, which would yield a per-character \"palette\" and\n * declarations like `stroke: r;`.\n */\nexport const hasPalette = (palette: unknown): palette is string[] =>\n Array.isArray(palette) && palette.length > 0;\n\n/** Whether `theme` should render per-item colour at all. */\nexport const isColorTheme = (theme: string | undefined, palette: unknown): boolean =>\n theme != null && COLOR_THEMES.has(theme) && hasPalette(palette);\n\n/**\n * `look` is interpolated into a CSS selector by every stylesheet that emits palette\n * rules, and it is a top-level config key \u2014 so it is settable from diagram text via\n * frontmatter or an init directive, and `config.sanitize` only removes values containing\n * `<`, `>` or `url(data:`. Braces and quotes survive, which is enough to close the\n * attribute selector early and open a rule block of the caller's choosing, escaping the\n * `#svgId` scoping stylis applies.\n *\n * Every real look is a bare word, so anything else is rejected outright rather than\n * escaped. Validate here, at the point of interpolation, so no caller has to remember.\n */\nconst SAFE_LOOK = /^[\\w-]+$/;\n\nexport const safeLook = (look: unknown): string => {\n // Only stringify types that actually describe themselves -- `String()` on a plain object\n // or array would pass through as the meaningless `[object Object]`, which happens to\n // still fail SAFE_LOOK today but would be a silent bug waiting for a future look-alike\n // regex, and is exactly the kind of default-stringification mistake this function exists\n // to guard against elsewhere.\n const s = typeof look === 'string' || typeof look === 'number' ? String(look) : '';\n return SAFE_LOOK.test(s) ? s : 'classic';\n};\n\n/**\n * Number of palette slots a stylesheet should emit.\n *\n * With a palette, this is exactly `palette.length` -- not the limit, not a cap. That is not\n * a policy choice: `stampColorSlot` assigns `colorIndex % palette.length`, so the ids it\n * can produce are precisely `0 .. palette.length - 1`. Emitting fewer leaves items stamped\n * with no rule to match, and emitting more is dead CSS. Deriving both from the same length\n * is what makes the two impossible to disagree, rather than something a bound has to keep\n * lined up.\n *\n * Three separate bugs came out of letting these drift apart -- a limit longer than the\n * palette, a palette longer than the limit, and then a palette longer than the cap that was\n * added to bound the limit. `paletteSlotCount` below is the single source of truth for both\n * sides, and `slotsAgree` in the spec pins that they never diverge again.\n *\n * `THEME_COLOR_LIMIT` still governs callers that emit slots *without* stamping -- timeline\n * numbers `.section-N` classes rather than palette slots -- and is bounded there, because a\n * loop runs on it directly and `Infinity` is reachable from front matter\n * (`THEME_COLOR_LIMIT: .inf` parses to it under the `JSON_SCHEMA` mermaid uses).\n */\nexport const paletteSlotCount = (palette: unknown): number =>\n hasPalette(palette) ? palette.length : 0;\n\nexport const colorSlotCount = (themeColorLimit: unknown, palette?: unknown): number => {\n if (hasPalette(palette)) {\n return paletteSlotCount(palette);\n }\n return typeof themeColorLimit === 'number' &&\n Number.isInteger(themeColorLimit) &&\n themeColorLimit > 0 &&\n themeColorLimit <= MAX_COLOR_SLOTS\n ? themeColorLimit\n : DEFAULT_COLOR_SLOTS;\n};\n\n/**\n * Stamp the element with its palette slot, so the diagram's `[data-color-id]` rules can\n * find it. A no-op for every theme without a palette, which is what keeps this safe to\n * call from shared rendering code used by diagrams that never opt in.\n *\n * The slot wraps at the palette length rather than indexing raw, so a palette shorter\n * than the emitted slot count cannot produce `stroke: undefined`.\n *\n * An absent `colorIndex` means \"this element is not part of the cycle\", so nothing is\n * stamped. It used to fall back to `colorIndex ?? 0`, which says the opposite -- an\n * unnumbered element was stamped `color-0` and painted in the first palette colour. That\n * was invisible while the only unnumbered containers were ones whose diagram emits no\n * matching rules (class namespaces, block containers), and it stops being invisible the\n * moment such a diagram gains palette rules: state's composites opt out of the cycle when\n * the author has styled them, and would otherwise all come back as `color-0`.\n */\nexport const stampColorSlot = <T extends SVGGraphicsElement>(\n shapeSvg: D3Selection<T>,\n colorIndex: number | undefined,\n theme: string | undefined,\n palette: unknown\n): void => {\n if (colorIndex === undefined || !isColorTheme(theme, palette)) {\n return;\n }\n const slot = colorIndex % paletteSlotCount(palette);\n shapeSvg.attr('data-color-id', `color-${slot}`);\n};\n"], "mappings": ";;;;;AAgBO,IAAM,eAAe,oBAAI,IAAI,CAAC,eAAe,kBAAkB,CAAC;AAGhE,IAAM,sBAAsB;AAM5B,IAAM,kBAAkB;AAOxB,IAAM,aAAa,wBAAC,YACzB,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GADnB;AAInB,IAAM,eAAe,wBAAC,OAA2B,YACtD,SAAS,QAAQ,aAAa,IAAI,KAAK,KAAK,WAAW,OAAO,GADpC;AAc5B,IAAM,YAAY;AAEX,IAAM,WAAW,wBAAC,SAA0B;AAMjD,QAAM,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,OAAO,IAAI,IAAI;AAChF,SAAO,UAAU,KAAK,CAAC,IAAI,IAAI;AACjC,GARwB;AA8BjB,IAAM,mBAAmB,wBAAC,YAC/B,WAAW,OAAO,IAAI,QAAQ,SAAS,GADT;AAGzB,IAAM,iBAAiB,wBAAC,iBAA0B,YAA8B;AACrF,MAAI,WAAW,OAAO,GAAG;AACvB,WAAO,iBAAiB,OAAO;AAAA,EACjC;AACA,SAAO,OAAO,oBAAoB,YAChC,OAAO,UAAU,eAAe,KAChC,kBAAkB,KAClB,mBAAmB,kBACjB,kBACA;AACN,GAV8B;AA4BvB,IAAM,iBAAiB,wBAC5B,UACA,YACA,OACA,YACS;AACT,MAAI,eAAe,UAAa,CAAC,aAAa,OAAO,OAAO,GAAG;AAC7D;AAAA,EACF;AACA,QAAM,OAAO,aAAa,iBAAiB,OAAO;AAClD,WAAS,KAAK,iBAAiB,SAAS,IAAI,EAAE;AAChD,GAX8B;", "names": [] }