@marimo-team/islands 0.25.3-dev21 → 0.25.3-dev211

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.
Files changed (717) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DfG3PRaJ.js → ConnectedDataExplorerComponent-D6RHJV8D.js} +120 -116
  2. package/dist/{ErrorBoundary-ttSr7_91.js → ErrorBoundary-BpGAot_X.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-B3w-XWX8.js → abnfDiagram-VCTEODGH-0q7Lbmii.js} +2 -2
  4. package/dist/any-language-editor-jGaR0EAo.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-C18U9A6k.js → architectureDiagram-5GKGNRK7-1PydKPmE.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-DoQ-AILx.js} +15 -27
  7. package/dist/assets/__vite-browser-external-CBRgnYY0.js +1 -0
  8. package/dist/assets/worker-DiPNP__U.js +113 -0
  9. package/dist/{button-CvjkwdSk.js → button-B6RjDKJx.js} +227 -217
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CkVzWJEX.js} +2 -2
  11. package/dist/{chat-ui-w8SVynkn.js → chat-ui-B5pH_aXk.js} +1277 -1275
  12. package/dist/{check-BMKBBrdc.js → check-Z_vtI3OL.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-CrS5bzqW.js → chunk-IMKFNOWR-DEzZZ1Zc.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-StpvPvUG.js → chunk-SHT3W25Y-CuHj-hnn.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-CBXjbYZC.js → chunk-SVP7TREG-BPiZK2bA.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Dp6bIb-q.js → chunk-TICWLB2K-DF6XcIjM.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BFYuxV5g.js → classDiagram-ZZMXUADV-CMdR-cP4.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-D_tjXVMl.js → classDiagram-v2-VYDZK3BY-DRvc3_7Y.js} +2 -2
  20. package/dist/code-visibility-B5trOyTh.js +134 -0
  21. package/dist/{common-CeebhhhO.js → common-BiM3Xm_d.js} +9837 -9658
  22. package/dist/config-DbjzjwxX.js +636 -0
  23. package/dist/{copy-icon-BgUO8jsi.js → copy-icon-CG57cxwb.js} +3 -3
  24. package/dist/createReducer-DePYJ0F3.js +63 -0
  25. package/dist/{cynefinDiagram-5FMLGOSQ-DSgYrHB2.js → cynefinDiagram-5FMLGOSQ-BkBjEY9l.js} +1 -1
  26. package/dist/{dagre-GXQ25YYZ-GYlruCBf.js → dagre-GXQ25YYZ-D-HEaW4I.js} +1 -1
  27. package/dist/{data-grid-overlay-editor-vxQ4E6k1.js → data-grid-overlay-editor-GxGkM-Al.js} +5 -5
  28. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  29. package/dist/{diagram-S7CK7UJ4-DRu5LX5_.js → diagram-S7CK7UJ4-Bn-oF-rB.js} +1 -1
  30. package/dist/{diagram-UQ7AKVKN-DD7uiejP.js → diagram-UQ7AKVKN-BpzzXWBj.js} +1 -1
  31. package/dist/{diagram-VX7I27RA-ByH9KAJJ.js → diagram-VX7I27RA-COOtvfBP.js} +1 -1
  32. package/dist/{diagram-Z3DM3KII-BsQHPRke.js → diagram-Z3DM3KII-K5cXvbrl.js} +1 -1
  33. package/dist/{dialog-Cc479ZEH.js → dialog-DsZ49mmU.js} +26 -26
  34. package/dist/{dist-Bvs24R_E.js → dist-11T5wq3_.js} +31 -30
  35. package/dist/{dist-F8Ep36rk.js → dist-17ugwGZc.js} +59 -58
  36. package/dist/{dist-BMQf5zIS.js → dist-7cRjf--y.js} +123 -122
  37. package/dist/{dist-diIx5qJW.js → dist-B8pkfkzc.js} +31 -30
  38. package/dist/{dist-CN_BeG2I.js → dist-B9UoM_wq.js} +58 -57
  39. package/dist/{dist-CLdpGQVr.js → dist-BGuMTs1i.js} +118 -117
  40. package/dist/{dist-BK4Oj99o.js → dist-BHW11fni.js} +18 -17
  41. package/dist/dist-BLY4m1Wi.js +850 -0
  42. package/dist/{dist-PXYI_yuP.js → dist-BNlcVoxs.js} +24 -23
  43. package/dist/{dist-D6d86eNJ.js → dist-BQYe5Bju.js} +47 -46
  44. package/dist/{dist-CkTTIdBe.js → dist-BX1mn8na.js} +337 -337
  45. package/dist/{dist-DiDibOYr.js → dist-Bh0PTKUW.js} +151 -150
  46. package/dist/{dist-CSJ0MDRw.js → dist-CKaXK92p.js} +56 -55
  47. package/dist/{dist-C8fAKSH1.js → dist-CLhwfhpJ.js} +53 -52
  48. package/dist/{dist-DoE4XJBC.js → dist-CYeqK4Xw.js} +9801 -10649
  49. package/dist/{dist-BLfKmR-l.js → dist-Cxlp3WH6.js} +16 -15
  50. package/dist/{dist-D-wwqlzJ.js → dist-DSJPGj42.js} +158 -157
  51. package/dist/{dist-BorMD-uX.js → dist-DU9ICsWZ.js} +75 -74
  52. package/dist/{dist-DBtSl3sf.js → dist-DWv0ALBi.js} +47 -46
  53. package/dist/{dist-OR1OnN-y.js → dist-DiBpWEsB.js} +31 -30
  54. package/dist/{dist-CrSP_H1M.js → dist-DiLFTlnl.js} +242 -242
  55. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  56. package/dist/{dist-CSbh1PNP.js → dist-cVDV5IuI.js} +23 -22
  57. package/dist/{dist-BuaRHxOc.js → dist-fRfaOoq8.js} +44 -43
  58. package/dist/{dist-Cg7gzw-a.js → dist-gTTI-9Px.js} +78 -77
  59. package/dist/dropdown-menu-CSNdPSnS.js +6083 -0
  60. package/dist/{ebnfDiagram-PWID7BFC-CQRK5_eT.js → ebnfDiagram-PWID7BFC-BI1ZmjVm.js} +2 -2
  61. package/dist/{erDiagram-RLTQ6QDP-BKtWrDVh.js → erDiagram-RLTQ6QDP-BtWjj9xy.js} +1 -1
  62. package/dist/{error-banner-CFPHFw4h.js → error-banner-IsonnvA0.js} +84 -83
  63. package/dist/error-entries-CRPfxMqm.js +2505 -0
  64. package/dist/{esm-D2gTsXEV.js → esm-BgKjMvWw.js} +530 -529
  65. package/dist/{esm-AMFssoNp.js → esm-CHjVSexr.js} +328 -329
  66. package/dist/feedback-button-CAxVqhvl.js +374 -0
  67. package/dist/{flowDiagram-HODETNUW-1IN-ih2E.js → flowDiagram-HODETNUW-DQvZh96A.js} +2 -2
  68. package/dist/{formats-X4mMQWzX.js → formats-BK4VefeF.js} +6 -6
  69. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  70. package/dist/{glide-data-editor-CSYHMRDu.js → glide-data-editor-CBX2wLtn.js} +417 -415
  71. package/dist/{glide-utils-DM9zBO4W.js → glide-utils-CQddGpa-.js} +16 -16
  72. package/dist/{globals-Fpiwkv4D.js → globals-DuCA4LSq.js} +3 -3
  73. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  74. package/dist/{error-entries-Damuuclv.js → html-to-image-CgAnxFi8.js} +26911 -26769
  75. package/dist/{infoDiagram-27XIBGKW-CkB-zr4p.js → infoDiagram-27XIBGKW-BdAObnc_.js} +1 -1
  76. package/dist/{ishikawaDiagram-5VMMS53U-wlyLjBmM.js → ishikawaDiagram-5VMMS53U-DP-jE8dw.js} +1 -1
  77. package/dist/{kanban-definition-UXKFOSKX-C2usY0i-.js → kanban-definition-UXKFOSKX-BkMgPjfm.js} +1 -1
  78. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  79. package/dist/{loader-BQOfjTIJ.js → loader-BisjOLiW.js} +12 -12
  80. package/dist/main.js +10128 -8127
  81. package/dist/{mermaid-BA-Z6wGL.js → mermaid-B-OFqheA.js} +52 -52
  82. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  83. package/dist/{mindmap-definition-YA3MSWOX-nKOWS8RR.js → mindmap-definition-YA3MSWOX-Du7toAZX.js} +1 -1
  84. package/dist/{number-overlay-editor-BOjqsq7D.js → number-overlay-editor-KvS7Rgw9.js} +3 -3
  85. package/dist/{dist-CN8zI-XC.js → objectWithoutPropertiesLoose-ai5NUqGa.js} +262 -240
  86. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  87. package/dist/{once-BtE-wKRL.js → once-NnpX_HnX.js} +1 -1
  88. package/dist/{pegDiagram-XKGWAZYB-Dac9EIKD.js → pegDiagram-XKGWAZYB-CX_729j0.js} +2 -2
  89. package/dist/{pieDiagram-E7YTZNPT-B7S-DsW_.js → pieDiagram-E7YTZNPT-dtTFJcVI.js} +1 -1
  90. package/dist/precisionRound-DhAzeRcm.js +49 -0
  91. package/dist/{process-output-CTPTtxXB.js → process-output-Bmev5azU.js} +4 -4
  92. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  93. package/dist/{railroadDiagram-O6MQD6OU-Cfa5Q-hr.js → railroadDiagram-O6MQD6OU-Dsc2Cf4c.js} +2 -2
  94. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  95. package/dist/{requirementDiagram-BXWQKSXE-d8zXCJIk.js → requirementDiagram-BXWQKSXE-D3g0c60I.js} +1 -1
  96. package/dist/{reveal-component-Brpo7CP8.js → reveal-component-CqBvRKJz.js} +426 -403
  97. package/dist/schemas-CdscWNy_.js +4747 -0
  98. package/dist/select-BSxlITBo.js +1200 -0
  99. package/dist/{sequenceDiagram-WJ2MYXX4-b6pei3lJ.js → sequenceDiagram-WJ2MYXX4-w1pWbIpk.js} +1 -1
  100. package/dist/{spec-BJzgmvVa.js → spec-Dq8RNplp.js} +41 -11
  101. package/dist/{stateDiagram-D77RDMKH-Be-xSpaZ.js → stateDiagram-D77RDMKH-BCv7kP4H.js} +2 -2
  102. package/dist/{stateDiagram-v2-MP3YSRHH-5Y-FxesJ.js → stateDiagram-v2-MP3YSRHH-D1cTOt0x.js} +2 -2
  103. package/dist/static-state-BldGd99T.js +72 -0
  104. package/dist/strings-BGW-fXVJ.js +507 -0
  105. package/dist/style.css +1 -1
  106. package/dist/{swimlanes-42K2YHIH-nRRl4NU0.js → swimlanes-42K2YHIH-DNO6jDnd.js} +1 -1
  107. package/dist/{swimlanesDiagram-VR7AAH4N-BS79510V.js → swimlanesDiagram-VR7AAH4N-CkSWsxYJ.js} +2 -2
  108. package/dist/{swiper-component-DKntyn8p.js → swiper-component-Dir1o8ml.js} +2 -2
  109. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  110. package/dist/{timeline-definition-24CTP7MA-Cfzf8WPf.js → timeline-definition-24CTP7MA-DQvqGFM9.js} +1 -1
  111. package/dist/toast-BWIPi3aF.js +491 -0
  112. package/dist/{tooltip-CtBZ5q8p.js → tooltip-C3UnvsRe.js} +329 -305
  113. package/dist/use-restore-focus-BWO0z1lo.js +1672 -0
  114. package/dist/{useAsyncData-BD534XmW.js → useAsyncData-DsZMvNXn.js} +15 -14
  115. package/dist/{useDateFormatter-DkajGwwd.js → useDateFormatter-ChB4eyim.js} +2 -2
  116. package/dist/{useDeepCompareMemoize-C72_RNsu.js → useDeepCompareMemoize-KMBBux8C.js} +1 -1
  117. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-cMNQ80CK.js} +1 -1
  118. package/dist/useLifecycle-BquH9ET-.js +112 -0
  119. package/dist/useTheme-B9C1JIy9.js +307 -0
  120. package/dist/utils-DXkj82sK.js +813 -0
  121. package/dist/{vega-component-DfcYgj-Y.js → vega-component-9NOEITbg.js} +170 -169
  122. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  123. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  124. package/dist/{vennDiagram-4TSXK5OY-CeY6Z1xZ.js → vennDiagram-4TSXK5OY-BfzXp4SH.js} +1 -1
  125. package/dist/{wardleyDiagram-VM6X3IG4-Cxs9NtVi.js → wardleyDiagram-VM6X3IG4-CiqEE8eq.js} +1 -1
  126. package/dist/{xychartDiagram-S5SC5T6Z-DNWbvPK7.js → xychartDiagram-S5SC5T6Z-BTIHCVOI.js} +2 -2
  127. package/package.json +16 -20
  128. package/src/__mocks__/document.tsx +63 -0
  129. package/src/__mocks__/notebook.ts +3 -4
  130. package/src/__mocks__/partial.ts +26 -0
  131. package/src/__mocks__/requests.ts +1 -4
  132. package/src/__tests__/main.test.tsx +24 -1
  133. package/src/__tests__/module-entry.test.ts +44 -0
  134. package/src/__tests__/mount.test.ts +1 -21
  135. package/src/__tests__/{host-imports.test.ts → replica-imports.test.ts} +12 -11
  136. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  137. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  138. package/src/components/app-config/ai-config.tsx +7 -2
  139. package/src/components/app-config/app-config-form.tsx +5 -9
  140. package/src/components/app-config/user-config-form.tsx +35 -56
  141. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  142. package/src/components/chat/__tests__/notebook-access-selector.test.tsx +64 -0
  143. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  144. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  145. package/src/components/chat/acp/blocks.tsx +3 -2
  146. package/src/components/chat/acp/session-tabs.tsx +8 -3
  147. package/src/components/chat/chat-components.tsx +2 -0
  148. package/src/components/chat/chat-panel.tsx +20 -101
  149. package/src/components/chat/chat-utils.ts +4 -8
  150. package/src/components/chat/notebook-access-selector.stories.tsx +22 -0
  151. package/src/components/chat/notebook-access-selector.tsx +100 -0
  152. package/src/components/data-table/TableBottomBar.tsx +4 -1
  153. package/src/components/data-table/TableTopBar.tsx +2 -0
  154. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  155. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  156. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  157. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  158. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  159. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  160. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  161. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  162. package/src/components/data-table/__tests__/pagination.test.tsx +28 -1
  163. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  164. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  165. package/src/components/data-table/cell-selection/feature.ts +2 -2
  166. package/src/components/data-table/cell-selection/types.ts +2 -2
  167. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  168. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  169. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  170. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  171. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  172. package/src/components/data-table/charts/charts.tsx +40 -30
  173. package/src/components/data-table/charts/storage.ts +16 -5
  174. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  175. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  176. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  177. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  178. package/src/components/data-table/columns.tsx +45 -46
  179. package/src/components/data-table/context-menu.tsx +36 -20
  180. package/src/components/data-table/data-table.tsx +17 -1
  181. package/src/components/data-table/date-popover.tsx +6 -4
  182. package/src/components/data-table/export-actions.tsx +76 -48
  183. package/src/components/data-table/pagination.tsx +44 -33
  184. package/src/components/data-table/range-focus/atoms.ts +4 -6
  185. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  186. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  187. package/src/components/data-table/renderers.tsx +16 -7
  188. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  189. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  190. package/src/components/data-table/search-highlight.tsx +79 -0
  191. package/src/components/data-table/types.ts +1 -0
  192. package/src/components/data-table/url-detector.tsx +49 -9
  193. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  194. package/src/components/datasources/__tests__/column-preview.test.tsx +96 -5
  195. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  196. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  197. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  198. package/src/components/datasources/column-preview.tsx +17 -12
  199. package/src/components/datasources/datasources.tsx +56 -43
  200. package/src/components/datasources/utils.ts +32 -23
  201. package/src/components/debugger/debugger-code.tsx +5 -0
  202. package/src/components/dependency-graph/custom-node.tsx +1 -1
  203. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  204. package/src/components/dependency-graph/elements.ts +24 -21
  205. package/src/components/dependency-graph/minimap-content.tsx +13 -19
  206. package/src/components/dependency-graph/panels.tsx +4 -2
  207. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  208. package/src/components/editor/SortableCell.tsx +1 -3
  209. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  210. package/src/{core/attachments/__tests__/toast.test.tsx → components/editor/__tests__/agent-toast.test.tsx} +1 -1
  211. package/src/components/editor/__tests__/data-attributes.test.tsx +28 -14
  212. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  213. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -2
  214. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  215. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  216. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +34 -8
  217. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +2 -6
  218. package/src/components/editor/actions/__tests__/useCellActionButton.test.tsx +113 -0
  219. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  220. package/src/components/editor/actions/pair-with-agent-commands.ts +2 -2
  221. package/src/components/editor/actions/pair-with-agent-modal.tsx +19 -16
  222. package/src/components/editor/actions/useCellActionButton.tsx +36 -31
  223. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  224. package/src/components/editor/actions/useNotebookActions.tsx +19 -23
  225. package/src/components/editor/actions/useRestartKernel.tsx +1 -1
  226. package/src/components/editor/actions/useSetCodeVisibility.ts +11 -12
  227. package/src/components/editor/actions/useSetOutputsExpanded.ts +14 -19
  228. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  229. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  230. package/src/components/editor/alerts/__tests__/connection-notice.test.tsx +1 -1
  231. package/src/components/editor/alerts/connection-notice.tsx +1 -1
  232. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  233. package/src/components/editor/alerts/stdin-blocking-alert.tsx +1 -1
  234. package/src/components/editor/app-container.tsx +1 -1
  235. package/src/components/editor/boundary/__tests__/ErrorBoundary.test.tsx +1 -1
  236. package/src/components/editor/cell/CreateCellButton.tsx +15 -21
  237. package/src/components/editor/cell/DeleteButton.tsx +3 -2
  238. package/src/components/editor/cell/RunButton.tsx +2 -2
  239. package/src/components/editor/cell/StagedAICell.tsx +10 -2
  240. package/src/components/editor/cell/StopButton.tsx +2 -2
  241. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +4 -2
  242. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  243. package/src/components/editor/cell/cell-actions.tsx +12 -1
  244. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  245. package/src/components/editor/cell/code/cell-editor.tsx +46 -165
  246. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  247. package/src/components/editor/cell/collapse.tsx +11 -4
  248. package/src/components/editor/cell/useAddCell.ts +4 -8
  249. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  250. package/src/components/editor/cell/useRunCells.ts +2 -5
  251. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  252. package/src/components/editor/cell/useSplitCell.tsx +10 -6
  253. package/src/components/editor/chrome/components/__tests__/feedback-button.test.tsx +1 -1
  254. package/src/components/editor/chrome/components/feedback-button.tsx +2 -8
  255. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +31 -2
  256. package/src/components/editor/chrome/panels/__tests__/runtime-details.test.tsx +1 -1
  257. package/src/components/editor/chrome/panels/__tests__/sandbox-recovery.test.tsx +1 -1
  258. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  259. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +1 -1
  260. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  261. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  262. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  263. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  264. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  265. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  266. package/src/components/editor/chrome/panels/session-panel.tsx +1 -1
  267. package/src/components/editor/chrome/panels/snippets-panel.tsx +10 -12
  268. package/src/components/editor/chrome/panels/variable-panel.tsx +1 -5
  269. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +6 -9
  270. package/src/components/editor/chrome/wrapper/__tests__/document-sync-status.test.tsx +92 -0
  271. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +62 -3
  272. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +4 -4
  273. package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
  274. package/src/components/editor/chrome/wrapper/footer-items/document-sync-status.tsx +54 -0
  275. package/src/components/editor/chrome/wrapper/footer-items/lsp-status.tsx +1 -1
  276. package/src/components/editor/chrome/wrapper/footer-items/machine-stats.tsx +1 -1
  277. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  278. package/src/components/editor/chrome/wrapper/footer.tsx +2 -2
  279. package/src/components/editor/chrome/wrapper/minimap-state.ts +1 -1
  280. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  281. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  282. package/src/components/editor/columns/__tests__/cell-column.test.tsx +1 -1
  283. package/src/components/editor/columns/cell-column.tsx +1 -1
  284. package/src/components/editor/columns/sortable-column.tsx +2 -2
  285. package/src/components/editor/connections/components.tsx +5 -10
  286. package/src/components/editor/controls/Controls.tsx +4 -4
  287. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  288. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  289. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +10 -4
  290. package/src/components/editor/controls/command-palette.tsx +22 -10
  291. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  292. package/src/components/editor/documentation.css +34 -11
  293. package/src/components/editor/dynamic-favicon.tsx +21 -40
  294. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +25 -20
  295. package/src/components/editor/errors/auto-fix.tsx +4 -8
  296. package/src/components/editor/errors/send-error-report-button.tsx +3 -6
  297. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  298. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  299. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  300. package/src/components/editor/file-tree/file-explorer.tsx +48 -15
  301. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  302. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  303. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  304. package/src/components/editor/header/__tests__/filename-form.test.tsx +1 -4
  305. package/src/components/editor/header/__tests__/status.test.tsx +5 -3
  306. package/src/components/editor/header/app-header.tsx +1 -1
  307. package/src/components/editor/header/filename-form.tsx +1 -1
  308. package/src/components/editor/header/status.tsx +40 -20
  309. package/src/components/editor/links/__tests__/cell-link.test.tsx +148 -0
  310. package/src/components/editor/links/cell-link-list.tsx +1 -1
  311. package/src/components/editor/links/cell-link.tsx +17 -7
  312. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  313. package/src/components/editor/navigation/__tests__/navigation.test.ts +108 -85
  314. package/src/components/editor/navigation/__tests__/selection.test.ts +43 -21
  315. package/src/components/editor/navigation/clipboard.ts +17 -14
  316. package/src/components/editor/navigation/focus-utils.ts +23 -4
  317. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +17 -33
  318. package/src/components/editor/navigation/navigation.ts +71 -70
  319. package/src/components/editor/navigation/selection.ts +30 -29
  320. package/src/components/editor/notebook-banner.tsx +1 -0
  321. package/src/components/editor/notebook-cell.tsx +29 -32
  322. package/src/components/editor/output/MarimoErrorOutput.tsx +2 -1
  323. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  324. package/src/components/editor/output/Outputs.css +8 -0
  325. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  326. package/src/components/editor/output/ansi-reduce.ts +33 -20
  327. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  328. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  329. package/src/components/editor/package-alert.tsx +14 -7
  330. package/src/components/editor/renderers/cell-array.tsx +22 -33
  331. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  332. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  333. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  334. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +4 -4
  335. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  336. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +1 -1
  337. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
  338. package/src/components/editor/stdin-blocking-alert.tsx +1 -1
  339. package/src/components/home/components.tsx +17 -31
  340. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  341. package/src/components/icons/large-spinner.tsx +3 -14
  342. package/src/components/icons/spinner.tsx +1 -1
  343. package/src/components/markdown/markdown-renderer.tsx +4 -8
  344. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  345. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  346. package/src/components/pages/edit-page.tsx +19 -17
  347. package/src/components/pages/home-page.tsx +164 -91
  348. package/src/components/scratchpad/scratchpad.test.tsx +89 -0
  349. package/src/components/scratchpad/scratchpad.tsx +43 -32
  350. package/src/components/scratchpad/state.ts +7 -0
  351. package/src/components/slides/__tests__/minimap-actions.test.tsx +22 -29
  352. package/src/components/slides/__tests__/minimap.test.ts +38 -19
  353. package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +37 -0
  354. package/src/components/slides/minimap.tsx +17 -12
  355. package/src/components/slides/reveal-component.tsx +58 -26
  356. package/src/components/slides/reveal-slides.css +5 -4
  357. package/src/components/slides/slide-cell-view.tsx +4 -5
  358. package/src/components/slides/slide-form.tsx +1 -0
  359. package/src/components/slides/slide-scroll-hint.tsx +3 -1
  360. package/src/components/sort/SortableCellsProvider.tsx +8 -25
  361. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  362. package/src/components/tracing/tracing.tsx +46 -42
  363. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  364. package/src/components/ui/__tests__/range-slider.test.tsx +165 -0
  365. package/src/components/ui/alert-dialog.tsx +1 -1
  366. package/src/components/ui/draggable-popover.tsx +24 -2
  367. package/src/components/ui/range-slider.tsx +22 -9
  368. package/src/core/MarimoApp.tsx +1 -1
  369. package/src/core/ai/__tests__/staged-cells.test.ts +32 -33
  370. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +8 -12
  371. package/src/core/ai/context/providers/cell-output.ts +2 -2
  372. package/src/core/ai/context/providers/datasource.ts +6 -2
  373. package/src/core/ai/staged-cells.ts +18 -19
  374. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +40 -88
  375. package/src/core/ai/tools/__tests__/registry.test.ts +17 -0
  376. package/src/core/ai/tools/base.ts +18 -15
  377. package/src/core/ai/tools/edit-notebook-tool.ts +86 -80
  378. package/src/core/ai/tools/registry.ts +13 -10
  379. package/src/core/ai/tools/sample-tool.ts +6 -7
  380. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -37
  381. package/src/core/cells/__tests__/actions.test.ts +624 -35
  382. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  383. package/src/core/cells/__tests__/arrange.test.ts +432 -0
  384. package/src/core/cells/__tests__/cell.test.ts +39 -0
  385. package/src/core/cells/__tests__/cells.test.ts +251 -2540
  386. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  387. package/src/core/cells/__tests__/document.test.ts +164 -0
  388. package/src/core/cells/__tests__/editor.test.ts +57 -0
  389. package/src/core/cells/__tests__/flush-document.test.ts +40 -0
  390. package/src/core/cells/__tests__/harness.ts +106 -0
  391. package/src/core/cells/__tests__/layout.test.ts +177 -0
  392. package/src/core/cells/__tests__/notebook-outline.test.ts +122 -0
  393. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  394. package/src/core/cells/__tests__/session.test.ts +445 -211
  395. package/src/core/cells/__tests__/utils.test.ts +9 -108
  396. package/src/core/cells/actions.ts +675 -6
  397. package/src/core/cells/add-missing-import.ts +47 -43
  398. package/src/core/cells/arrange.ts +529 -0
  399. package/src/core/cells/cell.ts +14 -3
  400. package/src/core/cells/cells.ts +126 -1363
  401. package/src/core/cells/columns.ts +38 -0
  402. package/src/core/cells/document.ts +178 -0
  403. package/src/core/cells/edit.ts +30 -0
  404. package/src/core/cells/editor.ts +82 -0
  405. package/src/core/cells/layout.ts +221 -0
  406. package/src/core/cells/outputs.ts +12 -28
  407. package/src/core/cells/pending-delete-service.ts +3 -7
  408. package/src/core/cells/session.ts +76 -53
  409. package/src/core/cells/types.ts +22 -6
  410. package/src/core/cells/utils.ts +10 -8
  411. package/src/core/codemirror/__tests__/format.test.ts +0 -34
  412. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  413. package/src/core/codemirror/ai/__tests__/resources.test.ts +117 -0
  414. package/src/core/codemirror/ai/resources.ts +1 -1
  415. package/src/core/codemirror/cells/__tests__/extensions.test.ts +2 -6
  416. package/src/core/codemirror/cells/extensions.ts +30 -28
  417. package/src/core/codemirror/cells/state.ts +1 -6
  418. package/src/core/codemirror/copilot/__tests__/getCodes.test.ts +6 -7
  419. package/src/core/codemirror/copilot/getCodes.ts +2 -2
  420. package/src/core/codemirror/format.ts +6 -23
  421. package/src/core/codemirror/keymaps/vim.ts +4 -8
  422. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  423. package/src/core/codemirror/language/__tests__/utils.test.ts +187 -15
  424. package/src/core/codemirror/language/cell-code.ts +114 -0
  425. package/src/core/codemirror/language/extension.ts +52 -35
  426. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  427. package/src/core/codemirror/language/languages/python.ts +16 -10
  428. package/src/core/codemirror/language/languages/sql/sql.ts +16 -24
  429. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  430. package/src/core/codemirror/language/types.ts +7 -7
  431. package/src/core/codemirror/language/utils.ts +9 -39
  432. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +1 -2
  433. package/src/core/codemirror/lsp/notebook-lsp.ts +6 -5
  434. package/src/core/codemirror/lsp/utils.ts +1 -1
  435. package/src/core/codemirror/misc/__tests__/paste.test.ts +3 -1
  436. package/src/core/codemirror/misc/paste.ts +1 -1
  437. package/src/core/codemirror/readonly/__tests__/extension.test.ts +1 -1
  438. package/src/core/codemirror/readonly/extension.ts +2 -2
  439. package/src/core/codemirror/utils.ts +1 -0
  440. package/src/core/config/__tests__/app-config.test.ts +64 -0
  441. package/src/core/config/__tests__/config-schema.test.ts +13 -6
  442. package/src/core/config/config-schema.ts +13 -4
  443. package/src/core/config/config.ts +26 -12
  444. package/src/core/config/feature-flag.tsx +0 -2
  445. package/src/core/datasets/__tests__/data-source-discovery.test.ts +5 -5
  446. package/src/core/datasets/data-source-discovery.ts +4 -6
  447. package/src/core/documentation/doc-lookup.ts +1 -1
  448. package/src/core/edit-app.tsx +21 -28
  449. package/src/core/errors/error-entries.ts +4 -1
  450. package/src/core/export/hooks.ts +1 -1
  451. package/src/core/export/layout.ts +2 -2
  452. package/src/core/hotkeys/actions.ts +2 -4
  453. package/src/core/islands/__tests__/bootstrap.test.ts +69 -17
  454. package/src/core/islands/__tests__/bridge.test.ts +72 -25
  455. package/src/core/islands/bootstrap.ts +46 -53
  456. package/src/core/islands/bridge.ts +75 -24
  457. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  458. package/src/core/islands/components/web-components.tsx +1 -1
  459. package/src/core/islands/worker/__tests__/controller.test.ts +62 -4
  460. package/src/core/islands/worker/controller.ts +4 -10
  461. package/src/core/islands/worker/worker.tsx +33 -33
  462. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  463. package/src/core/kernel/__tests__/handlers.test.ts +7 -153
  464. package/src/core/kernel/handlers.ts +19 -72
  465. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  466. package/src/core/meta/code-visibility.ts +18 -9
  467. package/src/core/network/__tests__/connection.test.ts +1 -1
  468. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  469. package/src/core/network/connection-notice.ts +1 -1
  470. package/src/core/network/connection.ts +1 -1
  471. package/src/core/network/requests-lazy.ts +2 -5
  472. package/src/core/network/requests-network.ts +5 -31
  473. package/src/core/network/requests-static.ts +1 -4
  474. package/src/core/network/requests-toasting.tsx +1 -4
  475. package/src/core/network/types.ts +6 -14
  476. package/src/core/network/useConnectionNotice.ts +8 -2
  477. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  478. package/src/core/notebook/attachments.ts +38 -0
  479. package/src/core/notebook/document/__tests__/codemirror.test.ts +758 -0
  480. package/src/core/notebook/document/__tests__/draft.test.ts +363 -0
  481. package/src/core/notebook/document/__tests__/fixtures.ts +92 -0
  482. package/src/core/notebook/document/__tests__/hooks.test.tsx +391 -0
  483. package/src/core/notebook/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  484. package/src/core/notebook/document/__tests__/model.test.ts +247 -0
  485. package/src/core/notebook/document/__tests__/protocol-fixtures.ts +38 -0
  486. package/src/core/notebook/document/__tests__/recovery-scenarios.test.ts +230 -0
  487. package/src/core/notebook/document/__tests__/scenario-fixture.ts +43 -0
  488. package/src/core/notebook/document/__tests__/scenarios.test.ts +205 -0
  489. package/src/core/notebook/document/__tests__/state.test.ts +459 -0
  490. package/src/core/notebook/document/__tests__/submission.test.ts +203 -0
  491. package/src/core/notebook/document/__tests__/test-host.test.ts +92 -0
  492. package/src/core/notebook/document/__tests__/test-host.ts +247 -0
  493. package/src/core/notebook/document/__tests__/test-notebook.ts +149 -0
  494. package/src/core/notebook/document/__tests__/user.ts +61 -0
  495. package/src/core/notebook/document/codemirror.ts +216 -0
  496. package/src/core/notebook/document/draft.ts +502 -0
  497. package/src/core/notebook/document/hooks.tsx +118 -0
  498. package/src/core/notebook/document/index.ts +24 -0
  499. package/src/core/notebook/document/model.ts +102 -0
  500. package/src/core/notebook/document/select.ts +41 -0
  501. package/src/core/notebook/document/state.ts +378 -0
  502. package/src/core/notebook/document/sync.ts +369 -0
  503. package/src/core/notebook/index.ts +19 -0
  504. package/src/core/notebook/operations.ts +30 -0
  505. package/src/core/notebook/replica/AGENTS.md +11 -0
  506. package/src/core/{host → notebook/replica}/__tests__/attachments.test.ts +9 -10
  507. package/src/core/{host → notebook/replica}/__tests__/document.test.ts +18 -16
  508. package/src/core/notebook/replica/__tests__/fixtures.ts +79 -0
  509. package/src/core/notebook/replica/__tests__/replica.test.ts +550 -0
  510. package/src/core/notebook/replica/backend.ts +19 -0
  511. package/src/core/notebook/replica/connection.ts +42 -0
  512. package/src/core/notebook/replica/index.ts +13 -0
  513. package/src/core/notebook/replica/replica.ts +286 -0
  514. package/src/core/{host → notebook/replica}/resources/attachments.ts +6 -3
  515. package/src/core/{host → notebook/replica}/resources/document.ts +4 -4
  516. package/src/core/packages/__tests__/useSandboxController.test.tsx +1 -1
  517. package/src/core/packages/useSandboxController.ts +1 -1
  518. package/src/core/run-app.tsx +48 -51
  519. package/src/core/runtime/__tests__/adapter.test.ts +1 -1
  520. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  521. package/src/core/runtime/__tests__/runtime.test.ts +32 -74
  522. package/src/core/runtime/adapter.ts +1 -1
  523. package/src/core/runtime/config.ts +2 -2
  524. package/src/core/runtime/runtime.ts +33 -46
  525. package/src/core/runtime/types.ts +0 -2
  526. package/src/core/saving/__tests__/document.test.tsx +123 -0
  527. package/src/core/saving/__tests__/state.test.ts +111 -0
  528. package/src/core/saving/__tests__/useAutoSave.test.tsx +67 -0
  529. package/src/core/saving/document.ts +33 -0
  530. package/src/core/saving/filename.ts +1 -1
  531. package/src/core/saving/save-component.tsx +39 -60
  532. package/src/core/saving/state.ts +38 -39
  533. package/src/core/saving/useAutoSave.ts +17 -53
  534. package/src/core/session/__tests__/backend.test.ts +236 -0
  535. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +184 -0
  536. package/src/core/{websocket → session}/__tests__/useNotebookSession.hook.test.tsx +213 -262
  537. package/src/core/session/active-notebook.ts +28 -0
  538. package/src/core/session/backend.ts +123 -0
  539. package/src/core/session/embedded.ts +7 -0
  540. package/src/core/{websocket → session}/types.ts +2 -2
  541. package/src/core/session/useNotebookReplica.ts +74 -0
  542. package/src/core/{websocket → session}/useNotebookSession.tsx +77 -64
  543. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  544. package/src/core/state/jotai.ts +1 -1
  545. package/src/core/static/__tests__/files.test.ts +38 -10
  546. package/src/core/static/files.ts +2 -0
  547. package/src/core/{websocket → transports}/__tests__/connection-state.test.ts +1 -13
  548. package/src/core/transports/__tests__/server.test.ts +408 -0
  549. package/src/core/transports/__tests__/static.test.ts +306 -0
  550. package/src/core/transports/__tests__/wasm.test.ts +166 -0
  551. package/src/core/{websocket → transports}/connection-state.ts +9 -16
  552. package/src/core/transports/server.ts +152 -0
  553. package/src/core/transports/static.ts +166 -0
  554. package/src/core/transports/wasm.ts +97 -0
  555. package/src/core/wasm/__tests__/bridge.test.ts +105 -65
  556. package/src/core/wasm/__tests__/worker.test.ts +187 -0
  557. package/src/core/wasm/bridge.ts +60 -71
  558. package/src/core/wasm/rpc.ts +15 -3
  559. package/src/core/wasm/store.ts +3 -3
  560. package/src/core/wasm/worker/bootstrap.ts +55 -51
  561. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  562. package/src/core/wasm/worker/save-worker.ts +9 -10
  563. package/src/core/wasm/worker/types.ts +29 -18
  564. package/src/core/wasm/worker/worker.ts +89 -45
  565. package/src/css/app/codemirror.css +1 -1
  566. package/src/css/globals.css +66 -22
  567. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  568. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  569. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  570. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  571. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  572. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  573. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  574. package/src/hooks/useAsyncData.ts +3 -3
  575. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  576. package/src/hooks/useDebounce.ts +6 -10
  577. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  578. package/src/hooks/useDelayElapsed.ts +14 -10
  579. package/src/hooks/useElapsedTime.ts +3 -3
  580. package/src/hooks/useEventListener.ts +1 -2
  581. package/src/hooks/useInputHistory.ts +4 -7
  582. package/src/hooks/useInternalStateWithSync.ts +6 -6
  583. package/src/hooks/useResizeObserver.ts +4 -7
  584. package/src/hooks/useScript.ts +67 -70
  585. package/src/hooks/useStateForKey.ts +93 -0
  586. package/src/mount.tsx +20 -15
  587. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  588. package/src/plugins/core/registerReactComponent.tsx +1 -1
  589. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  590. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  591. package/src/plugins/impl/DataTablePlugin.tsx +69 -37
  592. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  593. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  594. package/src/plugins/impl/FormPlugin.tsx +1 -1
  595. package/src/plugins/impl/RangeSliderPlugin.tsx +16 -2
  596. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  597. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  598. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +387 -1
  599. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  600. package/src/plugins/impl/__tests__/RangeSliderPlugin.test.tsx +109 -0
  601. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  602. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +153 -0
  603. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  604. package/src/plugins/impl/anywidget/model.ts +3 -1
  605. package/src/plugins/impl/anywidget/registry.ts +18 -14
  606. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  607. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  608. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  609. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  610. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  611. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  612. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  613. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  614. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  615. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  616. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  617. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  618. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  619. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  620. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  621. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  622. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  623. package/src/plugins/impl/panel/utils.ts +17 -0
  624. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  625. package/src/plugins/impl/plotly/selection.ts +25 -19
  626. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  627. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  628. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  629. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  630. package/src/stories/button.stories.tsx +1 -0
  631. package/src/stories/cell.stories.tsx +2 -5
  632. package/src/stories/data-table.stories.tsx +31 -0
  633. package/src/stories/editor.stories.tsx +24 -17
  634. package/src/stories/form-wrapper.stories.tsx +2 -2
  635. package/src/stories/layout/vertical/one-column.stories.tsx +28 -23
  636. package/src/stories/runtime-story.tsx +23 -17
  637. package/src/stories/select.stories.tsx +2 -2
  638. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  639. package/src/utils/__tests__/arrays.test.ts +35 -0
  640. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  641. package/src/utils/__tests__/{createReducer.test.ts → createReducer.test.tsx} +29 -0
  642. package/src/utils/__tests__/numbers.test.ts +105 -1
  643. package/src/utils/__tests__/objects.test.ts +21 -0
  644. package/src/utils/arrays.ts +11 -2
  645. package/src/utils/blob.ts +1 -1
  646. package/src/utils/createReducer.ts +53 -26
  647. package/src/utils/maps.ts +0 -3
  648. package/src/utils/numbers.ts +79 -18
  649. package/src/utils/objects.ts +4 -9
  650. package/src/utils/once.ts +19 -19
  651. package/src/utils/tracer.ts +9 -5
  652. package/src/utils/zod-utils.ts +1 -3
  653. package/dist/any-language-editor-D4q-IFcM.js +0 -76
  654. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  655. package/dist/assets/worker-HPY9fbhz.js +0 -114
  656. package/dist/code-visibility-DPm4oWK1.js +0 -138
  657. package/dist/dropdown-menu-DHP9P4VE.js +0 -4678
  658. package/dist/feedback-button-DVgw4Y7F.js +0 -379
  659. package/dist/html-to-image-BdA59IUD.js +0 -3251
  660. package/dist/label-DoysmLMW.js +0 -281
  661. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  662. package/dist/objectWithoutPropertiesLoose-BRDcTsfB.js +0 -23
  663. package/dist/precisionRound-DMIVQrt2.js +0 -97
  664. package/dist/select-DemoKT4G.js +0 -1344
  665. package/dist/toDate-B5mIgxwZ.js +0 -1185
  666. package/dist/toast-PkdZraM8.js +0 -1830
  667. package/dist/use-restore-focus-CKjfFjLM.js +0 -6369
  668. package/dist/useLifecycle-BGWzTwHK.js +0 -169
  669. package/dist/useTheme-BhQvec43.js +0 -904
  670. package/src/components/editor/cell/code/__tests__/rtc-editor-config.test.ts +0 -50
  671. package/src/components/editor/chrome/wrapper/footer-items/rtc-status.tsx +0 -65
  672. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  673. package/src/components/editor/navigation/state.ts +0 -65
  674. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +0 -656
  675. package/src/core/ai/tools/run-cells-tool.ts +0 -303
  676. package/src/core/attachments/state.ts +0 -18
  677. package/src/core/cells/__tests__/apply-transaction.test.ts +0 -991
  678. package/src/core/cells/__tests__/document-changes.test.ts +0 -1140
  679. package/src/core/cells/__tests__/document-roundtrip.test.ts +0 -392
  680. package/src/core/cells/document-changes.ts +0 -944
  681. package/src/core/codemirror/rtc/extension.ts +0 -453
  682. package/src/core/codemirror/rtc/loro/__tests__/sync.test.ts +0 -52
  683. package/src/core/codemirror/rtc/loro/awareness.ts +0 -531
  684. package/src/core/codemirror/rtc/loro/colors.ts +0 -38
  685. package/src/core/codemirror/rtc/loro/sync.ts +0 -168
  686. package/src/core/host/AGENTS.md +0 -11
  687. package/src/core/host/__tests__/events.test.ts +0 -46
  688. package/src/core/host/__tests__/fixtures.ts +0 -39
  689. package/src/core/host/__tests__/notebook-client.test.ts +0 -433
  690. package/src/core/host/__tests__/protocol.test.ts +0 -214
  691. package/src/core/host/__tests__/stream.test.ts +0 -211
  692. package/src/core/host/connection.ts +0 -81
  693. package/src/core/host/events.ts +0 -61
  694. package/src/core/host/notebook-client.ts +0 -290
  695. package/src/core/host/protocol.ts +0 -1943
  696. package/src/core/host/resources/resource.ts +0 -59
  697. package/src/core/host/stream.ts +0 -213
  698. package/src/core/rtc/state.ts +0 -34
  699. package/src/core/state/__tests__/jotai.test.ts +0 -25
  700. package/src/core/wasm/worker/message-buffer.ts +0 -33
  701. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  702. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -170
  703. package/src/core/websocket/state.ts +0 -7
  704. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  705. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  706. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  707. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  708. package/src/core/websocket/transports/basic.ts +0 -102
  709. package/src/core/websocket/transports/create.ts +0 -31
  710. package/src/core/websocket/transports/sse.ts +0 -308
  711. package/src/core/websocket/transports/transport.ts +0 -50
  712. package/src/core/websocket/transports/ws.ts +0 -98
  713. package/src/core/websocket/useNotebookClient.ts +0 -88
  714. package/src/utils/__tests__/id-tree.test.ts +0 -2145
  715. package/src/utils/id-tree.tsx +0 -1134
  716. /package/src/{core/attachments/toast.ts → components/editor/agent-toast.ts} +0 -0
  717. /package/src/core/{websocket → session}/connection-utils.ts +0 -0
@@ -1,2145 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
- import { beforeEach, describe, expect, it } from "vitest";
3
- import { SETUP_CELL_ID } from "@/core/cells/ids";
4
- import {
5
- type CellColumnId,
6
- type CellIndex,
7
- CollapsibleTree,
8
- MultiColumn,
9
- } from "../id-tree";
10
-
11
- let tree: CollapsibleTree<string>;
12
-
13
- describe("CollapsibleTree", () => {
14
- beforeEach(() => {
15
- tree = CollapsibleTree.from(["one", "two", "three", "four"]);
16
- });
17
-
18
- it("initializes correctly", () => {
19
- expect(tree.toString()).toMatchInlineSnapshot(`
20
- "one
21
- two
22
- three
23
- four
24
- "
25
- `);
26
- });
27
-
28
- it("collapses nodes correctly with no end", () => {
29
- const collapsedTree = tree.collapse("two", undefined);
30
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
31
- expect(collapsedTree.isCollapsed("two")).toBe(true);
32
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
33
- "one
34
- two (collapsed)
35
- three
36
- four
37
- "
38
- `);
39
- });
40
-
41
- it("collapses nodes correctly with an 'until' in the middle", () => {
42
- const collapsedTree = tree.collapse("two", "three");
43
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
44
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
45
- "one
46
- two (collapsed)
47
- three
48
- four
49
- "
50
- `);
51
- });
52
-
53
- it("collapses nodes correctly with an 'until' at the end", () => {
54
- const collapsedTree = tree.collapse("two", "four");
55
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
56
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
57
- "one
58
- two (collapsed)
59
- three
60
- four
61
- "
62
- `);
63
- });
64
-
65
- it("collapses nodes correctly if just the last node", () => {
66
- const collapsedTree = tree.collapse("four", undefined);
67
- expect(collapsedTree.nodes[3].isCollapsed).toBe(true);
68
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
69
- "one
70
- two
71
- three
72
- four (collapsed)
73
- "
74
- `);
75
- });
76
-
77
- it("collapses nodes correctly if just the first node", () => {
78
- const collapsedTree = tree.collapse("one", undefined);
79
- expect(collapsedTree.nodes[0].isCollapsed).toBe(true);
80
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
81
- "one (collapsed)
82
- two
83
- three
84
- four
85
- "
86
- `);
87
- });
88
-
89
- it("can double collapse", () => {
90
- const collapsedTree = tree
91
- .collapse("three", undefined)
92
- .collapse("two", undefined);
93
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
94
- "one
95
- two (collapsed)
96
- three (collapsed)
97
- four
98
- "
99
- `);
100
- });
101
-
102
- it("failures to collapse", () => {
103
- expect(() =>
104
- tree.collapse("five", undefined),
105
- ).toThrowErrorMatchingInlineSnapshot(
106
- "[Error: Node five not found in tree. Valid ids: one,two,three,four]",
107
- );
108
- expect(() =>
109
- tree.collapse("one", "five"),
110
- ).toThrowErrorMatchingInlineSnapshot(
111
- "[Error: Node five not found in tree]",
112
- );
113
- expect(() =>
114
- tree.collapse("two", "one"),
115
- ).toThrowErrorMatchingInlineSnapshot(
116
- "[Error: Node one is before node two]",
117
- );
118
- expect(() => {
119
- tree = tree.collapse("two", undefined);
120
- tree = tree.collapse("two", undefined);
121
- }).toThrowErrorMatchingInlineSnapshot(
122
- "[Error: Node two is already collapsed]",
123
- );
124
- });
125
-
126
- it("collapses all nodes from leaves to root in one call", () => {
127
- const collapsedTree = tree.collapseAll([
128
- { id: "one", until: undefined },
129
- { id: "two", until: undefined },
130
- { id: "three", until: undefined },
131
- { id: "four", until: undefined },
132
- ]);
133
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
134
- "one (collapsed)
135
- two (collapsed)
136
- three (collapsed)
137
- four (collapsed)
138
- "
139
- `);
140
- });
141
-
142
- it("collapses some nodes from leaves to root in one call", () => {
143
- const collapsedTree = tree.collapseAll([
144
- { id: "one", until: undefined },
145
- null,
146
- { id: "three", until: "four" },
147
- null,
148
- ]);
149
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
150
- "one (collapsed)
151
- two
152
- three (collapsed)
153
- four
154
- "
155
- `);
156
- });
157
-
158
- it("failures to collapse all", () => {
159
- expect(() => tree.collapseAll([])).toThrowErrorMatchingInlineSnapshot(
160
- "[Error: No collapse ranges provided]",
161
- );
162
- expect(() =>
163
- tree.collapseAll([
164
- { id: "one", until: undefined },
165
- { id: "two", until: undefined },
166
- ]),
167
- ).toThrowErrorMatchingInlineSnapshot(
168
- "[Error: Collapse ranges length 2 does not match tree length 4]",
169
- );
170
- expect(() =>
171
- tree.collapseAll([null, { id: "one", until: undefined }, null, null]),
172
- ).toThrowErrorMatchingInlineSnapshot(
173
- "[Error: Node two does not match collapse range id one]",
174
- );
175
- expect(() =>
176
- tree.collapseAll([{ id: "one", until: "five" }, null, null, null]),
177
- ).toThrowErrorMatchingInlineSnapshot(
178
- "[Error: Node five not found in tree]",
179
- );
180
- expect(() =>
181
- tree.collapseAll([null, { id: "two", until: "one" }, null, null]),
182
- ).toThrowErrorMatchingInlineSnapshot(
183
- "[Error: Node one is before node two]",
184
- );
185
- });
186
-
187
- it("expands nodes correctly", () => {
188
- const collapsed = tree.collapse("two", undefined);
189
- expect(collapsed.nodes[1].isCollapsed).toBe(true);
190
- const expandedTree = collapsed.expand("two");
191
- expect(expandedTree.nodes[1].isCollapsed).toBe(false);
192
- expect(expandedTree.toString()).toMatchInlineSnapshot(`
193
- "one
194
- two
195
- three
196
- four
197
- "
198
- `);
199
- });
200
-
201
- it("expands all nested collapsed nodes correctly in one call", () => {
202
- let collapsed = tree.collapse("three", undefined);
203
- expect(collapsed.nodes[2].isCollapsed).toBe(true);
204
- collapsed = collapsed.collapse("two", undefined);
205
- expect(collapsed.nodes[1].isCollapsed).toBe(true);
206
- const expandedTree = collapsed.expandAll();
207
- expect(expandedTree.nodes[1].isCollapsed).toBe(false);
208
- expect(expandedTree.toString()).toMatchInlineSnapshot(`
209
- "one
210
- two
211
- three
212
- four
213
- "
214
- `);
215
- });
216
-
217
- it("fails to expand when node not found", () => {
218
- expect(() => tree.expand("five")).toThrowErrorMatchingInlineSnapshot(
219
- "[Error: Node five not found in tree. Valid ids: one,two,three,four]",
220
- );
221
- });
222
-
223
- it("expand on already expanded node is a no-op", () => {
224
- // Expanding an already expanded node should return the same tree (no-op)
225
- const result = tree.expand("one");
226
- expect(result).toBe(tree);
227
- // Can call multiple times without error
228
- const result2 = tree.expand("one");
229
- expect(result2).toBe(tree);
230
- });
231
-
232
- it("moves nodes correctly", () => {
233
- const movedTree = tree.move(0, 1);
234
- expect(movedTree.toString()).toMatchInlineSnapshot(`
235
- "two
236
- one
237
- three
238
- four
239
- "
240
- `);
241
- });
242
-
243
- it("moves a collapsed node correctly", () => {
244
- const collapsed = tree.collapse("two", "three");
245
- expect(collapsed.toString()).toMatchInlineSnapshot(`
246
- "one
247
- two (collapsed)
248
- three
249
- four
250
- "
251
- `);
252
- const movedTree = collapsed.move(1, 2);
253
- expect(movedTree.toString()).toMatchInlineSnapshot(`
254
- "one
255
- four
256
- two (collapsed)
257
- three
258
- "
259
- `);
260
- });
261
-
262
- it("inserts nodes correctly", () => {
263
- tree = tree.insert("newNode", 1);
264
- expect(tree.toString()).toMatchInlineSnapshot(`
265
- "one
266
- newNode
267
- two
268
- three
269
- four
270
- "
271
- `);
272
-
273
- tree = tree.insert("newNode2", tree.length - 1);
274
- expect(tree.toString()).toMatchInlineSnapshot(`
275
- "one
276
- newNode
277
- two
278
- three
279
- newNode2
280
- four
281
- "
282
- `);
283
-
284
- tree = tree.insert("newNode3", 10_000);
285
- expect(tree.toString()).toMatchInlineSnapshot(`
286
- "one
287
- newNode
288
- two
289
- three
290
- newNode2
291
- four
292
- newNode3
293
- "
294
- `);
295
- });
296
-
297
- it("counts children correctly", () => {
298
- expect(tree.getCount("who")).toBe(0);
299
- expect(tree.getCount("one")).toBe(0);
300
- // Collapse three and get count
301
- tree = tree.collapse("three", undefined);
302
- expect(tree.getCount("three")).toBe(1);
303
- // Collapse two and get count
304
- tree = tree.collapse("two", undefined);
305
- expect(tree.getCount("two")).toBe(2);
306
- });
307
-
308
- it("finds deep correctly", () => {
309
- expect(tree.find("three")).toEqual(["three"]);
310
- tree = tree.collapse("three", undefined).collapse("two", undefined);
311
- expect(tree.find("three")).toEqual(["two", "three"]);
312
- expect(tree.find("four")).toEqual(["two", "three", "four"]);
313
- expect(tree.toString()).toMatchInlineSnapshot(`
314
- "one
315
- two (collapsed)
316
- three (collapsed)
317
- four
318
- "
319
- `);
320
- });
321
-
322
- it("finds and expands correctly at a leaf", () => {
323
- tree = tree.collapse("three", undefined).collapse("two", undefined);
324
- expect(tree.toString()).toMatchInlineSnapshot(`
325
- "one
326
- two (collapsed)
327
- three (collapsed)
328
- four
329
- "
330
- `);
331
- const prevTree = tree;
332
- tree = tree.findAndExpandDeep("four");
333
- expect(tree.toString()).toMatchInlineSnapshot(`
334
- "one
335
- two
336
- three
337
- four
338
- "
339
- `);
340
- // doesn't mutate
341
- expect(prevTree.toString()).not.toEqual(tree.toString());
342
- });
343
-
344
- it("finds and expands correctly at a non-leaf", () => {
345
- tree = tree.collapse("three", undefined).collapse("two", undefined);
346
- expect(tree.toString()).toMatchInlineSnapshot(`
347
- "one
348
- two (collapsed)
349
- three (collapsed)
350
- four
351
- "
352
- `);
353
- const prevTree = tree;
354
- tree = tree.findAndExpandDeep("three");
355
- expect(tree.toString()).toMatchInlineSnapshot(`
356
- "one
357
- two
358
- three
359
- four
360
- "
361
- `);
362
- // doesn't mutate
363
- expect(prevTree.toString()).not.toEqual(tree.toString());
364
- });
365
-
366
- it("can delete nodes", () => {
367
- tree = tree.collapse("three", undefined).collapse("two", undefined);
368
- expect(tree.toString()).toMatchInlineSnapshot(`
369
- "one
370
- two (collapsed)
371
- three (collapsed)
372
- four
373
- "
374
- `);
375
- tree = tree.deleteAtIndex(1);
376
- expect(tree.toString()).toMatchInlineSnapshot(`
377
- "one
378
- three (collapsed)
379
- four
380
- "
381
- `);
382
- });
383
-
384
- it("can delete non-collapsed nodes without throwing", () => {
385
- // Deleting a non-collapsed node should not throw
386
- // (previously this would throw "Node is already expanded" internally)
387
- const result = tree.deleteAtIndex(1);
388
- expect(result.toString()).toMatchInlineSnapshot(`
389
- "one
390
- three
391
- four
392
- "
393
- `);
394
- });
395
-
396
- it("fails to delete nodes", () => {
397
- expect(() => tree.deleteAtIndex(5)).toThrowErrorMatchingInlineSnapshot(
398
- "[Error: Node at index 5 not found in tree]",
399
- );
400
- });
401
-
402
- it("top-level ids are correct", () => {
403
- expect(tree.topLevelIds).toMatchInlineSnapshot(`
404
- [
405
- "one",
406
- "two",
407
- "three",
408
- "four",
409
- ]
410
- `);
411
-
412
- const collapsed = tree.collapse("two", undefined);
413
- expect(collapsed.topLevelIds).toMatchInlineSnapshot(`
414
- [
415
- "one",
416
- "two",
417
- ]
418
- `);
419
- });
420
-
421
- it("in order ids are correct", () => {
422
- expect(tree.inOrderIds).toMatchInlineSnapshot(`
423
- [
424
- "one",
425
- "two",
426
- "three",
427
- "four",
428
- ]
429
- `);
430
-
431
- const collapsed = tree.collapse("two", undefined);
432
-
433
- // Not mutable
434
- expect(tree.inOrderIds).toEqual(tree.inOrderIds);
435
-
436
- expect(collapsed.inOrderIds).toMatchInlineSnapshot(`
437
- [
438
- "one",
439
- "two",
440
- "three",
441
- "four",
442
- ]
443
- `);
444
- });
445
-
446
- it("atOrThrow throws if not found", () => {
447
- expect(() => tree.atOrThrow(5)).toThrowErrorMatchingInlineSnapshot(
448
- "[Error: Node at index 5 not found in tree]",
449
- );
450
- });
451
-
452
- it("first and last are correct", () => {
453
- expect(tree.first()).toBe("one");
454
- expect(tree.last()).toBe("four");
455
- });
456
-
457
- it("length is correct", () => {
458
- expect(tree.length).toBe(4);
459
-
460
- const collapsed = tree.collapse("two", undefined);
461
- expect(collapsed.length).toBe(2);
462
- });
463
-
464
- it("moves to front and back correctly", () => {
465
- tree = tree.collapse("three", undefined).moveToBack("three");
466
- expect(tree.toString()).toMatchInlineSnapshot(`
467
- "one
468
- two
469
- three (collapsed)
470
- four
471
- "
472
- `);
473
-
474
- tree = tree.moveToFront("two");
475
- expect(tree.toString()).toMatchInlineSnapshot(`
476
- "two
477
- one
478
- three (collapsed)
479
- four
480
- "
481
- `);
482
- });
483
-
484
- it("getDescendants correctly", () => {
485
- expect(tree.getDescendants("two")).toEqual([]);
486
- expect(tree.getDescendants("who")).toEqual([]);
487
-
488
- tree = tree.collapse("three", undefined).collapse("two", undefined);
489
- expect(tree.getDescendants("two")).toMatchInlineSnapshot(`
490
- [
491
- "three",
492
- "four",
493
- ]
494
- `);
495
-
496
- // We only get descendants of the top level
497
- expect(tree.getDescendants("three")).toMatchInlineSnapshot("[]");
498
- });
499
-
500
- it("handles slice correctly", () => {
501
- // Normal slice in middle
502
- expect(tree.slice(1, 3)).toEqual(["two", "three"]);
503
-
504
- // Slice from beginning
505
- expect(tree.slice(0, 2)).toEqual(["one", "two"]);
506
-
507
- // Slice to end
508
- expect(tree.slice(2, 4)).toEqual(["three", "four"]);
509
-
510
- // Slice entire array
511
- expect(tree.slice(0, 4)).toEqual(["one", "two", "three", "four"]);
512
-
513
- // Slice with out-of-bounds end
514
- expect(tree.slice(2, 10)).toEqual(["three", "four"]);
515
-
516
- // Slice with negative start (should work like normal array slice)
517
- expect(tree.slice(-2, 4)).toEqual(["three", "four"]);
518
-
519
- // Slice with start > end (should return empty array)
520
- expect(tree.slice(3, 1)).toEqual([]);
521
-
522
- // Slice with same start and end
523
- expect(tree.slice(2, 2)).toEqual([]);
524
-
525
- // Slice single element
526
- expect(tree.slice(1, 2)).toEqual(["two"]);
527
- });
528
-
529
- it("handles slice with collapsed nodes", () => {
530
- const collapsed = tree.collapse("two", "three");
531
-
532
- // Should only return top-level nodes, not descendants
533
- expect(collapsed.slice(0, 2)).toEqual(["one", "two"]);
534
- expect(collapsed.slice(1, 3)).toEqual(["two", "four"]);
535
- });
536
-
537
- it("handles after correctly", () => {
538
- // Normal case - get next node
539
- expect(tree.after("one")).toBe("two");
540
- expect(tree.after("two")).toBe("three");
541
- expect(tree.after("three")).toBe("four");
542
-
543
- // Edge case - last node should return undefined
544
- expect(tree.after("four")).toBeUndefined();
545
-
546
- // Edge case - non-existent node should throw
547
- expect(() => tree.after("nonexistent")).toThrow(
548
- "Node nonexistent not found in tree. Valid ids: one,two,three,four",
549
- );
550
- });
551
-
552
- it("handles after with collapsed nodes", () => {
553
- const collapsed = tree.collapse("two", "three");
554
-
555
- // Should return next top-level node
556
- expect(collapsed.after("one")).toBe("two");
557
- expect(collapsed.after("two")).toBe("four");
558
- expect(collapsed.after("four")).toBeUndefined();
559
-
560
- // Should throw for collapsed children that aren't top-level
561
- expect(() => collapsed.after("three")).toThrow(
562
- "Node three not found in tree. Valid ids: one,two,four",
563
- );
564
- });
565
-
566
- it("handles before correctly", () => {
567
- // Normal case - get previous node
568
- expect(tree.before("two")).toBe("one");
569
- expect(tree.before("three")).toBe("two");
570
- expect(tree.before("four")).toBe("three");
571
-
572
- // Edge case - first node should return undefined
573
- expect(tree.before("one")).toBeUndefined();
574
-
575
- // Edge case - non-existent node should throw
576
- expect(() => tree.before("nonexistent")).toThrow(
577
- "Node nonexistent not found in tree. Valid ids: one,two,three,four",
578
- );
579
- });
580
-
581
- it("handles before with collapsed nodes", () => {
582
- const collapsed = tree.collapse("two", "three");
583
-
584
- // Should return previous top-level node
585
- expect(collapsed.before("two")).toBe("one");
586
- expect(collapsed.before("four")).toBe("two");
587
- expect(collapsed.before("one")).toBeUndefined();
588
-
589
- // Should throw for collapsed children that aren't top-level
590
- expect(() => collapsed.before("three")).toThrow(
591
- "Node three not found in tree. Valid ids: one,two,four",
592
- );
593
- });
594
-
595
- it("handles slice on empty tree", () => {
596
- const emptyTree = CollapsibleTree.from([]);
597
- expect(emptyTree.slice(0, 1)).toEqual([]);
598
- expect(emptyTree.slice(0, 0)).toEqual([]);
599
- });
600
-
601
- it("handles after/before on single element tree", () => {
602
- const singleTree = CollapsibleTree.from(["single"]);
603
- expect(singleTree.after("single")).toBeUndefined();
604
- expect(singleTree.before("single")).toBeUndefined();
605
- });
606
-
607
- it("handles split correctly", () => {
608
- const [left, right] = tree.split("two");
609
- expect(left.toString()).toMatchInlineSnapshot(`
610
- "one
611
- "
612
- `);
613
- expect(right?.toString()).toMatchInlineSnapshot(`
614
- "two
615
- three
616
- four
617
- "
618
- `);
619
- });
620
-
621
- it("handles split at the beginning", () => {
622
- const [left, right] = tree.split("one");
623
- expect(left.toString()).toMatchInlineSnapshot(`
624
- ""
625
- `);
626
- expect(right?.toString()).toMatchInlineSnapshot(`
627
- "one
628
- two
629
- three
630
- four
631
- "
632
- `);
633
- });
634
-
635
- it("handles split at the end", () => {
636
- const [left, right] = tree.split("four");
637
- expect(left.toString()).toMatchInlineSnapshot(`
638
- "one
639
- two
640
- three
641
- "
642
- `);
643
- expect(right?.toString()).toMatchInlineSnapshot(`
644
- "four
645
- "
646
- `);
647
- });
648
-
649
- it("throws when splitting on non-existent node", () => {
650
- expect(() => tree.split("five")).toThrow("Node five not found in tree");
651
- });
652
- });
653
-
654
- describe("CollapsibleTree edge cases", () => {
655
- let tree: CollapsibleTree<string>;
656
-
657
- beforeEach(() => {
658
- tree = CollapsibleTree.from(["A", "B", "C", "D"]);
659
- });
660
-
661
- it("handles insert at start and end", () => {
662
- tree = tree.insertAtStart("Z");
663
- expect(tree.topLevelIds).toEqual(["Z", "A", "B", "C", "D"]);
664
- tree = tree.insertAtEnd("Y");
665
- expect(tree.topLevelIds).toEqual(["Z", "A", "B", "C", "D", "Y"]);
666
- });
667
-
668
- it("handles delete with expand", () => {
669
- tree = tree.collapse("B", "D");
670
- tree = tree.deleteAtIndex(1);
671
- expect(tree.topLevelIds).toEqual(["A", "C", "D"]);
672
- });
673
-
674
- it("finds nodes correctly", () => {
675
- tree = tree.collapse("B", "D");
676
- expect(tree.find("C")).toEqual(["B", "C"]);
677
- expect(tree.find("E")).toEqual([]);
678
- });
679
-
680
- it("handles equality check", () => {
681
- const tree2 = CollapsibleTree.from(["A", "B", "C", "D"]);
682
- expect(tree.equals(tree2)).toBe(true);
683
- tree2.collapse("B", "C");
684
- expect(tree.equals(tree2)).toBe(true);
685
- });
686
-
687
- it("handles getCount correctly", () => {
688
- tree = tree.collapse("B", "D");
689
- expect(tree.getCount("A")).toBe(0);
690
- expect(tree.getCount("B")).toBe(2);
691
- expect(tree.getCount("C")).toBe(0);
692
- });
693
-
694
- it("handles findAndExpandDeep", () => {
695
- tree = tree.collapse("B", "D");
696
- tree = tree.findAndExpandDeep("C");
697
- expect(tree.topLevelIds).toEqual(["A", "B", "C", "D"]);
698
- });
699
-
700
- it("handles multiple collapses and expands", () => {
701
- tree = tree.collapse("B", "C").collapse("A", "D");
702
- expect(tree.topLevelIds).toEqual(["A"]);
703
- tree = tree.expand("A").expand("B");
704
- expect(tree.topLevelIds).toEqual(["A", "B", "C", "D"]);
705
- });
706
-
707
- it("handles edge cases for first and last", () => {
708
- expect(tree.first()).toBe("A");
709
- expect(tree.last()).toBe("D");
710
- tree = CollapsibleTree.from([]);
711
- expect(() => tree.first()).toThrow("Node at index 0 not found in tree");
712
- expect(() => tree.last()).toThrow("Node at index -1 not found in tree");
713
- });
714
-
715
- it("handles inOrderIds with nested structure", () => {
716
- tree = tree.collapse("B", "C");
717
- expect(tree.inOrderIds).toEqual(["A", "B", "C", "D"]);
718
- });
719
-
720
- it("handles moving within bounds", () => {
721
- tree = tree.move(0, 2);
722
- expect(tree.topLevelIds).toEqual(["B", "C", "A", "D"]);
723
- });
724
-
725
- it("handles moving out of bounds", () => {
726
- tree = tree.move(0, 10);
727
- expect(tree.topLevelIds).toEqual(["B", "C", "D", "A"]);
728
- });
729
- });
730
-
731
- describe("MultiColumn", () => {
732
- let multiColumn: MultiColumn<string>;
733
- let columnIds: CellColumnId[];
734
-
735
- beforeEach(() => {
736
- multiColumn = MultiColumn.from([
737
- ["A1", "A2", "A3"],
738
- ["B1", "B2"],
739
- ["C1", "C2", "C3", "C4"],
740
- ]);
741
-
742
- columnIds = multiColumn.getColumns().map((c) => c.id);
743
- });
744
-
745
- it("initializes correctly", () => {
746
- expect(multiColumn.colLength).toBe(3);
747
- expect(multiColumn.idLength).toBe(9);
748
- expect(multiColumn.topLevelIds).toEqual([
749
- ["A1", "A2", "A3"],
750
- ["B1", "B2"],
751
- ["C1", "C2", "C3", "C4"],
752
- ]);
753
- });
754
-
755
- it("creates from empty array", () => {
756
- const emptyMultiColumn = MultiColumn.from([]);
757
- expect(emptyMultiColumn.colLength).toBe(1);
758
- expect(emptyMultiColumn.idLength).toBe(0);
759
- expect(emptyMultiColumn.isEmpty()).toBe(true);
760
- });
761
-
762
- it("creates from ids and columns", () => {
763
- const idAndColumns: [string, number | undefined | null][] = [
764
- ["A1", 0],
765
- ["A2", 0],
766
- ["B1", 1],
767
- ["C1", 2],
768
- ["C2", undefined],
769
- ["D1", null],
770
- ["E1", -1],
771
- ];
772
- const fromIdsAndColumns = MultiColumn.fromIdsAndColumns(idAndColumns);
773
- expect(fromIdsAndColumns.colLength).toBe(3);
774
- expect(fromIdsAndColumns.topLevelIds).toEqual([
775
- ["A1", "A2"],
776
- ["B1"],
777
- ["C1", "C2", "D1", "E1"],
778
- ]);
779
- });
780
-
781
- it("iterates top-level ids", () => {
782
- const ids = [...multiColumn.iterateTopLevelIds];
783
- expect(ids).toEqual(["A1", "A2", "A3", "B1", "B2", "C1", "C2", "C3", "C4"]);
784
- });
785
-
786
- it("gets in-order ids", () => {
787
- expect(multiColumn.inOrderIds).toEqual([
788
- "A1",
789
- "A2",
790
- "A3",
791
- "B1",
792
- "B2",
793
- "C1",
794
- "C2",
795
- "C3",
796
- "C4",
797
- ]);
798
- });
799
-
800
- it("checks if it has only one column", () => {
801
- expect(multiColumn.hasOnlyOneColumn()).toBe(false);
802
- const singleColumn = MultiColumn.from([["A1", "A2"]]);
803
- expect(singleColumn.hasOnlyOneColumn()).toBe(true);
804
- });
805
-
806
- it("checks if it has only one id", () => {
807
- expect(multiColumn.hasOnlyOneId()).toBe(false);
808
- const singleId = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
809
- expect(singleId.hasOnlyOneId()).toBe(false);
810
- const singleIdSingleColumn = MultiColumn.from([["A1"]]);
811
- expect(singleIdSingleColumn.hasOnlyOneId()).toBe(true);
812
- });
813
-
814
- it("adds a column", () => {
815
- const columnId = multiColumn.getColumns()[0].id;
816
- const newMultiColumn = multiColumn.addColumn(columnId);
817
- expect(newMultiColumn.colLength).toBe(4);
818
- expect(newMultiColumn.topLevelIds[1]).toEqual([]);
819
- });
820
-
821
- it("inserts a breakpoint", () => {
822
- const withBreakpoint = multiColumn.insertBreakpoint("C3");
823
- expect(withBreakpoint.colLength).toBe(4);
824
- expect(withBreakpoint.topLevelIds).toEqual([
825
- ["A1", "A2", "A3"],
826
- ["B1", "B2"],
827
- ["C1", "C2"],
828
- ["C3", "C4"],
829
- ]);
830
- });
831
-
832
- it("deletes a column", () => {
833
- const withoutBreakpoint = multiColumn.delete(columnIds[1]);
834
- expect(withoutBreakpoint.colLength).toBe(2);
835
- expect(withoutBreakpoint.topLevelIds).toEqual([
836
- ["A1", "A2", "A3", "B1", "B2"],
837
- ["C1", "C2", "C3", "C4"],
838
- ]);
839
- });
840
-
841
- it("deletes a column with only one column", () => {
842
- const singleColumn = MultiColumn.from([["A1", "A2"]]);
843
- const deleted = singleColumn.delete(columnIds[0]);
844
- expect(deleted.colLength).toBe(1);
845
- expect(deleted.topLevelIds).toEqual([["A1", "A2"]]);
846
- });
847
-
848
- it("deletes the first column", () => {
849
- const deleted = multiColumn.delete(columnIds[0]);
850
- expect(deleted.colLength).toBe(2);
851
- expect(deleted.topLevelIds).toEqual([
852
- ["B1", "B2", "A1", "A2", "A3"],
853
- ["C1", "C2", "C3", "C4"],
854
- ]);
855
- });
856
-
857
- it("merges all columns", () => {
858
- const merged = multiColumn.mergeAllColumns();
859
- expect(merged.colLength).toBe(1);
860
- expect(merged.topLevelIds).toEqual([
861
- ["A1", "A2", "A3", "B1", "B2", "C1", "C2", "C3", "C4"],
862
- ]);
863
- });
864
-
865
- it("moves within a column", () => {
866
- const moved = multiColumn.moveWithinColumn(
867
- columnIds[0],
868
- 0 as CellIndex,
869
- 2 as CellIndex,
870
- );
871
- expect(moved.topLevelIds[0]).toEqual(["A2", "A3", "A1"]);
872
- });
873
-
874
- it("moves across columns", () => {
875
- const moved = multiColumn.moveAcrossColumns(
876
- columnIds[0],
877
- "A1",
878
- columnIds[1],
879
- "B1",
880
- );
881
- expect(moved.topLevelIds).toEqual([
882
- ["A2", "A3"],
883
- ["A1", "B1", "B2"],
884
- ["C1", "C2", "C3", "C4"],
885
- ]);
886
- });
887
-
888
- it("moves a column", () => {
889
- const moved = multiColumn.moveColumn(columnIds[0], columnIds[2]);
890
- expect(moved.topLevelIds).toEqual([
891
- ["B1", "B2"],
892
- ["C1", "C2", "C3", "C4"],
893
- ["A1", "A2", "A3"],
894
- ]);
895
- });
896
-
897
- it("moves a column before", () => {
898
- const moved = multiColumn.moveColumn(columnIds[1], columnIds[0]);
899
- expect(moved.topLevelIds).toEqual([
900
- ["B1", "B2"],
901
- ["A1", "A2", "A3"],
902
- ["C1", "C2", "C3", "C4"],
903
- ]);
904
- });
905
-
906
- it("moves to the left", () => {
907
- const moved = multiColumn.moveColumn(columnIds[1], "_left_");
908
- expect(moved.topLevelIds).toEqual([
909
- ["B1", "B2"],
910
- ["A1", "A2", "A3"],
911
- ["C1", "C2", "C3", "C4"],
912
- ]);
913
- });
914
-
915
- it("moves to the right", () => {
916
- const moved = multiColumn.moveColumn(columnIds[1], "_right_");
917
- expect(moved.topLevelIds).toEqual([
918
- ["A1", "A2", "A3"],
919
- ["C1", "C2", "C3", "C4"],
920
- ["B1", "B2"],
921
- ]);
922
- });
923
-
924
- it("moves to a new column", () => {
925
- const newColumn = multiColumn.moveToNewColumn("B1");
926
- expect(newColumn.colLength).toBe(4);
927
- expect(newColumn.topLevelIds).toEqual([
928
- ["A1", "A2", "A3"],
929
- ["B2"],
930
- ["C1", "C2", "C3", "C4"],
931
- ["B1"],
932
- ]);
933
- });
934
-
935
- it("gets column with id", () => {
936
- const column = multiColumn.findWithId("B2");
937
- expect(multiColumn.indexOf(column)).toBe(1);
938
- expect(column.topLevelIds).toEqual(["B1", "B2"]);
939
- });
940
-
941
- it("transforms by id", () => {
942
- const transformed = multiColumn.transformWithCellId("B1", (tree) =>
943
- tree.moveToFront("B2"),
944
- );
945
- expect(transformed.topLevelIds[1]).toEqual(["B2", "B1"]);
946
- });
947
-
948
- it("transforms all columns", () => {
949
- const transformed = multiColumn.transformAll((tree) =>
950
- tree.insertAtStart("NEW"),
951
- );
952
- expect(transformed.topLevelIds).toEqual([
953
- ["NEW", "A1", "A2", "A3"],
954
- ["NEW", "B1", "B2"],
955
- ["NEW", "C1", "C2", "C3", "C4"],
956
- ]);
957
- });
958
-
959
- it("inserts an id", () => {
960
- const inserted = multiColumn.insertId("D1", columnIds[1], 1 as CellIndex);
961
- expect(inserted.topLevelIds[1]).toEqual(["B1", "D1", "B2"]);
962
- });
963
-
964
- describe("moveCellsRelativeTo", () => {
965
- it("moves a single cell before target in same column", () => {
966
- const moved = multiColumn.moveCellsRelativeTo(["A3"], "A1", "before");
967
- expect(moved.topLevelIds).toEqual([
968
- ["A3", "A1", "A2"],
969
- ["B1", "B2"],
970
- ["C1", "C2", "C3", "C4"],
971
- ]);
972
- });
973
-
974
- it("moves a single cell after target in same column", () => {
975
- const moved = multiColumn.moveCellsRelativeTo(["A1"], "A3", "after");
976
- expect(moved.topLevelIds).toEqual([
977
- ["A2", "A3", "A1"],
978
- ["B1", "B2"],
979
- ["C1", "C2", "C3", "C4"],
980
- ]);
981
- });
982
-
983
- it("moves multiple cells before target", () => {
984
- const moved = multiColumn.moveCellsRelativeTo(
985
- ["A1", "A2"],
986
- "B2",
987
- "before",
988
- );
989
- expect(moved.topLevelIds).toEqual([
990
- ["A3"],
991
- ["B1", "A1", "A2", "B2"],
992
- ["C1", "C2", "C3", "C4"],
993
- ]);
994
- });
995
-
996
- it("moves multiple cells after target", () => {
997
- const moved = multiColumn.moveCellsRelativeTo(
998
- ["A2", "A3"],
999
- "B1",
1000
- "after",
1001
- );
1002
- expect(moved.topLevelIds).toEqual([
1003
- ["A1"],
1004
- ["B1", "A2", "A3", "B2"],
1005
- ["C1", "C2", "C3", "C4"],
1006
- ]);
1007
- });
1008
-
1009
- it("returns same MultiColumn when cellIds is empty", () => {
1010
- const moved = multiColumn.moveCellsRelativeTo([], "A1", "before");
1011
- expect(moved).toBe(multiColumn);
1012
- });
1013
-
1014
- it("inserts at end when target is among moved cells", () => {
1015
- const moved = multiColumn.moveCellsRelativeTo(
1016
- ["A1", "A2"],
1017
- "A2",
1018
- "after",
1019
- );
1020
- // Target A2 was removed; insert at end of column 0
1021
- expect(moved.topLevelIds).toEqual([
1022
- ["A3", "A1", "A2"],
1023
- ["B1", "B2"],
1024
- ["C1", "C2", "C3", "C4"],
1025
- ]);
1026
- });
1027
-
1028
- it("throws when node not found", () => {
1029
- expect(() =>
1030
- multiColumn.moveCellsRelativeTo(["Z1"], "A1", "before"),
1031
- ).toThrow("Cell Z1 not found in any column");
1032
- });
1033
- });
1034
-
1035
- it("deletes by id", () => {
1036
- const deleted = multiColumn.deleteById("B1");
1037
- expect(deleted.topLevelIds[1]).toEqual(["B2"]);
1038
- });
1039
-
1040
- it("compacts", () => {
1041
- const withEmpty = MultiColumn.from([["A1"], [], ["C1"], []]);
1042
- const compacted = withEmpty.compact();
1043
- expect(compacted.colLength).toBe(2);
1044
- expect(compacted.topLevelIds).toEqual([["A1"], ["C1"]]);
1045
- });
1046
-
1047
- it("handles errors", () => {
1048
- expect(() => multiColumn.findWithId("Z1")).toThrow(
1049
- "Cell Z1 not found in any column",
1050
- );
1051
- expect(multiColumn.colLength).toBeGreaterThan(2);
1052
- expect(() => multiColumn.delete("123" as CellColumnId)).toThrow(
1053
- /Column 123 not found/,
1054
- );
1055
- });
1056
-
1057
- it("checks if it's empty", () => {
1058
- expect(multiColumn.isEmpty()).toBe(false);
1059
- const emptyMultiColumn = MultiColumn.from([[]]);
1060
- expect(emptyMultiColumn.isEmpty()).toBe(true);
1061
- });
1062
-
1063
- it("gets columns", () => {
1064
- const columns = multiColumn.getColumns();
1065
- expect(columns.length).toBe(3);
1066
- expect(columns[0].topLevelIds).toEqual(["A1", "A2", "A3"]);
1067
- });
1068
-
1069
- it("gets index of a column", () => {
1070
- const column = multiColumn.atOrThrow(1);
1071
- const index = multiColumn.indexOf(column);
1072
- expect(index).toBe(1);
1073
- });
1074
-
1075
- it("handles at and atOrThrow", () => {
1076
- expect(multiColumn.at(1)?.topLevelIds).toEqual(["B1", "B2"]);
1077
- expect(multiColumn.atOrThrow(1).topLevelIds).toEqual(["B1", "B2"]);
1078
- expect(multiColumn.at(5)).toBeUndefined();
1079
- expect(() => multiColumn.atOrThrow(5)).toThrow("Column 5 not found");
1080
- });
1081
-
1082
- it("handles moving the last item in a column", () => {
1083
- const multiColumn = MultiColumn.from([["A1", "A2"], ["B1"]]);
1084
- const columnIds = multiColumn.getColumns().map((c) => c.id);
1085
- const moved = multiColumn.moveAcrossColumns(
1086
- columnIds[1],
1087
- "B1",
1088
- columnIds[0],
1089
- "A2",
1090
- );
1091
- expect(moved.topLevelIds).toEqual([["A1", "B1", "A2"], []]);
1092
- expect(moved.colLength).toBe(2);
1093
- });
1094
-
1095
- it("handles moving all items from a column", () => {
1096
- const multiColumn = MultiColumn.from([
1097
- ["A1", "A2"],
1098
- ["B1", "B2"],
1099
- ]);
1100
- const columnIds = multiColumn.getColumns().map((c) => c.id);
1101
- let moved = multiColumn.moveAcrossColumns(
1102
- columnIds[1],
1103
- "B1",
1104
- columnIds[0],
1105
- "A2",
1106
- );
1107
- moved = moved.moveAcrossColumns(columnIds[1], "B2", columnIds[0], "A2");
1108
- expect(moved.topLevelIds).toEqual([["A1", "B1", "B2", "A2"], []]);
1109
- expect(moved.colLength).toBe(2);
1110
- });
1111
-
1112
- it("handles inserting at out-of-bounds indices", () => {
1113
- const multiColumn = MultiColumn.from([["A1"], ["B1"]]);
1114
- const columnId = multiColumn.atOrThrow(1).id;
1115
- const inserted = multiColumn.insertId("C1", columnId, 10 as CellIndex);
1116
- expect(inserted.topLevelIds).toEqual([["A1"], ["B1", "C1"]]);
1117
- });
1118
-
1119
- it("handles collapsing and expanding in multi-column setup", () => {
1120
- let multiColumn = MultiColumn.from([
1121
- ["A1", "A2", "A3"],
1122
- ["B1", "B2"],
1123
- ]);
1124
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1125
- tree.collapse("A1", "A3"),
1126
- );
1127
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1", "B2"]]);
1128
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1129
- tree.expand("A1"),
1130
- );
1131
- expect(multiColumn.topLevelIds).toEqual([
1132
- ["A1", "A2", "A3"],
1133
- ["B1", "B2"],
1134
- ]);
1135
- });
1136
-
1137
- it("handles moving a column to its own position", () => {
1138
- const multiColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1139
- const moved = multiColumn.moveColumn(columnIds[1], columnIds[1]);
1140
- expect(moved.topLevelIds).toEqual([["A1"], ["B1"], ["C1"]]);
1141
- });
1142
-
1143
- it("handles compacting when all columns are empty", () => {
1144
- const multiColumn = MultiColumn.from([[], [], []]);
1145
- const compacted = multiColumn.compact();
1146
- expect(compacted.colLength).toBe(1);
1147
- expect(compacted.isEmpty()).toBe(true);
1148
- });
1149
-
1150
- it("handles transformById when id is not found", () => {
1151
- const multiColumn = MultiColumn.from([["A1"], ["B1"]]);
1152
- const transformed = multiColumn.transformWithCellId("C1", (tree) =>
1153
- tree.insertAtEnd("C2"),
1154
- );
1155
- expect(transformed).toBe(multiColumn);
1156
- });
1157
-
1158
- it("handles nested collapses and expands", () => {
1159
- let multiColumn = MultiColumn.from([["A1", "A2", "A3", "A4", "A5"]]);
1160
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1161
- tree.collapse("A2", "A4"),
1162
- );
1163
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1164
- tree.collapse("A1", undefined),
1165
- );
1166
- expect(multiColumn.topLevelIds).toEqual([["A1"]]);
1167
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1168
- tree.expand("A1"),
1169
- );
1170
- expect(multiColumn.topLevelIds).toEqual([["A1", "A2", "A5"]]);
1171
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1172
- tree.expand("A2"),
1173
- );
1174
- expect(multiColumn.topLevelIds).toEqual([["A1", "A2", "A3", "A4", "A5"]]);
1175
- });
1176
-
1177
- it("handles moving to a new column when it's the last item", () => {
1178
- let multiColumn = MultiColumn.from([["A1"], ["B1"]]);
1179
- multiColumn = multiColumn.moveToNewColumn("B1");
1180
- expect(multiColumn.colLength).toBe(3);
1181
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["B1"]]);
1182
- });
1183
-
1184
- it("handles deleting the last item in a column", () => {
1185
- let multiColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1186
- multiColumn = multiColumn.deleteById("B1");
1187
- expect(multiColumn.colLength).toBe(3);
1188
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["C1"]]);
1189
- });
1190
-
1191
- it("handles multiple operations in sequence", () => {
1192
- let multiColumn = MultiColumn.from([
1193
- ["A1", "A2"],
1194
- ["B1", "B2"],
1195
- ["C1", "C2"],
1196
- ]);
1197
- const columnIds = multiColumn.getColumns().map((c) => c.id);
1198
- multiColumn = multiColumn.moveAcrossColumns(
1199
- columnIds[0],
1200
- "A2",
1201
- columnIds[1],
1202
- "B1",
1203
- );
1204
- expect(multiColumn.topLevelIds).toEqual([
1205
- ["A1"],
1206
- ["A2", "B1", "B2"],
1207
- ["C1", "C2"],
1208
- ]);
1209
- multiColumn = multiColumn.insertId("D1", columnIds[2], 1 as CellIndex);
1210
- expect(multiColumn.topLevelIds).toEqual([
1211
- ["A1"],
1212
- ["A2", "B1", "B2"],
1213
- ["C1", "D1", "C2"],
1214
- ]);
1215
- multiColumn = multiColumn.deleteById("B1");
1216
- expect(multiColumn.topLevelIds).toEqual([
1217
- ["A1"],
1218
- ["A2", "B2"],
1219
- ["C1", "D1", "C2"],
1220
- ]);
1221
- multiColumn = multiColumn.moveColumn(columnIds[0], columnIds[2]);
1222
- expect(multiColumn.topLevelIds).toEqual([
1223
- ["A2", "B2"],
1224
- ["C1", "D1", "C2"],
1225
- ["A1"],
1226
- ]);
1227
- multiColumn = multiColumn.compact();
1228
- expect(multiColumn.topLevelIds).toEqual([
1229
- ["A2", "B2"],
1230
- ["C1", "D1", "C2"],
1231
- ["A1"],
1232
- ]);
1233
- });
1234
-
1235
- it("handles setup cell existence", () => {
1236
- const multiColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1237
- const columnIds = multiColumn.getColumnIds();
1238
- expect(multiColumn.setupCellExists()).toBe(false);
1239
- const multiColumn2 = multiColumn.insertId(SETUP_CELL_ID, columnIds[0], 0);
1240
- expect(multiColumn2.setupCellExists()).toBe(true);
1241
- const multiColumn3 = multiColumn2.deleteById(SETUP_CELL_ID);
1242
- expect(multiColumn3.setupCellExists()).toBe(false);
1243
- });
1244
-
1245
- it("handles get method", () => {
1246
- const column = multiColumn.get(columnIds[1]);
1247
- expect(column?.topLevelIds).toEqual(["B1", "B2"]);
1248
- });
1249
-
1250
- it("handles get method with non-existent column id", () => {
1251
- const column = multiColumn.get("non-existent-id" as CellColumnId);
1252
- expect(column).toBeUndefined();
1253
- });
1254
-
1255
- it("handles indexOfOrThrow method", () => {
1256
- expect(multiColumn.indexOfOrThrow(columnIds[1])).toBe(1);
1257
- });
1258
-
1259
- it("handles indexOfOrThrow method with non-existent column id", () => {
1260
- expect(() =>
1261
- multiColumn.indexOfOrThrow("non-existent-id" as CellColumnId),
1262
- ).toThrow("Column non-existent-id not found. Possible values: ");
1263
- });
1264
-
1265
- it("handles transform method", () => {
1266
- const transformed = multiColumn.transform(columnIds[1], (tree) =>
1267
- tree.moveToFront("B2"),
1268
- );
1269
- expect(transformed.topLevelIds[1]).toEqual(["B2", "B1"]);
1270
- });
1271
-
1272
- it("handles transform method with non-existent column id", () => {
1273
- const transformed = multiColumn.transform(
1274
- "non-existent-id" as CellColumnId,
1275
- (tree) => tree.moveToFront("B2"),
1276
- );
1277
- const columns1 = multiColumn.getColumns();
1278
- const columns2 = transformed.getColumns();
1279
- const allEqual = columns1.every((c, i) => c.equals(columns2[i]));
1280
- expect(allEqual).toBe(true);
1281
- expect(transformed).toBe(multiColumn);
1282
- });
1283
-
1284
- it("handles moving across columns with undefined toId", () => {
1285
- const moved = multiColumn.moveAcrossColumns(
1286
- columnIds[0],
1287
- "A1",
1288
- columnIds[1],
1289
- undefined,
1290
- );
1291
- expect(moved.topLevelIds).toEqual([
1292
- ["A2", "A3"],
1293
- ["A1", "B1", "B2"],
1294
- ["C1", "C2", "C3", "C4"],
1295
- ]);
1296
- });
1297
-
1298
- it("handles moving across columns to an empty column", () => {
1299
- const emptyColumnMultiColumn = multiColumn
1300
- .deleteById("B1")
1301
- .deleteById("B2");
1302
- const moved = emptyColumnMultiColumn.moveAcrossColumns(
1303
- columnIds[0],
1304
- "A1",
1305
- columnIds[1],
1306
- undefined,
1307
- );
1308
- expect(moved.topLevelIds).toEqual([
1309
- ["A2", "A3"],
1310
- ["A1"],
1311
- ["C1", "C2", "C3", "C4"],
1312
- ]);
1313
- });
1314
-
1315
- it("handles inserting a breakpoint at the start of a column", () => {
1316
- const withBreakpoint = multiColumn.insertBreakpoint("A1");
1317
- expect(withBreakpoint.colLength).toBe(4);
1318
- expect(withBreakpoint.topLevelIds).toEqual([
1319
- [],
1320
- ["A1", "A2", "A3"],
1321
- ["B1", "B2"],
1322
- ["C1", "C2", "C3", "C4"],
1323
- ]);
1324
- });
1325
-
1326
- it("handles inserting a breakpoint at the end of a column", () => {
1327
- const withBreakpoint = multiColumn.insertBreakpoint("A3");
1328
- expect(withBreakpoint.colLength).toBe(4);
1329
- expect(withBreakpoint.topLevelIds).toEqual([
1330
- ["A1", "A2"],
1331
- ["A3"],
1332
- ["B1", "B2"],
1333
- ["C1", "C2", "C3", "C4"],
1334
- ]);
1335
- });
1336
-
1337
- it("handles inserting a breakpoint in a single-item column", () => {
1338
- const singleItemColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1339
- const withBreakpoint = singleItemColumn.insertBreakpoint("B1");
1340
- expect(withBreakpoint.colLength).toBe(4);
1341
- expect(withBreakpoint.topLevelIds).toEqual([["A1"], [], ["B1"], ["C1"]]);
1342
- });
1343
- });
1344
-
1345
- describe("MultiColumn.fromWithPreviousShape", () => {
1346
- it("creates a new MultiColumn with a single column when no previous shape is provided", () => {
1347
- const ids = ["A1", "A2", "A3"];
1348
- const multiColumn = MultiColumn.fromWithPreviousShape(
1349
- ids,
1350
- MultiColumn.from([]),
1351
- );
1352
-
1353
- expect(multiColumn.colLength).toBe(1);
1354
- expect(multiColumn.topLevelIds).toEqual([ids]);
1355
- expect(multiColumn.inOrderIds).toEqual(ids);
1356
- });
1357
-
1358
- it("preserves column structure when ids exist in previous columns", () => {
1359
- const previousMultiColumn = MultiColumn.from([
1360
- ["A1", "A2"],
1361
- ["B1", "B2"],
1362
- ["C1", "C2"],
1363
- ]);
1364
-
1365
- // Subset of ids from different columns
1366
- const ids = ["A1", "B2", "C1"];
1367
- const multiColumn = MultiColumn.fromWithPreviousShape(
1368
- ids,
1369
- previousMultiColumn,
1370
- );
1371
-
1372
- expect(multiColumn.colLength).toBe(3);
1373
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B2"], ["C1"]]);
1374
- });
1375
-
1376
- it("preserves column structure with different ordering", () => {
1377
- const previousMultiColumn = MultiColumn.from([
1378
- ["A1", "A2", "A3"],
1379
- ["B1", "B2", "B3"],
1380
- ]);
1381
-
1382
- // Same ids but different order
1383
- const ids = ["A3", "B1", "A1", "B3"];
1384
- const multiColumn = MultiColumn.fromWithPreviousShape(
1385
- ids,
1386
- previousMultiColumn,
1387
- );
1388
-
1389
- expect(multiColumn.colLength).toBe(2);
1390
- expect(multiColumn.topLevelIds).toEqual([
1391
- ["A3", "A1"],
1392
- ["B1", "B3"],
1393
- ]);
1394
- });
1395
-
1396
- it("handles ids not present in previous shape", () => {
1397
- const previousMultiColumn = MultiColumn.from([
1398
- ["A1", "A2"],
1399
- ["B1", "B2"],
1400
- ]);
1401
-
1402
- // Include new ids not in previous shape
1403
- const ids = ["A1", "B1", "C1", "D1"];
1404
- const multiColumn = MultiColumn.fromWithPreviousShape(
1405
- ids,
1406
- previousMultiColumn,
1407
- );
1408
-
1409
- // New ids should be added to the last column
1410
- expect(multiColumn.colLength).toBe(2);
1411
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1", "C1", "D1"]]);
1412
- expect(multiColumn.inOrderIds).toEqual(["A1", "B1", "C1", "D1"]);
1413
- });
1414
-
1415
- it("preserves collapsed state from previous shape", () => {
1416
- // Create a previous shape with collapsed nodes
1417
- let previousMultiColumn = MultiColumn.from([
1418
- ["A1", "A2", "A3"],
1419
- ["B1", "B2", "B3"],
1420
- ]);
1421
-
1422
- // Collapse A1 to include A2
1423
- const columnIds = previousMultiColumn.getColumnIds();
1424
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1425
- tree.collapse("A1", "A2"),
1426
- );
1427
-
1428
- // Create new shape with same ids
1429
- const ids = ["A1", "A2", "A3", "B1", "B2", "B3"];
1430
- const multiColumn = MultiColumn.fromWithPreviousShape(
1431
- ids,
1432
- previousMultiColumn,
1433
- );
1434
-
1435
- // Check that A1 is still collapsed
1436
- const column = multiColumn.atOrThrow(0);
1437
- expect(column.isCollapsed("A1")).toBe(true);
1438
-
1439
- // Check that the structure is preserved
1440
- expect(multiColumn.colLength).toBe(2);
1441
- expect(multiColumn.topLevelIds).toEqual([
1442
- ["A1", "A3"],
1443
- ["B1", "B2", "B3"],
1444
- ]);
1445
- });
1446
-
1447
- it("handles empty columns in previous shape", () => {
1448
- // Create a previous shape with an empty column
1449
- const previousMultiColumn = MultiColumn.from([
1450
- ["A1", "A2"],
1451
- [],
1452
- ["C1", "C2"],
1453
- ]);
1454
-
1455
- const ids = ["A1", "C2"];
1456
- const multiColumn = MultiColumn.fromWithPreviousShape(
1457
- ids,
1458
- previousMultiColumn,
1459
- );
1460
-
1461
- // Should preserve the column structure
1462
- expect(multiColumn.colLength).toBe(3);
1463
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["C2"]]);
1464
- });
1465
-
1466
- it("handles subset of ids with nested collapsed structure", () => {
1467
- // Create a previous shape with nested collapsed structure
1468
- let previousMultiColumn = MultiColumn.from([
1469
- ["A1", "A2", "A3", "A4", "A5"],
1470
- ["B1", "B2", "B3"],
1471
- ]);
1472
-
1473
- // Collapse A1 to include A2, A3
1474
- const columnIds = previousMultiColumn.getColumnIds();
1475
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1476
- tree.collapse("A1", "A3"),
1477
- );
1478
-
1479
- // Create new shape with subset of ids
1480
- const ids = ["A1", "A2", "A3", "A4", "B1"];
1481
- const multiColumn = MultiColumn.fromWithPreviousShape(
1482
- ids,
1483
- previousMultiColumn,
1484
- );
1485
-
1486
- // Check that collapsed state is preserved
1487
- const column = multiColumn.atOrThrow(0);
1488
- expect(column.isCollapsed("A1")).toBe(true);
1489
-
1490
- // Check that the structure is preserved
1491
- expect(multiColumn.colLength).toBe(2);
1492
- expect(multiColumn.topLevelIds).toEqual([["A1", "A4"], ["B1"]]);
1493
- expect(multiColumn.inOrderIds).toEqual(["A1", "A2", "A3", "A4", "B1"]);
1494
- });
1495
-
1496
- it("handles reordering of columns", () => {
1497
- // Create a previous shape with specific column order
1498
- const previousMultiColumn = MultiColumn.from([
1499
- ["A1", "A2"],
1500
- ["B1", "B2"],
1501
- ["C1", "C2"],
1502
- ]);
1503
-
1504
- // Create new shape with ids in different order
1505
- const ids = ["C1", "A1", "B1"];
1506
- const multiColumn = MultiColumn.fromWithPreviousShape(
1507
- ids,
1508
- previousMultiColumn,
1509
- );
1510
-
1511
- // Column order should be preserved
1512
- expect(multiColumn.colLength).toBe(3);
1513
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1"], ["C1"]]);
1514
- });
1515
-
1516
- it("handles completely different set of ids", () => {
1517
- const previousMultiColumn = MultiColumn.from([
1518
- ["A1", "A2"],
1519
- ["B1", "B2"],
1520
- ]);
1521
-
1522
- // Completely different set of ids
1523
- const ids = ["X1", "X2", "X3"];
1524
- const multiColumn = MultiColumn.fromWithPreviousShape(
1525
- ids,
1526
- previousMultiColumn,
1527
- );
1528
-
1529
- // New ids should be added to the last column
1530
- expect(multiColumn.colLength).toBe(2);
1531
- expect(multiColumn.topLevelIds).toEqual([["X1", "X2", "X3"], []]);
1532
- expect(multiColumn.inOrderIds).toEqual(["X1", "X2", "X3"]);
1533
- });
1534
-
1535
- it("preserves tree structure within columns", () => {
1536
- // Create a previous shape with complex tree structure
1537
- let previousMultiColumn = MultiColumn.from([
1538
- ["A1", "A2", "A3", "A4"],
1539
- ["B1", "B2", "B3"],
1540
- ]);
1541
-
1542
- // Create complex tree structure by collapsing nodes
1543
- const columnIds = previousMultiColumn.getColumnIds();
1544
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1545
- tree.collapse("A1", "A2"),
1546
- );
1547
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1548
- tree.collapse("B1", "B2"),
1549
- );
1550
-
1551
- // Create new shape with same ids
1552
- const ids = ["A1", "A2", "A3", "A4", "B1", "B2", "B3"];
1553
- const multiColumn = MultiColumn.fromWithPreviousShape(
1554
- ids,
1555
- previousMultiColumn,
1556
- );
1557
-
1558
- // Check that collapsed state is preserved in both columns
1559
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1560
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(true);
1561
-
1562
- // Check that the structure is preserved
1563
- expect(multiColumn.colLength).toBe(2);
1564
- expect(multiColumn.topLevelIds).toEqual([
1565
- ["A1", "A3", "A4"],
1566
- ["B1", "B3"],
1567
- ]);
1568
- });
1569
-
1570
- it("handles empty array of ids", () => {
1571
- const previousMultiColumn = MultiColumn.from([
1572
- ["A1", "A2"],
1573
- ["B1", "B2"],
1574
- ]);
1575
-
1576
- const multiColumn = MultiColumn.fromWithPreviousShape(
1577
- [],
1578
- previousMultiColumn,
1579
- );
1580
-
1581
- expect(multiColumn.colLength).toBe(2);
1582
- expect(multiColumn.topLevelIds).toEqual([[], []]);
1583
- expect(multiColumn.inOrderIds).toEqual([]);
1584
- });
1585
-
1586
- it("handles single id", () => {
1587
- const previousMultiColumn = MultiColumn.from([
1588
- ["A1", "A2"],
1589
- ["B1", "B2"],
1590
- ]);
1591
-
1592
- const multiColumn = MultiColumn.fromWithPreviousShape(
1593
- ["A1"],
1594
- previousMultiColumn,
1595
- );
1596
-
1597
- expect(multiColumn.colLength).toBe(2);
1598
- expect(multiColumn.topLevelIds).toEqual([["A1"], []]);
1599
- expect(multiColumn.inOrderIds).toEqual(["A1"]);
1600
- });
1601
-
1602
- it("preserves complex nested structure across multiple columns", () => {
1603
- // Create a complex structure with multiple columns and nested collapsed nodes
1604
- let previousMultiColumn = MultiColumn.from([
1605
- ["A1", "A2", "A3", "A4", "A5"],
1606
- ["B1", "B2", "B3", "B4"],
1607
- ["C1", "C2", "C3"],
1608
- ]);
1609
-
1610
- // Create complex nested structure
1611
- const columnIds = previousMultiColumn.getColumnIds();
1612
-
1613
- // Collapse in first column
1614
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1615
- tree.collapse("A1", "A2"),
1616
- );
1617
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1618
- tree.collapse("A3", "A4"),
1619
- );
1620
-
1621
- // Collapse in second column
1622
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1623
- tree.collapse("B1", "B2"),
1624
- );
1625
-
1626
- // Collapse in third column
1627
- previousMultiColumn = previousMultiColumn.transform(columnIds[2], (tree) =>
1628
- tree.collapse("C1", "C2"),
1629
- );
1630
-
1631
- // Create new shape with all ids
1632
- const ids = [
1633
- "A1",
1634
- "A2",
1635
- "A3",
1636
- "A4",
1637
- "A5",
1638
- "B1",
1639
- "B2",
1640
- "B3",
1641
- "B4",
1642
- "C1",
1643
- "C2",
1644
- "C3",
1645
- ];
1646
-
1647
- const multiColumn = MultiColumn.fromWithPreviousShape(
1648
- ids,
1649
- previousMultiColumn,
1650
- );
1651
-
1652
- // Check that all collapsed states are preserved
1653
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1654
- expect(multiColumn.atOrThrow(0).isCollapsed("A3")).toBe(true);
1655
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(true);
1656
- expect(multiColumn.atOrThrow(2).isCollapsed("C1")).toBe(true);
1657
-
1658
- // Check that the structure is preserved
1659
- expect(multiColumn.colLength).toBe(3);
1660
- expect(multiColumn.topLevelIds).toEqual([
1661
- ["A1", "A3", "A5"],
1662
- ["B1", "B3", "B4"],
1663
- ["C1", "C3"],
1664
- ]);
1665
- });
1666
-
1667
- it("handles case where ids are distributed differently than in previous shape", () => {
1668
- const previousMultiColumn = MultiColumn.from([
1669
- ["A1", "A2"],
1670
- ["B1", "B2"],
1671
- ["C1", "C2"],
1672
- ]);
1673
-
1674
- // Create new shape with ids from different columns grouped together
1675
- const ids = ["A1", "B1", "C1", "A2", "B2", "C2"];
1676
- const multiColumn = MultiColumn.fromWithPreviousShape(
1677
- ids,
1678
- previousMultiColumn,
1679
- );
1680
-
1681
- // Should preserve the original column structure
1682
- expect(multiColumn.colLength).toBe(3);
1683
- expect(multiColumn.topLevelIds).toEqual([
1684
- ["A1", "A2"],
1685
- ["B1", "B2"],
1686
- ["C1", "C2"],
1687
- ]);
1688
- });
1689
-
1690
- it("handles case where previous shape has collapsed nodes but new ids don't include children", () => {
1691
- // Create a previous shape with collapsed nodes
1692
- let previousMultiColumn = MultiColumn.from([
1693
- ["A1", "A2", "A3"],
1694
- ["B1", "B2", "B3"],
1695
- ]);
1696
-
1697
- // Collapse nodes in both columns
1698
- const columnIds = previousMultiColumn.getColumnIds();
1699
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1700
- tree.collapse("A1", "A2"),
1701
- );
1702
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1703
- tree.collapse("B1", "B2"),
1704
- );
1705
-
1706
- // Create new shape without the collapsed children
1707
- const ids = ["A1", "A3", "B1", "B3"];
1708
- const multiColumn = MultiColumn.fromWithPreviousShape(
1709
- ids,
1710
- previousMultiColumn,
1711
- );
1712
-
1713
- // Nodes should not be collapsed since children are missing
1714
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(false);
1715
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(false);
1716
-
1717
- // Check that the structure is preserved
1718
- expect(multiColumn.colLength).toBe(2);
1719
- expect(multiColumn.topLevelIds).toEqual([
1720
- ["A1", "A3"],
1721
- ["B1", "B3"],
1722
- ]);
1723
- });
1724
-
1725
- it("handles deeply nested collapsed structure", () => {
1726
- // Create a tree with deeply nested collapsed structure
1727
- let previousTree = CollapsibleTree.from([
1728
- "one",
1729
- "two",
1730
- "three",
1731
- "four",
1732
- "five",
1733
- "six",
1734
- "seven",
1735
- "eight",
1736
- ]);
1737
-
1738
- // Create nested collapses
1739
- previousTree = previousTree.collapse("one", "two");
1740
-
1741
- // Get the collapsed node and collapse its children
1742
- const oneNode = previousTree._nodeMap.get("one");
1743
- expect(oneNode).toBeDefined();
1744
-
1745
- // Create a new tree with the same structure
1746
- const tree = CollapsibleTree.fromWithPreviousShape(
1747
- ["one", "two", "three", "four", "five", "six", "seven", "eight"],
1748
- previousTree,
1749
- );
1750
-
1751
- // Check that collapsed state is preserved
1752
- expect(tree.isCollapsed("one")).toBe(true);
1753
- expect(tree.topLevelIds).toEqual([
1754
- "one",
1755
- "three",
1756
- "four",
1757
- "five",
1758
- "six",
1759
- "seven",
1760
- "eight",
1761
- ]);
1762
- });
1763
-
1764
- it("handles case where collapsed node is at the end", () => {
1765
- // Create a tree with collapsed node at the end
1766
- let previousTree = CollapsibleTree.from([
1767
- "one",
1768
- "two",
1769
- "three",
1770
- "four",
1771
- "five",
1772
- ]);
1773
- previousTree = previousTree.collapse("four", "five");
1774
-
1775
- // Create new tree with same ids
1776
- const tree = CollapsibleTree.fromWithPreviousShape(
1777
- ["one", "two", "three", "four", "five"],
1778
- previousTree,
1779
- );
1780
-
1781
- // Check that collapsed state is preserved
1782
- expect(tree.isCollapsed("four")).toBe(true);
1783
- expect(tree.topLevelIds).toEqual(["one", "two", "three", "four"]);
1784
- });
1785
-
1786
- it("handles case with multiple collapsed nodes in the same column", () => {
1787
- // Create a previous shape with multiple collapsed nodes in the same column
1788
- let previousMultiColumn = MultiColumn.from([
1789
- ["A1", "A2", "A3", "A4", "A5", "A6"],
1790
- ["B1", "B2"],
1791
- ]);
1792
-
1793
- // Collapse multiple nodes in the first column
1794
- const columnIds = previousMultiColumn.getColumnIds();
1795
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1796
- tree.collapse("A1", "A2"),
1797
- );
1798
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1799
- tree.collapse("A3", "A4"),
1800
- );
1801
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1802
- tree.collapse("A5", "A6"),
1803
- );
1804
-
1805
- // Create new shape with same ids
1806
- const ids = ["A1", "A2", "A3", "A4", "A5", "A6", "B1", "B2"];
1807
- const multiColumn = MultiColumn.fromWithPreviousShape(
1808
- ids,
1809
- previousMultiColumn,
1810
- );
1811
-
1812
- // Check that all collapsed states are preserved
1813
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1814
- expect(multiColumn.atOrThrow(0).isCollapsed("A3")).toBe(true);
1815
- expect(multiColumn.atOrThrow(0).isCollapsed("A5")).toBe(true);
1816
-
1817
- // Check that the structure is preserved
1818
- expect(multiColumn.colLength).toBe(2);
1819
- expect(multiColumn.topLevelIds).toEqual([
1820
- ["A1", "A3", "A5"],
1821
- ["B1", "B2"],
1822
- ]);
1823
- });
1824
-
1825
- it("handles case where all columns are empty in previous shape", () => {
1826
- // Create a previous shape with all empty columns
1827
- const previousMultiColumn = MultiColumn.from([[], [], []]);
1828
-
1829
- // Create new shape with new ids
1830
- const ids = ["A1", "B1", "C1"];
1831
- const multiColumn = MultiColumn.fromWithPreviousShape(
1832
- ids,
1833
- previousMultiColumn,
1834
- );
1835
-
1836
- // New ids should be added to the last column
1837
- expect(multiColumn.colLength).toBe(3);
1838
- expect(multiColumn.topLevelIds).toEqual([["A1", "B1", "C1"], [], []]);
1839
- expect(multiColumn.inOrderIds).toEqual(["A1", "B1", "C1"]);
1840
- });
1841
-
1842
- it("handles case with very large number of ids", () => {
1843
- // Create a previous shape with a reasonable number of ids
1844
- const previousIds1 = Array.from({ length: 50 }, (_, i) => `A${i}`);
1845
- const previousIds2 = Array.from({ length: 50 }, (_, i) => `B${i}`);
1846
- const previousMultiColumn = MultiColumn.from([previousIds1, previousIds2]);
1847
-
1848
- // Create a large set of ids
1849
- const ids = [
1850
- ...Array.from({ length: 50 }, (_, i) => `A${i}`),
1851
- ...Array.from({ length: 50 }, (_, i) => `B${i}`),
1852
- ...Array.from({ length: 50 }, (_, i) => `C${i}`),
1853
- ];
1854
-
1855
- const multiColumn = MultiColumn.fromWithPreviousShape(
1856
- ids,
1857
- previousMultiColumn,
1858
- );
1859
-
1860
- // Should preserve the column structure for existing ids and add new ids to last column
1861
- expect(multiColumn.colLength).toBe(2);
1862
- expect(multiColumn.topLevelIds[0].length).toBe(50);
1863
- expect(multiColumn.topLevelIds[1].length).toBe(100); // 50 B ids + 50 C ids
1864
- expect(multiColumn.inOrderIds.length).toBe(150); // All ids should be included
1865
- });
1866
- });
1867
-
1868
- describe("CollapsibleTree.fromWithPreviousShape", () => {
1869
- it("creates a new tree when no previous tree is provided", () => {
1870
- const ids = ["one", "two", "three"];
1871
- const tree = CollapsibleTree.fromWithPreviousShape(ids);
1872
-
1873
- expect(tree.topLevelIds).toEqual(ids);
1874
- expect(tree.inOrderIds).toEqual(ids);
1875
- expect(tree.nodes.length).toBe(3);
1876
- expect(tree.nodes.every((node) => !node.isCollapsed)).toBe(true);
1877
- });
1878
-
1879
- it("preserves the tree ID when previous tree is provided", () => {
1880
- const previousTree = CollapsibleTree.from(["one", "two", "three"]);
1881
- const tree = CollapsibleTree.fromWithPreviousShape(
1882
- ["one", "two"],
1883
- previousTree,
1884
- );
1885
-
1886
- expect(tree.id).toBe(previousTree.id);
1887
- });
1888
-
1889
- it("preserves collapsed state from previous tree", () => {
1890
- // Create a tree with collapsed nodes
1891
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
1892
- previousTree = previousTree.collapse("one", "two");
1893
-
1894
- // Create new tree with same ids
1895
- const tree = CollapsibleTree.fromWithPreviousShape(
1896
- ["one", "two", "three", "four"],
1897
- previousTree,
1898
- );
1899
-
1900
- // Check that one is still collapsed
1901
- expect(tree.isCollapsed("one")).toBe(true);
1902
- expect(tree.topLevelIds).toEqual(["one", "three", "four"]);
1903
- });
1904
-
1905
- it("handles subset of ids from previous tree", () => {
1906
- // Create a tree with collapsed nodes
1907
- let previousTree = CollapsibleTree.from([
1908
- "one",
1909
- "two",
1910
- "three",
1911
- "four",
1912
- "five",
1913
- ]);
1914
- previousTree = previousTree.collapse("one", "two");
1915
- previousTree = previousTree.collapse("three", "four");
1916
-
1917
- // Create new tree with subset of ids
1918
- const tree = CollapsibleTree.fromWithPreviousShape(
1919
- ["one", "two", "three"],
1920
- previousTree,
1921
- );
1922
-
1923
- // Check that collapsed state is preserved for existing nodes
1924
- expect(tree.isCollapsed("one")).toBe(true);
1925
- expect(tree.topLevelIds).toEqual(["one", "three"]);
1926
- });
1927
-
1928
- it("handles different ordering of ids", () => {
1929
- // Create a tree with collapsed nodes
1930
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
1931
- previousTree = previousTree.collapse("one", "two");
1932
-
1933
- // Create new tree with different order
1934
- const tree = CollapsibleTree.fromWithPreviousShape(
1935
- ["three", "one", "two", "four"],
1936
- previousTree,
1937
- );
1938
-
1939
- // Check that collapsed state is preserved
1940
- expect(tree.isCollapsed("one")).toBe(true);
1941
- expect(tree.topLevelIds).toEqual(["three", "one", "four"]);
1942
- });
1943
-
1944
- it("handles new ids not in previous tree", () => {
1945
- // Create a tree with collapsed nodes
1946
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
1947
- previousTree = previousTree.collapse("one", "two");
1948
-
1949
- // Create new tree with new ids
1950
- const tree = CollapsibleTree.fromWithPreviousShape(
1951
- ["one", "two", "three", "four", "five"],
1952
- previousTree,
1953
- );
1954
-
1955
- // Check that collapsed state is preserved for existing nodes
1956
- expect(tree.isCollapsed("one")).toBe(true);
1957
- expect(tree.topLevelIds).toEqual(["one", "three", "four", "five"]);
1958
- });
1959
-
1960
- it("handles completely different set of ids", () => {
1961
- // Create a tree with collapsed nodes
1962
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
1963
- previousTree = previousTree.collapse("one", "two");
1964
-
1965
- // Create new tree with completely different ids
1966
- const tree = CollapsibleTree.fromWithPreviousShape(
1967
- ["four", "five", "six"],
1968
- previousTree,
1969
- );
1970
-
1971
- // Should have no collapsed nodes
1972
- expect(tree.nodes.every((node) => !node.isCollapsed)).toBe(true);
1973
- expect(tree.topLevelIds).toEqual(["four", "five", "six"]);
1974
- });
1975
-
1976
- it("preserves nested collapsed structure", () => {
1977
- // Create a tree with nested collapsed structure
1978
- let previousTree = CollapsibleTree.from([
1979
- "one",
1980
- "two",
1981
- "three",
1982
- "four",
1983
- "five",
1984
- "six",
1985
- ]);
1986
- previousTree = previousTree.collapse("one", "two");
1987
-
1988
- // Collapse the node that contains the collapsed children
1989
- const oneNode = previousTree.nodes.find((n) => n.value === "one");
1990
- expect(oneNode).toBeDefined();
1991
- expect(oneNode?.children.length).toBeGreaterThan(0);
1992
-
1993
- // Create a new tree with the same structure
1994
- const tree = CollapsibleTree.fromWithPreviousShape(
1995
- ["one", "two", "three", "four", "five", "six"],
1996
- previousTree,
1997
- );
1998
-
1999
- // Check that collapsed state is preserved
2000
- expect(tree.isCollapsed("one")).toBe(true);
2001
- expect(tree.topLevelIds).toEqual(["one", "three", "four", "five", "six"]);
2002
- });
2003
-
2004
- it("collapses using the last available child when some children are missing", () => {
2005
- // Create a tree with collapsed nodes
2006
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2007
- previousTree = previousTree.collapse("one", "three");
2008
-
2009
- // Create new tree without "two" but with "three"
2010
- const tree = CollapsibleTree.fromWithPreviousShape(
2011
- ["one", "three", "four"],
2012
- previousTree,
2013
- );
2014
-
2015
- // Should still collapse "one" with "three" as the child
2016
- expect(tree.isCollapsed("one")).toBe(true);
2017
- expect(tree.topLevelIds).toEqual(["one", "four"]);
2018
- });
2019
-
2020
- it("preserves collapse state when children order changes", () => {
2021
- // Create a tree with collapsed nodes
2022
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2023
- previousTree = previousTree.collapse("one", "two");
2024
-
2025
- // Create new tree with children in different order
2026
- const tree = CollapsibleTree.fromWithPreviousShape(
2027
- ["one", "three", "two", "four"],
2028
- previousTree,
2029
- );
2030
-
2031
- // Should still be collapsed
2032
- expect(tree.isCollapsed("one")).toBe(true);
2033
- // But the topLevelIds will be different because the children order changed
2034
- expect(tree.topLevelIds).toEqual(["one", "four"]);
2035
- });
2036
-
2037
- it("handles empty arrays correctly", () => {
2038
- const previousTree = CollapsibleTree.from(["one", "two", "three"]);
2039
- const tree = CollapsibleTree.fromWithPreviousShape([], previousTree);
2040
-
2041
- expect(tree.nodes.length).toBe(0);
2042
- expect(tree.topLevelIds).toEqual([]);
2043
- expect(tree.inOrderIds).toEqual([]);
2044
- });
2045
-
2046
- it("handles single item arrays correctly", () => {
2047
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
2048
- previousTree = previousTree.collapse("one", "two");
2049
-
2050
- const tree = CollapsibleTree.fromWithPreviousShape(["one"], previousTree);
2051
-
2052
- expect(tree.nodes.length).toBe(1);
2053
- expect(tree.topLevelIds).toEqual(["one"]);
2054
- expect(tree.inOrderIds).toEqual(["one"]);
2055
- // Should not be collapsed since there are no children
2056
- expect(tree.isCollapsed("one")).toBe(false);
2057
- });
2058
-
2059
- it("handles multiple collapsed nodes in sequence", () => {
2060
- let previousTree = CollapsibleTree.from([
2061
- "one",
2062
- "two",
2063
- "three",
2064
- "four",
2065
- "five",
2066
- "six",
2067
- ]);
2068
- previousTree = previousTree.collapse("one", "two");
2069
- previousTree = previousTree.collapse("three", "four");
2070
- previousTree = previousTree.collapse("five", "six");
2071
-
2072
- const tree = CollapsibleTree.fromWithPreviousShape(
2073
- ["one", "two", "three", "four", "five", "six"],
2074
- previousTree,
2075
- );
2076
-
2077
- expect(tree.isCollapsed("one")).toBe(true);
2078
- expect(tree.isCollapsed("three")).toBe(true);
2079
- expect(tree.isCollapsed("five")).toBe(true);
2080
- expect(tree.topLevelIds).toEqual(["one", "three", "five"]);
2081
- });
2082
-
2083
- it("handles case where collapsed node's children are not in new ids", () => {
2084
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2085
- previousTree = previousTree.collapse("one", "two");
2086
-
2087
- // Create new tree without the collapsed child
2088
- const tree = CollapsibleTree.fromWithPreviousShape(
2089
- ["one", "three", "four"],
2090
- previousTree,
2091
- );
2092
-
2093
- // Should not be collapsed since the child is missing
2094
- expect(tree.isCollapsed("one")).toBe(false);
2095
- expect(tree.topLevelIds).toEqual(["one", "three", "four"]);
2096
- });
2097
-
2098
- it("reproduces GitHub issue #6188: inOrderIds with nested collapses", () => {
2099
- // Simplified reproduction case:
2100
- // Cell A (markdown): # Section 1 Header
2101
- // Cell B (markdown): ## Subsection 1.1 Header
2102
- // Cell C (python): print("This cell should be in Subsection 1.1")
2103
- // Cell D (python): print("This cell should also be in Subsection 1.1")
2104
- // Cell E (markdown): ## Subsection 1.2 Header
2105
- // Cell F (python): print("This cell should be in Subsection 1.2")
2106
-
2107
- const originalOrder = [
2108
- "CellA", // # Section 1 Header
2109
- "CellB", // ## Subsection 1.1 Header
2110
- "CellC", // print("This cell should be in Subsection 1.1")
2111
- "CellD", // print("This cell should also be in Subsection 1.1")
2112
- "CellE", // ## Subsection 1.2 Header
2113
- "CellF", // print("This cell should be in Subsection 1.2")
2114
- ];
2115
-
2116
- // Create notebook with one column
2117
- let notebook = MultiColumn.from([originalOrder]);
2118
-
2119
- // Before collapsing, order should be correct
2120
- expect(notebook.inOrderIds).toEqual(originalOrder);
2121
-
2122
- const columnId = notebook.getColumnIds()[0];
2123
-
2124
- // Follow the exact reproduction steps from the GitHub issue:
2125
- // 1. Collapse B (subsection 1.1) - includes C, D (stops before E)
2126
- notebook = notebook.transform(columnId, (tree) =>
2127
- tree.collapse("CellB", "CellD"),
2128
- );
2129
-
2130
- // 2. Collapse E (subsection 1.2) - includes F (to end)
2131
- notebook = notebook.transform(columnId, (tree) =>
2132
- tree.collapse("CellE", undefined),
2133
- );
2134
-
2135
- // 3. Collapse A (section 1) - includes all the rest (B, E and their children)
2136
- notebook = notebook.transform(columnId, (tree) =>
2137
- tree.collapse("CellA", undefined),
2138
- );
2139
-
2140
- const finalOrder = notebook.inOrderIds;
2141
-
2142
- // inOrderIds now correctly preserves logical order even with nested collapses
2143
- expect(finalOrder).toEqual(originalOrder);
2144
- });
2145
- });