@perspective-dev/viewer 5.2.0 → 5.3.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 (133) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/monokai.css +1 -1
  9. package/dist/css/phosphor.css +1 -1
  10. package/dist/css/pro-dark.css +1 -1
  11. package/dist/css/pro.css +1 -1
  12. package/dist/css/solarized-dark.css +1 -1
  13. package/dist/css/solarized.css +1 -1
  14. package/dist/css/themes.css +1 -1
  15. package/dist/css/vaporwave.css +1 -1
  16. package/dist/docs/perspective-docs.json +1 -1
  17. package/dist/esm/perspective-viewer.d.ts +1 -0
  18. package/dist/esm/perspective-viewer.inline.js +9 -9
  19. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  20. package/dist/esm/perspective-viewer.js +4 -4
  21. package/dist/esm/perspective-viewer.js.map +2 -2
  22. package/dist/esm/plugin.d.ts +10 -0
  23. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
  24. package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
  25. package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
  26. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  27. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
  28. package/dist/wasm/perspective-viewer.d.ts +43 -36
  29. package/dist/wasm/perspective-viewer.js +194 -136
  30. package/dist/wasm/perspective-viewer.wasm +0 -0
  31. package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
  32. package/package.json +1 -1
  33. package/src/css/column-settings-panel.css +9 -5
  34. package/src/css/column-style.css +209 -31
  35. package/src/css/dom/select.css +2 -1
  36. package/src/css/form/code-editor.css +4 -4
  37. package/src/rust/agent/tools.rs +48 -17
  38. package/src/rust/components/column_selector/active_column.rs +2 -2
  39. package/src/rust/components/column_selector/add_expression_button.rs +3 -3
  40. package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
  41. package/src/rust/components/column_selector/config_selector.rs +8 -0
  42. package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
  43. package/src/rust/components/column_selector/inactive_column.rs +2 -2
  44. package/src/rust/components/column_selector.rs +48 -4
  45. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
  46. package/src/rust/components/column_settings_sidebar/style_tab.rs +262 -225
  47. package/src/rust/components/column_settings_sidebar.rs +88 -69
  48. package/src/rust/components/editable_header.rs +85 -144
  49. package/src/rust/components/expression_editor.rs +9 -16
  50. package/src/rust/components/form/code_editor.rs +16 -6
  51. package/src/rust/components/form/debug.rs +1 -0
  52. package/src/rust/components/form/mod.rs +3 -1
  53. package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
  54. package/src/rust/components/form/named_value_picker.rs +86 -0
  55. package/src/rust/components/form/palette_selector.rs +440 -0
  56. package/src/rust/components/main_panel/msg.rs +2 -2
  57. package/src/rust/components/main_panel/reconcile.rs +4 -4
  58. package/src/rust/components/plugin_tab.rs +4 -28
  59. package/src/rust/components/settings_panel.rs +25 -20
  60. package/src/rust/components/viewer/msg.rs +46 -7
  61. package/src/rust/components/viewer/panels.rs +11 -9
  62. package/src/rust/components/viewer/render.rs +8 -3
  63. package/src/rust/components/viewer/settings.rs +163 -23
  64. package/src/rust/components/viewer/snapshots.rs +46 -21
  65. package/src/rust/components/viewer/wiring.rs +14 -25
  66. package/src/rust/components/viewer.rs +12 -8
  67. package/src/rust/config/column_config_schema.rs +100 -41
  68. package/src/rust/config/css_scale.rs +882 -0
  69. package/src/rust/config/mod.rs +2 -0
  70. package/src/rust/config/options.rs +10 -0
  71. package/src/rust/config/viewer_config.rs +49 -55
  72. package/src/rust/config/workspace_config.rs +132 -5
  73. package/src/rust/custom_elements/viewer.rs +214 -155
  74. package/src/rust/custom_events.rs +14 -1
  75. package/src/rust/js/regular_layout.rs +3 -3
  76. package/src/rust/lib.rs +2 -0
  77. package/src/rust/presentation/column_locator.rs +19 -5
  78. package/src/rust/presentation.rs +192 -36
  79. package/src/rust/queries/column_locator.rs +74 -44
  80. package/src/rust/queries/get_viewer_config.rs +0 -3
  81. package/src/rust/queries/mod.rs +2 -0
  82. package/src/rust/queries/palette_set.rs +82 -0
  83. package/src/rust/queries/plugin_column_styles.rs +6 -2
  84. package/src/rust/renderer/dispatch.rs +16 -4
  85. package/src/rust/renderer/plugin_config.rs +309 -47
  86. package/src/rust/renderer.rs +15 -42
  87. package/src/rust/session/column_defaults_update.rs +15 -8
  88. package/src/rust/session/metadata.rs +5 -1
  89. package/src/rust/session/props.rs +1 -0
  90. package/src/rust/session.rs +9 -6
  91. package/src/rust/tasks/create_panel.rs +44 -57
  92. package/src/rust/tasks/edit_expression.rs +19 -23
  93. package/src/rust/tasks/edit_window.rs +8 -10
  94. package/src/rust/tasks/eject.rs +4 -1
  95. package/src/rust/tasks/pipeline.rs +2 -2
  96. package/src/rust/tasks/presize_panels.rs +44 -5
  97. package/src/rust/tasks/reset_all.rs +4 -4
  98. package/src/rust/tasks/restore_and_render.rs +17 -44
  99. package/src/rust/tasks/restore_panel.rs +11 -13
  100. package/src/rust/tasks/set_edit_mode.rs +3 -1
  101. package/src/rust/tasks/sync_update_panels.rs +11 -1
  102. package/src/rust/tasks/update_theme.rs +23 -18
  103. package/src/rust/utils/css_vars.rs +26 -0
  104. package/src/rust/utils/mod.rs +2 -0
  105. package/src/rust/utils/spawn.rs +1 -1
  106. package/src/rust/workspace.rs +435 -272
  107. package/src/svg/lock.svg +7 -0
  108. package/src/themes/botanical.css +10 -0
  109. package/src/themes/defaults.css +12 -0
  110. package/src/themes/dracula.css +17 -0
  111. package/src/themes/gruvbox-dark.css +20 -0
  112. package/src/themes/gruvbox.css +10 -0
  113. package/src/themes/icons.css +1 -0
  114. package/src/themes/monokai.css +22 -0
  115. package/src/themes/phosphor.css +5 -0
  116. package/src/themes/pro-dark.css +10 -0
  117. package/src/themes/solarized-dark.css +7 -0
  118. package/src/themes/solarized.css +7 -0
  119. package/src/themes/vaporwave.css +20 -0
  120. package/src/ts/perspective-viewer.ts +1 -0
  121. package/src/ts/plugin.ts +14 -0
  122. package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
  123. package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
  124. package/src/ts/ts-rs/ViewerConfig.ts +1 -1
  125. package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
  126. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
  127. package/src/rust/components/form/color_range_selector.rs +0 -137
  128. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
  129. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
  130. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
  131. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
  132. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
  133. /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
@@ -15,6 +15,7 @@ pub(crate) mod primitive_field;
15
15
  mod symbol;
16
16
 
17
17
  use std::collections::HashMap;
18
+ use std::rc::Rc;
18
19
 
19
20
  use itertools::Itertools;
20
21
  use perspective_client::config::ColumnType;
@@ -22,7 +23,7 @@ use yew::{Html, Properties, function_component, html};
22
23
 
23
24
  use self::agg_depth_selector::*;
24
25
  use self::primitive_field::{
25
- BoolField, ColorField, ColorRangeField, EnumField, NumberFieldPrimitive,
26
+ BoolField, ColorField, EnumField, GradientStopsField, NumberFieldPrimitive, PaletteField,
26
27
  };
27
28
  use crate::components::column_settings_sidebar::style_tab::symbol::SymbolStyle;
28
29
  use crate::components::datetime_column_style::DatetimeColumnStyle;
@@ -30,15 +31,16 @@ use crate::components::number_series_style::NumberSeriesStyle;
30
31
  use crate::components::string_column_style::StringColumnStyle;
31
32
  use crate::components::style_controls::CustomNumberFormat;
32
33
  use crate::config::{
33
- ControlSpec, CustomNumberFormatConfig, DatetimeColumnStyleConfig, NumberSeriesStyleConfig,
34
- StringColumnStyleConfig,
34
+ ControlSpec, CssKind, CustomNumberFormatConfig, DatetimeColumnStyleConfig,
35
+ NumberSeriesStyleConfig, StringColumnStyleConfig,
35
36
  };
36
37
  use crate::presentation::Presentation;
37
- use crate::queries::{fetch_column_abs_max, get_column_config_schema};
38
+ use crate::queries::{fetch_column_abs_max, get_column_config_schema, named_values};
38
39
  use crate::renderer::Renderer;
39
40
  use crate::session::Session;
40
41
  use crate::tasks::send_column_config;
41
42
  use crate::utils::PtrEqRc;
43
+ use crate::workspace::Workspace;
42
44
 
43
45
  #[derive(Clone, PartialEq, Properties)]
44
46
  pub struct StyleTabProps {
@@ -62,6 +64,7 @@ pub struct StyleTabProps {
62
64
  pub presentation: Presentation,
63
65
  pub renderer: Renderer,
64
66
  pub session: Session,
67
+ pub workspace: Workspace,
65
68
  }
66
69
 
67
70
  #[function_component]
@@ -93,6 +96,33 @@ pub fn StyleTab(props: &StyleTabProps) -> Html {
93
96
  });
94
97
 
95
98
  let raw_config = props.renderer.get_columns_config(&props.column_name);
99
+
100
+ let named_for = |kind| Rc::new(named_values(&props.workspace, &props.presentation, kind));
101
+ let named_gradients = named_for(CssKind::Gradient);
102
+ let named_palettes = named_for(CssKind::Palette);
103
+ let named_colors = named_for(CssKind::Color);
104
+
105
+ let restored = Rc::new(props.presentation.palette());
106
+ let on_pin = {
107
+ let presentation = props.presentation.clone();
108
+ let revision = revision.clone();
109
+ yew::Callback::from(move |(kind, literal): (CssKind, String)| {
110
+ if let Err(error) = presentation.pin_style(kind, &literal) {
111
+ tracing::warn!("Failed to pin named {kind:?}: {error:?}");
112
+ }
113
+
114
+ revision.set(*revision + 1);
115
+ })
116
+ };
117
+ let schema = get_column_config_schema(
118
+ &props.renderer,
119
+ &props.view_config,
120
+ &props.metadata,
121
+ &props.column_name,
122
+ raw_config.as_ref(),
123
+ abs_max,
124
+ );
125
+
96
126
  let on_change = {
97
127
  let state = props.clone();
98
128
  let column_name = props.column_name.clone();
@@ -110,231 +140,238 @@ pub fn StyleTab(props: &StyleTabProps) -> Html {
110
140
  .and_then(|m| serde_json::from_value::<T>(serde_json::Value::Object(m.clone())).ok())
111
141
  }
112
142
 
113
- let components = get_column_config_schema(
114
- &props.renderer,
115
- &props.view_config,
116
- &props.metadata,
117
- &props.column_name,
118
- raw_config.as_ref(),
119
- abs_max,
120
- )
121
- .map(|schema| {
122
- schema
123
- .fields
124
- .into_iter()
125
- .filter_map(|spec| {
126
- let keys: Vec<String> = spec
127
- .serialized_keys()
128
- .into_iter()
129
- .map(|s| s.to_string())
130
- .collect();
131
- let component = match spec {
132
- ControlSpec::AggregateDepth => {
133
- let aggregate_depth = raw_config
134
- .as_ref()
135
- .and_then(|m| m.get("aggregate_depth"))
136
- .and_then(|v| v.as_u64())
137
- .unwrap_or(0) as u32;
138
- html! {
139
- <AggregateDepthSelector
140
- group_by_depth={props.group_by_depth}
141
- on_change={on_change.clone()}
142
- column_name={props.column_name.to_owned()}
143
- value={aggregate_depth}
144
- keys={keys.clone()}
145
- />
146
- }
147
- },
148
- ControlSpec::NumberSeriesStyle {
149
- default: default_config,
150
- } => {
151
- let config: Option<NumberSeriesStyleConfig> = deser_sub(&raw_config);
152
- html! {
153
- <NumberSeriesStyle
154
- {config}
155
- {default_config}
156
- on_change={on_change.clone()}
157
- keys={keys.clone()}
158
- />
159
- }
160
- },
161
- ControlSpec::DatetimeFormat => {
162
- let config: Option<DatetimeColumnStyleConfig> = deser_sub(&raw_config);
163
- let enable_time_config = props.ty.unwrap() == ColumnType::Datetime;
164
- html! {
165
- <DatetimeColumnStyle
166
- {enable_time_config}
167
- {config}
168
- on_change={on_change.clone()}
169
- keys={keys.clone()}
170
- />
171
- }
172
- },
173
- ControlSpec::StringFormat => {
174
- let config: Option<StringColumnStyleConfig> = deser_sub(&raw_config);
175
- html! {
176
- <StringColumnStyle
177
- {config}
178
- on_change={on_change.clone()}
179
- keys={keys.clone()}
180
- />
181
- }
182
- },
183
- ControlSpec::Symbols {
184
- default: default_config,
185
- } => {
186
- let restored_config: HashMap<String, String> = raw_config
187
- .as_ref()
188
- .and_then(|m| m.get("symbols"))
189
- .and_then(|v| serde_json::from_value(v.clone()).ok())
190
- .unwrap_or_default();
143
+ let components = schema
144
+ .map(|schema| {
145
+ schema
146
+ .fields
147
+ .into_iter()
148
+ .filter_map(|spec| {
149
+ let keys: Vec<String> = spec
150
+ .serialized_keys()
151
+ .into_iter()
152
+ .map(|s| s.to_string())
153
+ .collect();
154
+ let component = match spec {
155
+ ControlSpec::AggregateDepth => {
156
+ let aggregate_depth = raw_config
157
+ .as_ref()
158
+ .and_then(|m| m.get("aggregate_depth"))
159
+ .and_then(|v| v.as_u64())
160
+ .unwrap_or(0)
161
+ as u32;
162
+ html! {
163
+ <AggregateDepthSelector
164
+ group_by_depth={props.group_by_depth}
165
+ on_change={on_change.clone()}
166
+ column_name={props.column_name.to_owned()}
167
+ value={aggregate_depth}
168
+ keys={keys.clone()}
169
+ />
170
+ }
171
+ },
172
+ ControlSpec::NumberSeriesStyle {
173
+ default: default_config,
174
+ } => {
175
+ let config: Option<NumberSeriesStyleConfig> = deser_sub(&raw_config);
176
+ html! {
177
+ <NumberSeriesStyle
178
+ {config}
179
+ {default_config}
180
+ on_change={on_change.clone()}
181
+ keys={keys.clone()}
182
+ />
183
+ }
184
+ },
185
+ ControlSpec::DatetimeFormat => {
186
+ let config: Option<DatetimeColumnStyleConfig> = deser_sub(&raw_config);
187
+ let enable_time_config = props.ty.unwrap() == ColumnType::Datetime;
188
+ html! {
189
+ <DatetimeColumnStyle
190
+ {enable_time_config}
191
+ {config}
192
+ on_change={on_change.clone()}
193
+ keys={keys.clone()}
194
+ />
195
+ }
196
+ },
197
+ ControlSpec::StringFormat => {
198
+ let config: Option<StringColumnStyleConfig> = deser_sub(&raw_config);
199
+ html! {
200
+ <StringColumnStyle
201
+ {config}
202
+ on_change={on_change.clone()}
203
+ keys={keys.clone()}
204
+ />
205
+ }
206
+ },
207
+ ControlSpec::Symbols {
208
+ default: default_config,
209
+ } => {
210
+ let restored_config: HashMap<String, String> = raw_config
211
+ .as_ref()
212
+ .and_then(|m| m.get("symbols"))
213
+ .and_then(|v| serde_json::from_value(v.clone()).ok())
214
+ .unwrap_or_default();
191
215
 
192
- html! {
193
- <SymbolStyle
194
- {default_config}
195
- restored_config={Some(restored_config)}
196
- on_change={on_change.clone()}
197
- column_name={props.column_name.clone()}
198
- selected_theme={props.selected_theme.clone()}
199
- session={props.session.clone()}
200
- keys={keys.clone()}
201
- />
202
- }
203
- },
204
- ControlSpec::NumberFormat => {
205
- let restored_config: CustomNumberFormatConfig = raw_config
206
- .as_ref()
207
- .and_then(|m| m.get("number_format"))
208
- .and_then(|v| serde_json::from_value(v.clone()).ok())
209
- .unwrap_or_default();
216
+ html! {
217
+ <SymbolStyle
218
+ {default_config}
219
+ restored_config={Some(restored_config)}
220
+ on_change={on_change.clone()}
221
+ column_name={props.column_name.clone()}
222
+ selected_theme={props.selected_theme.clone()}
223
+ session={props.session.clone()}
224
+ keys={keys.clone()}
225
+ />
226
+ }
227
+ },
228
+ ControlSpec::NumberFormat => {
229
+ let restored_config: CustomNumberFormatConfig = raw_config
230
+ .as_ref()
231
+ .and_then(|m| m.get("number_format"))
232
+ .and_then(|v| serde_json::from_value(v.clone()).ok())
233
+ .unwrap_or_default();
210
234
 
211
- html! {
212
- <CustomNumberFormat
213
- {restored_config}
214
- on_change={on_change.clone()}
215
- view_type={props.ty.unwrap()}
216
- column_name={props.column_name.clone()}
217
- keys={keys.clone()}
218
- />
219
- }
220
- },
221
- ControlSpec::Enum {
222
- key,
223
- variants,
224
- default,
225
- } => {
226
- let current = raw_config
227
- .as_ref()
228
- .and_then(|m| m.get(&key))
229
- .and_then(|v| v.as_str().map(|s| s.to_string()));
235
+ html! {
236
+ <CustomNumberFormat
237
+ {restored_config}
238
+ on_change={on_change.clone()}
239
+ view_type={props.ty.unwrap()}
240
+ column_name={props.column_name.clone()}
241
+ keys={keys.clone()}
242
+ />
243
+ }
244
+ },
245
+ ControlSpec::Enum {
246
+ key,
247
+ variants,
248
+ default,
249
+ } => {
250
+ let current = raw_config
251
+ .as_ref()
252
+ .and_then(|m| m.get(&key))
253
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
230
254
 
231
- html! {
232
- <EnumField
233
- field_key={key}
234
- {variants}
235
- {default}
236
- {current}
237
- on_change={on_change.clone()}
238
- />
239
- }
240
- },
241
- ControlSpec::Bool { key, default } => {
242
- let current = raw_config
243
- .as_ref()
244
- .and_then(|m| m.get(&key))
245
- .and_then(|v| v.as_bool());
246
- html! {
247
- <BoolField
248
- field_key={key}
249
- {default}
250
- {current}
251
- on_change={on_change.clone()}
252
- />
253
- }
254
- },
255
- ControlSpec::Color { key, default } => {
256
- let current = raw_config
257
- .as_ref()
258
- .and_then(|m| m.get(&key))
259
- .and_then(|v| v.as_str().map(|s| s.to_string()));
260
- html! {
261
- <ColorField
262
- field_key={key}
263
- {default}
264
- {current}
265
- on_change={on_change.clone()}
266
- />
267
- }
268
- },
269
- ControlSpec::ColorRange {
270
- key_pos,
271
- key_neg,
272
- default_pos,
273
- default_neg,
274
- is_gradient,
275
- } => {
276
- let current_pos = raw_config
277
- .as_ref()
278
- .and_then(|m| m.get(&key_pos))
279
- .and_then(|v| v.as_str().map(|s| s.to_string()));
280
- let current_neg = raw_config
281
- .as_ref()
282
- .and_then(|m| m.get(&key_neg))
283
- .and_then(|v| v.as_str().map(|s| s.to_string()));
284
- html! {
285
- <ColorRangeField
286
- field_key_pos={key_pos}
287
- field_key_neg={key_neg}
288
- {default_pos}
289
- {default_neg}
290
- {current_pos}
291
- {current_neg}
292
- {is_gradient}
293
- on_change={on_change.clone()}
294
- />
295
- }
296
- },
297
- ControlSpec::Number {
298
- key,
299
- default,
300
- min,
301
- max,
302
- step,
303
- include,
304
- } => {
305
- let current = raw_config
306
- .as_ref()
307
- .and_then(|m| m.get(&key))
308
- .and_then(|v| v.as_f64());
309
- html! {
310
- <NumberFieldPrimitive
311
- field_key={key}
312
- {default}
313
- {current}
314
- {min}
315
- {max}
316
- {step}
317
- {include}
318
- on_change={on_change.clone()}
319
- />
320
- }
321
- },
322
- // String primitive has no caller yet — wire when a
323
- // plugin emits one.
324
- ControlSpec::String { .. } => {
325
- return None;
326
- },
327
- };
255
+ html! {
256
+ <EnumField
257
+ field_key={key}
258
+ {variants}
259
+ {default}
260
+ {current}
261
+ on_change={on_change.clone()}
262
+ />
263
+ }
264
+ },
265
+ ControlSpec::Bool { key, default } => {
266
+ let current = raw_config
267
+ .as_ref()
268
+ .and_then(|m| m.get(&key))
269
+ .and_then(|v| v.as_bool());
270
+ html! {
271
+ <BoolField
272
+ field_key={key}
273
+ {default}
274
+ {current}
275
+ on_change={on_change.clone()}
276
+ />
277
+ }
278
+ },
279
+ ControlSpec::Color { key, default } => {
280
+ let current = raw_config
281
+ .as_ref()
282
+ .and_then(|m| m.get(&key))
283
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
284
+ html! {
285
+ <ColorField
286
+ field_key={key}
287
+ {default}
288
+ {current}
289
+ named={named_colors.clone()}
290
+ restored={restored.clone()}
291
+ on_pin={on_pin.clone()}
292
+ on_change={on_change.clone()}
293
+ />
294
+ }
295
+ },
296
+ ControlSpec::Palette { key, default, max } => {
297
+ let current = raw_config
298
+ .as_ref()
299
+ .and_then(|m| m.get(&key))
300
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
301
+ html! {
302
+ <PaletteField
303
+ field_key={key}
304
+ {default}
305
+ {max}
306
+ {current}
307
+ named={named_palettes.clone()}
308
+ restored={restored.clone()}
309
+ on_pin={on_pin.clone()}
310
+ on_change={on_change.clone()}
311
+ />
312
+ }
313
+ },
314
+ ControlSpec::GradientStops {
315
+ key,
316
+ default,
317
+ discrete,
318
+ } => {
319
+ let current = raw_config
320
+ .as_ref()
321
+ .and_then(|m| m.get(&key))
322
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
323
+ html! {
324
+ <GradientStopsField
325
+ field_key={key}
326
+ {default}
327
+ {discrete}
328
+ {current}
329
+ named={named_gradients.clone()}
330
+ restored={restored.clone()}
331
+ on_pin={on_pin.clone()}
332
+ on_change={on_change.clone()}
333
+ />
334
+ }
335
+ },
336
+ ControlSpec::Number {
337
+ key,
338
+ default,
339
+ min,
340
+ max,
341
+ step,
342
+ include,
343
+ } => {
344
+ let current = raw_config
345
+ .as_ref()
346
+ .and_then(|m| m.get(&key))
347
+ .and_then(|v| v.as_f64());
348
+ html! {
349
+ <NumberFieldPrimitive
350
+ field_key={key}
351
+ {default}
352
+ {current}
353
+ {min}
354
+ {max}
355
+ {step}
356
+ {include}
357
+ on_change={on_change.clone()}
358
+ />
359
+ }
360
+ },
361
+ ControlSpec::String { .. } => {
362
+ return None;
363
+ },
364
+ };
328
365
 
329
- let key = format!("{}::{}", props.column_name, keys.join("+"));
330
- Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
331
- })
332
- .collect_vec()
333
- })
334
- .unwrap_or_else(|error| {
335
- tracing::error!("{}", error);
336
- vec![]
337
- });
366
+ let key = format!("{}::{}", props.column_name, keys.join("+"));
367
+ Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
368
+ })
369
+ .collect_vec()
370
+ })
371
+ .unwrap_or_else(|error| {
372
+ tracing::error!("{}", error);
373
+ vec![]
374
+ });
338
375
 
339
376
  html! {
340
377
  <div id="style-tab">