@jupyterlab/application 4.6.0-alpha.5 → 4.6.0-beta.1
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/lib/dockpanel.d.ts +40 -0
- package/lib/dockpanel.js +231 -0
- package/lib/dockpanel.js.map +1 -0
- package/lib/frontend.js +1 -0
- package/lib/frontend.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js.map +1 -1
- package/lib/lab.js +1 -0
- package/lib/lab.js.map +1 -1
- package/lib/layoutrestorer.js +7 -0
- package/lib/layoutrestorer.js.map +1 -1
- package/lib/mimerenderers.js +1 -0
- package/lib/mimerenderers.js.map +1 -1
- package/lib/shell.d.ts +61 -4
- package/lib/shell.js +350 -41
- package/lib/shell.js.map +1 -1
- package/lib/status.d.ts +1 -1
- package/lib/status.js +0 -2
- package/lib/status.js.map +1 -1
- package/lib/tokens.d.ts +2 -2
- package/lib/tokens.js +0 -2
- package/lib/tokens.js.map +1 -1
- package/lib/utils.js +1 -0
- package/lib/utils.js.map +1 -1
- package/package.json +13 -13
- package/src/dockpanel.ts +267 -0
- package/src/frontend.ts +1 -0
- package/src/index.ts +2 -5
- package/src/lab.ts +1 -0
- package/src/layoutrestorer.ts +13 -0
- package/src/mimerenderers.ts +1 -0
- package/src/shell.ts +438 -40
- package/src/status.ts +1 -2
- package/src/tokens.ts +2 -3
- package/src/utils.ts +1 -0
- package/style/buttons.css +12 -4
- package/style/dockpanel.css +5 -1
- package/style/menus.css +10 -2
- package/style/scrollbar.css +69 -28
- package/style/sidepanel.css +162 -19
- package/style/tabs.css +5 -7
package/src/shell.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
4
|
|
|
4
5
|
import type { DocumentRegistry } from '@jupyterlab/docregistry';
|
|
5
6
|
import { DocumentWidget } from '@jupyterlab/docregistry';
|
|
@@ -8,7 +9,6 @@ import { nullTranslator } from '@jupyterlab/translation';
|
|
|
8
9
|
import type { SidePanel } from '@jupyterlab/ui-components';
|
|
9
10
|
import {
|
|
10
11
|
classes,
|
|
11
|
-
DockPanelSvg,
|
|
12
12
|
LabIcon,
|
|
13
13
|
TabBarSvg,
|
|
14
14
|
tabIcon,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from '@lumino/widgets';
|
|
36
36
|
import type { JupyterFrontEnd } from './frontend';
|
|
37
37
|
import type { LayoutRestorer } from './layoutrestorer';
|
|
38
|
+
import { OptimizedDockPanelSvg } from './dockpanel';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* The class name added to AppShell instances.
|
|
@@ -63,6 +64,11 @@ const DEFAULT_RANK = 900;
|
|
|
63
64
|
|
|
64
65
|
const ACTIVITY_CLASS = 'jp-Activity';
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The default relative size of the down area when it is expanded.
|
|
69
|
+
*/
|
|
70
|
+
const DEFAULT_DOWN_AREA_SIZE = 0.25;
|
|
71
|
+
|
|
66
72
|
/**
|
|
67
73
|
* The JupyterLab application shell token.
|
|
68
74
|
*/
|
|
@@ -135,8 +141,36 @@ export namespace ILabShell {
|
|
|
135
141
|
* Set to `false` for a more compact layout.
|
|
136
142
|
*/
|
|
137
143
|
dockPanelPadding?: boolean;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether to freeze panel dimensions during handle drag to improve resize
|
|
147
|
+
* performance when panels contain heavy DOM content.
|
|
148
|
+
*
|
|
149
|
+
* The default is `true`.
|
|
150
|
+
*/
|
|
151
|
+
optimizeResize?: boolean;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Position of the side activity bars.
|
|
155
|
+
*
|
|
156
|
+
* The default is `'side'`, which keeps each activity bar on the natural
|
|
157
|
+
* side of its area (left for the left area, right for the right area).
|
|
158
|
+
* `'top'` and `'bottom'` move both activity bars to the top or bottom of
|
|
159
|
+
* their respective area, displaying the tabs horizontally.
|
|
160
|
+
*/
|
|
161
|
+
activityBarPosition?: ActivityBarPosition;
|
|
138
162
|
}
|
|
139
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Position of a side activity bar within its area.
|
|
166
|
+
*
|
|
167
|
+
* `'side'` keeps the activity bar on the natural side of the area
|
|
168
|
+
* (left for the left area, right for the right area).
|
|
169
|
+
* `'top'` and `'bottom'` move the activity bar to the top or bottom
|
|
170
|
+
* of the side area, displaying the tabs horizontally.
|
|
171
|
+
*/
|
|
172
|
+
export type ActivityBarPosition = 'side' | 'top' | 'bottom';
|
|
173
|
+
|
|
140
174
|
/**
|
|
141
175
|
* Widget position
|
|
142
176
|
*/
|
|
@@ -245,6 +279,11 @@ export namespace ILabShell {
|
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
export interface IDownArea {
|
|
282
|
+
/**
|
|
283
|
+
* A flag denoting whether the down area has been collapsed.
|
|
284
|
+
*/
|
|
285
|
+
readonly collapsed?: boolean;
|
|
286
|
+
|
|
248
287
|
/**
|
|
249
288
|
* The current widget that has down area focus.
|
|
250
289
|
*/
|
|
@@ -358,7 +397,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
358
397
|
const hboxPanel = new BoxPanel();
|
|
359
398
|
const vsplitPanel = (this._vsplitPanel =
|
|
360
399
|
new Private.RestorableSplitPanel());
|
|
361
|
-
const dockPanel = (this._dockPanel = new
|
|
400
|
+
const dockPanel = (this._dockPanel = new OptimizedDockPanelSvg({
|
|
362
401
|
hiddenMode: Widget.HiddenMode.Display
|
|
363
402
|
}));
|
|
364
403
|
MessageLoop.installMessageHook(dockPanel, this._dockChildHook);
|
|
@@ -368,8 +407,14 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
368
407
|
const downPanel = (this._downPanel = new TabPanelSvg({
|
|
369
408
|
tabsMovable: true
|
|
370
409
|
}));
|
|
371
|
-
const leftHandler = (this._leftHandler = new Private.SideBarHandler(
|
|
372
|
-
|
|
410
|
+
const leftHandler = (this._leftHandler = new Private.SideBarHandler({
|
|
411
|
+
side: 'left',
|
|
412
|
+
host: hboxPanel
|
|
413
|
+
}));
|
|
414
|
+
const rightHandler = (this._rightHandler = new Private.SideBarHandler({
|
|
415
|
+
side: 'right',
|
|
416
|
+
host: hboxPanel
|
|
417
|
+
}));
|
|
373
418
|
const rootLayout = new BoxLayout();
|
|
374
419
|
|
|
375
420
|
headerPanel.id = 'jp-header-panel';
|
|
@@ -386,11 +431,15 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
386
431
|
leftHandler.sideBar.addClass('jp-mod-left');
|
|
387
432
|
leftHandler.sideBar.node.setAttribute('role', 'complementary');
|
|
388
433
|
leftHandler.stackedPanel.id = 'jp-left-stack';
|
|
434
|
+
leftHandler.area.addClass('jp-SideArea');
|
|
435
|
+
leftHandler.area.node.setAttribute('data-side', 'left');
|
|
389
436
|
|
|
390
437
|
rightHandler.sideBar.addClass(SIDEBAR_CLASS);
|
|
391
438
|
rightHandler.sideBar.addClass('jp-mod-right');
|
|
392
439
|
rightHandler.sideBar.node.setAttribute('role', 'complementary');
|
|
393
440
|
rightHandler.stackedPanel.id = 'jp-right-stack';
|
|
441
|
+
rightHandler.area.addClass('jp-SideArea');
|
|
442
|
+
rightHandler.area.node.setAttribute('data-side', 'right');
|
|
394
443
|
|
|
395
444
|
dockPanel.node.setAttribute('role', 'main');
|
|
396
445
|
|
|
@@ -405,10 +454,10 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
405
454
|
hsplitPanel.orientation = 'horizontal';
|
|
406
455
|
bottomPanel.direction = 'bottom-to-top';
|
|
407
456
|
|
|
408
|
-
SplitPanel.setStretch(leftHandler.
|
|
457
|
+
SplitPanel.setStretch(leftHandler.area, 0);
|
|
409
458
|
SplitPanel.setStretch(downPanel, 0);
|
|
410
459
|
SplitPanel.setStretch(dockPanel, 1);
|
|
411
|
-
SplitPanel.setStretch(rightHandler.
|
|
460
|
+
SplitPanel.setStretch(rightHandler.area, 0);
|
|
412
461
|
|
|
413
462
|
BoxPanel.setStretch(leftHandler.sideBar, 0);
|
|
414
463
|
BoxPanel.setStretch(hsplitPanel, 1);
|
|
@@ -416,9 +465,9 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
416
465
|
|
|
417
466
|
SplitPanel.setStretch(vsplitPanel, 1);
|
|
418
467
|
|
|
419
|
-
hsplitPanel.addWidget(leftHandler.
|
|
468
|
+
hsplitPanel.addWidget(leftHandler.area);
|
|
420
469
|
hsplitPanel.addWidget(dockPanel);
|
|
421
|
-
hsplitPanel.addWidget(rightHandler.
|
|
470
|
+
hsplitPanel.addWidget(rightHandler.area);
|
|
422
471
|
|
|
423
472
|
vsplitPanel.addWidget(hsplitPanel);
|
|
424
473
|
vsplitPanel.addWidget(downPanel);
|
|
@@ -585,6 +634,13 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
585
634
|
return this._tracker.currentWidget;
|
|
586
635
|
}
|
|
587
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Whether the down area is collapsed.
|
|
639
|
+
*/
|
|
640
|
+
get downCollapsed(): boolean {
|
|
641
|
+
return this._downPanel.isHidden;
|
|
642
|
+
}
|
|
643
|
+
|
|
588
644
|
/**
|
|
589
645
|
* A signal emitted when the main area's layout is modified.
|
|
590
646
|
*/
|
|
@@ -804,7 +860,13 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
804
860
|
title => title.owner.id === id
|
|
805
861
|
);
|
|
806
862
|
if (tabIndex >= 0) {
|
|
863
|
+
const wasHidden = this._downPanel.isHidden;
|
|
807
864
|
this._downPanel.currentIndex = tabIndex;
|
|
865
|
+
if (wasHidden) {
|
|
866
|
+
this._showDownPanel();
|
|
867
|
+
this._onLayoutModified();
|
|
868
|
+
}
|
|
869
|
+
this._downPanel.currentWidget?.activate();
|
|
808
870
|
return;
|
|
809
871
|
}
|
|
810
872
|
|
|
@@ -982,6 +1044,13 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
982
1044
|
}
|
|
983
1045
|
: undefined;
|
|
984
1046
|
|
|
1047
|
+
if (options?.rank !== undefined) {
|
|
1048
|
+
this._sideOptionsCache.set(widget, {
|
|
1049
|
+
...this._sideOptionsCache.get(widget),
|
|
1050
|
+
rank: options.rank
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
|
|
985
1054
|
switch (area || 'main') {
|
|
986
1055
|
case 'bottom':
|
|
987
1056
|
return this._addToBottomArea(widget, options);
|
|
@@ -1005,13 +1074,19 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1005
1074
|
}
|
|
1006
1075
|
|
|
1007
1076
|
/**
|
|
1008
|
-
* Move a widget
|
|
1077
|
+
* Move a widget to a new area and update the shell user layout.
|
|
1009
1078
|
*
|
|
1010
|
-
* The
|
|
1079
|
+
* The widget is reparented to `area` immediately. The type used as the
|
|
1080
|
+
* user-layout key is determined from `widget.id`, falling back to
|
|
1081
|
+
* `widget.id` itself.
|
|
1011
1082
|
*
|
|
1012
1083
|
* #### Notes
|
|
1013
|
-
* If `mode` is undefined, both
|
|
1014
|
-
*
|
|
1084
|
+
* If `mode` is undefined, both modes are updated in the user layout.
|
|
1085
|
+
* When `mode` is set, only that mode's user layout is updated, but the
|
|
1086
|
+
* live widget is still reparented regardless of mode.
|
|
1087
|
+
*
|
|
1088
|
+
* The new layout is stored in the shell user layout. Callers are
|
|
1089
|
+
* responsible for persisting it when needed.
|
|
1015
1090
|
*
|
|
1016
1091
|
* @param widget Widget to move
|
|
1017
1092
|
* @param area New area
|
|
@@ -1027,12 +1102,22 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1027
1102
|
'multiple-document': ILabShell.IUserLayout;
|
|
1028
1103
|
} {
|
|
1029
1104
|
const type = this._idTypeMap.get(widget.id) ?? widget.id;
|
|
1105
|
+
const rank = this._sideOptionsCache.get(widget)?.rank;
|
|
1030
1106
|
for (const m of ['single-document', 'multiple-document'].filter(
|
|
1031
1107
|
c => !mode || c === mode
|
|
1032
1108
|
)) {
|
|
1109
|
+
const position = this._userLayout[m as DockPanel.Mode][type];
|
|
1033
1110
|
this._userLayout[m as DockPanel.Mode][type] = {
|
|
1034
|
-
...
|
|
1035
|
-
area
|
|
1111
|
+
...position,
|
|
1112
|
+
area,
|
|
1113
|
+
...(rank !== undefined
|
|
1114
|
+
? {
|
|
1115
|
+
options: {
|
|
1116
|
+
...position?.options,
|
|
1117
|
+
rank
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
: {})
|
|
1036
1121
|
};
|
|
1037
1122
|
}
|
|
1038
1123
|
|
|
@@ -1057,6 +1142,16 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1057
1142
|
this._onLayoutModified();
|
|
1058
1143
|
}
|
|
1059
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Collapse the down area.
|
|
1147
|
+
*/
|
|
1148
|
+
collapseDown(): void {
|
|
1149
|
+
if (!this._downPanel.isHidden) {
|
|
1150
|
+
this._hideDownPanel();
|
|
1151
|
+
this._onLayoutModified();
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1060
1155
|
/**
|
|
1061
1156
|
* Dispose the shell.
|
|
1062
1157
|
*/
|
|
@@ -1092,6 +1187,19 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1092
1187
|
this._onLayoutModified();
|
|
1093
1188
|
}
|
|
1094
1189
|
|
|
1190
|
+
/**
|
|
1191
|
+
* Expand the down area.
|
|
1192
|
+
*/
|
|
1193
|
+
expandDown(): void {
|
|
1194
|
+
if (this._downPanel.stackedPanel.widgets.length === 0) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
this._showDownPanel();
|
|
1199
|
+
this._downPanel.currentWidget?.activate();
|
|
1200
|
+
this._onLayoutModified();
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1095
1203
|
/**
|
|
1096
1204
|
* Close all widgets in the main and down area.
|
|
1097
1205
|
*/
|
|
@@ -1215,12 +1323,42 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1215
1323
|
// Rehydrate the down area
|
|
1216
1324
|
if (downArea) {
|
|
1217
1325
|
const { currentWidget, widgets, size } = downArea;
|
|
1326
|
+
const collapsed = downArea.collapsed ?? !size;
|
|
1218
1327
|
|
|
1219
1328
|
const widgetIds = widgets?.map(widget => widget.id) ?? [];
|
|
1329
|
+
const otherAreaWidgetIds = new Set<string>();
|
|
1330
|
+
const collectMainWidgetIds = (
|
|
1331
|
+
area?: ILabShell.AreaConfig | null
|
|
1332
|
+
): void => {
|
|
1333
|
+
if (!area) {
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
if (area.type === 'tab-area') {
|
|
1337
|
+
area.widgets.forEach(widget => {
|
|
1338
|
+
otherAreaWidgetIds.add(widget.id);
|
|
1339
|
+
});
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
area.children.forEach(collectMainWidgetIds);
|
|
1343
|
+
};
|
|
1344
|
+
collectMainWidgetIds(mainArea?.dock?.main);
|
|
1345
|
+
leftArea?.widgets?.forEach(widget => {
|
|
1346
|
+
otherAreaWidgetIds.add(widget.id);
|
|
1347
|
+
});
|
|
1348
|
+
rightArea?.widgets?.forEach(widget => {
|
|
1349
|
+
otherAreaWidgetIds.add(widget.id);
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1220
1352
|
// Remove absent widgets
|
|
1221
1353
|
this._downPanel.tabBar.titles
|
|
1222
1354
|
.filter(title => !widgetIds.includes(title.owner.id))
|
|
1223
|
-
.
|
|
1355
|
+
.forEach(title => {
|
|
1356
|
+
if (otherAreaWidgetIds.has(title.owner.id)) {
|
|
1357
|
+
title.owner.parent = null;
|
|
1358
|
+
} else {
|
|
1359
|
+
title.owner.close();
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1224
1362
|
// Add new widgets
|
|
1225
1363
|
const titleIds = this._downPanel.tabBar.titles.map(
|
|
1226
1364
|
title => title.owner.id
|
|
@@ -1247,18 +1385,23 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1247
1385
|
const index = this._downPanel.stackedPanel.widgets.findIndex(
|
|
1248
1386
|
widget => widget.id === currentWidget.id
|
|
1249
1387
|
);
|
|
1250
|
-
if (index) {
|
|
1388
|
+
if (index >= 0) {
|
|
1251
1389
|
this._downPanel.currentIndex = index;
|
|
1252
|
-
this._downPanel.currentWidget?.activate();
|
|
1253
1390
|
}
|
|
1254
1391
|
}
|
|
1255
1392
|
|
|
1256
|
-
if (size && size > 0.0) {
|
|
1257
|
-
this.
|
|
1393
|
+
if (!collapsed && widgets?.length && size && size > 0.0) {
|
|
1394
|
+
this._showDownPanel(size);
|
|
1395
|
+
this._downPanel.currentWidget?.activate();
|
|
1258
1396
|
} else {
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1397
|
+
this._hideDownPanel();
|
|
1398
|
+
if (size && size > 0.0) {
|
|
1399
|
+
// Remember the saved size so a later expand restores the user's
|
|
1400
|
+
// previous height. `_hideDownPanel` seeds `_lastDownAreaSize`
|
|
1401
|
+
// from the current splitter, which at cold startup reflects the
|
|
1402
|
+
// default layout rather than the persisted value.
|
|
1403
|
+
this._lastDownAreaSize = size;
|
|
1404
|
+
}
|
|
1262
1405
|
}
|
|
1263
1406
|
}
|
|
1264
1407
|
|
|
@@ -1309,9 +1452,15 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1309
1452
|
: this._dockPanel.saveLayout()
|
|
1310
1453
|
},
|
|
1311
1454
|
downArea: {
|
|
1455
|
+
collapsed: this.downCollapsed,
|
|
1312
1456
|
currentWidget: this._downPanel.currentWidget,
|
|
1313
1457
|
widgets: Array.from(this._downPanel.stackedPanel.widgets),
|
|
1314
|
-
size:
|
|
1458
|
+
size:
|
|
1459
|
+
this._downPanel.stackedPanel.widgets.length === 0
|
|
1460
|
+
? 0
|
|
1461
|
+
: this.downCollapsed
|
|
1462
|
+
? this._lastDownAreaSize
|
|
1463
|
+
: this._vsplitPanel.relativeSizes()[1]
|
|
1315
1464
|
},
|
|
1316
1465
|
leftArea: this._leftHandler.dehydrate(),
|
|
1317
1466
|
rightArea: this._rightHandler.dehydrate(),
|
|
@@ -1395,13 +1544,39 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1395
1544
|
}
|
|
1396
1545
|
this._dockPanel.fit();
|
|
1397
1546
|
}
|
|
1547
|
+
|
|
1548
|
+
if (config.optimizeResize !== undefined) {
|
|
1549
|
+
this._dockPanel.optimizeResize = config.optimizeResize;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (config.activityBarPosition !== undefined) {
|
|
1553
|
+
this._setActivityBarPosition('left', config.activityBarPosition);
|
|
1554
|
+
this._setActivityBarPosition('right', config.activityBarPosition);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/**
|
|
1559
|
+
* Move the activity bar of a side area to a new position.
|
|
1560
|
+
*
|
|
1561
|
+
* @param side The side area to update.
|
|
1562
|
+
* @param position The new position of the activity bar.
|
|
1563
|
+
*/
|
|
1564
|
+
private _setActivityBarPosition(
|
|
1565
|
+
side: 'left' | 'right',
|
|
1566
|
+
position: ILabShell.ActivityBarPosition
|
|
1567
|
+
): void {
|
|
1568
|
+
const handler = side === 'left' ? this._leftHandler : this._rightHandler;
|
|
1569
|
+
if (handler.position === position) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
handler.position = position;
|
|
1573
|
+
this._onLayoutModified();
|
|
1398
1574
|
}
|
|
1399
1575
|
|
|
1400
1576
|
/**
|
|
1401
1577
|
* Returns the widgets for an application area.
|
|
1402
1578
|
*/
|
|
1403
1579
|
widgets(area?: ILabShell.Area): IterableIterator<Widget> {
|
|
1404
|
-
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
1405
1580
|
switch (area ?? 'main') {
|
|
1406
1581
|
case 'main':
|
|
1407
1582
|
return this._dockPanel.widgets();
|
|
@@ -1417,6 +1592,8 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1417
1592
|
return this._menuHandler.panel.children();
|
|
1418
1593
|
case 'bottom':
|
|
1419
1594
|
return this._bottomPanel.children();
|
|
1595
|
+
case 'down':
|
|
1596
|
+
return this._downPanel.stackedPanel.children();
|
|
1420
1597
|
default:
|
|
1421
1598
|
throw new Error(`Invalid area: ${area}`);
|
|
1422
1599
|
}
|
|
@@ -1435,7 +1612,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1435
1612
|
private _updateTitlePanelTitle() {
|
|
1436
1613
|
let current = this.currentWidget;
|
|
1437
1614
|
const inputElement = this._titleHandler.inputElement;
|
|
1438
|
-
inputElement.value = current ? current.title
|
|
1615
|
+
inputElement.value = current ? TabBarSvg.titleLabel(current.title) : '';
|
|
1439
1616
|
inputElement.title = current ? current.title.caption : '';
|
|
1440
1617
|
}
|
|
1441
1618
|
|
|
@@ -1675,11 +1852,30 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1675
1852
|
}
|
|
1676
1853
|
|
|
1677
1854
|
this._downPanel.addWidget(widget);
|
|
1678
|
-
this._onLayoutModified();
|
|
1679
1855
|
|
|
1680
1856
|
if (this._downPanel.isHidden) {
|
|
1681
|
-
this.
|
|
1857
|
+
this._showDownPanel();
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
this._onLayoutModified();
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
private _showDownPanel(size: number = this._lastDownAreaSize): void {
|
|
1864
|
+
const downSize = size > 0.0 ? size : DEFAULT_DOWN_AREA_SIZE;
|
|
1865
|
+
this._lastDownAreaSize = downSize;
|
|
1866
|
+
this._vsplitPanel.setRelativeSizes([
|
|
1867
|
+
Math.max(1.0 - downSize, 0.0),
|
|
1868
|
+
downSize
|
|
1869
|
+
]);
|
|
1870
|
+
this._downPanel.show();
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
private _hideDownPanel(): void {
|
|
1874
|
+
const size = this._vsplitPanel.relativeSizes()[1];
|
|
1875
|
+
if (size > 0.0) {
|
|
1876
|
+
this._lastDownAreaSize = size;
|
|
1682
1877
|
}
|
|
1878
|
+
this._downPanel.hide();
|
|
1683
1879
|
}
|
|
1684
1880
|
|
|
1685
1881
|
/*
|
|
@@ -1765,7 +1961,7 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1765
1961
|
*/
|
|
1766
1962
|
private _onTabPanelChanged(): void {
|
|
1767
1963
|
if (this._downPanel.stackedPanel.widgets.length === 0) {
|
|
1768
|
-
this.
|
|
1964
|
+
this._hideDownPanel();
|
|
1769
1965
|
}
|
|
1770
1966
|
this._onLayoutModified();
|
|
1771
1967
|
}
|
|
@@ -1840,9 +2036,10 @@ export class LabShell extends Widget implements JupyterFrontEnd.IShell {
|
|
|
1840
2036
|
ILabShell.ICurrentPathChangedArgs
|
|
1841
2037
|
>(this);
|
|
1842
2038
|
private _modeChanged = new Signal<this, DockPanel.Mode>(this);
|
|
1843
|
-
private _dockPanel:
|
|
2039
|
+
private _dockPanel: OptimizedDockPanelSvg;
|
|
1844
2040
|
private _downPanel: TabPanel;
|
|
1845
2041
|
private _isRestored = false;
|
|
2042
|
+
private _lastDownAreaSize = DEFAULT_DOWN_AREA_SIZE;
|
|
1846
2043
|
private _layoutModified = new Signal<this, void>(this);
|
|
1847
2044
|
private _layoutDebouncer = new Debouncer(() => {
|
|
1848
2045
|
this._layoutModified.emit(undefined);
|
|
@@ -1988,18 +2185,31 @@ namespace Private {
|
|
|
1988
2185
|
/**
|
|
1989
2186
|
* Construct a new side bar handler.
|
|
1990
2187
|
*/
|
|
1991
|
-
constructor() {
|
|
2188
|
+
constructor(options: SideBarHandler.IOptions) {
|
|
2189
|
+
this._side = options.side;
|
|
2190
|
+
this._host = options.host;
|
|
1992
2191
|
this._sideBar = new TabBar<Widget>({
|
|
1993
2192
|
insertBehavior: 'none',
|
|
1994
2193
|
removeBehavior: 'none',
|
|
1995
2194
|
allowDeselect: true,
|
|
1996
2195
|
orientation: 'vertical'
|
|
1997
2196
|
});
|
|
2197
|
+
// Mirror the initial position on the bar via `data-side`. The setter
|
|
2198
|
+
// keeps it in sync on subsequent transitions.
|
|
2199
|
+
this._sideBar.node.setAttribute('data-side', this._side);
|
|
1998
2200
|
this._stackedPanel = new StackedPanel();
|
|
2201
|
+
this._area = new BoxPanel({ direction: 'top-to-bottom', spacing: 0 });
|
|
2202
|
+
// The stacked panel always lives inside the area wrapper. It is the
|
|
2203
|
+
// only stretchable child so the activity bar (when inside the wrapper)
|
|
2204
|
+
// takes only its natural size at the top or bottom.
|
|
2205
|
+
BoxPanel.setStretch(this._stackedPanel, 1);
|
|
2206
|
+
BoxPanel.setStretch(this._sideBar, 0);
|
|
2207
|
+
this._area.addWidget(this._stackedPanel);
|
|
1999
2208
|
this._sideBar.hide();
|
|
2000
2209
|
this._stackedPanel.hide();
|
|
2001
2210
|
this._lastCurrent = null;
|
|
2002
2211
|
this._sideBar.currentChanged.connect(this._onCurrentChanged, this);
|
|
2212
|
+
this._sideBar.tabCloseRequested.connect(this._onTabCloseRequested, this);
|
|
2003
2213
|
this._sideBar.tabActivateRequested.connect(
|
|
2004
2214
|
this._onTabActivateRequested,
|
|
2005
2215
|
this
|
|
@@ -2028,6 +2238,77 @@ namespace Private {
|
|
|
2028
2238
|
return this._stackedPanel;
|
|
2029
2239
|
}
|
|
2030
2240
|
|
|
2241
|
+
/**
|
|
2242
|
+
* Get the wrapper panel for this side area.
|
|
2243
|
+
*
|
|
2244
|
+
* The wrapper always contains the stacked panel. When the activity bar is
|
|
2245
|
+
* positioned inside the area (top or bottom), it is also a child of this
|
|
2246
|
+
* wrapper. The wrapper is what gets added to the main horizontal split.
|
|
2247
|
+
*/
|
|
2248
|
+
get area(): BoxPanel {
|
|
2249
|
+
return this._area;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Get the current position of the activity bar.
|
|
2254
|
+
*/
|
|
2255
|
+
get position(): ILabShell.ActivityBarPosition {
|
|
2256
|
+
return this._position;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* Set the position of the activity bar relative to the area.
|
|
2261
|
+
*
|
|
2262
|
+
* In `'side'` mode the activity bar is reattached to the host panel
|
|
2263
|
+
* (e.g. the shell's hboxPanel) on its natural side. In `'top'` or
|
|
2264
|
+
* `'bottom'` mode the activity bar is reparented inside the area wrapper
|
|
2265
|
+
* above or below the stacked panel and laid out horizontally.
|
|
2266
|
+
*/
|
|
2267
|
+
set position(value: ILabShell.ActivityBarPosition) {
|
|
2268
|
+
if (this._position === value) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
this._position = value;
|
|
2272
|
+
|
|
2273
|
+
// The user-collapse flag only has meaning in horizontal mode and should
|
|
2274
|
+
// not carry over from a prior interaction in a different mode.
|
|
2275
|
+
this._isCollapsedByUser = false;
|
|
2276
|
+
|
|
2277
|
+
// Detach the activity bar from its current parent before reattaching
|
|
2278
|
+
// it to the new one (host or area wrapper).
|
|
2279
|
+
this._sideBar.parent = null;
|
|
2280
|
+
|
|
2281
|
+
// Update the orientation and the position-related attributes on the
|
|
2282
|
+
// activity bar. The icon/label rotation depends on these.
|
|
2283
|
+
const isLeft = this._side === 'left';
|
|
2284
|
+
this._sideBar.orientation = value === 'side' ? 'vertical' : 'horizontal';
|
|
2285
|
+
// Keep `jp-mod-left`/`jp-mod-right` for backward compatibility with
|
|
2286
|
+
// existing themes that target those classes.
|
|
2287
|
+
this._sideBar.toggleClass('jp-mod-left', isLeft && value === 'side');
|
|
2288
|
+
this._sideBar.toggleClass('jp-mod-right', !isLeft && value === 'side');
|
|
2289
|
+
// `data-side` mirrors the `data-orientation` attribute set by Lumino
|
|
2290
|
+
// and is the canonical hook for new CSS rules.
|
|
2291
|
+
const dataSide = value === 'side' ? (isLeft ? 'left' : 'right') : value;
|
|
2292
|
+
this._sideBar.node.setAttribute('data-side', dataSide);
|
|
2293
|
+
|
|
2294
|
+
// In 'side' mode, clicking the active tab collapses the area (the
|
|
2295
|
+
// activity bar stays as a thin strip). That collapse UX makes no sense
|
|
2296
|
+
// when the activity bar is at the top or bottom — clicking would just
|
|
2297
|
+
// leave an empty horizontal strip — so disable deselection there.
|
|
2298
|
+
this._sideBar.allowDeselect = value === 'side';
|
|
2299
|
+
|
|
2300
|
+
if (value === 'side') {
|
|
2301
|
+
const index = isLeft ? 0 : this._host.widgets.length;
|
|
2302
|
+
this._host.insertWidget(index, this._sideBar);
|
|
2303
|
+
} else if (value === 'top') {
|
|
2304
|
+
this._area.insertWidget(0, this._sideBar);
|
|
2305
|
+
} else {
|
|
2306
|
+
this._area.addWidget(this._sideBar);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
this._refreshVisibility();
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2031
2312
|
/**
|
|
2032
2313
|
* Signal fires when the stack panel or the sidebar changes
|
|
2033
2314
|
*/
|
|
@@ -2054,13 +2335,19 @@ namespace Private {
|
|
|
2054
2335
|
*
|
|
2055
2336
|
* #### Notes
|
|
2056
2337
|
* This will open the most recently used tab, or the first tab
|
|
2057
|
-
* if there is no most recently used.
|
|
2338
|
+
* if there is no most recently used. In `'top'` or `'bottom'` mode it
|
|
2339
|
+
* also re-shows the wrapper area if a previous `collapse()` call hid it.
|
|
2058
2340
|
*/
|
|
2059
2341
|
expand(): void {
|
|
2342
|
+
this._isCollapsedByUser = false;
|
|
2060
2343
|
const previous =
|
|
2061
2344
|
this._lastCurrent || (this._items.length > 0 && this._items[0].widget);
|
|
2062
2345
|
if (previous) {
|
|
2063
2346
|
this.activate(previous.id);
|
|
2347
|
+
} else {
|
|
2348
|
+
// No tab to activate, but we still need to reflect the cleared
|
|
2349
|
+
// collapse flag (relevant in 'top'/'bottom' mode).
|
|
2350
|
+
this._refreshVisibility();
|
|
2064
2351
|
}
|
|
2065
2352
|
}
|
|
2066
2353
|
|
|
@@ -2072,6 +2359,7 @@ namespace Private {
|
|
|
2072
2359
|
activate(id: string): void {
|
|
2073
2360
|
const widget = this._findWidgetByID(id);
|
|
2074
2361
|
if (widget) {
|
|
2362
|
+
this._isCollapsedByUser = false;
|
|
2075
2363
|
this._sideBar.currentTitle = widget.title;
|
|
2076
2364
|
widget.activate();
|
|
2077
2365
|
}
|
|
@@ -2086,9 +2374,16 @@ namespace Private {
|
|
|
2086
2374
|
|
|
2087
2375
|
/**
|
|
2088
2376
|
* Collapse the sidebar so no items are expanded.
|
|
2377
|
+
*
|
|
2378
|
+
* #### Notes
|
|
2379
|
+
* In `'side'` mode this only deselects the active tab, leaving the
|
|
2380
|
+
* activity bar visible. In `'top'` or `'bottom'` mode it also hides the
|
|
2381
|
+
* wrapper area entirely so the column can reclaim its space.
|
|
2089
2382
|
*/
|
|
2090
2383
|
collapse(): void {
|
|
2384
|
+
this._isCollapsedByUser = true;
|
|
2091
2385
|
this._sideBar.currentTitle = null;
|
|
2386
|
+
this._refreshVisibility();
|
|
2092
2387
|
}
|
|
2093
2388
|
|
|
2094
2389
|
/**
|
|
@@ -2106,7 +2401,7 @@ namespace Private {
|
|
|
2106
2401
|
const title = this._sideBar.insertTab(index, widget.title);
|
|
2107
2402
|
// Store the parent id in the title dataset
|
|
2108
2403
|
// in order to dispatch click events to the right widget.
|
|
2109
|
-
title.dataset = { id: widget.id };
|
|
2404
|
+
title.dataset = { ...title.dataset, id: widget.id };
|
|
2110
2405
|
if (title.icon instanceof LabIcon) {
|
|
2111
2406
|
// bind an appropriate style to the icon
|
|
2112
2407
|
title.icon = title.icon.bindprops({
|
|
@@ -2162,15 +2457,49 @@ namespace Private {
|
|
|
2162
2457
|
* Rehydrate the side bar.
|
|
2163
2458
|
*/
|
|
2164
2459
|
rehydrate(data: ILabShell.ISideArea): void {
|
|
2460
|
+
if (Array.isArray(data.widgets)) {
|
|
2461
|
+
const widgetIds = data.widgets.map(widget => widget.id);
|
|
2462
|
+
const widgetIdSet = new Set(widgetIds);
|
|
2463
|
+
|
|
2464
|
+
// Add widgets that are in the saved layout but not currently
|
|
2465
|
+
// in the sidebar.
|
|
2466
|
+
const currentIds = this._stackedPanel.widgets.map(widget => widget.id);
|
|
2467
|
+
data.widgets
|
|
2468
|
+
.filter(widget => !currentIds.includes(widget.id))
|
|
2469
|
+
.forEach(widget => {
|
|
2470
|
+
this.addWidget(widget, DEFAULT_RANK);
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
// Merge the saved order into the current sidebar slots so widgets
|
|
2474
|
+
// absent from the saved layout keep their rank-relative positions.
|
|
2475
|
+
let savedIndex = 0;
|
|
2476
|
+
const targetIds = this._stackedPanel.widgets.map(widget =>
|
|
2477
|
+
widgetIdSet.has(widget.id) ? widgetIds[savedIndex++] : widget.id
|
|
2478
|
+
);
|
|
2479
|
+
|
|
2480
|
+
targetIds.forEach((id, targetIndex) => {
|
|
2481
|
+
const currentIndex = this._stackedPanel.widgets.findIndex(
|
|
2482
|
+
widget => widget.id === id
|
|
2483
|
+
);
|
|
2484
|
+
if (currentIndex >= 0 && currentIndex !== targetIndex) {
|
|
2485
|
+
const widget = this._stackedPanel.widgets[currentIndex];
|
|
2486
|
+
ArrayExt.move(this._items, currentIndex, targetIndex);
|
|
2487
|
+
this._stackedPanel.insertWidget(targetIndex, widget);
|
|
2488
|
+
this._sideBar.insertTab(targetIndex, widget.title);
|
|
2489
|
+
}
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
if (data.visible) {
|
|
2494
|
+
this.show();
|
|
2495
|
+
} else {
|
|
2496
|
+
this.hide();
|
|
2497
|
+
}
|
|
2165
2498
|
if (data.currentWidget) {
|
|
2166
2499
|
this.activate(data.currentWidget.id);
|
|
2167
|
-
}
|
|
2168
|
-
if (data.collapsed) {
|
|
2500
|
+
} else if (data.collapsed) {
|
|
2169
2501
|
this.collapse();
|
|
2170
2502
|
}
|
|
2171
|
-
if (!data.visible) {
|
|
2172
|
-
this.hide();
|
|
2173
|
-
}
|
|
2174
2503
|
if (data.widgetStates) {
|
|
2175
2504
|
this._stackedPanel.widgets.forEach((w: SidePanel) => {
|
|
2176
2505
|
if (w.id && w.content instanceof SplitPanel) {
|
|
@@ -2181,7 +2510,11 @@ namespace Private {
|
|
|
2181
2510
|
typeof expansion === 'boolean' &&
|
|
2182
2511
|
w.content instanceof AccordionPanel
|
|
2183
2512
|
) {
|
|
2184
|
-
|
|
2513
|
+
if (expansion) {
|
|
2514
|
+
w.content.expand(widx);
|
|
2515
|
+
} else {
|
|
2516
|
+
w.content.collapse(widx);
|
|
2517
|
+
}
|
|
2185
2518
|
}
|
|
2186
2519
|
});
|
|
2187
2520
|
if (state.sizes) {
|
|
@@ -2205,6 +2538,7 @@ namespace Private {
|
|
|
2205
2538
|
*/
|
|
2206
2539
|
show(): void {
|
|
2207
2540
|
this._isHiddenByUser = false;
|
|
2541
|
+
this._isCollapsedByUser = false;
|
|
2208
2542
|
this._refreshVisibility();
|
|
2209
2543
|
}
|
|
2210
2544
|
|
|
@@ -2242,10 +2576,36 @@ namespace Private {
|
|
|
2242
2576
|
* Refresh the visibility of the side bar and stacked panel.
|
|
2243
2577
|
*/
|
|
2244
2578
|
private _refreshVisibility(): void {
|
|
2245
|
-
|
|
2579
|
+
if (this._position === 'side') {
|
|
2580
|
+
// In 'side' mode the activity bar lives outside the wrapper and the
|
|
2581
|
+
// wrapper holds only the stack panel. Hiding the stack panel when no
|
|
2582
|
+
// widget is current collapses the area down to the activity bar.
|
|
2583
|
+
this._stackedPanel.setHidden(this._sideBar.currentTitle === null);
|
|
2584
|
+
} else {
|
|
2585
|
+
// In 'top'/'bottom' mode the activity bar lives inside the wrapper
|
|
2586
|
+
// alongside the stack panel. The stack panel stays visible (and
|
|
2587
|
+
// stretches as an empty area when no widget is current) so the
|
|
2588
|
+
// activity bar stays anchored at the top or bottom of the wrapper.
|
|
2589
|
+
this._stackedPanel.setHidden(this._items.length === 0);
|
|
2590
|
+
}
|
|
2246
2591
|
this._sideBar.setHidden(
|
|
2247
2592
|
this._isHiddenByUser || this._sideBar.titles.length === 0
|
|
2248
2593
|
);
|
|
2594
|
+
// Hide the wrapper area so the parent split panel can reclaim its
|
|
2595
|
+
// allocated width when no contents would be visible.
|
|
2596
|
+
if (this._position === 'side') {
|
|
2597
|
+
// In 'side' mode wrapper visibility tracks the stack panel.
|
|
2598
|
+
this._area.setHidden(this._stackedPanel.isHidden);
|
|
2599
|
+
} else {
|
|
2600
|
+
// In 'top'/'bottom' mode the wrapper hides only when the user has
|
|
2601
|
+
// explicitly collapsed the side area or there are no widgets at all.
|
|
2602
|
+
// Toggling the activity bar visibility ('Show Left/Right Activity Bar')
|
|
2603
|
+
// hides only the bar — the stack panel and selected widget stay
|
|
2604
|
+
// visible inside the wrapper, matching the 'side' mode semantic.
|
|
2605
|
+
this._area.setHidden(
|
|
2606
|
+
this._isCollapsedByUser || this._sideBar.titles.length === 0
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2249
2609
|
this._updated.emit();
|
|
2250
2610
|
}
|
|
2251
2611
|
|
|
@@ -2282,6 +2642,16 @@ namespace Private {
|
|
|
2282
2642
|
args.title.owner.activate();
|
|
2283
2643
|
}
|
|
2284
2644
|
|
|
2645
|
+
/**
|
|
2646
|
+
* Handle a `tabCloseRequested` signal from the sidebar.
|
|
2647
|
+
*/
|
|
2648
|
+
private _onTabCloseRequested(
|
|
2649
|
+
sender: TabBar<Widget>,
|
|
2650
|
+
args: TabBar.ITabCloseRequestedArgs<Widget>
|
|
2651
|
+
): void {
|
|
2652
|
+
args.title.owner.close();
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2285
2655
|
/*
|
|
2286
2656
|
* Handle the `widgetRemoved` signal from the stacked panel.
|
|
2287
2657
|
*/
|
|
@@ -2295,13 +2665,41 @@ namespace Private {
|
|
|
2295
2665
|
}
|
|
2296
2666
|
|
|
2297
2667
|
private _isHiddenByUser = false;
|
|
2668
|
+
private _isCollapsedByUser = false;
|
|
2298
2669
|
private _items = new Array<Private.IRankItem>();
|
|
2299
2670
|
private _sideBar: TabBar<Widget>;
|
|
2300
2671
|
private _stackedPanel: StackedPanel;
|
|
2672
|
+
private _area: BoxPanel;
|
|
2673
|
+
private _host: BoxPanel;
|
|
2301
2674
|
private _lastCurrent: Widget | null;
|
|
2675
|
+
private _side: 'left' | 'right';
|
|
2676
|
+
private _position: ILabShell.ActivityBarPosition = 'side';
|
|
2302
2677
|
private _updated: Signal<SideBarHandler, void> = new Signal(this);
|
|
2303
2678
|
}
|
|
2304
2679
|
|
|
2680
|
+
/**
|
|
2681
|
+
* The namespace for the `SideBarHandler` statics.
|
|
2682
|
+
*/
|
|
2683
|
+
export namespace SideBarHandler {
|
|
2684
|
+
/**
|
|
2685
|
+
* The options used to create a `SideBarHandler`.
|
|
2686
|
+
*/
|
|
2687
|
+
export interface IOptions {
|
|
2688
|
+
/**
|
|
2689
|
+
* The natural side of the area (`'left'` or `'right'`). The activity
|
|
2690
|
+
* bar lives on this side when the position is `'side'`.
|
|
2691
|
+
*/
|
|
2692
|
+
side: 'left' | 'right';
|
|
2693
|
+
|
|
2694
|
+
/**
|
|
2695
|
+
* The host panel that owns the activity bar in `'side'` mode. The
|
|
2696
|
+
* handler reattaches the bar to this host on transitions back to
|
|
2697
|
+
* `'side'` so the shell does not need to manage that reparenting.
|
|
2698
|
+
*/
|
|
2699
|
+
host: BoxPanel;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2305
2703
|
export class SkipLinkWidget extends Widget {
|
|
2306
2704
|
/**
|
|
2307
2705
|
* Construct a new skipLink widget.
|
|
@@ -2411,7 +2809,7 @@ namespace Private {
|
|
|
2411
2809
|
if (widget == null) {
|
|
2412
2810
|
return;
|
|
2413
2811
|
}
|
|
2414
|
-
const oldName = widget.title
|
|
2812
|
+
const oldName = TabBarSvg.titleLabel(widget.title);
|
|
2415
2813
|
const inputElement = this.inputElement;
|
|
2416
2814
|
const newName = inputElement.value;
|
|
2417
2815
|
inputElement.blur();
|