@hpcc-js/phosphor 3.7.1 → 3.8.0
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/dist/index.js +140 -86
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +2 -2
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +4 -4
- package/src/BasePanel.ts +87 -0
- package/src/DockPanel.ts +139 -66
- package/src/SplitPanel.ts +82 -32
- package/src/TabPanel.ts +43 -52
- package/src/WidgetAdapter.css +2 -1
- package/types/BasePanel.d.ts +28 -0
- package/types/DockPanel.d.ts +19 -10
- package/types/SplitPanel.d.ts +17 -10
- package/types/TabPanel.d.ts +17 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hpcc-js/phosphor",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "hpcc-js - Viz Phosphor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.umd.cjs",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"update-major": "npx --yes npm-check-updates -u"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@hpcc-js/common": "^3.8.
|
|
42
|
+
"@hpcc-js/common": "^3.8.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@hpcc-js/esbuild-plugins": "^1.9.
|
|
45
|
+
"@hpcc-js/esbuild-plugins": "^1.9.2",
|
|
46
46
|
"@lumino/algorithm": "2.0.4",
|
|
47
47
|
"@lumino/commands": "2.3.3",
|
|
48
48
|
"@lumino/messaging": "2.0.4",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"url": "https://github.com/hpcc-systems/Visualization/issues"
|
|
60
60
|
},
|
|
61
61
|
"homepage": "https://github.com/hpcc-systems/Visualization",
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "9d31241482c78decca86e3bf624244ce5b40f3d6"
|
|
63
63
|
}
|
package/src/BasePanel.ts
ADDED
|
@@ -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
|
+
}
|
package/src/DockPanel.ts
CHANGED
|
@@ -1,43 +1,144 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Widget, select as d3Select } from "@hpcc-js/common";
|
|
2
|
+
import { BasePanel } from "./BasePanel.ts";
|
|
3
|
+
import { DockLayout, DockPanel as PhosphorDockPanel, MessageLoop, Widget as PWidget } from "./phosphor-shim.ts";
|
|
4
|
+
import { IClosable, WidgetAdapter } from "./WidgetAdapter.ts";
|
|
3
5
|
import { PDockPanel } from "./PDockPanel.ts";
|
|
4
|
-
import { IClosable, Msg, WidgetAdapter } from "./WidgetAdapter.ts";
|
|
5
6
|
|
|
6
7
|
import "../src/DockPanel.css";
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
+
export namespace DockPanel {
|
|
10
|
+
export interface IAddWidgetOptions extends BasePanel.IAddWidgetOptions {
|
|
11
|
+
/** Tab title */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Insertion mode relative to refWidget */
|
|
14
|
+
location?: PhosphorDockPanel.InsertMode;
|
|
15
|
+
/** Whether the tab can be closed */
|
|
16
|
+
closable?: boolean | IClosable;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class DockPanel extends BasePanel {
|
|
9
21
|
private _dock = new PDockPanel({ mode: "multiple-document" });
|
|
22
|
+
protected _content = this._dock.content();
|
|
10
23
|
|
|
11
24
|
constructor() {
|
|
12
25
|
super();
|
|
13
|
-
this._tag = "div";
|
|
14
26
|
this._dock.id = "p" + this.id();
|
|
15
|
-
MessageLoop.installMessageHook(this, this);
|
|
16
27
|
}
|
|
17
28
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
});
|
|
27
|
-
return retVal;
|
|
28
|
-
}
|
|
29
|
+
private _pendingDefaults: Map<WidgetAdapter, { defaultSize?: number }> = new Map();
|
|
30
|
+
addWidget(widget: Widget, options: DockPanel.IAddWidgetOptions): this;
|
|
31
|
+
/** @deprecated Use options object form instead */
|
|
32
|
+
addWidget(widget: Widget, title: string, location?: PhosphorDockPanel.InsertMode, refWidget?: Widget, closable?: boolean | IClosable, padding?: number): this;
|
|
33
|
+
addWidget(widget: Widget, titleOrOptions: string | DockPanel.IAddWidgetOptions, location: PhosphorDockPanel.InsertMode = "split-right", refWidget?: Widget, closable?: boolean | IClosable, padding: number = 8) {
|
|
34
|
+
const opts: DockPanel.IAddWidgetOptions = typeof titleOrOptions === "string"
|
|
35
|
+
? { title: titleOrOptions, location, refWidget, closable, padding }
|
|
36
|
+
: titleOrOptions;
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
const {
|
|
39
|
+
title = "",
|
|
40
|
+
location: loc = "split-right",
|
|
41
|
+
refWidget: ref,
|
|
42
|
+
closable: canClose,
|
|
43
|
+
padding: pad = 8,
|
|
44
|
+
minSize,
|
|
45
|
+
defaultSize
|
|
46
|
+
} = opts;
|
|
47
|
+
|
|
48
|
+
const addMode: PhosphorDockPanel.IAddOptions = { mode: loc, ref: this.getWidgetAdapter(ref) };
|
|
49
|
+
const wa = new WidgetAdapter(this, widget, {}, canClose);
|
|
33
50
|
wa.title.label = title;
|
|
34
|
-
wa.padding =
|
|
51
|
+
wa.padding = pad;
|
|
52
|
+
|
|
53
|
+
if (minSize != null) {
|
|
54
|
+
const style = wa.node.style;
|
|
55
|
+
if (loc === "split-left" || loc === "split-right") {
|
|
56
|
+
style.minWidth = `${minSize}px`;
|
|
57
|
+
} else {
|
|
58
|
+
style.minHeight = `${minSize}px`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
this._dock.addWidget(wa, addMode);
|
|
36
63
|
this._dock.appendContent(wa);
|
|
37
64
|
this._dock.tabsMovable = true;
|
|
65
|
+
|
|
66
|
+
if (defaultSize != null) {
|
|
67
|
+
this._pendingDefaults.set(wa, { defaultSize });
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
return this;
|
|
39
71
|
}
|
|
40
72
|
|
|
73
|
+
private _applyPendingDefaults() {
|
|
74
|
+
if (this._pendingDefaults.size === 0) return;
|
|
75
|
+
|
|
76
|
+
const config = this._dock.saveLayout() as DockLayout.ILayoutConfig;
|
|
77
|
+
if (!config.main) {
|
|
78
|
+
this._pendingDefaults.clear();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build a lookup: widgetId → { defaultSize }
|
|
83
|
+
const lookup = new Map<string, { defaultSize?: number }>();
|
|
84
|
+
for (const [wa, defaults] of this._pendingDefaults) {
|
|
85
|
+
lookup.set(wa.widget.id(), defaults);
|
|
86
|
+
}
|
|
87
|
+
this._pendingDefaults.clear();
|
|
88
|
+
|
|
89
|
+
const containerWidth = this.width();
|
|
90
|
+
const containerHeight = this.height();
|
|
91
|
+
|
|
92
|
+
const applySizes = (area: DockLayout.AreaConfig, availWidth: number, availHeight: number): void => {
|
|
93
|
+
if (!area || area.type !== "split-area") return;
|
|
94
|
+
|
|
95
|
+
const isHorizontal = area.orientation === "horizontal";
|
|
96
|
+
const totalSpace = isHorizontal ? availWidth : availHeight;
|
|
97
|
+
const n = area.children.length;
|
|
98
|
+
|
|
99
|
+
// Collect requested pixel sizes per child
|
|
100
|
+
let usedSpace = 0;
|
|
101
|
+
let flexCount = 0;
|
|
102
|
+
const pixelSizes: (number | null)[] = area.children.map((child, i) => {
|
|
103
|
+
if (child.type === "tab-area") {
|
|
104
|
+
for (const w of (child as any).widgets) {
|
|
105
|
+
const defaults = lookup.get(w?.__id);
|
|
106
|
+
if (defaults) {
|
|
107
|
+
const size = defaults.defaultSize;
|
|
108
|
+
if (size != null) {
|
|
109
|
+
usedSpace += size;
|
|
110
|
+
return size;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
flexCount++;
|
|
116
|
+
return null;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Only apply if at least one child has a default
|
|
120
|
+
if (flexCount < n) {
|
|
121
|
+
const remainingSpace = Math.max(0, totalSpace - usedSpace);
|
|
122
|
+
const flexSize = flexCount > 0 ? remainingSpace / flexCount : 0;
|
|
123
|
+
area.sizes = pixelSizes.map(px => px != null ? px : flexSize);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Recurse into nested split areas with their allocated portion
|
|
127
|
+
for (let i = 0; i < area.children.length; i++) {
|
|
128
|
+
const child = area.children[i];
|
|
129
|
+
if (child.type === "split-area") {
|
|
130
|
+
const ratio = area.sizes[i] / (area.sizes.reduce((a, b) => a + b, 0) || 1);
|
|
131
|
+
const childW = isHorizontal ? availWidth * ratio : availWidth;
|
|
132
|
+
const childH = isHorizontal ? availHeight : availHeight * ratio;
|
|
133
|
+
applySizes(child, childW, childH);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
applySizes(config.main, containerWidth, containerHeight);
|
|
139
|
+
this._dock.restoreLayout(config as any);
|
|
140
|
+
}
|
|
141
|
+
|
|
41
142
|
removeWidget(widget: Widget) {
|
|
42
143
|
const wa = this.getWidgetAdapter(widget);
|
|
43
144
|
if (wa) {
|
|
@@ -74,25 +175,16 @@ export class DockPanel extends HTMLWidget implements IMessageHandler, IMessageHo
|
|
|
74
175
|
return this;
|
|
75
176
|
}
|
|
76
177
|
|
|
77
|
-
private _pPlaceholder;
|
|
78
178
|
enter(domNode, element) {
|
|
79
179
|
super.enter(domNode, element);
|
|
80
|
-
this.
|
|
81
|
-
PWidget.attach(this._dock, this._pPlaceholder.node());
|
|
180
|
+
PWidget.attach(this._dock, domNode);
|
|
82
181
|
}
|
|
83
182
|
|
|
84
183
|
_prevHideSingleTabs;
|
|
85
184
|
update(domNode, element) {
|
|
86
185
|
super.update(domNode, element);
|
|
87
|
-
|
|
88
|
-
this._pPlaceholder
|
|
89
|
-
.style("width", this.width() + "px")
|
|
90
|
-
.style("height", this.height() + "px")
|
|
91
|
-
.style("overflow", "hidden")
|
|
92
|
-
;
|
|
93
|
-
|
|
94
186
|
element.select(".lm-Widget")
|
|
95
|
-
.style("width", this.
|
|
187
|
+
.style("width", this.width() + "px")
|
|
96
188
|
.style("height", this.height() + "px")
|
|
97
189
|
;
|
|
98
190
|
|
|
@@ -100,8 +192,21 @@ export class DockPanel extends HTMLWidget implements IMessageHandler, IMessageHo
|
|
|
100
192
|
}
|
|
101
193
|
|
|
102
194
|
exit(domNode, element) {
|
|
195
|
+
if (this._dock.isAttached) {
|
|
196
|
+
if (this._dock.node.isConnected) {
|
|
197
|
+
// Proper Lumino detach — propagates AfterDetach to all children
|
|
198
|
+
// (TabBars etc.), clearing their isAttached flags before disposal.
|
|
199
|
+
PWidget.detach(this._dock);
|
|
200
|
+
} else {
|
|
201
|
+
// The DOM node has already been removed from the document by the
|
|
202
|
+
// parent framework (e.g. React unmount). We cannot call the
|
|
203
|
+
// static Widget.detach() because it guards against !isConnected,
|
|
204
|
+
// so manually broadcast AfterDetach so that Lumino's isAttached
|
|
205
|
+
// bookkeeping is consistent before we dispose child widgets.
|
|
206
|
+
MessageLoop.sendMessage(this._dock, PWidget.Msg.AfterDetach);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
103
209
|
[...this.widgets()].forEach(w => this.removeWidget(w));
|
|
104
|
-
PWidget.detach(this._dock);
|
|
105
210
|
super.exit(domNode, element);
|
|
106
211
|
}
|
|
107
212
|
|
|
@@ -112,6 +217,7 @@ export class DockPanel extends HTMLWidget implements IMessageHandler, IMessageHo
|
|
|
112
217
|
this.layoutObj(null);
|
|
113
218
|
}
|
|
114
219
|
return super.render((w) => {
|
|
220
|
+
this._applyPendingDefaults();
|
|
115
221
|
this._dock.content().watchRendered(this, callback);
|
|
116
222
|
this._dock.update();
|
|
117
223
|
setTimeout(() => {
|
|
@@ -137,39 +243,6 @@ export class DockPanel extends HTMLWidget implements IMessageHandler, IMessageHo
|
|
|
137
243
|
this._dock.fit();
|
|
138
244
|
}
|
|
139
245
|
|
|
140
|
-
// Phosphor Messaging ---
|
|
141
|
-
messageHook(handler: IMessageHandler, msg: Message): boolean {
|
|
142
|
-
if (handler === this) {
|
|
143
|
-
this.processMessage(msg);
|
|
144
|
-
}
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private _lazyLayoutChanged = Utility.debounce(async () => {
|
|
149
|
-
this.layoutChanged();
|
|
150
|
-
}, 1000);
|
|
151
|
-
|
|
152
|
-
_prevActive: Widget;
|
|
153
|
-
processMessage(msg: Message): void {
|
|
154
|
-
switch (msg.type) {
|
|
155
|
-
case Msg.WAActivateRequest.type:
|
|
156
|
-
const wa = (msg as Msg.WAActivateRequest).wa;
|
|
157
|
-
const widget = wa.widget;
|
|
158
|
-
if (this._prevActive !== widget) {
|
|
159
|
-
this._prevActive = widget;
|
|
160
|
-
this.childActivation(widget, wa);
|
|
161
|
-
}
|
|
162
|
-
break;
|
|
163
|
-
case Msg.WALayoutChanged.type:
|
|
164
|
-
this._lazyLayoutChanged();
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
active(): Widget {
|
|
170
|
-
return this._prevActive;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
246
|
// Events ---
|
|
174
247
|
childActivation(w: Widget, wa: WidgetAdapter) {
|
|
175
248
|
}
|
package/src/SplitPanel.ts
CHANGED
|
@@ -1,36 +1,61 @@
|
|
|
1
1
|
import { HTMLWidget, SVGWidget, Widget } from "@hpcc-js/common";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { BasePanel } from "./BasePanel.ts";
|
|
3
|
+
import { SplitPanel as PSplitPanel, Widget as PWidget } from "./phosphor-shim.ts";
|
|
4
|
+
import { WidgetAdapter, WidgetAdapterArray } from "./WidgetAdapter.ts";
|
|
4
5
|
|
|
5
6
|
import "../src/DockPanel.css";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export namespace SplitPanel {
|
|
9
|
+
|
|
10
|
+
export interface IAddWidgetOptions extends BasePanel.IAddWidgetOptions {
|
|
11
|
+
/** Maximum size in pixels */
|
|
12
|
+
maxSize?: number;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class SplitPanel extends BasePanel {
|
|
8
17
|
private _split: PSplitPanel;
|
|
9
|
-
|
|
18
|
+
protected _content = WidgetAdapterArray.create();
|
|
10
19
|
|
|
11
|
-
constructor(
|
|
20
|
+
constructor(readonly _orientation: "horizontal" | "vertical" = "vertical") {
|
|
12
21
|
super();
|
|
13
|
-
this._split = new PSplitPanel({ orientation });
|
|
14
|
-
this._tag = "div";
|
|
22
|
+
this._split = new PSplitPanel({ orientation: _orientation });
|
|
15
23
|
this._split.id = "p" + this.id();
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
private _pendingDefaults: Map<number, { defaultSize?: number }> = new Map();
|
|
27
|
+
addWidget(widget: SVGWidget | HTMLWidget, options?: SplitPanel.IAddWidgetOptions): this {
|
|
28
|
+
const opts = options || {};
|
|
29
|
+
const wa = new WidgetAdapter(this, widget);
|
|
30
|
+
wa.padding = opts.padding ?? 0;
|
|
31
|
+
|
|
32
|
+
if (opts.minSize != null || opts.maxSize != null) {
|
|
33
|
+
const style = wa.node.style;
|
|
34
|
+
if (opts.minSize != null) {
|
|
35
|
+
if (this._orientation === "horizontal") {
|
|
36
|
+
style.minWidth = `${opts.minSize}px`;
|
|
37
|
+
} else {
|
|
38
|
+
style.minHeight = `${opts.minSize}px`;
|
|
39
|
+
}
|
|
24
40
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
if (opts.maxSize != null) {
|
|
42
|
+
if (this._orientation === "horizontal") {
|
|
43
|
+
style.maxWidth = `${opts.maxSize}px`;
|
|
44
|
+
} else {
|
|
45
|
+
style.maxHeight = `${opts.maxSize}px`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
29
49
|
|
|
30
|
-
addWidget(widget: SVGWidget | HTMLWidget) {
|
|
31
|
-
const wa = new WidgetAdapter(this, widget);
|
|
32
50
|
this._split.addWidget(wa);
|
|
33
|
-
this.
|
|
51
|
+
this._content.push(wa);
|
|
52
|
+
|
|
53
|
+
if (opts.defaultSize != null) {
|
|
54
|
+
this._pendingDefaults.set(this._content.length - 1, {
|
|
55
|
+
defaultSize: opts.defaultSize
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
return this;
|
|
35
60
|
}
|
|
36
61
|
|
|
@@ -62,26 +87,51 @@ export class SplitPanel extends HTMLWidget {
|
|
|
62
87
|
|
|
63
88
|
render(callback?: (w: Widget) => void): this {
|
|
64
89
|
return super.render(w => {
|
|
65
|
-
this.
|
|
90
|
+
this._applyPendingDefaults();
|
|
91
|
+
this._content.watchRendered(this, callback);
|
|
66
92
|
this._split.update();
|
|
67
93
|
});
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
96
|
+
private _applyPendingDefaults() {
|
|
97
|
+
if (this._pendingDefaults.size === 0) return;
|
|
98
|
+
|
|
99
|
+
const isHorizontal = this._orientation === "horizontal";
|
|
100
|
+
const totalSpace = isHorizontal ? this.width() : this.height();
|
|
101
|
+
const n = this._content.length;
|
|
102
|
+
|
|
103
|
+
let usedSpace = 0;
|
|
104
|
+
let flexCount = 0;
|
|
105
|
+
const pixelSizes: (number | null)[] = [];
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < n; i++) {
|
|
108
|
+
const defaults = this._pendingDefaults.get(i);
|
|
109
|
+
if (defaults) {
|
|
110
|
+
const size = defaults.defaultSize;
|
|
111
|
+
if (size != null) {
|
|
112
|
+
pixelSizes.push(size);
|
|
113
|
+
usedSpace += size;
|
|
114
|
+
continue;
|
|
78
115
|
}
|
|
79
|
-
|
|
116
|
+
}
|
|
117
|
+
pixelSizes.push(null);
|
|
118
|
+
flexCount++;
|
|
80
119
|
}
|
|
120
|
+
|
|
121
|
+
this._pendingDefaults.clear();
|
|
122
|
+
|
|
123
|
+
const remainingSpace = Math.max(0, totalSpace - usedSpace);
|
|
124
|
+
const flexSize = flexCount > 0 ? remainingSpace / flexCount : 0;
|
|
125
|
+
const sizes = pixelSizes.map(px => px != null ? px : flexSize);
|
|
126
|
+
this._split.setRelativeSizes(sizes);
|
|
81
127
|
}
|
|
82
128
|
|
|
83
|
-
//
|
|
84
|
-
childActivation(w: Widget) {
|
|
129
|
+
// Events ---
|
|
130
|
+
childActivation(w: Widget, wa: WidgetAdapter) {
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
layoutChanged() {
|
|
85
134
|
}
|
|
86
135
|
}
|
|
87
136
|
SplitPanel.prototype._class += " phosphor_SplitPanel";
|
|
137
|
+
|
package/src/TabPanel.ts
CHANGED
|
@@ -1,55 +1,63 @@
|
|
|
1
1
|
import { HTMLWidget, SVGWidget, Widget } from "@hpcc-js/common";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { BasePanel } from "./BasePanel.ts";
|
|
3
|
+
import { TabPanel as PTabPanel, Widget as PWidget } from "./phosphor-shim.ts";
|
|
4
|
+
import { WidgetAdapter, WidgetAdapterArray, WidgetAdapterExt } from "./WidgetAdapter.ts";
|
|
4
5
|
|
|
5
6
|
import "../src/DockPanel.css";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export namespace TabPanel {
|
|
9
|
+
|
|
10
|
+
export interface IAddWidgetOptions {
|
|
11
|
+
/** Inner padding in pixels (default 8) */
|
|
12
|
+
padding?: number;
|
|
13
|
+
/** Tab title */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Widget adapter extensions (overflow settings) */
|
|
16
|
+
ext?: WidgetAdapterExt;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class TabPanel extends BasePanel {
|
|
8
21
|
private _tab = new PTabPanel({ tabPlacement: "top" });
|
|
9
|
-
protected
|
|
22
|
+
protected _content = WidgetAdapterArray.create();
|
|
10
23
|
|
|
11
24
|
constructor() {
|
|
12
25
|
super();
|
|
13
|
-
this._tag = "div";
|
|
14
26
|
this._tab.id = "p" + this.id();
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
addWidget(widget: SVGWidget | HTMLWidget, options: TabPanel.IAddWidgetOptions): this;
|
|
30
|
+
/** @deprecated Use options object form instead */
|
|
31
|
+
addWidget(widget: SVGWidget | HTMLWidget, title: string, ext?: WidgetAdapterExt): this;
|
|
32
|
+
addWidget(widget: SVGWidget | HTMLWidget, titleOrOptions: string | TabPanel.IAddWidgetOptions, ext: WidgetAdapterExt = {}) {
|
|
33
|
+
const opts: TabPanel.IAddWidgetOptions = typeof titleOrOptions === "string"
|
|
34
|
+
? { title: titleOrOptions, ext }
|
|
35
|
+
: titleOrOptions;
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
});
|
|
32
|
-
return retVal;
|
|
33
|
-
}
|
|
37
|
+
const {
|
|
38
|
+
title = "",
|
|
39
|
+
ext: widgetExt = {},
|
|
40
|
+
padding = 8
|
|
41
|
+
} = opts;
|
|
34
42
|
|
|
35
|
-
addWidget(widget: SVGWidget | HTMLWidget, title: string, ext: WidgetAdapterExt = {}) {
|
|
36
43
|
if (!this._prevActive) {
|
|
37
44
|
this._prevActive = widget;
|
|
38
45
|
}
|
|
39
|
-
const wa = new WidgetAdapter(this, widget, undefined, undefined,
|
|
46
|
+
const wa = new WidgetAdapter(this, widget, undefined, undefined, widgetExt);
|
|
40
47
|
wa.title.label = title;
|
|
41
|
-
wa.padding =
|
|
48
|
+
wa.padding = padding;
|
|
49
|
+
|
|
42
50
|
this._tab.addWidget(wa);
|
|
43
|
-
this.
|
|
51
|
+
this._content.push(wa);
|
|
44
52
|
return this;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
removeWidget(widget: SVGWidget | HTMLWidget) {
|
|
48
56
|
const wa = this.getWidgetAdapter(widget);
|
|
49
57
|
if (wa) {
|
|
50
|
-
const found = this.
|
|
58
|
+
const found = this._content.indexOf(wa);
|
|
51
59
|
if (found >= 0) {
|
|
52
|
-
this.
|
|
60
|
+
this._content.splice(found, 1);
|
|
53
61
|
}
|
|
54
62
|
widget.target(null);
|
|
55
63
|
wa.dispose();
|
|
@@ -77,35 +85,11 @@ export class TabPanel extends HTMLWidget {
|
|
|
77
85
|
|
|
78
86
|
render(callback?: (w: Widget) => void): this {
|
|
79
87
|
return super.render(w => {
|
|
80
|
-
this.
|
|
88
|
+
this._content.watchRendered(this, callback);
|
|
81
89
|
this._tab.update();
|
|
82
90
|
});
|
|
83
91
|
}
|
|
84
92
|
|
|
85
|
-
// Phosphor Messaging ---
|
|
86
|
-
messageHook(handler: IMessageHandler, msg: Message): boolean {
|
|
87
|
-
if (handler === this) {
|
|
88
|
-
this.processMessage(msg);
|
|
89
|
-
}
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private _prevActive: Widget;
|
|
94
|
-
processMessage(msg: Message): void {
|
|
95
|
-
switch (msg.type) {
|
|
96
|
-
case "wa-activate-request":
|
|
97
|
-
const widget = (msg as Msg.WAActivateRequest).wa.widget;
|
|
98
|
-
if (this._prevActive !== widget) {
|
|
99
|
-
this._prevActive = widget;
|
|
100
|
-
this.childActivation(widget);
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
childActivation(w: Widget) {
|
|
107
|
-
}
|
|
108
|
-
|
|
109
93
|
active(): Widget;
|
|
110
94
|
active(_: Widget);
|
|
111
95
|
active(_?: Widget): Widget | this {
|
|
@@ -113,5 +97,12 @@ export class TabPanel extends HTMLWidget {
|
|
|
113
97
|
this._tab.currentWidget = this.getWidgetAdapter(_);
|
|
114
98
|
return this;
|
|
115
99
|
}
|
|
100
|
+
|
|
101
|
+
// Events ---
|
|
102
|
+
childActivation(w: Widget, wa: WidgetAdapter) {
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
layoutChanged() {
|
|
106
|
+
}
|
|
116
107
|
}
|
|
117
108
|
TabPanel.prototype._class += " phosphor_TabPanel";
|