@perspective-dev/viewer 5.3.1 → 5.5.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 (291) 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.d.ts +2 -0
  32. package/dist/esm/perspective-viewer.inline.js +8 -8
  33. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  34. package/dist/esm/perspective-viewer.js +5 -5
  35. package/dist/esm/perspective-viewer.js.map +2 -2
  36. package/dist/esm/plugin.d.ts +9 -0
  37. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  43. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  44. package/dist/wasm/perspective-viewer.d.ts +137 -104
  45. package/dist/wasm/perspective-viewer.js +337 -269
  46. package/dist/wasm/perspective-viewer.wasm +0 -0
  47. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
  48. package/package.json +7 -7
  49. package/src/css/_context_menu.css +1 -1
  50. package/src/css/_dropdown_menu.css +1 -1
  51. package/src/css/_viewer_bundle.css +7 -8
  52. package/src/css/column-dropdown.css +0 -3
  53. package/src/css/column-selector.css +18 -7
  54. package/src/css/column-settings-panel.css +0 -225
  55. package/src/css/column-style.css +175 -0
  56. package/src/css/config-selector.css +19 -11
  57. package/src/css/filter-dropdown.css +5 -5
  58. package/src/css/form/debug.css +37 -4
  59. package/src/css/function-dropdown.css +5 -5
  60. package/src/css/panel-tab.css +23 -0
  61. package/src/css/plugin-selector.css +5 -5
  62. package/src/css/render-warning.css +1 -1
  63. package/src/css/status-bar.css +63 -21
  64. package/src/css/type-icon.css +1 -3
  65. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  66. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  67. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  68. package/src/css/{containers → ui/containers}/tabs.css +2 -1
  69. package/src/css/viewer.css +32 -26
  70. package/src/rust/agent/tools.rs +80 -7
  71. package/src/rust/components/chat_panel.rs +1 -1
  72. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  73. package/src/rust/components/column_dropdown.rs +2 -2
  74. package/src/rust/components/column_selector/active_column.rs +24 -9
  75. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  76. package/src/rust/components/column_selector/config_selector.rs +2 -2
  77. package/src/rust/components/column_selector/filter_column.rs +8 -5
  78. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  79. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  80. package/src/rust/components/column_selector/sort_column.rs +7 -4
  81. package/src/rust/components/column_selector.rs +14 -3
  82. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  83. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  84. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  85. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
  86. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  87. package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
  88. package/src/rust/components/column_settings_sidebar.rs +3 -4
  89. package/src/rust/components/copy_dropdown.rs +1 -1
  90. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  91. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  92. package/src/rust/components/datetime_column_style.rs +1 -2
  93. package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
  94. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  95. package/src/rust/components/export_dropdown.rs +1 -1
  96. package/src/rust/components/expression_editor.rs +1 -1
  97. package/src/rust/components/filter_dropdown.rs +2 -2
  98. package/src/rust/components/function_dropdown.rs +2 -2
  99. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  100. package/src/rust/components/main_panel/msg.rs +4 -0
  101. package/src/rust/components/main_panel/reconcile.rs +9 -4
  102. package/src/rust/components/main_panel/render.rs +62 -11
  103. package/src/rust/components/main_panel/update.rs +39 -10
  104. package/src/rust/components/main_panel.rs +40 -2
  105. package/src/rust/components/mod.rs +5 -8
  106. package/src/rust/components/new_panel_menu.rs +97 -0
  107. package/src/rust/components/number_series_style.rs +2 -3
  108. package/src/rust/components/panel_menu.rs +61 -87
  109. package/src/rust/components/panel_tab.rs +100 -43
  110. package/src/rust/components/plugin_tab.rs +58 -4
  111. package/src/rust/components/render_warning.rs +2 -2
  112. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  113. package/src/rust/components/settings_panel.rs +8 -2
  114. package/src/rust/components/status_bar.rs +113 -19
  115. package/src/rust/components/status_indicator.rs +9 -14
  116. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  117. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  118. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  119. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  120. package/src/rust/components/viewer/msg.rs +43 -30
  121. package/src/rust/components/viewer/panels.rs +53 -19
  122. package/src/rust/components/viewer/render.rs +16 -13
  123. package/src/rust/components/viewer/settings.rs +137 -68
  124. package/src/rust/components/viewer/snapshots.rs +5 -10
  125. package/src/rust/components/viewer/wiring.rs +11 -22
  126. package/src/rust/components/viewer.rs +15 -10
  127. package/src/rust/components/window_editor.rs +8 -9
  128. package/src/rust/config/column_config_schema.rs +312 -33
  129. package/src/rust/config/datetime_column_style.rs +0 -11
  130. package/src/rust/config/mod.rs +0 -4
  131. package/src/rust/config/options.rs +50 -0
  132. package/src/rust/config/workspace_config.rs +70 -24
  133. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  134. package/src/rust/custom_elements/mod.rs +0 -3
  135. package/src/rust/custom_elements/viewer.rs +194 -115
  136. package/src/rust/js/plugin.rs +3 -87
  137. package/src/rust/lib.rs +19 -18
  138. package/src/rust/presentation/column_locator.rs +3 -7
  139. package/src/rust/presentation.rs +68 -38
  140. package/src/rust/queries/hosted_tables.rs +38 -0
  141. package/src/rust/queries/mod.rs +2 -0
  142. package/src/rust/queries/palette_set.rs +1 -1
  143. package/src/rust/queries/plugin_column_styles.rs +6 -5
  144. package/src/rust/renderer/activate.rs +1 -3
  145. package/src/rust/renderer/dispatch.rs +83 -22
  146. package/src/rust/renderer/limits.rs +24 -5
  147. package/src/rust/renderer/plugin_config.rs +201 -41
  148. package/src/rust/renderer/plugin_store.rs +2 -1
  149. package/src/rust/renderer/props.rs +7 -1
  150. package/src/rust/renderer.rs +23 -27
  151. package/src/rust/session/props.rs +8 -3
  152. package/src/rust/session/view_subscription.rs +19 -6
  153. package/src/rust/session.rs +189 -150
  154. package/src/rust/tasks/create_panel.rs +5 -2
  155. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  156. package/src/rust/tasks/eject.rs +1 -0
  157. package/src/rust/tasks/pipeline.rs +18 -67
  158. package/src/rust/tasks/presize_panels.rs +24 -1
  159. package/src/rust/tasks/restore_and_render.rs +0 -17
  160. package/src/rust/tasks/restore_panel.rs +129 -30
  161. package/src/rust/tasks/send_column_config.rs +4 -1
  162. package/src/rust/tasks/send_plugin_config.rs +1 -1
  163. package/src/rust/tasks/set_edit_mode.rs +1 -1
  164. package/src/rust/tasks/sync_update_panels.rs +147 -67
  165. package/src/rust/tasks/table_lifecycle.rs +7 -1
  166. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  167. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  168. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  169. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  170. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  171. package/src/rust/{components → ui}/containers/select.rs +0 -6
  172. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  173. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  174. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  175. package/src/rust/{components → ui}/context_menu.rs +8 -13
  176. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  177. package/src/rust/{components → ui}/font_loader.rs +0 -26
  178. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  179. package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
  180. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  181. package/src/rust/{components → ui}/form/mod.rs +1 -10
  182. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  183. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  184. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  185. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  186. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  187. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  188. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  189. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  190. package/src/rust/ui/mod.rs +56 -0
  191. package/src/rust/ui/modal.rs +61 -0
  192. package/src/rust/{components → ui}/portal.rs +175 -79
  193. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  194. package/src/rust/utils/completion.rs +0 -6
  195. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  196. package/src/rust/utils/css_vars.rs +13 -0
  197. package/src/rust/utils/local_fonts.rs +395 -0
  198. package/src/rust/utils/mod.rs +4 -0
  199. package/src/rust/workspace.rs +47 -0
  200. package/src/svg/boolean-type.svg +3 -2
  201. package/src/svg/checkbox-checked-icon.svg +4 -7
  202. package/src/svg/checkbox-hover.svg +3 -3
  203. package/src/svg/checkbox-off.svg +2 -3
  204. package/src/svg/checkbox-on.svg +3 -3
  205. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  206. package/src/svg/datagrid-select-column.svg +8 -15
  207. package/src/svg/datagrid-select-region.svg +8 -15
  208. package/src/svg/datagrid-select-row-tree.svg +7 -12
  209. package/src/svg/datagrid-select-row.svg +8 -13
  210. package/src/svg/date-type.svg +4 -23
  211. package/src/svg/drawer-tab-hover.svg +4 -6
  212. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  213. package/src/svg/drawer-tab-invert.svg +3 -5
  214. package/src/svg/drawer-tab.svg +5 -5
  215. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  216. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  217. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  218. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  219. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  220. package/src/svg/new-panel-icon.svg +4 -0
  221. package/src/svg/number-type.svg +3 -3
  222. package/src/svg/radio-hover.svg +3 -3
  223. package/src/svg/radio-off.svg +3 -3
  224. package/src/svg/radio-on.svg +3 -3
  225. package/src/svg/status_ok.svg +3 -26
  226. package/src/svg/status_pending.svg +3 -0
  227. package/src/svg/string-type.svg +4 -8
  228. package/src/svg/theme-icon.svg +5 -5
  229. package/src/themes/blueprint.css +0 -57
  230. package/src/themes/botanical.css +0 -36
  231. package/src/themes/defaults.css +1 -24
  232. package/src/themes/dracula.css +0 -58
  233. package/src/themes/eggplant.css +0 -58
  234. package/src/themes/gruvbox-dark.css +0 -65
  235. package/src/themes/gruvbox.css +1 -39
  236. package/src/themes/icons.css +2 -8
  237. package/src/themes/intl/ar.css +198 -0
  238. package/src/themes/intl/de.css +63 -40
  239. package/src/themes/intl/es.css +58 -35
  240. package/src/themes/intl/fr.css +57 -34
  241. package/src/themes/intl/ja.css +59 -36
  242. package/src/themes/intl/pt.css +54 -31
  243. package/src/themes/intl/zh.css +65 -42
  244. package/src/themes/intl.css +34 -8
  245. package/src/themes/ledger.css +1 -59
  246. package/src/themes/monokai.css +0 -51
  247. package/src/themes/nord.css +0 -55
  248. package/src/themes/phosphor.css +0 -48
  249. package/src/themes/pro-dark.css +0 -37
  250. package/src/themes/pro.css +3 -23
  251. package/src/themes/solarized-dark.css +0 -31
  252. package/src/themes/solarized.css +1 -24
  253. package/src/themes/vaporwave.css +0 -44
  254. package/src/themes/velvet.css +0 -60
  255. package/src/ts/extensions.ts +0 -13
  256. package/src/ts/perspective-viewer.ts +2 -0
  257. package/src/ts/plugin.ts +9 -0
  258. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  259. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  260. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  261. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  262. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  263. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  264. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  265. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  266. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  267. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  268. package/src/css/plugin-settings-panel.css +0 -171
  269. package/src/rust/components/modal.rs +0 -177
  270. package/src/rust/components/string_column_style.rs +0 -121
  271. package/src/rust/config/string_column_style.rs +0 -135
  272. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  273. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  274. package/src/rust/custom_elements/modal.rs +0 -299
  275. package/src/ts/ts-rs/FormatMode.ts +0 -3
  276. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  277. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
  278. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
  279. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
  280. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
  281. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
  282. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
  283. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  284. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  285. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  286. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  287. /package/src/css/{dom → ui/dom}/select.css +0 -0
  288. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  289. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  290. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  291. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -21,42 +21,6 @@
21
21
  //! own per-panel themed background), but while the plugin is missing — not yet
22
22
  //! mounted, first draw pending, or torn down — the host-theme container shows
23
23
  //! through a panel themed differently, as a visible artifact.
24
- //!
25
- //! There is no native cascade channel from the light-DOM plugin to the frame
26
- //! (siblings in the flattened tree), so the value is mirrored imperatively:
27
- //! each panel's *plugin element* — light-DOM, slotted under the panel's slot,
28
- //! mounted eagerly at panel creation (`create_panel_model` /
29
- //! `Renderer::mount_active_plugin`) — is where the document theme rules
30
- //! actually resolve, so its computed `--psp--background-color` is copied onto
31
- //! the frame's inline style, where the frame's shadow parts inherit it. The
32
- //! plugin is the ONLY sound source element:
33
- //!
34
- //! - An owned hidden probe child of the viewer does NOT work: an *unslotted*
35
- //! light-DOM child of a shadow host is outside the flattened tree, and
36
- //! `getComputedStyle` returns all-empty for it even though the document rules
37
- //! match it syntactically (selector matching is DOM-tree; style computation
38
- //! is flat-tree).
39
- //! - The `<perspective-viewer-tab>` is slotted, but stamps its own `theme` attr
40
- //! in `PanelTab::rendered` — an unordered sibling lifecycle relative to this
41
- //! pass (the theme-stamp-lag class of bug).
42
- //! - The plugin's `theme` attr, by contrast, is stamped *synchronously at the
43
- //! mutation sites* (`update_theme`, `restorePanel`, …) before the events that
44
- //! schedule this render, so it is current when this pass reads it.
45
- //!
46
- //! A read is trusted ONLY when the plugin's stamped attr equals the theme
47
- //! being mirrored: a freshly-created panel's plugin is mounted *unstamped*
48
- //! (the attr first lands inside its first locked dispatch), and reading it
49
- //! early would silently mirror the HOST theme's values. Trusted reads seed
50
- //! [`MainPanel::theme_backgrounds`], the fallback for frames with no readable
51
- //! plugin (an unregistered plugin name stays lazily unmounted; plugin-switch
52
- //! and teardown transients). A frame with neither leaves the pass gate
53
- //! unlatched, so it retries on subsequent renders until the dispatch stamp
54
- //! lands (the render that follows that dispatch's `update_count` bump).
55
- //!
56
- //! Unlike the plugin `theme`/`active` stamps (see the NOTE in [`reconcile`]),
57
- //! the frame is pure viewer chrome — no plugin dispatch reads it — so
58
- //! stamping it from this async `rendered` pass cannot split a plugin draw
59
- //! across paints, and it needs no lock.
60
24
 
61
25
  use wasm_bindgen::prelude::*;
62
26
  use yew::prelude::*;
@@ -69,12 +33,7 @@ use crate::workspace::PanelId;
69
33
  /// `.rl-panel::part(container)` background resolves.
70
34
  const BACKGROUND_VAR: &str = "--psp--background-color";
71
35
 
72
- /// The inputs the frame backgrounds were last computed from; re-mirroring is
73
- /// skipped while these are unchanged, so the forced style recalcs of
74
- /// `getComputedStyle` don't run on every render (e.g. the per-update
75
- /// `update_count` renders of a streaming table). `available_themes` is part of
76
- /// the key because theme CSS registering late changes the *computed* value
77
- /// under an unchanged theme name.
36
+ /// The inputs the frame backgrounds were last computed from.
78
37
  pub(super) type FrameThemeSnapshot = (
79
38
  Vec<PanelId>,
80
39
  Vec<(String, Option<String>)>,
@@ -82,12 +41,7 @@ pub(super) type FrameThemeSnapshot = (
82
41
  );
83
42
 
84
43
  impl MainPanel {
85
- /// Mirror each panel's effective theme background (its own theme, else the
86
- /// registry default — the same fallback the tab/menu use) onto its
87
- /// `<regular-layout-frame>`'s inline `--psp--background-color`, read off
88
- /// the panel's stamped plugin element (else the theme-keyed cache of prior
89
- /// reads). A panel with no resolvable theme has the property removed,
90
- /// falling back to the inherited host-theme value.
44
+ /// Mirror each panel's effective theme background.
91
45
  pub(super) fn stamp_frame_themes(&mut self, ctx: &Context<Self>) {
92
46
  let Some(layout) = self.layout_ref.cast::<web_sys::Element>() else {
93
47
  return;
@@ -104,8 +58,6 @@ impl MainPanel {
104
58
  return;
105
59
  }
106
60
 
107
- // A theme-registry change can re-value an unchanged theme name, so
108
- // cached reads from the previous registry are unsound.
109
61
  if let Some((_, _, prev_themes)) = &self.stamped_frame_themes
110
62
  && prev_themes != &snapshot.2
111
63
  {
@@ -155,19 +107,11 @@ impl MainPanel {
155
107
  }
156
108
  }
157
109
 
158
- // Latch only a fully-resolved pass; an unresolved frame (unmounted or
159
- // not-yet-stamped plugin) retries each render until its dispatch
160
- // stamp lands.
161
110
  self.stamped_frame_themes = complete.then_some(snapshot);
162
111
  }
163
112
  }
164
113
 
165
- /// Read the computed [`BACKGROUND_VAR`] off `slot`'s plugin element — the
166
- /// viewer light-DOM child mounted under exactly that slot name (the tab and
167
- /// toolbar use `tab-`/`statusbar-extra-` prefixed slots). Requires the
168
- /// plugin's `theme` attr to already equal `theme` — during a plugin switch
169
- /// two elements briefly share the slot, and a freshly-mounted plugin is not
170
- /// yet stamped; both are disambiguated by the attr check.
114
+ /// Read the computed [`BACKGROUND_VAR`] off `slot`'s plugin element.
171
115
  fn read_plugin_background(viewer: &web_sys::Element, slot: &str, theme: &str) -> Option<String> {
172
116
  let children = viewer.children();
173
117
  let plugin = (0..children.length())
@@ -25,6 +25,10 @@ pub enum MainPanelMsg {
25
25
  /// deliver in every observed context.
26
26
  StagedChanged,
27
27
 
28
+ /// A layout tree was staged for the retained panel set
29
+ /// (`layout_staged` PubSub): re-render so `reconcile` applies it.
30
+ LayoutStaged,
31
+
28
32
  /// The `<regular-layout>` tree changed (fired by its
29
33
  /// `regular-layout-update` event). Used to detect panels removed from
30
34
  /// the layout (e.g. a frame's close button) so they can be disposed.
@@ -190,16 +190,21 @@ impl MainPanel {
190
190
  self.inserted.push(name.to_owned());
191
191
  }
192
192
 
193
- // Remove cells whose panels are gone.
193
+ let mut pending = std::mem::take(&mut self.pending_removals);
194
194
  self.inserted.retain(|name| {
195
195
  if panel_ids.iter().any(|id| id.as_str() == name) {
196
- true
197
- } else {
196
+ return true;
197
+ }
198
+
199
+ if pending.insert(name.clone()) {
198
200
  let _ = layout.remove_panel(name);
199
- false
200
201
  }
202
+
203
+ true
201
204
  });
202
205
 
206
+ self.pending_removals = pending;
207
+
203
208
  // A removed panel can't stay "maximized" (regular-layout drops the
204
209
  // maximize stylesheet when its panel leaves the layout).
205
210
  if let Some(m) = &self.maximized
@@ -18,7 +18,8 @@ use wasm_bindgen::prelude::*;
18
18
  use yew::prelude::*;
19
19
 
20
20
  use super::{MainPanel, MainPanelMsg, MainPanelProps};
21
- use crate::components::panel_menu::PanelMenu;
21
+ use crate::components::new_panel_menu::NewPanelPick;
22
+ use crate::components::panel_menu::{PanelCommand, PanelMenu};
22
23
  use crate::components::panel_tab::PanelTab;
23
24
  use crate::components::render_warning::RenderWarning;
24
25
  use crate::components::status_bar::StatusBar;
@@ -73,14 +74,26 @@ impl MainPanel {
73
74
  let active_slot = renderer.slot_name();
74
75
  // Panel-count chrome (`single`/`multi`, closable, draggable)
75
76
  let placed_count = ctx.props().workspace.placed_count();
77
+ let closing = self
78
+ .inserted
79
+ .iter()
80
+ .filter(|name| !ctx.props().panel_ids.iter().any(|id| id.as_str() == *name))
81
+ .map(|name| PanelId::from(name.as_str()))
82
+ .collect::<Vec<_>>();
76
83
  let cells = ctx
77
84
  .props()
78
85
  .panel_ids
79
86
  .iter()
80
87
  .filter(|id| !ctx.props().workspace.is_staged(id))
81
- .map(|id| {
88
+ .map(|id| (id, false))
89
+ .chain(closing.iter().map(|id| (id, true)))
90
+ .map(|(id, is_closing)| {
82
91
  let name = id.as_str().to_owned();
83
92
  let mut class = classes!("rl-panel");
93
+ if is_closing {
94
+ class.push("closing");
95
+ }
96
+
84
97
  if placed_count > 1 {
85
98
  if active_slot.as_deref() == Some(name.as_str()) {
86
99
  class.push("active");
@@ -103,7 +116,9 @@ impl MainPanel {
103
116
  let on_activate = ctx.props().on_activate_panel.clone();
104
117
  let activate_name = name.clone();
105
118
  let onpointerdown = Callback::from(move |_: web_sys::PointerEvent| {
106
- on_activate.emit(activate_name.clone());
119
+ if !is_closing {
120
+ on_activate.emit(activate_name.clone());
121
+ }
107
122
  });
108
123
 
109
124
  // Right-click anywhere in the panel opens the context menu. This
@@ -214,23 +229,41 @@ impl MainPanel {
214
229
  on_settings.emit(());
215
230
  })
216
231
  };
232
+ let closing_tabs = closing.iter().filter_map(|id| {
233
+ let panel = ctx.props().workspace.closing_panel(id)?;
234
+ let title = panel.session.get_title().filter(|t| !t.is_empty());
235
+ let theme = panel.renderer.theme().or_else(|| {
236
+ ctx.props()
237
+ .presentation_props
238
+ .available_themes
239
+ .first()
240
+ .cloned()
241
+ });
242
+ Some((id, panel.session, title, theme, true))
243
+ });
217
244
  let tabs = ctx
218
245
  .props()
219
246
  .panel_ids
220
247
  .iter()
221
248
  .filter(|id| !ctx.props().workspace.is_staged(id))
222
- .map(|id| {
223
- let name = id.as_str().to_owned();
249
+ .filter_map(|id| {
250
+ let panel = ctx.props().workspace.panel(id)?;
251
+ let name = id.as_str();
224
252
  let title = ctx
225
253
  .props()
226
254
  .panel_titles
227
255
  .iter()
228
- .find(|(pid, _)| pid == &name)
256
+ .find(|(pid, _)| pid == name)
229
257
  .and_then(|(_, t)| t.clone());
230
- let active = active_slot.as_deref() == Some(name.as_str());
258
+ let theme = ctx.props().effective_panel_theme(name);
259
+ Some((id, panel.session, title, theme, false))
260
+ })
261
+ .chain(closing_tabs)
262
+ .map(|(id, session, title, theme, is_closing)| {
263
+ let name = id.as_str().to_owned();
264
+ let active = !is_closing && active_slot.as_deref() == Some(name.as_str());
231
265
  let visible = !self.hidden_tabs.contains(name.as_str());
232
- let is_master = ctx.props().panel_masters.contains(id);
233
- let theme = ctx.props().effective_panel_theme(&name);
266
+ let is_master = !is_closing && ctx.props().panel_masters.contains(id);
234
267
 
235
268
  html! {
236
269
  <PanelTab
@@ -238,14 +271,16 @@ impl MainPanel {
238
271
  viewer={viewer_elem.clone()}
239
272
  panel_id={name.clone()}
240
273
  {title}
274
+ {session}
241
275
  {theme}
242
276
  {active}
243
277
  {visible}
244
278
  {is_master}
245
279
  {single}
246
- {closable}
280
+ closable={closable && !is_closing}
247
281
  {is_settings_open}
248
- {draggable}
282
+ draggable={draggable && !is_closing}
283
+ closing={is_closing}
249
284
  on_select={on_select.clone()}
250
285
  on_close={on_close.clone()}
251
286
  on_open_settings={on_open_settings.clone()}
@@ -349,11 +384,27 @@ impl MainPanel {
349
384
  })
350
385
  .unwrap_or_default();
351
386
 
387
+ let on_new_panel = {
388
+ let on_command = ctx.props().on_panel_command.clone();
389
+ let id = renderer.slot_name().unwrap_or_default();
390
+ Callback::from(move |pick: NewPanelPick| {
391
+ let cmd = match pick {
392
+ NewPanelPick::FromTable { client, table } => {
393
+ PanelCommand::NewFrom { client, table }
394
+ },
395
+ NewPanelPick::FromPanel(source) => PanelCommand::NewFromPanel(source),
396
+ };
397
+
398
+ on_command.emit((id.clone(), cmd));
399
+ })
400
+ };
401
+
352
402
  html! {
353
403
  <div id="main_column">
354
404
  <StatusBar
355
405
  id="status_bar"
356
406
  on_reset={ctx.props().on_reset.clone()}
407
+ {on_new_panel}
357
408
  session_props={ctx.props().session_props.clone()}
358
409
  presentation_props={ctx.props().presentation_props.clone()}
359
410
  is_settings_open={ctx.props().is_settings_open}
@@ -22,6 +22,7 @@ use yew::prelude::*;
22
22
  use super::MainPanel;
23
23
  use crate::components::panel_menu::PanelCommand;
24
24
  use crate::js::{Layout, RegularLayout};
25
+ use crate::renderer::SUBPIXEL_EPSILON;
25
26
  use crate::tasks::resize_callback;
26
27
 
27
28
  impl MainPanel {
@@ -42,11 +43,9 @@ impl MainPanel {
42
43
  false
43
44
  }
44
45
 
46
+ /// Report closed every panel this commit dropped from the layout, and
47
+ /// re-render so its frame leaves the DOM in the commit's paint.
45
48
  pub(super) fn on_layout_updated(&mut self, ctx: &Context<Self>) -> bool {
46
- // A panel still tracked in `inserted` but no longer present in
47
- // the layout (calculatePath → null) was removed by the layout —
48
- // i.e. its frame's close button was pressed. Report it for
49
- // disposal.
50
49
  let Some(el) = self.layout_ref.cast::<web_sys::HtmlElement>() else {
51
50
  return false;
52
51
  };
@@ -68,10 +67,14 @@ impl MainPanel {
68
67
  }
69
68
  });
70
69
 
70
+ let closed_any = !closed.is_empty();
71
71
  for id in closed {
72
- ctx.props().on_close_panel.emit(id);
72
+ self.pending_removals.remove(&id);
73
+ ctx.props().on_panel_closed.emit(id);
73
74
  }
74
75
 
76
+ let reissue = self.forget_dropped_removals(&layout);
77
+
75
78
  // A layout change (divider drag, insert, restore) reflows the
76
79
  // grid cells without resizing the host, so the host
77
80
  // `ResizeObserver` never fires. Resize every activated panel's
@@ -92,6 +95,19 @@ impl MainPanel {
92
95
  if let Some(panel) = ctx.props().workspace.panel(id)
93
96
  && panel.renderer.is_plugin_activated().unwrap_or(false)
94
97
  {
98
+ if let Some((w, h)) = panel.renderer.take_presized_box()
99
+ && let Some(plugin) = panel.renderer.active_plugin()
100
+ {
101
+ let rect = plugin
102
+ .unchecked_ref::<web_sys::Element>()
103
+ .get_bounding_client_rect();
104
+ if (rect.width() - w).abs() <= SUBPIXEL_EPSILON
105
+ && (rect.height() - h).abs() <= SUBPIXEL_EPSILON
106
+ {
107
+ continue;
108
+ }
109
+ }
110
+
95
111
  resize_callback(&panel.session, &panel.renderer).emit(());
96
112
  }
97
113
  }
@@ -111,10 +127,27 @@ impl MainPanel {
111
127
  self.hidden_tabs = hidden;
112
128
  true
113
129
  } else {
114
- false
130
+ closed_any || reissue
115
131
  }
116
132
  }
117
133
 
134
+ /// Forget every issued removal whose panel survived the commit, so the
135
+ /// next `reconcile` re-issues it.
136
+ fn forget_dropped_removals(&mut self, layout: &RegularLayout) -> bool {
137
+ let dropped = self
138
+ .pending_removals
139
+ .iter()
140
+ .filter(|id| layout.contains_panel(id))
141
+ .cloned()
142
+ .collect::<Vec<_>>();
143
+ let reissue = !dropped.is_empty();
144
+ for id in dropped {
145
+ self.pending_removals.remove(&id);
146
+ }
147
+
148
+ reissue
149
+ }
150
+
118
151
  pub(super) fn on_tab_selected(&self, ctx: &Context<Self>, name: String) -> bool {
119
152
  ctx.props().on_activate_panel.emit(name);
120
153
  false
@@ -169,10 +202,6 @@ impl MainPanel {
169
202
 
170
203
  self.maximized = None;
171
204
  },
172
- // The stage menu (no target panel) offers ONLY `NewFrom`, whose
173
- // handler resolves the client/table purely from the loaded-clients
174
- // registry — the panel id is unused there, so the empty id never
175
- // reaches a panel lookup.
176
205
  cmd => ctx
177
206
  .props()
178
207
  .on_panel_command
@@ -61,10 +61,14 @@ pub struct MainPanelProps {
61
61
  /// active panel.
62
62
  pub on_activate_panel: Callback<String>,
63
63
 
64
- /// Fired with a panel id when its frame's close button removes it from the
65
- /// layout, so the root can dispose the panel.
64
+ /// Fired with a panel id from a tab × (an app-initiated close): the root
65
+ /// removes it from the model at once and parks its engines.
66
66
  pub on_close_panel: Callback<String>,
67
67
 
68
+ /// Fired with a panel id once the `regular-layout` tree no longer holds
69
+ /// it, so the root can eject the parked panel.
70
+ pub on_panel_closed: Callback<String>,
71
+
68
72
  /// Fired with `(panel id, command)` when the panel context menu selects a
69
73
  /// command the root executes (New/Duplicate/Reset/ToggleMaster/Close).
70
74
  /// Maximize/Restore are handled HERE (this component owns the layout
@@ -203,6 +207,10 @@ pub struct MainPanel {
203
207
  /// every listener, and nothing ever noticed.
204
208
  listener_target: Option<web_sys::HtmlElement>,
205
209
 
210
+ /// Ids gone from the model whose `removePanel` has been issued to the
211
+ /// layout and not yet committed.
212
+ pending_removals: HashSet<String>,
213
+
206
214
  /// Per-panel `ResizeObserver`s, keyed by panel id, each observing that
207
215
  /// panel's slotted plugin element and resizing only that panel's
208
216
  /// `Renderer`. Bound in `BeforeResize` for the DRAGGED panel only (it is
@@ -242,6 +250,9 @@ pub struct MainPanel {
242
250
  /// `Workspace::staged_changed` → [`MainPanelMsg::StagedChanged`] on THIS
243
251
  /// component's scope (see the message doc for why not the root's).
244
252
  _staged_sub: crate::utils::Subscription,
253
+
254
+ /// `Workspace::layout_staged` → [`MainPanelMsg::LayoutStaged`].
255
+ _layout_staged_sub: crate::utils::Subscription,
245
256
  }
246
257
 
247
258
  impl Component for MainPanel {
@@ -343,10 +354,20 @@ impl Component for MainPanel {
343
354
  .add_listener(move |()| cb.emit(()))
344
355
  };
345
356
 
357
+ let layout_staged_sub = {
358
+ use crate::utils::AddListener;
359
+ let cb = ctx.link().callback(|_: ()| MainPanelMsg::LayoutStaged);
360
+ ctx.props()
361
+ .workspace
362
+ .layout_staged()
363
+ .add_listener(move |()| cb.emit(()))
364
+ };
365
+
346
366
  Self {
347
367
  main_panel_ref: NodeRef::default(),
348
368
  layout_ref: NodeRef::default(),
349
369
  inserted: Vec::new(),
370
+ pending_removals: HashSet::new(),
350
371
  _layout_update_listener: listener,
351
372
  _layout_select_listener: select_listener,
352
373
  _layout_before_resize_listener: before_resize_listener,
@@ -359,6 +380,7 @@ impl Component for MainPanel {
359
380
  theme_backgrounds: HashMap::new(),
360
381
  stamped_frame_themes: None,
361
382
  _staged_sub: staged_sub,
383
+ _layout_staged_sub: layout_staged_sub,
362
384
  }
363
385
  }
364
386
 
@@ -366,6 +388,7 @@ impl Component for MainPanel {
366
388
  match msg {
367
389
  MainPanelMsg::PointerEvent(event) => self.on_pointer_event(ctx, event),
368
390
  MainPanelMsg::StagedChanged => true,
391
+ MainPanelMsg::LayoutStaged => true,
369
392
  MainPanelMsg::LayoutUpdated => self.on_layout_updated(ctx),
370
393
  MainPanelMsg::TabSelected(name) => self.on_tab_selected(ctx, name),
371
394
  MainPanelMsg::ContextMenu(id, x, y) => self.on_context_menu(ctx, id, x, y),
@@ -376,6 +399,7 @@ impl Component for MainPanel {
376
399
  }
377
400
 
378
401
  fn changed(&mut self, ctx: &Context<Self>, old: &Self::Properties) -> bool {
402
+ self.close_unstaged_panels(ctx, old);
379
403
  ctx.props() != old
380
404
  }
381
405
 
@@ -403,3 +427,17 @@ impl Component for MainPanel {
403
427
 
404
428
  fn destroy(&mut self, _ctx: &Context<Self>) {}
405
429
  }
430
+
431
+ impl MainPanel {
432
+ /// Report closed every panel gone from the model that the layout never
433
+ /// held, since it has no commit to wait for.
434
+ fn close_unstaged_panels(&self, ctx: &Context<Self>, old: &MainPanelProps) {
435
+ for id in &old.panel_ids {
436
+ if !ctx.props().panel_ids.contains(id)
437
+ && !self.inserted.iter().any(|n| n == id.as_str())
438
+ {
439
+ ctx.props().on_panel_closed.emit(id.as_str().to_owned());
440
+ }
441
+ }
442
+ }
443
+ }
@@ -16,36 +16,33 @@
16
16
 
17
17
  #[cfg(feature = "llm-agent")]
18
18
  pub mod chat_panel;
19
+ pub mod code_editor;
19
20
  pub mod column_dropdown;
20
21
  pub mod column_selector;
21
22
  pub mod column_settings_sidebar;
22
- pub mod containers;
23
- pub mod context_menu;
24
23
  pub mod copy_dropdown;
25
24
  pub mod datetime_column_style;
25
+ pub mod debug_panel;
26
+ pub mod dragdrop_list;
26
27
  pub mod editable_header;
27
28
  pub mod empty_row;
28
29
  pub mod export_dropdown;
29
30
  pub mod expression_editor;
30
31
  pub mod filter_dropdown;
31
- pub mod font_loader;
32
- pub mod form;
33
32
  pub mod function_dropdown;
34
33
  pub mod global_filter_bar;
35
34
  pub mod main_panel;
36
- pub mod modal;
35
+ pub mod new_panel_menu;
37
36
  pub mod number_series_style;
38
37
  pub mod panel_menu;
39
38
  pub mod panel_tab;
40
39
  pub mod plugin_selector;
41
40
  pub mod plugin_tab;
42
- pub mod portal;
43
41
  pub mod render_warning;
42
+ pub mod rows_counter;
44
43
  pub mod settings_panel;
45
44
  pub mod status_bar;
46
- pub mod status_bar_counter;
47
45
  pub mod status_indicator;
48
- pub mod string_column_style;
49
46
  pub mod style;
50
47
  pub mod style_controls;
51
48
  pub mod type_icon;
@@ -0,0 +1,97 @@
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::rc::Rc;
14
+
15
+ use yew::prelude::*;
16
+
17
+ /// A pick in the "New" menu: a fresh panel on a hosted `Table`, or a copy of
18
+ /// an existing panel (the same operation as the context menu's "Duplicate").
19
+ #[derive(Clone, PartialEq)]
20
+ pub enum NewPanelPick {
21
+ FromTable { client: String, table: String },
22
+ FromPanel(String),
23
+ }
24
+
25
+ /// `(client name, its hosted table names)` per loaded client — the resolved
26
+ /// output of `queries::fetch_hosted_tables`.
27
+ pub type HostedTables = Rc<Vec<(String, Vec<String>)>>;
28
+
29
+ /// `(panel id, title)` per panel, in layout-insertion order.
30
+ pub type PanelLabels = Rc<Vec<(String, Option<String>)>>;
31
+
32
+ #[derive(Properties, PartialEq)]
33
+ pub struct NewPanelMenuProps {
34
+ /// `None` while the hosted-table fetch is in flight.
35
+ pub tables: Option<HostedTables>,
36
+ pub panels: PanelLabels,
37
+ pub callback: Callback<NewPanelPick>,
38
+ }
39
+
40
+ /// The "New" menu body shared by the status bar dropdown and the context
41
+ /// menu's "New" flyout.
42
+ #[function_component]
43
+ pub fn NewPanelMenu(props: &NewPanelMenuProps) -> Html {
44
+ let pick = |value: NewPanelPick| props.callback.reform(move |_: MouseEvent| value.clone());
45
+
46
+ let tables = match &props.tables {
47
+ None => html! { <span class="no-results">{ "Loading..." }</span> },
48
+ Some(clients) if clients.iter().all(|(_, tables)| tables.is_empty()) => {
49
+ html! { <span class="no-results">{ "No tables" }</span> }
50
+ },
51
+ Some(clients) => {
52
+ let multi = clients.len() > 1;
53
+ clients
54
+ .iter()
55
+ .map(|(client, tables)| {
56
+ let rows = tables.iter().map(|table| {
57
+ let onmousedown = pick(NewPanelPick::FromTable {
58
+ client: client.clone(),
59
+ table: table.clone(),
60
+ });
61
+ html! { <span class="dropdown-menu-item" {onmousedown}>{ table }</span> }
62
+ });
63
+
64
+ html! {
65
+ <>
66
+ if multi { <span class="dropdown-group-sublabel">{ client }</span> }
67
+ { for rows }
68
+ </>
69
+ }
70
+ })
71
+ .collect::<Html>()
72
+ },
73
+ };
74
+
75
+ let panels = if props.panels.is_empty() {
76
+ html! { <span class="no-results">{ "No panels" }</span> }
77
+ } else {
78
+ props
79
+ .panels
80
+ .iter()
81
+ .map(|(id, title)| {
82
+ let onmousedown = pick(NewPanelPick::FromPanel(id.clone()));
83
+ let label = title.clone().unwrap_or_else(|| id.clone());
84
+ html! { <span class="dropdown-menu-item" {onmousedown}>{ label }</span> }
85
+ })
86
+ .collect::<Html>()
87
+ };
88
+
89
+ html! {
90
+ <div class="new-panel-menu">
91
+ <span class="dropdown-group-label" data-label="new-from-table" />
92
+ <div class="dropdown-group-container">{ tables }</div>
93
+ <span class="dropdown-group-label" data-label="new-from-panel" />
94
+ <div class="dropdown-group-container">{ panels }</div>
95
+ </div>
96
+ }
97
+ }
@@ -13,9 +13,8 @@
13
13
  use web_sys::{HtmlInputElement, InputEvent};
14
14
  use yew::prelude::*;
15
15
 
16
- use super::modal::{ModalLink, SetModalLink};
17
- use crate::components::form::select_enum_field::SelectEnumField;
18
16
  use crate::config::*;
17
+ use crate::ui::{IntlLabel, ModalLink, SelectEnumField, SetModalLink};
19
18
  use crate::utils::WeakScope;
20
19
 
21
20
  #[derive(Properties)]
@@ -113,7 +112,7 @@ impl Component for NumberSeriesStyle {
113
112
  });
114
113
  html! {
115
114
  <div class="row">
116
- <label id="stack-label" />
115
+ <IntlLabel name="stack" />
117
116
  <input type="checkbox" id="stack-checkbox" {checked} {oninput} />
118
117
  </div>
119
118
  }