@marimo-team/islands 0.21.2-dev57 → 0.21.2-dev59
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/{ConnectedDataExplorerComponent-D0GoOd_c.js → ConnectedDataExplorerComponent-DrWDbHRV.js} +1 -1
- package/dist/main.js +243 -16
- package/dist/{spec-Bfvf9Hre.js → spec-oVDndBz4.js} +25 -16
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/editor/file-tree/file-explorer.tsx +16 -2
- package/src/components/editor/file-tree/file-viewer.tsx +17 -3
- package/src/core/cells/__tests__/apply-transaction.test.ts +279 -0
- package/src/core/cells/__tests__/cells.test.ts +6 -0
- package/src/core/cells/__tests__/document-changes.test.ts +575 -0
- package/src/core/cells/__tests__/document-roundtrip.test.ts +376 -0
- package/src/core/cells/cells.ts +28 -3
- package/src/core/cells/document-changes.ts +644 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +2 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +9 -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 +5 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +19 -1
- package/src/utils/createReducer.ts +26 -11
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Round-trip tests: perform actions on a primary notebook (producing changes
|
|
5
|
+
* via the middleware), then apply those changes to a replica notebook via
|
|
6
|
+
* applyTransactionChanges. The two should converge to identical document state.
|
|
7
|
+
*
|
|
8
|
+
* This catches drift between what the middleware emits and what
|
|
9
|
+
* apply-transaction consumes.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { python } from "@codemirror/lang-python";
|
|
13
|
+
import { EditorState } from "@codemirror/state";
|
|
14
|
+
import { EditorView } from "@codemirror/view";
|
|
15
|
+
import {
|
|
16
|
+
afterAll,
|
|
17
|
+
afterEach,
|
|
18
|
+
beforeAll,
|
|
19
|
+
beforeEach,
|
|
20
|
+
describe,
|
|
21
|
+
expect,
|
|
22
|
+
it,
|
|
23
|
+
} from "vitest";
|
|
24
|
+
import { cellId } from "@/__tests__/branded";
|
|
25
|
+
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
26
|
+
import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
|
|
27
|
+
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
28
|
+
import { MultiColumn } from "@/utils/id-tree";
|
|
29
|
+
import { exportedForTesting, type NotebookState } from "../cells";
|
|
30
|
+
import {
|
|
31
|
+
applyTransactionChanges,
|
|
32
|
+
exportedForTesting as middlewareExports,
|
|
33
|
+
} from "../document-changes";
|
|
34
|
+
import { CellId } from "../ids";
|
|
35
|
+
|
|
36
|
+
const { initialNotebookState, reducer, createActions } = exportedForTesting;
|
|
37
|
+
const { drainChanges } = middlewareExports;
|
|
38
|
+
|
|
39
|
+
function createEditor(code: string) {
|
|
40
|
+
const state = EditorState.create({
|
|
41
|
+
doc: code,
|
|
42
|
+
extensions: [
|
|
43
|
+
python(),
|
|
44
|
+
adaptiveLanguageConfiguration({
|
|
45
|
+
cellId: cellId("cell1"),
|
|
46
|
+
completionConfig: {
|
|
47
|
+
activate_on_typing: true,
|
|
48
|
+
signature_hint_on_typing: false,
|
|
49
|
+
copilot: false,
|
|
50
|
+
codeium_api_key: null,
|
|
51
|
+
},
|
|
52
|
+
hotkeys: new OverridingHotkeyProvider({}),
|
|
53
|
+
placeholderType: "marimo-import",
|
|
54
|
+
lspConfig: {},
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
return new EditorView({ state, parent: document.body });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Primary notebook: performs actions, middleware produces changes ---
|
|
62
|
+
|
|
63
|
+
let primary: NotebookState;
|
|
64
|
+
|
|
65
|
+
const primaryActions = createActions((action) => {
|
|
66
|
+
primary = reducer(primary, action);
|
|
67
|
+
for (const [cellIdString, handle] of Object.entries(primary.cellHandles)) {
|
|
68
|
+
// @ts-expect-error - Object.entries doesn't know keys are CellId
|
|
69
|
+
const cid: CellId = cellIdString;
|
|
70
|
+
if (!handle.current) {
|
|
71
|
+
const view = createEditor(primary.cellData[cid].code);
|
|
72
|
+
const h: CellHandle = { editorView: view, editorViewOrNull: view };
|
|
73
|
+
primary.cellHandles[cid] = { current: h };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// --- Replica notebook: receives changes via applyTransactionChanges ---
|
|
79
|
+
|
|
80
|
+
let replica: NotebookState;
|
|
81
|
+
|
|
82
|
+
const replicaActions = createActions((action) => {
|
|
83
|
+
replica = reducer(replica, action);
|
|
84
|
+
for (const [cellIdString, handle] of Object.entries(replica.cellHandles)) {
|
|
85
|
+
// @ts-expect-error - Object.entries doesn't know keys are CellId
|
|
86
|
+
const cid: CellId = cellIdString;
|
|
87
|
+
if (!handle.current) {
|
|
88
|
+
const view = createEditor(replica.cellData[cid].code);
|
|
89
|
+
const h: CellHandle = { editorView: view, editorViewOrNull: view };
|
|
90
|
+
replica.cellHandles[cid] = { current: h };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let i = 0;
|
|
96
|
+
const originalCreate = CellId.create.bind(CellId);
|
|
97
|
+
|
|
98
|
+
beforeAll(() => {
|
|
99
|
+
CellId.create = () => cellId(`${i++}`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
i = 0;
|
|
104
|
+
primary = initialNotebookState();
|
|
105
|
+
primary.cellIds = MultiColumn.from([]);
|
|
106
|
+
drainChanges();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
middlewareExports.cancelPendingChanges();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterAll(() => {
|
|
114
|
+
CellId.create = originalCreate;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/** Set up both notebooks with the same initial cells. */
|
|
118
|
+
function setup(...codes: string[]) {
|
|
119
|
+
for (const code of codes) {
|
|
120
|
+
primaryActions.createNewCell({
|
|
121
|
+
cellId: "__end__",
|
|
122
|
+
before: false,
|
|
123
|
+
code,
|
|
124
|
+
newCellId: CellId.create(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply the setup changes to the replica so both start identical
|
|
129
|
+
const setupChanges = drainChanges();
|
|
130
|
+
replica = initialNotebookState();
|
|
131
|
+
replica.cellIds = MultiColumn.from([]);
|
|
132
|
+
applyTransactionChanges(
|
|
133
|
+
setupChanges,
|
|
134
|
+
replicaActions,
|
|
135
|
+
() => replica.cellIds.inOrderIds,
|
|
136
|
+
);
|
|
137
|
+
// Drain any changes the replica's middleware produced
|
|
138
|
+
drainChanges();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Drain changes from the primary's middleware and apply them to the replica.
|
|
143
|
+
*/
|
|
144
|
+
function sync() {
|
|
145
|
+
const changes = drainChanges();
|
|
146
|
+
applyTransactionChanges(
|
|
147
|
+
changes,
|
|
148
|
+
replicaActions,
|
|
149
|
+
() => replica.cellIds.inOrderIds,
|
|
150
|
+
);
|
|
151
|
+
// Drain any changes the replica's middleware produced (we don't want those)
|
|
152
|
+
drainChanges();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extract the document-relevant state: cell ordering, code, name, config.
|
|
157
|
+
* This is the "NotebookDocument" equivalent — what the Python side tracks.
|
|
158
|
+
*
|
|
159
|
+
* TODO(column-config): config.column is excluded because the column
|
|
160
|
+
* reducers (addColumnBreakpoint, dropOverNewColumn, moveColumn, etc.)
|
|
161
|
+
* update cellIds (MultiColumn structure) but don't sync config.column
|
|
162
|
+
* on affected cells. The middleware correctly emits set-config changes with
|
|
163
|
+
* the new column index, but the primary's config.column stays stale,
|
|
164
|
+
* causing a mismatch with the replica. Fix: have the column reducers
|
|
165
|
+
* update config.column as part of their state transition, then remove
|
|
166
|
+
* the { column: _, ...config } exclusion here.
|
|
167
|
+
*/
|
|
168
|
+
function documentSnapshot(state: NotebookState) {
|
|
169
|
+
return state.cellIds.inOrderIds.map((id) => {
|
|
170
|
+
const { column: _, ...config } = state.cellData[id].config;
|
|
171
|
+
return {
|
|
172
|
+
id,
|
|
173
|
+
code: state.cellData[id].code,
|
|
174
|
+
name: state.cellData[id].name,
|
|
175
|
+
config,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
describe("document round-trip", () => {
|
|
181
|
+
it("initial setup converges", () => {
|
|
182
|
+
setup("a", "b", "c");
|
|
183
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("createNewCell at end", () => {
|
|
187
|
+
setup("a", "b");
|
|
188
|
+
primaryActions.createNewCell({
|
|
189
|
+
cellId: "__end__",
|
|
190
|
+
before: false,
|
|
191
|
+
code: "c",
|
|
192
|
+
newCellId: CellId.create(),
|
|
193
|
+
});
|
|
194
|
+
sync();
|
|
195
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("createNewCell before first cell", () => {
|
|
199
|
+
setup("a", "b");
|
|
200
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
201
|
+
primaryActions.createNewCell({
|
|
202
|
+
cellId: a,
|
|
203
|
+
before: true,
|
|
204
|
+
code: "before-a",
|
|
205
|
+
newCellId: CellId.create(),
|
|
206
|
+
});
|
|
207
|
+
sync();
|
|
208
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("createNewCell between cells", () => {
|
|
212
|
+
setup("a", "b", "c");
|
|
213
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
214
|
+
primaryActions.createNewCell({
|
|
215
|
+
cellId: a,
|
|
216
|
+
before: false,
|
|
217
|
+
code: "between",
|
|
218
|
+
newCellId: CellId.create(),
|
|
219
|
+
});
|
|
220
|
+
sync();
|
|
221
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("createNewCell with hideCode config", () => {
|
|
225
|
+
setup("a");
|
|
226
|
+
primaryActions.createNewCell({
|
|
227
|
+
cellId: "__end__",
|
|
228
|
+
before: false,
|
|
229
|
+
code: "hidden",
|
|
230
|
+
newCellId: CellId.create(),
|
|
231
|
+
hideCode: true,
|
|
232
|
+
});
|
|
233
|
+
sync();
|
|
234
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("deleteCell", () => {
|
|
238
|
+
setup("a", "b", "c");
|
|
239
|
+
const [, b] = primary.cellIds.inOrderIds;
|
|
240
|
+
primaryActions.deleteCell({ cellId: b });
|
|
241
|
+
sync();
|
|
242
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("updateCellCode", () => {
|
|
246
|
+
setup("a", "b");
|
|
247
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
248
|
+
primaryActions.updateCellCode({
|
|
249
|
+
cellId: a,
|
|
250
|
+
code: "updated",
|
|
251
|
+
formattingChange: false,
|
|
252
|
+
});
|
|
253
|
+
sync();
|
|
254
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("updateCellName", () => {
|
|
258
|
+
setup("a");
|
|
259
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
260
|
+
primaryActions.updateCellName({ cellId: a, name: "my_var" });
|
|
261
|
+
sync();
|
|
262
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("updateCellConfig", () => {
|
|
266
|
+
setup("a");
|
|
267
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
268
|
+
primaryActions.updateCellConfig({
|
|
269
|
+
cellId: a,
|
|
270
|
+
config: { hide_code: true, disabled: true },
|
|
271
|
+
});
|
|
272
|
+
sync();
|
|
273
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("moveCell down", () => {
|
|
277
|
+
setup("a", "b", "c");
|
|
278
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
279
|
+
primaryActions.moveCell({ cellId: a, before: false });
|
|
280
|
+
sync();
|
|
281
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("sendToTop", () => {
|
|
285
|
+
setup("a", "b", "c");
|
|
286
|
+
const c = primary.cellIds.inOrderIds[2];
|
|
287
|
+
primaryActions.sendToTop({ cellId: c });
|
|
288
|
+
sync();
|
|
289
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("sendToBottom", () => {
|
|
293
|
+
setup("a", "b", "c");
|
|
294
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
295
|
+
primaryActions.sendToBottom({ cellId: a });
|
|
296
|
+
sync();
|
|
297
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("dropCellOverCell", () => {
|
|
301
|
+
setup("a", "b", "c");
|
|
302
|
+
const [a, , c] = primary.cellIds.inOrderIds;
|
|
303
|
+
primaryActions.dropCellOverCell({ cellId: c, overCellId: a });
|
|
304
|
+
sync();
|
|
305
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("multiple operations in sequence", () => {
|
|
309
|
+
setup("a", "b", "c");
|
|
310
|
+
|
|
311
|
+
// Add a cell
|
|
312
|
+
const [a] = primary.cellIds.inOrderIds;
|
|
313
|
+
primaryActions.createNewCell({
|
|
314
|
+
cellId: a,
|
|
315
|
+
before: false,
|
|
316
|
+
code: "new",
|
|
317
|
+
newCellId: CellId.create(),
|
|
318
|
+
});
|
|
319
|
+
sync();
|
|
320
|
+
|
|
321
|
+
// Rename it
|
|
322
|
+
const newId = primary.cellIds.inOrderIds[1];
|
|
323
|
+
primaryActions.updateCellName({ cellId: newId, name: "inserted" });
|
|
324
|
+
sync();
|
|
325
|
+
|
|
326
|
+
// Move it to top
|
|
327
|
+
primaryActions.sendToTop({ cellId: newId });
|
|
328
|
+
sync();
|
|
329
|
+
|
|
330
|
+
// Update code on another cell
|
|
331
|
+
const last =
|
|
332
|
+
primary.cellIds.inOrderIds[primary.cellIds.inOrderIds.length - 1];
|
|
333
|
+
primaryActions.updateCellCode({
|
|
334
|
+
cellId: last,
|
|
335
|
+
code: "modified",
|
|
336
|
+
formattingChange: false,
|
|
337
|
+
});
|
|
338
|
+
sync();
|
|
339
|
+
|
|
340
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("create then delete", () => {
|
|
344
|
+
setup("a", "b");
|
|
345
|
+
primaryActions.createNewCell({
|
|
346
|
+
cellId: "__end__",
|
|
347
|
+
before: false,
|
|
348
|
+
code: "temporary",
|
|
349
|
+
newCellId: CellId.create(),
|
|
350
|
+
});
|
|
351
|
+
sync();
|
|
352
|
+
|
|
353
|
+
const newId =
|
|
354
|
+
primary.cellIds.inOrderIds[primary.cellIds.inOrderIds.length - 1];
|
|
355
|
+
primaryActions.deleteCell({ cellId: newId });
|
|
356
|
+
sync();
|
|
357
|
+
|
|
358
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("addColumnBreakpoint", () => {
|
|
362
|
+
setup("a", "b", "c");
|
|
363
|
+
const [, b] = primary.cellIds.inOrderIds;
|
|
364
|
+
primaryActions.addColumnBreakpoint({ cellId: b });
|
|
365
|
+
sync();
|
|
366
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("dropOverNewColumn", () => {
|
|
370
|
+
setup("a", "b", "c");
|
|
371
|
+
const [, b] = primary.cellIds.inOrderIds;
|
|
372
|
+
primaryActions.dropOverNewColumn({ cellId: b });
|
|
373
|
+
sync();
|
|
374
|
+
expect(documentSnapshot(primary)).toEqual(documentSnapshot(replica));
|
|
375
|
+
});
|
|
376
|
+
});
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -29,6 +29,7 @@ import type { CellConfig } from "../network/types";
|
|
|
29
29
|
import { isRtcEnabled } from "../rtc/state";
|
|
30
30
|
import { createDeepEqualAtom, store } from "../state/jotai";
|
|
31
31
|
import { prepareCellForExecution, transitionCell } from "./cell";
|
|
32
|
+
import { documentTransactionMiddleware } from "./document-changes";
|
|
32
33
|
import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "./ids";
|
|
33
34
|
import { type CellLog, getCellLogsForMessage } from "./logs";
|
|
34
35
|
import {
|
|
@@ -174,6 +175,7 @@ export interface CreateNewCellAction {
|
|
|
174
175
|
*/
|
|
175
176
|
const {
|
|
176
177
|
reducer,
|
|
178
|
+
addMiddleware,
|
|
177
179
|
createActions,
|
|
178
180
|
useActions,
|
|
179
181
|
valueAtom: notebookAtom,
|
|
@@ -756,13 +758,28 @@ const {
|
|
|
756
758
|
},
|
|
757
759
|
handleCellMessage: (state, message: CellMessage) => {
|
|
758
760
|
const cellId = message.cell_id;
|
|
759
|
-
|
|
761
|
+
let nextState = updateCellRuntimeState({
|
|
760
762
|
state,
|
|
761
763
|
cellId,
|
|
762
764
|
cellReducer: (cell) => {
|
|
763
765
|
return transitionCell(cell, message);
|
|
764
766
|
},
|
|
765
767
|
});
|
|
768
|
+
// When a cell is queued for execution, snapshot the current code
|
|
769
|
+
// as lastCodeRun. This clears staleness for cells executed by the
|
|
770
|
+
// kernel (e.g. via code_mode). If the user edits during execution,
|
|
771
|
+
// code !== lastCodeRun keeps the cell stale.
|
|
772
|
+
if (message.status === "queued") {
|
|
773
|
+
nextState = updateCellData({
|
|
774
|
+
state: nextState,
|
|
775
|
+
cellId,
|
|
776
|
+
cellReducer: (cell) => ({
|
|
777
|
+
...cell,
|
|
778
|
+
lastCodeRun: cell.code.trim(),
|
|
779
|
+
edited: false,
|
|
780
|
+
}),
|
|
781
|
+
});
|
|
782
|
+
}
|
|
766
783
|
return {
|
|
767
784
|
...nextState,
|
|
768
785
|
cellLogs: [...nextState.cellLogs, ...getCellLogsForMessage(message)],
|
|
@@ -1405,6 +1422,12 @@ const {
|
|
|
1405
1422
|
},
|
|
1406
1423
|
});
|
|
1407
1424
|
|
|
1425
|
+
// We apply the middleware here (rather than inline in createReducerAndAtoms)
|
|
1426
|
+
// so that the document transaction middleware can import CellActions and
|
|
1427
|
+
// strictly type the dispatched actions without creating a circular dependency.
|
|
1428
|
+
// @ts-expect-error - TODO: We should have better types for the middleware that are strict
|
|
1429
|
+
addMiddleware(documentTransactionMiddleware);
|
|
1430
|
+
|
|
1408
1431
|
function isCellCodeHidden(state: NotebookState, cellId: CellId): boolean {
|
|
1409
1432
|
return (
|
|
1410
1433
|
Boolean(state.cellData[cellId].config.hide_code) &&
|
|
@@ -1789,8 +1812,10 @@ export function createTracebackInfoAtom(
|
|
|
1789
1812
|
* Use this hook to dispatch cell actions. This hook will not cause a re-render
|
|
1790
1813
|
* when cells change.
|
|
1791
1814
|
*/
|
|
1792
|
-
export function useCellActions(
|
|
1793
|
-
|
|
1815
|
+
export function useCellActions(
|
|
1816
|
+
options: { skipMiddleware?: boolean } = {},
|
|
1817
|
+
): CellActions {
|
|
1818
|
+
return useActions(options);
|
|
1794
1819
|
}
|
|
1795
1820
|
|
|
1796
1821
|
/**
|