@perspective-dev/viewer 4.4.1 → 4.5.1

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 +1 -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/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/esm/bootstrap.d.ts +2 -1
  24. package/dist/esm/column-format.d.ts +51 -0
  25. package/dist/esm/extensions.d.ts +2 -0
  26. package/dist/esm/perspective-viewer.d.ts +3 -1
  27. package/dist/esm/perspective-viewer.inline.js +1 -2
  28. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  29. package/dist/esm/perspective-viewer.js +1 -2
  30. package/dist/esm/perspective-viewer.js.map +4 -4
  31. package/dist/esm/perspective-viewer.worker.d.ts +2 -0
  32. package/dist/esm/plugin.d.ts +16 -72
  33. package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
  34. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
  35. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -3
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +77 -18
  38. package/dist/wasm/perspective-viewer.js +302 -148
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +44 -9
  44. package/src/css/column-style.css +35 -2
  45. package/src/css/containers/scroll-panel.css +2 -1
  46. package/src/css/containers/tabs.css +8 -52
  47. package/src/css/dom/checkbox.css +2 -6
  48. package/src/css/form/code-editor.css +1 -0
  49. package/src/css/form/debug.css +3 -10
  50. package/src/css/plugin-selector.css +33 -0
  51. package/src/css/plugin-settings-panel.css +99 -0
  52. package/src/css/viewer.css +143 -3
  53. package/src/rust/components/column_dropdown.rs +3 -1
  54. package/src/rust/components/column_selector/active_column.rs +16 -19
  55. package/src/rust/components/column_selector/config_selector.rs +20 -20
  56. package/src/rust/components/column_selector/filter_column.rs +14 -14
  57. package/src/rust/components/column_selector/inactive_column.rs +10 -15
  58. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  59. package/src/rust/components/column_selector/sort_column.rs +7 -7
  60. package/src/rust/components/column_selector.rs +55 -37
  61. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +395 -0
  63. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  64. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  65. package/src/rust/components/column_settings_sidebar.rs +44 -49
  66. package/src/rust/components/containers/dragdrop_list.rs +32 -5
  67. package/src/rust/components/containers/mod.rs +0 -1
  68. package/src/rust/components/containers/scroll_panel.rs +21 -7
  69. package/src/rust/components/containers/sidebar.rs +8 -6
  70. package/src/rust/components/containers/split_panel.rs +3 -3
  71. package/src/rust/components/containers/tab_list.rs +3 -9
  72. package/src/rust/components/copy_dropdown.rs +2 -3
  73. package/src/rust/components/datetime_column_style.rs +19 -81
  74. package/src/rust/components/editable_header.rs +17 -3
  75. package/src/rust/components/export_dropdown.rs +2 -3
  76. package/src/rust/components/expression_editor.rs +29 -17
  77. package/src/rust/components/filter_dropdown.rs +2 -1
  78. package/src/rust/components/form/color_range_selector.rs +14 -7
  79. package/src/rust/components/form/debug.rs +47 -37
  80. package/src/rust/components/main_panel.rs +24 -65
  81. package/src/rust/components/mod.rs +2 -1
  82. package/src/rust/components/number_series_style.rs +161 -0
  83. package/src/rust/components/plugin_tab.rs +221 -0
  84. package/src/rust/components/settings_panel.rs +181 -59
  85. package/src/rust/components/status_bar.rs +141 -174
  86. package/src/rust/components/status_indicator.rs +15 -22
  87. package/src/rust/components/string_column_style.rs +20 -82
  88. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  89. package/src/rust/components/viewer.rs +169 -132
  90. package/src/rust/config/column_config_schema.rs +195 -0
  91. package/src/rust/config/columns_config.rs +4 -97
  92. package/src/rust/config/datetime_column_style.rs +0 -5
  93. package/src/rust/config/mod.rs +8 -2
  94. package/src/rust/config/number_series_style.rs +79 -0
  95. package/src/rust/config/plugin_static_config.rs +144 -0
  96. package/src/rust/config/string_column_style.rs +0 -5
  97. package/src/rust/config/viewer_config.rs +5 -6
  98. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  99. package/src/rust/custom_elements/debug_plugin.rs +1 -3
  100. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  101. package/src/rust/custom_elements/viewer.rs +62 -73
  102. package/src/rust/custom_events.rs +181 -224
  103. package/src/rust/js/plugin.rs +45 -117
  104. package/src/rust/lib.rs +34 -5
  105. package/src/rust/presentation/drag_helpers.rs +206 -0
  106. package/src/rust/presentation/props.rs +8 -0
  107. package/src/rust/presentation.rs +256 -41
  108. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  109. package/src/rust/queries/column_values.rs +59 -0
  110. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  111. package/src/rust/queries/exports.rs +96 -0
  112. package/src/rust/queries/fetch_column_stats.rs +94 -0
  113. package/src/rust/queries/get_viewer_config.rs +54 -0
  114. package/src/rust/queries/mod.rs +44 -0
  115. package/src/rust/queries/plugin_column_styles.rs +101 -0
  116. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  117. package/src/rust/renderer/activate.rs +1 -0
  118. package/src/rust/renderer/limits.rs +9 -4
  119. package/src/rust/renderer/plugin_store.rs +12 -0
  120. package/src/rust/renderer/props.rs +28 -3
  121. package/src/rust/renderer/registry.rs +40 -15
  122. package/src/rust/renderer.rs +703 -60
  123. package/src/rust/session/column_defaults_update.rs +20 -28
  124. package/src/rust/session/drag_drop_update.rs +10 -10
  125. package/src/rust/session/metadata.rs +31 -16
  126. package/src/rust/session/props.rs +15 -6
  127. package/src/rust/session/view_subscription.rs +10 -0
  128. package/src/rust/session.rs +109 -147
  129. package/src/rust/tasks/copy_export.rs +178 -158
  130. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  131. package/src/rust/tasks/edit_expression.rs +68 -88
  132. package/src/rust/tasks/eject.rs +25 -22
  133. package/src/rust/tasks/intersection_observer.rs +8 -21
  134. package/src/rust/tasks/mod.rs +19 -21
  135. package/src/rust/tasks/reset_all.rs +98 -0
  136. package/src/rust/tasks/resize_observer.rs +11 -33
  137. package/src/rust/tasks/restore_and_render.rs +128 -90
  138. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +39 -35
  139. package/src/rust/tasks/send_plugin_config.rs +33 -33
  140. package/src/rust/tasks/update_and_render.rs +75 -49
  141. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  142. package/src/rust/tasks/validate_expression.rs +61 -0
  143. package/src/rust/utils/browser/selection.rs +4 -4
  144. package/src/rust/utils/mod.rs +0 -63
  145. package/src/svg/checkbox-checked-icon.svg +1 -1
  146. package/src/svg/checkbox-unchecked-icon.svg +1 -1
  147. package/src/svg/mega-menu-icons-density.svg +23 -0
  148. package/src/svg/mega-menu-icons-map-density.svg +24 -0
  149. package/src/svg/mega-menu-icons-map-line.svg +19 -0
  150. package/src/themes/botanical.css +27 -53
  151. package/src/themes/defaults.css +24 -36
  152. package/src/themes/dracula.css +36 -54
  153. package/src/themes/gruvbox-dark.css +39 -59
  154. package/src/themes/gruvbox.css +16 -28
  155. package/src/themes/icons.css +5 -0
  156. package/src/themes/intl/de.css +43 -6
  157. package/src/themes/intl/es.css +43 -6
  158. package/src/themes/intl/fr.css +43 -6
  159. package/src/themes/intl/ja.css +43 -6
  160. package/src/themes/intl/pt.css +43 -6
  161. package/src/themes/intl/zh.css +43 -6
  162. package/src/themes/intl.css +38 -4
  163. package/src/themes/monokai.css +45 -61
  164. package/src/themes/phosphor.css +20 -29
  165. package/src/themes/pro-dark.css +25 -34
  166. package/src/themes/solarized-dark.css +21 -36
  167. package/src/themes/solarized.css +13 -23
  168. package/src/themes/vaporwave.css +40 -74
  169. package/src/ts/bootstrap.ts +14 -3
  170. package/src/ts/column-format.ts +162 -0
  171. package/src/ts/extensions.ts +4 -0
  172. package/src/ts/perspective-viewer.ts +9 -1
  173. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  174. package/src/ts/plugin.ts +25 -101
  175. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  176. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  177. package/src/ts/ts-rs/ViewerConfig.ts +1 -2
  178. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  179. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  180. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  181. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  182. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  183. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  184. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  185. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  186. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  187. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  188. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  190. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  191. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  192. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  193. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  194. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  195. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  196. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  197. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  198. package/src/rust/components/number_column_style.rs +0 -491
  199. package/src/rust/config/number_column_style.rs +0 -136
  200. package/src/rust/dragdrop.rs +0 -481
  201. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  202. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  203. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  204. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  205. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  206. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  207. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  208. package/src/ts/ts-rs/FormatMode.ts +0 -3
  209. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  210. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  211. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  212. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  213. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  214. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  215. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  216. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  217. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  218. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  219. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  220. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline0.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline1.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline2.js +0 -0
  223. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline3.js +0 -0
  224. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline4.js +0 -0
  225. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  226. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  227. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -43,14 +43,11 @@ use super::style::LocalStyle;
43
43
  use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
44
44
  use crate::components::containers::scroll_panel_item::ScrollPanelItem;
45
45
  use crate::css;
46
- use crate::dragdrop::*;
47
- use crate::presentation::ColumnLocator;
46
+ use crate::presentation::{ColumnLocator, DragDropContainer, Presentation};
47
+ use crate::queries::{ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet};
48
48
  use crate::renderer::*;
49
49
  use crate::session::drag_drop_update::*;
50
50
  use crate::session::*;
51
- use crate::tasks::{
52
- ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, can_render_column_styles,
53
- };
54
51
  use crate::utils::*;
55
52
 
56
53
  #[derive(Properties)]
@@ -77,11 +74,28 @@ pub struct ColumnSelectorProps {
77
74
  // State
78
75
  pub session: Session,
79
76
  pub renderer: Renderer,
80
- pub dragdrop: DragDrop,
77
+ pub presentation: Presentation,
81
78
 
82
79
  /// Fires when this component is resized via the UI.
83
80
  #[prop_or_default]
84
81
  pub on_resize: Option<Rc<PubSub<()>>>,
82
+
83
+ /// Trap-door width pinned by the parent `SettingsPanel` so switching
84
+ /// tabs doesn't shrink the panel. Threaded into the inner
85
+ /// `ScrollPanel` as `initial_width`.
86
+ #[prop_or_default]
87
+ pub initial_width: f64,
88
+
89
+ /// Fires when the inner `ScrollPanel` measures its natural width.
90
+ /// Routed up to `SettingsPanel` which keeps the running max.
91
+ #[prop_or_default]
92
+ pub on_auto_width: Callback<f64>,
93
+
94
+ /// External "release the trap-door" signal from the outer settings
95
+ /// split-panel divider reset. Forwarded into `self.on_reset` so both
96
+ /// inner `ScrollPanel`s drop their cached `viewport_width`.
97
+ #[prop_or_default]
98
+ pub on_dimensions_reset: Option<Rc<PubSub<()>>>,
85
99
  }
86
100
 
87
101
  impl PartialEq for ColumnSelectorProps {
@@ -93,6 +107,7 @@ impl PartialEq for ColumnSelectorProps {
93
107
  && self.drag_column == rhs.drag_column
94
108
  && self.metadata == rhs.metadata
95
109
  && self.selected_theme == rhs.selected_theme
110
+ && self.initial_width == rhs.initial_width
96
111
  }
97
112
  }
98
113
 
@@ -102,7 +117,6 @@ pub enum ColumnSelectorMsg {
102
117
  /// from `ConfigSelector` after it mutates the view config.
103
118
  Redraw,
104
119
  HoverActiveIndex(Option<usize>),
105
- SetWidth(f64),
106
120
  Drop((String, DragTarget, DragEffect, usize)),
107
121
  }
108
122
 
@@ -111,10 +125,9 @@ use ColumnSelectorMsg::*;
111
125
  /// A `ColumnSelector` controls the `columns` field of the `ViewConfig`,
112
126
  /// deriving its options from the table columns and `ViewConfig` expressions.
113
127
  pub struct ColumnSelector {
114
- _subscriptions: [Subscription; 1],
128
+ _subscriptions: Vec<Subscription>,
115
129
  drag_container: DragDropContainer,
116
130
  column_dropdown: ColumnDropDownElement,
117
- viewport_width: f64,
118
131
  on_reset: Rc<PubSub<()>>,
119
132
  }
120
133
 
@@ -124,12 +137,14 @@ impl Component for ColumnSelector {
124
137
 
125
138
  fn create(ctx: &Context<Self>) -> Self {
126
139
  let ColumnSelectorProps {
127
- dragdrop, session, ..
140
+ presentation,
141
+ session,
142
+ ..
128
143
  } = ctx.props();
129
144
 
130
145
  let drop_sub = {
131
146
  let cb = ctx.link().callback(ColumnSelectorMsg::Drop);
132
- dragdrop.drop_received.add_listener(cb)
147
+ presentation.drop_received.add_listener(cb)
133
148
  };
134
149
 
135
150
  let drag_container = DragDropContainer::new(|| {}, {
@@ -138,28 +153,32 @@ impl Component for ColumnSelector {
138
153
  });
139
154
 
140
155
  let column_dropdown = ColumnDropDownElement::new(session.clone());
156
+ let on_reset: Rc<PubSub<()>> = Default::default();
157
+ let mut subscriptions = vec![drop_sub];
158
+ if let Some(outer_reset) = ctx.props().on_dimensions_reset.as_ref() {
159
+ let on_reset = on_reset.clone();
160
+ subscriptions.push(outer_reset.add_listener(move |()| on_reset.emit(())));
161
+ }
162
+
141
163
  Self {
142
- _subscriptions: [drop_sub],
143
- viewport_width: 0f64,
164
+ _subscriptions: subscriptions,
144
165
  drag_container,
145
166
  column_dropdown,
146
- on_reset: Default::default(),
167
+ on_reset,
147
168
  }
148
169
  }
149
170
 
150
171
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
151
172
  match msg {
152
173
  Redraw => true,
153
- SetWidth(w) => {
154
- self.viewport_width = w;
155
- false
156
- },
157
174
  HoverActiveIndex(Some(to_index)) => ctx
158
175
  .props()
159
- .dragdrop
176
+ .presentation
160
177
  .notify_drag_enter(DragTarget::Active, to_index),
161
178
  HoverActiveIndex(_) => {
162
- ctx.props().dragdrop.notify_drag_leave(DragTarget::Active);
179
+ ctx.props()
180
+ .presentation
181
+ .notify_drag_leave(DragTarget::Active);
163
182
  true
164
183
  },
165
184
  Drop((column, DragTarget::Active, DragEffect::Move(DragTarget::Active), index)) => {
@@ -170,7 +189,7 @@ impl Component for ColumnSelector {
170
189
  .iter()
171
190
  .position(|x| x.as_ref() == Some(&column));
172
191
 
173
- let min_cols = ctx.props().renderer.metadata().min;
192
+ let min_cols = ctx.props().renderer.metadata().min_config_columns;
174
193
  let is_to_empty = !config
175
194
  .columns
176
195
  .get(index)
@@ -247,7 +266,7 @@ impl Component for ColumnSelector {
247
266
  let ColumnSelectorProps {
248
267
  session,
249
268
  renderer,
250
- dragdrop,
269
+ presentation,
251
270
  ..
252
271
  } = ctx.props();
253
272
  let metadata = &ctx.props().metadata;
@@ -271,18 +290,18 @@ impl Component for ColumnSelector {
271
290
  };
272
291
 
273
292
  let is_aggregated = config.is_aggregated();
274
- let columns_iter = ColumnsIteratorSet::new(&config, metadata, renderer, dragdrop);
293
+ let columns_iter = ColumnsIteratorSet::new(&config, metadata, renderer, presentation);
275
294
  let onselect = ctx.link().callback(|()| Redraw);
276
295
  let ondragenter = ctx.link().callback(HoverActiveIndex);
277
296
  let ondragover = Callback::from(|_event: DragEvent| _event.prevent_default());
278
297
  let ondrop = Callback::from({
279
- clone!(dragdrop);
280
- move |event| dragdrop.notify_drop(&event)
298
+ clone!(presentation);
299
+ move |event| presentation.notify_drop(&event)
281
300
  });
282
301
 
283
302
  let ondragend = Callback::from({
284
- clone!(dragdrop);
285
- move |_| dragdrop.notify_drag_end()
303
+ clone!(presentation);
304
+ move |_| presentation.notify_drag_end()
286
305
  });
287
306
 
288
307
  let mut active_classes = classes!("scrollable");
@@ -333,7 +352,7 @@ impl Component for ColumnSelector {
333
352
  drag_column={ctx.props().drag_column.clone()}
334
353
  metadata={metadata.clone()}
335
354
  selected_theme={ctx.props().selected_theme.clone()}
336
- {dragdrop}
355
+ {presentation}
337
356
  {renderer}
338
357
  {session}
339
358
  />
@@ -368,7 +387,7 @@ impl Component for ColumnSelector {
368
387
  .and_then(|n| metadata.get_column_table_type(n))
369
388
  .or_else(|| {
370
389
  if matches!(name.state, ActiveColumnStateData::DragOver) {
371
- dragdrop
390
+ presentation
372
391
  .get_drag_column()
373
392
  .and_then(|c| metadata.get_column_table_type(&c))
374
393
  } else {
@@ -381,10 +400,8 @@ impl Component for ColumnSelector {
381
400
  .map(|n| metadata.is_column_expression(n))
382
401
  .unwrap_or(false);
383
402
 
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);
403
+ let can_render_styles =
404
+ name.get_name().is_some() && renderer.can_render_column_styles();
388
405
 
389
406
  let show_edit_btn = is_expression || can_render_styles;
390
407
  let on_open_expr_panel = &ctx.props().on_open_expr_panel;
@@ -405,7 +422,7 @@ impl Component for ColumnSelector {
405
422
  {ondragenter}
406
423
  ondragend={&ondragend}
407
424
  onselect={&onselect}
408
- {dragdrop}
425
+ {presentation}
409
426
  {renderer}
410
427
  {session}
411
428
  />
@@ -435,7 +452,7 @@ impl Component for ColumnSelector {
435
452
  onselect={&onselect}
436
453
  ondragend={&ondragend}
437
454
  on_open_expr_panel={&ctx.props().on_open_expr_panel}
438
- {dragdrop}
455
+ {presentation}
439
456
  {renderer}
440
457
  {session}
441
458
  />
@@ -471,13 +488,14 @@ impl Component for ColumnSelector {
471
488
  <div id="selected-columns" key="__active_columns__">
472
489
  <ScrollPanel
473
490
  id="active-columns"
491
+ omit_autosize_div={true}
474
492
  class={active_classes}
475
493
  dragover={ondragover}
476
494
  dragenter={&self.drag_container.dragenter}
477
495
  dragleave={&self.drag_container.dragleave}
478
496
  viewport_ref={&self.drag_container.noderef}
479
- initial_width={self.viewport_width}
480
- on_auto_width={ctx.link().callback(ColumnSelectorMsg::SetWidth)}
497
+ initial_width={ctx.props().initial_width}
498
+ on_auto_width={ctx.props().on_auto_width.clone()}
481
499
  drop={ondrop}
482
500
  on_resize={&ctx.props().on_resize}
483
501
  on_dimensions_reset={&self.on_reset}
@@ -14,17 +14,19 @@ use perspective_client::clone;
14
14
  use yew::{Callback, Html, Properties, function_component, html};
15
15
 
16
16
  use crate::components::form::number_field::NumberField;
17
- use crate::config::ColumnConfigValueUpdate;
17
+ use crate::config::ColumnConfigFieldUpdate;
18
18
 
19
19
  // ░░░█▀█░█▀▄░█▀█░█▀█░█▀▀░█▀▄░▀█▀░▀█▀░█▀▀░█▀▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
20
20
  // ░░░█▀▀░█▀▄░█░█░█▀▀░█▀▀░█▀▄░░█░░░█░░█▀▀░▀▀█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
21
21
  // ░░░▀░░░▀░▀░▀▀▀░▀░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀▀▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
22
22
  #[derive(Properties, PartialEq)]
23
23
  pub struct AggregateDepthSelectorProps {
24
- pub on_change: Callback<ColumnConfigValueUpdate>,
24
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
25
25
  pub value: u32,
26
26
  pub group_by_depth: u32,
27
27
  pub column_name: String,
28
+ #[prop_or_default]
29
+ pub keys: Vec<String>,
28
30
  }
29
31
 
30
32
  #[function_component]
@@ -36,12 +38,18 @@ pub fn AggregateDepthSelector(props: &AggregateDepthSelectorProps) -> Html {
36
38
  });
37
39
 
38
40
  let on_change = yew::use_callback(
39
- (state.setter(), props.on_change.clone()),
41
+ (state.setter(), props.on_change.clone(), props.keys.clone()),
40
42
  |x: Option<f64>, deps| {
41
- deps.0.set(x.unwrap_or_default() as u32);
42
- deps.1.emit(ColumnConfigValueUpdate::AggregateDepth(
43
- x.unwrap_or_default() as u32,
44
- ))
43
+ let depth = x.unwrap_or_default() as u32;
44
+ deps.0.set(depth);
45
+ let mut value = serde_json::Map::new();
46
+ if depth > 0 {
47
+ value.insert("aggregate_depth".to_owned(), serde_json::json!(depth));
48
+ }
49
+ deps.1.emit(ColumnConfigFieldUpdate {
50
+ keys: deps.2.clone(),
51
+ value,
52
+ })
45
53
  },
46
54
  );
47
55
 
@@ -0,0 +1,395 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! Schema-driven generic widgets for the Style tab. Each widget renders a
14
+ //! single primitive [`crate::config::ControlSpec`] variant and emits a
15
+ //! [`crate::config::ColumnConfigFieldUpdate`] on change. Built on top of
16
+ //! the existing form components ([`Select`], [`OptionalField`],
17
+ //! [`ColorSelector`]) so that they visually match the rich Yew widgets in
18
+ //! the same sidebar.
19
+
20
+ use std::rc::Rc;
21
+
22
+ use itertools::Itertools;
23
+ use serde_json::Value;
24
+ use wasm_bindgen::JsCast;
25
+ use web_sys::{HtmlInputElement, MouseEvent};
26
+ use yew::{Callback, Html, Properties, classes, function_component, html, use_callback};
27
+
28
+ use crate::components::containers::select::{Select, SelectItem};
29
+ use crate::components::form::color_range_selector::ColorRangeSelector;
30
+ use crate::components::form::color_selector::ColorSelector;
31
+ use crate::components::form::number_field::NumberField;
32
+ use crate::components::form::optional_field::OptionalField;
33
+ use crate::config::{ColumnConfigFieldUpdate, EnumVariant};
34
+
35
+ fn emit(on_change: &Callback<ColumnConfigFieldUpdate>, key: &str, value: Option<Value>) {
36
+ let mut map = serde_json::Map::new();
37
+ if let Some(v) = value {
38
+ map.insert(key.to_owned(), v);
39
+ }
40
+
41
+ on_change.emit(ColumnConfigFieldUpdate {
42
+ keys: vec![key.to_owned()],
43
+ value: map,
44
+ });
45
+ }
46
+
47
+ fn emit_color_range(
48
+ on_change: &Callback<ColumnConfigFieldUpdate>,
49
+ key_pos: &str,
50
+ key_neg: &str,
51
+ default_pos: &str,
52
+ default_neg: &str,
53
+ new_pos: &str,
54
+ new_neg: &str,
55
+ ) {
56
+ let mut value = serde_json::Map::new();
57
+ if new_pos != default_pos {
58
+ value.insert(key_pos.to_owned(), Value::String(new_pos.to_owned()));
59
+ }
60
+ if new_neg != default_neg {
61
+ value.insert(key_neg.to_owned(), Value::String(new_neg.to_owned()));
62
+ }
63
+ on_change.emit(ColumnConfigFieldUpdate {
64
+ keys: vec![key_pos.to_owned(), key_neg.to_owned()],
65
+ value,
66
+ });
67
+ }
68
+
69
+ #[derive(Properties, PartialEq)]
70
+ pub struct EnumFieldProps {
71
+ pub field_key: String,
72
+ pub variants: Vec<EnumVariant>,
73
+ pub default: String,
74
+ pub current: Option<String>,
75
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
76
+ }
77
+
78
+ #[function_component]
79
+ pub fn EnumField(props: &EnumFieldProps) -> Html {
80
+ let selected = props
81
+ .current
82
+ .clone()
83
+ .unwrap_or_else(|| props.default.clone());
84
+
85
+ let checked = selected != props.default;
86
+ let values: Rc<Vec<SelectItem<String>>> = Rc::new(
87
+ props
88
+ .variants
89
+ .iter()
90
+ .map(|v| SelectItem::Option(v.value.clone()))
91
+ .collect_vec(),
92
+ );
93
+
94
+ let on_select = use_callback(
95
+ (
96
+ props.field_key.clone(),
97
+ props.default.clone(),
98
+ props.on_change.clone(),
99
+ ),
100
+ |value: String, (key, default, on_change)| {
101
+ if value == *default {
102
+ emit(on_change, key, None);
103
+ } else {
104
+ emit(on_change, key, Some(Value::String(value)));
105
+ }
106
+ },
107
+ );
108
+
109
+ let on_reset = use_callback(
110
+ (props.field_key.clone(), props.on_change.clone()),
111
+ |_: MouseEvent, (key, on_change)| emit(on_change, key, None),
112
+ );
113
+
114
+ html! {
115
+ <div class="row">
116
+ <OptionalField label={props.field_key.clone()} on_check={on_reset} {checked}>
117
+ <Select<String> {values} {selected} {on_select} />
118
+ </OptionalField>
119
+ </div>
120
+ }
121
+ }
122
+
123
+ #[derive(Properties, PartialEq)]
124
+ pub struct BoolFieldProps {
125
+ pub field_key: String,
126
+ pub default: bool,
127
+ pub current: Option<bool>,
128
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
129
+ }
130
+
131
+ #[function_component]
132
+ pub fn BoolField(props: &BoolFieldProps) -> Html {
133
+ let current = props.current.unwrap_or(props.default);
134
+ let oninput = use_callback(
135
+ (
136
+ props.field_key.clone(),
137
+ props.default,
138
+ props.on_change.clone(),
139
+ ),
140
+ |e: yew::events::InputEvent, (key, default, on_change)| {
141
+ let target: HtmlInputElement = e.target().unwrap().unchecked_into();
142
+ let next = target.checked();
143
+ if next == *default {
144
+ emit(on_change, key, None);
145
+ } else {
146
+ emit(on_change, key, Some(Value::Bool(next)));
147
+ }
148
+ },
149
+ );
150
+
151
+ let checked = current != props.default;
152
+ let on_reset = use_callback(
153
+ (props.field_key.clone(), props.on_change.clone()),
154
+ |_: MouseEvent, (key, on_change)| emit(on_change, key, None),
155
+ );
156
+
157
+ html! {
158
+ <div class="row">
159
+ <OptionalField label={props.field_key.clone()} on_check={on_reset} {checked}>
160
+ <div class="bool-field-container">
161
+ <input
162
+ type="checkbox"
163
+ class="alternate"
164
+ id={format!("{}-checkbox", props.field_key)}
165
+ checked={current}
166
+ {oninput}
167
+ />
168
+ <label for={format!("{}-checkbox", props.field_key)} class="bool-field-desc">
169
+ { if current { "Enabled" } else { "Disabled" } }
170
+ </label>
171
+ </div>
172
+ </OptionalField>
173
+ </div>
174
+ }
175
+ }
176
+
177
+ #[derive(Properties, PartialEq)]
178
+ pub struct NumberFieldPrimitiveProps {
179
+ pub field_key: String,
180
+ pub default: f64,
181
+ pub current: Option<f64>,
182
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
183
+
184
+ #[prop_or_default]
185
+ pub include: Option<bool>,
186
+
187
+ #[prop_or_default]
188
+ pub min: Option<f64>,
189
+
190
+ #[prop_or_default]
191
+ pub max: Option<f64>,
192
+
193
+ #[prop_or_default]
194
+ pub step: Option<f64>,
195
+ }
196
+
197
+ #[function_component]
198
+ pub fn NumberFieldPrimitive(props: &NumberFieldPrimitiveProps) -> Html {
199
+ let on_change_inner = use_callback(
200
+ (
201
+ props.field_key.clone(),
202
+ props.default,
203
+ props.on_change.clone(),
204
+ props.include,
205
+ ),
206
+ |value: Option<f64>, (key, default, on_change, include)| match value {
207
+ Some(v) if include.unwrap_or_default() || v != *default => emit(
208
+ on_change,
209
+ key,
210
+ Some(
211
+ serde_json::Number::from_f64(v)
212
+ .map(Value::Number)
213
+ .unwrap_or(Value::Null),
214
+ ),
215
+ ),
216
+ None if include.unwrap_or_default() => emit(
217
+ on_change,
218
+ key,
219
+ Some(
220
+ serde_json::Number::from_f64(*default)
221
+ .map(Value::Number)
222
+ .unwrap_or(Value::Null),
223
+ ),
224
+ ),
225
+ _ => emit(on_change, key, None),
226
+ },
227
+ );
228
+
229
+ html! {
230
+ <NumberField
231
+ label={props.field_key.clone()}
232
+ current_value={props.current}
233
+ default={props.default}
234
+ min={props.min}
235
+ max={props.max}
236
+ step={props.step}
237
+ on_change={on_change_inner}
238
+ />
239
+ }
240
+ }
241
+
242
+ #[derive(Properties, PartialEq)]
243
+ pub struct ColorRangeFieldProps {
244
+ pub field_key_pos: String,
245
+ pub field_key_neg: String,
246
+ pub default_pos: String,
247
+ pub default_neg: String,
248
+ pub current_pos: Option<String>,
249
+ pub current_neg: Option<String>,
250
+ pub is_gradient: bool,
251
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
252
+ }
253
+
254
+ #[function_component]
255
+ pub fn ColorRangeField(props: &ColorRangeFieldProps) -> Html {
256
+ let pos = props
257
+ .current_pos
258
+ .clone()
259
+ .unwrap_or_else(|| props.default_pos.clone());
260
+ let neg = props
261
+ .current_neg
262
+ .clone()
263
+ .unwrap_or_else(|| props.default_neg.clone());
264
+ let is_modified = (props.current_pos.is_some()
265
+ && props.current_pos.as_deref() != Some(props.default_pos.as_str()))
266
+ || (props.current_neg.is_some()
267
+ && props.current_neg.as_deref() != Some(props.default_neg.as_str()));
268
+
269
+ // Multi-key emit: write whichever side(s) differ from default,
270
+ // clear the others. Mirrors the apply semantics of
271
+ // `ColumnConfigFieldUpdate { keys, value }` with both keys owned.
272
+ let on_pos_color = use_callback(
273
+ (
274
+ props.field_key_pos.clone(),
275
+ props.field_key_neg.clone(),
276
+ props.default_pos.clone(),
277
+ props.default_neg.clone(),
278
+ props.on_change.clone(),
279
+ neg.clone(),
280
+ ),
281
+ |new_pos: String, (key_pos, key_neg, default_pos, default_neg, on_change, neg)| {
282
+ emit_color_range(
283
+ on_change,
284
+ key_pos,
285
+ key_neg,
286
+ default_pos,
287
+ default_neg,
288
+ &new_pos,
289
+ neg,
290
+ );
291
+ },
292
+ );
293
+
294
+ let on_neg_color = use_callback(
295
+ (
296
+ props.field_key_pos.clone(),
297
+ props.field_key_neg.clone(),
298
+ props.default_pos.clone(),
299
+ props.default_neg.clone(),
300
+ props.on_change.clone(),
301
+ pos.clone(),
302
+ ),
303
+ |new_neg: String, (key_pos, key_neg, default_pos, default_neg, on_change, pos)| {
304
+ emit_color_range(
305
+ on_change,
306
+ key_pos,
307
+ key_neg,
308
+ default_pos,
309
+ default_neg,
310
+ pos,
311
+ &new_neg,
312
+ );
313
+ },
314
+ );
315
+
316
+ let on_reset = use_callback(
317
+ (
318
+ props.field_key_pos.clone(),
319
+ props.field_key_neg.clone(),
320
+ props.on_change.clone(),
321
+ ),
322
+ |_: (), (key_pos, key_neg, on_change)| {
323
+ on_change.emit(ColumnConfigFieldUpdate {
324
+ keys: vec![key_pos.clone(), key_neg.clone()],
325
+ value: serde_json::Map::new(),
326
+ });
327
+ },
328
+ );
329
+
330
+ html! {
331
+ <div class="row">
332
+ <ColorRangeSelector
333
+ pos_class={classes!(props.field_key_pos.clone())}
334
+ neg_class={classes!(props.field_key_neg.clone())}
335
+ pos_color={pos}
336
+ neg_color={neg}
337
+ is_gradient={props.is_gradient}
338
+ {on_pos_color}
339
+ {on_neg_color}
340
+ {on_reset}
341
+ {is_modified}
342
+ />
343
+ </div>
344
+ }
345
+ }
346
+
347
+ #[derive(Properties, PartialEq)]
348
+ pub struct ColorFieldProps {
349
+ pub field_key: String,
350
+ pub default: String,
351
+ pub current: Option<String>,
352
+ pub on_change: Callback<ColumnConfigFieldUpdate>,
353
+ }
354
+
355
+ #[function_component]
356
+ pub fn ColorField(props: &ColorFieldProps) -> Html {
357
+ let color = props
358
+ .current
359
+ .clone()
360
+ .unwrap_or_else(|| props.default.clone());
361
+ let is_modified =
362
+ props.current.as_deref() != Some(props.default.as_str()) && props.current.is_some();
363
+
364
+ let on_color = use_callback(
365
+ (
366
+ props.field_key.clone(),
367
+ props.default.clone(),
368
+ props.on_change.clone(),
369
+ ),
370
+ |value: String, (key, default, on_change)| {
371
+ if value == *default {
372
+ emit(on_change, key, None);
373
+ } else {
374
+ emit(on_change, key, Some(Value::String(value)));
375
+ }
376
+ },
377
+ );
378
+
379
+ let on_reset = use_callback(
380
+ (props.field_key.clone(), props.on_change.clone()),
381
+ |_: (), (key, on_change)| emit(on_change, key, None),
382
+ );
383
+
384
+ html! {
385
+ <div class="row">
386
+ <ColorSelector
387
+ {color}
388
+ {on_color}
389
+ {on_reset}
390
+ {is_modified}
391
+ title={Some(format!("{}-label", props.field_key))}
392
+ />
393
+ </div>
394
+ }
395
+ }