diff --git a/.cspell/code-terms.txt b/.cspell/code-terms.txt index f2b37dcaf..bdde26d14 100644 --- a/.cspell/code-terms.txt +++ b/.cspell/code-terms.txt @@ -110,6 +110,7 @@ strikethrough stringifying struct STYLECLASS +STYLEDEF STYLEOPTS subcomponent subcomponents diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 9a1a27abe..7bc467c83 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -558,6 +558,29 @@ stateDiagram-v2 { logLevel: 0, fontFamily: 'courier' } ); }); + it(' can have styles applied ', () => { + imgSnapshotTest( + ` +stateDiagram-v2 +AState +style AState fill:#636,border:1px solid red,color:white; + `, + { logLevel: 0, fontFamily: 'courier' } + ); + }); + it(' should let styles take preceedence over classes', () => { + imgSnapshotTest( + ` +stateDiagram-v2 +AState: Should NOT be white +BState +classDef exampleStyleClass fill:#fff,color: blue; +class AState,BState exampleStyleClass +style AState fill:#636,border:1px solid red,color:white; + `, + { logLevel: 0, fontFamily: 'courier' } + ); + }); }); it('1433: should render a simple state diagram with a title', () => { imgSnapshotTest( diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index f94c6d82e..7f3c1a5b7 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -75,25 +75,127 @@ -
----
-config:
-  theme: neo
-  look: neo
-  layout: elk
-  elk.mergeEdges: true
-  themeVariables: {}
----
+    

Case 1

+
+
+stateDiagram-v2
+AState: Should NOT be white
+BState
+classDef exampleStyleClass fill:#fff,color: blue;
+class AState,BState exampleStyleClass
+style AState fill:#636,border:1px solid red,color:white;
+      
+
+        %%{init: {"look": "classic"} }%%
+stateDiagram-v2
+AState: Should NOT be white
+BState
+classDef exampleStyleClass fill:#fff,color: blue;
+class AState,BState exampleStyleClass
+style AState fill:#636,border:1px solid red,color:white;
+      
+
+stateDiagram-v2
 
+classDef exampleStyleClass background:#bbb,border:1px solid red;
+a --> b
+class a exampleStyleClass
+%% a:::exampleStyleClass
+      
+
 stateDiagram
-  direction TB
-  A --> B
-  A --> C
-  A --> D
-  A --> E
-  A --> F
-      
+ direction TB + + accTitle: This is the accessible title + accDescr: This is an accessible description + + classDef notMoving fill:white + classDef movement font-style:italic; + classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow + + [*] --> Still:::notMoving + Still --> [*] + Still --> Moving:::movement + Moving --> Still + Moving --> Crash:::movement + Crash:::badBadEvent --> [*] +
+
+stateDiagram-v2
+    MyState
+    note left of MyState : I am a leftie
+    note right of MyState : I am a rightie
+
+      
+
+stateDiagram
+%% direction LR
+
+        state C0 {
+          A0 --> B0
+        }
+
+      C0 --> Apa0
+
+      
+
+stateDiagram
+direction LR
+
+
+        state C1 {
+          A1 --> B1
+        }
+
+      C1 --> Apa1
+
+      
+ +

Case 2

+
+
+stateDiagram
+direction LR
+      state Gorilla0 {
+        state Apa0 {
+          A0 --> B0
+        }
+
+      }
+      Apa --> Gorilla0:Label
+      A0 --> C0
+      %% C1: "`This is C`"
+      
+
+stateDiagram
+direction LR
+        state Apa1 {
+          A1
+        }
+
+      Apa11 --> Apa1
+      A1 --> C1
+      %% C1: "`This is C`"
+      
+
+stateDiagram
+    [*] --> Level1
+
+    state Level1 {
+        [*] --> Level2
+
+        state Level2 {
+            [*] --> level2
+            level2 --> Level3
+
+            state Level3 {
+                [*] --> level3
+                level3 --> [*]
+            }
+        }
+    }
+      
+
 ---
 config:
@@ -184,20 +286,7 @@ Apa --> C
 A --> B & C["C"]
       
-
-stateDiagram
-direction LR
-      state Gorilla0 {
-        state Apa0 {
-          A0 --> B0
-        }
 
-      }
-      Apa0 --> C0
-      A0 --> C0
-      C1: "`This is C`"
-      
 flowchart LR
     subgraph Gorilla
@@ -237,7 +326,7 @@ stateDiagram
 
       
-
+    
 flowchart LR
     Apa --Hello--> C
 
@@ -375,7 +464,7 @@ stateDiagram-v2
       mermaid.initialize({
         theme: 'neo',
         // handdrawnSeed: 12,
-        look: 'neo',
+        look: 'handdrawn',
         // 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
         // layout: 'dagre',
         // layout: 'elk',
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
index 7fe2e3a73..66c852a14 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts
@@ -806,7 +806,8 @@ const addNodeFromVertex = (
       labelStyle: '',
       parentId,
       padding: config.flowchart?.padding || 8,
-      cssStyles: vertex.styles.join(' '),
+      cssStyles: vertex.styles,
+      cssCompiledStyles: getCompiledStyles(vertex.classes),
       cssClasses: vertex.classes.join(' '),
       shape: getTypeFromVertex(vertex),
       dir: vertex.dir,
@@ -820,6 +821,22 @@ const addNodeFromVertex = (
   }
 };
 
+function getCompiledStyles(classDefs: string[]) {
+  let compiledStyles: string[] = [];
+  for (const customClass of classDefs) {
+    const cssClass = classes.get(customClass);
+    if (cssClass) {
+      if (cssClass.styles) {
+        compiledStyles = [...compiledStyles, ...(cssClass.styles ?? [])];
+      }
+      if (cssClass.textStyles) {
+        compiledStyles = [...compiledStyles, ...(cssClass.textStyles ?? [])];
+      }
+    }
+  }
+  return compiledStyles;
+}
+
 export const getData = () => {
   const config = getConfig();
   const nodes: Node[] = [];
@@ -844,7 +861,7 @@ export const getData = () => {
       labelStyle: '',
       parentId: parentDB.get(subGraph.id),
       padding: config.flowchart?.padding || 8,
-      cssStyles: '',
+      cssStyles: [],
       cssClasses: '',
       shape: 'rect',
       dir: subGraph.dir,
diff --git a/packages/mermaid/src/diagrams/state/dataFetcher.js b/packages/mermaid/src/diagrams/state/dataFetcher.js
index a94fb681d..cfc78ff88 100644
--- a/packages/mermaid/src/diagrams/state/dataFetcher.js
+++ b/packages/mermaid/src/diagrams/state/dataFetcher.js
@@ -54,21 +54,39 @@ export function stateDomId(itemId = '', counter = 0, type = '', typeSpacer = DOM
   return `${DOMID_STATE}-${itemId}${typeStr}-${counter}`;
 }
 
-const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, look) => {
+const setupDoc = (parentParsedItem, doc, diagramStates, nodes, edges, altFlag, look, classes) => {
   // graphItemCount = 0;
   log.trace('items', doc);
   doc.forEach((item) => {
     switch (item.stmt) {
       case STMT_STATE:
-        dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look);
+        dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look, classes);
         break;
       case DEFAULT_STATE_TYPE:
-        dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look);
+        dataFetcher(parentParsedItem, item, diagramStates, nodes, edges, altFlag, look, classes);
         break;
       case STMT_RELATION:
         {
-          dataFetcher(parentParsedItem, item.state1, diagramStates, nodes, edges, altFlag, look);
-          dataFetcher(parentParsedItem, item.state2, diagramStates, nodes, edges, altFlag, look);
+          dataFetcher(
+            parentParsedItem,
+            item.state1,
+            diagramStates,
+            nodes,
+            edges,
+            altFlag,
+            look,
+            classes
+          );
+          dataFetcher(
+            parentParsedItem,
+            item.state2,
+            diagramStates,
+            nodes,
+            edges,
+            altFlag,
+            look,
+            classes
+          );
           const edgeData = {
             id: 'edge' + graphItemCount,
             start: item.state1.id,
@@ -134,8 +152,9 @@ let cssClasses = newClassesList(); // style classes defined by a classDef
  *
  * @param nodes
  * @param nodeData
+ * @param classes
  */
-function insertOrUpdateNode(nodes, nodeData) {
+function insertOrUpdateNode(nodes, nodeData, classes) {
   if (!nodeData.id || nodeData.id === '' || nodeData.id === '') {
     return;
   }
@@ -143,29 +162,62 @@ function insertOrUpdateNode(nodes, nodeData) {
   //Populate node style attributes if nodeData has classes defined
   if (nodeData.cssClasses) {
     nodeData.cssClasses.split(' ').forEach((cssClass) => {
-      if (cssClasses[cssClass]) {
-        cssClasses[cssClass].styles.forEach((style) => {
-          // Populate nodeData with style attributes specifically to be used by rough.js
-          if (style && style.startsWith('fill:')) {
-            nodeData.backgroundColor = style.replace('fill:', '');
-          }
-          if (style && style.startsWith('stroke:')) {
-            nodeData.borderColor = style.replace('stroke:', '');
-          }
-          if (style && style.startsWith('stroke-width:')) {
-            nodeData.borderWidth = style.replace('stroke-width:', '');
-          }
+      if (classes.get(cssClass)) {
+        const classDef = classes.get(cssClass);
+        // classDef.styles.forEach((style) => {
+        //   nodeData.cssCompiledStyles += style + ',';
+        //   // Populate nodeData with style attributes specifically to be used by rough.js
+        //   if (style && style.startsWith('fill:')) {
+        //     nodeData.backgroundColor = style.replace('fill:', '');
+        //   }
+        //   if (style && style.startsWith('stroke:')) {
+        //     nodeData.borderColor = style.replace('stroke:', '');
+        //   }
+        //   if (style && style.startsWith('stroke-width:')) {
+        //     nodeData.borderWidth = style.replace('stroke-width:', '');
+        //   }
 
-          nodeData.cssStyles += style + ';';
-        });
-        cssClasses[cssClass].textStyles.forEach((style) => {
-          nodeData.labelStyle += style + ';';
-          if (style && style.startsWith('fill:')) {
-            nodeData.labelTextColor = style.replace('fill:', '');
-          }
-        });
+        //   nodeData.cssStyles += style + ';';
+        // });
+
+        // classDef.textStyles.forEach((style) => {
+        //   nodeData.labelStyle += style + ';';
+        //   if (style && style.startsWith('fill:')) {
+        //     nodeData.labelTextColor = style.replace('fill:', '');
+        //   }
+        // });
+        nodeData.cssCompiledStyles = [...nodeData.cssCompiledStyles, ...classDef.styles];
       }
     });
+    //Populate node style attributes if nodeData has classes defined
+    if (nodeData.cssStyles) {
+      // nodeData.cssStyles.split(' ').forEach((csStyle) => {
+      //   if (classes[cssClass]) {
+      //     classes[cssClass].styles.forEach((style) => {
+      //       // Populate nodeData with style attributes specifically to be used by rough.js
+      //       if (style && style.startsWith('fill:')) {
+      //         nodeData.backgroundColor = style.replace('fill:', '');
+      //       }
+      //       if (style && style.startsWith('stroke:')) {
+      //         nodeData.borderColor = style.replace('stroke:', '');
+      //       }
+      //       if (style && style.startsWith('stroke-width:')) {
+      //         nodeData.borderWidth = style.replace('stroke-width:', '');
+      //       }
+      //       nodeData.cssStyles += style + ';';
+      //     });
+      //     classes[cssClass].textStyles.forEach((style) => {
+      //       nodeData.labelStyle += style + ';';
+      //       if (style && style.startsWith('fill:')) {
+      //         nodeData.labelTextColor = style.replace('fill:', '');
+      //       }
+      //     });
+      //   }
+      // });
+    }
+    // nodeData.labelTextColor = '#ffffff';
+    // nodeData.labelStyle = 'color:#ffffff';
+    // nodeData.cssStyles = 'fill:#f77';
   }
   const existingNodeData = nodes.find((node) => node.id === nodeData.id);
   if (existingNodeData) {
@@ -197,7 +249,7 @@ function getClassesFromDbInfo(dbInfoItem) {
         }
         //add comma for all other classes
         else {
-          classStr += dbInfoItem.classes[i] + ' ';
+          classStr += dbInfoItem.classes[i] + ',';
         }
       }
       return classStr;
@@ -206,9 +258,41 @@ function getClassesFromDbInfo(dbInfoItem) {
     }
   }
 }
-export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, altFlag, look) => {
+/**
+ * Get classes from the db for the info item.
+ * If there aren't any or if dbInfoItem isn't defined, return an empty string.
+ * Else create 1 string from the list of classes found
+ *
+ * @param {undefined | null | object} dbInfoItem
+ * @returns {string}
+ */
+function getStylesFromDbInfo(dbInfoItem) {
+  if (dbInfoItem === undefined || dbInfoItem === null) {
+    return;
+  } else {
+    if (dbInfoItem.styles) {
+      return dbInfoItem.styles;
+    } else {
+      return [];
+    }
+  }
+}
+export const dataFetcher = (
+  parent,
+  parsedItem,
+  diagramStates,
+  nodes,
+  edges,
+  altFlag,
+  look,
+  classes
+) => {
   const itemId = parsedItem.id;
-  const classStr = getClassesFromDbInfo(diagramStates.get(itemId));
+  const dbState = diagramStates.get(itemId);
+  const classStr = getClassesFromDbInfo(dbState);
+  const style = getStylesFromDbInfo(dbState);
+
+  log.info('dataFetcher parsedItem', parsedItem, dbState, style);
 
   if (itemId !== 'root') {
     let shape = SHAPE_STATE;
@@ -229,6 +313,7 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
         shape,
         description: common.sanitizeText(itemId, getConfig()),
         cssClasses: `${classStr} ${CSS_DIAGRAM_STATE}`,
+        cssStyles: style,
       };
     }
 
@@ -287,7 +372,8 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
       shape: newNode.shape,
       label: newNode.description,
       cssClasses: newNode.cssClasses,
-      cssStyles: '',
+      cssCompiledStyles: [],
+      cssStyles: newNode.cssStyles,
       id: itemId,
       dir: newNode.dir,
       domId: stateDomId(itemId, graphItemCount),
@@ -319,7 +405,8 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
         label: parsedItem.note.text,
         cssClasses: CSS_DIAGRAM_NOTE,
         // useHtmlLabels: false,
-        cssStyles: '', // styles.style,
+        cssStyles: [],
+        cssCompilesStyles: [],
         id: itemId + NOTE_ID + '-' + graphItemCount,
         domId: stateDomId(itemId, graphItemCount, NOTE),
         type: newNode.type,
@@ -334,7 +421,7 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
         shape: SHAPE_NOTEGROUP,
         label: parsedItem.note.text,
         cssClasses: newNode.cssClasses,
-        cssStyles: '', // styles.style,
+        cssStyles: [],
         id: itemId + PARENT_ID,
         domId: stateDomId(itemId, graphItemCount, PARENT),
         type: 'group',
@@ -352,11 +439,11 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
       nodeData.parentId = parentNodeId;
 
       //insert groupData
-      insertOrUpdateNode(nodes, groupData);
+      insertOrUpdateNode(nodes, groupData, classes);
       //insert noteData
-      insertOrUpdateNode(nodes, noteData);
+      insertOrUpdateNode(nodes, noteData, classes);
       //insert nodeData
-      insertOrUpdateNode(nodes, nodeData);
+      insertOrUpdateNode(nodes, nodeData, classes);
 
       let from = itemId;
       let to = noteData.id;
@@ -382,12 +469,12 @@ export const dataFetcher = (parent, parsedItem, diagramStates, nodes, edges, alt
         look,
       });
     } else {
-      insertOrUpdateNode(nodes, nodeData);
+      insertOrUpdateNode(nodes, nodeData, classes);
     }
   }
   if (parsedItem.doc) {
     log.trace('Adding nodes children ');
-    setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, look);
+    setupDoc(parsedItem, parsedItem.doc, diagramStates, nodes, edges, !altFlag, look, classes);
   }
 };
 
diff --git a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
index 1be9148b8..fed63c444 100644
--- a/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
+++ b/packages/mermaid/src/diagrams/state/parser/state-style.spec.js
@@ -217,4 +217,50 @@ describe('ClassDefs and classes when parsing a State diagram', () => {
       });
     });
   });
+
+  describe('style statement for a state (style)', () => {
+    describe('defining (style)', () => {
+      it('has "style" as a keyword, an id, and can set a css style attribute', function () {
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        style id1 background:#bbb`);
+        stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
+
+        expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+      });
+      it('has "style" as a keyword, an id, and can set a css style attribute', function () {
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        id2
+        style id1,id2 background:#bbb`);
+        stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
+
+        expect(data4Layout.nodes[0].cssStyles).toEqual(['background:#bbb']);
+        expect(data4Layout.nodes[1].cssStyles).toEqual(['background:#bbb']);
+      });
+
+      it('can define multiple attributes separated by commas', function () {
+        stateDiagram.parser.parse(`stateDiagram-v2
+        id1
+        id2
+        style id1,id2 background:#bbb, font-weight:bold, font-style:italic;`);
+
+        stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
+        const data4Layout = stateDiagram.parser.yy.getData();
+
+        expect(data4Layout.nodes[0].cssStyles).toEqual([
+          'background:#bbb',
+          'font-weight:bold',
+          'font-style:italic',
+        ]);
+        expect(data4Layout.nodes[1].cssStyles).toEqual([
+          'background:#bbb',
+          'font-weight:bold',
+          'font-style:italic',
+        ]);
+      });
+    });
+  });
 });
diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
index 44235ecd4..e3bc51235 100644
--- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
+++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
@@ -27,6 +27,13 @@
 %x CLASSDEFID
 %x CLASS
 %x CLASS_STYLE
+
+// Style statement states
+%x STYLE
+%x STYLE_IDS
+%x STYLEDEF_STYLES
+%x STYLEDEF_STYLEOPTS
+
 %x NOTE
 %x NOTE_ID
 %x NOTE_TEXT
@@ -75,6 +82,10 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili
 (\w+)+((","\s*\w+)*)     { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
 [^\n]*             { this.popState(); return 'STYLECLASS' }
 
+"style"\s+   { this.pushState('STYLE'); return 'style'; }
+