@perspective-dev/viewer 5.3.1 → 5.4.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.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +3 -3
- package/dist/esm/perspective-viewer.js +5 -5
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
- package/dist/wasm/perspective-viewer.d.ts +62 -73
- package/dist/wasm/perspective-viewer.js +226 -238
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
- 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 +8 -5
- package/src/css/column-settings-panel.css +0 -225
- package/src/css/column-style.css +13 -0
- package/src/css/config-selector.css +18 -10
- package/src/css/filter-dropdown.css +5 -5
- package/src/css/form/debug.css +28 -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 +52 -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/viewer.css +32 -26
- 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 +15 -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 +3 -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/primitive_field.rs +15 -17
- 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 +4 -5
- 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} +5 -5
- 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/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 +26 -2
- package/src/rust/components/mod.rs +5 -7
- 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 +1 -1
- 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 +2 -2
- package/src/rust/components/status_bar.rs +113 -19
- package/src/rust/components/status_indicator.rs +5 -14
- package/src/rust/components/string_column_style.rs +1 -2
- 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 +50 -19
- package/src/rust/components/viewer/render.rs +15 -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 +1 -21
- 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 +2 -27
- package/src/rust/config/mod.rs +0 -2
- package/src/rust/config/workspace_config.rs +2 -1
- 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 +0 -18
- package/src/rust/js/plugin.rs +0 -86
- package/src/rust/lib.rs +12 -15
- 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/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 +1 -0
- package/src/rust/renderer/props.rs +2 -1
- package/src/rust/renderer.rs +14 -27
- package/src/rust/session/props.rs +4 -3
- package/src/rust/session/view_subscription.rs +19 -6
- package/src/rust/session.rs +38 -70
- package/src/rust/tasks/create_panel.rs +1 -1
- package/src/rust/tasks/dismiss_render_warning.rs +1 -1
- package/src/rust/tasks/eject.rs +1 -0
- package/src/rust/tasks/pipeline.rs +0 -60
- package/src/rust/tasks/presize_panels.rs +24 -1
- package/src/rust/tasks/restore_and_render.rs +0 -17
- package/src/rust/tasks/send_column_config.rs +1 -1
- package/src/rust/tasks/send_plugin_config.rs +1 -1
- package/src/rust/tasks/set_edit_mode.rs +1 -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/ui/form/intl_label.rs +81 -0
- 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 +174 -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/mod.rs +2 -0
- package/src/rust/workspace.rs +35 -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/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 +1 -8
- package/src/themes/intl/ar.css +185 -0
- package/src/themes/intl/de.css +45 -35
- package/src/themes/intl/es.css +40 -30
- package/src/themes/intl/fr.css +39 -29
- package/src/themes/intl/ja.css +41 -31
- package/src/themes/intl/pt.css +37 -27
- package/src/themes/intl/zh.css +47 -37
- package/src/themes/intl.css +16 -3
- 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/ts-rs/ViewOnUpdateResp.ts +1 -1
- package/src/css/plugin-settings-panel.css +0 -171
- package/src/rust/components/modal.rs +0 -177
- 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/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/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/{containers → ui/containers}/tabs.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
|
@@ -26,25 +26,18 @@ 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
29
|
#[prop_or_default]
|
|
31
30
|
pub class: Classes,
|
|
32
31
|
|
|
33
32
|
#[prop_or_default]
|
|
34
33
|
pub orientation: Orientation,
|
|
35
34
|
|
|
36
|
-
/// Whether to OMIT children that render nothing (an empty fragment, or
|
|
37
|
-
/// any nesting of empty fragments — see [`is_empty_html`]) instead of
|
|
38
|
-
/// rendering them as empty panes. A skipped pane takes its divider with
|
|
39
|
-
/// it.
|
|
40
35
|
#[prop_or_default]
|
|
41
36
|
pub skip_empty: bool,
|
|
42
37
|
|
|
43
|
-
/// Should the child panels by wrapped in `<div>` elements?
|
|
44
38
|
#[prop_or_default]
|
|
45
39
|
pub no_wrap: bool,
|
|
46
40
|
|
|
47
|
-
/// Should the panels be rendered/sized in _reverse_ order?
|
|
48
41
|
#[prop_or_default]
|
|
49
42
|
pub reverse: bool,
|
|
50
43
|
|
|
@@ -60,31 +53,13 @@ pub struct SplitPanelProps {
|
|
|
60
53
|
#[prop_or_default]
|
|
61
54
|
pub initial_size: Option<i32>,
|
|
62
55
|
|
|
63
|
-
/// When `true`, a divider drag does NOT apply pane sizes directly:
|
|
64
|
-
/// `MoveResizing` only emits `on_resize` with the proposed dims, and pane
|
|
65
|
-
/// 0's committed size is driven exclusively by the controlled
|
|
66
|
-
/// [`Self::size`] prop — set by the parent when it is ready (e.g. after
|
|
67
|
-
/// pre-rendering panel content at the target size). This is the presize
|
|
68
|
-
/// gate for continuous divider drags (`PRESIZE_EVERYWHERE_PLAN.md`,
|
|
69
|
-
/// P1): geometry never outruns content. Only meaningful when the
|
|
70
|
-
/// resizable pane is child 0 (as in the viewer's `app_panel`).
|
|
71
56
|
#[prop_or_default]
|
|
72
57
|
pub deferred: bool,
|
|
73
58
|
|
|
74
|
-
/// Controlled committed size for pane 0 (deferred mode). Unlike
|
|
75
|
-
/// [`Self::initial_size`] (read once at `create`), changes to this prop
|
|
76
|
-
/// re-derive pane 0's style on every `changed`.
|
|
77
59
|
#[prop_or_default]
|
|
78
60
|
pub size: Option<i32>,
|
|
79
61
|
}
|
|
80
62
|
|
|
81
|
-
/// `true` when `node` renders NOTHING: an empty fragment, or a fragment of
|
|
82
|
-
/// nothing but empty fragments. SEMANTIC, not literal — the `html!`
|
|
83
|
-
/// if-syntax (and other wrappers) nests an empty branch inside another
|
|
84
|
-
/// `VList` rather than yielding a bare `<></>`, which is why a literal
|
|
85
|
-
/// `x != html! { <></> }` comparison is not a usable emptiness test (see
|
|
86
|
-
/// the mechanism unit test). Deliberately conservative: `VText("")` and
|
|
87
|
-
/// other node kinds are NOT considered empty.
|
|
88
63
|
fn is_empty_html(node: &Html) -> bool {
|
|
89
64
|
match node {
|
|
90
65
|
Html::VList(list) => list.iter().all(is_empty_html),
|
|
@@ -92,8 +67,6 @@ fn is_empty_html(node: &Html) -> bool {
|
|
|
92
67
|
}
|
|
93
68
|
}
|
|
94
69
|
|
|
95
|
-
/// The fixed-size pane style for a committed size (the complement pane
|
|
96
|
-
/// flex-fills).
|
|
97
70
|
fn size_style(orientation: Orientation, x: i32) -> String {
|
|
98
71
|
match orientation {
|
|
99
72
|
Orientation::Horizontal => {
|
|
@@ -132,19 +105,6 @@ pub enum SplitPanelMsg {
|
|
|
132
105
|
|
|
133
106
|
/// A panel with 2 sub panels and a mouse-draggable divider which allows
|
|
134
107
|
/// apportioning the panel's width.
|
|
135
|
-
///
|
|
136
|
-
/// # Examples
|
|
137
|
-
///
|
|
138
|
-
/// ```
|
|
139
|
-
/// html! {
|
|
140
|
-
/// <SplitPanel id="app_panel">
|
|
141
|
-
/// <div id="A">
|
|
142
|
-
/// <div id="B">
|
|
143
|
-
/// <a href=".."></a>
|
|
144
|
-
/// </div>
|
|
145
|
-
/// </SplitPanel>
|
|
146
|
-
/// }
|
|
147
|
-
/// ```
|
|
148
108
|
pub struct SplitPanel {
|
|
149
109
|
resize_state: Option<ResizingState>,
|
|
150
110
|
refs: Vec<NodeRef>,
|
|
@@ -159,8 +119,6 @@ impl Component for SplitPanel {
|
|
|
159
119
|
fn create(ctx: &Context<Self>) -> Self {
|
|
160
120
|
assert!(ctx.props().validate());
|
|
161
121
|
let len = ctx.props().children.len();
|
|
162
|
-
// cant just use vec![Default::default(); len] as it would
|
|
163
|
-
// use the same underlying NodeRef for each element.
|
|
164
122
|
let refs = Vec::from_iter(std::iter::repeat_with(Default::default).take(len));
|
|
165
123
|
|
|
166
124
|
let mut styles = vec![Default::default(); len];
|
|
@@ -179,6 +137,14 @@ impl Component for SplitPanel {
|
|
|
179
137
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
180
138
|
match msg {
|
|
181
139
|
SplitPanelMsg::Reset(index) => {
|
|
140
|
+
if ctx.props().deferred {
|
|
141
|
+
if let Some(cb) = &ctx.props().on_reset {
|
|
142
|
+
cb.emit(());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
182
148
|
self.styles[index] = None;
|
|
183
149
|
self.on_reset.clone_from(&ctx.props().on_reset);
|
|
184
150
|
},
|
|
@@ -201,9 +167,6 @@ impl Component for SplitPanel {
|
|
|
201
167
|
cb.emit(state.get_dimensions(client_offset));
|
|
202
168
|
}
|
|
203
169
|
|
|
204
|
-
// Deferred mode: propose only — the committed size arrives
|
|
205
|
-
// back through the controlled `size` prop once the parent
|
|
206
|
-
// has pre-rendered content at the target.
|
|
207
170
|
if !ctx.props().deferred {
|
|
208
171
|
self.styles[state.index] = state.get_style(client_offset);
|
|
209
172
|
}
|
|
@@ -239,8 +202,6 @@ impl Component for SplitPanel {
|
|
|
239
202
|
}
|
|
240
203
|
}
|
|
241
204
|
|
|
242
|
-
// Deferred mode: pane 0's style tracks the controlled `size` prop
|
|
243
|
-
// (`None` = natural width, e.g. after a divider double-click reset).
|
|
244
205
|
if ctx.props().deferred {
|
|
245
206
|
self.styles[0] = ctx
|
|
246
207
|
.props()
|
|
@@ -255,15 +216,6 @@ impl Component for SplitPanel {
|
|
|
255
216
|
let skip_empty = ctx.props().skip_empty;
|
|
256
217
|
let orientation = ctx.props().orientation;
|
|
257
218
|
|
|
258
|
-
// Pair each *visible* pane with its ORIGINAL child index and key every
|
|
259
|
-
// pane (and divider) by that stable identity — not its post-filter
|
|
260
|
-
// position. Toggling one pane in/out (e.g. the settings sidebar) then
|
|
261
|
-
// never shifts a sibling's key, so Yew reconciles the survivor in place
|
|
262
|
-
// rather than remounting it. A `MainPanel` remount here would tear down
|
|
263
|
-
// and recreate the embedded `<regular-layout>` and the `<slot>`s
|
|
264
|
-
// projecting the plugins — the bug this avoids. Dividers are independent
|
|
265
|
-
// keyed nodes for the same reason: a divider appearing before a pane must
|
|
266
|
-
// not alter that pane's own keyed subtree.
|
|
267
219
|
let panes = ctx
|
|
268
220
|
.props()
|
|
269
221
|
.children
|
|
@@ -276,8 +228,6 @@ impl Component for SplitPanel {
|
|
|
276
228
|
let mut nodes: Vec<Html> = Vec::with_capacity(panes.len() * 2);
|
|
277
229
|
let mut prev: Option<usize> = None;
|
|
278
230
|
for (pos, (i, x)) in panes.into_iter().enumerate() {
|
|
279
|
-
// A divider precedes every visible pane except the first; it resizes
|
|
280
|
-
// the *previous* visible pane (`prev`, an original index).
|
|
281
231
|
if let Some(p) = prev {
|
|
282
232
|
nodes.push(html! {
|
|
283
233
|
<SplitPanelDivider
|
|
@@ -289,9 +239,6 @@ impl Component for SplitPanel {
|
|
|
289
239
|
});
|
|
290
240
|
}
|
|
291
241
|
|
|
292
|
-
// The last visible pane flex-fills (rendered bare, no width
|
|
293
|
-
// override); earlier panes are `SplitPanelChild`s that can carry a
|
|
294
|
-
// dragged size.
|
|
295
242
|
nodes.push(if pos == last {
|
|
296
243
|
html! { <key={i}>{ x }</> }
|
|
297
244
|
} else {
|
|
@@ -348,7 +295,6 @@ impl PartialEq for SplitPanelDividerProps {
|
|
|
348
295
|
}
|
|
349
296
|
}
|
|
350
297
|
|
|
351
|
-
/// The resize handle for a `SplitPanel`.
|
|
352
298
|
#[function_component(SplitPanelDivider)]
|
|
353
299
|
fn split_panel_divider(props: &SplitPanelDividerProps) -> Html {
|
|
354
300
|
let orientation = props.orientation;
|
|
@@ -371,11 +317,6 @@ fn split_panel_divider(props: &SplitPanelDividerProps) -> Html {
|
|
|
371
317
|
SplitPanelMsg::Reset(i)
|
|
372
318
|
});
|
|
373
319
|
|
|
374
|
-
// TODO Not sure why, but under some circumstances this can trigger a
|
|
375
|
-
// `dragstart`, leading to further drag events which cause perspective
|
|
376
|
-
// havoc. `event.prevent_default()` in `onmousedown` alternatively fixes
|
|
377
|
-
// this, but also prevents this event from trigger focus-stealing e.g. from
|
|
378
|
-
// open dialogs.
|
|
379
320
|
let ondragstart = Callback::from(|event: DragEvent| event.prevent_default());
|
|
380
321
|
|
|
381
322
|
html! {
|
|
@@ -411,8 +352,6 @@ fn split_panel_child(props: &SplitPanelChildProps) -> Html {
|
|
|
411
352
|
}
|
|
412
353
|
}
|
|
413
354
|
|
|
414
|
-
/// The state for the `Resizing` action, including the `MouseEvent` callbacks
|
|
415
|
-
/// and panel starting dimensions.
|
|
416
355
|
struct ResizingState {
|
|
417
356
|
mousemove: Closure<dyn Fn(MouseEvent)>,
|
|
418
357
|
mouseup: Closure<dyn Fn(MouseEvent)>,
|
|
@@ -429,9 +368,6 @@ struct ResizingState {
|
|
|
429
368
|
}
|
|
430
369
|
|
|
431
370
|
impl Drop for ResizingState {
|
|
432
|
-
/// On `drop`, we must remove these event listeners from the document
|
|
433
|
-
/// `body`. Without this, the `Closure` objects would not leak, but the
|
|
434
|
-
/// document will continue to call them, causing runtime exceptions.
|
|
435
371
|
fn drop(&mut self) {
|
|
436
372
|
let result: ApiResult<()> = (|| {
|
|
437
373
|
let mousemove = self.mousemove.as_ref().unchecked_ref();
|
|
@@ -446,12 +382,8 @@ impl Drop for ResizingState {
|
|
|
446
382
|
}
|
|
447
383
|
}
|
|
448
384
|
|
|
449
|
-
/// The minimum size a split panel child can be, including when overridden via
|
|
450
|
-
/// user drag/drop.
|
|
451
385
|
const MINIMUM_SIZE: i32 = 8;
|
|
452
386
|
|
|
453
|
-
/// When the instantiated, capture the initial dimensions and create the
|
|
454
|
-
/// MouseEvent callbacks.
|
|
455
387
|
impl ResizingState {
|
|
456
388
|
pub fn new(
|
|
457
389
|
index: usize,
|
|
@@ -540,7 +472,6 @@ impl ResizingState {
|
|
|
540
472
|
}
|
|
541
473
|
}
|
|
542
474
|
|
|
543
|
-
/// Adds the event listeners, the corollary of `Drop`.
|
|
544
475
|
fn register_listeners(&self) -> ApiResult<()> {
|
|
545
476
|
let mousemove = self.mousemove.as_ref().unchecked_ref();
|
|
546
477
|
global::body().add_event_listener_with_callback("mousemove", mousemove)?;
|
|
@@ -548,8 +479,6 @@ impl ResizingState {
|
|
|
548
479
|
Ok(global::body().add_event_listener_with_callback("mouseup", mouseup)?)
|
|
549
480
|
}
|
|
550
481
|
|
|
551
|
-
/// Helper functions capture and release the global cursor while dragging is
|
|
552
|
-
/// occurring.
|
|
553
482
|
fn capture_cursor(&mut self) -> ApiResult<()> {
|
|
554
483
|
self.pointer_elem.set_pointer_capture(self.pointer_id)?;
|
|
555
484
|
self.cursor = self.body_style.get_property_value("cursor")?;
|
|
@@ -562,7 +491,6 @@ impl ResizingState {
|
|
|
562
491
|
Ok(())
|
|
563
492
|
}
|
|
564
493
|
|
|
565
|
-
/// " but for release
|
|
566
494
|
fn release_cursor(&self) -> ApiResult<()> {
|
|
567
495
|
self.pointer_elem.release_pointer_capture(self.pointer_id)?;
|
|
568
496
|
Ok(self.body_style.set_property("cursor", &self.cursor)?)
|
|
@@ -10,22 +10,26 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
use std::fmt::Display;
|
|
14
|
+
|
|
13
15
|
use yew::{Callback, Children, Component, Html, Properties, classes, html};
|
|
14
16
|
|
|
15
|
-
use crate::
|
|
17
|
+
use crate::ui::form::intl_label::{intl_content_style, intl_slug};
|
|
18
|
+
|
|
19
|
+
pub trait TabItem: PartialEq + Display + Clone + Default + 'static {}
|
|
20
|
+
|
|
21
|
+
impl TabItem for String {}
|
|
22
|
+
|
|
23
|
+
impl TabItem for &'static str {}
|
|
16
24
|
|
|
17
25
|
#[derive(Properties, Debug, PartialEq)]
|
|
18
|
-
pub struct TabListProps<T:
|
|
19
|
-
/// All tabs
|
|
26
|
+
pub struct TabListProps<T: TabItem> {
|
|
20
27
|
pub tabs: Vec<T>,
|
|
21
28
|
|
|
22
|
-
/// Fires when the selected tab changes in the UI.
|
|
23
29
|
pub on_tab_change: Callback<(usize, T)>,
|
|
24
30
|
|
|
25
|
-
// Which tab is selected.
|
|
26
31
|
pub selected_tab: Option<usize>,
|
|
27
32
|
|
|
28
|
-
// The currently instantiated tabs.
|
|
29
33
|
pub children: Children,
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -33,12 +37,12 @@ pub enum TabListMsg {
|
|
|
33
37
|
SetSelected(usize),
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
pub struct TabList<T:
|
|
40
|
+
pub struct TabList<T: TabItem> {
|
|
37
41
|
t: std::marker::PhantomData<T>,
|
|
38
42
|
selected_idx: usize,
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
impl<T:
|
|
45
|
+
impl<T: TabItem> Component for TabList<T> {
|
|
42
46
|
type Message = TabListMsg;
|
|
43
47
|
type Properties = TabListProps<T>;
|
|
44
48
|
|
|
@@ -75,9 +79,11 @@ impl<T: ColumnTab> Component for TabList<T> {
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
let onclick = ctx.link().callback(move |_| TabListMsg::SetSelected(idx));
|
|
82
|
+
let title = tab.to_string();
|
|
83
|
+
let style = intl_content_style(&format!("{}-tab", intl_slug(&title)), &title);
|
|
78
84
|
html! {
|
|
79
85
|
<span {class} {onclick}>
|
|
80
|
-
<div class="tab-title" id={
|
|
86
|
+
<div class="tab-title" id={title} {style} />
|
|
81
87
|
<div class="tab-border" />
|
|
82
88
|
</span>
|
|
83
89
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
use web_sys::MouseEvent;
|
|
14
14
|
use yew::prelude::*;
|
|
15
15
|
|
|
16
|
-
/// A single command row in a
|
|
16
|
+
/// A single command row in a `ContextMenu`.
|
|
17
17
|
#[derive(Clone, PartialEq)]
|
|
18
18
|
pub struct ContextMenuItem {
|
|
19
19
|
pub label: String,
|
|
@@ -21,17 +21,19 @@ pub struct ContextMenuItem {
|
|
|
21
21
|
pub disabled: bool,
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/// A row in a
|
|
24
|
+
/// A row in a `ContextMenu`: a plain command `ContextMenuItem`, a
|
|
25
25
|
/// non-interactive group label, or a row with a hover-opened sub-menu flyout.
|
|
26
26
|
#[derive(Clone, PartialEq)]
|
|
27
27
|
pub enum ContextMenuEntry {
|
|
28
28
|
Item(ContextMenuItem),
|
|
29
29
|
Header(String),
|
|
30
|
+
|
|
31
|
+
/// Arbitrary content rendered in place, whose own handlers must close
|
|
32
|
+
/// the menu.
|
|
33
|
+
Custom(Html),
|
|
30
34
|
Submenu {
|
|
31
35
|
label: String,
|
|
32
36
|
|
|
33
|
-
/// Click-through command for the parent row itself (`None` makes the
|
|
34
|
-
/// row hover-only).
|
|
35
37
|
on_select: Option<Callback<()>>,
|
|
36
38
|
entries: Vec<ContextMenuEntry>,
|
|
37
39
|
},
|
|
@@ -41,19 +43,11 @@ pub enum ContextMenuEntry {
|
|
|
41
43
|
pub struct ContextMenuProps {
|
|
42
44
|
pub entries: Vec<ContextMenuEntry>,
|
|
43
45
|
|
|
44
|
-
/// Fired after an item is selected. Outside-click dismissal is the
|
|
45
|
-
/// enclosing `PortalModal`'s job (blur), not this component's.
|
|
46
46
|
#[prop_or_default]
|
|
47
47
|
pub on_close: Callback<()>,
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/// The command-item list of a panel context menu.
|
|
51
|
-
/// command set is supplied by the parent as plain [`Callback`]s, and
|
|
52
|
-
/// positioning/dismissal/theming belong to the enclosing `PortalModal` (the
|
|
53
|
-
/// same body-mounted, `theme`-attributed delivery as the Export/Copy menus).
|
|
54
|
-
/// Sub-menus are CSS-only `:hover` flyouts nested inside their parent row, so
|
|
55
|
-
/// they live inside the same `PortalModal` host (focus never leaves it) and
|
|
56
|
-
/// selection routes through the same `on_select` + `on_close` path.
|
|
50
|
+
/// The command-item list of a panel context menu.
|
|
57
51
|
pub struct ContextMenu;
|
|
58
52
|
|
|
59
53
|
impl Component for ContextMenu {
|
|
@@ -101,6 +95,7 @@ fn render_entry(entry: &ContextMenuEntry, on_close: &Callback<()>) -> Html {
|
|
|
101
95
|
ContextMenuEntry::Header(label) => {
|
|
102
96
|
html! { <span class="context-menu-header">{ label.clone() }</span> }
|
|
103
97
|
},
|
|
98
|
+
ContextMenuEntry::Custom(content) => content.clone(),
|
|
104
99
|
ContextMenuEntry::Submenu {
|
|
105
100
|
label,
|
|
106
101
|
on_select,
|
|
@@ -22,10 +22,31 @@ use yew::prelude::*;
|
|
|
22
22
|
use crate::js::{IntersectionObserver, IntersectionObserverEntry};
|
|
23
23
|
|
|
24
24
|
pub type DragEndCallback = Closure<dyn FnMut(DragEvent)>;
|
|
25
|
+
pub type PointerDownCallback = Closure<dyn FnMut(PointerEvent)>;
|
|
26
|
+
|
|
27
|
+
pub fn closest_draggable(event: &Event) -> Option<HtmlElement> {
|
|
28
|
+
event
|
|
29
|
+
.composed_path()
|
|
30
|
+
.get(0)
|
|
31
|
+
.dyn_into::<Element>()
|
|
32
|
+
.ok()?
|
|
33
|
+
.closest("[draggable=\"true\"]")
|
|
34
|
+
.ok()
|
|
35
|
+
.flatten()?
|
|
36
|
+
.dyn_into::<HtmlElement>()
|
|
37
|
+
.ok()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn clear_document_selection() -> ApiResult<()> {
|
|
41
|
+
if let Some(selection) = global::window().get_selection()?
|
|
42
|
+
&& !selection.is_collapsed()
|
|
43
|
+
{
|
|
44
|
+
selection.remove_all_ranges()?;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Ok(())
|
|
48
|
+
}
|
|
25
49
|
|
|
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
50
|
pub fn dragenter_helper(callback: impl Fn() + 'static, target: NodeRef) -> Callback<DragEvent> {
|
|
30
51
|
Callback::from({
|
|
31
52
|
move |event: DragEvent| {
|
|
@@ -51,9 +72,6 @@ pub fn dragenter_helper(callback: impl Fn() + 'static, target: NodeRef) -> Callb
|
|
|
51
72
|
})
|
|
52
73
|
}
|
|
53
74
|
|
|
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
75
|
pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Callback<DragEvent> {
|
|
58
76
|
Callback::from({
|
|
59
77
|
clone!(drag_ref);
|
|
@@ -67,23 +85,6 @@ pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Cal
|
|
|
67
85
|
.or_else(|| Some(JsValue::UNDEFINED.unchecked_into::<EventTarget>()))
|
|
68
86
|
.and_then(|x| x.dyn_into::<Element>().ok());
|
|
69
87
|
|
|
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
88
|
if related_target
|
|
88
89
|
.as_ref()
|
|
89
90
|
.map(|x| x.has_attribute("aria-hidden"))
|
|
@@ -104,8 +105,6 @@ pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Cal
|
|
|
104
105
|
let current_target = drag_ref.cast::<HtmlElement>().unwrap();
|
|
105
106
|
match related_target {
|
|
106
107
|
Some(ref related) => {
|
|
107
|
-
// Due to virtual dom these events sometimes fire after
|
|
108
|
-
// the node is removed ...
|
|
109
108
|
if !current_target.contains(Some(related))
|
|
110
109
|
&& related.parent_element().is_some()
|
|
111
110
|
{
|
|
@@ -113,8 +112,6 @@ pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Cal
|
|
|
113
112
|
}
|
|
114
113
|
},
|
|
115
114
|
None => {
|
|
116
|
-
// Safari (OSX and iOS) don't set `relatedTarget`, so we need to
|
|
117
|
-
// read a memoized value from the `"dragenter"` event.
|
|
118
115
|
let dataset = current_target.dataset();
|
|
119
116
|
if dataset.get("safaridragleave").is_some() {
|
|
120
117
|
dataset.delete("safaridragleave");
|
|
@@ -151,9 +148,7 @@ impl DragDropContainer {
|
|
|
151
148
|
}
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
/// is called even under aggressive DOM mutation after `dragstart` is fired.
|
|
156
|
-
pub(super) struct DragTargetState {
|
|
151
|
+
pub struct DragTargetState {
|
|
157
152
|
target: HtmlElement,
|
|
158
153
|
shadow_root: ShadowRoot,
|
|
159
154
|
alive: Rc<Cell<bool>>,
|
|
@@ -161,7 +156,7 @@ pub(super) struct DragTargetState {
|
|
|
161
156
|
}
|
|
162
157
|
|
|
163
158
|
impl DragTargetState {
|
|
164
|
-
pub
|
|
159
|
+
pub fn new(host: HtmlElement, target: HtmlElement) -> Self {
|
|
165
160
|
let shadow_root = host.shadow_root().unwrap();
|
|
166
161
|
let alive = Rc::new(Cell::new(true));
|
|
167
162
|
let observer = IntersectionObserver::new(
|
|
@@ -24,18 +24,10 @@ use yew::prelude::*;
|
|
|
24
24
|
|
|
25
25
|
use crate::utils::*;
|
|
26
26
|
|
|
27
|
-
/// This test string is injected into the DOM with the target `font-family`
|
|
28
|
-
/// applied. It is important for this string to contain the correct unicode
|
|
29
|
-
/// range, as otherwise the browser may download the latin-only variant of the
|
|
30
|
-
/// font which will later be invalidated.
|
|
31
27
|
const FONT_TEST_SAMPLE: &str = "ABCDΔ";
|
|
32
28
|
|
|
33
|
-
/// How long to wait for the test string to receive the font before proceeding
|
|
34
|
-
/// anyway (with a consoel warning).
|
|
35
29
|
const FONT_DOWNLOAD_TIMEOUT_MS: i32 = 1000;
|
|
36
30
|
|
|
37
|
-
/// `state` is private to force construction of props with the `::new()` static
|
|
38
|
-
/// method, which initializes the async `load_fonts_task()` method.
|
|
39
31
|
#[derive(Clone, Properties)]
|
|
40
32
|
pub struct FontLoaderProps {
|
|
41
33
|
state: Rc<FontLoaderState>,
|
|
@@ -105,8 +97,6 @@ impl FontLoaderProps {
|
|
|
105
97
|
self.state.fonts.borrow()
|
|
106
98
|
}
|
|
107
99
|
|
|
108
|
-
/// We only want errors in this task to warn, since they are not necessarily
|
|
109
|
-
/// error conditions and mainly of interest to developers.
|
|
110
100
|
async fn load_fonts_task_safe(self) -> ApiResult<JsValue> {
|
|
111
101
|
if let Err(msg) = self.load_fonts_task().await {
|
|
112
102
|
web_sys::console::warn_1(&msg.into());
|
|
@@ -115,13 +105,6 @@ impl FontLoaderProps {
|
|
|
115
105
|
Ok(JsValue::UNDEFINED)
|
|
116
106
|
}
|
|
117
107
|
|
|
118
|
-
/// Awaits loading of a required set of font/weight pairs, given an element
|
|
119
|
-
/// with a CSS variable of the format:
|
|
120
|
-
/// ```css
|
|
121
|
-
/// perspective-viewer {
|
|
122
|
-
/// --preload-fonts: "Roboto Mono:200;Open Sans:300,400";
|
|
123
|
-
/// }
|
|
124
|
-
/// ```
|
|
125
108
|
async fn load_fonts_task(self) -> ApiResult<JsValue> {
|
|
126
109
|
await_dom_loaded().await?;
|
|
127
110
|
let txt = global::window()
|
|
@@ -143,7 +126,6 @@ impl FontLoaderProps {
|
|
|
143
126
|
let font_face = js_sys::Reflect::get(&entry, &intern("value").into())?
|
|
144
127
|
.dyn_into::<web_sys::FontFace>()?;
|
|
145
128
|
|
|
146
|
-
// Safari always has to be "different".
|
|
147
129
|
if family == &font_face.family().replace('"', "")
|
|
148
130
|
&& (weight == &font_face.weight()
|
|
149
131
|
|| (font_face.weight() == "normal" && weight == "400"))
|
|
@@ -174,8 +156,6 @@ impl FontLoaderProps {
|
|
|
174
156
|
}
|
|
175
157
|
}
|
|
176
158
|
|
|
177
|
-
// An async task which times out. Can be used to timeout an optional async task
|
|
178
|
-
// by combinging with `Promise::any`.
|
|
179
159
|
fn timeout_font_task(
|
|
180
160
|
family: &str,
|
|
181
161
|
weight: &str,
|
|
@@ -187,9 +167,6 @@ fn timeout_font_task(
|
|
|
187
167
|
}
|
|
188
168
|
}
|
|
189
169
|
|
|
190
|
-
/// Generates a `<span>` for a specific font family and weight with `opacity:0`,
|
|
191
|
-
/// since not all of the fonts may be shown and when e.g. the settings panel is
|
|
192
|
-
/// closed, and this will defer font loading.
|
|
193
170
|
fn font_test_html((family, weight): &(String, String)) -> Html {
|
|
194
171
|
let style = format!("opacity:0;font-family:\"{family}\";font-weight:{weight}");
|
|
195
172
|
|
|
@@ -208,7 +185,6 @@ fn parse_font(txt: &str) -> Option<Vec<(String, String)>> {
|
|
|
208
185
|
}
|
|
209
186
|
}
|
|
210
187
|
|
|
211
|
-
/// Parse a `--preload-fonts` value into (Family, Weight) tuples.
|
|
212
188
|
fn parse_fonts(txt: &str) -> Vec<(String, String)> {
|
|
213
189
|
let trim = txt.trim();
|
|
214
190
|
let trim = if trim.len() > 2 {
|
|
@@ -223,8 +199,6 @@ fn parse_fonts(txt: &str) -> Vec<(String, String)> {
|
|
|
223
199
|
.collect::<Vec<_>>()
|
|
224
200
|
}
|
|
225
201
|
|
|
226
|
-
/// wasm_bindgen doesn't fully implement `FontFaceIterator`, but this is
|
|
227
|
-
/// basically how it would be implemented if it was.
|
|
228
202
|
fn font_iter(
|
|
229
203
|
iter: web_sys::FontFaceSetIterator,
|
|
230
204
|
) -> impl Iterator<Item = web_sys::FontFaceSetIteratorResult> {
|
|
@@ -14,6 +14,8 @@ use wasm_bindgen::JsCast;
|
|
|
14
14
|
use web_sys::*;
|
|
15
15
|
use yew::prelude::*;
|
|
16
16
|
|
|
17
|
+
use crate::ui::form::intl_label::IntlLabel;
|
|
18
|
+
|
|
17
19
|
#[derive(Properties, PartialEq)]
|
|
18
20
|
pub struct ColorProps {
|
|
19
21
|
pub color: String,
|
|
@@ -22,7 +24,7 @@ pub struct ColorProps {
|
|
|
22
24
|
pub is_modified: bool,
|
|
23
25
|
|
|
24
26
|
#[prop_or_default]
|
|
25
|
-
pub
|
|
27
|
+
pub label: Option<String>,
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
#[function_component(ColorSelector)]
|
|
@@ -42,7 +44,7 @@ pub fn color_component(props: &ColorProps) -> Html {
|
|
|
42
44
|
|
|
43
45
|
html! {
|
|
44
46
|
<>
|
|
45
|
-
<
|
|
47
|
+
<IntlLabel name={props.label.clone().unwrap_or_else(|| "color".to_owned())} />
|
|
46
48
|
<div class="color-gradient-container">
|
|
47
49
|
<input class="parameter" type="color" value={props.color.to_owned()} {oninput} />
|
|
48
50
|
if props.is_modified {
|
|
@@ -0,0 +1,81 @@
|
|
|
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 yew::prelude::*;
|
|
14
|
+
|
|
15
|
+
/// Lowercases `key` and folds every other character to `-`, producing the
|
|
16
|
+
/// middle segment of a `--psp-label--{slug}--content` intl variable name.
|
|
17
|
+
pub fn intl_slug(key: &str) -> String {
|
|
18
|
+
key.chars()
|
|
19
|
+
.map(|x| {
|
|
20
|
+
if x.is_ascii_alphanumeric() {
|
|
21
|
+
x.to_ascii_lowercase()
|
|
22
|
+
} else {
|
|
23
|
+
'-'
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
.collect()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn humanize(key: &str) -> String {
|
|
30
|
+
let mut out = String::with_capacity(key.len());
|
|
31
|
+
for x in key.chars() {
|
|
32
|
+
if x.is_alphanumeric() {
|
|
33
|
+
if out.is_empty() {
|
|
34
|
+
out.extend(x.to_uppercase());
|
|
35
|
+
} else {
|
|
36
|
+
out.push(x);
|
|
37
|
+
}
|
|
38
|
+
} else if matches!(x, '_' | '-') || x.is_whitespace() {
|
|
39
|
+
out.push(' ');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// The inline `style` that routes an element's `:before` text through the intl
|
|
47
|
+
/// indirection: `--psp-label--content` is set to a reference to
|
|
48
|
+
/// `--psp-label--{var}--content`, falling back to `fallback`, and a generic
|
|
49
|
+
/// stylesheet rule renders it.
|
|
50
|
+
pub fn intl_content_style(var: &str, fallback: &str) -> String {
|
|
51
|
+
format!("--psp-label--content: var(--psp-label--{var}--content, \"{fallback}\")")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[derive(Properties, PartialEq)]
|
|
55
|
+
pub struct IntlLabelProps {
|
|
56
|
+
pub name: String,
|
|
57
|
+
|
|
58
|
+
#[prop_or_default]
|
|
59
|
+
pub group: bool,
|
|
60
|
+
|
|
61
|
+
#[prop_or_default]
|
|
62
|
+
pub class: Classes,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// A `<label>` whose text is the intl string for a schema key.
|
|
66
|
+
#[function_component(IntlLabel)]
|
|
67
|
+
pub fn intl_label(props: &IntlLabelProps) -> Html {
|
|
68
|
+
let slug = intl_slug(&props.name);
|
|
69
|
+
let (id, var) = if props.group {
|
|
70
|
+
(
|
|
71
|
+
format!("{}-group-label", props.name),
|
|
72
|
+
format!("group-{slug}"),
|
|
73
|
+
)
|
|
74
|
+
} else {
|
|
75
|
+
(format!("{}-label", props.name), slug)
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let style = intl_content_style(&var, &humanize(&props.name));
|
|
79
|
+
let class = classes!("intl-label", props.class.clone());
|
|
80
|
+
html! { <label {class} {id} {style} /> }
|
|
81
|
+
}
|