@perspective-dev/viewer 4.2.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 (240) 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 -0
  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/extensions.d.ts +32 -1
  23. package/dist/esm/perspective-viewer.d.ts +1 -0
  24. package/dist/esm/perspective-viewer.inline.js +2 -2
  25. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  26. package/dist/esm/perspective-viewer.js +2 -2
  27. package/dist/esm/perspective-viewer.js.map +4 -4
  28. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  29. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
  30. package/dist/wasm/perspective-viewer.d.ts +57 -53
  31. package/dist/wasm/perspective-viewer.js +197 -164
  32. package/dist/wasm/perspective-viewer.wasm +0 -0
  33. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  34. package/package.json +9 -6
  35. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  36. package/src/css/column-dropdown.css +109 -0
  37. package/src/{less/column-selector.less → css/column-selector.css} +161 -159
  38. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  39. package/src/{less/column-style.less → css/column-style.css} +52 -66
  40. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  41. package/src/css/config-selector.css +441 -0
  42. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  43. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  44. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  45. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  46. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  47. package/src/css/dom/checkbox.css +102 -0
  48. package/src/css/dom/scrollbar.css +35 -0
  49. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  50. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  51. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  52. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  53. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  54. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  55. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  56. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  57. package/src/css/plugin-selector.css +261 -0
  58. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  59. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  60. package/src/css/type-icon.css +116 -0
  61. package/src/{less/viewer.less → css/viewer.css} +112 -146
  62. package/src/rust/components/column_dropdown.rs +229 -119
  63. package/src/rust/components/column_selector/active_column.rs +81 -62
  64. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  65. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  66. package/src/rust/components/column_selector/config_selector.rs +374 -185
  67. package/src/rust/components/column_selector/empty_column.rs +2 -2
  68. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  69. package/src/rust/components/column_selector/filter_column.rs +37 -26
  70. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  71. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  72. package/src/rust/components/column_selector/pivot_column.rs +21 -10
  73. package/src/rust/components/column_selector/sort_column.rs +23 -13
  74. package/src/rust/components/column_selector.rs +189 -100
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  80. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  81. package/src/rust/components/column_settings_sidebar.rs +91 -53
  82. package/src/rust/components/containers/dragdrop_list.rs +29 -7
  83. package/src/rust/components/containers/scroll_panel.rs +8 -1
  84. package/src/rust/components/containers/select.rs +3 -3
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +3 -2
  87. package/src/rust/components/containers/tab_list.rs +1 -1
  88. package/src/rust/components/copy_dropdown.rs +7 -28
  89. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  90. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  91. package/src/rust/components/datetime_column_style.rs +4 -2
  92. package/src/rust/components/editable_header.rs +7 -4
  93. package/src/rust/components/empty_row.rs +1 -1
  94. package/src/rust/components/export_dropdown.rs +4 -30
  95. package/src/rust/components/expression_editor.rs +19 -10
  96. package/src/rust/components/filter_dropdown.rs +246 -102
  97. package/src/rust/components/font_loader.rs +11 -28
  98. package/src/rust/components/form/code_editor.rs +17 -2
  99. package/src/rust/components/form/color_range_selector.rs +19 -6
  100. package/src/rust/components/form/debug.rs +30 -13
  101. package/src/rust/components/function_dropdown.rs +186 -113
  102. package/src/rust/components/main_panel.rs +71 -89
  103. package/src/rust/components/mod.rs +1 -1
  104. package/src/rust/components/modal.rs +7 -1
  105. package/src/rust/components/number_column_style.rs +22 -7
  106. package/src/rust/components/plugin_selector.rs +34 -92
  107. package/src/rust/components/portal.rs +274 -0
  108. package/src/rust/components/render_warning.rs +72 -123
  109. package/src/rust/components/settings_panel.rs +115 -11
  110. package/src/rust/components/status_bar.rs +222 -98
  111. package/src/rust/components/status_bar_counter.rs +8 -20
  112. package/src/rust/components/status_indicator.rs +64 -111
  113. package/src/rust/components/string_column_style.rs +2 -2
  114. package/src/rust/components/style/style_cache.rs +5 -1
  115. package/src/rust/components/viewer.rs +391 -39
  116. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  117. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  118. package/src/rust/custom_elements/mod.rs +0 -7
  119. package/src/rust/custom_elements/modal.rs +7 -103
  120. package/src/rust/custom_elements/viewer.rs +99 -35
  121. package/src/rust/custom_events.rs +23 -2
  122. package/src/rust/dragdrop.rs +149 -10
  123. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  124. package/src/rust/js/plugin.rs +20 -1
  125. package/src/rust/lib.rs +5 -4
  126. package/src/rust/presentation/props.rs +39 -0
  127. package/src/rust/presentation/sheets.rs +3 -3
  128. package/src/rust/presentation.rs +44 -8
  129. package/src/rust/renderer/limits.rs +32 -3
  130. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  131. package/src/rust/renderer/registry.rs +8 -1
  132. package/src/rust/renderer.rs +83 -9
  133. package/src/rust/session/column_defaults_update.rs +18 -0
  134. package/src/rust/session/metadata.rs +23 -2
  135. package/src/rust/session/props.rs +178 -0
  136. package/src/rust/session/replace_expression_update.rs +1 -0
  137. package/src/rust/session.rs +124 -117
  138. package/src/rust/tasks/column_locator.rs +133 -0
  139. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  140. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  141. package/src/rust/{model → tasks}/eject.rs +2 -2
  142. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  143. package/src/rust/{model → tasks}/intersection_observer.rs +22 -4
  144. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  145. package/src/rust/tasks/mod.rs +52 -0
  146. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  147. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  148. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  149. package/src/rust/tasks/structural.rs +53 -0
  150. package/src/rust/utils/mod.rs +4 -0
  151. package/src/rust/utils/modal_position.rs +110 -0
  152. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  153. package/src/rust/utils/pubsub.rs +11 -1
  154. package/src/svg/bg-pattern.png +0 -0
  155. package/src/svg/close-icon.svg +1 -1
  156. package/src/svg/expression.svg +1 -1
  157. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  158. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  159. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  160. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  161. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  162. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  163. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  164. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  165. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  166. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  167. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  168. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  169. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  171. package/src/svg/radio-hover.svg +1 -1
  172. package/src/svg/radio-off.svg +1 -1
  173. package/src/svg/radio-on.svg +1 -1
  174. package/src/themes/botanical.css +157 -0
  175. package/src/themes/defaults.css +139 -0
  176. package/src/themes/dracula.css +233 -0
  177. package/src/themes/gruvbox-dark.css +255 -0
  178. package/src/themes/gruvbox.css +134 -0
  179. package/src/themes/icons.css +124 -0
  180. package/src/themes/intl/de.css +102 -0
  181. package/src/themes/intl/es.css +102 -0
  182. package/src/themes/intl/fr.css +102 -0
  183. package/src/themes/intl/ja.css +102 -0
  184. package/src/themes/intl/pt.css +102 -0
  185. package/src/themes/intl/zh.css +102 -0
  186. package/src/themes/intl.css +102 -0
  187. package/src/themes/monokai.css +233 -0
  188. package/src/themes/pro-dark.css +158 -0
  189. package/src/themes/{themes.less → pro.css} +17 -20
  190. package/src/themes/solarized-dark.css +135 -0
  191. package/src/themes/solarized.css +95 -0
  192. package/src/themes/themes.css +22 -0
  193. package/src/themes/vaporwave.css +256 -0
  194. package/src/ts/extensions.ts +73 -2
  195. package/src/ts/perspective-viewer.ts +1 -0
  196. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  197. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
  198. package/tsconfig.json +1 -0
  199. package/dist/css/variables.css +0 -0
  200. package/src/less/column-dropdown.less +0 -95
  201. package/src/less/config-selector.less +0 -363
  202. package/src/less/dom/checkbox.less +0 -100
  203. package/src/less/plugin-selector.less +0 -183
  204. package/src/less/type-icon.less +0 -68
  205. package/src/rust/components/error_message.rs +0 -56
  206. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  207. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  208. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  209. package/src/rust/model/column_locator.rs +0 -82
  210. package/src/rust/model/is_invalid_drop.rs +0 -36
  211. package/src/rust/model/mod.rs +0 -100
  212. package/src/rust/model/reset_all.rs +0 -38
  213. package/src/rust/model/structural.rs +0 -244
  214. package/src/themes/dracula.less +0 -101
  215. package/src/themes/gruvbox-dark.less +0 -116
  216. package/src/themes/gruvbox.less +0 -152
  217. package/src/themes/icons.less +0 -130
  218. package/src/themes/intl/de.less +0 -102
  219. package/src/themes/intl/es.less +0 -102
  220. package/src/themes/intl/fr.less +0 -102
  221. package/src/themes/intl/ja.less +0 -102
  222. package/src/themes/intl/pt.less +0 -102
  223. package/src/themes/intl/zh.less +0 -102
  224. package/src/themes/intl.less +0 -102
  225. package/src/themes/monokai.less +0 -107
  226. package/src/themes/pro-dark.less +0 -147
  227. package/src/themes/pro.less +0 -186
  228. package/src/themes/solarized-dark.less +0 -78
  229. package/src/themes/solarized.less +0 -102
  230. package/src/themes/vaporwave.less +0 -145
  231. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  232. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  233. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  234. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  235. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  236. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  237. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  238. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  239. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  240. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -31,14 +31,25 @@ use crate::config::*;
31
31
  use crate::custom_events::*;
32
32
  use crate::dragdrop::*;
33
33
  use crate::js::*;
34
- use crate::model::*;
35
34
  use crate::presentation::*;
36
35
  use crate::renderer::*;
37
36
  use crate::root::Root;
38
- use crate::session::{ResetOptions, Session};
37
+ use crate::session::{ResetOptions, Session, TableLoadState};
38
+ use crate::tasks::*;
39
39
  use crate::utils::*;
40
40
  use crate::*;
41
41
 
42
+ #[derive(serde::Deserialize, Default)]
43
+ struct ResizeOptions {
44
+ dimensions: Option<ResizeDimensions>,
45
+ }
46
+
47
+ #[derive(serde::Deserialize, Clone, Copy)]
48
+ struct ResizeDimensions {
49
+ width: f64,
50
+ height: f64,
51
+ }
52
+
42
53
  /// The `<perspective-viewer>` custom element.
43
54
  ///
44
55
  /// # JavaScript Examples
@@ -63,7 +74,7 @@ use crate::*;
63
74
  /// await viewer.load(worker);
64
75
  /// await viewer.restore({table: "table_one"});
65
76
  /// ```
66
- #[derive(Clone, PerspectiveProperties!)]
77
+ #[derive(Clone)]
67
78
  #[wasm_bindgen]
68
79
  pub struct PerspectiveViewerElement {
69
80
  elem: HtmlElement,
@@ -77,9 +88,41 @@ pub struct PerspectiveViewerElement {
77
88
  _subscriptions: Rc<[Subscription; 2]>,
78
89
  }
79
90
 
91
+ impl HasCustomEvents for PerspectiveViewerElement {
92
+ fn custom_events(&self) -> &CustomEvents {
93
+ &self.custom_events
94
+ }
95
+ }
96
+
97
+ impl HasPresentation for PerspectiveViewerElement {
98
+ fn presentation(&self) -> &Presentation {
99
+ &self.presentation
100
+ }
101
+ }
102
+
103
+ impl HasRenderer for PerspectiveViewerElement {
104
+ fn renderer(&self) -> &Renderer {
105
+ &self.renderer
106
+ }
107
+ }
108
+
109
+ impl HasSession for PerspectiveViewerElement {
110
+ fn session(&self) -> &Session {
111
+ &self.session
112
+ }
113
+ }
114
+
115
+ impl StateProvider for PerspectiveViewerElement {
116
+ type State = PerspectiveViewerElement;
117
+
118
+ fn clone_state(&self) -> Self::State {
119
+ self.clone()
120
+ }
121
+ }
122
+
80
123
  impl CustomElementMetadata for PerspectiveViewerElement {
81
124
  const CUSTOM_ELEMENT_NAME: &'static str = "perspective-viewer";
82
- const STATICS: &'static [&'static str] = ["registerPlugin", "getExprTKCommands"].as_slice();
125
+ const STATICS: &'static [&'static str] = ["registerPlugin"].as_slice();
83
126
  }
84
127
 
85
128
  #[wasm_bindgen]
@@ -109,7 +152,7 @@ impl PerspectiveViewerElement {
109
152
  session: session.clone(),
110
153
  renderer: renderer.clone(),
111
154
  presentation: presentation.clone(),
112
- dragdrop: DragDrop::default(),
155
+ dragdrop: DragDrop::new(&elem),
113
156
  custom_events: custom_events.clone(),
114
157
  });
115
158
 
@@ -136,7 +179,9 @@ impl PerspectiveViewerElement {
136
179
  move |_| ApiFuture::spawn(state.delete_all(&root))
137
180
  });
138
181
 
139
- let resize_handle = ResizeObserverHandle::new(&elem, &renderer, &session, &root);
182
+ let resize_handle =
183
+ ResizeObserverHandle::new(&elem, &renderer, &session, &presentation, &root);
184
+
140
185
  let intersect_handle =
141
186
  IntersectionObserverHandle::new(&elem, &presentation, &session, &renderer);
142
187
 
@@ -241,28 +286,18 @@ impl PerspectiveViewerElement {
241
286
  .unwrap_or_else(|_| js_sys::Promise::resolve(&table));
242
287
 
243
288
  let _plugin = self.renderer.get_active_plugin()?;
244
- let task = self.session.reset(ResetOptions {
289
+ let reset_task = self.session.reset(ResetOptions {
245
290
  config: true,
246
291
  expressions: true,
247
292
  stats: true,
248
- ..ResetOptions::default()
293
+ table: Some(session::TableIntermediateState::Reloaded),
249
294
  });
250
295
 
251
- let mut config = ViewConfigUpdate {
252
- columns: Some(self.session.get_view_config().columns.clone()),
253
- ..ViewConfigUpdate::default()
254
- };
255
-
256
- let metadata = self.renderer.metadata();
257
- self.session
258
- .set_update_column_defaults(&mut config, &metadata);
259
- self.session.update_view_config(config)?;
260
-
261
296
  clone!(self.renderer, self.session);
262
297
  Ok(ApiFuture::new_throttled(async move {
263
298
  let task = async {
264
299
  // Ignore this error, which is blown away by the table anyway.
265
- let _ = task.await;
300
+ let _ = reset_task.await;
266
301
  let jstable = JsFuture::from(promise)
267
302
  .await
268
303
  .map_err(|x| apierror!(TableError(x)))?;
@@ -326,7 +361,7 @@ impl PerspectiveViewerElement {
326
361
  /// await viewer.delete();
327
362
  /// ```
328
363
  pub fn delete(self) -> ApiFuture<()> {
329
- self.delete_all(self.root())
364
+ self.delete_all(&self.root)
330
365
  }
331
366
 
332
367
  /// Restart this `<perspective-viewer>` to its initial state, before
@@ -336,7 +371,7 @@ impl PerspectiveViewerElement {
336
371
  /// again, or alternatively `Self::delete` if this viewer is no longer
337
372
  /// needed.
338
373
  pub fn eject(&mut self) -> ApiFuture<()> {
339
- if self.session.has_table() {
374
+ if matches!(self.session.has_table(), Some(TableLoadState::Loaded)) {
340
375
  let mut state = Self::new_from_shadow(
341
376
  self.elem.clone(),
342
377
  self.elem.shadow_root().unwrap().unchecked_into(),
@@ -540,11 +575,26 @@ impl PerspectiveViewerElement {
540
575
  PerspectiveViewerMsg::ToggleSettingsComplete(settings, sender),
541
576
  );
542
577
 
578
+ let task = if let OptionalUpdate::Update(_) = &decoded_update.table {
579
+ Some(this.session.reset(ResetOptions {
580
+ config: true,
581
+ expressions: true,
582
+ stats: true,
583
+ ..ResetOptions::default()
584
+ }))
585
+ } else {
586
+ None
587
+ };
588
+
543
589
  let result = this
544
590
  .restore_and_render(decoded_update.clone(), {
545
591
  clone!(this, decoded_update.table);
546
592
  async move {
547
593
  if let OptionalUpdate::Update(name) = table {
594
+ if let Some(task) = task {
595
+ task.await?;
596
+ }
597
+
548
598
  this.session.set_table(name).await?;
549
599
  this.session
550
600
  .update_column_defaults(&this.renderer.metadata());
@@ -746,24 +796,25 @@ impl PerspectiveViewerElement {
746
796
  ///
747
797
  /// # Arguments
748
798
  ///
749
- /// - `force` - If [`Self::resize`] is called with `false` or without an
750
- /// argument, and _auto-size_ mode is enabled via [`Self::setAutoSize`],
751
- /// [`Self::resize`] will log a warning and auto-disable auto-size mode.
799
+ /// - `options` - An optional object with the following fields:
800
+ /// - `dimensions` - An optional object `{width, height}` providing
801
+ /// explicit size hints (in pixels) for the plugin container. When
802
+ /// provided, the plugin element will be temporarily sized to these
803
+ /// dimensions during resize, then reset.
752
804
  ///
753
805
  /// # JavaScript Examples
754
806
  ///
755
807
  /// ```javascript
756
- /// await viewer.resize(true)
808
+ /// await viewer.resize()
809
+ /// await viewer.resize({dimensions: {width: 800, height: 600}})
757
810
  /// ```
758
811
  #[wasm_bindgen]
759
- pub fn resize(&self, force: Option<bool>) -> ApiFuture<()> {
760
- if !force.unwrap_or_default() && self.resize_handle.borrow().is_some() {
761
- let msg: JsValue = "`resize(false)` called, disabling auto-size. It can be \
762
- re-enabled with `setAutoSize(true)`."
763
- .into();
764
- web_sys::console::warn_1(&msg);
765
- *self.resize_handle.borrow_mut() = None;
766
- }
812
+ pub fn resize(&self, options: Option<JsValue>) -> ApiFuture<()> {
813
+ let opts: ResizeOptions = options
814
+ .map(|v| v.into_serde_ext())
815
+ .transpose()
816
+ .unwrap_or_default()
817
+ .unwrap_or_default();
767
818
 
768
819
  let state = self.clone_state();
769
820
  ApiFuture::new_throttled(async move {
@@ -771,6 +822,11 @@ impl PerspectiveViewerElement {
771
822
  state
772
823
  .update_and_render(ViewConfigUpdate::default())?
773
824
  .await?;
825
+ } else if let Some(dims) = opts.dimensions {
826
+ state
827
+ .renderer()
828
+ .resize_with_dimensions(dims.width, dims.height)
829
+ .await?;
774
830
  } else {
775
831
  state.renderer().resize().await?;
776
832
  }
@@ -806,6 +862,7 @@ impl PerspectiveViewerElement {
806
862
  &self.elem,
807
863
  &self.renderer,
808
864
  &self.session,
865
+ &self.presentation,
809
866
  &self.root,
810
867
  ));
811
868
  *self.resize_handle.borrow_mut() = handle;
@@ -833,7 +890,7 @@ impl PerspectiveViewerElement {
833
890
  /// viewer.setAutoPause(false);
834
891
  /// ```
835
892
  #[wasm_bindgen]
836
- pub fn setAutoPause(&self, autopause: bool) {
893
+ pub fn setAutoPause(&self, autopause: bool) -> ApiFuture<()> {
837
894
  if autopause {
838
895
  let handle = Some(IntersectionObserverHandle::new(
839
896
  &self.elem,
@@ -845,7 +902,14 @@ impl PerspectiveViewerElement {
845
902
  *self.intersection_handle.borrow_mut() = handle;
846
903
  } else {
847
904
  *self.intersection_handle.borrow_mut() = None;
905
+ if self.session.set_pause(false) {
906
+ return ApiFuture::new(
907
+ self.restore_and_render(ViewerConfigUpdate::default(), async move { Ok(()) }),
908
+ );
909
+ }
848
910
  }
911
+
912
+ ApiFuture::new(async move { Ok(()) })
849
913
  }
850
914
 
851
915
  /// Return a [`perspective_js::JsViewWindow`] for the currently selected
@@ -890,7 +954,7 @@ impl PerspectiveViewerElement {
890
954
  /// # JavaScript Examples
891
955
  ///
892
956
  /// ```javascript
893
- /// viewer.style = "--icon--color: red";
957
+ /// viewer.style = "--psp--color: red";
894
958
  /// await viewer.restyleElement();
895
959
  /// ```
896
960
  #[wasm_bindgen]
@@ -22,10 +22,10 @@ use yew::html::ImplicitClone;
22
22
 
23
23
  use crate::config::*;
24
24
  use crate::js::JsPerspectiveViewerPlugin;
25
- use crate::model::*;
26
25
  use crate::presentation::Presentation;
27
26
  use crate::renderer::*;
28
27
  use crate::session::Session;
28
+ use crate::tasks::*;
29
29
  use crate::utils::*;
30
30
  use crate::*;
31
31
 
@@ -55,7 +55,6 @@ impl Deref for CustomEventsDataRc {
55
55
  }
56
56
  }
57
57
 
58
- #[derive(PerspectiveProperties!)]
59
58
  struct CustomEventsData {
60
59
  elem: HtmlElement,
61
60
  session: Session,
@@ -64,6 +63,24 @@ struct CustomEventsData {
64
63
  last_dispatched: RefCell<Option<ViewerConfig>>,
65
64
  }
66
65
 
66
+ impl HasPresentation for CustomEventsData {
67
+ fn presentation(&self) -> &Presentation {
68
+ &self.presentation
69
+ }
70
+ }
71
+
72
+ impl HasRenderer for CustomEventsData {
73
+ fn renderer(&self) -> &Renderer {
74
+ &self.renderer
75
+ }
76
+ }
77
+
78
+ impl HasSession for CustomEventsData {
79
+ fn session(&self) -> &Session {
80
+ &self.session
81
+ }
82
+ }
83
+
67
84
  impl CustomEvents {
68
85
  pub fn new(
69
86
  elem: &HtmlElement,
@@ -171,6 +188,10 @@ impl CustomEvents {
171
188
  {
172
189
  self.0.0.dispatch_event(name, event)
173
190
  }
191
+
192
+ pub fn dispatch_raw_event(&self, event: &web_sys::CustomEvent) -> ApiResult<bool> {
193
+ self.0.0.elem.dispatch_event(event).map_err(|e| e.into())
194
+ }
174
195
  }
175
196
 
176
197
  impl CustomEventsDataRc {
@@ -10,7 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use std::cell::RefCell;
13
+ use std::cell::{Cell, RefCell};
14
14
  use std::ops::Deref;
15
15
  use std::rc::Rc;
16
16
 
@@ -20,9 +20,18 @@ use web_sys::*;
20
20
  use yew::html::ImplicitClone;
21
21
  use yew::prelude::*;
22
22
 
23
+ use crate::js::{IntersectionObserver, IntersectionObserverEntry};
23
24
  use crate::utils::*;
24
25
  use crate::*;
25
26
 
27
+ /// Value-semantic snapshot of the drag/drop state threaded through the
28
+ /// component tree for visual feedback (drag-highlight CSS classes).
29
+ #[derive(Clone, Debug, PartialEq, Default)]
30
+ pub struct DragDropProps {
31
+ /// Column name currently being dragged, if a drag is in progress.
32
+ pub column: Option<String>,
33
+ }
34
+
26
35
  #[derive(Clone, Debug)]
27
36
  struct DragFrom {
28
37
  column: String,
@@ -49,20 +58,52 @@ impl DragState {
49
58
  }
50
59
  }
51
60
 
52
- #[derive(Default)]
61
+ pub type DragEndCallback = Closure<dyn FnMut(DragEvent)>;
62
+
53
63
  pub struct DragDropState {
54
64
  drag_state: RefCell<DragState>,
55
65
  pub drop_received: PubSub<(String, DragTarget, DragEffect, usize)>,
56
- pub dragstart_received: PubSub<DragEffect>,
57
- pub dragend_received: PubSub<()>,
66
+
67
+ /// Injected callback from the root component, replacing the former
68
+ /// `dragstart_received: PubSub` field.
69
+ pub on_dragstart: RefCell<Option<Callback<DragEffect>>>,
70
+
71
+ /// Injected callback from the root component, replacing the former
72
+ /// `dragend_received: PubSub` field.
73
+ pub on_dragend: RefCell<Option<Callback<()>>>,
74
+
75
+ /// The host `<perspective-viewer>` element, used to attach the fallback
76
+ /// `dragend` listener on a stable DOM node outside the virtual DOM.
77
+ elem: HtmlElement,
78
+
79
+ /// Host-level `dragend` listener closure, stored so it can be removed
80
+ /// when a new drag starts. Attached to `elem` rather than `document`
81
+ /// to keep the listener scoped to this component instance.
82
+ host_dragend: RefCell<Option<DragEndCallback>>,
83
+
84
+ drag_target: RefCell<Option<DragTargetState>>,
58
85
  }
59
86
 
60
87
  /// The `<perspective-viewer>` drag/drop service, which manages drag/drop user
61
88
  /// interactions across components. It is a component-level service, since only
62
89
  /// one drag/drop action can be executed by the user at a time.
63
- #[derive(Clone, Default)]
90
+ #[derive(Clone)]
64
91
  pub struct DragDrop(Rc<DragDropState>);
65
92
 
93
+ impl DragDrop {
94
+ pub fn new(elem: &HtmlElement) -> Self {
95
+ Self(Rc::new(DragDropState {
96
+ drag_state: Default::default(),
97
+ drop_received: Default::default(),
98
+ on_dragstart: Default::default(),
99
+ on_dragend: Default::default(),
100
+ elem: elem.clone(),
101
+ host_dragend: Default::default(),
102
+ drag_target: Default::default(),
103
+ }))
104
+ }
105
+ }
106
+
66
107
  impl Deref for DragDrop {
67
108
  type Target = Rc<DragDropState>;
68
109
 
@@ -80,6 +121,14 @@ impl PartialEq for DragDrop {
80
121
  impl ImplicitClone for DragDrop {}
81
122
 
82
123
  impl DragDrop {
124
+ /// Snapshot the drag state as a [`DragDropProps`] value for threading
125
+ /// through the component tree without PubSub subscriptions.
126
+ pub fn to_props(&self) -> DragDropProps {
127
+ DragDropProps {
128
+ column: self.get_drag_column(),
129
+ }
130
+ }
131
+
83
132
  /// Get the column name currently being drag/dropped.
84
133
  pub fn get_drag_column(&self) -> Option<String> {
85
134
  match *self.drag_state.borrow() {
@@ -110,7 +159,7 @@ impl DragDrop {
110
159
  event.stop_propagation();
111
160
  if let Some(dt) = event.data_transfer() {
112
161
  dt.set_drop_effect("move");
113
- dt.set_data("text/plain", "{}").unwrap();
162
+ // dt.set_data("text/plain", "{}").unwrap();
114
163
  }
115
164
 
116
165
  let original: HtmlElement = event.target().into_apierror()?.unchecked_into();
@@ -129,6 +178,10 @@ impl DragDrop {
129
178
  event.offset_y(),
130
179
  );
131
180
 
181
+ *self.drag_target.borrow_mut() =
182
+ Some(DragTargetState::new(self.elem.clone(), original.clone()));
183
+
184
+ // Drag image does not register correctly unless we wait.
132
185
  ApiFuture::spawn(async move {
133
186
  request_animation_frame().await;
134
187
  original.remove_child(&elem)?;
@@ -152,6 +205,7 @@ impl DragDrop {
152
205
  pub fn notify_drop(&self, event: &DragEvent) {
153
206
  event.prevent_default();
154
207
  event.stop_propagation();
208
+
155
209
  let action = match &*self.drag_state.borrow() {
156
210
  DragState::DragOverInProgress(
157
211
  DragFrom { column, effect },
@@ -160,6 +214,7 @@ impl DragDrop {
160
214
  _ => None,
161
215
  };
162
216
 
217
+ self.drag_target.borrow_mut().take();
163
218
  *self.drag_state.borrow_mut() = DragState::NoDrag;
164
219
  if let Some(action) = action {
165
220
  self.drop_received.emit(action);
@@ -169,10 +224,14 @@ impl DragDrop {
169
224
  /// Start the drag/drop action with the name of the column being dragged.
170
225
  pub fn notify_drag_start(&self, column: String, effect: DragEffect) {
171
226
  *self.drag_state.borrow_mut() = DragState::DragInProgress(DragFrom { column, effect });
172
- let emit = self.dragstart_received.callback();
227
+ self.register_host_dragend();
228
+ let emit = self.on_dragstart.borrow().clone();
173
229
  ApiFuture::spawn(async move {
174
230
  request_animation_frame().await;
175
- emit.emit(effect);
231
+ if let Some(cb) = emit {
232
+ cb.emit(effect);
233
+ }
234
+
176
235
  Ok(())
177
236
  });
178
237
  }
@@ -180,12 +239,38 @@ impl DragDrop {
180
239
  /// End the drag/drop action by resetting the state to default.
181
240
  pub fn notify_drag_end(&self) {
182
241
  if self.drag_state.borrow().is_drag_in_progress() {
242
+ self.drag_target.borrow_mut().take();
183
243
  *self.drag_state.borrow_mut() = DragState::NoDrag;
184
- let emit = self.dragend_received.callback();
185
- emit.emit(());
244
+ if let Some(cb) = self.on_dragend.borrow().as_ref() {
245
+ cb.emit(());
246
+ }
186
247
  }
187
248
  }
188
249
 
250
+ /// Register a `dragend` listener on the host `<perspective-viewer>`
251
+ /// element so that drag-end cleanup fires even when Yew re-renders
252
+ /// remove the original dragged element from the shadow DOM. The host
253
+ /// element is outside the virtual DOM and therefore stable.
254
+ fn register_host_dragend(&self) {
255
+ // Remove any previously registered listener.
256
+ if let Some(prev) = self.host_dragend.borrow_mut().take() {
257
+ let _ = self
258
+ .elem
259
+ .remove_event_listener_with_callback("dragend", prev.as_ref().unchecked_ref());
260
+ }
261
+
262
+ let this = self.clone();
263
+ let closure = Closure::wrap(Box::new(move |_event: DragEvent| {
264
+ this.notify_drag_end();
265
+ }) as Box<dyn FnMut(DragEvent)>);
266
+
267
+ self.elem
268
+ .add_event_listener_with_callback("dragend", closure.as_ref().unchecked_ref())
269
+ .unwrap();
270
+
271
+ *self.host_dragend.borrow_mut() = Some(closure);
272
+ }
273
+
189
274
  /// Leave the `action` zone.
190
275
  pub fn notify_drag_leave(&self, drag_target: DragTarget) {
191
276
  let reset = match *self.drag_state.borrow() {
@@ -340,3 +425,57 @@ impl DragDropContainer {
340
425
  }
341
426
  }
342
427
  }
428
+
429
+ /// A really, really unfortunate hack that is needed to guarantee that `dragend`
430
+ /// is called even under aggressive DOM mutation after `dragstart` is fired.
431
+ struct DragTargetState {
432
+ target: HtmlElement,
433
+ shadow_root: ShadowRoot,
434
+ alive: Rc<Cell<bool>>,
435
+ observer: IntersectionObserver,
436
+ }
437
+
438
+ impl DragTargetState {
439
+ fn new(host: HtmlElement, target: HtmlElement) -> Self {
440
+ let shadow_root = host.shadow_root().unwrap();
441
+ let alive = Rc::new(Cell::new(true));
442
+ let observer = IntersectionObserver::new(
443
+ &Closure::<dyn FnMut(js_sys::Array)>::new({
444
+ clone!(target, shadow_root, alive);
445
+ move |records: js_sys::Array| {
446
+ if !alive.get() {
447
+ return;
448
+ }
449
+
450
+ for record in records.iter() {
451
+ let record: IntersectionObserverEntry = record.unchecked_into();
452
+ if !record.is_intersecting() {
453
+ shadow_root.append_child(&target).unwrap();
454
+ return;
455
+ }
456
+ }
457
+ }
458
+ })
459
+ .into_js_value()
460
+ .unchecked_into(),
461
+ );
462
+
463
+ observer.observe(target.as_ref());
464
+ Self {
465
+ target,
466
+ shadow_root,
467
+ alive,
468
+ observer,
469
+ }
470
+ }
471
+ }
472
+
473
+ impl Drop for DragTargetState {
474
+ fn drop(&mut self) {
475
+ self.alive.set(false);
476
+ self.observer.unobserve(&self.target);
477
+ if self.target.is_connected() {
478
+ let _ = self.shadow_root.remove_child(&self.target);
479
+ }
480
+ }
481
+ }
@@ -10,17 +10,19 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- :host {
14
- .scroll-panel-container {
15
- width: 100%;
16
- }
13
+ //! Engine-handle types for the four major state singletons.
14
+ //!
15
+ //! These are the async-machinery halves of the engine/value split described in
16
+ //! the props modules. They own JS object handles, draw locks, async
17
+ //! subscriptions, and PubSub channels — i.e. anything that cannot be cheaply
18
+ //! cloned into a plain `PartialEq` prop.
19
+ //!
20
+ //! **Current status (Step 3 of the migration):** Each `*Engine` type is a thin
21
+ //! type alias for the existing `Rc<*Handle>` wrapper. They will be replaced
22
+ //! with true struct types in later migration steps once the component tree has
23
+ //! been updated to consume value-semantic props instead of the old handles.
17
24
 
18
- .scroll-panel-auto-width {
19
- height: 1px;
20
- margin-top: -1px;
21
- }
22
-
23
- .scroll-panel-content {
24
- position: relative;
25
- }
26
- }
25
+ pub use crate::dragdrop::DragDrop as DragDropEngine;
26
+ pub use crate::presentation::Presentation as PresentationEngine;
27
+ pub use crate::renderer::Renderer as RendererEngine;
28
+ pub use crate::session::Session as SessionEngine;
@@ -10,6 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ use perspective_client::config::GroupRollupMode;
13
14
  use perspective_js::utils::*;
14
15
  use serde::*;
15
16
  use wasm_bindgen::prelude::*;
@@ -61,6 +62,9 @@ extern "C" {
61
62
  #[wasm_bindgen(method, getter)]
62
63
  pub fn priority(this: &JsPerspectiveViewerPlugin) -> Option<i32>;
63
64
 
65
+ #[wasm_bindgen(method, getter)]
66
+ pub fn group_rollups(this: &JsPerspectiveViewerPlugin) -> Option<js_sys::Array>;
67
+
64
68
  /// Don't call this method directly. Instead, call the corresponding method on the PluginColumnStyles model.
65
69
  #[wasm_bindgen(method, catch)]
66
70
  pub fn can_render_column_styles(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>) -> ApiResult<bool>;
@@ -137,7 +141,7 @@ impl ColumnSelectMode {
137
141
  }
138
142
  }
139
143
 
140
- #[derive(Clone, Debug, Default)]
144
+ #[derive(Clone, Debug, Default, PartialEq)]
141
145
  pub struct ViewConfigRequirements {
142
146
  pub min: Option<usize>,
143
147
  pub names: Option<Vec<String>>,
@@ -146,6 +150,7 @@ pub struct ViewConfigRequirements {
146
150
  pub max_cells: Option<usize>,
147
151
  pub name: String,
148
152
  pub render_warning: bool,
153
+ group_rollups: Option<Vec<GroupRollupMode>>,
149
154
  }
150
155
 
151
156
  impl ViewConfigRequirements {
@@ -155,6 +160,17 @@ impl ViewConfigRequirements {
155
160
  .map(|x| index < x.len() - 1)
156
161
  .unwrap_or(false)
157
162
  }
163
+
164
+ pub fn get_group_rollups(&self, rollup_features: &[GroupRollupMode]) -> Vec<GroupRollupMode> {
165
+ self.group_rollups
166
+ .clone()
167
+ .map(|x| {
168
+ x.into_iter()
169
+ .filter(|y| rollup_features.is_empty() || rollup_features.contains(y))
170
+ .collect()
171
+ })
172
+ .unwrap_or_default()
173
+ }
158
174
  }
159
175
 
160
176
  impl JsPerspectiveViewerPlugin {
@@ -169,6 +185,9 @@ impl JsPerspectiveViewerPlugin {
169
185
  max_cells: self.max_cells(),
170
186
  name: self.name(),
171
187
  render_warning: self.render_warning().unwrap_or(true),
188
+ group_rollups: self
189
+ .group_rollups()
190
+ .map(|x| x.into_serde_ext::<Vec<GroupRollupMode>>().unwrap()),
172
191
  })
173
192
  }
174
193
  }