torch-glare 1.2.8 → 1.3.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 (41) hide show
  1. package/apps/lib/components/TableDnDWrapper.ts +495 -0
  2. package/apps/lib/components/TextEditor.tsx +53 -1
  3. package/dist/bin/index.js +5 -0
  4. package/dist/bin/index.js.map +1 -1
  5. package/dist/src/commands/mcp.d.ts +2 -0
  6. package/dist/src/commands/mcp.d.ts.map +1 -0
  7. package/dist/src/commands/mcp.js +91 -0
  8. package/dist/src/commands/mcp.js.map +1 -0
  9. package/dist/src/shared/configureFonts.d.ts +6 -0
  10. package/dist/src/shared/configureFonts.d.ts.map +1 -0
  11. package/dist/src/shared/configureFonts.js +106 -0
  12. package/dist/src/shared/configureFonts.js.map +1 -0
  13. package/dist/src/shared/configureGlobalCss.d.ts +6 -0
  14. package/dist/src/shared/configureGlobalCss.d.ts.map +1 -0
  15. package/dist/src/shared/configureGlobalCss.js +154 -0
  16. package/dist/src/shared/configureGlobalCss.js.map +1 -0
  17. package/dist/src/shared/configureTailwind.d.ts +7 -0
  18. package/dist/src/shared/configureTailwind.d.ts.map +1 -0
  19. package/dist/src/shared/configureTailwind.js +163 -0
  20. package/dist/src/shared/configureTailwind.js.map +1 -0
  21. package/dist/src/shared/detectFramework.d.ts +23 -0
  22. package/dist/src/shared/detectFramework.d.ts.map +1 -0
  23. package/dist/src/shared/detectFramework.js +119 -0
  24. package/dist/src/shared/detectFramework.js.map +1 -0
  25. package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
  26. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +18 -2
  27. package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
  28. package/dist/src/shared/installBaseUtils.d.ts +5 -0
  29. package/dist/src/shared/installBaseUtils.d.ts.map +1 -0
  30. package/dist/src/shared/installBaseUtils.js +79 -0
  31. package/dist/src/shared/installBaseUtils.js.map +1 -0
  32. package/dist/src/shared/resolveAliases.d.ts +24 -0
  33. package/dist/src/shared/resolveAliases.d.ts.map +1 -0
  34. package/dist/src/shared/resolveAliases.js +98 -0
  35. package/dist/src/shared/resolveAliases.js.map +1 -0
  36. package/docs/components/breadcrumb.md +955 -0
  37. package/docs/components/button-group.md +647 -0
  38. package/docs/components/text-editor.md +711 -0
  39. package/docs/components/toggle-button.md +640 -0
  40. package/docs/tutorials/getting-started.md +123 -431
  41. package/package.json +1 -1
@@ -0,0 +1,711 @@
1
+ ---
2
+ name: TextEditor
3
+ version: 1.1.15
4
+ status: stable
5
+ category: components/editors
6
+ tags: [editor, rich-text, block-editor, editorjs, markdown, rtl, accessible]
7
+ last-reviewed: 2024-11-05
8
+ bundle-size: 45kb
9
+ dependencies:
10
+ - "@editorjs/editorjs": "^2.28.0"
11
+ - "@editorjs/header": "^2.8.0"
12
+ - "@editorjs/list": "^1.9.0"
13
+ - "@editorjs/nested-list": "^1.4.0"
14
+ - "@editorjs/checklist": "^1.6.0"
15
+ - "@editorjs/quote": "^2.6.0"
16
+ - "@editorjs/warning": "^1.4.0"
17
+ - "@editorjs/code": "^2.9.0"
18
+ - "@editorjs/delimiter": "^1.4.0"
19
+ - "@editorjs/embed": "^2.7.0"
20
+ - "@editorjs/table": "^2.3.0"
21
+ - "@editorjs/link": "^2.6.0"
22
+ - "@editorjs/simple-image": "^1.6.0"
23
+ - "@editorjs/raw": "^2.5.0"
24
+ - "@editorjs/marker": "^1.4.0"
25
+ - "@editorjs/inline-code": "^1.5.0"
26
+ - "@editorjs/underline": "^1.1.0"
27
+ - "@editorjs/text-variant-tune": "^1.0.0"
28
+ - "class-variance-authority": "^0.7.0"
29
+ ---
30
+
31
+ # TextEditor
32
+
33
+ > A feature-rich block-style text editor built on Editor.js with 18+ block tools, automatic RTL/LTR detection, markdown paste support, dark mode theming, and imperative ref methods for programmatic control.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install torch-glare
39
+ ```
40
+
41
+ ## Import
42
+
43
+ ```typescript
44
+ import { TextEditor } from 'torch-glare/lib/components/TextEditor'
45
+ // or
46
+ import { TextEditor } from 'torch-glare/lib/components'
47
+ ```
48
+
49
+ ## Quick Examples
50
+
51
+ ### Basic Usage
52
+
53
+ ```typescript
54
+ import { TextEditor } from 'torch-glare/lib/components/TextEditor'
55
+
56
+ function Example() {
57
+ return (
58
+ <TextEditor
59
+ placeholder="Start writing..."
60
+ onChange={(data) => console.log(data)}
61
+ />
62
+ )
63
+ }
64
+ ```
65
+
66
+ ### With Initial Data
67
+
68
+ ```typescript
69
+ import { TextEditor } from 'torch-glare/lib/components/TextEditor'
70
+ import type { OutputData } from '@editorjs/editorjs'
71
+
72
+ function Example() {
73
+ const initialData: OutputData = {
74
+ time: Date.now(),
75
+ blocks: [
76
+ {
77
+ type: 'header',
78
+ data: { text: 'Welcome', level: 2 }
79
+ },
80
+ {
81
+ type: 'paragraph',
82
+ data: { text: 'Start editing this document.' }
83
+ }
84
+ ]
85
+ }
86
+
87
+ return (
88
+ <TextEditor
89
+ data={initialData}
90
+ onChange={(data) => console.log('Saved:', data)}
91
+ />
92
+ )
93
+ }
94
+ ```
95
+
96
+ ### Controlled with Ref
97
+
98
+ ```typescript
99
+ import { useRef } from 'react'
100
+ import { TextEditor, TextEditorRef } from 'torch-glare/lib/components/TextEditor'
101
+
102
+ function EditorWithControls() {
103
+ const editorRef = useRef<TextEditorRef>(null)
104
+
105
+ const handleSave = async () => {
106
+ if (editorRef.current) {
107
+ const data = await editorRef.current.save()
108
+ console.log('Editor content:', data)
109
+ }
110
+ }
111
+
112
+ const handleClear = () => {
113
+ editorRef.current?.clear()
114
+ }
115
+
116
+ return (
117
+ <div>
118
+ <div className="flex gap-2 mb-4">
119
+ <button onClick={handleSave}>Save</button>
120
+ <button onClick={handleClear}>Clear</button>
121
+ </div>
122
+ <TextEditor ref={editorRef} size="L" />
123
+ </div>
124
+ )
125
+ }
126
+ ```
127
+
128
+ ### Different Sizes
129
+
130
+ ```typescript
131
+ <TextEditor size="S" placeholder="Small editor (200px)" />
132
+ <TextEditor size="M" placeholder="Medium editor (300px)" />
133
+ <TextEditor size="L" placeholder="Large editor (400px)" />
134
+ <TextEditor size="XL" placeholder="Extra large editor (500px)" />
135
+ ```
136
+
137
+ ### Custom Min Height
138
+
139
+ ```typescript
140
+ <TextEditor
141
+ minHeight={600}
142
+ placeholder="Custom height editor"
143
+ />
144
+ ```
145
+
146
+ ### Read-Only Mode
147
+
148
+ ```typescript
149
+ function ReadOnlyEditor({ content }: { content: OutputData }) {
150
+ return (
151
+ <TextEditor
152
+ data={content}
153
+ readOnly={true}
154
+ size="L"
155
+ />
156
+ )
157
+ }
158
+ ```
159
+
160
+ ### With Theme Override
161
+
162
+ ```typescript
163
+ <TextEditor theme="dark" size="M" placeholder="Dark theme editor" />
164
+ <TextEditor theme="light" size="M" placeholder="Light theme editor" />
165
+ ```
166
+
167
+ ### Disabled State
168
+
169
+ ```typescript
170
+ <TextEditor
171
+ disabled
172
+ data={someData}
173
+ size="M"
174
+ />
175
+ ```
176
+
177
+ ### Custom Tools Override
178
+
179
+ ```typescript
180
+ import Header from '@editorjs/header'
181
+ import List from '@editorjs/list'
182
+
183
+ function MinimalEditor() {
184
+ const customTools = {
185
+ header: {
186
+ class: Header,
187
+ inlineToolbar: true,
188
+ config: { levels: [2, 3], defaultLevel: 2 }
189
+ },
190
+ list: {
191
+ class: List,
192
+ inlineToolbar: true
193
+ }
194
+ }
195
+
196
+ return (
197
+ <TextEditor
198
+ tools={customTools}
199
+ placeholder="Only headers and lists allowed"
200
+ />
201
+ )
202
+ }
203
+ ```
204
+
205
+ ### With onReady Callback
206
+
207
+ ```typescript
208
+ function EditorWithReadyState() {
209
+ const [isReady, setIsReady] = useState(false)
210
+
211
+ return (
212
+ <div>
213
+ {!isReady && <div>Loading editor...</div>}
214
+ <TextEditor
215
+ onReady={() => setIsReady(true)}
216
+ placeholder="Editor loads asynchronously"
217
+ />
218
+ </div>
219
+ )
220
+ }
221
+ ```
222
+
223
+ ### Markdown Paste Support
224
+
225
+ ```typescript
226
+ function MarkdownEditor() {
227
+ return (
228
+ <TextEditor
229
+ size="L"
230
+ placeholder="Try pasting markdown content here..."
231
+ onChange={(data) => console.log(data)}
232
+ />
233
+ )
234
+ }
235
+ // Pasting markdown like "# Heading\n- item 1\n- item 2" will
236
+ // auto-convert to Editor.js header and list blocks.
237
+ ```
238
+
239
+ ## API Reference
240
+
241
+ ### Props
242
+
243
+ | Prop | Type | Default | Description |
244
+ |------|------|---------|-------------|
245
+ | `variant` | `'PresentationStyle'` | `'PresentationStyle'` | Visual style variant |
246
+ | `size` | `'S' \| 'M' \| 'L' \| 'XL'` | - | Size preset controlling min-height and padding |
247
+ | `theme` | `'light' \| 'dark' \| 'default'` | - | Override theme for this component |
248
+ | `data` | `OutputData` | - | Initial editor content (Editor.js OutputData format) |
249
+ | `onChange` | `(data: OutputData) => void` | - | Called when content changes (debounced) |
250
+ | `onReady` | `() => void` | - | Called when editor finishes initialization |
251
+ | `readOnly` | `boolean` | `false` | Makes the editor non-editable |
252
+ | `placeholder` | `string` | `'Write something or press / to select a tool'` | Placeholder text for empty editor |
253
+ | `autofocus` | `boolean` | `false` | Auto-focus the editor on mount |
254
+ | `tools` | `Record<string, any>` | Default 18+ tools | Override the Editor.js tools configuration |
255
+ | `minHeight` | `number` | - | Custom min-height in pixels (overrides size) |
256
+ | `disabled` | `boolean` | `false` | Disables interaction with reduced opacity |
257
+ | `className` | `string` | - | Additional CSS classes |
258
+
259
+ ### Ref Methods (TextEditorRef)
260
+
261
+ | Method | Signature | Description |
262
+ |--------|-----------|-------------|
263
+ | `save` | `() => Promise<OutputData>` | Saves and returns the current editor content |
264
+ | `clear` | `() => void` | Clears all editor content |
265
+ | `render` | `(data: OutputData) => Promise<void>` | Renders the provided data into the editor |
266
+ | `focus` | `(atEnd?: boolean) => boolean` | Focuses the editor; optionally at the end of content |
267
+ | `getInstance` | `() => EditorJS \| null` | Returns the raw Editor.js instance |
268
+
269
+ ### Size Variants
270
+
271
+ | Size | Min Height | Padding |
272
+ |------|-----------|---------|
273
+ | S | 200px | 8px (p-2) |
274
+ | M | 300px | 12px (p-3) |
275
+ | L | 400px | 16px (p-4) |
276
+ | XL | 500px | 20px (p-5) |
277
+
278
+ ### Default Block Tools
279
+
280
+ | Tool | Shortcut | Description |
281
+ |------|----------|-------------|
282
+ | Header | `Cmd+Shift+H` | H1-H6 headings |
283
+ | List | `Cmd+Shift+L` | Ordered/unordered lists |
284
+ | NestedList | - | Multi-level nested lists |
285
+ | Checklist | - | Interactive checkboxes |
286
+ | Quote | `Cmd+Shift+O` | Block quotes with caption |
287
+ | Warning | - | Warning/notice blocks |
288
+ | Code | `Cmd+Shift+C` | Code snippets |
289
+ | Delimiter | - | Horizontal divider |
290
+ | Embed | - | YouTube, Vimeo, CodePen, Twitter, Instagram, GitHub |
291
+ | Table | `Cmd+Alt+T` | Data tables (default 2x3) |
292
+ | LinkTool | - | Link previews |
293
+ | SimpleImage | - | Image blocks via URL |
294
+ | Raw | - | Raw HTML blocks |
295
+ | Chart | - | Interactive chart blocks (bar, line, pie, doughnut, radar, polar) |
296
+ | Marker | `Cmd+Shift+M` | Inline text highlighting |
297
+ | InlineCode | `Cmd+Shift+I` | Inline code formatting |
298
+ | Underline | - | Inline underline formatting |
299
+ | TextVariant | - | Block-level text tune (callout, citation, details) |
300
+
301
+ ### TypeScript
302
+
303
+ ```typescript
304
+ import { HTMLAttributes } from 'react'
305
+ import { VariantProps } from 'class-variance-authority'
306
+ import { OutputData } from '@editorjs/editorjs'
307
+
308
+ type Themes = 'light' | 'dark' | 'default'
309
+
310
+ export interface TextEditorRef {
311
+ save: () => Promise<OutputData>
312
+ clear: () => void
313
+ render: (data: OutputData) => Promise<void>
314
+ focus: (atEnd?: boolean) => boolean
315
+ getInstance: () => EditorJS | null
316
+ }
317
+
318
+ interface TextEditorProps
319
+ extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>,
320
+ VariantProps<typeof textEditorStyles> {
321
+ theme?: Themes
322
+ data?: OutputData
323
+ onChange?: (data: OutputData) => void
324
+ onReady?: () => void
325
+ readOnly?: boolean
326
+ placeholder?: string
327
+ autofocus?: boolean
328
+ tools?: Record<string, any>
329
+ minHeight?: number
330
+ }
331
+
332
+ export const TextEditor: React.ForwardRefExoticComponent<
333
+ TextEditorProps & React.RefAttributes<TextEditorRef>
334
+ >
335
+
336
+ export type { TextEditorProps, OutputData }
337
+ ```
338
+
339
+ ## Common Patterns
340
+
341
+ ### Blog Post Editor
342
+
343
+ ```typescript
344
+ import { useRef, useState } from 'react'
345
+ import { TextEditor, TextEditorRef } from 'torch-glare/lib/components/TextEditor'
346
+ import { Button } from 'torch-glare/lib/components/Button'
347
+ import type { OutputData } from '@editorjs/editorjs'
348
+
349
+ function BlogPostEditor() {
350
+ const editorRef = useRef<TextEditorRef>(null)
351
+ const [isSaving, setIsSaving] = useState(false)
352
+
353
+ const handlePublish = async () => {
354
+ if (!editorRef.current) return
355
+ setIsSaving(true)
356
+
357
+ try {
358
+ const content = await editorRef.current.save()
359
+ await publishPost(content)
360
+ } finally {
361
+ setIsSaving(false)
362
+ }
363
+ }
364
+
365
+ return (
366
+ <div className="max-w-3xl mx-auto">
367
+ <TextEditor
368
+ ref={editorRef}
369
+ size="XL"
370
+ autofocus
371
+ placeholder="Write your blog post..."
372
+ onChange={(data) => autoSaveDraft(data)}
373
+ />
374
+ <div className="mt-4 flex justify-end">
375
+ <Button
376
+ variant="PrimeStyle"
377
+ is_loading={isSaving}
378
+ onClick={handlePublish}
379
+ >
380
+ Publish
381
+ </Button>
382
+ </div>
383
+ </div>
384
+ )
385
+ }
386
+ ```
387
+
388
+ ### Preview Mode Toggle
389
+
390
+ ```typescript
391
+ function EditorWithPreview() {
392
+ const editorRef = useRef<TextEditorRef>(null)
393
+ const [readOnly, setReadOnly] = useState(false)
394
+
395
+ return (
396
+ <div>
397
+ <div className="flex gap-2 mb-4">
398
+ <Button
399
+ variant={readOnly ? 'BorderStyle' : 'PrimeStyle'}
400
+ onClick={() => setReadOnly(false)}
401
+ >
402
+ Edit
403
+ </Button>
404
+ <Button
405
+ variant={readOnly ? 'PrimeStyle' : 'BorderStyle'}
406
+ onClick={() => setReadOnly(true)}
407
+ >
408
+ Preview
409
+ </Button>
410
+ </div>
411
+ <TextEditor
412
+ ref={editorRef}
413
+ readOnly={readOnly}
414
+ size="L"
415
+ />
416
+ </div>
417
+ )
418
+ }
419
+ ```
420
+
421
+ ### Load and Render Saved Content
422
+
423
+ ```typescript
424
+ function DocumentViewer({ documentId }: { documentId: string }) {
425
+ const editorRef = useRef<TextEditorRef>(null)
426
+
427
+ useEffect(() => {
428
+ async function loadDocument() {
429
+ const data = await fetchDocument(documentId)
430
+ await editorRef.current?.render(data)
431
+ }
432
+ loadDocument()
433
+ }, [documentId])
434
+
435
+ return (
436
+ <TextEditor
437
+ ref={editorRef}
438
+ readOnly
439
+ size="XL"
440
+ />
441
+ )
442
+ }
443
+ ```
444
+
445
+ ### Form Integration
446
+
447
+ ```typescript
448
+ import { useForm, Controller } from 'react-hook-form'
449
+
450
+ function ArticleForm() {
451
+ const { control, handleSubmit } = useForm()
452
+
453
+ return (
454
+ <form onSubmit={handleSubmit(onSubmit)}>
455
+ <Controller
456
+ name="content"
457
+ control={control}
458
+ render={({ field }) => (
459
+ <TextEditor
460
+ data={field.value}
461
+ onChange={field.onChange}
462
+ size="L"
463
+ placeholder="Article content..."
464
+ />
465
+ )}
466
+ />
467
+ <Button type="submit" variant="PrimeStyle">
468
+ Save Article
469
+ </Button>
470
+ </form>
471
+ )
472
+ }
473
+ ```
474
+
475
+ ## Testing
476
+
477
+ ### Unit Test Example
478
+
479
+ ```typescript
480
+ import { render, screen, waitFor } from '@testing-library/react'
481
+ import { TextEditor, TextEditorRef } from 'torch-glare/lib/components/TextEditor'
482
+
483
+ describe('TextEditor', () => {
484
+ it('renders the editor container', () => {
485
+ const { container } = render(
486
+ <TextEditor size="M" />
487
+ )
488
+
489
+ expect(container.querySelector('.torch-text-editor')).toBeInTheDocument()
490
+ })
491
+
492
+ it('applies size class', () => {
493
+ const { container } = render(
494
+ <TextEditor size="L" />
495
+ )
496
+
497
+ const editor = container.querySelector('.torch-text-editor')
498
+ expect(editor).toHaveClass('min-h-[400px]')
499
+ })
500
+
501
+ it('applies disabled state', () => {
502
+ const { container } = render(
503
+ <TextEditor disabled />
504
+ )
505
+
506
+ const editor = container.querySelector('.torch-text-editor')
507
+ expect(editor).toHaveClass('cursor-not-allowed', 'pointer-events-none')
508
+ })
509
+
510
+ it('calls onReady when editor initializes', async () => {
511
+ const onReady = jest.fn()
512
+ render(<TextEditor onReady={onReady} />)
513
+
514
+ await waitFor(() => {
515
+ expect(onReady).toHaveBeenCalledTimes(1)
516
+ })
517
+ })
518
+
519
+ it('exposes ref methods', async () => {
520
+ const ref = React.createRef<TextEditorRef>()
521
+ render(<TextEditor ref={ref} />)
522
+
523
+ await waitFor(() => {
524
+ expect(ref.current).toBeDefined()
525
+ expect(ref.current?.save).toBeDefined()
526
+ expect(ref.current?.clear).toBeDefined()
527
+ expect(ref.current?.render).toBeDefined()
528
+ expect(ref.current?.focus).toBeDefined()
529
+ expect(ref.current?.getInstance).toBeDefined()
530
+ })
531
+ })
532
+ })
533
+ ```
534
+
535
+ ## Accessibility
536
+
537
+ ### Keyboard Support
538
+
539
+ - **Tab**: Navigate between block tools and editor content
540
+ - **Enter**: Create new block
541
+ - **/**: Open block tool selector (slash command)
542
+ - **Cmd+Shift+H**: Insert header
543
+ - **Cmd+Shift+L**: Insert list
544
+ - **Cmd+Shift+O**: Insert quote
545
+ - **Cmd+Shift+C**: Insert code block
546
+ - **Cmd+Alt+T**: Insert table
547
+ - **Cmd+Shift+M**: Apply marker highlight
548
+ - **Cmd+Shift+I**: Apply inline code
549
+
550
+ ### ARIA Attributes
551
+
552
+ The TextEditor renders semantic HTML through Editor.js:
553
+
554
+ ```html
555
+ <div
556
+ data-theme="dark"
557
+ class="torch-text-editor"
558
+ spellcheck="false"
559
+ translate="no"
560
+ >
561
+ <div id="torch-editor-xxxxx">
562
+ <!-- Editor.js renders accessible content blocks -->
563
+ </div>
564
+ </div>
565
+ ```
566
+
567
+ ### RTL/LTR Support
568
+
569
+ - Automatic per-block direction detection based on first visible character
570
+ - Supports Arabic, Hebrew, Farsi, and other RTL scripts
571
+ - List bullets and checkboxes flip correctly in RTL mode
572
+ - Mixed-direction documents supported (each block detects independently)
573
+
574
+ ### Screen Reader Support
575
+
576
+ - Editor.js blocks render as semantic HTML (headings, lists, paragraphs)
577
+ - Block tools accessible via keyboard navigation
578
+ - Inline toolbar supports keyboard shortcuts
579
+
580
+ ## Styling
581
+
582
+ ### Custom Styles with className
583
+
584
+ ```typescript
585
+ <TextEditor
586
+ className="shadow-lg border border-gray-200 rounded-lg"
587
+ size="L"
588
+ />
589
+ ```
590
+
591
+ ### Theme Customization
592
+
593
+ ```css
594
+ /* Custom theme variables */
595
+ [data-theme="custom"] .torch-text-editor {
596
+ --color-background: #your-bg;
597
+ --color-text-primary: #your-text;
598
+ --color-border: #your-border;
599
+ }
600
+ ```
601
+
602
+ ### Dark Mode
603
+
604
+ The TextEditor includes comprehensive dark mode CSS variable overrides for:
605
+
606
+ - Editor.js core popover menus
607
+ - Inline toolbar
608
+ - Table plugin cells, borders, and toolbox
609
+ - Search fields in popovers
610
+ - Chart block tool elements
611
+ - Scrollbar styling
612
+
613
+ Dark mode activates automatically via `data-theme="dark"` on the component or a parent element.
614
+
615
+ ### Design Token Classes
616
+
617
+ The PresentationStyle variant maps to the following design tokens:
618
+
619
+ - Background: `bg-background-presentation-form-field-primary`
620
+ - Text: `text-content-presentation-global-primary`
621
+ - Selection: `bg-background-presentation-action-hover`
622
+ - Marker: `bg-background-presentation-state-warning-primary`
623
+ - Toolbar: `text-content-presentation-action-light-primary`
624
+ - Popover: `bg-background-presentation-form-field-primary`
625
+ - Code: `bg-background-presentation-action-secondary`
626
+
627
+ ## Performance
628
+
629
+ | Metric | Value |
630
+ |--------|-------|
631
+ | Bundle size (gzip) | ~45kb (with all tools) |
632
+ | First render | ~50ms |
633
+ | onChange debounce | 500ms + requestIdleCallback |
634
+ | Tree-shakeable | Partially (tools are bundled) |
635
+
636
+ ### Optimization Tips
637
+
638
+ 1. Use `tools` prop to include only needed block tools for smaller bundles
639
+ 2. The `onChange` callback is debounced (500ms) and deferred to idle time via `requestIdleCallback`
640
+ 3. Auto-direction uses `requestAnimationFrame` batching to avoid layout thrashing
641
+ 4. Markdown paste uses a single `render()` call instead of per-block insertion
642
+ 5. MutationObserver watches only direct children of the redactor (not full subtree)
643
+
644
+ ## Troubleshooting
645
+
646
+ ### Common Issues
647
+
648
+ #### Editor not initializing
649
+
650
+ **Solution:** Ensure the component is mounted in a client component with `"use client"` directive. TextEditor requires browser APIs.
651
+
652
+ ```typescript
653
+ "use client"
654
+
655
+ import { TextEditor } from 'torch-glare/lib/components/TextEditor'
656
+
657
+ export default function Page() {
658
+ return <TextEditor />
659
+ }
660
+ ```
661
+
662
+ #### onChange fires too frequently
663
+
664
+ **Solution:** The onChange is already debounced at 500ms + `requestIdleCallback`. For additional throttling, wrap your handler:
665
+
666
+ ```typescript
667
+ const debouncedSave = useMemo(
668
+ () => debounce((data: OutputData) => saveToServer(data), 2000),
669
+ []
670
+ )
671
+
672
+ <TextEditor onChange={debouncedSave} />
673
+ ```
674
+
675
+ #### RTL text not detected
676
+
677
+ **Solution:** The auto-direction detects RTL based on the first visible character. If your text starts with LTR characters (numbers, punctuation), the block will be LTR. Start with an RTL character for correct detection.
678
+
679
+ #### Tailwind resets strip heading styles
680
+
681
+ **Solution:** The TextEditor injects heading styles automatically via an internal stylesheet that restores h1-h6 sizing within the `.torch-text-editor` container. No additional configuration needed.
682
+
683
+ ## Related Components
684
+
685
+ - [Input](/docs/components/input.md) - Single-line text input
686
+ - [Textarea](/docs/components/textarea.md) - Multi-line plain text input
687
+ - [Form](/docs/components/form.md) - Form wrapper for validation
688
+
689
+ ## Browser Support
690
+
691
+ - Chrome 90+ (requestIdleCallback supported)
692
+ - Firefox 88+ (requestIdleCallback supported)
693
+ - Safari 14+ (fallback to setTimeout for requestIdleCallback)
694
+ - Edge 90+
695
+ - Mobile browsers (touch-friendly block controls)
696
+
697
+ ## Changelog
698
+
699
+ ### v1.1.15
700
+ - Added ChartBlockTool for interactive chart creation
701
+ - Added markdown paste support with auto-conversion
702
+ - Added auto RTL/LTR detection per block
703
+ - Comprehensive dark mode CSS variable overrides
704
+ - Debounced onChange with requestIdleCallback scheduling
705
+ - Performance-optimized MutationObserver for direction tracking
706
+
707
+ ### v1.1.0
708
+ - Initial stable release with 18 block tools
709
+ - PresentationStyle variant
710
+ - 4 size presets
711
+ - Imperative ref API