@perspective-dev/viewer 5.0.0 → 5.2.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 +9 -9
- 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/docs/perspective-docs.json +1 -0
- package/dist/esm/column-format.d.ts +19 -28
- 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 +4 -4
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/providers.d.ts +79 -0
- package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
- package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
- package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
- package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
- package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
- package/dist/esm/ts-rs/Notation.d.ts +17 -0
- package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
- package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
- package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
- package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
- package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
- package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/Unit.d.ts +1 -0
- package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
- package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +72 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +47 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
- package/dist/wasm/perspective-viewer.d.ts +222 -26
- package/dist/wasm/perspective-viewer.js +378 -125
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
- package/package.json +2 -1
- package/src/css/_viewer_bundle.css +1 -0
- package/src/css/column-selector.css +9 -0
- package/src/css/column-settings-panel.css +67 -2
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +60 -4
- package/src/css/containers/context-menu.css +12 -6
- package/src/css/containers/tabs.css +2 -2
- package/src/css/form/code-editor.css +2 -3
- package/src/css/form/mirrored-textarea.css +81 -0
- package/src/css/viewer.css +336 -0
- package/src/rust/agent/client/mod.rs +19 -0
- package/src/rust/agent/client/protocol.rs +320 -0
- package/src/rust/agent/client/transport.rs +287 -0
- package/src/rust/agent/client/turn.rs +174 -0
- package/src/rust/agent/config.rs +159 -0
- package/src/rust/agent/docs.rs +293 -0
- package/src/rust/agent/markdown/block.rs +509 -0
- package/src/rust/agent/markdown/entity.rs +52 -0
- package/src/rust/agent/markdown/events.rs +71 -0
- package/src/rust/agent/markdown/inline.rs +497 -0
- package/src/rust/agent/markdown/mod.rs +466 -0
- package/src/rust/agent/mod.rs +23 -0
- package/src/rust/agent/preamble.md +16 -0
- package/src/rust/agent/runtime.rs +201 -0
- package/src/rust/agent/slot.rs +236 -0
- package/src/rust/agent/tools.rs +1110 -0
- package/src/rust/components/chat_panel.rs +289 -0
- package/src/rust/components/column_selector/active_column.rs +21 -27
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +83 -8
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/inactive_column.rs +8 -1
- package/src/rust/components/column_selector.rs +27 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +110 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +6 -0
- package/src/rust/components/editable_header.rs +2 -0
- package/src/rust/components/form/code_editor.rs +14 -23
- package/src/rust/components/form/debug.rs +8 -5
- package/src/rust/components/form/mirrored_textarea.rs +165 -0
- package/src/rust/components/form/mod.rs +1 -0
- package/src/rust/components/main_panel.rs +5 -0
- package/src/rust/components/mod.rs +3 -0
- package/src/rust/components/panel_menu.rs +7 -0
- package/src/rust/components/panel_tab.rs +5 -0
- package/src/rust/components/portal.rs +7 -0
- package/src/rust/components/settings_panel.rs +80 -2
- package/src/rust/components/viewer/msg.rs +2 -0
- package/src/rust/components/viewer/panels.rs +9 -14
- package/src/rust/components/viewer/render.rs +14 -0
- package/src/rust/components/viewer/settings.rs +33 -1
- package/src/rust/components/viewer.rs +2 -0
- package/src/rust/components/window_editor.rs +985 -0
- package/src/rust/config/datetime_column_style/custom.rs +17 -0
- package/src/rust/config/datetime_column_style/simple.rs +10 -0
- package/src/rust/config/number_string_format.rs +33 -0
- package/src/rust/config/options.rs +18 -0
- package/src/rust/config/plugin_static_config.rs +120 -5
- package/src/rust/config/viewer_config.rs +220 -1
- package/src/rust/config/workspace_config.rs +7 -6
- package/src/rust/custom_elements/viewer.rs +209 -18
- package/src/rust/custom_events.rs +29 -0
- package/src/rust/lib.rs +17 -1
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +8 -0
- package/src/rust/queries/columns_iter_set.rs +7 -1
- package/src/rust/queries/get_viewer_config.rs +5 -1
- package/src/rust/renderer/dispatch.rs +85 -58
- package/src/rust/renderer.rs +34 -1
- package/src/rust/session/column_defaults_update.rs +18 -0
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +64 -9
- package/src/rust/session/replace_expression_update.rs +2 -0
- package/src/rust/session.rs +7 -5
- package/src/rust/tasks/create_panel.rs +18 -8
- package/src/rust/tasks/edit_window.rs +155 -0
- package/src/rust/tasks/mod.rs +2 -0
- package/src/rust/tasks/restore_panel.rs +34 -17
- package/src/rust/tasks/sync_update_panels.rs +7 -23
- package/src/rust/utils/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/debounce.rs +76 -27
- package/src/rust/workspace.rs +112 -20
- package/src/svg/pin-icon.svg +5 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/de.css +23 -0
- package/src/themes/intl/es.css +23 -0
- package/src/themes/intl/fr.css +23 -0
- package/src/themes/intl/ja.css +23 -0
- package/src/themes/intl/pt.css +23 -0
- package/src/themes/intl/zh.css +23 -0
- package/src/themes/intl.css +26 -1
- package/src/ts/column-format.ts +67 -46
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/providers.ts +96 -0
- package/src/ts/ts-rs/CompactDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyCode.ts +3 -0
- package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
- package/src/ts/ts-rs/CurrencySign.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
- package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
- package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
- package/src/ts/ts-rs/FormatMode.ts +3 -0
- package/src/ts/ts-rs/FormatUnit.ts +3 -0
- package/src/ts/ts-rs/Notation.ts +11 -0
- package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +48 -1
- package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
- package/src/ts/ts-rs/RestoreOptions.ts +20 -0
- package/src/ts/ts-rs/RoundingMode.ts +3 -0
- package/src/ts/ts-rs/RoundingPriority.ts +3 -0
- package/src/ts/ts-rs/SignDisplay.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
- package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
- package/src/ts/ts-rs/StringColorMode.ts +3 -0
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
- package/src/ts/ts-rs/Unit.ts +3 -0
- package/src/ts/ts-rs/UnitDisplay.ts +3 -0
- package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
- package/src/ts/ts-rs/UseGrouping.ts +3 -0
- package/src/ts/ts-rs/ViewerConfig.ts +48 -1
- package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +81 -2
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +47 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
|
@@ -52,6 +52,31 @@ where
|
|
|
52
52
|
|
|
53
53
|
#[prop_or_default]
|
|
54
54
|
pub allow_duplicates: bool,
|
|
55
|
+
|
|
56
|
+
/// Single-slot mode: the list holds at most one item, a dragover
|
|
57
|
+
/// preview REPLACES the current item rather than inserting beside it,
|
|
58
|
+
/// and the trailing `EmptyColumn` autocomplete renders only while the
|
|
59
|
+
/// slot is empty.
|
|
60
|
+
#[prop_or_default]
|
|
61
|
+
pub single_slot: bool,
|
|
62
|
+
|
|
63
|
+
/// The plugin-declared visual role this slot fills, e.g. `"X Axis"`
|
|
64
|
+
/// for a `Y Line`'s `group_by` (see `PluginStaticConfig`). Rendered
|
|
65
|
+
/// as the slot's label THROUGH the intl indirection: the role is a
|
|
66
|
+
/// key, not display text, so `--psp-label--role--x-axis--content`
|
|
67
|
+
/// supplies the words and a language variant can override them. A
|
|
68
|
+
/// role with no such label falls back to the declared English, and
|
|
69
|
+
/// no role at all falls back to the slot's own generic label.
|
|
70
|
+
#[prop_or_default]
|
|
71
|
+
pub role_label: Option<AttrValue>,
|
|
72
|
+
|
|
73
|
+
/// The in-flight drag is INVALID for this list (a parent-defined rule,
|
|
74
|
+
/// e.g. the window editor's Table-columns-only slots): the dragover
|
|
75
|
+
/// preview is suppressed and the invalid-X overlay renders instead,
|
|
76
|
+
/// like a duplicate drag over `group_by`/`split_by`. The parent's drop
|
|
77
|
+
/// handler is still responsible for ignoring the drop itself.
|
|
78
|
+
#[prop_or_default]
|
|
79
|
+
pub is_invalid: bool,
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
impl<T, U> PartialEq for DragDropListProps<T, U>
|
|
@@ -66,6 +91,9 @@ where
|
|
|
66
91
|
&& self.allow_duplicates == other.allow_duplicates
|
|
67
92
|
&& self.is_dragover == other.is_dragover
|
|
68
93
|
&& self.disabled == other.disabled
|
|
94
|
+
&& self.single_slot == other.single_slot
|
|
95
|
+
&& self.is_invalid == other.is_invalid
|
|
96
|
+
&& self.role_label == other.role_label
|
|
69
97
|
}
|
|
70
98
|
}
|
|
71
99
|
|
|
@@ -202,7 +230,51 @@ where
|
|
|
202
230
|
let invalid_drag: bool;
|
|
203
231
|
let mut valid_duplicate_drag = false;
|
|
204
232
|
|
|
205
|
-
let columns_html = {
|
|
233
|
+
let columns_html = if ctx.props().single_slot {
|
|
234
|
+
invalid_drag = ctx.props().is_invalid && ctx.props().is_dragover.is_some();
|
|
235
|
+
|
|
236
|
+
// Dragging the slot's own pill over its own slot is a no-op
|
|
237
|
+
// move - keep showing the pill instead of the drop preview
|
|
238
|
+
// (mirrors the multi-column branch's `is_self_move` handling).
|
|
239
|
+
let is_self_move = ctx
|
|
240
|
+
.props()
|
|
241
|
+
.presentation
|
|
242
|
+
.get_drag_target()
|
|
243
|
+
.map(|x| V::is_self_move(x))
|
|
244
|
+
.unwrap_or_default();
|
|
245
|
+
|
|
246
|
+
let close = ctx.props().parent.callback(|_| V::close(0));
|
|
247
|
+
let dragenter = ctx.props().parent.callback({
|
|
248
|
+
let container_noderef = container_noderef.clone();
|
|
249
|
+
move |event: DragEvent| {
|
|
250
|
+
event.stop_propagation();
|
|
251
|
+
event.prevent_default();
|
|
252
|
+
if event.related_target().is_none()
|
|
253
|
+
&& let Some(elem) = container_noderef.cast::<HtmlElement>()
|
|
254
|
+
{
|
|
255
|
+
let _ = elem.dataset().set("safaridragleave", "true");
|
|
256
|
+
}
|
|
257
|
+
V::dragenter(0)
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if ctx.props().is_dragover.is_some() && !is_self_move && !invalid_drag {
|
|
262
|
+
html! {
|
|
263
|
+
<div class="pivot-column" ondragenter={dragenter}>
|
|
264
|
+
<div class="config-drop" />
|
|
265
|
+
</div>
|
|
266
|
+
}
|
|
267
|
+
} else if let Some(column) = ctx.props().children.iter().next() {
|
|
268
|
+
html! {
|
|
269
|
+
<div class="pivot-column" ondragenter={dragenter}>
|
|
270
|
+
{ Html::from(column) }
|
|
271
|
+
<span class="row_close" onmousedown={close} />
|
|
272
|
+
</div>
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
html! {}
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
206
278
|
let mut columns = ctx
|
|
207
279
|
.props()
|
|
208
280
|
.children
|
|
@@ -211,7 +283,10 @@ where
|
|
|
211
283
|
.enumerate()
|
|
212
284
|
.collect::<Vec<_>>();
|
|
213
285
|
|
|
214
|
-
invalid_drag = if
|
|
286
|
+
invalid_drag = if ctx.props().is_invalid && ctx.props().is_dragover.is_some() {
|
|
287
|
+
// Parent-defined invalidity: no preview, X overlay only.
|
|
288
|
+
true
|
|
289
|
+
} else if let Some((x, column)) = &ctx.props().is_dragover {
|
|
215
290
|
let index = *x;
|
|
216
291
|
let is_append = index == columns.len();
|
|
217
292
|
let is_self_move = ctx
|
|
@@ -314,16 +389,48 @@ where
|
|
|
314
389
|
.collect::<Html>()
|
|
315
390
|
};
|
|
316
391
|
|
|
392
|
+
let show_empty = if ctx.props().single_slot {
|
|
393
|
+
ctx.props().children.is_empty() && ctx.props().is_dragover.is_none()
|
|
394
|
+
} else {
|
|
395
|
+
ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag)
|
|
396
|
+
};
|
|
397
|
+
|
|
317
398
|
let column_dropdown = ctx.props().column_dropdown.clone();
|
|
318
399
|
let exclude = ctx.props().exclude.clone();
|
|
319
400
|
let on_select = ctx.props().parent.callback(V::create);
|
|
320
401
|
let class = classes!("rrow");
|
|
321
402
|
let is_enabled = true;
|
|
322
403
|
|
|
404
|
+
// The role is data from the plugin, so the var NAME is built
|
|
405
|
+
// here while the var VALUE stays in the stylesheets - custom
|
|
406
|
+
// properties inherit, so the label's `:before` picks it up.
|
|
407
|
+
let role_style = ctx.props().role_label.as_ref().map(|role| {
|
|
408
|
+
let slug = role
|
|
409
|
+
.chars()
|
|
410
|
+
.map(|x| {
|
|
411
|
+
if x.is_ascii_alphanumeric() {
|
|
412
|
+
x.to_ascii_lowercase()
|
|
413
|
+
} else {
|
|
414
|
+
'-'
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
.collect::<String>();
|
|
418
|
+
|
|
419
|
+
// The second var is a PRESENCE FLAG: CSS cannot ask whether a
|
|
420
|
+
// custom property is set, so the slot's stylesheet reads it as
|
|
421
|
+
// the secondary label's `display` and gets `none` by fallback
|
|
422
|
+
// when no role was declared.
|
|
423
|
+
format!(
|
|
424
|
+
"--psp-label--pivot--content: var(--psp-label--role--{slug}--content, \
|
|
425
|
+
\"{role}\"); --psp-label--pivot-secondary--display: inline-block"
|
|
426
|
+
)
|
|
427
|
+
});
|
|
428
|
+
|
|
323
429
|
html! {
|
|
324
430
|
<div ref={&self.elem} {class}>
|
|
325
431
|
<div
|
|
326
432
|
id={ctx.props().name}
|
|
433
|
+
style={role_style}
|
|
327
434
|
ondragover={is_enabled.then_some(dragover)}
|
|
328
435
|
ondragenter={is_enabled.then_some(drag_container.dragenter)}
|
|
329
436
|
ondragleave={is_enabled.then_some(drag_container.dragleave)}
|
|
@@ -345,7 +452,7 @@ where
|
|
|
345
452
|
onmousedown={ctx.props().parent.callback(move |_| V::close(0))}
|
|
346
453
|
/>
|
|
347
454
|
</div>
|
|
348
|
-
} else if
|
|
455
|
+
} else if show_empty {
|
|
349
456
|
<EmptyColumn {column_dropdown} {exclude} {on_select} />
|
|
350
457
|
} else if invalid_drag {
|
|
351
458
|
<InvalidColumn />
|
|
@@ -11,14 +11,17 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
use perspective_client::clone;
|
|
14
|
-
use
|
|
14
|
+
use wasm_bindgen::JsCast;
|
|
15
|
+
use wasm_bindgen::prelude::Closure;
|
|
16
|
+
use web_sys::HtmlElement;
|
|
15
17
|
use yew::{
|
|
16
|
-
Callback, Children, Html, Properties, function_component, html, use_effect_with,
|
|
17
|
-
|
|
18
|
+
Callback, Children, Html, Properties, function_component, html, use_effect_with, use_mut_ref,
|
|
19
|
+
use_node_ref,
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
use crate::components::containers::sidebar_close_button::SidebarCloseButton;
|
|
21
23
|
use crate::components::editable_header::{EditableHeader, EditableHeaderProps};
|
|
24
|
+
use crate::js::{ResizeObserver, ResizeObserverEntry};
|
|
22
25
|
|
|
23
26
|
#[derive(PartialEq, Clone, Properties)]
|
|
24
27
|
pub struct SidebarProps {
|
|
@@ -31,6 +34,28 @@ pub struct SidebarProps {
|
|
|
31
34
|
pub width_override: Option<i32>,
|
|
32
35
|
pub selected_tab: Option<usize>,
|
|
33
36
|
pub header_props: EditableHeaderProps,
|
|
37
|
+
|
|
38
|
+
/// Trap-door width shared across this sidebar's tabs: the lifted
|
|
39
|
+
/// running max of the widths this component reports through
|
|
40
|
+
/// `on_auto_width`. Held by the parent (ultimately
|
|
41
|
+
/// `PerspectiveViewer`'s geometry state, like the settings panel's
|
|
42
|
+
/// Query/Plugin/Debug trap-door) so it survives tab switches AND
|
|
43
|
+
/// sidebar re-mounts, and clears on divider reset.
|
|
44
|
+
#[prop_or_default]
|
|
45
|
+
pub auto_width: f64,
|
|
46
|
+
|
|
47
|
+
/// Fires with the sidebar's rendered width after each render; the
|
|
48
|
+
/// owner keeps the running max threaded back as `auto_width`.
|
|
49
|
+
#[prop_or_default]
|
|
50
|
+
pub on_auto_width: Callback<f64>,
|
|
51
|
+
|
|
52
|
+
/// Pinned state for the header's pin toggle; the button renders only
|
|
53
|
+
/// when `on_toggle_pin` is provided.
|
|
54
|
+
#[prop_or_default]
|
|
55
|
+
pub is_pinned: bool,
|
|
56
|
+
|
|
57
|
+
#[prop_or_default]
|
|
58
|
+
pub on_toggle_pin: Option<Callback<()>>,
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
/// Sidebars are designed to live in a
|
|
@@ -39,33 +64,85 @@ pub struct SidebarProps {
|
|
|
39
64
|
pub fn Sidebar(p: &SidebarProps) -> Html {
|
|
40
65
|
let id = &p.id_prefix;
|
|
41
66
|
let noderef = use_node_ref();
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
67
|
+
|
|
68
|
+
// The trap-door reports the sidebar's rendered width to its owner via
|
|
69
|
+
// a `ResizeObserver` on the sidebar element, NOT a render effect:
|
|
70
|
+
// width changes are driven by DOM mutations anywhere in the tab
|
|
71
|
+
// subtree (e.g. the window editor staging a long column name into a
|
|
72
|
+
// slot), which need not re-render this component at all. The observer
|
|
73
|
+
// sees every one. `contentRect` (not the border box) is load-bearing:
|
|
74
|
+
// the sizer below is a content child, so ratcheting the border box
|
|
75
|
+
// would feed any sidebar padding back into unbounded growth. A manual
|
|
76
|
+
// divider drag (`width_override`) disables the ratchet until the
|
|
77
|
+
// divider resets.
|
|
78
|
+
let live_props = use_mut_ref(|| (Callback::<f64>::default(), None::<i32>));
|
|
79
|
+
*live_props.borrow_mut() = (p.on_auto_width.clone(), p.width_override);
|
|
80
|
+
use_effect_with((), {
|
|
81
|
+
clone!(noderef, live_props);
|
|
82
|
+
move |_| {
|
|
83
|
+
let closure =
|
|
84
|
+
Closure::<dyn FnMut(js_sys::Array)>::new(move |entries: js_sys::Array| {
|
|
85
|
+
let (on_auto_width, width_override) = live_props.borrow().clone();
|
|
86
|
+
if width_override.is_none() {
|
|
87
|
+
for entry in entries.iter() {
|
|
88
|
+
let entry: ResizeObserverEntry = entry.unchecked_into();
|
|
89
|
+
on_auto_width.emit(entry.content_rect().width());
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let observer = ResizeObserver::new(closure.as_ref().unchecked_ref());
|
|
95
|
+
let elem = noderef.cast::<HtmlElement>();
|
|
96
|
+
if let Some(elem) = &elem {
|
|
97
|
+
observer.observe(elem);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
move || {
|
|
101
|
+
if let Some(elem) = &elem {
|
|
102
|
+
observer.unobserve(elem);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
drop(closure);
|
|
59
106
|
}
|
|
60
107
|
}
|
|
61
108
|
});
|
|
62
109
|
|
|
63
|
-
let
|
|
110
|
+
let auto_width = if p.width_override.is_none() {
|
|
111
|
+
p.auto_width
|
|
112
|
+
} else {
|
|
113
|
+
0.0
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let width_style = format!("min-width: 200px; width: {}px", auto_width);
|
|
117
|
+
let pin_button = p.on_toggle_pin.as_ref().map(|cb| {
|
|
118
|
+
let onclick = {
|
|
119
|
+
let cb = cb.clone();
|
|
120
|
+
Callback::from(move |_: web_sys::MouseEvent| cb.emit(()))
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
let mut class = yew::classes!("sidebar_pin_button");
|
|
124
|
+
if p.is_pinned {
|
|
125
|
+
class.push("is-pinned");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
html! {
|
|
129
|
+
<span
|
|
130
|
+
id={format!("{id}_pin_button")}
|
|
131
|
+
{class}
|
|
132
|
+
title={if p.is_pinned { "Unpin" } else { "Pin" }}
|
|
133
|
+
{onclick}
|
|
134
|
+
/>
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
64
138
|
html! {
|
|
65
139
|
<>
|
|
66
140
|
<SidebarCloseButton id={format!("{id}_close_button")} on_close_sidebar={&p.on_close} />
|
|
67
141
|
<div class="sidebar_column" id={format!("{id}_sidebar")} ref={noderef}>
|
|
68
|
-
<div class="sidebar_header"
|
|
142
|
+
<div class="sidebar_header">
|
|
143
|
+
<EditableHeader ..p.header_props.clone() />
|
|
144
|
+
{ pin_button }
|
|
145
|
+
</div>
|
|
69
146
|
<div class="sidebar_border" id={format!("{id}_border")} />
|
|
70
147
|
{ p.children.iter().collect::<Html>() }
|
|
71
148
|
<div class="sidebar-auto-width" style={width_style} />
|
|
@@ -26,6 +26,10 @@ pub struct SplitPanelProps {
|
|
|
26
26
|
#[prop_or_default]
|
|
27
27
|
pub id: Option<String>,
|
|
28
28
|
|
|
29
|
+
/// Extra classes on the root element (ignored under `no_wrap`).
|
|
30
|
+
#[prop_or_default]
|
|
31
|
+
pub class: Classes,
|
|
32
|
+
|
|
29
33
|
#[prop_or_default]
|
|
30
34
|
pub orientation: Orientation,
|
|
31
35
|
|
|
@@ -110,6 +114,7 @@ impl SplitPanelProps {
|
|
|
110
114
|
impl PartialEq for SplitPanelProps {
|
|
111
115
|
fn eq(&self, other: &Self) -> bool {
|
|
112
116
|
self.id == other.id
|
|
117
|
+
&& self.class == other.class
|
|
113
118
|
&& self.children == other.children
|
|
114
119
|
&& self.orientation == other.orientation
|
|
115
120
|
&& self.reverse == other.reverse
|
|
@@ -305,6 +310,7 @@ impl Component for SplitPanel {
|
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
let mut classes = classes!("split-panel");
|
|
313
|
+
classes.extend(ctx.props().class.clone());
|
|
308
314
|
if orientation == Orientation::Vertical {
|
|
309
315
|
classes.push("orient-vertical");
|
|
310
316
|
}
|
|
@@ -119,10 +119,12 @@ impl Component for EditableHeader {
|
|
|
119
119
|
}
|
|
120
120
|
let metadata = &ctx.props().metadata;
|
|
121
121
|
let expressions = metadata.get_expression_columns();
|
|
122
|
+
let windows = metadata.get_window_columns();
|
|
122
123
|
let found = metadata
|
|
123
124
|
.get_table_columns()?
|
|
124
125
|
.iter()
|
|
125
126
|
.chain(expressions)
|
|
127
|
+
.chain(windows)
|
|
126
128
|
.contains(&new_value);
|
|
127
129
|
Some(!found)
|
|
128
130
|
})()
|
|
@@ -18,6 +18,7 @@ use web_sys::*;
|
|
|
18
18
|
use yew::prelude::*;
|
|
19
19
|
|
|
20
20
|
use crate::components::form::highlight::highlight;
|
|
21
|
+
use crate::components::form::mirrored_textarea::MirroredTextarea;
|
|
21
22
|
use crate::components::function_dropdown::{FunctionDropDownElement, FunctionDropDownPortal};
|
|
22
23
|
use crate::exprtk::{Cursor, tokenize};
|
|
23
24
|
use crate::utils::*;
|
|
@@ -174,30 +175,20 @@ pub fn code_editor(props: &CodeEditorProps) -> Html {
|
|
|
174
175
|
<>
|
|
175
176
|
<div id="editor" {class}>
|
|
176
177
|
<div id="line_numbers" ref={lineno_ref}>{ line_numbers }</div>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
178
|
+
<MirroredTextarea
|
|
179
|
+
id="textarea_editable"
|
|
180
|
+
mirror_id="content"
|
|
181
|
+
class={classes!("editor-inner", class)}
|
|
182
|
+
mirror={terms}
|
|
183
|
+
{textarea_ref}
|
|
184
|
+
mirror_ref={content_ref}
|
|
185
|
+
{disabled}
|
|
186
|
+
{oninput}
|
|
187
|
+
{onscroll}
|
|
188
|
+
{onkeydown}
|
|
189
|
+
>
|
|
188
190
|
<div id="editor-height-sizer" />
|
|
189
|
-
|
|
190
|
-
{ terms }
|
|
191
|
-
{ {
|
|
192
|
-
// A linebreak which pushs a textarea into scroll overflow
|
|
193
|
-
// may not necessarily do so in the `<pre>`, because there is
|
|
194
|
-
// no cursor when the last line has no content, so add
|
|
195
|
-
// some space here to make sure overlfow is in sync
|
|
196
|
-
// with the text area.
|
|
197
|
-
" "
|
|
198
|
-
} }
|
|
199
|
-
</pre>
|
|
200
|
-
</div>
|
|
191
|
+
</MirroredTextarea>
|
|
201
192
|
</div>
|
|
202
193
|
<FunctionDropDownPortal
|
|
203
194
|
element={(*portal_dropdown).clone()}
|
|
@@ -23,12 +23,14 @@ use crate::presentation::*;
|
|
|
23
23
|
use crate::renderer::*;
|
|
24
24
|
use crate::session::*;
|
|
25
25
|
use crate::utils::*;
|
|
26
|
+
use crate::workspace::Workspace;
|
|
26
27
|
|
|
27
28
|
#[derive(Clone, PartialEq, Properties)]
|
|
28
29
|
pub struct DebugPanelProps {
|
|
29
30
|
pub presentation: Presentation,
|
|
30
31
|
pub renderer: Renderer,
|
|
31
32
|
pub session: Session,
|
|
33
|
+
pub workspace: Workspace,
|
|
32
34
|
|
|
33
35
|
/// Trap-door width pinned by the parent `SettingsPanel` so switching
|
|
34
36
|
/// tabs doesn't shrink the panel. Threaded into the hidden sizer
|
|
@@ -236,15 +238,16 @@ impl DebugPanelProps {
|
|
|
236
238
|
ApiFuture::spawn(async move {
|
|
237
239
|
match serde_json::from_str(&text) {
|
|
238
240
|
Ok(config) => {
|
|
239
|
-
|
|
241
|
+
let active =
|
|
242
|
+
props.workspace.active_renderer().as_ref() == Some(&props.renderer);
|
|
243
|
+
match crate::tasks::restore_panel(
|
|
240
244
|
&props.session,
|
|
241
245
|
&props.renderer,
|
|
242
246
|
&props.presentation,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
crate::tasks::RunOrigin::Public,
|
|
247
|
+
&props.workspace,
|
|
248
|
+
crate::tasks::RestoreMode::Existing { active },
|
|
246
249
|
config,
|
|
247
|
-
|
|
250
|
+
crate::tasks::RestoreErrors::Suppress,
|
|
248
251
|
)
|
|
249
252
|
.await
|
|
250
253
|
{
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
//! A `<textarea>` whose box is sized by its own text.
|
|
14
|
+
//!
|
|
15
|
+
//! A textarea cannot grow with its content — it scrolls at whatever height
|
|
16
|
+
//! it was given. The technique here, shared by [`CodeEditor`] and the chat
|
|
17
|
+
//! prompt, is to make the textarea a purely functional layer: it takes the
|
|
18
|
+
//! keystrokes, the selection and the caret, but paints no glyphs
|
|
19
|
+
//! (`color: transparent`). Underneath sits a `<pre>` mirror of the same
|
|
20
|
+
//! characters which IS painted and IS in flow, so the ordinary layout
|
|
21
|
+
//! engine sizes the box.
|
|
22
|
+
//!
|
|
23
|
+
//! This is deliberately not a JS measure-and-resize loop
|
|
24
|
+
//! (`scrollHeight` → inline `height`): that re-measures on a reflow it
|
|
25
|
+
//! just forced, drifts whenever font-size or the loaded font changes
|
|
26
|
+
//! without an `input` event to re-trigger it, and jitters at fractional
|
|
27
|
+
//! line heights. Here the two layers share their text metrics, so growth
|
|
28
|
+
//! is exact by construction and free at runtime.
|
|
29
|
+
//!
|
|
30
|
+
//! Consumers decide the AXES: the mirror's `white-space` governs growth,
|
|
31
|
+
//! so `pre-wrap` grows only downward (the chat prompt) while `pre` also
|
|
32
|
+
//! grows sideways (the expression editor). They also own the box —
|
|
33
|
+
//! padding, borders, min/max size — with `mirrored-textarea.css` fixing
|
|
34
|
+
//! only what MUST agree between the two layers.
|
|
35
|
+
//!
|
|
36
|
+
//! [`CodeEditor`]: super::code_editor::CodeEditor
|
|
37
|
+
|
|
38
|
+
use yew::html::IntoPropValue;
|
|
39
|
+
use yew::prelude::*;
|
|
40
|
+
|
|
41
|
+
#[derive(Clone)]
|
|
42
|
+
pub struct Mirror(pub Html);
|
|
43
|
+
|
|
44
|
+
impl PartialEq for Mirror {
|
|
45
|
+
fn eq(&self, _other: &Self) -> bool {
|
|
46
|
+
false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl IntoPropValue<Mirror> for Html {
|
|
51
|
+
fn into_prop_value(self) -> Mirror {
|
|
52
|
+
Mirror(self)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[derive(Properties, PartialEq)]
|
|
57
|
+
pub struct MirroredTextareaProps {
|
|
58
|
+
/// `id` of the `<textarea>` itself — consumer CSS and tests key on it.
|
|
59
|
+
pub id: AttrValue,
|
|
60
|
+
|
|
61
|
+
/// The mirror's content: the same characters the textarea holds,
|
|
62
|
+
/// as plain text, or marked up (`CodeEditor` passes highlighted
|
|
63
|
+
/// spans). A stale mirror means a caret that drifts from the glyphs,
|
|
64
|
+
/// so this is re-rendered unconditionally — see [`Mirror`].
|
|
65
|
+
pub mirror: Mirror,
|
|
66
|
+
|
|
67
|
+
/// `id` of the mirror `<pre>`, for consumers that style it directly.
|
|
68
|
+
#[prop_or_default]
|
|
69
|
+
pub mirror_id: Option<AttrValue>,
|
|
70
|
+
|
|
71
|
+
/// Extra classes for the positioned container.
|
|
72
|
+
#[prop_or_default]
|
|
73
|
+
pub class: Classes,
|
|
74
|
+
|
|
75
|
+
#[prop_or_default]
|
|
76
|
+
pub textarea_ref: NodeRef,
|
|
77
|
+
|
|
78
|
+
#[prop_or_default]
|
|
79
|
+
pub mirror_ref: NodeRef,
|
|
80
|
+
|
|
81
|
+
/// The `placeholder` ATTRIBUTE, which exists for assistive tech: it
|
|
82
|
+
/// is painted transparent, because a CSS-`content` label cannot be
|
|
83
|
+
/// written into an attribute and so cannot be localized. The text a
|
|
84
|
+
/// sighted user reads comes from the mirror instead — see
|
|
85
|
+
/// [`Self::is_empty`].
|
|
86
|
+
#[prop_or_default]
|
|
87
|
+
pub placeholder: AttrValue,
|
|
88
|
+
|
|
89
|
+
/// Whether [`Self::mirror`] is empty, which the component cannot see
|
|
90
|
+
/// for itself (the mirror is opaque `Html`). Tags the mirror with
|
|
91
|
+
/// `is-empty` so a consumer's stylesheet can supply placeholder text
|
|
92
|
+
/// through `content`, and therefore through the intl label
|
|
93
|
+
/// mechanism, rather than through the untranslatable attribute.
|
|
94
|
+
#[prop_or_default]
|
|
95
|
+
pub is_empty: bool,
|
|
96
|
+
|
|
97
|
+
#[prop_or_default]
|
|
98
|
+
pub disabled: bool,
|
|
99
|
+
|
|
100
|
+
#[prop_or_default]
|
|
101
|
+
pub oninput: Callback<InputEvent>,
|
|
102
|
+
|
|
103
|
+
#[prop_or_default]
|
|
104
|
+
pub onkeydown: Callback<KeyboardEvent>,
|
|
105
|
+
|
|
106
|
+
#[prop_or_default]
|
|
107
|
+
pub onscroll: Callback<Event>,
|
|
108
|
+
|
|
109
|
+
/// Extra nodes inside the positioned container, e.g. `CodeEditor`'s
|
|
110
|
+
/// minimum-height sizer.
|
|
111
|
+
#[prop_or_default]
|
|
112
|
+
pub children: Children,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[function_component]
|
|
116
|
+
pub fn MirroredTextarea(props: &MirroredTextareaProps) -> Html {
|
|
117
|
+
html! {
|
|
118
|
+
<div class={classes!("mirrored-textarea", props.class.clone())}>
|
|
119
|
+
// `scrollable` is the viewer's webkit scrollbar styling: the
|
|
120
|
+
// input layer scrolls wherever a consumer lets it (the
|
|
121
|
+
// expression editor syncs its `scrollTop` to the mirror and
|
|
122
|
+
// line numbers), and is inert where it does not (the chat
|
|
123
|
+
// prompt, whose wrapper scrolls instead).
|
|
124
|
+
<textarea
|
|
125
|
+
id={props.id.clone()}
|
|
126
|
+
ref={props.textarea_ref.clone()}
|
|
127
|
+
class="mirrored-textarea-input scrollable"
|
|
128
|
+
spellcheck="false"
|
|
129
|
+
placeholder={props.placeholder.clone()}
|
|
130
|
+
disabled={props.disabled}
|
|
131
|
+
oninput={props.oninput.clone()}
|
|
132
|
+
onkeydown={props.onkeydown.clone()}
|
|
133
|
+
onscroll={props.onscroll.clone()}
|
|
134
|
+
/>
|
|
135
|
+
{ props.children.iter().collect::<Html>() }
|
|
136
|
+
<pre
|
|
137
|
+
id={props.mirror_id.clone()}
|
|
138
|
+
ref={props.mirror_ref.clone()}
|
|
139
|
+
class={classes!(
|
|
140
|
+
"mirrored-textarea-mirror",
|
|
141
|
+
props.is_empty.then_some("is-empty"),
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
{ props.mirror.0.clone() }
|
|
145
|
+
// A trailing newline opens a line box in the textarea but
|
|
146
|
+
// not in the `<pre>` (which has no caret to place there),
|
|
147
|
+
// so the mirror would come up one line short exactly when
|
|
148
|
+
// the text is about to overflow. The space forces it.
|
|
149
|
+
{ " " }
|
|
150
|
+
</pre>
|
|
151
|
+
</div>
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[cfg(test)]
|
|
156
|
+
mod tests {
|
|
157
|
+
use super::*;
|
|
158
|
+
|
|
159
|
+
#[test]
|
|
160
|
+
fn mirror_never_compares_equal() {
|
|
161
|
+
let mirror = Mirror(Html::default());
|
|
162
|
+
assert!(mirror != mirror.clone());
|
|
163
|
+
assert!(Mirror(Html::default()) != Mirror(Html::default()));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -291,6 +291,11 @@ impl Component for MainPanel {
|
|
|
291
291
|
.link()
|
|
292
292
|
.callback(|(id, x, y)| MainPanelMsg::ContextMenu(id, x, y));
|
|
293
293
|
let contextmenu_listener = Closure::wrap(Box::new(move |event: web_sys::Event| {
|
|
294
|
+
// Shift+right-click passes through to the native browser menu.
|
|
295
|
+
if event.unchecked_ref::<web_sys::MouseEvent>().shift_key() {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
294
299
|
let path = event.composed_path();
|
|
295
300
|
let mut panel_id = None;
|
|
296
301
|
for i in 0..path.length() {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
//! necessary for public Custom Elements. The rest are internal components of
|
|
15
15
|
//! these 4.
|
|
16
16
|
|
|
17
|
+
#[cfg(feature = "llm-agent")]
|
|
18
|
+
pub mod chat_panel;
|
|
17
19
|
pub mod column_dropdown;
|
|
18
20
|
pub mod column_selector;
|
|
19
21
|
pub mod column_settings_sidebar;
|
|
@@ -48,3 +50,4 @@ pub mod style;
|
|
|
48
50
|
pub mod style_controls;
|
|
49
51
|
pub mod type_icon;
|
|
50
52
|
pub mod viewer;
|
|
53
|
+
pub mod window_editor;
|