@perspective-dev/viewer 4.5.1 → 4.5.2

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 (83) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -1
  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/monokai.css +1 -1
  7. package/dist/css/phosphor.css +1 -1
  8. package/dist/css/pro-dark.css +1 -1
  9. package/dist/css/themes.css +1 -1
  10. package/dist/css/vaporwave.css +1 -1
  11. package/dist/esm/perspective-viewer.cdn.d.ts +3 -0
  12. package/dist/esm/perspective-viewer.inline.d.ts +3 -0
  13. package/dist/esm/perspective-viewer.inline.js +1 -1
  14. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  15. package/dist/esm/perspective-viewer.js +1 -1
  16. package/dist/esm/perspective-viewer.js.map +2 -2
  17. package/dist/esm/ts-rs/OnUpdateData.d.ts +6 -0
  18. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
  19. package/dist/wasm/perspective-viewer.d.ts +15 -15
  20. package/dist/wasm/perspective-viewer.js +69 -69
  21. package/dist/wasm/perspective-viewer.wasm +0 -0
  22. package/dist/wasm/perspective-viewer.wasm.d.ts +15 -15
  23. package/package.json +1 -1
  24. package/src/{rust/components/style/local_style.rs → css/_column_dropdown.css} +14 -35
  25. package/src/css/_dropdown_menu.css +14 -0
  26. package/src/css/_filter_dropdown.css +14 -0
  27. package/src/css/_function_dropdown.css +14 -0
  28. package/src/css/_viewer_bundle.css +41 -0
  29. package/src/css/viewer.css +2 -2
  30. package/src/rust/components/column_dropdown.rs +3 -3
  31. package/src/rust/components/column_selector/aggregate_selector.rs +0 -3
  32. package/src/rust/components/column_selector/config_selector.rs +0 -3
  33. package/src/rust/components/column_selector/empty_column.rs +0 -3
  34. package/src/rust/components/column_selector/filter_column.rs +0 -3
  35. package/src/rust/components/column_selector/invalid_column.rs +0 -4
  36. package/src/rust/components/column_selector.rs +0 -3
  37. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +0 -3
  38. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +0 -3
  39. package/src/rust/components/column_settings_sidebar.rs +0 -3
  40. package/src/rust/components/containers/dropdown_menu.rs +1 -3
  41. package/src/rust/components/containers/scroll_panel.rs +1 -3
  42. package/src/rust/components/containers/split_panel.rs +0 -4
  43. package/src/rust/components/containers/tab_list.rs +0 -3
  44. package/src/rust/components/datetime_column_style.rs +0 -3
  45. package/src/rust/components/empty_row.rs +0 -3
  46. package/src/rust/components/expression_editor.rs +2 -3
  47. package/src/rust/components/filter_dropdown.rs +3 -3
  48. package/src/rust/components/font_loader.rs +2 -2
  49. package/src/rust/components/form/code_editor.rs +0 -3
  50. package/src/rust/components/form/debug.rs +0 -4
  51. package/src/rust/components/function_dropdown.rs +3 -3
  52. package/src/rust/components/number_series_style.rs +0 -3
  53. package/src/rust/components/plugin_selector.rs +0 -3
  54. package/src/rust/components/plugin_tab.rs +0 -5
  55. package/src/rust/components/portal.rs +5 -2
  56. package/src/rust/components/render_warning.rs +0 -3
  57. package/src/rust/components/status_bar.rs +3 -2
  58. package/src/rust/components/string_column_style.rs +0 -3
  59. package/src/rust/components/style/mod.rs +60 -27
  60. package/src/rust/components/style/style_provider.rs +49 -16
  61. package/src/rust/components/style_controls/number_string_format.rs +1 -8
  62. package/src/rust/components/type_icon.rs +1 -4
  63. package/src/rust/components/viewer.rs +1 -3
  64. package/src/rust/custom_elements/copy_dropdown.rs +6 -2
  65. package/src/rust/custom_elements/export_dropdown.rs +6 -2
  66. package/src/rust/lib.rs +0 -1
  67. package/src/rust/renderer.rs +13 -3
  68. package/src/rust/session/metadata.rs +11 -1
  69. package/src/rust/session.rs +14 -1
  70. package/src/themes/botanical.css +2 -0
  71. package/src/themes/dracula.css +2 -0
  72. package/src/themes/gruvbox-dark.css +2 -0
  73. package/src/themes/monokai.css +2 -0
  74. package/src/themes/phosphor.css +2 -0
  75. package/src/themes/pro-dark.css +2 -0
  76. package/src/themes/vaporwave.css +2 -0
  77. package/tsconfig.json +1 -1
  78. package/src/rust/components/style/style_cache.rs +0 -136
  79. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772 → perspective-viewer-93064d0d79855d97}/inline0.js +0 -0
  80. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772 → perspective-viewer-93064d0d79855d97}/inline1.js +0 -0
  81. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772 → perspective-viewer-93064d0d79855d97}/inline2.js +0 -0
  82. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772 → perspective-viewer-93064d0d79855d97}/inline3.js +0 -0
  83. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772 → perspective-viewer-93064d0d79855d97}/inline4.js +0 -0
@@ -13,10 +13,8 @@
13
13
  use yew::prelude::*;
14
14
 
15
15
  use super::modal::{ModalLink, SetModalLink};
16
- use super::style::LocalStyle;
17
16
  use crate::components::form::select_enum_field::SelectEnumField;
18
17
  use crate::config::*;
19
- use crate::css;
20
18
  use crate::utils::WeakScope;
21
19
 
22
20
  /// Format-only widget for [`String`] columns. Renders the `format` enum
@@ -91,7 +89,6 @@ impl Component for StringColumnStyle {
91
89
  let format_mode_changed = ctx.link().callback(StringColumnStyleMsg::FormatChanged);
92
90
  html! {
93
91
  <>
94
- <LocalStyle href={css!("column-style")} />
95
92
  <div id="column-style-container" class="string-column-style-container">
96
93
  <SelectEnumField<FormatMode>
97
94
  label="format"
@@ -10,43 +10,76 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! A micro-framework for associating local CSS snippets with `yew::Component`s
14
- //! in a Custom Element's `ShadowRoot`.
13
+ //! Associates a single, build-time CSS bundle with a `yew::Component` tree
14
+ //! rendered into a Custom Element's `ShadowRoot`.
15
15
  //!
16
- //! Embedding a `<LocalStyle>` element will only create the underlying `<style>`
17
- //! tag once (when `Component::view()` is called the first time), even if
18
- //! multiple copies of the `Component` exist in the tree.
16
+ //! Each `src/css` file is inlined and minified by `lightningcss` (see
17
+ //! `build.rs` + the `_viewer_bundle.css` and per-popup `_column_dropdown.css`
18
+ //! / `_filter_dropdown.css` / `_function_dropdown.css` / `_dropdown_menu.css`
19
+ //! entrypoints) into one stylesheet per "surface". A `<StyleProvider>` at the
20
+ //! root of a shadow tree adopts its surface's stylesheet exactly once;
21
+ //! constructed `CSSStyleSheet`s are cheap and shared across every viewer
22
+ //! instance.
19
23
  //!
20
24
  //! # Example
21
25
  //!
22
- //! ```
26
+ //! ```ignore
23
27
  //! html! {
24
- //! <StyleProvider>
25
- //! <LocalStyle href={ css!("my-style") } />
28
+ //! <StyleProvider root={host} surface={StyleSurface::Viewer}>
26
29
  //! <h1>{ "I am styled!" }</h1>
27
30
  //! </StyleProvider>
28
31
  //! }
29
32
  //! ```
30
33
 
31
- mod local_style;
32
- mod style_cache;
33
34
  mod style_provider;
34
35
 
35
- pub use local_style::LocalStyle;
36
- pub use style_provider::StyleProvider;
37
-
38
- #[macro_export]
39
- macro_rules! css {
40
- ($name:expr) => {{
41
- (
42
- $name,
43
- include_str!(concat!(env!("OUT_DIR"), "/css/", $name, ".css")),
44
- )
45
- }};
46
- ($path:expr, $name:expr) => {{
47
- (
48
- $name,
49
- include_str!(concat!(env!("OUT_DIR"), "/", $path, "/", $name, ".css")),
50
- )
51
- }};
36
+ pub use style_provider::{StyleProvider, StyleSurface};
37
+
38
+ /// The viewer-surface bundle: every `src/css` file except the floating-popup
39
+ /// sheets. Adopted into the `<perspective-viewer>` ShadowRoot.
40
+ fn viewer_bundle() -> &'static str {
41
+ include_str!(concat!(env!("OUT_DIR"), "/css/_viewer_bundle.css"))
42
+ }
43
+
44
+ /// Per-popup-surface bundles. Each floating popup root adopts exactly its own
45
+ /// self-contained sheet (mirroring the pre-bundle behavior where every popup
46
+ /// instance carried only its own CSS) so the four popup styles never collide —
47
+ /// in particular `column-dropdown.css`'s `!important` inverted rules stay
48
+ /// confined to the column-name completion dropdown.
49
+ fn column_dropdown_bundle() -> &'static str {
50
+ include_str!(concat!(env!("OUT_DIR"), "/css/_column_dropdown.css"))
51
+ }
52
+ fn filter_dropdown_bundle() -> &'static str {
53
+ include_str!(concat!(env!("OUT_DIR"), "/css/_filter_dropdown.css"))
54
+ }
55
+ fn function_dropdown_bundle() -> &'static str {
56
+ include_str!(concat!(env!("OUT_DIR"), "/css/_function_dropdown.css"))
57
+ }
58
+ fn dropdown_menu_bundle() -> &'static str {
59
+ include_str!(concat!(env!("OUT_DIR"), "/css/_dropdown_menu.css"))
60
+ }
61
+
62
+ thread_local! {
63
+ static VIEWER_SHEET: web_sys::CssStyleSheet = make_sheet(viewer_bundle());
64
+ static COLUMN_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(column_dropdown_bundle());
65
+ static FILTER_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(filter_dropdown_bundle());
66
+ static FUNCTION_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(function_dropdown_bundle());
67
+ static DROPDOWN_MENU_SHEET: web_sys::CssStyleSheet = make_sheet(dropdown_menu_bundle());
68
+ }
69
+
70
+ fn make_sheet(css: &str) -> web_sys::CssStyleSheet {
71
+ let sheet = web_sys::CssStyleSheet::new().unwrap();
72
+ sheet.replace_sync(css).unwrap();
73
+ sheet
74
+ }
75
+
76
+ /// The memoized, shared `CSSStyleSheet` for a given [`StyleSurface`].
77
+ pub(crate) fn surface_sheet(surface: StyleSurface) -> web_sys::CssStyleSheet {
78
+ match surface {
79
+ StyleSurface::Viewer => VIEWER_SHEET.with(|x| x.clone()),
80
+ StyleSurface::ColumnDropdown => COLUMN_DROPDOWN_SHEET.with(|x| x.clone()),
81
+ StyleSurface::FilterDropdown => FILTER_DROPDOWN_SHEET.with(|x| x.clone()),
82
+ StyleSurface::FunctionDropdown => FUNCTION_DROPDOWN_SHEET.with(|x| x.clone()),
83
+ StyleSurface::DropdownMenu => DROPDOWN_MENU_SHEET.with(|x| x.clone()),
84
+ }
52
85
  }
@@ -10,9 +10,26 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ use wasm_bindgen::{JsCast, JsValue};
13
14
  use yew::prelude::*;
14
15
 
15
- use super::style_cache::StyleCache;
16
+ use super::surface_sheet;
17
+
18
+ /// Which prebuilt CSS bundle a [`StyleProvider`] adopts into its root.
19
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
20
+ pub enum StyleSurface {
21
+ /// The `<perspective-viewer>` ShadowRoot.
22
+ #[default]
23
+ Viewer,
24
+ /// Column-name completion dropdown (inverted color scheme).
25
+ ColumnDropdown,
26
+ /// Filter-value completion dropdown.
27
+ FilterDropdown,
28
+ /// Expression function-completion dropdown.
29
+ FunctionDropdown,
30
+ /// Copy / Export menu (non-inverted color scheme).
31
+ DropdownMenu,
32
+ }
16
33
 
17
34
  #[derive(Properties, PartialEq)]
18
35
  pub struct StyleProviderProps {
@@ -20,32 +37,48 @@ pub struct StyleProviderProps {
20
37
 
21
38
  #[prop_or(true)]
22
39
  pub is_shadow: bool,
40
+
41
+ #[prop_or_default]
42
+ pub surface: StyleSurface,
43
+
23
44
  pub children: Children,
24
45
  }
25
46
 
26
- /// A context which injects any CSS snippet registered within its tree, doing
27
- /// so only once for each unqiue snippet name.
28
- ///
29
- /// CSS can be registered within sub-components via the `<LocalStyle>` component
30
- /// and `css!()` resource inlining macro.
31
- pub struct StyleProvider {
32
- cache: StyleCache,
33
- }
47
+ /// Adopts the build-time CSS bundle for its [`StyleSurface`] into the root's
48
+ /// `adoptedStyleSheets` once, on creation. Unlike the previous per-snippet
49
+ /// `LocalStyle` registration, children no longer register CSS individually.
50
+ pub struct StyleProvider;
34
51
 
35
52
  impl Component for StyleProvider {
36
53
  type Message = ();
37
54
  type Properties = StyleProviderProps;
38
55
 
39
56
  fn create(ctx: &Context<Self>) -> Self {
40
- let cache = StyleCache::new(ctx.props().is_shadow, &ctx.props().root);
41
- Self { cache }
57
+ adopt_surface_sheet(ctx.props());
58
+ Self
42
59
  }
43
60
 
44
61
  fn view(&self, ctx: &Context<Self>) -> Html {
45
- html! {
46
- <ContextProvider<StyleCache> context={self.cache.clone()}>
47
- { for ctx.props().children.iter() }
48
- </ContextProvider<StyleCache>>
49
- }
62
+ html! { <>{ for ctx.props().children.iter() }</> }
63
+ }
64
+ }
65
+
66
+ /// Push the surface's shared stylesheet onto the root's `adoptedStyleSheets`,
67
+ /// unless it is already present (a root may host more than one provider).
68
+ fn adopt_surface_sheet(props: &StyleProviderProps) {
69
+ let root: JsValue = if props.is_shadow {
70
+ props.root.shadow_root().unwrap().into()
71
+ } else {
72
+ web_sys::window().unwrap().document().unwrap().into()
73
+ };
74
+
75
+ let sheets = js_sys::Reflect::get(&root, &"adoptedStyleSheets".into())
76
+ .unwrap()
77
+ .unchecked_into::<js_sys::Array>();
78
+
79
+ let sheet = surface_sheet(props.surface);
80
+ let sheet_val: &JsValue = sheet.as_ref();
81
+ if sheets.index_of(sheet_val, 0) < 0 {
82
+ sheets.push(sheet_val);
50
83
  }
51
84
  }
@@ -19,9 +19,7 @@ use perspective_client::config::ColumnType;
19
19
  pub use types::*;
20
20
  use yew::{Callback, Component, Properties, html};
21
21
 
22
- use crate::components::style::LocalStyle;
23
22
  use crate::config::*;
24
- use crate::css;
25
23
 
26
24
  #[derive(Properties, PartialEq, Clone)]
27
25
  pub struct CustomNumberFormatProps {
@@ -211,12 +209,7 @@ impl Component for CustomNumberFormat {
211
209
 
212
210
  fn view(&self, ctx: &yew::prelude::Context<Self>) -> yew::prelude::Html {
213
211
  html! {
214
- <>
215
- <LocalStyle href={css!("column-style")} />
216
- { self.style_section(ctx) }
217
- { self.digits_section(ctx) }
218
- { self.misc_section(ctx) }
219
- </>
212
+ <>{ self.style_section(ctx) }{ self.digits_section(ctx) }{ self.misc_section(ctx) }</>
220
213
  }
221
214
  }
222
215
  }
@@ -14,9 +14,6 @@ use perspective_client::config::ColumnType;
14
14
  use yew::html::IntoPropValue;
15
15
  use yew::{Properties, classes, function_component, html};
16
16
 
17
- use crate::components::style::LocalStyle;
18
- use crate::css;
19
-
20
17
  #[derive(PartialEq, Debug, Clone, Copy)]
21
18
  pub enum TypeIconType {
22
19
  Type(ColumnType),
@@ -31,7 +28,7 @@ pub struct TypeIconProps {
31
28
  #[function_component(TypeIcon)]
32
29
  pub fn type_icon(p: &TypeIconProps) -> yew::Html {
33
30
  let class = classes!(p.ty.to_string(), "type-icon");
34
- html! { <><LocalStyle href={css!("type-icon")} /><span {class} /></> }
31
+ html! { <><span {class} /></> }
35
32
  }
36
33
 
37
34
  impl From<ColumnType> for TypeIconType {
@@ -21,12 +21,11 @@ use yew::prelude::*;
21
21
 
22
22
  use super::containers::split_panel::SplitPanel;
23
23
  use super::font_loader::{FontLoader, FontLoaderProps, FontLoaderStatus};
24
- use super::style::{LocalStyle, StyleProvider};
24
+ use super::style::StyleProvider;
25
25
  use crate::components::column_settings_sidebar::ColumnSettingsPanel;
26
26
  use crate::components::main_panel::MainPanel;
27
27
  use crate::components::settings_panel::{SelectedTab, SettingsPanel};
28
28
  use crate::config::*;
29
- use crate::css;
30
29
  use crate::js::JsPerspectiveViewerPlugin;
31
30
  use crate::presentation::{
32
31
  ColumnLocator, ColumnSettingsTab, DragDropProps, Presentation, PresentationProps,
@@ -608,7 +607,6 @@ impl Component for PerspectiveViewer {
608
607
 
609
608
  html! {
610
609
  <StyleProvider root={ctx.props().elem.clone()}>
611
- <LocalStyle href={css!("viewer")} />
612
610
  <div id="component_container">
613
611
  if is_settings_open {
614
612
  <SplitPanel
@@ -22,7 +22,7 @@ use yew::prelude::*;
22
22
  use super::viewer::PerspectiveViewerElement;
23
23
  use crate::components::copy_dropdown::CopyDropDownMenu;
24
24
  use crate::components::portal::PortalModal;
25
- use crate::components::style::StyleProvider;
25
+ use crate::components::style::{StyleProvider, StyleSurface};
26
26
  use crate::config::*;
27
27
  use crate::js::*;
28
28
  use crate::presentation::Presentation;
@@ -77,9 +77,13 @@ impl Component for CopyDropDownWrapper {
77
77
  fn view(&self, ctx: &Context<Self>) -> Html {
78
78
  let on_close = ctx.link().callback(|_| CopyDropDownWrapperMsg::Close);
79
79
  html! {
80
- <StyleProvider root={ctx.props().custom_element.clone()}>
80
+ <StyleProvider
81
+ root={ctx.props().custom_element.clone()}
82
+ surface={StyleSurface::DropdownMenu}
83
+ >
81
84
  <PortalModal
82
85
  tag_name="perspective-copy-menu"
86
+ surface={StyleSurface::DropdownMenu}
83
87
  target={self.target.clone()}
84
88
  own_focus=true
85
89
  {on_close}
@@ -22,7 +22,7 @@ use yew::prelude::*;
22
22
  use super::viewer::PerspectiveViewerElement;
23
23
  use crate::components::export_dropdown::ExportDropDownMenu;
24
24
  use crate::components::portal::PortalModal;
25
- use crate::components::style::StyleProvider;
25
+ use crate::components::style::{StyleProvider, StyleSurface};
26
26
  use crate::config::*;
27
27
  use crate::presentation::Presentation;
28
28
  use crate::renderer::*;
@@ -77,9 +77,13 @@ impl Component for ExportDropDownWrapper {
77
77
  fn view(&self, ctx: &Context<Self>) -> Html {
78
78
  let on_close = ctx.link().callback(|_| ExportDropDownWrapperMsg::Close);
79
79
  html! {
80
- <StyleProvider root={ctx.props().custom_element.clone()}>
80
+ <StyleProvider
81
+ root={ctx.props().custom_element.clone()}
82
+ surface={StyleSurface::DropdownMenu}
83
+ >
81
84
  <PortalModal
82
85
  tag_name="perspective-export-menu"
86
+ surface={StyleSurface::DropdownMenu}
83
87
  target={self.target.clone()}
84
88
  own_focus=true
85
89
  {on_close}
package/src/rust/lib.rs CHANGED
@@ -20,7 +20,6 @@
20
20
  #![feature(stmt_expr_attributes)]
21
21
  #![feature(try_blocks)]
22
22
  #![allow(async_fn_in_trait)]
23
- #![feature(more_qualified_paths)]
24
23
  #![warn(
25
24
  clippy::all,
26
25
  clippy::panic_in_result_fn,
@@ -544,6 +544,16 @@ impl Renderer {
544
544
  .and_then(|idx| names.get(idx))
545
545
  .map(|s| s.as_str());
546
546
 
547
+ // If the view schema itself hasn't been built yet (e.g. restore
548
+ // landed before `create_view` populated `view_schema`), refuse
549
+ // to answer rather than returning an empty schema. The strip
550
+ // pass in `update_columns_configs` treats `Err` as "leave the
551
+ // incoming cfg untouched"; an empty schema, by contrast, would
552
+ // cause `cfg.retain(|k, _| active.contains(k))` to drop every
553
+ // key and zero out user-supplied column config.
554
+ if !session.metadata().has_view_schema() {
555
+ return Err(JsValue::from("view_schema not initialized").into());
556
+ }
547
557
  let Some(view_type) = session.metadata().get_column_view_type(column_name) else {
548
558
  return Ok(ColumnConfigSchema { fields: vec![] });
549
559
  };
@@ -1040,7 +1050,7 @@ impl Renderer {
1040
1050
  }),
1041
1051
  ];
1042
1052
 
1043
- let (x, _, y) = select_all(tasks.into_iter()).await;
1053
+ let (x, _, y) = select_all(tasks).await;
1044
1054
  x.ok_or_else(|| y.into_iter().next().unwrap())
1045
1055
  }
1046
1056
 
@@ -1048,8 +1058,8 @@ impl Renderer {
1048
1058
  async fn resize_with_explicit_dimensions(&self) -> TaskResult {
1049
1059
  let plugin = self.get_active_plugin()?;
1050
1060
  let main_panel: &web_sys::HtmlElement = plugin.unchecked_ref();
1051
- let new_width = format!("{}px", &self.0.borrow().viewer_elem.client_width());
1052
- let new_height = format!("{}px", &self.0.borrow().viewer_elem.client_height());
1061
+ let new_width = format!("{}px", self.0.borrow().viewer_elem.client_width());
1062
+ let new_height = format!("{}px", self.0.borrow().viewer_elem.client_height());
1053
1063
  main_panel.style().set_property("width", &new_width)?;
1054
1064
  main_panel.style().set_property("height", &new_height)?;
1055
1065
  let result = plugin.resize().await;
@@ -95,6 +95,16 @@ impl SessionMetadata {
95
95
  Ok(())
96
96
  }
97
97
 
98
+ /// Whether `view_schema` has been populated by a prior successful
99
+ /// `create_view`. Used by `Session::create_view` to force a full
100
+ /// build on first run even when `is_clean` happens to be true, so
101
+ /// that downstream consumers (`query_column_config_schema`,
102
+ /// strip-on-write in `update_columns_configs`) always see a real
103
+ /// view schema rather than `None`.
104
+ pub fn has_view_schema(&self) -> bool {
105
+ self.as_ref().and_then(|s| s.view_schema.as_ref()).is_some()
106
+ }
107
+
98
108
  pub(super) fn update_expressions(
99
109
  &mut self,
100
110
  valid_recs: &perspective_client::ExprValidationResult,
@@ -302,7 +312,7 @@ impl SessionMetadata {
302
312
  Some(
303
313
  self.get_expression_columns()
304
314
  .cloned()
305
- .chain(self.get_table_columns()?.clone().into_iter())
315
+ .chain(self.get_table_columns()?.clone())
306
316
  .map(move |name| {
307
317
  self.get_column_table_type(&name)
308
318
  .map(|coltype| (name, coltype))
@@ -760,7 +760,20 @@ impl ValidSession<'_> {
760
760
  /// `update()` subscription, consuming this `ValidSession<'_>` and returning
761
761
  /// the original `&Session`.
762
762
  pub async fn create_view(&self) -> Result<Option<View>, ApiError> {
763
- if !self.0.reset_clean() && !self.0.borrow().is_paused {
763
+ // Always consume `is_clean` to preserve the "mark up-to-date"
764
+ // contract of `reset_clean`. Force a full build if no view
765
+ // schema has been built yet — `query_column_config_schema` (and
766
+ // downstream `update_columns_configs`) needs a populated
767
+ // `view_schema` to avoid destructively stripping every key from
768
+ // incoming `columns_config` payloads. Without this fallback, a
769
+ // first restore that lands with `is_clean = true` (e.g., when a
770
+ // prior validate already consumed the flag) would skip the
771
+ // view-schema-building inner block, leaving the schema empty
772
+ // and causing strip-on-write to silently zero out
773
+ // user-supplied column config.
774
+ let was_clean = self.0.reset_clean();
775
+ let needs_schema = !self.0.metadata().has_view_schema();
776
+ if (!was_clean && !self.0.borrow().is_paused) || needs_schema {
764
777
  if !self.1 {
765
778
  let config = self.0.borrow().config.clone();
766
779
  if let Some(sub) = &mut self.0.borrow_mut().view_sub.as_mut() {
@@ -33,6 +33,7 @@ perspective-viewer[theme="Botanical"] {
33
33
  --psp-expression--function--color: #7bc96f;
34
34
  --psp-expression--psp-error--color: rgb(232, 131, 106);
35
35
  --psp-calendar--filter: invert(1);
36
+ --psp-input--filter: invert(1);
36
37
  --psp-warning--color: #1a2e1a;
37
38
  --psp-warning--background: var(--psp--color);
38
39
 
@@ -110,6 +111,7 @@ perspective-string-column-style[theme="Botanical"] {
110
111
  --psp-expression--function--color: #7bc96f;
111
112
  --psp-expression--psp-error--color: rgb(232, 131, 106);
112
113
  --psp-calendar--filter: invert(1);
114
+ --psp-input--filter: invert(1);
113
115
  --psp-warning--color: #1a2e1a;
114
116
  --psp-warning--background: var(--psp--color);
115
117
 
@@ -32,6 +32,7 @@ perspective-viewer[theme="Dracula"] {
32
32
  --psp-expression--function--color: #22a0ce;
33
33
  --psp-expression--psp-error--color: rgb(255, 136, 136);
34
34
  --psp-calendar--filter: invert(1);
35
+ --psp-input--filter: invert(1);
35
36
  --psp-warning--color: #242526;
36
37
  --psp-warning--background: var(--psp--color);
37
38
 
@@ -170,6 +171,7 @@ perspective-dropdown[theme="Dracula"] {
170
171
  --psp-expression--function--color: #22a0ce;
171
172
  --psp-expression--psp-error--color: rgb(255, 136, 136);
172
173
  --psp-calendar--filter: invert(1);
174
+ --psp-input--filter: invert(1);
173
175
  --psp-warning--color: #242526;
174
176
  --psp-warning--background: var(--psp--color);
175
177
  --psp-icon--select-arrow--mask-image: var(
@@ -32,6 +32,7 @@ perspective-viewer[theme="Gruvbox Dark"] {
32
32
  --psp-expression--function--color: #22a0ce;
33
33
  --psp-expression--psp-error--color: rgb(255, 136, 136);
34
34
  --psp-calendar--filter: invert(1);
35
+ --psp-input--filter: invert(1);
35
36
  --psp-warning--color: #242526;
36
37
  --psp-warning--background: var(--psp--color);
37
38
 
@@ -183,6 +184,7 @@ perspective-dropdown[theme="Gruvbox Dark"] {
183
184
  --psp-expression--function--color: #22a0ce;
184
185
  --psp-expression--psp-error--color: rgb(255, 136, 136);
185
186
  --psp-calendar--filter: invert(1);
187
+ --psp-input--filter: invert(1);
186
188
  --psp-warning--color: #242526;
187
189
  --psp-warning--background: var(--psp--color);
188
190
  --psp-icon--select-arrow--mask-image: var(
@@ -32,6 +32,7 @@ perspective-viewer[theme="Monokai"] {
32
32
  --psp-expression--function--color: #22a0ce;
33
33
  --psp-expression--psp-error--color: rgb(255, 136, 136);
34
34
  --psp-calendar--filter: invert(1);
35
+ --psp-input--filter: invert(1);
35
36
  --psp-warning--color: #242526;
36
37
  --psp-warning--background: var(--psp--color);
37
38
 
@@ -183,6 +184,7 @@ perspective-string-column-style[theme="Monokai"] {
183
184
  --psp-expression--function--color: #22a0ce;
184
185
  --psp-expression--psp-error--color: rgb(255, 136, 136);
185
186
  --psp-calendar--filter: invert(1);
187
+ --psp-input--filter: invert(1);
186
188
  --psp-warning--color: #242526;
187
189
  --psp-warning--background: var(--psp--color);
188
190
  --psp-icon--select-arrow--mask-image: var(
@@ -43,6 +43,7 @@ perspective-viewer[theme="Phosphor"] {
43
43
  --psp-expression--function--color: var(--theme-green-bright);
44
44
  --psp-expression--psp-error--color: var(--theme-red);
45
45
  --psp-calendar--filter: invert(1) hue-rotate(70deg);
46
+ --psp-input--filter: invert(1) hue-rotate(70deg);
46
47
  --psp-warning--color: var(--theme-bg0);
47
48
  --psp-warning--background: var(--theme-amber);
48
49
 
@@ -153,6 +154,7 @@ perspective-string-column-style[theme="Phosphor"] {
153
154
  --psp-expression--function--color: var(--theme-green-bright);
154
155
  --psp-expression--psp-error--color: var(--theme-red);
155
156
  --psp-calendar--filter: invert(1) hue-rotate(70deg);
157
+ --psp-input--filter: invert(1) hue-rotate(70deg);
156
158
  --psp-warning--color: var(--theme-bg0);
157
159
  --psp-warning--background: var(--theme-amber);
158
160
  --psp-icon--select-arrow--mask-image: var(
@@ -33,6 +33,7 @@ perspective-viewer[theme="Pro Dark"] {
33
33
  --psp-expression--function--color: #22a0ce;
34
34
  --psp-expression--error--color: rgb(255, 136, 136);
35
35
  --psp-calendar--filter: invert(1);
36
+ --psp-input--filter: invert(1);
36
37
  --psp-warning--color: #242526;
37
38
  --psp-warning--background: var(--psp--color);
38
39
 
@@ -127,6 +128,7 @@ perspective-string-column-style[theme="Pro Dark"] {
127
128
  --psp-expression--function--color: #22a0ce;
128
129
  --psp-expression--error--color: rgb(255, 136, 136);
129
130
  --psp-calendar--filter: invert(1);
131
+ --psp-input--filter: invert(1);
130
132
  --psp-warning--color: #242526;
131
133
  --psp-warning--background: var(--psp--color);
132
134
 
@@ -32,6 +32,7 @@ perspective-viewer[theme="Vaporwave"] {
32
32
  --psp-expression--function--color: #22a0ce;
33
33
  --psp-expression--psp-error--color: rgb(255, 136, 136);
34
34
  --psp-calendar--filter: invert(1);
35
+ --psp-input--filter: invert(1);
35
36
  --psp-warning--color: #242526;
36
37
  --psp-warning--background: var(--psp--color);
37
38
 
@@ -195,6 +196,7 @@ perspective-string-column-style[theme="Vaporwave"] {
195
196
  --psp-expression--function--color: #22a0ce;
196
197
  --psp-expression--psp-error--color: rgb(255, 136, 136);
197
198
  --psp-calendar--filter: invert(1);
199
+ --psp-input--filter: invert(1);
198
200
  --psp-warning--color: #242526;
199
201
  --psp-warning--background: var(--psp--color);
200
202
  --psp-icon--select-arrow--mask-image: var(
package/tsconfig.json CHANGED
@@ -12,5 +12,5 @@
12
12
  "allowImportingTsExtensions": true,
13
13
  "typeRoots": ["./node_modules/@types"]
14
14
  },
15
- "files": ["src/ts/perspective-viewer.ts"]
15
+ "include": ["./src/ts/**/*"]
16
16
  }