react-simple-dock 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.css ADDED
@@ -0,0 +1,192 @@
1
+ /* Light Theme, feel free to override */
2
+ :root {
3
+ --sd-grid-gap: 3px;
4
+ }
5
+
6
+ :root[data-theme="light"] {
7
+ --sd-background-color: #fff;
8
+ --sd-text-color: #000;
9
+ --sd-tab-border-color: #bdbdbd;
10
+ --sd-tab-background-color: white;
11
+ --sd-header-background-color: white;
12
+ --sd-header-border-color: #cecece;
13
+ --sd-overlay-color: rgba(69, 159, 232, 0.51);
14
+ --sd-highlight-color: #4591e8;
15
+ }
16
+
17
+ /* Dark Theme, feel free to override */
18
+ :root[data-theme="dark"] {
19
+ --sd-background-color: #22272e;
20
+ --sd-text-color: #fff;
21
+ --sd-border-color: #666;
22
+ --sd-tab-border-color: #111;
23
+ --sd-highlight-color: #4591e8;
24
+ --sd-tab-background-color: #1c2128;
25
+ --sd-header-background-color: #333;
26
+ --sd-header-border-color: #444;
27
+ --sd-overlay-color: rgba(105, 159, 232, 0.51);
28
+ }
29
+
30
+ body[data-jp-theme-light] {
31
+ /* don't care about the theme, just that we're in JupyterLab */
32
+ --sd-grid-gap: 7px;
33
+ --sd-background-color: var(--jp-layout-color3);
34
+ }
35
+
36
+ .resize-border {
37
+ position: absolute;
38
+ background-color: var(--sd-background-color, #fff);
39
+ opacity: 0;
40
+ z-index: 10;
41
+ }
42
+
43
+ .resize-border:hover {
44
+ opacity: 0.8;
45
+ }
46
+
47
+ .resize-border.bottom {
48
+ bottom: calc(0px - var(--sd-grid-gap));
49
+ left: 0;
50
+ right: 0;
51
+ height: calc((var(--sd-grid-gap)));
52
+ cursor: row-resize;
53
+ }
54
+
55
+ .resize-border.right {
56
+ top: 0;
57
+ bottom: 0;
58
+ right: calc(0px - var(--sd-grid-gap));
59
+ width: calc((var(--sd-grid-gap)));
60
+ cursor: col-resize;
61
+ }
62
+
63
+ .container {
64
+ width: 100%;
65
+ height: 100%;
66
+ }
67
+
68
+ .tab-handle-overlay {
69
+ position: absolute;
70
+ box-sizing: border-box;
71
+ display: none;
72
+ top: 0;
73
+ left: 0;
74
+ right: 0;
75
+ bottom: 0;
76
+ z-index: 100;
77
+ background: var(--sd-overlay-color, rgba(69, 159, 232, 0.51));
78
+ border: 2px dashed var(--sd-highlight-color, #4591e8);
79
+
80
+ pointer-events: none;
81
+ transition: 0s linear;
82
+ transition-property: left, right, top, bottom, width, height;
83
+ }
84
+
85
+ .panel {
86
+ position: relative;
87
+ display: flex;
88
+ flex: 1 100000000 0;
89
+ align-self: stretch;
90
+ height: 100%;
91
+ width: 100%;
92
+ background: var(--sd-background-color);
93
+ }
94
+
95
+ .panel.leaf {
96
+ flex-direction: column;
97
+ }
98
+
99
+ .row > .panel-content,
100
+ .column > .panel-content {
101
+ display: grid;
102
+ grid-gap: var(--sd-grid-gap);
103
+ position: absolute;
104
+ left: 0;
105
+ top: 0;
106
+ width: 100%;
107
+ height: 100%;
108
+ }
109
+
110
+ .leaf > .panel-content {
111
+ position: relative;
112
+ flex: 1;
113
+ }
114
+
115
+ .leaf > .panel-content > div {
116
+ background: var(--sd-tab-background-color);
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ width: 100%;
121
+ height: 100%;
122
+ overflow: scroll;
123
+ }
124
+
125
+ /* TAB HEADER */
126
+
127
+ .tab-header {
128
+ display: flex;
129
+ flex-direction: row;
130
+ height: var(--sd-header-height);
131
+ }
132
+
133
+ .tab-header-border {
134
+ position: relative;
135
+ height: 1px;
136
+ background: var(--sd-tab-border-color, #cecece);
137
+ margin-top: -2px;
138
+ z-index: 0;
139
+ }
140
+
141
+ .tab-header-bottom {
142
+ position: relative;
143
+ background: var(--sd-tab-background-color, white);
144
+ height: 3px;
145
+ z-index: 2;
146
+ border: solid var(--sd-tab-border-color, #bdbdbd);
147
+ border-width: 0 1px 1px 1px;
148
+ /*
149
+ Why did I put this ? It messes up the overall width of the layout
150
+ margin-left: -1px;
151
+ margin-right: -1px;
152
+ */
153
+ }
154
+
155
+ .tab-placeholder {
156
+ width: 0;
157
+ transition: width 0.1s linear;
158
+ height: 0;
159
+ }
160
+
161
+ .tab-handle {
162
+ position: relative;
163
+ box-sizing: border-box;
164
+ padding: 5px;
165
+ color: var(--sd-text-color, #000);
166
+ background: var(--sd-tab-background-color, white);
167
+ font-size: calc((var(--sd-header-height) - 12px) * 0.8);
168
+ border: solid var(--sd-tab-border-color, #bdbdbd);
169
+ border-width: 1px 1px 1px 1px;
170
+ /* Too overlap the border of the next handle and avoid double borders */
171
+ margin-right: -1px;
172
+ z-index: 1;
173
+ }
174
+
175
+ .tab-handle:nth-child(2) {
176
+ border-left-width: 1px;
177
+ }
178
+
179
+ .tab-handle.tab-handle__hidden {
180
+ z-index: 0;
181
+ }
182
+
183
+ .tab-handle.tab-handle__visible:before {
184
+ content: "";
185
+ display: block;
186
+ background: var(--sd-highlight-color, #4591e8);
187
+ position: absolute;
188
+ top: 0;
189
+ left: 0;
190
+ right: 0;
191
+ height: 2px;
192
+ }
package/index.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import "./index.css";
3
+ import { LayoutConfig, PanelProps } from "./types";
4
+ export declare const Panel: (props: PanelProps) => any;
5
+ export declare function Layout(props: {
6
+ children: React.ReactElement<PanelProps>[] | React.ReactElement<PanelProps>;
7
+ defaultConfig?: LayoutConfig;
8
+ wrapDnd?: boolean;
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ export declare namespace Layout {
11
+ var defaultProps: {
12
+ wrapDnd: boolean;
13
+ };
14
+ }
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "react-simple-dock",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "main": "index.js",
5
- "types": "index.d.ts",
6
5
  "description": "Simple dock component for React",
7
6
  "repository": "https://github.com/percevalw/react-simple-dock",
8
7
  "author": "Perceval Wajsbürt <perceval.wajsburt@gmail.com>",
@@ -14,11 +13,6 @@
14
13
  "react": ">=17.0.0",
15
14
  "react-dom": ">=17.0.0"
16
15
  },
17
- "files": [
18
- "dist",
19
- "LICENSE",
20
- "README.md"
21
- ],
22
16
  "browserslist": {
23
17
  "production": [
24
18
  ">0.2%",
@@ -30,5 +24,6 @@
30
24
  "last 1 firefox version",
31
25
  "last 1 safari version"
32
26
  ]
33
- }
27
+ },
28
+ "types": "index.d.ts"
34
29
  }
package/types.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ import React from "react";
2
+ /**
3
+ * Leaf layout configuration.
4
+ *
5
+ * Represents a panel that contains one or more tabs along with the current tab index and its size.
6
+ */
7
+ export type LeafLayoutConfig = {
8
+ kind: "leaf";
9
+ tabs: string[];
10
+ tabIndex: number;
11
+ size: number;
12
+ nesting?: number;
13
+ };
14
+ /**
15
+ * Container layout configuration.
16
+ *
17
+ * Represents a container panel that arranges its child panels in either a row or a column.
18
+ */
19
+ export type ContainerLayoutConfig = {
20
+ kind: "row" | "column";
21
+ children: LayoutConfig[];
22
+ size: number;
23
+ nesting?: number;
24
+ };
25
+ /**
26
+ * Layout configuration.
27
+ *
28
+ * A union type that represents either a leaf panel (with tabs) or a container panel (row/column with children).
29
+ */
30
+ export type LayoutConfig = LeafLayoutConfig | ContainerLayoutConfig;
31
+ /**
32
+ * Properties for a panel component.
33
+ *
34
+ * Defines the children to render within the panel, an optional name, and an optional header.
35
+ */
36
+ export type PanelProps = {
37
+ children: React.ReactNode;
38
+ name?: string;
39
+ header?: React.ReactNode;
40
+ };
41
+ /**
42
+ * Represents a drop zone within the layout.
43
+ *
44
+ * Includes the rectangular coordinates, the associated layout configuration,
45
+ * an a to indicate the drop position, and a reference to the related DOM element.
46
+ */
47
+ export type Zone = {
48
+ rect: {
49
+ left: number;
50
+ top: number;
51
+ width: number;
52
+ height: number;
53
+ };
54
+ config: LayoutConfig;
55
+ index: "LEFT" | "RIGHT" | "TOP" | "BOTTOM" | "CENTER" | "TAB";
56
+ element: HTMLElement;
57
+ before?: string;
58
+ };
59
+ /**
60
+ * Drag and drop item for tab header drag operations.
61
+ *
62
+ * Contains the panel name and a reference to the draggable element.
63
+ */
64
+ export type DnDItem = {
65
+ name: string;
66
+ handleElement: HTMLDivElement;
67
+ };
package/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/utils.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { Zone, LayoutConfig } from "./types";
2
+ /**
3
+ * Moves a panel within the layout based on a drop zone and panel name.
4
+ *
5
+ * This function traverses the layout config tree, removing the panel from its original position,
6
+ * and re-inserting it based on the provided drop zone. It handles both leaf and container nodes,
7
+ * ensuring proper size calculations and layout updates.
8
+ *
9
+ * @param zone - The drop zone where the panel is to be moved.
10
+ * @param name - The name of the panel being moved.
11
+ * @param inside - The layout config in which the move operation takes place.
12
+ * @returns The updated layout config after moving the panel.
13
+ */
14
+ export declare const movePanel: (zone: Zone | null, name: string, inside: LayoutConfig) => LayoutConfig;
15
+ /**
16
+ * Filters panels from a layout configuration based on a provided list of panel names.
17
+ *
18
+ * Traverses the layout configuration tree and removes any panels (or tabs within panels)
19
+ * whose names are not included in the specified list. It simplifies nodes by filtering
20
+ * out unwanted tabs and recursively updating container nodes.
21
+ *
22
+ * @param names - An array of panel names that should remain in the layout.
23
+ * @param inside - The layout configuration to filter.
24
+ * @returns The updated layout configuration with only the specified panels, or null if empty.
25
+ */
26
+ export declare const filterPanels: (names: string[], inside: LayoutConfig) => LayoutConfig;
package/utils.js ADDED
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Simplifies a layout configuration by flattening nested layouts with the same kind.
3
+ *
4
+ * If the layout configuration is a row or column, this function will flatten any nested child configurations
5
+ * that have the same kind as the parent. It recalculates the size of nested items based on their parent's size.
6
+ *
7
+ * @param config - The layout configuration to simplify.
8
+ * @returns The simplified layout configuration.
9
+ */
10
+ const simplifyLayout = (config) => {
11
+ if (config.kind === "row" || config.kind === "column") {
12
+ let expandedChildren = [];
13
+ let changed = false;
14
+ config.children.forEach((child) => {
15
+ if (child.kind === config.kind) {
16
+ changed = true;
17
+ const totalChildContainerSize = child.children.reduce((a, b) => a + b.size, 0);
18
+ child.children.forEach((grandChild) => {
19
+ expandedChildren.push({
20
+ ...grandChild,
21
+ size: (child.size * grandChild.size) / totalChildContainerSize,
22
+ });
23
+ });
24
+ }
25
+ else {
26
+ expandedChildren.push(child);
27
+ }
28
+ });
29
+ if (changed) {
30
+ config = { ...config, children: expandedChildren };
31
+ }
32
+ }
33
+ return config;
34
+ };
35
+ /**
36
+ * Moves a panel within the layout based on a drop zone and panel name.
37
+ *
38
+ * This function traverses the layout config tree, removing the panel from its original position,
39
+ * and re-inserting it based on the provided drop zone. It handles both leaf and container nodes,
40
+ * ensuring proper size calculations and layout updates.
41
+ *
42
+ * @param zone - The drop zone where the panel is to be moved.
43
+ * @param name - The name of the panel being moved.
44
+ * @param inside - The layout config in which the move operation takes place.
45
+ * @returns The updated layout config after moving the panel.
46
+ */
47
+ export const movePanel = (zone, name, inside) => {
48
+ const editLayout = (visitedConfig) => {
49
+ let config = visitedConfig;
50
+ if (config.kind === "leaf" && config.tabs.includes(name)) {
51
+ /* If it's a simple leaf, try to remove the matching tab if it was
52
+ * the tab that was picked by the user (since we're moving it) */
53
+ const newTabs = config.tabs.filter((tabName) => tabName !== name);
54
+ config = {
55
+ ...config,
56
+ tabs: newTabs,
57
+ tabIndex: Math.min(newTabs.length - 1, config.tabIndex),
58
+ };
59
+ if (config.tabs.length === 0) {
60
+ config = null;
61
+ }
62
+ }
63
+ /* If this zone is the zone targeted as drop-zone by the user */
64
+ if (zone && visitedConfig === zone.config) {
65
+ if (config === null) {
66
+ return {
67
+ kind: "leaf",
68
+ tabs: [name],
69
+ tabIndex: 0,
70
+ size: visitedConfig.size,
71
+ };
72
+ }
73
+ // or it's a container zone
74
+ else if (config.kind === "row" || config.kind === "column") {
75
+ const totalSize = config.children.reduce((a, b) => a + b.size, 0);
76
+ const fraction = config.kind === "column"
77
+ ? zone.rect.height / zone.element.parentElement.offsetHeight
78
+ : zone.rect.width / zone.element.parentElement.offsetWidth;
79
+ // x / (y + x) = a
80
+ // x = a y + a x
81
+ // x = a y / ( 1 - a )
82
+ const newConfigKind = zone.index === "TOP" || zone.index === "BOTTOM" ? "column" : "row";
83
+ if (config.kind === newConfigKind) {
84
+ const newZone = {
85
+ kind: "leaf",
86
+ tabs: [name],
87
+ tabIndex: 0,
88
+ size: (totalSize * fraction) / (1 - fraction),
89
+ };
90
+ config = {
91
+ ...config,
92
+ children: zone.index === "LEFT" || zone.index === "TOP"
93
+ ? [newZone, ...config.children.map(editLayout).filter((c) => c !== null)]
94
+ : [...config.children.map(editLayout).filter((c) => c !== null), newZone],
95
+ };
96
+ }
97
+ else {
98
+ const newZone = {
99
+ kind: "leaf",
100
+ tabs: [name],
101
+ tabIndex: 0,
102
+ size: 50,
103
+ };
104
+ const oldConfig = {
105
+ ...config,
106
+ // Remove the panel that is being moved
107
+ children: config.children.map(editLayout).filter((c) => c !== null),
108
+ size: 50,
109
+ };
110
+ config = {
111
+ kind: newConfigKind,
112
+ children: zone.index === "TOP" || zone.index === "LEFT" ? [newZone, oldConfig] : [oldConfig, newZone],
113
+ size: config.size,
114
+ };
115
+ }
116
+ }
117
+ // Or it's a leaf zone
118
+ else if (config.kind === "leaf") {
119
+ if (zone.index === "CENTER") {
120
+ const newTabs = [...config.tabs.filter((tabName) => tabName !== name), name];
121
+ config = {
122
+ ...config,
123
+ tabs: newTabs,
124
+ tabIndex: newTabs.length - 1,
125
+ };
126
+ }
127
+ else if (zone.index === "TAB") {
128
+ const newTabs = [...config.tabs];
129
+ let insertIndex = newTabs.findIndex((tabName) => tabName === zone.before);
130
+ if (insertIndex === -1) {
131
+ insertIndex = newTabs.length;
132
+ }
133
+ newTabs.splice(insertIndex, 0, name);
134
+ config = {
135
+ ...config,
136
+ tabs: newTabs,
137
+ tabIndex: insertIndex,
138
+ };
139
+ }
140
+ else {
141
+ const newZone = {
142
+ kind: "leaf",
143
+ tabs: [name],
144
+ tabIndex: 0,
145
+ size: 50,
146
+ };
147
+ config = {
148
+ kind: zone.index === "TOP" || zone.index === "BOTTOM" ? "column" : "row",
149
+ children: zone.index === "TOP" || zone.index === "LEFT"
150
+ ? [newZone, { ...config, size: 50 }]
151
+ : [{ ...config, size: 50, }, newZone],
152
+ size: config.size,
153
+ };
154
+ }
155
+ }
156
+ }
157
+ // If there is nothing left after removing the dropped zone
158
+ else if (config === null) {
159
+ return null;
160
+ }
161
+ // Otherwise, recurse into the node
162
+ else {
163
+ let hasChanged = false;
164
+ if (config.kind !== "leaf") {
165
+ const children = config.children
166
+ .map((child) => {
167
+ const updated = editLayout(child);
168
+ if (updated !== child) {
169
+ hasChanged = true;
170
+ }
171
+ return updated;
172
+ })
173
+ .filter((child) => child !== null);
174
+ if (hasChanged) {
175
+ config = { ...config, children };
176
+ }
177
+ }
178
+ }
179
+ if ((config.kind === "leaf" && config.tabs.length === 0) ||
180
+ (config.kind !== "leaf" && config.children.length === 0)) {
181
+ return null;
182
+ }
183
+ /* Simplify a node of the layout by flattening row children of rows
184
+ * or column children of columns */
185
+ config = simplifyLayout(config);
186
+ return config;
187
+ };
188
+ return editLayout(inside);
189
+ };
190
+ /**
191
+ * Filters panels from a layout configuration based on a provided list of panel names.
192
+ *
193
+ * Traverses the layout configuration tree and removes any panels (or tabs within panels)
194
+ * whose names are not included in the specified list. It simplifies nodes by filtering
195
+ * out unwanted tabs and recursively updating container nodes.
196
+ *
197
+ * @param names - An array of panel names that should remain in the layout.
198
+ * @param inside - The layout configuration to filter.
199
+ * @returns The updated layout configuration with only the specified panels, or null if empty.
200
+ */
201
+ export const filterPanels = (names, inside) => {
202
+ const editLayout = (visitedConfig) => {
203
+ let config = visitedConfig;
204
+ if (config.kind === "leaf" && !config.tabs.every((name) => names.includes(name))) {
205
+ /* If it's a simple leaf, try to remove the matching tab if it was
206
+ * the tab that was picked by the user (since we're moving it) */
207
+ config = {
208
+ ...config,
209
+ tabs: config.tabs.filter((name) => names.includes(name)),
210
+ tabIndex: Math.min(config.tabs.length - 1, config.tabIndex),
211
+ };
212
+ if (config.tabs.length === 0) {
213
+ config = null;
214
+ }
215
+ }
216
+ // If there is nothing left after removing the dropped zone
217
+ if (config === null) {
218
+ return null;
219
+ }
220
+ // Otherwise, recurse into the node
221
+ else {
222
+ let hasChanged = false;
223
+ if (config.kind !== "leaf") {
224
+ const children = config.children
225
+ .map((child) => {
226
+ const updated = editLayout(child);
227
+ if (updated !== child) {
228
+ hasChanged = true;
229
+ }
230
+ return updated;
231
+ })
232
+ .filter((child) => child !== null);
233
+ if (hasChanged) {
234
+ config = { ...config, children };
235
+ }
236
+ }
237
+ }
238
+ if ((config.kind === "leaf" && config.tabs.length === 0) ||
239
+ (config.kind !== "leaf" && config.children.length === 0)) {
240
+ return null;
241
+ }
242
+ /* Simplify a node of the layout by flattening row children of rows
243
+ * or column children of columns */
244
+ config = simplifyLayout(config);
245
+ return config;
246
+ };
247
+ return editLayout(inside);
248
+ };