@perspective-dev/viewer 5.1.0 → 5.3.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 (245) hide show
  1. package/dist/cdn/perspective-viewer.js +9 -9
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/docs/perspective-docs.json +1 -0
  24. package/dist/esm/column-format.d.ts +19 -28
  25. package/dist/esm/perspective-viewer.d.ts +3 -0
  26. package/dist/esm/perspective-viewer.inline.js +9 -9
  27. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  28. package/dist/esm/perspective-viewer.js +9 -9
  29. package/dist/esm/perspective-viewer.js.map +4 -4
  30. package/dist/esm/plugin.d.ts +10 -0
  31. package/dist/esm/providers.d.ts +79 -0
  32. package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
  33. package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
  34. package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
  35. package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
  36. package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
  37. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
  38. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
  39. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
  40. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
  41. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
  42. package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
  43. package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
  44. package/dist/esm/ts-rs/Notation.d.ts +17 -0
  45. package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
  46. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +42 -2
  47. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
  48. package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
  49. package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
  50. package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
  51. package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
  52. package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
  53. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
  54. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
  55. package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
  56. package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
  57. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
  58. package/dist/esm/ts-rs/Unit.d.ts +1 -0
  59. package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
  60. package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
  61. package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
  62. package/dist/esm/ts-rs/ViewerConfig.d.ts +42 -2
  63. package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
  64. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +63 -0
  65. package/dist/esm/ts-rs/WindowSpec.d.ts +3 -2
  66. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  67. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +20 -9
  68. package/dist/wasm/perspective-viewer.d.ts +157 -41
  69. package/dist/wasm/perspective-viewer.js +452 -151
  70. package/dist/wasm/perspective-viewer.wasm +0 -0
  71. package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
  72. package/package.json +2 -1
  73. package/src/css/_viewer_bundle.css +1 -0
  74. package/src/css/column-selector.css +8 -0
  75. package/src/css/column-settings-panel.css +9 -5
  76. package/src/css/column-style.css +209 -31
  77. package/src/css/config-selector.css +38 -3
  78. package/src/css/dom/select.css +2 -1
  79. package/src/css/form/code-editor.css +6 -7
  80. package/src/css/form/mirrored-textarea.css +81 -0
  81. package/src/css/viewer.css +328 -0
  82. package/src/rust/agent/client/mod.rs +19 -0
  83. package/src/rust/agent/client/protocol.rs +320 -0
  84. package/src/rust/agent/client/transport.rs +287 -0
  85. package/src/rust/agent/client/turn.rs +174 -0
  86. package/src/rust/agent/config.rs +159 -0
  87. package/src/rust/agent/docs.rs +293 -0
  88. package/src/rust/agent/markdown/block.rs +509 -0
  89. package/src/rust/agent/markdown/entity.rs +52 -0
  90. package/src/rust/agent/markdown/events.rs +71 -0
  91. package/src/rust/agent/markdown/inline.rs +497 -0
  92. package/src/rust/agent/markdown/mod.rs +466 -0
  93. package/src/rust/agent/mod.rs +23 -0
  94. package/src/rust/agent/preamble.md +16 -0
  95. package/src/rust/agent/runtime.rs +201 -0
  96. package/src/rust/agent/slot.rs +236 -0
  97. package/src/rust/agent/tools.rs +1141 -0
  98. package/src/rust/components/chat_panel.rs +289 -0
  99. package/src/rust/components/column_selector/active_column.rs +2 -2
  100. package/src/rust/components/column_selector/add_expression_button.rs +3 -3
  101. package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
  102. package/src/rust/components/column_selector/config_selector.rs +84 -7
  103. package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
  104. package/src/rust/components/column_selector/inactive_column.rs +2 -2
  105. package/src/rust/components/column_selector.rs +55 -4
  106. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
  107. package/src/rust/components/column_settings_sidebar/style_tab.rs +262 -225
  108. package/src/rust/components/column_settings_sidebar.rs +88 -69
  109. package/src/rust/components/containers/dragdrop_list.rs +37 -0
  110. package/src/rust/components/editable_header.rs +85 -144
  111. package/src/rust/components/expression_editor.rs +9 -16
  112. package/src/rust/components/form/code_editor.rs +29 -28
  113. package/src/rust/components/form/debug.rs +9 -5
  114. package/src/rust/components/form/mirrored_textarea.rs +165 -0
  115. package/src/rust/components/form/mod.rs +4 -1
  116. package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
  117. package/src/rust/components/form/named_value_picker.rs +86 -0
  118. package/src/rust/components/form/palette_selector.rs +440 -0
  119. package/src/rust/components/main_panel/msg.rs +2 -2
  120. package/src/rust/components/main_panel/reconcile.rs +4 -4
  121. package/src/rust/components/mod.rs +2 -0
  122. package/src/rust/components/plugin_tab.rs +4 -28
  123. package/src/rust/components/settings_panel.rs +105 -22
  124. package/src/rust/components/viewer/msg.rs +46 -7
  125. package/src/rust/components/viewer/panels.rs +17 -20
  126. package/src/rust/components/viewer/render.rs +10 -3
  127. package/src/rust/components/viewer/settings.rs +163 -23
  128. package/src/rust/components/viewer/snapshots.rs +46 -21
  129. package/src/rust/components/viewer/wiring.rs +14 -25
  130. package/src/rust/components/viewer.rs +12 -8
  131. package/src/rust/components/window_editor.rs +75 -103
  132. package/src/rust/config/column_config_schema.rs +100 -41
  133. package/src/rust/config/css_scale.rs +882 -0
  134. package/src/rust/config/datetime_column_style/custom.rs +17 -0
  135. package/src/rust/config/datetime_column_style/simple.rs +10 -0
  136. package/src/rust/config/mod.rs +2 -0
  137. package/src/rust/config/number_string_format.rs +33 -0
  138. package/src/rust/config/options.rs +28 -0
  139. package/src/rust/config/plugin_static_config.rs +120 -5
  140. package/src/rust/config/viewer_config.rs +216 -3
  141. package/src/rust/config/workspace_config.rs +138 -10
  142. package/src/rust/custom_elements/viewer.rs +369 -119
  143. package/src/rust/custom_events.rs +43 -1
  144. package/src/rust/js/regular_layout.rs +3 -3
  145. package/src/rust/lib.rs +15 -0
  146. package/src/rust/presentation/column_locator.rs +19 -5
  147. package/src/rust/presentation.rs +200 -36
  148. package/src/rust/queries/column_locator.rs +74 -44
  149. package/src/rust/queries/get_viewer_config.rs +2 -1
  150. package/src/rust/queries/mod.rs +2 -0
  151. package/src/rust/queries/palette_set.rs +82 -0
  152. package/src/rust/queries/plugin_column_styles.rs +6 -2
  153. package/src/rust/renderer/dispatch.rs +16 -4
  154. package/src/rust/renderer/plugin_config.rs +309 -47
  155. package/src/rust/renderer.rs +15 -42
  156. package/src/rust/session/column_defaults_update.rs +32 -7
  157. package/src/rust/session/metadata.rs +23 -21
  158. package/src/rust/session/props.rs +1 -0
  159. package/src/rust/session/replace_expression_update.rs +1 -0
  160. package/src/rust/session.rs +9 -6
  161. package/src/rust/tasks/create_panel.rs +46 -49
  162. package/src/rust/tasks/edit_expression.rs +19 -23
  163. package/src/rust/tasks/edit_window.rs +9 -11
  164. package/src/rust/tasks/eject.rs +4 -1
  165. package/src/rust/tasks/pipeline.rs +2 -2
  166. package/src/rust/tasks/presize_panels.rs +44 -5
  167. package/src/rust/tasks/reset_all.rs +4 -4
  168. package/src/rust/tasks/restore_and_render.rs +17 -44
  169. package/src/rust/tasks/restore_panel.rs +37 -22
  170. package/src/rust/tasks/set_edit_mode.rs +3 -1
  171. package/src/rust/tasks/sync_update_panels.rs +18 -24
  172. package/src/rust/tasks/update_theme.rs +23 -18
  173. package/src/rust/utils/css_vars.rs +26 -0
  174. package/src/rust/utils/mod.rs +2 -0
  175. package/src/rust/utils/spawn.rs +1 -1
  176. package/src/rust/workspace.rs +454 -199
  177. package/src/svg/lock.svg +7 -0
  178. package/src/themes/botanical.css +10 -0
  179. package/src/themes/defaults.css +12 -0
  180. package/src/themes/dracula.css +17 -0
  181. package/src/themes/gruvbox-dark.css +20 -0
  182. package/src/themes/gruvbox.css +10 -0
  183. package/src/themes/icons.css +1 -0
  184. package/src/themes/intl/de.css +16 -0
  185. package/src/themes/intl/es.css +16 -0
  186. package/src/themes/intl/fr.css +16 -0
  187. package/src/themes/intl/ja.css +16 -0
  188. package/src/themes/intl/pt.css +16 -0
  189. package/src/themes/intl/zh.css +16 -0
  190. package/src/themes/intl.css +16 -0
  191. package/src/themes/monokai.css +22 -0
  192. package/src/themes/phosphor.css +5 -0
  193. package/src/themes/pro-dark.css +10 -0
  194. package/src/themes/solarized-dark.css +7 -0
  195. package/src/themes/solarized.css +7 -0
  196. package/src/themes/vaporwave.css +20 -0
  197. package/src/ts/column-format.ts +67 -46
  198. package/src/ts/perspective-viewer.ts +3 -0
  199. package/src/ts/plugin.ts +14 -0
  200. package/src/ts/providers.ts +96 -0
  201. package/src/ts/ts-rs/CompactDisplay.ts +3 -0
  202. package/src/ts/ts-rs/CurrencyCode.ts +3 -0
  203. package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
  204. package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
  205. package/src/ts/ts-rs/CurrencySign.ts +3 -0
  206. package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
  207. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
  208. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
  209. package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
  210. package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
  211. package/src/ts/ts-rs/FormatMode.ts +3 -0
  212. package/src/ts/ts-rs/FormatUnit.ts +3 -0
  213. package/src/ts/ts-rs/Notation.ts +11 -0
  214. package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
  215. package/src/ts/ts-rs/PanelViewerConfig.ts +48 -2
  216. package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
  217. package/src/ts/ts-rs/RestoreOptions.ts +20 -0
  218. package/src/ts/ts-rs/RoundingMode.ts +3 -0
  219. package/src/ts/ts-rs/RoundingPriority.ts +3 -0
  220. package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
  221. package/src/ts/ts-rs/SignDisplay.ts +3 -0
  222. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
  223. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
  224. package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
  225. package/src/ts/ts-rs/StringColorMode.ts +3 -0
  226. package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
  227. package/src/ts/ts-rs/Unit.ts +3 -0
  228. package/src/ts/ts-rs/UnitDisplay.ts +3 -0
  229. package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
  230. package/src/ts/ts-rs/UseGrouping.ts +3 -0
  231. package/src/ts/ts-rs/ViewerConfig.ts +48 -2
  232. package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
  233. package/src/ts/ts-rs/ViewerConfigUpdate.ts +72 -2
  234. package/src/ts/ts-rs/WindowSpec.ts +3 -2
  235. package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
  236. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +18 -9
  237. package/dist/esm/ts-rs/WindowAggregate.d.ts +0 -1
  238. package/src/rust/components/form/color_range_selector.rs +0 -137
  239. package/src/ts/ts-rs/WindowAggregate.ts +0 -3
  240. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
  241. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
  242. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
  243. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
  244. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
  245. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
@@ -25,7 +25,8 @@ use super::msg::PerspectiveViewerMsg::*;
25
25
  use crate::components::font_loader::FontLoaderStatus;
26
26
  use crate::components::settings_panel::SelectedTab;
27
27
  use crate::config::*;
28
- use crate::presentation::{ColumnLocator, ColumnSettingsTab};
28
+ use crate::presentation::{ColumnSettingsTab, ColumnSettingsTarget, OpenColumnSettings};
29
+ use crate::queries::get_current_column_locator;
29
30
  use crate::tasks::*;
30
31
 
31
32
  /// The settings sidebar's geometry state, folded into one field on
@@ -69,6 +70,19 @@ pub(super) struct SettingsGeometry {
69
70
  /// than FLOATING over the main panel (the default absolute overlay).
70
71
  /// Session UI state, not part of the saved config.
71
72
  pub column_settings_pinned: bool,
73
+
74
+ /// Latest-wins deferred `open_column_settings` snapshot: the newest
75
+ /// target not yet applied while a docked-drawer presize sweep is in
76
+ /// flight. `UpdateColumnSettingsCommit` applies THIS slot (not a copy
77
+ /// captured at spawn), so a newer target arriving mid-sweep wins and
78
+ /// out-of-order commits are inexpressible.
79
+ pub column_settings_target: Option<OpenColumnSettings>,
80
+ pub column_settings_commit_pending: bool,
81
+
82
+ /// The docked drawer's span, cached whenever it is measurable (pin
83
+ /// toggle, docked unmount) - used to presize for a drawer that MOUNTS
84
+ /// directly into pinned mode, when it isn't in the DOM to measure.
85
+ pub column_settings_docked_width: Option<f64>,
72
86
  }
73
87
 
74
88
  impl PerspectiveViewer {
@@ -76,6 +90,7 @@ impl PerspectiveViewer {
76
90
  &mut self,
77
91
  ctx: &Context<Self>,
78
92
  update: Option<SettingsUpdate>,
93
+ announce: bool,
79
94
  resolve: Option<Sender<ApiResult<JsValue>>>,
80
95
  ) -> bool {
81
96
  match (update, resolve) {
@@ -85,15 +100,15 @@ impl PerspectiveViewer {
85
100
  false
86
101
  },
87
102
  (Some(SettingsUpdate::SetDefault), resolve) => {
88
- self.init_toggle_settings_task(ctx, Some(false), resolve);
103
+ self.init_toggle_settings_task(ctx, Some(false), announce, resolve);
89
104
  false
90
105
  },
91
106
  (Some(SettingsUpdate::Update(force)), resolve) => {
92
- self.init_toggle_settings_task(ctx, Some(force), resolve);
107
+ self.init_toggle_settings_task(ctx, Some(force), announce, resolve);
93
108
  false
94
109
  },
95
110
  (None, resolve) => {
96
- self.init_toggle_settings_task(ctx, None, resolve);
111
+ self.init_toggle_settings_task(ctx, None, announce, resolve);
97
112
  false
98
113
  },
99
114
  }
@@ -150,10 +165,10 @@ impl PerspectiveViewer {
150
165
  &mut self,
151
166
  ctx: &Context<Self>,
152
167
  force: Option<bool>,
168
+ announce: bool,
153
169
  sender: Option<Sender<ApiResult<JsValue>>>,
154
170
  ) {
155
171
  let is_open = ctx.props().presentation.is_settings_open();
156
- ctx.props().presentation.set_settings_before_open(!is_open);
157
172
  match force {
158
173
  Some(force) if is_open == force => {
159
174
  if let Some(sender) = sender {
@@ -161,6 +176,7 @@ impl PerspectiveViewer {
161
176
  }
162
177
  },
163
178
  Some(_) | None => {
179
+ ctx.props().presentation.set_settings_before_open(!is_open);
164
180
  let force = !is_open;
165
181
  let callback = ctx.link().callback(move |resolve| {
166
182
  let update = SettingsUpdate::Update(force);
@@ -195,7 +211,7 @@ impl PerspectiveViewer {
195
211
  let presents = presize_visible_panels_grown(&workspace, &elem).await;
196
212
  let (notify, rendered) = channel::<()>();
197
213
  callback.emit(notify);
198
- presentation.set_settings_open(false);
214
+ presentation.set_settings_open(false, announce);
199
215
  rendered.await?;
200
216
  // `notify` fires in `rendered()` (same task as the
201
217
  // Yew DOM patch) and this future resumes as one of
@@ -219,7 +235,7 @@ impl PerspectiveViewer {
219
235
 
220
236
  let (notify, rendered) = channel::<()>();
221
237
  callback.emit(notify);
222
- presentation.set_settings_open(true);
238
+ presentation.set_settings_open(true, announce);
223
239
  rendered.await?;
224
240
  presents.reveal();
225
241
  resize_visible_panels(&workspace).await;
@@ -339,36 +355,34 @@ impl PerspectiveViewer {
339
355
  pub(super) fn on_open_column_settings(
340
356
  &mut self,
341
357
  ctx: &Context<Self>,
342
- locator: Option<ColumnLocator>,
358
+ target: Option<ColumnSettingsTarget>,
343
359
  sender: Option<Sender<()>>,
344
360
  toggle: bool,
345
361
  ) -> bool {
346
362
  let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
347
- if locator == open_column_settings.locator {
363
+ if target == open_column_settings.target {
348
364
  if toggle {
349
365
  ctx.props().presentation.set_open_column_settings(None);
350
366
  }
351
367
  } else {
352
- open_column_settings.locator.clone_from(&locator);
353
- open_column_settings.tab = if matches!(locator, Some(ColumnLocator::NewExpression)) {
354
- Some(ColumnSettingsTab::Attributes)
355
- } else {
356
- locator.as_ref().and_then(|x| {
357
- x.name().map(|x| {
358
- if self.session_props.is_column_active(x) {
359
- ColumnSettingsTab::Style
360
- } else {
361
- ColumnSettingsTab::Attributes
362
- }
368
+ open_column_settings.target.clone_from(&target);
369
+ open_column_settings.tab = match &target {
370
+ Some(ColumnSettingsTarget::NewExpression) => Some(ColumnSettingsTab::Attributes),
371
+ Some(ColumnSettingsTarget::Column(name)) => {
372
+ Some(if self.session_props.is_column_active(name) {
373
+ ColumnSettingsTab::Style
374
+ } else {
375
+ ColumnSettingsTab::Attributes
363
376
  })
364
- })
377
+ },
378
+ None => None,
365
379
  };
366
380
 
367
381
  ctx.props()
368
382
  .presentation
369
383
  .set_open_column_settings(Some(open_column_settings));
370
384
 
371
- if locator.is_some() {
385
+ if target.is_some() {
372
386
  self.settings_geometry.selected_tab = SelectedTab::Query;
373
387
  }
374
388
  }
@@ -402,9 +416,135 @@ impl PerspectiveViewer {
402
416
  }
403
417
  }
404
418
 
405
- pub(super) fn on_toggle_column_settings_pin(&mut self) -> bool {
419
+ pub(super) fn on_toggle_column_settings_pin(&mut self, ctx: &Context<Self>) -> bool {
420
+ let is_pinned = self.settings_geometry.column_settings_pinned;
421
+ let delta_w = measure_column_settings_pin_delta(&ctx.props().elem, is_pinned);
422
+ if let Some(delta_w) = delta_w {
423
+ self.settings_geometry.column_settings_docked_width = Some(delta_w.abs());
424
+ }
425
+
426
+ self.presize_column_settings_shift(ctx, delta_w, ToggleColumnSettingsPinComplete);
427
+ false
428
+ }
429
+
430
+ pub(super) fn on_toggle_column_settings_pin_complete(&mut self, resolve: Sender<()>) -> bool {
406
431
  self.settings_geometry.column_settings_pinned =
407
432
  !self.settings_geometry.column_settings_pinned;
433
+ self.on_rendered.push(resolve);
434
+ true
435
+ }
436
+
437
+ /// Shared presize choreography for a column-settings geometry shift:
438
+ /// pre-size every visible panel by `delta_w` (`None` = no sweep, commit
439
+ /// only), send `commit_msg` to apply the deferred state on the render
440
+ /// commit, reveal the staged frames in that same paint, then reactively
441
+ /// finalize at the exact settled cells (I6).
442
+ fn presize_column_settings_shift(
443
+ &self,
444
+ ctx: &Context<Self>,
445
+ delta_w: Option<f64>,
446
+ commit_msg: fn(Sender<()>) -> super::msg::PerspectiveViewerMsg,
447
+ ) {
448
+ let workspace = ctx.props().workspace.clone();
449
+ let elem = ctx.props().elem.clone();
450
+ let callback = ctx.link().callback(commit_msg);
451
+ ApiFuture::spawn(async move {
452
+ let presents = match delta_w {
453
+ Some(delta_w) => presize_visible_panels_open(&workspace, &elem, delta_w, 0.0).await,
454
+ None => StagedPresents::default(),
455
+ };
456
+
457
+ let (notify, rendered) = channel::<()>();
458
+ callback.emit(notify);
459
+ rendered.await?;
460
+ presents.reveal();
461
+ resize_visible_panels(&workspace).await;
462
+ Ok(())
463
+ });
464
+ }
465
+
466
+ /// Whether the drawer renders for this `open_column_settings` snapshot
467
+ /// (the `render()` mount predicate). Sensitive to the session snapshot
468
+ /// too — `snapshots.rs` re-evaluates it across an `UpdateSession` apply,
469
+ /// which can invalidate the open column's locator (e.g. a drag
470
+ /// replacing it in `columns`) and unmount the drawer without any
471
+ /// `UpdateColumnSettings` traffic.
472
+ pub(super) fn is_column_settings_mounted(&self, ocs: &OpenColumnSettings) -> bool {
473
+ get_current_column_locator(
474
+ ocs,
475
+ &self.active_renderer,
476
+ &self.session_props.config,
477
+ &self.session_props.metadata,
478
+ )
479
+ .is_some()
480
+ }
481
+
482
+ /// The `open_column_settings` snapshot handler: mount/unmount of the
483
+ /// DOCKED drawer moves `#main_panel_container`'s flex box just like the
484
+ /// pin toggle — and no `before-resize` fires for it — so those
485
+ /// transitions defer the snapshot behind the presize choreography.
486
+ /// Floating transitions and in-place locator changes apply synchronously
487
+ /// as before, unless a deferred commit is in flight (they queue into the
488
+ /// latest-wins slot to preserve commit order).
489
+ pub(super) fn on_update_column_settings(
490
+ &mut self,
491
+ ctx: &Context<Self>,
492
+ ocs: OpenColumnSettings,
493
+ ) -> bool {
494
+ self.refresh_session_snapshot(ctx);
495
+ let effective = self
496
+ .settings_geometry
497
+ .column_settings_target
498
+ .as_ref()
499
+ .unwrap_or(&self.presentation_props.open_column_settings);
500
+ if ocs == *effective {
501
+ return false;
502
+ }
503
+
504
+ let was_mounted =
505
+ self.is_column_settings_mounted(&self.presentation_props.open_column_settings);
506
+ let will_mount = self.is_column_settings_mounted(&ocs);
507
+ let needs_presize =
508
+ self.settings_geometry.column_settings_pinned && was_mounted != will_mount;
509
+ if !needs_presize && !self.settings_geometry.column_settings_commit_pending {
510
+ self.presentation_props.open_column_settings = ocs;
511
+ return true;
512
+ }
513
+
514
+ self.settings_geometry.column_settings_target = Some(ocs);
515
+ if self.settings_geometry.column_settings_commit_pending {
516
+ return false;
517
+ }
518
+
519
+ self.settings_geometry.column_settings_commit_pending = true;
520
+ let delta_w = if !needs_presize {
521
+ None
522
+ } else if was_mounted {
523
+ // Unmounting - the docked drawer is still in the DOM, and its
524
+ // freed span is the same measurement as an unpin.
525
+ let delta = measure_column_settings_pin_delta(&ctx.props().elem, true);
526
+ if let Some(delta) = delta {
527
+ self.settings_geometry.column_settings_docked_width = Some(delta.abs());
528
+ }
529
+
530
+ delta
531
+ } else {
532
+ // Mounting directly into pinned mode - not yet in the DOM; the
533
+ // span cached at the last dock/unmount predicts the shrink.
534
+ self.settings_geometry.column_settings_docked_width
535
+ };
536
+
537
+ self.presize_column_settings_shift(ctx, delta_w, UpdateColumnSettingsCommit);
538
+ false
539
+ }
540
+
541
+ pub(super) fn on_update_column_settings_commit(&mut self, resolve: Sender<()>) -> bool {
542
+ self.settings_geometry.column_settings_commit_pending = false;
543
+ self.on_rendered.push(resolve);
544
+ if let Some(ocs) = self.settings_geometry.column_settings_target.take() {
545
+ self.presentation_props.open_column_settings = ocs;
546
+ }
547
+
408
548
  true
409
549
  }
410
550
 
@@ -15,17 +15,45 @@
15
15
  //! subscriptions/callbacks) into the root's props fields, re-rendering only
16
16
  //! on an actual change.
17
17
 
18
+ use futures::channel::oneshot::channel;
19
+ use perspective_js::utils::ApiFuture;
18
20
  use yew::prelude::*;
19
21
 
20
22
  use super::PerspectiveViewer;
21
- use crate::presentation::{DragDropProps, OpenColumnSettings, PresentationProps};
23
+ use crate::presentation::{DragDropProps, PresentationProps};
22
24
  use crate::renderer::RendererProps;
23
25
  use crate::session::{SessionProps, TableLoadState, ViewStats};
26
+ use crate::tasks::resize_visible_panels;
24
27
 
25
28
  impl PerspectiveViewer {
26
- pub(super) fn on_update_session(&mut self, props: SessionProps) -> bool {
29
+ pub(super) fn refresh_session_snapshot(&mut self, ctx: &Context<Self>) {
30
+ let props = self.active_session.to_props();
31
+ if props != self.session_props {
32
+ self.on_update_session(ctx, props);
33
+ }
34
+ }
35
+
36
+ pub(super) fn on_update_session(&mut self, ctx: &Context<Self>, props: SessionProps) -> bool {
27
37
  let changed = props != self.session_props;
38
+ let was_mounted =
39
+ self.is_column_settings_mounted(&self.presentation_props.open_column_settings);
28
40
  self.session_props = props;
41
+ let now_mounted =
42
+ self.is_column_settings_mounted(&self.presentation_props.open_column_settings);
43
+ if self.settings_geometry.column_settings_pinned
44
+ && was_mounted != now_mounted
45
+ && !self.settings_geometry.column_settings_commit_pending
46
+ {
47
+ let (notify, rendered) = channel::<()>();
48
+ self.on_rendered.push(notify);
49
+ let workspace = ctx.props().workspace.clone();
50
+ ApiFuture::spawn(async move {
51
+ rendered.await?;
52
+ resize_visible_panels(&workspace).await;
53
+ Ok(())
54
+ });
55
+ }
56
+
29
57
  changed
30
58
  }
31
59
 
@@ -52,18 +80,17 @@ impl PerspectiveViewer {
52
80
  ctx: &Context<Self>,
53
81
  props: PresentationProps,
54
82
  ) -> bool {
55
- // Default-theme fan-out: when the registry default (first available
56
- // theme) changes — the async theme discovery resolving at boot, or a
57
- // `resetThemes` — push the new default into every panel's renderer
58
- // cache (locked draws stamp the effective theme from it), and
59
- // restyle the panels whose captured `--psp-*` CSS is STALE against
60
- // the new effective value (`Renderer::needs_restyle` — the plugin's
61
- // captured theme is the baseline; plugins only re-read CSS at
62
- // `restyle()`/first-draw, so a plain redraw would not repaint a
63
- // panel that first drew before discovery resolved, while a panel
64
- // that captured post-discovery — or owes its first paint — restyles
65
- // nothing). The outer default-diff scopes the scan; the per-panel
66
- // gate is state, never call history or DOM state.
83
+ // Boot fill-in: theme discovery resolving turns an empty registry
84
+ // into a real one, and any panel created before that has no theme to
85
+ // stamp. Give those — and ONLY those — the new default, then restyle
86
+ // the ones whose captured `--psp-*` CSS is now stale
87
+ // (`Renderer::needs_restyle`; plugins re-read CSS only at
88
+ // `restyle()`/first-draw, so a plain redraw would not repaint them).
89
+ //
90
+ // A panel that already has a theme is never touched here: `theme` is
91
+ // concrete state, so re-ordering the registry — or any other
92
+ // `resetThemes` that leaves a panel's theme available — must repaint
93
+ // nothing.
67
94
  let old_default = self.presentation_props.available_themes.first().cloned();
68
95
  let new_default = props.available_themes.first().cloned();
69
96
  if old_default != new_default {
@@ -74,7 +101,11 @@ impl PerspectiveViewer {
74
101
  .into_iter()
75
102
  .filter_map(|id| ctx.props().workspace.panel(&id))
76
103
  {
77
- panel.renderer.set_default_theme(new_default.clone());
104
+ if panel.renderer.theme().is_some() {
105
+ continue;
106
+ }
107
+
108
+ panel.renderer.set_theme(new_default.clone());
78
109
  if panel.renderer.needs_restyle() {
79
110
  let renderer = panel.renderer.clone();
80
111
  crate::utils::spawn_owned("default-theme-restyle", async move {
@@ -102,12 +133,6 @@ impl PerspectiveViewer {
102
133
  changed
103
134
  }
104
135
 
105
- pub(super) fn on_update_column_settings(&mut self, ocs: OpenColumnSettings) -> bool {
106
- let changed = ocs != self.presentation_props.open_column_settings;
107
- self.presentation_props.open_column_settings = ocs;
108
- changed
109
- }
110
-
111
136
  pub(super) fn on_update_dragdrop(&mut self, props: DragDropProps) -> bool {
112
137
  let changed = props != self.dragdrop_props;
113
138
  self.dragdrop_props = props;
@@ -343,7 +343,9 @@ pub(super) fn create_active_subscriptions(
343
343
  pub(super) fn create_shared_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
344
344
  let presentation_props_sub = {
345
345
  let presentation = ctx.props().presentation.clone();
346
- let cb_settings = ctx.link().callback(UpdateSettingsOpen);
346
+ let cb_settings = ctx
347
+ .link()
348
+ .callback(|(open, _): (bool, bool)| UpdateSettingsOpen(open));
347
349
  let cb_theme = {
348
350
  let pres = presentation.clone();
349
351
  ctx.link()
@@ -368,17 +370,6 @@ pub(super) fn create_shared_subscriptions(ctx: &Context<PerspectiveViewer>) -> V
368
370
  vec![sub1, sub2, sub3]
369
371
  };
370
372
 
371
- let dragdrop_props_sub = {
372
- let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
373
- let sub1 = ctx
374
- .props()
375
- .presentation
376
- .drop_received
377
- .add_notify_listener(&cb_clear);
378
-
379
- vec![sub1]
380
- };
381
-
382
373
  // The `Workspace`-owned global filter set → the root's render snapshot.
383
374
  let filters_sub = {
384
375
  let cb = ctx.link().callback(|_: ()| UpdateGlobalFilters);
@@ -388,11 +379,12 @@ pub(super) fn create_shared_subscriptions(ctx: &Context<PerspectiveViewer>) -> V
388
379
  .add_notify_listener(&cb)
389
380
  };
390
381
 
391
- // Staged-set transitions (`Workspace::stage_panel`/`clear_staged`) → a
392
- // root re-render for the stage-level chrome (`only-child` class,
393
- // binding resync). `MainPanel` inserts/reveals promoted panels via its
394
- // OWN `staged_changed` subscription (`MainPanelMsg::StagedChanged`) —
395
- // see `WorkspaceData::staged_changed`.
382
+ // Panel-phase transitions (`Workspace::insert_panel` at
383
+ // `PanelPhase::Staging` / `Workspace::promote`) → a root re-render for
384
+ // the stage-level chrome (`only-child` class, binding resync).
385
+ // `MainPanel` inserts/reveals promoted panels via its OWN
386
+ // `staged_changed` subscription (`MainPanelMsg::StagedChanged`) — see
387
+ // `WorkspaceData::staged_changed`.
396
388
  let staged_sub = {
397
389
  let cb = ctx.link().callback(|_: ()| LayoutChanged);
398
390
  ctx.props()
@@ -403,7 +395,6 @@ pub(super) fn create_shared_subscriptions(ctx: &Context<PerspectiveViewer>) -> V
403
395
 
404
396
  let mut subscriptions = Vec::new();
405
397
  subscriptions.extend(presentation_props_sub);
406
- subscriptions.extend(dragdrop_props_sub);
407
398
  subscriptions.push(filters_sub);
408
399
  subscriptions.push(staged_sub);
409
400
  subscriptions
@@ -446,15 +437,13 @@ pub(super) fn inject_active_callbacks(
446
437
  let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
447
438
  let mut msgs = vec![UpdateRenderer(Box::new(r.to_props(Some(limits))))];
448
439
  if !limits.is_update {
449
- let locator = get_current_column_locator(
450
- &presentation.get_open_column_settings(),
451
- &r,
452
- &s.get_view_config(),
453
- &s.metadata(),
454
- );
440
+ let ocs = presentation.get_open_column_settings();
441
+ let still_open =
442
+ get_current_column_locator(&ocs, &r, &s.get_view_config(), &s.metadata())
443
+ .is_some();
455
444
 
456
445
  msgs.push(OpenColumnSettings {
457
- locator,
446
+ target: ocs.target.filter(|_| still_open),
458
447
  sender: None,
459
448
  toggle: false,
460
449
  });
@@ -166,7 +166,7 @@ impl Component for PerspectiveViewer {
166
166
  let renderer_props = active_renderer.to_props(None);
167
167
  let presentation_props = ctx.props().presentation.to_props(PtrEqRc::new(vec![]));
168
168
  let on_close_column_settings = ctx.link().callback(|_| OpenColumnSettings {
169
- locator: None,
169
+ target: None,
170
170
  sender: None,
171
171
  toggle: false,
172
172
  });
@@ -319,8 +319,8 @@ impl Component for PerspectiveViewer {
319
319
  ClearGlobalFilters => self.on_clear_global_filters(ctx),
320
320
 
321
321
  // Settings sidebar + divider pump + column settings (`settings.rs`)
322
- ToggleSettingsInit(update, resolve) => {
323
- self.on_toggle_settings_init(ctx, update, resolve)
322
+ ToggleSettingsInit(update, announce, resolve) => {
323
+ self.on_toggle_settings_init(ctx, update, announce, resolve)
324
324
  },
325
325
  ToggleSettingsComplete(update, resolve) => {
326
326
  self.on_toggle_settings_complete(ctx, update, resolve)
@@ -333,25 +333,29 @@ impl Component for PerspectiveViewer {
333
333
  SettingsPanelTabChanged(tab) => self.on_settings_panel_tab_changed(tab),
334
334
  SettingsPanelAutoWidth(w) => self.on_settings_panel_auto_width(w),
335
335
  OpenColumnSettings {
336
- locator,
336
+ target,
337
337
  sender,
338
338
  toggle,
339
- } => self.on_open_column_settings(ctx, locator, sender, toggle),
339
+ } => self.on_open_column_settings(ctx, target, sender, toggle),
340
340
  ColumnSettingsPanelSizeUpdate(x) => self.on_column_settings_panel_size_update(x),
341
341
  ColumnSettingsPanelAutoWidth(w) => self.on_column_settings_panel_auto_width(w),
342
- ToggleColumnSettingsPin => self.on_toggle_column_settings_pin(),
342
+ ToggleColumnSettingsPin => self.on_toggle_column_settings_pin(ctx),
343
+ ToggleColumnSettingsPinComplete(resolve) => {
344
+ self.on_toggle_column_settings_pin_complete(resolve)
345
+ },
343
346
  ColumnSettingsTabChanged(tab) => self.on_column_settings_tab_changed(ctx, tab),
344
347
  ToggleDebug => self.on_toggle_debug(ctx),
345
348
 
346
349
  // Value-semantic snapshot plumbing (`snapshots.rs`)
347
- UpdateSession(props) => self.on_update_session(*props),
350
+ UpdateSession(props) => self.on_update_session(ctx, *props),
348
351
  UpdateSessionStats(stats, has_table) => self.on_update_session_stats(stats, has_table),
349
352
  UpdateGlobalFilters => self.on_update_global_filters(ctx),
350
353
  UpdateRenderer(props) => self.on_update_renderer(*props),
351
354
  UpdatePresentation(props) => self.on_update_presentation(ctx, *props),
352
355
  UpdateSettingsOpen(open) => self.on_update_settings_open(open),
353
356
  UpdateIsWorkspace(is_workspace) => self.on_update_is_workspace(is_workspace),
354
- UpdateColumnSettings(ocs) => self.on_update_column_settings(*ocs),
357
+ UpdateColumnSettings(ocs) => self.on_update_column_settings(ctx, *ocs),
358
+ UpdateColumnSettingsCommit(resolve) => self.on_update_column_settings_commit(resolve),
355
359
  UpdateDragDrop(props) => self.on_update_dragdrop(*props),
356
360
  UpdateInFlight(count) => self.on_update_in_flight(count),
357
361
  }