@jupyterlab/apputils-extension 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.
@@ -0,0 +1,621 @@
1
+ /*
2
+ * Copyright (c) Jupyter Development Team.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+
6
+ import type {
7
+ JupyterFrontEnd,
8
+ JupyterFrontEndPlugin
9
+ } from '@jupyterlab/application';
10
+ import {
11
+ type IMovableSectionDestination,
12
+ IMovableSectionRegistry,
13
+ type IMovableSectionSource
14
+ } from '@jupyterlab/apputils';
15
+ import { IStateDB } from '@jupyterlab/statedb';
16
+ import { ITranslator } from '@jupyterlab/translation';
17
+ import type {
18
+ ReadonlyPartialJSONObject,
19
+ ReadonlyPartialJSONValue
20
+ } from '@lumino/coreutils';
21
+ import type { AccordionLayout, AccordionPanel, Widget } from '@lumino/widgets';
22
+
23
+ /**
24
+ * State DB key for persisting moved sections.
25
+ */
26
+ const MOVE_STATE_KEY = 'section-mover:layout';
27
+
28
+ /**
29
+ * Shape of the persisted state.
30
+ * Keyed by target panel ID; each entry records ordered sections, their
31
+ * absolute index in the destination accordion, and whether each was
32
+ * collapsed when the state was saved.
33
+ */
34
+ interface IMoveSectionsState {
35
+ [targetId: string]: Array<{
36
+ sourceId: string;
37
+ sectionId: string;
38
+ index?: number;
39
+ collapsed?: true;
40
+ }>;
41
+ }
42
+
43
+ namespace CommandIDs {
44
+ export const moveSectionTo = 'apputils:move-section-to';
45
+ export const moveSectionBack = 'apputils:move-section-back';
46
+ }
47
+
48
+ /**
49
+ * Plugin that moves sections between any registered source sidebar
50
+ * and target panel. Sources and targets announce themselves via
51
+ * IMovableSectionRegistry.
52
+ */
53
+ export const moveSectionsPlugin: JupyterFrontEndPlugin<void> = {
54
+ id: '@jupyterlab/apputils-extension:move-sections',
55
+ description:
56
+ 'Enables moving sections between any registered source and target sidebars.',
57
+ requires: [ITranslator],
58
+ optional: [IMovableSectionRegistry, IStateDB],
59
+ autoStart: true,
60
+ activate: (
61
+ app: JupyterFrontEnd,
62
+ translator: ITranslator,
63
+ registry: IMovableSectionRegistry | null,
64
+ stateDB: IStateDB | null
65
+ ): void => {
66
+ if (!registry) {
67
+ return;
68
+ }
69
+
70
+ const trans = translator.load('jupyterlab');
71
+
72
+ // section title element (in source sidebar) → section identity
73
+ const titleToSection = new WeakMap<
74
+ HTMLElement,
75
+ { sourceId: string; sectionId: string }
76
+ >();
77
+
78
+ const widgetToInfo = new Map<
79
+ Widget,
80
+ {
81
+ sourceId: string;
82
+ sectionId: string;
83
+ sourceLabel: string;
84
+ targetId: string;
85
+ }
86
+ >();
87
+
88
+ // For context-menu lookup
89
+ const hostedTitleToWidget = new WeakMap<HTMLElement, Widget>();
90
+
91
+ // Keyed by accordion instance so a recreated accordion (after the last
92
+ // hosted section leaves and the FileBrowser disposes it) gets fresh
93
+ // listeners on the next move-in.
94
+ const dragSetupDone = new WeakSet<AccordionPanel>();
95
+ const pendingSections = new Map<
96
+ string,
97
+ Map<string, { targetId: string; collapsed: boolean; index?: number }>
98
+ >();
99
+
100
+ // Capture last right-clicked element before the context menu opens
101
+ let lastContextEl: HTMLElement | null = null;
102
+ document.addEventListener(
103
+ 'contextmenu',
104
+ (ev: MouseEvent) => {
105
+ lastContextEl = ev.target as HTMLElement;
106
+ },
107
+ true
108
+ );
109
+
110
+ const saveState = async (): Promise<void> => {
111
+ if (!stateDB) {
112
+ return;
113
+ }
114
+ const state: IMoveSectionsState = {};
115
+ for (const [targetId, { panel }] of registry.getTargets()) {
116
+ const accordion = panel.accordionPanel;
117
+ const allWidgets = accordion ? Array.from(accordion.widgets) : [];
118
+ const sections = panel.sections
119
+ .map(w => {
120
+ const info = widgetToInfo.get(w);
121
+ if (!info || info.targetId !== targetId) {
122
+ return null;
123
+ }
124
+ // Detect collapsed state: widget content is hidden when collapsed
125
+ const isCollapsed = w.isHidden;
126
+ const index = allWidgets.indexOf(w);
127
+ return {
128
+ sourceId: info.sourceId,
129
+ sectionId: info.sectionId,
130
+ ...(index >= 0 ? { index } : {}),
131
+ ...(isCollapsed ? { collapsed: true as const } : {})
132
+ };
133
+ })
134
+ .filter((e): e is NonNullable<typeof e> => e !== null);
135
+ // Apply in saved accordion order so insertWidget on restore lands in
136
+ // the right spot relative to non-moved widgets.
137
+ sections.sort(
138
+ (a, b) =>
139
+ (a.index ?? Number.MAX_SAFE_INTEGER) -
140
+ (b.index ?? Number.MAX_SAFE_INTEGER)
141
+ );
142
+ if (sections.length > 0) {
143
+ state[targetId] = sections;
144
+ }
145
+ }
146
+ await stateDB.save(
147
+ MOVE_STATE_KEY,
148
+ state as unknown as ReadonlyPartialJSONValue
149
+ );
150
+ };
151
+
152
+ // Re-appending all titles in logical to fix keyboard Tab navigation.
153
+ const syncTitleDOMOrder = (accordion: AccordionPanel): void => {
154
+ const layout = accordion.layout as AccordionLayout;
155
+ for (const title of layout.titles) {
156
+ accordion.node.appendChild(title);
157
+ }
158
+ };
159
+
160
+ const addDragHandle = (widget: Widget, accordion: AccordionPanel): void => {
161
+ const idx = Array.from(accordion.widgets).indexOf(widget);
162
+ if (idx < 0) {
163
+ return;
164
+ }
165
+ const titleEl = accordion.titles[idx];
166
+ if (!titleEl.querySelector('.jp-movable-section-dragHandle')) {
167
+ const handle = document.createElement('span');
168
+ handle.className = 'jp-movable-section-dragHandle';
169
+ titleEl.prepend(handle);
170
+ }
171
+ };
172
+
173
+ const setupAccordionDrag = (accordion: AccordionPanel): void => {
174
+ if (dragSetupDone.has(accordion)) {
175
+ return;
176
+ }
177
+ dragSetupDone.add(accordion);
178
+
179
+ // Persist collapse/expand state whenever the user toggles a section.
180
+ accordion.expansionToggled.connect(() => void saveState());
181
+
182
+ let draggedWidget: Widget | null = null;
183
+ let startY = 0;
184
+ let isDragging = false;
185
+
186
+ const indicator = document.createElement('div');
187
+ indicator.className = 'jp-movable-section-dropIndicator';
188
+ indicator.style.display = 'none';
189
+ accordion.node.appendChild(indicator);
190
+
191
+ const getTargetSlot = (clientY: number): number => {
192
+ const layout = accordion.layout as AccordionLayout;
193
+ const titles = Array.from(layout.titles);
194
+ for (let i = 0; i < titles.length; i++) {
195
+ const rect = titles[i].getBoundingClientRect();
196
+ if (clientY < rect.top + rect.height / 2) {
197
+ return i;
198
+ }
199
+ }
200
+ return titles.length;
201
+ };
202
+
203
+ const showIndicator = (targetSlot: number): void => {
204
+ const layout = accordion.layout as AccordionLayout;
205
+ const titles = Array.from(layout.titles);
206
+ const panelRect = accordion.node.getBoundingClientRect();
207
+ let top: number;
208
+ if (targetSlot < titles.length) {
209
+ top = titles[targetSlot].getBoundingClientRect().top - panelRect.top;
210
+ } else {
211
+ const last = titles[titles.length - 1].getBoundingClientRect();
212
+ top = last.bottom - panelRect.top;
213
+ }
214
+ indicator.style.top = `${top}px`;
215
+ indicator.style.display = 'block';
216
+ };
217
+
218
+ const endDrag = (clientY?: number): void => {
219
+ indicator.style.display = 'none';
220
+ accordion.node.classList.remove('jp-mod-dragging');
221
+ if (isDragging && draggedWidget && clientY !== undefined) {
222
+ const currentIdx = Array.from(accordion.widgets).indexOf(
223
+ draggedWidget
224
+ );
225
+ const targetSlot = getTargetSlot(clientY);
226
+ const insertIdx =
227
+ targetSlot > currentIdx ? targetSlot - 1 : targetSlot;
228
+ if (insertIdx !== currentIdx) {
229
+ accordion.insertWidget(insertIdx, draggedWidget);
230
+ syncTitleDOMOrder(accordion);
231
+ void saveState();
232
+ }
233
+ }
234
+ draggedWidget = null;
235
+ isDragging = false;
236
+ };
237
+
238
+ accordion.node.addEventListener('pointerdown', (event: PointerEvent) => {
239
+ const target = event.target as HTMLElement;
240
+ const titleEl = target.closest(
241
+ '.jp-AccordionPanel-title'
242
+ ) as HTMLElement | null;
243
+ if (!titleEl || !target.closest('.jp-movable-section-dragHandle')) {
244
+ return;
245
+ }
246
+ const layout = accordion.layout as AccordionLayout;
247
+ const idx = Array.from(layout.titles).indexOf(titleEl);
248
+ if (idx < 0) {
249
+ return;
250
+ }
251
+ draggedWidget = accordion.widgets[idx];
252
+ startY = event.clientY;
253
+ accordion.node.setPointerCapture(event.pointerId);
254
+ });
255
+
256
+ accordion.node.addEventListener('pointermove', (event: PointerEvent) => {
257
+ if (!draggedWidget) {
258
+ return;
259
+ }
260
+ if (!isDragging && Math.abs(event.clientY - startY) > 5) {
261
+ isDragging = true;
262
+ accordion.node.classList.add('jp-mod-dragging');
263
+ }
264
+ if (isDragging) {
265
+ showIndicator(getTargetSlot(event.clientY));
266
+ }
267
+ });
268
+
269
+ accordion.node.addEventListener('pointerup', (event: PointerEvent) => {
270
+ endDrag(event.clientY);
271
+ });
272
+
273
+ accordion.node.addEventListener('pointercancel', () => {
274
+ endDrag();
275
+ });
276
+ };
277
+
278
+ // Core move operations
279
+
280
+ const applyMoveToTarget = (
281
+ widget: Widget,
282
+ sourceId: string,
283
+ sectionId: string,
284
+ sourceLabel: string,
285
+ targetId: string,
286
+ targetPanel: IMovableSectionDestination,
287
+ collapsed = false,
288
+ index?: number
289
+ ): void => {
290
+ // Capture hidden state before reparenting
291
+ const wasHidden = widget.isHidden;
292
+ targetPanel.addSection(widget);
293
+
294
+ widgetToInfo.set(widget, { sourceId, sectionId, sourceLabel, targetId });
295
+
296
+ const accordion = targetPanel.accordionPanel;
297
+ if (accordion) {
298
+ if (typeof index === 'number') {
299
+ const total = accordion.widgets.length;
300
+ const target = Math.max(0, Math.min(index, total - 1));
301
+ if (accordion.widgets[target] !== widget) {
302
+ accordion.insertWidget(target, widget);
303
+ syncTitleDOMOrder(accordion);
304
+ }
305
+ }
306
+
307
+ setupAccordionDrag(accordion);
308
+ addDragHandle(widget, accordion);
309
+
310
+ const idx = Array.from(accordion.widgets).indexOf(widget);
311
+ if (idx >= 0) {
312
+ const hostedTitle = accordion.titles[idx];
313
+ hostedTitle.classList.add('jp-hosted-section');
314
+ hostedTitleToWidget.set(hostedTitle, widget);
315
+
316
+ if (wasHidden || collapsed) {
317
+ // Ensure the content widget is actually hidden (needed when
318
+ // restoring state where the widget was expanded in the source).
319
+ widget.hide();
320
+ hostedTitle.classList.remove('lm-mod-expanded');
321
+ hostedTitle.setAttribute('aria-expanded', 'false');
322
+ }
323
+ }
324
+ }
325
+ };
326
+
327
+ /**
328
+ * Move a section from its source sidebar to a target panel.
329
+ * Returns true on success, false if the section is not yet available.
330
+ */
331
+ const moveSection = (
332
+ sourceId: string,
333
+ sectionId: string,
334
+ targetId: string,
335
+ collapsed = false,
336
+ index?: number
337
+ ): boolean => {
338
+ const sourceEntry = registry.getSources().get(sourceId);
339
+ const targetEntry = registry.getTargets().get(targetId);
340
+ if (!sourceEntry || !targetEntry) {
341
+ return false;
342
+ }
343
+ const widget = sourceEntry.sidebar.removeSectionById(sectionId);
344
+ if (!widget) {
345
+ return false;
346
+ }
347
+ applyMoveToTarget(
348
+ widget,
349
+ sourceId,
350
+ sectionId,
351
+ sourceEntry.label,
352
+ targetId,
353
+ targetEntry.panel,
354
+ collapsed,
355
+ index
356
+ );
357
+ void saveState();
358
+ return true;
359
+ };
360
+
361
+ const moveSectionBack = (widget: Widget): void => {
362
+ const info = widgetToInfo.get(widget);
363
+ if (!info) {
364
+ return;
365
+ }
366
+ const sourceEntry = registry.getSources().get(info.sourceId);
367
+ const targetEntry = registry.getTargets().get(info.targetId);
368
+ if (!sourceEntry || !targetEntry) {
369
+ return;
370
+ }
371
+ const isHidden = widget.isHidden;
372
+ targetEntry.panel.removeSectionWidget(widget);
373
+ sourceEntry.sidebar.reinsertSection(widget);
374
+ widgetToInfo.delete(widget);
375
+
376
+ // After reinsertion the accordion assigns a fresh title element.
377
+ // Re-register it so the context menu works on this section again.
378
+ const restored = sourceEntry.sidebar
379
+ .getSections()
380
+ .find(s => s.id === info.sectionId);
381
+ if (restored) {
382
+ restored.titleNode.classList.add('jp-movable-section');
383
+ titleToSection.set(restored.titleNode, {
384
+ sourceId: info.sourceId,
385
+ sectionId: info.sectionId
386
+ });
387
+ if (isHidden) {
388
+ restored.widget.hide();
389
+ restored.titleNode.classList.remove('lm-mod-expanded');
390
+ restored.titleNode.setAttribute('aria-expanded', 'false');
391
+ }
392
+ }
393
+
394
+ void saveState();
395
+ };
396
+
397
+ // Source / target wiring
398
+
399
+ const setupSource = (
400
+ sourceId: string,
401
+ source: IMovableSectionSource
402
+ ): void => {
403
+ for (const section of source.getSections()) {
404
+ section.titleNode.classList.add('jp-movable-section');
405
+ titleToSection.set(section.titleNode, {
406
+ sourceId,
407
+ sectionId: section.id
408
+ });
409
+ }
410
+
411
+ source.sectionAdded.connect((_sender, section) => {
412
+ section.titleNode.classList.add('jp-movable-section');
413
+ titleToSection.set(section.titleNode, {
414
+ sourceId,
415
+ sectionId: section.id
416
+ });
417
+
418
+ // Fulfill any pending state restoration for this section
419
+ const pending = pendingSections.get(sourceId);
420
+ if (pending?.has(section.id)) {
421
+ const { targetId, collapsed, index } = pending.get(section.id)!;
422
+ pending.delete(section.id);
423
+ if (pending.size === 0) {
424
+ pendingSections.delete(sourceId);
425
+ }
426
+ moveSection(sourceId, section.id, targetId, collapsed, index);
427
+ }
428
+ });
429
+ };
430
+
431
+ const setupTarget = (
432
+ targetId: string,
433
+ targetLabel: string,
434
+ panel: IMovableSectionDestination
435
+ ): void => {
436
+ app.contextMenu.addItem({
437
+ command: CommandIDs.moveSectionTo,
438
+ args: { targetId, targetLabel },
439
+ selector: '.jp-movable-section',
440
+ rank: 10
441
+ });
442
+
443
+ if (panel.accordionPanel && panel.sections.length > 0) {
444
+ setupAccordionDrag(panel.accordionPanel);
445
+ }
446
+ };
447
+
448
+ app.commands.addCommand(CommandIDs.moveSectionTo, {
449
+ label: (args: ReadonlyPartialJSONObject) =>
450
+ trans.__('Move to %1', (args.targetLabel as string) ?? ''),
451
+ describedBy: {
452
+ args: {
453
+ type: 'object',
454
+ properties: {
455
+ targetId: { type: 'string' },
456
+ targetLabel: { type: 'string' }
457
+ }
458
+ }
459
+ },
460
+ isVisible: (args: ReadonlyPartialJSONObject) => {
461
+ if (!lastContextEl) {
462
+ return false;
463
+ }
464
+ const titleEl = lastContextEl.closest(
465
+ '.jp-movable-section'
466
+ ) as HTMLElement | null;
467
+ if (!titleEl) {
468
+ return false;
469
+ }
470
+ // A title currently hosting a moved-in section is not a valid source —
471
+ // it should only offer "Move back".
472
+ if (titleEl.classList.contains('jp-hosted-section')) {
473
+ return false;
474
+ }
475
+ const sectionInfo = titleToSection.get(titleEl);
476
+ if (!sectionInfo) {
477
+ return false;
478
+ }
479
+ const targetId = args.targetId as string;
480
+ if (!targetId) {
481
+ return false;
482
+ }
483
+ // Don't offer to move a section to the same panel it originates from
484
+ const sourceEntry = registry.getSources().get(sectionInfo.sourceId);
485
+ const targetEntry = registry.getTargets().get(targetId);
486
+ if (!sourceEntry || !targetEntry) {
487
+ return false;
488
+ }
489
+ return (
490
+ (sourceEntry.sidebar as unknown) !== (targetEntry.panel as unknown)
491
+ );
492
+ },
493
+ execute: (args: ReadonlyPartialJSONObject) => {
494
+ const targetId = args.targetId as string;
495
+ if (!targetId || !lastContextEl) {
496
+ return;
497
+ }
498
+ const titleEl = lastContextEl.closest(
499
+ '.jp-movable-section'
500
+ ) as HTMLElement | null;
501
+ if (!titleEl || titleEl.classList.contains('jp-hosted-section')) {
502
+ return;
503
+ }
504
+ const sectionInfo = titleToSection.get(titleEl);
505
+ if (!sectionInfo) {
506
+ return;
507
+ }
508
+ moveSection(sectionInfo.sourceId, sectionInfo.sectionId, targetId);
509
+ }
510
+ });
511
+
512
+ app.commands.addCommand(CommandIDs.moveSectionBack, {
513
+ label: () => {
514
+ if (!lastContextEl) {
515
+ return trans.__('Move back');
516
+ }
517
+ const titleEl = lastContextEl.closest(
518
+ '.jp-hosted-section'
519
+ ) as HTMLElement | null;
520
+ if (!titleEl) {
521
+ return trans.__('Move back');
522
+ }
523
+ const widget = hostedTitleToWidget.get(titleEl);
524
+ if (!widget) {
525
+ return trans.__('Move back');
526
+ }
527
+ const info = widgetToInfo.get(widget);
528
+ return info
529
+ ? trans.__('Move back to %1', info.sourceLabel)
530
+ : trans.__('Move back');
531
+ },
532
+ describedBy: {
533
+ args: {
534
+ type: 'object',
535
+ properties: {}
536
+ }
537
+ },
538
+ isVisible: () => {
539
+ if (!lastContextEl) {
540
+ return false;
541
+ }
542
+ const titleEl = lastContextEl.closest(
543
+ '.jp-hosted-section'
544
+ ) as HTMLElement | null;
545
+ return titleEl ? hostedTitleToWidget.has(titleEl) : false;
546
+ },
547
+ execute: () => {
548
+ if (!lastContextEl) {
549
+ return;
550
+ }
551
+ const titleEl = lastContextEl.closest(
552
+ '.jp-hosted-section'
553
+ ) as HTMLElement | null;
554
+ if (!titleEl) {
555
+ return;
556
+ }
557
+ const widget = hostedTitleToWidget.get(titleEl);
558
+ if (widget) {
559
+ moveSectionBack(widget);
560
+ }
561
+ }
562
+ });
563
+
564
+ app.contextMenu.addItem({
565
+ command: CommandIDs.moveSectionBack,
566
+ selector: '.jp-hosted-section',
567
+ rank: 10
568
+ });
569
+
570
+ // Bootstrap from registry
571
+
572
+ for (const [id, { sidebar }] of registry.getSources()) {
573
+ setupSource(id, sidebar);
574
+ }
575
+ for (const [id, { label, panel }] of registry.getTargets()) {
576
+ setupTarget(id, label, panel);
577
+ }
578
+
579
+ registry.sourcePanelRegistered.connect((_sender, { id, sidebar }) => {
580
+ setupSource(id, sidebar);
581
+ });
582
+ registry.targetPanelRegistered.connect((_sender, { id, label, panel }) => {
583
+ setupTarget(id, label, panel);
584
+ });
585
+
586
+ // State restoration
587
+
588
+ if (stateDB) {
589
+ void stateDB.fetch(MOVE_STATE_KEY).then(value => {
590
+ if (!value) {
591
+ return;
592
+ }
593
+ const state = value as IMoveSectionsState;
594
+
595
+ for (const [targetId, sections] of Object.entries(state)) {
596
+ for (const { sourceId, sectionId, collapsed, index } of sections) {
597
+ if (
598
+ !moveSection(
599
+ sourceId,
600
+ sectionId,
601
+ targetId,
602
+ collapsed ?? false,
603
+ index
604
+ )
605
+ ) {
606
+ // Section not yet available — wait for sectionAdded signal
607
+ if (!pendingSections.has(sourceId)) {
608
+ pendingSections.set(sourceId, new Map());
609
+ }
610
+ pendingSections.get(sourceId)!.set(sectionId, {
611
+ targetId,
612
+ collapsed: collapsed ?? false,
613
+ index
614
+ });
615
+ }
616
+ }
617
+ }
618
+ });
619
+ }
620
+ }
621
+ };
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ /* eslint-disable @typescript-eslint/no-explicit-any */
5
6
 
6
7
  import type {
7
8
  JupyterFrontEnd,
package/src/palette.ts CHANGED
@@ -38,6 +38,10 @@ export class Palette implements ICommandPalette {
38
38
  const trans = this.translator.load('jupyterlab');
39
39
  this._palette = palette;
40
40
  this._palette.title.label = '';
41
+ this._palette.title.dataset = {
42
+ ...this._palette.title.dataset,
43
+ jpTabLabel: trans.__('Commands')
44
+ };
41
45
  this._palette.title.caption = trans.__('Command Palette');
42
46
  }
43
47
 
@@ -90,7 +94,7 @@ export namespace Palette {
90
94
  ): ICommandPalette {
91
95
  const { commands, shell } = app;
92
96
  const trans = translator.load('jupyterlab');
93
- const palette = Private.createPalette(app, translator);
97
+ const palette = Private.createPalette(app);
94
98
  const modalPalette = new ModalCommandPalette({
95
99
  commandPalette: palette,
96
100
  restore: () => {
@@ -183,10 +187,9 @@ export namespace Palette {
183
187
  */
184
188
  export function restore(
185
189
  app: JupyterFrontEnd,
186
- restorer: ILayoutRestorer,
187
- translator: ITranslator
190
+ restorer: ILayoutRestorer
188
191
  ): void {
189
- const palette = Private.createPalette(app, translator);
192
+ const palette = Private.createPalette(app);
190
193
  // Let the application restorer track the command palette for restoration of
191
194
  // application state (e.g. setting the command palette as the current side bar
192
195
  // widget).
@@ -206,10 +209,7 @@ namespace Private {
206
209
  /**
207
210
  * Create the application-wide command palette.
208
211
  */
209
- export function createPalette(
210
- app: JupyterFrontEnd,
211
- translator: ITranslator
212
- ): CommandPalette {
212
+ export function createPalette(app: JupyterFrontEnd): CommandPalette {
213
213
  if (!palette) {
214
214
  // use a renderer tweaked to use inline svg icons
215
215
  palette = new CommandPalette({
@@ -218,8 +218,6 @@ namespace Private {
218
218
  });
219
219
  palette.id = 'command-palette';
220
220
  palette.title.icon = paletteIcon;
221
- const trans = translator.load('jupyterlab');
222
- palette.title.label = trans.__('Commands');
223
221
  }
224
222
 
225
223
  return palette;
package/src/shortcuts.tsx CHANGED
@@ -2,6 +2,7 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
+ /* eslint-disable @typescript-eslint/no-explicit-any */
5
6
 
6
7
  import { CommandRegistry } from '@lumino/commands';
7
8
  import { Selector } from '@lumino/domutils';
@@ -200,7 +200,9 @@
200
200
  }
201
201
 
202
202
  .jp-toast-button:focus {
203
- outline: 1px solid var(--jp-reject-color-normal, var(--jp-layout-color2));
203
+ outline-width: var(--jp-focus-outline-width);
204
+ outline-color: var(--jp-reject-color-normal, var(--jp-layout-color2));
205
+ outline-style: solid;
204
206
  outline-offset: 1px;
205
207
  -moz-outline-radius: 0;
206
208
  }