@perspective-dev/viewer 4.4.1 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +1 -2
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.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/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/monokai.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/esm/bootstrap.d.ts +2 -1
- package/dist/esm/column-format.d.ts +51 -0
- package/dist/esm/extensions.d.ts +2 -0
- package/dist/esm/perspective-viewer.d.ts +3 -1
- package/dist/esm/perspective-viewer.inline.js +1 -2
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +1 -2
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/perspective-viewer.worker.d.ts +2 -0
- package/dist/esm/plugin.d.ts +16 -72
- package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -3
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
- package/dist/wasm/perspective-viewer.d.ts +77 -18
- package/dist/wasm/perspective-viewer.js +302 -148
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
- package/package.json +24 -2
- package/src/css/column-selector.css +3 -2
- package/src/css/column-settings-panel.css +44 -9
- package/src/css/column-style.css +35 -2
- package/src/css/containers/scroll-panel.css +2 -1
- package/src/css/containers/tabs.css +8 -52
- package/src/css/dom/checkbox.css +2 -6
- package/src/css/form/code-editor.css +1 -0
- package/src/css/form/debug.css +3 -10
- package/src/css/plugin-selector.css +33 -0
- package/src/css/plugin-settings-panel.css +99 -0
- package/src/css/viewer.css +143 -3
- package/src/rust/components/column_dropdown.rs +3 -1
- package/src/rust/components/column_selector/active_column.rs +16 -19
- package/src/rust/components/column_selector/config_selector.rs +20 -20
- package/src/rust/components/column_selector/filter_column.rs +14 -14
- package/src/rust/components/column_selector/inactive_column.rs +10 -15
- package/src/rust/components/column_selector/pivot_column.rs +7 -7
- package/src/rust/components/column_selector/sort_column.rs +7 -7
- package/src/rust/components/column_selector.rs +55 -37
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +395 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
- package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
- package/src/rust/components/column_settings_sidebar.rs +44 -49
- package/src/rust/components/containers/dragdrop_list.rs +32 -5
- package/src/rust/components/containers/mod.rs +0 -1
- package/src/rust/components/containers/scroll_panel.rs +21 -7
- package/src/rust/components/containers/sidebar.rs +8 -6
- package/src/rust/components/containers/split_panel.rs +3 -3
- package/src/rust/components/containers/tab_list.rs +3 -9
- package/src/rust/components/copy_dropdown.rs +2 -3
- package/src/rust/components/datetime_column_style.rs +19 -81
- package/src/rust/components/editable_header.rs +17 -3
- package/src/rust/components/export_dropdown.rs +2 -3
- package/src/rust/components/expression_editor.rs +29 -17
- package/src/rust/components/filter_dropdown.rs +2 -1
- package/src/rust/components/form/color_range_selector.rs +14 -7
- package/src/rust/components/form/debug.rs +47 -37
- package/src/rust/components/main_panel.rs +24 -65
- package/src/rust/components/mod.rs +2 -1
- package/src/rust/components/number_series_style.rs +161 -0
- package/src/rust/components/plugin_tab.rs +221 -0
- package/src/rust/components/settings_panel.rs +181 -59
- package/src/rust/components/status_bar.rs +141 -174
- package/src/rust/components/status_indicator.rs +15 -22
- package/src/rust/components/string_column_style.rs +20 -82
- package/src/rust/components/style_controls/number_string_format.rs +14 -30
- package/src/rust/components/viewer.rs +169 -132
- package/src/rust/config/column_config_schema.rs +195 -0
- package/src/rust/config/columns_config.rs +4 -97
- package/src/rust/config/datetime_column_style.rs +0 -5
- package/src/rust/config/mod.rs +8 -2
- package/src/rust/config/number_series_style.rs +79 -0
- package/src/rust/config/plugin_static_config.rs +144 -0
- package/src/rust/config/string_column_style.rs +0 -5
- package/src/rust/config/viewer_config.rs +5 -6
- package/src/rust/custom_elements/copy_dropdown.rs +30 -18
- package/src/rust/custom_elements/debug_plugin.rs +1 -3
- package/src/rust/custom_elements/export_dropdown.rs +26 -18
- package/src/rust/custom_elements/viewer.rs +62 -73
- package/src/rust/custom_events.rs +181 -224
- package/src/rust/js/plugin.rs +45 -117
- package/src/rust/lib.rs +34 -5
- package/src/rust/presentation/drag_helpers.rs +206 -0
- package/src/rust/presentation/props.rs +8 -0
- package/src/rust/presentation.rs +256 -41
- package/src/rust/{tasks → queries}/column_locator.rs +17 -73
- package/src/rust/queries/column_values.rs +59 -0
- package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
- package/src/rust/queries/exports.rs +96 -0
- package/src/rust/queries/fetch_column_stats.rs +94 -0
- package/src/rust/queries/get_viewer_config.rs +54 -0
- package/src/rust/queries/mod.rs +44 -0
- package/src/rust/queries/plugin_column_styles.rs +101 -0
- package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
- package/src/rust/renderer/activate.rs +1 -0
- package/src/rust/renderer/limits.rs +9 -4
- package/src/rust/renderer/plugin_store.rs +12 -0
- package/src/rust/renderer/props.rs +28 -3
- package/src/rust/renderer/registry.rs +40 -15
- package/src/rust/renderer.rs +703 -60
- package/src/rust/session/column_defaults_update.rs +20 -28
- package/src/rust/session/drag_drop_update.rs +10 -10
- package/src/rust/session/metadata.rs +31 -16
- package/src/rust/session/props.rs +15 -6
- package/src/rust/session/view_subscription.rs +10 -0
- package/src/rust/session.rs +109 -147
- package/src/rust/tasks/copy_export.rs +178 -158
- package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
- package/src/rust/tasks/edit_expression.rs +68 -88
- package/src/rust/tasks/eject.rs +25 -22
- package/src/rust/tasks/intersection_observer.rs +8 -21
- package/src/rust/tasks/mod.rs +19 -21
- package/src/rust/tasks/reset_all.rs +98 -0
- package/src/rust/tasks/resize_observer.rs +11 -33
- package/src/rust/tasks/restore_and_render.rs +128 -90
- package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +39 -35
- package/src/rust/tasks/send_plugin_config.rs +33 -33
- package/src/rust/tasks/update_and_render.rs +75 -49
- package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
- package/src/rust/tasks/validate_expression.rs +61 -0
- package/src/rust/utils/browser/selection.rs +4 -4
- package/src/rust/utils/mod.rs +0 -63
- package/src/svg/checkbox-checked-icon.svg +1 -1
- package/src/svg/checkbox-unchecked-icon.svg +1 -1
- package/src/svg/mega-menu-icons-density.svg +23 -0
- package/src/svg/mega-menu-icons-map-density.svg +24 -0
- package/src/svg/mega-menu-icons-map-line.svg +19 -0
- package/src/themes/botanical.css +27 -53
- package/src/themes/defaults.css +24 -36
- package/src/themes/dracula.css +36 -54
- package/src/themes/gruvbox-dark.css +39 -59
- package/src/themes/gruvbox.css +16 -28
- package/src/themes/icons.css +5 -0
- package/src/themes/intl/de.css +43 -6
- package/src/themes/intl/es.css +43 -6
- package/src/themes/intl/fr.css +43 -6
- package/src/themes/intl/ja.css +43 -6
- package/src/themes/intl/pt.css +43 -6
- package/src/themes/intl/zh.css +43 -6
- package/src/themes/intl.css +38 -4
- package/src/themes/monokai.css +45 -61
- package/src/themes/phosphor.css +20 -29
- package/src/themes/pro-dark.css +25 -34
- package/src/themes/solarized-dark.css +21 -36
- package/src/themes/solarized.css +13 -23
- package/src/themes/vaporwave.css +40 -74
- package/src/ts/bootstrap.ts +14 -3
- package/src/ts/column-format.ts +162 -0
- package/src/ts/extensions.ts +4 -0
- package/src/ts/perspective-viewer.ts +9 -1
- package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
- package/src/ts/plugin.ts +25 -101
- package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
- package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
- package/src/ts/ts-rs/ViewerConfig.ts +1 -2
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
- package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
- package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
- package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
- package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
- package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
- package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
- package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
- package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
- package/src/rust/components/number_column_style.rs +0 -491
- package/src/rust/config/number_column_style.rs +0 -136
- package/src/rust/dragdrop.rs +0 -481
- package/src/rust/tasks/plugin_column_styles.rs +0 -98
- package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
- package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
- package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
- package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
- package/src/ts/ts-rs/PluginConfig.ts +0 -4
- package/src/ts/ts-rs/RoundingMode.ts +0 -3
- package/src/ts/ts-rs/RoundingPriority.ts +0 -3
- package/src/ts/ts-rs/SignDisplay.ts +0 -3
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
- package/src/ts/ts-rs/UseGrouping.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline4.js +0 -0
- /package/src/rust/{tasks → config}/export_method.rs +0 -0
- /package/src/rust/{tasks → queries}/export_app.rs +0 -0
- /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
package/src/rust/lib.rs
CHANGED
|
@@ -33,15 +33,16 @@ pub mod components;
|
|
|
33
33
|
pub mod config;
|
|
34
34
|
pub mod custom_elements;
|
|
35
35
|
mod custom_events;
|
|
36
|
-
mod dragdrop;
|
|
37
36
|
pub mod exprtk;
|
|
38
37
|
mod js;
|
|
38
|
+
mod presentation;
|
|
39
39
|
mod root;
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
mod
|
|
41
|
+
#[doc(hidden)]
|
|
42
|
+
pub mod queries;
|
|
43
43
|
mod renderer;
|
|
44
44
|
mod session;
|
|
45
|
+
|
|
45
46
|
#[doc(hidden)]
|
|
46
47
|
pub mod tasks;
|
|
47
48
|
pub mod utils;
|
|
@@ -50,6 +51,8 @@ pub mod utils;
|
|
|
50
51
|
extern crate macro_rules_attribute;
|
|
51
52
|
extern crate alloc;
|
|
52
53
|
|
|
54
|
+
use std::cell::RefCell;
|
|
55
|
+
|
|
53
56
|
use perspective_js::utils::*;
|
|
54
57
|
use wasm_bindgen::prelude::*;
|
|
55
58
|
|
|
@@ -63,9 +66,10 @@ use crate::utils::define_web_component;
|
|
|
63
66
|
const TS_APPEND_CONTENT: &'static str = r#"
|
|
64
67
|
import type {
|
|
65
68
|
ColumnType,
|
|
66
|
-
TableInitOptions,
|
|
69
|
+
TableInitOptions,
|
|
67
70
|
ColumnWindow,
|
|
68
71
|
ViewWindow,
|
|
72
|
+
TypedArrayWindow,
|
|
69
73
|
OnUpdateOptions,
|
|
70
74
|
JoinOptions,
|
|
71
75
|
UpdateOptions,
|
|
@@ -76,6 +80,7 @@ import type {
|
|
|
76
80
|
|
|
77
81
|
export type * from "../../src/ts/ts-rs/ViewerConfig.d.ts";
|
|
78
82
|
export type * from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
83
|
+
export type * from "../../src/ts/ts-rs/PluginStaticConfig.d.ts";
|
|
79
84
|
import type {ViewerConfig} from "../../src/ts/ts-rs/ViewerConfig.d.ts";
|
|
80
85
|
import type {ViewerConfigUpdate} from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
81
86
|
"#;
|
|
@@ -96,13 +101,37 @@ pub fn registerPlugin(name: &str) {
|
|
|
96
101
|
/// preserve backwards-compatible synchronous API).
|
|
97
102
|
#[cfg(not(feature = "external-bootstrap"))]
|
|
98
103
|
#[wasm_bindgen(js_name = "init")]
|
|
99
|
-
pub fn js_init() {
|
|
104
|
+
pub fn js_init(module: js_sys::WebAssembly::Module, url: web_sys::Url) {
|
|
100
105
|
console_error_panic_hook::set_once();
|
|
101
106
|
perspective_js::utils::set_global_logging();
|
|
102
107
|
define_web_components!("export * as psp from '../../perspective-viewer.js'");
|
|
108
|
+
MODULE.with_borrow_mut(|f| {
|
|
109
|
+
*f = Some((module, url));
|
|
110
|
+
});
|
|
111
|
+
|
|
103
112
|
tracing::info!("Perspective initialized.");
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
thread_local! {
|
|
116
|
+
static MODULE: RefCell<Option<(js_sys::WebAssembly::Module, web_sys::Url)>> = RefCell::default();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[cfg(not(feature = "external-bootstrap"))]
|
|
120
|
+
#[wasm_bindgen(js_name = "get_wasm_module")]
|
|
121
|
+
pub fn js_get_module() -> Result<js_sys::WebAssembly::Module, JsValue> {
|
|
122
|
+
MODULE
|
|
123
|
+
.with_borrow(|f| f.clone().map(|x| x.0))
|
|
124
|
+
.ok_or_else(|| "Uninited module".into())
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[cfg(not(feature = "external-bootstrap"))]
|
|
128
|
+
#[wasm_bindgen(js_name = "get_worker_url")]
|
|
129
|
+
pub fn js_get_worker_url() -> Result<web_sys::Url, JsValue> {
|
|
130
|
+
MODULE
|
|
131
|
+
.with_borrow(|f| f.clone().map(|x| x.1))
|
|
132
|
+
.ok_or_else(|| "Uninited module".into())
|
|
133
|
+
}
|
|
134
|
+
|
|
106
135
|
/// Register Web Components with the global registry, given a Perspective
|
|
107
136
|
/// module.
|
|
108
137
|
///
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use std::cell::Cell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
|
+
|
|
16
|
+
use perspective_client::clone;
|
|
17
|
+
use perspective_js::utils::*;
|
|
18
|
+
use wasm_bindgen::prelude::*;
|
|
19
|
+
use web_sys::*;
|
|
20
|
+
use yew::prelude::*;
|
|
21
|
+
|
|
22
|
+
use crate::js::{IntersectionObserver, IntersectionObserverEntry};
|
|
23
|
+
|
|
24
|
+
pub type DragEndCallback = Closure<dyn FnMut(DragEvent)>;
|
|
25
|
+
|
|
26
|
+
/// Safari does not set `relatedTarget` on `"dragleave"`, which makes it
|
|
27
|
+
/// impossible to determine whether a logical drag leave has happened with just
|
|
28
|
+
/// this event, so use function on `"dragenter"` to capture the `relatedTarget`.
|
|
29
|
+
pub fn dragenter_helper(callback: impl Fn() + 'static, target: NodeRef) -> Callback<DragEvent> {
|
|
30
|
+
Callback::from({
|
|
31
|
+
move |event: DragEvent| {
|
|
32
|
+
let r = (|| -> ApiResult<()> {
|
|
33
|
+
event.stop_propagation();
|
|
34
|
+
event.prevent_default();
|
|
35
|
+
if event.related_target().is_none() {
|
|
36
|
+
target
|
|
37
|
+
.cast::<HtmlElement>()
|
|
38
|
+
.into_apierror()?
|
|
39
|
+
.dataset()
|
|
40
|
+
.set("safaridragleave", "true")?;
|
|
41
|
+
}
|
|
42
|
+
Ok(())
|
|
43
|
+
})();
|
|
44
|
+
|
|
45
|
+
if let Err(e) = r {
|
|
46
|
+
web_sys::console::warn_1(&e.into());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
callback();
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// HTML drag/drop will fire a bubbling `dragleave` event over all children of a
|
|
55
|
+
/// `dragleave`-listened-to element, so we need to filter out the events from
|
|
56
|
+
/// the children elements with this esoteric DOM arcana.
|
|
57
|
+
pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Callback<DragEvent> {
|
|
58
|
+
Callback::from({
|
|
59
|
+
clone!(drag_ref);
|
|
60
|
+
move |event: DragEvent| {
|
|
61
|
+
let r = (|| -> ApiResult<()> {
|
|
62
|
+
event.stop_propagation();
|
|
63
|
+
event.prevent_default();
|
|
64
|
+
|
|
65
|
+
let mut related_target = event
|
|
66
|
+
.related_target()
|
|
67
|
+
.or_else(|| Some(JsValue::UNDEFINED.unchecked_into::<EventTarget>()))
|
|
68
|
+
.and_then(|x| x.dyn_into::<Element>().ok());
|
|
69
|
+
|
|
70
|
+
// This is a wild chrome bug. `dragleave` can fire with the `relatedTarget`
|
|
71
|
+
// property set to an element inside the closed `ShadowRoot` hosted by a
|
|
72
|
+
// browser-native `<select>` tag, which fails the `.contains()` check
|
|
73
|
+
// below. This mystery `ShadowRoot` has a structure that looks like this
|
|
74
|
+
// (tested in Chrome 92), which we try to detect as best we can below.
|
|
75
|
+
//
|
|
76
|
+
// ```html
|
|
77
|
+
// <div aria-hidden="true">Selected Text Here</siv>
|
|
78
|
+
// <slot name="user-agent-custom-assign-slot"></slot>
|
|
79
|
+
// ```
|
|
80
|
+
//
|
|
81
|
+
// This is pretty course though, since there is no guarantee this structure
|
|
82
|
+
// will be maintained in future Chrome versions; the `.expect()` in this
|
|
83
|
+
// method chain should at least warn us if this regresses.
|
|
84
|
+
//
|
|
85
|
+
// Wait - you don't believe me? Throw a debugger statement inside this
|
|
86
|
+
// conditional and drag a column over a pivot-mode active columns list.
|
|
87
|
+
if related_target
|
|
88
|
+
.as_ref()
|
|
89
|
+
.map(|x| x.has_attribute("aria-hidden"))
|
|
90
|
+
.unwrap_or_default()
|
|
91
|
+
{
|
|
92
|
+
related_target = Some(
|
|
93
|
+
related_target
|
|
94
|
+
.into_apierror()?
|
|
95
|
+
.parent_node()
|
|
96
|
+
.into_apierror()?
|
|
97
|
+
.dyn_ref::<ShadowRoot>()
|
|
98
|
+
.ok_or_else(|| JsValue::from("Chrome drag/drop bug detection failed"))?
|
|
99
|
+
.host()
|
|
100
|
+
.unchecked_into::<Element>(),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let current_target = drag_ref.cast::<HtmlElement>().unwrap();
|
|
105
|
+
match related_target {
|
|
106
|
+
Some(ref related) => {
|
|
107
|
+
// Due to virtual dom these events sometimes fire after
|
|
108
|
+
// the node is removed ...
|
|
109
|
+
if !current_target.contains(Some(related))
|
|
110
|
+
&& related.parent_element().is_some()
|
|
111
|
+
{
|
|
112
|
+
callback();
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
None => {
|
|
116
|
+
// Safari (OSX and iOS) don't set `relatedTarget`, so we need to
|
|
117
|
+
// read a memoized value from the `"dragenter"` event.
|
|
118
|
+
let dataset = current_target.dataset();
|
|
119
|
+
if dataset.get("safaridragleave").is_some() {
|
|
120
|
+
dataset.delete("safaridragleave");
|
|
121
|
+
} else {
|
|
122
|
+
callback();
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
Ok(())
|
|
127
|
+
})();
|
|
128
|
+
|
|
129
|
+
if let Err(e) = r {
|
|
130
|
+
web_sys::console::warn_1(&e.into());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[derive(Clone)]
|
|
137
|
+
pub struct DragDropContainer {
|
|
138
|
+
pub noderef: NodeRef,
|
|
139
|
+
pub dragenter: Callback<DragEvent>,
|
|
140
|
+
pub dragleave: Callback<DragEvent>,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
impl DragDropContainer {
|
|
144
|
+
pub fn new<F: Fn() + 'static, G: Fn() + 'static>(ondragenter: F, ondragleave: G) -> Self {
|
|
145
|
+
let noderef = NodeRef::default();
|
|
146
|
+
Self {
|
|
147
|
+
dragenter: dragenter_helper(ondragenter, noderef.clone()),
|
|
148
|
+
dragleave: dragleave_helper(ondragleave, noderef.clone()),
|
|
149
|
+
noderef,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// A really, really unfortunate hack that is needed to guarantee that `dragend`
|
|
155
|
+
/// is called even under aggressive DOM mutation after `dragstart` is fired.
|
|
156
|
+
pub(super) struct DragTargetState {
|
|
157
|
+
target: HtmlElement,
|
|
158
|
+
shadow_root: ShadowRoot,
|
|
159
|
+
alive: Rc<Cell<bool>>,
|
|
160
|
+
observer: IntersectionObserver,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
impl DragTargetState {
|
|
164
|
+
pub(super) fn new(host: HtmlElement, target: HtmlElement) -> Self {
|
|
165
|
+
let shadow_root = host.shadow_root().unwrap();
|
|
166
|
+
let alive = Rc::new(Cell::new(true));
|
|
167
|
+
let observer = IntersectionObserver::new(
|
|
168
|
+
&Closure::<dyn FnMut(js_sys::Array)>::new({
|
|
169
|
+
clone!(target, shadow_root, alive);
|
|
170
|
+
move |records: js_sys::Array| {
|
|
171
|
+
if !alive.get() {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for record in records.iter() {
|
|
176
|
+
let record: IntersectionObserverEntry = record.unchecked_into();
|
|
177
|
+
if !record.is_intersecting() {
|
|
178
|
+
shadow_root.append_child(&target).unwrap();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.into_js_value()
|
|
185
|
+
.unchecked_into(),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
observer.observe(target.as_ref());
|
|
189
|
+
Self {
|
|
190
|
+
target,
|
|
191
|
+
shadow_root,
|
|
192
|
+
alive,
|
|
193
|
+
observer,
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
impl Drop for DragTargetState {
|
|
199
|
+
fn drop(&mut self) {
|
|
200
|
+
self.alive.set(false);
|
|
201
|
+
self.observer.unobserve(&self.target);
|
|
202
|
+
if self.target.is_connected() {
|
|
203
|
+
let _ = self.shadow_root.remove_child(&self.target);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
use crate::presentation::OpenColumnSettings;
|
|
14
14
|
use crate::utils::PtrEqRc;
|
|
15
15
|
|
|
16
|
+
/// Value-semantic snapshot of the drag/drop state threaded through the
|
|
17
|
+
/// component tree for visual feedback (drag-highlight CSS classes).
|
|
18
|
+
#[derive(Clone, Debug, PartialEq, Default)]
|
|
19
|
+
pub struct DragDropProps {
|
|
20
|
+
/// Column name currently being dragged, if a drag is in progress.
|
|
21
|
+
pub column: Option<String>,
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
/// Value-semantic snapshot of the presentation/UI state used by the root
|
|
17
25
|
/// component to drive `is_settings_open`, `selected_theme`, and
|
|
18
26
|
/// `available_themes` into child components via plain props.
|
package/src/rust/presentation.rs
CHANGED
|
@@ -11,27 +11,28 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
mod column_locator;
|
|
14
|
+
pub mod drag_helpers;
|
|
14
15
|
mod props;
|
|
15
16
|
mod sheets;
|
|
16
17
|
|
|
17
18
|
use std::cell::RefCell;
|
|
18
|
-
use std::collections::
|
|
19
|
+
use std::collections::HashSet;
|
|
19
20
|
use std::ops::Deref;
|
|
20
21
|
use std::rc::Rc;
|
|
21
22
|
|
|
22
23
|
use async_lock::Mutex;
|
|
23
|
-
use perspective_js::utils
|
|
24
|
+
use perspective_js::utils::*;
|
|
25
|
+
use wasm_bindgen::prelude::*;
|
|
24
26
|
use web_sys::*;
|
|
25
27
|
use yew::html::ImplicitClone;
|
|
26
28
|
use yew::prelude::*;
|
|
27
29
|
|
|
28
30
|
pub use self::column_locator::{ColumnLocator, ColumnSettingsTab, ColumnTab, OpenColumnSettings};
|
|
29
|
-
|
|
30
|
-
use
|
|
31
|
+
use self::drag_helpers::DragTargetState;
|
|
32
|
+
pub use self::drag_helpers::{DragDropContainer, DragEndCallback};
|
|
33
|
+
pub use self::props::{DragDropProps, PresentationProps};
|
|
31
34
|
use crate::utils::*;
|
|
32
35
|
|
|
33
|
-
pub type ColumnConfigMap = HashMap<String, ColumnConfigValues>;
|
|
34
|
-
|
|
35
36
|
/// The available themes as detected in the browser environment or set
|
|
36
37
|
/// explicitly when CORS prevents detection. Detection is expensive and
|
|
37
38
|
/// typically must be performed only once, when `document.styleSheets` is
|
|
@@ -41,14 +42,71 @@ struct ThemeData {
|
|
|
41
42
|
themes: Option<Vec<String>>,
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
#[derive(Clone, Debug)]
|
|
46
|
+
struct DragFrom {
|
|
47
|
+
column: String,
|
|
48
|
+
effect: DragEffect,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug)]
|
|
52
|
+
struct DragOver {
|
|
53
|
+
target: DragTarget,
|
|
54
|
+
index: usize,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[derive(Debug, Default)]
|
|
58
|
+
enum DragState {
|
|
59
|
+
#[default]
|
|
60
|
+
NoDrag,
|
|
61
|
+
DragInProgress(DragFrom),
|
|
62
|
+
DragOverInProgress(DragFrom, DragOver),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl DragState {
|
|
66
|
+
const fn is_drag_in_progress(&self) -> bool {
|
|
67
|
+
!matches!(self, Self::NoDrag)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
/// Actual presentations tate struct with some fields hidden.
|
|
45
72
|
pub struct PresentationHandle {
|
|
46
73
|
viewer_elem: HtmlElement,
|
|
47
74
|
theme_data: Mutex<ThemeData>,
|
|
48
75
|
is_settings_open: RefCell<bool>,
|
|
49
76
|
open_column_settings: RefCell<OpenColumnSettings>,
|
|
50
|
-
columns_config: RefCell<ColumnConfigMap>,
|
|
51
77
|
is_workspace: RefCell<Option<bool>>,
|
|
78
|
+
|
|
79
|
+
/// Drag/drop in-progress state. Empty (`NoDrag`) when no user drag is
|
|
80
|
+
/// active. Mutated by `notify_drag_*` / `notify_drop`; read by component
|
|
81
|
+
/// CSS-class derivations (`is_dragover`, `get_drag_column`).
|
|
82
|
+
drag_state: RefCell<DragState>,
|
|
83
|
+
pub drop_received: PubSub<(String, DragTarget, DragEffect, usize)>,
|
|
84
|
+
|
|
85
|
+
/// Injected callback from the root component fired after a drag begins
|
|
86
|
+
/// (one frame later, to let the drag image latch). Replaces the former
|
|
87
|
+
/// `dragstart_received: PubSub` field on `DragDrop`.
|
|
88
|
+
pub on_dragstart: RefCell<Option<Callback<DragEffect>>>,
|
|
89
|
+
|
|
90
|
+
/// Injected callback from the root component fired when the drag ends,
|
|
91
|
+
/// regardless of drop outcome.
|
|
92
|
+
pub on_dragend: RefCell<Option<Callback<()>>>,
|
|
93
|
+
|
|
94
|
+
/// Host-level `dragend` listener closure, attached to `viewer_elem` to
|
|
95
|
+
/// guarantee `dragend` fires even when virtual DOM updates remove the
|
|
96
|
+
/// dragged element from the shadow tree.
|
|
97
|
+
host_dragend: RefCell<Option<DragEndCallback>>,
|
|
98
|
+
|
|
99
|
+
/// IntersectionObserver-based fallback for the drag image, kept alive for
|
|
100
|
+
/// the duration of the drag.
|
|
101
|
+
drag_target: RefCell<Option<DragTargetState>>,
|
|
102
|
+
|
|
103
|
+
/// Per-element dedup cell for `perspective-config-update` event
|
|
104
|
+
/// dispatch. Read+written by `crate::custom_events::dispatch_*`
|
|
105
|
+
/// helpers; living here means every consumer with a `&Presentation`
|
|
106
|
+
/// (subscriptions in `wire_custom_events`, `tasks::send_plugin_config`,
|
|
107
|
+
/// `setSelection`) sees the same cache without separate plumbing.
|
|
108
|
+
pub last_dispatched_config: RefCell<Option<crate::config::ViewerConfig>>,
|
|
109
|
+
|
|
52
110
|
pub settings_open_changed: PubSub<bool>,
|
|
53
111
|
|
|
54
112
|
/// Injected callback from the root component, replacing the former
|
|
@@ -58,6 +116,11 @@ pub struct PresentationHandle {
|
|
|
58
116
|
pub column_settings_open_changed: PubSub<(bool, Option<String>)>,
|
|
59
117
|
pub theme_config_updated: PubSub<(PtrEqRc<Vec<String>>, Option<usize>)>,
|
|
60
118
|
pub on_eject: PubSub<()>,
|
|
119
|
+
|
|
120
|
+
/// Fires for status-bar / main-panel pointer events that target the
|
|
121
|
+
/// statusbar element. `wire_custom_events` formats the `PointerEvent`'s
|
|
122
|
+
/// `type_()` into a `perspective-statusbar-{type}` `CustomEvent` name.
|
|
123
|
+
pub statusbar_pointer_event: PubSub<PointerEvent>,
|
|
61
124
|
}
|
|
62
125
|
|
|
63
126
|
/// State object responsible for the non-persistable/gui element state,
|
|
@@ -91,17 +154,28 @@ impl Presentation {
|
|
|
91
154
|
settings_before_open_changed: Default::default(),
|
|
92
155
|
column_settings_open_changed: Default::default(),
|
|
93
156
|
on_is_workspace_changed: Default::default(),
|
|
94
|
-
columns_config: Default::default(),
|
|
95
157
|
is_settings_open: Default::default(),
|
|
96
158
|
open_column_settings: Default::default(),
|
|
97
159
|
theme_config_updated: PubSub::default(),
|
|
98
160
|
on_eject: PubSub::default(),
|
|
161
|
+
statusbar_pointer_event: PubSub::default(),
|
|
162
|
+
last_dispatched_config: Default::default(),
|
|
163
|
+
drag_state: Default::default(),
|
|
164
|
+
drop_received: Default::default(),
|
|
165
|
+
on_dragstart: Default::default(),
|
|
166
|
+
on_dragend: Default::default(),
|
|
167
|
+
host_dragend: Default::default(),
|
|
168
|
+
drag_target: Default::default(),
|
|
99
169
|
}));
|
|
100
170
|
|
|
101
171
|
ApiFuture::spawn(theme.clone().init());
|
|
102
172
|
theme
|
|
103
173
|
}
|
|
104
174
|
|
|
175
|
+
pub fn viewer_elem(&self) -> &HtmlElement {
|
|
176
|
+
&self.viewer_elem
|
|
177
|
+
}
|
|
178
|
+
|
|
105
179
|
pub fn is_visible(&self) -> bool {
|
|
106
180
|
self.viewer_elem
|
|
107
181
|
.offset_parent()
|
|
@@ -276,51 +350,192 @@ impl Presentation {
|
|
|
276
350
|
Ok(true)
|
|
277
351
|
}
|
|
278
352
|
|
|
279
|
-
///
|
|
280
|
-
|
|
281
|
-
|
|
353
|
+
/// Snapshot the drag state as a [`DragDropProps`] value for threading
|
|
354
|
+
/// through the component tree without PubSub subscriptions.
|
|
355
|
+
pub fn drag_drop_props(&self) -> DragDropProps {
|
|
356
|
+
DragDropProps {
|
|
357
|
+
column: self.get_drag_column(),
|
|
358
|
+
}
|
|
282
359
|
}
|
|
283
360
|
|
|
284
|
-
|
|
285
|
-
|
|
361
|
+
/// Get the column name currently being drag/dropped.
|
|
362
|
+
pub fn get_drag_column(&self) -> Option<String> {
|
|
363
|
+
match *self.drag_state.borrow() {
|
|
364
|
+
DragState::DragInProgress(DragFrom { ref column, .. })
|
|
365
|
+
| DragState::DragOverInProgress(DragFrom { ref column, .. }, _) => Some(column.clone()),
|
|
366
|
+
_ => None,
|
|
367
|
+
}
|
|
286
368
|
}
|
|
287
369
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
370
|
+
pub fn get_drag_target(&self) -> Option<DragTarget> {
|
|
371
|
+
match *self.drag_state.borrow() {
|
|
372
|
+
DragState::DragInProgress(DragFrom {
|
|
373
|
+
effect: DragEffect::Move(target),
|
|
374
|
+
..
|
|
375
|
+
})
|
|
376
|
+
| DragState::DragOverInProgress(
|
|
377
|
+
DragFrom {
|
|
378
|
+
effect: DragEffect::Move(target),
|
|
379
|
+
..
|
|
380
|
+
},
|
|
381
|
+
_,
|
|
382
|
+
) => Some(target),
|
|
383
|
+
_ => None,
|
|
384
|
+
}
|
|
291
385
|
}
|
|
292
386
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
let mut config = self.columns_config.borrow_mut();
|
|
298
|
-
*config = HashMap::default()
|
|
299
|
-
},
|
|
300
|
-
crate::config::OptionalUpdate::Missing => {},
|
|
301
|
-
crate::config::OptionalUpdate::Update(update) => {
|
|
302
|
-
for (col_name, new_config) in update.into_iter() {
|
|
303
|
-
self.columns_config
|
|
304
|
-
.borrow_mut()
|
|
305
|
-
.insert(col_name, new_config);
|
|
306
|
-
}
|
|
307
|
-
},
|
|
387
|
+
pub fn set_drag_image(&self, event: &DragEvent) -> ApiResult<()> {
|
|
388
|
+
event.stop_propagation();
|
|
389
|
+
if let Some(dt) = event.data_transfer() {
|
|
390
|
+
dt.set_drop_effect("move");
|
|
308
391
|
}
|
|
392
|
+
|
|
393
|
+
let original: HtmlElement = event.target().into_apierror()?.unchecked_into();
|
|
394
|
+
let elem: HtmlElement = original
|
|
395
|
+
.children()
|
|
396
|
+
.get_with_index(0)
|
|
397
|
+
.unwrap()
|
|
398
|
+
.clone_node_with_deep(true)?
|
|
399
|
+
.unchecked_into();
|
|
400
|
+
|
|
401
|
+
elem.class_list().toggle("snap-drag-image")?;
|
|
402
|
+
original.append_child(&elem)?;
|
|
403
|
+
event.data_transfer().into_apierror()?.set_drag_image(
|
|
404
|
+
&elem,
|
|
405
|
+
event.offset_x(),
|
|
406
|
+
event.offset_y(),
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
*self.drag_target.borrow_mut() = Some(DragTargetState::new(
|
|
410
|
+
self.viewer_elem.clone(),
|
|
411
|
+
original.clone(),
|
|
412
|
+
));
|
|
413
|
+
|
|
414
|
+
// Drag image does not register correctly unless we wait.
|
|
415
|
+
ApiFuture::spawn(async move {
|
|
416
|
+
request_animation_frame().await;
|
|
417
|
+
original.remove_child(&elem)?;
|
|
418
|
+
Ok(())
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
Ok(())
|
|
309
422
|
}
|
|
310
423
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
424
|
+
/// Is the drag/drop state currently in `action`?
|
|
425
|
+
pub fn is_dragover(&self, drag_target: DragTarget) -> Option<(usize, String)> {
|
|
426
|
+
match *self.drag_state.borrow() {
|
|
427
|
+
DragState::DragOverInProgress(
|
|
428
|
+
DragFrom { ref column, .. },
|
|
429
|
+
DragOver { target, index },
|
|
430
|
+
) if target == drag_target => Some((index, column.clone())),
|
|
431
|
+
_ => None,
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
pub fn notify_drop(&self, event: &DragEvent) {
|
|
436
|
+
event.prevent_default();
|
|
437
|
+
event.stop_propagation();
|
|
438
|
+
|
|
439
|
+
let action = match &*self.drag_state.borrow() {
|
|
440
|
+
DragState::DragOverInProgress(
|
|
441
|
+
DragFrom { column, effect },
|
|
442
|
+
DragOver { target, index },
|
|
443
|
+
) => Some((column.to_string(), *target, *effect, *index)),
|
|
444
|
+
_ => None,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
self.drag_target.borrow_mut().take();
|
|
448
|
+
*self.drag_state.borrow_mut() = DragState::NoDrag;
|
|
449
|
+
if let Some(action) = action {
|
|
450
|
+
self.drop_received.emit(action);
|
|
321
451
|
}
|
|
322
452
|
}
|
|
323
453
|
|
|
454
|
+
/// Start the drag/drop action with the name of the column being dragged.
|
|
455
|
+
pub fn notify_drag_start(&self, column: String, effect: DragEffect) {
|
|
456
|
+
*self.drag_state.borrow_mut() = DragState::DragInProgress(DragFrom { column, effect });
|
|
457
|
+
self.register_host_dragend();
|
|
458
|
+
let emit = self.on_dragstart.borrow().clone();
|
|
459
|
+
ApiFuture::spawn(async move {
|
|
460
|
+
request_animation_frame().await;
|
|
461
|
+
if let Some(cb) = emit {
|
|
462
|
+
cb.emit(effect);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
Ok(())
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/// End the drag/drop action by resetting the state to default.
|
|
470
|
+
pub fn notify_drag_end(&self) {
|
|
471
|
+
if self.drag_state.borrow().is_drag_in_progress() {
|
|
472
|
+
self.drag_target.borrow_mut().take();
|
|
473
|
+
*self.drag_state.borrow_mut() = DragState::NoDrag;
|
|
474
|
+
if let Some(cb) = self.on_dragend.borrow().as_ref() {
|
|
475
|
+
cb.emit(());
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/// Register a `dragend` listener on the host `<perspective-viewer>`
|
|
481
|
+
/// element so that drag-end cleanup fires even when Yew re-renders
|
|
482
|
+
/// remove the original dragged element from the shadow DOM. The host
|
|
483
|
+
/// element is outside the virtual DOM and therefore stable.
|
|
484
|
+
fn register_host_dragend(&self) {
|
|
485
|
+
if let Some(prev) = self.host_dragend.borrow_mut().take() {
|
|
486
|
+
let _ = self
|
|
487
|
+
.viewer_elem
|
|
488
|
+
.remove_event_listener_with_callback("dragend", prev.as_ref().unchecked_ref());
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let this = self.clone();
|
|
492
|
+
let closure = Closure::wrap(Box::new(move |_event: DragEvent| {
|
|
493
|
+
this.notify_drag_end();
|
|
494
|
+
}) as Box<dyn FnMut(DragEvent)>);
|
|
495
|
+
|
|
496
|
+
self.viewer_elem
|
|
497
|
+
.add_event_listener_with_callback("dragend", closure.as_ref().unchecked_ref())
|
|
498
|
+
.unwrap();
|
|
499
|
+
|
|
500
|
+
*self.host_dragend.borrow_mut() = Some(closure);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/// Leave the `action` zone.
|
|
504
|
+
pub fn notify_drag_leave(&self, drag_target: DragTarget) {
|
|
505
|
+
let reset = match *self.drag_state.borrow() {
|
|
506
|
+
DragState::DragOverInProgress(
|
|
507
|
+
DragFrom { ref column, effect },
|
|
508
|
+
DragOver { target, .. },
|
|
509
|
+
) if target == drag_target => Some((column.clone(), effect)),
|
|
510
|
+
_ => None,
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
if let Some((column, effect)) = reset {
|
|
514
|
+
self.notify_drag_start(column, effect);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/// Enter the `action` zone at `index`, which must be <= the number of
|
|
519
|
+
/// children in the container.
|
|
520
|
+
pub fn notify_drag_enter(&self, target: DragTarget, index: usize) -> bool {
|
|
521
|
+
let mut drag_state = self.drag_state.borrow_mut();
|
|
522
|
+
let should_render = match &*drag_state {
|
|
523
|
+
DragState::DragOverInProgress(_, drag_to) => {
|
|
524
|
+
drag_to.target != target || drag_to.index != index
|
|
525
|
+
},
|
|
526
|
+
_ => true,
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
*drag_state = match &*drag_state {
|
|
530
|
+
DragState::DragOverInProgress(drag_from, _) | DragState::DragInProgress(drag_from) => {
|
|
531
|
+
DragState::DragOverInProgress(drag_from.clone(), DragOver { target, index })
|
|
532
|
+
},
|
|
533
|
+
_ => DragState::NoDrag,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
should_render
|
|
537
|
+
}
|
|
538
|
+
|
|
324
539
|
/// Snapshot the current presentation state as a [`PresentationProps`]
|
|
325
540
|
/// value suitable for passing as a Yew prop. Called by the root component
|
|
326
541
|
/// whenever a presentation-related PubSub event fires.
|