@backmatter/studio 0.0.11 → 0.0.13

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 (34) hide show
  1. package/dist/chunks/{UiKitApp-Cu0CS1NU.js → UiKitApp-ZZXQt3zy.js} +29 -29
  2. package/dist/chunks/{WorkspaceRoutes-Dzpaj8ov.js → WorkspaceRoutes-CCDPtLla.js} +22396 -22185
  3. package/dist/chunks/{dashboard-kWmpeuAP.js → dashboard-Dw0zfTXg.js} +35 -35
  4. package/dist/chunks/{index-Bryt_VJQ.js → index-DhZGCAru.js} +1 -1
  5. package/dist/dashboard.js +2 -2
  6. package/dist/index.js +199 -193
  7. package/dist/site-widget.css +9 -1
  8. package/dist/site-widget.js +6060 -5936
  9. package/dist/studio.css +5 -25
  10. package/package.json +6 -2
  11. package/src/chat/ChatProvider.tsx +13 -2
  12. package/src/chat/Composer.tsx +2 -1
  13. package/src/chat/PointerChip.tsx +15 -2
  14. package/src/chat/chat-helpers.ts +6 -0
  15. package/src/chat/pointer-arm.ts +3 -0
  16. package/src/chrome/WorkspaceStatusBar.tsx +57 -19
  17. package/src/chrome/commit-error.ts +82 -0
  18. package/src/chrome/commit-failure.tsx +111 -0
  19. package/src/install-preview-pointer.ts +116 -0
  20. package/src/pages/BlocksList.tsx +17 -2
  21. package/src/pages/DocEditorPage.tsx +95 -1
  22. package/src/pages/EmbedPublishPage.tsx +27 -24
  23. package/src/pages/RenderedBody.tsx +141 -322
  24. package/src/pages/document-paint.ts +79 -0
  25. package/src/pages/editor-grab.ts +39 -0
  26. package/src/pages/html-to-markdown.ts +26 -0
  27. package/src/pages/preview-bridge.ts +8 -1
  28. package/src/pages/selection-format.ts +5 -2
  29. package/src/preview-highlight.ts +16 -0
  30. package/src/preview-pointer-host.ts +30 -0
  31. package/src/shell-resets.css +13 -0
  32. package/src/site-changes.ts +46 -0
  33. package/src/site-pointer.ts +39 -6
  34. package/src/site-widget.tsx +66 -23
@@ -6,28 +6,23 @@ import {
6
6
  useImperativeHandle,
7
7
  useRef,
8
8
  useState,
9
- type KeyboardEvent,
10
9
  type ReactNode,
11
- type Ref,
12
10
  } from "react";
13
- import { renderToStaticMarkup } from "react-dom/server";
14
11
  import * as stylex from "@stylexjs/stylex";
15
12
  import type { BlockSnapshot } from "../api";
16
- import { ContentBlockView, contentBlockFields, contentBlockLabel, contentBlockProps } from "../content-blocks";
17
- import { colors, radius, space, type } from "../styles/tokens.stylex";
13
+ import { contentBlockFields, contentBlockLabel, contentBlockProps } from "../content-blocks";
14
+ import { colors, radius, space } from "../styles/tokens.stylex";
18
15
  import {
19
16
  blockFromMarkdown,
20
17
  blocksFromSource,
21
- codeParts,
22
18
  joinBlocks,
23
19
  jsxBlockSource,
24
20
  jsxWithProps,
25
21
  type BodyBlock,
26
22
  } from "./body-blocks";
27
- import { editorMdxComponents } from "./body-mdx";
28
- import { compileBody } from "./compile-body";
23
+ import { documentNodes } from "./document-paint";
29
24
  import { BlockSettingsDialog } from "./BlockSettingsDialog";
30
- import { markdownFromEditable } from "./html-to-markdown";
25
+ import { markdownFromDocument } from "./html-to-markdown";
31
26
  import { SelectionBar, type FormatSelection } from "./SelectionBar";
32
27
  import {
33
28
  applyBlockFormat,
@@ -43,6 +38,8 @@ export type RenderedBodyHandle = {
43
38
  insert: (markdown: string) => void;
44
39
  };
45
40
 
41
+ const BODY_HOST = "body";
42
+
46
43
  export const RenderedBody = forwardRef<
47
44
  RenderedBodyHandle,
48
45
  {
@@ -55,29 +52,24 @@ export const RenderedBody = forwardRef<
55
52
  >(function RenderedBody({ value, catalog = [], readOnly, labelledBy, onChange }, ref) {
56
53
  const local = useRef(value);
57
54
  const hosts = useRef(new Map<string, HTMLElement>());
58
- const commits = useRef(new Map<string, () => void>());
59
55
  const linking = useRef(false);
60
56
  const [blocks, setBlocks] = useState(() => blocksFromSource(value));
61
- const [activeId, setActiveId] = useState<string | null>(null);
62
- const [focusId, setFocusId] = useState<string | null>(null);
57
+ const repaint = useRef(true);
58
+ const editing = useRef(false);
63
59
  const [selection, setSelection] = useState<FormatSelection | null>(null);
64
60
  const [settingsId, setSettingsId] = useState<string | null>(null);
65
- const selectionBlock = useRef<string | null>(null);
66
61
 
67
- const register = useCallback((id: string, host: HTMLElement, commit: () => void) => {
62
+ const register = useCallback((id: string, host: HTMLElement) => {
68
63
  hosts.current.set(id, host);
69
- commits.current.set(id, commit);
70
64
  }, []);
71
65
 
72
66
  const unregister = useCallback((id: string) => {
73
67
  hosts.current.delete(id);
74
- commits.current.delete(id);
75
68
  }, []);
76
69
 
77
70
  const syncSelection = useCallback(() => {
78
71
  if (linking.current) return;
79
72
  const next = readSelection(hosts.current);
80
- selectionBlock.current = next?.blockId ?? null;
81
73
  setSelection(next?.selection ?? null);
82
74
  }, []);
83
75
 
@@ -95,87 +87,95 @@ export const RenderedBody = forwardRef<
95
87
  useEffect(() => {
96
88
  if (value === local.current) return;
97
89
  local.current = value;
90
+ repaint.current = true;
98
91
  setBlocks(blocksFromSource(value));
99
92
  }, [value]);
100
93
 
101
94
  function publish(next: BodyBlock[]) {
102
95
  const source = joinBlocks(next);
103
96
  local.current = source;
97
+ repaint.current = true;
104
98
  setBlocks(next);
105
99
  if (source !== value) onChange(source);
106
100
  }
107
101
 
102
+ function bodyHost(): HTMLElement | undefined {
103
+ return hosts.current.get(BODY_HOST);
104
+ }
105
+
106
+ function commitDocument() {
107
+ const host = bodyHost();
108
+ if (!host) return;
109
+ const source = markdownFromDocument(host);
110
+ if (source === local.current) return;
111
+ local.current = source;
112
+ setBlocks(blocksFromSource(source));
113
+ if (source !== value) onChange(source);
114
+ }
115
+
108
116
  function insert(markdown: string) {
117
+ const host = bodyHost();
118
+ const source = host ? markdownFromDocument(host) : local.current;
119
+ const current = blocksFromSource(source);
109
120
  const id = crypto.randomUUID();
110
121
  const block = blockFromMarkdown(markdown, id);
111
- const index = blocks.findIndex((item) => item.id === activeId);
112
- const next = [...blocks];
113
- if (index === -1) next.push(block);
122
+ const index = caretIndex(host);
123
+ const next = [...current];
124
+ if (index < 0) next.push(block);
114
125
  else next.splice(index + 1, 0, block);
115
126
  if (block.kind === "jsx" && catalog.some((item) => item.name === block.jsxName)) {
116
127
  setSettingsId(id);
117
- } else {
118
- setFocusId(id);
119
128
  }
120
129
  publish(next);
121
130
  }
122
131
 
123
132
  useImperativeHandle(ref, () => ({ insert }));
124
133
 
125
- function commitHost(id: string) {
126
- commits.current.get(id)?.();
127
- }
128
-
129
134
  function onFormat(format: BlockFormat) {
130
- const id = selectionBlock.current;
131
- const host = id ? hosts.current.get(id) : undefined;
135
+ const host = bodyHost();
132
136
  const range = currentRange();
133
- if (!id || !host || !range) return;
137
+ if (!host || !range || !host.contains(range.startContainer)) return;
134
138
  if (blockFormatOf(host, range) === format) return;
135
- applyBlockFormat(host, format, range);
136
- placeCaretAtEnd(host);
137
- commitHost(id);
139
+ const next = applyBlockFormat(host, format, range);
140
+ placeCaretAtEnd(next);
141
+ commitDocument();
138
142
  }
139
143
 
140
144
  function onInline(tag: "strong" | "em") {
141
- const id = selectionBlock.current;
142
- const host = id ? hosts.current.get(id) : undefined;
145
+ const host = bodyHost();
143
146
  const range = currentRange();
144
- if (!id || !host || !range || !host.contains(range.commonAncestorContainer)) return;
147
+ if (!host || !range || !host.contains(range.commonAncestorContainer)) return;
145
148
  toggleInline(range, tag);
146
- commitHost(id);
149
+ commitDocument();
147
150
  }
148
151
 
149
152
  function onLink(href: string, range: Range) {
150
- const id = selectionBlock.current;
151
- const host = id ? hosts.current.get(id) : undefined;
152
- if (!id || !host) return;
153
+ const host = bodyHost();
154
+ if (!host) return;
153
155
  host.focus();
154
156
  applyLink(range, href);
155
- commitHost(id);
157
+ commitDocument();
156
158
  }
157
159
 
158
- function commit(id: string, markdown: string) {
159
- const current = blocks.find((block) => block.id === id);
160
- if (!current || current.markdown.trim() === markdown.trim()) return;
161
- publish(
162
- blocks.map((block) => (block.id === id ? blockFromMarkdown(markdown, id) : block)),
163
- );
160
+ function caretIndex(host: HTMLElement | undefined): number {
161
+ if (!host) return -1;
162
+ const selection = window.getSelection();
163
+ if (!selection || selection.rangeCount === 0) return -1;
164
+ let node: Node | null = selection.getRangeAt(0).startContainer;
165
+ while (node && node.parentElement !== host) node = node.parentNode;
166
+ if (!(node instanceof HTMLElement) || node.parentElement !== host) return -1;
167
+ return [...host.children].indexOf(node);
164
168
  }
165
169
 
166
- function split(id: string, before: string, after: string) {
167
- const nextId = crypto.randomUUID();
168
- const next: BodyBlock[] = [];
169
- for (const block of blocks) {
170
- if (block.id !== id) {
171
- next.push(block);
172
- continue;
173
- }
174
- next.push(blockFromMarkdown(before, block.id));
175
- next.push(blockFromMarkdown(after, nextId));
176
- }
177
- setFocusId(nextId);
178
- publish(next);
170
+ function replaceAtom(id: string, markdown: string) {
171
+ const host = bodyHost();
172
+ const atom = host?.querySelector(`[data-body-atom="${id}"]`);
173
+ if (atom) atom.setAttribute("data-body-markdown", encodeURIComponent(markdown));
174
+ const source = host ? markdownFromDocument(host) : markdown;
175
+ local.current = source;
176
+ repaint.current = true;
177
+ setBlocks(blocksFromSource(source));
178
+ if (source !== value) onChange(source);
179
179
  }
180
180
 
181
181
  const settingsBlock = blocks.find((block) => block.id === settingsId);
@@ -187,41 +187,18 @@ export const RenderedBody = forwardRef<
187
187
 
188
188
  return (
189
189
  <RenderedSurface labelledBy={labelledBy}>
190
- {blocks.length === 0 ? (
191
- <EditableBlock
192
- block={{ id: "empty", markdown: "", kind: "text" }}
193
- readOnly={readOnly}
194
- autoFocus={false}
195
- catalog={catalog}
196
- onOpenSettings={setSettingsId}
197
- onRegister={register}
198
- onUnregister={unregister}
199
- onFocus={() => setActiveId(null)}
200
- onCommit={(markdown) => {
201
- if (!markdown.trim()) return;
202
- publish([blockFromMarkdown(markdown, crypto.randomUUID())]);
203
- }}
204
- onSplit={() => {}}
205
- onRemove={() => {}}
206
- />
207
- ) : (
208
- blocks.map((block) => (
209
- <EditableBlock
210
- key={block.id}
211
- block={block}
212
- readOnly={readOnly}
213
- autoFocus={focusId === block.id}
214
- catalog={catalog}
215
- onOpenSettings={setSettingsId}
216
- onRegister={register}
217
- onUnregister={unregister}
218
- onFocus={() => setActiveId(block.id)}
219
- onCommit={(markdown) => commit(block.id, markdown)}
220
- onSplit={(before, after) => split(block.id, before, after)}
221
- onRemove={() => publish(blocks.filter((item) => item.id !== block.id))}
222
- />
223
- ))
224
- )}
190
+ <DocumentField
191
+ blocks={blocks}
192
+ catalog={catalog}
193
+ readOnly={readOnly}
194
+ labelledBy={labelledBy}
195
+ repaint={repaint}
196
+ editing={editing}
197
+ onRegister={register}
198
+ onUnregister={unregister}
199
+ onCommit={commitDocument}
200
+ onOpenSettings={setSettingsId}
201
+ />
225
202
  {selection && !readOnly ? (
226
203
  <SelectionBar
227
204
  selection={selection}
@@ -255,11 +232,17 @@ export const RenderedBody = forwardRef<
255
232
  props,
256
233
  settings.def.fields,
257
234
  );
258
- commit(settings.block.id, markdown);
235
+ replaceAtom(settings.block.id, markdown);
259
236
  setSettingsId(null);
260
237
  }}
261
238
  onRemove={() => {
262
- publish(blocks.filter((block) => block.id !== settings.block.id));
239
+ const host = bodyHost();
240
+ host?.querySelector(`[data-body-atom="${settings.block.id}"]`)?.remove();
241
+ const source = host ? markdownFromDocument(host) : "";
242
+ local.current = source;
243
+ repaint.current = true;
244
+ setBlocks(blocksFromSource(source));
245
+ if (source !== value) onChange(source);
263
246
  setSettingsId(null);
264
247
  }}
265
248
  />
@@ -287,185 +270,86 @@ function RenderedSurface({
287
270
  );
288
271
  }
289
272
 
290
- function EditableBlock({
291
- block,
273
+ function DocumentField({
274
+ blocks,
292
275
  catalog,
293
276
  readOnly,
294
- autoFocus,
295
- onOpenSettings,
277
+ labelledBy,
278
+ repaint,
279
+ editing,
296
280
  onRegister,
297
281
  onUnregister,
298
- onFocus,
299
282
  onCommit,
300
- onSplit,
301
- onRemove,
283
+ onOpenSettings,
302
284
  }: {
303
- block: BodyBlock;
285
+ blocks: BodyBlock[];
304
286
  catalog: Array<BlockSnapshot & { label?: string }>;
305
287
  readOnly?: boolean;
306
- autoFocus: boolean;
307
- onOpenSettings: (id: string) => void;
308
- onRegister: (id: string, host: HTMLElement, commit: () => void) => void;
288
+ labelledBy: string;
289
+ repaint: { current: boolean };
290
+ editing: { current: boolean };
291
+ onRegister: (id: string, host: HTMLElement) => void;
309
292
  onUnregister: (id: string) => void;
310
- onFocus: () => void;
311
- onCommit: (markdown: string) => void;
312
- onSplit: (before: string, after: string) => void;
313
- onRemove: () => void;
293
+ onCommit: () => void;
294
+ onOpenSettings: (id: string) => void;
314
295
  }) {
315
- const ref = useRef<HTMLElement>(null);
316
- const editing = useRef(false);
317
- const commitRef = useRef<() => void>(() => {});
318
- commitRef.current = () => {
319
- const host = ref.current;
320
- if (!host || readOnly) return;
321
- onCommit(markdownFromEditable(host, block));
322
- };
296
+ const ref = useRef<HTMLDivElement>(null);
323
297
 
324
298
  useEffect(() => {
325
- if (autoFocus) ref.current?.focus();
326
- }, [autoFocus]);
327
-
328
- useEffect(() => {
329
- if (block.kind !== "text") return;
330
299
  const host = ref.current;
331
300
  if (!host) return;
332
- onRegister(block.id, host, () => commitRef.current());
333
- return () => onUnregister(block.id);
334
- }, [block.id, block.kind, onRegister, onUnregister]);
301
+ onRegister(BODY_HOST, host);
302
+ return () => onUnregister(BODY_HOST);
303
+ }, [onRegister, onUnregister]);
335
304
 
336
305
  useEffect(() => {
337
306
  const host = ref.current;
338
- if (!host || editing.current) return;
339
- if (block.kind === "code") {
340
- host.textContent = codeParts(block.markdown).code;
341
- return;
342
- }
343
- if (!block.markdown.trim()) {
344
- host.innerHTML = "";
345
- return;
346
- }
307
+ if (!host) return;
308
+ const forced = repaint.current;
309
+ repaint.current = false;
310
+ if (editing.current && !forced) return;
347
311
  let cancelled = false;
348
- void compileBody(block.markdown, editorMdxComponents(block.jsxName))
349
- .then((result) => {
350
- if (cancelled || editing.current || ref.current !== host) return;
351
- if (result.error || result.node == null) {
352
- host.textContent = block.markdown;
353
- return;
354
- }
355
- try {
356
- host.innerHTML = renderToStaticMarkup(result.node).replace(/<link\b[^>]*>/g, "");
357
- } catch {
358
- host.textContent = block.markdown;
359
- }
360
- })
361
- .catch(() => {
362
- if (!cancelled && ref.current === host) host.textContent = block.markdown;
363
- });
312
+ void documentNodes(blocks, catalog).then((nodes) => {
313
+ if (cancelled || ref.current !== host) return;
314
+ if (editing.current && !forced) return;
315
+ host.replaceChildren(...nodes);
316
+ });
364
317
  return () => {
365
318
  cancelled = true;
366
319
  };
367
- }, [block.kind, block.markdown]);
368
-
369
- function commitFromDom() {
370
- const host = ref.current;
371
- if (!host || readOnly) return;
372
- onCommit(markdownFromEditable(host, block));
373
- }
374
-
375
- function onKeyDown(event: KeyboardEvent<HTMLElement>) {
376
- if (readOnly) return;
377
- const emptyText = (ref.current?.innerText ?? "").trim() === "";
378
- if (event.key === "Backspace" && block.kind === "text" && emptyText) {
379
- event.preventDefault();
380
- onRemove();
381
- return;
382
- }
383
- if (event.key !== "Enter" || event.shiftKey || block.kind !== "text") return;
384
- const host = ref.current;
385
- if (!host) return;
386
- const parts = splitAtCaret(host);
387
- if (!parts) return;
388
- event.preventDefault();
389
- onSplit(parts.before, parts.after);
390
- }
391
-
392
- const empty = !block.markdown.trim();
393
- const catalogBlock =
394
- block.kind === "jsx" ? catalog.find((item) => item.name === block.jsxName) : undefined;
395
-
396
- if (catalogBlock) {
397
- return (
398
- <div data-body-block="" {...stylex.props(styles.block)}>
399
- <button
400
- type="button"
401
- aria-label={`Edit ${catalogBlock.name} settings`}
402
- onClick={() => onOpenSettings(block.id)}
403
- {...stylex.props(styles.card)}
404
- >
405
- <ContentBlockView
406
- name={catalogBlock.name}
407
- props={contentBlockProps(block.markdown, catalogBlock.name)}
408
- />
409
- </button>
410
- </div>
411
- );
412
- }
320
+ }, [blocks, catalog, editing, repaint]);
413
321
 
414
322
  return (
415
323
  <div
416
- data-body-block=""
417
- {...stylex.props(styles.block)}
418
- >
419
- {block.kind === "code" ? (
420
- <pre {...stylex.props(styles.pre)}>
421
- <code
422
- ref={ref as Ref<HTMLElement>}
423
- role="textbox"
424
- aria-multiline="true"
425
- aria-label={block.lang ? `${block.lang} code` : "Code"}
426
- contentEditable={!readOnly}
427
- suppressContentEditableWarning
428
- spellCheck={false}
429
- onFocus={() => {
430
- editing.current = true;
431
- onFocus();
432
- }}
433
- onBlur={() => {
434
- editing.current = false;
435
- commitFromDom();
436
- }}
437
- onKeyDown={onKeyDown}
438
- {...stylex.props(styles.codeText)}
439
- />
440
- </pre>
441
- ) : (
442
- <div
443
- ref={ref as Ref<HTMLDivElement>}
444
- role="textbox"
445
- aria-multiline="true"
446
- aria-label={block.jsxName ?? "Text"}
447
- contentEditable={!readOnly}
448
- suppressContentEditableWarning
449
- spellCheck
450
- data-text-block=""
451
- data-empty={empty ? "" : undefined}
452
- onFocus={() => {
453
- editing.current = true;
454
- onFocus();
455
- }}
456
- onBlur={() => {
457
- editing.current = false;
458
- commitFromDom();
459
- }}
460
- onKeyDown={onKeyDown}
461
- onClick={(event) => {
462
- const anchor = (event.target as HTMLElement).closest?.("a");
463
- if (anchor) event.preventDefault();
464
- }}
465
- {...stylex.props(styles.text, empty && styles.placeholder)}
466
- />
467
- )}
468
- </div>
324
+ ref={ref}
325
+ role="textbox"
326
+ aria-multiline="true"
327
+ aria-labelledby={labelledBy}
328
+ contentEditable={readOnly ? "false" : "true"}
329
+ suppressContentEditableWarning
330
+ spellCheck
331
+ data-text-block=""
332
+ data-no-ring=""
333
+ onFocus={() => {
334
+ editing.current = true;
335
+ }}
336
+ onBlur={() => {
337
+ editing.current = false;
338
+ if (!readOnly) onCommit();
339
+ }}
340
+ onClick={(event) => {
341
+ const target = event.target;
342
+ if (!(target instanceof Element)) return;
343
+ const atom = target.closest("[data-body-atom]");
344
+ if (atom instanceof HTMLElement) {
345
+ event.preventDefault();
346
+ onOpenSettings(atom.getAttribute("data-body-atom") ?? "");
347
+ return;
348
+ }
349
+ if (target.closest("a")) event.preventDefault();
350
+ }}
351
+ {...stylex.props(styles.text)}
352
+ />
469
353
  );
470
354
  }
471
355
 
@@ -509,31 +393,6 @@ function currentRange(): Range | null {
509
393
  return current.getRangeAt(0);
510
394
  }
511
395
 
512
- function splitAtCaret(root: HTMLElement): { before: string; after: string } | null {
513
- const selection = window.getSelection();
514
- if (!selection || selection.rangeCount === 0) return null;
515
- const range = selection.getRangeAt(0);
516
- if (!root.contains(range.startContainer)) return null;
517
-
518
- const beforeRange = range.cloneRange();
519
- beforeRange.selectNodeContents(root);
520
- beforeRange.setEnd(range.startContainer, range.startOffset);
521
- const afterRange = range.cloneRange();
522
- afterRange.selectNodeContents(root);
523
- afterRange.setStart(range.endContainer, range.endOffset);
524
-
525
- const beforeHost = document.createElement("div");
526
- beforeHost.appendChild(beforeRange.cloneContents());
527
- const afterHost = document.createElement("div");
528
- afterHost.appendChild(afterRange.cloneContents());
529
- return {
530
- before: markdownFromEditable(beforeHost, { id: "before", kind: "text", markdown: "" }),
531
- after: markdownFromEditable(afterHost, { id: "after", kind: "text", markdown: "" }),
532
- };
533
- }
534
-
535
- const hover = "@media (hover: hover)";
536
-
537
396
  const styles = stylex.create({
538
397
  surface: {
539
398
  minHeight: "26rem",
@@ -545,54 +404,14 @@ const styles = stylex.create({
545
404
  borderRadius: radius.md,
546
405
  backgroundColor: colors.bgInput,
547
406
  },
548
- block: {
549
- marginBottom: space.lg,
550
- ":last-child": {
551
- marginBottom: 0,
552
- },
553
- },
554
- card: {
555
- display: "block",
556
- width: "100%",
557
- margin: 0,
558
- padding: 0,
559
- borderWidth: 0,
560
- borderRadius: radius.md,
561
- backgroundColor: "transparent",
562
- color: "inherit",
563
- fontFamily: "inherit",
564
- fontSize: "inherit",
565
- lineHeight: "inherit",
566
- textAlign: "left",
567
- cursor: "pointer",
568
- [hover]: {
569
- ":hover": {
570
- backgroundColor: colors.bgHover,
571
- },
572
- },
573
- },
574
407
  text: {
575
408
  minHeight: "1.5em",
576
- outlineWidth: 0,
577
- },
578
- placeholder: {
579
- ":empty::before": {
580
- content: '"Write…"',
581
- color: colors.textMuted,
409
+ outline: "none",
410
+ ":focus": {
411
+ outline: "none",
412
+ },
413
+ ":focus-visible": {
414
+ outline: "none",
582
415
  },
583
- },
584
- pre: {
585
- margin: 0,
586
- padding: space.md,
587
- borderRadius: radius.md,
588
- backgroundColor: colors.bgSurface,
589
- overflowX: "auto",
590
- },
591
- codeText: {
592
- fontFamily: type.mono,
593
- fontSize: type.sm,
594
- lineHeight: 1.5,
595
- whiteSpace: "pre",
596
- outlineWidth: 0,
597
416
  },
598
417
  });