@lyfie/luthor 2.2.0 → 2.3.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.
Files changed (35) hide show
  1. package/README.md +228 -475
  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 +203 -53
  7. package/dist/index.js +3 -16
  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 -20
package/README.md CHANGED
@@ -1,567 +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
37
- npm install @lyfie/luthor-headless @lyfie/luthor
38
-
39
- # pnpm
40
- pnpm add @lyfie/luthor-headless @lyfie/luthor
14
+ pnpm add @lyfie/luthor react react-dom
41
15
  ```
42
16
 
43
- **That's it!** All required Lexical packages are automatically installed as dependencies of `@lyfie/luthor`.
44
-
45
- ### What Gets Installed
46
-
47
- When you install `@lyfie/luthor`, your package manager (npm/pnpm) automatically installs:
48
- - `@lyfie/luthor-headless` (the core editor)
49
- - `lexical` (the Lexical framework)
50
- - All `@lexical/*` packages (code, html, link, list, markdown, react, rich-text, selection, table, utils)
51
-
52
- These satisfy the peer dependencies of luthor-headless, so you don't need to install anything else.
53
-
54
- ### Peer Dependencies
55
-
56
- Only React remains as a peer dependency (which you already have in your project):
57
- - `react` (^18.0.0 or ^19.0.0)
58
- - `react-dom` (^18.0.0 or ^19.0.0)
59
-
60
- ---
61
-
62
17
  ## Quick Start
63
18
 
64
- ### Using the Extensive Editor (Recommended)
65
-
66
- The fastest way to get a full-featured editor:
67
-
68
19
  ```tsx
69
20
  import { ExtensiveEditor } from "@lyfie/luthor";
70
- import type { ExtensiveEditorRef } from "@lyfie/luthor";
71
- import { useRef } from "react";
72
-
73
- function App() {
74
- const editorRef = useRef<ExtensiveEditorRef>(null);
75
-
76
- const handleSave = () => {
77
- const html = editorRef.current?.getHTML();
78
- const markdown = editorRef.current?.getMarkdown();
79
- console.log({ html, markdown });
80
- };
81
-
82
- return (
83
- <div>
84
- <ExtensiveEditor
85
- ref={editorRef}
86
- placeholder="Start writing..."
87
- onReady={(methods) => {
88
- console.log("Editor ready!", methods);
89
- }}
90
- />
91
- <button onClick={handleSave}>Save Content</button>
92
- </div>
93
- );
21
+ import "@lyfie/luthor/styles.css";
22
+
23
+ export function App() {
24
+ return <ExtensiveEditor placeholder="Start writing..." />;
94
25
  }
95
26
  ```
96
27
 
97
- This gives you:
98
- - ✅ Full-featured toolbar
99
- - ✅ All formatting options (bold, italic, underline, etc.)
100
- - ✅ Lists, tables, images, links
101
- - ✅ Code blocks with syntax highlighting
102
- - ✅ HTML/Markdown export
103
- - ✅ Dark mode support
104
- - ✅ Command palette (Cmd+K / Ctrl+K)
105
-
106
- ### Using Preset Definitions
28
+ ## What This Package Exposes
107
29
 
108
- 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`
109
37
 
110
38
  ```tsx
111
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
112
- import { extensiveExtensions } from "@lyfie/luthor";
113
-
114
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
115
-
116
- function MyToolbar() {
117
- const { commands, activeStates } = useEditor();
118
-
119
- return (
120
- <div className="my-custom-toolbar">
121
- <button onClick={() => commands.toggleBold()}>
122
- Bold
123
- </button>
124
- {/* Add your custom UI */}
125
- </div>
126
- );
127
- }
39
+ import { headless } from "@lyfie/luthor";
128
40
 
129
- function App() {
130
- return (
131
- <Provider extensions={extensiveExtensions}>
132
- <MyToolbar />
133
- <RichText placeholder="Start writing..." />
134
- </Provider>
135
- );
136
- }
41
+ const { createEditorSystem, richTextExtension, boldExtension } = headless;
137
42
  ```
138
43
 
139
- ---
140
-
141
- ## Available Presets
142
-
143
- All presets are exported with their configurations and can be used as starting points:
144
-
145
- ### 1. **Minimal** - `minimalPreset`
146
- Lightweight editor for short text and basic formatting.
147
- - Bold, italic, link
148
- - Perfect for comments or short descriptions
149
-
150
- ### 2. **Classic** - `classicPreset`
151
- Traditional rich text editor feel.
152
- - Standard formatting toolbar
153
- - Good for general content editing
154
-
155
- ### 3. **Blog** - `blogPreset`
156
- Optimized for blog post writing.
157
- - Headings, images, quotes
158
- - Clean reading experience
159
-
160
- ### 4. **CMS** - `cmsPreset`
161
- Content management system focused.
162
- - Advanced formatting options
163
- - Image handling with alignment
164
- - Tables for structured content
165
-
166
- ### 5. **Docs** - `docsPreset`
167
- Documentation and technical writing.
168
- - Code blocks with syntax highlighting
169
- - Tables and lists
170
- - Markdown export
171
-
172
- ### 6. **Chat** - `chatPreset`
173
- Lightweight for messaging interfaces.
174
- - Minimal formatting
175
- - Emoji and mentions (with custom extensions)
176
-
177
- ### 7. **Email** - `emailPreset`
178
- Email composition focused.
179
- - Safe HTML output
180
- - Link handling
181
- - 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
+ ```
182
111
 
183
- ### 8. **Markdown** - `markdownPreset`
184
- Markdown-first editing.
185
- - Markdown shortcuts
186
- - Preview mode
187
- - Clean export
112
+ ## Valid Argument Sets
188
113
 
189
- ### 9. **Code** - `codePreset`
190
- Code snippet editor.
191
- - Syntax highlighting
192
- - Multiple language support
193
- - Line numbers
114
+ ### `ToolbarItemType`
194
115
 
195
- ### 10. **Default** - `defaultPreset`
196
- Balanced general-purpose editor.
197
- - 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"`.
198
117
 
199
- ### 11. **Extensive** - `extensivePreset` + `ExtensiveEditor`
200
- Full-featured editor with everything.
201
- - All extensions included
202
- - Complete toolbar
203
- - Pre-built component
118
+ ### `FeatureFlag`
204
119
 
205
- ---
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"`.
206
121
 
207
- ## Usage Examples
122
+ ## Usage Recipes
208
123
 
209
- ### Example 1: Using Preset Registry
124
+ ### 1) Minimal with defaults
210
125
 
211
126
  ```tsx
212
- import { presetRegistry } from "@lyfie/luthor";
213
-
214
- // Get a preset by ID
215
- const blogPreset = presetRegistry.blog;
216
- const minimalPreset = presetRegistry.minimal;
127
+ import { ExtensiveEditor } from "@lyfie/luthor";
128
+ import "@lyfie/luthor/styles.css";
217
129
 
218
- console.log(blogPreset.label); // "Blog"
219
- console.log(blogPreset.toolbar); // ["heading", "bold", "italic", ...]
130
+ export function App() {
131
+ return <ExtensiveEditor />;
132
+ }
220
133
  ```
221
134
 
222
- ### Example 2: Customizing a Preset
135
+ ### 2) Placeholder per mode + restricted modes
223
136
 
224
137
  ```tsx
225
- import { defaultPreset } from "@lyfie/luthor";
226
- import { createEditorSystem } from "@lyfie/luthor-headless";
227
-
228
- // Clone and customize
229
- const myPreset = {
230
- ...defaultPreset,
231
- id: "my-custom",
232
- label: "My Custom Editor",
233
- toolbar: ["bold", "italic", "link"], // Override toolbar
234
- config: {
235
- ...defaultPreset.config,
236
- placeholder: "Write your story...",
237
- },
238
- };
138
+ <ExtensiveEditor
139
+ initialMode="visual"
140
+ availableModes={["visual", "jsonb"]}
141
+ placeholder={{
142
+ visual: "Write release notes...",
143
+ jsonb: "Paste JSONB...",
144
+ }}
145
+ />
239
146
  ```
240
147
 
241
- ### Example 3: Building with Extensions
148
+ ### 3) Feature-gated editor (product tiers)
242
149
 
243
150
  ```tsx
244
- import { extensiveExtensions } from "@lyfie/luthor";
245
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
246
-
247
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
248
-
249
- function Editor() {
250
- const { commands } = useEditor();
251
-
252
- return (
253
- <div>
254
- <button onClick={() => commands.toggleBold()}>Bold</button>
255
- <button onClick={() => commands.insertTable({ rows: 3, cols: 3 })}>
256
- Insert Table
257
- </button>
258
- <RichText />
259
- </div>
260
- );
261
- }
262
-
263
- function App() {
264
- return (
265
- <Provider extensions={extensiveExtensions}>
266
- <Editor />
267
- </Provider>
268
- );
269
- }
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
+ />
270
165
  ```
271
166
 
272
- ### Example 4: Export/Import Content
167
+ ### 4) Slash command allowlist
273
168
 
274
169
  ```tsx
275
- import { ExtensiveEditor } from "@lyfie/luthor";
276
- import type { ExtensiveEditorRef } from "@lyfie/luthor";
277
- import { useRef, useState } from "react";
278
-
279
- function App() {
280
- const editorRef = useRef<ExtensiveEditorRef>(null);
281
- const [savedContent, setSavedContent] = useState("");
282
-
283
- const handleExport = () => {
284
- const html = editorRef.current?.getHTML();
285
- const markdown = editorRef.current?.getMarkdown();
286
- setSavedContent(html || "");
287
- console.log({ html, markdown });
288
- };
289
-
290
- const handleImport = () => {
291
- editorRef.current?.injectHTML(savedContent);
292
- // or
293
- editorRef.current?.injectMarkdown("# Hello\n\nMarkdown content");
294
- };
295
-
296
- return (
297
- <div>
298
- <ExtensiveEditor ref={editorRef} />
299
- <button onClick={handleExport}>Export</button>
300
- <button onClick={handleImport}>Import</button>
301
- </div>
302
- );
303
- }
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
+ />
304
181
  ```
305
182
 
306
- ---
307
-
308
- ## API Reference
309
-
310
- ### ExtensiveEditor Component
183
+ ### 5) Code language list replacement
311
184
 
312
185
  ```tsx
313
- import { ExtensiveEditor } from "@lyfie/luthor";
314
- 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
+ />
315
194
  ```
316
195
 
317
- **Props:**
318
- ```typescript
319
- interface ExtensiveEditorProps {
320
- placeholder?: string; // Placeholder text
321
- className?: string; // CSS class for container
322
- onReady?: (ref: ExtensiveEditorRef) => void; // Called when editor is ready
323
- }
324
- ```
196
+ Optional: use highlight.js stylesheet for richer code colors
325
197
 
326
- **Ref Methods:**
327
- ```typescript
328
- interface ExtensiveEditorRef {
329
- injectMarkdown: (content: string) => void; // Import markdown
330
- injectHTML: (content: string) => void; // Import HTML
331
- getMarkdown: () => string; // Export as markdown
332
- getHTML: () => string; // Export as HTML
333
- }
198
+ ```bash
199
+ pnpm add highlight.js
334
200
  ```
335
201
 
336
- ### Preset Registry
337
-
338
202
  ```tsx
339
- import { presetRegistry } from "@lyfie/luthor";
340
- import type { EditorPreset } from "@lyfie/luthor";
341
- ```
203
+ import { ExtensiveEditor } from "@lyfie/luthor";
204
+ import "@lyfie/luthor/styles.css";
205
+ import "highlight.js/styles/github.css"; // any highlight.js theme
342
206
 
343
- **Type:**
344
- ```typescript
345
- interface EditorPreset {
346
- id: string; // Unique preset ID
347
- label: string; // Display name
348
- description?: string; // Preset description
349
- extensions?: Extension[]; // Included extensions
350
- config?: EditorConfig; // Editor configuration
351
- theme?: LuthorTheme; // Custom theme
352
- toolbar?: string[]; // Toolbar items
353
- components?: Record<string, unknown>; // Custom components
354
- css?: string; // CSS file path
207
+ export function App() {
208
+ return <ExtensiveEditor syntaxHighlighting="auto" />;
355
209
  }
356
210
  ```
357
211
 
358
- **Available Presets:**
359
- - `presetRegistry.minimal`
360
- - `presetRegistry.classic`
361
- - `presetRegistry.blog`
362
- - `presetRegistry.cms`
363
- - `presetRegistry.docs`
364
- - `presetRegistry.chat`
365
- - `presetRegistry.email`
366
- - `presetRegistry.markdown`
367
- - `presetRegistry.code`
368
- - `presetRegistry.default`
369
- - `presetRegistry.extensive`
370
-
371
- ### Extension Sets
372
-
373
- ```tsx
374
- import { extensiveExtensions } from "@lyfie/luthor";
375
- ```
376
-
377
- Pre-configured extension arrays that can be used with luthor-headless:
212
+ What happens:
378
213
 
379
- ```typescript
380
- const extensions = extensiveExtensions as const;
381
- const { Provider } = createEditorSystem<typeof extensions>();
382
- ```
383
-
384
- ---
385
-
386
- ## Comparison: Headless vs Luthor
387
-
388
- | Feature | @lyfie/luthor-headless | @lyfie/luthor |
389
- |---------|----------------------|---------------|
390
- | **Installation** | Manual Lexical deps | Zero additional deps |
391
- | **Bundle Size** | Minimal | Includes all Lexical |
392
- | **Setup Time** | More configuration | Instant |
393
- | **Flexibility** | Maximum control | Pre-configured |
394
- | **Use Case** | Custom editors | Quick implementation |
395
- | **UI Components** | Build your own | ExtensiveEditor included |
396
- | **Presets** | None | 11 ready-to-use |
397
- | **Dependencies** | Peer deps | Bundled deps |
398
-
399
- **Choose luthor-headless when:**
400
- - Building completely custom UI
401
- - Need minimal bundle size
402
- - Want control over Lexical versions
403
- - Have specific dependency requirements
404
-
405
- **Choose @lyfie/luthor when:**
406
- - Want to start quickly
407
- - Need a working editor ASAP
408
- - Don't want to manage dependencies
409
- - Want ready-to-use components
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.
410
221
 
411
- [📖 Learn more about luthor-headless](../headless/README.md)
412
-
413
- ---
414
-
415
- ## Advanced Usage
416
-
417
- ### Creating Custom Presets
222
+ ### 6) Per-instance shortcut remap/disable
418
223
 
419
224
  ```tsx
420
- import type { EditorPreset } from "@lyfie/luthor";
421
- import {
422
- boldExtension,
423
- italicExtension,
424
- linkExtension
425
- } from "@lyfie/luthor-headless";
426
-
427
- const myCustomPreset: EditorPreset = {
428
- id: "my-custom",
429
- label: "My Custom Editor",
430
- description: "A tailored editor for my use case",
431
- extensions: [boldExtension, italicExtension, linkExtension],
432
- config: {
433
- placeholder: "Start typing...",
434
- editable: true,
435
- },
436
- toolbar: ["bold", "italic", "link"],
437
- };
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
+ />
438
236
  ```
439
237
 
440
- ### Extending Existing Presets
238
+ ### 7) Command palette shortcut-only mode
441
239
 
442
240
  ```tsx
443
- import { defaultPreset } from "@lyfie/luthor";
444
- import { myCustomExtension } from "./my-extension";
445
-
446
- const enhancedPreset: EditorPreset = {
447
- ...defaultPreset,
448
- id: "enhanced-default",
449
- extensions: [
450
- ...(defaultPreset.extensions || []),
451
- myCustomExtension,
452
- ],
453
- toolbar: [
454
- ...(defaultPreset.toolbar || []),
455
- "myCustomCommand",
456
- ],
457
- };
241
+ <ExtensiveEditor commandPaletteShortcutOnly />
458
242
  ```
459
243
 
460
- ### Accessing Luthor-Headless Features
461
-
462
- Since `@lyfie/luthor` depends on `@lyfie/luthor-headless`, you have access to all headless features:
244
+ ### 8) Style token overrides
463
245
 
464
246
  ```tsx
465
- import { createEditorSystem, RichText } from "@lyfie/luthor-headless";
466
- import { extensiveExtensions } from "@lyfie/luthor";
467
-
468
- const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
469
-
470
- // Use all luthor-headless APIs
471
- function MyEditor() {
472
- const { commands, activeStates, lexical } = useEditor();
473
-
474
- // Access Lexical editor instance
475
- lexical?.update(() => {
476
- // Direct Lexical operations
477
- });
478
-
479
- return <RichText />;
480
- }
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
+ />
481
263
  ```
482
264
 
483
- ---
484
-
485
- ## TypeScript Support
265
+ ### 9) Emoji library auto-detection (works in `apps/demo`)
486
266
 
487
- Fully typed with TypeScript. All exports include type definitions:
267
+ Install emoji-mart data in the app:
488
268
 
489
- ```typescript
490
- import type {
491
- EditorPreset,
492
- ExtensiveEditorRef,
493
- ExtensiveEditorProps,
494
- ExtensiveEditorMode,
495
- } from "@lyfie/luthor";
269
+ ```bash
270
+ pnpm add -F demo @emoji-mart/data
496
271
  ```
497
272
 
498
- ---
499
-
500
- ## Styling
501
-
502
- The `ExtensiveEditor` component includes default styles. To customize:
273
+ Then use `ExtensiveEditor` normally:
503
274
 
504
275
  ```tsx
505
276
  import { ExtensiveEditor } from "@lyfie/luthor";
277
+ import "@lyfie/luthor/styles.css";
506
278
 
507
- // Add custom class
508
- <ExtensiveEditor className="my-editor" />
509
- ```
510
-
511
- ```css
512
- /* Override default styles */
513
- .my-editor {
514
- --luthor-bg: #ffffff;
515
- --luthor-text: #000000;
516
- --luthor-border: #e5e5e5;
517
- }
518
-
519
- /* Dark mode */
520
- .my-editor.dark {
521
- --luthor-bg: #1a1a1a;
522
- --luthor-text: #ffffff;
523
- --luthor-border: #333333;
279
+ export function App() {
280
+ return <ExtensiveEditor />;
524
281
  }
525
282
  ```
526
283
 
527
- ---
284
+ What happens:
528
285
 
529
- ## Migration from Headless
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.
530
289
 
531
- If you're using luthor-headless and want to switch:
290
+ ## Notes and Nuances
532
291
 
533
- **Before:**
534
- ```bash
535
- # npm
536
- npm install @lyfie/luthor-headless
537
- npm install lexical @lexical/react @lexical/html # ... many packages
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`.
538
301
 
539
- # pnpm
540
- pnpm add @lyfie/luthor-headless
541
- pnpm add lexical @lexical/react @lexical/html # ... many packages
542
- ```
543
-
544
- **After:**
545
- ```bash
546
- # npm
547
- npm install @lyfie/luthor-headless @lyfie/luthor
548
-
549
- # pnpm
550
- pnpm add @lyfie/luthor-headless @lyfie/luthor
551
-
552
- # Remove individual @lexical/* packages if desired
553
- ```
554
-
555
- Your code doesn't need to change! All luthor-headless APIs work the same way.
556
-
557
- ---
302
+ ## Which Package Should I Use?
558
303
 
559
- ## Examples
304
+ - Use `@lyfie/luthor` for fast onboarding and polished defaults.
305
+ - Use `@lyfie/luthor-headless` for full extension/UI control.
560
306
 
561
- Check out the [demo site](https://luthor.lyfie.app/demo) for live examples of all presets.
307
+ ## Documentation
562
308
 
563
- ---
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)
564
313
 
565
- **Built with ❤️ by the Luthor Team**
314
+ ## Workspace Development
566
315
 
567
- 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
+ ```