Repository: maxGraph/maxGraph
Pull request #403 (2024-05-02 07:29:56)

chore(deps-dev): bump ejs from 3.1.9 to 3.1.10
Bumps the npm_and_yarn group with 1 update in the / directory: [ejs](https://github.com/mde/ejs).

Updates `ejs` from 3.1.9 to 3.1.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/mde/ejs/releases">ejs's releases</a>.</em></p>
<blockquote>
<h2>v3.1.10</h2>
<p>Version 3.1.10</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/mde/ejs/commit/d3f807dea9ce904e20a47a661f2310ce9134dc2a"><code>d3f807d</code></a> Version 3.1.10</li>
<li><a href="https://github.com/mde/ejs/commit/9ee26dde5d7015d9c0e2ff87314cabeac5247c02"><code>9ee26dd</code></a> Mocha TDD</li>
<li><a href="https://github.com/mde/ejs/commit/e469741dca7df2eb400199e1cdb74621e3f89aa5"><code>e469741</code></a> Basic pollution protection</li>
<li><a href="https://github.com/mde/ejs/commit/715e9507fa3e6122dc6430fe0f25a6e6ded300c1"><code>715e950</code></a> Merge pull request <a href="https://redirect.github.com/mde/ejs/issues/756">#756</a> from Jeffrey-mu/main</li>
<li><a href="https://github.com/mde/ejs/commit/cabe3146ad964a1e98db7742abf435906ca79406"><code>cabe314</code></a> Include advanced usage examples</li>
<li><a href="https://github.com/mde/ejs/commit/29b076cdbbf3eb1b4323b33299ab6d79391b2c33"><code>29b076c</code></a> Added header</li>
<li><a href="https://github.com/mde/ejs/commit/11503c79af882e3635b513d57c7f1813792eb127"><code>11503c7</code></a> Merge branch 'main' of github.com:mde/ejs into main</li>
<li><a href="https://github.com/mde/ejs/commit/7690404e2fc1688756938e4d2fc19e0fac77d736"><code>7690404</code></a> Added security banner to README</li>
<li><a href="https://github.com/mde/ejs/commit/f47d7aedd51a983e4f73045f962b1209096b5800"><code>f47d7ae</code></a> Update SECURITY.md</li>
<li><a href="https://github.com/mde/ejs/commit/828cea1687e3db459ab09d2f405d2444c7580b90"><code>828cea1</code></a> Update SECURITY.md</li>
<li>Additional commits viewable in <a href="https://github.com/mde/ejs/compare/v3.1.9...v3.1.10">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ejs&package-manager=npm_and_yarn&previous-version=3.1.9&new-version=3.1.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/maxGraph/maxGraph/network/alerts).

</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 793de476e..c100c0600 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # `maxGraph` Change Log
 
+## 0.10.2
+
+Release date: `2024-05-24`
+
+For more details, see the [0.10.2 Changelog](https://github.com/maxGraph/maxGraph/releases/tag/v0.10.2) on the GitHub release page.
+
+This release contains bug fixes.
+
 ## 0.10.1
 
 Release date: `2024-04-23`
diff --git a/package-lock.json b/package-lock.json
index 0e1bcfd87..2c2917a69 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11490,9 +11490,9 @@
       "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
     },
     "node_modules/ejs": {
-      "version": "3.1.9",
-      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
-      "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
+      "version": "3.1.10",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+      "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
       "dev": true,
       "dependencies": {
         "jake": "^10.8.5"
@@ -28219,7 +28219,7 @@
     },
     "packages/core": {
       "name": "@maxgraph/core",
-      "version": "0.10.1",
+      "version": "0.10.2",
       "license": "Apache-2.0",
       "devDependencies": {
         "jest": "^29.3.1",
diff --git a/packages/core/css/common.css b/packages/core/css/common.css
index f7eb430ae..21f389211 100644
--- a/packages/core/css/common.css
+++ b/packages/core/css/common.css
@@ -25,7 +25,7 @@ div.mxRubberband {
 	padding: 0px;
 	margin: 0px;
 }
-div.MaxWindow {
+div.mxWindow {
 	-webkit-box-shadow: 3px 3px 12px #C0C0C0;
 	-moz-box-shadow: 3px 3px 12px #C0C0C0;
 	box-shadow: 3px 3px 12px #C0C0C0;
@@ -36,7 +36,7 @@ div.MaxWindow {
 	overflow: hidden;
 	z-index: 1;
 }
-table.MaxWindow {
+table.mxWindow {
 	border-collapse: collapse;
 	table-layout: fixed;
   	font-family: Arial;
diff --git a/packages/core/package.json b/packages/core/package.json
index 6028b9de4..01416189f 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -2,7 +2,7 @@
   "name": "@maxgraph/core",
   "license": "Apache-2.0",
   "private": false,
-  "version": "0.10.1",
+  "version": "0.10.2",
   "type": "module",
   "description": "maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.",
   "keywords": [
diff --git a/packages/core/src/Client.ts b/packages/core/src/Client.ts
index fcd30a36a..846fba40a 100644
--- a/packages/core/src/Client.ts
+++ b/packages/core/src/Client.ts
@@ -20,7 +20,7 @@ class Client {
   /**
    * The version of the `maxGraph` library.
    */
-  static VERSION = '0.10.1';
+  static VERSION = '0.10.2';
 
   /**
    * Base path for all URLs in the core without trailing slash.
diff --git a/packages/core/src/gui/MaxWindow.ts b/packages/core/src/gui/MaxWindow.ts
index 2cb6af317..54cec9c83 100644
--- a/packages/core/src/gui/MaxWindow.ts
+++ b/packages/core/src/gui/MaxWindow.ts
@@ -188,7 +188,7 @@ class MaxWindow extends EventSource {
     minimizable = true,
     movable = true,
     replaceNode: HTMLElement | null = null,
-    style = ''
+    style?: string
   ) {
     super();
 
@@ -235,7 +235,7 @@ class MaxWindow extends EventSource {
     y: number,
     width: number | null = null,
     height: number | null = null,
-    style = 'MaxWindow'
+    style = 'mxWindow'
   ): void {
     this.div = document.createElement('div');
     this.div.className = style;
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 362c86e94..9718ae826 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -197,7 +197,7 @@ export type CellStateStyle = {
   endFill?: boolean;
   /**
    * Set the fill color of the end arrow marker if {@link endFill} is `true`.
-   * If not set, use {@link startStrokeColor}.
+   * If not set, use {@link strokeColor}.
    * @since 0.10.0
    */
   endFillColor?: ColorValue;
@@ -257,11 +257,13 @@ export type CellStateStyle = {
    */
   exitY?: number;
   /**
-   * The possible values are all HTML color names or HEX codes, as well as special keywords such
-   * as `swimlane`, `inherit`, `indicated` to use the color code of a related cell or the
-   * indicator shape.
+   * The possible values are all HTML color names or HEX codes, as well as special keywords such as:
+   * - `indicated` to use the color of a related cell or the indicator shape
+   * - `inherit` to use the color of the direct parent cell
+   * - `none` for no color
+   * - `swimlane` to use the color of the parent swimlane if one exists in the parent hierarchy
    */
-  fillColor?: ColorValue;
+  fillColor?: SpecialStyleColorValue;
   /**
    * Possible range is `0-100`.
    */
@@ -290,9 +292,13 @@ export type CellStateStyle = {
    */
   foldable?: boolean;
   /**
-   * The possible values are all HTML color names or HEX codes.
+   * The possible values are all HTML color names or HEX codes, as well as special keywords such as:
+   * - `indicated` to use the color of a related cell or the indicator shape
+   * - `inherit` to use the color of the direct parent cell
+   * - `none` for no color
+   * - `swimlane` to use the color of the parent swimlane if one exists in the parent hierarchy
    */
-  fontColor?: ColorValue;
+  fontColor?: SpecialStyleColorValue;
   /**
    * The possible values are names such as `Arial; Dialog; Verdana; Times New Roman`.
    */
@@ -312,9 +318,13 @@ export type CellStateStyle = {
    */
   glass?: boolean;
   /**
-   * The possible values are all HTML color names or HEX codes.
+   * The possible values are all HTML color names or HEX codes, as well as special keywords such as:
+   * - `indicated` to use the color of a related cell or the indicator shape
+   * - `inherit` to use the color of the direct parent cell
+   * - `none` for no color
+   * - `swimlane` to use the color of the parent swimlane if one exists in the parent hierarchy
    */
-  gradientColor?: ColorValue;
+  gradientColor?: SpecialStyleColorValue;
   /**
    * Generally, and by default in maxGraph, gradient painting is done from the value of {@link fillColor} to the value of {@link gradientColor}.
    * If we take the example of 'north', this means that the {@link fillColor} color is at the bottom of paint pattern
@@ -733,11 +743,13 @@ export type CellStateStyle = {
    */
   startSize?: number;
   /**
-   * The possible values are all HTML color names or HEX codes, as well as special keywords such
-   * as `swimlane`, `inherit`, `indicated` to use the color code of a related cell or the
-   * indicator shape or `none` for no color.
+   * The possible values are all HTML color names or HEX codes, as well as special keywords such as:
+   * - `indicated` to use the color of a related cell or the indicator shape
+   * - `inherit` to use the color of the direct parent cell
+   * - `none` for no color
+   * - `swimlane` to use the color of the parent swimlane if one exists in the parent hierarchy
    */
-  strokeColor?: ColorValue;
+  strokeColor?: SpecialStyleColorValue;
   /**
    * The possible range is `0-100`.
    */
@@ -752,7 +764,7 @@ export type CellStateStyle = {
   /**
    * The fill color of the `swimlane` background.
    * The possible values are all HTML color names or HEX codes.
-   * @default no backgroune
+   * @default no background
    */
   swimlaneFillColor?: ColorValue;
   /**
@@ -841,6 +853,14 @@ export type NumericCellStateStyleKeys = NonNullable<
 >;
 
 export type ColorValue = string;
+/** Color values and special placeholders used to resolve colors (see {@link CellRenderer.resolveColor}) for style properties. */
+export type SpecialStyleColorValue =
+  | 'indicated'
+  | 'inherit'
+  | 'none'
+  | 'swimlane'
+  | (string & {});
+
 export type DirectionValue = 'north' | 'south' | 'east' | 'west';
 export type TextDirectionValue = '' | 'ltr' | 'rtl' | 'auto';
 export type AlignValue = 'left' | 'center' | 'right';
diff --git a/packages/core/src/view/handler/ConnectionHandler.ts b/packages/core/src/view/handler/ConnectionHandler.ts
index 7b4f4dba1..aaffc9898 100644
--- a/packages/core/src/view/handler/ConnectionHandler.ts
+++ b/packages/core/src/view/handler/ConnectionHandler.ts
@@ -534,7 +534,7 @@ class ConnectionHandler extends EventSource implements GraphPlugin {
   /**
    * Starts a new connection for the given state and coordinates.
    */
-  start(state: CellState, x: number, y: number, edgeState: CellState) {
+  start(state: CellState, x: number, y: number, edgeState?: CellState) {
     this.previous = state;
     this.first = new Point(x, y);
     this.edgeState = edgeState ?? this.createEdgeState();
@@ -1806,7 +1806,10 @@ class ConnectionHandler extends EventSource implements GraphPlugin {
         }
       } catch (e) {
         MaxLog.show();
-        // MaxLog.debug(e.message);
+        const errorMessage = `Error in ConnectionHandler: ${
+          e instanceof Error ? e.message + '\n' + e.stack : 'unknown cause'
+        }`;
+        MaxLog.debug(errorMessage);
       } finally {
         model.endUpdate();
       }
diff --git a/packages/core/src/view/mixins/CellsMixin.ts b/packages/core/src/view/mixins/CellsMixin.ts
index 87b2ed2a3..28c08680c 100644
--- a/packages/core/src/view/mixins/CellsMixin.ts
+++ b/packages/core/src/view/mixins/CellsMixin.ts
@@ -880,20 +880,20 @@ export const CellsMixin: PartialType = {
   /**
    * Returns the clone for the given cell. Uses {@link cloneCells}.
    *
-   * @param cell {@link mxCell} to be cloned.
+   * @param cell {@link Cell} to be cloned.
    * @param allowInvalidEdges Optional boolean that specifies if invalid edges
    * should be cloned. Default is `true`.
    * @param mapping Optional mapping for existing clones.
    * @param keepPosition Optional boolean indicating if the position of the cells should
    * be updated to reflect the lost parent cell. Default is `false`.
    */
-  cloneCell(cell, allowInvalidEdges = false, mapping = null, keepPosition = false) {
+  cloneCell(cell, allowInvalidEdges = false, mapping = {}, keepPosition = false) {
     return this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0];
   },
 
   /**
    * Returns the clones for the given cells. The clones are created recursively
-   * using {@link mxGraphModel.cloneCells}. If the terminal of an edge is not in the
+   * using {@link cloneCells}. If the terminal of an edge is not in the
    * given array, then the respective end is assigned a terminal point and the
    * terminal is removed.
    *
diff --git a/packages/core/src/view/style/Stylesheet.ts b/packages/core/src/view/style/Stylesheet.ts
index 01f01e877..ce52c644b 100644
--- a/packages/core/src/view/style/Stylesheet.ts
+++ b/packages/core/src/view/style/Stylesheet.ts
@@ -164,6 +164,9 @@ export class Stylesheet {
    *   - registered styles referenced in `baseStyleNames`, in the order of the array
    *   - `cellStyle` parameter
    *
+   * To fully unset a style property i.e. the property is not set even if a value is set in the default style or in the referenced styles,
+   * set the `cellStyle` property to `none`. For example. `cellStyle.fillColor = 'none'`
+   *
    * @param cellStyle An object that represents the style.
    * @param defaultStyle Default style used as reference to compute the returned style.
    */
@@ -186,15 +189,13 @@ export class Stylesheet {
     }
 
     // Merges cellStyle into style
-    const filteredCellStyle = clone(cellStyle); // Clones the cellStyle to avoid modifying the original object pass as parameter
-    for (const key of Object.keys(filteredCellStyle)) {
-      (filteredCellStyle[key] === undefined || filteredCellStyle[key] == NONE) &&
-        delete filteredCellStyle[key];
+    for (const key of Object.keys(cellStyle)) {
+      // @ts-ignore
+      if (cellStyle[key] !== undefined) {
+        // @ts-ignore
+        cellStyle[key] == NONE ? delete style[key] : (style[key] = cellStyle[key]);
+      }
     }
-    style = {
-      ...style,
-      ...filteredCellStyle,
-    };
 
     // Remove the 'baseStyleNames' that may have been copied from the cellStyle parameter to match the method signature
     'baseStyleNames' in style && delete style.baseStyleNames;
diff --git a/packages/html/stories/Collapse.stories.js b/packages/html/stories/Collapse.stories.ts
similarity index 66%
rename from packages/html/stories/Collapse.stories.js
rename to packages/html/stories/Collapse.stories.ts
index 3f5ac2766..bea1c89d1 100644
--- a/packages/html/stories/Collapse.stories.js
+++ b/packages/html/stories/Collapse.stories.ts
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { Graph, Rectangle } from '@maxgraph/core';
+import { type Cell, type CellStyle, Graph, Rectangle } from '@maxgraph/core';
 
 import { globalTypes, globalValues } from './shared/args.js';
 import {
@@ -34,7 +34,7 @@ export default {
   },
 };
 
-const Template = ({ label, ...args }) => {
+const Template = ({ label, ...args }: Record<string, string>) => {
   configureImagesBasePath();
   const container = createGraphContainer(args);
 
@@ -42,22 +42,21 @@ const Template = ({ label, ...args }) => {
   const parent = graph.getDefaultParent();
   configureExpandedAndCollapsedImages(graph);
 
-  const getStyle = function () {
-    // Extends Transactions.getStyle to show an image when collapsed
-    // TODO cannot use super without a parent class
-    // let style = super.getStyle();
-    let style = '';
-    if (this.isCollapsed()) {
-      style = {
-        ...style,
-        shape: 'image',
-        image: 'http://www.jgraph.com/images/mxgraph.gif',
-        noLabel: 1,
-        imageBackground: '#C3D9FF',
-        imageBorder: '#6482B9',
-      };
+  // Extends Cell.getStyle to show an image when collapsed
+  const getStyle = function (this: Cell): CellStyle {
+    if (!this.isCollapsed()) {
+      return this.style;
     }
-    return style;
+    return {
+      // Need to create a copy the original style as we don't want to change the original style stored in the Cell
+      // Otherwise, when expanding the cell, the style will be incorrect
+      ...this.style,
+      shape: 'image',
+      image: './images/package.png',
+      noLabel: true,
+      imageBackground: '#e7e9ef',
+      imageBorder: '#6482B9',
+    };
   };
 
   graph.batchUpdate(() => {
@@ -68,8 +67,8 @@ const Template = ({ label, ...args }) => {
       size: [200, 200],
       style: { shape: 'swimlane', startSize: 20 },
     });
-    v1.geometry.alternateBounds = new Rectangle(0, 0, 110, 70);
-    // v1.getStyle = getStyle;
+    v1.geometry!.alternateBounds = new Rectangle(0, 0, 110, 70);
+    v1.getStyle = getStyle;
 
     const v11 = graph.insertVertex({
       parent: v1,
@@ -77,7 +76,7 @@ const Template = ({ label, ...args }) => {
       position: [10, 40],
       size: [120, 80],
     });
-    // v11.getStyle = getStyle;
+    v11.getStyle = getStyle;
   });
 
   return container;
diff --git a/packages/html/stories/ColorStylePlaceHolders.stories.ts b/packages/html/stories/ColorStylePlaceHolders.stories.ts
new file mode 100644
index 000000000..a1d1d1611
--- /dev/null
+++ b/packages/html/stories/ColorStylePlaceHolders.stories.ts
@@ -0,0 +1,123 @@
+/*
+Copyright 2024-present The maxGraph project Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { constants, Graph } from '@maxgraph/core';
+import { globalTypes, globalValues } from './shared/args.js';
+import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
+
+export default {
+  title: 'Styles/ColorStylePlaceHolders',
+  argTypes: {
+    ...globalTypes,
+  },
+  args: {
+    ...globalValues,
+  },
+};
+
+const Template = ({ label, ...args }: Record<string, string>) => {
+  configureImagesBasePath();
+  const container = createGraphContainer(args);
+
+  // Creates the graph inside the given container
+  const graph = new Graph(container);
+
+  // Disables global features
+  graph.cellsSelectable = false;
+  graph.cellsLocked = true;
+
+  // Sets global styles
+  const defaultVertexStyle = graph.getStylesheet().getDefaultVertexStyle();
+  defaultVertexStyle.foldable = false;
+
+  graph.getStylesheet().putCellStyle('swimlane', {
+    shape: constants.SHAPE.SWIMLANE,
+    startSize: 30,
+    fillColor: '#ffffff',
+    strokeColor: 'red',
+    swimlaneLine: true,
+    swimlaneFillColor: '#ffffff',
+  });
+
+  // Gets the default parent for inserting new cells. This
+  // is normally the first child of the root (ie. layer 0).
+  const parent = graph.getDefaultParent();
+
+  // Adds cells to the model in a single step
+  graph.batchUpdate(() => {
+    const swimlane = graph.insertVertex(parent, null, 'swimlane', 0, 0, 640, 400, {
+      baseStyleNames: ['swimlane'],
+    });
+
+    const parentInSwimlane = graph.insertVertex({
+      parent: swimlane,
+      value: 'Parent in swimlane',
+      position: [20, 80],
+      size: [400, 230],
+      style: { fillColor: '#e8dbdb', strokeColor: 'black' },
+    });
+
+    graph.insertVertex({
+      parent: parentInSwimlane,
+      value: 'child with none stroke color',
+      position: [10, 10],
+      size: [180, 30],
+      style: { strokeColor: 'none' },
+    });
+
+    graph.insertVertex({
+      parent: parentInSwimlane,
+      value: 'child with swimlane stroke color',
+      position: [205, 60],
+      size: [180, 30],
+      style: { strokeColor: 'swimlane' },
+    });
+    graph.insertVertex({
+      parent: parentInSwimlane,
+      value: 'child with swimlane fill color',
+      position: [10, 60],
+      size: [180, 30],
+      style: { fillColor: 'swimlane' },
+    });
+
+    graph.insertVertex({
+      parent: parentInSwimlane,
+      value: 'child with inherit stroke color',
+      position: [10, 160],
+      size: [180, 30],
+      style: { strokeColor: 'inherit' },
+    });
+    graph.insertVertex({
+      parent: parentInSwimlane,
+      value: 'child with inherit fill color',
+      position: [205, 160],
+      size: [180, 30],
+      style: { fillColor: 'inherit' },
+    });
+
+    graph.insertVertex({
+      // parent: swimlane,
+      value: 'vertex with none fill color',
+      position: [10, 410],
+      size: [180, 30],
+      style: { fillColor: 'none' },
+    });
+  });
+
+  return container;
+};
+
+export const Default = Template.bind({});
diff --git a/packages/html/stories/ContextIcons.stories.js b/packages/html/stories/ContextIcons.stories.ts
similarity index 79%
rename from packages/html/stories/ContextIcons.stories.js
rename to packages/html/stories/ContextIcons.stories.ts
index dcbc61f4e..079cbeced 100644
--- a/packages/html/stories/ContextIcons.stories.js
+++ b/packages/html/stories/ContextIcons.stories.ts
@@ -16,6 +16,7 @@ limitations under the License.
 */
 
 import {
+  Client,
   Graph,
   InternalEvent,
   RubberBandHandler,
@@ -23,6 +24,9 @@ import {
   styleUtils,
   domUtils,
   VertexHandler,
+  type CellState,
+  type ConnectionHandler,
+  type SelectionHandler,
 } from '@maxgraph/core';
 
 import {
@@ -31,7 +35,7 @@ import {
   rubberBandTypes,
   rubberBandValues,
 } from './shared/args.js';
-import { createGraphContainer } from './shared/configure.js';
+import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
 // style required by RubberBand
 import '@maxgraph/core/css/common.css';
 
@@ -47,18 +51,25 @@ export default {
   },
 };
 
-const Template = ({ label, ...args }) => {
+const Template = ({ label, ...args }: Record<string, any>) => {
+  configureImagesBasePath();
   const container = createGraphContainer(args);
 
-  class mxVertexToolHandler extends VertexHandler {
-    // Defines a subclass for VertexHandler that adds a set of clickable
-    // icons to every selected vertex.
+  // convenient function to create an image element available when the demo is not deployed in the root context
+  const createImage = (fileName: string) => {
+    return domUtils.createImage(`${Client.imageBasePath}/${fileName}`);
+  };
 
-    domNode = null;
+  // Defines a subclass for VertexHandler that adds a set of clickable icons to every selected vertex.
+  class CustomVertexToolHandler extends VertexHandler {
+    private domNode: HTMLDivElement = null!;
 
-    init() {
-      super.init();
+    constructor(state: CellState) {
+      super(state);
+      this.init();
+    }
 
+    init() {
       // In this example we force the use of DIVs for images in IE. This
       // handles transparency in PNG images properly in IE and fixes the
       // problem that IE routes all mouse events for a gesture via the
@@ -67,13 +78,8 @@ const Template = ({ label, ...args }) => {
       this.domNode.style.position = 'absolute';
       this.domNode.style.whiteSpace = 'nowrap';
 
-      // Workaround for event redirection via image tag in quirks and IE8
-      const createImage = (src) => {
-        return domUtils.createImage(src);
-      };
-
       // Delete
-      let img = createImage('images/delete2.png');
+      let img = createImage('delete2.png');
       img.setAttribute('title', 'Delete');
       img.style.cursor = 'pointer';
       img.style.width = '16px';
@@ -82,14 +88,14 @@ const Template = ({ label, ...args }) => {
         // Disables dragging the image
         InternalEvent.consume(evt);
       });
-      InternalEvent.addListener(img, 'click', (evt) => {
+      InternalEvent.addListener(img, 'click', (evt: Event) => {
         this.graph.removeCells([this.state.cell]);
         InternalEvent.consume(evt);
       });
       this.domNode.appendChild(img);
 
       // Size
-      img = createImage('images/fit_to_size.png');
+      img = createImage('fit_to_size.png');
       img.setAttribute('title', 'Resize');
       img.style.cursor = 'se-resize';
       img.style.width = '16px';
@@ -104,14 +110,14 @@ const Template = ({ label, ...args }) => {
       this.domNode.appendChild(img);
 
       // Move
-      img = createImage('images/plus.png');
+      img = createImage('plus.png');
       img.setAttribute('title', 'Move');
       img.style.cursor = 'move';
       img.style.width = '16px';
       img.style.height = '16px';
 
-      const graphHandler = graph.getPlugin('SelectionHandler');
-      const connectionHandler = graph.getPlugin('ConnectionHandler');
+      const graphHandler = graph.getPlugin('SelectionHandler') as SelectionHandler;
+      const connectionHandler = graph.getPlugin('ConnectionHandler') as ConnectionHandler;
 
       InternalEvent.addGestureListeners(img, (evt) => {
         graphHandler.start(
@@ -127,7 +133,7 @@ const Template = ({ label, ...args }) => {
       this.domNode.appendChild(img);
 
       // Connect
-      img = createImage('images/check.png');
+      img = createImage('check.png');
       img.setAttribute('title', 'Connect');
       img.style.cursor = 'pointer';
       img.style.width = '16px';
@@ -163,20 +169,20 @@ const Template = ({ label, ...args }) => {
       }
     }
 
-    destroy(sender, me) {
-      super.destroy(sender, me);
+    onDestroy() {
+      super.onDestroy();
 
-      if (this.domNode != null) {
-        this.domNode.parentNode.removeChild(this.domNode);
-        this.domNode = null;
+      if (this.domNode) {
+        this.domNode.parentNode?.removeChild(this.domNode);
+        this.domNode = null!;
       }
     }
   }
 
   class MyCustomGraph extends Graph {
-    createHandler(state) {
+    createHandler(state: CellState) {
       if (state != null && state.cell.isVertex()) {
-        return new mxVertexToolHandler(state);
+        return new CustomVertexToolHandler(state);
       }
       return super.createHandler(state);
     }
@@ -186,7 +192,7 @@ const Template = ({ label, ...args }) => {
   const graph = new MyCustomGraph(container);
   graph.setConnectable(true);
 
-  const connectionHandler = graph.getPlugin('ConnectionHandler');
+  const connectionHandler = graph.getPlugin('ConnectionHandler') as ConnectionHandler;
   connectionHandler.createTarget = true;
 
   // Uncomment the following if you want the container
@@ -214,7 +220,7 @@ const Template = ({ label, ...args }) => {
       position: [200, 150],
       size: [80, 30],
     });
-    const e1 = graph.insertEdge({
+    graph.insertEdge({
       parent,
       source: v1,
       target: v2,
diff --git a/packages/html/stories/HelloPort.stories.ts b/packages/html/stories/HelloPort.stories.ts
index 788e3b1b7..95aab27c0 100644
--- a/packages/html/stories/HelloPort.stories.ts
+++ b/packages/html/stories/HelloPort.stories.ts
@@ -32,8 +32,7 @@ import {
   rubberBandValues,
 } from './shared/args.js';
 import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
-// style required by RubberBand and popup
-import '@maxgraph/core/css/common.css';
+import '@maxgraph/core/css/common.css'; // style required by RubberBand and popup
 
 export default {
   title: 'Connections/HelloPort',
diff --git a/packages/html/stories/JsonData.stories.ts b/packages/html/stories/JsonData.stories.ts
index 0c005f494..6aa2d0d93 100644
--- a/packages/html/stories/JsonData.stories.ts
+++ b/packages/html/stories/JsonData.stories.ts
@@ -34,8 +34,7 @@ import {
   rubberBandValues,
 } from './shared/args.js';
 import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
-// style required by RubberBand
-import '@maxgraph/core/css/common.css';
+import '@maxgraph/core/css/common.css'; // style required by RubberBand and popup
 
 export default {
   title: 'Xml_Json/JsonData',
diff --git a/packages/html/stories/SwimLanes.stories.js b/packages/html/stories/SwimLanes.stories.ts
similarity index 86%
rename from packages/html/stories/SwimLanes.stories.js
rename to packages/html/stories/SwimLanes.stories.ts
index 8aa37076b..0a6bbf1ad 100644
--- a/packages/html/stories/SwimLanes.stories.js
+++ b/packages/html/stories/SwimLanes.stories.ts
@@ -27,6 +27,11 @@ import {
   StackLayout,
   LayoutManager,
   Graph,
+  type Cell,
+  type EdgeParameters,
+  type EventObject,
+  type SelectionHandler,
+  type VertexParameters,
 } from '@maxgraph/core';
 import { globalTypes, globalValues } from './shared/args.js';
 import {
@@ -45,7 +50,7 @@ export default {
   },
 };
 
-const Template = ({ label, ...args }) => {
+const Template = ({ label, ...args }: Record<string, string>) => {
   configureImagesBasePath();
   const container = createGraphContainer(args);
   container.style.background = ''; // no grid
@@ -66,6 +71,10 @@ const Template = ({ label, ...args }) => {
   // const editor = new Editor(null);
   // editor.setGraphContainer(container);
   // const { graph } = editor;
+  type CustomGraph = Graph & {
+    isPool(cell: Cell | null): boolean;
+  };
+
   const graph = new Graph(container);
   const model = graph.getDataModel();
 
@@ -75,7 +84,7 @@ const Template = ({ label, ...args }) => {
   graph.setResizeContainer(true);
   configureExpandedAndCollapsedImages(graph);
 
-  const graphHandler = graph.getPlugin('SelectionHandler');
+  const graphHandler = graph.getPlugin('SelectionHandler') as SelectionHandler;
   graphHandler.setRemoveCellsFromParent(false);
 
   // Changes the default vertex style in-place
@@ -150,11 +159,10 @@ const Template = ({ label, ...args }) => {
 
     // End-states are no valid sources
     const previousIsValidSource = graph.isValidSource;
-
     graph.isValidSource = function (cell) {
-      if (previousIsValidSource.apply(this, arguments)) {
+      if (previousIsValidSource.apply(this, [cell]) && cell) {
         const style = cell.getStyle();
-        return style == null || !style.baseStyleNames.includes('end');
+        return style == null || !style.baseStyleNames?.includes('end');
       }
       return false;
     };
@@ -166,11 +174,12 @@ const Template = ({ label, ...args }) => {
     // the example below, so we use the state
     // style below
     graph.isValidTarget = function (cell) {
+      if (!cell) return false;
       const style = cell.getStyle();
       return (
         !cell.isEdge() &&
         !this.isSwimlane(cell) &&
-        (style == null || !!style.baseStyleNames.includes('state'))
+        (style == null || !!style.baseStyleNames?.includes('state'))
       );
     };
 
@@ -181,17 +190,17 @@ const Template = ({ label, ...args }) => {
     graph.setSplitEnabled(false);
 
     // Returns true for valid drop operations
-    graph.isValidDropTarget = function (target, cells, evt) {
+    graph.isValidDropTarget = function (this: CustomGraph, target, cells, evt) {
       if (this.isSplitEnabled() && this.isSplitTarget(target, cells, evt)) {
         return true;
       }
 
-      const model = this.getDataModel();
       let lane = false;
       let pool = false;
       let cell = false;
 
       // Checks if any lanes or pools are selected
+      cells ??= [];
       for (let i = 0; i < cells.length; i++) {
         const tmp = cells[i].getParent();
         lane = lane || this.isPool(tmp);
@@ -208,15 +217,15 @@ const Template = ({ label, ...args }) => {
     };
 
     // Adds new method for identifying a pool
-    graph.isPool = function (cell) {
+    (graph as CustomGraph).isPool = function (cell: Cell | null) {
       const model = this.getDataModel();
-      const parent = cell.getParent();
+      const parent = cell?.getParent();
 
-      return parent != null && parent.getParent() == model.getRoot();
+      return parent?.getParent() == model.getRoot();
     };
 
     // Keeps widths on collapse/expand
-    const foldingHandler = function (sender, evt) {
+    const foldingHandler = function (_sender: any, evt: EventObject) {
       const cells = evt.getProperty('cells');
 
       for (let i = 0; i < cells.length; i++) {
@@ -232,17 +241,16 @@ const Template = ({ label, ...args }) => {
   }
 
   // Changes swimlane orientation while collapsed
-  const getStyle = function () {
-    // TODO super cannot be used here
-    // let style = super.getStyle();
-    let style = {};
-
-    if (this.isCollapsed()) {
-      style.horizontal = true;
-      style.align = 'left';
-      style.spacingLeft = 14;
+  const getStyle = function (this: Cell) {
+    if (!this.isCollapsed()) {
+      return this.style;
     }
-
+    // Need to create a copy the original style as we don't want to change the original style stored in the Cell
+    // Otherwise, when expanding the cell, the style will be incorrect
+    const style = { ...this.style };
+    style.horizontal = true;
+    style.align = 'left';
+    style.spacingLeft = 14;
     return style;
   };
 
@@ -268,11 +276,12 @@ const Template = ({ label, ...args }) => {
 
   layoutMgr.getLayout = function (cell) {
     if (
+      cell &&
       !cell.isEdge() &&
       cell.getChildCount() > 0 &&
-      (cell.getParent() == model.getRoot() || graph.isPool(cell))
+      (cell.getParent() == model.getRoot() || (graph as CustomGraph).isPool(cell))
     ) {
-      layout.fill = graph.isPool(cell);
+      layout.fill = (graph as CustomGraph).isPool(cell);
 
       return layout;
     }
@@ -284,17 +293,15 @@ const Template = ({ label, ...args }) => {
   // is normally the first child of the root (ie. layer 0).
   const parent = graph.getDefaultParent();
 
-  const insertVertex = (options) => {
+  const insertVertex = (options: VertexParameters) => {
     const v = graph.insertVertex(options);
-    // TODO find a way to restore
-    // v.getStyle = getStyle;
+    v.getStyle = getStyle;
     return v;
   };
 
-  const insertEdge = (options) => {
+  const insertEdge = (options: EdgeParameters) => {
     const e = graph.insertEdge(options);
-    // TODO find a way to restore
-    // e.getStyle = getStyle;
+    e.getStyle = getStyle;
     return e;
   };
 
@@ -458,8 +465,6 @@ const Template = ({ label, ...args }) => {
       style: { baseStyleNames: ['end'] },
     });
 
-    let e = null;
-
     insertEdge({
       parent: lane1a,
       source: start1,
@@ -514,7 +519,11 @@ const Template = ({ label, ...args }) => {
       value: 'Yes',
       source: step44,
       target: step111,
-      style: { verticalAlign: 'bottom', horizontal: 0, labelBackgroundColor: 'white' },
+      style: {
+        verticalAlign: 'bottom',
+        horizontal: false,
+        labelBackgroundColor: 'white',
+      },
     });
 
     insertEdge({
@@ -524,7 +533,7 @@ const Template = ({ label, ...args }) => {
       target: end2,
       style: { verticalAlign: 'bottom' },
     });
-    e = insertEdge({
+    let e = insertEdge({
       parent: lane2a,
       value: 'No',
       source: step444,
@@ -532,10 +541,10 @@ const Template = ({ label, ...args }) => {
       style: { verticalAlign: 'top' },
     });
 
-    e.geometry.points = [
+    e.geometry!.points = [
       new Point(
-        step444.geometry.x + step444.geometry.width / 2,
-        end3.geometry.y + end3.geometry.height / 2
+        step444.geometry!.x + step444.geometry!.width / 2,
+        end3.geometry!.y + end3.geometry!.height / 2
       ),
     ];
 
@@ -558,10 +567,10 @@ const Template = ({ label, ...args }) => {
       style: { baseStyleNames: ['crossover'] },
     });
 
-    e.geometry.points = [
+    e.geometry!.points = [
       new Point(
-        step33.geometry.x + step33.geometry.width / 2 + 20,
-        step11.geometry.y + (step11.geometry.height * 4) / 5
+        step33.geometry!.x + step33.geometry!.width / 2 + 20,
+        step11.geometry!.y + (step11.geometry!.height * 4) / 5
       ),
     ];
 
diff --git a/packages/html/stories/Toolbar.stories.ts b/packages/html/stories/Toolbar.stories.ts
index b6596d91f..37bdefc2b 100644
--- a/packages/html/stories/Toolbar.stories.ts
+++ b/packages/html/stories/Toolbar.stories.ts
@@ -35,7 +35,11 @@ import {
   rubberBandTypes,
   rubberBandValues,
 } from './shared/args.js';
-import { createGraphContainer } from './shared/configure.js';
+import {
+  configureExpandedAndCollapsedImages,
+  configureImagesBasePath,
+  createGraphContainer,
+} from './shared/configure.js';
 // style required by RubberBand
 import '@maxgraph/core/css/common.css';
 
@@ -52,6 +56,7 @@ export default {
 };
 
 const Template = ({ label, ...args }: { [p: string]: any }) => {
+  configureImagesBasePath();
   const div = document.createElement('div');
   const container = createGraphContainer(args);
   div.appendChild(container);
@@ -80,6 +85,7 @@ const Template = ({ label, ...args }: { [p: string]: any }) => {
   // using the fastest rendering available on the browser
   const model = new GraphDataModel();
   const graph = new Graph(container, model);
+  configureExpandedAndCollapsedImages(graph);
   graph.dropEnabled = true;
 
   // Matches DnD inside the graph
diff --git a/packages/html/stories/UserObject.stories.ts b/packages/html/stories/UserObject.stories.ts
index 10105d668..6b0e9c750 100644
--- a/packages/html/stories/UserObject.stories.ts
+++ b/packages/html/stories/UserObject.stories.ts
@@ -39,6 +39,7 @@ import {
   rubberBandValues,
 } from './shared/args.js';
 import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
+import '@maxgraph/core/css/common.css'; // style required by popup
 
 export default {
   title: 'Xml_Json/UserObject',
diff --git a/packages/html/stories/Window.stories.js b/packages/html/stories/Window.stories.ts
similarity index 91%
rename from packages/html/stories/Window.stories.js
rename to packages/html/stories/Window.stories.ts
index 7dba159c7..822277d86 100644
--- a/packages/html/stories/Window.stories.js
+++ b/packages/html/stories/Window.stories.ts
@@ -33,8 +33,7 @@ import {
   rubberBandValues,
 } from './shared/args.js';
 import { configureImagesBasePath, createGraphContainer } from './shared/configure.js';
-// style required by RubberBand
-import '@maxgraph/core/css/common.css';
+import '@maxgraph/core/css/common.css'; // style required by RubberBand and MaxWindow/MaxLog
 
 export default {
   title: 'Windows/Windows',
@@ -50,7 +49,7 @@ export default {
   },
 };
 
-const Template = ({ label, ...args }) => {
+const Template = ({ label, ...args }: Record<string, string>) => {
   configureImagesBasePath();
   const container = createGraphContainer(args);
 
@@ -88,12 +87,13 @@ const Template = ({ label, ...args }) => {
   graph.batchUpdate(() => {
     const v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
     const v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
-    const e1 = graph.insertEdge(parent, null, '', v1, v2);
+    graph.insertEdge(parent, null, '', v1, v2);
   });
 
   wnd.setMaximizable(true);
   wnd.setResizable(true);
   wnd.setVisible(true);
+  wnd.setClosable(true);
 
   const lorem =
     'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ';
@@ -114,8 +114,9 @@ const Template = ({ label, ...args }) => {
   wnd.setScrollable(true);
   wnd.setResizable(true);
   wnd.setVisible(true);
+  wnd.setClosable(true);
 
-  content = content.cloneNode(true);
+  content = content.cloneNode(true) as HTMLDivElement;
   content.style.width = '400px';
 
   wnd = new MaxWindow(
@@ -132,6 +133,7 @@ const Template = ({ label, ...args }) => {
   wnd.setScrollable(true);
   wnd.setResizable(true);
   wnd.setVisible(true);
+  wnd.setClosable(true);
 
   MaxLog.show();
 


Test patch:
diff --git a/packages/core/__tests__/view/style/StyleSheet.test.ts b/packages/core/__tests__/view/style/StyleSheet.test.ts
index e1604219d..cca66bf51 100644
--- a/packages/core/__tests__/view/style/StyleSheet.test.ts
+++ b/packages/core/__tests__/view/style/StyleSheet.test.ts
@@ -199,25 +199,27 @@ describe('getCellStyle', () => {
   });
 
   // This was the mxGraph behaviour, see https://github.com/jgraph/mxgraph/blob/v4.2.2/javascript/src/js/view/mxStylesheet.js#L236-L239
-  test('Setting properties with "none" value fallback to property value of the default and "base" styles', () => {
+  test('Setting properties with "none" value remove the property value', () => {
     const stylesheet = new Stylesheet();
     stylesheet.putCellStyle('style-1', {
       fillColor: 'orange',
+      rounded: true,
     });
 
     const cellStyle = stylesheet.getCellStyle(
       {
         baseStyleNames: ['style-1'],
         fillColor: NONE,
+        gradientColor: NONE, // not set in default and baseStyles
         shape: 'cloud',
         strokeColor: NONE,
       },
-      { strokeColor: 'pink' }
+      { opacity: 50, strokeColor: 'pink' }
     );
     expect(cellStyle).toStrictEqual({
-      fillColor: 'orange',
+      opacity: 50, // from default
+      rounded: true, // from style-1
       shape: 'cloud',
-      strokeColor: 'pink',
     });
   });