@zuilib/text-editor 0.0.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.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @zuilib/text-editor
2
+
3
+ Markdown editor for ZUI Design System — a React component built on [Lexical](https://lexical.dev/) with markdown shortcuts, code highlighting, checklists, and raw/rich/view modes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @zuilib/text-editor @zuilib/core lexical @lexical/react @lexical/markdown @lexical/rich-text @lexical/code @lexical/list @lexical/link
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { useState } from 'react'
15
+ import { MarkdownEditor } from '@zuilib/text-editor'
16
+ import '@zuilib/text-editor/styles.css'
17
+
18
+ function Notes() {
19
+ const [value, setValue] = useState('# Hello\n\n- [ ] Try me')
20
+
21
+ return <MarkdownEditor value={value} onChange={setValue} />
22
+ }
23
+ ```
24
+
25
+ ## Modes
26
+
27
+ ```tsx
28
+ // Rich markdown editing (default) — shortcuts like `#`, `- [ ]`, ``` `
29
+ <MarkdownEditor mode="edit-md" value={value} onChange={setValue} />
30
+
31
+ // Raw markdown in a plain textarea
32
+ <MarkdownEditor mode="edit-raw" value={value} onChange={setValue} />
33
+
34
+ // Read-only rendered view
35
+ <MarkdownEditor mode="view" value={value} />
36
+ ```
37
+
38
+ ## Props
39
+
40
+ - `value`: `string` — markdown source
41
+ - `onChange`: `(value: string) => void`
42
+ - `mode`: `'edit-md' | 'edit-raw' | 'view'` (default `'edit-md'`)
43
+ - `placeholder`: `string` (default `'Start writing...'`)
44
+ - `readOnly`: `boolean`
45
+ - `autoFocus`: `boolean`
46
+ - `className`: `string`
47
+
48
+ ## Features
49
+
50
+ - Markdown shortcuts: headings, lists, checklists, code blocks, quotes, links
51
+ - Syntax-highlighted fenced code blocks
52
+ - Undo/redo history
53
+ - Cursor-stable mode switching between raw and rich editing
54
+
55
+ ## License
56
+
57
+ MIT
package/dist/index.css ADDED
@@ -0,0 +1,79 @@
1
+ /* src/styles.css */
2
+ .zui-text-editor {
3
+ position: relative;
4
+ width: 100%;
5
+ height: 100%;
6
+ overflow-y: auto;
7
+ }
8
+ .zui-text-editor-content {
9
+ min-height: 100%;
10
+ padding: 1.5rem 2rem;
11
+ outline: none;
12
+ }
13
+ .zui-text-editor-placeholder {
14
+ position: absolute;
15
+ top: 1.5rem;
16
+ left: 2rem;
17
+ pointer-events: none;
18
+ user-select: none;
19
+ }
20
+ .zui-checklist {
21
+ list-style: none;
22
+ margin-left: 0;
23
+ padding-left: 0;
24
+ }
25
+ .zui-checklist li[role=checkbox] {
26
+ position: relative;
27
+ padding-left: 1.75rem;
28
+ margin-left: 0;
29
+ cursor: pointer;
30
+ list-style: none;
31
+ }
32
+ .zui-checklist li[role=checkbox]::before {
33
+ content: "";
34
+ position: absolute;
35
+ left: 0;
36
+ top: 0.25em;
37
+ width: 1rem;
38
+ height: 1rem;
39
+ border: 2px solid currentColor;
40
+ border-radius: 0.2rem;
41
+ opacity: 0.5;
42
+ background: transparent;
43
+ }
44
+ .zui-checklist li[role=checkbox][aria-checked=true]::before {
45
+ background: currentColor;
46
+ opacity: 0.5;
47
+ }
48
+ .zui-checklist li[role=checkbox][aria-checked=true]::after {
49
+ content: "";
50
+ position: absolute;
51
+ left: 0.3rem;
52
+ top: 0.35em;
53
+ width: 0.4rem;
54
+ height: 0.7rem;
55
+ border: solid white;
56
+ border-width: 0 2px 2px 0;
57
+ transform: rotate(45deg);
58
+ }
59
+ .zui-text-editor-textarea {
60
+ width: 100%;
61
+ height: 100%;
62
+ min-height: 100%;
63
+ padding: 1.5rem 2rem;
64
+ outline: none;
65
+ resize: none;
66
+ background: transparent;
67
+ color: inherit;
68
+ font-family:
69
+ ui-monospace,
70
+ SFMono-Regular,
71
+ "SF Mono",
72
+ Menlo,
73
+ Consolas,
74
+ "Liberation Mono",
75
+ monospace;
76
+ font-size: 0.875rem;
77
+ line-height: 1.6;
78
+ border: none;
79
+ }
@@ -0,0 +1,14 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type Props = Readonly<{
4
+ value?: string;
5
+ onChange?: (value: string) => void;
6
+ placeholder?: string;
7
+ readOnly?: boolean;
8
+ className?: string;
9
+ mode?: 'edit-raw' | 'edit-md' | 'view';
10
+ autoFocus?: boolean;
11
+ }>;
12
+ declare function MarkdownEditor({ value, onChange, placeholder, readOnly, className, mode, autoFocus, }: Props): react_jsx_runtime.JSX.Element;
13
+
14
+ export { MarkdownEditor, type Props as MarkdownEditorProps };
package/dist/index.js ADDED
@@ -0,0 +1,391 @@
1
+ // src/MarkdownEditor.tsx
2
+ import { useCallback, useEffect as useEffect5, useMemo, useRef as useRef2, useState } from "react";
3
+ import { LexicalComposer } from "@lexical/react/LexicalComposer";
4
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
5
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
6
+ import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
7
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
8
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
9
+ import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
10
+ import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
11
+ import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
12
+ import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
13
+ import { CHECK_LIST, CODE, TRANSFORMERS } from "@lexical/markdown";
14
+ import { useLexicalComposerContext as useLexicalComposerContext5 } from "@lexical/react/LexicalComposerContext";
15
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
16
+ import { ListItemNode, ListNode as ListNode2 } from "@lexical/list";
17
+ import { CodeHighlightNode, CodeNode } from "@lexical/code";
18
+ import { AutoLinkNode, LinkNode } from "@lexical/link";
19
+
20
+ // src/plugins/ChecklistShortcutPlugin.tsx
21
+ import { useEffect } from "react";
22
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
23
+ import {
24
+ $isListItemNode,
25
+ $isListNode,
26
+ ListNode
27
+ } from "@lexical/list";
28
+ import { $getNodeByKey, TextNode } from "lexical";
29
+ var CHECK_PATTERN = /^\[(\s|x)?\]\s/i;
30
+ function ChecklistShortcutPlugin() {
31
+ const [editor] = useLexicalComposerContext();
32
+ useEffect(() => {
33
+ const unregisterMutation = editor.registerMutationListener(
34
+ ListNode,
35
+ (mutations) => {
36
+ editor.getEditorState().read(() => {
37
+ for (const [key, type] of mutations) {
38
+ if (type === "destroyed") continue;
39
+ const dom = editor.getElementByKey(key);
40
+ const node = $getNodeByKey(key);
41
+ if (dom && $isListNode(node)) {
42
+ ;
43
+ dom.__lexicalListType = node.getListType();
44
+ }
45
+ }
46
+ });
47
+ }
48
+ );
49
+ const unregisterTransform = editor.registerNodeTransform(
50
+ TextNode,
51
+ (textNode) => {
52
+ const listItem = textNode.getParent();
53
+ if (!$isListItemNode(listItem)) return;
54
+ if (listItem.getFirstChild()?.getKey() !== textNode.getKey()) return;
55
+ const listNode = listItem.getParent();
56
+ if (!$isListNode(listNode) || listNode.getListType() === "check")
57
+ return;
58
+ const text = textNode.getTextContent();
59
+ const match = text.match(CHECK_PATTERN);
60
+ if (!match) return;
61
+ const checked = match[1] === "x" || match[1] === "X";
62
+ textNode.setTextContent(text.slice(match[0].length));
63
+ listNode.setListType("check");
64
+ listItem.setChecked(checked);
65
+ textNode.select(0, 0);
66
+ }
67
+ );
68
+ return () => {
69
+ unregisterMutation();
70
+ unregisterTransform();
71
+ };
72
+ }, [editor]);
73
+ return null;
74
+ }
75
+
76
+ // src/plugins/CodeBlockShortcutPlugin.tsx
77
+ import { useEffect as useEffect2 } from "react";
78
+ import { useLexicalComposerContext as useLexicalComposerContext2 } from "@lexical/react/LexicalComposerContext";
79
+ import {
80
+ $createParagraphNode,
81
+ $getSelection,
82
+ $isParagraphNode,
83
+ $isRangeSelection,
84
+ $isRootOrShadowRoot,
85
+ COMMAND_PRIORITY_LOW,
86
+ KEY_ARROW_DOWN_COMMAND,
87
+ TextNode as TextNode2
88
+ } from "lexical";
89
+ import { $createCodeNode, $isCodeNode } from "@lexical/code";
90
+ var CODE_BLOCK_PATTERN = /^```(\w[\w-]*)?$/;
91
+ function CodeBlockShortcutPlugin() {
92
+ const [editor] = useLexicalComposerContext2();
93
+ useEffect2(() => {
94
+ const unregisterTransform = editor.registerNodeTransform(
95
+ TextNode2,
96
+ (textNode) => {
97
+ const parent = textNode.getParent();
98
+ if (!$isParagraphNode(parent)) return;
99
+ if (parent.getFirstChild()?.getKey() !== textNode.getKey()) return;
100
+ const grandParent = parent.getParent();
101
+ if (!grandParent || !$isRootOrShadowRoot(grandParent)) return;
102
+ const text = textNode.getTextContent().trimEnd();
103
+ const match = text.match(CODE_BLOCK_PATTERN);
104
+ if (!match) return;
105
+ const language = match[1] || void 0;
106
+ const codeNode = $createCodeNode(language);
107
+ parent.replace(codeNode);
108
+ codeNode.selectEnd();
109
+ }
110
+ );
111
+ const unregisterCommand = editor.registerCommand(
112
+ KEY_ARROW_DOWN_COMMAND,
113
+ () => {
114
+ const selection = $getSelection();
115
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
116
+ const anchor = selection.anchor;
117
+ const anchorNode = anchor.getNode();
118
+ let topNode = anchorNode;
119
+ while (topNode.getParent() && !$isRootOrShadowRoot(topNode.getParent())) {
120
+ topNode = topNode.getParent();
121
+ }
122
+ if (!$isCodeNode(topNode)) return false;
123
+ const root = topNode.getParent();
124
+ if (!root || root.getLastChild()?.getKey() !== topNode.getKey()) return false;
125
+ const codeText = topNode.getTextContent();
126
+ const lastNewline = codeText.lastIndexOf("\n");
127
+ const isOnLastLine = anchor.offset >= lastNewline + 1;
128
+ if (!isOnLastLine) return false;
129
+ const paragraph = $createParagraphNode();
130
+ topNode.insertAfter(paragraph);
131
+ paragraph.selectStart();
132
+ return true;
133
+ },
134
+ COMMAND_PRIORITY_LOW
135
+ );
136
+ return () => {
137
+ unregisterTransform();
138
+ unregisterCommand();
139
+ };
140
+ }, [editor]);
141
+ return null;
142
+ }
143
+
144
+ // src/plugins/CodeHighlightPlugin.tsx
145
+ import { useEffect as useEffect3 } from "react";
146
+ import { useLexicalComposerContext as useLexicalComposerContext3 } from "@lexical/react/LexicalComposerContext";
147
+ import { registerCodeHighlighting } from "@lexical/code";
148
+ function CodeHighlightPlugin() {
149
+ const [editor] = useLexicalComposerContext3();
150
+ useEffect3(() => {
151
+ return registerCodeHighlighting(editor);
152
+ }, [editor]);
153
+ return null;
154
+ }
155
+
156
+ // src/plugins/MarkdownSyncPlugin.tsx
157
+ import { useEffect as useEffect4, useRef } from "react";
158
+ import { useLexicalComposerContext as useLexicalComposerContext4 } from "@lexical/react/LexicalComposerContext";
159
+ import {
160
+ $convertFromMarkdownString,
161
+ $convertToMarkdownString
162
+ } from "@lexical/markdown";
163
+ function MarkdownSyncPlugin({
164
+ initialMarkdown = "",
165
+ onChange,
166
+ transformers
167
+ }) {
168
+ const [editor] = useLexicalComposerContext4();
169
+ const lastMarkdownRef = useRef("");
170
+ const onChangeRef = useRef(onChange);
171
+ onChangeRef.current = onChange;
172
+ useEffect4(() => {
173
+ if (initialMarkdown && initialMarkdown !== lastMarkdownRef.current) {
174
+ lastMarkdownRef.current = initialMarkdown;
175
+ editor.update(() => {
176
+ $convertFromMarkdownString(initialMarkdown, transformers);
177
+ }, { tag: "initial-load" });
178
+ }
179
+ }, [editor, initialMarkdown]);
180
+ useEffect4(() => {
181
+ return editor.registerUpdateListener(
182
+ ({ editorState, dirtyElements, dirtyLeaves, tags }) => {
183
+ if (dirtyElements.size === 0 && dirtyLeaves.size === 0) return;
184
+ if (tags.has("initial-load")) return;
185
+ editorState.read(() => {
186
+ const markdown = $convertToMarkdownString(transformers);
187
+ onChangeRef.current?.(markdown);
188
+ });
189
+ }
190
+ );
191
+ }, [editor]);
192
+ return null;
193
+ }
194
+
195
+ // src/theme.ts
196
+ var editorTheme = {
197
+ ltr: "text-left",
198
+ rtl: "text-right",
199
+ placeholder: "text-muted-foreground opacity-50",
200
+ paragraph: "mb-2",
201
+ heading: {
202
+ h1: "text-4xl font-bold mb-4",
203
+ h2: "text-3xl font-bold mb-3",
204
+ h3: "text-2xl font-bold mb-2",
205
+ h4: "text-xl font-bold mb-2",
206
+ h5: "text-lg font-bold mb-1",
207
+ h6: "text-base font-bold mb-1"
208
+ },
209
+ list: {
210
+ listitem: "ml-8",
211
+ listitemChecked: "line-through opacity-50",
212
+ listitemUnchecked: "",
213
+ nested: {
214
+ listitem: "ml-8"
215
+ },
216
+ checklist: "zui-checklist",
217
+ ol: "list-decimal ml-4",
218
+ ul: "list-disc ml-4"
219
+ },
220
+ link: "text-primary underline hover:opacity-80 cursor-pointer",
221
+ text: {
222
+ bold: "font-bold",
223
+ code: "bg-muted px-1.5 py-0.5 rounded font-mono text-sm",
224
+ italic: "italic",
225
+ strikethrough: "line-through",
226
+ subscript: "text-xs align-sub",
227
+ superscript: "text-xs align-super",
228
+ underline: "underline",
229
+ underlineStrikethrough: "underline line-through"
230
+ },
231
+ code: "bg-muted rounded-md p-4 font-mono text-sm block my-2 overflow-x-auto",
232
+ codeHighlight: {
233
+ atrule: "text-blue-500",
234
+ attr: "text-yellow-500",
235
+ boolean: "text-purple-500",
236
+ builtin: "text-cyan-500",
237
+ cdata: "text-gray-500",
238
+ char: "text-green-500",
239
+ class: "text-yellow-500",
240
+ "class-name": "text-yellow-500",
241
+ comment: "text-gray-500",
242
+ constant: "text-purple-500",
243
+ deleted: "text-red-500",
244
+ doctype: "text-gray-500",
245
+ entity: "text-red-500",
246
+ function: "text-blue-500",
247
+ important: "text-red-500",
248
+ inserted: "text-green-500",
249
+ keyword: "text-purple-500",
250
+ namespace: "text-purple-500",
251
+ number: "text-green-500",
252
+ operator: "text-gray-500",
253
+ prolog: "text-gray-500",
254
+ property: "text-blue-500",
255
+ punctuation: "text-gray-500",
256
+ regex: "text-red-500",
257
+ selector: "text-blue-500",
258
+ string: "text-green-500",
259
+ symbol: "text-green-500",
260
+ tag: "text-red-500",
261
+ url: "text-blue-500",
262
+ variable: "text-blue-500"
263
+ },
264
+ quote: "border-l-4 border-border pl-4 italic"
265
+ };
266
+
267
+ // src/MarkdownEditor.tsx
268
+ import { jsx, jsxs } from "react/jsx-runtime";
269
+ var SYNC_TRANSFORMERS = [CHECK_LIST, ...TRANSFORMERS];
270
+ var SHORTCUT_TRANSFORMERS = TRANSFORMERS.filter((t) => t !== CODE);
271
+ function onError(error) {
272
+ console.error(error);
273
+ }
274
+ var editorNodes = [
275
+ HeadingNode,
276
+ ListNode2,
277
+ ListItemNode,
278
+ QuoteNode,
279
+ CodeNode,
280
+ CodeHighlightNode,
281
+ AutoLinkNode,
282
+ LinkNode
283
+ ];
284
+ function ReadOnlyPlugin({ readOnly }) {
285
+ const [editor] = useLexicalComposerContext5();
286
+ useEffect5(() => {
287
+ editor.setEditable(!readOnly);
288
+ }, [editor, readOnly]);
289
+ return null;
290
+ }
291
+ function MarkdownEditor({
292
+ value,
293
+ onChange,
294
+ placeholder = "Start writing...",
295
+ readOnly = false,
296
+ className,
297
+ mode = "edit-md",
298
+ autoFocus = false
299
+ }) {
300
+ const latestValueRef = useRef2(value ?? "");
301
+ const [mountKey, setMountKey] = useState(0);
302
+ const [capturedMarkdown, setCapturedMarkdown] = useState(value ?? "");
303
+ useEffect5(() => {
304
+ if (value !== void 0) {
305
+ latestValueRef.current = value;
306
+ }
307
+ }, [value]);
308
+ const handleTextChange = useCallback(
309
+ (e) => {
310
+ const newValue = e.target.value;
311
+ latestValueRef.current = newValue;
312
+ onChange?.(newValue);
313
+ },
314
+ [onChange]
315
+ );
316
+ const handleLexicalChange = useCallback(
317
+ (newValue) => {
318
+ latestValueRef.current = newValue;
319
+ onChange?.(newValue);
320
+ },
321
+ [onChange]
322
+ );
323
+ const prevModeRef = useRef2(mode);
324
+ useEffect5(() => {
325
+ if (prevModeRef.current === "edit-raw" && mode !== "edit-raw") {
326
+ setCapturedMarkdown(latestValueRef.current);
327
+ setMountKey((k) => k + 1);
328
+ }
329
+ prevModeRef.current = mode;
330
+ }, [mode]);
331
+ const initialConfig = useMemo(
332
+ () => ({
333
+ namespace: "ZuiTextEditor",
334
+ theme: editorTheme,
335
+ onError,
336
+ nodes: editorNodes
337
+ }),
338
+ []
339
+ );
340
+ if (mode === "edit-raw") {
341
+ return /* @__PURE__ */ jsx("div", { className: `zui-text-editor ${className ?? ""}`, children: /* @__PURE__ */ jsx(
342
+ "textarea",
343
+ {
344
+ className: "zui-text-editor-textarea",
345
+ value: value ?? "",
346
+ onChange: handleTextChange,
347
+ placeholder,
348
+ readOnly,
349
+ autoFocus,
350
+ spellCheck: false
351
+ }
352
+ ) });
353
+ }
354
+ return /* @__PURE__ */ jsx(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsxs("div", { className: `zui-text-editor ${className ?? ""}`, children: [
355
+ /* @__PURE__ */ jsx(
356
+ RichTextPlugin,
357
+ {
358
+ contentEditable: /* @__PURE__ */ jsx(
359
+ ContentEditable,
360
+ {
361
+ className: "zui-text-editor-content",
362
+ "aria-placeholder": placeholder,
363
+ placeholder: /* @__PURE__ */ jsx("div", { className: "zui-text-editor-placeholder", children: placeholder })
364
+ }
365
+ ),
366
+ ErrorBoundary: LexicalErrorBoundary
367
+ }
368
+ ),
369
+ /* @__PURE__ */ jsx(
370
+ MarkdownSyncPlugin,
371
+ {
372
+ initialMarkdown: mode === "view" ? value ?? "" : capturedMarkdown,
373
+ onChange: handleLexicalChange,
374
+ transformers: SYNC_TRANSFORMERS
375
+ }
376
+ ),
377
+ /* @__PURE__ */ jsx(HistoryPlugin, {}),
378
+ /* @__PURE__ */ jsx(ListPlugin, {}),
379
+ /* @__PURE__ */ jsx(CheckListPlugin, {}),
380
+ /* @__PURE__ */ jsx(ChecklistShortcutPlugin, {}),
381
+ /* @__PURE__ */ jsx(CodeBlockShortcutPlugin, {}),
382
+ /* @__PURE__ */ jsx(CodeHighlightPlugin, {}),
383
+ /* @__PURE__ */ jsx(LinkPlugin, {}),
384
+ /* @__PURE__ */ jsx(MarkdownShortcutPlugin, { transformers: SHORTCUT_TRANSFORMERS }),
385
+ /* @__PURE__ */ jsx(ReadOnlyPlugin, { readOnly: readOnly || mode === "view" }),
386
+ autoFocus && /* @__PURE__ */ jsx(AutoFocusPlugin, {})
387
+ ] }) }, mountKey);
388
+ }
389
+ export {
390
+ MarkdownEditor
391
+ };
@@ -0,0 +1,81 @@
1
+ .zui-text-editor {
2
+ position: relative;
3
+ width: 100%;
4
+ height: 100%;
5
+ overflow-y: auto;
6
+ }
7
+
8
+ .zui-text-editor-content {
9
+ min-height: 100%;
10
+ padding: 1.5rem 2rem;
11
+ outline: none;
12
+ }
13
+
14
+ .zui-text-editor-placeholder {
15
+ position: absolute;
16
+ top: 1.5rem;
17
+ left: 2rem;
18
+ pointer-events: none;
19
+ user-select: none;
20
+ }
21
+
22
+ /* Checklist styles */
23
+ .zui-checklist {
24
+ list-style: none;
25
+ margin-left: 0;
26
+ padding-left: 0;
27
+ }
28
+
29
+ .zui-checklist li[role='checkbox'] {
30
+ position: relative;
31
+ padding-left: 1.75rem;
32
+ margin-left: 0;
33
+ cursor: pointer;
34
+ list-style: none;
35
+ }
36
+
37
+ .zui-checklist li[role='checkbox']::before {
38
+ content: '';
39
+ position: absolute;
40
+ left: 0;
41
+ top: 0.25em;
42
+ width: 1rem;
43
+ height: 1rem;
44
+ border: 2px solid currentColor;
45
+ border-radius: 0.2rem;
46
+ opacity: 0.5;
47
+ background: transparent;
48
+ }
49
+
50
+ .zui-checklist li[role='checkbox'][aria-checked='true']::before {
51
+ background: currentColor;
52
+ opacity: 0.5;
53
+ }
54
+
55
+ .zui-checklist li[role='checkbox'][aria-checked='true']::after {
56
+ content: '';
57
+ position: absolute;
58
+ left: 0.3rem;
59
+ top: 0.35em;
60
+ width: 0.4rem;
61
+ height: 0.7rem;
62
+ border: solid white;
63
+ border-width: 0 2px 2px 0;
64
+ transform: rotate(45deg);
65
+ }
66
+
67
+ .zui-text-editor-textarea {
68
+ width: 100%;
69
+ height: 100%;
70
+ min-height: 100%;
71
+ padding: 1.5rem 2rem;
72
+ outline: none;
73
+ resize: none;
74
+ background: transparent;
75
+ color: inherit;
76
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
77
+ 'Liberation Mono', monospace;
78
+ font-size: 0.875rem;
79
+ line-height: 1.6;
80
+ border: none;
81
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@zuilib/text-editor",
3
+ "version": "0.0.0",
4
+ "description": "ZUI — A markdown editor component wrapping Lexical",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": ["**/*.css"],
9
+ "files": ["dist"],
10
+ "scripts": {
11
+ "build": "tsup"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./styles.css": "./dist/styles.css",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^18.0.0 || ^19.0.0",
23
+ "react-dom": "^18.0.0 || ^19.0.0",
24
+ "lexical": "^0.35.0",
25
+ "@lexical/react": "^0.35.0",
26
+ "@lexical/markdown": "^0.35.0",
27
+ "@lexical/rich-text": "^0.35.0",
28
+ "@lexical/code": "^0.35.0",
29
+ "@lexical/list": "^0.35.0",
30
+ "@lexical/link": "^0.35.0"
31
+ },
32
+ "dependencies": {
33
+ "@zuilib/core": "^0.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^18.0.0 || ^19.0.0",
37
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
38
+ "react": "^18.0.0 || ^19.0.0",
39
+ "react-dom": "^18.0.0 || ^19.0.0",
40
+ "lexical": "^0.35.0",
41
+ "@lexical/react": "^0.35.0",
42
+ "@lexical/markdown": "^0.35.0",
43
+ "@lexical/rich-text": "^0.35.0",
44
+ "@lexical/code": "^0.35.0",
45
+ "@lexical/list": "^0.35.0",
46
+ "@lexical/link": "^0.35.0"
47
+ },
48
+ "keywords": [
49
+ "lexical",
50
+ "markdown",
51
+ "editor",
52
+ "zui",
53
+ "design-system",
54
+ "react",
55
+ "typescript"
56
+ ]
57
+ }