@perspective-dev/viewer 5.3.0 → 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 (265) hide show
  1. package/dist/cdn/perspective-viewer.js +8 -8
  2. package/dist/cdn/perspective-viewer.js.map +3 -3
  3. package/dist/css/blueprint.css +1 -0
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -0
  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 -0
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -0
  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 -0
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/column-format.d.ts +16 -3
  31. package/dist/esm/extensions.d.ts +0 -5
  32. package/dist/esm/perspective-viewer.inline.js +7 -7
  33. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  34. package/dist/esm/perspective-viewer.js +8 -8
  35. package/dist/esm/perspective-viewer.js.map +3 -3
  36. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  37. package/dist/wasm/perspective-viewer.d.ts +62 -73
  38. package/dist/wasm/perspective-viewer.js +230 -238
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
  41. package/package.json +7 -7
  42. package/src/css/_context_menu.css +1 -1
  43. package/src/css/_dropdown_menu.css +1 -1
  44. package/src/css/_viewer_bundle.css +7 -8
  45. package/src/css/column-dropdown.css +0 -3
  46. package/src/css/column-selector.css +8 -5
  47. package/src/css/column-settings-panel.css +0 -206
  48. package/src/css/column-style.css +95 -0
  49. package/src/css/config-selector.css +18 -10
  50. package/src/css/filter-dropdown.css +5 -5
  51. package/src/css/form/debug.css +28 -4
  52. package/src/css/function-dropdown.css +5 -5
  53. package/src/css/panel-tab.css +23 -0
  54. package/src/css/plugin-selector.css +6 -5
  55. package/src/css/render-warning.css +1 -1
  56. package/src/css/status-bar.css +52 -21
  57. package/src/css/type-icon.css +1 -3
  58. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  59. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  60. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  61. package/src/css/{containers → ui/containers}/tabs.css +13 -3
  62. package/src/css/{dom → ui/dom}/select.css +19 -8
  63. package/src/css/viewer.css +32 -31
  64. package/src/rust/agent/tools.rs +5 -2
  65. package/src/rust/components/chat_panel.rs +1 -1
  66. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  67. package/src/rust/components/column_dropdown.rs +2 -2
  68. package/src/rust/components/column_selector/active_column.rs +15 -9
  69. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  70. package/src/rust/components/column_selector/config_selector.rs +2 -2
  71. package/src/rust/components/column_selector/filter_column.rs +8 -5
  72. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  73. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  74. package/src/rust/components/column_selector/sort_column.rs +7 -4
  75. package/src/rust/components/column_selector.rs +3 -3
  76. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  77. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  79. package/src/rust/components/column_settings_sidebar/style_tab.rs +296 -238
  80. package/src/rust/components/column_settings_sidebar.rs +3 -4
  81. package/src/rust/components/copy_dropdown.rs +1 -1
  82. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  83. package/src/rust/components/datetime_column_style/simple.rs +16 -6
  84. package/src/rust/components/datetime_column_style.rs +51 -12
  85. package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
  86. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  87. package/src/rust/components/export_dropdown.rs +1 -1
  88. package/src/rust/components/expression_editor.rs +1 -1
  89. package/src/rust/components/filter_dropdown.rs +2 -2
  90. package/src/rust/components/function_dropdown.rs +2 -2
  91. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  92. package/src/rust/components/main_panel/reconcile.rs +9 -4
  93. package/src/rust/components/main_panel/render.rs +62 -11
  94. package/src/rust/components/main_panel/update.rs +39 -10
  95. package/src/rust/components/main_panel.rs +26 -2
  96. package/src/rust/components/mod.rs +5 -7
  97. package/src/rust/components/new_panel_menu.rs +97 -0
  98. package/src/rust/components/number_series_style.rs +2 -3
  99. package/src/rust/components/panel_menu.rs +61 -87
  100. package/src/rust/components/panel_tab.rs +100 -43
  101. package/src/rust/components/plugin_tab.rs +125 -87
  102. package/src/rust/components/render_warning.rs +2 -2
  103. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  104. package/src/rust/components/settings_panel.rs +3 -2
  105. package/src/rust/components/status_bar.rs +113 -20
  106. package/src/rust/components/status_indicator.rs +5 -14
  107. package/src/rust/components/string_column_style.rs +1 -2
  108. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  109. package/src/rust/components/style_controls/number_string_format/digits_section.rs +25 -15
  110. package/src/rust/components/style_controls/number_string_format/misc_section.rs +7 -3
  111. package/src/rust/components/style_controls/number_string_format/style_section.rs +2 -1
  112. package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
  113. package/src/rust/components/style_controls/number_string_format.rs +37 -14
  114. package/src/rust/components/viewer/msg.rs +43 -30
  115. package/src/rust/components/viewer/panels.rs +50 -19
  116. package/src/rust/components/viewer/render.rs +15 -13
  117. package/src/rust/components/viewer/settings.rs +137 -68
  118. package/src/rust/components/viewer/snapshots.rs +5 -10
  119. package/src/rust/components/viewer/wiring.rs +1 -21
  120. package/src/rust/components/viewer.rs +15 -10
  121. package/src/rust/components/window_editor.rs +8 -9
  122. package/src/rust/config/column_config_schema.rs +358 -52
  123. package/src/rust/config/datetime_column_style.rs +4 -21
  124. package/src/rust/config/mod.rs +0 -2
  125. package/src/rust/config/number_string_format.rs +211 -18
  126. package/src/rust/config/workspace_config.rs +2 -1
  127. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  128. package/src/rust/custom_elements/mod.rs +0 -3
  129. package/src/rust/custom_elements/viewer.rs +0 -18
  130. package/src/rust/js/plugin.rs +0 -86
  131. package/src/rust/lib.rs +12 -15
  132. package/src/rust/presentation/column_locator.rs +3 -7
  133. package/src/rust/presentation.rs +85 -38
  134. package/src/rust/queries/hosted_tables.rs +38 -0
  135. package/src/rust/queries/mod.rs +2 -0
  136. package/src/rust/queries/palette_set.rs +1 -1
  137. package/src/rust/queries/plugin_column_styles.rs +2 -2
  138. package/src/rust/renderer/activate.rs +1 -3
  139. package/src/rust/renderer/dispatch.rs +117 -30
  140. package/src/rust/renderer/limits.rs +24 -5
  141. package/src/rust/renderer/plugin_config.rs +56 -8
  142. package/src/rust/renderer/props.rs +2 -1
  143. package/src/rust/renderer.rs +14 -27
  144. package/src/rust/session/props.rs +4 -3
  145. package/src/rust/session/view_subscription.rs +19 -6
  146. package/src/rust/session.rs +38 -70
  147. package/src/rust/tasks/create_panel.rs +1 -1
  148. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  149. package/src/rust/tasks/eject.rs +1 -0
  150. package/src/rust/tasks/pipeline.rs +0 -60
  151. package/src/rust/tasks/presize_panels.rs +24 -1
  152. package/src/rust/tasks/restore_and_render.rs +0 -17
  153. package/src/rust/tasks/send_column_config.rs +1 -1
  154. package/src/rust/tasks/send_plugin_config.rs +1 -1
  155. package/src/rust/tasks/set_edit_mode.rs +1 -1
  156. package/src/rust/ui/containers/control_group.rs +113 -0
  157. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  158. package/src/rust/{components → ui}/containers/mod.rs +1 -4
  159. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  160. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  161. package/src/rust/{components → ui}/containers/select.rs +8 -8
  162. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  163. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  164. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  165. package/src/rust/{components → ui}/context_menu.rs +8 -13
  166. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  167. package/src/rust/{components → ui}/font_loader.rs +0 -26
  168. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  169. package/src/rust/ui/form/intl_label.rs +81 -0
  170. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  171. package/src/rust/{components → ui}/form/mod.rs +1 -10
  172. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  173. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  174. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  175. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  176. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  177. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  178. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  179. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  180. package/src/rust/ui/mod.rs +56 -0
  181. package/src/rust/ui/modal.rs +61 -0
  182. package/src/rust/{components → ui}/portal.rs +174 -79
  183. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  184. package/src/rust/utils/completion.rs +0 -6
  185. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  186. package/src/rust/utils/mod.rs +2 -0
  187. package/src/rust/workspace.rs +35 -0
  188. package/src/svg/boolean-type.svg +3 -2
  189. package/src/svg/checkbox-checked-icon.svg +4 -7
  190. package/src/svg/checkbox-hover.svg +3 -3
  191. package/src/svg/checkbox-off.svg +2 -3
  192. package/src/svg/checkbox-on.svg +3 -3
  193. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  194. package/src/svg/datagrid-select-column.svg +8 -15
  195. package/src/svg/datagrid-select-region.svg +8 -15
  196. package/src/svg/datagrid-select-row-tree.svg +7 -12
  197. package/src/svg/datagrid-select-row.svg +8 -13
  198. package/src/svg/date-type.svg +4 -23
  199. package/src/svg/drawer-tab-hover.svg +4 -6
  200. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  201. package/src/svg/drawer-tab-invert.svg +3 -5
  202. package/src/svg/drawer-tab.svg +5 -5
  203. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  204. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  205. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  206. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  207. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  208. package/src/svg/new-panel-icon.svg +4 -0
  209. package/src/svg/number-type.svg +3 -3
  210. package/src/svg/radio-hover.svg +3 -3
  211. package/src/svg/radio-off.svg +3 -3
  212. package/src/svg/radio-on.svg +3 -3
  213. package/src/svg/status_ok.svg +3 -26
  214. package/src/svg/string-type.svg +4 -8
  215. package/src/svg/theme-icon.svg +5 -5
  216. package/src/themes/blueprint.css +133 -0
  217. package/src/themes/botanical.css +3 -52
  218. package/src/themes/defaults.css +3 -144
  219. package/src/themes/dracula.css +1 -71
  220. package/src/themes/eggplant.css +131 -0
  221. package/src/themes/gruvbox-dark.css +2 -79
  222. package/src/themes/gruvbox.css +2 -40
  223. package/src/themes/icons.css +2 -10
  224. package/src/themes/intl/ar.css +185 -0
  225. package/src/themes/intl/de.css +65 -33
  226. package/src/themes/intl/es.css +60 -28
  227. package/src/themes/intl/fr.css +60 -28
  228. package/src/themes/intl/ja.css +62 -30
  229. package/src/themes/intl/pt.css +58 -26
  230. package/src/themes/intl/zh.css +68 -36
  231. package/src/themes/intl.css +42 -7
  232. package/src/themes/ledger.css +137 -0
  233. package/src/themes/monokai.css +1 -64
  234. package/src/themes/nord.css +131 -0
  235. package/src/themes/phosphor.css +1 -61
  236. package/src/themes/pro-dark.css +1 -52
  237. package/src/themes/pro.css +98 -0
  238. package/src/themes/solarized-dark.css +1 -46
  239. package/src/themes/solarized.css +1 -24
  240. package/src/themes/themes.css +10 -5
  241. package/src/themes/vaporwave.css +1 -57
  242. package/src/themes/velvet.css +132 -0
  243. package/src/ts/column-format.ts +52 -2
  244. package/src/ts/extensions.ts +0 -13
  245. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  246. package/src/css/plugin-settings-panel.css +0 -99
  247. package/src/rust/components/modal.rs +0 -177
  248. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  249. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  250. package/src/rust/custom_elements/modal.rs +0 -299
  251. package/src/svg/dropdown-selector-light.svg +0 -3
  252. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
  253. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
  254. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
  255. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
  256. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
  257. /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
  258. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  259. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  260. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  261. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  262. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  263. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  264. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  265. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -41,10 +41,6 @@ impl Default for DatetimeFormatType {
41
41
  }
42
42
 
43
43
  impl DatetimeFormatType {
44
- fn is_simple(&self) -> bool {
45
- self == &Self::Simple(SimpleDatetimeStyleConfig::default())
46
- }
47
-
48
44
  pub fn time_zone(&self) -> &Option<String> {
49
45
  match self {
50
46
  DatetimeFormatType::Custom(x) => &x.time_zone,
@@ -62,12 +58,13 @@ impl DatetimeFormatType {
62
58
 
63
59
  /// A model for the JSON serialized style configuration for a column of type
64
60
  /// `datetime`.
65
- #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, TS)]
61
+ #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TS)]
66
62
  // #[derive(WasmDescribe!, FromWasmAbi!)]
67
63
  pub struct DatetimeColumnStyleConfig {
68
64
  #[serde(default)]
69
- #[serde(skip_serializing_if = "DatetimeFormatType::is_simple")]
70
- pub date_format: DatetimeFormatType,
65
+ #[serde(skip_serializing_if = "Option::is_none")]
66
+ #[ts(optional, as = "Option<_>")]
67
+ pub date_format: Option<DatetimeFormatType>,
71
68
 
72
69
  #[serde(default)]
73
70
  #[serde(skip_serializing_if = "DatetimeColorMode::is_none")]
@@ -78,17 +75,3 @@ pub struct DatetimeColumnStyleConfig {
78
75
  #[ts(skip)]
79
76
  pub color: Option<String>,
80
77
  }
81
-
82
- impl Default for DatetimeColumnStyleConfig {
83
- fn default() -> Self {
84
- Self {
85
- date_format: DatetimeFormatType::Simple(SimpleDatetimeStyleConfig {
86
- time_zone: Default::default(),
87
- date_style: SimpleDatetimeFormat::Short,
88
- time_style: SimpleDatetimeFormat::Medium,
89
- }),
90
- datetime_color_mode: Default::default(),
91
- color: Default::default(),
92
- }
93
- }
94
- }
@@ -15,7 +15,6 @@
15
15
 
16
16
  mod column_config_schema;
17
17
  mod columns_config;
18
- mod css_scale;
19
18
  mod datetime_column_style;
20
19
  mod export_method;
21
20
  mod kvpair;
@@ -30,7 +29,6 @@ mod workspace_config;
30
29
 
31
30
  pub use column_config_schema::*;
32
31
  pub use columns_config::*;
33
- pub use css_scale::*;
34
32
  pub use datetime_column_style::*;
35
33
  pub use export_method::*;
36
34
  pub use number_series_style::*;
@@ -255,13 +255,106 @@ pub struct CustomNumberFormatConfig {
255
255
  pub sign_display: Option<SignDisplay>,
256
256
  }
257
257
 
258
+ /// The active plugin's `NumberFormat` default overlaid on the built-in
259
+ /// `number_format` values.
260
+ #[derive(Clone, Debug, PartialEq)]
261
+ pub struct NumberFormatDefaults {
262
+ pub is_float: bool,
263
+ pub style: NumberFormatStyle,
264
+ pub notation: Notation,
265
+ pub minimum_integer_digits: f64,
266
+ pub fraction: (f64, f64),
267
+ pub significant: (f64, f64),
268
+ pub rounding_priority: RoundingPriority,
269
+ pub rounding_mode: RoundingMode,
270
+ pub trailing_zero_display: TrailingZeroDisplay,
271
+ pub use_grouping: UseGrouping,
272
+ pub sign_display: SignDisplay,
273
+ }
274
+
275
+ impl NumberFormatDefaults {
276
+ pub fn builtin(is_float: bool) -> Self {
277
+ Self {
278
+ is_float,
279
+ style: NumberFormatStyle::default(),
280
+ notation: Notation::default(),
281
+ minimum_integer_digits: 1.,
282
+ fraction: if is_float { (2., 2.) } else { (0., 0.) },
283
+ significant: (1., 21.),
284
+ rounding_priority: RoundingPriority::default(),
285
+ rounding_mode: RoundingMode::default(),
286
+ trailing_zero_display: TrailingZeroDisplay::default(),
287
+ use_grouping: UseGrouping::default(),
288
+ sign_display: SignDisplay::default(),
289
+ }
290
+ }
291
+
292
+ /// Overlay a plugin-declared partial default onto [`Self::builtin`].
293
+ pub fn resolve(spec_default: Option<&CustomNumberFormatConfig>, is_float: bool) -> Self {
294
+ let mut out = Self::builtin(is_float);
295
+ let Some(d) = spec_default else {
296
+ return out;
297
+ };
298
+
299
+ if let Some(style) = &d._style {
300
+ out.style = style.clone();
301
+ }
302
+
303
+ if let Some(notation) = &d._notation {
304
+ out.notation = notation.clone();
305
+ }
306
+
307
+ if let Some(v) = d.minimum_integer_digits {
308
+ out.minimum_integer_digits = v;
309
+ }
310
+
311
+ if let Some(v) = d.minimum_fraction_digits {
312
+ out.fraction.0 = v;
313
+ }
314
+
315
+ if let Some(v) = d.maximum_fraction_digits {
316
+ out.fraction.1 = v;
317
+ }
318
+
319
+ if let Some(v) = d.minimum_significant_digits {
320
+ out.significant.0 = v;
321
+ }
322
+
323
+ if let Some(v) = d.maximum_significant_digits {
324
+ out.significant.1 = v;
325
+ }
326
+
327
+ if let Some(v) = d.rounding_priority {
328
+ out.rounding_priority = v;
329
+ }
330
+
331
+ if let Some(v) = d.rounding_mode {
332
+ out.rounding_mode = v;
333
+ }
334
+
335
+ if let Some(v) = d.trailing_zero_display {
336
+ out.trailing_zero_display = v;
337
+ }
338
+
339
+ if let Some(v) = d.use_grouping {
340
+ out.use_grouping = v;
341
+ }
342
+
343
+ if let Some(v) = d.sign_display {
344
+ out.sign_display = v;
345
+ }
346
+
347
+ out
348
+ }
349
+ }
350
+
258
351
  impl CustomNumberFormatConfig {
259
- pub fn filter_default(self, is_float: bool) -> Self {
260
- let (frac_min, frac_max) = if is_float { (2., 2.) } else { (0., 0.) };
352
+ pub fn filter_default(self, defaults: &NumberFormatDefaults) -> Self {
353
+ let (frac_min, frac_max) = defaults.fraction;
261
354
  let rounding_increment = self.rounding_increment;
262
355
  let use_grouping = self
263
356
  .use_grouping
264
- .filter(|val| *val != UseGrouping::default());
357
+ .filter(|val| *val != defaults.use_grouping);
265
358
 
266
359
  let mut minimum_fraction_digits =
267
360
  self.minimum_fraction_digits.filter(|val| *val != frac_min);
@@ -269,7 +362,7 @@ impl CustomNumberFormatConfig {
269
362
  let mut maximum_fraction_digits =
270
363
  self.maximum_fraction_digits.filter(|val| *val != frac_max);
271
364
 
272
- let mut show_frac = is_float
365
+ let mut show_frac = defaults.is_float
273
366
  && (minimum_fraction_digits.is_some()
274
367
  || maximum_fraction_digits.is_some()
275
368
  || use_grouping.is_some()
@@ -277,7 +370,7 @@ impl CustomNumberFormatConfig {
277
370
  self._style,
278
371
  Some(NumberFormatStyle::Percent | NumberFormatStyle::Unit(_))
279
372
  ))
280
- || !is_float && matches!(self._style, Some(NumberFormatStyle::Currency(_)));
373
+ || !defaults.is_float && matches!(self._style, Some(NumberFormatStyle::Currency(_)));
281
374
 
282
375
  // Rounding increment does not work unless `minimum_fraction_digits`
283
376
  // and `maximum_fraction_digits` are set to 0.
@@ -287,39 +380,139 @@ impl CustomNumberFormatConfig {
287
380
  maximum_fraction_digits = Some(0.);
288
381
  }
289
382
 
290
- let minimum_significant_digits = self.minimum_significant_digits.filter(|val| *val != 1.);
291
- let maximum_significant_digits = self.maximum_significant_digits.filter(|val| *val != 21.);
383
+ let minimum_significant_digits = self
384
+ .minimum_significant_digits
385
+ .filter(|val| *val != defaults.significant.0);
386
+
387
+ let maximum_significant_digits = self
388
+ .maximum_significant_digits
389
+ .filter(|val| *val != defaults.significant.1);
390
+
292
391
  let show_sig = minimum_significant_digits.is_some() || maximum_significant_digits.is_some();
293
392
  Self {
294
- _style: self
295
- ._style
296
- .filter(|style| !matches!(style, NumberFormatStyle::Decimal)),
297
- minimum_integer_digits: self.minimum_integer_digits.filter(|val| *val != 1.),
393
+ _style: self._style.filter(|style| *style != defaults.style),
394
+ minimum_integer_digits: self
395
+ .minimum_integer_digits
396
+ .filter(|val| *val != defaults.minimum_integer_digits),
298
397
  minimum_fraction_digits: show_frac
299
398
  .then_some(minimum_fraction_digits.unwrap_or(frac_min)),
300
399
  maximum_fraction_digits: show_frac
301
400
  .then_some(maximum_fraction_digits.unwrap_or(frac_max)),
302
401
  minimum_significant_digits: show_sig
303
- .then_some(minimum_significant_digits.unwrap_or(1.)),
402
+ .then_some(minimum_significant_digits.unwrap_or(defaults.significant.0)),
304
403
  maximum_significant_digits: show_sig
305
- .then_some(minimum_significant_digits.unwrap_or(21.)),
404
+ .then_some(maximum_significant_digits.unwrap_or(defaults.significant.1)),
306
405
  rounding_priority: self
307
406
  .rounding_priority
308
- .filter(|val| *val != RoundingPriority::default()),
407
+ .filter(|val| *val != defaults.rounding_priority),
309
408
  rounding_increment,
310
409
  rounding_mode: self
311
410
  .rounding_mode
312
- .filter(|val| *val != RoundingMode::default()),
411
+ .filter(|val| *val != defaults.rounding_mode),
313
412
  trailing_zero_display: self
314
413
  .trailing_zero_display
315
- .filter(|val| *val != TrailingZeroDisplay::default()),
414
+ .filter(|val| *val != defaults.trailing_zero_display),
316
415
  _notation: self
317
416
  ._notation
318
- .filter(|notation| !matches!(notation, Notation::Standard)),
417
+ .filter(|notation| *notation != defaults.notation),
319
418
  use_grouping,
320
419
  sign_display: self
321
420
  .sign_display
322
- .filter(|val| *val != SignDisplay::default()),
421
+ .filter(|val| *val != defaults.sign_display),
422
+ }
423
+ }
424
+ }
425
+
426
+ #[cfg(test)]
427
+ mod tests {
428
+ use super::*;
429
+
430
+ fn sig(min: f64, max: f64) -> CustomNumberFormatConfig {
431
+ CustomNumberFormatConfig {
432
+ minimum_significant_digits: Some(min),
433
+ maximum_significant_digits: Some(max),
434
+ ..Default::default()
323
435
  }
324
436
  }
437
+
438
+ #[test]
439
+ fn resolve_overlays_partial_default_on_builtin() {
440
+ let spec = CustomNumberFormatConfig {
441
+ _notation: Some(Notation::Compact(CompactDisplay::Short)),
442
+ minimum_fraction_digits: Some(0.),
443
+ maximum_fraction_digits: Some(1.),
444
+ ..Default::default()
445
+ };
446
+
447
+ let defaults = NumberFormatDefaults::resolve(Some(&spec), true);
448
+ assert_eq!(defaults.notation, Notation::Compact(CompactDisplay::Short));
449
+ assert_eq!(defaults.fraction, (0., 1.));
450
+ assert_eq!(defaults.style, NumberFormatStyle::Decimal);
451
+ assert_eq!(defaults.significant, (1., 21.));
452
+ assert_eq!(
453
+ NumberFormatDefaults::resolve(None, true),
454
+ NumberFormatDefaults::builtin(true)
455
+ );
456
+ }
457
+
458
+ #[test]
459
+ fn filter_preserves_max_significant_digits() {
460
+ let filtered = sig(1., 5.).filter_default(&NumberFormatDefaults::builtin(true));
461
+ assert_eq!(filtered.minimum_significant_digits, Some(1.));
462
+ assert_eq!(filtered.maximum_significant_digits, Some(5.));
463
+
464
+ let filtered = sig(3., 21.).filter_default(&NumberFormatDefaults::builtin(true));
465
+ assert_eq!(filtered.minimum_significant_digits, Some(3.));
466
+ assert_eq!(filtered.maximum_significant_digits, Some(21.));
467
+ }
468
+
469
+ #[test]
470
+ fn filter_elides_values_equal_to_resolved_defaults() {
471
+ let defaults = NumberFormatDefaults {
472
+ fraction: (0., 1.),
473
+ notation: Notation::Compact(CompactDisplay::Short),
474
+ ..NumberFormatDefaults::builtin(true)
475
+ };
476
+
477
+ let config = CustomNumberFormatConfig {
478
+ _notation: Some(Notation::Compact(CompactDisplay::Short)),
479
+ minimum_fraction_digits: Some(0.),
480
+ maximum_fraction_digits: Some(1.),
481
+ ..Default::default()
482
+ };
483
+
484
+ assert_eq!(
485
+ config.filter_default(&defaults),
486
+ CustomNumberFormatConfig::default()
487
+ );
488
+ }
489
+
490
+ #[test]
491
+ fn filter_serializes_builtin_values_under_override() {
492
+ let defaults = NumberFormatDefaults {
493
+ notation: Notation::Compact(CompactDisplay::Short),
494
+ ..NumberFormatDefaults::builtin(true)
495
+ };
496
+
497
+ let config = CustomNumberFormatConfig {
498
+ _notation: Some(Notation::Standard),
499
+ ..Default::default()
500
+ };
501
+
502
+ let filtered = config.filter_default(&defaults);
503
+ assert_eq!(filtered._notation, Some(Notation::Standard));
504
+ }
505
+
506
+ #[test]
507
+ fn filter_bakes_fraction_digits_for_percent_style() {
508
+ let config = CustomNumberFormatConfig {
509
+ _style: Some(NumberFormatStyle::Percent),
510
+ ..Default::default()
511
+ };
512
+
513
+ let filtered = config.filter_default(&NumberFormatDefaults::builtin(true));
514
+ assert_eq!(filtered._style, Some(NumberFormatStyle::Percent));
515
+ assert_eq!(filtered.minimum_fraction_digits, Some(2.));
516
+ assert_eq!(filtered.maximum_fraction_digits, Some(2.));
517
+ }
325
518
  }
@@ -14,7 +14,8 @@ use std::collections::BTreeMap;
14
14
 
15
15
  use perspective_client::config::Filter;
16
16
 
17
- use crate::config::{CssKind, PanelViewerConfig, ViewerConfigInitial};
17
+ use crate::config::{PanelViewerConfig, ViewerConfigInitial};
18
+ use crate::utils::CssKind;
18
19
 
19
20
  /// The workspace config format (`{version, active?, layout, panels}`) —
20
21
  /// the multi-panel counterpart of the single-panel [`ViewerConfig`] — as
@@ -66,16 +66,7 @@ impl PerspectiveDebugPluginElement {
66
66
  }
67
67
 
68
68
  /// Delegates to `draw()` VIRTUALLY — through the JS element's `draw`
69
- /// property, never `self.draw(view)` (Rust static dispatch). This
70
- /// element is the documented base class for custom plugins
71
- /// (`class MyPlugin extends
72
- /// customElements.get("perspective-viewer-plugin")`), whose contract is
73
- /// "`update()` defaults to dispatch to `draw()`" — a
74
- /// subclass overriding only `draw` must receive `update`-path repaints
75
- /// (`BindDisposition::Unchanged`/`Adopted` runs,
76
- /// `PLUGIN_DRAW_INVARIANT_PLAN.md`); the static call bypassed the
77
- /// override and repainted the Debug CSV instead (the
78
- /// `view_lifecycle.spec` regression).
69
+ /// property, never `self.draw(view)` (Rust static dispatch).
79
70
  pub fn update(&self, view: &perspective_js::View) -> ApiFuture<()> {
80
71
  clone!(self.elem, view);
81
72
  ApiFuture::new(async move {
@@ -13,8 +13,5 @@
13
13
  //! Each file in `custom_elements` exports a single struct which will be the
14
14
  //! public [`wasm_bindgen`] API to a JavaScript Custom Element.
15
15
 
16
- pub mod copy_dropdown;
17
16
  pub mod debug_plugin;
18
- pub mod export_dropdown;
19
- pub mod modal;
20
17
  pub mod viewer;
@@ -512,31 +512,13 @@ impl PerspectiveViewerElement {
512
512
  .expect("just-reserved panel is present")
513
513
  });
514
514
 
515
- // Carrying `Some(notify)` marks this load as the reservation's
516
- // owner — the only call that may place or discard it below.
517
515
  (panel, Some(self.layout_changed_notify()))
518
516
  },
519
517
  };
520
518
 
521
- // A `Table` payload targets this panel's engines; a `Client` registers
522
- // inertly against it. Selecting the panel here (not at construction)
523
- // keeps the registry race safe — by `load()` time real plugins have
524
- // registered.
525
519
  let session = panel.session;
526
520
  let renderer = panel.renderer;
527
-
528
- // Open the pending-load window SYNCHRONOUSLY, at the call site — this
529
- // is what fixes the ordering. The payload's RESET disposition (a
530
- // `Table` resets the view; a `Client` does not) is unknown until the
531
- // promise resolves, but the window's POSITION on the config-commit
532
- // stream is fixed NOW. A `restore()` a caller fires immediately after
533
- // this unawaited `load()` (the React prop-binding pattern, which has
534
- // no async ordering guarantees) commits INTO this window's journal and
535
- // is replayed over the reset base if the payload proves to be a
536
- // `Table` — so a moved-async reset can no longer clobber a later
537
- // commit. See `SESSION_CONFIG_COHERENCE_PLAN.md`.
538
521
  let generation = session.begin_pending_load();
539
-
540
522
  clone!(self.workspace, self.presentation);
541
523
  Ok(ApiFuture::new_throttled(async move {
542
524
  let _effect = effect;
@@ -30,92 +30,6 @@ extern "C" {
30
30
  pub type JsPerspectiveViewer;
31
31
 
32
32
  /// A `<perspective-viewer>` plugin custom element.
33
- ///
34
- /// # Capability tiers
35
- ///
36
- /// Only `get_static_config`, `draw`, `save`/`restore` and `delete` are
37
- /// REQUIRED. Every other method is optional — detected once per element
38
- /// tag ([`PluginCapabilities`], methods live on the class prototype) —
39
- /// with a defined host fallback, so a minimal plugin renders correctly
40
- /// and each added method buys performance or polish, never correctness:
41
- ///
42
- /// | method | host fallback when absent |
43
- /// |------------|--------------------------------------------------------|
44
- /// | `update` | `draw` — every repaint is a full render (such plugins |
45
- /// | | must NOT treat `draw` as a state-reset license) |
46
- /// | `resize` | no-op — CSS reflows the box; next dispatch repaints |
47
- /// | `restyle` | no-op — the plugin re-reads CSS at its next render |
48
- /// | `clear` | no-op |
49
- /// | `deselect` | no-op |
50
- /// | `presize` | held style-override presize (`Renderer` resize paths) |
51
- /// | `render` | image export reports unsupported |
52
- ///
53
- /// # Call discipline
54
- ///
55
- /// Plugin implementations assume the host NEVER overlaps calls on the
56
- /// same element: each of the rendering methods (`draw`, `update`,
57
- /// `render`, `clear`, `resize`) and `delete` must run to completion
58
- /// before the next begins. Every call site must therefore hold that
59
- /// plugin's per-`Renderer` draw lock, witnessed by
60
- /// [`crate::utils::RenderGuard`] — the guard-taking dispatch helpers
61
- /// (`Renderer::draw_fresh`/`update_bound`,
62
- /// `renderer::activate::activate_plugin`) and the already-locked
63
- /// `render_task`/`update_lazy`/`resize`/`restyle_all`/`dispose`
64
- /// entrypoints do not compile from an unlocked context. Synchronous
65
- /// calls (`restore`, `restyle`, the schema queries) are exempt, though
66
- /// `restyle` should ride inside a locked restyle-then-update sequence
67
- /// (see `Renderer::restyle_all`).
68
- ///
69
- /// # Dispatch semantics (`PLUGIN_DRAW_INVARIANT_PLAN.md`, amended
70
- /// 2026-07-16)
71
- ///
72
- /// The host invokes `draw` **iff there is a NEW `View` for this
73
- /// plugin** — `bind_view` REBUILT the engine `View`, or a
74
- /// freshly-selected plugin element owes its first paint of the bound
75
- /// one — so implementations may treat `draw` as "new data shape"
76
- /// (reset zoom/scroll/domain state). The witness enforcing this is
77
- /// [`crate::session::FreshView`], which only the rebuild and
78
- /// first-paint paths mint.
79
- ///
80
- /// `update` is a repaint of the SAME `View`, invoked iff a
81
- /// plugin-visible SOURCE changed — one of exactly six: (1) data
82
- /// (`View::on_update`), (2) an adopted placeholder-config delta,
83
- /// (3) a genuinely CHANGED `plugin_config`/`columns_config` the host
84
- /// just delivered via `restore` (so `update`-after-`restore` is the
85
- /// sanctioned delivery pairing), (4) genuinely changed CSS vars just
86
- /// applied via `restyle` (`Renderer::restyle_all`, or the fused
87
- /// stale-capture restyle immediately preceding a dispatch inside
88
- /// `draw_view` — gated by `Renderer::needs_restyle`), (5) a
89
- /// render-limits change (warning dismissed), (6) an explicit PUBLIC
90
- /// element-API call that reconciled as a no-op
91
- /// ([`crate::tasks::RunOrigin::Public`] — the documented
92
- /// `viewer.restore({})` refresh affordance). An INTERNAL run that
93
- /// changed none of these dispatches nothing.
94
- ///
95
- /// `resize` is geometry/chrome only, from retained state (and is also
96
- /// the activation-chrome nudge — implementations should no-op it
97
- /// while hidden).
98
- ///
99
- /// `restore`/`save` are state transfer, NOT rendering: plugins must
100
- /// not render internally from them, and must not call host APIs from
101
- /// inside `restore` (a `restore`-triggered `restorePanel` echo is how
102
- /// the initial-load double render happened — user gestures may echo,
103
- /// host-delivered restores must not). (The built-in `Debug` plugin's
104
- /// own `resize()` delegating to its `draw()` is plugin-INTERNAL and
105
- /// exempt.)
106
- ///
107
- /// # Render-callable contract (invariant I5,
108
- /// `SESSION_CONFIG_COHERENCE_PLAN.md`)
109
- ///
110
- /// From inside `draw`/`update`/`render`/`resize`, a plugin may call
111
- /// back into its host `<perspective-viewer>` ONLY the per-panel
112
- /// snapshot getters — `getViewConfigPanel`, `getTablePanel`,
113
- /// `getClientPanel`, `getEditPortPanel` — which answer from the run's
114
- /// pinned `RenderContext`, so plugin-visible state always equals the
115
- /// snapshot being drawn, never a fresher in-flight commit.
116
- /// Lock-acquiring host methods (`savePanel`, `restorePanel`, `flush`,
117
- /// `download`/`export`/`copy`, …) DEADLOCK on the non-reentrant draw
118
- /// lock if called from a render and are forbidden.
119
33
  #[derive(Clone)]
120
34
  pub type JsPerspectiveViewerPlugin;
121
35
 
package/src/rust/lib.rs CHANGED
@@ -26,9 +26,6 @@
26
26
  clippy::fallible_impl_from,
27
27
  clippy::unneeded_field_pattern
28
28
  )]
29
- // Holding a `Ref` across an `await` is the sibling footgun of the config
30
- // write-back race this crate was rearchitected against (see
31
- // `SESSION_CONFIG_COHERENCE_PLAN.md`) — deny, don't warn.
32
29
  #![deny(clippy::await_holding_refcell_ref)]
33
30
 
34
31
  #[cfg(feature = "llm-agent")]
@@ -49,6 +46,7 @@ mod session;
49
46
 
50
47
  #[doc(hidden)]
51
48
  pub mod tasks;
49
+ pub mod ui;
52
50
  pub mod utils;
53
51
  mod workspace;
54
52
 
@@ -61,9 +59,7 @@ use std::cell::RefCell;
61
59
  use perspective_js::utils::*;
62
60
  use wasm_bindgen::prelude::*;
63
61
 
64
- use crate::custom_elements::copy_dropdown::CopyDropDownMenuElement;
65
62
  use crate::custom_elements::debug_plugin::PerspectiveDebugPluginElement;
66
- use crate::custom_elements::export_dropdown::ExportDropDownMenuElement;
67
63
  use crate::custom_elements::viewer::PerspectiveViewerElement;
68
64
  use crate::utils::define_web_component;
69
65
 
@@ -71,19 +67,21 @@ use crate::utils::define_web_component;
71
67
  const TS_APPEND_CONTENT: &'static str = r#"
72
68
  import type {
73
69
  ColumnType,
74
- TableInitOptions,
75
70
  ColumnWindow,
76
- ViewWindow,
77
- TypedArrayWindow,
78
- OnUpdateOptions,
71
+ DeleteOptions,
72
+ Features,
79
73
  JoinOptions,
74
+ OnRemoveData,
75
+ OnUpdateData,
76
+ OnUpdateOptions,
77
+ Scalar,
78
+ SystemInfo,
79
+ TableInitOptions,
80
+ TypedArrayWindow,
80
81
  UpdateOptions,
81
- DeleteOptions,
82
82
  ViewConfig,
83
83
  ViewConfigUpdate,
84
- SystemInfo,
85
- Scalar,
86
- Features,
84
+ ViewWindow,
87
85
  } from "@perspective-dev/client";
88
86
 
89
87
  export type * from "../../src/ts/ts-rs/ViewerConfig.d.ts";
@@ -107,6 +105,7 @@ export type * from "../../src/ts/ts-rs/DatetimeFormatType.d.ts";
107
105
  export type * from "../../src/ts/ts-rs/StringColorMode.d.ts";
108
106
  export type * from "../../src/ts/ts-rs/DatetimeColorMode.d.ts";
109
107
  export type * from "../../src/ts/ts-rs/FormatMode.d.ts";
108
+
110
109
  import type {GetTableOptions} from "../../src/ts/ts-rs/GetTableOptions.d.ts";
111
110
  import type {PanelOptions} from "../../src/ts/ts-rs/PanelOptions.d.ts";
112
111
  import type {RestoreOptions} from "../../src/ts/ts-rs/RestoreOptions.d.ts";
@@ -177,8 +176,6 @@ pub fn js_get_worker_url() -> Result<web_sys::Url, JsValue> {
177
176
  pub fn bootstrap_web_components(psp: &JsValue) {
178
177
  define_web_component::<PerspectiveViewerElement>(psp);
179
178
  define_web_component::<PerspectiveDebugPluginElement>(psp);
180
- define_web_component::<CopyDropDownMenuElement>(psp);
181
- define_web_component::<ExportDropDownMenuElement>(psp);
182
179
  }
183
180
 
184
181
  /// Defining the web components needs an extern struct to reference the
@@ -12,6 +12,8 @@
12
12
 
13
13
  use std::fmt::Display;
14
14
 
15
+ use crate::ui::TabItem;
16
+
15
17
  /// Locates a view column.
16
18
  /// Table columns are those defined on the table, but their types will reflect
17
19
  /// the view type, not the table type.
@@ -110,10 +112,4 @@ impl OpenColumnSettings {
110
112
  }
111
113
  }
112
114
 
113
- pub trait ColumnTab: PartialEq + Display + Clone + Default + 'static {}
114
-
115
- impl ColumnTab for String {}
116
-
117
- impl ColumnTab for &'static str {}
118
-
119
- impl ColumnTab for ColumnSettingsTab {}
115
+ impl TabItem for ColumnSettingsTab {}