composeai 0.1.1
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/README.md +265 -0
- package/dist/index.cjs +4750 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +896 -0
- package/dist/index.d.ts +896 -0
- package/dist/index.js +4747 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
- package/src/composer.css +481 -0
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "composeai",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "The modern React composer for AI applications — a drop-in Lexical-powered chat input with markdown, mentions, slash commands, attachments, voice, streaming stop, and opt-in plugins for copilots, chatbots, and agent UIs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./composer.css": "./src/composer.css"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src/composer.css",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": [
|
|
23
|
+
"./src/composer.css"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"clean": "rm -rf dist"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
33
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
34
|
+
"mermaid": "^10 || ^11"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"mermaid": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@lexical/react": "^0.45.0",
|
|
43
|
+
"lexical": "^0.45.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/react": "^18.3.12",
|
|
47
|
+
"@types/react-dom": "^18.3.1",
|
|
48
|
+
"react": "^18.3.1",
|
|
49
|
+
"react-dom": "^18.3.1",
|
|
50
|
+
"tsup": "^8.3.0",
|
|
51
|
+
"typescript": "^5.6.3"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"react",
|
|
58
|
+
"composeai",
|
|
59
|
+
"composer",
|
|
60
|
+
"ai-composer",
|
|
61
|
+
"ai-chat",
|
|
62
|
+
"chat-input",
|
|
63
|
+
"chatbox",
|
|
64
|
+
"copilot",
|
|
65
|
+
"llm",
|
|
66
|
+
"agent-ui",
|
|
67
|
+
"lexical",
|
|
68
|
+
"markdown",
|
|
69
|
+
"mentions",
|
|
70
|
+
"slash-commands",
|
|
71
|
+
"attachments"
|
|
72
|
+
],
|
|
73
|
+
"license": "MIT",
|
|
74
|
+
"homepage": "https://igbaryya.github.io/react-comporsor/",
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "git+https://github.com/igbaryya/react-comporsor.git"
|
|
78
|
+
},
|
|
79
|
+
"bugs": {
|
|
80
|
+
"url": "https://github.com/igbaryya/react-comporsor/issues"
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/composer.css
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/* ComposeAI — component styles.
|
|
2
|
+
*
|
|
3
|
+
* Plain CSS (no Tailwind required) keyed off the CSS variables every consumer
|
|
4
|
+
* defines for their theme:
|
|
5
|
+
* --background, --foreground, --primary, --primary-foreground,
|
|
6
|
+
* --muted, --muted-foreground, --border, --card, --card-foreground,
|
|
7
|
+
* --popover, --popover-foreground, --accent, --accent-foreground,
|
|
8
|
+
* --destructive, --success, --warning
|
|
9
|
+
* Values are HSL components (e.g. `258 90% 62%`) so utilities can compose
|
|
10
|
+
* with opacities via `hsl(var(--primary) / 0.1)`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* ----------------------------------------------------------------------------
|
|
14
|
+
* Composer-wide custom properties.
|
|
15
|
+
*
|
|
16
|
+
* These are READ by both the package CSS below and the Tailwind utility
|
|
17
|
+
* classes the components use (`bg-card`, `rounded-[28px]`, etc.). They are
|
|
18
|
+
* SET inline on the composer root by `<Composer />` itself when the consumer
|
|
19
|
+
* passes `tokens={...}`, so they only affect that one composer instance —
|
|
20
|
+
* never the consumer app's global theme.
|
|
21
|
+
*
|
|
22
|
+
* --composer-radius outer card corner radius (default 28px)
|
|
23
|
+
* --composer-font-size editor base font size (default 15px)
|
|
24
|
+
* --composer-font-family editor font family (default inherit)
|
|
25
|
+
* ------------------------------------------------------------------------- */
|
|
26
|
+
|
|
27
|
+
[data-composer-root] {
|
|
28
|
+
border-radius: var(--composer-radius, 28px);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* The gradient overlay and drop overlay sit absolutely inside the card and
|
|
32
|
+
* must mirror the outer corner radius. They no longer need a hardcoded
|
|
33
|
+
* value of their own. */
|
|
34
|
+
[data-composer-root] > [data-composer-overlay] {
|
|
35
|
+
border-radius: inherit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[data-composer-root] .composer-editor {
|
|
39
|
+
font-size: var(--composer-font-size, 15px);
|
|
40
|
+
font-family: var(--composer-font-family, inherit);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.composer-editor {
|
|
44
|
+
position: relative;
|
|
45
|
+
display: block;
|
|
46
|
+
width: 100%;
|
|
47
|
+
font-size: 15px;
|
|
48
|
+
line-height: 1.6;
|
|
49
|
+
color: hsl(var(--foreground));
|
|
50
|
+
outline: none;
|
|
51
|
+
overflow-wrap: anywhere;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.composer-paragraph {
|
|
55
|
+
margin: 0;
|
|
56
|
+
}
|
|
57
|
+
.composer-paragraph + .composer-paragraph {
|
|
58
|
+
margin-top: 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.composer-quote {
|
|
62
|
+
margin: 0.5rem 0;
|
|
63
|
+
padding-inline-start: 0.75rem;
|
|
64
|
+
border-inline-start: 2px solid hsl(var(--border));
|
|
65
|
+
color: hsl(var(--muted-foreground));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.composer-h1 { font-size: 1.25rem; font-weight: 600; letter-spacing: -0.01em; margin: 0.75rem 0 0.5rem; }
|
|
69
|
+
.composer-h2 { font-size: 1.125rem; font-weight: 600; letter-spacing: -0.01em; margin: 0.75rem 0 0.5rem; }
|
|
70
|
+
.composer-h3 { font-size: 1rem; font-weight: 600; letter-spacing: -0.01em; margin: 0.625rem 0 0.375rem; }
|
|
71
|
+
.composer-h4 { font-size: 0.9375rem; font-weight: 600; margin: 0.5rem 0 0.375rem; }
|
|
72
|
+
.composer-h5 { font-size: 0.875rem; font-weight: 600; margin: 0.5rem 0 0.25rem; }
|
|
73
|
+
.composer-h6 { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin: 0.5rem 0 0.25rem; }
|
|
74
|
+
|
|
75
|
+
.composer-ul {
|
|
76
|
+
margin: 0.25rem 0;
|
|
77
|
+
padding-inline-start: 1.5rem;
|
|
78
|
+
list-style: disc;
|
|
79
|
+
}
|
|
80
|
+
.composer-ol {
|
|
81
|
+
margin: 0.25rem 0;
|
|
82
|
+
padding-inline-start: 1.5rem;
|
|
83
|
+
list-style: decimal;
|
|
84
|
+
}
|
|
85
|
+
.composer-ul > li + li,
|
|
86
|
+
.composer-ol > li + li {
|
|
87
|
+
margin-top: 0.125rem;
|
|
88
|
+
}
|
|
89
|
+
.composer-ul::marker,
|
|
90
|
+
.composer-ol::marker,
|
|
91
|
+
.composer-li::marker {
|
|
92
|
+
color: hsl(var(--muted-foreground));
|
|
93
|
+
}
|
|
94
|
+
.composer-li {
|
|
95
|
+
padding-inline-start: 0.25rem;
|
|
96
|
+
}
|
|
97
|
+
.composer-li-nested {
|
|
98
|
+
list-style: circle;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.composer-bold { font-weight: 600; }
|
|
102
|
+
.composer-italic { font-style: italic; }
|
|
103
|
+
.composer-underline { text-decoration: underline; text-underline-offset: 2px; }
|
|
104
|
+
.composer-strike { text-decoration: line-through; }
|
|
105
|
+
|
|
106
|
+
.composer-code {
|
|
107
|
+
padding: 0.0625rem 0.25rem;
|
|
108
|
+
border-radius: 0.25rem;
|
|
109
|
+
border: 1px solid hsl(var(--border));
|
|
110
|
+
background: hsl(var(--muted));
|
|
111
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
112
|
+
font-size: 13px;
|
|
113
|
+
}
|
|
114
|
+
.composer-code-block {
|
|
115
|
+
display: block;
|
|
116
|
+
margin: 0.5rem 0;
|
|
117
|
+
padding: 0.5rem 0.75rem;
|
|
118
|
+
border-radius: 0.5rem;
|
|
119
|
+
border: 1px solid hsl(var(--border));
|
|
120
|
+
background: hsl(var(--muted));
|
|
121
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
122
|
+
font-size: 13px;
|
|
123
|
+
line-height: 1.6;
|
|
124
|
+
overflow-x: auto;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.composer-link {
|
|
128
|
+
color: hsl(var(--primary));
|
|
129
|
+
text-decoration: underline;
|
|
130
|
+
text-underline-offset: 2px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.composer-md-token {
|
|
134
|
+
color: hsl(var(--muted-foreground) / 0.5);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ----------------------------------------------------------------------------
|
|
138
|
+
* Slack-style block markdown.
|
|
139
|
+
* Paragraphs get a `data-md-block` attribute set by MarkdownPlugin based on
|
|
140
|
+
* the leading marker on their line. We style the entire paragraph for blocks
|
|
141
|
+
* while leaving the markers visible (rendered as muted tokens by the plugin).
|
|
142
|
+
* ------------------------------------------------------------------------- */
|
|
143
|
+
|
|
144
|
+
/* Headings: font-scale/weight applied via `::first-line` so the heading
|
|
145
|
+
* styling stays confined to the marker's line even when the user inserts
|
|
146
|
+
* soft line breaks (Shift+Enter) inside the same paragraph. The margins
|
|
147
|
+
* stay on the paragraph itself because `::first-line` cannot affect box
|
|
148
|
+
* properties. */
|
|
149
|
+
.composer-paragraph[data-md-block="heading-1"] {
|
|
150
|
+
margin-top: 0.5rem;
|
|
151
|
+
margin-bottom: 0.125rem;
|
|
152
|
+
}
|
|
153
|
+
.composer-paragraph[data-md-block="heading-1"]::first-line {
|
|
154
|
+
font-size: 1.625rem;
|
|
155
|
+
line-height: 1.2;
|
|
156
|
+
font-weight: 700;
|
|
157
|
+
letter-spacing: -0.015em;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.composer-paragraph[data-md-block="heading-2"] {
|
|
161
|
+
margin-top: 0.5rem;
|
|
162
|
+
margin-bottom: 0.125rem;
|
|
163
|
+
}
|
|
164
|
+
.composer-paragraph[data-md-block="heading-2"]::first-line {
|
|
165
|
+
font-size: 1.375rem;
|
|
166
|
+
line-height: 1.25;
|
|
167
|
+
font-weight: 700;
|
|
168
|
+
letter-spacing: -0.012em;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.composer-paragraph[data-md-block="heading-3"] {
|
|
172
|
+
margin-top: 0.375rem;
|
|
173
|
+
}
|
|
174
|
+
.composer-paragraph[data-md-block="heading-3"]::first-line {
|
|
175
|
+
font-size: 1.1875rem;
|
|
176
|
+
line-height: 1.3;
|
|
177
|
+
font-weight: 700;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.composer-paragraph[data-md-block="heading-4"] {
|
|
181
|
+
margin-top: 0.25rem;
|
|
182
|
+
}
|
|
183
|
+
.composer-paragraph[data-md-block="heading-4"]::first-line {
|
|
184
|
+
font-size: 1.0625rem;
|
|
185
|
+
line-height: 1.35;
|
|
186
|
+
font-weight: 700;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.composer-paragraph[data-md-block="heading-5"]::first-line {
|
|
190
|
+
font-size: 0.9375rem;
|
|
191
|
+
line-height: 1.4;
|
|
192
|
+
font-weight: 700;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.composer-paragraph[data-md-block="heading-6"]::first-line {
|
|
196
|
+
font-size: 0.8125rem;
|
|
197
|
+
line-height: 1.4;
|
|
198
|
+
font-weight: 700;
|
|
199
|
+
text-transform: uppercase;
|
|
200
|
+
letter-spacing: 0.06em;
|
|
201
|
+
color: hsl(var(--muted-foreground));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.composer-paragraph[data-md-block="quote"] {
|
|
205
|
+
position: relative;
|
|
206
|
+
padding-inline-start: 0.875rem;
|
|
207
|
+
margin-inline-start: 0.125rem;
|
|
208
|
+
color: hsl(var(--muted-foreground));
|
|
209
|
+
font-style: italic;
|
|
210
|
+
}
|
|
211
|
+
.composer-paragraph[data-md-block="quote"]::before {
|
|
212
|
+
content: "";
|
|
213
|
+
position: absolute;
|
|
214
|
+
inset-inline-start: 0;
|
|
215
|
+
top: 0.2rem;
|
|
216
|
+
bottom: 0.2rem;
|
|
217
|
+
width: 3px;
|
|
218
|
+
border-radius: 9999px;
|
|
219
|
+
background: hsl(var(--primary) / 0.55);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* Lists: visible indentation rail, marker recolored so it reads as an active
|
|
223
|
+
* bullet rather than a stray dash. Marker stays in flow so the caret can
|
|
224
|
+
* traverse it naturally. `text-indent` is direction-aware in modern engines
|
|
225
|
+
* (it indents from the line's start edge) so the negative value pulls the
|
|
226
|
+
* marker into the gutter on both sides. */
|
|
227
|
+
.composer-paragraph[data-md-block="list-bullet"],
|
|
228
|
+
.composer-paragraph[data-md-block="list-numbered"] {
|
|
229
|
+
padding-inline-start: 1.25rem;
|
|
230
|
+
text-indent: -0.875rem;
|
|
231
|
+
}
|
|
232
|
+
.composer-paragraph[data-md-block="list-bullet"] > .composer-md-token:first-child,
|
|
233
|
+
.composer-paragraph[data-md-block="list-numbered"] > .composer-md-token:first-child {
|
|
234
|
+
color: hsl(var(--primary) / 0.85);
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
}
|
|
237
|
+
.composer-paragraph[data-md-block="list-bullet"] + .composer-paragraph[data-md-block="list-bullet"],
|
|
238
|
+
.composer-paragraph[data-md-block="list-numbered"] + .composer-paragraph[data-md-block="list-numbered"] {
|
|
239
|
+
margin-top: 0.125rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.composer-paragraph[data-md-block="code-fence-open"],
|
|
243
|
+
.composer-paragraph[data-md-block="code-fence-close"],
|
|
244
|
+
.composer-paragraph[data-md-block="code-line"] {
|
|
245
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
246
|
+
font-size: 13px;
|
|
247
|
+
line-height: 1.55;
|
|
248
|
+
background: hsl(var(--muted));
|
|
249
|
+
padding: 0.125rem 0.625rem;
|
|
250
|
+
border-inline-start: 3px solid hsl(var(--primary) / 0.4);
|
|
251
|
+
}
|
|
252
|
+
.composer-paragraph[data-md-block^="code-"] + .composer-paragraph[data-md-block^="code-"] {
|
|
253
|
+
margin-top: 0;
|
|
254
|
+
}
|
|
255
|
+
.composer-paragraph[data-md-block="code-fence-open"] {
|
|
256
|
+
padding-top: 0.375rem;
|
|
257
|
+
border-start-start-radius: 0.375rem;
|
|
258
|
+
border-start-end-radius: 0.375rem;
|
|
259
|
+
color: hsl(var(--muted-foreground));
|
|
260
|
+
}
|
|
261
|
+
.composer-paragraph[data-md-block="code-fence-close"] {
|
|
262
|
+
padding-bottom: 0.375rem;
|
|
263
|
+
border-end-start-radius: 0.375rem;
|
|
264
|
+
border-end-end-radius: 0.375rem;
|
|
265
|
+
color: hsl(var(--muted-foreground));
|
|
266
|
+
}
|
|
267
|
+
/* Live-mode fence lines: the visible body is empty (the `` ``` `` chars are
|
|
268
|
+
* stashed on the BlockParagraphNode). Optional language tag from
|
|
269
|
+
* `data-md-lang` is surfaced as a small label so the user can still see
|
|
270
|
+
* what language they typed. The default `<br>` Lexical inserts in empty
|
|
271
|
+
* paragraphs gives the line just enough height to be selectable / clickable. */
|
|
272
|
+
.composer-paragraph[data-md-block="code-fence-open"][data-md-lang]::before {
|
|
273
|
+
content: attr(data-md-lang);
|
|
274
|
+
display: inline-block;
|
|
275
|
+
font-size: 11px;
|
|
276
|
+
color: hsl(var(--muted-foreground) / 0.7);
|
|
277
|
+
margin-inline-end: 0.5rem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.composer-paragraph[data-md-block="hr"] {
|
|
281
|
+
position: relative;
|
|
282
|
+
color: transparent;
|
|
283
|
+
user-select: none;
|
|
284
|
+
margin: 0.5rem 0;
|
|
285
|
+
height: 1px;
|
|
286
|
+
}
|
|
287
|
+
.composer-paragraph[data-md-block="hr"]::after {
|
|
288
|
+
content: "";
|
|
289
|
+
position: absolute;
|
|
290
|
+
inset-inline: 0;
|
|
291
|
+
top: 50%;
|
|
292
|
+
border-top: 1px solid hsl(var(--border));
|
|
293
|
+
}
|
|
294
|
+
.composer-paragraph[data-md-block="hr"] .composer-md-token {
|
|
295
|
+
visibility: hidden;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Headings: muted leading `#` tokens get a bit more presence so they still
|
|
299
|
+
* read as scaffolding rather than disappearing into the body type. */
|
|
300
|
+
.composer-paragraph[data-md-block^="heading-"] > .composer-md-token:first-child {
|
|
301
|
+
color: hsl(var(--muted-foreground) / 0.55);
|
|
302
|
+
font-weight: 600;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.composer-mention {
|
|
306
|
+
display: inline;
|
|
307
|
+
padding: 0.0625rem 0.375rem;
|
|
308
|
+
border-radius: 0.375rem;
|
|
309
|
+
background: hsl(var(--primary) / 0.1);
|
|
310
|
+
color: hsl(var(--primary));
|
|
311
|
+
font-weight: 500;
|
|
312
|
+
/* The chip wraps an editable TextNode — make sure its child looks
|
|
313
|
+
and acts like part of the chip surface. */
|
|
314
|
+
white-space: pre;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* Trigger character ("@" / "#" / …) is rendered as a pseudo-element so
|
|
318
|
+
it is visible but not part of the editable text content. Backspacing
|
|
319
|
+
can only ever shrink the label inside the chip — the trigger glyph
|
|
320
|
+
stays put until the entire chip is removed (which happens
|
|
321
|
+
automatically when the label becomes empty, via
|
|
322
|
+
MentionNode#canBeEmpty = false). */
|
|
323
|
+
.composer-mention::before {
|
|
324
|
+
content: attr(data-mention-prefix);
|
|
325
|
+
user-select: none;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.composer-token-attr { color: hsl(var(--primary)); }
|
|
329
|
+
.composer-token-comment { color: hsl(var(--muted-foreground)); font-style: italic; }
|
|
330
|
+
.composer-token-function { color: hsl(var(--primary)); }
|
|
331
|
+
.composer-token-operator { color: hsl(var(--muted-foreground)); }
|
|
332
|
+
.composer-token-property { color: hsl(var(--foreground)); }
|
|
333
|
+
.composer-token-punctuation { color: hsl(var(--muted-foreground)); }
|
|
334
|
+
.composer-token-selector { color: hsl(var(--success)); }
|
|
335
|
+
.composer-token-variable { color: hsl(var(--warning)); }
|
|
336
|
+
|
|
337
|
+
/* Scoped thin scrollbar for the editor's max-height area. */
|
|
338
|
+
.composer-editor {
|
|
339
|
+
scrollbar-width: thin;
|
|
340
|
+
scrollbar-color: hsl(var(--border)) transparent;
|
|
341
|
+
}
|
|
342
|
+
.composer-editor::-webkit-scrollbar {
|
|
343
|
+
width: 6px;
|
|
344
|
+
height: 6px;
|
|
345
|
+
}
|
|
346
|
+
.composer-editor::-webkit-scrollbar-track {
|
|
347
|
+
background: transparent;
|
|
348
|
+
}
|
|
349
|
+
.composer-editor::-webkit-scrollbar-thumb {
|
|
350
|
+
background: hsl(var(--border));
|
|
351
|
+
border-radius: 9999px;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/* Soft mount animation for small popovers (attachment type picker, …).
|
|
355
|
+
Subtle fade + 4px slide-down so the dropdown feels tied to its trigger
|
|
356
|
+
instead of popping abruptly into existence. */
|
|
357
|
+
@keyframes composer-popover-in {
|
|
358
|
+
from {
|
|
359
|
+
opacity: 0;
|
|
360
|
+
transform: translateY(-4px);
|
|
361
|
+
}
|
|
362
|
+
to {
|
|
363
|
+
opacity: 1;
|
|
364
|
+
transform: translateY(0);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
.composer-popover-in {
|
|
368
|
+
animation: composer-popover-in 160ms ease-out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/* ----------------------------------------------------------------------------
|
|
372
|
+
* Ghosted autocomplete overlay.
|
|
373
|
+
*
|
|
374
|
+
* GhostedAutoCompletePlugin portals a non-interactive div into the
|
|
375
|
+
* `.composer-editor-block` container. It contains:
|
|
376
|
+
* [.composer-ghost-overlay-typed] — invisible spacer matching what the
|
|
377
|
+
* user typed so the suggestion lines
|
|
378
|
+
* up exactly with the caret.
|
|
379
|
+
* [.composer-ghost-suggestion] — the muted "ghost" remainder.
|
|
380
|
+
*
|
|
381
|
+
* The overlay sits BEHIND the contenteditable (`z-index: 0` vs the
|
|
382
|
+
* editor's stacking-context-implicit `auto`) and is `pointer-events:
|
|
383
|
+
* none` so it never intercepts clicks/selection. The padding utility
|
|
384
|
+
* classes set on the overlay element MUST mirror the editor's padding —
|
|
385
|
+
* `EditorShell.tsx`'s `editorClass` is the source of truth.
|
|
386
|
+
* ------------------------------------------------------------------------- */
|
|
387
|
+
|
|
388
|
+
.composer-ghost-overlay {
|
|
389
|
+
font-size: var(--composer-font-size, 15px);
|
|
390
|
+
font-family: var(--composer-font-family, inherit);
|
|
391
|
+
line-height: 1.6;
|
|
392
|
+
/* `pre-wrap`: preserves spaces and lets a long suggestion wrap onto
|
|
393
|
+
* the next line. `break-word` is the modern alias of `word-wrap` —
|
|
394
|
+
* needed so an unbroken URL-style suggestion can still wrap inside
|
|
395
|
+
* the editor's bounds.
|
|
396
|
+
* Color is transparent on the container; only the suggestion span
|
|
397
|
+
* re-colors itself. The typed span stays transparent so it occupies
|
|
398
|
+
* caret-aligned space without bleeding any text into the overlay. */
|
|
399
|
+
white-space: pre-wrap;
|
|
400
|
+
word-wrap: break-word;
|
|
401
|
+
color: transparent;
|
|
402
|
+
/* Sit behind the contenteditable so the caret blink and selection
|
|
403
|
+
* highlight (which Lexical paints on the editor itself) stay on top. */
|
|
404
|
+
z-index: 0;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.composer-ghost-overlay-typed {
|
|
408
|
+
/* `visibility: hidden` keeps the text in the box-model — its width
|
|
409
|
+
* pushes the suggestion span to the right place — but never paints. */
|
|
410
|
+
visibility: hidden;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.composer-ghost-suggestion {
|
|
414
|
+
color: hsl(var(--muted-foreground) / 0.6);
|
|
415
|
+
/* Just enough emphasis to look like a suggestion (slightly italic
|
|
416
|
+
* reads as "this isn't your text yet"). Skip if your design system
|
|
417
|
+
* prefers a flat muted look. */
|
|
418
|
+
font-style: italic;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Inline-layout overrides: mirror the editor's 36px row line-height so
|
|
422
|
+
* the suggestion sits on the same baseline as the typed text. */
|
|
423
|
+
.composer-ghost-overlay.composer-ghost-overlay--inline {
|
|
424
|
+
line-height: 2.25rem;
|
|
425
|
+
white-space: nowrap;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* ----------------------------------------------------------------------------
|
|
429
|
+
* Inline / single-line layout.
|
|
430
|
+
*
|
|
431
|
+
* Activated when `<Composer multiline={false} />`. The card collapses from a
|
|
432
|
+
* stacked column (attachments / editor / toolbar+send / mermaid) to a single
|
|
433
|
+
* horizontal bar (toolbar | editor | send), with the attachment tray and the
|
|
434
|
+
* drag overlay still rendering above where applicable.
|
|
435
|
+
*
|
|
436
|
+
* Wins over `--composer-radius` on the card itself because this rule
|
|
437
|
+
* targets the same element with an extra attribute, raising specificity.
|
|
438
|
+
* ------------------------------------------------------------------------- */
|
|
439
|
+
|
|
440
|
+
[data-composer-root][data-composer-inline] {
|
|
441
|
+
border-radius: 9999px;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* The contenteditable in inline mode needs its own line-height because the
|
|
445
|
+
* base `.composer-editor` rule (specificity 0,1,0) defaults to `1.6`, which
|
|
446
|
+
* shrinks the line box to ~24px and leaves typed text top-aligned inside
|
|
447
|
+
* the 36px row while the absolutely-positioned placeholder (its own
|
|
448
|
+
* line-height) appears centered — a visible mismatch the moment the user
|
|
449
|
+
* starts typing. The compound-class selector below raises specificity to
|
|
450
|
+
* 0,2,0 so the inline override always wins regardless of stylesheet load
|
|
451
|
+
* order, with the line-height matching the 36px row height so a single
|
|
452
|
+
* line of text is perfectly centered. */
|
|
453
|
+
.composer-editor.composer-editor--inline {
|
|
454
|
+
line-height: 2.25rem; /* matches h-9 / leading-9 (36px) */
|
|
455
|
+
scrollbar-width: none;
|
|
456
|
+
}
|
|
457
|
+
.composer-editor.composer-editor--inline::-webkit-scrollbar {
|
|
458
|
+
display: none;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Lexical wraps every line in a block-level <p>. In inline mode we collapse
|
|
462
|
+
* those to inline so the editor scrolls horizontally instead of stacking
|
|
463
|
+
* vertically, and the inline content baseline aligns to the parent line
|
|
464
|
+
* box (centered, thanks to the 36px line-height above). */
|
|
465
|
+
.composer-editor--inline .composer-paragraph,
|
|
466
|
+
.composer-editor--inline p {
|
|
467
|
+
display: inline;
|
|
468
|
+
margin: 0;
|
|
469
|
+
white-space: nowrap;
|
|
470
|
+
line-height: inherit;
|
|
471
|
+
}
|
|
472
|
+
.composer-editor--inline .composer-paragraph + .composer-paragraph {
|
|
473
|
+
margin-top: 0;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* The MermaidSlot is already gated out by the React tree when multiline is
|
|
477
|
+
* false, but this defensive rule keeps any stray `[data-mermaid-tile]` from
|
|
478
|
+
* showing if a consumer renders one manually inside an inline composer. */
|
|
479
|
+
[data-composer-root][data-composer-inline] [data-mermaid-tile] {
|
|
480
|
+
display: none;
|
|
481
|
+
}
|