@perspective-dev/viewer 5.3.1 → 5.5.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.
- package/dist/cdn/perspective-viewer.js +6 -6
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -0
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/extensions.d.ts +0 -5
- package/dist/esm/perspective-viewer.d.ts +2 -0
- package/dist/esm/perspective-viewer.inline.js +8 -8
- package/dist/esm/perspective-viewer.inline.js.map +2 -2
- package/dist/esm/perspective-viewer.js +5 -5
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +9 -0
- package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
- package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
- package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
- package/dist/wasm/perspective-viewer.d.ts +137 -104
- package/dist/wasm/perspective-viewer.js +337 -269
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
- package/package.json +7 -7
- package/src/css/_context_menu.css +1 -1
- package/src/css/_dropdown_menu.css +1 -1
- package/src/css/_viewer_bundle.css +7 -8
- package/src/css/column-dropdown.css +0 -3
- package/src/css/column-selector.css +18 -7
- package/src/css/column-settings-panel.css +0 -225
- package/src/css/column-style.css +175 -0
- package/src/css/config-selector.css +19 -11
- package/src/css/filter-dropdown.css +5 -5
- package/src/css/form/debug.css +37 -4
- package/src/css/function-dropdown.css +5 -5
- package/src/css/panel-tab.css +23 -0
- package/src/css/plugin-selector.css +5 -5
- package/src/css/render-warning.css +1 -1
- package/src/css/status-bar.css +63 -21
- package/src/css/type-icon.css +1 -3
- package/src/css/{containers → ui/containers}/context-menu.css +22 -16
- package/src/css/ui/containers/dropdown-menu-items.css +66 -0
- package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
- package/src/css/{containers → ui/containers}/tabs.css +2 -1
- package/src/css/viewer.css +32 -26
- package/src/rust/agent/tools.rs +80 -7
- package/src/rust/components/chat_panel.rs +1 -1
- package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
- package/src/rust/components/column_dropdown.rs +2 -2
- package/src/rust/components/column_selector/active_column.rs +24 -9
- package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
- package/src/rust/components/column_selector/config_selector.rs +2 -2
- package/src/rust/components/column_selector/filter_column.rs +8 -5
- package/src/rust/components/column_selector/inactive_column.rs +4 -2
- package/src/rust/components/column_selector/pivot_column.rs +4 -3
- package/src/rust/components/column_selector/sort_column.rs +7 -4
- package/src/rust/components/column_selector.rs +14 -3
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
- package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
- package/src/rust/components/column_settings_sidebar.rs +3 -4
- package/src/rust/components/copy_dropdown.rs +1 -1
- package/src/rust/components/datetime_column_style/custom.rs +1 -3
- package/src/rust/components/datetime_column_style/simple.rs +1 -2
- package/src/rust/components/datetime_column_style.rs +1 -2
- package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
- package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
- package/src/rust/components/export_dropdown.rs +1 -1
- package/src/rust/components/expression_editor.rs +1 -1
- package/src/rust/components/filter_dropdown.rs +2 -2
- package/src/rust/components/function_dropdown.rs +2 -2
- package/src/rust/components/main_panel/frame_theme.rs +3 -59
- package/src/rust/components/main_panel/msg.rs +4 -0
- package/src/rust/components/main_panel/reconcile.rs +9 -4
- package/src/rust/components/main_panel/render.rs +62 -11
- package/src/rust/components/main_panel/update.rs +39 -10
- package/src/rust/components/main_panel.rs +40 -2
- package/src/rust/components/mod.rs +5 -8
- package/src/rust/components/new_panel_menu.rs +97 -0
- package/src/rust/components/number_series_style.rs +2 -3
- package/src/rust/components/panel_menu.rs +61 -87
- package/src/rust/components/panel_tab.rs +100 -43
- package/src/rust/components/plugin_tab.rs +58 -4
- package/src/rust/components/render_warning.rs +2 -2
- package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
- package/src/rust/components/settings_panel.rs +8 -2
- package/src/rust/components/status_bar.rs +113 -19
- package/src/rust/components/status_indicator.rs +9 -14
- package/src/rust/components/{style/mod.rs → style.rs} +32 -15
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
- package/src/rust/components/viewer/msg.rs +43 -30
- package/src/rust/components/viewer/panels.rs +53 -19
- package/src/rust/components/viewer/render.rs +16 -13
- package/src/rust/components/viewer/settings.rs +137 -68
- package/src/rust/components/viewer/snapshots.rs +5 -10
- package/src/rust/components/viewer/wiring.rs +11 -22
- package/src/rust/components/viewer.rs +15 -10
- package/src/rust/components/window_editor.rs +8 -9
- package/src/rust/config/column_config_schema.rs +312 -33
- package/src/rust/config/datetime_column_style.rs +0 -11
- package/src/rust/config/mod.rs +0 -4
- package/src/rust/config/options.rs +50 -0
- package/src/rust/config/workspace_config.rs +70 -24
- package/src/rust/custom_elements/debug_plugin.rs +1 -10
- package/src/rust/custom_elements/mod.rs +0 -3
- package/src/rust/custom_elements/viewer.rs +194 -115
- package/src/rust/js/plugin.rs +3 -87
- package/src/rust/lib.rs +19 -18
- package/src/rust/presentation/column_locator.rs +3 -7
- package/src/rust/presentation.rs +68 -38
- package/src/rust/queries/hosted_tables.rs +38 -0
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +1 -1
- package/src/rust/queries/plugin_column_styles.rs +6 -5
- package/src/rust/renderer/activate.rs +1 -3
- package/src/rust/renderer/dispatch.rs +83 -22
- package/src/rust/renderer/limits.rs +24 -5
- package/src/rust/renderer/plugin_config.rs +201 -41
- package/src/rust/renderer/plugin_store.rs +2 -1
- package/src/rust/renderer/props.rs +7 -1
- package/src/rust/renderer.rs +23 -27
- package/src/rust/session/props.rs +8 -3
- package/src/rust/session/view_subscription.rs +19 -6
- package/src/rust/session.rs +189 -150
- package/src/rust/tasks/create_panel.rs +5 -2
- package/src/rust/tasks/dismiss_render_warning.rs +1 -1
- package/src/rust/tasks/eject.rs +1 -0
- package/src/rust/tasks/pipeline.rs +18 -67
- package/src/rust/tasks/presize_panels.rs +24 -1
- package/src/rust/tasks/restore_and_render.rs +0 -17
- package/src/rust/tasks/restore_panel.rs +129 -30
- package/src/rust/tasks/send_column_config.rs +4 -1
- package/src/rust/tasks/send_plugin_config.rs +1 -1
- package/src/rust/tasks/set_edit_mode.rs +1 -1
- package/src/rust/tasks/sync_update_panels.rs +147 -67
- package/src/rust/tasks/table_lifecycle.rs +7 -1
- package/src/rust/{components → ui}/containers/control_group.rs +25 -3
- package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
- package/src/rust/{components → ui}/containers/mod.rs +0 -4
- package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
- package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
- package/src/rust/{components → ui}/containers/select.rs +0 -6
- package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
- package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
- package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
- package/src/rust/{components → ui}/context_menu.rs +8 -13
- package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
- package/src/rust/{components → ui}/font_loader.rs +0 -26
- package/src/rust/{components → ui}/form/color_selector.rs +4 -2
- package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
- package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
- package/src/rust/{components → ui}/form/mod.rs +1 -10
- package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
- package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
- package/src/rust/{components → ui}/form/number_field.rs +1 -1
- package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
- package/src/rust/{components → ui}/form/optional_field.rs +3 -1
- package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
- package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
- package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
- package/src/rust/ui/mod.rs +56 -0
- package/src/rust/ui/modal.rs +61 -0
- package/src/rust/{components → ui}/portal.rs +175 -79
- package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
- package/src/rust/utils/completion.rs +0 -6
- package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
- package/src/rust/utils/css_vars.rs +13 -0
- package/src/rust/utils/local_fonts.rs +395 -0
- package/src/rust/utils/mod.rs +4 -0
- package/src/rust/workspace.rs +47 -0
- package/src/svg/boolean-type.svg +3 -2
- package/src/svg/checkbox-checked-icon.svg +4 -7
- package/src/svg/checkbox-hover.svg +3 -3
- package/src/svg/checkbox-off.svg +2 -3
- package/src/svg/checkbox-on.svg +3 -3
- package/src/svg/checkbox-unchecked-icon.svg +3 -3
- package/src/svg/datagrid-select-column.svg +8 -15
- package/src/svg/datagrid-select-region.svg +8 -15
- package/src/svg/datagrid-select-row-tree.svg +7 -12
- package/src/svg/datagrid-select-row.svg +8 -13
- package/src/svg/date-type.svg +4 -23
- package/src/svg/drawer-tab-hover.svg +4 -6
- package/src/svg/drawer-tab-invert-hover.svg +5 -5
- package/src/svg/drawer-tab-invert.svg +3 -5
- package/src/svg/drawer-tab.svg +5 -5
- package/src/svg/mega-menu-icons-candlestick.svg +5 -37
- package/src/svg/mega-menu-icons-ohlc.svg +4 -39
- package/src/svg/mega-menu-icons-sunburst.svg +4 -38
- package/src/svg/mega-menu-icons-treemap.svg +3 -43
- package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
- package/src/svg/new-panel-icon.svg +4 -0
- package/src/svg/number-type.svg +3 -3
- package/src/svg/radio-hover.svg +3 -3
- package/src/svg/radio-off.svg +3 -3
- package/src/svg/radio-on.svg +3 -3
- package/src/svg/status_ok.svg +3 -26
- package/src/svg/status_pending.svg +3 -0
- package/src/svg/string-type.svg +4 -8
- package/src/svg/theme-icon.svg +5 -5
- package/src/themes/blueprint.css +0 -57
- package/src/themes/botanical.css +0 -36
- package/src/themes/defaults.css +1 -24
- package/src/themes/dracula.css +0 -58
- package/src/themes/eggplant.css +0 -58
- package/src/themes/gruvbox-dark.css +0 -65
- package/src/themes/gruvbox.css +1 -39
- package/src/themes/icons.css +2 -8
- package/src/themes/intl/ar.css +198 -0
- package/src/themes/intl/de.css +63 -40
- package/src/themes/intl/es.css +58 -35
- package/src/themes/intl/fr.css +57 -34
- package/src/themes/intl/ja.css +59 -36
- package/src/themes/intl/pt.css +54 -31
- package/src/themes/intl/zh.css +65 -42
- package/src/themes/intl.css +34 -8
- package/src/themes/ledger.css +1 -59
- package/src/themes/monokai.css +0 -51
- package/src/themes/nord.css +0 -55
- package/src/themes/phosphor.css +0 -48
- package/src/themes/pro-dark.css +0 -37
- package/src/themes/pro.css +3 -23
- package/src/themes/solarized-dark.css +0 -31
- package/src/themes/solarized.css +1 -24
- package/src/themes/vaporwave.css +0 -44
- package/src/themes/velvet.css +0 -60
- package/src/ts/extensions.ts +0 -13
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/plugin.ts +9 -0
- package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
- package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
- package/src/ts/ts-rs/GetViewOptions.ts +7 -0
- package/src/ts/ts-rs/RestoreOptions.ts +7 -1
- package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
- package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/src/css/plugin-settings-panel.css +0 -171
- package/src/rust/components/modal.rs +0 -177
- package/src/rust/components/string_column_style.rs +0 -121
- package/src/rust/config/string_column_style.rs +0 -135
- package/src/rust/custom_elements/copy_dropdown.rs +0 -219
- package/src/rust/custom_elements/export_dropdown.rs +0 -216
- package/src/rust/custom_elements/modal.rs +0 -299
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
- /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
- /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
- /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
- /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
- /package/src/css/{dom → ui/dom}/select.css +0 -0
- /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
- /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
- /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
- /package/src/rust/{components → ui}/form/number_input.rs +0 -0
|
@@ -19,31 +19,61 @@ use wasm_bindgen::prelude::*;
|
|
|
19
19
|
use web_sys::*;
|
|
20
20
|
use yew::prelude::*;
|
|
21
21
|
|
|
22
|
-
use crate::
|
|
23
|
-
use crate::
|
|
22
|
+
use crate::ui::modal::ModalOrientation;
|
|
23
|
+
use crate::ui::style::StyleProvider;
|
|
24
24
|
use crate::utils::*;
|
|
25
25
|
|
|
26
|
+
/// The `slot` every modal host is assigned in the viewer's light DOM, so it
|
|
27
|
+
/// is in the flat tree.
|
|
28
|
+
pub const MODAL_SLOT: &str = "modal";
|
|
29
|
+
|
|
30
|
+
/// Find the `<perspective-viewer>` that `target` lives in — as a light-DOM
|
|
31
|
+
/// descendant, or through any number of shadow roots.
|
|
32
|
+
fn nearest_viewer(target: &HtmlElement) -> Option<HtmlElement> {
|
|
33
|
+
let is_viewer = |el: &Element| el.tag_name().eq_ignore_ascii_case("perspective-viewer");
|
|
34
|
+
if let Ok(Some(viewer)) = target.closest("perspective-viewer") {
|
|
35
|
+
return Some(viewer.unchecked_into());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let mut node: Node = target.clone().into();
|
|
39
|
+
loop {
|
|
40
|
+
let root = node.get_root_node();
|
|
41
|
+
let host = root.dyn_ref::<ShadowRoot>()?.host();
|
|
42
|
+
if is_viewer(&host) {
|
|
43
|
+
return Some(host.unchecked_into());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if let Ok(Some(viewer)) = host.closest("perspective-viewer") {
|
|
47
|
+
return Some(viewer.unchecked_into());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
node = host.into();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A modal (menu / completion dropdown) anchored to a `target` element,
|
|
55
|
+
/// hosted as a light-DOM child of the `<perspective-viewer>` in the top layer.
|
|
26
56
|
#[derive(Properties, PartialEq)]
|
|
27
57
|
pub struct PortalModalProps {
|
|
28
58
|
pub children: Children,
|
|
29
|
-
|
|
30
|
-
/// The element to position relative to. `None` means closed.
|
|
31
59
|
pub target: Option<HtmlElement>,
|
|
60
|
+
pub tag_name: &'static str,
|
|
61
|
+
pub theme: String,
|
|
62
|
+
pub sheet: web_sys::CssStyleSheet,
|
|
32
63
|
|
|
33
|
-
///
|
|
64
|
+
/// Adds this component's own light-dismiss (an outside `pointerdown` or
|
|
65
|
+
/// Escape → `on_close`) instead of `popover="auto"`, whose platform
|
|
66
|
+
/// dismissal fires on the opening click's own pointerup.
|
|
34
67
|
#[prop_or(true)]
|
|
35
68
|
pub own_focus: bool,
|
|
36
69
|
|
|
37
|
-
/// Called when the portal closes (blur, etc).
|
|
38
70
|
#[prop_or_default]
|
|
39
71
|
pub on_close: Callback<()>,
|
|
40
72
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/// Which popup CSS surface this portal's ShadowRoot adopts.
|
|
46
|
-
pub surface: StyleSurface,
|
|
73
|
+
/// The `<perspective-viewer>` to mount under, defaulting to the nearest
|
|
74
|
+
/// viewer around `target`.
|
|
75
|
+
#[prop_or_default]
|
|
76
|
+
pub mount: Option<HtmlElement>,
|
|
47
77
|
}
|
|
48
78
|
|
|
49
79
|
pub enum PortalModalMsg {
|
|
@@ -58,28 +88,49 @@ pub struct PortalModal {
|
|
|
58
88
|
visible: bool,
|
|
59
89
|
rev_vert: ModalOrientation,
|
|
60
90
|
anchor: Rc<Cell<ModalAnchor>>,
|
|
61
|
-
|
|
91
|
+
/// The `own_focus` light-dismiss listeners (document-level, capture),
|
|
92
|
+
/// present only while the modal is open.
|
|
93
|
+
_dismiss: Option<DismissHandlers>,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// The dismiss closures of an open `own_focus` modal: outside `pointerdown`
|
|
97
|
+
/// and Escape `keydown` on the document (capture), `blur` on the host.
|
|
98
|
+
struct DismissHandlers {
|
|
99
|
+
pointerdown: Closure<dyn FnMut(PointerEvent)>,
|
|
100
|
+
keydown: Closure<dyn FnMut(KeyboardEvent)>,
|
|
101
|
+
blur: Closure<dyn FnMut(FocusEvent)>,
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
impl PortalModal {
|
|
65
|
-
fn
|
|
66
|
-
if
|
|
67
|
-
|
|
105
|
+
fn attach(&self, ctx: &Context<Self>, target: &HtmlElement) {
|
|
106
|
+
if self.host.is_connected() {
|
|
107
|
+
return;
|
|
68
108
|
}
|
|
109
|
+
|
|
110
|
+
let parent = ctx
|
|
111
|
+
.props()
|
|
112
|
+
.mount
|
|
113
|
+
.clone()
|
|
114
|
+
.or_else(|| nearest_viewer(target))
|
|
115
|
+
.unwrap_or_else(|| {
|
|
116
|
+
tracing::warn!(
|
|
117
|
+
"`{}` target is outside any <perspective-viewer>; mounting on <body> unthemed",
|
|
118
|
+
ctx.props().tag_name
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
global::body()
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
let _ = parent.append_child(&self.host);
|
|
125
|
+
let _ = self.host.show_popover();
|
|
69
126
|
}
|
|
70
127
|
|
|
71
|
-
fn
|
|
128
|
+
fn detach(&mut self) {
|
|
129
|
+
self.remove_dismiss_handlers();
|
|
72
130
|
if self.host.is_connected() {
|
|
73
|
-
let _ =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if let Some(closure) = self._blur_closure.as_ref() {
|
|
77
|
-
self.host
|
|
78
|
-
.remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
|
|
79
|
-
.unwrap()
|
|
131
|
+
let _ = self.host.hide_popover();
|
|
132
|
+
self.host.remove();
|
|
80
133
|
}
|
|
81
|
-
|
|
82
|
-
self._blur_closure = None;
|
|
83
134
|
}
|
|
84
135
|
|
|
85
136
|
fn position_against_target(&mut self, target: &HtmlElement) {
|
|
@@ -90,12 +141,10 @@ impl PortalModal {
|
|
|
90
141
|
let left = target_rect.left();
|
|
91
142
|
|
|
92
143
|
if !self.visible {
|
|
93
|
-
// First pass: position at default anchor, invisible
|
|
94
144
|
self.top = top + height - 1.0;
|
|
95
145
|
self.left = left;
|
|
96
146
|
self.visible = false;
|
|
97
147
|
} else {
|
|
98
|
-
// Second pass: compute actual anchor and reposition
|
|
99
148
|
let anchor = calc_relative_position(&self.host, top, left, height, width);
|
|
100
149
|
self.anchor.set(anchor);
|
|
101
150
|
let modal_rect = self.host.get_bounding_client_rect();
|
|
@@ -104,9 +153,15 @@ impl PortalModal {
|
|
|
104
153
|
self.left = new_left;
|
|
105
154
|
self.rev_vert.set(anchor.is_rev_vert());
|
|
106
155
|
}
|
|
156
|
+
|
|
157
|
+
let style = self.host.style();
|
|
158
|
+
let _ = style.set_property("top", &format!("{}px", self.top));
|
|
159
|
+
let _ = style.set_property("left", &format!("{}px", self.left));
|
|
107
160
|
}
|
|
108
161
|
|
|
109
|
-
|
|
162
|
+
/// This component's light-dismiss for `own_focus` modals: an outside
|
|
163
|
+
/// `pointerdown` (document capture) or Escape.
|
|
164
|
+
fn setup_dismiss_handlers(&mut self, ctx: &Context<Self>) {
|
|
110
165
|
let on_close = {
|
|
111
166
|
let target = ctx.props().target.clone();
|
|
112
167
|
ctx.props().on_close.reform(move |_| {
|
|
@@ -116,15 +171,77 @@ impl PortalModal {
|
|
|
116
171
|
})
|
|
117
172
|
};
|
|
118
173
|
|
|
119
|
-
let
|
|
174
|
+
let host = self.host.clone();
|
|
175
|
+
let pointerdown = Closure::wrap(Box::new({
|
|
176
|
+
let on_close = on_close.clone();
|
|
177
|
+
move |event: PointerEvent| {
|
|
178
|
+
if !event.composed_path().includes(host.as_ref(), 0) {
|
|
179
|
+
on_close.emit(());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}) as Box<dyn FnMut(PointerEvent)>);
|
|
183
|
+
|
|
184
|
+
let keydown = Closure::wrap(Box::new({
|
|
185
|
+
let on_close = on_close.clone();
|
|
186
|
+
move |event: KeyboardEvent| {
|
|
187
|
+
if event.key() == "Escape" {
|
|
188
|
+
on_close.emit(());
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}) as Box<dyn FnMut(KeyboardEvent)>);
|
|
192
|
+
|
|
193
|
+
let blur = Closure::wrap(Box::new(move |_: FocusEvent| {
|
|
120
194
|
on_close.emit(());
|
|
121
195
|
}) as Box<dyn FnMut(FocusEvent)>);
|
|
122
196
|
|
|
123
197
|
let _ = self
|
|
124
198
|
.host
|
|
125
|
-
.add_event_listener_with_callback("blur",
|
|
199
|
+
.add_event_listener_with_callback("blur", blur.as_ref().unchecked_ref());
|
|
200
|
+
|
|
201
|
+
let document = global::document();
|
|
202
|
+
let _ = document.add_event_listener_with_callback_and_bool(
|
|
203
|
+
"pointerdown",
|
|
204
|
+
pointerdown.as_ref().unchecked_ref(),
|
|
205
|
+
true,
|
|
206
|
+
);
|
|
126
207
|
|
|
127
|
-
|
|
208
|
+
let _ = document.add_event_listener_with_callback_and_bool(
|
|
209
|
+
"keydown",
|
|
210
|
+
keydown.as_ref().unchecked_ref(),
|
|
211
|
+
true,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
self._dismiss = Some(DismissHandlers {
|
|
215
|
+
pointerdown,
|
|
216
|
+
keydown,
|
|
217
|
+
blur,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn remove_dismiss_handlers(&mut self) {
|
|
222
|
+
if let Some(DismissHandlers {
|
|
223
|
+
pointerdown,
|
|
224
|
+
keydown,
|
|
225
|
+
blur,
|
|
226
|
+
}) = self._dismiss.take()
|
|
227
|
+
{
|
|
228
|
+
let _ = self
|
|
229
|
+
.host
|
|
230
|
+
.remove_event_listener_with_callback("blur", blur.as_ref().unchecked_ref());
|
|
231
|
+
|
|
232
|
+
let document = global::document();
|
|
233
|
+
let _ = document.remove_event_listener_with_callback_and_bool(
|
|
234
|
+
"pointerdown",
|
|
235
|
+
pointerdown.as_ref().unchecked_ref(),
|
|
236
|
+
true,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
let _ = document.remove_event_listener_with_callback_and_bool(
|
|
240
|
+
"keydown",
|
|
241
|
+
keydown.as_ref().unchecked_ref(),
|
|
242
|
+
true,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
128
245
|
}
|
|
129
246
|
}
|
|
130
247
|
|
|
@@ -138,8 +255,14 @@ impl Component for PortalModal {
|
|
|
138
255
|
.unwrap()
|
|
139
256
|
.unchecked_into();
|
|
140
257
|
|
|
141
|
-
host.
|
|
142
|
-
host.
|
|
258
|
+
host.set_attribute("popover", "manual").unwrap();
|
|
259
|
+
host.set_attribute("slot", MODAL_SLOT).unwrap();
|
|
260
|
+
|
|
261
|
+
let style = host.style();
|
|
262
|
+
style.set_property("position", "fixed").unwrap();
|
|
263
|
+
style.set_property("right", "auto").unwrap();
|
|
264
|
+
style.set_property("bottom", "auto").unwrap();
|
|
265
|
+
style.set_property("margin", "0").unwrap();
|
|
143
266
|
let init = ShadowRootInit::new(ShadowRootMode::Open);
|
|
144
267
|
let shadow_root = if let Some(elem) = host.shadow_root() {
|
|
145
268
|
elem
|
|
@@ -156,7 +279,7 @@ impl Component for PortalModal {
|
|
|
156
279
|
visible: false,
|
|
157
280
|
rev_vert: Default::default(),
|
|
158
281
|
anchor: Default::default(),
|
|
159
|
-
|
|
282
|
+
_dismiss: None,
|
|
160
283
|
}
|
|
161
284
|
}
|
|
162
285
|
|
|
@@ -170,32 +293,20 @@ impl Component for PortalModal {
|
|
|
170
293
|
}
|
|
171
294
|
|
|
172
295
|
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
173
|
-
// The host element (`tag_name`) and its adopted sheet (`surface`) are
|
|
174
|
-
// fixed at `create` — a swap at the same vdom position must be keyed
|
|
175
|
-
// so Yew recreates the component (see `PanelMenu`'s menu→picker
|
|
176
|
-
// stages).
|
|
177
296
|
debug_assert_eq!(ctx.props().tag_name, old_props.tag_name);
|
|
178
|
-
debug_assert_eq!(ctx.props().
|
|
297
|
+
debug_assert_eq!(ctx.props().sheet, old_props.sheet);
|
|
179
298
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
match (old_target, new_target, self._blur_closure.as_ref()) {
|
|
184
|
-
(None, Some(_), Some(closure)) => {
|
|
299
|
+
match (&old_props.target, &ctx.props().target) {
|
|
300
|
+
(None, Some(_)) => {
|
|
185
301
|
self.visible = false;
|
|
186
|
-
self.
|
|
187
|
-
.remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
|
|
188
|
-
.unwrap();
|
|
189
|
-
|
|
190
|
-
self._blur_closure = None;
|
|
302
|
+
self.remove_dismiss_handlers();
|
|
191
303
|
},
|
|
192
|
-
(
|
|
193
|
-
|
|
194
|
-
self.
|
|
304
|
+
(Some(old), None) => {
|
|
305
|
+
let _ = old.class_list().remove_1("modal-target");
|
|
306
|
+
self.detach();
|
|
195
307
|
},
|
|
196
|
-
(Some(
|
|
197
|
-
|
|
198
|
-
return true;
|
|
308
|
+
(Some(old), Some(new)) if old != new => {
|
|
309
|
+
let _ = old.class_list().remove_1("modal-target");
|
|
199
310
|
},
|
|
200
311
|
_ => {},
|
|
201
312
|
}
|
|
@@ -209,17 +320,13 @@ impl Component for PortalModal {
|
|
|
209
320
|
return html! {};
|
|
210
321
|
}
|
|
211
322
|
|
|
212
|
-
let
|
|
213
|
-
let css = format!(
|
|
214
|
-
":host{{top:{}px;left:{}px{}}}",
|
|
215
|
-
self.top, self.left, opacity
|
|
216
|
-
);
|
|
323
|
+
let css = if self.visible { "" } else { ":host{opacity:0}" };
|
|
217
324
|
|
|
218
325
|
let portal_content = html! {
|
|
219
326
|
<>
|
|
220
327
|
<style>{ css }</style>
|
|
221
328
|
<ContextProvider<ModalOrientation> context={self.rev_vert.clone()}>
|
|
222
|
-
<StyleProvider root={self.host.clone()}
|
|
329
|
+
<StyleProvider root={self.host.clone()} sheet={ctx.props().sheet.clone()}>
|
|
223
330
|
{ for ctx.props().children.iter() }
|
|
224
331
|
</StyleProvider>
|
|
225
332
|
</ContextProvider<ModalOrientation>>
|
|
@@ -234,34 +341,27 @@ impl Component for PortalModal {
|
|
|
234
341
|
if !self.host.is_connected() {
|
|
235
342
|
let theme = ctx.props().theme.as_str();
|
|
236
343
|
self.host.set_attribute("theme", theme).unwrap();
|
|
237
|
-
|
|
238
|
-
// First render with a target: attach to body, position invisible
|
|
239
|
-
self.position_against_target(target);
|
|
240
|
-
self.attach_to_body();
|
|
241
|
-
|
|
242
|
-
// Propagate theme from target
|
|
243
344
|
if let Some(theme) = target.get_attribute("theme") {
|
|
244
345
|
let _ = self.host.set_attribute("theme", &theme);
|
|
245
346
|
}
|
|
246
347
|
|
|
348
|
+
self.position_against_target(target);
|
|
349
|
+
self.attach(ctx, target);
|
|
247
350
|
target.class_list().add_1("modal-target").unwrap();
|
|
248
|
-
|
|
249
351
|
if ctx.props().own_focus {
|
|
250
352
|
self.host.set_attribute("tabindex", "0").unwrap();
|
|
251
|
-
self.
|
|
353
|
+
self.setup_dismiss_handlers(ctx);
|
|
354
|
+
let _ = self.host.focus();
|
|
252
355
|
}
|
|
253
356
|
|
|
254
|
-
// Schedule second positioning pass
|
|
255
357
|
let link = ctx.link().clone();
|
|
256
358
|
wasm_bindgen_futures::spawn_local(async move {
|
|
257
359
|
request_animation_frame().await;
|
|
258
360
|
link.send_message(PortalModalMsg::Reposition);
|
|
259
361
|
});
|
|
260
362
|
} else if self.visible {
|
|
261
|
-
// Second pass: reposition with correct anchor
|
|
262
363
|
self.position_against_target(target);
|
|
263
|
-
|
|
264
|
-
if ctx.props().own_focus && self._blur_closure.is_some() {
|
|
364
|
+
if ctx.props().own_focus && self._dismiss.is_some() {
|
|
265
365
|
let _ = self.host.focus();
|
|
266
366
|
}
|
|
267
367
|
}
|
|
@@ -271,14 +371,10 @@ impl Component for PortalModal {
|
|
|
271
371
|
fn destroy(&mut self, ctx: &Context<Self>) {
|
|
272
372
|
if let Some(target) = &ctx.props().target {
|
|
273
373
|
target.class_list().remove_1("modal-target").unwrap();
|
|
274
|
-
if target.get_attribute("theme").is_some() {
|
|
275
|
-
let _ = self.host.remove_attribute("theme");
|
|
276
|
-
}
|
|
277
|
-
|
|
278
374
|
let event = CustomEvent::new("-perspective-close-expression").unwrap();
|
|
279
375
|
let _ = target.dispatch_event(&event);
|
|
280
376
|
}
|
|
281
377
|
|
|
282
|
-
self.
|
|
378
|
+
self.detach();
|
|
283
379
|
}
|
|
284
380
|
}
|
|
@@ -13,42 +13,15 @@
|
|
|
13
13
|
use wasm_bindgen::{JsCast, JsValue};
|
|
14
14
|
use yew::prelude::*;
|
|
15
15
|
|
|
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
|
-
/// Panel context menu (right-click a panel).
|
|
33
|
-
ContextMenu,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
16
|
#[derive(Properties, PartialEq)]
|
|
37
17
|
pub struct StyleProviderProps {
|
|
38
18
|
pub root: web_sys::HtmlElement,
|
|
39
|
-
|
|
40
19
|
#[prop_or(true)]
|
|
41
20
|
pub is_shadow: bool,
|
|
42
|
-
|
|
43
|
-
#[prop_or_default]
|
|
44
|
-
pub surface: StyleSurface,
|
|
45
|
-
|
|
21
|
+
pub sheet: web_sys::CssStyleSheet,
|
|
46
22
|
pub children: Children,
|
|
47
23
|
}
|
|
48
24
|
|
|
49
|
-
/// Adopts the build-time CSS bundle for its [`StyleSurface`] into the root's
|
|
50
|
-
/// `adoptedStyleSheets` once, on creation. Unlike the previous per-snippet
|
|
51
|
-
/// `LocalStyle` registration, children no longer register CSS individually.
|
|
52
25
|
pub struct StyleProvider;
|
|
53
26
|
|
|
54
27
|
impl Component for StyleProvider {
|
|
@@ -56,7 +29,7 @@ impl Component for StyleProvider {
|
|
|
56
29
|
type Properties = StyleProviderProps;
|
|
57
30
|
|
|
58
31
|
fn create(ctx: &Context<Self>) -> Self {
|
|
59
|
-
|
|
32
|
+
adopt_sheet(ctx.props());
|
|
60
33
|
Self
|
|
61
34
|
}
|
|
62
35
|
|
|
@@ -65,9 +38,7 @@ impl Component for StyleProvider {
|
|
|
65
38
|
}
|
|
66
39
|
}
|
|
67
40
|
|
|
68
|
-
|
|
69
|
-
/// unless it is already present (a root may host more than one provider).
|
|
70
|
-
fn adopt_surface_sheet(props: &StyleProviderProps) {
|
|
41
|
+
fn adopt_sheet(props: &StyleProviderProps) {
|
|
71
42
|
let root: JsValue = if props.is_shadow {
|
|
72
43
|
props.root.shadow_root().unwrap().into()
|
|
73
44
|
} else {
|
|
@@ -78,8 +49,7 @@ fn adopt_surface_sheet(props: &StyleProviderProps) {
|
|
|
78
49
|
.unwrap()
|
|
79
50
|
.unchecked_into::<js_sys::Array>();
|
|
80
51
|
|
|
81
|
-
let
|
|
82
|
-
let sheet_val: &JsValue = sheet.as_ref();
|
|
52
|
+
let sheet_val: &JsValue = props.sheet.as_ref();
|
|
83
53
|
if sheets.index_of(sheet_val, 0) < 0 {
|
|
84
54
|
sheets.push(sheet_val);
|
|
85
55
|
}
|
|
@@ -15,12 +15,6 @@ use std::future::Future;
|
|
|
15
15
|
use futures::channel::oneshot;
|
|
16
16
|
use perspective_js::utils::*;
|
|
17
17
|
|
|
18
|
-
/// A completion handle for message-based public API methods (invariant I6 —
|
|
19
|
-
/// see `SESSION_CONFIG_COHERENCE_PLAN.md`): its ONLY resolution API is
|
|
20
|
-
/// [`Completion::resolve_after`], which takes a run future — so resolving a
|
|
21
|
-
/// public method's promise at message-handling time (before the renders it
|
|
22
|
-
/// caused have drawn) is unwritable, not merely discouraged. Dropping an
|
|
23
|
-
/// unresolved `Completion` rejects the caller as cancelled.
|
|
24
18
|
pub struct Completion(Option<oneshot::Sender<ApiResult<()>>>);
|
|
25
19
|
|
|
26
20
|
impl Completion {
|
|
@@ -12,9 +12,28 @@
|
|
|
12
12
|
|
|
13
13
|
use std::collections::BTreeMap;
|
|
14
14
|
|
|
15
|
+
use serde::{Deserialize, Serialize};
|
|
15
16
|
use serde_json::Value;
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
19
|
+
pub struct GradientStopSpec {
|
|
20
|
+
pub color: String,
|
|
21
|
+
pub offset: f64,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn canonicalize_gradient_stops(mut stops: Vec<GradientStopSpec>) -> Vec<GradientStopSpec> {
|
|
25
|
+
for stop in &mut stops {
|
|
26
|
+
stop.offset = (stop.offset.clamp(0.0, 1.0) * 1000.0).round() / 1000.0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stops.sort_by(|a, b| {
|
|
30
|
+
a.offset
|
|
31
|
+
.partial_cmp(&b.offset)
|
|
32
|
+
.unwrap_or(std::cmp::Ordering::Equal)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
stops
|
|
36
|
+
}
|
|
18
37
|
|
|
19
38
|
pub const USER_VAR_PREFIX: &str = "--psp-user--";
|
|
20
39
|
|
|
@@ -64,8 +83,8 @@ impl CssKind {
|
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
/// The property name a whole-value `var(--psp-user--…)` reference
|
|
68
|
-
///
|
|
86
|
+
/// The property name a whole-value `var(--psp-user--…)` reference names, or
|
|
87
|
+
/// `None` when `src` is not one.
|
|
69
88
|
pub fn parse_var_ref(src: &str) -> Option<&str> {
|
|
70
89
|
let inner = src.trim().strip_prefix("var(")?.strip_suffix(')')?.trim();
|
|
71
90
|
if !inner.starts_with(USER_VAR_PREFIX)
|
|
@@ -129,8 +148,8 @@ fn channel_from_token(token: &str) -> Result<u8, String> {
|
|
|
129
148
|
Ok(value.round().clamp(0.0, 255.0) as u8)
|
|
130
149
|
}
|
|
131
150
|
|
|
132
|
-
/// A color literal — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()`
|
|
133
|
-
///
|
|
151
|
+
/// A color literal — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()` or
|
|
152
|
+
/// `rgba()` — normalized to lowercase `#rrggbb`.
|
|
134
153
|
pub fn normalize_css_color(src: &str) -> Result<String, String> {
|
|
135
154
|
let src = src.trim();
|
|
136
155
|
if let Some(hex) = src.strip_prefix('#') {
|
|
@@ -260,8 +279,8 @@ fn format_percent(offset: f64) -> String {
|
|
|
260
279
|
format!("{text}%")
|
|
261
280
|
}
|
|
262
281
|
|
|
263
|
-
/// The canonical gradient string for `stops`: `linear-gradient(to
|
|
264
|
-
///
|
|
282
|
+
/// The canonical gradient string for `stops`: `linear-gradient(to right,
|
|
283
|
+
/// #rrggbb P%, …)` with every position explicit.
|
|
265
284
|
pub fn gradient_to_css(stops: &[GradientStopSpec]) -> String {
|
|
266
285
|
let body = canonicalize_gradient_stops(stops.to_vec())
|
|
267
286
|
.iter()
|
|
@@ -272,26 +291,20 @@ pub fn gradient_to_css(stops: &[GradientStopSpec]) -> String {
|
|
|
272
291
|
format!("linear-gradient(to right, {body})")
|
|
273
292
|
}
|
|
274
293
|
|
|
275
|
-
/// The canonical palette string for `colors`: `linear-gradient(to
|
|
276
|
-
///
|
|
294
|
+
/// The canonical palette string for `colors`: `linear-gradient(to right,
|
|
295
|
+
/// #rrggbb, …)` with no positions.
|
|
277
296
|
pub fn palette_to_css(colors: &[String]) -> String {
|
|
278
297
|
format!("linear-gradient(to right, {})", colors.join(", "))
|
|
279
298
|
}
|
|
280
299
|
|
|
281
|
-
/// A serialized
|
|
282
|
-
///
|
|
283
|
-
/// [`ControlSpec::GradientStops`]: super::ControlSpec::GradientStops
|
|
300
|
+
/// A serialized `ControlSpec::GradientStops` value.
|
|
284
301
|
#[derive(Clone, Debug, PartialEq)]
|
|
285
302
|
pub enum CssGradient {
|
|
286
|
-
/// Canonical stops (sorted, rounded), length ≥ 2.
|
|
287
303
|
Literal(Vec<GradientStopSpec>),
|
|
288
|
-
/// A `--psp-user--gradient-…` property name.
|
|
289
304
|
Var(String),
|
|
290
305
|
}
|
|
291
306
|
|
|
292
307
|
impl CssGradient {
|
|
293
|
-
/// Parse a gradient string of at least 2 stops, with optional
|
|
294
|
-
/// positions filled by the CSS implicit rules.
|
|
295
308
|
pub fn parse(src: &str) -> Result<Self, String> {
|
|
296
309
|
if let Some(name) = parse_kind_var(src, CssKind::Gradient)? {
|
|
297
310
|
return Ok(CssGradient::Var(name));
|
|
@@ -348,20 +361,14 @@ impl CssGradient {
|
|
|
348
361
|
}
|
|
349
362
|
}
|
|
350
363
|
|
|
351
|
-
/// A serialized
|
|
352
|
-
///
|
|
353
|
-
/// [`ControlSpec::Palette`]: super::ControlSpec::Palette
|
|
364
|
+
/// A serialized `ControlSpec::Palette` value.
|
|
354
365
|
#[derive(Clone, Debug, PartialEq)]
|
|
355
366
|
pub enum CssPalette {
|
|
356
|
-
/// Ordered `#rrggbb` entries, length ≥ 1.
|
|
357
367
|
Literal(Vec<String>),
|
|
358
|
-
/// A `--psp-user--palette-…` property name.
|
|
359
368
|
Var(String),
|
|
360
369
|
}
|
|
361
370
|
|
|
362
371
|
impl CssPalette {
|
|
363
|
-
/// Parse a palette string of at least 1 entry, with positions
|
|
364
|
-
/// forbidden.
|
|
365
372
|
pub fn parse(src: &str) -> Result<Self, String> {
|
|
366
373
|
if let Some(name) = parse_kind_var(src, CssKind::Palette)? {
|
|
367
374
|
return Ok(CssPalette::Var(name));
|
|
@@ -395,14 +402,10 @@ impl CssPalette {
|
|
|
395
402
|
}
|
|
396
403
|
}
|
|
397
404
|
|
|
398
|
-
/// A serialized
|
|
399
|
-
///
|
|
400
|
-
/// [`ControlSpec::Color`]: super::ControlSpec::Color
|
|
405
|
+
/// A serialized `ControlSpec::Color` value.
|
|
401
406
|
#[derive(Clone, Debug, PartialEq)]
|
|
402
407
|
pub enum CssColor {
|
|
403
|
-
/// Lowercase `#rrggbb`.
|
|
404
408
|
Literal(String),
|
|
405
|
-
/// A `--psp-user--color-…` property name.
|
|
406
409
|
Var(String),
|
|
407
410
|
}
|
|
408
411
|
|
|
@@ -435,8 +438,8 @@ pub fn canonicalize_css_color(src: &str) -> Result<String, String> {
|
|
|
435
438
|
CssKind::Color.canonicalize(src)
|
|
436
439
|
}
|
|
437
440
|
|
|
438
|
-
/// One CSS literal stored in a panel's `columns_config`: its column,
|
|
439
|
-
///
|
|
441
|
+
/// One CSS literal stored in a panel's `columns_config`: its column, key,
|
|
442
|
+
/// schema kind and canonical value — the unit of "in use".
|
|
440
443
|
#[derive(Clone, Debug, PartialEq)]
|
|
441
444
|
pub struct CssLiteralUse {
|
|
442
445
|
pub column: String,
|
|
@@ -445,8 +448,8 @@ pub struct CssLiteralUse {
|
|
|
445
448
|
pub literal: String,
|
|
446
449
|
}
|
|
447
450
|
|
|
448
|
-
/// The workspace palette: `restored` unioned with every in-use
|
|
449
|
-
///
|
|
451
|
+
/// The workspace palette: `restored` unioned with every in-use literal, each
|
|
452
|
+
/// named deterministically.
|
|
450
453
|
pub fn assign_palette_names(
|
|
451
454
|
restored: &BTreeMap<String, String>,
|
|
452
455
|
host: &[NamedValue],
|
|
@@ -487,9 +490,9 @@ pub fn palette_name_for(
|
|
|
487
490
|
.map(|(name, _)| name.clone())
|
|
488
491
|
}
|
|
489
492
|
|
|
490
|
-
/// Replace every whole-value `var(--psp-user--…)` string in one
|
|
491
|
-
///
|
|
492
|
-
///
|
|
493
|
+
/// Replace every whole-value `var(--psp-user--…)` string in one column's config
|
|
494
|
+
/// entry with the literal `lookup` resolves it to, removing and reporting keys
|
|
495
|
+
/// that do not resolve.
|
|
493
496
|
pub fn resolve_css_refs(
|
|
494
497
|
entry: &mut serde_json::Map<String, Value>,
|
|
495
498
|
lookup: &dyn Fn(&str) -> Option<String>,
|
|
@@ -24,3 +24,16 @@ pub fn read_custom_property(elem: &Element, name: &str) -> Option<String> {
|
|
|
24
24
|
let value = value.trim();
|
|
25
25
|
(!value.is_empty()).then(|| value.to_owned())
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
/// The intl string `--psp-label--{slug}--content` as inherited by `elem`,
|
|
29
|
+
/// unquoted, or `None` when no theme defines it.
|
|
30
|
+
pub fn read_intl_string(elem: &Element, slug: &str) -> Option<String> {
|
|
31
|
+
let raw = read_custom_property(elem, &format!("--psp-label--{slug}--content"))?;
|
|
32
|
+
let unquoted = raw
|
|
33
|
+
.strip_prefix('"')
|
|
34
|
+
.and_then(|x| x.strip_suffix('"'))
|
|
35
|
+
.or_else(|| raw.strip_prefix('\'').and_then(|x| x.strip_suffix('\'')))
|
|
36
|
+
.unwrap_or(&raw);
|
|
37
|
+
|
|
38
|
+
Some(unquoted.replace("\\\"", "\""))
|
|
39
|
+
}
|