@marimo-team/islands 0.23.14-dev39 → 0.23.14-dev45
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/dist/{chat-ui-CO8WuXGb.js → chat-ui-DXPRhRO2.js} +3132 -3132
- package/dist/{code-visibility-CqDnWUEQ.js → code-visibility-D-7iipEs.js} +717 -708
- package/dist/{html-to-image-DLSOS-bE.js → html-to-image-DOqQBSQc.js} +2236 -2208
- package/dist/main.js +998 -987
- package/dist/{process-output-B9ysqk1y.js → process-output-kGk2Jc2x.js} +1 -1
- package/dist/{reveal-component-BtwLhHQU.js → reveal-component-BrJ-d3iB.js} +2 -2
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/app-config/user-config-form.tsx +26 -0
- package/src/components/debugger/debugger-code.tsx +7 -2
- package/src/components/editor/code/readonly-python-code.tsx +12 -4
- package/src/components/editor/notebook-cell.tsx +7 -0
- package/src/components/editor/output/MarimoTracebackOutput.tsx +18 -1
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/cell.ts +5 -3
- package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
- package/src/core/codemirror/cells/debugger-state.ts +92 -0
- package/src/core/codemirror/cells/extensions.ts +22 -0
- package/src/core/config/feature-flag.tsx +2 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +4 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +9 -0
- package/src/plugins/core/RenderHTML.tsx +35 -1
- package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
|
@@ -49,6 +49,7 @@ export function createErrorToastingRequests(
|
|
|
49
49
|
sendListFiles: "Failed to list files",
|
|
50
50
|
sendSearchFiles: "Failed to search files",
|
|
51
51
|
sendPdb: "Failed to start debug session",
|
|
52
|
+
sendSetBreakpoints: "", // No toast
|
|
52
53
|
sendCreateFileOrFolder: "Failed to create file or folder",
|
|
53
54
|
sendDeleteFileOrFolder: "Failed to delete file or folder",
|
|
54
55
|
sendCopyFileOrFolder: "Failed to duplicate file or folder",
|
|
@@ -66,6 +66,7 @@ export type ListDataSourceConnectionRequest =
|
|
|
66
66
|
schemas["ListDataSourceConnectionRequest"];
|
|
67
67
|
export type ValidateSQLRequest = schemas["ValidateSQLRequest"];
|
|
68
68
|
export type DebugCellRequest = schemas["DebugCellRequest"];
|
|
69
|
+
export type SetBreakpointsRequest = schemas["SetBreakpointsRequest"];
|
|
69
70
|
export type ReadCodeResponse = schemas["ReadCodeResponse"];
|
|
70
71
|
export type RecentFilesResponse = schemas["RecentFilesResponse"];
|
|
71
72
|
export type RenameNotebookRequest = schemas["RenameNotebookRequest"];
|
|
@@ -172,6 +173,7 @@ export interface EditRequests {
|
|
|
172
173
|
getUsageStats: () => Promise<UsageResponse>;
|
|
173
174
|
// Debugger
|
|
174
175
|
sendPdb: (request: DebugCellRequest) => Promise<null>;
|
|
176
|
+
sendSetBreakpoints: (request: SetBreakpointsRequest) => Promise<null>;
|
|
175
177
|
// File explorer requests
|
|
176
178
|
sendListFiles: (request: FileListRequest) => Promise<FileListResponse>;
|
|
177
179
|
sendSearchFiles: (request: FileSearchRequest) => Promise<FileSearchResponse>;
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -257,6 +257,10 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
257
257
|
throwNotImplemented();
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
+
sendSetBreakpoints: EditRequests["sendSetBreakpoints"] = async () => {
|
|
261
|
+
throwNotImplemented();
|
|
262
|
+
};
|
|
263
|
+
|
|
260
264
|
sendRun: EditRequests["sendRun"] = async (request) => {
|
|
261
265
|
await this.rpc.proxy.request.loadPackages(request.codes.join("\n"));
|
|
262
266
|
|
|
@@ -32,6 +32,7 @@ import { cacheInfoAtom } from "../cache/requests";
|
|
|
32
32
|
import { SCRATCH_CELL_ID } from "../cells/ids";
|
|
33
33
|
import { useRunsActions } from "../cells/runs";
|
|
34
34
|
import { focusAndScrollCellOutputIntoView } from "../cells/scrollCellIntoView";
|
|
35
|
+
import { debuggerCurrentLineAtom } from "../codemirror/cells/debugger-state";
|
|
35
36
|
import type { CellData } from "../cells/types";
|
|
36
37
|
import { capabilitiesAtom } from "../config/capabilities";
|
|
37
38
|
import { useSetAppConfig } from "../config/config";
|
|
@@ -196,6 +197,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
196
197
|
const runtimeManager = useRuntimeManager();
|
|
197
198
|
const setCacheInfo = useSetAtom(cacheInfoAtom);
|
|
198
199
|
const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
|
|
200
|
+
const setDebuggerCurrentLine = useSetAtom(debuggerCurrentLineAtom);
|
|
199
201
|
const {
|
|
200
202
|
setNamespaces: setStorageNamespaces,
|
|
201
203
|
filterFromVariables: filterStorageFromVariables,
|
|
@@ -392,6 +394,13 @@ export function useMarimoKernelConnection(opts: {
|
|
|
392
394
|
case "focus-cell":
|
|
393
395
|
focusAndScrollCellOutputIntoView(msg.data.cell_id);
|
|
394
396
|
return;
|
|
397
|
+
case "active-line":
|
|
398
|
+
setDebuggerCurrentLine(
|
|
399
|
+
msg.data.line == null
|
|
400
|
+
? null
|
|
401
|
+
: { cellId: msg.data.cell_id, line: msg.data.line },
|
|
402
|
+
);
|
|
403
|
+
return;
|
|
395
404
|
case "notebook-document-transaction":
|
|
396
405
|
handleDocumentTransaction(msg.data.transaction);
|
|
397
406
|
return;
|
|
@@ -199,6 +199,37 @@ const keyImagesBySrc: TransformFn = (
|
|
|
199
199
|
return cloneElement(reactNode, { key: `${src}-${index}` });
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
// Decorator: applies a checked-state-based key to task-list checkbox
|
|
203
|
+
// <input> elements (e.g. GFM `- [ ]` / `- [x]` rendered by
|
|
204
|
+
// pymdownx.tasklist) so they remount when their checked-ness changes.
|
|
205
|
+
//
|
|
206
|
+
// pymdownx.tasklist omits the `checked` HTML attribute entirely for
|
|
207
|
+
// unchecked items (rather than emitting checked="false"). When markdown
|
|
208
|
+
// edits shift which item lands at a given tree position (e.g. deleting
|
|
209
|
+
// completed items above unchecked ones), reusing the same DOM <input>
|
|
210
|
+
// across a checked -> unchecked item is a controlled -> uncontrolled prop
|
|
211
|
+
// transition for React: React stops managing the DOM `checked` property in
|
|
212
|
+
// that transition instead of resetting it, leaving the previous item's
|
|
213
|
+
// checked state visually stuck until a full remount. Baking checked-ness
|
|
214
|
+
// into the key forces a fresh node instead of reusing the stale one.
|
|
215
|
+
const keyTaskListCheckboxesByChecked: TransformFn = (
|
|
216
|
+
reactNode: ReactNode,
|
|
217
|
+
domNode: DOMNode,
|
|
218
|
+
index: number,
|
|
219
|
+
): JSX.Element | undefined => {
|
|
220
|
+
if (!(domNode instanceof Element) || domNode.name !== "input") {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
if (domNode.attribs?.type !== "checkbox") {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
if (!isValidElement(reactNode)) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const checked = "checked" in domNode.attribs;
|
|
230
|
+
return cloneElement(reactNode, { key: `checkbox-${index}-${checked}` });
|
|
231
|
+
};
|
|
232
|
+
|
|
202
233
|
// Wrap elements with data-marimo-doc attribute in a DocHoverTarget
|
|
203
234
|
const wrapDocHoverTargets: TransformFn = (
|
|
204
235
|
reactNode: ReactNode,
|
|
@@ -326,7 +357,10 @@ function parseHtml({
|
|
|
326
357
|
// and may further wrap/clone it. Used for cross-cutting concerns that
|
|
327
358
|
// should apply regardless of which (if any) match-and-replace transform
|
|
328
359
|
// ran above.
|
|
329
|
-
const decoratorFunctions: TransformFn[] = [
|
|
360
|
+
const decoratorFunctions: TransformFn[] = [
|
|
361
|
+
keyImagesBySrc,
|
|
362
|
+
keyTaskListCheckboxesByChecked,
|
|
363
|
+
];
|
|
330
364
|
|
|
331
365
|
return parse(html, {
|
|
332
366
|
replace: (domNode: DOMNode, index: number) => {
|
|
@@ -114,6 +114,50 @@ describe("parseHtml", () => {
|
|
|
114
114
|
expect(result.key).toBe("https://cdn.example.com/b.png-0");
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
test("checked task-list checkbox is keyed by index and checked state", () => {
|
|
118
|
+
const html = '<li><input type="checkbox" checked disabled> item</li>';
|
|
119
|
+
const result = parseHtml({ html }) as React.ReactElement<{
|
|
120
|
+
children: React.ReactElement[];
|
|
121
|
+
}>;
|
|
122
|
+
const checkbox = result.props.children[0];
|
|
123
|
+
expect(checkbox.key).toBe("checkbox-0-true");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("unchecked task-list checkbox is keyed by index and checked state", () => {
|
|
127
|
+
const html = '<li><input type="checkbox" disabled> item</li>';
|
|
128
|
+
const result = parseHtml({ html }) as React.ReactElement<{
|
|
129
|
+
children: React.ReactElement[];
|
|
130
|
+
}>;
|
|
131
|
+
const checkbox = result.props.children[0];
|
|
132
|
+
expect(checkbox.key).toBe("checkbox-0-false");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("task list: item reused at same position gets a new key when checked state flips", () => {
|
|
136
|
+
// Simulates editing markdown so an unchecked item now sits where a
|
|
137
|
+
// checked item used to be (e.g. an earlier checked item was deleted).
|
|
138
|
+
const before = parseHtml({
|
|
139
|
+
html: '<ul><li><input type="checkbox" checked disabled> a</li></ul>',
|
|
140
|
+
}) as React.ReactElement<{ children: React.ReactElement }>;
|
|
141
|
+
const after = parseHtml({
|
|
142
|
+
html: '<ul><li><input type="checkbox" disabled> b</li></ul>',
|
|
143
|
+
}) as React.ReactElement<{ children: React.ReactElement }>;
|
|
144
|
+
const beforeLi = before.props.children as React.ReactElement<{
|
|
145
|
+
children: React.ReactElement[];
|
|
146
|
+
}>;
|
|
147
|
+
const afterLi = after.props.children as React.ReactElement<{
|
|
148
|
+
children: React.ReactElement[];
|
|
149
|
+
}>;
|
|
150
|
+
const beforeKey = beforeLi.props.children[0].key;
|
|
151
|
+
const afterKey = afterLi.props.children[0].key;
|
|
152
|
+
expect(beforeKey).not.toBe(afterKey);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("non-checkbox input is left alone", () => {
|
|
156
|
+
const html = '<input type="text" value="hi">';
|
|
157
|
+
const result = parseHtml({ html }) as React.ReactElement;
|
|
158
|
+
expect(result.key).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
117
161
|
test("codehilite with copy button", () => {
|
|
118
162
|
const html =
|
|
119
163
|
'<div class="codehilite"><pre><code>console.log("Hello");</code></pre></div>';
|