@perspective-dev/viewer 4.3.0 → 4.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 (227) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  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/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/perspective-viewer.inline.js +2 -2
  23. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  24. package/dist/esm/perspective-viewer.js +2 -2
  25. package/dist/esm/perspective-viewer.js.map +4 -4
  26. package/dist/wasm/perspective-viewer.d.ts +57 -53
  27. package/dist/wasm/perspective-viewer.js +190 -165
  28. package/dist/wasm/perspective-viewer.wasm +0 -0
  29. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  30. package/package.json +7 -5
  31. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  32. package/src/css/column-dropdown.css +109 -0
  33. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  34. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  35. package/src/{less/column-style.less → css/column-style.css} +52 -66
  36. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  37. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  38. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  39. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  40. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  41. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  42. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  43. package/src/css/dom/checkbox.css +102 -0
  44. package/src/css/dom/scrollbar.css +35 -0
  45. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  46. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  47. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  48. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  49. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  50. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  51. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  52. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  53. package/src/css/plugin-selector.css +261 -0
  54. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  55. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  56. package/src/css/type-icon.css +116 -0
  57. package/src/{less/viewer.less → css/viewer.css} +112 -146
  58. package/src/rust/components/column_dropdown.rs +229 -119
  59. package/src/rust/components/column_selector/active_column.rs +81 -62
  60. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  61. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  62. package/src/rust/components/column_selector/config_selector.rs +315 -199
  63. package/src/rust/components/column_selector/empty_column.rs +2 -2
  64. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  65. package/src/rust/components/column_selector/filter_column.rs +37 -26
  66. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  67. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  68. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  69. package/src/rust/components/column_selector/sort_column.rs +23 -13
  70. package/src/rust/components/column_selector.rs +163 -84
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  72. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  73. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  77. package/src/rust/components/column_settings_sidebar.rs +91 -53
  78. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  79. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  80. package/src/rust/components/containers/split_panel.rs +1 -0
  81. package/src/rust/components/containers/tab_list.rs +1 -1
  82. package/src/rust/components/copy_dropdown.rs +7 -28
  83. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  84. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  85. package/src/rust/components/datetime_column_style.rs +4 -2
  86. package/src/rust/components/editable_header.rs +7 -4
  87. package/src/rust/components/empty_row.rs +1 -1
  88. package/src/rust/components/export_dropdown.rs +4 -30
  89. package/src/rust/components/expression_editor.rs +19 -10
  90. package/src/rust/components/filter_dropdown.rs +246 -102
  91. package/src/rust/components/font_loader.rs +11 -28
  92. package/src/rust/components/form/code_editor.rs +17 -2
  93. package/src/rust/components/form/color_range_selector.rs +19 -6
  94. package/src/rust/components/form/debug.rs +30 -13
  95. package/src/rust/components/function_dropdown.rs +186 -113
  96. package/src/rust/components/main_panel.rs +71 -89
  97. package/src/rust/components/mod.rs +1 -1
  98. package/src/rust/components/modal.rs +7 -1
  99. package/src/rust/components/number_column_style.rs +22 -7
  100. package/src/rust/components/plugin_selector.rs +34 -102
  101. package/src/rust/components/portal.rs +274 -0
  102. package/src/rust/components/render_warning.rs +72 -123
  103. package/src/rust/components/settings_panel.rs +115 -11
  104. package/src/rust/components/status_bar.rs +222 -98
  105. package/src/rust/components/status_bar_counter.rs +8 -20
  106. package/src/rust/components/status_indicator.rs +64 -114
  107. package/src/rust/components/string_column_style.rs +2 -2
  108. package/src/rust/components/style/style_cache.rs +5 -1
  109. package/src/rust/components/viewer.rs +391 -39
  110. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  111. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  112. package/src/rust/custom_elements/mod.rs +0 -7
  113. package/src/rust/custom_elements/modal.rs +7 -103
  114. package/src/rust/custom_elements/viewer.rs +99 -35
  115. package/src/rust/custom_events.rs +23 -2
  116. package/src/rust/dragdrop.rs +149 -10
  117. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  118. package/src/rust/js/plugin.rs +1 -1
  119. package/src/rust/lib.rs +5 -4
  120. package/src/rust/presentation/props.rs +39 -0
  121. package/src/rust/presentation/sheets.rs +3 -3
  122. package/src/rust/presentation.rs +44 -8
  123. package/src/rust/renderer/limits.rs +32 -3
  124. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  125. package/src/rust/renderer.rs +83 -9
  126. package/src/rust/session/column_defaults_update.rs +1 -1
  127. package/src/rust/session/metadata.rs +23 -2
  128. package/src/rust/session/props.rs +178 -0
  129. package/src/rust/session.rs +124 -117
  130. package/src/rust/tasks/column_locator.rs +133 -0
  131. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  132. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  133. package/src/rust/{model → tasks}/eject.rs +2 -2
  134. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  135. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  136. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  137. package/src/rust/tasks/mod.rs +52 -0
  138. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  139. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  140. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  141. package/src/rust/tasks/structural.rs +53 -0
  142. package/src/rust/utils/mod.rs +4 -0
  143. package/src/rust/utils/modal_position.rs +110 -0
  144. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  145. package/src/rust/utils/pubsub.rs +11 -1
  146. package/src/svg/bg-pattern.png +0 -0
  147. package/src/svg/close-icon.svg +1 -1
  148. package/src/svg/expression.svg +1 -1
  149. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  150. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  151. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  152. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  153. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  154. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  155. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  156. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  157. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  158. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  159. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  160. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  161. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  162. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  163. package/src/svg/radio-hover.svg +1 -1
  164. package/src/svg/radio-off.svg +1 -1
  165. package/src/svg/radio-on.svg +1 -1
  166. package/src/themes/botanical.css +157 -0
  167. package/src/themes/defaults.css +139 -0
  168. package/src/themes/dracula.css +233 -0
  169. package/src/themes/gruvbox-dark.css +255 -0
  170. package/src/themes/gruvbox.css +134 -0
  171. package/src/themes/icons.css +124 -0
  172. package/src/themes/intl/de.css +102 -0
  173. package/src/themes/intl/es.css +102 -0
  174. package/src/themes/intl/fr.css +102 -0
  175. package/src/themes/intl/ja.css +102 -0
  176. package/src/themes/intl/pt.css +102 -0
  177. package/src/themes/intl/zh.css +102 -0
  178. package/src/themes/intl.css +102 -0
  179. package/src/themes/monokai.css +233 -0
  180. package/src/themes/pro-dark.css +158 -0
  181. package/src/themes/{themes.less → pro.css} +17 -21
  182. package/src/themes/solarized-dark.css +135 -0
  183. package/src/themes/solarized.css +95 -0
  184. package/src/themes/themes.css +22 -0
  185. package/src/themes/vaporwave.css +256 -0
  186. package/dist/css/variables.css +0 -0
  187. package/src/less/column-dropdown.less +0 -95
  188. package/src/less/dom/checkbox.less +0 -100
  189. package/src/less/plugin-selector.less +0 -183
  190. package/src/less/type-icon.less +0 -68
  191. package/src/rust/components/error_message.rs +0 -56
  192. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  193. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  194. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  195. package/src/rust/model/column_locator.rs +0 -82
  196. package/src/rust/model/is_invalid_drop.rs +0 -36
  197. package/src/rust/model/mod.rs +0 -100
  198. package/src/rust/model/reset_all.rs +0 -38
  199. package/src/rust/model/structural.rs +0 -244
  200. package/src/themes/botanical.less +0 -142
  201. package/src/themes/dracula.less +0 -101
  202. package/src/themes/gruvbox-dark.less +0 -116
  203. package/src/themes/gruvbox.less +0 -152
  204. package/src/themes/icons.less +0 -130
  205. package/src/themes/intl/de.less +0 -102
  206. package/src/themes/intl/es.less +0 -102
  207. package/src/themes/intl/fr.less +0 -102
  208. package/src/themes/intl/ja.less +0 -102
  209. package/src/themes/intl/pt.less +0 -102
  210. package/src/themes/intl/zh.less +0 -102
  211. package/src/themes/intl.less +0 -102
  212. package/src/themes/monokai.less +0 -107
  213. package/src/themes/pro-dark.less +0 -147
  214. package/src/themes/pro.less +0 -186
  215. package/src/themes/solarized-dark.less +0 -78
  216. package/src/themes/solarized.less +0 -102
  217. package/src/themes/vaporwave.less +0 -145
  218. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  223. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  224. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  225. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  226. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  227. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -27,6 +27,7 @@ use std::rc::Rc;
27
27
 
28
28
  pub use empty_column::*;
29
29
  pub use invalid_column::*;
30
+ use perspective_client::config::ViewConfig;
30
31
  use perspective_js::utils::ApiFuture;
31
32
  pub use pivot_column::*;
32
33
  use web_sys::*;
@@ -39,17 +40,20 @@ use self::inactive_column::*;
39
40
  use super::containers::scroll_panel::*;
40
41
  use super::containers::split_panel::{Orientation, SplitPanel};
41
42
  use super::style::LocalStyle;
43
+ use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
42
44
  use crate::components::containers::scroll_panel_item::ScrollPanelItem;
43
- use crate::custom_elements::ColumnDropDownElement;
45
+ use crate::css;
44
46
  use crate::dragdrop::*;
45
- use crate::model::*;
46
47
  use crate::presentation::ColumnLocator;
47
48
  use crate::renderer::*;
49
+ use crate::session::drag_drop_update::*;
48
50
  use crate::session::*;
51
+ use crate::tasks::{
52
+ ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, can_render_column_styles,
53
+ };
49
54
  use crate::utils::*;
50
- use crate::*;
51
55
 
52
- #[derive(Properties, PerspectiveProperties!)]
56
+ #[derive(Properties)]
53
57
  pub struct ColumnSelectorProps {
54
58
  /// Fires when the expression/config column is open.
55
59
  pub on_open_expr_panel: Callback<ColumnLocator>,
@@ -57,30 +61,48 @@ pub struct ColumnSelectorProps {
57
61
  /// This is passed to the add_expression_button for styling.
58
62
  pub selected_column: Option<ColumnLocator>,
59
63
 
60
- /// Fires when this component is resized via the UI.
61
- #[prop_or_default]
62
- pub on_resize: Option<Rc<PubSub<()>>>,
64
+ /// Value props threaded from root's `SessionProps` / `RendererProps`.
65
+ pub has_table: Option<TableLoadState>,
66
+ pub named_column_count: usize,
67
+ pub view_config: PtrEqRc<ViewConfig>,
68
+ pub drag_column: Option<String>,
69
+
70
+ /// Cloned session metadata snapshot — threaded from `SessionProps`
71
+ /// so that metadata changes trigger re-renders via prop diffing.
72
+ pub metadata: SessionMetadataRc,
73
+
74
+ /// Selected theme name, threaded for PortalModal consumers.
75
+ pub selected_theme: Option<String>,
63
76
 
64
77
  // State
65
78
  pub session: Session,
66
79
  pub renderer: Renderer,
67
80
  pub dragdrop: DragDrop,
81
+
82
+ /// Fires when this component is resized via the UI.
83
+ #[prop_or_default]
84
+ pub on_resize: Option<Rc<PubSub<()>>>,
68
85
  }
69
86
 
70
87
  impl PartialEq for ColumnSelectorProps {
71
88
  fn eq(&self, rhs: &Self) -> bool {
72
89
  self.selected_column == rhs.selected_column
90
+ && self.has_table == rhs.has_table
91
+ && self.named_column_count == rhs.named_column_count
92
+ && self.view_config == rhs.view_config
93
+ && self.drag_column == rhs.drag_column
94
+ && self.metadata == rhs.metadata
95
+ && self.selected_theme == rhs.selected_theme
73
96
  }
74
97
  }
75
98
 
76
99
  #[derive(Debug)]
77
100
  pub enum ColumnSelectorMsg {
78
- TableLoaded,
79
- ViewCreated,
101
+ /// Triggers a plain re-render; used as `onselect`/`ondragenter` callbacks
102
+ /// from `ConfigSelector` after it mutates the view config.
103
+ Redraw,
80
104
  HoverActiveIndex(Option<usize>),
81
105
  SetWidth(f64),
82
- Drag(DragEffect),
83
- DragEnd,
84
106
  Drop((String, DragTarget, DragEffect, usize)),
85
107
  }
86
108
 
@@ -89,8 +111,7 @@ use ColumnSelectorMsg::*;
89
111
  /// A `ColumnSelector` controls the `columns` field of the `ViewConfig`,
90
112
  /// deriving its options from the table columns and `ViewConfig` expressions.
91
113
  pub struct ColumnSelector {
92
- _subscriptions: [Subscription; 5],
93
- named_row_count: usize,
114
+ _subscriptions: [Subscription; 1],
94
115
  drag_container: DragDropContainer,
95
116
  column_dropdown: ColumnDropDownElement,
96
117
  viewport_width: f64,
@@ -103,44 +124,14 @@ impl Component for ColumnSelector {
103
124
 
104
125
  fn create(ctx: &Context<Self>) -> Self {
105
126
  let ColumnSelectorProps {
106
- dragdrop,
107
- renderer,
108
- session,
109
- ..
127
+ dragdrop, session, ..
110
128
  } = ctx.props();
111
- let table_sub = {
112
- let cb = ctx.link().callback(|_| ColumnSelectorMsg::TableLoaded);
113
- session.table_loaded.add_listener(cb)
114
- };
115
-
116
- let view_sub = {
117
- let cb = ctx.link().callback(|_| ColumnSelectorMsg::ViewCreated);
118
- session.view_created.add_listener(cb)
119
- };
120
129
 
121
130
  let drop_sub = {
122
131
  let cb = ctx.link().callback(ColumnSelectorMsg::Drop);
123
132
  dragdrop.drop_received.add_listener(cb)
124
133
  };
125
134
 
126
- let drag_sub = {
127
- let cb = ctx.link().callback(ColumnSelectorMsg::Drag);
128
- dragdrop.dragstart_received.add_listener(cb)
129
- };
130
-
131
- let dragend_sub = {
132
- let cb = ctx.link().callback(|_| ColumnSelectorMsg::DragEnd);
133
- dragdrop.dragend_received.add_listener(cb)
134
- };
135
-
136
- let named = maybe! {
137
- let plugin =
138
- renderer.get_active_plugin().ok()?;
139
-
140
- Some(plugin.config_column_names()?.length() as usize)
141
- };
142
-
143
- let named_row_count = named.unwrap_or_default();
144
135
  let drag_container = DragDropContainer::new(|| {}, {
145
136
  let link = ctx.link().clone();
146
137
  move || link.send_message(ColumnSelectorMsg::HoverActiveIndex(None))
@@ -148,8 +139,7 @@ impl Component for ColumnSelector {
148
139
 
149
140
  let column_dropdown = ColumnDropDownElement::new(session.clone());
150
141
  Self {
151
- _subscriptions: [table_sub, view_sub, drop_sub, drag_sub, dragend_sub],
152
- named_row_count,
142
+ _subscriptions: [drop_sub],
153
143
  viewport_width: 0f64,
154
144
  drag_container,
155
145
  column_dropdown,
@@ -159,23 +149,11 @@ impl Component for ColumnSelector {
159
149
 
160
150
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
161
151
  match msg {
162
- Drag(DragEffect::Move(DragTarget::Active)) => false,
163
- Drag(_) | DragEnd | TableLoaded => true,
152
+ Redraw => true,
164
153
  SetWidth(w) => {
165
154
  self.viewport_width = w;
166
155
  false
167
156
  },
168
- ViewCreated => {
169
- let named = maybe! {
170
- let plugin =
171
- ctx.props().renderer.get_active_plugin().ok()?;
172
-
173
- Some(plugin.config_column_names()?.length() as usize)
174
- };
175
-
176
- self.named_row_count = named.unwrap_or_default();
177
- true
178
- },
179
157
  HoverActiveIndex(Some(to_index)) => ctx
180
158
  .props()
181
159
  .dragdrop
@@ -185,33 +163,77 @@ impl Component for ColumnSelector {
185
163
  true
186
164
  },
187
165
  Drop((column, DragTarget::Active, DragEffect::Move(DragTarget::Active), index)) => {
188
- if !ctx.props().is_invalid_columns_column(&column, index) {
189
- let update = ctx.props().session.create_drag_drop_update(
166
+ let is_invalid = {
167
+ let config = &ctx.props().view_config;
168
+ let from_index = config
169
+ .columns
170
+ .iter()
171
+ .position(|x| x.as_ref() == Some(&column));
172
+
173
+ let min_cols = ctx.props().renderer.metadata().min;
174
+ let is_to_empty = !config
175
+ .columns
176
+ .get(index)
177
+ .map(|x| x.is_some())
178
+ .unwrap_or_default();
179
+
180
+ min_cols
181
+ .and_then(|x| from_index.map(|fi| fi < x))
182
+ .unwrap_or_default()
183
+ && is_to_empty
184
+ };
185
+ if !is_invalid {
186
+ let col_type = ctx
187
+ .props()
188
+ .metadata
189
+ .get_column_table_type(column.as_str())
190
+ .unwrap();
191
+
192
+ let update = ctx.props().view_config.create_drag_drop_update(
190
193
  column,
194
+ col_type,
191
195
  index,
192
196
  DragTarget::Active,
193
197
  DragEffect::Move(DragTarget::Active),
194
198
  &ctx.props().renderer.metadata(),
199
+ ctx.props().metadata.get_features().unwrap(),
195
200
  );
196
201
 
197
- if let Ok(task) = ctx.props().update_and_render(update) {
198
- ApiFuture::spawn(task);
202
+ let session = ctx.props().session.clone();
203
+ let renderer = ctx.props().renderer.clone();
204
+ if session.update_view_config(update).is_ok() {
205
+ ApiFuture::spawn(async move {
206
+ renderer.apply_pending_plugin()?;
207
+ renderer.draw(session.validate().await?.create_view()).await
208
+ });
199
209
  }
200
210
  }
201
211
 
202
212
  true
203
213
  },
204
214
  Drop((column, DragTarget::Active, effect, index)) => {
205
- let update = ctx.props().session.create_drag_drop_update(
215
+ let col_type = ctx
216
+ .props()
217
+ .metadata
218
+ .get_column_table_type(column.as_str())
219
+ .unwrap();
220
+ let update = ctx.props().view_config.create_drag_drop_update(
206
221
  column,
222
+ col_type,
207
223
  index,
208
224
  DragTarget::Active,
209
225
  effect,
210
226
  &ctx.props().renderer.metadata(),
227
+ ctx.props().metadata.get_features().unwrap(),
211
228
  );
212
229
 
213
- if let Ok(task) = ctx.props().update_and_render(update) {
214
- ApiFuture::spawn(task);
230
+ let session = ctx.props().session.clone();
231
+ let renderer = ctx.props().renderer.clone();
232
+ if session.update_view_config(update).is_ok() {
233
+ ApiFuture::spawn(async move {
234
+ renderer.apply_pending_plugin()?;
235
+ renderer.draw(session.validate().await?.create_view()).await
236
+ });
215
237
  }
216
238
 
217
239
  true
@@ -228,10 +250,29 @@ impl Component for ColumnSelector {
228
250
  dragdrop,
229
251
  ..
230
252
  } = ctx.props();
231
- let config = session.get_view_config();
253
+ let metadata = &ctx.props().metadata;
254
+
255
+ // When `config.columns` is empty but the table has columns (transient
256
+ // state during `load()` after `reset()` clears the config), fill in
257
+ // all table columns as active — matching `validate_view_config()`.
258
+ let prop_config = &ctx.props().view_config;
259
+ let config = if prop_config.columns.is_empty() {
260
+ if let Some(table_cols) = metadata.get_table_columns() {
261
+ ViewConfig {
262
+ columns: table_cols.iter().map(|c| Some(c.clone())).collect(),
263
+ ..(**prop_config).clone()
264
+ }
265
+ .into()
266
+ } else {
267
+ prop_config.clone()
268
+ }
269
+ } else {
270
+ prop_config.clone()
271
+ };
272
+
232
273
  let is_aggregated = config.is_aggregated();
233
- let columns_iter = ctx.props().column_selector_iter_set(&config);
234
- let onselect = ctx.link().callback(|()| ViewCreated);
274
+ let columns_iter = ColumnsIteratorSet::new(&config, metadata, renderer, dragdrop);
275
+ let onselect = ctx.link().callback(|()| Redraw);
235
276
  let ondragenter = ctx.link().callback(HoverActiveIndex);
236
277
  let ondragover = Callback::from(|_event: DragEvent| _event.prevent_default());
237
278
  let ondrop = Callback::from({
@@ -244,8 +285,8 @@ impl Component for ColumnSelector {
244
285
  move |_| dragdrop.notify_drag_end()
245
286
  });
246
287
 
247
- let mut active_classes = classes!();
248
- if ctx.props().dragdrop.get_drag_column().is_some() {
288
+ let mut active_classes = classes!("scrollable");
289
+ if ctx.props().drag_column.is_some() {
249
290
  active_classes.push("dragdrop-highlight");
250
291
  };
251
292
 
@@ -258,8 +299,7 @@ impl Component for ColumnSelector {
258
299
  + config.split_by.len()
259
300
  + config.filter.len()
260
301
  + config.sort.len()) as f64,
261
- session
262
- .metadata()
302
+ metadata
263
303
  .get_features()
264
304
  .map(|x| {
265
305
  let mut y = 0.0;
@@ -288,7 +328,11 @@ impl Component for ColumnSelector {
288
328
  <ScrollPanelItem key="config_selector" {size_hint}>
289
329
  <ConfigSelector
290
330
  onselect={onselect.clone()}
291
- ondragenter={ctx.link().callback(|()| ViewCreated)}
331
+ ondragenter={ctx.link().callback(|()| Redraw)}
332
+ view_config={ctx.props().view_config.clone()}
333
+ drag_column={ctx.props().drag_column.clone()}
334
+ metadata={metadata.clone()}
335
+ selected_theme={ctx.props().selected_theme.clone()}
292
336
  {dragdrop}
293
337
  {renderer}
294
338
  {session}
@@ -296,11 +340,11 @@ impl Component for ColumnSelector {
296
340
  </ScrollPanelItem>
297
341
  };
298
342
 
299
- let mut named_count = self.named_row_count;
343
+ let mut named_count = ctx.props().named_column_count;
300
344
  let mut active_columns: Vec<_> = columns_iter
301
345
  .active()
302
346
  .enumerate()
303
- .map(|(idx, name)| {
347
+ .map(|(idx, name): (usize, ActiveColumnState)| {
304
348
  let ondragenter = ondragenter.reform(move |_| Some(idx));
305
349
  let size_hint = if named_count > 0 { 50.0 } else { 28.0 };
306
350
  named_count = named_count.saturating_sub(1);
@@ -315,6 +359,34 @@ impl Component for ColumnSelector {
315
359
  Some(ColumnLocator::Table(x)) | Some(ColumnLocator::Expression(x))
316
360
  if x == &key );
317
361
 
362
+ // Compute metadata-derived props here so that changes to
363
+ // session metadata propagate via prop diffing.
364
+ // For DragOver placeholders, resolve the type from the
365
+ // dragged column (since `get_name()` returns `None`).
366
+ let col_type = name
367
+ .get_name()
368
+ .and_then(|n| metadata.get_column_table_type(n))
369
+ .or_else(|| {
370
+ if matches!(name.state, ActiveColumnStateData::DragOver) {
371
+ dragdrop
372
+ .get_drag_column()
373
+ .and_then(|c| metadata.get_column_table_type(&c))
374
+ } else {
375
+ None
376
+ }
377
+ });
378
+
379
+ let is_expression = name
380
+ .get_name()
381
+ .map(|n| metadata.is_column_expression(n))
382
+ .unwrap_or(false);
383
+
384
+ let can_render_styles = name
385
+ .get_name()
386
+ .and_then(|n| can_render_column_styles(renderer, &config, metadata, n).ok())
387
+ .unwrap_or(false);
388
+
389
+ let show_edit_btn = is_expression || can_render_styles;
318
390
  let on_open_expr_panel = &ctx.props().on_open_expr_panel;
319
391
  html_nested! {
320
392
  <ScrollPanelItem {key} {size_hint}>
@@ -323,6 +395,11 @@ impl Component for ColumnSelector {
323
395
  {idx}
324
396
  {is_aggregated}
325
397
  {is_editing}
398
+ {is_expression}
399
+ {show_edit_btn}
400
+ {col_type}
401
+ view_config={config.clone()}
402
+ metadata={metadata.clone()}
326
403
  {name}
327
404
  {on_open_expr_panel}
328
405
  {ondragenter}
@@ -344,6 +421,7 @@ impl Component for ColumnSelector {
344
421
  .map(|(idx, vc)| {
345
422
  let selected_column = ctx.props().selected_column.as_ref();
346
423
  let is_editing = matches!(selected_column, Some(ColumnLocator::Expression(x)) if x.as_str() == vc.name);
424
+ let is_expression = metadata.is_column_expression(vc.name);
347
425
  html_nested! {
348
426
  <ScrollPanelItem key={vc.name} size_hint=28.0>
349
427
  <InactiveColumn
@@ -351,6 +429,9 @@ impl Component for ColumnSelector {
351
429
  visible={vc.is_visible}
352
430
  name={vc.name.to_owned()}
353
431
  {is_editing}
432
+ {is_expression}
433
+ view_config={config.clone()}
434
+ metadata={metadata.clone()}
354
435
  onselect={&onselect}
355
436
  ondragend={&ondragend}
356
437
  on_open_expr_panel={&ctx.props().on_open_expr_panel}
@@ -365,14 +446,7 @@ impl Component for ColumnSelector {
365
446
 
366
447
  let size = 28.0;
367
448
 
368
- let add_column = if ctx
369
- .props()
370
- .session
371
- .metadata()
372
- .get_features()
373
- .unwrap()
374
- .expressions
375
- {
449
+ let add_column = if metadata.get_features().unwrap().expressions {
376
450
  html_nested! {
377
451
  <ScrollPanelItem key="__add_expression__" size_hint={size}>
378
452
  <AddExpressionButton
@@ -417,6 +491,7 @@ impl Component for ColumnSelector {
417
491
  <ScrollPanel
418
492
  id="sub-columns"
419
493
  key="__sub_columns__"
494
+ class={classes!("scrollable")}
420
495
  on_resize={&ctx.props().on_resize}
421
496
  on_dimensions_reset={&self.on_reset}
422
497
  children={inactive_children}
@@ -435,6 +510,10 @@ impl Component for ColumnSelector {
435
510
  >
436
511
  { for selected_columns }
437
512
  </SplitPanel>
513
+ <ColumnDropDownPortal
514
+ element={self.column_dropdown.clone()}
515
+ theme={ctx.props().selected_theme.clone().unwrap_or_default()}
516
+ />
438
517
  </>
439
518
  }
440
519
  }
@@ -18,8 +18,8 @@ use perspective_client::clone;
18
18
  use yew::{Html, Properties, function_component, html};
19
19
 
20
20
  use crate::components::empty_row::EmptyRow;
21
+ use crate::components::filter_dropdown::FilterDropDownElement;
21
22
  use crate::config::SymbolKVPair;
22
- use crate::custom_elements::FilterDropDownElement;
23
23
 
24
24
  #[derive(Properties, PartialEq)]
25
25
  pub struct RowSelectorProps {
@@ -16,10 +16,11 @@ use itertools::Itertools;
16
16
  use yew::{Callback, Html, Properties, html};
17
17
 
18
18
  use crate::components::column_settings_sidebar::style_tab::symbol::symbol_pairs_item::PairsListItem;
19
+ use crate::components::filter_dropdown::FilterDropDownElement;
19
20
  use crate::components::style::LocalStyle;
20
21
  use crate::config::SymbolKVPair;
21
22
  use crate::css;
22
- use crate::custom_elements::FilterDropDownElement;
23
+ use crate::utils::PtrEqRc;
23
24
 
24
25
  #[derive(Properties, PartialEq)]
25
26
  pub struct PairsListProps {
@@ -28,7 +29,7 @@ pub struct PairsListProps {
28
29
  pub update_pairs: Callback<Vec<SymbolKVPair>>,
29
30
  pub id: Option<String>,
30
31
  pub row_dropdown: Rc<FilterDropDownElement>,
31
- pub values: Rc<Vec<String>>,
32
+ pub values: PtrEqRc<Vec<String>>,
32
33
  pub column_name: String,
33
34
  }
34
35
 
@@ -16,8 +16,9 @@ use yew::{Callback, Html, Properties, html};
16
16
 
17
17
  use crate::components::column_settings_sidebar::style_tab::symbol::row_selector::RowSelector;
18
18
  use crate::components::column_settings_sidebar::style_tab::symbol::symbol_selector::SymbolSelector;
19
+ use crate::components::filter_dropdown::FilterDropDownElement;
19
20
  use crate::config::SymbolKVPair;
20
- use crate::custom_elements::FilterDropDownElement;
21
+ use crate::utils::PtrEqRc;
21
22
 
22
23
  #[derive(Properties, PartialEq)]
23
24
  pub struct PairsListItemProps {
@@ -26,7 +27,7 @@ pub struct PairsListItemProps {
26
27
  pub pairs: Vec<SymbolKVPair>,
27
28
  pub update_pairs: Callback<Vec<SymbolKVPair>>,
28
29
  pub row_dropdown: Rc<FilterDropDownElement>,
29
- pub values: Rc<Vec<String>>,
30
+ pub values: PtrEqRc<Vec<String>>,
30
31
  pub focused: bool,
31
32
  pub set_focused_index: Callback<Option<usize>>,
32
33
  pub column_name: String,
@@ -10,18 +10,17 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use std::rc::Rc;
14
-
15
13
  use itertools::Itertools;
16
14
  use yew::{Callback, Html, Properties, function_component, html};
17
15
 
18
16
  use crate::components::containers::select::{Select, SelectItem};
17
+ use crate::utils::PtrEqRc;
19
18
 
20
19
  #[derive(Properties, PartialEq)]
21
20
  pub struct SymbolSelectorProps {
22
21
  pub index: usize,
23
22
  pub selected_value: Option<String>,
24
- pub values: Rc<Vec<String>>,
23
+ pub values: PtrEqRc<Vec<String>>,
25
24
  pub callback: Callback<String>,
26
25
  }
27
26
 
@@ -23,10 +23,10 @@ use itertools::Itertools;
23
23
  use yew::{Callback, Html, Properties, html};
24
24
 
25
25
  use crate::components::column_settings_sidebar::style_tab::symbol::symbol_pairs::PairsList;
26
+ use crate::components::filter_dropdown::{FilterDropDownElement, FilterDropDownPortal};
26
27
  use crate::components::style::LocalStyle;
27
28
  use crate::config::{ColumnConfigValueUpdate, KeyValueOpts, SymbolKVPair};
28
29
  use crate::css;
29
- use crate::custom_elements::FilterDropDownElement;
30
30
  use crate::session::Session;
31
31
 
32
32
  #[derive(Properties, PartialEq, Clone)]
@@ -36,6 +36,8 @@ pub struct SymbolAttrProps {
36
36
  pub restored_config: Option<HashMap<String, String>>,
37
37
  pub on_change: Callback<ColumnConfigValueUpdate>,
38
38
  pub default_config: KeyValueOpts,
39
+ /// Selected theme name, threaded for PortalModal consumers.
40
+ pub selected_theme: Option<String>,
39
41
  }
40
42
  impl SymbolAttrProps {
41
43
  pub fn next_default_symbol(&self, pairs_len: usize) -> String {
@@ -125,6 +127,10 @@ impl yew::Component for SymbolStyle {
125
127
  values={Rc::new(ctx.props().default_config.values.clone())}
126
128
  {update_pairs}
127
129
  />
130
+ <FilterDropDownPortal
131
+ element={(*self.row_dropdown).clone()}
132
+ theme={ctx.props().selected_theme.clone().unwrap_or_default()}
133
+ />
128
134
  </>
129
135
  }
130
136
  }