jupyterlab-tour 4.0.1 → 4.1.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/README.md CHANGED
@@ -7,7 +7,9 @@
7
7
  [![PyPI](https://img.shields.io/pypi/v/jupyterlab-tour)](https://pypi.org/project/jupyterlab-tour)
8
8
  [![conda-forge](https://img.shields.io/conda/vn/conda-forge/jupyterlab-tour)](https://anaconda.org/conda-forge/jupyterlab-tour)
9
9
 
10
- A JupyterLab UI Tour based on [react-joyride](https://docs.react-joyride.com).
10
+ A JupyterLab UI Tour based on [react-joyride](https://react-joyride.com/) v2. The site
11
+ currently only has documentation for v3. Docs for the v2 release used here are on the
12
+ [v2.9.3 tag](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md).
11
13
 
12
14
  ![demo](https://raw.githubusercontent.com/jupyterlab-contrib/jupyterlab-tour/main/doc/tourDemo.gif)
13
15
 
@@ -69,7 +71,7 @@ own _Tours_ as data.
69
71
  - Open the JupyterLab _Advanced Settings_ panel <kbd>Ctrl+,</kbd>
70
72
  - Select _Tours_ from list of settings groups
71
73
  - In the editor, create JSON(5) compatible with the
72
- [react-joyride data model](https://docs.react-joyride.com/props)
74
+ [react-joyride data model](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/props.md)
73
75
  - The _Tour_ will be available from the _Help Menu_, as well as the _Command Palette_
74
76
 
75
77
  ### A simple Tour
@@ -206,7 +208,7 @@ tour.stepChanged.connect((_, data) => {
206
208
  ```
207
209
 
208
210
  > `data` is an object of type
209
- > [`CallbackProps`](https://docs.react-joyride.com/callback).
211
+ > [`CallbackProps`](https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/callback.md).
210
212
 
211
213
  ## Disabling the User, Notebook, and Default Tours
212
214
 
@@ -1,5 +1,6 @@
1
1
  import { Notebook, NotebookModel, NotebookPanel } from '@jupyterlab/notebook';
2
2
  import { CommandRegistry } from '@lumino/commands';
3
+ import { PromiseDelegate } from '@lumino/coreutils';
3
4
  import { DocumentRegistry } from '@jupyterlab/docregistry';
4
5
  import { ServerConnection } from '@jupyterlab/services';
5
6
  import { CodeMirrorEditorFactory, CodeMirrorMimeTypeService, EditorLanguageRegistry } from '@jupyterlab/codemirror';
@@ -8,14 +9,30 @@ import 'jest';
8
9
  import { CommandIDs } from '../constants';
9
10
  import plugin from '../index';
10
11
  import { NS } from '../tokens';
12
+ import * as nbformat from '@jupyterlab/nbformat';
13
+ import { YNotebook } from '@jupyter/ydoc';
11
14
  const DEFAULT_TOURS_SIZE = 2;
12
15
  const [corePlugin, userPlugin, notebookPlugin, defaultsPlugin] = plugin;
16
+ async function mockFetch(input, init) {
17
+ if (!(typeof input == 'object' &&
18
+ input.url.match(/api\/config\/jupyterlabtourplugin/) != null)) {
19
+ throw new Error(`cannot handle ${input}`);
20
+ }
21
+ return {
22
+ status: 200,
23
+ json: async () => {
24
+ return {};
25
+ }
26
+ };
27
+ }
13
28
  function mockApp() {
14
29
  return {
15
30
  commands: new CommandRegistry(),
16
31
  restored: Promise.resolve(),
17
32
  docRegistry: new DocumentRegistry(),
18
- serviceManager: { serverSettings: ServerConnection.makeSettings() }
33
+ serviceManager: {
34
+ serverSettings: ServerConnection.makeSettings({ fetch: mockFetch })
35
+ }
19
36
  };
20
37
  }
21
38
  function aTour() {
@@ -128,15 +145,28 @@ describe(notebookPlugin.id, () => {
128
145
  mimeTypeService,
129
146
  contentFactory: new NotebookPanel.ContentFactory({ editorFactory })
130
147
  });
131
- const model = new NotebookModel();
148
+ const sharedModel = YNotebook.create({
149
+ data: {
150
+ nbformat: nbformat.MAJOR_VERSION,
151
+ nbformat_minor: nbformat.MINOR_VERSION,
152
+ metadata: {
153
+ kernelspec: { name: '', display_name: '' },
154
+ language_info: { name: '' }
155
+ }
156
+ }
157
+ });
158
+ const model = new NotebookModel({ sharedModel });
132
159
  notebook.model = model;
133
160
  notebookTourManager.addNotebook(notebook);
134
161
  expect(notebookTourManager.tourManager.tours.size).toBe(0);
135
- model.setMetadata(NS, {
136
- tours: [aTour()]
137
- });
162
+ let changed = new PromiseDelegate();
163
+ notebookTourManager.notebookToursChanged.connect(() => changed.resolve());
164
+ model.setMetadata(NS, { tours: [aTour()] });
165
+ await changed.promise;
166
+ changed = new PromiseDelegate();
138
167
  expect(notebookTourManager.tourManager.tours.size).toBe(1);
139
168
  model.deleteMetadata(NS);
169
+ await changed.promise;
140
170
  expect(notebookTourManager.tourManager.tours.size).toBe(0);
141
171
  });
142
172
  });
@@ -0,0 +1 @@
1
+ import 'jest';
@@ -0,0 +1,86 @@
1
+ import 'jest';
2
+ import { STATUS } from 'react-joyride';
3
+ import { TourManager } from '../tourManager';
4
+ import { TourHandler } from '../tour';
5
+ function event(status, index) {
6
+ return { status, index, step: { target: 'body', content: '' } };
7
+ }
8
+ function runningTour(controlled = false) {
9
+ const tour = new TourHandler('tour', 'Tour', undefined, null, 0, controlled);
10
+ tour.addStep({ target: 'body', content: 'one' });
11
+ tour.addStep({ target: 'body', content: 'two' });
12
+ tour.handleTourEvent(event(STATUS.RUNNING, 0));
13
+ return tour;
14
+ }
15
+ describe('TourHandler', () => {
16
+ it('leaves the tour uncontrolled by default', () => {
17
+ const tour = new TourHandler('tour', 'Tour');
18
+ expect(tour.controlled).toBe(false);
19
+ expect(tour.currentStepIndex).toBe(-1);
20
+ expect(tour.isRunning()).toBe(false);
21
+ });
22
+ it('updates the shared index when a controlled tour sets currentStepIndex', () => {
23
+ const tour = new TourHandler('tour', 'Tour', undefined, null, 0, true);
24
+ tour.currentStepIndex = 2;
25
+ expect(tour.currentStepIndex).toBe(2);
26
+ expect(tour.isRunning()).toBe(true);
27
+ });
28
+ it('follows Joyride callbacks on an uncontrolled tour', () => {
29
+ const tour = runningTour(false);
30
+ expect(tour.currentStepIndex).toBe(0);
31
+ tour.handleTourEvent(event(STATUS.RUNNING, 1));
32
+ expect(tour.currentStepIndex).toBe(1);
33
+ });
34
+ it('stores a currentStepIndex write on an uncontrolled tour', () => {
35
+ const tour = runningTour(false);
36
+ tour.currentStepIndex = 1;
37
+ expect(tour.currentStepIndex).toBe(1);
38
+ });
39
+ it('keeps a controlled currentStepIndex when Joyride reports another index', () => {
40
+ const tour = runningTour(true);
41
+ tour.currentStepIndex = 1;
42
+ tour.handleTourEvent(event(STATUS.RUNNING, 0));
43
+ expect(tour.currentStepIndex).toBe(1);
44
+ });
45
+ it('returns to idle when the tour finishes', () => {
46
+ const tour = runningTour(false);
47
+ tour.handleTourEvent(event(STATUS.FINISHED, 1));
48
+ expect(tour.currentStepIndex).toBe(-1);
49
+ expect(tour.isRunning()).toBe(false);
50
+ });
51
+ });
52
+ describe('TourManager', () => {
53
+ it('resets currentStepIndex when a controlled tour is launched', async () => {
54
+ const manager = new TourManager();
55
+ const handler = manager.addTour({
56
+ id: 'sample',
57
+ label: 'Sample',
58
+ hasHelpEntry: false,
59
+ controlled: true,
60
+ steps: [{ target: 'body', content: 'one', placement: 'center' }]
61
+ });
62
+ if (!handler) {
63
+ throw new Error('tour was not created');
64
+ }
65
+ handler.currentStepIndex = 1;
66
+ await manager.launch([handler], true);
67
+ expect(handler.currentStepIndex).toBe(0);
68
+ manager.dispose();
69
+ });
70
+ it('does not reset currentStepIndex when an uncontrolled tour is launched', async () => {
71
+ const manager = new TourManager();
72
+ const handler = manager.addTour({
73
+ id: 'sample',
74
+ label: 'Sample',
75
+ hasHelpEntry: false,
76
+ steps: [{ target: 'body', content: 'one', placement: 'center' }]
77
+ });
78
+ if (!handler) {
79
+ throw new Error('tour was not created');
80
+ }
81
+ handler.handleTourEvent(event(STATUS.RUNNING, 1));
82
+ await manager.launch([handler], true);
83
+ expect(handler.currentStepIndex).toBe(1);
84
+ manager.dispose();
85
+ });
86
+ });
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
- import { ISignal } from '@lumino/signaling';
3
- import { ITourManager } from './tokens';
4
- import { TourHandler } from './tour';
2
+ import type { ISignal } from '@lumino/signaling';
3
+ import type { ITourManager } from './tokens';
4
+ import type { TourHandler } from './tour';
5
5
  /**
6
6
  * Tour container
7
7
  */
package/lib/components.js CHANGED
@@ -18,8 +18,9 @@ class Tour extends React.Component {
18
18
  };
19
19
  this._handleJoyrideCallback = (data) => {
20
20
  const { status } = data;
21
+ const handler = this.props.tours[this.state.index];
21
22
  const finishedStatuses = [STATUS.FINISHED, STATUS.SKIPPED];
22
- this.props.tours[this.state.index].handleTourEvent(data);
23
+ handler.handleTourEvent(data);
23
24
  if (finishedStatuses.includes(status)) {
24
25
  this.setState({ run: false });
25
26
  const newIndex = this.state.index + 1;
@@ -37,7 +38,18 @@ class Tour extends React.Component {
37
38
  };
38
39
  }
39
40
  render() {
40
- return this.props.tours && this.props.tours[this.state.index] ? (React.createElement(ReactJoyride, { key: this.props.tours[this.state.index].id, ...this.props.tours[this.state.index].options, callback: this._handleJoyrideCallback, run: this.state.run, steps: this.props.tours[this.state.index].steps })) : null;
41
+ var _a;
42
+ const handler = (_a = this.props.tours) === null || _a === void 0 ? void 0 : _a[this.state.index];
43
+ if (!handler) {
44
+ return null;
45
+ }
46
+ const options = { ...handler.options };
47
+ delete options.stepIndex;
48
+ const joyride = (stepIndex) => (React.createElement(ReactJoyride, { key: handler.id, ...options, callback: this._handleJoyrideCallback, run: this.state.run, steps: handler.steps, ...(stepIndex === undefined ? {} : { stepIndex }) }));
49
+ if (!handler.controlled) {
50
+ return joyride();
51
+ }
52
+ return (React.createElement(UseSignal, { signal: handler.currentStepIndexChanged, initialArgs: handler.currentStepIndex }, () => joyride(handler.currentStepIndex >= 0 ? handler.currentStepIndex : undefined)));
41
53
  }
42
54
  }
43
55
  /**
@@ -1,4 +1,4 @@
1
- import { Props as JoyrideProps } from 'react-joyride';
1
+ import type { Props as JoyrideProps } from 'react-joyride';
2
2
  /**
3
3
  * Command IDs
4
4
  */
package/lib/defaults.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { JupyterFrontEnd } from '@jupyterlab/application';
2
- import { INotebookTracker } from '@jupyterlab/notebook';
3
- import { ITourManager } from './tokens';
1
+ import type { JupyterFrontEnd } from '@jupyterlab/application';
2
+ import type { INotebookTracker } from '@jupyterlab/notebook';
3
+ import type { ITourManager } from './tokens';
4
4
  /**
5
5
  * Add all default tours
6
6
  *
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react';
2
2
  import { ReactWidget } from '@jupyterlab/apputils';
3
- import { Notebook } from '@jupyterlab/notebook';
4
- import { TranslationBundle } from '@jupyterlab/translation';
5
- import { INotebookTourManager } from './tokens';
3
+ import type { Notebook } from '@jupyterlab/notebook';
4
+ import type { TranslationBundle } from '@jupyterlab/translation';
5
+ import type { INotebookTourManager } from './tokens';
6
6
  export declare class TourButton extends ReactWidget {
7
7
  /**
8
8
  * Construct a new notebook toolbar item for tours
@@ -1,7 +1,7 @@
1
- import { ErrorObject } from 'ajv';
2
- import { Notebook } from '@jupyterlab/notebook';
3
- import { ISignal } from '@lumino/signaling';
4
- import { INotebookTourManager, ITourManager } from './tokens';
1
+ import type { ErrorObject, ValidateFunction } from 'ajv';
2
+ import type { Notebook } from '@jupyterlab/notebook';
3
+ import type { ISignal } from '@lumino/signaling';
4
+ import type { INotebookTourManager, ITourManager } from './tokens';
5
5
  /**
6
6
  * The NotebookTourManager is needed to sync Notebook metadata with the TourManager
7
7
  */
@@ -24,13 +24,19 @@ export declare class NotebookTourManager implements INotebookTourManager {
24
24
  * @returns the list of errors
25
25
  */
26
26
  getNotebookValidationErrors(notebook: Notebook): ErrorObject[];
27
+ /** A signal that emits when notebook tours changes. */
27
28
  get notebookToursChanged(): ISignal<INotebookTourManager, Notebook>;
28
29
  private _onNotebookDisposed;
29
30
  private _cleanNotebookTours;
30
31
  /**
31
- * The metadata changed, and therefor maybe tours: remove and re-add all of them.
32
+ * The metadata changed, and therefore maybe tours: remove all of them, and
33
+ * maybe re-add.
32
34
  */
33
35
  private _notebookMetadataChanged;
36
+ /**
37
+ * Tour metadata was found:
38
+ */
39
+ private _updateFromNotebookMetadata;
34
40
  /**
35
41
  * Actually create a tour from JSON
36
42
  */
@@ -38,6 +44,12 @@ export declare class NotebookTourManager implements INotebookTourManager {
38
44
  private _tourManager;
39
45
  private _notebookTours;
40
46
  private _notebookToursChanged;
41
- private _validator;
42
47
  private _validationErrors;
43
48
  }
49
+ /** A namespace for private values. */
50
+ export declare namespace Private {
51
+ /**
52
+ * Get a singleton pre-compiled validator function.
53
+ */
54
+ function ensureValidator(): Promise<ValidateFunction>;
55
+ }
@@ -1,8 +1,7 @@
1
- import Ajv from 'ajv';
2
1
  import { Signal } from '@lumino/signaling';
3
- import USER_SCHEMA from '../schema/user-tours.json';
4
2
  import { notebookTourIcon } from './icons';
5
3
  import { NOTEBOOK_PLUGIN_ID, NS } from './tokens';
4
+ import { PromiseDelegate } from '@lumino/coreutils';
6
5
  /**
7
6
  * The NotebookTourManager is needed to sync Notebook metadata with the TourManager
8
7
  */
@@ -12,10 +11,6 @@ export class NotebookTourManager {
12
11
  this._notebookToursChanged = new Signal(this);
13
12
  this._validationErrors = new Map();
14
13
  this._tourManager = options.tourManager;
15
- // `jupyter.lab...` keywords custom keywords rejected by default
16
- // we may be able to do better than `strict: false` by defining
17
- // custom keywords https://ajv.js.org/keywords.html
18
- this._validator = new Ajv({ strict: false }).compile(USER_SCHEMA);
19
14
  }
20
15
  get tourManager() {
21
16
  return this._tourManager;
@@ -31,11 +26,9 @@ export class NotebookTourManager {
31
26
  if (!notebook.model) {
32
27
  return;
33
28
  }
34
- ((_a = notebook.model.metadataChanged) !== null && _a !== void 0 ? _a : notebook.model.metadata.changed).connect(() => {
35
- this._notebookMetadataChanged(notebook);
36
- });
29
+ ((_a = notebook.model.metadataChanged) !== null && _a !== void 0 ? _a : notebook.model.metadata.changed).connect(async () => await this._notebookMetadataChanged(notebook));
37
30
  notebook.disposed.connect(this._onNotebookDisposed, this);
38
- this._notebookMetadataChanged(notebook);
31
+ await this._notebookMetadataChanged(notebook);
39
32
  }
40
33
  /**
41
34
  * Get the list of full tour ids for this notebook
@@ -59,6 +52,7 @@ export class NotebookTourManager {
59
52
  getNotebookValidationErrors(notebook) {
60
53
  return this._validationErrors.get(notebook) || [];
61
54
  }
55
+ /** A signal that emits when notebook tours changes. */
62
56
  get notebookToursChanged() {
63
57
  return this._notebookToursChanged;
64
58
  }
@@ -71,10 +65,10 @@ export class NotebookTourManager {
71
65
  }
72
66
  }
73
67
  /**
74
- * The metadata changed, and therefor maybe tours: remove and re-add all of them.
68
+ * The metadata changed, and therefore maybe tours: remove all of them, and
69
+ * maybe re-add.
75
70
  */
76
- _notebookMetadataChanged(notebook) {
77
- var _a, _b;
71
+ async _notebookMetadataChanged(notebook) {
78
72
  const { model } = notebook;
79
73
  const metadata = model
80
74
  ? model.getMetadata
@@ -82,34 +76,42 @@ export class NotebookTourManager {
82
76
  : // @ts-expect-error JLab 3 API
83
77
  model.metadata.get(NS)
84
78
  : null;
85
- const trans = this._tourManager.translator;
86
79
  this._cleanNotebookTours(notebook);
87
80
  this._validationErrors.set(notebook, []);
88
81
  if (metadata) {
89
- this._validator(metadata);
90
- const errors = this._validator.errors || [];
91
- this._validationErrors.set(notebook, errors);
92
- if (errors.length) {
93
- console.error(trans.__('Validation errors found: fix them in Advanced Settings'));
94
- console.table(errors);
95
- }
96
- else {
97
- const tours = (_a = metadata['tours']) !== null && _a !== void 0 ? _a : [];
98
- for (const tour of this.tourManager.sortTours(tours)) {
99
- try {
100
- this._addNotebookTour(notebook, tour);
101
- this._tourManager.launch([tour.id], false);
102
- }
103
- catch (error) {
104
- console.groupCollapsed(trans.__('Error encountered adding notebook tour %1 (%2)', tour.label, tour.id), error);
105
- console.table(tour.steps);
106
- console.log((_b = tour.options) !== null && _b !== void 0 ? _b : {});
107
- console.groupEnd();
108
- }
82
+ await this._updateFromNotebookMetadata(notebook, metadata);
83
+ }
84
+ this._notebookToursChanged.emit(notebook);
85
+ }
86
+ /**
87
+ * Tour metadata was found:
88
+ */
89
+ async _updateFromNotebookMetadata(notebook, metadata) {
90
+ var _a, _b;
91
+ const { translator } = this._tourManager;
92
+ const _validator = await Private.ensureValidator();
93
+ _validator(metadata);
94
+ const errors = _validator.errors || [];
95
+ this._validationErrors.set(notebook, errors);
96
+ if (errors.length) {
97
+ console.error(translator.__('Validation errors found: fix them in Advanced Settings'));
98
+ console.table(errors);
99
+ }
100
+ else {
101
+ const tours = (_a = metadata['tours']) !== null && _a !== void 0 ? _a : [];
102
+ for (const tour of this.tourManager.sortTours(tours)) {
103
+ try {
104
+ this._addNotebookTour(notebook, tour);
105
+ this._tourManager.launch([tour.id], false);
106
+ }
107
+ catch (error) {
108
+ console.groupCollapsed(translator.__('Error encountered adding notebook tour %1 (%2)', tour.label, tour.id), error);
109
+ console.table(tour.steps);
110
+ console.log((_b = tour.options) !== null && _b !== void 0 ? _b : {});
111
+ console.groupEnd();
109
112
  }
110
113
  }
111
114
  }
112
- this._notebookToursChanged.emit(notebook);
113
115
  }
114
116
  /**
115
117
  * Actually create a tour from JSON
@@ -122,3 +124,27 @@ export class NotebookTourManager {
122
124
  });
123
125
  }
124
126
  }
127
+ /** A namespace for private values. */
128
+ export var Private;
129
+ (function (Private) {
130
+ let _validator = null;
131
+ let _loading = null;
132
+ /**
133
+ * Get a singleton pre-compiled validator function.
134
+ */
135
+ async function ensureValidator() {
136
+ if (!_loading) {
137
+ _loading = new PromiseDelegate();
138
+ const { Ajv } = await import('ajv');
139
+ const schema = await import('../schema/user-tours.json');
140
+ // `jupyter.lab...` keywords custom keywords rejected by default
141
+ // we may be able to do better than `strict: false` by defining
142
+ // custom keywords https://ajv.js.org/keywords.html
143
+ const ajv = new Ajv({ strict: false });
144
+ _validator = ajv.compile(schema);
145
+ _loading.resolve(_validator);
146
+ }
147
+ return _loading.promise;
148
+ }
149
+ Private.ensureValidator = ensureValidator;
150
+ })(Private || (Private = {}));
package/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab-tour",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "A JupyterLab UI Tour.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "scripts": {
33
33
  "build": "jlpm build:lib && jlpm build:labextension:dev",
34
- "build:labextension": "jupyter labextension build --debug .",
35
- "build:labextension:dev": "jupyter labextension build --debug --development True .",
34
+ "build:labextension": "jupyter-builder build --debug .",
35
+ "build:labextension:dev": "jupyter-builder build --debug --development True .",
36
36
  "build:lib": "jlpm build:lib:prod --sourceMap",
37
37
  "build:lib:prod": "tsc -b src",
38
38
  "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
@@ -41,7 +41,7 @@
41
41
  "clean:labextension": "rimraf jupyterlab_tour/labextension jupyterlab_tour/_version.py",
42
42
  "clean:lib": "rimraf lib build/.cache/tsc",
43
43
  "clean:lintcache": "rimraf ./build/.cache/eslint ./build/.cache/stylelint",
44
- "dedupe": "yarn-berry-deduplicate --strategy=fewer --fail",
44
+ "dedupe": "yarn dedupe --check",
45
45
  "eslint": "jlpm eslint:check --fix",
46
46
  "eslint:check": "eslint . --cache --cache-location=./build/.cache/eslint --ext .ts,.tsx",
47
47
  "install:extension": "jlpm build",
@@ -54,32 +54,32 @@
54
54
  "stylelint:check": "stylelint --cache --cache-location=./build/.cache/stylelint \"style/**/*.css\"",
55
55
  "test": "jest --coverage",
56
56
  "watch": "run-p watch:src watch:labextension",
57
- "watch:labextension": "jupyter labextension watch .",
57
+ "watch:labextension": "jupyter-builder watch .",
58
58
  "watch:src": "jlpm build:lib -w"
59
59
  },
60
60
  "dependencies": {
61
- "@jupyterlab/application": "^4.0.0",
62
- "@jupyterlab/apputils": "^4.0.0",
63
- "@jupyterlab/mainmenu": "^4.0.0",
64
- "@jupyterlab/notebook": "^4.0.0",
65
- "@jupyterlab/services": "^7.0.0",
66
- "@jupyterlab/settingregistry": "^4.0.0",
67
- "@jupyterlab/statedb": "^4.0.0",
68
- "@jupyterlab/translation": "^4.0.0",
69
- "@jupyterlab/ui-components": "^4.0.0",
61
+ "@jupyterlab/application": "^4.3.0",
62
+ "@jupyterlab/apputils": "^4.3.0",
63
+ "@jupyterlab/mainmenu": "^4.3.0",
64
+ "@jupyterlab/notebook": "^4.3.0",
65
+ "@jupyterlab/services": "^7.3.0",
66
+ "@jupyterlab/settingregistry": "^4.3.0",
67
+ "@jupyterlab/statedb": "^4.3.0",
68
+ "@jupyterlab/translation": "^4.3.0",
69
+ "@jupyterlab/ui-components": "^4.3.0",
70
70
  "@lumino/coreutils": "^2.1.1",
71
71
  "@lumino/disposable": "^2.1.1",
72
72
  "@lumino/signaling": "^2.1.1",
73
- "@lumino/widgets": "^2.1.1",
74
- "ajv": "^8.12.0",
73
+ "@lumino/widgets": "^2.3.1",
74
+ "ajv": "^8.17.1",
75
75
  "react": "^18.2.0",
76
- "react-joyride": "^2.5.5"
76
+ "react-joyride": "^2.9.3"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@babel/core": "^7.20.12",
80
80
  "@babel/preset-env": "^7.20.2",
81
- "@jupyterlab/builder": "^4.0.0",
82
- "@jupyterlab/testutils": "^4.0.0",
81
+ "@jupyter/builder": "^1.2.1",
82
+ "@jupyterlab/testutils": "^4.3.0",
83
83
  "@types/jest": "^29.2.0",
84
84
  "@types/json-schema": "^7.0.11",
85
85
  "@types/react": "^18.0.26",
@@ -93,8 +93,8 @@
93
93
  "eslint-plugin-react-hooks": "^4.6.0",
94
94
  "jest": "^29.2.0",
95
95
  "npm-run-all": "^4.1.5",
96
- "prettier": "^3.0.0",
97
- "rimraf": "^5.0.1",
96
+ "prettier": "^3.6.2",
97
+ "rimraf": "^6.1.2",
98
98
  "source-map-loader": "^1.0.2",
99
99
  "style-loader": "^3.3.1",
100
100
  "stylelint": "^15.10.1",
@@ -102,7 +102,6 @@
102
102
  "stylelint-config-standard": "^34.0.0",
103
103
  "ts-jest": "^29.1.1",
104
104
  "typescript": "~5.0.4",
105
- "yarn-berry-deduplicate": "^6.1.1",
106
105
  "yjs": "^13.5.40"
107
106
  },
108
107
  "sideEffects": [
@@ -110,14 +109,25 @@
110
109
  "style/index.js"
111
110
  ],
112
111
  "resolutions": {
113
- "@types/node": "^18.0.0",
112
+ "@types/node": "^22.10.1",
114
113
  "@types/react": "^18.0.26",
115
- "react": "^18.2.0"
114
+ "react": "^18.2.0",
115
+ "yjs": "^13.5.40"
116
116
  },
117
117
  "jupyterlab": {
118
118
  "extension": true,
119
119
  "schemaDir": "schema",
120
- "outputDir": "jupyterlab_tour/labextension"
120
+ "outputDir": "jupyterlab_tour/labextension",
121
+ "sharedPackages": {
122
+ "ajv": {
123
+ "bundled": true,
124
+ "singleton": false
125
+ },
126
+ "react-joyride": {
127
+ "bundled": true,
128
+ "singleton": false
129
+ }
130
+ }
121
131
  },
122
132
  "styleModule": "style/index.js",
123
133
  "eslintIgnore": [
@@ -165,6 +175,13 @@
165
175
  "args": "none"
166
176
  }
167
177
  ],
178
+ "@typescript-eslint/consistent-type-imports": [
179
+ "error",
180
+ {
181
+ "prefer": "type-imports",
182
+ "fixStyle": "separate-type-imports"
183
+ }
184
+ ],
168
185
  "@typescript-eslint/no-explicit-any": "off",
169
186
  "@typescript-eslint/no-namespace": "off",
170
187
  "@typescript-eslint/no-use-before-define": "off",
@@ -197,6 +214,13 @@
197
214
  "options": {
198
215
  "tabWidth": 4
199
216
  }
217
+ },
218
+ {
219
+ "files": ".github/workflows/*.yml",
220
+ "options": {
221
+ "proseWrap": "never",
222
+ "printWidth": 120
223
+ }
200
224
  }
201
225
  ]
202
226
  },
package/lib/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { JupyterFrontEndPlugin } from '@jupyterlab/application';
1
+ import type { JupyterFrontEndPlugin } from '@jupyterlab/application';
2
2
  import { INotebookTourManager, ITourManager, IUserTourManager } from './tokens';
3
- declare const _default: (JupyterFrontEndPlugin<void> | JupyterFrontEndPlugin<ITourManager> | JupyterFrontEndPlugin<IUserTourManager> | JupyterFrontEndPlugin<INotebookTourManager>)[];
3
+ declare const _default: (JupyterFrontEndPlugin<ITourManager> | JupyterFrontEndPlugin<IUserTourManager> | JupyterFrontEndPlugin<INotebookTourManager> | JupyterFrontEndPlugin<void>)[];
4
4
  export default _default;
package/lib/plugin.js CHANGED
@@ -163,10 +163,13 @@ function activateDefaults(app, tourManager, nbTracker) {
163
163
  if (nbTracker &&
164
164
  (app.name !== 'Jupyter Notebook' ||
165
165
  window.location.pathname.match(/\/notebooks\/.+$/))) {
166
+ let hasOfferedNotebookTour = false;
166
167
  nbTracker.widgetAdded.connect(() => {
167
- if (tourManager.tours.has(NOTEBOOK_ID)) {
168
- tourManager.launch([NOTEBOOK_ID], false);
168
+ if (hasOfferedNotebookTour || !tourManager.tours.has(NOTEBOOK_ID)) {
169
+ return;
169
170
  }
171
+ hasOfferedNotebookTour = true;
172
+ void tourManager.launch([NOTEBOOK_ID], false);
170
173
  });
171
174
  }
172
175
  Promise.all([app.restored, tourManager.ready]).then(() => {
@@ -11,7 +11,7 @@
11
11
  "properties": {
12
12
  "tours": {
13
13
  "type": "array",
14
- "description": "An array of a tours. Each requires an `id`, `label` and `steps[]`, and may have `options`, see https://docs.react-joyride.com",
14
+ "description": "An array of tours. Each requires an `id`, `label` and `steps[]`, and may have `options`. For `steps` and `options`, see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md",
15
15
  "default": [],
16
16
  "items": {
17
17
  "type": "object",
package/lib/tokens.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { ErrorObject } from 'ajv';
2
- import { ISettingRegistry } from '@jupyterlab/settingregistry';
3
- import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
4
- import { Notebook } from '@jupyterlab/notebook';
1
+ import type { ErrorObject } from 'ajv';
2
+ import type { ISettingRegistry } from '@jupyterlab/settingregistry';
3
+ import type { ITranslator, TranslationBundle } from '@jupyterlab/translation';
4
+ import type { Notebook } from '@jupyterlab/notebook';
5
5
  import { Token } from '@lumino/coreutils';
6
- import { IDisposable } from '@lumino/disposable';
7
- import { ISignal } from '@lumino/signaling';
8
- import { LabIcon, RankedMenu } from '@jupyterlab/ui-components';
9
- import React from 'react';
10
- import { CallBackProps, Placement, Props as JoyrideProps, Step } from 'react-joyride';
6
+ import type { IDisposable } from '@lumino/disposable';
7
+ import type { ISignal } from '@lumino/signaling';
8
+ import type { LabIcon, RankedMenu } from '@jupyterlab/ui-components';
9
+ import type React from 'react';
10
+ import type { CallBackProps, Placement, Props as JoyrideProps, Step } from 'react-joyride';
11
11
  /**
12
12
  * Namespace for everything
13
13
  */
@@ -93,7 +93,7 @@ export interface ITour {
93
93
  /**
94
94
  * Tour options
95
95
  *
96
- * @see https://docs.react-joyride.com/props
96
+ * @see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/props.md
97
97
  *
98
98
  * #### Notes
99
99
  *
@@ -113,6 +113,11 @@ export interface ITour {
113
113
  * Translation domain for this tour
114
114
  */
115
115
  translation?: string;
116
+ /**
117
+ * When true, the tour step is driven by {@link ITourHandler.currentStepIndex}.
118
+ * Default is false, which leaves Joyride uncontrolled.
119
+ */
120
+ controlled?: boolean;
116
121
  }
117
122
  /**
118
123
  * Tour handler interface
@@ -137,8 +142,9 @@ export interface ITourHandler extends IDisposable {
137
142
  createAndAddStep(target: string, content: React.ReactNode, placement?: StepPlacement, title?: string): Step;
138
143
  /**
139
144
  * The index of the current step of the tour. Returns -1 if tour isn't active.
145
+ * Setting this moves the tour only when {@link controlled} is true.
140
146
  */
141
- readonly currentStepIndex: number;
147
+ currentStepIndex: number;
142
148
  /**
143
149
  * Is the tour running?
144
150
  */
@@ -191,6 +197,15 @@ export interface ITourHandler extends IDisposable {
191
197
  * A signal emitted when the tour step has changed.
192
198
  */
193
199
  readonly stepChanged: ISignal<this, CallBackProps>;
200
+ /**
201
+ * When true, Joyride is controlled by {@link currentStepIndex}.
202
+ * When false, Joyride advances on its own.
203
+ */
204
+ readonly controlled: boolean;
205
+ /**
206
+ * A signal emitted when {@link currentStepIndex} changes.
207
+ */
208
+ readonly currentStepIndexChanged: ISignal<this, number>;
194
209
  /**
195
210
  * The array of steps the tour currently contains. Each step will be followed
196
211
  * in order as the tour progresses.
package/lib/tour.d.ts CHANGED
@@ -1,14 +1,16 @@
1
1
  /// <reference types="react" />
2
- import { ISignal } from '@lumino/signaling';
3
- import { LabIcon } from '@jupyterlab/ui-components';
4
- import { CallBackProps, Props as JoyrideProps, Step } from 'react-joyride';
5
- import { ITourHandler, StepPlacement } from './tokens';
2
+ import type { ISignal } from '@lumino/signaling';
3
+ import type { LabIcon } from '@jupyterlab/ui-components';
4
+ import type { CallBackProps, Props as JoyrideProps, Step } from 'react-joyride';
5
+ import type { ITourHandler, StepPlacement } from './tokens';
6
6
  export declare class TourHandler implements ITourHandler {
7
- constructor(id: string, label: string, options?: Omit<JoyrideProps, 'steps'>, icon?: LabIcon | null, version?: number);
7
+ constructor(id: string, label: string, options?: Omit<JoyrideProps, 'steps'>, icon?: LabIcon | null, version?: number, controlled?: boolean);
8
8
  /**
9
9
  * The index of the current step of the tour. Returns -1 if tour isn't active.
10
+ * Setting this moves the tour only when {@link controlled} is true.
10
11
  */
11
12
  get currentStepIndex(): number;
13
+ set currentStepIndex(index: number);
12
14
  /**
13
15
  * A signal emitted when all the steps of the tour have been seen and tour is finished.
14
16
  */
@@ -51,6 +53,14 @@ export declare class TourHandler implements ITourHandler {
51
53
  * A signal emitted when the tour step has changed.
52
54
  */
53
55
  get stepChanged(): ISignal<this, CallBackProps>;
56
+ /**
57
+ * When true, Joyride is controlled by {@link currentStepIndex}.
58
+ */
59
+ get controlled(): boolean;
60
+ /**
61
+ * A signal emitted when {@link currentStepIndex} changes.
62
+ */
63
+ get currentStepIndexChanged(): ISignal<this, number>;
54
64
  /**
55
65
  * The array of steps the tour currently contains. Each step will be followed
56
66
  * in order as the tour progresses.
@@ -114,14 +124,15 @@ export declare class TourHandler implements ITourHandler {
114
124
  private _finished;
115
125
  private _started;
116
126
  private _stepChanged;
127
+ private _currentStepIndexChanged;
117
128
  private _currentStepIndex;
118
129
  private _id;
119
130
  private _isDisposed;
120
131
  private _label;
121
132
  private _options;
122
133
  private _previousStatus;
123
- private _previousStepIndex;
124
134
  private _steps;
125
135
  private _icon;
126
136
  private _version;
137
+ private _controlled;
127
138
  }
package/lib/tour.js CHANGED
@@ -5,7 +5,7 @@ import { TutorialDefaultOptions } from './constants';
5
5
  import { tourIcon } from './icons';
6
6
  // TODO should be IDisposable !! handling signal connection clearance
7
7
  export class TourHandler {
8
- constructor(id, label, options, icon = null, version = -1) {
8
+ constructor(id, label, options, icon = null, version = -1, controlled = false) {
9
9
  /**
10
10
  * Handle react-joyride callbacks
11
11
  *
@@ -19,7 +19,7 @@ export class TourHandler {
19
19
  // Handle status changes when they occur
20
20
  if (status !== this._previousStatus) {
21
21
  this._previousStatus = status;
22
- this._currentStepIndex = -1;
22
+ this.currentStepIndex = -1;
23
23
  if (status === STATUS.FINISHED) {
24
24
  this._finished.emit(data);
25
25
  }
@@ -27,7 +27,7 @@ export class TourHandler {
27
27
  this._skipped.emit(data);
28
28
  }
29
29
  else if (status === STATUS.RUNNING) {
30
- this._currentStepIndex = 0;
30
+ this.currentStepIndex = 0;
31
31
  this._started.emit(data);
32
32
  }
33
33
  else if (status === STATUS.ERROR) {
@@ -36,9 +36,8 @@ export class TourHandler {
36
36
  }
37
37
  // Emit step change event
38
38
  if (status === STATUS.RUNNING) {
39
- if (index !== this._previousStepIndex) {
40
- this._previousStepIndex = index;
41
- this._currentStepIndex = data.index;
39
+ if (!this._controlled) {
40
+ this.currentStepIndex = index;
42
41
  }
43
42
  this._stepChanged.emit(data);
44
43
  }
@@ -47,15 +46,17 @@ export class TourHandler {
47
46
  this._finished = new Signal(this);
48
47
  this._started = new Signal(this);
49
48
  this._stepChanged = new Signal(this);
49
+ this._currentStepIndexChanged = new Signal(this);
50
50
  this._currentStepIndex = -1;
51
51
  this._isDisposed = false;
52
52
  this._previousStatus = STATUS.READY;
53
- this._previousStepIndex = -1;
54
53
  this._steps = new Array();
54
+ this._controlled = false;
55
55
  this._label = label;
56
56
  this._id = id;
57
57
  this._icon = icon || null;
58
58
  this._version = version;
59
+ this._controlled = controlled;
59
60
  const { styles, ...others } = options !== null && options !== void 0 ? options : {};
60
61
  this._options = { ...TutorialDefaultOptions, ...others };
61
62
  if (!this._options.styles) {
@@ -68,10 +69,18 @@ export class TourHandler {
68
69
  }
69
70
  /**
70
71
  * The index of the current step of the tour. Returns -1 if tour isn't active.
72
+ * Setting this moves the tour only when {@link controlled} is true.
71
73
  */
72
74
  get currentStepIndex() {
73
75
  return this._currentStepIndex;
74
76
  }
77
+ set currentStepIndex(index) {
78
+ if (index === this._currentStepIndex) {
79
+ return;
80
+ }
81
+ this._currentStepIndex = index;
82
+ this._currentStepIndexChanged.emit(index);
83
+ }
75
84
  /**
76
85
  * A signal emitted when all the steps of the tour have been seen and tour is finished.
77
86
  */
@@ -136,6 +145,18 @@ export class TourHandler {
136
145
  get stepChanged() {
137
146
  return this._stepChanged;
138
147
  }
148
+ /**
149
+ * When true, Joyride is controlled by {@link currentStepIndex}.
150
+ */
151
+ get controlled() {
152
+ return this._controlled;
153
+ }
154
+ /**
155
+ * A signal emitted when {@link currentStepIndex} changes.
156
+ */
157
+ get currentStepIndexChanged() {
158
+ return this._currentStepIndexChanged;
159
+ }
139
160
  /**
140
161
  * The array of steps the tour currently contains. Each step will be followed
141
162
  * in order as the tour progresses.
@@ -1,7 +1,7 @@
1
- import { ISignal } from '@lumino/signaling';
2
- import { TranslationBundle } from '@jupyterlab/translation';
1
+ import type { ISignal } from '@lumino/signaling';
2
+ import type { TranslationBundle } from '@jupyterlab/translation';
3
3
  import { LabIcon } from '@jupyterlab/ui-components';
4
- import { ITour, ITourHandler, ITourManager, ITourManagerOptions } from './tokens';
4
+ import type { ITour, ITourHandler, ITourManager, ITourManagerOptions } from './tokens';
5
5
  import { TourHandler } from './tour';
6
6
  /**
7
7
  * The TourManager is needed to manage creation, removal and launching of Tutorials
@@ -24,12 +24,13 @@ export class TourManager {
24
24
  * The tour label will not be translated.
25
25
  */
26
26
  this.createTour = (args) => {
27
- var _a, _b, _c, _d;
27
+ var _a, _b, _c, _d, _e;
28
28
  const { id, label } = args;
29
29
  const addToHelpMenu = (_a = args.hasHelpEntry) !== null && _a !== void 0 ? _a : true;
30
30
  const options = (_b = args.options) !== null && _b !== void 0 ? _b : {};
31
31
  const icon = (_c = args.icon) !== null && _c !== void 0 ? _c : null;
32
32
  const version = (_d = args.version) !== null && _d !== void 0 ? _d : 0;
33
+ const controlled = (_e = args.controlled) !== null && _e !== void 0 ? _e : false;
33
34
  if (this._tours.has(id)) {
34
35
  throw new Error(this._trans.__("Error creating new tour. TourHandler id's must be unique.\nTutorial with the id: '%1' already exists.", id));
35
36
  }
@@ -38,7 +39,7 @@ export class TourManager {
38
39
  options.locale = this._locale;
39
40
  }
40
41
  // Create tour and add it to help menu if needed
41
- const newTutorial = new TourHandler(id, label, options, icon, version);
42
+ const newTutorial = new TourHandler(id, label, options, icon, version, controlled);
42
43
  if (this._menu && addToHelpMenu) {
43
44
  const options = {
44
45
  args: {
@@ -173,7 +174,8 @@ export class TourManager {
173
174
  hasHelpEntry: tour.hasHelpEntry === false ? false : true,
174
175
  options: tour.options,
175
176
  icon: tour.icon ? LabIcon.resolve({ icon: tour.icon }) : undefined,
176
- version: tour.version
177
+ version: tour.version,
178
+ controlled: tour.controlled
177
179
  });
178
180
  tour.steps.forEach(step => {
179
181
  const translatedStep = { ...step };
@@ -229,6 +231,11 @@ export class TourManager {
229
231
  : !this._state.map(s => s.id).includes(tour.id));
230
232
  }
231
233
  const startTours = () => {
234
+ tourList.forEach(tour => {
235
+ if (tour.controlled) {
236
+ tour.currentStepIndex = 0;
237
+ }
238
+ });
232
239
  this._activeTours = tourList;
233
240
  this._tourLaunched.emit(tourList);
234
241
  };
@@ -1,4 +1,4 @@
1
- import { ITourManager, IUserTourManager } from './tokens';
1
+ import type { ITourManager, IUserTourManager } from './tokens';
2
2
  /**
3
3
  * The UserTourManager is needed to manage syncing of user settings with the TourManager
4
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab-tour",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "A JupyterLab UI Tour.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "scripts": {
33
33
  "build": "jlpm build:lib && jlpm build:labextension:dev",
34
- "build:labextension": "jupyter labextension build --debug .",
35
- "build:labextension:dev": "jupyter labextension build --debug --development True .",
34
+ "build:labextension": "jupyter-builder build --debug .",
35
+ "build:labextension:dev": "jupyter-builder build --debug --development True .",
36
36
  "build:lib": "jlpm build:lib:prod --sourceMap",
37
37
  "build:lib:prod": "tsc -b src",
38
38
  "build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
@@ -41,7 +41,7 @@
41
41
  "clean:labextension": "rimraf jupyterlab_tour/labextension jupyterlab_tour/_version.py",
42
42
  "clean:lib": "rimraf lib build/.cache/tsc",
43
43
  "clean:lintcache": "rimraf ./build/.cache/eslint ./build/.cache/stylelint",
44
- "dedupe": "yarn-berry-deduplicate --strategy=fewer --fail",
44
+ "dedupe": "yarn dedupe --check",
45
45
  "eslint": "jlpm eslint:check --fix",
46
46
  "eslint:check": "eslint . --cache --cache-location=./build/.cache/eslint --ext .ts,.tsx",
47
47
  "install:extension": "jlpm build",
@@ -54,32 +54,32 @@
54
54
  "stylelint:check": "stylelint --cache --cache-location=./build/.cache/stylelint \"style/**/*.css\"",
55
55
  "test": "jest --coverage",
56
56
  "watch": "run-p watch:src watch:labextension",
57
- "watch:labextension": "jupyter labextension watch .",
57
+ "watch:labextension": "jupyter-builder watch .",
58
58
  "watch:src": "jlpm build:lib -w"
59
59
  },
60
60
  "dependencies": {
61
- "@jupyterlab/application": "^4.0.0",
62
- "@jupyterlab/apputils": "^4.0.0",
63
- "@jupyterlab/mainmenu": "^4.0.0",
64
- "@jupyterlab/notebook": "^4.0.0",
65
- "@jupyterlab/services": "^7.0.0",
66
- "@jupyterlab/settingregistry": "^4.0.0",
67
- "@jupyterlab/statedb": "^4.0.0",
68
- "@jupyterlab/translation": "^4.0.0",
69
- "@jupyterlab/ui-components": "^4.0.0",
61
+ "@jupyterlab/application": "^4.3.0",
62
+ "@jupyterlab/apputils": "^4.3.0",
63
+ "@jupyterlab/mainmenu": "^4.3.0",
64
+ "@jupyterlab/notebook": "^4.3.0",
65
+ "@jupyterlab/services": "^7.3.0",
66
+ "@jupyterlab/settingregistry": "^4.3.0",
67
+ "@jupyterlab/statedb": "^4.3.0",
68
+ "@jupyterlab/translation": "^4.3.0",
69
+ "@jupyterlab/ui-components": "^4.3.0",
70
70
  "@lumino/coreutils": "^2.1.1",
71
71
  "@lumino/disposable": "^2.1.1",
72
72
  "@lumino/signaling": "^2.1.1",
73
- "@lumino/widgets": "^2.1.1",
74
- "ajv": "^8.12.0",
73
+ "@lumino/widgets": "^2.3.1",
74
+ "ajv": "^8.17.1",
75
75
  "react": "^18.2.0",
76
- "react-joyride": "^2.5.5"
76
+ "react-joyride": "^2.9.3"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@babel/core": "^7.20.12",
80
80
  "@babel/preset-env": "^7.20.2",
81
- "@jupyterlab/builder": "^4.0.0",
82
- "@jupyterlab/testutils": "^4.0.0",
81
+ "@jupyter/builder": "^1.2.1",
82
+ "@jupyterlab/testutils": "^4.3.0",
83
83
  "@types/jest": "^29.2.0",
84
84
  "@types/json-schema": "^7.0.11",
85
85
  "@types/react": "^18.0.26",
@@ -93,8 +93,8 @@
93
93
  "eslint-plugin-react-hooks": "^4.6.0",
94
94
  "jest": "^29.2.0",
95
95
  "npm-run-all": "^4.1.5",
96
- "prettier": "^3.0.0",
97
- "rimraf": "^5.0.1",
96
+ "prettier": "^3.6.2",
97
+ "rimraf": "^6.1.2",
98
98
  "source-map-loader": "^1.0.2",
99
99
  "style-loader": "^3.3.1",
100
100
  "stylelint": "^15.10.1",
@@ -102,7 +102,6 @@
102
102
  "stylelint-config-standard": "^34.0.0",
103
103
  "ts-jest": "^29.1.1",
104
104
  "typescript": "~5.0.4",
105
- "yarn-berry-deduplicate": "^6.1.1",
106
105
  "yjs": "^13.5.40"
107
106
  },
108
107
  "sideEffects": [
@@ -110,14 +109,25 @@
110
109
  "style/index.js"
111
110
  ],
112
111
  "resolutions": {
113
- "@types/node": "^18.0.0",
112
+ "@types/node": "^22.10.1",
114
113
  "@types/react": "^18.0.26",
115
- "react": "^18.2.0"
114
+ "react": "^18.2.0",
115
+ "yjs": "^13.5.40"
116
116
  },
117
117
  "jupyterlab": {
118
118
  "extension": true,
119
119
  "schemaDir": "schema",
120
- "outputDir": "jupyterlab_tour/labextension"
120
+ "outputDir": "jupyterlab_tour/labextension",
121
+ "sharedPackages": {
122
+ "ajv": {
123
+ "bundled": true,
124
+ "singleton": false
125
+ },
126
+ "react-joyride": {
127
+ "bundled": true,
128
+ "singleton": false
129
+ }
130
+ }
121
131
  },
122
132
  "styleModule": "style/index.js",
123
133
  "eslintIgnore": [
@@ -165,6 +175,13 @@
165
175
  "args": "none"
166
176
  }
167
177
  ],
178
+ "@typescript-eslint/consistent-type-imports": [
179
+ "error",
180
+ {
181
+ "prefer": "type-imports",
182
+ "fixStyle": "separate-type-imports"
183
+ }
184
+ ],
168
185
  "@typescript-eslint/no-explicit-any": "off",
169
186
  "@typescript-eslint/no-namespace": "off",
170
187
  "@typescript-eslint/no-use-before-define": "off",
@@ -197,6 +214,13 @@
197
214
  "options": {
198
215
  "tabWidth": 4
199
216
  }
217
+ },
218
+ {
219
+ "files": ".github/workflows/*.yml",
220
+ "options": {
221
+ "proseWrap": "never",
222
+ "printWidth": 120
223
+ }
200
224
  }
201
225
  ]
202
226
  },
@@ -11,7 +11,7 @@
11
11
  "properties": {
12
12
  "tours": {
13
13
  "type": "array",
14
- "description": "An array of a tours. Each requires an `id`, `label` and `steps[]`, and may have `options`, see https://docs.react-joyride.com",
14
+ "description": "An array of tours. Each requires an `id`, `label` and `steps[]`, and may have `options`. For `steps` and `options`, see https://github.com/gilbarbara/react-joyride/blob/v2.9.3/docs/SUMMARY.md",
15
15
  "default": [],
16
16
  "items": {
17
17
  "type": "object",