diff --git a/.changeset/modern-owl-dance.md b/.changeset/modern-owl-dance.md
new file mode 100644
index 0000000..2cc2d08
--- /dev/null
+++ b/.changeset/modern-owl-dance.md
@@ -0,0 +1,16 @@
+---
+'@projectstorm/react-workspaces-core': minor
+'@projectstorm/react-workspaces-behavior-panel-dropzone': minor
+'@projectstorm/react-workspaces-dropzone-plugin-tabs': minor
+'@projectstorm/react-workspaces-dropzone-plugin-tray': minor
+'@projectstorm/react-workspaces-behavior-divider-dropzone': patch
+'@projectstorm/react-workspaces-behavior-resize': patch
+'@projectstorm/react-workspaces-defaults': patch
+'@projectstorm/react-workspaces-model-floating-window': patch
+'@projectstorm/react-workspaces-model-tabs': patch
+'@projectstorm/react-workspaces-model-tray': patch
+---
+
+Upgrade the workspace to the latest TypeScript, React, Storybook, and build-tool dependencies.
+
+Add improved panel drop targets, including reliable transform-zone activation, root-level tab and tray splitting, and gap-free directional layouts while preserving boundary drop zones.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2d510df..a8a1158 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,6 +7,11 @@ on:
concurrency: ${{ github.workflow }}-${{ github.ref }}
+permissions:
+ contents: write
+ pull-requests: write
+ id-token: write
+
jobs:
release:
name: Release
@@ -23,6 +28,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: "${{ steps.nvm.outputs.NVMRC }}"
+ registry-url: 'https://registry.npmjs.org'
- name: Install PNPM
uses: pnpm/action-setup@v4
@@ -40,8 +46,7 @@ jobs:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: publish storybook
if: steps.changesets.outputs.published == 'true'
- run: pnpm release:storybook
\ No newline at end of file
+ run: pnpm release:storybook
diff --git a/demo/.storybook/main.ts b/demo/.storybook/main.ts
index 92e29a6..90cf157 100644
--- a/demo/.storybook/main.ts
+++ b/demo/.storybook/main.ts
@@ -3,6 +3,12 @@ import type { StorybookConfig } from '@storybook/react-webpack5';
const config: StorybookConfig = {
stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-webpack5-compiler-swc'],
+ core: {
+ disableWhatsNewNotifications: true
+ },
+ features: {
+ sidebarOnboardingChecklist: false
+ },
framework: {
name: '@storybook/react-webpack5',
options: {}
diff --git a/demo/package.json b/demo/package.json
index 32252dd..4fbcd7e 100644
--- a/demo/package.json
+++ b/demo/package.json
@@ -18,20 +18,20 @@
"@projectstorm/react-workspaces-model-floating-window": "workspace:*",
"@projectstorm/react-workspaces-model-tabs": "workspace:*",
"@projectstorm/react-workspaces-model-tray": "workspace:*",
- "lodash": "^4.17.23",
- "react": "^19.2.4",
+ "lodash": "^4.18.1",
+ "react": "^19.2.7",
"typeface-open-sans": "^1.1.13"
},
"devDependencies": {
- "@types/lodash": "^4.17.23",
- "@types/node": "^25.2.3",
- "@types/react": "19.2.14",
+ "@types/lodash": "^4.17.24",
+ "@types/node": "^26.1.1",
+ "@types/react": "19.2.17",
"@types/react-dom": "^19.2.3",
- "react-dom": "19.2.4",
+ "react-dom": "19.2.7",
"source-map-loader": "^5.0.0",
- "storybook": "^10.2.9",
- "@storybook/react-webpack5": "^10.2.9",
- "@storybook/addon-webpack5-compiler-swc": "^4.0.2",
- "webpack": "^5.105.2"
+ "storybook": "^10.4.6",
+ "@storybook/react-webpack5": "^10.4.6",
+ "@storybook/addon-webpack5-compiler-swc": "^4.0.3",
+ "webpack": "^5.108.4"
}
}
diff --git a/demo/stories/Affinity.stories.tsx b/demo/stories/Affinity.stories.tsx
index 0664db7..9126ae1 100644
--- a/demo/stories/Affinity.stories.tsx
+++ b/demo/stories/Affinity.stories.tsx
@@ -2,28 +2,29 @@ import * as React from 'react';
import { useState } from 'react';
import 'typeface-open-sans';
import { WorkspaceNodeModel } from '@projectstorm/react-workspaces-core';
-import { CompInternal, SharedArgs, useEngine } from './helpers/tools';
+import { CompInternal, SharedArgs, StoryArgs, useEngine } from './helpers/tools';
import { DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';
-export const AffinityStories = function (args) {
- const engine = useEngine(args);
- const [model] = useState(() => {
- let model = new WorkspaceNodeModel();
- model.setHorizontal(true);
- model
- .addModel(new DefaultWorkspacePanelModel('left'))
- .addModel(new DefaultWorkspacePanelModel('left'))
- .addModel(new DefaultWorkspacePanelModel('left'))
- .addModel(new DefaultWorkspacePanelModel('expand').setExpand(true, true))
- .addModel(new DefaultWorkspacePanelModel('right'))
- .addModel(new DefaultWorkspacePanelModel('right'))
- .addModel(new DefaultWorkspacePanelModel('right'));
- return model;
- });
- return ;
-}.bind({});
-
-AffinityStories.args = SharedArgs;
+export const AffinityStories = Object.assign(
+ function AffinityStories(args: StoryArgs) {
+ const engine = useEngine(args);
+ const [model] = useState(() => {
+ let model = new WorkspaceNodeModel();
+ model.setHorizontal(true);
+ model
+ .addModel(new DefaultWorkspacePanelModel('left'))
+ .addModel(new DefaultWorkspacePanelModel('left'))
+ .addModel(new DefaultWorkspacePanelModel('left'))
+ .addModel(new DefaultWorkspacePanelModel('expand').setExpand(true, true))
+ .addModel(new DefaultWorkspacePanelModel('right'))
+ .addModel(new DefaultWorkspacePanelModel('right'))
+ .addModel(new DefaultWorkspacePanelModel('right'));
+ return model;
+ });
+ return ;
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/Basic.stories.tsx b/demo/stories/Basic.stories.tsx
index eec1e4f..e0ee8a7 100644
--- a/demo/stories/Basic.stories.tsx
+++ b/demo/stories/Basic.stories.tsx
@@ -1,18 +1,19 @@
import * as React from 'react';
import { useState } from 'react';
import 'typeface-open-sans';
-import { CompInternal, SharedArgs, useEngine } from './helpers/tools';
+import { CompInternal, SharedArgs, StoryArgs, useEngine } from './helpers/tools';
import { createSimpleModel } from './helpers/simpleModel';
-export const Basic = function (args) {
- const engine = useEngine(args);
- const [model] = useState(() => {
- return createSimpleModel();
- });
- return ;
-}.bind({});
-
-Basic.args = SharedArgs;
+export const Basic = Object.assign(
+ function Basic(args: StoryArgs) {
+ const engine = useEngine(args);
+ const [model] = useState(() => {
+ return createSimpleModel();
+ });
+ return ;
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/Complex.stories.tsx b/demo/stories/Complex.stories.tsx
index f5c9595..6ab9d1e 100644
--- a/demo/stories/Complex.stories.tsx
+++ b/demo/stories/Complex.stories.tsx
@@ -1,38 +1,39 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import 'typeface-open-sans';
-import { Buttons, CompInternal, SharedArgs, useEngine, useRootModel } from './helpers/tools';
+import { Buttons, CompInternal, SharedArgs, StoryArgs, useEngine, useRootModel } from './helpers/tools';
import { createComplexModel } from './helpers/complexModel';
import { useForceUpdate } from '@projectstorm/react-workspaces-core';
-export const ComplexLayout = function (args) {
- const forceUpdate = useForceUpdate();
- const engine = useEngine(args);
- const [model] = useState(() => {
- return createComplexModel(engine);
- });
- useEffect(() => {
- return engine.registerListener({
- lockUpdated: () => {
- forceUpdate();
- }
+export const ComplexLayout = Object.assign(
+ function ComplexLayout(args: StoryArgs) {
+ const forceUpdate = useForceUpdate();
+ const engine = useEngine(args);
+ const [model] = useState(() => {
+ return createComplexModel(engine);
});
- }, []);
- useRootModel(model, args);
- return (
- {
- engine.setLocked(!engine.locked);
+ useEffect(() => {
+ return engine.registerListener({
+ lockUpdated: () => {
+ forceUpdate();
}
- }}
- >
-
-
- );
-}.bind({});
-
-ComplexLayout.args = SharedArgs;
+ });
+ }, []);
+ useRootModel(model, args);
+ return (
+ {
+ engine.setLocked(!engine.locked);
+ }
+ }}
+ >
+
+
+ );
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/FloatingWindows.stories.tsx b/demo/stories/FloatingWindows.stories.tsx
index 7e2becd..adae3d9 100644
--- a/demo/stories/FloatingWindows.stories.tsx
+++ b/demo/stories/FloatingWindows.stories.tsx
@@ -4,55 +4,56 @@ import 'typeface-open-sans';
import { DefaultWindowModel, DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';
import { WorkspaceTabModel } from '@projectstorm/react-workspaces-model-tabs';
import { RootWorkspaceModel } from '@projectstorm/react-workspaces-model-floating-window';
-import { CompInternal, genVerticalNode, SharedArgs, useEngine, useRootModel } from './helpers/tools';
-
-export const FloatingWindows = function (args) {
- const engine = useEngine(args);
- const [model] = useState(() => {
- let model = new RootWorkspaceModel(engine, false);
- model.setHorizontal(true);
- model
-
- //left panel
- .addModel(genVerticalNode())
-
- //tab panel
- .addModel(
- new WorkspaceTabModel()
-
- .addModel(new DefaultWorkspacePanelModel('Tab 4'))
- .addModel(new DefaultWorkspacePanelModel('Tab 5'))
- .addModel(new DefaultWorkspacePanelModel('Tab 6'))
- )
-
- .addModel(genVerticalNode());
-
- const window1 = new DefaultWindowModel(new DefaultWorkspacePanelModel('Floating window 1'));
- window1.position.update({
- top: 100,
- left: 100
- });
- window1.setWidth(400);
- window1.setHeight(400);
- model.addFloatingWindow(window1);
-
- const window2 = new DefaultWindowModel(new DefaultWorkspacePanelModel('Floating window 2'));
- window2.position.update({
- top: 100,
- left: 600
+import { CompInternal, genVerticalNode, SharedArgs, StoryArgs, useEngine, useRootModel } from './helpers/tools';
+
+export const FloatingWindows = Object.assign(
+ function FloatingWindows(args: StoryArgs) {
+ const engine = useEngine(args);
+ const [model] = useState(() => {
+ let model = new RootWorkspaceModel(engine, false);
+ model.setHorizontal(true);
+ model
+
+ //left panel
+ .addModel(genVerticalNode())
+
+ //tab panel
+ .addModel(
+ new WorkspaceTabModel()
+
+ .addModel(new DefaultWorkspacePanelModel('Tab 4'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 5'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 6'))
+ )
+
+ .addModel(genVerticalNode());
+
+ const window1 = new DefaultWindowModel(new DefaultWorkspacePanelModel('Floating window 1'));
+ window1.position.update({
+ top: 100,
+ left: 100
+ });
+ window1.setWidth(400);
+ window1.setHeight(400);
+ model.addFloatingWindow(window1);
+
+ const window2 = new DefaultWindowModel(new DefaultWorkspacePanelModel('Floating window 2'));
+ window2.position.update({
+ top: 100,
+ left: 600
+ });
+ window2.setWidth(400);
+ window2.setHeight(400);
+ model.addFloatingWindow(window2);
+
+ return model;
});
- window2.setWidth(400);
- window2.setHeight(400);
- model.addFloatingWindow(window2);
-
- return model;
- });
- useRootModel(model, args);
-
- return ;
-}.bind({});
+ useRootModel(model, args);
-FloatingWindows.args = SharedArgs;
+ return ;
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/MinMax.stories.tsx b/demo/stories/MinMax.stories.tsx
index 34e1e9d..f196b78 100644
--- a/demo/stories/MinMax.stories.tsx
+++ b/demo/stories/MinMax.stories.tsx
@@ -4,53 +4,54 @@ import 'typeface-open-sans';
import { WorkspaceNodeModel } from '@projectstorm/react-workspaces-core';
import { DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';
import { WorkspaceTabModel } from '@projectstorm/react-workspaces-model-tabs';
-import { CompInternal, genVerticalNode, SharedArgs, useEngine } from './helpers/tools';
-
-export const MinMax = function (args) {
- const engine = useEngine(args);
- const [model] = useState(() => {
- let model = new WorkspaceNodeModel();
- model.setHorizontal(true);
-
- const genNode2 = () => {
- const node = genVerticalNode();
- node.minimumSize.update({
- width: 50
- });
- node.maximumSize.update({
- width: 250
- });
- return node;
- };
-
- model
-
- //left panel
- .addModel(genNode2())
-
- //tab panel
- .addModel(
- new WorkspaceTabModel()
- .addModel(new DefaultWorkspacePanelModel('Tab 1'))
- .addModel(new DefaultWorkspacePanelModel('Tab 2'))
- .addModel(new DefaultWorkspacePanelModel('Tab 3'))
- )
-
- //tab panel
- .addModel(
- new WorkspaceTabModel()
- .addModel(new DefaultWorkspacePanelModel('Tab 4'))
- .addModel(new DefaultWorkspacePanelModel('Tab 5'))
- .addModel(new DefaultWorkspacePanelModel('Tab 6'))
- )
-
- .addModel(genNode2());
- return model;
- });
- return ;
-}.bind(this);
-
-MinMax.args = SharedArgs;
+import { CompInternal, genVerticalNode, SharedArgs, StoryArgs, useEngine } from './helpers/tools';
+
+export const MinMax = Object.assign(
+ function MinMax(args: StoryArgs) {
+ const engine = useEngine(args);
+ const [model] = useState(() => {
+ let model = new WorkspaceNodeModel();
+ model.setHorizontal(true);
+
+ const genNode2 = () => {
+ const node = genVerticalNode();
+ node.minimumSize.update({
+ width: 50
+ });
+ node.maximumSize.update({
+ width: 250
+ });
+ return node;
+ };
+
+ model
+
+ //left panel
+ .addModel(genNode2())
+
+ //tab panel
+ .addModel(
+ new WorkspaceTabModel()
+ .addModel(new DefaultWorkspacePanelModel('Tab 1'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 2'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 3'))
+ )
+
+ //tab panel
+ .addModel(
+ new WorkspaceTabModel()
+ .addModel(new DefaultWorkspacePanelModel('Tab 4'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 5'))
+ .addModel(new DefaultWorkspacePanelModel('Tab 6'))
+ )
+
+ .addModel(genNode2());
+ return model;
+ });
+ return ;
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/SwitchModels.stories.tsx b/demo/stories/SwitchModels.stories.tsx
index 88733f8..667b30c 100644
--- a/demo/stories/SwitchModels.stories.tsx
+++ b/demo/stories/SwitchModels.stories.tsx
@@ -1,39 +1,40 @@
import * as React from 'react';
import { useState } from 'react';
import 'typeface-open-sans';
-import { Buttons, CompInternal, SharedArgs, useEngine } from './helpers/tools';
+import { Buttons, CompInternal, SharedArgs, StoryArgs, useEngine } from './helpers/tools';
import { createSimpleModel } from './helpers/simpleModel';
import { createComplexModel } from './helpers/complexModel';
-export const SwitchModels = function (args) {
- const engine = useEngine(args);
- const [model1] = useState(() => {
- return createSimpleModel();
- });
- const [model2] = useState(() => {
- return createComplexModel(engine);
- });
+export const SwitchModels = Object.assign(
+ function SwitchModels(args: StoryArgs) {
+ const engine = useEngine(args);
+ const [model1] = useState(() => {
+ return createSimpleModel();
+ });
+ const [model2] = useState(() => {
+ return createComplexModel(engine);
+ });
- const [selected, setSelected] = useState(model1);
+ const [selected, setSelected] = useState(model1);
- return (
- {
- if (selected === model1) {
- setSelected(model2);
- } else {
- setSelected(model1);
+ return (
+ {
+ if (selected === model1) {
+ setSelected(model2);
+ } else {
+ setSelected(model1);
+ }
}
- }
- }}
- >
-
-
- );
-}.bind({});
-
-SwitchModels.args = SharedArgs;
+ }}
+ >
+
+
+ );
+ },
+ { args: SharedArgs }
+);
export default {
title: 'Workspace',
diff --git a/demo/stories/helpers/tools.tsx b/demo/stories/helpers/tools.tsx
index 252a8c2..3bbe778 100644
--- a/demo/stories/helpers/tools.tsx
+++ b/demo/stories/helpers/tools.tsx
@@ -25,6 +25,7 @@ import {
} from '@projectstorm/react-workspaces-behavior-panel-dropzone';
import { draggingItemDividerBehavior } from '@projectstorm/react-workspaces-behavior-divider-dropzone';
import { WorkspaceTabFactory } from '@projectstorm/react-workspaces-model-tabs';
+import { WorkspaceTrayModel } from '@projectstorm/react-workspaces-model-tray';
import { resizingBehavior } from '@projectstorm/react-workspaces-behavior-resize';
import { RootWorkspaceModel } from '@projectstorm/react-workspaces-model-floating-window';
import { ConvertToTabZone, getDirectiveForTabModel } from '@projectstorm/react-workspaces-dropzone-plugin-tabs';
@@ -60,14 +61,16 @@ export const SharedArgs = {
[DebugOptions.DebugWindows]: false
};
-export const useRootModel = (model: RootWorkspaceModel, args) => {
+export type StoryArgs = typeof SharedArgs;
+
+export const useRootModel = (model: RootWorkspaceModel, args: StoryArgs) => {
useEffect(() => {
model.setDebug(args[DebugOptions.DebugWindows]);
}, [args[DebugOptions.DebugWindows]]);
return model;
};
-export const useEngine = (args: { DebugDividers?: boolean; DebugResizers?: boolean; DebugPanels?: boolean } = {}) => {
+export const useEngine = (args: StoryArgs = SharedArgs) => {
const [debugLayer] = useState(() => {
return new DebugLayer({
dividers: args[DebugOptions.DebugDividers],
@@ -109,13 +112,21 @@ export const useEngine = (args: { DebugDividers?: boolean; DebugResizers?: boole
engine: e,
getDropZoneForModel: (model) => {
return (
- getDirectiveForTrayModel(model) ||
+ getDirectiveForTrayModel(
+ model,
+ [],
+ () => new ExpandNodeModel(),
+ !(e.rootModel.flatten().find((candidate) => candidate.id === e.draggingID) instanceof WorkspaceTrayModel)
+ ) ||
getDirectiveForWorkspaceNode({
node: model,
transformZones: [ConvertToTabZone(tabFactory), ConvertToTrayZone(trayFactory)],
- generateParentNode: () => new ExpandNodeModel()
+ generateParentNode: () => new ExpandNodeModel(),
+ // Nested expand groups provide their own ordered divider dropzones.
+ // The root is also an ExpandNodeModel, but its direct children retain panel split targets.
+ allowSplit: !(model.parent instanceof ExpandNodeModel) || model.parent.parent === null
}) ||
- getDirectiveForTabModel(model)
+ getDirectiveForTabModel(model, [], () => new ExpandNodeModel())
);
},
debug: false
diff --git a/demo/typeface-open-sans.d.ts b/demo/typeface-open-sans.d.ts
new file mode 100644
index 0000000..2cb4a05
--- /dev/null
+++ b/demo/typeface-open-sans.d.ts
@@ -0,0 +1 @@
+declare module 'typeface-open-sans';
diff --git a/example-project-js/package.json b/example-project-js/package.json
index 92a82ab..adac635 100644
--- a/example-project-js/package.json
+++ b/example-project-js/package.json
@@ -12,19 +12,19 @@
"@projectstorm/react-workspaces-behavior-resize": "workspace:*",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
- "react": "^19.2.4",
- "react-dom": "^19.2.4"
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7"
},
"devDependencies": {
- "html-webpack-plugin": "^5.6.6",
- "@types/react": "^19.2.14",
+ "html-webpack-plugin": "^5.6.7",
+ "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
- "babel-loader": "^10.0.0",
- "@babel/core": "^7.29.0",
- "@babel/preset-env": "^7.29.0",
- "@babel/preset-react": "^7.28.5",
- "webpack": "^5.105.2",
- "webpack-cli": "^6.0.1",
- "webpack-dev-server": "^5.2.3"
+ "babel-loader": "^10.1.1",
+ "@babel/core": "^8.0.1",
+ "@babel/preset-env": "^8.0.2",
+ "@babel/preset-react": "^8.0.1",
+ "webpack": "^5.108.4",
+ "webpack-cli": "^7.2.1",
+ "webpack-dev-server": "^6.0.0"
}
}
diff --git a/package.json b/package.json
index d5341fa..5e8583c 100644
--- a/package.json
+++ b/package.json
@@ -30,17 +30,18 @@
"release:storybook": "tsc --build && cd demo && pnpm storybook:build && ./node_modules/.bin/storybook-to-ghpages --existing-output-dir .out"
},
"devDependencies": {
- "babel-loader": "^10.0.0",
+ "babel-loader": "^10.1.1",
"source-map-loader": "^5.0.0",
- "@babel/core": "^7.29.0",
- "@babel/preset-react": "^7.28.5",
- "@babel/preset-env": "^7.29.0",
- "prettier": "^3.8.1",
- "typescript": "^5.9.3",
- "terser-webpack-plugin": "^5.3.16",
- "@changesets/cli": "^2.29.8",
- "webpack": "^5.105.2",
- "webpack-cli": "^6.0.1",
- "webpack-node-externals": "^3.0.0"
+ "@babel/core": "^8.0.1",
+ "@babel/preset-react": "^8.0.1",
+ "@babel/preset-env": "^8.0.2",
+ "prettier": "^3.9.5",
+ "typescript": "^7.0.2",
+ "terser-webpack-plugin": "^5.6.1",
+ "@changesets/cli": "^2.31.0",
+ "webpack": "^5.108.4",
+ "webpack-cli": "^7.2.1",
+ "webpack-node-externals": "^3.0.0",
+ "npm-check-updates": "^22.2.9"
}
}
diff --git a/packages/behavior-divider-dropzone/package.json b/packages/behavior-divider-dropzone/package.json
index 7a0b992..aef8236 100644
--- a/packages/behavior-divider-dropzone/package.json
+++ b/packages/behavior-divider-dropzone/package.json
@@ -1,6 +1,7 @@
{
"name": "@projectstorm/react-workspaces-behavior-divider-dropzone",
"version": "2.4.12",
+ "repository": "https://github.com/projectstorm/react-workspaces.git",
"main": "./dist/index.umd.js",
"module": "./dist/index.jsx",
"types": "./dist/@types/index.d.ts",
@@ -12,10 +13,10 @@
},
"dependencies": {
"@projectstorm/react-workspaces-core": "workspace:*",
- "react": "^19.2.4",
+ "react": "^19.2.7",
"@emotion/styled": "^11.14.1"
},
"devDependencies": {
- "@types/react": "^19.2.14"
+ "@types/react": "^19.2.17"
}
-}
\ No newline at end of file
+}
diff --git a/packages/behavior-panel-dropzone/package.json b/packages/behavior-panel-dropzone/package.json
index 8486b1a..3618f42 100644
--- a/packages/behavior-panel-dropzone/package.json
+++ b/packages/behavior-panel-dropzone/package.json
@@ -1,6 +1,7 @@
{
"name": "@projectstorm/react-workspaces-behavior-panel-dropzone",
"version": "2.3.13",
+ "repository": "https://github.com/projectstorm/react-workspaces.git",
"main": "./dist/index.umd.js",
"module": "./dist/index.jsx",
"types": "./dist/@types/index.d.ts",
@@ -12,15 +13,15 @@
},
"dependencies": {
"@projectstorm/react-workspaces-core": "workspace:*",
- "@fortawesome/free-solid-svg-icons": "^7.2.0",
- "@fortawesome/react-fontawesome": "^3.2.0",
- "@fortawesome/fontawesome-svg-core": "^7.2.0",
- "react": "^19.2.4",
- "lodash": "^4.17.23",
+ "@fortawesome/free-solid-svg-icons": "^7.3.0",
+ "@fortawesome/react-fontawesome": "^3.4.0",
+ "@fortawesome/fontawesome-svg-core": "^7.3.0",
+ "react": "^19.2.7",
+ "lodash": "^4.18.1",
"@emotion/styled": "^11.14.1"
},
"devDependencies": {
- "@types/lodash": "^4.17.23",
- "@types/react": "^19.2.14"
+ "@types/lodash": "^4.17.24",
+ "@types/react": "^19.2.17"
}
-}
\ No newline at end of file
+}
diff --git a/packages/behavior-panel-dropzone/src/DropZoneAlignmentButtonWidget.tsx b/packages/behavior-panel-dropzone/src/DropZoneAlignmentButtonWidget.tsx
index 9e6be2e..f7e25fa 100644
--- a/packages/behavior-panel-dropzone/src/DropZoneAlignmentButtonWidget.tsx
+++ b/packages/behavior-panel-dropzone/src/DropZoneAlignmentButtonWidget.tsx
@@ -50,6 +50,9 @@ export const DropZoneAlignmentButtonWidget: React.FC {
+ setEntered(true);
+ },
onDrop: props.handleDrop
});
diff --git a/packages/behavior-panel-dropzone/src/DropZoneLayerButtonWidget.tsx b/packages/behavior-panel-dropzone/src/DropZoneLayerButtonWidget.tsx
index 0e34a40..5dccc3f 100644
--- a/packages/behavior-panel-dropzone/src/DropZoneLayerButtonWidget.tsx
+++ b/packages/behavior-panel-dropzone/src/DropZoneLayerButtonWidget.tsx
@@ -34,7 +34,7 @@ export const DefaultDropZoneLayerButtonTheme: DropZoneLayerButtonTheme = {
},
text: {
color: 'white',
- size: 11
+ size: 10
}
};
@@ -66,6 +66,9 @@ namespace S {
font-size: ${(p) => p.theme.text.size}px;
color: ${(p) => p.theme.text.color};
padding-top: 5px;
+ width: 100%;
+ text-align: center;
+ line-height: 1.1;
pointer-events: none;
`;
@@ -77,8 +80,8 @@ namespace S {
border 0.3s,
background 0.3s;
pointer-events: all;
- width: 60px;
- height: 60px;
+ width: 68px;
+ height: 68px;
margin: 2px;
display: flex;
flex-direction: column;
diff --git a/packages/behavior-panel-dropzone/src/DropZoneLayerPanelWidget.tsx b/packages/behavior-panel-dropzone/src/DropZoneLayerPanelWidget.tsx
index 87d7300..a9ef276 100644
--- a/packages/behavior-panel-dropzone/src/DropZoneLayerPanelWidget.tsx
+++ b/packages/behavior-panel-dropzone/src/DropZoneLayerPanelWidget.tsx
@@ -101,19 +101,26 @@ export const DropZoneLayerPanelWidget: React.FC =
})}
-
- {props.directive.transformZones.map((zone) => {
- return (
-
- );
- })}
-
+
+
+ {props.directive.transformZones.map((zone) => {
+ return (
+
+ );
+ })}
+
+ {props.directive.transformZones.length > 0 ? (
+
+ {props.directive.transformZones.length === 1 ? 'Drag into the above' : 'Drag into one of these'}
+
+ ) : null}
+
{props.debug ? {props.model.id.substring(0, 7)} : null}
@@ -149,6 +156,20 @@ namespace S {
flex-wrap: wrap;
`;
+ export const TransformActions = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ `;
+
+ export const ActionsHint = styled.div`
+ color: white;
+ font-size: 12px;
+ margin-top: 8px;
+ text-align: center;
+ pointer-events: none;
+ `;
+
export const Layer = styled.div<{ visible: boolean }>`
position: absolute;
top: 0;
diff --git a/packages/behavior-panel-dropzone/src/DropZoneTransformWidget.tsx b/packages/behavior-panel-dropzone/src/DropZoneTransformWidget.tsx
index 25b5ba6..f9130da 100644
--- a/packages/behavior-panel-dropzone/src/DropZoneTransformWidget.tsx
+++ b/packages/behavior-panel-dropzone/src/DropZoneTransformWidget.tsx
@@ -28,6 +28,12 @@ export const DropZoneTransformWidget: React.FC = (
useDroppableModel({
forwardRef: ref,
engine: props.engine,
+ // The drop-zone layer is mounted after the drag has already entered the
+ // panel. In that case this square may never receive dragenter, but it will
+ // receive dragover once it is beneath the cursor.
+ onDragover: () => {
+ setEntered(true);
+ },
onDrop: (model) => {
props.zone.transform({
model,
@@ -39,5 +45,7 @@ export const DropZoneTransformWidget: React.FC = (
return {props.zone.render({ entered, theme: props.theme })};
};
namespace S {
- export const Container = styled.div``;
+ export const Container = styled.div`
+ pointer-events: all;
+ `;
}
diff --git a/packages/behavior-panel-dropzone/src/index.tsx b/packages/behavior-panel-dropzone/src/index.tsx
index 8fb03a7..5633345 100644
--- a/packages/behavior-panel-dropzone/src/index.tsx
+++ b/packages/behavior-panel-dropzone/src/index.tsx
@@ -59,43 +59,46 @@ export interface GetDirectiveForWorkspaceNodeOptions {
node: WorkspaceModel;
transformZones?: TransformZone[];
generateParentNode?: () => WorkspaceNodeModel;
+ allowSplit?: boolean;
}
export const getDirectiveForWorkspaceNode = (
options: GetDirectiveForWorkspaceNodeOptions
): DropZonePanelDirective | null => {
- const { node, transformZones, generateParentNode } = options;
+ const { node, transformZones, generateParentNode, allowSplit = true } = options;
if (!(node instanceof WorkspaceCollectionModel) && node.parent instanceof WorkspaceNodeModel) {
return {
transformZones: [ReplaceZone, ...(transformZones || [])],
- splitZones: [
- {
- alignment: node.parent.vertical ? Alignment.LEFT : Alignment.TOP,
- handleDrop: (model, engine) => {
- const parent = node.parent as WorkspaceNodeModel;
- const m = generateParentNode?.() || new WorkspaceNodeModel();
- m.setVertical(!parent.vertical);
- m.setExpand(model.expandHorizontal, model.expandHorizontal);
- m.addModel(model);
- parent.replaceModel(node, m);
- m.addModel(node);
- engine.normalize();
- }
- },
- {
- alignment: node.parent.vertical ? Alignment.RIGHT : Alignment.BOTTOM,
- handleDrop: (model, engine) => {
- const parent = node.parent as WorkspaceNodeModel;
- const m = generateParentNode?.() || new WorkspaceNodeModel();
- m.setVertical(!parent.vertical);
- m.setExpand(model.expandHorizontal, model.expandHorizontal);
- m.addModel(model);
- parent.replaceModel(node, m);
- m.addModel(node, 0);
- engine.normalize();
- }
- }
- ]
+ splitZones: allowSplit
+ ? [
+ {
+ alignment: node.parent.vertical ? Alignment.LEFT : Alignment.TOP,
+ handleDrop: (model, engine) => {
+ const parent = node.parent as WorkspaceNodeModel;
+ const m = generateParentNode?.() || new WorkspaceNodeModel();
+ m.setVertical(!parent.vertical);
+ m.setExpand(model.expandHorizontal, model.expandVertical);
+ m.addModel(model);
+ parent.replaceModel(node, m);
+ m.addModel(node);
+ engine.normalize();
+ }
+ },
+ {
+ alignment: node.parent.vertical ? Alignment.RIGHT : Alignment.BOTTOM,
+ handleDrop: (model, engine) => {
+ const parent = node.parent as WorkspaceNodeModel;
+ const m = generateParentNode?.() || new WorkspaceNodeModel();
+ m.setVertical(!parent.vertical);
+ m.setExpand(model.expandHorizontal, model.expandVertical);
+ m.addModel(model);
+ parent.replaceModel(node, m);
+ m.addModel(node, 0);
+ engine.normalize();
+ }
+ }
+ ]
+ : []
};
}
};
diff --git a/packages/behavior-resize/package.json b/packages/behavior-resize/package.json
index d16ad21..71d0754 100644
--- a/packages/behavior-resize/package.json
+++ b/packages/behavior-resize/package.json
@@ -1,6 +1,7 @@
{
"name": "@projectstorm/react-workspaces-behavior-resize",
"version": "2.3.11",
+ "repository": "https://github.com/projectstorm/react-workspaces.git",
"main": "./dist/index.umd.js",
"module": "./dist/index.jsx",
"types": "./dist/@types/index.d.ts",
@@ -12,10 +13,10 @@
},
"dependencies": {
"@projectstorm/react-workspaces-core": "workspace:*",
- "react": "^19.2.4",
+ "react": "^19.2.7",
"@emotion/styled": "^11.14.1"
},
"devDependencies": {
- "@types/react": "^19.2.14"
+ "@types/react": "^19.2.17"
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/package.json b/packages/core/package.json
index 01ef474..c55e995 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,7 @@
{
"name": "@projectstorm/react-workspaces-core",
"version": "4.2.3",
+ "repository": "https://github.com/projectstorm/react-workspaces.git",
"main": "./dist/index.umd.js",
"module": "./dist/index.jsx",
"types": "./dist/@types/index.d.ts",
@@ -11,18 +12,18 @@
"build": "../../node_modules/.bin/webpack"
},
"dependencies": {
- "react": "^19.2.4",
- "react-dom": "^19.2.4",
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
"@emotion/styled": "^11.14.1",
"@emotion/react": "^11.14.0",
- "lodash": "^4.17.23",
+ "lodash": "^4.18.1",
"color": "^5.0.3",
- "uuid": "^13.0.0"
+ "uuid": "^14.0.1"
},
"devDependencies": {
- "@types/react": "^19.2.14",
+ "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
- "@types/lodash": "^4.17.23",
- "@types/uuid": "^10.0.0"
+ "@types/lodash": "^4.17.24",
+ "@types/uuid": "^11.0.0"
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/src/core-models/WorkspaceModel.ts b/packages/core/src/core-models/WorkspaceModel.ts
index 6716660..fa892f1 100644
--- a/packages/core/src/core-models/WorkspaceModel.ts
+++ b/packages/core/src/core-models/WorkspaceModel.ts
@@ -53,7 +53,7 @@ export class WorkspaceModelSize extends Size {
if (size.height != null) {
if (size.height < this.min.height) {
size.height = this.min.height;
- } else if (this.max.height > 0 && size.width > this.max.height) {
+ } else if (this.max.height > 0 && size.height > this.max.height) {
size.height = this.max.height;
}
}
diff --git a/packages/core/src/core/WorkspaceEngine.tsx b/packages/core/src/core/WorkspaceEngine.tsx
index c566310..4459489 100644
--- a/packages/core/src/core/WorkspaceEngine.tsx
+++ b/packages/core/src/core/WorkspaceEngine.tsx
@@ -31,7 +31,7 @@ export class WorkspaceEngineError extends Error {
export class WorkspaceEngine extends BaseObserver implements WorkspaceEngineInterface {
// factories
- factories: { [type: string]: WorkspaceModelFactory };
+ factories: { [type: string]: WorkspaceModelFactory };
draggingID: string;
layerManager: LayerManager;
repainting: boolean;
@@ -124,12 +124,12 @@ export class WorkspaceEngine extends BaseObserver imple
.forEach((m: WorkspaceCollectionModel) => m.normalize());
}
- registerFactory(factory: WorkspaceModelFactory) {
+ registerFactory(factory: WorkspaceModelFactory) {
this.factories[factory.type] = factory;
factory.setEngine(this);
}
- getFactory(model: WorkspaceModel | string): T {
+ getFactory>(model: WorkspaceModel | string): T {
if (typeof model !== 'string') {
model = model.type;
}
@@ -140,11 +140,15 @@ export class WorkspaceEngine extends BaseObserver imple
}
setDraggingNode(id: string) {
- if (this.draggingID !== id) {
- this.draggingID = id;
- this.iterateListeners((cb) => cb.modelDragStart?.());
- } else if (id === null) {
+ if (this.draggingID === id) {
+ return;
+ }
+
+ this.draggingID = id;
+ if (id === null) {
this.iterateListeners((cb) => cb.modelDragEnd?.());
+ } else {
+ this.iterateListeners((cb) => cb.modelDragStart?.());
}
}
}
diff --git a/packages/core/src/entities/node/ExpandNodeModel.ts b/packages/core/src/entities/node/ExpandNodeModel.ts
index 8abde62..bea7677 100644
--- a/packages/core/src/entities/node/ExpandNodeModel.ts
+++ b/packages/core/src/entities/node/ExpandNodeModel.ts
@@ -84,7 +84,10 @@ export class ExpandNodeModel<
);
const static_length = _.sum(
- this.r_divisions.map((d) => (this.vertical ? d.size.height : d.size.width)).concat(static_lengths)
+ this.r_divisions
+ .slice(1, -1)
+ .map((d) => (this.vertical ? d.size.height : d.size.width))
+ .concat(static_lengths)
);
if (length > static_length) {
diff --git a/packages/core/src/widgets/WorkspaceWidget.tsx b/packages/core/src/widgets/WorkspaceWidget.tsx
index ab57691..4d3ab65 100644
--- a/packages/core/src/widgets/WorkspaceWidget.tsx
+++ b/packages/core/src/widgets/WorkspaceWidget.tsx
@@ -30,6 +30,8 @@ namespace S {
export const WorkspaceWidget: React.FC = (props) => {
const ref_container = useRef(null);
const timerListener = useRef(null);
+ const rootModelKeys = useRef(new WeakMap