@perspective-dev/viewer 5.3.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) 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.d.ts +2 -0
  32. package/dist/esm/perspective-viewer.inline.js +8 -8
  33. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  34. package/dist/esm/perspective-viewer.js +5 -5
  35. package/dist/esm/perspective-viewer.js.map +2 -2
  36. package/dist/esm/plugin.d.ts +9 -0
  37. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  43. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  44. package/dist/wasm/perspective-viewer.d.ts +137 -104
  45. package/dist/wasm/perspective-viewer.js +337 -269
  46. package/dist/wasm/perspective-viewer.wasm +0 -0
  47. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
  48. package/package.json +7 -7
  49. package/src/css/_context_menu.css +1 -1
  50. package/src/css/_dropdown_menu.css +1 -1
  51. package/src/css/_viewer_bundle.css +7 -8
  52. package/src/css/column-dropdown.css +0 -3
  53. package/src/css/column-selector.css +18 -7
  54. package/src/css/column-settings-panel.css +0 -225
  55. package/src/css/column-style.css +175 -0
  56. package/src/css/config-selector.css +19 -11
  57. package/src/css/filter-dropdown.css +5 -5
  58. package/src/css/form/debug.css +37 -4
  59. package/src/css/function-dropdown.css +5 -5
  60. package/src/css/panel-tab.css +23 -0
  61. package/src/css/plugin-selector.css +5 -5
  62. package/src/css/render-warning.css +1 -1
  63. package/src/css/status-bar.css +63 -21
  64. package/src/css/type-icon.css +1 -3
  65. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  66. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  67. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  68. package/src/css/{containers → ui/containers}/tabs.css +2 -1
  69. package/src/css/viewer.css +32 -26
  70. package/src/rust/agent/tools.rs +80 -7
  71. package/src/rust/components/chat_panel.rs +1 -1
  72. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  73. package/src/rust/components/column_dropdown.rs +2 -2
  74. package/src/rust/components/column_selector/active_column.rs +24 -9
  75. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  76. package/src/rust/components/column_selector/config_selector.rs +2 -2
  77. package/src/rust/components/column_selector/filter_column.rs +8 -5
  78. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  79. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  80. package/src/rust/components/column_selector/sort_column.rs +7 -4
  81. package/src/rust/components/column_selector.rs +14 -3
  82. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  83. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  84. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  85. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
  86. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  87. package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
  88. package/src/rust/components/column_settings_sidebar.rs +3 -4
  89. package/src/rust/components/copy_dropdown.rs +1 -1
  90. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  91. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  92. package/src/rust/components/datetime_column_style.rs +1 -2
  93. package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
  94. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  95. package/src/rust/components/export_dropdown.rs +1 -1
  96. package/src/rust/components/expression_editor.rs +1 -1
  97. package/src/rust/components/filter_dropdown.rs +2 -2
  98. package/src/rust/components/function_dropdown.rs +2 -2
  99. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  100. package/src/rust/components/main_panel/msg.rs +4 -0
  101. package/src/rust/components/main_panel/reconcile.rs +9 -4
  102. package/src/rust/components/main_panel/render.rs +62 -11
  103. package/src/rust/components/main_panel/update.rs +39 -10
  104. package/src/rust/components/main_panel.rs +40 -2
  105. package/src/rust/components/mod.rs +5 -8
  106. package/src/rust/components/new_panel_menu.rs +97 -0
  107. package/src/rust/components/number_series_style.rs +2 -3
  108. package/src/rust/components/panel_menu.rs +61 -87
  109. package/src/rust/components/panel_tab.rs +100 -43
  110. package/src/rust/components/plugin_tab.rs +58 -4
  111. package/src/rust/components/render_warning.rs +2 -2
  112. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  113. package/src/rust/components/settings_panel.rs +8 -2
  114. package/src/rust/components/status_bar.rs +113 -19
  115. package/src/rust/components/status_indicator.rs +9 -14
  116. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  117. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  118. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  119. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  120. package/src/rust/components/viewer/msg.rs +43 -30
  121. package/src/rust/components/viewer/panels.rs +53 -19
  122. package/src/rust/components/viewer/render.rs +16 -13
  123. package/src/rust/components/viewer/settings.rs +137 -68
  124. package/src/rust/components/viewer/snapshots.rs +5 -10
  125. package/src/rust/components/viewer/wiring.rs +11 -22
  126. package/src/rust/components/viewer.rs +15 -10
  127. package/src/rust/components/window_editor.rs +8 -9
  128. package/src/rust/config/column_config_schema.rs +312 -33
  129. package/src/rust/config/datetime_column_style.rs +0 -11
  130. package/src/rust/config/mod.rs +0 -4
  131. package/src/rust/config/options.rs +50 -0
  132. package/src/rust/config/workspace_config.rs +70 -24
  133. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  134. package/src/rust/custom_elements/mod.rs +0 -3
  135. package/src/rust/custom_elements/viewer.rs +194 -115
  136. package/src/rust/js/plugin.rs +3 -87
  137. package/src/rust/lib.rs +19 -18
  138. package/src/rust/presentation/column_locator.rs +3 -7
  139. package/src/rust/presentation.rs +68 -38
  140. package/src/rust/queries/hosted_tables.rs +38 -0
  141. package/src/rust/queries/mod.rs +2 -0
  142. package/src/rust/queries/palette_set.rs +1 -1
  143. package/src/rust/queries/plugin_column_styles.rs +6 -5
  144. package/src/rust/renderer/activate.rs +1 -3
  145. package/src/rust/renderer/dispatch.rs +83 -22
  146. package/src/rust/renderer/limits.rs +24 -5
  147. package/src/rust/renderer/plugin_config.rs +201 -41
  148. package/src/rust/renderer/plugin_store.rs +2 -1
  149. package/src/rust/renderer/props.rs +7 -1
  150. package/src/rust/renderer.rs +23 -27
  151. package/src/rust/session/props.rs +8 -3
  152. package/src/rust/session/view_subscription.rs +19 -6
  153. package/src/rust/session.rs +189 -150
  154. package/src/rust/tasks/create_panel.rs +5 -2
  155. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  156. package/src/rust/tasks/eject.rs +1 -0
  157. package/src/rust/tasks/pipeline.rs +18 -67
  158. package/src/rust/tasks/presize_panels.rs +24 -1
  159. package/src/rust/tasks/restore_and_render.rs +0 -17
  160. package/src/rust/tasks/restore_panel.rs +129 -30
  161. package/src/rust/tasks/send_column_config.rs +4 -1
  162. package/src/rust/tasks/send_plugin_config.rs +1 -1
  163. package/src/rust/tasks/set_edit_mode.rs +1 -1
  164. package/src/rust/tasks/sync_update_panels.rs +147 -67
  165. package/src/rust/tasks/table_lifecycle.rs +7 -1
  166. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  167. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  168. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  169. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  170. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  171. package/src/rust/{components → ui}/containers/select.rs +0 -6
  172. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  173. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  174. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  175. package/src/rust/{components → ui}/context_menu.rs +8 -13
  176. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  177. package/src/rust/{components → ui}/font_loader.rs +0 -26
  178. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  179. package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
  180. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  181. package/src/rust/{components → ui}/form/mod.rs +1 -10
  182. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  183. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  184. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  185. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  186. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  187. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  188. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  189. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  190. package/src/rust/ui/mod.rs +56 -0
  191. package/src/rust/ui/modal.rs +61 -0
  192. package/src/rust/{components → ui}/portal.rs +175 -79
  193. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  194. package/src/rust/utils/completion.rs +0 -6
  195. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  196. package/src/rust/utils/css_vars.rs +13 -0
  197. package/src/rust/utils/local_fonts.rs +395 -0
  198. package/src/rust/utils/mod.rs +4 -0
  199. package/src/rust/workspace.rs +47 -0
  200. package/src/svg/boolean-type.svg +3 -2
  201. package/src/svg/checkbox-checked-icon.svg +4 -7
  202. package/src/svg/checkbox-hover.svg +3 -3
  203. package/src/svg/checkbox-off.svg +2 -3
  204. package/src/svg/checkbox-on.svg +3 -3
  205. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  206. package/src/svg/datagrid-select-column.svg +8 -15
  207. package/src/svg/datagrid-select-region.svg +8 -15
  208. package/src/svg/datagrid-select-row-tree.svg +7 -12
  209. package/src/svg/datagrid-select-row.svg +8 -13
  210. package/src/svg/date-type.svg +4 -23
  211. package/src/svg/drawer-tab-hover.svg +4 -6
  212. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  213. package/src/svg/drawer-tab-invert.svg +3 -5
  214. package/src/svg/drawer-tab.svg +5 -5
  215. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  216. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  217. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  218. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  219. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  220. package/src/svg/new-panel-icon.svg +4 -0
  221. package/src/svg/number-type.svg +3 -3
  222. package/src/svg/radio-hover.svg +3 -3
  223. package/src/svg/radio-off.svg +3 -3
  224. package/src/svg/radio-on.svg +3 -3
  225. package/src/svg/status_ok.svg +3 -26
  226. package/src/svg/status_pending.svg +3 -0
  227. package/src/svg/string-type.svg +4 -8
  228. package/src/svg/theme-icon.svg +5 -5
  229. package/src/themes/blueprint.css +0 -57
  230. package/src/themes/botanical.css +0 -36
  231. package/src/themes/defaults.css +1 -24
  232. package/src/themes/dracula.css +0 -58
  233. package/src/themes/eggplant.css +0 -58
  234. package/src/themes/gruvbox-dark.css +0 -65
  235. package/src/themes/gruvbox.css +1 -39
  236. package/src/themes/icons.css +2 -8
  237. package/src/themes/intl/ar.css +198 -0
  238. package/src/themes/intl/de.css +63 -40
  239. package/src/themes/intl/es.css +58 -35
  240. package/src/themes/intl/fr.css +57 -34
  241. package/src/themes/intl/ja.css +59 -36
  242. package/src/themes/intl/pt.css +54 -31
  243. package/src/themes/intl/zh.css +65 -42
  244. package/src/themes/intl.css +34 -8
  245. package/src/themes/ledger.css +1 -59
  246. package/src/themes/monokai.css +0 -51
  247. package/src/themes/nord.css +0 -55
  248. package/src/themes/phosphor.css +0 -48
  249. package/src/themes/pro-dark.css +0 -37
  250. package/src/themes/pro.css +3 -23
  251. package/src/themes/solarized-dark.css +0 -31
  252. package/src/themes/solarized.css +1 -24
  253. package/src/themes/vaporwave.css +0 -44
  254. package/src/themes/velvet.css +0 -60
  255. package/src/ts/extensions.ts +0 -13
  256. package/src/ts/perspective-viewer.ts +2 -0
  257. package/src/ts/plugin.ts +9 -0
  258. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  259. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  260. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  261. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  262. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  263. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  264. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  265. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  266. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  267. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  268. package/src/css/plugin-settings-panel.css +0 -171
  269. package/src/rust/components/modal.rs +0 -177
  270. package/src/rust/components/string_column_style.rs +0 -121
  271. package/src/rust/config/string_column_style.rs +0 -135
  272. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  273. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  274. package/src/rust/custom_elements/modal.rs +0 -299
  275. package/src/ts/ts-rs/FormatMode.ts +0 -3
  276. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  277. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
  278. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
  279. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
  280. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
  281. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
  282. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
  283. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  284. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  285. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  286. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  287. /package/src/css/{dom → ui/dom}/select.css +0 -0
  288. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  289. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  290. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  291. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -0,0 +1,395 @@
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
+ //! Page-global Local Font Access state shared by every `Font`
14
+ //! control on the page: the `local-fonts` permission, the enumerated
15
+ //! family list once granted, and a measured web-safe fallback list
16
+ //! otherwise.
17
+
18
+ use std::cell::{Cell, RefCell};
19
+ use std::rc::Rc;
20
+
21
+ use perspective_js::utils::global;
22
+ use wasm_bindgen::prelude::*;
23
+ use wasm_bindgen_futures::JsFuture;
24
+ use yew::Callback;
25
+
26
+ use super::{PubSub, Subscription};
27
+
28
+ /// CSS generic families always offered first, in this order.
29
+ pub const GENERIC_FONT_FAMILIES: [&str; 5] =
30
+ ["inherit", "monospace", "sans-serif", "serif", "system-ui"];
31
+
32
+ const FALLBACK_FONT_FAMILIES: [&str; 28] = [
33
+ "Arial",
34
+ "Arial Black",
35
+ "Calibri",
36
+ "Cambria",
37
+ "Comic Sans MS",
38
+ "Consolas",
39
+ "Courier New",
40
+ "DejaVu Sans",
41
+ "DejaVu Serif",
42
+ "Georgia",
43
+ "Helvetica",
44
+ "Helvetica Neue",
45
+ "Impact",
46
+ "Liberation Sans",
47
+ "Liberation Serif",
48
+ "Lucida Console",
49
+ "Menlo",
50
+ "Monaco",
51
+ "Noto Sans",
52
+ "Noto Serif",
53
+ "Roboto",
54
+ "SF Mono",
55
+ "Segoe UI",
56
+ "Tahoma",
57
+ "Times New Roman",
58
+ "Trebuchet MS",
59
+ "Ubuntu",
60
+ "Verdana",
61
+ ];
62
+
63
+ const FONT_TEST_SAMPLE: &str = "mmmmmmmmmmlli ABCDΔ 0123";
64
+ const FONT_TEST_BASELINES: [&str; 3] = ["monospace", "sans-serif", "serif"];
65
+
66
+ /// The `local-fonts` permission as last observed.
67
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
68
+ pub enum LocalFontsAccess {
69
+ /// No `window.queryLocalFonts` (non-Chromium, or insecure context).
70
+ Unsupported,
71
+ Prompt,
72
+ Granted,
73
+ Denied,
74
+ }
75
+
76
+ #[derive(Default)]
77
+ struct LocalFontsState {
78
+ access: Cell<Option<LocalFontsAccess>>,
79
+ probing: Cell<bool>,
80
+ families: RefCell<Option<Rc<Vec<String>>>>,
81
+ fallback: RefCell<Option<Rc<Vec<String>>>>,
82
+ on_status_change: RefCell<Option<Closure<dyn Fn()>>>,
83
+ changed: PubSub<()>,
84
+ }
85
+
86
+ thread_local! {
87
+ static STATE: LocalFontsState = LocalFontsState::default();
88
+ }
89
+
90
+ /// The permission state, or `None` before [`probe_local_fonts_access`]
91
+ /// has resolved.
92
+ pub fn local_fonts_access() -> Option<LocalFontsAccess> {
93
+ STATE.with(|s| s.access.get())
94
+ }
95
+
96
+ /// Subscribe to any change in access state or family list.
97
+ pub fn on_local_fonts_changed(cb: Callback<()>) -> Subscription {
98
+ STATE.with(|s| s.changed.add_notify_listener(&cb))
99
+ }
100
+
101
+ /// The options a `Font` control offers for `current`: the generic
102
+ /// keywords, then the enumerated families when granted (and non-empty) or
103
+ /// the measured fallback otherwise, then `current` itself if absent.
104
+ pub fn font_family_options(current: &str) -> Vec<String> {
105
+ let installed = STATE.with(|s| {
106
+ let enumerated = match s.access.get() {
107
+ Some(LocalFontsAccess::Granted) => s.families.borrow().clone(),
108
+ _ => None,
109
+ };
110
+
111
+ match enumerated {
112
+ Some(families) if !families.is_empty() => families,
113
+ _ => fallback_families(s),
114
+ }
115
+ });
116
+
117
+ compose_font_family_options(current, &installed)
118
+ }
119
+
120
+ fn compose_font_family_options(current: &str, installed: &[String]) -> Vec<String> {
121
+ let mut out: Vec<String> = GENERIC_FONT_FAMILIES
122
+ .iter()
123
+ .map(|x| (*x).to_owned())
124
+ .collect();
125
+
126
+ out.extend(installed.iter().cloned());
127
+ if !out.iter().any(|x| x == current) {
128
+ out.push(current.to_owned());
129
+ }
130
+
131
+ out
132
+ }
133
+
134
+ /// Resolve the permission state once, without ever showing a prompt.
135
+ pub fn probe_local_fonts_access() {
136
+ let already = STATE.with(|s| s.access.get().is_some() || s.probing.replace(true));
137
+ if already {
138
+ return;
139
+ }
140
+
141
+ if query_local_fonts_fn().is_none() {
142
+ set_access(LocalFontsAccess::Unsupported);
143
+ return;
144
+ }
145
+
146
+ wasm_bindgen_futures::spawn_local(async move {
147
+ match query_permission_status().await {
148
+ Some(status) => {
149
+ install_status_listener(&status);
150
+ let access = access_of_status(&status);
151
+ set_access(access);
152
+ if access == LocalFontsAccess::Granted {
153
+ load_families().await;
154
+ }
155
+ },
156
+ None => match call_query_local_fonts() {
157
+ Some(promise) => match families_from(promise).await {
158
+ Some(families) => set_granted(families),
159
+ None => set_access(LocalFontsAccess::Prompt),
160
+ },
161
+ None => set_access(LocalFontsAccess::Unsupported),
162
+ },
163
+ }
164
+ });
165
+ }
166
+
167
+ /// Ask the browser for local fonts, synchronously from a user-gesture handler
168
+ /// so `queryLocalFonts()` may prompt.
169
+ pub fn request_local_fonts_access() {
170
+ let Some(promise) = call_query_local_fonts() else {
171
+ set_access(LocalFontsAccess::Unsupported);
172
+ return;
173
+ };
174
+
175
+ wasm_bindgen_futures::spawn_local(async move {
176
+ match families_from(promise).await {
177
+ Some(families) => set_granted(families),
178
+ None => {
179
+ let access = match query_permission_status().await {
180
+ Some(status) => access_of_status(&status),
181
+ None => LocalFontsAccess::Prompt,
182
+ };
183
+
184
+ set_access(access);
185
+ },
186
+ }
187
+ });
188
+ }
189
+
190
+ fn set_access(access: LocalFontsAccess) {
191
+ STATE.with(|s| {
192
+ s.probing.set(false);
193
+ let prev = s.access.replace(Some(access));
194
+ if access != LocalFontsAccess::Granted {
195
+ s.families.borrow_mut().take();
196
+ }
197
+
198
+ if prev != Some(access) {
199
+ s.changed.emit(());
200
+ }
201
+ });
202
+ }
203
+
204
+ fn set_granted(families: Vec<String>) {
205
+ STATE.with(|s| {
206
+ s.probing.set(false);
207
+ s.access.set(Some(LocalFontsAccess::Granted));
208
+ *s.families.borrow_mut() = Some(Rc::new(families));
209
+ s.changed.emit(());
210
+ });
211
+ }
212
+
213
+ async fn load_families() {
214
+ if let Some(promise) = call_query_local_fonts()
215
+ && let Some(families) = families_from(promise).await
216
+ {
217
+ set_granted(families);
218
+ }
219
+ }
220
+
221
+ fn query_local_fonts_fn() -> Option<js_sys::Function> {
222
+ js_sys::Reflect::get(&global::window(), &JsValue::from_str("queryLocalFonts"))
223
+ .ok()?
224
+ .dyn_into::<js_sys::Function>()
225
+ .ok()
226
+ }
227
+
228
+ fn call_query_local_fonts() -> Option<js_sys::Promise> {
229
+ query_local_fonts_fn()?
230
+ .call0(&global::window())
231
+ .ok()?
232
+ .dyn_into::<js_sys::Promise>()
233
+ .ok()
234
+ }
235
+
236
+ /// The enumerated families, or `None` when the call yielded none, which
237
+ /// Chromium resolves in place of rejecting a dismissed or blocked prompt.
238
+ async fn families_from(promise: js_sys::Promise) -> Option<Vec<String>> {
239
+ let fonts = JsFuture::from(promise).await.ok()?;
240
+ let family_key = JsValue::from_str("family");
241
+ let mut families: Vec<String> = js_sys::Array::from(&fonts)
242
+ .iter()
243
+ .filter_map(|font| js_sys::Reflect::get(&font, &family_key).ok()?.as_string())
244
+ .collect();
245
+
246
+ families.sort_unstable();
247
+ families.dedup();
248
+ (!families.is_empty()).then_some(families)
249
+ }
250
+
251
+ async fn query_permission_status() -> Option<JsValue> {
252
+ let navigator: JsValue = global::window().navigator().into();
253
+ let permissions = js_sys::Reflect::get(&navigator, &JsValue::from_str("permissions")).ok()?;
254
+ let query = js_sys::Reflect::get(&permissions, &JsValue::from_str("query"))
255
+ .ok()?
256
+ .dyn_into::<js_sys::Function>()
257
+ .ok()?;
258
+
259
+ let descriptor = js_sys::Object::new();
260
+ js_sys::Reflect::set(
261
+ &descriptor,
262
+ &JsValue::from_str("name"),
263
+ &JsValue::from_str("local-fonts"),
264
+ )
265
+ .ok()?;
266
+
267
+ let promise = query
268
+ .call1(&permissions, &descriptor)
269
+ .ok()?
270
+ .dyn_into::<js_sys::Promise>()
271
+ .ok()?;
272
+
273
+ let status = JsFuture::from(promise).await.ok()?;
274
+ (!status.is_undefined() && !status.is_null()).then_some(status)
275
+ }
276
+
277
+ fn access_of_status(status: &JsValue) -> LocalFontsAccess {
278
+ let state = js_sys::Reflect::get(status, &JsValue::from_str("state"))
279
+ .ok()
280
+ .and_then(|x| x.as_string());
281
+
282
+ match state.as_deref() {
283
+ Some("granted") => LocalFontsAccess::Granted,
284
+ Some("denied") => LocalFontsAccess::Denied,
285
+ _ => LocalFontsAccess::Prompt,
286
+ }
287
+ }
288
+
289
+ fn install_status_listener(status: &JsValue) {
290
+ let target = status.clone();
291
+ let closure = Closure::<dyn Fn()>::new(move || {
292
+ let access = access_of_status(&target);
293
+ set_access(access);
294
+ if access == LocalFontsAccess::Granted {
295
+ wasm_bindgen_futures::spawn_local(load_families());
296
+ }
297
+ });
298
+
299
+ let _ = js_sys::Reflect::set(
300
+ status,
301
+ &JsValue::from_str("onchange"),
302
+ closure.as_ref().unchecked_ref(),
303
+ );
304
+
305
+ STATE.with(|s| *s.on_status_change.borrow_mut() = Some(closure));
306
+ }
307
+
308
+ fn fallback_families(state: &LocalFontsState) -> Rc<Vec<String>> {
309
+ state
310
+ .fallback
311
+ .borrow_mut()
312
+ .get_or_insert_with(|| Rc::new(detect_fallback_families()))
313
+ .clone()
314
+ }
315
+
316
+ /// The subset of [`FALLBACK_FONT_FAMILIES`] the browser can render,
317
+ /// detected by comparing rendered widths against the generic baselines.
318
+ fn detect_fallback_families() -> Vec<String> {
319
+ let Some(ctx) = canvas_context() else {
320
+ return vec![];
321
+ };
322
+
323
+ let baselines: Vec<f64> = FONT_TEST_BASELINES
324
+ .iter()
325
+ .map(|x| measure_width(&ctx, x))
326
+ .collect();
327
+
328
+ FALLBACK_FONT_FAMILIES
329
+ .iter()
330
+ .filter(|family| {
331
+ FONT_TEST_BASELINES
332
+ .iter()
333
+ .zip(&baselines)
334
+ .any(|(baseline, width)| {
335
+ measure_width(&ctx, &format!("\"{family}\", {baseline}")) != *width
336
+ })
337
+ })
338
+ .map(|x| (*x).to_owned())
339
+ .collect()
340
+ }
341
+
342
+ fn canvas_context() -> Option<web_sys::CanvasRenderingContext2d> {
343
+ global::document()
344
+ .create_element("canvas")
345
+ .ok()?
346
+ .dyn_into::<web_sys::HtmlCanvasElement>()
347
+ .ok()?
348
+ .get_context("2d")
349
+ .ok()??
350
+ .dyn_into::<web_sys::CanvasRenderingContext2d>()
351
+ .ok()
352
+ }
353
+
354
+ fn measure_width(ctx: &web_sys::CanvasRenderingContext2d, family: &str) -> f64 {
355
+ ctx.set_font(&format!("72px {family}"));
356
+ ctx.measure_text(FONT_TEST_SAMPLE)
357
+ .map(|x| x.width())
358
+ .unwrap_or_default()
359
+ }
360
+
361
+ #[cfg(test)]
362
+ mod tests {
363
+ use super::*;
364
+
365
+ fn owned(xs: &[&str]) -> Vec<String> {
366
+ xs.iter().map(|x| (*x).to_owned()).collect()
367
+ }
368
+
369
+ #[test]
370
+ fn generic_families_lead_and_current_appends_when_missing() {
371
+ let options = compose_font_family_options("Zapf Chancery", &owned(&["Arial", "Menlo"]));
372
+ assert_eq!(
373
+ options,
374
+ owned(&[
375
+ "inherit",
376
+ "monospace",
377
+ "sans-serif",
378
+ "serif",
379
+ "system-ui",
380
+ "Arial",
381
+ "Menlo",
382
+ "Zapf Chancery"
383
+ ])
384
+ );
385
+ }
386
+
387
+ #[test]
388
+ fn current_is_not_duplicated() {
389
+ let options = compose_font_family_options("Arial", &owned(&["Arial"]));
390
+ assert_eq!(options.iter().filter(|x| *x == "Arial").count(), 1);
391
+
392
+ let options = compose_font_family_options("inherit", &[]);
393
+ assert_eq!(options, owned(&GENERIC_FONT_FAMILIES));
394
+ }
395
+ }
@@ -18,11 +18,13 @@
18
18
 
19
19
  mod browser;
20
20
  mod completion;
21
+ mod css;
21
22
  mod css_vars;
22
23
  mod custom_element;
23
24
  mod datetime;
24
25
  mod debounce;
25
26
  mod hooks;
27
+ mod local_fonts;
26
28
  mod modal_position;
27
29
  mod number_format;
28
30
  mod pending_effects;
@@ -36,11 +38,13 @@ mod tests;
36
38
 
37
39
  pub use browser::*;
38
40
  pub use completion::*;
41
+ pub use css::*;
39
42
  pub use css_vars::*;
40
43
  pub use custom_element::*;
41
44
  pub use datetime::*;
42
45
  pub use debounce::*;
43
46
  pub use hooks::*;
47
+ pub use local_fonts::*;
44
48
  pub use modal_position::*;
45
49
  pub use number_format::*;
46
50
  pub use pending_effects::*;
@@ -327,6 +327,16 @@ struct WorkspaceData {
327
327
  /// Fires on every [`PanelPhase`] transition.
328
328
  staged_changed: Rc<PubSub<()>>,
329
329
 
330
+ /// Fires when a layout tree is staged for a RETAINED panel set (a
331
+ /// `panels`-less `restoreWorkspace`), whose props-driven render would
332
+ /// otherwise never consume it.
333
+ layout_staged: Rc<PubSub<()>>,
334
+
335
+ /// Panels removed from `panels` whose layout cell has not yet been
336
+ /// reclaimed, so their engines and plugin pixels stay alive until the
337
+ /// commit.
338
+ closing: Vec<Panel>,
339
+
330
340
  /// Coalesced `layout_changed`/`active_changed` delivery (see
331
341
  /// [`LayoutEmitter`]).
332
342
  emitter: LayoutEmitter,
@@ -352,6 +362,8 @@ impl Workspace {
352
362
  pending_layout: None,
353
363
  effects: EffectLedger::default(),
354
364
  staged_changed: Rc::new(PubSub::default()),
365
+ layout_staged: Rc::new(PubSub::default()),
366
+ closing: Vec::new(),
355
367
  emitter: LayoutEmitter::default(),
356
368
  })))
357
369
  }
@@ -414,6 +426,12 @@ impl Workspace {
414
426
  self.0.borrow().staged_changed.clone()
415
427
  }
416
428
 
429
+ /// A handle to the `layout_staged` PubSub (see
430
+ /// [`WorkspaceData::layout_staged`]).
431
+ pub fn layout_staged(&self) -> Rc<PubSub<()>> {
432
+ self.0.borrow().layout_staged.clone()
433
+ }
434
+
417
435
  /// Promote a staged panel toward layout insertion, returning whether it
418
436
  /// was still staged — restore completion and the staging deadline RACE
419
437
  /// to promote, and only the winner proceeds. Emits `staged_changed` —
@@ -823,6 +841,35 @@ impl Workspace {
823
841
  removed
824
842
  }
825
843
 
844
+ /// Park a panel just removed by [`Self::remove_panel`] until its layout
845
+ /// cell is reclaimed (see [`WorkspaceData::closing`]).
846
+ pub fn stash_closing(&self, panel: Panel) {
847
+ self.0.borrow_mut().closing.push(panel);
848
+ }
849
+
850
+ /// A parked closing panel by id (clone; shares engine state).
851
+ pub fn closing_panel(&self, id: &PanelId) -> Option<Panel> {
852
+ self.0
853
+ .borrow()
854
+ .closing
855
+ .iter()
856
+ .find(|p| &p.id == id)
857
+ .cloned()
858
+ }
859
+
860
+ /// Un-park a closing panel for disposal, once its cell is gone.
861
+ pub fn take_closing(&self, id: &PanelId) -> Option<Panel> {
862
+ let mut data = self.0.borrow_mut();
863
+ let idx = data.closing.iter().position(|p| &p.id == id)?;
864
+ Some(data.closing.remove(idx))
865
+ }
866
+
867
+ /// Un-park EVERY closing panel — element teardown must dispose them
868
+ /// too, or their `View`/plugin would leak.
869
+ pub fn take_all_closing(&self) -> Vec<Panel> {
870
+ std::mem::take(&mut self.0.borrow_mut().closing)
871
+ }
872
+
826
873
  /// Set the active panel. Returns `false` (no-op) if `id` is not a known
827
874
  /// panel.
828
875
  pub fn set_active(&self, id: PanelId) -> bool {
@@ -1,3 +1,4 @@
1
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <path d="M0.5 5.8V8C0.5 8.82843 1.17157 9.5 2 9.5H4.8C5.62843 9.5 6.3 8.82843 6.3 8V7.2C6.3 6.92386 6.52386 6.7 6.8 6.7H8C8.82843 6.7 9.5 6.02843 9.5 5.2V3C9.5 2.17157 8.82843 1.5 8 1.5H5.2C4.37157 1.5 3.7 2.17157 3.7 3V3.8C3.7 4.07614 3.47614 4.3 3.2 4.3H2C1.17157 4.3 0.5 4.97157 0.5 5.8Z" stroke="#042121"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="1.5" y="3.5" width="10" height="6" rx="3"/>
3
+ <circle cx="8.4" cy="6.5" r="1.9" fill="#042121" stroke="none"/>
3
4
  </svg>
@@ -1,7 +1,4 @@
1
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <defs></defs>
3
- <path
4
- d="M 2 0 L 12 0 C 13.105 0 14 0.895 14 2 L 14 12 C 14 13.105 13.105 14 12 14 L 2 14 C 0.895 14 0 13.105 0 12 L 0 2 C 0 0.895 0.895 0 2 0 Z M 3.646 7.354 L 5.646 9.354 L 6 9.707 L 6.354 9.354 L 10.854 4.854 L 10.146 4.146 L 6 8.293 L 4.354 6.646 Z"
5
- fill="#000000"></path>
6
- <path d="M4 7L6 9L10.5 4.5" style="visibility: hidden;"></path>
7
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="0.5" y="0.5" width="13" height="13" rx="2"/>
3
+ <path d="M3.5 7l2.5 2.5 4.5-4.5" stroke-width="1.2"/>
4
+ </svg>
@@ -1,4 +1,4 @@
1
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <rect x="0.5" y="0.5" width="13" height="13" rx="1.5" stroke="#3F4343"/>
3
- <rect x="4" y="4" width="6" height="6" rx="2" fill="#3F4343"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="0.5" y="0.5" width="13" height="13" rx="2"/>
3
+ <circle cx="7" cy="7" r="1.5" fill="#042121" stroke="none"/>
4
4
  </svg>
@@ -1,4 +1,3 @@
1
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <rect x="0.5" y="0.5" width="13" height="13" rx="1.5" stroke="#3F4343"/>
3
- <rect x="5" y="5" width="4" height="4" rx="2" fill="#3F4343"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="0.5" y="0.5" width="13" height="13" rx="2"/>
4
3
  </svg>
@@ -1,4 +1,4 @@
1
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <rect x="0.5" y="0.5" width="13" height="13" rx="1.5" stroke="#3F4343"/>
3
- <rect x="2" y="2" width="10" height="10" rx="2" fill="#3F4343"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="0.5" y="0.5" width="13" height="13" rx="2"/>
3
+ <path d="M3.5 7l2.5 2.5 4.5-4.5" stroke-width="1.2"/>
4
4
  </svg>
@@ -1,3 +1,3 @@
1
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <rect x="0.5" y="0.5" width="13" height="13" rx="1.5" stroke="#000000" />
3
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="0.5" y="0.5" width="13" height="13" rx="2"/>
3
+ </svg>
@@ -1,15 +1,8 @@
1
- <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <defs></defs>
3
- <rect x="3" y="4" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
4
- </rect>
5
- <rect x="3" y="6.973" width="15" height="1"
6
- style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;"></rect>
7
- <rect x="3" y="10" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
8
- </rect>
9
- <rect x="3" y="13" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
10
- </rect>
11
- <rect x="3" y="16" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
12
- </rect>
13
- <rect style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;" y="4" width="3" height="13" x="6">
14
- </rect>
15
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M3 4.5h15"/>
3
+ <path d="M3 7.5h15"/>
4
+ <path d="M3 10.5h15"/>
5
+ <path d="M3 13.5h15"/>
6
+ <path d="M3 16.5h15"/>
7
+ <rect x="6" y="4" width="3" height="13" fill="#042121" stroke="none"/>
8
+ </svg>
@@ -1,15 +1,8 @@
1
- <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <defs></defs>
3
- <rect x="3" y="4" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
4
- </rect>
5
- <rect x="3" y="6.973" width="15" height="1"
6
- style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;"></rect>
7
- <rect x="3" y="10" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
8
- </rect>
9
- <rect x="3" y="13" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
10
- </rect>
11
- <rect x="3" y="16" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
12
- </rect>
13
- <rect style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;" y="7" width="6" height="7" x="6">
14
- </rect>
15
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M3 4.5h15"/>
3
+ <path d="M3 7.5h15"/>
4
+ <path d="M3 10.5h15"/>
5
+ <path d="M3 13.5h15"/>
6
+ <path d="M3 16.5h15"/>
7
+ <rect x="6" y="7" width="6" height="7" fill="#042121" stroke="none"/>
8
+ </svg>
@@ -1,13 +1,8 @@
1
- <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <defs></defs>
3
- <rect x="3" y="4" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
4
- </rect>
5
- <rect x="5" y="6.973" width="13" height="4"
6
- style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;"></rect>
7
- <rect x="7" y="10" width="11" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
8
- </rect>
9
- <rect x="5" y="13" width="13" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
10
- </rect>
11
- <rect x="3" y="16" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
12
- </rect>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M3 4.5h15"/>
3
+ <path d="M5 7.5h13"/>
4
+ <path d="M7 10.5h11"/>
5
+ <path d="M5 13.5h13"/>
6
+ <path d="M3 16.5h15"/>
7
+ <rect x="5" y="7" width="13" height="4" fill="#042121" stroke="none"/>
13
8
  </svg>
@@ -1,13 +1,8 @@
1
- <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <defs></defs>
3
- <rect x="3" y="4" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
4
- </rect>
5
- <rect x="3" y="6.973" width="15" height="4"
6
- style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;"></rect>
7
- <rect x="3" y="10" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
8
- </rect>
9
- <rect x="3" y="13" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
10
- </rect>
11
- <rect x="3" y="16" width="15" height="1" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 0px;">
12
- </rect>
13
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none" stroke="#042121" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M3 4.5h15"/>
3
+ <path d="M3 7.5h15"/>
4
+ <path d="M3 10.5h15"/>
5
+ <path d="M3 13.5h15"/>
6
+ <path d="M3 16.5h15"/>
7
+ <rect x="3" y="7" width="15" height="4" fill="#042121" stroke="none"/>
8
+ </svg>