@perspective-dev/viewer 5.3.1 → 5.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 (255) hide show
  1. package/dist/cdn/perspective-viewer.js +6 -6
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -0
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/extensions.d.ts +0 -5
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +5 -5
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  36. package/dist/wasm/perspective-viewer.d.ts +62 -73
  37. package/dist/wasm/perspective-viewer.js +226 -238
  38. package/dist/wasm/perspective-viewer.wasm +0 -0
  39. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
  40. package/package.json +7 -7
  41. package/src/css/_context_menu.css +1 -1
  42. package/src/css/_dropdown_menu.css +1 -1
  43. package/src/css/_viewer_bundle.css +7 -8
  44. package/src/css/column-dropdown.css +0 -3
  45. package/src/css/column-selector.css +8 -5
  46. package/src/css/column-settings-panel.css +0 -225
  47. package/src/css/column-style.css +13 -0
  48. package/src/css/config-selector.css +18 -10
  49. package/src/css/filter-dropdown.css +5 -5
  50. package/src/css/form/debug.css +28 -4
  51. package/src/css/function-dropdown.css +5 -5
  52. package/src/css/panel-tab.css +23 -0
  53. package/src/css/plugin-selector.css +5 -5
  54. package/src/css/render-warning.css +1 -1
  55. package/src/css/status-bar.css +52 -21
  56. package/src/css/type-icon.css +1 -3
  57. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  58. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  59. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  60. package/src/css/viewer.css +32 -26
  61. package/src/rust/components/chat_panel.rs +1 -1
  62. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  63. package/src/rust/components/column_dropdown.rs +2 -2
  64. package/src/rust/components/column_selector/active_column.rs +15 -9
  65. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  66. package/src/rust/components/column_selector/config_selector.rs +2 -2
  67. package/src/rust/components/column_selector/filter_column.rs +8 -5
  68. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  69. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  70. package/src/rust/components/column_selector/sort_column.rs +7 -4
  71. package/src/rust/components/column_selector.rs +3 -3
  72. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  73. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  75. package/src/rust/components/column_settings_sidebar/style_tab.rs +4 -5
  76. package/src/rust/components/column_settings_sidebar.rs +3 -4
  77. package/src/rust/components/copy_dropdown.rs +1 -1
  78. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  79. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  80. package/src/rust/components/datetime_column_style.rs +1 -2
  81. package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
  82. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  83. package/src/rust/components/export_dropdown.rs +1 -1
  84. package/src/rust/components/expression_editor.rs +1 -1
  85. package/src/rust/components/filter_dropdown.rs +2 -2
  86. package/src/rust/components/function_dropdown.rs +2 -2
  87. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  88. package/src/rust/components/main_panel/reconcile.rs +9 -4
  89. package/src/rust/components/main_panel/render.rs +62 -11
  90. package/src/rust/components/main_panel/update.rs +39 -10
  91. package/src/rust/components/main_panel.rs +26 -2
  92. package/src/rust/components/mod.rs +5 -7
  93. package/src/rust/components/new_panel_menu.rs +97 -0
  94. package/src/rust/components/number_series_style.rs +2 -3
  95. package/src/rust/components/panel_menu.rs +61 -87
  96. package/src/rust/components/panel_tab.rs +100 -43
  97. package/src/rust/components/plugin_tab.rs +1 -1
  98. package/src/rust/components/render_warning.rs +2 -2
  99. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  100. package/src/rust/components/settings_panel.rs +2 -2
  101. package/src/rust/components/status_bar.rs +113 -19
  102. package/src/rust/components/status_indicator.rs +5 -14
  103. package/src/rust/components/string_column_style.rs +1 -2
  104. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  105. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  106. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  107. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  108. package/src/rust/components/viewer/msg.rs +43 -30
  109. package/src/rust/components/viewer/panels.rs +50 -19
  110. package/src/rust/components/viewer/render.rs +15 -13
  111. package/src/rust/components/viewer/settings.rs +137 -68
  112. package/src/rust/components/viewer/snapshots.rs +5 -10
  113. package/src/rust/components/viewer/wiring.rs +1 -21
  114. package/src/rust/components/viewer.rs +15 -10
  115. package/src/rust/components/window_editor.rs +8 -9
  116. package/src/rust/config/column_config_schema.rs +2 -27
  117. package/src/rust/config/mod.rs +0 -2
  118. package/src/rust/config/workspace_config.rs +2 -1
  119. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  120. package/src/rust/custom_elements/mod.rs +0 -3
  121. package/src/rust/custom_elements/viewer.rs +0 -18
  122. package/src/rust/js/plugin.rs +0 -86
  123. package/src/rust/lib.rs +12 -15
  124. package/src/rust/presentation/column_locator.rs +3 -7
  125. package/src/rust/presentation.rs +68 -38
  126. package/src/rust/queries/hosted_tables.rs +38 -0
  127. package/src/rust/queries/mod.rs +2 -0
  128. package/src/rust/queries/palette_set.rs +1 -1
  129. package/src/rust/renderer/activate.rs +1 -3
  130. package/src/rust/renderer/dispatch.rs +83 -22
  131. package/src/rust/renderer/limits.rs +24 -5
  132. package/src/rust/renderer/plugin_config.rs +1 -0
  133. package/src/rust/renderer/props.rs +2 -1
  134. package/src/rust/renderer.rs +14 -27
  135. package/src/rust/session/props.rs +4 -3
  136. package/src/rust/session/view_subscription.rs +19 -6
  137. package/src/rust/session.rs +38 -70
  138. package/src/rust/tasks/create_panel.rs +1 -1
  139. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  140. package/src/rust/tasks/eject.rs +1 -0
  141. package/src/rust/tasks/pipeline.rs +0 -60
  142. package/src/rust/tasks/presize_panels.rs +24 -1
  143. package/src/rust/tasks/restore_and_render.rs +0 -17
  144. package/src/rust/tasks/send_column_config.rs +1 -1
  145. package/src/rust/tasks/send_plugin_config.rs +1 -1
  146. package/src/rust/tasks/set_edit_mode.rs +1 -1
  147. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  148. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  149. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  150. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  151. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  152. package/src/rust/{components → ui}/containers/select.rs +0 -6
  153. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  154. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  155. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  156. package/src/rust/{components → ui}/context_menu.rs +8 -13
  157. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  158. package/src/rust/{components → ui}/font_loader.rs +0 -26
  159. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  160. package/src/rust/ui/form/intl_label.rs +81 -0
  161. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  162. package/src/rust/{components → ui}/form/mod.rs +1 -10
  163. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  164. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  165. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  166. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  167. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  168. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  169. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  170. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  171. package/src/rust/ui/mod.rs +56 -0
  172. package/src/rust/ui/modal.rs +61 -0
  173. package/src/rust/{components → ui}/portal.rs +174 -79
  174. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  175. package/src/rust/utils/completion.rs +0 -6
  176. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  177. package/src/rust/utils/mod.rs +2 -0
  178. package/src/rust/workspace.rs +35 -0
  179. package/src/svg/boolean-type.svg +3 -2
  180. package/src/svg/checkbox-checked-icon.svg +4 -7
  181. package/src/svg/checkbox-hover.svg +3 -3
  182. package/src/svg/checkbox-off.svg +2 -3
  183. package/src/svg/checkbox-on.svg +3 -3
  184. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  185. package/src/svg/datagrid-select-column.svg +8 -15
  186. package/src/svg/datagrid-select-region.svg +8 -15
  187. package/src/svg/datagrid-select-row-tree.svg +7 -12
  188. package/src/svg/datagrid-select-row.svg +8 -13
  189. package/src/svg/date-type.svg +4 -23
  190. package/src/svg/drawer-tab-hover.svg +4 -6
  191. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  192. package/src/svg/drawer-tab-invert.svg +3 -5
  193. package/src/svg/drawer-tab.svg +5 -5
  194. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  195. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  196. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  197. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  198. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  199. package/src/svg/new-panel-icon.svg +4 -0
  200. package/src/svg/number-type.svg +3 -3
  201. package/src/svg/radio-hover.svg +3 -3
  202. package/src/svg/radio-off.svg +3 -3
  203. package/src/svg/radio-on.svg +3 -3
  204. package/src/svg/status_ok.svg +3 -26
  205. package/src/svg/string-type.svg +4 -8
  206. package/src/svg/theme-icon.svg +5 -5
  207. package/src/themes/blueprint.css +0 -57
  208. package/src/themes/botanical.css +0 -36
  209. package/src/themes/defaults.css +1 -24
  210. package/src/themes/dracula.css +0 -58
  211. package/src/themes/eggplant.css +0 -58
  212. package/src/themes/gruvbox-dark.css +0 -65
  213. package/src/themes/gruvbox.css +1 -39
  214. package/src/themes/icons.css +1 -8
  215. package/src/themes/intl/ar.css +185 -0
  216. package/src/themes/intl/de.css +45 -35
  217. package/src/themes/intl/es.css +40 -30
  218. package/src/themes/intl/fr.css +39 -29
  219. package/src/themes/intl/ja.css +41 -31
  220. package/src/themes/intl/pt.css +37 -27
  221. package/src/themes/intl/zh.css +47 -37
  222. package/src/themes/intl.css +16 -3
  223. package/src/themes/ledger.css +1 -59
  224. package/src/themes/monokai.css +0 -51
  225. package/src/themes/nord.css +0 -55
  226. package/src/themes/phosphor.css +0 -48
  227. package/src/themes/pro-dark.css +0 -37
  228. package/src/themes/pro.css +3 -23
  229. package/src/themes/solarized-dark.css +0 -31
  230. package/src/themes/solarized.css +1 -24
  231. package/src/themes/vaporwave.css +0 -44
  232. package/src/themes/velvet.css +0 -60
  233. package/src/ts/extensions.ts +0 -13
  234. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  235. package/src/css/plugin-settings-panel.css +0 -171
  236. package/src/rust/components/modal.rs +0 -177
  237. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  238. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  239. package/src/rust/custom_elements/modal.rs +0 -299
  240. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
  241. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
  242. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
  243. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
  244. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
  245. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
  246. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  247. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  248. /package/src/css/{containers → ui/containers}/tabs.css +0 -0
  249. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  250. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  251. /package/src/css/{dom → ui/dom}/select.css +0 -0
  252. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  253. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  254. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  255. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -60,7 +60,7 @@ fn wire_panel_render_sub(session: &Session, renderer: &Renderer) -> Subscription
60
60
  clone!(renderer, session);
61
61
  ApiFuture::spawn(async move {
62
62
  renderer
63
- .update_lazy(async move { Ok(session.get_view()) })
63
+ .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
64
64
  .await
65
65
  .ignore_view_delete()
66
66
  .map(|_| ())
@@ -28,7 +28,7 @@ pub fn dismiss_render_warning_callback(session: &Session, renderer: &Renderer) -
28
28
  ApiFuture::spawn(async move {
29
29
  renderer.disable_active_plugin_render_warning();
30
30
  renderer
31
- .update_lazy(async move { Ok(session.get_view()) })
31
+ .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
32
32
  .await
33
33
  });
34
34
  })
@@ -34,6 +34,7 @@ pub fn delete_all<T: Component>(workspace: &Workspace, root: &Root<T>) -> ApiFut
34
34
  let panels = workspace
35
35
  .take_reserved()
36
36
  .into_iter()
37
+ .chain(workspace.take_all_closing())
37
38
  .chain(
38
39
  workspace
39
40
  .panel_ids()
@@ -10,21 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! The render pipeline (see `SESSION_CONFIG_COHERENCE_PLAN.md`) — the ONE
14
- //! review surface for how config-driven renders happen.
15
- //!
16
- //! Invariants enforced here and by the types this composes:
17
- //! - **I1** config mutation is a synchronous commit
18
- //! ([`Session::commit_view_config`]) — no async writer exists.
19
- //! - **I2** every run consumes an immutable snapshot taken inside the draw
20
- //! lock; async stages never re-read the live config (the `ValidatedSnapshot`
21
- //! type-state token).
22
- //! - **I3** runs serialize on the per-`Renderer` draw lock (the [`RenderGuard`]
23
- //! witness — plugin dispatch without it does not compile) and every commit
24
- //! schedules a run, so the final render always reflects the final commit.
25
- //! - **I5** the run pins a [`RenderContext`] so plugin read-backs mid-render
26
- //! answer from the run's snapshot, never live state.
27
-
28
13
  use std::rc::Rc;
29
14
 
30
15
  use futures::future::LocalBoxFuture;
@@ -79,30 +64,10 @@ pub async fn bind_snapshot(
79
64
  /// legitimate concurrent public calls).
80
65
  #[derive(Clone, Copy, Debug, PartialEq, Eq)]
81
66
  pub enum RunOrigin {
82
- /// A public element API entry (`restore`, `restorePanel`, `reset`,
83
- /// `load`, …): an explicit external request, so a run that reconciles
84
- /// `Unchanged` with no state delta STILL repaints — the documented
85
- /// no-op-restore refresh affordance (`update` source 6, "the user
86
- /// asked" — `PLUGIN_DRAW_INVARIANT_PLAN.md` amendment).
87
67
  Public,
88
- /// A host-initiated run (UI commits, unpause resumes, subscriptions):
89
- /// a reconciled no-op dispatches nothing.
90
68
  Internal,
91
69
  }
92
70
 
93
- /// The ONE decision table mapping a run's [`BindDisposition`] to its plugin
94
- /// dispatch (`PLUGIN_DRAW_INVARIANT_PLAN.md`, as amended 2026-07-16):
95
- /// `plugin.draw` fires iff there is a NEW `View` — a REBUILD, or a
96
- /// freshly-selected plugin's first paint of the bound `View`
97
- /// (`promote_first_paint`). `plugin.update` fires iff the `View` is
98
- /// unchanged but a plugin-visible SOURCE changed: the `Adopted`
99
- /// config delta, a genuinely changed plugin/columns bucket this run
100
- /// delivered via `plugin.restore` (`plugin_state_changed`), or an explicit
101
- /// public API request ([`RunOrigin::Public`]). An INTERNAL run that
102
- /// reconciled `Unchanged` with no state delta dispatches NOTHING — no
103
- /// defensive repaints (this is what makes an inert plugin echo, e.g. the
104
- /// datagrid's `toggle_edit_mode` `restorePanel`, render-silent). Deferred
105
- /// binds dispatch nothing.
106
71
  pub async fn dispatch_bound(
107
72
  guard: &RenderGuard,
108
73
  renderer: &Renderer,
@@ -343,31 +308,6 @@ async fn render_run(
343
308
  }
344
309
  }
345
310
 
346
- /// One TRANSACTIONAL activation repaint (see the I5 audit gap in
347
- /// `SESSION_CONFIG_COHERENCE_PLAN.md` §4): under the panel's draw lock,
348
- /// stamp the `active` class + theme and `plugin.resize()` in ONE dispatch,
349
- /// so the activation-dependent chrome (e.g. the datagrid's edit
350
- /// column-header row, decided by its style listener reading that class) and
351
- /// the class that styles it land in a single paint commit, never split.
352
- ///
353
- /// `resize`, NOT `draw`/`update`: activation creates no new `View` and
354
- /// changes no data (`plugin.draw` ⇔ new `View` —
355
- /// `PLUGIN_DRAW_INVARIANT_PLAN.md`), and a full dispatch on charts is a
356
- /// fetch + multi-blit repaint — the stacked-tab two-stage regression, paid
357
- /// by BOTH sides of every tab switch. `resize` is each plugin's cheap
358
- /// repaint-from-retained-state, and both built-ins skip it while hidden, so
359
- /// the OUTGOING (unslotted) panel's nudge costs nothing. Deliberately NOT
360
- /// debounced/throttled: activation is a one-shot interaction, not a data
361
- /// stream, and the throttle timer is a task boundary the browser paints
362
- /// across.
363
- ///
364
- /// EXCEPTION: a panel whose `table_updated` redraws were dropped while it
365
- /// was hidden ([`Renderer::take_data_stale`]) HAS changed data — its
366
- /// retained frame is stale, so this activation dispatches one full
367
- /// `plugin.update` (same `View`, fresh data; `draw` stays reserved for new
368
- /// `View`s) instead of the chrome-only repaint. `draw_view` stamps the
369
- /// activation class + theme itself, so the atomicity contract above holds
370
- /// on this branch too.
371
311
  pub async fn activation_render(session: Session, renderer: Renderer) -> ApiResult<()> {
372
312
  let result = {
373
313
  clone!(session, renderer);
@@ -166,13 +166,14 @@ pub async fn presize_visible_panels_open(
166
166
  pub async fn presize_visible_panels_pane_width(
167
167
  workspace: &Workspace,
168
168
  elem: &web_sys::HtmlElement,
169
+ pane_sel: &str,
169
170
  pane_width: f64,
170
171
  ) -> StagedPresents {
171
172
  let Some(mpc) = shadow_rect(elem, "#main_panel_container") else {
172
173
  return StagedPresents::default();
173
174
  };
174
175
 
175
- let Some(pane) = shadow_rect(elem, "#app_panel > .split-panel-child") else {
176
+ let Some(pane) = shadow_rect(elem, pane_sel) else {
176
177
  return StagedPresents::default();
177
178
  };
178
179
 
@@ -308,6 +309,28 @@ fn shadow_rect(elem: &web_sys::HtmlElement, sel: &str) -> Option<web_sys::DomRec
308
309
  )
309
310
  }
310
311
 
312
+ /// The width the `SplitPanel` pane at `pane_sel` takes with no divider
313
+ /// override, measured under one forced synchronous layout that no paint or
314
+ /// `ResizeObserver` can observe, or `None` when the pane isn't in the DOM.
315
+ pub fn measure_pane_natural_width(elem: &web_sys::HtmlElement, pane_sel: &str) -> Option<f64> {
316
+ let pane = elem
317
+ .shadow_root()?
318
+ .query_selector(pane_sel)
319
+ .ok()??
320
+ .dyn_into::<web_sys::HtmlElement>()
321
+ .ok()?;
322
+
323
+ let style = pane.style();
324
+ let css_text = style.css_text();
325
+ let class_name = pane.class_name();
326
+ style.set_css_text("");
327
+ let _ = pane.class_list().remove_1("is-width-override");
328
+ let width = pane.get_bounding_client_rect().width();
329
+ style.set_css_text(&css_text);
330
+ pane.set_class_name(&class_name);
331
+ Some(width)
332
+ }
333
+
311
334
  /// The open-state geometry deltas cached for the next settings *open* (P2):
312
335
  /// `(layout_area.w − mpc.w, main_column.h − mpc.h)`. Only meaningful while the
313
336
  /// settings pane is open (i.e. measured at close time).
@@ -10,10 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! Apply a full [`ViewerConfigUpdate`] (settings, theme, title, plugin,
14
- //! plugin_config, columns_config, view_config) and re-draw, on the snapshot
15
- //! pipeline (see `tasks/pipeline.rs` / `SESSION_CONFIG_COHERENCE_PLAN.md`).
16
-
17
13
  use futures::Future;
18
14
  use perspective_client::clone;
19
15
 
@@ -28,19 +24,6 @@ use crate::*;
28
24
  /// Apply a full [`ViewerConfigUpdate`] (theme, title, plugin selection,
29
25
  /// plugin config, columns config, view config) to the engines and re-draw.
30
26
  /// Returns an [`ApiFuture<()>`] which resolves when the draw completes.
31
- ///
32
- /// `origin` says who initiated the restore ([`RunOrigin`]): a `Public`
33
- /// element-API call keeps the no-op-restore refresh affordance (an
34
- /// `Unchanged` reconcile still repaints via `update`); an `Internal`
35
- /// restore that reconciles `Unchanged` and changes no plugin state
36
- /// dispatches nothing.
37
- ///
38
- /// This function owns the PRE-LOCK prologue only (element-level settings /
39
- /// title / host-theme mirror, plugin resolution, the synchronous config
40
- /// commit and spinner token); the run itself is
41
- /// [`locked_run`] with the caller's `task` awaited inside the lock. The
42
- /// `update`'s `table` field is NOT applied here — table binding is the
43
- /// `task`'s job (see `restore_panel` / `table_lifecycle`).
44
27
  pub fn restore_and_render(
45
28
  session: &Session,
46
29
  renderer: &Renderer,
@@ -49,7 +49,7 @@ pub fn send_column_config(
49
49
 
50
50
  clone!(session);
51
51
  renderer
52
- .update_lazy(async move { Ok(session.get_view()) })
52
+ .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
53
53
  .await?;
54
54
  renderer.column_style_changed.emit(columns_configs);
55
55
  Ok(())
@@ -44,7 +44,7 @@ pub fn send_plugin_config(session: &Session, renderer: &Renderer, update: Column
44
44
  .restore(&plugin_token, Some(&columns_configs))?;
45
45
  clone!(session);
46
46
  renderer
47
- .update_lazy(async move { Ok(session.get_view()) })
47
+ .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
48
48
  .await?;
49
49
  renderer.plugin_config_changed.emit(plugin_config);
50
50
  }
@@ -51,7 +51,7 @@ pub fn set_edit_mode(session: &Session, renderer: &Renderer, mode: &str) {
51
51
  .restore(&plugin_token, Some(&columns_configs))?;
52
52
  clone!(session);
53
53
  renderer
54
- .update_lazy(async move { Ok(session.get_view()) })
54
+ .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
55
55
  .await?;
56
56
  renderer.plugin_config_changed.emit(plugin_config);
57
57
  }
@@ -12,7 +12,12 @@
12
12
 
13
13
  use wasm_bindgen::JsCast;
14
14
  use web_sys::{Event, MouseEvent};
15
- use yew::{Callback, Html, Properties, function_component, html, use_callback, use_mut_ref};
15
+ use yew::{
16
+ Callback, Html, Properties, function_component, html, use_callback, use_effect_with,
17
+ use_mut_ref, use_node_ref,
18
+ };
19
+
20
+ use crate::ui::form::intl_label::IntlLabel;
16
21
 
17
22
  #[derive(Properties, PartialEq)]
18
23
  pub struct ControlGroupProps {
@@ -78,12 +83,29 @@ pub fn ControlGroup(props: &ControlGroupProps) -> Html {
78
83
  }
79
84
  });
80
85
 
86
+ let details_ref = use_node_ref();
87
+ {
88
+ let details_ref = details_ref.clone();
89
+ use_effect_with(
90
+ (props.group_key.clone(), props.on_toggle.clone()),
91
+ move |(group_key, on_toggle)| {
92
+ let group_key = group_key.clone();
93
+ let on_toggle = on_toggle.clone();
94
+ move || {
95
+ if let Some(el) = details_ref.cast::<web_sys::Element>() {
96
+ on_toggle.emit((group_key, el.has_attribute("open")));
97
+ }
98
+ }
99
+ },
100
+ );
101
+ }
102
+
81
103
  let open = *use_mut_ref(|| props.open).borrow();
82
104
  html! {
83
- <details class="control-group" {open} {ontoggle}>
105
+ <details ref={details_ref} class="control-group" {open} {ontoggle}>
84
106
  <summary {onclick}>
85
107
  <span class="control-group-chevron shift-alt-icon" />
86
- <label id={format!("{}-group-label", props.group_key)} />
108
+ <IntlLabel name={props.group_key.clone()} group=true />
87
109
  </summary>
88
110
  { props.children.clone() }
89
111
  </details>
@@ -27,6 +27,7 @@ where
27
27
  pub callback: Callback<T>,
28
28
  }
29
29
 
30
+ /// A clickable item list, optionally grouped (`DropDownMenuItem::OptGroup`).
30
31
  pub struct DropDownMenu<T>
31
32
  where
32
33
  T: Into<Html> + Clone + PartialEq + 'static,
@@ -64,7 +65,9 @@ where
64
65
  });
65
66
 
66
67
  html! {
67
- <span onmousedown={click} class="selected">{ x.clone().into() }</span>
68
+ <span onmousedown={click} class="dropdown-menu-item">
69
+ { x.clone().into() }
70
+ </span>
68
71
  }
69
72
  },
70
73
  DropDownMenuItem::OptGroup(name, xs) => {
@@ -78,7 +81,7 @@ where
78
81
  move |_: MouseEvent| value.clone()
79
82
  });
80
83
  html! {
81
- <span onmousedown={ click }>
84
+ <span onmousedown={ click } class="dropdown-menu-item">
82
85
  { x.clone().into() }
83
86
  </span>
84
87
  }
@@ -10,11 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! `containers` are generic container-like components for collections of other
14
- //! `Component` types.
15
-
16
13
  pub mod control_group;
17
- pub mod dragdrop_list;
18
14
  pub mod dropdown_menu;
19
15
  pub mod scroll_panel;
20
16
  pub mod scroll_panel_item;
@@ -10,9 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- // Forked from https://github.com/AircastDev/yew-virtual-scroller (Apache 2.0)
14
- // Adds support for Yew 0.19, auto-width and a simplified message structure.
15
-
16
13
  use std::ops::Range;
17
14
  use std::rc::Rc;
18
15
 
@@ -67,8 +64,6 @@ pub struct ScrollPanelProps {
67
64
  }
68
65
 
69
66
  impl ScrollPanelProps {
70
- /// Calculate the total virtual height of this scroll panel from the `size`
71
- /// prop of its children.
72
67
  fn total_height(&self) -> f64 {
73
68
  self.children
74
69
  .iter()
@@ -148,8 +143,6 @@ impl Component for ScrollPanel {
148
143
  self.calculate_window_content(ctx)
149
144
  },
150
145
 
151
- // TODO don't hardcode pixel offsets - fix the CSS container to not
152
- // require pixel offsets to work.
153
146
  ScrollPanelMsg::UpdateViewportDimensions => {
154
147
  let viewport = self.viewport_elem(ctx);
155
148
  let rect = viewport.get_bounding_client_rect();
@@ -174,9 +167,6 @@ impl Component for ScrollPanel {
174
167
  }
175
168
  }
176
169
 
177
- /// If the new total row height is different than last time this component
178
- /// was rendered, we need to double-render to read the container's
179
- /// potentially updated height.
180
170
  fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
181
171
  let total_height = ctx.props().total_height();
182
172
  self.needs_rerender =
@@ -213,8 +203,6 @@ impl Component for ScrollPanel {
213
203
  ]
214
204
  });
215
205
 
216
- // TODO This glitches - we should use the `sticky` positioning strategy that
217
- // `regular-table` uses.
218
206
  html! {
219
207
  <div
220
208
  ref={self.viewport(ctx)}
@@ -306,16 +294,6 @@ impl ScrollPanel {
306
294
  .map(|x| x.0)
307
295
  .unwrap_or_default();
308
296
 
309
- // Why is this `end_node + 2`, I can see you asking yourself? `end_node` is the
310
- // index of the last visible child, but [`Range`] is an open interval so we must
311
- // increment by 1. The next rendered element is always occluded by the parent
312
- // container, it may seem unnecessary to render it, however not doing so causing
313
- // scroll glitching in Chrome:
314
- // * When the first pixel of the `end_node + 1` child is scrolled into view, the
315
- // container element it is embedded in will expand past the end of the scroll
316
- // container.
317
- // * Chrome detects this and helpfully scrolls this new element into view,
318
- // re-triggering the on scroll callback.
319
297
  let visible_range = start_node..ctx.props().children.len().min(end_node + 2);
320
298
  let content_window = Some(ContentWindow {
321
299
  scroll_top,
@@ -10,9 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- // Forked from https://github.com/AircastDev/yew-virtual-scroller (Apache 2.0)
14
- // Adds support for Yew 0.19, auto-width and a simplified message structure.
15
-
16
13
  use std::cell::Cell;
17
14
  use std::rc::Rc;
18
15
 
@@ -22,21 +19,11 @@ use yew::prelude::*;
22
19
  pub struct ScrollPanelItemProps {
23
20
  pub children: Children,
24
21
 
25
- /// The expected size of this component in pixels. Calculating this value
26
- /// ahead of time makes it easier to implement a high-performance virtual
27
- /// renderer without resorting to weird tricks, _but_ checking that this
28
- /// hint is correct is nearly as expensive. So, we only generate the
29
- /// validation code in debug builds.
30
22
  pub size_hint: f64,
31
23
 
32
- /// Should we measure the child sizes or just rely on the hint? This
33
- /// option may be enabled during debugging to validation render optimization
34
- /// via [`Self::size_hint`], but will faile tests due to the extra DOM
35
- /// structure it emits.
36
24
  #[prop_or_default]
37
25
  pub is_debug_mode: bool,
38
26
 
39
- /// Communicate the panel size to virtual parent
40
27
  #[prop_or_default]
41
28
  measured_size: Rc<Cell<Option<f64>>>,
42
29
  }
@@ -88,10 +88,6 @@ where
88
88
  true
89
89
  },
90
90
 
91
- // All the up and down arrow keys to instantly select, which is
92
- // useful for keyboard warrios. This is not ada without the shift
93
- // key. I'm not sure if it is _with_ the shift key either, but its
94
- // cool.
95
91
  SelectMsg::KeyboardInput(is_shift, idx, code) => {
96
92
  if is_shift {
97
93
  if code.as_str() == "ArrowUp" {
@@ -114,13 +110,11 @@ where
114
110
  }
115
111
  }
116
112
 
117
- // The `<select>` has its own state not refelcted by `SelectProps`.
118
113
  fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
119
114
  self.selected = ctx.props().selected.clone();
120
115
  true
121
116
  }
122
117
 
123
- // Annoyingly, `<select>` cannot be updated from its HTML alone.
124
118
  fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
125
119
  if let Some(elem) = self.select_ref.cast::<web_sys::HtmlSelectElement>() {
126
120
  elem.set_value(&format!("{}", self.selected))
@@ -19,38 +19,25 @@ use yew::{
19
19
  use_node_ref,
20
20
  };
21
21
 
22
- use crate::components::containers::sidebar_close_button::SidebarCloseButton;
23
- use crate::components::editable_header::{EditableHeader, EditableHeaderProps};
24
22
  use crate::js::{ResizeObserver, ResizeObserverEntry};
23
+ use crate::ui::containers::sidebar_close_button::SidebarCloseButton;
25
24
 
26
25
  #[derive(PartialEq, Clone, Properties)]
27
26
  pub struct SidebarProps {
28
- /// The component's children.
29
27
  pub children: Children,
30
28
 
31
- /// When this callback is called, the sidebar will close
32
29
  pub on_close: Callback<()>,
33
30
  pub id_prefix: String,
34
31
  pub width_override: Option<i32>,
35
32
  pub selected_tab: Option<usize>,
36
- pub header_props: EditableHeaderProps,
37
-
38
- /// Trap-door width shared across this sidebar's tabs: the lifted
39
- /// running max of the widths this component reports through
40
- /// `on_auto_width`. Held by the parent (ultimately
41
- /// `PerspectiveViewer`'s geometry state, like the settings panel's
42
- /// Query/Plugin/Debug trap-door) so it survives tab switches AND
43
- /// sidebar re-mounts, and clears on divider reset.
33
+ pub header: Html,
34
+
44
35
  #[prop_or_default]
45
36
  pub auto_width: f64,
46
37
 
47
- /// Fires with the sidebar's rendered width after each render; the
48
- /// owner keeps the running max threaded back as `auto_width`.
49
38
  #[prop_or_default]
50
39
  pub on_auto_width: Callback<f64>,
51
40
 
52
- /// Pinned state for the header's pin toggle; the button renders only
53
- /// when `on_toggle_pin` is provided.
54
41
  #[prop_or_default]
55
42
  pub is_pinned: bool,
56
43
 
@@ -58,23 +45,12 @@ pub struct SidebarProps {
58
45
  pub on_toggle_pin: Option<Callback<()>>,
59
46
  }
60
47
 
61
- /// Sidebars are designed to live in a
62
- /// [`super::split_panel::SplitPanel`]
48
+ /// Sidebars are designed to live in a `SplitPanel`.
63
49
  #[function_component]
64
50
  pub fn Sidebar(p: &SidebarProps) -> Html {
65
51
  let id = &p.id_prefix;
66
52
  let noderef = use_node_ref();
67
53
 
68
- // The trap-door reports the sidebar's rendered width to its owner via
69
- // a `ResizeObserver` on the sidebar element, NOT a render effect:
70
- // width changes are driven by DOM mutations anywhere in the tab
71
- // subtree (e.g. the window editor staging a long column name into a
72
- // slot), which need not re-render this component at all. The observer
73
- // sees every one. `contentRect` (not the border box) is load-bearing:
74
- // the sizer below is a content child, so ratcheting the border box
75
- // would feed any sidebar padding back into unbounded growth. A manual
76
- // divider drag (`width_override`) disables the ratchet until the
77
- // divider resets.
78
54
  let live_props = use_mut_ref(|| (Callback::<f64>::default(), None::<i32>));
79
55
  *live_props.borrow_mut() = (p.on_auto_width.clone(), p.width_override);
80
56
  use_effect_with((), {
@@ -139,10 +115,7 @@ pub fn Sidebar(p: &SidebarProps) -> Html {
139
115
  <>
140
116
  <SidebarCloseButton id={format!("{id}_close_button")} on_close_sidebar={&p.on_close} />
141
117
  <div class="sidebar_column" id={format!("{id}_sidebar")} ref={noderef}>
142
- <div class="sidebar_header">
143
- <EditableHeader ..p.header_props.clone() />
144
- { pin_button }
145
- </div>
118
+ <div class="sidebar_header">{ p.header.clone() }{ pin_button }</div>
146
119
  <div class="sidebar_border" id={format!("{id}_border")} />
147
120
  { p.children.iter().collect::<Html>() }
148
121
  <div class="sidebar-auto-width" style={width_style} />