@perspective-dev/viewer 5.0.0 → 5.2.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 (213) 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 +2 -0
  26. package/dist/esm/perspective-viewer.inline.js +8 -8
  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/providers.d.ts +79 -0
  31. package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
  32. package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
  33. package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
  34. package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
  35. package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
  36. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
  37. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
  38. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
  39. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
  40. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
  41. package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
  42. package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
  43. package/dist/esm/ts-rs/Notation.d.ts +17 -0
  44. package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
  45. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +43 -1
  46. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
  47. package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
  48. package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
  49. package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
  50. package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
  51. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
  52. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
  53. package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
  54. package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
  55. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
  56. package/dist/esm/ts-rs/Unit.d.ts +1 -0
  57. package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
  58. package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
  59. package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
  60. package/dist/esm/ts-rs/ViewerConfig.d.ts +43 -1
  61. package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
  62. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +72 -0
  63. package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
  64. package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
  65. package/dist/esm/ts-rs/WindowSpec.d.ts +47 -0
  66. package/dist/esm/ts-rs/Windows.d.ts +10 -0
  67. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
  68. package/dist/wasm/perspective-viewer.d.ts +222 -26
  69. package/dist/wasm/perspective-viewer.js +378 -125
  70. package/dist/wasm/perspective-viewer.wasm +0 -0
  71. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
  72. package/package.json +2 -1
  73. package/src/css/_viewer_bundle.css +1 -0
  74. package/src/css/column-selector.css +9 -0
  75. package/src/css/column-settings-panel.css +67 -2
  76. package/src/css/column-style.css +4 -3
  77. package/src/css/config-selector.css +60 -4
  78. package/src/css/containers/context-menu.css +12 -6
  79. package/src/css/containers/tabs.css +2 -2
  80. package/src/css/form/code-editor.css +2 -3
  81. package/src/css/form/mirrored-textarea.css +81 -0
  82. package/src/css/viewer.css +336 -0
  83. package/src/rust/agent/client/mod.rs +19 -0
  84. package/src/rust/agent/client/protocol.rs +320 -0
  85. package/src/rust/agent/client/transport.rs +287 -0
  86. package/src/rust/agent/client/turn.rs +174 -0
  87. package/src/rust/agent/config.rs +159 -0
  88. package/src/rust/agent/docs.rs +293 -0
  89. package/src/rust/agent/markdown/block.rs +509 -0
  90. package/src/rust/agent/markdown/entity.rs +52 -0
  91. package/src/rust/agent/markdown/events.rs +71 -0
  92. package/src/rust/agent/markdown/inline.rs +497 -0
  93. package/src/rust/agent/markdown/mod.rs +466 -0
  94. package/src/rust/agent/mod.rs +23 -0
  95. package/src/rust/agent/preamble.md +16 -0
  96. package/src/rust/agent/runtime.rs +201 -0
  97. package/src/rust/agent/slot.rs +236 -0
  98. package/src/rust/agent/tools.rs +1110 -0
  99. package/src/rust/components/chat_panel.rs +289 -0
  100. package/src/rust/components/column_selector/active_column.rs +21 -27
  101. package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
  102. package/src/rust/components/column_selector/config_selector.rs +83 -8
  103. package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
  104. package/src/rust/components/column_selector/inactive_column.rs +8 -1
  105. package/src/rust/components/column_selector.rs +27 -3
  106. package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
  107. package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
  108. package/src/rust/components/column_settings_sidebar.rs +163 -16
  109. package/src/rust/components/containers/dragdrop_list.rs +110 -3
  110. package/src/rust/components/containers/sidebar.rs +99 -22
  111. package/src/rust/components/containers/split_panel.rs +6 -0
  112. package/src/rust/components/editable_header.rs +2 -0
  113. package/src/rust/components/form/code_editor.rs +14 -23
  114. package/src/rust/components/form/debug.rs +8 -5
  115. package/src/rust/components/form/mirrored_textarea.rs +165 -0
  116. package/src/rust/components/form/mod.rs +1 -0
  117. package/src/rust/components/main_panel.rs +5 -0
  118. package/src/rust/components/mod.rs +3 -0
  119. package/src/rust/components/panel_menu.rs +7 -0
  120. package/src/rust/components/panel_tab.rs +5 -0
  121. package/src/rust/components/portal.rs +7 -0
  122. package/src/rust/components/settings_panel.rs +80 -2
  123. package/src/rust/components/viewer/msg.rs +2 -0
  124. package/src/rust/components/viewer/panels.rs +9 -14
  125. package/src/rust/components/viewer/render.rs +14 -0
  126. package/src/rust/components/viewer/settings.rs +33 -1
  127. package/src/rust/components/viewer.rs +2 -0
  128. package/src/rust/components/window_editor.rs +985 -0
  129. package/src/rust/config/datetime_column_style/custom.rs +17 -0
  130. package/src/rust/config/datetime_column_style/simple.rs +10 -0
  131. package/src/rust/config/number_string_format.rs +33 -0
  132. package/src/rust/config/options.rs +18 -0
  133. package/src/rust/config/plugin_static_config.rs +120 -5
  134. package/src/rust/config/viewer_config.rs +220 -1
  135. package/src/rust/config/workspace_config.rs +7 -6
  136. package/src/rust/custom_elements/viewer.rs +209 -18
  137. package/src/rust/custom_events.rs +29 -0
  138. package/src/rust/lib.rs +17 -1
  139. package/src/rust/presentation/column_locator.rs +16 -1
  140. package/src/rust/presentation.rs +8 -0
  141. package/src/rust/queries/columns_iter_set.rs +7 -1
  142. package/src/rust/queries/get_viewer_config.rs +5 -1
  143. package/src/rust/renderer/dispatch.rs +85 -58
  144. package/src/rust/renderer.rs +34 -1
  145. package/src/rust/session/column_defaults_update.rs +18 -0
  146. package/src/rust/session/drag_drop_update.rs +18 -0
  147. package/src/rust/session/metadata.rs +64 -9
  148. package/src/rust/session/replace_expression_update.rs +2 -0
  149. package/src/rust/session.rs +7 -5
  150. package/src/rust/tasks/create_panel.rs +18 -8
  151. package/src/rust/tasks/edit_window.rs +155 -0
  152. package/src/rust/tasks/mod.rs +2 -0
  153. package/src/rust/tasks/restore_panel.rs +34 -17
  154. package/src/rust/tasks/sync_update_panels.rs +7 -23
  155. package/src/rust/utils/browser/dragdrop.rs +12 -0
  156. package/src/rust/utils/browser/tests/debounce.rs +49 -0
  157. package/src/rust/utils/debounce.rs +76 -27
  158. package/src/rust/workspace.rs +112 -20
  159. package/src/svg/pin-icon.svg +5 -0
  160. package/src/themes/icons.css +1 -0
  161. package/src/themes/intl/de.css +23 -0
  162. package/src/themes/intl/es.css +23 -0
  163. package/src/themes/intl/fr.css +23 -0
  164. package/src/themes/intl/ja.css +23 -0
  165. package/src/themes/intl/pt.css +23 -0
  166. package/src/themes/intl/zh.css +23 -0
  167. package/src/themes/intl.css +26 -1
  168. package/src/ts/column-format.ts +67 -46
  169. package/src/ts/perspective-viewer.ts +2 -0
  170. package/src/ts/providers.ts +96 -0
  171. package/src/ts/ts-rs/CompactDisplay.ts +3 -0
  172. package/src/ts/ts-rs/CurrencyCode.ts +3 -0
  173. package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
  174. package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
  175. package/src/ts/ts-rs/CurrencySign.ts +3 -0
  176. package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
  177. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
  178. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
  179. package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
  180. package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
  181. package/src/ts/ts-rs/FormatMode.ts +3 -0
  182. package/src/ts/ts-rs/FormatUnit.ts +3 -0
  183. package/src/ts/ts-rs/Notation.ts +11 -0
  184. package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
  185. package/src/ts/ts-rs/PanelViewerConfig.ts +48 -1
  186. package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
  187. package/src/ts/ts-rs/RestoreOptions.ts +20 -0
  188. package/src/ts/ts-rs/RoundingMode.ts +3 -0
  189. package/src/ts/ts-rs/RoundingPriority.ts +3 -0
  190. package/src/ts/ts-rs/SignDisplay.ts +3 -0
  191. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
  192. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
  193. package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
  194. package/src/ts/ts-rs/StringColorMode.ts +3 -0
  195. package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
  196. package/src/ts/ts-rs/Unit.ts +3 -0
  197. package/src/ts/ts-rs/UnitDisplay.ts +3 -0
  198. package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
  199. package/src/ts/ts-rs/UseGrouping.ts +3 -0
  200. package/src/ts/ts-rs/ViewerConfig.ts +48 -1
  201. package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
  202. package/src/ts/ts-rs/ViewerConfigUpdate.ts +81 -2
  203. package/src/ts/ts-rs/WindowSort.ts +9 -0
  204. package/src/ts/ts-rs/WindowSortDir.ts +8 -0
  205. package/src/ts/ts-rs/WindowSpec.ts +47 -0
  206. package/src/ts/ts-rs/Windows.ts +10 -0
  207. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
  208. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
  209. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
  210. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
  211. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
  212. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
  213. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
@@ -299,6 +299,7 @@ impl PanelMenu {
299
299
 
300
300
  html! {
301
301
  <PortalModal
302
+ key="perspective-context-menu"
302
303
  tag_name="perspective-context-menu"
303
304
  surface={StyleSurface::ContextMenu}
304
305
  target={Some(self.anchor.clone())}
@@ -453,7 +454,13 @@ impl PanelMenu {
453
454
  };
454
455
 
455
456
  html! {
457
+ // Keyed by host tag: the menu→picker swap happens at the same
458
+ // vdom position, and `PortalModal`'s host element + adopted
459
+ // surface sheet are create-time-only — unkeyed reuse would leave
460
+ // the picker inside the `<perspective-context-menu>` host with
461
+ // the context-menu sheet.
456
462
  <PortalModal
463
+ key={tag_name}
457
464
  {tag_name}
458
465
  surface={StyleSurface::DropdownMenu}
459
466
  {target}
@@ -316,6 +316,11 @@ impl Component for PanelTab {
316
316
 
317
317
  let link = ctx.link().clone();
318
318
  let contextmenu = Closure::wrap(Box::new(move |event: MouseEvent| {
319
+ // Shift+right-click passes through to the native browser menu.
320
+ if event.shift_key() {
321
+ return;
322
+ }
323
+
319
324
  event.prevent_default();
320
325
  event.stop_propagation();
321
326
  link.send_message(PanelTabMsg::ContextMenu(
@@ -170,6 +170,13 @@ impl Component for PortalModal {
170
170
  }
171
171
 
172
172
  fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
173
+ // The host element (`tag_name`) and its adopted sheet (`surface`) are
174
+ // fixed at `create` — a swap at the same vdom position must be keyed
175
+ // so Yew recreates the component (see `PanelMenu`'s menu→picker
176
+ // stages).
177
+ debug_assert_eq!(ctx.props().tag_name, old_props.tag_name);
178
+ debug_assert_eq!(ctx.props().surface, old_props.surface);
179
+
173
180
  let new_target = &ctx.props().target;
174
181
  let old_target = &old_props.target;
175
182
 
@@ -21,13 +21,14 @@ use super::plugin_selector::PluginSelector;
21
21
  use super::plugin_tab::PluginTab;
22
22
  use crate::components::containers::sidebar_close_button::SidebarCloseButton;
23
23
  use crate::components::form::debug::DebugPanel;
24
- use crate::config::PluginUpdate;
24
+ use crate::config::{PluginStaticConfig, PluginUpdate};
25
25
  use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
26
26
  use crate::renderer::*;
27
27
  use crate::session::column_defaults_update::*;
28
28
  use crate::session::*;
29
29
  use crate::tasks::update_plugin_and_render;
30
30
  use crate::utils::*;
31
+ use crate::workspace::Workspace;
31
32
 
32
33
  #[derive(Clone, Properties)]
33
34
  pub struct SettingsPanelProps {
@@ -42,6 +43,15 @@ pub struct SettingsPanelProps {
42
43
  pub available_plugins: PtrEqRc<Vec<String>>,
43
44
  pub has_table: Option<TableLoadState>,
44
45
  pub named_column_count: usize,
46
+
47
+ /// The ACTIVE plugin's declared contract, threaded as a value prop so
48
+ /// that switching plugins re-renders the panes that read it. The
49
+ /// renderer handle cannot serve this: it is excluded from prop
50
+ /// equality (it is a handle, not a value), so a plugin swap that
51
+ /// leaves the view config untouched — Y Line back to Datagrid, both
52
+ /// of which name one column slot — would otherwise change nothing any
53
+ /// component compares.
54
+ pub plugin_static_config: Rc<PluginStaticConfig>,
45
55
  pub view_config: PtrEqRc<ViewConfig>,
46
56
 
47
57
  /// Snapshot of the active plugin's `plugin_config` bucket, threaded
@@ -90,6 +100,7 @@ pub struct SettingsPanelProps {
90
100
  pub session: Session,
91
101
  pub renderer: Renderer,
92
102
  pub presentation: Presentation,
103
+ pub workspace: Workspace,
93
104
  }
94
105
 
95
106
  impl PartialEq for SettingsPanelProps {
@@ -99,6 +110,7 @@ impl PartialEq for SettingsPanelProps {
99
110
  && self.available_plugins == rhs.available_plugins
100
111
  && self.has_table == rhs.has_table
101
112
  && self.named_column_count == rhs.named_column_count
113
+ && self.plugin_static_config == rhs.plugin_static_config
102
114
  && self.view_config == rhs.view_config
103
115
  && self.plugin_config == rhs.plugin_config
104
116
  && self.drag_column == rhs.drag_column
@@ -116,6 +128,11 @@ pub enum SelectedTab {
116
128
  Query,
117
129
  Plugin,
118
130
  Debug,
131
+
132
+ /// The embedded LLM agent's chat panel. The variant exists in every
133
+ /// build; its tab button and body render only under the `llm-agent`
134
+ /// feature, and only once `agentConfig()` has been called.
135
+ Chat,
119
136
  }
120
137
 
121
138
  #[function_component]
@@ -186,8 +203,15 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
186
203
  .unwrap();
187
204
 
188
205
  let group_rollups = plugin_config.get_group_rollups(&rollup_features);
206
+ let split_rollup_features = session_metadata
207
+ .get_features()
208
+ .map(|x| x.get_split_rollup_modes())
209
+ .unwrap();
210
+
211
+ let split_rollups = plugin_config.get_split_rollups(&split_rollup_features);
189
212
  let mut update = ViewConfigUpdate {
190
213
  group_rollup_mode: group_rollups.first().cloned(),
214
+ split_rollup_mode: split_rollups.first().cloned(),
191
215
  ..ViewConfigUpdate::default()
192
216
  };
193
217
 
@@ -237,6 +261,52 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
237
261
  }
238
262
  };
239
263
 
264
+ // The chat tab is zero-affordance until `agentConfig()` is called; the
265
+ // subscription re-renders this panel when that happens (and as the
266
+ // transcript updates while the chat body is mounted).
267
+ #[cfg(feature = "llm-agent")]
268
+ let (chat_tab_button, chat_body) = {
269
+ let update = use_force_update();
270
+ let agent = presentation.agent.clone();
271
+ use_effect_with((), move |_| {
272
+ let sub = agent
273
+ .on_update
274
+ .add_notify_listener(&Callback::from(move |_| update.force_update()));
275
+
276
+ move || drop(sub)
277
+ });
278
+
279
+ let button = if presentation.agent.is_configured() {
280
+ let on_select_column = props.on_select_column.clone();
281
+ let set_chat = {
282
+ let on_select_tab = props.on_select_tab.clone();
283
+ Callback::from(move |_: PointerEvent| {
284
+ on_select_tab.emit(SelectedTab::Chat);
285
+ on_select_column.emit(None)
286
+ })
287
+ };
288
+
289
+ html! {
290
+ <div
291
+ id="chat_tabbar_tab"
292
+ class={tab_class(selected, SelectedTab::Chat)}
293
+ onpointerdown={set_chat}
294
+ />
295
+ }
296
+ } else {
297
+ html! {}
298
+ };
299
+
300
+ let body = html! {
301
+ <crate::components::chat_panel::ChatPanel agent={presentation.agent.clone()} />
302
+ };
303
+
304
+ (button, body)
305
+ };
306
+
307
+ #[cfg(not(feature = "llm-agent"))]
308
+ let (chat_tab_button, chat_body) = (html! {}, html! {});
309
+
240
310
  let on_open_expr_panel = use_callback(props.on_select_column.clone(), |c, on_select| {
241
311
  on_select.emit(Some(c))
242
312
  });
@@ -270,6 +340,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
270
340
  class={tab_class(selected, SelectedTab::Debug)}
271
341
  onpointerdown={set_debug}
272
342
  />
343
+ { chat_tab_button }
273
344
  </div>
274
345
  if selected == SelectedTab::Query {
275
346
  <ColumnSelector
@@ -278,6 +349,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
278
349
  {selected_column}
279
350
  has_table={props.has_table.clone()}
280
351
  named_column_count={props.named_column_count}
352
+ plugin_static_config={props.plugin_static_config.clone()}
281
353
  view_config={props.view_config.clone()}
282
354
  drag_column={props.drag_column.clone()}
283
355
  metadata={props.metadata.clone()}
@@ -298,11 +370,14 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
298
370
  // initial_width={width}
299
371
  // on_auto_width={on_auto_width.clone()}
300
372
  />
373
+ } else if selected == SelectedTab::Chat {
374
+ { chat_body }
301
375
  } else {
302
376
  <DebugPanel
303
377
  {presentation}
304
378
  {renderer}
305
379
  {session}
380
+ workspace={props.workspace.clone()}
306
381
  initial_width={width}
307
382
  on_auto_width={on_auto_width.clone()}
308
383
  />
@@ -310,7 +385,10 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
310
385
  // Sibling sizer keeps the panel width pinned across tab
311
386
  // switches; lives outside the tab-body so it survives the
312
387
  // tab subtree's unmount.
313
- <div class="scroll-panel-auto-width" style={format!("width:{}px", width)} />
388
+ <div
389
+ class="scroll-panel-auto-width"
390
+ style={format!("width:{}px", width)}
391
+ />
314
392
  </div>
315
393
  }
316
394
  }
@@ -46,6 +46,8 @@ pub struct MasterSelection {
46
46
  #[derive(Debug)]
47
47
  pub enum PerspectiveViewerMsg {
48
48
  ColumnSettingsPanelSizeUpdate(Option<i32>),
49
+ ColumnSettingsPanelAutoWidth(f64),
50
+ ToggleColumnSettingsPin,
49
51
  ColumnSettingsTabChanged(ColumnSettingsTab),
50
52
  OpenColumnSettings {
51
53
  locator: Option<ColumnLocator>,
@@ -27,7 +27,7 @@ use super::wiring::{
27
27
  clear_active_callbacks, create_active_subscriptions, inject_active_callbacks,
28
28
  subscribe_panel_titles,
29
29
  };
30
- use crate::config::{TableUpdate, ViewerConfigUpdate};
30
+ use crate::config::{ViewerConfigInitial, ViewerConfigUpdate};
31
31
  use crate::queries::*;
32
32
  use crate::renderer::Renderer;
33
33
  use crate::session::*;
@@ -216,18 +216,18 @@ impl PerspectiveViewer {
216
216
 
217
217
  pub(super) fn on_new_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
218
218
  if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
219
- let table_name = panel.session.get_table().map(|t| t.get_name().to_owned());
219
+ let Some(table_name) = panel.session.get_table().map(|t| t.get_name().to_owned())
220
+ else {
221
+ tracing::warn!("Source panel has no `Table` to create a new panel from");
222
+ return false;
223
+ };
224
+
220
225
  let elem = ctx.props().elem.clone();
221
226
  let presentation = ctx.props().presentation.clone();
222
227
  let workspace = ctx.props().workspace.clone();
223
228
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
224
229
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
225
230
  ApiFuture::spawn(async move {
226
- let update = ViewerConfigUpdate {
227
- table: table_name.map(TableUpdate::Update).unwrap_or_default(),
228
- ..Default::default()
229
- };
230
-
231
231
  let client = panel.session.get_client();
232
232
  let new_id = create_panel(
233
233
  &elem,
@@ -235,7 +235,7 @@ impl PerspectiveViewer {
235
235
  &workspace,
236
236
  &notify,
237
237
  None,
238
- update,
238
+ ViewerConfigInitial::new(table_name).into(),
239
239
  client,
240
240
  )
241
241
  .await?;
@@ -273,18 +273,13 @@ impl PerspectiveViewer {
273
273
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
274
274
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
275
275
  ApiFuture::spawn(async move {
276
- let update = ViewerConfigUpdate {
277
- table: TableUpdate::Update(table),
278
- ..Default::default()
279
- };
280
-
281
276
  let new_id = create_panel(
282
277
  &elem,
283
278
  &presentation,
284
279
  &workspace,
285
280
  &notify,
286
281
  None,
287
- update,
282
+ ViewerConfigInitial::new(table).into(),
288
283
  Some(client),
289
284
  )
290
285
  .await?;
@@ -94,6 +94,7 @@ impl PerspectiveViewer {
94
94
  {available_plugins}
95
95
  {has_table}
96
96
  {named_column_count}
97
+ plugin_static_config={self.renderer_props.config.clone()}
97
98
  {view_config}
98
99
  plugin_config={self.renderer_props.plugin_config.clone()}
99
100
  {drag_column}
@@ -108,6 +109,7 @@ impl PerspectiveViewer {
108
109
  {presentation}
109
110
  {renderer}
110
111
  {session}
112
+ workspace={ctx.props().workspace.clone()}
111
113
  />
112
114
  }
113
115
  } else {
@@ -116,10 +118,18 @@ impl PerspectiveViewer {
116
118
 
117
119
  let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
118
120
  let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
121
+ // Pinning the drawer is a pure CSS positioning flip on `#modal_panel`
122
+ // (absolute overlay over the main panel <-> static flex sibling
123
+ // between the main panel and the settings pane) - the drawer NEVER
124
+ // reparents, so neither `MainPanel` nor the drawer subtree remounts
125
+ // on toggle, and the divider/width-override machinery is identical
126
+ // in both modes.
127
+ let is_pinned = self.settings_geometry.column_settings_pinned;
119
128
  let column_settings_panel = html! {
120
129
  if let Some(selected_column) = selected_column {
121
130
  <SplitPanel
122
131
  id="modal_panel"
132
+ class={classes!(is_pinned.then_some("pinned"))}
123
133
  reverse=true
124
134
  initial_size={self.settings_geometry.column_settings_width_override}
125
135
  on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
@@ -130,6 +140,10 @@ impl PerspectiveViewer {
130
140
  {selected_tab}
131
141
  on_close={self.on_close_column_settings.clone()}
132
142
  width_override={self.settings_geometry.column_settings_width_override}
143
+ auto_width={self.settings_geometry.column_settings_auto_width}
144
+ on_auto_width={ctx.link().callback(ColumnSettingsPanelAutoWidth)}
145
+ {is_pinned}
146
+ on_toggle_pin={ctx.link().callback(|()| ToggleColumnSettingsPin)}
133
147
  {on_select_tab}
134
148
  plugin_name={self.renderer_props.plugin_name.clone()}
135
149
  {metadata}
@@ -57,6 +57,18 @@ pub(super) struct SettingsGeometry {
57
57
 
58
58
  /// User-dragged column-settings drawer width override.
59
59
  pub column_settings_width_override: Option<i32>,
60
+
61
+ /// High-water-mark auto width reported by the column-settings drawer -
62
+ /// the trap-door shared across its Style/Attributes/Window tabs, lifted
63
+ /// here (like `auto_width`) so it survives drawer re-opens and clears
64
+ /// on divider reset.
65
+ pub column_settings_auto_width: f64,
66
+
67
+ /// Whether the column-settings drawer is PINNED - laid out as a static
68
+ /// flex sibling between the main panel and the settings panel - rather
69
+ /// than FLOATING over the main panel (the default absolute overlay).
70
+ /// Session UI state, not part of the saved config.
71
+ pub column_settings_pinned: bool,
60
72
  }
61
73
 
62
74
  impl PerspectiveViewer {
@@ -373,7 +385,27 @@ impl PerspectiveViewer {
373
385
 
374
386
  pub(super) fn on_column_settings_panel_size_update(&mut self, x: Option<i32>) -> bool {
375
387
  self.settings_geometry.column_settings_width_override = x;
376
- false
388
+ if x.is_none() {
389
+ self.settings_geometry.column_settings_auto_width = 0.0;
390
+ true
391
+ } else {
392
+ false
393
+ }
394
+ }
395
+
396
+ pub(super) fn on_column_settings_panel_auto_width(&mut self, w: f64) -> bool {
397
+ if w > self.settings_geometry.column_settings_auto_width {
398
+ self.settings_geometry.column_settings_auto_width = w;
399
+ true
400
+ } else {
401
+ false
402
+ }
403
+ }
404
+
405
+ pub(super) fn on_toggle_column_settings_pin(&mut self) -> bool {
406
+ self.settings_geometry.column_settings_pinned =
407
+ !self.settings_geometry.column_settings_pinned;
408
+ true
377
409
  }
378
410
 
379
411
  pub(super) fn on_column_settings_tab_changed(
@@ -338,6 +338,8 @@ impl Component for PerspectiveViewer {
338
338
  toggle,
339
339
  } => self.on_open_column_settings(ctx, locator, sender, toggle),
340
340
  ColumnSettingsPanelSizeUpdate(x) => self.on_column_settings_panel_size_update(x),
341
+ ColumnSettingsPanelAutoWidth(w) => self.on_column_settings_panel_auto_width(w),
342
+ ToggleColumnSettingsPin => self.on_toggle_column_settings_pin(),
341
343
  ColumnSettingsTabChanged(tab) => self.on_column_settings_tab_changed(ctx, tab),
342
344
  ToggleDebug => self.on_toggle_debug(ctx),
343
345