@lyfie/luthor 2.1.0 → 2.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 (35) hide show
  1. package/README.md +228 -463
  2. package/dist/ExtensiveEditor-Cn84lsVT.d.ts +448 -0
  3. package/dist/index-BtdZVw3X.d.ts +17 -0
  4. package/dist/index-DFnca6tP.d.ts +30 -0
  5. package/dist/index.css +1 -1
  6. package/dist/index.d.ts +206 -52
  7. package/dist/index.js +3 -1
  8. package/dist/presets/chat-window/index.css +1 -0
  9. package/dist/presets/chat-window/index.d.ts +22 -0
  10. package/dist/presets/chat-window/index.js +3 -0
  11. package/dist/presets/email-compose/index.css +1 -0
  12. package/dist/presets/email-compose/index.d.ts +17 -0
  13. package/dist/presets/email-compose/index.js +3 -0
  14. package/dist/presets/extensive/index.css +1 -0
  15. package/dist/presets/extensive/index.d.ts +5 -0
  16. package/dist/presets/extensive/index.js +3 -0
  17. package/dist/presets/headless-editor/index.css +1 -0
  18. package/dist/presets/headless-editor/index.d.ts +13 -0
  19. package/dist/presets/headless-editor/index.js +1 -0
  20. package/dist/presets/md-text/index.css +1 -0
  21. package/dist/presets/md-text/index.d.ts +15 -0
  22. package/dist/presets/md-text/index.js +3 -0
  23. package/dist/presets/notes/index.css +1 -0
  24. package/dist/presets/notes/index.d.ts +21 -0
  25. package/dist/presets/notes/index.js +3 -0
  26. package/dist/presets/notion-like/index.css +1 -0
  27. package/dist/presets/notion-like/index.d.ts +17 -0
  28. package/dist/presets/notion-like/index.js +3 -0
  29. package/dist/presets/rich-text-box/index.css +1 -0
  30. package/dist/presets/rich-text-box/index.d.ts +17 -0
  31. package/dist/presets/rich-text-box/index.js +3 -0
  32. package/dist/presets/simple-text/index.css +1 -0
  33. package/dist/presets/simple-text/index.d.ts +15 -0
  34. package/dist/presets/simple-text/index.js +3 -0
  35. package/package.json +76 -18
package/README.md CHANGED
@@ -1,555 +1,320 @@
1
- # Luthor Presets
1
+ # @lyfie/luthor
2
2
 
3
- **Batteries-included presets and plug-and-play configurations for the Luthor editor**
3
+ Plug-and-play React editor preset package built on top of `@lyfie/luthor-headless`.
4
4
 
5
- This package provides ready-to-use editor presets built on top of [@lyfie/luthor-headless](../headless/README.md). All Lexical dependencies are included - no additional installations required.
5
+ ## Package responsibility
6
6
 
7
- [![npm version](https://badge.fury.io/js/%40lyfie%2Fluthor.svg)](https://badge.fury.io/js/%40lyfie%2Fluthor)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
-
10
- ---
11
-
12
- ## Why Use This Package?
13
-
14
- ### ✅ Zero Configuration Hassle
15
- - All Lexical packages bundled as dependencies
16
- - No peer dependency warnings
17
- - No version conflicts to resolve
18
-
19
- ### 🎮 Ready-to-Use Editors
20
- - Multiple presets for different use cases
21
- - Pre-built toolbar components
22
- - Styled and themed out of the box
23
-
24
- ### 🔧 Still Fully Customizable
25
- - Extend or modify any preset
26
- - Access all luthor-headless features
27
- - Build on top of presets with your own extensions
28
-
29
- ---
7
+ - Owns preset UX/composition and plug-and-play defaults.
8
+ - Reuses headless behavior APIs instead of re-implementing Lexical semantics.
9
+ - Exposes headless namespace for advanced users who need lower-level control.
30
10
 
31
11
  ## Installation
32
12
 
33
- Install both the headless package and this preset package:
34
-
35
13
  ```bash
36
- npm install @lyfie/luthor-headless
37
- npm install @lyfie/luthor
14
+ pnpm add @lyfie/luthor react react-dom
38
15
  ```
39
16
 
40
- **That's it!** All required Lexical packages are automatically installed as dependencies of `@lyfie/luthor`.
41
-
42
- ### What Gets Installed
43
-
44
- When you install `@lyfie/luthor`, npm automatically installs:
45
- - `@lyfie/luthor-headless` (the core editor)
46
- - `lexical` (the Lexical framework)
47
- - All `@lexical/*` packages (code, html, link, list, markdown, react, rich-text, selection, table, utils)
48
-
49
- These satisfy the peer dependencies of luthor-headless, so you don't need to install anything else.
50
-
51
- ### Peer Dependencies
52
-
53
- Only React remains as a peer dependency (which you already have in your project):
54
- - `react` (^18.0.0 or ^19.0.0)
55
- - `react-dom` (^18.0.0 or ^19.0.0)
56
-
57
- ---
58
-
59
17
  ## Quick Start
60
18
 
61
- ### Using the Extensive Editor (Recommended)
62
-
63
- The fastest way to get a full-featured editor:
64
-
65
19
  ```tsx
66
20
  import { ExtensiveEditor } from "@lyfie/luthor";
67
- import type { ExtensiveEditorRef } from "@lyfie/luthor";
68
- import { useRef } from "react";
69
-
70
- function App() {
71
- const editorRef = useRef<ExtensiveEditorRef>(null);
72
-
73
- const handleSave = () => {
74
- const html = editorRef.current?.getHTML();
75
- const markdown = editorRef.current?.getMarkdown();
76
- console.log({ html, markdown });
77
- };
78
-
79
- return (
80
- <div>
81
- <ExtensiveEditor
82
- ref={editorRef}
83
- placeholder="Start writing..."
84
- onReady={(methods) => {
85
- console.log("Editor ready!", methods);
86
- }}
87
- />
88
- <button onClick={handleSave}>Save Content</button>
89
- </div>
90
- );
21
+ import "@lyfie/luthor/styles.css";
22
+
23
+ export function App() {
24
+ return <ExtensiveEditor placeholder="Start writing..." />;
91
25
  }
92
26
  ```
93
27
 
94
- This gives you:
95
- - ✅ Full-featured toolbar
96
- - ✅ All formatting options (bold, italic, underline, etc.)
97
- - ✅ Lists, tables, images, links
98
- - ✅ Code blocks with syntax highlighting
99
- - ✅ HTML/Markdown export
100
- - ✅ Dark mode support
101
- - ✅ Command palette (Cmd+K / Ctrl+K)
102
-
103
- ### Using Preset Definitions
28
+ ## What This Package Exposes
104
29
 
105
- For more control over the editor setup:
30
+ - Preset editor component: `ExtensiveEditor`
31
+ - Additional preset editors: `SimpleTextEditor`, `RichTextBoxEditor`, `ChatWindowEditor`, `EmailComposeEditor`, `MDTextEditor`, `NotionLikeEditor`, `HeadlessEditorPreset`, `NotesEditor`
32
+ - Preset builder helpers: `extensivePreset`, `createExtensivePreset`, `presetRegistry`
33
+ - Extension composition helpers: `createExtensiveExtensions`, `extensiveExtensions`
34
+ - Feature flag helpers: `resolveFeatureFlags`, `isFeatureEnabled`, `DEFAULT_FEATURE_FLAGS`
35
+ - Core UI + command utilities: `Toolbar`, `FloatingToolbar`, `generateCommands`, `registerKeyboardShortcuts`, and more
36
+ - Full headless passthrough namespace: `headless`
106
37
 
107
38
  ```tsx
108
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
109
- import { extensiveExtensions } from "@lyfie/luthor";
110
-
111
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
112
-
113
- function MyToolbar() {
114
- const { commands, activeStates } = useEditor();
115
-
116
- return (
117
- <div className="my-custom-toolbar">
118
- <button onClick={() => commands.toggleBold()}>
119
- Bold
120
- </button>
121
- {/* Add your custom UI */}
122
- </div>
123
- );
124
- }
39
+ import { headless } from "@lyfie/luthor";
125
40
 
126
- function App() {
127
- return (
128
- <Provider extensions={extensiveExtensions}>
129
- <MyToolbar />
130
- <RichText placeholder="Start writing..." />
131
- </Provider>
132
- );
133
- }
41
+ const { createEditorSystem, richTextExtension, boldExtension } = headless;
134
42
  ```
135
43
 
136
- ---
137
-
138
- ## Available Presets
139
-
140
- All presets are exported with their configurations and can be used as starting points:
141
-
142
- ### 1. **Minimal** - `minimalPreset`
143
- Lightweight editor for short text and basic formatting.
144
- - Bold, italic, link
145
- - Perfect for comments or short descriptions
146
-
147
- ### 2. **Classic** - `classicPreset`
148
- Traditional rich text editor feel.
149
- - Standard formatting toolbar
150
- - Good for general content editing
151
-
152
- ### 3. **Blog** - `blogPreset`
153
- Optimized for blog post writing.
154
- - Headings, images, quotes
155
- - Clean reading experience
156
-
157
- ### 4. **CMS** - `cmsPreset`
158
- Content management system focused.
159
- - Advanced formatting options
160
- - Image handling with alignment
161
- - Tables for structured content
162
-
163
- ### 5. **Docs** - `docsPreset`
164
- Documentation and technical writing.
165
- - Code blocks with syntax highlighting
166
- - Tables and lists
167
- - Markdown export
168
-
169
- ### 6. **Chat** - `chatPreset`
170
- Lightweight for messaging interfaces.
171
- - Minimal formatting
172
- - Emoji and mentions (with custom extensions)
173
-
174
- ### 7. **Email** - `emailPreset`
175
- Email composition focused.
176
- - Safe HTML output
177
- - Link handling
178
- - Simple formatting
44
+ ## Preset Catalog
45
+
46
+ - `ExtensiveEditor`: full-feature visual/jsonb preset.
47
+ - `SimpleTextEditor`: plain-text locked preset.
48
+ - `RichTextBoxEditor`: inline formatting + list preset with optional compact toolbar.
49
+ - `ChatWindowEditor`: chat composer with send action row.
50
+ - `EmailComposeEditor`: compose shell (To/CC/BCC/Subject) + rich body.
51
+ - `MDTextEditor`: visual/markdown mode switching.
52
+ - `NotionLikeEditor`: slash-first + draggable defaults.
53
+ - `HeadlessEditorPreset`: minimal plain-control reference.
54
+ - `NotesEditor`: note-taking shell with title/actions callbacks.
55
+
56
+ ## ExtensiveEditor API
57
+
58
+ ### Props
59
+
60
+ | Prop | Type | Default | Notes |
61
+ |---|---|---|---|
62
+ | `className` | `string` | `undefined` | Appended to wrapper root. |
63
+ | `variantClassName` | `string` | `undefined` | Extra preset variant class on wrapper. |
64
+ | `onReady` | `(methods: ExtensiveEditorRef) => void` | `undefined` | Called when editor methods are ready. |
65
+ | `initialTheme` | `"light" \| "dark"` | `"light"` | Initial visual theme mode. |
66
+ | `theme` | `Partial<LuthorTheme>` | `undefined` | Theme class/style overrides merged with default theme. |
67
+ | `defaultContent` | `string` | `undefined` | If provided, injected as initial content (JSON parsed or plain text converted to JSONB). |
68
+ | `showDefaultContent` | `boolean` | `true` | If `true` and `defaultContent` is not provided, loads built-in welcome content. |
69
+ | `placeholder` | `string \| { visual?: string; jsonb?: string }` | `"Write anything..."` | String applies to visual mode; object lets you set visual and JSONB placeholders separately. |
70
+ | `initialMode` | `"visual" \| "jsonb"` | `"visual"` | Initial open mode; clamped to `availableModes`. |
71
+ | `availableModes` | `readonly ("visual" \| "jsonb")[]` | `["visual", "jsonb"]` | Visible mode tabs and allowed switching targets. |
72
+ | `toolbarLayout` | `ToolbarLayout` | `TRADITIONAL_TOOLBAR_LAYOUT` | Custom toolbar sections/items order. |
73
+ | `toolbarVisibility` | `Partial<Record<ToolbarItemType, boolean>>` | `undefined` | Per-item hide/show map. Unsupported items are auto-hidden. |
74
+ | `toolbarPosition` | `"top" \| "bottom"` | `"top"` | Renders toolbar above or below visual editor. |
75
+ | `toolbarAlignment` | `"left" \| "center" \| "right"` | `"left"` | Horizontal toolbar alignment class. |
76
+ | `toolbarClassName` | `string` | `undefined` | Extra class for toolbar root (`.luthor-toolbar`). |
77
+ | `toolbarStyleVars` | `ToolbarStyleVars` | `undefined` | Inline `--luthor-toolbar-*` CSS variable overrides. |
78
+ | `isToolbarEnabled` | `boolean` | `true` | Hides toolbar UI only; keyboard/commands still exist unless feature-flagged off. |
79
+ | `quoteClassName` | `string` | `undefined` | Appended to quote node class. |
80
+ | `quoteStyleVars` | `QuoteStyleVars` | `undefined` | Inline `--luthor-quote-*` CSS variable overrides. |
81
+ | `defaultSettings` | `DefaultSettings` | `undefined` | High-level style token API for common color settings. |
82
+ | `editorThemeOverrides` | `LuthorEditorThemeOverrides` | `undefined` | Inline editor-wide `--luthor-*` token overrides. |
83
+ | `fontFamilyOptions` | `readonly FontFamilyOption[]` | preset defaults | Sanitized: duplicates removed, invalid tokens removed, `default` auto-added if missing. |
84
+ | `fontSizeOptions` | `readonly FontSizeOption[]` | preset defaults | Sanitized similarly to font family options. |
85
+ | `minimumDefaultLineHeight` | `string \| number` | `1.5` | Sets the editor default line height and the minimum selectable/accepted line-height floor for the line-height feature; minimum accepted value is `1.0`. |
86
+ | `lineHeightOptions` | `readonly LineHeightOption[]` | preset defaults | Uses unitless ratios (`"1.5"`) for non-default values; minimum allowed is `1.0`. |
87
+ | `scaleByRatio` | `boolean` | `false` | Image resize behavior (`true` = keep ratio by default, Shift unlocks). |
88
+ | `headingOptions` | `readonly ("h1"\|"h2"\|"h3"\|"h4"\|"h5"\|"h6")[]` | all headings | Invalid/duplicate entries are removed. |
89
+ | `paragraphLabel` | `string` | `"Paragraph"` behavior | Label for paragraph entry in block format menu/commands. |
90
+ | `syncHeadingOptionsWithCommands` | `boolean` | `true` | Syncs heading command generation with `headingOptions`. |
91
+ | `slashCommandVisibility` | `SlashCommandVisibility` | `undefined` | Slash command filter using allowlist/denylist or enabled-ID selection array form. |
92
+ | `shortcutConfig` | `ShortcutConfig` | `undefined` | Per-instance command shortcut config (disable/remap, collision and native conflict prevention). |
93
+ | `commandPaletteShortcutOnly` | `boolean` | `false` | When `true`, command palette only shows commands that have a visible keyboard shortcut. |
94
+ | `isDraggableBoxEnabled` | `boolean` | `undefined` | Shortcut for enabling/disabling draggable block UI (maps into `featureFlags.draggableBlock`). |
95
+ | `featureFlags` | `Partial<Record<FeatureFlag, boolean>>` | all `true` | Central capability switchboard; affects extensions, toolbar, commands, shortcuts. |
96
+ | `syntaxHighlighting` | `"auto" \| "disabled"` | extension default (`"auto"`) | Code block syntax highlighting strategy. |
97
+ | `codeHighlightProvider` | `CodeHighlightProvider \| null` | `undefined` | Injected highlight provider instance. |
98
+ | `loadCodeHighlightProvider` | `() => Promise<CodeHighlightProvider \| null>` | `undefined` | Lazy async provider loader. |
99
+ | `maxAutoDetectCodeLength` | `number` | `12000` in code intelligence extension | Guard for auto language detection input size. |
100
+ | `isCopyAllowed` | `boolean` | `true` | Enables/disables code block copy button and command path. |
101
+ | `languageOptions` | `readonly string[] \| { mode?: "append"\|"replace"; values: readonly string[] }` | default language catalog | Controls visible code language options. |
102
+
103
+ ### Ref Methods
104
+
105
+ ```ts
106
+ type ExtensiveEditorRef = {
107
+ injectJSONB: (content: string) => void;
108
+ getJSONB: () => string;
109
+ };
110
+ ```
179
111
 
180
- ### 8. **Markdown** - `markdownPreset`
181
- Markdown-first editing.
182
- - Markdown shortcuts
183
- - Preview mode
184
- - Clean export
112
+ ## Valid Argument Sets
185
113
 
186
- ### 9. **Code** - `codePreset`
187
- Code snippet editor.
188
- - Syntax highlighting
189
- - Multiple language support
190
- - Line numbers
114
+ ### `ToolbarItemType`
191
115
 
192
- ### 10. **Default** - `defaultPreset`
193
- Balanced general-purpose editor.
194
- - Good starting point for customization
116
+ `"fontFamily"`, `"fontSize"`, `"lineHeight"`, `"textColor"`, `"textHighlight"`, `"bold"`, `"italic"`, `"underline"`, `"strikethrough"`, `"subscript"`, `"superscript"`, `"code"`, `"link"`, `"blockFormat"`, `"quote"`, `"alignLeft"`, `"alignCenter"`, `"alignRight"`, `"alignJustify"`, `"codeBlock"`, `"unorderedList"`, `"orderedList"`, `"checkList"`, `"indentList"`, `"outdentList"`, `"horizontalRule"`, `"table"`, `"image"`, `"emoji"`, `"embed"`, `"undo"`, `"redo"`, `"commandPalette"`, `"themeToggle"`.
195
117
 
196
- ### 11. **Extensive** - `extensivePreset` + `ExtensiveEditor`
197
- Full-featured editor with everything.
198
- - All extensions included
199
- - Complete toolbar
200
- - Pre-built component
118
+ ### `FeatureFlag`
201
119
 
202
- ---
120
+ `"bold"`, `"italic"`, `"underline"`, `"strikethrough"`, `"fontFamily"`, `"fontSize"`, `"lineHeight"`, `"textColor"`, `"textHighlight"`, `"subscript"`, `"superscript"`, `"link"`, `"horizontalRule"`, `"table"`, `"list"`, `"history"`, `"image"`, `"blockFormat"`, `"code"`, `"codeIntelligence"`, `"codeFormat"`, `"tabIndent"`, `"enterKeyBehavior"`, `"iframeEmbed"`, `"youTubeEmbed"`, `"floatingToolbar"`, `"contextMenu"`, `"commandPalette"`, `"slashCommand"`, `"emoji"`, `"draggableBlock"`, `"customNode"`, `"themeToggle"`.
203
121
 
204
- ## Usage Examples
122
+ ## Usage Recipes
205
123
 
206
- ### Example 1: Using Preset Registry
124
+ ### 1) Minimal with defaults
207
125
 
208
126
  ```tsx
209
- import { presetRegistry } from "@lyfie/luthor";
210
-
211
- // Get a preset by ID
212
- const blogPreset = presetRegistry.blog;
213
- const minimalPreset = presetRegistry.minimal;
127
+ import { ExtensiveEditor } from "@lyfie/luthor";
128
+ import "@lyfie/luthor/styles.css";
214
129
 
215
- console.log(blogPreset.label); // "Blog"
216
- console.log(blogPreset.toolbar); // ["heading", "bold", "italic", ...]
130
+ export function App() {
131
+ return <ExtensiveEditor />;
132
+ }
217
133
  ```
218
134
 
219
- ### Example 2: Customizing a Preset
135
+ ### 2) Placeholder per mode + restricted modes
220
136
 
221
137
  ```tsx
222
- import { defaultPreset } from "@lyfie/luthor";
223
- import { createEditorSystem } from "@lyfie/luthor-headless";
224
-
225
- // Clone and customize
226
- const myPreset = {
227
- ...defaultPreset,
228
- id: "my-custom",
229
- label: "My Custom Editor",
230
- toolbar: ["bold", "italic", "link"], // Override toolbar
231
- config: {
232
- ...defaultPreset.config,
233
- placeholder: "Write your story...",
234
- },
235
- };
138
+ <ExtensiveEditor
139
+ initialMode="visual"
140
+ availableModes={["visual", "jsonb"]}
141
+ placeholder={{
142
+ visual: "Write release notes...",
143
+ jsonb: "Paste JSONB...",
144
+ }}
145
+ />
236
146
  ```
237
147
 
238
- ### Example 3: Building with Extensions
148
+ ### 3) Feature-gated editor (product tiers)
239
149
 
240
150
  ```tsx
241
- import { extensiveExtensions } from "@lyfie/luthor";
242
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
243
-
244
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
245
-
246
- function Editor() {
247
- const { commands } = useEditor();
248
-
249
- return (
250
- <div>
251
- <button onClick={() => commands.toggleBold()}>Bold</button>
252
- <button onClick={() => commands.insertTable({ rows: 3, cols: 3 })}>
253
- Insert Table
254
- </button>
255
- <RichText />
256
- </div>
257
- );
258
- }
259
-
260
- function App() {
261
- return (
262
- <Provider extensions={extensiveExtensions}>
263
- <Editor />
264
- </Provider>
265
- );
266
- }
151
+ <ExtensiveEditor
152
+ featureFlags={{
153
+ image: false,
154
+ iframeEmbed: false,
155
+ youTubeEmbed: false,
156
+ emoji: false,
157
+ commandPalette: false,
158
+ }}
159
+ toolbarVisibility={{
160
+ embed: false,
161
+ image: false,
162
+ emoji: false,
163
+ }}
164
+ />
267
165
  ```
268
166
 
269
- ### Example 4: Export/Import Content
167
+ ### 4) Slash command allowlist
270
168
 
271
169
  ```tsx
272
- import { ExtensiveEditor } from "@lyfie/luthor";
273
- import type { ExtensiveEditorRef } from "@lyfie/luthor";
274
- import { useRef, useState } from "react";
275
-
276
- function App() {
277
- const editorRef = useRef<ExtensiveEditorRef>(null);
278
- const [savedContent, setSavedContent] = useState("");
279
-
280
- const handleExport = () => {
281
- const html = editorRef.current?.getHTML();
282
- const markdown = editorRef.current?.getMarkdown();
283
- setSavedContent(html || "");
284
- console.log({ html, markdown });
285
- };
286
-
287
- const handleImport = () => {
288
- editorRef.current?.injectHTML(savedContent);
289
- // or
290
- editorRef.current?.injectMarkdown("# Hello\n\nMarkdown content");
291
- };
292
-
293
- return (
294
- <div>
295
- <ExtensiveEditor ref={editorRef} />
296
- <button onClick={handleExport}>Export</button>
297
- <button onClick={handleImport}>Import</button>
298
- </div>
299
- );
300
- }
170
+ <ExtensiveEditor
171
+ slashCommandVisibility={{
172
+ allowlist: [
173
+ "block.paragraph",
174
+ "block.heading1",
175
+ "block.heading2",
176
+ "insert.table",
177
+ ],
178
+ denylist: ["insert.image"],
179
+ }}
180
+ />
301
181
  ```
302
182
 
303
- ---
304
-
305
- ## API Reference
306
-
307
- ### ExtensiveEditor Component
183
+ ### 5) Code language list replacement
308
184
 
309
185
  ```tsx
310
- import { ExtensiveEditor } from "@lyfie/luthor";
311
- import type { ExtensiveEditorRef, ExtensiveEditorProps } from "@lyfie/luthor";
186
+ <ExtensiveEditor
187
+ languageOptions={{
188
+ mode: "replace",
189
+ values: ["plaintext", "typescript", "javascript", "markdown", "sql"],
190
+ }}
191
+ syntaxHighlighting="auto"
192
+ maxAutoDetectCodeLength={10000}
193
+ />
312
194
  ```
313
195
 
314
- **Props:**
315
- ```typescript
316
- interface ExtensiveEditorProps {
317
- placeholder?: string; // Placeholder text
318
- className?: string; // CSS class for container
319
- onReady?: (ref: ExtensiveEditorRef) => void; // Called when editor is ready
320
- }
321
- ```
196
+ Optional: use highlight.js stylesheet for richer code colors
322
197
 
323
- **Ref Methods:**
324
- ```typescript
325
- interface ExtensiveEditorRef {
326
- injectMarkdown: (content: string) => void; // Import markdown
327
- injectHTML: (content: string) => void; // Import HTML
328
- getMarkdown: () => string; // Export as markdown
329
- getHTML: () => string; // Export as HTML
330
- }
198
+ ```bash
199
+ pnpm add highlight.js
331
200
  ```
332
201
 
333
- ### Preset Registry
334
-
335
202
  ```tsx
336
- import { presetRegistry } from "@lyfie/luthor";
337
- import type { EditorPreset } from "@lyfie/luthor";
338
- ```
203
+ import { ExtensiveEditor } from "@lyfie/luthor";
204
+ import "@lyfie/luthor/styles.css";
205
+ import "highlight.js/styles/github.css"; // any highlight.js theme
339
206
 
340
- **Type:**
341
- ```typescript
342
- interface EditorPreset {
343
- id: string; // Unique preset ID
344
- label: string; // Display name
345
- description?: string; // Preset description
346
- extensions?: Extension[]; // Included extensions
347
- config?: EditorConfig; // Editor configuration
348
- theme?: LuthorTheme; // Custom theme
349
- toolbar?: string[]; // Toolbar items
350
- components?: Record<string, unknown>; // Custom components
351
- css?: string; // CSS file path
207
+ export function App() {
208
+ return <ExtensiveEditor syntaxHighlighting="auto" />;
352
209
  }
353
210
  ```
354
211
 
355
- **Available Presets:**
356
- - `presetRegistry.minimal`
357
- - `presetRegistry.classic`
358
- - `presetRegistry.blog`
359
- - `presetRegistry.cms`
360
- - `presetRegistry.docs`
361
- - `presetRegistry.chat`
362
- - `presetRegistry.email`
363
- - `presetRegistry.markdown`
364
- - `presetRegistry.code`
365
- - `presetRegistry.default`
366
- - `presetRegistry.extensive`
367
-
368
- ### Extension Sets
369
-
370
- ```tsx
371
- import { extensiveExtensions } from "@lyfie/luthor";
372
- ```
373
-
374
- Pre-configured extension arrays that can be used with luthor-headless:
375
-
376
- ```typescript
377
- const extensions = extensiveExtensions as const;
378
- const { Provider } = createEditorSystem<typeof extensions>();
379
- ```
380
-
381
- ---
382
-
383
- ## Comparison: Headless vs Luthor
384
-
385
- | Feature | @lyfie/luthor-headless | @lyfie/luthor |
386
- |---------|----------------------|---------------|
387
- | **Installation** | Manual Lexical deps | Zero additional deps |
388
- | **Bundle Size** | Minimal | Includes all Lexical |
389
- | **Setup Time** | More configuration | Instant |
390
- | **Flexibility** | Maximum control | Pre-configured |
391
- | **Use Case** | Custom editors | Quick implementation |
392
- | **UI Components** | Build your own | ExtensiveEditor included |
393
- | **Presets** | None | 11 ready-to-use |
394
- | **Dependencies** | Peer deps | Bundled deps |
395
-
396
- **Choose luthor-headless when:**
397
- - Building completely custom UI
398
- - Need minimal bundle size
399
- - Want control over Lexical versions
400
- - Have specific dependency requirements
401
-
402
- **Choose @lyfie/luthor when:**
403
- - Want to start quickly
404
- - Need a working editor ASAP
405
- - Don't want to manage dependencies
406
- - Want ready-to-use components
407
-
408
- [📖 Learn more about luthor-headless](../headless/README.md)
409
-
410
- ---
212
+ What happens:
411
213
 
412
- ## Advanced Usage
214
+ - Luthor does not auto-detect code language anymore. Language comes from the selected code language option.
215
+ - Language options are normalized through Lexical/Prism aliases (`md` -> `markdown`, `js` -> `javascript` family, `ts` -> `typescript`).
216
+ - Only Prism-supported loaded languages are accepted. Unsupported values (for example `yaml` without Prism YAML grammar loaded) fall back to plain text.
217
+ - `plaintext` uses plain fallback syntax styling.
218
+ - Non-plaintext languages emit `hljs-*` token classes.
219
+ - If a highlight.js stylesheet is loaded, those `hljs-*` tokens use highlight.js colors.
220
+ - Without highlight.js stylesheet, code uses the built-in muted/plain fallback.
413
221
 
414
- ### Creating Custom Presets
222
+ ### 6) Per-instance shortcut remap/disable
415
223
 
416
224
  ```tsx
417
- import type { EditorPreset } from "@lyfie/luthor";
418
- import {
419
- boldExtension,
420
- italicExtension,
421
- linkExtension
422
- } from "@lyfie/luthor-headless";
423
-
424
- const myCustomPreset: EditorPreset = {
425
- id: "my-custom",
426
- label: "My Custom Editor",
427
- description: "A tailored editor for my use case",
428
- extensions: [boldExtension, italicExtension, linkExtension],
429
- config: {
430
- placeholder: "Start typing...",
431
- editable: true,
432
- },
433
- toolbar: ["bold", "italic", "link"],
434
- };
225
+ <ExtensiveEditor
226
+ shortcutConfig={{
227
+ disabledCommandIds: ["format.italic"],
228
+ bindings: {
229
+ "format.bold": { key: "m", ctrlKey: true },
230
+ "palette.show": [
231
+ { key: "k", ctrlKey: true, shiftKey: true },
232
+ ],
233
+ },
234
+ }}
235
+ />
435
236
  ```
436
237
 
437
- ### Extending Existing Presets
238
+ ### 7) Command palette shortcut-only mode
438
239
 
439
240
  ```tsx
440
- import { defaultPreset } from "@lyfie/luthor";
441
- import { myCustomExtension } from "./my-extension";
442
-
443
- const enhancedPreset: EditorPreset = {
444
- ...defaultPreset,
445
- id: "enhanced-default",
446
- extensions: [
447
- ...(defaultPreset.extensions || []),
448
- myCustomExtension,
449
- ],
450
- toolbar: [
451
- ...(defaultPreset.toolbar || []),
452
- "myCustomCommand",
453
- ],
454
- };
241
+ <ExtensiveEditor commandPaletteShortcutOnly />
455
242
  ```
456
243
 
457
- ### Accessing Luthor-Headless Features
458
-
459
- Since `@lyfie/luthor` depends on `@lyfie/luthor-headless`, you have access to all headless features:
244
+ ### 8) Style token overrides
460
245
 
461
246
  ```tsx
462
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
463
- import { extensiveExtensions } from "@lyfie/luthor";
464
-
465
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
466
-
467
- // Use all luthor-headless APIs
468
- function MyEditor() {
469
- const { commands, activeStates, lexical } = useEditor();
470
-
471
- // Access Lexical editor instance
472
- lexical?.update(() => {
473
- // Direct Lexical operations
474
- });
475
-
476
- return <RichText />;
477
- }
247
+ <ExtensiveEditor
248
+ editorThemeOverrides={{
249
+ "--luthor-bg": "#fffaf2",
250
+ "--luthor-fg": "#431407",
251
+ "--luthor-accent": "#c2410c",
252
+ }}
253
+ toolbarStyleVars={{
254
+ "--luthor-toolbar-button-active-bg": "#ea580c",
255
+ "--luthor-toolbar-button-active-fg": "#ffffff",
256
+ }}
257
+ quoteStyleVars={{
258
+ "--luthor-quote-bg": "#fff7ed",
259
+ "--luthor-quote-fg": "#7c2d12",
260
+ "--luthor-quote-border": "#ea580c",
261
+ }}
262
+ />
478
263
  ```
479
264
 
480
- ---
481
-
482
- ## TypeScript Support
265
+ ### 9) Emoji library auto-detection (works in `apps/demo`)
483
266
 
484
- Fully typed with TypeScript. All exports include type definitions:
267
+ Install emoji-mart data in the app:
485
268
 
486
- ```typescript
487
- import type {
488
- EditorPreset,
489
- ExtensiveEditorRef,
490
- ExtensiveEditorProps,
491
- ExtensiveEditorMode,
492
- } from "@lyfie/luthor";
269
+ ```bash
270
+ pnpm add -F demo @emoji-mart/data
493
271
  ```
494
272
 
495
- ---
496
-
497
- ## Styling
498
-
499
- The `ExtensiveEditor` component includes default styles. To customize:
273
+ Then use `ExtensiveEditor` normally:
500
274
 
501
275
  ```tsx
502
276
  import { ExtensiveEditor } from "@lyfie/luthor";
277
+ import "@lyfie/luthor/styles.css";
503
278
 
504
- // Add custom class
505
- <ExtensiveEditor className="my-editor" />
506
- ```
507
-
508
- ```css
509
- /* Override default styles */
510
- .my-editor {
511
- --luthor-bg: #ffffff;
512
- --luthor-text: #000000;
513
- --luthor-border: #e5e5e5;
514
- }
515
-
516
- /* Dark mode */
517
- .my-editor.dark {
518
- --luthor-bg: #1a1a1a;
519
- --luthor-text: #ffffff;
520
- --luthor-border: #333333;
279
+ export function App() {
280
+ return <ExtensiveEditor />;
521
281
  }
522
282
  ```
523
283
 
524
- ---
525
-
526
- ## Migration from Headless
284
+ What happens:
527
285
 
528
- If you're using luthor-headless and want to switch:
529
-
530
- **Before:**
531
- ```bash
532
- npm install @lyfie/luthor-headless
533
- npm install lexical @lexical/react @lexical/html # ... many packages
534
- ```
286
+ - `:shortcode` suggestions and resolution use the detected emoji-mart dataset.
287
+ - Emoji toolbar dropdown uses the detected emoji-mart catalog.
288
+ - If the library is not installed/available, it automatically falls back to the built-in emoji list.
535
289
 
536
- **After:**
537
- ```bash
538
- npm install @lyfie/luthor-headless
539
- npm install @lyfie/luthor
540
- # Remove individual @lexical/* packages if desired
541
- ```
290
+ ## Notes and Nuances
542
291
 
543
- Your code doesn't need to change! All luthor-headless APIs work the same way.
292
+ - `featureFlags` are authoritative. If a feature is disabled, related toolbar items and commands are removed/no-op even if you attempt to show them.
293
+ - `slashCommandVisibility` keeps original command ordering; it only filters visibility.
294
+ - `shortcutConfig.disabledCommandIds` removes commands from keyboard handling and command UIs for that editor instance.
295
+ - `shortcutConfig` drops duplicate bindings by default and blocks native editable conflicts by default.
296
+ - `commandPaletteShortcutOnly` is optional; by default command palette can include command items without shortcuts.
297
+ - `languageOptions` normalizes aliases (for example `js` becomes `javascript`) and rejects duplicates after normalization.
298
+ - Emoji suggestions/tooling auto-detect external emoji-mart data when available, and otherwise use the built-in default emoji catalog.
299
+ - `defaultSettings` is style-only; behavior is controlled by explicit props (for example `featureFlags`, `availableModes`).
300
+ - `isToolbarEnabled={false}` hides toolbar UI, but editor shortcuts/features still work unless disabled via `featureFlags`.
544
301
 
545
- ---
302
+ ## Which Package Should I Use?
546
303
 
547
- ## Examples
304
+ - Use `@lyfie/luthor` for fast onboarding and polished defaults.
305
+ - Use `@lyfie/luthor-headless` for full extension/UI control.
548
306
 
549
- Check out the [demo site](https://luthor.lyfie.app/demo) for live examples of all presets.
307
+ ## Documentation
550
308
 
551
- ---
309
+ - Monorepo docs index: [../../documentation/index.md](../../documentation/index.md)
310
+ - Headless package README: [../headless/README.md](../headless/README.md)
311
+ - User docs: [../../documentation/user/luthor/getting-started.md](../../documentation/user/luthor/getting-started.md)
312
+ - Developer docs: [../../documentation/developer/luthor/architecture.md](../../documentation/developer/luthor/architecture.md)
552
313
 
553
- **Built with ❤️ by the Luthor Team**
314
+ ## Workspace Development
554
315
 
555
- MIT License - Use it however you want.
316
+ ```bash
317
+ pnpm --filter @lyfie/luthor dev
318
+ pnpm --filter @lyfie/luthor build
319
+ pnpm --filter @lyfie/luthor lint
320
+ ```