Skip to main content

logicaffeine_web/ui/components/
inline_editor.rs

1//! The Studio's editor set into a page of prose: the Syntax Guide's examples, exercises and
2//! quoted snippets.
3//!
4//! It is [`CodeEditor`] with the language server on — the same highlighting, hovers and
5//! squiggles — sized to the code it holds instead of filling a panel: the highlight layer sits in
6//! the page's flow and gives the editor its height, and the text area lies exactly over it and
7//! never scrolls, so the editor grows line by line as the reader types. A page holds hundreds of
8//! them, so each is analysed once it first comes on screen, as VS Code analyses the files it has
9//! open rather than every file in the folder.
10//!
11//! The editor carries no stylesheet of its own: its host places [`InlineEditorStyle`] once, ahead
12//! of the host's markup, so a block's CSS always comes before what it styles.
13
14use dioxus::prelude::*;
15
16use super::code_editor::{CodeEditor, Language, CODE_EDITOR_STYLE};
17
18const INLINE_EDITOR_STYLE: &str = r#"
19.inline-editor .code-editor {
20    height: auto;
21    background: transparent;
22}
23.inline-editor .code-editor-input {
24    flex: none;
25    min-height: 0;
26}
27.inline-editor .code-editor-highlight {
28    position: relative;
29    height: auto;
30    overflow: hidden;
31    padding-bottom: 16px;
32}
33/* A new line at the very end is a line the reader sees at once: the space holds it open. */
34.inline-editor .code-editor-highlight::after {
35    content: " ";
36}
37.inline-editor .code-editor-textarea {
38    overflow: hidden;
39    padding-bottom: 16px;
40}
41@media (max-width: 768px) {
42    .inline-editor .code-editor-highlight,
43    .inline-editor .code-editor-textarea {
44        padding-bottom: 12px;
45    }
46}
47"#;
48
49/// The stylesheets an inline editor is drawn with: the editor's own, and its fit to its code.
50#[component]
51pub fn InlineEditorStyle() -> Element {
52    rsx! {
53        style { "{CODE_EDITOR_STYLE}" }
54        style { "{INLINE_EDITOR_STYLE}" }
55    }
56}
57
58#[component]
59pub fn InlineEditor(
60    value: String,
61    on_change: EventHandler<String>,
62    language: Language,
63    #[props(default = false)]
64    readonly: bool,
65) -> Element {
66    let mut seen = use_signal(|| false);
67
68    rsx! {
69        div {
70            class: "inline-editor",
71            onvisible: move |evt| {
72                if !*seen.peek() && evt.data().is_intersecting().unwrap_or(false) {
73                    seen.set(true);
74                }
75            },
76            CodeEditor {
77                value,
78                on_change,
79                language,
80                readonly,
81                language_server: seen(),
82                placeholder: String::new(),
83                stylesheet: false,
84            }
85        }
86    }
87}
88
89#[cfg(test)]
90#[path = "../../../../../crates/logicaffeine_tests/unit/logicaffeine_web/ui/components/inline_editor/tests.rs"]
91mod tests;