@perspective-dev/viewer 4.5.1 → 5.0.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 (212) hide show
  1. package/dist/cdn/perspective-viewer.js +20 -1
  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/monokai.css +1 -1
  9. package/dist/css/phosphor.css +1 -1
  10. package/dist/css/pro-dark.css +1 -1
  11. package/dist/css/pro.css +1 -1
  12. package/dist/css/solarized-dark.css +1 -1
  13. package/dist/css/solarized.css +1 -1
  14. package/dist/css/themes.css +1 -1
  15. package/dist/css/vaporwave.css +1 -1
  16. package/dist/esm/extensions.d.ts +22 -3
  17. package/dist/esm/perspective-viewer.cdn.d.ts +3 -0
  18. package/dist/esm/perspective-viewer.d.ts +9 -0
  19. package/dist/esm/perspective-viewer.inline.d.ts +3 -0
  20. package/dist/esm/perspective-viewer.inline.js +20 -1
  21. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  22. package/dist/esm/perspective-viewer.js +20 -1
  23. package/dist/esm/perspective-viewer.js.map +4 -4
  24. package/dist/esm/plugin.d.ts +71 -5
  25. package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
  26. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
  27. package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
  28. package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
  29. package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
  30. package/dist/esm/ts-rs/Layout.d.ts +11 -0
  31. package/dist/esm/ts-rs/OnUpdateData.d.ts +6 -0
  32. package/dist/esm/ts-rs/Orientation.d.ts +4 -0
  33. package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
  34. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +39 -0
  35. package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
  36. package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
  37. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
  38. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -2
  39. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
  40. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
  41. package/dist/wasm/perspective-viewer.d.ts +213 -79
  42. package/dist/wasm/perspective-viewer.js +537 -189
  43. package/dist/wasm/perspective-viewer.wasm +0 -0
  44. package/dist/wasm/perspective-viewer.wasm.d.ts +36 -28
  45. package/dist/wasm/snippets/perspective-viewer-daeef4ceb066e6c7/inline0.js +6 -0
  46. package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline3.js → perspective-viewer-daeef4ceb066e6c7/inline4.js} +5 -0
  47. package/package.json +3 -2
  48. package/src/css/_column_dropdown.css +14 -0
  49. package/src/css/_context_menu.css +14 -0
  50. package/src/css/_dropdown_menu.css +14 -0
  51. package/src/css/_filter_dropdown.css +14 -0
  52. package/src/css/_function_dropdown.css +14 -0
  53. package/src/css/_viewer_bundle.css +41 -0
  54. package/src/css/column-settings-panel.css +1 -1
  55. package/src/css/containers/context-menu.css +101 -0
  56. package/src/css/panel-tab.css +193 -0
  57. package/src/css/plugin-selector.css +7 -10
  58. package/src/css/status-bar.css +75 -81
  59. package/src/css/viewer.css +181 -71
  60. package/src/rust/components/column_dropdown.rs +43 -33
  61. package/src/rust/components/column_selector/active_column.rs +7 -18
  62. package/src/rust/components/column_selector/aggregate_selector.rs +4 -10
  63. package/src/rust/components/column_selector/config_selector.rs +33 -84
  64. package/src/rust/components/column_selector/empty_column.rs +0 -3
  65. package/src/rust/components/column_selector/filter_column.rs +5 -18
  66. package/src/rust/components/column_selector/inactive_column.rs +3 -8
  67. package/src/rust/components/column_selector/invalid_column.rs +0 -4
  68. package/src/rust/components/column_selector/sort_column.rs +3 -6
  69. package/src/rust/components/column_selector.rs +5 -14
  70. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +0 -3
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +0 -3
  72. package/src/rust/components/column_settings_sidebar.rs +0 -3
  73. package/src/rust/components/containers/dropdown_menu.rs +1 -3
  74. package/src/rust/components/containers/scroll_panel.rs +1 -3
  75. package/src/rust/components/containers/split_panel.rs +175 -66
  76. package/src/rust/components/containers/tab_list.rs +0 -3
  77. package/src/rust/components/context_menu.rs +130 -0
  78. package/src/rust/components/datetime_column_style.rs +0 -3
  79. package/src/rust/components/empty_row.rs +0 -3
  80. package/src/rust/components/expression_editor.rs +2 -3
  81. package/src/rust/components/filter_dropdown.rs +3 -3
  82. package/src/rust/components/font_loader.rs +2 -2
  83. package/src/rust/components/form/code_editor.rs +0 -3
  84. package/src/rust/components/form/debug.rs +3 -4
  85. package/src/rust/components/function_dropdown.rs +3 -3
  86. package/src/rust/components/global_filter_bar.rs +76 -0
  87. package/src/rust/components/main_panel/frame_theme.rs +186 -0
  88. package/src/rust/components/main_panel/msg.rs +62 -0
  89. package/src/rust/components/main_panel/presize.rs +220 -0
  90. package/src/rust/components/main_panel/reconcile.rs +224 -0
  91. package/src/rust/components/main_panel/render.rs +385 -0
  92. package/src/rust/components/main_panel/update.rs +184 -0
  93. package/src/rust/components/main_panel.rs +311 -71
  94. package/src/rust/components/mod.rs +4 -0
  95. package/src/rust/components/number_series_style.rs +0 -3
  96. package/src/rust/components/panel_menu.rs +486 -0
  97. package/src/rust/components/panel_tab.rs +505 -0
  98. package/src/rust/components/plugin_selector.rs +0 -3
  99. package/src/rust/components/plugin_tab.rs +1 -6
  100. package/src/rust/components/portal.rs +5 -2
  101. package/src/rust/components/render_warning.rs +0 -3
  102. package/src/rust/components/settings_panel.rs +13 -5
  103. package/src/rust/components/status_bar.rs +113 -180
  104. package/src/rust/components/status_indicator.rs +6 -9
  105. package/src/rust/components/string_column_style.rs +0 -3
  106. package/src/rust/components/style/mod.rs +65 -27
  107. package/src/rust/components/style/style_provider.rs +51 -16
  108. package/src/rust/components/style_controls/number_string_format.rs +1 -8
  109. package/src/rust/components/type_icon.rs +1 -4
  110. package/src/rust/components/viewer/filters.rs +133 -0
  111. package/src/rust/components/viewer/msg.rs +180 -0
  112. package/src/rust/components/viewer/panels.rs +326 -0
  113. package/src/rust/components/viewer/render.rs +243 -0
  114. package/src/rust/components/viewer/settings.rs +402 -0
  115. package/src/rust/components/viewer/snapshots.rs +134 -0
  116. package/src/rust/components/viewer/wiring.rs +505 -0
  117. package/src/rust/components/viewer.rs +243 -758
  118. package/src/rust/config/export_method.rs +1 -1
  119. package/src/rust/config/mod.rs +4 -0
  120. package/src/rust/config/options.rs +71 -0
  121. package/src/rust/config/viewer_config.rs +24 -2
  122. package/src/rust/config/workspace_config.rs +92 -0
  123. package/src/rust/custom_elements/copy_dropdown.rs +9 -3
  124. package/src/rust/custom_elements/debug_plugin.rs +20 -1
  125. package/src/rust/custom_elements/export_dropdown.rs +9 -3
  126. package/src/rust/custom_elements/viewer.rs +1015 -286
  127. package/src/rust/custom_events.rs +212 -61
  128. package/src/rust/js/mod.rs +2 -0
  129. package/src/rust/js/plugin.rs +297 -10
  130. package/src/rust/js/regular_layout.rs +258 -0
  131. package/src/rust/lib.rs +20 -2
  132. package/src/rust/presentation.rs +66 -33
  133. package/src/rust/queries/get_viewer_config.rs +29 -11
  134. package/src/rust/queries/plugin_column_styles.rs +2 -2
  135. package/src/rust/renderer/activate.rs +41 -14
  136. package/src/rust/renderer/dispatch.rs +436 -0
  137. package/src/rust/renderer/plugin_config.rs +495 -0
  138. package/src/rust/renderer/registry.rs +18 -6
  139. package/src/rust/renderer/render_timer.rs +29 -10
  140. package/src/rust/renderer.rs +350 -827
  141. package/src/rust/session/column_defaults_update.rs +26 -8
  142. package/src/rust/session/metadata.rs +11 -1
  143. package/src/rust/session/view_subscription.rs +30 -15
  144. package/src/rust/session.rs +753 -251
  145. package/src/rust/tasks/apply_global_filters.rs +89 -0
  146. package/src/rust/tasks/auto_pause.rs +171 -0
  147. package/src/rust/tasks/copy_export.rs +29 -19
  148. package/src/rust/tasks/create_panel.rs +243 -0
  149. package/src/rust/tasks/dismiss_render_warning.rs +3 -1
  150. package/src/rust/tasks/edit_expression.rs +4 -4
  151. package/src/rust/tasks/eject.rs +64 -23
  152. package/src/rust/tasks/mod.rs +28 -4
  153. package/src/rust/tasks/pipeline.rs +411 -0
  154. package/src/rust/tasks/presize_panels.rs +324 -0
  155. package/src/rust/tasks/reset_all.rs +22 -12
  156. package/src/rust/tasks/resize_observer.rs +124 -87
  157. package/src/rust/tasks/restore_and_render.rs +80 -86
  158. package/src/rust/tasks/restore_panel.rs +145 -0
  159. package/src/rust/tasks/send_column_config.rs +5 -2
  160. package/src/rust/tasks/send_plugin_config.rs +5 -2
  161. package/src/rust/tasks/set_edit_mode.rs +59 -0
  162. package/src/rust/tasks/sync_update_panels.rs +152 -0
  163. package/src/rust/tasks/table_lifecycle.rs +158 -0
  164. package/src/rust/tasks/update_theme.rs +56 -12
  165. package/src/rust/utils/completion.rs +56 -0
  166. package/src/rust/utils/custom_element.rs +5 -0
  167. package/src/rust/utils/debounce.rs +130 -7
  168. package/src/rust/utils/mod.rs +6 -0
  169. package/src/rust/utils/pending_effects.rs +62 -0
  170. package/src/rust/{components/style/local_style.rs → utils/spawn.rs} +17 -19
  171. package/src/rust/utils/weak_scope.rs +0 -10
  172. package/src/rust/workspace.rs +846 -0
  173. package/src/svg/active-indicator.svg +3 -0
  174. package/src/themes/botanical.css +7 -2
  175. package/src/themes/defaults.css +54 -2
  176. package/src/themes/dracula.css +8 -3
  177. package/src/themes/gruvbox-dark.css +7 -2
  178. package/src/themes/gruvbox.css +5 -2
  179. package/src/themes/icons.css +4 -2
  180. package/src/themes/monokai.css +7 -2
  181. package/src/themes/phosphor.css +7 -2
  182. package/src/themes/pro-dark.css +7 -2
  183. package/src/themes/pro.css +2 -1
  184. package/src/themes/solarized-dark.css +5 -2
  185. package/src/themes/solarized.css +5 -2
  186. package/src/themes/vaporwave.css +7 -2
  187. package/src/ts/bootstrap.ts +8 -7
  188. package/src/ts/extensions.ts +30 -2
  189. package/src/ts/perspective-viewer.ts +10 -0
  190. package/src/ts/plugin.ts +72 -5
  191. package/src/ts/ts-rs/ClientOptions.ts +7 -0
  192. package/src/ts/ts-rs/ExportMethod.ts +3 -0
  193. package/src/ts/ts-rs/ExportOptions.ts +8 -0
  194. package/src/ts/ts-rs/GetClientOptions.ts +7 -0
  195. package/src/ts/ts-rs/GetTableOptions.ts +7 -0
  196. package/src/ts/ts-rs/Layout.ts +9 -0
  197. package/src/ts/ts-rs/Orientation.ts +6 -0
  198. package/src/ts/ts-rs/PanelOptions.ts +7 -0
  199. package/src/ts/ts-rs/PanelViewerConfig.ts +15 -0
  200. package/src/ts/ts-rs/SplitLayout.ts +5 -0
  201. package/src/ts/ts-rs/TabLayout.ts +11 -0
  202. package/src/ts/ts-rs/ViewerConfig.ts +6 -2
  203. package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
  204. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
  205. package/tsconfig.json +1 -1
  206. package/src/rust/components/style/style_cache.rs +0 -136
  207. package/src/rust/tasks/intersection_observer.rs +0 -98
  208. package/src/rust/tasks/update_and_render.rs +0 -110
  209. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline0.js → perspective-viewer-daeef4ceb066e6c7/inline1.js} +0 -0
  210. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline1.js → perspective-viewer-daeef4ceb066e6c7/inline2.js} +0 -0
  211. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline2.js → perspective-viewer-daeef4ceb066e6c7/inline3.js} +0 -0
  212. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline4.js → perspective-viewer-daeef4ceb066e6c7/inline5.js} +0 -0
@@ -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
+ use perspective_client::config::Filter;
13
14
  use wasm_bindgen_futures::spawn_local;
14
15
  use web_sys::*;
15
16
  use yew::prelude::*;
16
17
 
17
18
  use super::status_indicator::StatusIndicator;
18
- use super::style::LocalStyle;
19
19
  use crate::components::containers::select::*;
20
20
  use crate::components::copy_dropdown::CopyDropDownMenu;
21
21
  use crate::components::export_dropdown::ExportDropDownMenu;
22
+ use crate::components::global_filter_bar::GlobalFilterBar;
22
23
  use crate::components::portal::PortalModal;
23
24
  use crate::components::status_bar_counter::StatusBarRowsCounter;
25
+ use crate::components::style::StyleSurface;
24
26
  use crate::config::*;
25
27
  use crate::js::*;
26
28
  use crate::presentation::{Presentation, PresentationProps};
@@ -28,6 +30,7 @@ use crate::renderer::*;
28
30
  use crate::session::*;
29
31
  use crate::tasks::*;
30
32
  use crate::utils::*;
33
+ use crate::workspace::Workspace;
31
34
  use crate::*;
32
35
 
33
36
  #[derive(Clone, Properties)]
@@ -38,10 +41,6 @@ pub struct StatusBarProps {
38
41
  /// Fired when the reset button is clicked.
39
42
  pub on_reset: Callback<bool>,
40
43
 
41
- /// Fires when the settings button is clicked
42
- #[prop_or_default]
43
- pub on_settings: Option<Callback<()>>,
44
-
45
44
  /// Snapshots threaded from root. Component reads `has_table`, `stats`,
46
45
  /// `error`, `title` from session_props; `selected_theme`,
47
46
  /// `available_themes`, `is_workspace` from presentation_props.
@@ -56,10 +55,25 @@ pub struct StatusBarProps {
56
55
  /// In-flight render counter, threaded to `StatusIndicator`.
57
56
  pub update_count: u32,
58
57
 
58
+ /// Element-level global filters (fed by master/detail selection); rendered
59
+ /// as removable chips between the row stats and the menu icons.
60
+ pub global_filters: Vec<Filter>,
61
+
62
+ /// Remove the global filter at this index (a chip's ×).
63
+ pub on_remove_global_filter: Callback<usize>,
64
+
65
+ /// Clear all global filters (the "Clear" affordance).
66
+ pub on_clear_global_filters: Callback<()>,
67
+
59
68
  // State
60
69
  pub session: Session,
61
70
  pub renderer: Renderer,
62
71
  pub presentation: Presentation,
72
+
73
+ /// The multi-panel model, so a theme change can restyle EVERY panel (not
74
+ /// just the active one this status bar targets) — non-active panels that
75
+ /// inherit the host theme otherwise render stale CSS until they redraw.
76
+ pub workspace: Workspace,
63
77
  }
64
78
 
65
79
  impl PartialEq for StatusBarProps {
@@ -69,6 +83,7 @@ impl PartialEq for StatusBarProps {
69
83
  && self.presentation_props == other.presentation_props
70
84
  && self.is_settings_open == other.is_settings_open
71
85
  && self.update_count == other.update_count
86
+ && self.global_filters == other.global_filters
72
87
  }
73
88
  }
74
89
 
@@ -78,25 +93,17 @@ pub enum StatusBarMsg {
78
93
  Copy,
79
94
  CloseExport,
80
95
  CloseCopy,
81
- Noop,
82
96
  Eject,
83
97
  SetTheme(String),
84
98
  ResetTheme,
85
99
  PointerEvent(web_sys::PointerEvent),
86
- TitleInputEvent,
87
- TitleChangeEvent,
88
100
  }
89
101
 
90
102
  /// A toolbar with buttons, and `Table` & `View` status information.
91
103
  pub struct StatusBar {
92
104
  copy_ref: NodeRef,
93
105
  export_ref: NodeRef,
94
- input_ref: NodeRef,
95
106
  statusbar_ref: NodeRef,
96
- /// Local title tracks the live `<input>` value before the user commits the
97
- /// change (blur / Enter). Reset to the prop value whenever the prop
98
- /// changes.
99
- title: Option<String>,
100
107
  copy_target: Option<HtmlElement>,
101
108
  export_target: Option<HtmlElement>,
102
109
  }
@@ -105,127 +112,76 @@ impl Component for StatusBar {
105
112
  type Message = StatusBarMsg;
106
113
  type Properties = StatusBarProps;
107
114
 
108
- fn create(ctx: &Context<Self>) -> Self {
115
+ fn create(_ctx: &Context<Self>) -> Self {
109
116
  Self {
110
117
  copy_ref: NodeRef::default(),
111
118
  export_ref: NodeRef::default(),
112
- input_ref: NodeRef::default(),
113
119
  statusbar_ref: NodeRef::default(),
114
- title: ctx.props().session_props.title.clone(),
115
120
  copy_target: None,
116
121
  export_target: None,
117
122
  }
118
123
  }
119
124
 
120
- fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
121
- // Keep the local title in sync with the prop whenever the session title
122
- // changes externally (e.g. restore() call) or the settings panel opens /
123
- // closes (which resets the input element).
124
- if ctx.props().session_props.title != old_props.session_props.title
125
- || ctx.props().is_settings_open != old_props.is_settings_open
126
- {
127
- self.title = ctx.props().session_props.title.clone();
128
- }
129
- true
130
- }
131
-
132
125
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
133
- let r: ApiResult<bool> = (|| {
134
- Ok(match msg {
135
- StatusBarMsg::Reset(event) => {
136
- let all = event.shift_key();
137
- ctx.props().on_reset.emit(all);
138
- false
139
- },
140
- StatusBarMsg::ResetTheme => {
141
- update_theme(
142
- &ctx.props().session,
143
- &ctx.props().renderer,
144
- &ctx.props().presentation,
145
- None,
146
- );
147
- true
148
- },
149
- StatusBarMsg::SetTheme(theme_name) => {
150
- update_theme(
151
- &ctx.props().session,
152
- &ctx.props().renderer,
153
- &ctx.props().presentation,
154
- Some(theme_name),
155
- );
156
- false
157
- },
158
- StatusBarMsg::Export => {
159
- self.export_target = self.export_ref.cast::<HtmlElement>();
160
- true
161
- },
162
- StatusBarMsg::Copy => {
163
- self.copy_target = self.copy_ref.cast::<HtmlElement>();
164
- true
165
- },
166
- StatusBarMsg::CloseExport => {
167
- self.export_target = None;
168
- true
169
- },
170
- StatusBarMsg::CloseCopy => {
171
- self.copy_target = None;
172
- true
173
- },
174
- StatusBarMsg::Eject => {
175
- ctx.props().presentation.on_eject.emit(());
176
- false
177
- },
178
- StatusBarMsg::Noop => {
179
- self.title = ctx.props().session_props.title.clone();
180
- true
181
- },
182
- StatusBarMsg::TitleInputEvent => {
183
- let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
184
- let title = elem.value();
185
- let title = if title.trim().is_empty() {
186
- None
187
- } else {
188
- Some(title)
189
- };
190
-
191
- self.title = title;
192
- true
193
- },
194
- StatusBarMsg::TitleChangeEvent => {
195
- let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
196
- let title = elem.value();
197
- let title = if title.trim().is_empty() {
198
- None
199
- } else {
200
- Some(title)
201
- };
202
-
203
- ctx.props().session.set_title(title);
204
- false
205
- },
206
- StatusBarMsg::PointerEvent(event) => {
207
- if event.target().map(JsValue::from)
208
- == self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
209
- {
210
- ctx.props().presentation.statusbar_pointer_event.emit(event);
211
- }
126
+ match msg {
127
+ StatusBarMsg::Reset(event) => {
128
+ let all = event.shift_key();
129
+ ctx.props().on_reset.emit(all);
130
+ false
131
+ },
132
+ StatusBarMsg::ResetTheme => {
133
+ update_theme(
134
+ &ctx.props().renderer,
135
+ &ctx.props().presentation,
136
+ &ctx.props().workspace,
137
+ None,
138
+ );
139
+ true
140
+ },
141
+ StatusBarMsg::SetTheme(theme_name) => {
142
+ update_theme(
143
+ &ctx.props().renderer,
144
+ &ctx.props().presentation,
145
+ &ctx.props().workspace,
146
+ Some(theme_name),
147
+ );
148
+ false
149
+ },
150
+ StatusBarMsg::Export => {
151
+ self.export_target = self.export_ref.cast::<HtmlElement>();
152
+ true
153
+ },
154
+ StatusBarMsg::Copy => {
155
+ self.copy_target = self.copy_ref.cast::<HtmlElement>();
156
+ true
157
+ },
158
+ StatusBarMsg::CloseExport => {
159
+ self.export_target = None;
160
+ true
161
+ },
162
+ StatusBarMsg::CloseCopy => {
163
+ self.copy_target = None;
164
+ true
165
+ },
166
+ StatusBarMsg::Eject => {
167
+ ctx.props().presentation.on_eject.emit(());
168
+ false
169
+ },
170
+ StatusBarMsg::PointerEvent(event) => {
171
+ if event.target().map(JsValue::from)
172
+ == self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
173
+ {
174
+ ctx.props().presentation.statusbar_pointer_event.emit(event);
175
+ }
212
176
 
213
- false
214
- },
215
- })
216
- })();
217
- r.unwrap_or_else(|e| {
218
- web_sys::console::warn_1(&e.into());
219
- Default::default()
220
- })
177
+ false
178
+ },
179
+ }
221
180
  }
222
181
 
223
182
  fn view(&self, ctx: &Context<Self>) -> Html {
224
183
  let Self::Properties {
225
- presentation,
226
- renderer,
227
- session,
228
- ..
184
+ renderer, session, ..
229
185
  } = ctx.props();
230
186
 
231
187
  let has_table = ctx.props().session_props.has_table.clone();
@@ -234,12 +190,8 @@ impl Component for StatusBar {
234
190
  let title = &ctx.props().session_props.title;
235
191
 
236
192
  let mut is_updating_class_name = classes!();
237
- if title.is_some() {
238
- is_updating_class_name.push("titled");
239
- };
240
-
241
193
  if !is_settings_open {
242
- is_updating_class_name.push(["settings-closed", "titled"]);
194
+ is_updating_class_name.push("settings-closed");
243
195
  };
244
196
 
245
197
  if !matches!(has_table, Some(TableLoadState::Loaded)) {
@@ -247,34 +199,31 @@ impl Component for StatusBar {
247
199
  }
248
200
 
249
201
  // TODO Memoizing these would reduce some vdom diffing later on
250
- let onblur = ctx.link().callback(|_| StatusBarMsg::Noop);
251
202
  let onclose = ctx.link().callback(|_| StatusBarMsg::Eject);
252
203
  let onpointerdown = ctx.link().callback(StatusBarMsg::PointerEvent);
253
204
  let onexport = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
254
205
  let oncopy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
255
206
  let onreset = ctx.link().callback(StatusBarMsg::Reset);
256
- let onchange = ctx
257
- .link()
258
- .callback(|_: Event| StatusBarMsg::TitleChangeEvent);
259
-
260
- let oninput = ctx
261
- .link()
262
- .callback(|_: InputEvent| StatusBarMsg::TitleInputEvent);
263
-
264
- let is_menu = matches!(has_table, Some(TableLoadState::Loaded))
265
- && ctx.props().on_settings.as_ref().is_none();
266
- let is_title = is_menu
267
- || ctx.props().presentation_props.is_workspace
268
- || title.is_some()
269
- || is_errored
270
- || presentation.is_active(&self.input_ref.cast::<Element>());
271
-
272
- let is_settings = title.is_some()
273
- || ctx.props().presentation_props.is_workspace
274
- || !matches!(has_table, Some(TableLoadState::Loaded))
275
- || is_errored
276
- || is_settings_open
277
- || presentation.is_active(&self.input_ref.cast::<Element>());
207
+
208
+ // Project only the *active* panel's plugin toolbar into the shared status
209
+ // bar. Each panel's toolbar slots into `statusbar-extra-{its-panel-id}`
210
+ // (see datagrid `toolbar.ts`); the active panel's id comes from the
211
+ // active renderer this status bar is bound to.
212
+ let extra_slot = ctx
213
+ .props()
214
+ .renderer
215
+ .slot_name()
216
+ .map(|id| format!("statusbar-extra-{id}"))
217
+ .unwrap_or_else(|| "statusbar-extra".to_owned());
218
+ let is_menu = matches!(has_table, Some(TableLoadState::Loaded)) && is_settings_open;
219
+ // The editable title moved to the per-panel `<PanelTab>` headers; the
220
+ // status bar no longer hosts an `<input>`, so its `is_active` factor is
221
+ // gone. `is_title` now only gates the row counter.
222
+ let is_title =
223
+ is_menu || ctx.props().presentation_props.is_workspace || title.is_some() || is_errored;
224
+
225
+ let is_settings =
226
+ !matches!(has_table, Some(TableLoadState::Loaded)) || is_errored || is_settings_open;
278
227
 
279
228
  let on_copy_select = {
280
229
  let props = ctx.props().clone();
@@ -332,7 +281,6 @@ impl Component for StatusBar {
332
281
  if is_settings {
333
282
  html! {
334
283
  <>
335
- <LocalStyle href={css!("status-bar")} />
336
284
  <div
337
285
  ref={&self.statusbar_ref}
338
286
  id={ctx.props().id.clone()}
@@ -345,26 +293,18 @@ impl Component for StatusBar {
345
293
  update_count={ctx.props().update_count}
346
294
  session_props={ctx.props().session_props.clone()}
347
295
  />
348
- if is_title {
349
- <label
350
- class="input-sizer"
351
- data-value={self.title.clone().unwrap_or_default()}
352
- >
353
- <input
354
- ref={&self.input_ref}
355
- placeholder=""
356
- value={self.title.clone().unwrap_or_default()}
357
- size="10"
358
- {onblur}
359
- {onchange}
360
- {oninput}
361
- />
362
- <span id="status-bar-placeholder" />
363
- </label>
364
- }
365
296
  if is_title {
366
297
  <StatusBarRowsCounter stats={ctx.props().session_props.stats.clone()} />
367
298
  }
299
+ // Global-filter chips sit between the row stats and the
300
+ // menu icons (rendered only when there are filters).
301
+ if !ctx.props().global_filters.is_empty() {
302
+ <GlobalFilterBar
303
+ filters={ctx.props().global_filters.clone()}
304
+ on_remove={ctx.props().on_remove_global_filter.clone()}
305
+ on_clear={ctx.props().on_clear_global_filters.clone()}
306
+ />
307
+ }
368
308
  <div id="spacer" />
369
309
  if is_menu {
370
310
  <div id="menu-bar" class="section">
@@ -374,7 +314,7 @@ impl Component for StatusBar {
374
314
  on_change={ctx.link().callback(StatusBarMsg::SetTheme)}
375
315
  on_reset={ctx.link().callback(|_| StatusBarMsg::ResetTheme)}
376
316
  />
377
- <div id="plugin-settings"><slot name="statusbar-extra" /></div>
317
+ <div id="plugin-settings"><slot name={extra_slot} /></div>
378
318
  <span class="hover-target">
379
319
  <span id="reset" class="button" onmousedown={&onreset}>
380
320
  <span class="icon shift-alt-icon" />
@@ -403,14 +343,7 @@ impl Component for StatusBar {
403
343
  </span>
404
344
  </div>
405
345
  }
406
- if let Some(x) = ctx.props().on_settings.as_ref() {
407
- <div
408
- id="settings_button"
409
- class="noselect"
410
- onmousedown={x.reform(|_| ())}
411
- >
412
- <span class="icon" />
413
- </div>
346
+ if !is_settings_open {
414
347
  <div id="close_button" class="noselect" onmousedown={onclose}>
415
348
  <span class="icon" />
416
349
  </div>
@@ -418,6 +351,7 @@ impl Component for StatusBar {
418
351
  </div>
419
352
  <PortalModal
420
353
  tag_name="perspective-copy-menu"
354
+ surface={StyleSurface::DropdownMenu}
421
355
  target={self.copy_target.clone()}
422
356
  own_focus=true
423
357
  on_close={on_close_copy}
@@ -427,6 +361,7 @@ impl Component for StatusBar {
427
361
  </PortalModal>
428
362
  <PortalModal
429
363
  tag_name="perspective-export-menu"
364
+ surface={StyleSurface::DropdownMenu}
430
365
  target={self.export_target.clone()}
431
366
  own_focus=true
432
367
  on_close={on_close_export}
@@ -440,18 +375,16 @@ impl Component for StatusBar {
440
375
  </PortalModal>
441
376
  </>
442
377
  }
443
- } else if let Some(x) = ctx.props().on_settings.as_ref() {
378
+ } else {
379
+ // Settings closed + loaded: no docked status bar. The open-settings
380
+ // affordance lives on the `PanelTab`s; only the (default-hidden)
381
+ // eject button floats here.
444
382
  let class = classes!(is_updating_class_name, "floating");
445
383
  html! {
446
384
  <div id={ctx.props().id.clone()} {class}>
447
- <div id="settings_button" class="noselect" onmousedown={x.reform(|_| ())}>
448
- <span class="icon" />
449
- </div>
450
385
  <div id="close_button" class="noselect" onmousedown={&onclose} />
451
386
  </div>
452
387
  }
453
- } else {
454
- html! {}
455
388
  }
456
389
  }
457
390
  }
@@ -17,18 +17,20 @@ use yew::prelude::*;
17
17
 
18
18
  use crate::renderer::Renderer;
19
19
  use crate::session::{Session, SessionProps, TableLoadState};
20
+ use crate::tasks::apply_and_render;
20
21
  use crate::utils::*;
21
22
 
22
23
  /// Value-prop version: no PubSub subscriptions, no reducer.
23
24
  /// The parent (`StatusBar`) re-renders this component whenever
24
25
  /// `session_props.error/has_table/stats` or `update_count` change (via
25
- /// root's `IncrementUpdateCount` / `DecrementUpdateCount` / `UpdateSession`
26
- /// messages).
26
+ /// root's `UpdateInFlight` / `UpdateSession` messages).
27
27
  #[derive(PartialEq, Properties)]
28
28
  pub struct StatusIndicatorProps {
29
29
  pub renderer: Renderer,
30
30
  pub session: Session,
31
- /// Number of in-flight renders (>0 → "updating" spinner).
31
+ /// Number of in-flight CONFIG-DRIVEN runs (>0 → "updating" spinner) —
32
+ /// a level-triggered snapshot of `Session::in_flight_config_runs`
33
+ /// (RAII-settled; see `UPDATE_COUNT_REGRESSION_PLAN.md`).
32
34
  pub update_count: u32,
33
35
  /// Snapshot of session value props — read for `error`, `has_table`,
34
36
  /// `stats` to derive the icon state.
@@ -81,12 +83,7 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
81
83
  match &state {
82
84
  StatusIconState::Errored(..) => {
83
85
  session.reconnect().await?;
84
- let cfg = ViewConfigUpdate::default();
85
- session.update_view_config(cfg)?;
86
- renderer.apply_pending_plugin()?;
87
- renderer
88
- .draw(session.validate().await?.create_view())
89
- .await?;
86
+ apply_and_render(session, renderer, ViewConfigUpdate::default())?.await?;
90
87
  },
91
88
  StatusIconState::Normal => {
92
89
  session.status_indicator_clicked.emit(());
@@ -13,10 +13,8 @@
13
13
  use yew::prelude::*;
14
14
 
15
15
  use super::modal::{ModalLink, SetModalLink};
16
- use super::style::LocalStyle;
17
16
  use crate::components::form::select_enum_field::SelectEnumField;
18
17
  use crate::config::*;
19
- use crate::css;
20
18
  use crate::utils::WeakScope;
21
19
 
22
20
  /// Format-only widget for [`String`] columns. Renders the `format` enum
@@ -91,7 +89,6 @@ impl Component for StringColumnStyle {
91
89
  let format_mode_changed = ctx.link().callback(StringColumnStyleMsg::FormatChanged);
92
90
  html! {
93
91
  <>
94
- <LocalStyle href={css!("column-style")} />
95
92
  <div id="column-style-container" class="string-column-style-container">
96
93
  <SelectEnumField<FormatMode>
97
94
  label="format"
@@ -10,43 +10,81 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! A micro-framework for associating local CSS snippets with `yew::Component`s
14
- //! in a Custom Element's `ShadowRoot`.
13
+ //! Associates a single, build-time CSS bundle with a `yew::Component` tree
14
+ //! rendered into a Custom Element's `ShadowRoot`.
15
15
  //!
16
- //! Embedding a `<LocalStyle>` element will only create the underlying `<style>`
17
- //! tag once (when `Component::view()` is called the first time), even if
18
- //! multiple copies of the `Component` exist in the tree.
16
+ //! Each `src/css` file is inlined and minified by `lightningcss` (see
17
+ //! `build.rs` + the `_viewer_bundle.css` and per-popup `_column_dropdown.css`
18
+ //! / `_filter_dropdown.css` / `_function_dropdown.css` / `_dropdown_menu.css`
19
+ //! entrypoints) into one stylesheet per "surface". A `<StyleProvider>` at the
20
+ //! root of a shadow tree adopts its surface's stylesheet exactly once;
21
+ //! constructed `CSSStyleSheet`s are cheap and shared across every viewer
22
+ //! instance.
19
23
  //!
20
24
  //! # Example
21
25
  //!
22
- //! ```
26
+ //! ```ignore
23
27
  //! html! {
24
- //! <StyleProvider>
25
- //! <LocalStyle href={ css!("my-style") } />
28
+ //! <StyleProvider root={host} surface={StyleSurface::Viewer}>
26
29
  //! <h1>{ "I am styled!" }</h1>
27
30
  //! </StyleProvider>
28
31
  //! }
29
32
  //! ```
30
33
 
31
- mod local_style;
32
- mod style_cache;
33
34
  mod style_provider;
34
35
 
35
- pub use local_style::LocalStyle;
36
- pub use style_provider::StyleProvider;
37
-
38
- #[macro_export]
39
- macro_rules! css {
40
- ($name:expr) => {{
41
- (
42
- $name,
43
- include_str!(concat!(env!("OUT_DIR"), "/css/", $name, ".css")),
44
- )
45
- }};
46
- ($path:expr, $name:expr) => {{
47
- (
48
- $name,
49
- include_str!(concat!(env!("OUT_DIR"), "/", $path, "/", $name, ".css")),
50
- )
51
- }};
36
+ pub use style_provider::{StyleProvider, StyleSurface};
37
+
38
+ /// The viewer-surface bundle: every `src/css` file except the floating-popup
39
+ /// sheets. Adopted into the `<perspective-viewer>` ShadowRoot.
40
+ fn viewer_bundle() -> &'static str {
41
+ include_str!(concat!(env!("OUT_DIR"), "/css/_viewer_bundle.css"))
42
+ }
43
+
44
+ /// Per-popup-surface bundles. Each floating popup root adopts exactly its own
45
+ /// self-contained sheet (mirroring the pre-bundle behavior where every popup
46
+ /// instance carried only its own CSS) so the four popup styles never collide —
47
+ /// in particular `column-dropdown.css`'s `!important` inverted rules stay
48
+ /// confined to the column-name completion dropdown.
49
+ fn column_dropdown_bundle() -> &'static str {
50
+ include_str!(concat!(env!("OUT_DIR"), "/css/_column_dropdown.css"))
51
+ }
52
+ fn filter_dropdown_bundle() -> &'static str {
53
+ include_str!(concat!(env!("OUT_DIR"), "/css/_filter_dropdown.css"))
54
+ }
55
+ fn function_dropdown_bundle() -> &'static str {
56
+ include_str!(concat!(env!("OUT_DIR"), "/css/_function_dropdown.css"))
57
+ }
58
+ fn dropdown_menu_bundle() -> &'static str {
59
+ include_str!(concat!(env!("OUT_DIR"), "/css/_dropdown_menu.css"))
60
+ }
61
+ fn context_menu_bundle() -> &'static str {
62
+ include_str!(concat!(env!("OUT_DIR"), "/css/_context_menu.css"))
63
+ }
64
+
65
+ thread_local! {
66
+ static VIEWER_SHEET: web_sys::CssStyleSheet = make_sheet(viewer_bundle());
67
+ static COLUMN_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(column_dropdown_bundle());
68
+ static FILTER_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(filter_dropdown_bundle());
69
+ static FUNCTION_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(function_dropdown_bundle());
70
+ static DROPDOWN_MENU_SHEET: web_sys::CssStyleSheet = make_sheet(dropdown_menu_bundle());
71
+ static CONTEXT_MENU_SHEET: web_sys::CssStyleSheet = make_sheet(context_menu_bundle());
72
+ }
73
+
74
+ fn make_sheet(css: &str) -> web_sys::CssStyleSheet {
75
+ let sheet = web_sys::CssStyleSheet::new().unwrap();
76
+ sheet.replace_sync(css).unwrap();
77
+ sheet
78
+ }
79
+
80
+ /// The memoized, shared `CSSStyleSheet` for a given [`StyleSurface`].
81
+ pub(crate) fn surface_sheet(surface: StyleSurface) -> web_sys::CssStyleSheet {
82
+ match surface {
83
+ StyleSurface::Viewer => VIEWER_SHEET.with(|x| x.clone()),
84
+ StyleSurface::ColumnDropdown => COLUMN_DROPDOWN_SHEET.with(|x| x.clone()),
85
+ StyleSurface::FilterDropdown => FILTER_DROPDOWN_SHEET.with(|x| x.clone()),
86
+ StyleSurface::FunctionDropdown => FUNCTION_DROPDOWN_SHEET.with(|x| x.clone()),
87
+ StyleSurface::DropdownMenu => DROPDOWN_MENU_SHEET.with(|x| x.clone()),
88
+ StyleSurface::ContextMenu => CONTEXT_MENU_SHEET.with(|x| x.clone()),
89
+ }
52
90
  }