diff --git a/docs/syntax/diagrams.md b/docs/syntax/diagrams.md
index aeb0730ff..d56e0bf93 100644
--- a/docs/syntax/diagrams.md
+++ b/docs/syntax/diagrams.md
@@ -181,106 +181,77 @@ erDiagram
### Complex flowchart
-::::::{tab-set}
+:::::{tab-set}
-:::::{tab-item} Source
+::::{tab-item} Source
````markdown
```mermaid
-graph TB
- A["Painless Operators"]
-
- B["General"]
- C["Numeric"]
- D["Boolean"]
- E["Reference"]
- F["Array"]
-
- B1["Control expression flow and
value assignment"]
- C1["Mathematical operations and
bit manipulation"]
- D1["Boolean logic and
conditional evaluation"]
- E1["Object interaction and
safe data access"]
- F1["Array manipulation and
element access"]
-
- B2["Precedence ( )
Function Call ( )
Cast ( )
Conditional ? :
Elvis ?:
Assignment =
Compound Assignment $="]
- C2["Post/Pre Increment ++
Post/Pre Decrement --
Unary +/-
Bitwise Not ~
Multiplication *
Division /
Remainder %
Addition +
Subtraction -
Shift <<, >>, >>>
Bitwise And &
Bitwise Xor ^
Bitwise Or |"]
- D2["Boolean Not !
Comparison >, >=, <, <=
Instanceof instanceof
Equality ==, !=
Identity ===, !==
Boolean Xor ^
Boolean And &&
Boolean Or ||"]
- E2["Method Call . ( )
Field Access .
Null Safe ?.
New Instance new ( )
String Concatenation +
List/Map Init [ ], [ : ]
List/Map Access [ ]"]
- F2["Array Init [ ] { }
Array Access [ ]
Array Length .length
New Array new [ ]"]
-
- A --> B & C & D & E & F
- B --> B1
- C --> C1
- D --> D1
- E --> E1
- F --> F1
- B1 --> B2
- C1 --> C2
- D1 --> D2
- E1 --> E2
- F1 --> F2
-
- classDef rootNode fill:#0B64DD,stroke:#101C3F,stroke-width:2px,color:#fff
- classDef categoryBox fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#343741
- classDef descBox fill:#48EFCF,stroke:#343741,stroke-width:2px,color:#343741
- classDef exampleBox fill:#f5f7fa,stroke:#343741,stroke-width:2px,color:#343741
-
- class A rootNode
- class B,C,D,E,F categoryBox
- class B1,C1,D1,E1,F1 descBox
- class B2,C2,D2,E2,F2 exampleBox
+flowchart TD
+ subgraph t2["T2"]
+ direction TB
+ enterprise_app["Enterprise application"]
+
+ subgraph subscription["Subscription"]
+ subgraph rg["Resource Group"]
+ event_hub["event hub"]
+ storage_account["storage account"]
+ end
+ spacer1[" "]:::hidden
+ spacer2[" "]:::hidden
+ spacer1 ~~~ spacer2
+ end
+ enterprise_app -- "Azure Event Hubs
Data Receiver" --> event_hub
+ enterprise_app -- "Storage Blob Data
Contributor" --> storage_account
+ end
+
+ app_reg --> enterprise_app
+
+ subgraph t1["T1"]
+ direction TB
+ app_reg["App registration"]
+ client_secret["Client secret"]
+ app_reg --> client_secret
+ end
+
+ classDef hidden fill:none,stroke:none,color:transparent,width:0px;
```
````
-:::::
+::::
-:::::{tab-item} Rendered
+::::{tab-item} Rendered
```mermaid
-graph TB
- A["Painless Operators"]
-
- B["General"]
- C["Numeric"]
- D["Boolean"]
- E["Reference"]
- F["Array"]
-
- B1["Control expression flow and
value assignment"]
- C1["Mathematical operations and
bit manipulation"]
- D1["Boolean logic and
conditional evaluation"]
- E1["Object interaction and
safe data access"]
- F1["Array manipulation and
element access"]
-
- B2["Precedence ( )
Function Call ( )
Cast ( )
Conditional ? :
Elvis ?:
Assignment =
Compound Assignment $="]
- C2["Post/Pre Increment ++
Post/Pre Decrement --
Unary +/-
Bitwise Not ~
Multiplication *
Division /
Remainder %
Addition +
Subtraction -
Shift <<, >>, >>>
Bitwise And &
Bitwise Xor ^
Bitwise Or |"]
- D2["Boolean Not !
Comparison >, >=, <, <=
Instanceof instanceof
Equality ==, !=
Identity ===, !==
Boolean Xor ^
Boolean And &&
Boolean Or ||"]
- E2["Method Call . ( )
Field Access .
Null Safe ?.
New Instance new ( )
String Concatenation +
List/Map Init [ ], [ : ]
List/Map Access [ ]"]
- F2["Array Init [ ] { }
Array Access [ ]
Array Length .length
New Array new [ ]"]
-
- A --> B & C & D & E & F
- B --> B1
- C --> C1
- D --> D1
- E --> E1
- F --> F1
- B1 --> B2
- C1 --> C2
- D1 --> D2
- E1 --> E2
- F1 --> F2
-
- classDef rootNode fill:#0B64DD,stroke:#101C3F,stroke-width:2px,color:#fff
- classDef categoryBox fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#343741
- classDef descBox fill:#48EFCF,stroke:#343741,stroke-width:2px,color:#343741
- classDef exampleBox fill:#f5f7fa,stroke:#343741,stroke-width:2px,color:#343741
-
- class A rootNode
- class B,C,D,E,F categoryBox
- class B1,C1,D1,E1,F1 descBox
- class B2,C2,D2,E2,F2 exampleBox
+flowchart TD
+ subgraph t2["T2"]
+ direction TB
+ enterprise_app["Enterprise application"]
+
+ subgraph subscription["Subscription"]
+ subgraph rg["Resource Group"]
+ event_hub["event hub"]
+ storage_account["storage account"]
+ end
+ spacer1[" "]:::hidden
+ spacer2[" "]:::hidden
+ spacer1 ~~~ spacer2
+ end
+ enterprise_app -- "Azure Event Hubs
Data Receiver" --> event_hub
+ enterprise_app -- "Storage Blob Data
Contributor" --> storage_account
+ end
+
+ app_reg --> enterprise_app
+
+ subgraph t1["T1"]
+ direction TB
+ app_reg["App registration"]
+ client_secret["Client secret"]
+ app_reg --> client_secret
+ end
+
+ classDef hidden fill:none,stroke:none,color:transparent,width:0px;
```
-:::::
-
-::::::
+::::
+:::::
## Interactive controls
Mermaid diagrams include interactive controls that appear when you hover over the diagram:
diff --git a/src/Elastic.Documentation.Site/Assets/mermaid.test.ts b/src/Elastic.Documentation.Site/Assets/mermaid.test.ts
new file mode 100644
index 000000000..6a4ae1323
--- /dev/null
+++ b/src/Elastic.Documentation.Site/Assets/mermaid.test.ts
@@ -0,0 +1,302 @@
+import { normalizeToXml, sanitizeSvgNode } from './mermaid'
+
+// ---------------------------------------------------------------------------
+// Test helpers
+// ---------------------------------------------------------------------------
+
+const SVG_NS = 'http://www.w3.org/2000/svg'
+
+const wrapSvg = (inner: string) => ``
+
+const createRect = () => ({
+ width: 100,
+ height: 60,
+ top: 0,
+ left: 0,
+ bottom: 60,
+ right: 100,
+ x: 0,
+ y: 0,
+ toJSON: () => ({}),
+})
+
+const setVisibleRect = (element: HTMLElement) => {
+ Object.defineProperty(element, 'getBoundingClientRect', {
+ value: () => createRect(),
+ })
+ Object.defineProperty(element, 'getClientRects', {
+ value: () => [createRect()],
+ })
+}
+
+class MockIntersectionObserver {
+ callback: IntersectionObserverCallback
+ observe = jest.fn()
+ unobserve = jest.fn()
+ disconnect = jest.fn()
+
+ constructor(callback: IntersectionObserverCallback) {
+ this.callback = callback
+ }
+}
+
+const setupMermaid = () => {
+ window.mermaid = {
+ initialize: jest.fn(),
+ render: jest.fn().mockResolvedValue({
+ svg: ``,
+ }),
+ }
+}
+
+const setupScriptLoad = () =>
+ jest.spyOn(document.head, 'appendChild').mockImplementation((node) => {
+ if (node instanceof HTMLScriptElement && node.onload) {
+ node.onload(new Event('load'))
+ }
+ return node
+ })
+
+// ---------------------------------------------------------------------------
+// normalizeToXml
+// ---------------------------------------------------------------------------
+
+describe('normalizeToXml', () => {
+ it('converts
to self-closing
', () => {
+ expect(normalizeToXml('
')).toBe('
')
+ })
+
+ it('converts