@perspective-dev/viewer 5.3.0 → 5.4.0

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 (265) hide show
  1. package/dist/cdn/perspective-viewer.js +8 -8
  2. package/dist/cdn/perspective-viewer.js.map +3 -3
  3. package/dist/css/blueprint.css +1 -0
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -0
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -0
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -0
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -0
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -0
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/column-format.d.ts +16 -3
  31. package/dist/esm/extensions.d.ts +0 -5
  32. package/dist/esm/perspective-viewer.inline.js +7 -7
  33. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  34. package/dist/esm/perspective-viewer.js +8 -8
  35. package/dist/esm/perspective-viewer.js.map +3 -3
  36. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  37. package/dist/wasm/perspective-viewer.d.ts +62 -73
  38. package/dist/wasm/perspective-viewer.js +230 -238
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
  41. package/package.json +7 -7
  42. package/src/css/_context_menu.css +1 -1
  43. package/src/css/_dropdown_menu.css +1 -1
  44. package/src/css/_viewer_bundle.css +7 -8
  45. package/src/css/column-dropdown.css +0 -3
  46. package/src/css/column-selector.css +8 -5
  47. package/src/css/column-settings-panel.css +0 -206
  48. package/src/css/column-style.css +95 -0
  49. package/src/css/config-selector.css +18 -10
  50. package/src/css/filter-dropdown.css +5 -5
  51. package/src/css/form/debug.css +28 -4
  52. package/src/css/function-dropdown.css +5 -5
  53. package/src/css/panel-tab.css +23 -0
  54. package/src/css/plugin-selector.css +6 -5
  55. package/src/css/render-warning.css +1 -1
  56. package/src/css/status-bar.css +52 -21
  57. package/src/css/type-icon.css +1 -3
  58. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  59. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  60. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  61. package/src/css/{containers → ui/containers}/tabs.css +13 -3
  62. package/src/css/{dom → ui/dom}/select.css +19 -8
  63. package/src/css/viewer.css +32 -31
  64. package/src/rust/agent/tools.rs +5 -2
  65. package/src/rust/components/chat_panel.rs +1 -1
  66. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  67. package/src/rust/components/column_dropdown.rs +2 -2
  68. package/src/rust/components/column_selector/active_column.rs +15 -9
  69. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  70. package/src/rust/components/column_selector/config_selector.rs +2 -2
  71. package/src/rust/components/column_selector/filter_column.rs +8 -5
  72. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  73. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  74. package/src/rust/components/column_selector/sort_column.rs +7 -4
  75. package/src/rust/components/column_selector.rs +3 -3
  76. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  77. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  79. package/src/rust/components/column_settings_sidebar/style_tab.rs +296 -238
  80. package/src/rust/components/column_settings_sidebar.rs +3 -4
  81. package/src/rust/components/copy_dropdown.rs +1 -1
  82. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  83. package/src/rust/components/datetime_column_style/simple.rs +16 -6
  84. package/src/rust/components/datetime_column_style.rs +51 -12
  85. package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
  86. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  87. package/src/rust/components/export_dropdown.rs +1 -1
  88. package/src/rust/components/expression_editor.rs +1 -1
  89. package/src/rust/components/filter_dropdown.rs +2 -2
  90. package/src/rust/components/function_dropdown.rs +2 -2
  91. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  92. package/src/rust/components/main_panel/reconcile.rs +9 -4
  93. package/src/rust/components/main_panel/render.rs +62 -11
  94. package/src/rust/components/main_panel/update.rs +39 -10
  95. package/src/rust/components/main_panel.rs +26 -2
  96. package/src/rust/components/mod.rs +5 -7
  97. package/src/rust/components/new_panel_menu.rs +97 -0
  98. package/src/rust/components/number_series_style.rs +2 -3
  99. package/src/rust/components/panel_menu.rs +61 -87
  100. package/src/rust/components/panel_tab.rs +100 -43
  101. package/src/rust/components/plugin_tab.rs +125 -87
  102. package/src/rust/components/render_warning.rs +2 -2
  103. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  104. package/src/rust/components/settings_panel.rs +3 -2
  105. package/src/rust/components/status_bar.rs +113 -20
  106. package/src/rust/components/status_indicator.rs +5 -14
  107. package/src/rust/components/string_column_style.rs +1 -2
  108. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  109. package/src/rust/components/style_controls/number_string_format/digits_section.rs +25 -15
  110. package/src/rust/components/style_controls/number_string_format/misc_section.rs +7 -3
  111. package/src/rust/components/style_controls/number_string_format/style_section.rs +2 -1
  112. package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
  113. package/src/rust/components/style_controls/number_string_format.rs +37 -14
  114. package/src/rust/components/viewer/msg.rs +43 -30
  115. package/src/rust/components/viewer/panels.rs +50 -19
  116. package/src/rust/components/viewer/render.rs +15 -13
  117. package/src/rust/components/viewer/settings.rs +137 -68
  118. package/src/rust/components/viewer/snapshots.rs +5 -10
  119. package/src/rust/components/viewer/wiring.rs +1 -21
  120. package/src/rust/components/viewer.rs +15 -10
  121. package/src/rust/components/window_editor.rs +8 -9
  122. package/src/rust/config/column_config_schema.rs +358 -52
  123. package/src/rust/config/datetime_column_style.rs +4 -21
  124. package/src/rust/config/mod.rs +0 -2
  125. package/src/rust/config/number_string_format.rs +211 -18
  126. package/src/rust/config/workspace_config.rs +2 -1
  127. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  128. package/src/rust/custom_elements/mod.rs +0 -3
  129. package/src/rust/custom_elements/viewer.rs +0 -18
  130. package/src/rust/js/plugin.rs +0 -86
  131. package/src/rust/lib.rs +12 -15
  132. package/src/rust/presentation/column_locator.rs +3 -7
  133. package/src/rust/presentation.rs +85 -38
  134. package/src/rust/queries/hosted_tables.rs +38 -0
  135. package/src/rust/queries/mod.rs +2 -0
  136. package/src/rust/queries/palette_set.rs +1 -1
  137. package/src/rust/queries/plugin_column_styles.rs +2 -2
  138. package/src/rust/renderer/activate.rs +1 -3
  139. package/src/rust/renderer/dispatch.rs +117 -30
  140. package/src/rust/renderer/limits.rs +24 -5
  141. package/src/rust/renderer/plugin_config.rs +56 -8
  142. package/src/rust/renderer/props.rs +2 -1
  143. package/src/rust/renderer.rs +14 -27
  144. package/src/rust/session/props.rs +4 -3
  145. package/src/rust/session/view_subscription.rs +19 -6
  146. package/src/rust/session.rs +38 -70
  147. package/src/rust/tasks/create_panel.rs +1 -1
  148. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  149. package/src/rust/tasks/eject.rs +1 -0
  150. package/src/rust/tasks/pipeline.rs +0 -60
  151. package/src/rust/tasks/presize_panels.rs +24 -1
  152. package/src/rust/tasks/restore_and_render.rs +0 -17
  153. package/src/rust/tasks/send_column_config.rs +1 -1
  154. package/src/rust/tasks/send_plugin_config.rs +1 -1
  155. package/src/rust/tasks/set_edit_mode.rs +1 -1
  156. package/src/rust/ui/containers/control_group.rs +113 -0
  157. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  158. package/src/rust/{components → ui}/containers/mod.rs +1 -4
  159. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  160. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  161. package/src/rust/{components → ui}/containers/select.rs +8 -8
  162. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  163. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  164. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  165. package/src/rust/{components → ui}/context_menu.rs +8 -13
  166. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  167. package/src/rust/{components → ui}/font_loader.rs +0 -26
  168. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  169. package/src/rust/ui/form/intl_label.rs +81 -0
  170. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  171. package/src/rust/{components → ui}/form/mod.rs +1 -10
  172. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  173. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  174. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  175. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  176. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  177. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  178. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  179. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  180. package/src/rust/ui/mod.rs +56 -0
  181. package/src/rust/ui/modal.rs +61 -0
  182. package/src/rust/{components → ui}/portal.rs +174 -79
  183. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  184. package/src/rust/utils/completion.rs +0 -6
  185. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  186. package/src/rust/utils/mod.rs +2 -0
  187. package/src/rust/workspace.rs +35 -0
  188. package/src/svg/boolean-type.svg +3 -2
  189. package/src/svg/checkbox-checked-icon.svg +4 -7
  190. package/src/svg/checkbox-hover.svg +3 -3
  191. package/src/svg/checkbox-off.svg +2 -3
  192. package/src/svg/checkbox-on.svg +3 -3
  193. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  194. package/src/svg/datagrid-select-column.svg +8 -15
  195. package/src/svg/datagrid-select-region.svg +8 -15
  196. package/src/svg/datagrid-select-row-tree.svg +7 -12
  197. package/src/svg/datagrid-select-row.svg +8 -13
  198. package/src/svg/date-type.svg +4 -23
  199. package/src/svg/drawer-tab-hover.svg +4 -6
  200. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  201. package/src/svg/drawer-tab-invert.svg +3 -5
  202. package/src/svg/drawer-tab.svg +5 -5
  203. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  204. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  205. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  206. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  207. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  208. package/src/svg/new-panel-icon.svg +4 -0
  209. package/src/svg/number-type.svg +3 -3
  210. package/src/svg/radio-hover.svg +3 -3
  211. package/src/svg/radio-off.svg +3 -3
  212. package/src/svg/radio-on.svg +3 -3
  213. package/src/svg/status_ok.svg +3 -26
  214. package/src/svg/string-type.svg +4 -8
  215. package/src/svg/theme-icon.svg +5 -5
  216. package/src/themes/blueprint.css +133 -0
  217. package/src/themes/botanical.css +3 -52
  218. package/src/themes/defaults.css +3 -144
  219. package/src/themes/dracula.css +1 -71
  220. package/src/themes/eggplant.css +131 -0
  221. package/src/themes/gruvbox-dark.css +2 -79
  222. package/src/themes/gruvbox.css +2 -40
  223. package/src/themes/icons.css +2 -10
  224. package/src/themes/intl/ar.css +185 -0
  225. package/src/themes/intl/de.css +65 -33
  226. package/src/themes/intl/es.css +60 -28
  227. package/src/themes/intl/fr.css +60 -28
  228. package/src/themes/intl/ja.css +62 -30
  229. package/src/themes/intl/pt.css +58 -26
  230. package/src/themes/intl/zh.css +68 -36
  231. package/src/themes/intl.css +42 -7
  232. package/src/themes/ledger.css +137 -0
  233. package/src/themes/monokai.css +1 -64
  234. package/src/themes/nord.css +131 -0
  235. package/src/themes/phosphor.css +1 -61
  236. package/src/themes/pro-dark.css +1 -52
  237. package/src/themes/pro.css +98 -0
  238. package/src/themes/solarized-dark.css +1 -46
  239. package/src/themes/solarized.css +1 -24
  240. package/src/themes/themes.css +10 -5
  241. package/src/themes/vaporwave.css +1 -57
  242. package/src/themes/velvet.css +132 -0
  243. package/src/ts/column-format.ts +52 -2
  244. package/src/ts/extensions.ts +0 -13
  245. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  246. package/src/css/plugin-settings-panel.css +0 -99
  247. package/src/rust/components/modal.rs +0 -177
  248. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  249. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  250. package/src/rust/custom_elements/modal.rs +0 -299
  251. package/src/svg/dropdown-selector-light.svg +0 -3
  252. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
  253. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
  254. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
  255. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
  256. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
  257. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
  258. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  259. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  260. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  261. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  262. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  263. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  264. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  265. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -26,7 +26,7 @@ use crate::presentation::{
26
26
  ColumnSettingsTab, ColumnSettingsTarget, DragDropProps, PresentationProps,
27
27
  };
28
28
  use crate::renderer::RendererProps;
29
- use crate::session::{SessionProps, TableLoadState, ViewStats};
29
+ use crate::session::{SessionProps, TableLoadState};
30
30
  use crate::utils::Completion;
31
31
 
32
32
  /// The filter-bearing payload of a master panel's selection or click event
@@ -45,9 +45,31 @@ pub struct MasterSelection {
45
45
  pub cell_fallback: Option<Filter>,
46
46
  }
47
47
 
48
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49
+ pub enum Divider {
50
+ Settings,
51
+ ColumnSettings,
52
+ }
53
+
54
+ impl Divider {
55
+ /// The shadow-DOM selector of the divider's resizable pane (pane 0 of
56
+ /// its `SplitPanel`; the flex-fill pane renders bare).
57
+ pub fn pane_selector(self) -> &'static str {
58
+ match self {
59
+ Self::Settings => "#app_panel > .split-panel-child",
60
+ Self::ColumnSettings => "#modal_panel > .split-panel-child",
61
+ }
62
+ }
63
+ }
64
+
65
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
66
+ pub enum PaneTarget {
67
+ Width(i32),
68
+ Natural,
69
+ }
70
+
48
71
  #[derive(Debug)]
49
72
  pub enum PerspectiveViewerMsg {
50
- ColumnSettingsPanelSizeUpdate(Option<i32>),
51
73
  ColumnSettingsPanelAutoWidth(f64),
52
74
  ToggleColumnSettingsPin,
53
75
  ToggleColumnSettingsPinComplete(Sender<()>),
@@ -84,12 +106,18 @@ pub enum PerspectiveViewerMsg {
84
106
  /// nudge runs complete (invariant I6).
85
107
  SetActivePanel(String, Option<Completion>),
86
108
 
87
- /// The named panel's frame was closed (removed from the layout); remove it
88
- /// from the workspace and dispose its engines. The `Completion` resolves
89
- /// `removePanel()` after the eject's teardown run completes (invariant
90
- /// I6) — carrying any teardown error, which was previously dropped.
109
+ /// Close the named panel: remove it from the workspace now but keep its
110
+ /// engines parked until [`Self::PanelClosed`] reports the layout commit
111
+ /// that reclaims its cell. The `Completion` resolves `removePanel()`
112
+ /// after the eject's teardown run completes (invariant I6) — carrying
113
+ /// any teardown error.
91
114
  ClosePanel(String, Option<Completion>),
92
115
 
116
+ /// `MainPanel` saw the named panel leave the `regular-layout` tree, so
117
+ /// dispose its parked panel, or close outright one the workspace still
118
+ /// holds.
119
+ PanelClosed(String),
120
+
93
121
  /// `restoreWorkspace` finished replacing the panel set in the
94
122
  /// `Workspace` (new models inserted, old panels ejected, layout staged):
95
123
  /// activate the named panel, re-subscribe the per-panel wiring, and
@@ -130,26 +158,10 @@ pub enum PerspectiveViewerMsg {
130
158
  /// Some panel's title changed (any panel, via `_title_subscriptions`);
131
159
  /// re-render so the tab titles refresh.
132
160
  TitlesChanged,
133
- SettingsPanelSizeUpdate(Option<i32>),
134
-
135
- /// The settings-pane divider proposed a new pane width (per pointermove,
136
- /// from the *deferred* `SplitPanel` — it has NOT been applied). Feeds the
137
- /// latest-wins presize pump (`PRESIZE_EVERYWHERE_PLAN.md` P1): geometry
138
- /// commits only after every visible panel has rendered at its target.
139
- SettingsDividerMove(i32),
140
-
141
- /// Run one pump iteration: presize all visible panels at the newest
142
- /// proposed pane width, then commit it.
143
- SettingsDividerPump,
144
-
145
- /// Presize for this pane width completed — commit it (the deferred
146
- /// `SplitPanel`'s controlled `size`), then pump again if a newer target
147
- /// arrived meanwhile.
148
- SettingsDividerCommit(i32),
149
-
150
- /// Divider drag ended: reactively finalize every visible panel at its
151
- /// exact settled cell (debounced no-op when the presizes were exact).
152
- SettingsDividerFinish,
161
+ DividerMove(Divider, PaneTarget),
162
+ DividerPump(Divider),
163
+ DividerCommit(Divider, PaneTarget),
164
+ DividerFinish(Divider),
153
165
  SettingsPanelTabChanged(SelectedTab),
154
166
  SettingsPanelAutoWidth(f64),
155
167
  ToggleDebug,
@@ -203,10 +215,11 @@ pub enum PerspectiveViewerMsg {
203
215
  UpdateColumnSettingsCommit(Sender<()>),
204
216
  UpdateDragDrop(Box<DragDropProps>),
205
217
 
206
- /// Update only stats-related fields of `session_props` without touching
207
- /// `config`. This prevents `stats_changed` events (e.g. from `reset()`)
208
- /// from propagating a freshly-cleared config to the column selector.
209
- UpdateSessionStats(Option<ViewStats>, Option<TableLoadState>),
218
+ /// Update only the stats-derived fields of `session_props`
219
+ /// (`has_table_cells`, `has_table`) without touching `config`. This
220
+ /// prevents `stats_changed` events (e.g. from `reset()`) from propagating
221
+ /// a freshly-cleared config to the column selector.
222
+ UpdateSessionStats(bool, Option<TableLoadState>),
210
223
 
211
224
  /// Refresh the root's render snapshot of the `Workspace`-owned global
212
225
  /// filter set (dispatched by its `filters_changed` PubSub).
@@ -33,7 +33,7 @@ use crate::renderer::Renderer;
33
33
  use crate::session::*;
34
34
  use crate::tasks::*;
35
35
  use crate::utils::{Completion, spawn_owned};
36
- use crate::workspace::PanelId;
36
+ use crate::workspace::{Panel, PanelId};
37
37
 
38
38
  impl PerspectiveViewer {
39
39
  pub(super) fn on_layout_changed(&mut self, ctx: &Context<Self>) -> bool {
@@ -136,16 +136,11 @@ impl PerspectiveViewer {
136
136
  true
137
137
  }
138
138
 
139
- pub(super) fn on_close_panel(
140
- &mut self,
141
- ctx: &Context<Self>,
142
- id: String,
143
- completion: Option<Completion>,
144
- ) -> bool {
145
- let id = PanelId::from(id);
146
-
147
- let was_active = ctx.props().workspace.active_id().as_ref() == Some(&id);
148
- let removed = ctx.props().workspace.remove_panel(&id);
139
+ /// Remove `id` from the workspace and return the detached [`Panel`] with
140
+ /// its engines still alive, or `None` for an unknown id.
141
+ fn detach_panel(&mut self, ctx: &Context<Self>, id: &PanelId) -> Option<Panel> {
142
+ let was_active = ctx.props().workspace.active_id().as_ref() == Some(id);
143
+ let removed = ctx.props().workspace.remove_panel(id)?;
149
144
  if was_active {
150
145
  if let Some(next) = ctx.props().workspace.panel_ids().first().cloned() {
151
146
  ctx.props().workspace.set_active(next);
@@ -164,19 +159,55 @@ impl PerspectiveViewer {
164
159
  self.retarget_active(ctx, new_session, new_renderer);
165
160
  }
166
161
 
167
- let eject = removed.map(eject_panel);
168
- match (eject, completion) {
169
- (Some(eject), Some(completion)) => completion.resolve_after(eject),
170
- (Some(eject), None) => spawn_owned("close-panel", eject),
171
- (None, Some(completion)) => completion.resolve_after(async { Ok(()) }),
172
- (None, None) => {},
173
- }
174
-
175
162
  apply_global_filters(&ctx.props().workspace);
176
163
  self._title_subscriptions = subscribe_panel_titles(ctx);
164
+ Some(removed)
165
+ }
166
+
167
+ pub(super) fn on_close_panel(
168
+ &mut self,
169
+ ctx: &Context<Self>,
170
+ id: String,
171
+ completion: Option<Completion>,
172
+ ) -> bool {
173
+ let id = PanelId::from(id);
174
+ let Some(removed) = self.detach_panel(ctx, &id) else {
175
+ if let Some(completion) = completion {
176
+ completion.resolve_after(async { Ok(()) });
177
+ }
178
+
179
+ return false;
180
+ };
181
+
182
+ ctx.props().workspace.stash_closing(removed);
183
+ if let Some(completion) = completion {
184
+ self.pending_closes.insert(id, completion);
185
+ }
186
+
177
187
  true
178
188
  }
179
189
 
190
+ pub(super) fn on_panel_closed(&mut self, ctx: &Context<Self>, id: String) -> bool {
191
+ let id = PanelId::from(id);
192
+ if let Some(panel) = ctx.props().workspace.take_closing(&id) {
193
+ let eject = eject_panel(panel);
194
+ match self.pending_closes.remove(&id) {
195
+ Some(completion) => completion.resolve_after(eject),
196
+ None => spawn_owned("close-panel", eject),
197
+ }
198
+
199
+ return false;
200
+ }
201
+
202
+ match self.detach_panel(ctx, &id) {
203
+ Some(panel) => {
204
+ spawn_owned("close-panel", eject_panel(panel));
205
+ true
206
+ },
207
+ None => false,
208
+ }
209
+ }
210
+
180
211
  pub(super) fn on_duplicate_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
181
212
  if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
182
213
  let elem = ctx.props().elem.clone();
@@ -22,15 +22,15 @@ use yew::prelude::*;
22
22
 
23
23
  use super::PerspectiveViewer;
24
24
  use super::msg::PerspectiveViewerMsg::*;
25
+ use super::msg::{Divider, PaneTarget};
25
26
  use crate::components::column_settings_sidebar::ColumnSettingsPanel;
26
- use crate::components::containers::split_panel::SplitPanel;
27
- use crate::components::font_loader::FontLoader;
28
27
  use crate::components::main_panel::MainPanel;
29
28
  use crate::components::panel_menu::PanelCommand;
30
29
  use crate::components::settings_panel::SettingsPanel;
31
- use crate::components::style::StyleProvider;
30
+ use crate::components::style::StyleSurface;
32
31
  use crate::queries::*;
33
32
  use crate::session::TableLoadState;
33
+ use crate::ui::{FontLoader, SplitPanel, StyleProvider};
34
34
 
35
35
  impl PerspectiveViewer {
36
36
  pub(super) fn render(&self, ctx: &Context<Self>) -> Html {
@@ -58,10 +58,6 @@ impl PerspectiveViewer {
58
58
  toggle: true,
59
59
  });
60
60
 
61
- let on_column_settings_panel_resize = ctx
62
- .link()
63
- .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
64
-
65
61
  let on_close_settings = ctx
66
62
  .link()
67
63
  .callback(|()| ToggleSettingsInit(None, true, None));
@@ -135,9 +131,12 @@ impl PerspectiveViewer {
135
131
  id="modal_panel"
136
132
  class={classes!(is_pinned.then_some("pinned"))}
137
133
  reverse=true
134
+ deferred={is_pinned}
135
+ size={self.settings_geometry.column_settings_width_override}
138
136
  initial_size={self.settings_geometry.column_settings_width_override}
139
- on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
140
- on_resize={on_column_settings_panel_resize}
137
+ on_reset={ctx.link().callback(|_| DividerMove(Divider::ColumnSettings, PaneTarget::Natural))}
138
+ on_resize={ctx.link().callback(|(x, _): (i32, i32)| DividerMove(Divider::ColumnSettings, PaneTarget::Width(x)))}
139
+ on_resize_finished={ctx.link().callback(|_| DividerFinish(Divider::ColumnSettings))}
141
140
  >
142
141
  <ColumnSettingsPanel
143
142
  {selected_column}
@@ -171,6 +170,7 @@ impl PerspectiveViewer {
171
170
  {on_reset}
172
171
  on_activate_panel={ctx.link().callback(|id| SetActivePanel(id, None))}
173
172
  on_close_panel={ctx.link().callback(|id| ClosePanel(id, None))}
173
+ on_panel_closed={ctx.link().callback(PanelClosed)}
174
174
  on_panel_command={ctx.link().batch_callback(|(id, cmd): (String, PanelCommand)| {
175
175
  match cmd {
176
176
  PanelCommand::New => vec![NewPanel(id)],
@@ -178,6 +178,7 @@ impl PerspectiveViewer {
178
178
  vec![NewPanelFrom { client, table }]
179
179
  },
180
180
  PanelCommand::Duplicate => vec![DuplicatePanel(id)],
181
+ PanelCommand::NewFromPanel(source) => vec![DuplicatePanel(source)],
181
182
  PanelCommand::Reset => vec![ResetPanel(Some(id), false, None)],
182
183
  PanelCommand::ToggleMaster => vec![ToggleMaster(id)],
183
184
  PanelCommand::Close => vec![ClosePanel(id, None)],
@@ -233,8 +234,9 @@ impl PerspectiveViewer {
233
234
  };
234
235
 
235
236
  html! {
236
- <StyleProvider root={ctx.props().elem.clone()}>
237
+ <StyleProvider root={ctx.props().elem.clone()} sheet={StyleSurface::Viewer.sheet()}>
237
238
  <div id="component_container" class={is_single_panel}>
239
+ <slot name="modal" />
238
240
  <div id="layout_area">
239
241
  <SplitPanel
240
242
  id="app_panel"
@@ -243,9 +245,9 @@ impl PerspectiveViewer {
243
245
  deferred=true
244
246
  size={self.settings_geometry.pane_width_override}
245
247
  initial_size={self.settings_geometry.pane_width_override}
246
- on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
247
- on_resize={ctx.link().callback(|(x, _): (i32, i32)| SettingsDividerMove(x))}
248
- on_resize_finished={ctx.link().callback(|_| SettingsDividerFinish)}
248
+ on_reset={ctx.link().callback(|_| DividerMove(Divider::Settings, PaneTarget::Natural))}
249
+ on_resize={ctx.link().callback(|(x, _): (i32, i32)| DividerMove(Divider::Settings, PaneTarget::Width(x)))}
250
+ on_resize_finished={ctx.link().callback(|_| DividerFinish(Divider::Settings))}
249
251
  >
250
252
  { settings_panel }
251
253
  <div id="main_column_container">
@@ -10,11 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! Settings-sidebar handlers: the open/close toggle (with its presize
14
- //! choreography), the deferred divider's latest-wins presize pump
15
- //! (`PRESIZE_EVERYWHERE_PLAN.md` P1/P2), and the column-settings drawer. The
16
- //! presize sweeps themselves live in [`crate::tasks`] (`presize_panels`).
17
-
18
13
  use futures::channel::oneshot::{Sender, channel};
19
14
  use perspective_js::utils::*;
20
15
  use wasm_bindgen::prelude::*;
@@ -22,12 +17,13 @@ use yew::prelude::*;
22
17
 
23
18
  use super::PerspectiveViewer;
24
19
  use super::msg::PerspectiveViewerMsg::*;
25
- use crate::components::font_loader::FontLoaderStatus;
20
+ use super::msg::{Divider, PaneTarget};
26
21
  use crate::components::settings_panel::SelectedTab;
27
22
  use crate::config::*;
28
23
  use crate::presentation::{ColumnSettingsTab, ColumnSettingsTarget, OpenColumnSettings};
29
24
  use crate::queries::get_current_column_locator;
30
25
  use crate::tasks::*;
26
+ use crate::ui::FontLoaderStatus;
31
27
 
32
28
  /// The settings sidebar's geometry state, folded into one field on
33
29
  /// [`PerspectiveViewer`]: the pane/drawer width overrides, the divider
@@ -44,11 +40,9 @@ pub(super) struct SettingsGeometry {
44
40
  /// High-water-mark auto width reported by the settings panel.
45
41
  pub auto_width: f64,
46
42
 
47
- /// Latest-wins presize pump for the (deferred) settings divider: the
48
- /// newest proposed pane width not yet presized, and whether a pump
49
- /// iteration is in flight. See `SettingsDividerMove`.
50
- pub divider_target: Option<i32>,
51
- pub divider_pumping: bool,
43
+ pub settings_divider: DividerPump,
44
+
45
+ pub column_settings_divider: DividerPump,
52
46
 
53
47
  /// Open-state geometry deltas `(layout_area.w − mpc.w, main_column.h −
54
48
  /// mpc.h)`, cached at settings *close* time — used to presize panels to
@@ -85,6 +79,29 @@ pub(super) struct SettingsGeometry {
85
79
  pub column_settings_docked_width: Option<f64>,
86
80
  }
87
81
 
82
+ #[derive(Default)]
83
+ pub(super) struct DividerPump {
84
+ pub target: Option<PaneTarget>,
85
+ pub pumping: bool,
86
+ pub finish_pending: bool,
87
+ }
88
+
89
+ impl SettingsGeometry {
90
+ fn pump_mut(&mut self, divider: Divider) -> &mut DividerPump {
91
+ match divider {
92
+ Divider::Settings => &mut self.settings_divider,
93
+ Divider::ColumnSettings => &mut self.column_settings_divider,
94
+ }
95
+ }
96
+
97
+ fn is_divider_deferred(&self, divider: Divider) -> bool {
98
+ match divider {
99
+ Divider::Settings => true,
100
+ Divider::ColumnSettings => self.column_settings_pinned,
101
+ }
102
+ }
103
+ }
104
+
88
105
  impl PerspectiveViewer {
89
106
  pub(super) fn on_toggle_settings_init(
90
107
  &mut self,
@@ -257,84 +274,143 @@ impl PerspectiveViewer {
257
274
  };
258
275
  }
259
276
 
260
- pub(super) fn on_settings_panel_size_update(&mut self, x: Option<i32>) -> bool {
261
- match x {
262
- Some(x) => {
263
- self.settings_geometry.pane_width_override = Some(x);
264
- false
277
+ fn apply_pane_target(&mut self, divider: Divider, target: PaneTarget) -> bool {
278
+ let deferred = self.settings_geometry.is_divider_deferred(divider);
279
+ match (divider, target) {
280
+ (Divider::Settings, PaneTarget::Width(w)) => {
281
+ self.settings_geometry.pane_width_override = Some(w);
282
+ true
265
283
  },
266
- None => {
284
+ (Divider::Settings, PaneTarget::Natural) => {
267
285
  self.settings_geometry.pane_width_override = None;
268
286
  self.settings_geometry.auto_width = 0.0;
269
287
  self.on_settings_panel_dimensions_reset.emit(());
270
288
  true
271
289
  },
290
+ (Divider::ColumnSettings, PaneTarget::Width(w)) => {
291
+ self.settings_geometry.column_settings_width_override = Some(w);
292
+ deferred
293
+ },
294
+ (Divider::ColumnSettings, PaneTarget::Natural) => {
295
+ self.settings_geometry.column_settings_width_override = None;
296
+ self.settings_geometry.column_settings_auto_width = 0.0;
297
+ true
298
+ },
272
299
  }
273
300
  }
274
301
 
275
- pub(super) fn on_settings_divider_move(
302
+ pub(super) fn on_divider_move(
276
303
  &mut self,
277
304
  ctx: &Context<Self>,
278
- pane_width: i32,
305
+ divider: Divider,
306
+ target: PaneTarget,
279
307
  ) -> bool {
308
+ if !self.settings_geometry.is_divider_deferred(divider) {
309
+ return self.apply_pane_target(divider, target);
310
+ }
311
+
280
312
  // Latest-wins: overwrite the pending target; start the pump if
281
313
  // idle. Intermediate targets that arrive while a presize is in
282
314
  // flight are dropped (mirrors `PresizeQueue`'s single queued
283
315
  // slot) — the pane tracks the pointer at content-render rate.
284
- self.settings_geometry.divider_target = Some(pane_width);
285
- if !self.settings_geometry.divider_pumping {
286
- self.settings_geometry.divider_pumping = true;
287
- ctx.link().send_message(SettingsDividerPump);
316
+ let pump = self.settings_geometry.pump_mut(divider);
317
+ pump.target = Some(target);
318
+ if !pump.pumping {
319
+ pump.pumping = true;
320
+ ctx.link().send_message(DividerPump(divider));
288
321
  }
289
322
 
290
323
  false
291
324
  }
292
325
 
293
- pub(super) fn on_settings_divider_pump(&mut self, ctx: &Context<Self>) -> bool {
294
- if let Some(pane_width) = self.settings_geometry.divider_target.take() {
295
- let workspace = ctx.props().workspace.clone();
296
- let elem = ctx.props().elem.clone();
297
- let link = ctx.link().clone();
298
- ApiFuture::spawn(async move {
299
- let presents =
300
- presize_visible_panels_pane_width(&workspace, &elem, pane_width as f64).await;
301
- link.send_message(SettingsDividerCommit(pane_width));
302
- // Same task as the commit's re-render (whether Yew drained it
303
- // synchronously inside `send_message` or deferred it to a
304
- // microtask): the staged reveal and the pane-width geometry
305
- // land in one paint.
306
- presents.reveal();
307
- Ok(())
308
- });
309
- } else {
310
- self.settings_geometry.divider_pumping = false;
311
- }
326
+ pub(super) fn on_divider_pump(&mut self, ctx: &Context<Self>, divider: Divider) -> bool {
327
+ let workspace = ctx.props().workspace.clone();
328
+ let pump = self.settings_geometry.pump_mut(divider);
329
+ let Some(target) = pump.target.take() else {
330
+ pump.pumping = false;
331
+ if std::mem::take(&mut pump.finish_pending) {
332
+ ApiFuture::spawn(async move {
333
+ resize_visible_panels(&workspace).await;
334
+ Ok(())
335
+ });
336
+ }
337
+
338
+ return false;
339
+ };
340
+
341
+ let elem = ctx.props().elem.clone();
342
+ let pane_width = match target {
343
+ PaneTarget::Width(w) => Some(w as f64),
344
+ PaneTarget::Natural => measure_pane_natural_width(&elem, divider.pane_selector()),
345
+ };
346
+
347
+ let link = ctx.link().clone();
348
+ ApiFuture::spawn(async move {
349
+ let presents = match pane_width {
350
+ Some(pane_width) => {
351
+ presize_visible_panels_pane_width(
352
+ &workspace,
353
+ &elem,
354
+ divider.pane_selector(),
355
+ pane_width,
356
+ )
357
+ .await
358
+ },
359
+ None => StagedPresents::default(),
360
+ };
361
+
362
+ link.send_message(DividerCommit(divider, target));
363
+ presents.reveal();
364
+ Ok(())
365
+ });
312
366
 
313
367
  false
314
368
  }
315
369
 
316
- pub(super) fn on_settings_divider_commit(
370
+ pub(super) fn on_divider_commit(
317
371
  &mut self,
318
372
  ctx: &Context<Self>,
319
- pane_width: i32,
373
+ divider: Divider,
374
+ target: PaneTarget,
320
375
  ) -> bool {
321
376
  // Every visible panel has rendered at (approximately) its
322
377
  // target box — NOW move the geometry: the re-render below
323
- // applies this width to the deferred `SplitPanel`'s controlled
378
+ // applies this target to the deferred `SplitPanel`'s controlled
324
379
  // `size`, in the same task as the presizes' inline clears.
325
- self.settings_geometry.pane_width_override = Some(pane_width);
326
- ctx.link().send_message(SettingsDividerPump);
380
+ self.apply_pane_target(divider, target);
381
+ if target == PaneTarget::Natural {
382
+ self.settings_geometry.pump_mut(divider).finish_pending = true;
383
+ }
384
+
385
+ ctx.link().send_message(DividerPump(divider));
327
386
  true
328
387
  }
329
388
 
330
- pub(super) fn on_settings_divider_finish(&mut self, ctx: &Context<Self>) -> bool {
389
+ pub(super) fn on_divider_finish(&mut self, ctx: &Context<Self>, divider: Divider) -> bool {
390
+ if !self.settings_geometry.is_divider_deferred(divider) {
391
+ return false;
392
+ }
393
+
394
+ let pump = self.settings_geometry.pump_mut(divider);
395
+ if pump.pumping {
396
+ pump.finish_pending = true;
397
+ } else {
398
+ let workspace = ctx.props().workspace.clone();
399
+ ApiFuture::spawn(async move {
400
+ resize_visible_panels(&workspace).await;
401
+ Ok(())
402
+ });
403
+ }
404
+
405
+ false
406
+ }
407
+
408
+ fn resize_for_ratchet(&self, ctx: &Context<Self>) {
331
409
  let workspace = ctx.props().workspace.clone();
332
410
  ApiFuture::spawn(async move {
333
411
  resize_visible_panels(&workspace).await;
334
412
  Ok(())
335
413
  });
336
-
337
- false
338
414
  }
339
415
 
340
416
  pub(super) fn on_settings_panel_tab_changed(&mut self, tab: SelectedTab) -> bool {
@@ -343,9 +419,10 @@ impl PerspectiveViewer {
343
419
  changed
344
420
  }
345
421
 
346
- pub(super) fn on_settings_panel_auto_width(&mut self, w: f64) -> bool {
422
+ pub(super) fn on_settings_panel_auto_width(&mut self, ctx: &Context<Self>, w: f64) -> bool {
347
423
  if w > self.settings_geometry.auto_width {
348
424
  self.settings_geometry.auto_width = w;
425
+ self.resize_for_ratchet(ctx);
349
426
  true
350
427
  } else {
351
428
  false
@@ -397,19 +474,17 @@ impl PerspectiveViewer {
397
474
  true
398
475
  }
399
476
 
400
- pub(super) fn on_column_settings_panel_size_update(&mut self, x: Option<i32>) -> bool {
401
- self.settings_geometry.column_settings_width_override = x;
402
- if x.is_none() {
403
- self.settings_geometry.column_settings_auto_width = 0.0;
404
- true
405
- } else {
406
- false
407
- }
408
- }
409
-
410
- pub(super) fn on_column_settings_panel_auto_width(&mut self, w: f64) -> bool {
477
+ pub(super) fn on_column_settings_panel_auto_width(
478
+ &mut self,
479
+ ctx: &Context<Self>,
480
+ w: f64,
481
+ ) -> bool {
411
482
  if w > self.settings_geometry.column_settings_auto_width {
412
483
  self.settings_geometry.column_settings_auto_width = w;
484
+ if self.settings_geometry.column_settings_pinned {
485
+ self.resize_for_ratchet(ctx);
486
+ }
487
+
413
488
  true
414
489
  } else {
415
490
  false
@@ -561,12 +636,6 @@ impl PerspectiveViewer {
561
636
  true
562
637
  }
563
638
 
564
- /// Toggling the debug tab re-renders the settings panel only — the
565
- /// `DebugPanel` populates itself (`get_viewer_config` on mount +
566
- /// change subscriptions), so no plugin dispatch is owed. The old
567
- /// `just_render` here relied on the pre-amendment unconditional
568
- /// `Unchanged → update` arm and repainted the plugin as a side effect
569
- /// (`PLUGIN_DRAW_INVARIANT_PLAN.md` amendment, migration 2).
570
639
  pub(super) fn on_toggle_debug(&mut self, _ctx: &Context<Self>) -> bool {
571
640
  self.debug_open = !self.debug_open;
572
641
  true
@@ -22,7 +22,7 @@ use yew::prelude::*;
22
22
  use super::PerspectiveViewer;
23
23
  use crate::presentation::{DragDropProps, PresentationProps};
24
24
  use crate::renderer::RendererProps;
25
- use crate::session::{SessionProps, TableLoadState, ViewStats};
25
+ use crate::session::{SessionProps, TableLoadState};
26
26
  use crate::tasks::resize_visible_panels;
27
27
 
28
28
  impl PerspectiveViewer {
@@ -59,12 +59,12 @@ impl PerspectiveViewer {
59
59
 
60
60
  pub(super) fn on_update_session_stats(
61
61
  &mut self,
62
- stats: Option<ViewStats>,
62
+ has_table_cells: bool,
63
63
  has_table: Option<TableLoadState>,
64
64
  ) -> bool {
65
- let changed =
66
- stats != self.session_props.stats || has_table != self.session_props.has_table;
67
- self.session_props.stats = stats;
65
+ let changed = has_table_cells != self.session_props.has_table_cells
66
+ || has_table != self.session_props.has_table;
67
+ self.session_props.has_table_cells = has_table_cells;
68
68
  self.session_props.has_table = has_table;
69
69
  changed
70
70
  }
@@ -146,11 +146,6 @@ impl PerspectiveViewer {
146
146
  changed
147
147
  }
148
148
 
149
- /// LEVEL-triggered spinner count: ASSIGN the absolute in-flight
150
- /// config-run count from the session's RAII accounting — any missed or
151
- /// reordered notification is corrected by the next one, unlike the
152
- /// edge-counted increment/decrement pair this replaces (see
153
- /// `UPDATE_COUNT_REGRESSION_PLAN.md`).
154
149
  pub(super) fn on_update_in_flight(&mut self, count: u32) -> bool {
155
150
  let changed = count != self.update_count;
156
151
  self.update_count = count;