@jupyterlab/notebook 4.7.0-alpha.1 → 4.7.0-alpha.2

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/widget.js CHANGED
@@ -301,6 +301,12 @@ export class StaticNotebook extends WindowedList {
301
301
  this._contentVisibilityObserver = null;
302
302
  }
303
303
  super.dispose();
304
+ // Dispose cells that windowing modes may have detached from the layout.
305
+ for (const cell of this.cellsArray) {
306
+ cell.dispose();
307
+ }
308
+ this.cellsArray.length = 0;
309
+ this.viewModel.dispose();
304
310
  }
305
311
  onBeforeDetach(msg) {
306
312
  // Disconnect the content-visibility observer
@@ -510,7 +516,7 @@ export class StaticNotebook extends WindowedList {
510
516
  for (const cell of cells) {
511
517
  this._insertCell(++index, cell);
512
518
  }
513
- this._syncMarkdownCellTrust();
519
+ this._syncCellTrust();
514
520
  newValue.cells.changed.connect(this._onCellsChanged, this);
515
521
  newValue.metadataChanged.connect(this.onMetadataChanged, this);
516
522
  newValue.contentChanged.connect(this.onModelContentChanged, this);
@@ -562,7 +568,7 @@ export class StaticNotebook extends WindowedList {
562
568
  if (!this.model.sharedModel.cells.length) {
563
569
  this.addHeader();
564
570
  }
565
- this._syncMarkdownCellTrust();
571
+ this._syncCellTrust();
566
572
  this.update();
567
573
  }
568
574
  /**
@@ -588,7 +594,7 @@ export class StaticNotebook extends WindowedList {
588
594
  widget.inViewportChanged.connect(this._onCellInViewportChanged, this);
589
595
  widget.addClass(NB_CELL_CLASS);
590
596
  ArrayExt.insert(this.cellsArray, index, widget);
591
- this._syncMarkdownCellTrust(widget);
597
+ this._syncCellTrust(widget);
592
598
  this.onCellInserted(index, widget);
593
599
  this._scheduleCellRenderOnIdle();
594
600
  }
@@ -692,8 +698,8 @@ export class StaticNotebook extends WindowedList {
692
698
  this.onCellRemoved(index, widget);
693
699
  widget.dispose();
694
700
  }
695
- _shouldTrustMarkdown() {
696
- // Note: this returns false in a notebook without trsuted code cells;
701
+ _shouldTrustCell() {
702
+ // Note: this returns false in a notebook without trusted code cells;
697
703
  // This is intended since only Code cells carry trust status on disk.
698
704
  if (!this._notebookModel) {
699
705
  return false;
@@ -709,12 +715,18 @@ export class StaticNotebook extends WindowedList {
709
715
  }
710
716
  return hasCodeCell;
711
717
  }
712
- _syncMarkdownCellTrust(cell) {
713
- const trusted = this._shouldTrustMarkdown();
718
+ _syncCellTrust(cell) {
719
+ const trusted = this._shouldTrustCell();
714
720
  const trustHandler = this.rendermime.trustHandler;
715
721
  if (!trustHandler) {
716
722
  return;
717
723
  }
724
+ if (trusted) {
725
+ trustHandler.markTrusted(this.node);
726
+ }
727
+ else {
728
+ trustHandler.unmarkTrusted(this.node);
729
+ }
718
730
  const cells = cell ? [cell] : this.widgets;
719
731
  for (const widget of cells) {
720
732
  if (!(widget instanceof MarkdownCell)) {
@@ -730,7 +742,7 @@ export class StaticNotebook extends WindowedList {
730
742
  }
731
743
  _onCellStateChanged(model, args) {
732
744
  if (args.name === 'trusted') {
733
- this._syncMarkdownCellTrust();
745
+ this._syncCellTrust();
734
746
  }
735
747
  }
736
748
  /**
@@ -827,7 +839,7 @@ export class StaticNotebook extends WindowedList {
827
839
  }
828
840
  _scheduleCellRenderOnIdle() {
829
841
  if (this.notebookConfig.windowingMode !== 'none' && !this.isDisposed) {
830
- if (!this._idleCallBack) {
842
+ if (this._idleCallBack === null) {
831
843
  this._idleCallBack = requestIdleCallback((deadline) => {
832
844
  this._idleCallBack = null;
833
845
  // In case of timeout, render for some time even if it means freezing the UI
@@ -908,7 +920,7 @@ export class StaticNotebook extends WindowedList {
908
920
  }
909
921
  }
910
922
  else {
911
- if (this._idleCallBack) {
923
+ if (this._idleCallBack !== null) {
912
924
  window.cancelIdleCallback(this._idleCallBack);
913
925
  this._idleCallBack = null;
914
926
  }
@@ -1012,7 +1024,7 @@ export class StaticNotebook extends WindowedList {
1012
1024
  this._contentVisibilityObserver.observe(cell.node);
1013
1025
  });
1014
1026
  });
1015
- });
1027
+ }, this);
1016
1028
  }
1017
1029
  }
1018
1030
  /**
@@ -1190,7 +1202,7 @@ class ScrollbarItem {
1190
1202
  else if (model.executionState == 'running') {
1191
1203
  content = '[*]';
1192
1204
  }
1193
- else if (model.executionCount) {
1205
+ else if (model.executionCount !== null) {
1194
1206
  content = `[${model.executionCount}]`;
1195
1207
  }
1196
1208
  else {
@@ -2348,6 +2360,67 @@ export class Notebook extends StaticNotebook {
2348
2360
  });
2349
2361
  }
2350
2362
  }
2363
+ /**
2364
+ * Find the cell containing a node, traversing open shadow roots.
2365
+ *
2366
+ * Returned cell is `null` if the node is not in a cell of this notebook.
2367
+ */
2368
+ _cellContaining(node) {
2369
+ let current = node;
2370
+ while (current) {
2371
+ const element = current instanceof Element ? current : current.parentElement;
2372
+ if (element) {
2373
+ const index = this._findCell(element);
2374
+ if (index !== -1) {
2375
+ return this.widgets[index];
2376
+ }
2377
+ }
2378
+ const root = (element !== null && element !== void 0 ? element : current).getRootNode();
2379
+ current = root instanceof ShadowRoot ? root.host : null;
2380
+ }
2381
+ return null;
2382
+ }
2383
+ /**
2384
+ * Whether a shift-click on `target` should be left to the browser to handle
2385
+ * as an extension of the current text selection, rather than be turned into
2386
+ * a cell range selection.
2387
+ *
2388
+ * This is the case when the click lands in a region of a cell whose text the
2389
+ * browser selects - the output area of a code cell, or the rendered input of
2390
+ * a rendered markdown cell - and the current selection starts in such a
2391
+ * region too; see https://github.com/jupyterlab/jupyterlab/issues/4800. A
2392
+ * click on a cell prompt, on cell chrome, or on a cell without such a region
2393
+ * (an unrendered markdown cell, a code cell without outputs) always selects
2394
+ * cells.
2395
+ *
2396
+ * #### Notes
2397
+ * Only the anchor of the selection is considered, because a shift-click
2398
+ * moves the focus and leaves the anchor in place. The anchor may sit in a
2399
+ * different cell than the one clicked, which is what allows a selection to
2400
+ * be extended across the outputs of several cells; and disregarding the
2401
+ * focus keeps this working when a drag overshot the edge of an output and
2402
+ * left the focus outside of it.
2403
+ *
2404
+ * This trade-off between extending the text selection and extending the cell
2405
+ * selection can be adjusted in the future. For now the text selection wins to
2406
+ * preserve the behaviour from before refactor that users may be accustomed to.
2407
+ */
2408
+ _isExtendingTextSelection(selection, targetCell, target) {
2409
+ if (!selection || selection.isCollapsed) {
2410
+ return false;
2411
+ }
2412
+ if (!Private.isInTextRegion(targetCell, target)) {
2413
+ return false;
2414
+ }
2415
+ const { anchorNode } = selection;
2416
+ // The selection usually starts in the very cell which was clicked, which
2417
+ // can be checked without having to locate the cell of the anchor.
2418
+ if (Private.isInTextRegion(targetCell, anchorNode)) {
2419
+ return true;
2420
+ }
2421
+ const anchorCell = this._cellContaining(anchorNode);
2422
+ return !!anchorCell && Private.isInTextRegion(anchorCell, anchorNode);
2423
+ }
2351
2424
  /**
2352
2425
  * Find the cell index containing the target html element.
2353
2426
  *
@@ -2552,15 +2625,18 @@ export class Notebook extends StaticNotebook {
2552
2625
  this.deselectAll();
2553
2626
  }
2554
2627
  else if (targetArea === 'prompt' || targetArea === 'cell') {
2555
- // We don't want to prevent the default selection behavior
2556
- // if there is currently text selected in an output.
2557
- const hasSelection = ((_c = window.getSelection()) !== null && _c !== void 0 ? _c : '').toString() !== '';
2558
2628
  if (button === 0 &&
2559
2629
  shiftKey &&
2560
- !hasSelection &&
2561
- !['INPUT', 'OPTION'].includes(target.tagName)) {
2630
+ !['INPUT', 'OPTION'].includes(target.tagName) &&
2631
+ // We don't want to prevent the default selection behavior when the
2632
+ // user is extending a text selection into the clicked region.
2633
+ !this._isExtendingTextSelection(window.getSelection(), widget, target)) {
2562
2634
  // Prevent browser selecting text in prompt or output
2563
2635
  event.preventDefault();
2636
+ // Preventing the default also stops the browser from collapsing any
2637
+ // text selection which is already there; drop it explicitly so that
2638
+ // stale highlighted text is not left over the new cell selection.
2639
+ (_c = window.getSelection()) === null || _c === void 0 ? void 0 : _c.removeAllRanges();
2564
2640
  // Shift-click - extend selection
2565
2641
  try {
2566
2642
  this.extendContiguousSelectionTo(index);
@@ -2841,7 +2917,7 @@ export class Notebook extends StaticNotebook {
2841
2917
  const executionCount = activeCell.model
2842
2918
  .executionCount;
2843
2919
  countString = ' ';
2844
- if (executionCount) {
2920
+ if (executionCount !== null) {
2845
2921
  countString = executionCount.toString();
2846
2922
  }
2847
2923
  }
@@ -3101,6 +3177,60 @@ var Private;
3101
3177
  }
3102
3178
  }
3103
3179
  Private.NotebookPanelLayout = NotebookPanelLayout;
3180
+ /**
3181
+ * Check whether `node` is `parent` or a descendant of it.
3182
+ *
3183
+ * Unlike `Node.contains()` this traverses out of open shadow roots, so a
3184
+ * node rendered by a widget library which attaches a shadow root (such as
3185
+ * Panel or Bokeh) is still recognised as belonging to its host subtree.
3186
+ */
3187
+ function containsDeep(parent, node) {
3188
+ let current = node;
3189
+ while (current) {
3190
+ if (parent.contains(current)) {
3191
+ return true;
3192
+ }
3193
+ const root = current.getRootNode();
3194
+ current = root instanceof ShadowRoot ? root.host : null;
3195
+ }
3196
+ return false;
3197
+ }
3198
+ /**
3199
+ * Whether a cell exposes an output area.
3200
+ *
3201
+ * #### Notes
3202
+ * This is a structural check rather than `instanceof CodeCell` so that it
3203
+ * still holds when a federated extension loads its own copy of
3204
+ * `@jupyterlab/cells`.
3205
+ */
3206
+ function hasOutputArea(cell) {
3207
+ return 'outputArea' in cell;
3208
+ }
3209
+ /**
3210
+ * The regions of a cell whose text the browser, rather than the notebook,
3211
+ * is responsible for selecting: the output area of a code cell and the
3212
+ * rendered input of a cell which renders it, such as a rendered markdown
3213
+ * cell.
3214
+ */
3215
+ function textRegionsOf(cell) {
3216
+ var _a;
3217
+ const regions = [];
3218
+ if (hasOutputArea(cell)) {
3219
+ regions.push(cell.outputArea.node);
3220
+ }
3221
+ const rendered = (_a = cell.inputArea) === null || _a === void 0 ? void 0 : _a.renderedInput;
3222
+ if (rendered) {
3223
+ regions.push(rendered.node);
3224
+ }
3225
+ return regions;
3226
+ }
3227
+ /**
3228
+ * Whether a node is in a region of a cell whose text the browser selects.
3229
+ */
3230
+ function isInTextRegion(cell, node) {
3231
+ return textRegionsOf(cell).some(region => containsDeep(region, node));
3232
+ }
3233
+ Private.isInTextRegion = isInTextRegion;
3104
3234
  /**
3105
3235
  * Create a cell drag image.
3106
3236
  */