@zikojs/notebook 0.14.1 → 0.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zikojs/notebook",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "A lightweight, extensible JavaScript notebook component with Markdown, executable cells, persistent state, and pluggable runtimes.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -41,16 +41,15 @@
41
41
  "dependencies": {
42
42
  "@codemirror/autocomplete": "^6.20.3",
43
43
  "@codemirror/commands": "^6.11.0",
44
- "@codemirror/lang-javascript": "^6.2.5",
44
+ "@codemirror/lang-javascript": "latest",
45
45
  "@codemirror/lang-markdown": "^6.5.2",
46
46
  "@codemirror/language": "^6.12.4",
47
47
  "@codemirror/state": "^6.7.4",
48
48
  "@codemirror/view": "^6.43.11",
49
- "@zikojs/lucide": "^0.2.2",
50
49
  "@zikojs/console": "^0.8.0",
50
+ "@zikojs/lucide": "^0.2.2",
51
51
  "acorn": "^8.18.0",
52
52
  "codemirror": "^6.0.2",
53
- "lucide": "^1.44.0",
54
53
  "rehype-stringify": "^10.0.1",
55
54
  "remark": "^15.0.1",
56
55
  "remark-rehype": "^11.1.2",
@@ -1,7 +1,22 @@
1
- import { EditorView, keymap, lineNumbers, highlightActiveLine } from "@codemirror/view";
1
+ import {
2
+ EditorView,
3
+ keymap,
4
+ lineNumbers,
5
+ highlightActiveLine,
6
+ } from "@codemirror/view";
2
7
  import { EditorState, Compartment } from "@codemirror/state";
3
- import { defaultKeymap, history, historyKeymap, indentWithTab } from "@codemirror/commands";
4
- import { bracketMatching, indentUnit, syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";
8
+ import {
9
+ defaultKeymap,
10
+ history,
11
+ historyKeymap,
12
+ indentWithTab,
13
+ } from "@codemirror/commands";
14
+ import {
15
+ bracketMatching,
16
+ indentUnit,
17
+ syntaxHighlighting,
18
+ defaultHighlightStyle,
19
+ } from "@codemirror/language";
5
20
  import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
6
21
  import { javascript } from "@codemirror/lang-javascript";
7
22
  import { markdown } from "@codemirror/lang-markdown";
@@ -9,7 +24,6 @@ import { tags } from "ziko/dom";
9
24
 
10
25
  const { div } = tags;
11
26
 
12
-
13
27
  const getRuntimePlugin = (runtime, runtimePlugins = []) =>
14
28
  runtimePlugins.find((plugin) => plugin?.name === runtime);
15
29
 
@@ -39,7 +53,7 @@ export function createCodeEditor(app, cellData, actions) {
39
53
  const buildPluginKeymap = () => {
40
54
  const bindings = [];
41
55
 
42
- app.codeMirrorPlugins.forEach(plugin => {
56
+ app.codeMirrorPlugins.forEach((plugin) => {
43
57
  if (plugin && typeof plugin === "object" && plugin.extraKeys) {
44
58
  Object.entries(plugin.extraKeys).forEach(([key, run]) => {
45
59
  bindings.push({
@@ -47,7 +61,7 @@ export function createCodeEditor(app, cellData, actions) {
47
61
  run: () => {
48
62
  run();
49
63
  return true;
50
- }
64
+ },
51
65
  });
52
66
  });
53
67
  }
@@ -60,12 +74,49 @@ export function createCodeEditor(app, cellData, actions) {
60
74
  if (view) return;
61
75
 
62
76
  const shortcutKeymap = keymap.of([
77
+ {
78
+ key: "Ctrl-ArrowDown",
79
+ run: () => actions.moveToAdjacentCell(1),
80
+ },
81
+ {
82
+ key: "Ctrl-ArrowUp",
83
+ run: () => actions.moveToAdjacentCell(-1),
84
+ },
85
+ {
86
+ key: "ArrowDown",
87
+ run: (editorView) => {
88
+ const selection = editorView.state.selection.main;
89
+ const line = editorView.state.doc.lineAt(selection.head);
90
+
91
+ if (
92
+ selection.empty &&
93
+ line.number === editorView.state.doc.lines
94
+ ) {
95
+ return actions.moveToAdjacentCell(1);
96
+ }
97
+
98
+ return false;
99
+ },
100
+ },
101
+ {
102
+ key: "ArrowUp",
103
+ run: (editorView) => {
104
+ const selection = editorView.state.selection.main;
105
+ const line = editorView.state.doc.lineAt(selection.head);
106
+
107
+ if (selection.empty && line.number === 1) {
108
+ return actions.moveToAdjacentCell(-1);
109
+ }
110
+
111
+ return false;
112
+ },
113
+ },
63
114
  {
64
115
  key: "Shift-Enter",
65
116
  run: () => {
66
117
  actions.runCode(true);
67
118
  return true;
68
- }
119
+ },
69
120
  },
70
121
  {
71
122
  key: "Ctrl-Enter",
@@ -73,7 +124,7 @@ export function createCodeEditor(app, cellData, actions) {
73
124
  run: () => {
74
125
  actions.runCode(false);
75
126
  return true;
76
- }
127
+ },
77
128
  },
78
129
  {
79
130
  key: "Ctrl-Shift-d",
@@ -81,7 +132,7 @@ export function createCodeEditor(app, cellData, actions) {
81
132
  run: () => {
82
133
  app.notebookGlobalShortcuts.deleteActive();
83
134
  return true;
84
- }
135
+ },
85
136
  },
86
137
  {
87
138
  key: "Ctrl-Shift-m",
@@ -89,7 +140,7 @@ export function createCodeEditor(app, cellData, actions) {
89
140
  run: () => {
90
141
  app.notebookGlobalShortcuts.addMdBelow();
91
142
  return true;
92
- }
143
+ },
93
144
  },
94
145
  {
95
146
  key: "Ctrl-Shift-y",
@@ -97,14 +148,13 @@ export function createCodeEditor(app, cellData, actions) {
97
148
  run: () => {
98
149
  app.notebookGlobalShortcuts.addCodeBelow();
99
150
  return true;
100
- }
151
+ },
101
152
  },
102
153
  indentWithTab,
103
154
  ...buildPluginKeymap(),
104
155
  ...closeBracketsKeymap,
105
156
  ...defaultKeymap,
106
157
  ...historyKeymap,
107
-
108
158
  ]);
109
159
 
110
160
  const extraExtensions = Array.isArray(app.codeMirrorConfig)
@@ -114,39 +164,38 @@ export function createCodeEditor(app, cellData, actions) {
114
164
  const state = EditorState.create({
115
165
  doc: cellData.code,
116
166
  extensions: [
117
- lineNumbers(),
167
+ // lineNumbers(),
118
168
  highlightActiveLine(),
169
+ // EditorView.theme({
170
+ // "&": {
171
+ // fontFamily: '"Fira Code", SFMono-Regular, Consolas, monospace',
172
+ // fontSize: "13px",
173
+ // lineHeight: "17px",
174
+ // },
175
+ // ".cm-scroller, .cm-content, .cm-line, .cm-gutterElement": {
176
+ // lineHeight: "17px",
177
+ // },
178
+ // }),
119
179
 
120
180
  history(),
121
181
  bracketMatching(),
122
182
  closeBrackets(),
123
-
124
183
  syntaxHighlighting(defaultHighlightStyle),
125
-
126
184
  shortcutKeymap,
127
-
128
185
  EditorView.lineWrapping,
129
-
130
186
  indentUnit.of(" "),
131
187
  EditorState.tabSize.of(2),
132
-
133
188
  languageCompartment.of(
134
- languageFor(cellData.type, cellData.runtime, app.runtimePlugins)
135
- ),
189
+ languageFor(cellData.type, cellData.runtime, app.runtimePlugins),
190
+ ),
136
191
 
137
- themeCompartment.of(
138
- themeFor(cellData.runtime, app.runtimePlugins)
139
- ),
192
+ themeCompartment.of(themeFor(cellData.runtime, app.runtimePlugins)),
140
193
 
141
- readOnlyCompartment.of(
142
- EditorState.readOnly.of(cellData.readonly)
143
- ),
194
+ readOnlyCompartment.of(EditorState.readOnly.of(cellData.readonly)),
144
195
 
145
- editableCompartment.of(
146
- EditorView.editable.of(!cellData.readonly)
147
- ),
196
+ editableCompartment.of(EditorView.editable.of(!cellData.readonly)),
148
197
 
149
- EditorView.updateListener.of(update => {
198
+ EditorView.updateListener.of((update) => {
150
199
  if (update.docChanged) {
151
200
  cellData.code = update.state.doc.toString();
152
201
  }
@@ -155,24 +204,23 @@ themeCompartment.of(
155
204
  EditorView.domEventHandlers({
156
205
  focus: () => {
157
206
  actions.setActive();
158
- }
207
+ },
159
208
  }),
160
209
 
161
- ...extraExtensions
162
- ]
210
+ ...extraExtensions,
211
+ ],
163
212
  });
164
213
 
165
214
  view = new EditorView({
166
215
  state,
167
- parent: editorDom.element
216
+ parent: editorDom.element,
168
217
  });
169
218
 
170
219
  const lineHeight = view.defaultLineHeight || 19;
171
220
 
172
- view.dom.style.minHeight =
173
- `${Math.ceil(app.minLines * lineHeight + 8)}px`;
221
+ view.dom.style.minHeight = `${Math.ceil(app.minLines * lineHeight + 8)}px`;
174
222
 
175
- app.codeMirrorPlugins.forEach(plugin => {
223
+ app.codeMirrorPlugins.forEach((plugin) => {
176
224
  if (typeof plugin === "function") {
177
225
  plugin(view);
178
226
  }
@@ -182,10 +230,23 @@ themeCompartment.of(
182
230
  return {
183
231
  dom: editorDom.element,
184
232
 
185
- getValue: () =>
186
- view
187
- ? view.state.doc.toString()
188
- : cellData.code,
233
+ getValue: () => (view ? view.state.doc.toString() : cellData.code),
234
+
235
+ focusAt: (position) => {
236
+ requestAnimationFrame(() => {
237
+ if (!view) return;
238
+
239
+ const cursor = Math.max(
240
+ 0,
241
+ Math.min(position, view.state.doc.length),
242
+ );
243
+
244
+ view.dispatch({
245
+ selection: { anchor: cursor },
246
+ });
247
+ view.focus();
248
+ });
249
+ },
189
250
 
190
251
  sync: (isActive, isMarkdownView) => {
191
252
  if (!isMarkdownView) {
@@ -195,21 +256,25 @@ themeCompartment.of(
195
256
  view.dispatch({
196
257
  effects: [
197
258
  languageCompartment.reconfigure(
198
- languageFor(cellData.type, cellData.runtime, app.runtimePlugins)
259
+ languageFor(
260
+ cellData.type,
261
+ cellData.runtime,
262
+ app.runtimePlugins,
263
+ ),
199
264
  ),
200
265
 
201
266
  themeCompartment.reconfigure(
202
- themeFor(cellData.runtime, app.runtimePlugins)
267
+ themeFor(cellData.runtime, app.runtimePlugins),
203
268
  ),
204
269
 
205
270
  readOnlyCompartment.reconfigure(
206
- EditorState.readOnly.of(cellData.readonly)
271
+ EditorState.readOnly.of(cellData.readonly),
207
272
  ),
208
273
 
209
274
  editableCompartment.reconfigure(
210
- EditorView.editable.of(!cellData.readonly)
211
- )
212
- ]
275
+ EditorView.editable.of(!cellData.readonly),
276
+ ),
277
+ ],
213
278
  });
214
279
 
215
280
  const currentValue = view.state.doc.toString();
@@ -219,8 +284,8 @@ themeCompartment.of(
219
284
  changes: {
220
285
  from: 0,
221
286
  to: currentValue.length,
222
- insert: cellData.code
223
- }
287
+ insert: cellData.code,
288
+ },
224
289
  });
225
290
  }
226
291
  }
@@ -232,6 +297,6 @@ themeCompartment.of(
232
297
  view.destroy();
233
298
  view = null;
234
299
  }
235
- }
300
+ },
236
301
  };
237
- }
302
+ }
@@ -17,12 +17,14 @@ export const evaluateCodeAsync = async (
17
17
  importMap = {},
18
18
  plugins = [],
19
19
  runtime = null,
20
+ cdnFallback = true,
20
21
  } = {},
21
22
  scope,
22
23
  ) => {
23
24
  const validScope = scope || Object.create(null);
25
+ const importScope = Object.create(null);
24
26
 
25
- scope.console = Console
27
+ validScope.console = Console;
26
28
 
27
29
  const context = {
28
30
  TARGET,
@@ -30,6 +32,7 @@ export const evaluateCodeAsync = async (
30
32
  importMap,
31
33
  plugins,
32
34
  runtime,
35
+ cdnFallback
33
36
  };
34
37
 
35
38
  const selectedPlugin = plugins.find(
@@ -70,6 +73,8 @@ export const evaluateCodeAsync = async (
70
73
  {
71
74
  importMap,
72
75
  plugins,
76
+ cdnFallback,
77
+ importScopeIdentifier: "__imports__",
73
78
  },
74
79
  );
75
80
  // console.log(compiledCode)
@@ -77,6 +82,7 @@ export const evaluateCodeAsync = async (
77
82
  const runCell = new Function(
78
83
  "TARGET",
79
84
  "__scope__",
85
+ "__imports__",
80
86
  `
81
87
  // Bind the active TARGET for this cell evaluation
82
88
  if (typeof __Ziko__ !== "undefined" && __Ziko__.__Config__) {
@@ -89,9 +95,11 @@ export const evaluateCodeAsync = async (
89
95
  return (async () => {
90
96
  with (__scope__) {
91
97
  return await (async () => {
92
- ${compiledCode}
98
+ with (__imports__) {
99
+ ${compiledCode}
100
+ }
93
101
  })();
94
- }
102
+ }
95
103
  })();
96
104
  `,
97
105
  );
@@ -99,6 +107,7 @@ export const evaluateCodeAsync = async (
99
107
  let result = await runCell(
100
108
  TARGET,
101
109
  validScope,
110
+ importScope,
102
111
  );
103
112
 
104
113
  for (const plugin of plugins) {
@@ -1,5 +1,42 @@
1
1
 
2
2
  const CDN = "https://esm.sh/";
3
+ const MANIFEST_FILE = "ziko-notebook-imports.json";
4
+ const isProductionBuild =
5
+ typeof import.meta.env !== "undefined" && import.meta.env.PROD;
6
+
7
+ let buildImportMapPromise;
8
+
9
+ const loadBuildImportMap = () => {
10
+ if (!isProductionBuild || typeof document === "undefined") {
11
+ return Promise.resolve({});
12
+ }
13
+
14
+ if (!buildImportMapPromise) {
15
+ const manifestUrl = new URL(MANIFEST_FILE, document.baseURI);
16
+
17
+ buildImportMapPromise = fetch(manifestUrl).then(async (response) => {
18
+ if (response.status === 404) {
19
+ return {};
20
+ }
21
+
22
+ if (!response.ok) {
23
+ throw new Error(
24
+ `Failed to load the notebook import map: ${response.status} ${response.statusText}`,
25
+ );
26
+ }
27
+
28
+ const imports = await response.json();
29
+
30
+ if (!imports || typeof imports !== "object" || Array.isArray(imports)) {
31
+ throw new TypeError("The notebook import map must be a JSON object.");
32
+ }
33
+
34
+ return imports;
35
+ });
36
+ }
37
+
38
+ return buildImportMapPromise;
39
+ };
3
40
 
4
41
  const isURL = (source) =>
5
42
  /^(?:https?:\/\/|data:|blob:)/i.test(source);
@@ -16,8 +53,10 @@ export const resolveImport = async (
16
53
  importMap = {},
17
54
  plugins = [],
18
55
  resolverEndpoint = "/__notebook_resolve",
56
+ cdnFallback = true,
19
57
  } = {},
20
58
  ) => {
59
+ // Preserve explicit mappings, including package subpaths.
21
60
  if (importMap[source]) {
22
61
  return importMap[source];
23
62
  }
@@ -26,6 +65,7 @@ export const resolveImport = async (
26
65
  return source;
27
66
  }
28
67
 
68
+ // Preserve runtime-specific and custom resolution hooks.
29
69
  for (const plugin of plugins) {
30
70
  if (typeof plugin.resolveImport !== "function") {
31
71
  continue;
@@ -38,8 +78,17 @@ export const resolveImport = async (
38
78
  }
39
79
  }
40
80
 
81
+ const buildImportMap = await loadBuildImportMap();
82
+
83
+ if (typeof buildImportMap[source] === "string") {
84
+ return buildImportMap[source];
85
+ }
86
+
87
+ // Ask the host dev server to resolve installed packages.
41
88
  try {
42
- const params = new URLSearchParams({ specifier: source });
89
+ const params = new URLSearchParams({
90
+ specifier: source,
91
+ });
43
92
 
44
93
  const response = await fetch(
45
94
  `${resolverEndpoint}?${params.toString()}`,
@@ -56,9 +105,19 @@ export const resolveImport = async (
56
105
  // The Vite endpoint may not be available outside development.
57
106
  }
58
107
 
59
- return `${CDN}${source}`;
108
+ if (cdnFallback) {
109
+ return `${CDN}${source}`;
110
+ }
111
+
112
+ throw new Error(
113
+ `Cannot resolve "${source}" locally. ` +
114
+ `Install the package or provide an importMap entry. ` +
115
+ `CDN fallback is disabled.`,
116
+ );
60
117
  };
61
118
 
119
+ // Keep your existing rewriteImportNode implementation below.
120
+
62
121
  export const rewriteImportNode = async (
63
122
  node,
64
123
  code,
@@ -68,6 +127,8 @@ export const rewriteImportNode = async (
68
127
  node.source.value,
69
128
  context,
70
129
  );
130
+ const importScope =
131
+ context.importScopeIdentifier || "__scope__";
71
132
 
72
133
  // Safely quote the URL in generated JavaScript.
73
134
  const url = JSON.stringify(source);
@@ -96,7 +157,7 @@ export const rewriteImportNode = async (
96
157
 
97
158
  if (namespaceId) {
98
159
  return (
99
- `__scope__.${namespaceId} = ` +
160
+ `${importScope}.${namespaceId} = ` +
100
161
  `await import(${url});`
101
162
  );
102
163
  }
@@ -109,17 +170,19 @@ export const rewriteImportNode = async (
109
170
 
110
171
  if (defaultImportName) {
111
172
  lines.push(
112
- `__scope__.${defaultImportName} = ` +
173
+ `${importScope}.${defaultImportName} = ` +
113
174
  `${tmp}.default ?? ${tmp};`,
114
175
  );
115
176
  }
116
177
 
117
178
  for (const { imported, local } of namedImports) {
118
179
  lines.push(
119
- `__scope__.${local} = ` +
180
+ `${importScope}.${local} = ` +
120
181
  `${tmp}[${JSON.stringify(imported)}] !== undefined ` +
121
182
  `? ${tmp}[${JSON.stringify(imported)}] ` +
122
- `: (${tmp}.default ?? ${tmp});`,
183
+ `: (${tmp}.default?.[${JSON.stringify(imported)}] !== undefined ` +
184
+ `? ${tmp}.default[${JSON.stringify(imported)}] ` +
185
+ `: (${tmp}.default ?? ${tmp}));`,
123
186
  );
124
187
  }
125
188
 
@@ -17,6 +17,7 @@ export const transformImportsAndScope = async (
17
17
  importMap = {},
18
18
  plugins = [],
19
19
  resolverEndpoint,
20
+ importScopeIdentifier = "__scope__",
20
21
  } = {},
21
22
  ) => {
22
23
  try {
@@ -30,6 +31,7 @@ export const transformImportsAndScope = async (
30
31
  importMap,
31
32
  plugins,
32
33
  resolverEndpoint,
34
+ importScopeIdentifier,
33
35
  };
34
36
 
35
37
  const modifications = [];
@@ -95,6 +97,8 @@ export const transformImportsAndScope = async (
95
97
  transformedCode.slice(modification.end);
96
98
  }
97
99
 
100
+ console.log(transformedCode)
101
+
98
102
  return transformedCode;
99
103
  } catch (error) {
100
104
  if (
@@ -46,16 +46,16 @@ export function CellItem({ cellData, app, store }) {
46
46
  runtimePlugins: app.runtimePlugins
47
47
  });
48
48
 
49
- const outputGrid = div({ class: "cell-output-grid" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
50
- const inputGrid = div({ class: "cell-input-grid" }, inPrompt, bodySlot, controlsEl);
51
- const root = div({ class: "cell" }, inputGrid, outputGrid);
49
+ const inputRow = div({ class: "cell-input-row" }, inPrompt, bodySlot);
50
+ const outputRow = div({ class: "cell-output-row" }, outPrompt, div({ class: "output-wrapper" }, outputRef));
51
+ const root = div({ class: "cell" }, inputRow, controlsEl, outputRow);
52
52
  root.onClick(() => app.setActiveCell(cellData.id));
53
53
 
54
54
  cellData._dom = root.element;
55
55
  cellData._inPrompt = inPrompt.element;
56
56
  cellData._outPrompt = outPrompt.element;
57
57
  cellData._bodySlot = bodySlot.element;
58
- cellData._outputGrid = outputGrid.element;
58
+ cellData._outputRow = outputRow.element;
59
59
  cellData._setIn = setIn;
60
60
 
61
61
  app._refreshCell(cellData);
@@ -1,7 +1,8 @@
1
1
  /* --- MARKDOWN --- */
2
2
 
3
3
  .ziko-notebook .markdown-rendered-cell {
4
- grid-column: 1 / 3;
4
+ flex: 1 1 0;
5
+ min-width: 0;
5
6
  padding: 8px 12px;
6
7
  cursor: pointer;
7
8
  min-height: 38px;
@@ -33,7 +33,9 @@ export function MarkdownView({
33
33
  content
34
34
  );
35
35
 
36
- wrap.onClick(() => actions.editMarkdown());
36
+ wrap.element.addEventListener("dblclick", () =>
37
+ actions.editMarkdown()
38
+ );
37
39
 
38
40
  return wrap.element;
39
41
  }
package/src/index.css CHANGED
@@ -23,6 +23,10 @@
23
23
  display: flex;
24
24
  flex-direction: column;
25
25
  gap: 12px;
26
+ overflow: auto;
27
+ /* max-height: 100vh; */
28
+ /* scrollbar-color: rebeccapurple transparent; */
29
+ /* scrollbar-color: var(--prompt-in) transparent; */
26
30
  }
27
31
 
28
32
  .ziko-notebook .cell {
@@ -32,7 +36,7 @@
32
36
  border-left: 4px solid transparent;
33
37
  border-radius: 6px;
34
38
  padding: 8px;
35
- gap: 8px;
39
+ gap: 2px;
36
40
  background: #ffffff;
37
41
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
38
42
  position: relative;
@@ -43,17 +47,15 @@
43
47
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
44
48
  }
45
49
 
46
- .ziko-notebook .cell-input-grid {
47
- display: grid;
48
- grid-template-columns: 56px 1fr auto;
50
+ .ziko-notebook .cell-input-row {
51
+ display: flex;
49
52
  align-items: flex-start;
50
53
  gap: 8px;
51
54
  width: 100%;
52
55
  }
53
56
 
54
- .ziko-notebook .cell-output-grid {
55
- display: grid;
56
- grid-template-columns: 56px 1fr;
57
+ .ziko-notebook .cell-output-row {
58
+ display: flex;
57
59
  align-items: flex-start;
58
60
  gap: 8px;
59
61
  width: 100%;
@@ -81,7 +83,9 @@
81
83
  border-radius: 4px;
82
84
  background: var(--bg-editor);
83
85
  overflow: hidden;
84
- width: 100%;
86
+ flex: 1 1 0;
87
+ min-width: 0;
88
+ width: auto;
85
89
  transition: border-color 0.15s ease;
86
90
  }
87
91
 
@@ -111,13 +115,10 @@
111
115
  /* align-items: center; */
112
116
  align-items: baseline;
113
117
  justify-content: flex-end;
114
- grid-column: 1 / -1;
115
- gap: 4px;
116
- flex-shrink: 0;
118
+ gap: 2px;
117
119
  flex-wrap: wrap;
118
- /* border-bottom: 1px solid #f1f5f9; */
119
- padding-bottom: 6px;
120
- margin-bottom: 2px;
120
+ margin: 0;
121
+ padding: 0;
121
122
  }
122
123
 
123
124
  .ziko-notebook .cm-line{
@@ -127,7 +128,9 @@
127
128
  /* --- OUTPUT --- */
128
129
 
129
130
  .ziko-notebook .output-wrapper {
130
- width: 100%;
131
+ flex: 1 1 0;
132
+ min-width: 0;
133
+ width: auto;
131
134
  padding: 4px 0;
132
135
  }
133
136
 
@@ -151,17 +154,23 @@
151
154
  align-items: baseline;
152
155
  }
153
156
 
154
- .ziko-notebook .cell-input-grid {
155
- grid-template-columns: 42px 1fr;
156
- grid-template-rows: auto auto;
157
- }
158
-
159
- .ziko-notebook .cell-output-grid {
160
- grid-template-columns: 42px 1fr;
161
- }
162
-
163
157
  .ziko-notebook .prompt {
164
158
  font-size: 10px;
165
159
  padding-top: 6px;
166
160
  }
167
- }
161
+ }
162
+
163
+
164
+ /* TEMP */
165
+
166
+ .ziko-notebook .cm-editor{
167
+ margin: 1px;
168
+ outline: none;
169
+ }
170
+ .ziko-notebook .cm-scroller{
171
+ margin: 1px;
172
+ outline: none;
173
+ }
174
+ .ziko-notebook .cm-line{
175
+ margin: 1px;
176
+ }
package/src/notebook.js CHANGED
@@ -33,6 +33,7 @@ export class UINotebook extends UIElement {
33
33
  constructor({
34
34
  cells: initialCells = [],
35
35
  importMap = {},
36
+ cdnFallback = true,
36
37
  runCells = true,
37
38
  minLines = 3,
38
39
  maxCells = Infinity,
@@ -58,6 +59,7 @@ export class UINotebook extends UIElement {
58
59
  this.resetScope();
59
60
 
60
61
  this.importMap = importMap;
62
+ this.cdnFallback = cdnFallback;
61
63
  this.runtimePlugins = runtimePlugins;
62
64
  this.codeMirrorConfig = codeMirrorConfig;
63
65
  this.codeMirrorPlugins = codeMirrorPlugins;
@@ -224,7 +226,7 @@ export class UINotebook extends UIElement {
224
226
  _inPrompt: null,
225
227
  _outPrompt: null,
226
228
  _bodySlot: null,
227
- _outputGrid: null,
229
+ _outputRow: null,
228
230
  _outputRef: null,
229
231
  };
230
232
  });
@@ -312,6 +314,23 @@ export class UINotebook extends UIElement {
312
314
  return {
313
315
  setActive: () => app.setActiveCell(cellData.id),
314
316
 
317
+ moveToAdjacentCell: (direction) => {
318
+ const cells = app.store.get("cells");
319
+ const index = cells.findIndex(
320
+ (cell) => cell.id === cellData.id
321
+ );
322
+ const target = cells[index + direction];
323
+
324
+ if (!target) return false;
325
+
326
+ app.focusCell(target.id);
327
+ target._editor.focusAt(
328
+ direction > 0 ? 0 : target._editor.getValue().length
329
+ );
330
+
331
+ return true;
332
+ },
333
+
315
334
  runCode: async (autoNext = true) => {
316
335
  // If this cell's code is still loading, wait for it.
317
336
  await app._resolveCellCode(cellData);
@@ -349,6 +368,7 @@ export class UINotebook extends UIElement {
349
368
  cellData._outputRef,
350
369
  {
351
370
  importMap: app.importMap,
371
+ cdnFallback: app.cdnFallback,
352
372
  plugins: app.runtimePlugins,
353
373
  runtime: cellData.runtime,
354
374
  },
@@ -484,8 +504,8 @@ export class UINotebook extends UIElement {
484
504
  const showOutput =
485
505
  cellData.type === "code" && cellData.hasDomOutput;
486
506
 
487
- cellData._outputGrid.style.display =
488
- showOutput ? "grid" : "none";
507
+ cellData._outputRow.style.display =
508
+ showOutput ? "flex" : "none";
489
509
 
490
510
  cellData._outPrompt.textContent = `Out ${countText}:`;
491
511
  }
@@ -542,7 +562,11 @@ export class UINotebook extends UIElement {
542
562
  }
543
563
 
544
564
  this.store.set("activeCellId", id);
545
- this._refreshCell(cell);
565
+ cells.forEach((currentCell) => {
566
+ if (currentCell._dom) {
567
+ this._refreshCell(currentCell);
568
+ }
569
+ });
546
570
 
547
571
  // Scroll the target cell into view when it is outside the viewport.
548
572
  requestAnimationFrame(() => {
@@ -609,7 +633,7 @@ addCell(type = "code", initialCode = null, afterId = null, readonly = false) {
609
633
  _inPrompt: null,
610
634
  _outPrompt: null,
611
635
  _bodySlot: null,
612
- _outputGrid: null,
636
+ _outputRow: null,
613
637
  _outputRef: null,
614
638
  };
615
639