@marimo-team/islands 0.25.3-dev191 → 0.25.3-dev193
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-BBbOElk3.js → ConnectedDataExplorerComponent-BRuwLOIx.js} +9 -9
- package/dist/{ErrorBoundary--X0z83Yx.js → ErrorBoundary-BVu_xT-p.js} +2 -2
- package/dist/{abnfDiagram-VCTEODGH-DhMT6Uhd.js → abnfDiagram-VCTEODGH-LlJTOOU_.js} +2 -2
- package/dist/{architectureDiagram-5GKGNRK7-BVbEiJLq.js → architectureDiagram-5GKGNRK7-9PQtGLLZ.js} +1 -1
- package/dist/{arrays-R6JsImiT.js → arrays-DA6KkLfq.js} +6 -12
- package/dist/assets/__vite-browser-external-BCJsx8QS.js +1 -0
- package/dist/assets/{worker-C6e9ERyd.js → worker-DodjWT_Z.js} +2 -2
- package/dist/{capabilities-CI4xUPsp.js → capabilities-CvWU-ey_.js} +1 -1
- package/dist/{chat-ui-BguxTQEL.js → chat-ui-Bl2euMBD.js} +93 -93
- package/dist/{chunk-IMKFNOWR-By5hSbJJ.js → chunk-IMKFNOWR-DcXv-BD5.js} +1 -1
- package/dist/{chunk-SHT3W25Y-DFrEYG54.js → chunk-SHT3W25Y-VwnELN23.js} +1 -1
- package/dist/{chunk-SVP7TREG-DFXGc9ew.js → chunk-SVP7TREG-DgQpI5Zi.js} +1 -1
- package/dist/{chunk-TICWLB2K-DZdNIpoZ.js → chunk-TICWLB2K-CTg6VTJT.js} +1 -1
- package/dist/{classDiagram-ZZMXUADV-Cgj_eEQA.js → classDiagram-ZZMXUADV-UzmcFrnW.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-DLAUrSqj.js → classDiagram-v2-VYDZK3BY-DhjiI6IQ.js} +2 -2
- package/dist/{code-visibility-uDyLo9_i.js → code-visibility-BTVimCUw.js} +38 -42
- package/dist/{common-DWKY45Md.js → common-fTgq7gO-.js} +849 -849
- package/dist/{cynefinDiagram-5FMLGOSQ-Cikf9AKa.js → cynefinDiagram-5FMLGOSQ-O4ShIbJX.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-C5KEi5_l.js → dagre-GXQ25YYZ-dtHhhcPu.js} +1 -1
- package/dist/{data-grid-overlay-editor-CRnYWD1L.js → data-grid-overlay-editor-DAZZkLWg.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BaoBTQjR.js → diagram-S7CK7UJ4-ppPYVZ6W.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-DDufBsUA.js → diagram-UQ7AKVKN-CMZDf--k.js} +1 -1
- package/dist/{diagram-VX7I27RA-CY6RALJ8.js → diagram-VX7I27RA-CFvJdx-C.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Bc-LHsD6.js → diagram-Z3DM3KII-Dt6_uFbv.js} +1 -1
- package/dist/{dropdown-menu-drDjxJyV.js → dropdown-menu-CeaOsjWZ.js} +2 -2
- package/dist/{ebnfDiagram-PWID7BFC-Cz0J-tcC.js → ebnfDiagram-PWID7BFC-DpRFrKK-.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-DTXTDgFu.js → erDiagram-RLTQ6QDP-CqAU4b0u.js} +1 -1
- package/dist/{error-entries-CZ1NQ6Kv.js → error-entries-CYGwr5-O.js} +5596 -6012
- package/dist/feedback-button-DzFFn-n4.js +374 -0
- package/dist/{flowDiagram-HODETNUW-RbwWf2zc.js → flowDiagram-HODETNUW-i8-Gw8HO.js} +2 -2
- package/dist/{formats-DbXnijtH.js → formats-BtoHHy42.js} +1 -1
- package/dist/{glide-data-editor-CvRiA49N.js → glide-data-editor-DFsqVbWR.js} +5 -5
- package/dist/{globals-BR4CmGOS.js → globals-Dr0MRANu.js} +2 -2
- package/dist/{html-to-image-D_4_hQ8U.js → html-to-image-Cy5UDpQJ.js} +56 -56
- package/dist/{infoDiagram-27XIBGKW-DlmzZjmI.js → infoDiagram-27XIBGKW-DpfOKdLL.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-EN3TYP_q.js → ishikawaDiagram-5VMMS53U-Dg7mrMG7.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-CK_-Ru7Q.js → kanban-definition-UXKFOSKX-QB6e3Jrj.js} +1 -1
- package/dist/main.js +5875 -5876
- package/dist/{mermaid-DU9ZRmkG.js → mermaid-CeWG4Egr.js} +48 -48
- package/dist/{mindmap-definition-YA3MSWOX-C-hqgUvF.js → mindmap-definition-YA3MSWOX-BTz3uTGe.js} +1 -1
- package/dist/{number-overlay-editor-DiYGbxNa.js → number-overlay-editor-DupOSYHx.js} +1 -1
- package/dist/{once-eG8Vmvjd.js → once-DWGO2mQ2.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-BiMejYPB.js → pegDiagram-XKGWAZYB-3ZIEa3mB.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-B1zCZ1eq.js → pieDiagram-E7YTZNPT-BJAlk046.js} +1 -1
- package/dist/{process-output-DxSCHBWp.js → process-output-q9MTAIMX.js} +2 -2
- package/dist/{railroadDiagram-O6MQD6OU-BLcfaASl.js → railroadDiagram-O6MQD6OU-Dn3bsSRa.js} +2 -2
- package/dist/{requirementDiagram-BXWQKSXE-DtBOf52-.js → requirementDiagram-BXWQKSXE-wcOmQQCL.js} +1 -1
- package/dist/{reveal-component-D3DsXPIm.js → reveal-component-Bg8rP_Uo.js} +17 -17
- package/dist/{sequenceDiagram-WJ2MYXX4-DIwF19_n.js → sequenceDiagram-WJ2MYXX4-DSh_qxoF.js} +1 -1
- package/dist/{spec-DZDnnlgT.js → spec-C7Fos-If.js} +1 -1
- package/dist/{stateDiagram-D77RDMKH-sWk1AdYL.js → stateDiagram-D77RDMKH-DH_H8NMH.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CiNo-OI7.js → stateDiagram-v2-MP3YSRHH-D_JM7MEK.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Bj3hiOY5.js → swimlanes-42K2YHIH-CfoTJO-9.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-BzEo2r_N.js → swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js} +2 -2
- package/dist/{swiper-component-BkCm-kLg.js → swiper-component-D2geL1Ku.js} +1 -1
- package/dist/{timeline-definition-24CTP7MA-CrPQk42H.js → timeline-definition-24CTP7MA-BFtdScaL.js} +1 -1
- package/dist/{toDate-Cu2pIZcC.js → toDate-CQAFSwos.js} +24 -24
- package/dist/{useAsyncData-DEhLdRIu.js → useAsyncData-BeyZyjso.js} +1 -1
- package/dist/{useDeepCompareMemoize-RjZGVhBV.js → useDeepCompareMemoize-DOQgae0W.js} +1 -1
- package/dist/{useIframeCapabilities-CSdWn6NP.js → useIframeCapabilities-DNvaj9wM.js} +1 -1
- package/dist/{useLifecycle-DtcB2s1z.js → useLifecycle-n2lnCFS8.js} +1 -1
- package/dist/{useTheme-CoEtr4mK.js → useTheme-CY777LXN.js} +303 -99
- package/dist/{vega-component-BhjTrP9-.js → vega-component-0nQzA-I8.js} +6 -6
- package/dist/{vennDiagram-4TSXK5OY-BaYMoIpj.js → vennDiagram-4TSXK5OY-BXY8IGCA.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-DjHeuj8p.js → wardleyDiagram-VM6X3IG4-D0tFe6eC.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-Bc_hcuXS.js → xychartDiagram-S5SC5T6Z-Dx8wFM-y.js} +1 -1
- package/package.json +1 -1
- package/src/__mocks__/notebook.ts +0 -1
- package/src/__mocks__/partial.ts +26 -0
- package/src/__tests__/main.test.tsx +24 -1
- package/src/components/app-config/app-config-form.tsx +5 -9
- package/src/components/editor/__tests__/data-attributes.test.tsx +0 -1
- package/src/components/editor/__tests__/output-persistence.test.tsx +0 -1
- package/src/components/editor/actions/useCellActionButton.tsx +6 -5
- package/src/components/editor/actions/useConfigActions.tsx +3 -4
- package/src/components/editor/actions/useNotebookActions.tsx +5 -4
- package/src/components/editor/cell/code/cell-editor.tsx +13 -6
- package/src/components/editor/cell/collapse.tsx +6 -1
- package/src/components/editor/chrome/components/feedback-button.tsx +1 -7
- package/src/components/editor/controls/Controls.tsx +1 -1
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +8 -6
- package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -15
- package/src/components/editor/navigation/__tests__/selection.test.ts +44 -21
- package/src/components/editor/navigation/focus-utils.ts +19 -0
- package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +6 -13
- package/src/components/editor/navigation/navigation.ts +52 -43
- package/src/components/editor/navigation/selection.ts +30 -29
- package/src/components/editor/notebook-cell.tsx +10 -18
- package/src/components/editor/renderers/cell-array.tsx +6 -5
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
- package/src/components/pages/edit-page.tsx +0 -13
- package/src/components/scratchpad/scratchpad.tsx +0 -1
- package/src/components/slides/reveal-component.tsx +1 -1
- package/src/components/slides/slide-cell-view.tsx +0 -1
- package/src/components/sort/SortableCellsProvider.tsx +6 -5
- package/src/core/MarimoApp.tsx +1 -1
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -37
- package/src/core/cells/__tests__/actions.test.ts +190 -80
- package/src/core/cells/__tests__/arrange.test.ts +263 -0
- package/src/core/cells/__tests__/cells.test.ts +11 -1101
- package/src/core/cells/__tests__/document.test.ts +3 -32
- package/src/core/cells/__tests__/editor.test.ts +57 -0
- package/src/core/cells/__tests__/harness.ts +82 -0
- package/src/core/cells/__tests__/layout.test.ts +189 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
- package/src/core/cells/__tests__/session.test.ts +329 -174
- package/src/core/cells/__tests__/utils.test.ts +0 -1
- package/src/core/cells/actions.ts +247 -82
- package/src/core/cells/arrange.ts +341 -0
- package/src/core/cells/cells.ts +27 -516
- package/src/core/cells/document.ts +6 -5
- package/src/core/cells/edit.ts +30 -0
- package/src/core/cells/editor.ts +82 -0
- package/src/core/cells/layout.ts +152 -0
- package/src/core/cells/session.ts +75 -52
- package/src/core/cells/types.ts +0 -5
- package/src/core/cells/utils.ts +6 -4
- package/src/core/config/__tests__/app-config.test.ts +64 -0
- package/src/core/config/__tests__/config-schema.test.ts +1 -1
- package/src/core/config/config-schema.ts +6 -2
- package/src/core/config/config.ts +26 -12
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/kernel/handlers.ts +1 -13
- package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
- package/src/core/meta/code-visibility.ts +18 -9
- package/src/core/notebook/__tests__/operations.test.ts +51 -0
- package/src/core/notebook/document/__tests__/codemirror.test.ts +15 -0
- package/src/core/notebook/index.ts +2 -1
- package/src/core/notebook/operations.ts +30 -0
- package/src/core/saving/__tests__/state.test.ts +111 -0
- package/src/core/saving/save-component.tsx +19 -33
- package/src/core/saving/state.ts +38 -39
- package/src/core/saving/useAutoSave.ts +8 -35
- package/src/core/session/__tests__/backend.test.ts +1 -0
- package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +1 -0
- package/src/core/session/active-notebook.ts +11 -0
- package/src/core/session/backend.ts +17 -19
- package/src/core/session/embedded.ts +7 -0
- package/src/core/session/useNotebookReplica.ts +3 -1
- package/src/core/session/useNotebookSession.tsx +7 -8
- package/src/core/transports/__tests__/static.test.ts +139 -8
- package/src/core/transports/static.ts +98 -16
- package/src/core/wasm/__tests__/bridge.test.ts +1 -0
- package/src/mount.tsx +19 -10
- package/src/stories/cell.stories.tsx +0 -2
- package/src/stories/layout/vertical/one-column.stories.tsx +0 -2
- package/src/utils/__tests__/id-tree.test.ts +20 -1548
- package/src/utils/id-tree.tsx +12 -537
- package/dist/assets/__vite-browser-external-C8gQho07.js +0 -1
- package/dist/feedback-button-CDf1yRm_.js +0 -379
- package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
- package/src/components/editor/navigation/state.ts +0 -65
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/** Notebook operations that move cells within and across columns. */
|
|
4
|
+
|
|
5
|
+
import { dequal as isEqual } from "dequal";
|
|
6
|
+
import { atom, type Getter, type Setter } from "jotai";
|
|
7
|
+
import type { CellColumnId, CellIndex } from "@/utils/id-tree";
|
|
8
|
+
import { arrayMove } from "@/utils/arrays";
|
|
9
|
+
import { notebookAtom, type UndoMoveEntry } from "./cells";
|
|
10
|
+
import { documentOf, focusWhenMounted, modelOf } from "./edit";
|
|
11
|
+
import type { CellId } from "./ids";
|
|
12
|
+
import {
|
|
13
|
+
columnOf,
|
|
14
|
+
findColumn,
|
|
15
|
+
type Layout,
|
|
16
|
+
type LayoutColumn,
|
|
17
|
+
layoutAtom,
|
|
18
|
+
} from "./layout";
|
|
19
|
+
|
|
20
|
+
/** A placement to restore a cell to: its column and its index there. */
|
|
21
|
+
export interface Placement {
|
|
22
|
+
columnId: CellColumnId;
|
|
23
|
+
index: CellIndex;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A cell with the cells its fold hides, which move with it. */
|
|
27
|
+
function blockOf(column: LayoutColumn, id: CellId): CellId[] {
|
|
28
|
+
return [id, ...(column.folds.get(id) ?? [])];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A column as the blocks the page shows, in order. Together they hold every cell. */
|
|
32
|
+
function blocksOf(column: LayoutColumn): CellId[][] {
|
|
33
|
+
return column.visible.map((id) => blockOf(column, id));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The cells of every column, as arrays to rearrange. */
|
|
37
|
+
function columnsOf(layout: Layout): CellId[][] {
|
|
38
|
+
return layout.columns.map((column) => [...column.ids]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function without(columns: CellId[][], ids: ReadonlySet<CellId>): CellId[][] {
|
|
42
|
+
return columns.map((column) => column.filter((id) => !ids.has(id)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The `column` configs that make the document's columns match `columns`,
|
|
47
|
+
* in the form the file is written in: with one column no cell carries a
|
|
48
|
+
* column, and with several only the first cell of each carries its index.
|
|
49
|
+
* An emptied column keeps its place as a gap, since the next column's first
|
|
50
|
+
* cell still names its index, until a drop compacts it.
|
|
51
|
+
*/
|
|
52
|
+
function columnConfigs(
|
|
53
|
+
columns: readonly (readonly CellId[])[],
|
|
54
|
+
configured: (id: CellId) => number | null,
|
|
55
|
+
): Array<[CellId, number | null]> {
|
|
56
|
+
const fixes: Array<[CellId, number | null]> = [];
|
|
57
|
+
const single = columns.length === 1;
|
|
58
|
+
columns.forEach((column, index) => {
|
|
59
|
+
column.forEach((id, position) => {
|
|
60
|
+
const target = !single && position === 0 ? index : null;
|
|
61
|
+
if (configured(id) !== target) {
|
|
62
|
+
fixes.push([id, target]);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return fixes;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Make the document's cells fall into `columns`, in that order, as one edit.
|
|
71
|
+
* Nothing is written when they already do.
|
|
72
|
+
*/
|
|
73
|
+
export function arrange(get: Getter, set: Setter, columns: CellId[][]): void {
|
|
74
|
+
const document = documentOf(get);
|
|
75
|
+
const order = columns.flat();
|
|
76
|
+
const fixes = columnConfigs(
|
|
77
|
+
columns,
|
|
78
|
+
(id) => document.cells.get(id)?.config.column ?? null,
|
|
79
|
+
);
|
|
80
|
+
const reordered = !isEqual(order, document.order);
|
|
81
|
+
if (!reordered && fixes.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
set(modelOf(get).editAtom, (tx) => {
|
|
85
|
+
if (reordered) {
|
|
86
|
+
tx.setOrder(order);
|
|
87
|
+
}
|
|
88
|
+
for (const [id, column] of fixes) {
|
|
89
|
+
tx.updateConfig(id, { column });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Move a cell one block up or down its column, or to the start of the
|
|
96
|
+
* neighbouring column. A folded header moves with its section.
|
|
97
|
+
*/
|
|
98
|
+
export const moveCellAtom = atom(
|
|
99
|
+
null,
|
|
100
|
+
(
|
|
101
|
+
get,
|
|
102
|
+
set,
|
|
103
|
+
{
|
|
104
|
+
cellId,
|
|
105
|
+
before,
|
|
106
|
+
direction,
|
|
107
|
+
}: { cellId: CellId; before?: boolean; direction?: "left" | "right" },
|
|
108
|
+
): void => {
|
|
109
|
+
const layout = get(layoutAtom);
|
|
110
|
+
const column = columnOf(layout, cellId);
|
|
111
|
+
const at = layout.columns.indexOf(column);
|
|
112
|
+
const columns = columnsOf(layout);
|
|
113
|
+
if (direction) {
|
|
114
|
+
const to = direction === "left" ? at - 1 : at + 1;
|
|
115
|
+
if (to < 0 || to >= columns.length) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const block = blockOf(column, cellId);
|
|
119
|
+
const cleaned = without(columns, new Set(block));
|
|
120
|
+
cleaned[to] = [...block, ...cleaned[to]];
|
|
121
|
+
arrange(get, set, cleaned);
|
|
122
|
+
focusWhenMounted(set, cellId);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const blocks = blocksOf(column);
|
|
126
|
+
const index = blocks.findIndex((block) => block[0] === cellId);
|
|
127
|
+
const target = before ? index - 1 : index + 1;
|
|
128
|
+
if (index !== -1 && target >= 0 && target < blocks.length) {
|
|
129
|
+
columns[at] = arrayMove(blocks, index, target).flat();
|
|
130
|
+
arrange(get, set, columns);
|
|
131
|
+
}
|
|
132
|
+
focusWhenMounted(set, cellId);
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
function sendTo(
|
|
137
|
+
get: Getter,
|
|
138
|
+
set: Setter,
|
|
139
|
+
{ cellId, scroll = true }: { cellId: CellId; scroll?: boolean },
|
|
140
|
+
edge: "top" | "bottom",
|
|
141
|
+
): void {
|
|
142
|
+
const layout = get(layoutAtom);
|
|
143
|
+
const column = columnOf(layout, cellId);
|
|
144
|
+
const blocks = blocksOf(column);
|
|
145
|
+
const index = blocks.findIndex((block) => block[0] === cellId);
|
|
146
|
+
const target = edge === "top" ? 0 : blocks.length - 1;
|
|
147
|
+
if (index === -1 || index === target) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const columns = columnsOf(layout);
|
|
151
|
+
columns[layout.columns.indexOf(column)] = arrayMove(
|
|
152
|
+
blocks,
|
|
153
|
+
index,
|
|
154
|
+
target,
|
|
155
|
+
).flat();
|
|
156
|
+
arrange(get, set, columns);
|
|
157
|
+
if (scroll) {
|
|
158
|
+
focusWhenMounted(set, cellId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Move a cell, with its fold, to the top of its column. */
|
|
163
|
+
export const sendToTopAtom = atom(
|
|
164
|
+
null,
|
|
165
|
+
(get, set, options: { cellId: CellId; scroll?: boolean }): void =>
|
|
166
|
+
sendTo(get, set, options, "top"),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/** Move a cell, with its fold, to the bottom of its column. */
|
|
170
|
+
export const sendToBottomAtom = atom(
|
|
171
|
+
null,
|
|
172
|
+
(get, set, options: { cellId: CellId; scroll?: boolean }): void =>
|
|
173
|
+
sendTo(get, set, options, "bottom"),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
/** Drop a dragged cell, with its fold, where another cell is. */
|
|
177
|
+
export const dropCellOverCellAtom = atom(
|
|
178
|
+
null,
|
|
179
|
+
(
|
|
180
|
+
get,
|
|
181
|
+
set,
|
|
182
|
+
{ cellId, overCellId }: { cellId: CellId; overCellId: CellId },
|
|
183
|
+
): void => {
|
|
184
|
+
const layout = get(layoutAtom);
|
|
185
|
+
const from = columnOf(layout, cellId);
|
|
186
|
+
const to = columnOf(layout, overCellId);
|
|
187
|
+
const columns = columnsOf(layout);
|
|
188
|
+
if (from === to) {
|
|
189
|
+
const blocks = blocksOf(from);
|
|
190
|
+
const index = blocks.findIndex((block) => block[0] === cellId);
|
|
191
|
+
const target = blocks.findIndex((block) => block[0] === overCellId);
|
|
192
|
+
if (index === -1 || target === -1 || index === target) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
columns[layout.columns.indexOf(from)] = arrayMove(
|
|
196
|
+
blocks,
|
|
197
|
+
index,
|
|
198
|
+
target,
|
|
199
|
+
).flat();
|
|
200
|
+
arrange(get, set, columns);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const block = blockOf(from, cellId);
|
|
204
|
+
const cleaned = without(columns, new Set(block));
|
|
205
|
+
const target = cleaned[layout.columns.indexOf(to)];
|
|
206
|
+
const at = target.indexOf(overCellId);
|
|
207
|
+
target.splice(at, 0, ...block);
|
|
208
|
+
arrange(get, set, cleaned);
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
/** Drop a dragged cell, with its fold, at the start of a column. */
|
|
213
|
+
export const dropCellOverColumnAtom = atom(
|
|
214
|
+
null,
|
|
215
|
+
(
|
|
216
|
+
get,
|
|
217
|
+
set,
|
|
218
|
+
{ cellId, columnId }: { cellId: CellId; columnId: CellColumnId },
|
|
219
|
+
): void => {
|
|
220
|
+
const layout = get(layoutAtom);
|
|
221
|
+
const to = findColumn(layout, columnId);
|
|
222
|
+
if (to === undefined) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const block = blockOf(columnOf(layout, cellId), cellId);
|
|
226
|
+
const cleaned = without(columnsOf(layout), new Set(block));
|
|
227
|
+
cleaned[layout.columns.indexOf(to)].unshift(...block);
|
|
228
|
+
arrange(get, set, cleaned);
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
/** Move a cell, with its fold, to an index in a column. Out of range clamps. */
|
|
233
|
+
export const moveCellToIndexAtom = atom(
|
|
234
|
+
null,
|
|
235
|
+
(
|
|
236
|
+
get,
|
|
237
|
+
set,
|
|
238
|
+
{
|
|
239
|
+
cellId,
|
|
240
|
+
columnId,
|
|
241
|
+
index,
|
|
242
|
+
}: { cellId: CellId; columnId: CellColumnId; index: number },
|
|
243
|
+
): void => {
|
|
244
|
+
const layout = get(layoutAtom);
|
|
245
|
+
const to = findColumn(layout, columnId);
|
|
246
|
+
if (to === undefined) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const from = columnOf(layout, cellId);
|
|
250
|
+
const block = blockOf(from, cellId);
|
|
251
|
+
const columns = columnsOf(layout);
|
|
252
|
+
const fromIndex = from.ids.indexOf(cellId);
|
|
253
|
+
const cleaned = without(columns, new Set(block));
|
|
254
|
+
const target = cleaned[layout.columns.indexOf(to)];
|
|
255
|
+
let at = Math.max(0, Math.min(index, to.ids.length));
|
|
256
|
+
if (from === to && fromIndex < at) {
|
|
257
|
+
at -= block.length;
|
|
258
|
+
}
|
|
259
|
+
target.splice(Math.max(0, Math.min(at, target.length)), 0, ...block);
|
|
260
|
+
arrange(get, set, cleaned);
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Move cells to just before or after a target cell, in their order. With
|
|
266
|
+
* the cells' previous placements, the move can be undone.
|
|
267
|
+
*/
|
|
268
|
+
export const moveCellsRelativeToAtom = atom(
|
|
269
|
+
null,
|
|
270
|
+
(
|
|
271
|
+
get,
|
|
272
|
+
set,
|
|
273
|
+
{
|
|
274
|
+
cellIds,
|
|
275
|
+
targetCellId,
|
|
276
|
+
position,
|
|
277
|
+
previousPlacements,
|
|
278
|
+
}: {
|
|
279
|
+
cellIds: CellId[];
|
|
280
|
+
targetCellId: CellId;
|
|
281
|
+
position: "before" | "after";
|
|
282
|
+
previousPlacements?: Placement[];
|
|
283
|
+
},
|
|
284
|
+
): void => {
|
|
285
|
+
if (cellIds.length === 0) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const layout = get(layoutAtom);
|
|
289
|
+
const moving = new Set(cellIds);
|
|
290
|
+
const cleaned = without(columnsOf(layout), moving);
|
|
291
|
+
const target =
|
|
292
|
+
cleaned[layout.columns.indexOf(columnOf(layout, targetCellId))];
|
|
293
|
+
let at = target.indexOf(targetCellId);
|
|
294
|
+
if (at === -1) {
|
|
295
|
+
// The target moves too, so the cells go to the end of its column.
|
|
296
|
+
at = target.length;
|
|
297
|
+
} else if (position === "after") {
|
|
298
|
+
at += 1;
|
|
299
|
+
}
|
|
300
|
+
target.splice(at, 0, ...cellIds);
|
|
301
|
+
arrange(get, set, cleaned);
|
|
302
|
+
if (previousPlacements && previousPlacements.length === cellIds.length) {
|
|
303
|
+
const entry: UndoMoveEntry = {
|
|
304
|
+
type: "move",
|
|
305
|
+
cellIds,
|
|
306
|
+
placements: previousPlacements,
|
|
307
|
+
};
|
|
308
|
+
set(notebookAtom, (state) => ({
|
|
309
|
+
...state,
|
|
310
|
+
history: [...state.history, entry],
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
/** Put cells back where they were, by column and index. A vanished column takes the last. */
|
|
317
|
+
export function placeCells(
|
|
318
|
+
get: Getter,
|
|
319
|
+
set: Setter,
|
|
320
|
+
cellIds: readonly CellId[],
|
|
321
|
+
placements: readonly Placement[],
|
|
322
|
+
): void {
|
|
323
|
+
const layout = get(layoutAtom);
|
|
324
|
+
const cleaned = without(columnsOf(layout), new Set(cellIds));
|
|
325
|
+
const columnIndex = (columnId: CellColumnId) => {
|
|
326
|
+
const index = layout.columns.findIndex((column) => column.id === columnId);
|
|
327
|
+
return index === -1 ? cleaned.length - 1 : index;
|
|
328
|
+
};
|
|
329
|
+
const sorted = cellIds
|
|
330
|
+
.map((id, i) => ({
|
|
331
|
+
id,
|
|
332
|
+
column: columnIndex(placements[i].columnId),
|
|
333
|
+
index: placements[i].index,
|
|
334
|
+
}))
|
|
335
|
+
.toSorted((a, b) => a.column - b.column || a.index - b.index);
|
|
336
|
+
for (const { id, column, index } of sorted) {
|
|
337
|
+
const target = cleaned[column];
|
|
338
|
+
target.splice(Math.max(0, Math.min(index, target.length)), 0, id);
|
|
339
|
+
}
|
|
340
|
+
arrange(get, set, cleaned);
|
|
341
|
+
}
|