@perspective-dev/viewer 5.2.0 → 5.3.1

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 (179) 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/de.css +1 -1
  11. package/dist/css/intl/es.css +1 -1
  12. package/dist/css/intl/fr.css +1 -1
  13. package/dist/css/intl/ja.css +1 -1
  14. package/dist/css/intl/pt.css +1 -1
  15. package/dist/css/intl/zh.css +1 -1
  16. package/dist/css/intl.css +1 -1
  17. package/dist/css/ledger.css +1 -0
  18. package/dist/css/monokai.css +1 -1
  19. package/dist/css/nord.css +1 -0
  20. package/dist/css/phosphor.css +1 -1
  21. package/dist/css/pro-dark.css +1 -1
  22. package/dist/css/pro.css +1 -1
  23. package/dist/css/solarized-dark.css +1 -1
  24. package/dist/css/solarized.css +1 -1
  25. package/dist/css/themes.css +1 -1
  26. package/dist/css/vaporwave.css +1 -1
  27. package/dist/css/velvet.css +1 -0
  28. package/dist/docs/perspective-docs.json +1 -1
  29. package/dist/esm/column-format.d.ts +16 -3
  30. package/dist/esm/perspective-viewer.d.ts +1 -0
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +8 -8
  34. package/dist/esm/perspective-viewer.js.map +3 -3
  35. package/dist/esm/plugin.d.ts +10 -0
  36. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
  37. package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
  38. package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
  39. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  40. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
  41. package/dist/wasm/perspective-viewer.d.ts +43 -36
  42. package/dist/wasm/perspective-viewer.js +198 -136
  43. package/dist/wasm/perspective-viewer.wasm +0 -0
  44. package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
  45. package/package.json +1 -1
  46. package/src/css/column-settings-panel.css +28 -5
  47. package/src/css/column-style.css +291 -31
  48. package/src/css/containers/tabs.css +13 -3
  49. package/src/css/dom/select.css +21 -9
  50. package/src/css/form/code-editor.css +4 -4
  51. package/src/css/plugin-selector.css +1 -0
  52. package/src/css/plugin-settings-panel.css +72 -0
  53. package/src/css/viewer.css +0 -5
  54. package/src/rust/agent/tools.rs +53 -19
  55. package/src/rust/components/column_selector/active_column.rs +2 -2
  56. package/src/rust/components/column_selector/add_expression_button.rs +3 -3
  57. package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
  58. package/src/rust/components/column_selector/config_selector.rs +8 -0
  59. package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
  60. package/src/rust/components/column_selector/inactive_column.rs +2 -2
  61. package/src/rust/components/column_selector.rs +48 -4
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
  63. package/src/rust/components/column_settings_sidebar/style_tab.rs +330 -234
  64. package/src/rust/components/column_settings_sidebar.rs +88 -69
  65. package/src/rust/components/containers/control_group.rs +91 -0
  66. package/src/rust/components/containers/mod.rs +1 -0
  67. package/src/rust/components/containers/select.rs +8 -2
  68. package/src/rust/components/datetime_column_style/simple.rs +15 -4
  69. package/src/rust/components/datetime_column_style.rs +50 -10
  70. package/src/rust/components/editable_header.rs +85 -144
  71. package/src/rust/components/expression_editor.rs +9 -16
  72. package/src/rust/components/form/code_editor.rs +16 -6
  73. package/src/rust/components/form/debug.rs +1 -0
  74. package/src/rust/components/form/mod.rs +3 -1
  75. package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
  76. package/src/rust/components/form/named_value_picker.rs +86 -0
  77. package/src/rust/components/form/palette_selector.rs +440 -0
  78. package/src/rust/components/main_panel/msg.rs +2 -2
  79. package/src/rust/components/main_panel/reconcile.rs +4 -4
  80. package/src/rust/components/plugin_tab.rs +126 -112
  81. package/src/rust/components/settings_panel.rs +26 -20
  82. package/src/rust/components/status_bar.rs +0 -1
  83. package/src/rust/components/style_controls/number_string_format/digits_section.rs +22 -10
  84. package/src/rust/components/style_controls/number_string_format/misc_section.rs +6 -2
  85. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -0
  86. package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
  87. package/src/rust/components/style_controls/number_string_format.rs +37 -14
  88. package/src/rust/components/viewer/msg.rs +46 -7
  89. package/src/rust/components/viewer/panels.rs +11 -9
  90. package/src/rust/components/viewer/render.rs +8 -3
  91. package/src/rust/components/viewer/settings.rs +163 -23
  92. package/src/rust/components/viewer/snapshots.rs +46 -21
  93. package/src/rust/components/viewer/wiring.rs +14 -25
  94. package/src/rust/components/viewer.rs +12 -8
  95. package/src/rust/config/column_config_schema.rs +435 -45
  96. package/src/rust/config/css_scale.rs +882 -0
  97. package/src/rust/config/datetime_column_style.rs +4 -21
  98. package/src/rust/config/mod.rs +2 -0
  99. package/src/rust/config/number_string_format.rs +211 -18
  100. package/src/rust/config/options.rs +10 -0
  101. package/src/rust/config/viewer_config.rs +49 -55
  102. package/src/rust/config/workspace_config.rs +132 -5
  103. package/src/rust/custom_elements/viewer.rs +214 -155
  104. package/src/rust/custom_events.rs +14 -1
  105. package/src/rust/js/regular_layout.rs +3 -3
  106. package/src/rust/lib.rs +2 -0
  107. package/src/rust/presentation/column_locator.rs +19 -5
  108. package/src/rust/presentation.rs +209 -36
  109. package/src/rust/queries/column_locator.rs +74 -44
  110. package/src/rust/queries/get_viewer_config.rs +0 -3
  111. package/src/rust/queries/mod.rs +2 -0
  112. package/src/rust/queries/palette_set.rs +82 -0
  113. package/src/rust/queries/plugin_column_styles.rs +6 -2
  114. package/src/rust/renderer/dispatch.rs +50 -12
  115. package/src/rust/renderer/plugin_config.rs +362 -53
  116. package/src/rust/renderer.rs +15 -42
  117. package/src/rust/session/column_defaults_update.rs +15 -8
  118. package/src/rust/session/metadata.rs +5 -1
  119. package/src/rust/session/props.rs +1 -0
  120. package/src/rust/session.rs +9 -6
  121. package/src/rust/tasks/create_panel.rs +44 -57
  122. package/src/rust/tasks/edit_expression.rs +19 -23
  123. package/src/rust/tasks/edit_window.rs +8 -10
  124. package/src/rust/tasks/eject.rs +4 -1
  125. package/src/rust/tasks/pipeline.rs +2 -2
  126. package/src/rust/tasks/presize_panels.rs +44 -5
  127. package/src/rust/tasks/reset_all.rs +4 -4
  128. package/src/rust/tasks/restore_and_render.rs +17 -44
  129. package/src/rust/tasks/restore_panel.rs +11 -13
  130. package/src/rust/tasks/set_edit_mode.rs +3 -1
  131. package/src/rust/tasks/sync_update_panels.rs +11 -1
  132. package/src/rust/tasks/update_theme.rs +23 -18
  133. package/src/rust/utils/css_vars.rs +26 -0
  134. package/src/rust/utils/mod.rs +2 -0
  135. package/src/rust/utils/spawn.rs +1 -1
  136. package/src/rust/workspace.rs +435 -272
  137. package/src/svg/lock.svg +7 -0
  138. package/src/themes/blueprint.css +190 -0
  139. package/src/themes/botanical.css +14 -17
  140. package/src/themes/defaults.css +4 -110
  141. package/src/themes/dracula.css +19 -14
  142. package/src/themes/eggplant.css +189 -0
  143. package/src/themes/gruvbox-dark.css +23 -15
  144. package/src/themes/gruvbox.css +11 -1
  145. package/src/themes/icons.css +2 -2
  146. package/src/themes/intl/de.css +23 -1
  147. package/src/themes/intl/es.css +23 -1
  148. package/src/themes/intl/fr.css +23 -1
  149. package/src/themes/intl/ja.css +23 -1
  150. package/src/themes/intl/pt.css +23 -1
  151. package/src/themes/intl/zh.css +23 -1
  152. package/src/themes/intl.css +26 -4
  153. package/src/themes/ledger.css +195 -0
  154. package/src/themes/monokai.css +24 -14
  155. package/src/themes/nord.css +186 -0
  156. package/src/themes/phosphor.css +7 -14
  157. package/src/themes/pro-dark.css +12 -16
  158. package/src/themes/pro.css +118 -0
  159. package/src/themes/solarized-dark.css +9 -16
  160. package/src/themes/solarized.css +7 -0
  161. package/src/themes/themes.css +10 -5
  162. package/src/themes/vaporwave.css +22 -14
  163. package/src/themes/velvet.css +192 -0
  164. package/src/ts/column-format.ts +52 -2
  165. package/src/ts/perspective-viewer.ts +1 -0
  166. package/src/ts/plugin.ts +14 -0
  167. package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
  168. package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
  169. package/src/ts/ts-rs/ViewerConfig.ts +1 -1
  170. package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
  171. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
  172. package/src/rust/components/form/color_range_selector.rs +0 -137
  173. package/src/svg/dropdown-selector-light.svg +0 -3
  174. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline0.js +0 -0
  175. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline1.js +0 -0
  176. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline2.js +0 -0
  177. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline3.js +0 -0
  178. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline4.js +0 -0
  179. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline5.js +0 -0
@@ -42,6 +42,7 @@ use crate::tasks::{
42
42
  delete_expr, delete_window, save_expr, save_window, update_expr, update_window,
43
43
  };
44
44
  use crate::utils::PtrEqRc;
45
+ use crate::workspace::Workspace;
45
46
 
46
47
  #[derive(Clone, Derivative, Properties)]
47
48
  #[derivative(Debug)]
@@ -91,12 +92,13 @@ pub struct ColumnSettingsPanelProps {
91
92
 
92
93
  #[derivative(Debug = "ignore")]
93
94
  pub session: Session,
95
+
96
+ #[derivative(Debug = "ignore")]
97
+ pub workspace: Workspace,
94
98
  }
95
99
 
96
- impl ColumnSettingsPanelProps {
97
- /// Everything EXCEPT the trap-door `auto_width`: the props whose change
98
- /// invalidates the drafts that `initialize` rebuilds.
99
- fn identity_eq(&self, other: &Self) -> bool {
100
+ impl PartialEq for ColumnSettingsPanelProps {
101
+ fn eq(&self, other: &Self) -> bool {
100
102
  self.selected_column == other.selected_column
101
103
  && self.selected_tab == other.selected_tab
102
104
  && self.plugin_name == other.plugin_name
@@ -104,14 +106,46 @@ impl ColumnSettingsPanelProps {
104
106
  && self.view_config == other.view_config
105
107
  && self.column_stats == other.column_stats
106
108
  && self.selected_theme == other.selected_theme
109
+ && self.auto_width == other.auto_width
110
+ && self.is_pinned == other.is_pinned
107
111
  }
108
112
  }
109
113
 
110
- impl PartialEq for ColumnSettingsPanelProps {
111
- fn eq(&self, other: &Self) -> bool {
112
- self.identity_eq(other)
113
- && self.auto_width == other.auto_width
114
- && self.is_pinned == other.is_pinned
114
+ #[derive(Clone, PartialEq)]
115
+ struct Initials {
116
+ column_name: String,
117
+ expr: Rc<String>,
118
+ header: Option<String>,
119
+ window: Option<WindowSpec>,
120
+ }
121
+
122
+ impl Initials {
123
+ fn of(props: &ColumnSettingsPanelProps) -> Self {
124
+ let column_name = props
125
+ .metadata
126
+ .locator_name_or_default(&props.selected_column);
127
+
128
+ let expr = props
129
+ .metadata
130
+ .get_expression_by_alias(&column_name)
131
+ .or_else(|| props.view_config.expressions.get(&column_name).cloned())
132
+ .unwrap_or_default();
133
+
134
+ let expr = Rc::new(expr);
135
+ let header = (*expr != column_name).then_some(column_name.clone());
136
+
137
+ let window = props
138
+ .selected_column
139
+ .name()
140
+ .and_then(|name| props.view_config.windows.get(name))
141
+ .cloned();
142
+
143
+ Self {
144
+ column_name,
145
+ expr,
146
+ header,
147
+ window,
148
+ }
115
149
  }
116
150
  }
117
151
 
@@ -172,22 +206,23 @@ impl Component for ColumnSettingsPanel {
172
206
  initial_window_value: None,
173
207
  window_value: None,
174
208
  tabs: vec![],
175
- on_input: Callback::default(),
176
- on_save: Callback::default(),
177
- on_validate: Callback::default(),
209
+ on_input: ctx.link().callback(ColumnSettingsPanelMsg::SetExprValue),
210
+ on_save: ctx
211
+ .link()
212
+ .callback(ColumnSettingsPanelMsg::OnSaveAttributes),
213
+ on_validate: ctx.link().callback(ColumnSettingsPanelMsg::SetExprValid),
178
214
  };
179
215
 
180
- this.initialize(ctx);
216
+ this.reset_to(ctx, Initials::of(ctx.props()));
181
217
  this
182
218
  }
183
219
 
184
220
  fn changed(&mut self, ctx: &yew::prelude::Context<Self>, old_props: &Self::Properties) -> bool {
185
- // Only reached when props are unequal. Re-`initialize` (which wipes
186
- // in-progress expression/window drafts) only on IDENTITY changes -
187
- // a trap-door `auto_width` change re-renders the `Sidebar` sizer
188
- // alone.
189
- if !ctx.props().identity_eq(old_props) {
190
- self.initialize(ctx);
221
+ let next = Initials::of(ctx.props());
222
+ if ctx.props().selected_column != old_props.selected_column || next != self.initials() {
223
+ self.reset_to(ctx, next);
224
+ } else {
225
+ self.refresh_derived(ctx);
191
226
  }
192
227
 
193
228
  true
@@ -353,9 +388,9 @@ impl Component for ColumnSettingsPanel {
353
388
  };
354
389
 
355
390
  let header_props = props!(EditableHeaderProps {
391
+ value: self.header_value.clone(),
356
392
  initial_value: self.initial_header_value.clone(),
357
393
  placeholder: header_placeholder,
358
- reset_count: self.reset_count,
359
394
  editable: (ctx.props().selected_column.is_expr()
360
395
  && matches!(
361
396
  ctx.props().selected_tab,
@@ -374,7 +409,6 @@ impl Component for ColumnSettingsPanel {
374
409
  ]
375
410
  }),
376
411
  metadata: ctx.props().metadata.clone(),
377
- session: &ctx.props().session
378
412
  });
379
413
 
380
414
  let expr_editor = props!(ExpressionEditorProps {
@@ -382,6 +416,7 @@ impl Component for ColumnSettingsPanel {
382
416
  on_save: self.on_save.clone(),
383
417
  on_validate: self.on_validate.clone(),
384
418
  alias: ctx.props().selected_column.name().cloned(),
419
+ initial_expr: self.initial_expr_value.clone(),
385
420
  disabled: !ctx.props().selected_column.is_expr(),
386
421
  reset_count: self.reset_count,
387
422
  metadata: ctx.props().metadata.clone(),
@@ -457,6 +492,7 @@ impl Component for ColumnSettingsPanel {
457
492
  presentation: ctx.props().presentation.clone(),
458
493
  renderer: ctx.props().renderer.clone(),
459
494
  session: ctx.props().session.clone(),
495
+ workspace: ctx.props().workspace.clone(),
460
496
  };
461
497
 
462
498
  let tab_children = self.tabs.iter().map(|tab| match tab {
@@ -506,38 +542,22 @@ impl ColumnSettingsPanel {
506
542
  self.save_enabled = changed && valid;
507
543
  }
508
544
 
509
- fn initialize(&mut self, ctx: &yew::prelude::Context<Self>) {
510
- let column_name = ctx
511
- .props()
512
- .metadata
513
- .locator_name_or_default(&ctx.props().selected_column);
514
-
515
- let initial_expr_value = ctx
516
- .props()
517
- .metadata
518
- .get_expression_by_alias(&column_name)
519
- .unwrap_or_default();
520
-
521
- let initial_expr_value = Rc::new(initial_expr_value);
522
- let initial_header_value =
523
- (*initial_expr_value != column_name).then_some(column_name.clone());
545
+ fn initials(&self) -> Initials {
546
+ Initials {
547
+ column_name: self.column_name.clone(),
548
+ expr: self.initial_expr_value.clone(),
549
+ header: self.initial_header_value.clone(),
550
+ window: self.initial_window_value.clone(),
551
+ }
552
+ }
524
553
 
525
- let maybe_ty = ctx
554
+ fn refresh_derived(&mut self, ctx: &yew::prelude::Context<Self>) {
555
+ self.maybe_ty = ctx
526
556
  .props()
527
557
  .metadata
528
558
  .locator_view_type(&ctx.props().selected_column);
529
559
 
530
- // Specs are unnamed (the `windows` map key names them, and the
531
- // editable header owns naming in the UI), so drafts and saved specs
532
- // compare directly.
533
- let initial_window_value = ctx
534
- .props()
535
- .selected_column
536
- .name()
537
- .and_then(|name| ctx.props().view_config.windows.get(name))
538
- .cloned();
539
-
540
- let tabs = {
560
+ self.tabs = {
541
561
  let mut tabs = vec![];
542
562
  let is_new_expr = ctx.props().selected_column.is_new_expr();
543
563
  let show_styles = !is_new_expr
@@ -571,28 +591,27 @@ impl ColumnSettingsPanel {
571
591
 
572
592
  tabs
573
593
  };
594
+ }
574
595
 
575
- let on_input = ctx.link().callback(ColumnSettingsPanelMsg::SetExprValue);
576
- let on_save = ctx
577
- .link()
578
- .callback(ColumnSettingsPanelMsg::OnSaveAttributes);
579
-
580
- let on_validate = ctx.link().callback(ColumnSettingsPanelMsg::SetExprValid);
581
- *self = Self {
596
+ fn reset_to(&mut self, ctx: &yew::prelude::Context<Self>, initials: Initials) {
597
+ let Initials {
582
598
  column_name,
583
- expr_value: initial_expr_value.clone(),
584
- initial_expr_value,
585
- header_value: initial_header_value.clone(),
586
- initial_header_value,
587
- maybe_ty,
588
- window_value: initial_window_value.clone(),
589
- initial_window_value,
590
- tabs,
591
- header_valid: true,
592
- on_input,
593
- on_save,
594
- on_validate,
595
- ..*self
596
- }
599
+ expr,
600
+ header,
601
+ window,
602
+ } = initials;
603
+
604
+ self.column_name = column_name;
605
+ self.expr_value = expr.clone();
606
+ self.initial_expr_value = expr;
607
+ self.header_value = header.clone();
608
+ self.initial_header_value = header;
609
+ self.window_value = window.clone();
610
+ self.initial_window_value = window;
611
+ self.header_valid = true;
612
+ self.save_enabled = false;
613
+ self.reset_enabled = false;
614
+ self.reset_count = self.reset_count.wrapping_add(1);
615
+ self.refresh_derived(ctx);
597
616
  }
598
617
  }
@@ -0,0 +1,91 @@
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 wasm_bindgen::JsCast;
14
+ use web_sys::{Event, MouseEvent};
15
+ use yew::{Callback, Html, Properties, function_component, html, use_callback, use_mut_ref};
16
+
17
+ #[derive(Properties, PartialEq)]
18
+ pub struct ControlGroupProps {
19
+ pub group_key: String,
20
+
21
+ pub open: bool,
22
+
23
+ pub on_toggle: Callback<(String, bool)>,
24
+
25
+ pub children: Html,
26
+ }
27
+
28
+ #[function_component]
29
+ pub fn ControlGroup(props: &ControlGroupProps) -> Html {
30
+ let ontoggle = use_callback(
31
+ (props.group_key.clone(), props.on_toggle.clone()),
32
+ |event: Event, (group_key, on_toggle)| {
33
+ if let Some(el) = event
34
+ .target()
35
+ .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
36
+ {
37
+ on_toggle.emit((group_key.clone(), el.has_attribute("open")));
38
+ }
39
+ },
40
+ );
41
+
42
+ let onclick = use_callback((), |event: MouseEvent, _| {
43
+ if !event.shift_key() {
44
+ return;
45
+ }
46
+
47
+ event.prevent_default();
48
+ let Some(details) = event
49
+ .target()
50
+ .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
51
+ .and_then(|x| x.closest("details.control-group").ok().flatten())
52
+ else {
53
+ return;
54
+ };
55
+
56
+ let Some(scope) = details.closest(".tab-section").ok().flatten() else {
57
+ return;
58
+ };
59
+
60
+ let expand = !details.has_attribute("open");
61
+ let Ok(groups) = scope.query_selector_all("details.control-group") else {
62
+ return;
63
+ };
64
+
65
+ for i in 0..groups.length() {
66
+ let Some(el) = groups
67
+ .item(i)
68
+ .and_then(|x| x.dyn_into::<web_sys::Element>().ok())
69
+ else {
70
+ continue;
71
+ };
72
+
73
+ if expand {
74
+ let _ = el.set_attribute("open", "");
75
+ } else {
76
+ let _ = el.remove_attribute("open");
77
+ }
78
+ }
79
+ });
80
+
81
+ let open = *use_mut_ref(|| props.open).borrow();
82
+ html! {
83
+ <details class="control-group" {open} {ontoggle}>
84
+ <summary {onclick}>
85
+ <span class="control-group-chevron shift-alt-icon" />
86
+ <label id={format!("{}-group-label", props.group_key)} />
87
+ </summary>
88
+ { props.children.clone() }
89
+ </details>
90
+ }
91
+ }
@@ -13,6 +13,7 @@
13
13
  //! `containers` are generic container-like components for collections of other
14
14
  //! `Component` types.
15
15
 
16
+ pub mod control_group;
16
17
  pub mod dragdrop_list;
17
18
  pub mod dropdown_menu;
18
19
  pub mod scroll_panel;
@@ -230,9 +230,15 @@ where
230
230
  <label>
231
231
  { ctx.props().label.as_ref().map(|x| x.to_string()).unwrap_or_default() }
232
232
  </label>
233
- <div class={wrapper_class} data-value={value}>{ select }</div>
233
+ <div class={wrapper_class} data-value={value}>
234
+ { select }
235
+ <span class="select-arrow" />
236
+ </div>
234
237
  } else {
235
- <div class={wrapper_class} data-value={value}>{ select }</div>
238
+ <div class={wrapper_class} data-value={value}>
239
+ { select }
240
+ <span class="select-arrow" />
241
+ </div>
236
242
  }
237
243
  }
238
244
  }
@@ -22,6 +22,13 @@ pub struct DatetimeStyleSimpleProps {
22
22
  pub enable_time_config: bool,
23
23
  pub config: SimpleDatetimeStyleConfig,
24
24
 
25
+ /// The plugin-declared default preset for each style select.
26
+ #[prop_or(SimpleDatetimeFormat::Short)]
27
+ pub date_style_default: SimpleDatetimeFormat,
28
+
29
+ #[prop_or(SimpleDatetimeFormat::Medium)]
30
+ pub time_style_default: SimpleDatetimeFormat,
31
+
25
32
  #[prop_or_default]
26
33
  pub on_change: Callback<SimpleDatetimeStyleConfig>,
27
34
 
@@ -37,7 +44,10 @@ impl ModalLink<DatetimeStyleSimple> for DatetimeStyleSimpleProps {
37
44
 
38
45
  impl PartialEq for DatetimeStyleSimpleProps {
39
46
  fn eq(&self, other: &Self) -> bool {
40
- self.enable_time_config == other.enable_time_config && self.config == other.config
47
+ self.enable_time_config == other.enable_time_config
48
+ && self.config == other.config
49
+ && self.date_style_default == other.date_style_default
50
+ && self.time_style_default == other.time_style_default
41
51
  }
42
52
  }
43
53
 
@@ -69,12 +79,12 @@ impl Component for DatetimeStyleSimple {
69
79
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
70
80
  match msg {
71
81
  DatetimeStyleSimpleMsg::DateStyleChanged(format) => {
72
- self.config.date_style = format.unwrap_or_default();
82
+ self.config.date_style = format.unwrap_or(ctx.props().date_style_default);
73
83
  self.dispatch_config(ctx);
74
84
  true
75
85
  },
76
86
  DatetimeStyleSimpleMsg::TimeStyleChanged(format) => {
77
- self.config.time_style = format.unwrap_or_default();
87
+ self.config.time_style = format.unwrap_or(ctx.props().time_style_default);
78
88
  self.dispatch_config(ctx);
79
89
  true
80
90
  },
@@ -99,13 +109,14 @@ impl Component for DatetimeStyleSimple {
99
109
  label="date-style"
100
110
  on_change={ctx.link().callback(DatetimeStyleSimpleMsg::DateStyleChanged)}
101
111
  current_value={self.config.date_style}
112
+ default_value={ctx.props().date_style_default}
102
113
  />
103
114
  if ctx.props().enable_time_config {
104
115
  <SelectEnumField<SimpleDatetimeFormat>
105
116
  label="time-style"
106
117
  on_change={ctx.link().callback(DatetimeStyleSimpleMsg::TimeStyleChanged)}
107
118
  current_value={self.config.time_style}
108
- default_value={SimpleDatetimeFormat::Medium}
119
+ default_value={ctx.props().time_style_default}
109
120
  />
110
121
  }
111
122
  </>
@@ -38,6 +38,9 @@ pub struct DatetimeColumnStyleProps {
38
38
  pub enable_time_config: bool,
39
39
  pub config: Option<DatetimeColumnStyleConfig>,
40
40
 
41
+ /// The active plugin's declared default `date_format`.
42
+ pub default_format: Rc<DatetimeFormatType>,
43
+
41
44
  #[prop_or_default]
42
45
  pub on_change: Callback<ColumnConfigFieldUpdate>,
43
46
 
@@ -57,7 +60,9 @@ impl ModalLink<DatetimeColumnStyle> for DatetimeColumnStyleProps {
57
60
 
58
61
  impl PartialEq for DatetimeColumnStyleProps {
59
62
  fn eq(&self, other: &Self) -> bool {
60
- self.enable_time_config == other.enable_time_config && self.config == other.config
63
+ self.enable_time_config == other.enable_time_config
64
+ && self.config == other.config
65
+ && self.default_format == other.default_format
61
66
  }
62
67
  }
63
68
 
@@ -90,7 +95,9 @@ impl Component for DatetimeColumnStyle {
90
95
  std::mem::swap(&mut self.config, &mut new_config);
91
96
  rerender = true;
92
97
  }
93
- if old.enable_time_config != ctx.props().enable_time_config {
98
+ if old.enable_time_config != ctx.props().enable_time_config
99
+ || old.default_format != ctx.props().default_format
100
+ {
94
101
  rerender = true;
95
102
  }
96
103
  rerender
@@ -99,22 +106,24 @@ impl Component for DatetimeColumnStyle {
99
106
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
100
107
  match msg {
101
108
  DatetimeColumnStyleMsg::TimezoneChanged(val) => {
109
+ let mut format = self.effective_format(ctx);
102
110
  if Some(&*USER_TIMEZONE) != val.as_ref() {
103
- *self.config.date_format.time_zone_mut() = val;
111
+ *format.time_zone_mut() = val;
104
112
  } else {
105
- *self.config.date_format.time_zone_mut() = None;
113
+ *format.time_zone_mut() = None;
106
114
  }
107
115
 
116
+ self.set_format(ctx, format);
108
117
  self.dispatch_config(ctx);
109
118
  true
110
119
  },
111
120
  DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(simple) => {
112
- self.config.date_format = DatetimeFormatType::Simple(simple);
121
+ self.set_format(ctx, DatetimeFormatType::Simple(simple));
113
122
  self.dispatch_config(ctx);
114
123
  true
115
124
  },
116
125
  DatetimeColumnStyleMsg::CustomDatetimeStyleConfigChanged(custom) => {
117
- self.config.date_format = DatetimeFormatType::Custom(custom);
126
+ self.set_format(ctx, DatetimeFormatType::Custom(custom));
118
127
  self.dispatch_config(ctx);
119
128
  true
120
129
  },
@@ -122,6 +131,24 @@ impl Component for DatetimeColumnStyle {
122
131
  }
123
132
 
124
133
  fn view(&self, ctx: &Context<Self>) -> Html {
134
+ let format = self.effective_format(ctx);
135
+ let (date_style_default, time_style_default) = match &*ctx.props().default_format {
136
+ DatetimeFormatType::Simple(simple) => (simple.date_style, simple.time_style),
137
+ DatetimeFormatType::Custom(_) => {
138
+ (SimpleDatetimeFormat::Short, SimpleDatetimeFormat::Medium)
139
+ },
140
+ };
141
+
142
+ let simple_reset = {
143
+ let default_format = ctx.props().default_format.clone();
144
+ ctx.link().callback(move |_| {
145
+ DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(match &*default_format {
146
+ DatetimeFormatType::Simple(simple) => simple.clone(),
147
+ DatetimeFormatType::Custom(_) => SimpleDatetimeStyleConfig::default(),
148
+ })
149
+ })
150
+ };
151
+
125
152
  html! {
126
153
  <>
127
154
  <div id="column-style-container" class="datetime-column-style-container">
@@ -131,10 +158,10 @@ impl Component for DatetimeColumnStyle {
131
158
  values={ALL_TIMEZONES.with(|x| (*x).clone())}
132
159
  default_value={(*USER_TIMEZONE).clone()}
133
160
  on_change={ctx.link().callback(DatetimeColumnStyleMsg::TimezoneChanged)}
134
- current_value={self.config.date_format.time_zone().as_ref().unwrap_or(&*USER_TIMEZONE).clone()}
161
+ current_value={format.time_zone().as_ref().unwrap_or(&*USER_TIMEZONE).clone()}
135
162
  />
136
163
  }
137
- if let DatetimeFormatType::Simple(config) = &self.config.date_format {
164
+ if let DatetimeFormatType::Simple(config) = &format {
138
165
  if ctx.props().enable_time_config {
139
166
  <div class="row">
140
167
  <button
@@ -149,15 +176,17 @@ impl Component for DatetimeColumnStyle {
149
176
  enable_time_config={ctx.props().enable_time_config}
150
177
  on_change={ctx.link().callback(DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged)}
151
178
  config={config.clone()}
179
+ {date_style_default}
180
+ {time_style_default}
152
181
  />
153
- } else if let DatetimeFormatType::Custom(config) = &self.config.date_format {
182
+ } else if let DatetimeFormatType::Custom(config) = &format {
154
183
  if ctx.props().enable_time_config {
155
184
  <div class="row">
156
185
  <button
157
186
  id="datetime_format"
158
187
  data-title="Custom"
159
188
  data-title-hover="Switch to Simple"
160
- onclick={ctx.link().callback(|_| DatetimeColumnStyleMsg::SimpleDatetimeStyleConfigChanged(SimpleDatetimeStyleConfig::default()))}
189
+ onclick={simple_reset}
161
190
  />
162
191
  </div>
163
192
  }
@@ -201,6 +230,17 @@ static USER_TIMEZONE: LazyLock<String> = LazyLock::new(|| {
201
230
  });
202
231
 
203
232
  impl DatetimeColumnStyle {
233
+ fn effective_format(&self, ctx: &Context<Self>) -> DatetimeFormatType {
234
+ self.config
235
+ .date_format
236
+ .clone()
237
+ .unwrap_or_else(|| (*ctx.props().default_format).clone())
238
+ }
239
+
240
+ fn set_format(&mut self, ctx: &Context<Self>, format: DatetimeFormatType) {
241
+ self.config.date_format = (format != *ctx.props().default_format).then_some(format);
242
+ }
243
+
204
244
  /// When this config has changed, we must signal the wrapper element.
205
245
  fn dispatch_config(&self, ctx: &Context<Self>) {
206
246
  let value = if self.config == DatetimeColumnStyleConfig::default() {