Skip to content

Commit 57c73dd

Browse files
authored
feat: enhance panel components with new sizing options and layout features (#4611)
Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent 29fafa6 commit 57c73dd

8 files changed

Lines changed: 542 additions & 190 deletions

File tree

.github/workflows/ci.yml

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,46 @@ permissions:
1111
name: PR Testing
1212

1313
jobs:
14-
ci:
14+
build:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout Sources
18-
uses: actions/checkout@v4
18+
uses: actions/checkout@v6
19+
20+
- name: Install NodeJS
21+
uses: actions/setup-node@v6
22+
with:
23+
node-version: 22
24+
registry-url: "https://registry.npmjs.org"
25+
scope: "@hpcc-js"
26+
27+
- name: Install Dependencies
28+
run: |
29+
npm ci
30+
31+
- name: Build
32+
run: |
33+
npm run build
34+
35+
- name: Upload build artifacts
36+
uses: actions/upload-artifact@v7
37+
with:
38+
name: build-output
39+
path: |
40+
packages/*/dist/
41+
packages/*/types/
42+
43+
- name: Upload error logs
44+
if: ${{ failure() || cancelled() }}
45+
uses: actions/upload-artifact@v7
46+
with:
47+
name: all-logs
48+
path: ./**/*.log
49+
lint:
50+
runs-on: ubuntu-latest
51+
steps:
52+
- name: Checkout Sources
53+
uses: actions/checkout@v6
1954

2055
- name: Install NodeJS
2156
uses: actions/setup-node@v6
@@ -32,9 +67,29 @@ jobs:
3267
run: |
3368
npm run lint
3469
35-
- name: Build
70+
test:
71+
needs: build
72+
runs-on: ubuntu-latest
73+
steps:
74+
- name: Checkout Sources
75+
uses: actions/checkout@v6
76+
77+
- name: Install NodeJS
78+
uses: actions/setup-node@v6
79+
with:
80+
node-version: 22
81+
registry-url: "https://registry.npmjs.org"
82+
scope: "@hpcc-js"
83+
84+
- name: Install Dependencies
3685
run: |
37-
npm run build
86+
npm ci
87+
88+
- name: Download build artifacts
89+
uses: actions/download-artifact@v8
90+
with:
91+
name: build-output
92+
path: packages
3893

3994
- name: Install Test Dependencies
4095
run: |
@@ -61,7 +116,7 @@ jobs:
61116
62117
- name: Upload error logs
63118
if: ${{ failure() || cancelled() }}
64-
uses: actions/upload-artifact@v4
119+
uses: actions/upload-artifact@v7
65120
with:
66121
name: all-logs
67122
path: ./**/*.log

packages/marshaller/src/dashy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,9 @@ export class Dashy extends SplitPanel {
434434
this.loadGraph();
435435
break;
436436
case this._lhsDebugSheet:
437-
this._lhsDebugSheet.childActivation(this._lhsDebugSheet.active());
437+
const w = this._lhsDebugSheet.active();
438+
const wa = this._lhsDebugSheet.getWidgetAdapter(w);
439+
this._lhsDebugSheet.childActivation(w, wa);
438440
break;
439441
}
440442
})

packages/phosphor/index.html

Lines changed: 140 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,65 @@
22
<html>
33

44
<head>
5-
<title>Home</title>
5+
<meta charset="UTF-8">
6+
<title>@hpcc-js/phosphor — Panel Demos</title>
67
<style>
8+
* {
9+
box-sizing: border-box;
10+
}
11+
712
body {
8-
font-family: Arial, sans-serif;
13+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
914
margin: 0;
10-
padding: 0;
11-
background-color: #f0f0f0;
15+
padding: 24px;
16+
background: #f5f5f5;
17+
color: #333;
1218
}
1319

1420
h1 {
1521
text-align: center;
16-
margin-top: 50px;
22+
margin: 0 0 8px;
23+
font-size: 1.8rem;
24+
color: #222;
25+
}
26+
27+
.subtitle {
28+
text-align: center;
29+
margin: 0 0 24px;
30+
color: #666;
31+
font-size: 0.9rem;
32+
}
33+
34+
.demo-section {
35+
margin-bottom: 32px;
36+
}
37+
38+
.demo-section h2 {
39+
font-size: 1.1rem;
40+
margin: 0 0 4px;
41+
color: #1565c0;
42+
}
43+
44+
.demo-section p {
45+
margin: 0 0 12px;
46+
font-size: 0.85rem;
47+
color: #555;
1748
}
1849

19-
#placeholder {
50+
.demo-container {
2051
width: 100%;
52+
height: 360px;
53+
background: #fff;
54+
border: 1px solid #ddd;
55+
border-radius: 6px;
56+
padding: 8px;
57+
}
58+
59+
.demo-container-tall {
2160
height: 500px;
22-
background-color: #fff;
23-
margin-top: 20px;
2461
}
2562
</style>
26-
</head>
27-
28-
<body onresize="doResize()">
29-
<h1>ESM Quick Test</h1>
30-
<div id="placeholder"></div>
31-
<script type="module">
32-
import { Area, Line, Bubble } from "@hpcc-js/chart";
33-
import { DockPanel } from "./src/index.ts";
34-
63+
<script>
3564
const twoD = {
3665
columns: ["Subject", "Result"],
3766
data: [
@@ -42,29 +71,106 @@ <h1>ESM Quick Test</h1>
4271
["Science", 82]
4372
]
4473
};
74+
</script>
75+
</head>
4576

46-
const area = new Area()
47-
.columns(twoD.columns)
48-
.data(twoD.data);
49-
const line = new Line()
50-
.columns(twoD.columns)
51-
.data(twoD.data);
52-
const bubble = new Bubble()
53-
.columns(twoD.columns)
54-
.data(twoD.data)
55-
.paletteID("Dark2");
77+
<body onresize="doResize()">
78+
<h1>@hpcc-js/phosphor</h1>
79+
<p class="subtitle">Docking, splitting &amp; tabbing panels with size constraints</p>
80+
81+
<!-- Demo 1: DockPanel with defaultSize -->
82+
<div class="demo-section">
83+
<h2>DockPanel — defaultSize / relative sizes</h2>
84+
<p>Left sidebar defaults to ~200px via <code>defaultSize</code>; remaining two panels split equally via <code>setSizes()</code>.</p>
85+
<div id="demo-dock" class="demo-container demo-container-tall"></div>
86+
</div>
87+
<script type="module">
88+
import { Area, Line, Bubble, Column, Pie, Step } from "@hpcc-js/chart";
89+
import { DockPanel, SplitPanel } from "./src/index.ts";
90+
91+
const d1_area = new Area().columns(twoD.columns).data(twoD.data);
92+
const d1_bubble = new Bubble().columns(twoD.columns).data(twoD.data).paletteID("Dark2");
93+
const d1_line = new Line().columns(twoD.columns).data(twoD.data);
94+
const d1_col = new Column().columns(twoD.columns).data(twoD.data);
5695

5796
new DockPanel()
58-
.target("placeholder")
59-
.addWidget(area, "<drag me>")
60-
.addWidget(bubble, "<drag me>", "split-right", area)
61-
.addWidget(line, "<drag me>", "split-bottom", area)
62-
.render()
63-
;
97+
.target("demo-dock")
98+
.addWidget(d1_area, { title: "Sidebar (200px)", location: "split-left", defaultSize: 200 })
99+
.addWidget(d1_bubble, { title: "Main", location: "split-right", refWidget: d1_area })
100+
.addWidget(d1_line, { title: "Chart B", location: "split-bottom", refWidget: d1_bubble })
101+
.addWidget(d1_col, { title: "Tabbed", location: "tab-after", refWidget: d1_line })
102+
.hideSingleTabs(true)
103+
.render();
64104
</script>
105+
106+
<!-- Demo 2: DockPanel with min/max constraints -->
107+
<div class="demo-section">
108+
<h2>DockPanel — min/max constraints</h2>
109+
<p>Left panel constrained to min 150px. Try resizing — it won't shrink below 150px.</p>
110+
<div id="demo-dock-minmax" class="demo-container"></div>
111+
</div>
112+
<script type="module">
113+
import { Area, Line, Bubble, Column, Pie, Step } from "@hpcc-js/chart";
114+
import { DockPanel, SplitPanel } from "./src/index.ts";
115+
116+
const d2_area = new Area().columns(twoD.columns).data(twoD.data);
117+
const d2_line = new Line().columns(twoD.columns).data(twoD.data);
118+
const d2_bubble = new Bubble().columns(twoD.columns).data(twoD.data).paletteID("Paired");
119+
120+
new DockPanel()
121+
.target("demo-dock-minmax")
122+
.addWidget(d2_area, { title: "Constrained (150–300px)", defaultSize: 200, minSize: 150, maxSize: 300 })
123+
.addWidget(d2_line, { title: "Flexible", location: "split-right", refWidget: d2_area })
124+
.addWidget(d2_bubble, { title: "Bottom", location: "split-bottom", refWidget: d2_line })
125+
.render();
126+
</script>
127+
128+
<!-- Demo 3: SplitPanel with defaultSize -->
129+
<div class="demo-section">
130+
<h2>SplitPanel — horizontal with defaultSize</h2>
131+
<p>Three columns: left clamped at 180px, center flexible, right default and min at 250px.</p>
132+
<div id="demo-split" class="demo-container"></div>
133+
</div>
134+
<script type="module">
135+
import { Area, Line, Bubble, Column, Pie, Step } from "@hpcc-js/chart";
136+
import { DockPanel, SplitPanel } from "./src/index.ts";
137+
138+
const s1_col = new Column().columns(twoD.columns).data(twoD.data);
139+
const s1_area = new Area().columns(twoD.columns).data(twoD.data);
140+
const s1_pie = new Pie().columns(twoD.columns).data(twoD.data);
141+
142+
new SplitPanel("horizontal")
143+
.target("demo-split")
144+
.addWidget(s1_col, { defaultSize: 180, minSize: 180, maxSize: 180, padding: 4 })
145+
.addWidget(s1_area, { padding: 4 })
146+
.addWidget(s1_pie, { defaultSize: 250, minSize: 250, padding: 4 })
147+
.render();
148+
149+
</script>
150+
151+
<!-- Demo 4: SplitPanel vertical with defaultSize -->
152+
<div class="demo-section">
153+
<h2>SplitPanel — vertical with defaultSize</h2>
154+
<p>Top panel gets 100px default size, with min 80px and max 120px; bottom fills remaining space.</p>
155+
<div id="demo-split-v" class="demo-container"></div>
156+
</div>
157+
<script type="module">
158+
import { Area, Line, Bubble, Column, Pie, Step } from "@hpcc-js/chart";
159+
import { DockPanel, SplitPanel } from "./src/index.ts";
160+
161+
const s2_step = new Step().columns(twoD.columns).data(twoD.data);
162+
const s2_bubble = new Bubble().columns(twoD.columns).data(twoD.data).paletteID("Set2");
163+
164+
new SplitPanel("vertical")
165+
.target("demo-split-v")
166+
.addWidget(s2_step, { defaultSize: 100, minSize: 80, maxSize: 120, padding: 4 })
167+
.addWidget(s2_bubble, { padding: 4 })
168+
.render();
169+
</script>
170+
65171
<script>
66172
function doResize() {
67-
window.__editor?.resize()?.render();
173+
// Panels auto-resize via their own resize logic
68174
}
69175
</script>
70176
</body>

packages/phosphor/src/BasePanel.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { HTMLWidget, Widget, Utility } from "@hpcc-js/common";
2+
import { Widget as PWidget, IMessageHandler, IMessageHook, Message, MessageLoop } from "./phosphor-shim.ts";
3+
import { Msg, WidgetAdapter, WidgetAdapterArray } from "./WidgetAdapter.ts";
4+
5+
export namespace BasePanel {
6+
export interface IAddWidgetOptions {
7+
/** Minimum size in pixels */
8+
minSize?: number;
9+
/** Preferred/default size in pixels — used as initial size hint */
10+
defaultSize?: number;
11+
/** Inner padding in pixels (default 8) */
12+
padding?: number;
13+
14+
/** Reference widget for split/tab positioning */
15+
refWidget?: Widget;
16+
}
17+
}
18+
19+
export abstract class BasePanel extends HTMLWidget implements IMessageHandler, IMessageHook {
20+
21+
protected abstract _content: WidgetAdapterArray;
22+
23+
constructor() {
24+
super();
25+
this._tag = "div";
26+
MessageLoop.installMessageHook(this, this);
27+
}
28+
29+
getWidget(wa: PWidget): Widget | undefined {
30+
if (wa instanceof WidgetAdapter) {
31+
return wa.widget;
32+
}
33+
}
34+
35+
getWidgetAdapter(widget: Widget): WidgetAdapter | null {
36+
let retVal = null;
37+
this._content.some(wa => {
38+
if (wa.widget === widget) {
39+
retVal = wa;
40+
return true;
41+
}
42+
return false;
43+
});
44+
return retVal;
45+
}
46+
47+
protected _prevActive: Widget;
48+
active(): Widget {
49+
return this._prevActive;
50+
}
51+
52+
// Phosphor Messaging ---
53+
protected _lazyLayoutChanged = Utility.debounce(async () => {
54+
this.layoutChanged();
55+
}, 1000);
56+
57+
processMessage(msg: Message): void {
58+
switch (msg.type) {
59+
case Msg.WAActivateRequest.type:
60+
const wa = (msg as Msg.WAActivateRequest).wa;
61+
const widget = wa.widget;
62+
if (this._prevActive !== widget) {
63+
this._prevActive = widget;
64+
this.childActivation(widget, wa);
65+
}
66+
break;
67+
case Msg.WALayoutChanged.type:
68+
this._lazyLayoutChanged();
69+
break;
70+
}
71+
}
72+
73+
messageHook(handler: IMessageHandler, msg: Message): boolean {
74+
if (handler === this) {
75+
this.processMessage(msg);
76+
}
77+
return true;
78+
}
79+
80+
// Events ---
81+
childActivation(w: Widget, wa: WidgetAdapter) {
82+
}
83+
84+
layoutChanged() {
85+
}
86+
87+
}

0 commit comments

Comments
 (0)