@marimo-team/islands 0.25.3-dev6 → 0.25.3-dev61

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 (236) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-Da5OoEUL.js} +14 -14
  2. package/dist/{ErrorBoundary-Ca-Sa492.js → ErrorBoundary-BEfUX0zz.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
  4. package/dist/any-language-editor-k2B8YI3Y.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
  8. package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
  9. package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-C0_lCzql.js} +1139 -1140
  10. package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
  11. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
  12. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
  13. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
  14. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
  15. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  16. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
  17. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
  18. package/dist/{code-visibility-NGRjhbtA.js → code-visibility-CA0UOEgk.js} +6 -6
  19. package/dist/{common-B7_ZSg0P.js → common-BgoPSdwN.js} +4960 -4835
  20. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
  21. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
  22. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
  23. package/dist/{data-grid-overlay-editor-B_cmrkBm.js → data-grid-overlay-editor-CRjm80Ab.js} +5 -5
  24. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  25. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BPxtoS-z.js} +1 -1
  26. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
  27. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
  28. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.js} +1 -1
  29. package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
  30. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  31. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-B3eUiVJB.js} +177 -169
  32. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
  33. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.js} +1 -1
  34. package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
  35. package/dist/{error-entries-DSvFWT_M.js → error-entries-CMNtA8NK.js} +959 -959
  36. package/dist/{feedback-button-CTF2Nj9s.js → feedback-button-CZA7i0Ir.js} +29 -29
  37. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
  38. package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
  39. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  40. package/dist/{glide-data-editor-BQHF9puC.js → glide-data-editor-Bd52MLtf.js} +370 -368
  41. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
  42. package/dist/{globals-Cj8iqMEp.js → globals-BvANmUu6.js} +3 -3
  43. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  44. package/dist/{html-to-image-B8s5SgGm.js → html-to-image-6xcoTKhC.js} +29 -29
  45. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
  46. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
  47. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.js} +1 -1
  48. package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
  49. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  50. package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
  51. package/dist/main.js +3177 -2980
  52. package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
  53. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  54. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
  55. package/dist/{number-overlay-editor-DBcnm0SY.js → number-overlay-editor-C7ffluCS.js} +3 -3
  56. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  57. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  58. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-keVUAUeS.js} +2 -2
  59. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
  60. package/dist/precisionRound-DhAzeRcm.js +49 -0
  61. package/dist/{process-output-CKRXyszb.js → process-output-BJRJFobf.js} +4 -4
  62. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  63. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.js} +2 -2
  64. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  65. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CWo-UAKV.js} +1 -1
  66. package/dist/{reveal-component-DTBSuGkC.js → reveal-component-Dfuz8zQ_.js} +19 -19
  67. package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
  68. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
  69. package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
  70. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
  71. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
  72. package/dist/style.css +1 -1
  73. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
  74. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.js} +2 -2
  75. package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
  76. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  77. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-rv7QUa46.js} +1 -1
  78. package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
  79. package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
  80. package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
  81. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
  82. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-ChgNtdmE.js} +15 -14
  83. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
  84. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
  85. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
  86. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
  87. package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
  88. package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.js} +147 -147
  89. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  90. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  91. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-D36Zk7t-.js} +1 -1
  92. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
  93. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
  94. package/package.json +4 -4
  95. package/src/__tests__/host-imports.test.ts +1 -0
  96. package/src/components/app-config/ai-config.tsx +6 -2
  97. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  98. package/src/components/chat/chat-panel.tsx +3 -7
  99. package/src/components/chat/chat-utils.ts +4 -8
  100. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  101. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  102. package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
  103. package/src/components/data-table/charts/charts.tsx +14 -19
  104. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  105. package/src/components/data-table/columns.tsx +2 -1
  106. package/src/components/data-table/context-menu.tsx +36 -20
  107. package/src/components/data-table/date-popover.tsx +6 -4
  108. package/src/components/data-table/export-actions.tsx +267 -163
  109. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  110. package/src/components/data-table/schemas.ts +14 -36
  111. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  112. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  113. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  114. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  115. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  116. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  117. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  118. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  119. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  120. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  121. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  122. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  123. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  124. package/src/components/editor/connections/components.tsx +1 -1
  125. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  126. package/src/components/editor/controls/command-palette.tsx +22 -10
  127. package/src/components/editor/dynamic-favicon.tsx +21 -40
  128. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  129. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  130. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  131. package/src/components/editor/file-tree/file-explorer.tsx +10 -5
  132. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  133. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  134. package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
  135. package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
  136. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  137. package/src/components/icons/large-spinner.tsx +3 -14
  138. package/src/components/pages/home-page.tsx +4 -4
  139. package/src/components/slides/slide-cell-view.tsx +1 -1
  140. package/src/components/tracing/tracing.tsx +2 -2
  141. package/src/core/attachments/state.ts +5 -8
  142. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  143. package/src/core/cells/cells.ts +2 -1
  144. package/src/core/cells/pending-delete-service.ts +2 -6
  145. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  146. package/src/core/datasets/data-source-discovery.ts +4 -6
  147. package/src/core/edit-app.tsx +2 -2
  148. package/src/core/host/AGENTS.md +11 -2
  149. package/src/core/host/__tests__/attachments.test.ts +56 -0
  150. package/src/core/host/__tests__/document.test.ts +126 -0
  151. package/src/core/host/__tests__/events.test.ts +49 -0
  152. package/src/core/host/__tests__/fixtures.ts +41 -0
  153. package/src/core/host/__tests__/notebook-client.test.ts +496 -0
  154. package/src/core/host/connection.ts +81 -0
  155. package/src/core/host/events.ts +68 -0
  156. package/src/core/host/notebook-client.ts +316 -0
  157. package/src/core/host/resources/attachments.ts +19 -0
  158. package/src/core/host/resources/document.ts +101 -0
  159. package/src/core/hotkeys/actions.ts +2 -4
  160. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  161. package/src/core/islands/bootstrap.ts +0 -1
  162. package/src/core/network/connection.ts +4 -2
  163. package/src/core/network/useConnectionNotice.ts +8 -2
  164. package/src/core/run-app.tsx +2 -2
  165. package/src/core/runtime/__tests__/runtime.test.ts +5 -12
  166. package/src/core/runtime/runtime.ts +5 -5
  167. package/src/core/static/files.ts +2 -0
  168. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  169. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  170. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
  171. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
  172. package/src/core/websocket/connection-state.ts +70 -0
  173. package/src/core/websocket/state.ts +7 -0
  174. package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
  175. package/src/core/websocket/transports/create.ts +31 -0
  176. package/src/core/websocket/transports/sse.ts +14 -2
  177. package/src/core/websocket/transports/transport.ts +12 -23
  178. package/src/core/websocket/types.ts +9 -39
  179. package/src/core/websocket/useNotebookClient.ts +78 -0
  180. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  181. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  182. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  183. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  184. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  185. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  186. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  187. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  188. package/src/hooks/useAsyncData.ts +3 -3
  189. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  190. package/src/hooks/useDebounce.ts +5 -8
  191. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  192. package/src/hooks/useDelayElapsed.ts +14 -10
  193. package/src/hooks/useElapsedTime.ts +3 -3
  194. package/src/hooks/useInputHistory.ts +4 -7
  195. package/src/hooks/useInternalStateWithSync.ts +6 -6
  196. package/src/hooks/useResizeObserver.ts +4 -7
  197. package/src/hooks/useScript.ts +67 -70
  198. package/src/hooks/useStateForKey.ts +93 -0
  199. package/src/plugins/core/registerReactComponent.tsx +1 -1
  200. package/src/plugins/impl/DataTablePlugin.tsx +14 -15
  201. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  202. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  203. package/src/plugins/impl/FormPlugin.tsx +1 -1
  204. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  205. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
  206. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  207. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  208. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  209. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  210. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  211. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  212. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  213. package/src/plugins/impl/chat/chat-ui.tsx +10 -9
  214. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  215. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  216. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  217. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  218. package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
  219. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  220. package/src/plugins/impl/panel/utils.ts +17 -0
  221. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  222. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  223. package/src/stories/editor.stories.tsx +24 -17
  224. package/src/utils/__tests__/arrays.test.ts +35 -0
  225. package/src/utils/__tests__/numbers.test.ts +48 -1
  226. package/src/utils/__tests__/objects.test.ts +21 -0
  227. package/src/utils/arrays.ts +10 -0
  228. package/src/utils/numbers.ts +28 -17
  229. package/src/utils/objects.ts +3 -7
  230. package/src/utils/once.ts +19 -19
  231. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  232. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  233. package/dist/precisionRound-DMIVQrt2.js +0 -97
  234. package/src/core/host/__tests__/protocol.test.ts +0 -214
  235. package/src/core/host/protocol.ts +0 -1943
  236. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -54,6 +54,7 @@ function renderExportActions(
54
54
  props: Partial<ExportActionProps> = {},
55
55
  sizeLimit: { limitBytes: number; unavailableMessage: string } | null = null,
56
56
  mode: AppMode = "edit",
57
+ strictMode = false,
57
58
  ) {
58
59
  const store = createStore();
59
60
  store.set(downloadSizeLimitAtom, sizeLimit);
@@ -66,7 +67,7 @@ function renderExportActions(
66
67
  </TooltipProvider>
67
68
  </Provider>
68
69
  );
69
- const result = render(view(props));
70
+ const result = render(view(props), { reactStrictMode: strictMode });
70
71
  return {
71
72
  ...result,
72
73
  rerenderExportActions: (nextProps: Partial<ExportActionProps>) =>
@@ -284,7 +285,7 @@ describe("ExportActions dialog", () => {
284
285
  await waitFor(() => {
285
286
  expect(downloadByURL).toHaveBeenCalled();
286
287
  });
287
- expect(getExportMetadata).toHaveBeenCalledTimes(2);
288
+ expect(getExportMetadata).toHaveBeenCalledTimes(3);
288
289
  expect(
289
290
  screen.queryByText("GeoParquet export requires pyarrow."),
290
291
  ).not.toBeInTheDocument();
@@ -310,10 +311,10 @@ describe("ExportActions dialog", () => {
310
311
  mocks.handleInstallPackages.mock.calls[0][1]();
311
312
  });
312
313
  await waitFor(() => {
313
- expect(getExportMetadata).toHaveBeenCalledTimes(2);
314
+ expect(getExportMetadata).toHaveBeenCalledTimes(3);
314
315
  });
315
316
  expect(
316
- screen.getByRole("button", { name: "Install pyarrow" }),
317
+ await screen.findByRole("button", { name: "Install pyarrow" }),
317
318
  ).toBeInTheDocument();
318
319
  expect(
319
320
  screen.getByRole("button", { name: "Download GeoParquet" }),
@@ -530,6 +531,345 @@ describe("ExportActions dialog", () => {
530
531
  expect(downloadByURL).not.toHaveBeenCalled();
531
532
  });
532
533
 
534
+ it("does not revive an earlier source failure when switching back", async () => {
535
+ downloadAs.mockRejectedValueOnce(new Error("First source failed"));
536
+ const { rerenderExportActions } = renderExportActions({
537
+ metadataSource: "first",
538
+ });
539
+ await openDialog();
540
+ fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
541
+ expect(await screen.findByRole("alert")).toHaveTextContent(
542
+ "First source failed",
543
+ );
544
+
545
+ rerenderExportActions({ metadataSource: "second" });
546
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
547
+ rerenderExportActions({ metadataSource: "first" });
548
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
549
+ });
550
+
551
+ async function startGeoParquetInstall(strictMode = false) {
552
+ const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
553
+ downloadAs.mockResolvedValueOnce({
554
+ url: "",
555
+ filename: "",
556
+ error: "GeoParquet export requires pyarrow.",
557
+ missing_packages: ["pyarrow"],
558
+ });
559
+ const { rerenderExportActions, unmount } = renderExportActions(
560
+ { getExportMetadata, metadataSource: "first" },
561
+ null,
562
+ "edit",
563
+ strictMode,
564
+ );
565
+ const dialog = await openDialog();
566
+ fireEvent.click(
567
+ await screen.findByRole("button", { name: "Download GeoParquet" }),
568
+ );
569
+ fireEvent.click(
570
+ await screen.findByRole("button", { name: "Install pyarrow" }),
571
+ );
572
+ return { dialog, getExportMetadata, rerenderExportActions, unmount };
573
+ }
574
+
575
+ it("does not retry with a geometry choice after the final geometry disappears", async () => {
576
+ const { getExportMetadata } = await startGeoParquetInstall();
577
+ getExportMetadata.mockResolvedValue({
578
+ geometry_columns: [],
579
+ primary_geometry_column: null,
580
+ default_geometry_column: null,
581
+ formats: {},
582
+ });
583
+
584
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
585
+
586
+ expect(downloadAs).toHaveBeenCalledTimes(1);
587
+ expect(downloadByURL).not.toHaveBeenCalled();
588
+ expect(
589
+ await screen.findByText(
590
+ "The selected geometry is no longer available. Choose another geometry to export.",
591
+ ),
592
+ ).toBeInTheDocument();
593
+ fireEvent.click(screen.getByRole("button", { name: "Download Parquet" }));
594
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
595
+ expect(downloadAs).toHaveBeenLastCalledWith({ format: "parquet" });
596
+ });
597
+
598
+ it("does not export when installation finishes after the widget unmounts", async () => {
599
+ const { getExportMetadata, unmount } = await startGeoParquetInstall();
600
+ unmount();
601
+
602
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
603
+
604
+ expect(getExportMetadata).toHaveBeenCalledTimes(1);
605
+ expect(downloadAs).toHaveBeenCalledTimes(1);
606
+ expect(downloadByURL).not.toHaveBeenCalled();
607
+ });
608
+
609
+ it("does not export when post-install metadata resolves after unmount", async () => {
610
+ const { getExportMetadata, unmount } = await startGeoParquetInstall();
611
+ let resolveCheck: (value: ExportMetadata) => void = () => undefined;
612
+ getExportMetadata.mockImplementationOnce(
613
+ () =>
614
+ new Promise<ExportMetadata>((resolve) => {
615
+ resolveCheck = resolve;
616
+ }),
617
+ );
618
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
619
+ expect(getExportMetadata).toHaveBeenCalledTimes(2);
620
+ unmount();
621
+
622
+ await act(async () => resolveCheck(geometryMetadata));
623
+
624
+ expect(downloadAs).toHaveBeenCalledTimes(1);
625
+ expect(downloadByURL).not.toHaveBeenCalled();
626
+ });
627
+
628
+ it("does not deliver a retried export that resolves after unmount", async () => {
629
+ const { unmount } = await startGeoParquetInstall();
630
+ let resolveDownload: (value: {
631
+ url: string;
632
+ filename: string;
633
+ }) => void = () => undefined;
634
+ downloadAs.mockImplementationOnce(
635
+ () =>
636
+ new Promise((resolve) => {
637
+ resolveDownload = resolve;
638
+ }),
639
+ );
640
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
641
+ expect(downloadAs).toHaveBeenCalledTimes(2);
642
+ unmount();
643
+
644
+ await act(async () =>
645
+ resolveDownload({
646
+ url: "https://example.test/export",
647
+ filename: "table",
648
+ }),
649
+ );
650
+
651
+ expect(downloadByURL).not.toHaveBeenCalled();
652
+ });
653
+
654
+ it("retries after installation under StrictMode effect replay", async () => {
655
+ await startGeoParquetInstall(true);
656
+
657
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
658
+
659
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
660
+ expect(downloadAs).toHaveBeenLastCalledWith({
661
+ format: "parquet",
662
+ geometry_column: "location",
663
+ });
664
+ });
665
+
666
+ it("does not export when installation finishes after the dialog closes", async () => {
667
+ const { dialog, getExportMetadata } = await startGeoParquetInstall();
668
+ fireEvent.click(within(dialog).getByRole("button", { name: "Close" }));
669
+ await waitFor(() => {
670
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
671
+ });
672
+
673
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
674
+
675
+ expect(getExportMetadata).toHaveBeenCalledTimes(1);
676
+ expect(downloadAs).toHaveBeenCalledTimes(1);
677
+ expect(downloadByURL).not.toHaveBeenCalled();
678
+ });
679
+
680
+ it("does not export when installation finishes after the source changes", async () => {
681
+ const { getExportMetadata, rerenderExportActions } =
682
+ await startGeoParquetInstall();
683
+ rerenderExportActions({ getExportMetadata, metadataSource: "second" });
684
+ await waitFor(() => {
685
+ expect(getExportMetadata).toHaveBeenCalledTimes(2);
686
+ });
687
+
688
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
689
+
690
+ expect(getExportMetadata).toHaveBeenCalledTimes(2);
691
+ expect(downloadAs).toHaveBeenCalledTimes(1);
692
+ expect(downloadByURL).not.toHaveBeenCalled();
693
+ });
694
+
695
+ it("does not export a non-geographic retry after the dialog closes", async () => {
696
+ downloadAs.mockResolvedValueOnce({
697
+ url: "",
698
+ filename: "",
699
+ error: "Parquet export requires pyarrow.",
700
+ missing_packages: ["pyarrow"],
701
+ });
702
+ renderExportActions();
703
+ const dialog = await openDialog();
704
+ fireEvent.click(screen.getByRole("button", { name: "Download Parquet" }));
705
+ fireEvent.click(
706
+ await screen.findByRole("button", { name: "Install pyarrow" }),
707
+ );
708
+ fireEvent.click(within(dialog).getByRole("button", { name: "Close" }));
709
+
710
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
711
+
712
+ expect(downloadAs).toHaveBeenCalledTimes(1);
713
+ expect(downloadByURL).not.toHaveBeenCalled();
714
+ });
715
+
716
+ it("retries after installation when an earlier export succeeded", async () => {
717
+ const getExportMetadata = vi.fn().mockResolvedValue({
718
+ ...geometryMetadata,
719
+ formats: {
720
+ parquet: {
721
+ available: false,
722
+ reason: "GeoParquet export requires pyarrow.",
723
+ missing_packages: ["pyarrow"],
724
+ },
725
+ },
726
+ });
727
+ renderExportActions({ getExportMetadata });
728
+ await openDialog();
729
+ fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
730
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
731
+ fireEvent.click(
732
+ await screen.findByRole("button", { name: "Install pyarrow" }),
733
+ );
734
+ getExportMetadata.mockResolvedValue(geometryMetadata);
735
+
736
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
737
+
738
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(2));
739
+ expect(downloadAs).toHaveBeenLastCalledWith({
740
+ format: "parquet",
741
+ geometry_column: "location",
742
+ });
743
+ });
744
+
745
+ it("waits for the post-install metadata check before exporting", async () => {
746
+ let resolveCheck: (value: ExportMetadata) => void = () => undefined;
747
+ const getExportMetadata = vi.fn().mockResolvedValue({
748
+ ...geometryMetadata,
749
+ formats: {
750
+ parquet: {
751
+ available: false,
752
+ reason: "GeoParquet export requires pyarrow.",
753
+ missing_packages: ["pyarrow"],
754
+ },
755
+ },
756
+ });
757
+ renderExportActions({ getExportMetadata });
758
+ await openDialog();
759
+ fireEvent.click(
760
+ await screen.findByRole("button", { name: "Install pyarrow" }),
761
+ );
762
+ getExportMetadata.mockImplementationOnce(
763
+ () =>
764
+ new Promise((resolve) => {
765
+ resolveCheck = resolve;
766
+ }),
767
+ );
768
+
769
+ await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
770
+ expect(downloadAs).not.toHaveBeenCalled();
771
+
772
+ getExportMetadata.mockResolvedValue(geometryMetadata);
773
+ await act(async () => resolveCheck(geometryMetadata));
774
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
775
+ expect(downloadAs).toHaveBeenCalledTimes(1);
776
+ });
777
+
778
+ it("uses the new source eligibility after a settled package failure", async () => {
779
+ const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
780
+ downloadAs.mockResolvedValueOnce({
781
+ url: "",
782
+ filename: "",
783
+ error: "GeoParquet export requires pyarrow.",
784
+ missing_packages: ["pyarrow"],
785
+ });
786
+ const { rerenderExportActions } = renderExportActions({
787
+ getExportMetadata,
788
+ metadataSource: "first",
789
+ });
790
+ await openDialog();
791
+ fireEvent.click(
792
+ await screen.findByRole("button", { name: "Download GeoParquet" }),
793
+ );
794
+ await screen.findByRole("button", { name: "Install pyarrow" });
795
+
796
+ rerenderExportActions({ getExportMetadata, metadataSource: "second" });
797
+
798
+ await waitFor(() =>
799
+ expect(
800
+ screen.getByRole("button", { name: "Download GeoParquet" }),
801
+ ).toBeEnabled(),
802
+ );
803
+ expect(
804
+ screen.queryByRole("button", { name: "Install pyarrow" }),
805
+ ).not.toBeInTheDocument();
806
+ });
807
+
808
+ it("ignores metadata for an earlier source that resolves late", async () => {
809
+ let resolveFirst: (value: ExportMetadata) => void = () => undefined;
810
+ const first = vi.fn(
811
+ () =>
812
+ new Promise<ExportMetadata>((resolve) => {
813
+ resolveFirst = resolve;
814
+ }),
815
+ );
816
+ const second = vi.fn().mockResolvedValue({
817
+ ...geometryMetadata,
818
+ geometry_columns: [
819
+ { name: "region", encoding: "objects", crs: "EPSG:3857" },
820
+ ],
821
+ primary_geometry_column: "region",
822
+ default_geometry_column: "region",
823
+ });
824
+ const { rerenderExportActions } = renderExportActions({
825
+ getExportMetadata: first,
826
+ metadataSource: "first",
827
+ });
828
+ await openDialog();
829
+ rerenderExportActions({
830
+ getExportMetadata: second,
831
+ metadataSource: "second",
832
+ });
833
+ await waitFor(() =>
834
+ expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
835
+ "region",
836
+ ),
837
+ );
838
+
839
+ await act(async () => resolveFirst(geometryMetadata));
840
+
841
+ expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
842
+ "region",
843
+ );
844
+ });
845
+
846
+ it("does not use earlier metadata when the new source request fails", async () => {
847
+ const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
848
+ const { rerenderExportActions } = renderExportActions({
849
+ getExportMetadata,
850
+ metadataSource: "first",
851
+ });
852
+ await openDialog();
853
+ await waitFor(() =>
854
+ expect(
855
+ screen.getByRole("button", { name: "Download GeoParquet" }),
856
+ ).toBeEnabled(),
857
+ );
858
+ getExportMetadata.mockRejectedValueOnce(new Error("Connection lost"));
859
+
860
+ rerenderExportActions({ getExportMetadata, metadataSource: "second" });
861
+
862
+ expect(
863
+ await screen.findByText(
864
+ "Could not load geometry options: Connection lost",
865
+ ),
866
+ ).toBeInTheDocument();
867
+ expect(
868
+ screen.getByRole("button", { name: "Download Parquet" }),
869
+ ).toBeDisabled();
870
+ expect(screen.getByRole("button", { name: "Download CSV" })).toBeEnabled();
871
+ });
872
+
533
873
  it("shows formats in the approved order with an options toggle where supported", async () => {
534
874
  renderExportActions();
535
875
  const dialog = await openDialog();
@@ -591,6 +931,94 @@ describe("ExportActions dialog", () => {
591
931
  expect(within(dialog).queryByRole("group")).not.toBeInTheDocument();
592
932
  });
593
933
 
934
+ it("describes format options with their description and current settings", async () => {
935
+ renderExportActions({
936
+ getExportMetadata: vi.fn().mockResolvedValue(geometryMetadata),
937
+ });
938
+ await openDialog();
939
+ await screen.findByRole("button", { name: "GeoParquet options" });
940
+
941
+ for (const [label, description] of [
942
+ ["CSV", "Comma-separated values Comma · UTF-8"],
943
+ ["TSV", "Best for Excel and Google Sheets UTF-8"],
944
+ ["JSON", "Raw JSON data Escapes non-ASCII"],
945
+ ["GeoParquet", "Parquet with geometry and CRS metadata location"],
946
+ ["GeoJSON", "Geometry in longitude and latitude location"],
947
+ ]) {
948
+ expect(
949
+ screen.getByRole("button", { name: `${label} options` }),
950
+ ).toHaveAccessibleDescription(description);
951
+ }
952
+
953
+ const csvOptions = screen.getByRole("button", { name: "CSV options" });
954
+ fireEvent.click(csvOptions);
955
+ fireEvent.change(screen.getByRole("combobox", { name: "Delimiter" }), {
956
+ target: { value: ";" },
957
+ });
958
+ expect(csvOptions).toHaveAccessibleDescription(
959
+ "Comma-separated values Semicolon · UTF-8",
960
+ );
961
+ fireEvent.click(csvOptions);
962
+ expect(csvOptions).toHaveAccessibleDescription(
963
+ "Comma-separated values Semicolon · UTF-8",
964
+ );
965
+ });
966
+
967
+ it.each([
968
+ ["CSV", "Comma-separated values"],
969
+ ["TSV", "Best for Excel and Google Sheets"],
970
+ ["JSON", "Raw JSON data"],
971
+ ["GeoParquet", "Parquet with geometry and CRS metadata"],
972
+ ["GeoJSON", "Geometry in longitude and latitude"],
973
+ ])(
974
+ "toggles %s options from the row label and description",
975
+ async (label, description) => {
976
+ renderExportActions({
977
+ getExportMetadata: vi.fn().mockResolvedValue(geometryMetadata),
978
+ });
979
+ await openDialog();
980
+ const toggle = await screen.findByRole("button", {
981
+ name: `${label} options`,
982
+ });
983
+
984
+ fireEvent.click(screen.getByText(label));
985
+ expect(toggle).toHaveAttribute("aria-expanded", "true");
986
+ expect(
987
+ screen.getByRole("group", { name: `${label} options` }),
988
+ ).toBeInTheDocument();
989
+
990
+ fireEvent.click(screen.getByText(description));
991
+ expect(toggle).toHaveAttribute("aria-expanded", "false");
992
+ expect(
993
+ screen.queryByRole("group", { name: `${label} options` }),
994
+ ).not.toBeInTheDocument();
995
+ },
996
+ );
997
+
998
+ it("keeps download and copy independent of row expansion", async () => {
999
+ vi.stubGlobal(
1000
+ "fetch",
1001
+ vi.fn().mockResolvedValue({
1002
+ ok: true,
1003
+ text: vi.fn().mockResolvedValue("name,value\n"),
1004
+ }),
1005
+ );
1006
+ renderExportActions();
1007
+ await openDialog();
1008
+ const toggle = screen.getByRole("button", { name: "CSV options" });
1009
+
1010
+ fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
1011
+ await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
1012
+ expect(toggle).toHaveAttribute("aria-expanded", "false");
1013
+
1014
+ fireEvent.click(screen.getByText("CSV"));
1015
+ fireEvent.click(screen.getByRole("button", { name: "Copy CSV" }));
1016
+ expect(toggle).toHaveAttribute("aria-expanded", "true");
1017
+ await waitFor(() => {
1018
+ expect(copyToClipboard).toHaveBeenCalledWith("name,value\n");
1019
+ });
1020
+ });
1021
+
594
1022
  it("sends only changed CSV options with a download", async () => {
595
1023
  renderExportActions();
596
1024
  await openDialog();
@@ -1412,7 +1840,7 @@ describe("ExportActions GeoJSON", () => {
1412
1840
  );
1413
1841
  await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
1414
1842
  await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
1415
- expect(getExportMetadata).toHaveBeenCalledTimes(2);
1843
+ expect(getExportMetadata).toHaveBeenCalledTimes(3);
1416
1844
  expect(downloadAs).toHaveBeenLastCalledWith({
1417
1845
  format: "geojson",
1418
1846
  geometry_column: "boundary",
@@ -1685,7 +2113,7 @@ describe("ExportActions GeoJSON clipboard", () => {
1685
2113
  format: "geojson",
1686
2114
  geometry_column: "boundary",
1687
2115
  });
1688
- expect(getExportMetadata).toHaveBeenCalledTimes(2);
2116
+ expect(getExportMetadata).toHaveBeenCalledTimes(3);
1689
2117
  expect(downloadByURL).not.toHaveBeenCalled();
1690
2118
  });
1691
2119
 
@@ -13,8 +13,8 @@ import {
13
13
  XIcon,
14
14
  } from "lucide-react";
15
15
  import type { JSX } from "react";
16
- import React, { useMemo, useRef, useState } from "react";
17
- import { type UseFormReturn, useForm } from "react-hook-form";
16
+ import React, { useMemo, useState } from "react";
17
+ import { type UseFormReturn, useForm, useWatch } from "react-hook-form";
18
18
  import useResizeObserver from "use-resize-observer";
19
19
  import { PythonIcon } from "@/components/editor/cell/code/icons";
20
20
  import { Alert, AlertDescription } from "@/components/ui/alert";
@@ -103,16 +103,14 @@ export const TablePanel: React.FC<TablePanelProps> = ({
103
103
 
104
104
  const [selectedTab, setSelectedTab] = useState(DEFAULT_TAB_NAME);
105
105
  const [tabCounter, setTabCounter] = useState(tabs.length);
106
- const prevDisplayHeader = useRef(displayHeader);
106
+ const [prevDisplayHeader, setPrevDisplayHeader] = useState(displayHeader);
107
+
108
+ if (displayHeader !== prevDisplayHeader) {
109
+ setPrevDisplayHeader(displayHeader);
110
+ }
107
111
 
108
112
  // Auto-create a default chart tab when chart builder opens with no tabs
109
- if (
110
- displayHeader &&
111
- !prevDisplayHeader.current &&
112
- tabs.length === 0 &&
113
- cellId
114
- ) {
115
- prevDisplayHeader.current = displayHeader;
113
+ if (displayHeader && !prevDisplayHeader && tabs.length === 0 && cellId) {
116
114
  const tabName = getChartTabName(0, NEW_CHART_TYPE);
117
115
  const newTabs = new Map(tabsMap);
118
116
  newTabs.set(cellId, [
@@ -122,7 +120,6 @@ export const TablePanel: React.FC<TablePanelProps> = ({
122
120
  setTabCounter(1);
123
121
  setSelectedTab(tabName);
124
122
  }
125
- prevDisplayHeader.current = displayHeader;
126
123
 
127
124
  if (!displayHeader || (tabs.length === 0 && !displayHeader)) {
128
125
  return dataTable;
@@ -320,9 +317,10 @@ export const ChartPanel: React.FC<{
320
317
  saveChartType,
321
318
  getDataUrl,
322
319
  fieldTypes,
323
- hasSchema = Boolean(fieldTypes?.length),
320
+ hasSchema: providedHasSchema,
324
321
  isLargeDataset,
325
322
  }) => {
323
+ const hasSchema = providedHasSchema ?? Boolean(fieldTypes?.length);
326
324
  const { theme } = useTheme();
327
325
  const form = useForm<ChartSchemaType>({
328
326
  defaultValues: chartConfig ?? getChartDefaults(),
@@ -370,16 +368,13 @@ export const ChartPanel: React.FC<{
370
368
  // Re-run when the data table changes
371
369
  }, [tableData, renderLargeCharts, csvFieldTypes]);
372
370
 
373
- const formValues = form.watch();
374
-
375
- // This ensures the chart re-renders when the actual values change
376
- const memoizedFormValues = useMemo(() => {
377
- return structuredClone(formValues);
378
- }, [formValues]);
371
+ // useWatch returns a new object only when a value changes. It is typed as
372
+ // deeply partial, but the form always starts from complete defaults.
373
+ const formValues = useWatch({ control: form.control }) as ChartSchemaType;
379
374
 
380
375
  const specWithoutData = createSpecWithoutData(
381
376
  selectedChartType,
382
- memoizedFormValues,
377
+ formValues,
383
378
  theme,
384
379
  "container",
385
380
  CHART_HEIGHT,
@@ -6,7 +6,7 @@
6
6
 
7
7
  import type { Table } from "@tanstack/react-table";
8
8
  import { Columns3Icon, EyeIcon, EyeOffIcon, ScanEyeIcon } from "lucide-react";
9
- import React, { useMemo } from "react";
9
+ import React from "react";
10
10
  import { ColumnName } from "@/components/datasources/components";
11
11
  import { Button } from "@/components/ui/button";
12
12
  import {
@@ -24,6 +24,7 @@ import {
24
24
  } from "@/components/ui/popover";
25
25
  import { type BulkAction, useSelectList } from "@/components/ui/select-core";
26
26
  import type { DataType } from "@/core/kernel/messages";
27
+ import { Arrays } from "@/utils/arrays";
27
28
  import { cn } from "@/utils/cn";
28
29
  import { Events } from "@/utils/events";
29
30
  import { smartMatchFilter } from "@/utils/smartMatch";
@@ -67,9 +68,8 @@ export const ColumnVisibilityDropdown = <TData,>({
67
68
  const hideableIds = userColumns
68
69
  .filter((column) => column.getCanHide())
69
70
  .map((column) => column.id);
70
- const visibleHideableCount = hideableIds.filter(
71
- (id) => !hiddenIds.includes(id),
72
- ).length;
71
+ const visibleHideableColumnIds = Arrays.difference(hideableIds, hiddenIds);
72
+ const visibleHideableCount = visibleHideableColumnIds.length;
73
73
 
74
74
  const applyHidden = (next: string[] | string | null) => {
75
75
  const hidden = new Set(Array.isArray(next) ? next : []);
@@ -106,15 +106,10 @@ export const ColumnVisibilityDropdown = <TData,>({
106
106
  .filter((option) => !option.disabled)
107
107
  .map((option) => option.value)
108
108
  : [];
109
- const showOnlyColumnState = useMemo(
110
- () => ({
111
- visibleHideableColumnIds: hideableIds.filter(
112
- (id) => !hiddenIds.includes(id),
113
- ),
114
- hideableColumnIdSet: new Set(hideableIds),
115
- }),
116
- [hideableIds, hiddenIds],
117
- );
109
+ const showOnlyColumnState = {
110
+ visibleHideableColumnIds,
111
+ hideableColumnIdSet: new Set(hideableIds),
112
+ };
118
113
  const hasSearchBulkActions =
119
114
  showOnlyMatchIds.length > 0 || matchingActions.length > 0;
120
115
 
@@ -135,6 +135,7 @@ export function generateColumns<T>({
135
135
  }): ColumnDef<T>[] {
136
136
  // Row-headers are typically index columns
137
137
  const rowHeadersSet = new Set(rowHeaders.map(([columnName]) => columnName));
138
+ const wrappedColumnsSet = new Set(wrappedColumns);
138
139
 
139
140
  const typesByColumn = Maps.keyBy(fieldTypes, (entry) => entry[0]);
140
141
 
@@ -287,7 +288,7 @@ export function generateColumns<T>({
287
288
  }
288
289
 
289
290
  const justify = getJustify(key);
290
- const wrapped = wrappedColumns?.includes(key);
291
+ const wrapped = wrappedColumnsSet.has(key);
291
292
  const isCellSelected = cell?.getIsSelected?.() || false;
292
293
  const canSelectCell =
293
294
  (selection === "single-cell" || selection === "multi-cell") &&