diff --git a/.eslintrc.json b/.eslintrc.json index d83222f3a..1d052e22b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,16 @@ "plugin:@cspell/recommended", "prettier" ], - "plugins": ["@typescript-eslint", "no-only-tests", "html", "jest", "jsdoc", "json", "@cspell"], + "plugins": [ + "@typescript-eslint", + "no-only-tests", + "html", + "jest", + "jsdoc", + "json", + "@cspell", + "lodash" + ], "rules": { "curly": "error", "no-console": "error", @@ -53,7 +62,8 @@ "allowEmptyCatch": true } ], - "no-only-tests/no-only-tests": "error" + "no-only-tests/no-only-tests": "error", + "lodash/import-scope": ["error", "method"] }, "overrides": [ { diff --git a/.prettierignore b/.prettierignore index fe2c55574..b66f97d1c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,5 @@ cypress/platform/xss3.html .cache coverage # Autogenerated by PNPM -pnpm-lock.yaml \ No newline at end of file +pnpm-lock.yaml +stats \ No newline at end of file diff --git a/.vite/build.ts b/.vite/build.ts index f2dd7db05..1be46ad5a 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -56,7 +56,7 @@ interface BuildOptions { } export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions): InlineConfig => { - const external = ['require', 'fs', 'path']; + const external: (string | RegExp)[] = ['require', 'fs', 'path']; console.log(entryName, packageOptions[entryName]); const { name, file, packageName } = packageOptions[entryName]; let output: OutputOptions = [ @@ -80,7 +80,9 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) ); // Core build is used to generate file without bundled dependencies. // This is used by downstream projects to bundle dependencies themselves. - external.push(...Object.keys(dependencies)); + // Ignore dependencies and any dependencies of dependencies + // Adapted from the RegEx used by `rollup-plugin-node` + external.push(new RegExp('^(?:' + Object.keys(dependencies).join('|') + ')(?:/.+)?$')); // This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd. output = [ { diff --git a/package.json b/package.json index 36846a313..628fcecd2 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "@cspell/eslint-plugin": "^6.14.2", "@types/eslint": "^8.4.10", "@types/express": "^4.17.14", - "@types/jsdom": "^20.0.1", "@types/js-yaml": "^4.0.5", + "@types/jsdom": "^20.0.1", "@types/lodash": "^4.14.188", "@types/mdast": "^3.0.10", "@types/node": "^18.11.9", @@ -83,17 +83,19 @@ "eslint-plugin-jest": "^27.1.5", "eslint-plugin-jsdoc": "^39.6.2", "eslint-plugin-json": "^3.1.0", + "eslint-plugin-lodash": "^7.4.0", "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-tsdoc": "^0.2.17", + "eslint-plugin-unicorn": "^45.0.0", "express": "^4.18.2", "globby": "^13.1.2", "husky": "^8.0.2", "identity-obj-proxy": "^3.0.0", "jest": "^29.3.1", "jison": "^0.4.18", - "jsdom": "^20.0.2", "js-yaml": "^4.1.0", + "jsdom": "^20.0.2", "lint-staged": "^13.0.3", "path-browserify": "^1.0.1", "pnpm": "^7.15.0", diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 0b4c7aad1..26e846c20 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -54,12 +54,12 @@ "dependencies": { "@braintree/sanitize-url": "^6.0.0", "d3": "^7.0.0", - "dagre-d3-es": "7.0.2", + "dagre-d3-es": "7.0.4", "dompurify": "2.4.1", "fast-clone": "^1.5.13", "graphlib": "^2.1.8", "khroma": "^2.0.0", - "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "moment-mini": "^2.24.0", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", @@ -69,7 +69,7 @@ "@types/d3": "^7.4.0", "@types/dompurify": "^2.4.0", "@types/jsdom": "^20.0.1", - "@types/lodash": "^4.14.188", + "@types/lodash-es": "^4.17.6", "@types/micromatch": "^4.0.2", "@types/prettier": "^2.7.1", "@types/stylis": "^4.0.2", diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index 43fe311b3..8b77c8520 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -1,5 +1,5 @@ import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; -import graphlib from 'graphlib'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; import insertMarkers from './markers'; import { updateNodeBounds } from './shapes/util'; import { @@ -15,7 +15,7 @@ import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } f import { log } from '../logger'; const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { - log.info('Graph in recursive render: XXX', graphlib.json.write(graph), parentCluster); + log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster); const dir = graph.graph().rankdir; log.trace('Dir in recursive render - dir:', dir); @@ -96,7 +96,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { log.info('#############################################'); log.info(graph); dagreLayout(graph); - log.info('Graph after layout:', graphlib.json.write(graph)); + log.info('Graph after layout:', graphlibJson.write(graph)); // Move the nodes to the correct place let diff = 0; sortNodesByHierarchy(graph).forEach(function (v) { @@ -153,10 +153,10 @@ export const render = (elem, graph, markers, diagramtype, id) => { clearClusters(); clearGraphlib(); - log.warn('Graph at first:', graphlib.json.write(graph)); + log.warn('Graph at first:', graphlibJson.write(graph)); adjustClustersAndEdges(graph); - log.warn('Graph after:', graphlib.json.write(graph)); - // log.warn('Graph ever after:', graphlib.json.write(graph.node('A').graph)); + log.warn('Graph after:', graphlibJson.write(graph)); + // log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph)); recursiveRender(elem, graph, diagramtype); }; diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js index 56f656430..15fadc940 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js @@ -1,6 +1,7 @@ /** Decorates with functions required by mermaids dagre-wrapper. */ import { log } from '../logger'; -import graphlib from 'graphlib'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; export let clusterDb = {}; let decendants = {}; @@ -322,7 +323,7 @@ export const adjustClustersAndEdges = (graph, depth) => { graph.setEdge(v, w, edge, e.name); } }); - log.warn('Adjusted Graph', graphlib.json.write(graph)); + log.warn('Adjusted Graph', graphlibJson.write(graph)); extractor(graph, 0); log.trace(clusterDb); @@ -336,7 +337,7 @@ export const adjustClustersAndEdges = (graph, depth) => { }; export const extractor = (graph, depth) => { - log.warn('extractor - ', depth, graphlib.json.write(graph), graph.children('D')); + log.warn('extractor - ', depth, graphlibJson.write(graph), graph.children('D')); if (depth > 10) { log.error('Bailing out'); return; @@ -415,7 +416,7 @@ export const extractor = (graph, depth) => { return {}; }); - log.warn('Old graph before copy', graphlib.json.write(graph)); + log.warn('Old graph before copy', graphlibJson.write(graph)); copy(node, graph, clusterGraph, node); graph.setNode(node, { clusterNode: true, @@ -424,8 +425,8 @@ export const extractor = (graph, depth) => { labelText: clusterDb[node].labelText, graph: clusterGraph, }); - log.warn('New graph after copy node: (', node, ')', graphlib.json.write(clusterGraph)); - log.debug('Old graph after copy', graphlib.json.write(graph)); + log.warn('New graph after copy node: (', node, ')', graphlibJson.write(clusterGraph)); + log.debug('Old graph after copy', graphlibJson.write(graph)); } else { log.warn( 'Cluster ** ', diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js index a09e17f02..49b022f4d 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -1,4 +1,5 @@ -import graphlib from 'graphlib'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { validate, adjustClustersAndEdges, @@ -232,9 +233,9 @@ describe('Graphlib decorations', () => { g.setParent('D', 'C'); // log.info('Graph before', g.node('D')) - // log.info('Graph before', graphlib.json.write(g)) + // log.info('Graph before', graphlibJson.write(g)) adjustClustersAndEdges(g); - // log.info('Graph after', graphlib.json.write(g), g.node('C').graph) + // log.info('Graph after', graphlibJson.write(g), g.node('C').graph) const CGraph = g.node('C').graph; const DGraph = CGraph.node('D').graph; @@ -278,9 +279,9 @@ describe('Graphlib decorations', () => { g.setEdge('A', 'C', { data: 'link2' }, '2'); log.info('Graph before', g.node('D')); - log.info('Graph before', graphlib.json.write(g)); + log.info('Graph before', graphlibJson.write(g)); adjustClustersAndEdges(g); - log.trace('Graph after', graphlib.json.write(g)); + log.trace('Graph after', graphlibJson.write(g)); expect(g.nodes()).toEqual(['C', 'B', 'A']); expect(g.nodes().length).toBe(3); expect(g.edges().length).toBe(2); @@ -333,11 +334,11 @@ describe('Graphlib decorations', () => { g.setEdge('c', 'd', { data: 'link2' }, '2'); g.setEdge('d', 'e', { data: 'link2' }, '2'); - log.info('Graph before', graphlib.json.write(g)); + log.info('Graph before', graphlibJson.write(g)); adjustClustersAndEdges(g); const bGraph = g.node('b').graph; - // log.trace('Graph after', graphlib.json.write(g)) - log.info('Graph after', graphlib.json.write(bGraph)); + // log.trace('Graph after', graphlibJson.write(g)) + log.info('Graph after', graphlibJson.write(bGraph)); expect(bGraph.nodes().length).toBe(3); expect(bGraph.edges().length).toBe(2); }); @@ -359,13 +360,13 @@ describe('Graphlib decorations', () => { g.setParent('c', 'b'); g.setParent('e', 'c'); - log.info('Graph before', graphlib.json.write(g)); + log.info('Graph before', graphlibJson.write(g)); adjustClustersAndEdges(g); const aGraph = g.node('a').graph; const bGraph = aGraph.node('b').graph; - log.info('Graph after', graphlib.json.write(aGraph)); + log.info('Graph after', graphlibJson.write(aGraph)); const cGraph = bGraph.node('c').graph; - // log.trace('Graph after', graphlib.json.write(g)) + // log.trace('Graph after', graphlibJson.write(g)) expect(aGraph.nodes().length).toBe(1); expect(bGraph.nodes().length).toBe(1); expect(cGraph.nodes().length).toBe(1); @@ -387,14 +388,14 @@ flowchart TB const exportedGraph = JSON.parse( '{"options":{"directed":true,"multigraph":true,"compound":true},"nodes":[{"v":"A","value":{"labelStyle":"","shape":"rect","labelText":"A","rx":0,"ry":0,"class":"default","style":"","id":"A","width":500,"type":"group","padding":15}},{"v":"B","value":{"labelStyle":"","shape":"rect","labelText":"B","rx":0,"ry":0,"class":"default","style":"","id":"B","width":500,"type":"group","padding":15},"parent":"A"},{"v":"b","value":{"labelStyle":"","shape":"rect","labelText":"b","rx":0,"ry":0,"class":"default","style":"","id":"b","padding":15},"parent":"A"},{"v":"c","value":{"labelStyle":"","shape":"rect","labelText":"c","rx":0,"ry":0,"class":"default","style":"","id":"c","padding":15},"parent":"B"},{"v":"a","value":{"labelStyle":"","shape":"rect","labelText":"a","rx":0,"ry":0,"class":"default","style":"","id":"a","padding":15},"parent":"A"}],"edges":[{"v":"b","w":"B","name":"1","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-b-B","classes":"flowchart-link LS-b LE-B"}},{"v":"a","w":"c","name":"2","value":{"minlen":1,"arrowhead":"normal","arrowTypeStart":"arrow_open","arrowTypeEnd":"arrow_point","thickness":"normal","pattern":"solid","style":"fill:none","labelStyle":"","arrowheadStyle":"fill: #333","labelpos":"c","labelType":"text","label":"","id":"L-a-c","classes":"flowchart-link LS-a LE-c"}}],"value":{"rankdir":"TB","nodesep":50,"ranksep":50,"marginx":8,"marginy":8}}' ); - const gr = graphlib.json.read(exportedGraph); + const gr = graphlibJson.read(exportedGraph); - log.info('Graph before', graphlib.json.write(gr)); + log.info('Graph before', graphlibJson.write(gr)); adjustClustersAndEdges(gr); const aGraph = gr.node('A').graph; const bGraph = aGraph.node('B').graph; - log.info('Graph after', graphlib.json.write(aGraph)); - // log.trace('Graph after', graphlib.json.write(g)) + log.info('Graph after', graphlibJson.write(aGraph)); + // log.trace('Graph after', graphlibJson.write(g)) expect(aGraph.parent('c')).toBe('B'); expect(aGraph.parent('B')).toBe(undefined); }); diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index bca3c01c8..905e6ee2f 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -1,5 +1,5 @@ import { select } from 'd3'; -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { log } from '../../logger'; import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index 357647427..74e2a48c2 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -1,6 +1,6 @@ import { select } from 'd3'; import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { log } from '../../logger'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index d0771fe39..36267f415 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -1,4 +1,4 @@ -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { line, curveBasis, select } from 'd3'; import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; import { getConfig } from '../../config'; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index 0a736b22b..fa1b692cf 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -1,4 +1,4 @@ -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { select, curveLinear, selectAll } from 'd3'; import flowDb from './flowDb'; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 0fcbce545..7febc4034 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -1,4 +1,4 @@ -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { select, curveLinear, selectAll } from 'd3'; import { getConfig } from '../../config'; import { render as Render } from 'dagre-d3-es'; diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js index 6b741fc12..6cba33f88 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js @@ -1,6 +1,6 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash/filter'; +import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js index aa8e9217f..6fec233e7 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js @@ -1,6 +1,6 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash/filter'; +import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index 0338ec50c..10469d50d 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -1,6 +1,6 @@ import { line, select } from 'd3'; import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 03c678789..6fdeb723e 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -1,4 +1,4 @@ -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib'; import { select } from 'd3'; import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer.js b/packages/mermaid/src/diagrams/state/stateRenderer.js index 783460cc6..57b1d1665 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer.js @@ -1,6 +1,6 @@ import { select } from 'd3'; import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; -import graphlib from 'graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { log } from '../../logger'; import common from '../common/common'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 75e6ae2fe..0b2e85cd6 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -29,7 +29,7 @@ import utils, { directiveSanitizer } from './utils'; import DOMPurify from 'dompurify'; import { MermaidConfig } from './config.type'; import { evaluate } from './diagrams/common/common'; -import isEmpty from 'lodash/isEmpty'; +import isEmpty from 'lodash-es/isEmpty'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 4d3e07e6b..54262f10e 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -3,7 +3,7 @@ import utils from './utils'; import assignWithDepth from './assignWithDepth'; import { detectType } from './diagram-api/detectType'; import { addDiagrams } from './diagram-api/diagram-orchestration'; -import memoize from 'lodash/memoize'; +import memoize from 'lodash-es/memoize'; import { MockD3 } from 'd3'; addDiagrams(); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 82f89d61a..3689a01a1 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -21,7 +21,7 @@ import { log } from './logger'; import { detectType } from './diagram-api/detectType'; import assignWithDepth from './assignWithDepth'; import { MermaidConfig } from './config.type'; -import memoize from 'lodash/memoize'; +import memoize from 'lodash-es/memoize'; // Effectively an enum of the supported curve types, accessible by name const d3CurveTypes = { @@ -561,54 +561,77 @@ export const drawSimpleText = function ( return textElem; }; -export const wrapLabel = memoize( - (label, maxWidth, config) => { - if (!label) { - return label; - } - config = Object.assign( - { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '
' }, - config - ); - if (common.lineBreakRegex.test(label)) { - return label; - } - const words = label.split(' '); - const completedLines = []; - let nextLine = ''; - words.forEach((word, index) => { - const wordLength = calculateTextWidth(`${word} `, config); - const nextLineLength = calculateTextWidth(nextLine, config); - if (wordLength > maxWidth) { - const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config); - completedLines.push(nextLine, ...hyphenatedStrings); - nextLine = remainingWord; - } else if (nextLineLength + wordLength >= maxWidth) { - completedLines.push(nextLine); - nextLine = word; - } else { - nextLine = [nextLine, word].filter(Boolean).join(' '); - } - const currentWord = index + 1; - const isLastWord = currentWord === words.length; - if (isLastWord) { - completedLines.push(nextLine); - } - }); - return completedLines.filter((line) => line !== '').join(config.joinWith); - }, - (label, maxWidth, config) => - `${label}${maxWidth}${config.fontSize}${config.fontWeight}${config.fontFamily}${config.joinWith}` -); +interface WrapLabelConfig { + fontSize: number; + fontFamily: string; + fontWeight: number; + joinWith: string; +} -const breakString = memoize( - (word, maxWidth, hyphenCharacter = '-', config) => { +export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfig) => string = + memoize( + (label: string, maxWidth: string, config: WrapLabelConfig): string => { + if (!label) { + return label; + } + config = Object.assign( + { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '
' }, + config + ); + if (common.lineBreakRegex.test(label)) { + return label; + } + const words = label.split(' '); + const completedLines = []; + let nextLine = ''; + words.forEach((word, index) => { + const wordLength = calculateTextWidth(`${word} `, config); + const nextLineLength = calculateTextWidth(nextLine, config); + if (wordLength > maxWidth) { + const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config); + completedLines.push(nextLine, ...hyphenatedStrings); + nextLine = remainingWord; + } else if (nextLineLength + wordLength >= maxWidth) { + completedLines.push(nextLine); + nextLine = word; + } else { + nextLine = [nextLine, word].filter(Boolean).join(' '); + } + const currentWord = index + 1; + const isLastWord = currentWord === words.length; + if (isLastWord) { + completedLines.push(nextLine); + } + }); + return completedLines.filter((line) => line !== '').join(config.joinWith); + }, + (label, maxWidth, config) => + `${label}${maxWidth}${config.fontSize}${config.fontWeight}${config.fontFamily}${config.joinWith}` + ); + +interface BreakStringOutput { + hyphenatedStrings: string[]; + remainingWord: string; +} + +const breakString: ( + word: string, + maxWidth: number, + hyphenCharacter: string, + config: WrapLabelConfig +) => BreakStringOutput = memoize( + ( + word: string, + maxWidth: number, + hyphenCharacter = '-', + config: WrapLabelConfig + ): BreakStringOutput => { config = Object.assign( { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 }, config ); const characters = word.split(''); - const lines = []; + const lines: string[] = []; let currentLine = ''; characters.forEach((character, index) => { const nextLine = `${currentLine}${character}`; @@ -667,6 +690,16 @@ export function calculateTextWidth( return calculateTextDimensions(text, config).width; } +interface TextDimensionConfig { + fontSize?: number; + fontWeight?: number; + fontFamily?: string; +} +interface TextDimensions { + width: number; + height: number; + lineHeight?: number; +} /** * This calculates the dimensions of the given text, font size, font family, font weight, and * margins. @@ -676,15 +709,11 @@ export function calculateTextWidth( * the resulting size * @returns The dimensions for the given text */ -export const calculateTextDimensions = memoize( - function ( - text: string, - config: { - fontSize?: number; - fontWeight?: number; - fontFamily?: string; - } - ) { +export const calculateTextDimensions: ( + text: string, + config: TextDimensionConfig +) => TextDimensions = memoize( + (text: string, config: TextDimensionConfig): TextDimensions => { config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config); const { fontSize, fontFamily, fontWeight } = config; if (!text) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bd96ee8f..91e22e019 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,6 +91,9 @@ importers: eslint-plugin-json: specifier: ^3.1.0 version: 3.1.0 + eslint-plugin-lodash: + specifier: ^7.4.0 + version: 7.4.0_eslint@8.27.0 eslint-plugin-markdown: specifier: ^3.0.0 version: 3.0.0_eslint@8.27.0 @@ -100,6 +103,9 @@ importers: eslint-plugin-tsdoc: specifier: ^0.2.17 version: 0.2.17 + eslint-plugin-unicorn: + specifier: ^45.0.0 + version: 45.0.0_eslint@8.27.0 express: specifier: ^4.18.2 version: 4.18.2 @@ -179,8 +185,8 @@ importers: specifier: ^7.0.0 version: 7.6.1 dagre-d3-es: - specifier: 7.0.2 - version: 7.0.2 + specifier: 7.0.4 + version: 7.0.4 dompurify: specifier: 2.4.1 version: 2.4.1 @@ -193,7 +199,7 @@ importers: khroma: specifier: ^2.0.0 version: 2.0.0 - lodash: + lodash-es: specifier: ^4.17.21 version: 4.17.21 moment-mini: @@ -218,9 +224,9 @@ importers: '@types/jsdom': specifier: ^20.0.1 version: 20.0.1 - '@types/lodash': - specifier: ^4.14.188 - version: 4.14.188 + '@types/lodash-es': + specifier: ^4.17.6 + version: 4.17.6 '@types/micromatch': specifier: ^4.0.2 version: 4.0.2 @@ -1766,7 +1772,7 @@ packages: '@types/node': 18.11.9 ansi-escapes: 4.3.2 chalk: 4.1.2 - ci-info: 3.4.0 + ci-info: 3.6.2 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.2.0 @@ -2498,6 +2504,12 @@ packages: resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} dev: true + /@types/lodash-es/4.17.6: + resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==} + dependencies: + '@types/lodash': 4.14.188 + dev: true + /@types/lodash/4.14.188: resolution: {integrity: sha512-zmEmF5OIM3rb7SbLCFYoQhO4dGt2FRM9AMkxvA3LaADOF1n8in/zGJlWji9fmafLoNyz+FoL6FE0SLtGIArD7w==} dev: true @@ -3779,6 +3791,11 @@ packages: ieee754: 1.2.1 dev: true + /builtin-modules/3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + /bytes/3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} @@ -3969,8 +3986,9 @@ packages: engines: {node: '>=6.0'} dev: true - /ci-info/3.4.0: - resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} + /ci-info/3.6.2: + resolution: {integrity: sha512-lVZdhvbEudris15CLytp2u6Y0p5EKfztae9Fqa189MfNmln9F33XuH69v5fvNfiRN5/0eAUz2yJL3mo+nhaRKg==} + engines: {node: '>=8'} dev: true /cjs-module-lexer/1.2.2: @@ -3984,6 +4002,13 @@ packages: jsonlint: 1.6.0 dev: true + /clean-regexp/1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -4814,8 +4839,8 @@ packages: d3-zoom: 3.0.0 dev: false - /dagre-d3-es/7.0.2: - resolution: {integrity: sha512-m9+5yhzkf9gyklDMdWlQC/8bayGVlTF8GspmN6XC6nnZjas6kAmffvh0c/EcyFhQ+fp4QIl0fMpNdv76AJGlVQ==} + /dagre-d3-es/7.0.4: + resolution: {integrity: sha512-fQL8ldFR9UYpecz48d1smrXNJ9zGUK38Vl5OzX6Fhn9LR+oQh0GzHRPQylP5kWawmMTKm1QtqcHMVySMJ5CYaQ==} dependencies: d3: 7.6.1 lodash-es: 4.17.21 @@ -5554,6 +5579,16 @@ packages: vscode-json-languageservice: 4.2.1 dev: true + /eslint-plugin-lodash/7.4.0_eslint@8.27.0: + resolution: {integrity: sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==} + engines: {node: '>=10'} + peerDependencies: + eslint: '>=2' + dependencies: + eslint: 8.27.0 + lodash: 4.17.21 + dev: true + /eslint-plugin-markdown/3.0.0_eslint@8.27.0: resolution: {integrity: sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5578,6 +5613,31 @@ packages: '@microsoft/tsdoc-config': 0.16.2 dev: true + /eslint-plugin-unicorn/45.0.0_eslint@8.27.0: + resolution: {integrity: sha512-iP8cMRxXKHonKioOhnCoCcqVhoqhAp6rB+nsoLjXFDxTHz3btWMAp8xwzjHA0B1K6YV/U/Yvqn1bUXZt8sJPuQ==} + engines: {node: '>=14.18'} + peerDependencies: + eslint: '>=8.28.0' + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + ci-info: 3.6.2 + clean-regexp: 1.0.0 + eslint: 8.27.0 + eslint-utils: 3.0.0_eslint@8.27.0 + esquery: 1.4.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.0 + jsesc: 3.0.2 + lodash: 4.17.21 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.24 + regjsparser: 0.9.1 + safe-regex: 2.1.1 + semver: 7.3.8 + strip-indent: 3.0.0 + dev: true + /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -6052,16 +6112,6 @@ packages: resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==} dev: true - /follow-redirects/1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: true - /follow-redirects/1.15.2_debug@4.3.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -6648,7 +6698,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.2 + follow-redirects: 1.15.2_debug@4.3.2 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -6871,11 +6921,18 @@ packages: engines: {node: '>=4'} dev: true + /is-builtin-module/3.2.0: + resolution: {integrity: sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + /is-ci/3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true dependencies: - ci-info: 3.4.0 + ci-info: 3.6.2 dev: true /is-core-module/2.10.0: @@ -7173,7 +7230,7 @@ packages: '@types/node': 18.11.9 babel-jest: 29.3.1_@babel+core@7.12.3 chalk: 4.1.2 - ci-info: 3.4.0 + ci-info: 3.6.2 deepmerge: 4.2.2 glob: 7.2.3 graceful-fs: 4.2.10 @@ -7459,7 +7516,7 @@ packages: '@jest/types': 29.3.1 '@types/node': 18.11.9 chalk: 4.1.2 - ci-info: 3.4.0 + ci-info: 3.6.2 graceful-fs: 4.2.10 picomatch: 2.3.1 dev: true @@ -7648,12 +7705,23 @@ packages: - utf-8-validate dev: true + /jsesc/0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + /jsesc/2.5.2: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} hasBin: true dev: true + /jsesc/3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: true + /json-buffer/3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: true @@ -8969,6 +9037,11 @@ packages: xmlbuilder: 15.1.1 dev: true + /pluralize/8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true + /png-async/0.9.4: resolution: {integrity: sha512-B//AXX9TkneKfgtOpT1mdUnnhk2BImGD+a98vImsMU8uo1dBeHyW/kM2erWZ/CsYteTPU/xKG+t6T62heHkC3A==} dev: true @@ -9255,11 +9328,23 @@ packages: strip-indent: 3.0.0 dev: true + /regexp-tree/0.1.24: + resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} + hasBin: true + dev: true + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} dev: true + /regjsparser/0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + /remark-parse/10.0.1: resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==} dependencies: @@ -9491,6 +9576,12 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true + /safe-regex/2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + dependencies: + regexp-tree: 0.1.24 + dev: true + /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}