@perspective-dev/viewer 5.5.0 → 5.6.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 (201) hide show
  1. package/README.md +82 -3
  2. package/dist/cdn/perspective-viewer.js +9 -9
  3. package/dist/cdn/perspective-viewer.js.map +3 -3
  4. package/dist/css/blueprint.css +1 -1
  5. package/dist/css/botanical.css +1 -1
  6. package/dist/css/dracula.css +1 -1
  7. package/dist/css/eggplant.css +1 -1
  8. package/dist/css/gruvbox-dark.css +1 -1
  9. package/dist/css/gruvbox.css +1 -1
  10. package/dist/css/icons.css +1 -1
  11. package/dist/css/intl/ar.css +1 -1
  12. package/dist/css/intl/de.css +1 -1
  13. package/dist/css/intl/es.css +1 -1
  14. package/dist/css/intl/fr.css +1 -1
  15. package/dist/css/intl/ja.css +1 -1
  16. package/dist/css/intl/pt.css +1 -1
  17. package/dist/css/intl/zh.css +1 -1
  18. package/dist/css/intl.css +1 -1
  19. package/dist/css/ledger.css +1 -1
  20. package/dist/css/monokai.css +1 -1
  21. package/dist/css/nord.css +1 -1
  22. package/dist/css/phosphor.css +1 -1
  23. package/dist/css/pro-dark.css +1 -1
  24. package/dist/css/pro.css +1 -1
  25. package/dist/css/solarized-dark.css +1 -1
  26. package/dist/css/solarized.css +1 -1
  27. package/dist/css/themes.css +1 -1
  28. package/dist/css/vaporwave.css +1 -1
  29. package/dist/css/velvet.css +1 -1
  30. package/dist/docs/perspective-docs.json +1 -1
  31. package/dist/esm/column-format.js +2 -0
  32. package/dist/esm/column-format.js.map +7 -0
  33. package/dist/esm/extensions.d.ts +3 -13
  34. package/dist/esm/perspective-viewer.d.ts +1 -1
  35. package/dist/esm/perspective-viewer.js +9 -9
  36. package/dist/esm/perspective-viewer.js.map +3 -3
  37. package/dist/esm/plugin.d.ts +4 -1
  38. package/dist/esm/select-detail.d.ts +15 -0
  39. package/dist/esm/select-detail.js +2 -0
  40. package/dist/esm/select-detail.js.map +7 -0
  41. package/dist/esm/ts-rs/DerivedTableConfig.d.ts +35 -0
  42. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  43. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +7 -6
  44. package/dist/esm/ts-rs/RestoreOptions.d.ts +3 -7
  45. package/dist/esm/ts-rs/TableRef.d.ts +9 -0
  46. package/dist/esm/ts-rs/ViewerConfig.d.ts +7 -6
  47. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -5
  48. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  49. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +9 -0
  50. package/dist/wasm/perspective-viewer.d.ts +126 -35
  51. package/dist/wasm/perspective-viewer.js +320 -185
  52. package/dist/wasm/perspective-viewer.wasm +0 -0
  53. package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
  54. package/package.json +33 -20
  55. package/src/css/column-selector.css +205 -50
  56. package/src/css/ui/containers/context-menu.css +12 -1
  57. package/src/css/ui/containers/dropdown-menu-items.css +29 -0
  58. package/src/css/ui/containers/scroll-panel.css +4 -1
  59. package/src/rust/agent/client/protocol.rs +0 -89
  60. package/src/rust/agent/markdown/mod.rs +0 -238
  61. package/src/rust/agent/preamble.md +1 -0
  62. package/src/rust/agent/tools.rs +2 -16
  63. package/src/rust/components/column_selector/active_column.rs +15 -5
  64. package/src/rust/components/column_selector/add_expression_button.rs +9 -26
  65. package/src/rust/components/column_selector/config_selector.rs +34 -6
  66. package/src/rust/components/column_selector/derived_control.rs +229 -0
  67. package/src/rust/components/column_selector/expr_edit_button.rs +3 -2
  68. package/src/rust/components/column_selector/filter_column.rs +0 -2
  69. package/src/rust/components/column_selector/inactive_column.rs +4 -5
  70. package/src/rust/components/column_selector/pivot_column.rs +21 -1
  71. package/src/rust/components/column_selector/sort_column.rs +4 -3
  72. package/src/rust/components/column_selector.rs +21 -1
  73. package/src/rust/components/column_settings_sidebar/save_settings.rs +5 -3
  74. package/src/rust/components/column_settings_sidebar/style_tab.rs +1 -0
  75. package/src/rust/components/column_settings_sidebar.rs +25 -19
  76. package/src/rust/components/debug_panel.rs +38 -29
  77. package/src/rust/components/dragdrop_list.rs +8 -9
  78. package/src/rust/components/export_dropdown.rs +6 -0
  79. package/src/rust/components/main_panel/msg.rs +6 -4
  80. package/src/rust/components/main_panel/reconcile.rs +6 -1
  81. package/src/rust/components/main_panel/render.rs +70 -51
  82. package/src/rust/components/main_panel/update.rs +39 -1
  83. package/src/rust/components/main_panel.rs +97 -59
  84. package/src/rust/components/new_panel_menu.rs +69 -1
  85. package/src/rust/components/panel_menu.rs +63 -12
  86. package/src/rust/components/plugin_tab.rs +3 -2
  87. package/src/rust/components/render_warning.rs +2 -1
  88. package/src/rust/components/settings_panel.rs +4 -4
  89. package/src/rust/components/status_bar.rs +80 -21
  90. package/src/rust/components/status_indicator.rs +8 -1
  91. package/src/rust/components/viewer/filters.rs +15 -3
  92. package/src/rust/components/viewer/msg.rs +8 -0
  93. package/src/rust/components/viewer/panels.rs +44 -4
  94. package/src/rust/components/viewer/render.rs +1 -0
  95. package/src/rust/components/viewer/settings.rs +0 -22
  96. package/src/rust/components/viewer/snapshots.rs +2 -2
  97. package/src/rust/components/viewer/wiring.rs +18 -1
  98. package/src/rust/components/viewer.rs +47 -27
  99. package/src/rust/components/window_editor.rs +5 -4
  100. package/src/rust/config/column_config_schema.rs +38 -377
  101. package/src/rust/config/export_method.rs +24 -0
  102. package/src/rust/config/number_string_format.rs +0 -94
  103. package/src/rust/config/options.rs +3 -7
  104. package/src/rust/config/plugin_static_config.rs +0 -42
  105. package/src/rust/config/viewer_config.rs +47 -17
  106. package/src/rust/config/workspace_config.rs +158 -128
  107. package/src/rust/custom_elements/viewer.rs +189 -129
  108. package/src/rust/custom_events.rs +33 -18
  109. package/src/rust/derived.rs +266 -0
  110. package/src/rust/exprtk/tokenize.rs +0 -54
  111. package/src/rust/js/plugin.rs +1 -1
  112. package/src/rust/lib.rs +3 -0
  113. package/src/rust/presentation.rs +23 -4
  114. package/src/rust/queries/column_locator.rs +55 -48
  115. package/src/rust/queries/get_viewer_config.rs +1 -1
  116. package/src/rust/queries/plugin_column_styles.rs +22 -23
  117. package/src/rust/renderer/dispatch.rs +2 -0
  118. package/src/rust/renderer/plugin_config.rs +652 -487
  119. package/src/rust/renderer/props.rs +4 -0
  120. package/src/rust/renderer/registry.rs +2 -6
  121. package/src/rust/renderer/state.rs +205 -0
  122. package/src/rust/renderer.rs +201 -59
  123. package/src/rust/session/metadata.rs +35 -19
  124. package/src/rust/session/op_queue.rs +430 -0
  125. package/src/rust/session/panel_state.rs +327 -0
  126. package/src/rust/session/view_subscription.rs +42 -3
  127. package/src/rust/session.rs +931 -419
  128. package/src/rust/tasks/apply_global_filters.rs +49 -35
  129. package/src/rust/tasks/auto_pause.rs +22 -12
  130. package/src/rust/tasks/copy_export.rs +72 -0
  131. package/src/rust/tasks/create_panel.rs +46 -21
  132. package/src/rust/tasks/derive.rs +312 -0
  133. package/src/rust/tasks/mod.rs +6 -2
  134. package/src/rust/tasks/pipeline.rs +152 -89
  135. package/src/rust/tasks/reset_all.rs +31 -20
  136. package/src/rust/tasks/restore_derived.rs +200 -0
  137. package/src/rust/tasks/restore_panel.rs +153 -141
  138. package/src/rust/tasks/send_column_config.rs +56 -29
  139. package/src/rust/tasks/send_plugin_config.rs +52 -23
  140. package/src/rust/tasks/set_edit_mode.rs +33 -25
  141. package/src/rust/tasks/sync_update_panels.rs +40 -13
  142. package/src/rust/tasks/table_lifecycle.rs +70 -34
  143. package/src/rust/tasks/transactional_restore.rs +387 -0
  144. package/src/rust/tasks/update_theme.rs +45 -6
  145. package/src/rust/ui/containers/scroll_panel.rs +17 -10
  146. package/src/rust/ui/containers/split_panel.rs +0 -41
  147. package/src/rust/ui/context_menu.rs +157 -18
  148. package/src/rust/ui/font_loader.rs +23 -35
  149. package/src/rust/ui/form/mirrored_textarea.rs +0 -12
  150. package/src/rust/ui/form/multi_stop_gradient_selector.rs +0 -89
  151. package/src/rust/ui/form/palette_selector.rs +0 -51
  152. package/src/rust/ui/portal.rs +4 -10
  153. package/src/rust/utils/browser/mod.rs +2 -3
  154. package/src/{ts/perspective-viewer.inline.ts → rust/utils/browser/mouse.rs} +11 -10
  155. package/src/rust/utils/browser/request_animation_frame.rs +27 -8
  156. package/src/rust/utils/css.rs +0 -360
  157. package/src/rust/utils/{tests/mod.rs → in_flight.rs} +62 -2
  158. package/src/rust/utils/local_fonts.rs +0 -36
  159. package/src/rust/utils/mod.rs +4 -3
  160. package/src/rust/utils/modal_position.rs +15 -21
  161. package/src/rust/utils/pending_effects.rs +6 -30
  162. package/src/rust/utils/{browser/tests/mod.rs → plugin_host_state.rs} +31 -1
  163. package/src/rust/utils/pubsub.rs +0 -1
  164. package/src/rust/workspace.rs +187 -140
  165. package/src/svg/derived.svg +6 -0
  166. package/src/themes/icons.css +1 -0
  167. package/src/themes/intl/ar.css +10 -0
  168. package/src/themes/intl/de.css +10 -0
  169. package/src/themes/intl/es.css +10 -0
  170. package/src/themes/intl/fr.css +10 -0
  171. package/src/themes/intl/ja.css +10 -0
  172. package/src/themes/intl/pt.css +10 -0
  173. package/src/themes/intl/zh.css +10 -0
  174. package/src/themes/intl.css +10 -0
  175. package/src/themes/monokai.css +1 -1
  176. package/src/themes/vaporwave.css +1 -1
  177. package/src/ts/extensions.ts +3 -36
  178. package/src/ts/perspective-viewer.ts +1 -1
  179. package/src/ts/plugin.ts +4 -1
  180. package/src/{rust/utils/tests/request_animation_frame.rs → ts/select-detail.ts} +32 -26
  181. package/src/ts/ts-rs/DerivedTableConfig.ts +22 -0
  182. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  183. package/src/ts/ts-rs/PanelViewerConfig.ts +7 -6
  184. package/src/ts/ts-rs/RestoreOptions.ts +3 -7
  185. package/src/ts/ts-rs/TableRef.ts +9 -0
  186. package/src/ts/ts-rs/ViewerConfig.ts +7 -6
  187. package/src/ts/ts-rs/ViewerConfigUpdate.ts +7 -5
  188. package/src/ts/ts-rs/WorkspaceConfig.ts +7 -1
  189. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -0
  190. package/dist/esm/perspective-viewer.inline.d.ts +0 -3
  191. package/dist/esm/perspective-viewer.inline.js +0 -21
  192. package/dist/esm/perspective-viewer.inline.js.map +0 -7
  193. package/src/rust/tasks/restore_and_render.rs +0 -107
  194. package/src/rust/utils/browser/tests/debounce.rs +0 -163
  195. package/src/rust/utils/tests/pubsub.rs +0 -95
  196. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
  197. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
  198. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
  199. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
  200. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
  201. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
@@ -226,241 +226,3 @@ impl Writer {
226
226
  }
227
227
  }
228
228
  }
229
-
230
- #[cfg(test)]
231
- mod tests {
232
- use super::events::{Align, Event, Tag};
233
-
234
- fn parse(text: &str) -> Vec<Event> {
235
- super::block::parse(text)
236
- }
237
-
238
- fn start(tag: Tag) -> Event {
239
- Event::Start(tag)
240
- }
241
-
242
- fn text(x: &str) -> Event {
243
- Event::Text(x.to_owned())
244
- }
245
-
246
- #[test]
247
- fn paragraph_and_emphasis() {
248
- assert_eq!(parse("**Done!** Key *stats* here."), vec![
249
- start(Tag::Paragraph),
250
- start(Tag::Strong),
251
- text("Done!"),
252
- Event::End,
253
- text(" Key "),
254
- start(Tag::Emphasis),
255
- text("stats"),
256
- Event::End,
257
- text(" here."),
258
- Event::End,
259
- ]);
260
- }
261
-
262
- #[test]
263
- fn snake_case_is_not_emphasis() {
264
- assert_eq!(parse("call get_schema then set_view_config"), vec![
265
- start(Tag::Paragraph),
266
- text("call get_schema then set_view_config"),
267
- Event::End,
268
- ]);
269
- }
270
-
271
- #[test]
272
- fn nested_strong_emphasis() {
273
- assert_eq!(parse("**bold with *ital* inside**"), vec![
274
- start(Tag::Paragraph),
275
- start(Tag::Strong),
276
- text("bold with "),
277
- start(Tag::Emphasis),
278
- text("ital"),
279
- Event::End,
280
- text(" inside"),
281
- Event::End,
282
- Event::End,
283
- ]);
284
- }
285
-
286
- #[test]
287
- fn unpaired_delimiters_stay_literal() {
288
- // Space on both sides ⇒ the runs can neither open nor close, so
289
- // they stay in the running text buffer.
290
- assert_eq!(parse("2 * 3 * 4 and a ~ tilde"), vec![
291
- start(Tag::Paragraph),
292
- text("2 * 3 * 4 and a ~ tilde"),
293
- Event::End,
294
- ]);
295
- }
296
-
297
- #[test]
298
- fn code_spans_and_strikethrough() {
299
- assert_eq!(parse("run `pnpm test` and ~~skip~~ this"), vec![
300
- start(Tag::Paragraph),
301
- text("run "),
302
- Event::Code("pnpm test".to_owned()),
303
- text(" and "),
304
- start(Tag::Strikethrough),
305
- text("skip"),
306
- Event::End,
307
- text(" this"),
308
- Event::End,
309
- ]);
310
- }
311
-
312
- #[test]
313
- fn heading_rule_and_fence() {
314
- assert_eq!(parse("## Stats\n\n---\n\n```sql\nSELECT 1;\n```"), vec![
315
- start(Tag::Heading(2)),
316
- text("Stats"),
317
- Event::End,
318
- Event::Rule,
319
- start(Tag::CodeBlock(Some("sql".to_owned()))),
320
- text("SELECT 1;\n"),
321
- Event::End,
322
- ]);
323
- }
324
-
325
- #[test]
326
- fn nested_list_with_code_block_in_item() {
327
- assert_eq!(
328
- parse("- first\n- second:\n ```\n x = 1\n ```\n - inner"),
329
- vec![
330
- start(Tag::List(None)),
331
- start(Tag::Item),
332
- text("first"),
333
- Event::End,
334
- start(Tag::Item),
335
- text("second:"),
336
- start(Tag::CodeBlock(None)),
337
- text("x = 1\n"),
338
- Event::End,
339
- start(Tag::List(None)),
340
- start(Tag::Item),
341
- text("inner"),
342
- Event::End,
343
- Event::End,
344
- Event::End,
345
- Event::End,
346
- ]
347
- );
348
- }
349
-
350
- #[test]
351
- fn ordered_list_start() {
352
- assert_eq!(parse("3. three\n4. four"), vec![
353
- start(Tag::List(Some(3))),
354
- start(Tag::Item),
355
- text("three"),
356
- Event::End,
357
- start(Tag::Item),
358
- text("four"),
359
- Event::End,
360
- Event::End,
361
- ]);
362
- }
363
-
364
- #[test]
365
- fn table_with_alignment_and_pipe_in_code() {
366
- assert_eq!(
367
- parse("| Region | Sales |\n| :--- | ---: |\n| West | `a\\|b` |"),
368
- vec![
369
- start(Tag::Table(vec![Align::Left, Align::Right])),
370
- start(Tag::TableHead),
371
- start(Tag::TableCell),
372
- text("Region"),
373
- Event::End,
374
- start(Tag::TableCell),
375
- text("Sales"),
376
- Event::End,
377
- Event::End,
378
- start(Tag::TableRow),
379
- start(Tag::TableCell),
380
- text("West"),
381
- Event::End,
382
- start(Tag::TableCell),
383
- Event::Code("a|b".to_owned()),
384
- Event::End,
385
- Event::End,
386
- Event::End,
387
- ]
388
- );
389
- }
390
-
391
- #[test]
392
- fn dashes_after_paragraph_are_a_rule_not_a_table() {
393
- assert_eq!(parse("Title\n---"), vec![
394
- start(Tag::Paragraph),
395
- text("Title"),
396
- Event::End,
397
- Event::Rule,
398
- ]);
399
- }
400
-
401
- #[test]
402
- fn links_images_and_autolinks() {
403
- assert_eq!(
404
- parse("[safe](https://example.com \"hi\") ![leak](https://example.com/x.png) <https://a.co>"),
405
- vec![
406
- start(Tag::Paragraph),
407
- start(Tag::Link {
408
- dest_url: "https://example.com".to_owned(),
409
- title: "hi".to_owned(),
410
- }),
411
- text("safe"),
412
- Event::End,
413
- text(" "),
414
- start(Tag::Image {
415
- dest_url: "https://example.com/x.png".to_owned(),
416
- }),
417
- text("leak"),
418
- Event::End,
419
- text(" "),
420
- start(Tag::Link {
421
- dest_url: "https://a.co".to_owned(),
422
- title: String::new(),
423
- }),
424
- text("https://a.co"),
425
- Event::End,
426
- Event::End,
427
- ]
428
- );
429
- }
430
-
431
- #[test]
432
- fn raw_html_is_literal_text() {
433
- assert_eq!(parse("<script>alert(1)</script>"), vec![
434
- start(Tag::Paragraph),
435
- text("<script>alert(1)</script>"),
436
- Event::End,
437
- ]);
438
- }
439
-
440
- #[test]
441
- fn blockquote_and_breaks() {
442
- assert_eq!(parse("> quoted\n> more\n\nafter \nbreak"), vec![
443
- start(Tag::BlockQuote),
444
- start(Tag::Paragraph),
445
- text("quoted"),
446
- Event::SoftBreak,
447
- text("more"),
448
- Event::End,
449
- Event::End,
450
- start(Tag::Paragraph),
451
- text("after"),
452
- Event::HardBreak,
453
- text("break"),
454
- Event::End,
455
- ]);
456
- }
457
-
458
- #[test]
459
- fn entities_decode_or_stay_literal() {
460
- assert_eq!(parse("a &amp; b &#65; &unknown; c"), vec![
461
- start(Tag::Paragraph),
462
- text("a & b A &unknown; c"),
463
- Event::End,
464
- ]);
465
- }
466
- }
@@ -11,6 +11,7 @@ Panels: the viewer is a dashboard that can hold multiple panels (independent sid
11
11
 
12
12
  Rules:
13
13
  - Column names in configs must match the schema exactly, including case.
14
+ - A `set_view_config` or `add_panel` call that returns an error changed NOTHING - the viewer is exactly as it was, so there is nothing to undo.
14
15
  - If a tool returns an error, correct your input and retry. If the same call fails twice with the same error, stop and report the problem instead.
15
16
  - You have a fixed budget of model requests per prompt - be economical with tool calls.
16
17
  - After acting, answer with a single short sentence describing what changed. If the user asks a question you can answer from tool results, answer it directly.
@@ -482,7 +482,7 @@ pub struct ToolError(String);
482
482
  /// applied config.
483
483
  fn retyped_columns(
484
484
  session: &crate::session::Session,
485
- config: &crate::config::ViewerConfig,
485
+ config: &crate::config::ViewerConfig<String, String>,
486
486
  ) -> Vec<Value> {
487
487
  let metadata = session.metadata();
488
488
  config
@@ -873,22 +873,8 @@ impl PerspectiveViewerElement {
873
873
  async fn tool_set_view_config(&self, ctx: &ToolCtx, args: Value) -> Result<Value, ToolError> {
874
874
  let args: SetViewConfigArgs = serde_json::from_value(args)?;
875
875
  let target = args.panel.as_deref();
876
- let panel = self.agent_panel(target)?;
877
- let snapshot =
878
- get_viewer_config(&panel.session, &panel.renderer, &self.presentation).await?;
879
-
876
+ self.agent_panel(target)?;
880
877
  if let Err(err) = self.agent_restore(target, &args.config).await {
881
- let rolled = self
882
- .agent_restore(target, &serde_json::to_value(&snapshot)?)
883
- .await;
884
-
885
- let err = match rolled {
886
- Ok(()) => err,
887
- Err(rollback_err) => ToolError(format!(
888
- "{err} (additionally, restoring the prior config failed: {rollback_err})"
889
- )),
890
- };
891
-
892
878
  return Err(with_docs_hint(ctx, err));
893
879
  }
894
880
 
@@ -257,9 +257,9 @@ impl Component for ActiveColumn {
257
257
  };
258
258
 
259
259
  let ondragenter = ctx.props().ondragenter.reform(move |event: DragEvent| {
260
- // Safari does not set `relatedTarget` so this event must be allowed to
261
- // bubble so we can count entry/exit stacks to determine true
262
- // `"dragleave"`.
260
+ // Safari does not set `relatedTarget` so this event must be allowed
261
+ // to bubble so we can count entry/exit stacks to
262
+ // determine true `"dragleave"`.
263
263
  if event.related_target().is_some() {
264
264
  event.stop_propagation();
265
265
  event.prevent_default();
@@ -328,7 +328,7 @@ impl Component for ActiveColumn {
328
328
  let remove_column = if is_required {
329
329
  None
330
330
  } else {
331
- Some(ctx.link().callback({
331
+ Some(left_click(ctx.link().callback({
332
332
  let event_name = name.to_owned();
333
333
  move |event: MouseEvent| {
334
334
  ActiveColumnMsg::DeactivateColumn(
@@ -336,7 +336,7 @@ impl Component for ActiveColumn {
336
336
  event.shift_key(),
337
337
  )
338
338
  }
339
- }))
339
+ })))
340
340
  };
341
341
 
342
342
  let ondragend = &ctx.props().ondragend.reform(|_| ());
@@ -367,9 +367,11 @@ impl Component for ActiveColumn {
367
367
  if is_required {
368
368
  class.push("required");
369
369
  };
370
+
370
371
  if !is_required {
371
372
  class.push("shift-alt-icon");
372
373
  }
374
+
373
375
  html! {
374
376
  <div
375
377
  class={outer_classes}
@@ -468,6 +470,14 @@ impl ActiveColumnProps {
468
470
  fn get_is_required(&self, idx: usize) -> bool {
469
471
  let min_cols = self.renderer.metadata().min_config_columns.unwrap_or(0);
470
472
  idx < min_cols
473
+ && (self
474
+ .session
475
+ .get_view_config()
476
+ .columns
477
+ .iter()
478
+ .flatten()
479
+ .count()
480
+ == 1)
471
481
  }
472
482
 
473
483
  fn get_aggregate(&self, name: &str) -> Option<Aggregate> {
@@ -13,6 +13,7 @@
13
13
  use yew::prelude::*;
14
14
 
15
15
  use super::{ColumnLocator, ColumnSettingsTarget};
16
+ use crate::utils::left_click;
16
17
 
17
18
  #[derive(Clone, PartialEq, Properties)]
18
19
  pub struct AddExpressionButtonProps {
@@ -23,41 +24,23 @@ pub struct AddExpressionButtonProps {
23
24
  pub selected_column: Option<ColumnLocator>,
24
25
  }
25
26
 
26
- /// `onmouseover` is triggered incorrectly on the `DragTarget` of a
27
- /// drag/drop action after `DropEvent` has fired on the `DropTarget`,
28
- /// causing brief hover effects where the mouse _was_ before the action
29
- /// initiated. Various methods of correcting this were attempted, settling
30
- /// on a manual `dragdrop-hover` class toggle, using the `.button()` property
31
- /// to determine the mis-fired event. This was determined experimentally -
32
- /// according to my read of the spec, this is a bug in Chrome.
33
- ///
34
- /// As a result there are 3 hover states `MouseEnter(true)`,
35
- /// `MouseEnter(false)`, and `MouseLeave`; `MouseEnter(false)` can be
36
- /// replaced, but it causes an extra render of the DOM un-necessarily.
27
+ /// The "New Column" row; hover is CSS so a drag's stray `mouseover` cannot
28
+ /// pin the highlight.
37
29
  #[function_component]
38
30
  pub fn AddExpressionButton(p: &AddExpressionButtonProps) -> Html {
39
- let is_mouseover = yew::use_state_eq(|| false);
40
- let onmouseover = yew::use_callback(is_mouseover.setter(), |event: MouseEvent, mo| {
41
- mo.set(event.button() == 0);
42
- });
31
+ let onmousedown = left_click(
32
+ p.on_open_expr_panel
33
+ .reform(|_| ColumnSettingsTarget::NewExpression),
34
+ );
43
35
 
44
- let onmouseout = yew::use_callback(is_mouseover.setter(), |_event, mo| {
45
- mo.set(false);
46
- });
47
-
48
- let onmousedown = p
49
- .on_open_expr_panel
50
- .reform(|_| ColumnSettingsTarget::NewExpression);
51
-
52
- let class = if *is_mouseover || matches!(p.selected_column, Some(ColumnLocator::NewExpression))
53
- {
36
+ let class = if matches!(p.selected_column, Some(ColumnLocator::NewExpression)) {
54
37
  classes!("dragdrop-hover")
55
38
  } else {
56
39
  classes!()
57
40
  };
58
41
 
59
42
  html! {
60
- <div id="add-expression" data-index="-1" {class} {onmouseover} {onmouseout} {onmousedown}>
43
+ <div id="add-expression" data-index="-1" {class} {onmousedown}>
61
44
  <span class="icon" />
62
45
  <span id="add-expression-button" />
63
46
  </div>
@@ -24,7 +24,8 @@ use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPo
24
24
  use crate::components::dragdrop_list::*;
25
25
  use crate::components::filter_dropdown::{FilterDropDownElement, FilterDropDownPortal};
26
26
  use crate::config::PluginStaticConfig;
27
- use crate::presentation::Presentation;
27
+ use crate::presentation::{ColumnLocator, ColumnSettingsTarget, Presentation};
28
+ use crate::queries::has_column_config;
28
29
  use crate::renderer::*;
29
30
  use crate::session::drag_drop_update::*;
30
31
  use crate::session::*;
@@ -36,6 +37,13 @@ use crate::utils::*;
36
37
  pub struct ConfigSelectorProps {
37
38
  pub onselect: Callback<()>,
38
39
 
40
+ /// Opens a pivot column's settings, so its format config is reachable.
41
+ pub on_open_expr_panel: Callback<ColumnSettingsTarget>,
42
+
43
+ /// The column whose settings sidebar is open, threaded as a VALUE prop so
44
+ /// closing or retargeting the sidebar clears this pill's open indicator.
45
+ pub selected_column: Option<ColumnLocator>,
46
+
39
47
  #[prop_or_default]
40
48
  pub ondragenter: Callback<()>,
41
49
 
@@ -43,9 +51,11 @@ pub struct ConfigSelectorProps {
43
51
  /// (group_by, sort, filter, etc.) trigger re-renders via normal prop
44
52
  /// diffing rather than a PubSub `view_created` subscription.
45
53
  pub view_config: PtrEqRc<ViewConfig>,
54
+
46
55
  /// Column currently being dragged — threaded to show `dragdrop-highlight`
47
56
  /// without subscribing to `dragstart_received`/`dragend_received`.
48
57
  pub drag_column: Option<String>,
58
+
49
59
  /// Session metadata snapshot — threaded from `SessionProps`.
50
60
  pub metadata: SessionMetadataRc,
51
61
 
@@ -69,6 +79,7 @@ pub struct ConfigSelectorProps {
69
79
  impl PartialEq for ConfigSelectorProps {
70
80
  fn eq(&self, other: &Self) -> bool {
71
81
  self.view_config == other.view_config
82
+ && self.selected_column == other.selected_column
72
83
  && self.drag_column == other.drag_column
73
84
  && self.metadata == other.metadata
74
85
  && self.selected_theme == other.selected_theme
@@ -552,10 +563,10 @@ impl Component for ConfigSelector {
552
563
  }
553
564
  }
554
565
 
555
- // /// Should not render on change, as this component only depends on service
556
- // /// state.
557
- // fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool
558
- // { false
566
+ // /// Should not render on change, as this component only depends on
567
+ // service /// state.
568
+ // fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) ->
569
+ // bool { false
559
570
  // }
560
571
 
561
572
  fn view(&self, ctx: &Context<Self>) -> Html {
@@ -578,7 +589,18 @@ impl Component for ConfigSelector {
578
589
  )
579
590
  };
580
591
 
581
- let transpose = ctx.link().callback(|_| ConfigSelectorMsg::TransposePivots);
592
+ let has_config = |name: &str| {
593
+ has_column_config(name, config, &ctx.props().metadata, &ctx.props().renderer)
594
+ };
595
+
596
+ let editing_column = match &ctx.props().selected_column {
597
+ Some(ColumnLocator::Table(x))
598
+ | Some(ColumnLocator::Expression(x))
599
+ | Some(ColumnLocator::Window(x)) => Some(x.clone()),
600
+ _ => None,
601
+ };
602
+
603
+ let transpose = left_click(ctx.link().callback(|_| ConfigSelectorMsg::TransposePivots));
582
604
  let column_dropdown = self.column_dropdown.clone();
583
605
  let mut class = classes!();
584
606
 
@@ -665,6 +687,9 @@ impl Component for ConfigSelector {
665
687
  action={DragTarget::GroupBy}
666
688
  column={group_by.clone()}
667
689
  metadata={metadata.clone()}
690
+ on_open_expr_panel={has_config(group_by)
691
+ .then(|| ctx.props().on_open_expr_panel.clone())}
692
+ is_editing={editing_column.as_deref() == Some(group_by.as_str())}
668
693
  {presentation}
669
694
  opt_session={session}
670
695
  >
@@ -716,6 +741,9 @@ impl Component for ConfigSelector {
716
741
  action={ DragTarget::SplitBy }
717
742
  column={ split_by.clone() }
718
743
  metadata={metadata.clone()}
744
+ on_open_expr_panel={has_config(split_by)
745
+ .then(|| ctx.props().on_open_expr_panel.clone())}
746
+ is_editing={editing_column.as_deref() == Some(split_by.as_str())}
719
747
  {presentation}
720
748
  opt_session={session}>
721
749
  </PivotColumn>