@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.
Files changed (255) hide show
  1. package/dist/cdn/perspective-viewer.js +6 -6
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -0
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/extensions.d.ts +0 -5
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +5 -5
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  36. package/dist/wasm/perspective-viewer.d.ts +62 -73
  37. package/dist/wasm/perspective-viewer.js +226 -238
  38. package/dist/wasm/perspective-viewer.wasm +0 -0
  39. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
  40. package/package.json +7 -7
  41. package/src/css/_context_menu.css +1 -1
  42. package/src/css/_dropdown_menu.css +1 -1
  43. package/src/css/_viewer_bundle.css +7 -8
  44. package/src/css/column-dropdown.css +0 -3
  45. package/src/css/column-selector.css +8 -5
  46. package/src/css/column-settings-panel.css +0 -225
  47. package/src/css/column-style.css +13 -0
  48. package/src/css/config-selector.css +18 -10
  49. package/src/css/filter-dropdown.css +5 -5
  50. package/src/css/form/debug.css +28 -4
  51. package/src/css/function-dropdown.css +5 -5
  52. package/src/css/panel-tab.css +23 -0
  53. package/src/css/plugin-selector.css +5 -5
  54. package/src/css/render-warning.css +1 -1
  55. package/src/css/status-bar.css +52 -21
  56. package/src/css/type-icon.css +1 -3
  57. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  58. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  59. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  60. package/src/css/viewer.css +32 -26
  61. package/src/rust/components/chat_panel.rs +1 -1
  62. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  63. package/src/rust/components/column_dropdown.rs +2 -2
  64. package/src/rust/components/column_selector/active_column.rs +15 -9
  65. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  66. package/src/rust/components/column_selector/config_selector.rs +2 -2
  67. package/src/rust/components/column_selector/filter_column.rs +8 -5
  68. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  69. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  70. package/src/rust/components/column_selector/sort_column.rs +7 -4
  71. package/src/rust/components/column_selector.rs +3 -3
  72. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  73. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  75. package/src/rust/components/column_settings_sidebar/style_tab.rs +4 -5
  76. package/src/rust/components/column_settings_sidebar.rs +3 -4
  77. package/src/rust/components/copy_dropdown.rs +1 -1
  78. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  79. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  80. package/src/rust/components/datetime_column_style.rs +1 -2
  81. package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
  82. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  83. package/src/rust/components/export_dropdown.rs +1 -1
  84. package/src/rust/components/expression_editor.rs +1 -1
  85. package/src/rust/components/filter_dropdown.rs +2 -2
  86. package/src/rust/components/function_dropdown.rs +2 -2
  87. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  88. package/src/rust/components/main_panel/reconcile.rs +9 -4
  89. package/src/rust/components/main_panel/render.rs +62 -11
  90. package/src/rust/components/main_panel/update.rs +39 -10
  91. package/src/rust/components/main_panel.rs +26 -2
  92. package/src/rust/components/mod.rs +5 -7
  93. package/src/rust/components/new_panel_menu.rs +97 -0
  94. package/src/rust/components/number_series_style.rs +2 -3
  95. package/src/rust/components/panel_menu.rs +61 -87
  96. package/src/rust/components/panel_tab.rs +100 -43
  97. package/src/rust/components/plugin_tab.rs +1 -1
  98. package/src/rust/components/render_warning.rs +2 -2
  99. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  100. package/src/rust/components/settings_panel.rs +2 -2
  101. package/src/rust/components/status_bar.rs +113 -19
  102. package/src/rust/components/status_indicator.rs +5 -14
  103. package/src/rust/components/string_column_style.rs +1 -2
  104. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  105. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  106. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  107. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  108. package/src/rust/components/viewer/msg.rs +43 -30
  109. package/src/rust/components/viewer/panels.rs +50 -19
  110. package/src/rust/components/viewer/render.rs +15 -13
  111. package/src/rust/components/viewer/settings.rs +137 -68
  112. package/src/rust/components/viewer/snapshots.rs +5 -10
  113. package/src/rust/components/viewer/wiring.rs +1 -21
  114. package/src/rust/components/viewer.rs +15 -10
  115. package/src/rust/components/window_editor.rs +8 -9
  116. package/src/rust/config/column_config_schema.rs +2 -27
  117. package/src/rust/config/mod.rs +0 -2
  118. package/src/rust/config/workspace_config.rs +2 -1
  119. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  120. package/src/rust/custom_elements/mod.rs +0 -3
  121. package/src/rust/custom_elements/viewer.rs +0 -18
  122. package/src/rust/js/plugin.rs +0 -86
  123. package/src/rust/lib.rs +12 -15
  124. package/src/rust/presentation/column_locator.rs +3 -7
  125. package/src/rust/presentation.rs +68 -38
  126. package/src/rust/queries/hosted_tables.rs +38 -0
  127. package/src/rust/queries/mod.rs +2 -0
  128. package/src/rust/queries/palette_set.rs +1 -1
  129. package/src/rust/renderer/activate.rs +1 -3
  130. package/src/rust/renderer/dispatch.rs +83 -22
  131. package/src/rust/renderer/limits.rs +24 -5
  132. package/src/rust/renderer/plugin_config.rs +1 -0
  133. package/src/rust/renderer/props.rs +2 -1
  134. package/src/rust/renderer.rs +14 -27
  135. package/src/rust/session/props.rs +4 -3
  136. package/src/rust/session/view_subscription.rs +19 -6
  137. package/src/rust/session.rs +38 -70
  138. package/src/rust/tasks/create_panel.rs +1 -1
  139. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  140. package/src/rust/tasks/eject.rs +1 -0
  141. package/src/rust/tasks/pipeline.rs +0 -60
  142. package/src/rust/tasks/presize_panels.rs +24 -1
  143. package/src/rust/tasks/restore_and_render.rs +0 -17
  144. package/src/rust/tasks/send_column_config.rs +1 -1
  145. package/src/rust/tasks/send_plugin_config.rs +1 -1
  146. package/src/rust/tasks/set_edit_mode.rs +1 -1
  147. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  148. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  149. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  150. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  151. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  152. package/src/rust/{components → ui}/containers/select.rs +0 -6
  153. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  154. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  155. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  156. package/src/rust/{components → ui}/context_menu.rs +8 -13
  157. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  158. package/src/rust/{components → ui}/font_loader.rs +0 -26
  159. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  160. package/src/rust/ui/form/intl_label.rs +81 -0
  161. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  162. package/src/rust/{components → ui}/form/mod.rs +1 -10
  163. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  164. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  165. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  166. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  167. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  168. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  169. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  170. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  171. package/src/rust/ui/mod.rs +56 -0
  172. package/src/rust/ui/modal.rs +61 -0
  173. package/src/rust/{components → ui}/portal.rs +174 -79
  174. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  175. package/src/rust/utils/completion.rs +0 -6
  176. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  177. package/src/rust/utils/mod.rs +2 -0
  178. package/src/rust/workspace.rs +35 -0
  179. package/src/svg/boolean-type.svg +3 -2
  180. package/src/svg/checkbox-checked-icon.svg +4 -7
  181. package/src/svg/checkbox-hover.svg +3 -3
  182. package/src/svg/checkbox-off.svg +2 -3
  183. package/src/svg/checkbox-on.svg +3 -3
  184. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  185. package/src/svg/datagrid-select-column.svg +8 -15
  186. package/src/svg/datagrid-select-region.svg +8 -15
  187. package/src/svg/datagrid-select-row-tree.svg +7 -12
  188. package/src/svg/datagrid-select-row.svg +8 -13
  189. package/src/svg/date-type.svg +4 -23
  190. package/src/svg/drawer-tab-hover.svg +4 -6
  191. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  192. package/src/svg/drawer-tab-invert.svg +3 -5
  193. package/src/svg/drawer-tab.svg +5 -5
  194. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  195. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  196. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  197. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  198. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  199. package/src/svg/new-panel-icon.svg +4 -0
  200. package/src/svg/number-type.svg +3 -3
  201. package/src/svg/radio-hover.svg +3 -3
  202. package/src/svg/radio-off.svg +3 -3
  203. package/src/svg/radio-on.svg +3 -3
  204. package/src/svg/status_ok.svg +3 -26
  205. package/src/svg/string-type.svg +4 -8
  206. package/src/svg/theme-icon.svg +5 -5
  207. package/src/themes/blueprint.css +0 -57
  208. package/src/themes/botanical.css +0 -36
  209. package/src/themes/defaults.css +1 -24
  210. package/src/themes/dracula.css +0 -58
  211. package/src/themes/eggplant.css +0 -58
  212. package/src/themes/gruvbox-dark.css +0 -65
  213. package/src/themes/gruvbox.css +1 -39
  214. package/src/themes/icons.css +1 -8
  215. package/src/themes/intl/ar.css +185 -0
  216. package/src/themes/intl/de.css +45 -35
  217. package/src/themes/intl/es.css +40 -30
  218. package/src/themes/intl/fr.css +39 -29
  219. package/src/themes/intl/ja.css +41 -31
  220. package/src/themes/intl/pt.css +37 -27
  221. package/src/themes/intl/zh.css +47 -37
  222. package/src/themes/intl.css +16 -3
  223. package/src/themes/ledger.css +1 -59
  224. package/src/themes/monokai.css +0 -51
  225. package/src/themes/nord.css +0 -55
  226. package/src/themes/phosphor.css +0 -48
  227. package/src/themes/pro-dark.css +0 -37
  228. package/src/themes/pro.css +3 -23
  229. package/src/themes/solarized-dark.css +0 -31
  230. package/src/themes/solarized.css +1 -24
  231. package/src/themes/vaporwave.css +0 -44
  232. package/src/themes/velvet.css +0 -60
  233. package/src/ts/extensions.ts +0 -13
  234. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  235. package/src/css/plugin-settings-panel.css +0 -171
  236. package/src/rust/components/modal.rs +0 -177
  237. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  238. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  239. package/src/rust/custom_elements/modal.rs +0 -299
  240. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
  241. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
  242. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
  243. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
  244. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
  245. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
  246. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  247. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  248. /package/src/css/{containers → ui/containers}/tabs.css +0 -0
  249. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  250. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  251. /package/src/css/{dom → ui/dom}/select.css +0 -0
  252. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  253. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  254. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  255. /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::presentation::ColumnTab;
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: ColumnTab> {
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: ColumnTab> {
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: ColumnTab> Component for TabList<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={tab.to_string()} />
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 [`ContextMenu`].
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 [`ContextMenu`]: a plain command [`ContextMenuItem`], 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. Purely presentational — the
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
- /// 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 {
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(super) fn new(host: HtmlElement, target: HtmlElement) -> Self {
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 title: Option<String>,
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
- <label id={props.title.as_deref().unwrap_or("color-label").to_owned()} />
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
+ }