@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
@@ -0,0 +1,274 @@
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
+ use std::cell::Cell;
14
+ use std::rc::Rc;
15
+
16
+ use perspective_js::utils::global;
17
+ use wasm_bindgen::JsCast;
18
+ use wasm_bindgen::prelude::*;
19
+ use web_sys::*;
20
+ use yew::prelude::*;
21
+
22
+ use crate::components::modal::ModalOrientation;
23
+ use crate::components::style::StyleProvider;
24
+ use crate::utils::*;
25
+
26
+ #[derive(Properties, PartialEq)]
27
+ pub struct PortalModalProps {
28
+ pub children: Children,
29
+
30
+ /// The element to position relative to. `None` means closed.
31
+ pub target: Option<HtmlElement>,
32
+
33
+ /// Whether the portal manages its own focus and closes on blur.
34
+ #[prop_or(true)]
35
+ pub own_focus: bool,
36
+
37
+ /// Called when the portal closes (blur, etc).
38
+ #[prop_or_default]
39
+ pub on_close: Callback<()>,
40
+
41
+ pub tag_name: &'static str,
42
+
43
+ pub theme: String,
44
+ }
45
+
46
+ pub enum PortalModalMsg {
47
+ Reposition,
48
+ }
49
+
50
+ pub struct PortalModal {
51
+ host: HtmlElement,
52
+ shadow_root: Element,
53
+ top: f64,
54
+ left: f64,
55
+ visible: bool,
56
+ rev_vert: ModalOrientation,
57
+ anchor: Rc<Cell<ModalAnchor>>,
58
+ _blur_closure: Option<Closure<dyn FnMut(FocusEvent)>>,
59
+ }
60
+
61
+ impl PortalModal {
62
+ fn attach_to_body(&self) {
63
+ if !self.host.is_connected() {
64
+ let _ = global::body().append_child(&self.host);
65
+ }
66
+ }
67
+
68
+ fn detach_from_body(&mut self) {
69
+ if self.host.is_connected() {
70
+ let _ = global::body().remove_child(&self.host);
71
+ }
72
+
73
+ if let Some(closure) = self._blur_closure.as_ref() {
74
+ self.host
75
+ .remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
76
+ .unwrap()
77
+ }
78
+
79
+ self._blur_closure = None;
80
+ }
81
+
82
+ fn position_against_target(&mut self, target: &HtmlElement) {
83
+ let target_rect = target.get_bounding_client_rect();
84
+ let height = target_rect.height();
85
+ let width = target_rect.width();
86
+ let top = target_rect.top();
87
+ let left = target_rect.left();
88
+
89
+ if !self.visible {
90
+ // First pass: position at default anchor, invisible
91
+ self.top = top + height - 1.0;
92
+ self.left = left;
93
+ self.visible = false;
94
+ } else {
95
+ // Second pass: compute actual anchor and reposition
96
+ let anchor = calc_relative_position(&self.host, top, left, height, width);
97
+ self.anchor.set(anchor);
98
+ let modal_rect = self.host.get_bounding_client_rect();
99
+ let (new_top, new_left) = calc_anchor_position(anchor, &target_rect, &modal_rect);
100
+ self.top = new_top;
101
+ self.left = new_left;
102
+ self.rev_vert.set(anchor.is_rev_vert());
103
+ }
104
+ }
105
+
106
+ fn setup_blur_handler(&mut self, ctx: &Context<Self>) {
107
+ let on_close = {
108
+ let target = ctx.props().target.clone();
109
+ ctx.props().on_close.reform(move |_| {
110
+ if let Some(target) = &target {
111
+ target.class_list().remove_1("modal-target").unwrap();
112
+ }
113
+ })
114
+ };
115
+
116
+ let closure = Closure::wrap(Box::new(move |_: FocusEvent| {
117
+ on_close.emit(());
118
+ }) as Box<dyn FnMut(FocusEvent)>);
119
+
120
+ let _ = self
121
+ .host
122
+ .add_event_listener_with_callback("blur", closure.as_ref().unchecked_ref());
123
+
124
+ self._blur_closure = Some(closure);
125
+ }
126
+ }
127
+
128
+ impl Component for PortalModal {
129
+ type Message = PortalModalMsg;
130
+ type Properties = PortalModalProps;
131
+
132
+ fn create(ctx: &Context<Self>) -> Self {
133
+ let host: HtmlElement = global::document()
134
+ .create_element(ctx.props().tag_name)
135
+ .unwrap()
136
+ .unchecked_into();
137
+
138
+ host.style().set_property("position", "fixed").unwrap();
139
+ host.style().set_property("z-index", "10000").unwrap();
140
+ let init = ShadowRootInit::new(ShadowRootMode::Open);
141
+ let shadow_root = if let Some(elem) = host.shadow_root() {
142
+ elem
143
+ } else {
144
+ host.attach_shadow(&init).unwrap()
145
+ }
146
+ .unchecked_into::<Element>();
147
+
148
+ Self {
149
+ host,
150
+ shadow_root,
151
+ top: 0.0,
152
+ left: 0.0,
153
+ visible: false,
154
+ rev_vert: Default::default(),
155
+ anchor: Default::default(),
156
+ _blur_closure: None,
157
+ }
158
+ }
159
+
160
+ fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
161
+ match msg {
162
+ PortalModalMsg::Reposition => {
163
+ self.visible = true;
164
+ true
165
+ },
166
+ }
167
+ }
168
+
169
+ fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
170
+ let new_target = &ctx.props().target;
171
+ let old_target = &old_props.target;
172
+
173
+ match (old_target, new_target, self._blur_closure.as_ref()) {
174
+ (None, Some(_), Some(closure)) => {
175
+ self.visible = false;
176
+ self.host
177
+ .remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
178
+ .unwrap();
179
+
180
+ self._blur_closure = None;
181
+ },
182
+ (None, Some(_), None) => {
183
+ self.visible = false;
184
+ self._blur_closure = None;
185
+ },
186
+ (Some(_), None, _) => {
187
+ self.detach_from_body();
188
+ return true;
189
+ },
190
+ _ => {},
191
+ }
192
+
193
+ true
194
+ }
195
+
196
+ fn view(&self, ctx: &Context<Self>) -> Html {
197
+ let target = &ctx.props().target;
198
+ if target.is_none() {
199
+ return html! {};
200
+ }
201
+
202
+ let opacity = if self.visible { "" } else { ";opacity:0" };
203
+ let css = format!(
204
+ ":host{{top:{}px;left:{}px{}}}",
205
+ self.top, self.left, opacity
206
+ );
207
+
208
+ let portal_content = html! {
209
+ <>
210
+ <style>{ css }</style>
211
+ <ContextProvider<ModalOrientation> context={self.rev_vert.clone()}>
212
+ <StyleProvider root={self.host.clone()}>
213
+ { for ctx.props().children.iter() }
214
+ </StyleProvider>
215
+ </ContextProvider<ModalOrientation>>
216
+ </>
217
+ };
218
+
219
+ yew::create_portal(portal_content, self.shadow_root.clone())
220
+ }
221
+
222
+ fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
223
+ if let Some(target) = &ctx.props().target {
224
+ if !self.host.is_connected() {
225
+ let theme = ctx.props().theme.as_str();
226
+ self.host.set_attribute("theme", theme).unwrap();
227
+
228
+ // First render with a target: attach to body, position invisible
229
+ self.position_against_target(target);
230
+ self.attach_to_body();
231
+
232
+ // Propagate theme from target
233
+ if let Some(theme) = target.get_attribute("theme") {
234
+ let _ = self.host.set_attribute("theme", &theme);
235
+ }
236
+
237
+ target.class_list().add_1("modal-target").unwrap();
238
+
239
+ if ctx.props().own_focus {
240
+ self.host.set_attribute("tabindex", "0").unwrap();
241
+ self.setup_blur_handler(ctx);
242
+ }
243
+
244
+ // Schedule second positioning pass
245
+ let link = ctx.link().clone();
246
+ wasm_bindgen_futures::spawn_local(async move {
247
+ request_animation_frame().await;
248
+ link.send_message(PortalModalMsg::Reposition);
249
+ });
250
+ } else if self.visible {
251
+ // Second pass: reposition with correct anchor
252
+ self.position_against_target(target);
253
+
254
+ if ctx.props().own_focus && self._blur_closure.is_some() {
255
+ let _ = self.host.focus();
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ fn destroy(&mut self, ctx: &Context<Self>) {
262
+ if let Some(target) = &ctx.props().target {
263
+ target.class_list().remove_1("modal-target").unwrap();
264
+ if target.get_attribute("theme").is_some() {
265
+ let _ = self.host.remove_attribute("theme");
266
+ }
267
+
268
+ let event = CustomEvent::new("-perspective-close-expression").unwrap();
269
+ let _ = target.dispatch_event(&event);
270
+ }
271
+
272
+ self.detach_from_body();
273
+ }
274
+ }
@@ -13,144 +13,93 @@
13
13
  use yew::prelude::*;
14
14
 
15
15
  use super::style::LocalStyle;
16
- use crate::model::*;
17
- use crate::renderer::*;
18
- use crate::session::*;
19
- use crate::*;
16
+ use crate::css;
17
+ use crate::renderer::limits::RenderLimits;
20
18
 
21
- #[derive(Properties, PerspectiveProperties!)]
19
+ #[derive(Properties, PartialEq)]
22
20
  pub struct RenderWarningProps {
23
- // Current dimensions
24
- pub dimensions: Option<(usize, usize, Option<usize>, Option<usize>)>,
21
+ pub dimensions: Option<RenderLimits>,
25
22
 
26
- // State
27
- pub renderer: Renderer,
28
- pub session: Session,
23
+ /// Called when the user clicks "Render all points". The parent disables
24
+ /// the render warning on the active plugin and re-draws.
25
+ pub on_dismiss: Callback<()>,
29
26
  }
30
27
 
31
- impl PartialEq for RenderWarningProps {
32
- fn eq(&self, other: &Self) -> bool {
33
- self.dimensions == other.dimensions
34
- }
35
- }
36
-
37
- pub enum RenderWarningMsg {
38
- DismissWarning,
39
- }
40
-
41
- pub struct RenderWarning {
42
- col_warn: Option<(usize, usize)>,
43
- row_warn: Option<(usize, usize)>,
44
- }
45
-
46
- impl RenderWarning {
47
- fn update_warnings(&mut self, ctx: &Context<Self>) {
48
- if let Some((num_cols, num_rows, max_cols, max_rows)) = ctx.props().dimensions {
49
- let count = num_cols * num_rows;
50
- if max_cols.is_some_and(|x| x < num_cols) {
51
- self.col_warn = Some((max_cols.unwrap(), num_cols));
52
- } else {
53
- self.col_warn = None;
54
- }
55
-
56
- if max_rows.is_some_and(|x| x < num_rows) {
57
- self.row_warn = Some((num_cols * max_rows.unwrap(), count));
58
- } else {
59
- self.row_warn = None;
60
- }
28
+ #[function_component(RenderWarning)]
29
+ pub fn render_warning(props: &RenderWarningProps) -> Html {
30
+ let dimensions = props.dimensions;
31
+ let (col_warn, row_warn) = if let Some(limits) = dimensions {
32
+ let col_warn = if limits.max_cols.is_some_and(|x| x < limits.num_cols) {
33
+ Some((limits.max_cols.unwrap(), limits.num_cols))
61
34
  } else {
62
- self.col_warn = None;
63
- self.row_warn = None;
64
- }
65
- }
66
- }
67
-
68
- impl Component for RenderWarning {
69
- type Message = RenderWarningMsg;
70
- type Properties = RenderWarningProps;
71
-
72
- fn create(ctx: &Context<Self>) -> Self {
73
- let mut elem = Self {
74
- col_warn: None,
75
- row_warn: None,
35
+ None
76
36
  };
77
37
 
78
- elem.update_warnings(ctx);
79
- elem
80
- }
38
+ let row_warn = if limits.max_rows.is_some_and(|x| x < limits.num_rows) {
39
+ Some((
40
+ limits.num_cols * limits.max_rows.unwrap(),
41
+ limits.num_cols * limits.num_rows,
42
+ ))
43
+ } else {
44
+ None
45
+ };
81
46
 
82
- fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
83
- match msg {
84
- RenderWarningMsg::DismissWarning => {
85
- let state = ctx.props().clone_state();
86
- ApiFuture::spawn(async move {
87
- state.renderer().disable_active_plugin_render_warning();
88
- let view_task = state.session().get_view();
89
- state.renderer().update(view_task).await
90
- });
47
+ (col_warn, row_warn)
48
+ } else {
49
+ (None, None)
50
+ };
51
+
52
+ if col_warn.is_some() || row_warn.is_some() {
53
+ let warning = match (col_warn, row_warn) {
54
+ (Some((x, y)), Some((a, b))) => html! {
55
+ <span style="white-space: nowrap">
56
+ { "Rendering" }
57
+ { render_pair(x, y) }
58
+ { "of columns and" }
59
+ { render_pair(a, b) }
60
+ { "of points." }
61
+ </span>
62
+ },
63
+ (Some((x, y)), None) => html! {
64
+ <span style="white-space: nowrap">
65
+ { "Rendering" }
66
+ { render_pair(x, y) }
67
+ { "of columns." }
68
+ </span>
69
+ },
70
+ (None, Some((x, y))) => html! {
71
+ <span style="white-space: nowrap">
72
+ { "Rendering" }
73
+ { render_pair(x, y) }
74
+ { "of points." }
75
+ </span>
91
76
  },
77
+ _ => html! { <div /> },
92
78
  };
93
- true
94
- }
95
-
96
- fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
97
- self.update_warnings(ctx);
98
- true
99
- }
100
79
 
101
- fn view(&self, ctx: &Context<Self>) -> Html {
102
- if self.col_warn.is_some() || self.row_warn.is_some() {
103
- let warning = match (self.col_warn, self.row_warn) {
104
- (Some((x, y)), Some((a, b))) => html! {
105
- <span style="white-space: nowrap">
106
- { "Rendering" }
107
- { render_pair(x, y) }
108
- { "of columns and" }
109
- { render_pair(a, b) }
110
- { "of points." }
80
+ let on_dismiss = props.on_dismiss.clone();
81
+ let onclick = Callback::from(move |_: MouseEvent| on_dismiss.emit(()));
82
+ html! {
83
+ <>
84
+ <LocalStyle href={css!("render-warning")} />
85
+ <div
86
+ class="plugin_information plugin_information--warning"
87
+ id="plugin_information--size"
88
+ >
89
+ <span class="plugin_information__icon" />
90
+ <span class="plugin_information__text" id="plugin_information_count">
91
+ { warning }
111
92
  </span>
112
- },
113
- (Some((x, y)), None) => html! {
114
- <span style="white-space: nowrap">
115
- { "Rendering" }
116
- { render_pair(x, y) }
117
- { "of columns." }
118
- </span>
119
- },
120
- (None, Some((x, y))) => html! {
121
- <span style="white-space: nowrap">
122
- { "Rendering" }
123
- { render_pair(x, y) }
124
- { "of points." }
125
- </span>
126
- },
127
- _ => html! { <div /> },
128
- };
129
-
130
- let onclick = ctx.link().callback(|_| RenderWarningMsg::DismissWarning);
131
-
132
- html! {
133
- <>
134
- <LocalStyle href={css!("render-warning")} />
135
- <div
136
- class="plugin_information plugin_information--warning"
137
- id="plugin_information--size"
138
- >
139
- <span class="plugin_information__icon" />
140
- <span class="plugin_information__text" id="plugin_information_count">
141
- { warning }
93
+ <span class="plugin_information__actions">
94
+ <span class="plugin_information__action" onmousedown={onclick}>
95
+ { "Render all points" }
142
96
  </span>
143
- <span class="plugin_information__actions">
144
- <span class="plugin_information__action" onmousedown={onclick}>
145
- { "Render all points" }
146
- </span>
147
- </span>
148
- </div>
149
- </>
150
- }
151
- } else {
152
- html! {}
97
+ </span>
98
+ </div>
99
+ </>
153
100
  }
101
+ } else {
102
+ html! {}
154
103
  }
155
104
  }
156
105
 
@@ -12,20 +12,23 @@
12
12
 
13
13
  use std::rc::Rc;
14
14
 
15
+ use perspective_client::config::{ViewConfig, ViewConfigUpdate};
16
+ use perspective_js::utils::ApiFuture;
15
17
  use yew::prelude::*;
16
18
 
17
19
  use super::column_selector::ColumnSelector;
18
20
  use super::plugin_selector::PluginSelector;
19
- use crate::PerspectiveProperties;
20
21
  use crate::components::containers::sidebar_close_button::SidebarCloseButton;
22
+ use crate::config::PluginUpdate;
21
23
  use crate::dragdrop::*;
22
- use crate::model::*;
23
- use crate::presentation::{ColumnLocator, Presentation};
24
+ use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
24
25
  use crate::renderer::*;
26
+ use crate::session::column_defaults_update::*;
25
27
  use crate::session::*;
28
+ use crate::tasks::can_render_column_styles;
26
29
  use crate::utils::*;
27
30
 
28
- #[derive(Clone, Properties, PerspectiveProperties!)]
31
+ #[derive(Clone, Properties)]
29
32
  pub struct SettingsPanelProps {
30
33
  pub on_close: Callback<()>,
31
34
  pub on_resize: Rc<PubSub<()>>,
@@ -33,6 +36,25 @@ pub struct SettingsPanelProps {
33
36
  pub on_debug: Callback<()>,
34
37
  pub is_debug: bool,
35
38
 
39
+ /// Value props threaded from the root's `RendererProps` / `SessionProps`.
40
+ pub plugin_name: Option<String>,
41
+ pub available_plugins: PtrEqRc<Vec<String>>,
42
+ pub has_table: Option<TableLoadState>,
43
+ pub named_column_count: usize,
44
+ pub view_config: PtrEqRc<ViewConfig>,
45
+ /// Column currently being dragged (if any) — threaded to show drag
46
+ /// highlights without per-component `DragDrop` PubSub subscriptions.
47
+ pub drag_column: Option<String>,
48
+ /// Cloned session metadata snapshot — threaded from `SessionProps`
49
+ /// so that metadata changes trigger re-renders via prop diffing.
50
+ pub metadata: SessionMetadataRc,
51
+ /// Snapshot of the column-settings sidebar state — threaded from
52
+ /// `PresentationProps` so that open/close triggers re-renders.
53
+ pub open_column_settings: OpenColumnSettings,
54
+
55
+ /// Selected theme name, threaded for PortalModal consumers.
56
+ pub selected_theme: Option<String>,
57
+
36
58
  /// State
37
59
  pub dragdrop: DragDrop,
38
60
  pub session: Session,
@@ -41,8 +63,17 @@ pub struct SettingsPanelProps {
41
63
  }
42
64
 
43
65
  impl PartialEq for SettingsPanelProps {
44
- fn eq(&self, _rhs: &Self) -> bool {
45
- false
66
+ fn eq(&self, rhs: &Self) -> bool {
67
+ self.is_debug == rhs.is_debug
68
+ && self.plugin_name == rhs.plugin_name
69
+ && self.available_plugins == rhs.available_plugins
70
+ && self.has_table == rhs.has_table
71
+ && self.named_column_count == rhs.named_column_count
72
+ && self.view_config == rhs.view_config
73
+ && self.drag_column == rhs.drag_column
74
+ && self.metadata == rhs.metadata
75
+ && self.open_column_settings == rhs.open_column_settings
76
+ && self.selected_theme == rhs.selected_theme
46
77
  }
47
78
  }
48
79
 
@@ -55,7 +86,74 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
55
86
  session,
56
87
  ..
57
88
  } = &props;
58
- let selected_column = props.get_current_column_locator();
89
+
90
+ let selected_column = {
91
+ let locator = props.open_column_settings.locator.clone();
92
+ let config = &props.view_config;
93
+ locator.filter(|locator| match locator {
94
+ ColumnLocator::Table(name) => {
95
+ locator
96
+ .name()
97
+ .map(|n| {
98
+ config.columns.iter().any(|maybe_col| {
99
+ maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
100
+ }) || config.group_by.iter().any(|col| col == n)
101
+ || config.split_by.iter().any(|col| col == n)
102
+ || config.filter.iter().any(|col| col.column() == n)
103
+ || config.sort.iter().any(|col| &col.0 == n)
104
+ })
105
+ .unwrap_or_default()
106
+ && can_render_column_styles(&props.renderer, config, &props.metadata, name)
107
+ .unwrap_or_default()
108
+ },
109
+ _ => true,
110
+ })
111
+ };
112
+
113
+ let plugin_name = props.plugin_name.clone();
114
+ let available_plugins = props.available_plugins.clone();
115
+
116
+ // Dispatch callback: captures engine handles, constructs config update, renders
117
+ let on_select_plugin = {
118
+ clone!(renderer, session, presentation);
119
+ let session_metadata = props.metadata.clone();
120
+ Callback::from(move |plugin_name: String| {
121
+ if !session.is_errored() {
122
+ let metadata =
123
+ renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
124
+
125
+ let prev_metadata = renderer.metadata();
126
+ let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
127
+ let rollup_features = session_metadata
128
+ .get_features()
129
+ .map(|x| x.get_group_rollup_modes())
130
+ .unwrap();
131
+
132
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
133
+ let mut update = ViewConfigUpdate {
134
+ group_rollup_mode: group_rollups.first().cloned(),
135
+ ..ViewConfigUpdate::default()
136
+ };
137
+
138
+ update.set_update_column_defaults(
139
+ &session_metadata,
140
+ &session.get_view_config().columns,
141
+ requirements,
142
+ );
143
+
144
+ if session.update_view_config(update).is_ok() {
145
+ clone!(renderer, session);
146
+ ApiFuture::spawn(async move {
147
+ renderer.apply_pending_plugin()?;
148
+ renderer.draw(session.validate().await?.create_view()).await
149
+ });
150
+ }
151
+
152
+ presentation.set_open_column_settings(None);
153
+ }
154
+ })
155
+ };
156
+
59
157
  html! {
60
158
  <div id="settings_panel" class="sidebar_column noselect split-panel orient-vertical">
61
159
  if selected_column.is_none() {
@@ -68,14 +166,20 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
68
166
  id={if props.is_debug {"debug_close_button"} else {"debug_open_button"}}
69
167
  on_close_sidebar={&props.on_debug}
70
168
  />
71
- <PluginSelector {presentation} {renderer} {session} />
169
+ <PluginSelector {plugin_name} {available_plugins} {on_select_plugin} />
72
170
  <ColumnSelector
73
171
  on_resize={&props.on_resize}
74
172
  on_open_expr_panel={&props.on_select_column}
75
- selected_column={selected_column.clone()}
173
+ {selected_column}
174
+ has_table={props.has_table.clone()}
175
+ named_column_count={props.named_column_count}
176
+ view_config={props.view_config.clone()}
177
+ drag_column={props.drag_column.clone()}
178
+ metadata={props.metadata.clone()}
179
+ selected_theme={props.selected_theme.clone()}
76
180
  {dragdrop}
77
- {renderer}
78
- {session}
181
+ renderer={renderer.clone()}
182
+ session={session.clone()}
79
183
  />
80
184
  </div>
81
185
  }