@inshapardaz/likhari-react 0.1.0-dev.7

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.
@@ -0,0 +1,38 @@
1
+ import type { MantineThemeOverride } from '@mantine/core';
2
+ import type { SerializedEditorState } from 'lexical';
3
+ import { type FormatId } from '@inshapardaz/likhari-converters';
4
+ import { type EditorFeatureConfig, type FeatureConfigPresetName } from '@inshapardaz/likhari-core';
5
+ export interface EditorInitialContent {
6
+ format: Extract<FormatId, 'lexical-json' | 'plain-text'>;
7
+ value: string;
8
+ }
9
+ export interface EditorRootProps {
10
+ documentId?: string;
11
+ initialContent?: EditorInitialContent;
12
+ featureConfig?: EditorFeatureConfig;
13
+ featurePreset?: FeatureConfigPresetName;
14
+ theme?: MantineThemeOverride;
15
+ colorScheme?: 'light' | 'dark';
16
+ locale?: 'en' | 'ur' | 'pa-shahmukhi';
17
+ placeholder?: string;
18
+ /**
19
+ * CSS height for the editor box (toolbar + canvas together) — accepts any
20
+ * CSS length, e.g. '100%' to fill a sized parent, '60vh', or a number of
21
+ * pixels. The canvas scrolls internally once its content exceeds this,
22
+ * rather than the editor (and the page around it) growing without bound.
23
+ * Defaults to a fixed height so the editor has a sane size out of the box
24
+ * even when the host hasn't given its container an explicit height.
25
+ */
26
+ height?: string | number;
27
+ onChange?: (state: SerializedEditorState) => void;
28
+ onSave?: (content: string, format: FormatId) => void;
29
+ }
30
+ export interface EditorRef {
31
+ getContent(format: FormatId): string;
32
+ setContent(value: string, format: FormatId): void;
33
+ hasUnsavedChanges(): boolean;
34
+ confirmDiscard(): Promise<boolean>;
35
+ focus(): void;
36
+ }
37
+ export declare const EditorRoot: import("react").ForwardRefExoticComponent<EditorRootProps & import("react").RefAttributes<EditorRef>>;
38
+ //# sourceMappingURL=EditorRoot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAS1D,OAAO,KAAK,EAAe,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAMzH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;IACzD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,cAAc,CAAC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IAClD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAQD,eAAO,MAAM,UAAU,uGAoHrB,CAAC"}
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useImperativeHandle, useMemo, useRef, 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 { ListPlugin } from '@lexical/react/LexicalListPlugin';
8
+ import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
9
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
10
+ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
11
+ import { defaultFormatRegistry } from '@inshapardaz/likhari-converters';
12
+ import { resolveFeatureConfig } from '@inshapardaz/likhari-core';
13
+ import { EDITOR_NODES } from './nodes.js';
14
+ import { editorTheme } from './theme/editorTheme.js';
15
+ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
16
+ import { Toolbar } from './components/Toolbar.js';
17
+ function initialEditorStateJson(initialContent) {
18
+ if (!initialContent)
19
+ return undefined;
20
+ const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
21
+ return JSON.stringify(state);
22
+ }
23
+ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, locale = 'en', placeholder = 'Start writing…', height = '480px', onChange, onSave, }, ref) {
24
+ const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
25
+ const editorStateRef = useRef(null);
26
+ const lastSavedJsonRef = useRef(null);
27
+ const [isDirty, setIsDirty] = useState(false);
28
+ const initialConfig = useMemo(() => ({
29
+ namespace: `likhari-editor-${documentId ?? 'anonymous'}`,
30
+ nodes: EDITOR_NODES,
31
+ theme: editorTheme,
32
+ editorState: initialEditorStateJson(initialContent),
33
+ onError(error) {
34
+ throw error;
35
+ },
36
+ }),
37
+ // eslint-disable-next-line react-hooks/exhaustive-deps
38
+ []);
39
+ useImperativeHandle(ref, () => ({
40
+ getContent(format) {
41
+ if (!editorStateRef.current)
42
+ return '';
43
+ const json = editorStateRef.current.toJSON();
44
+ return defaultFormatRegistry.serialize(format, json);
45
+ },
46
+ setContent(value, format) {
47
+ // Applied on next render via editorState prop is not supported for
48
+ // an already-mounted LexicalComposer; hosts needing this before
49
+ // Phase 2's controlled-mode support should remount with a new
50
+ // `initialContent`/`documentId` in the meantime.
51
+ void value;
52
+ void format;
53
+ throw new Error('@inshapardaz/likhari-react: EditorRef.setContent is not implemented yet — controlled mode is Phase 2 work (docs/editor-architecture-design.md §5).');
54
+ },
55
+ hasUnsavedChanges() {
56
+ return isDirty;
57
+ },
58
+ async confirmDiscard() {
59
+ if (!isDirty)
60
+ return true;
61
+ if (typeof window === 'undefined')
62
+ return true;
63
+ return window.confirm('You have unsaved changes. Discard them?');
64
+ },
65
+ focus() {
66
+ rootElementRef.current?.focus();
67
+ },
68
+ }), [isDirty]);
69
+ const rootElementRef = useRef(null);
70
+ const handleChange = (state) => {
71
+ editorStateRef.current = state;
72
+ const json = JSON.stringify(state.toJSON());
73
+ setIsDirty(json !== lastSavedJsonRef.current);
74
+ onChange?.(state.toJSON());
75
+ };
76
+ const handleSave = () => {
77
+ if (!editorStateRef.current)
78
+ return;
79
+ const json = editorStateRef.current.toJSON();
80
+ const format = config.formats.plainText ? 'plain-text' : 'lexical-json';
81
+ const content = defaultFormatRegistry.serialize(format, json);
82
+ lastSavedJsonRef.current = JSON.stringify(json);
83
+ setIsDirty(false);
84
+ onSave?.(content, format);
85
+ };
86
+ const dir = locale === 'en' ? 'ltr' : 'rtl';
87
+ return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, children: _jsx("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: onSave ? handleSave : undefined, isDirty: isDirty, showSave: Boolean(onSave) }), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": "Editor content" }), placeholder: _jsx("div", { className: "likhari-placeholder", children: placeholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.links && _jsx(LinkPlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }));
88
+ });
@@ -0,0 +1,9 @@
1
+ import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
2
+ export interface ToolbarProps {
3
+ config: ResolvedEditorFeatureConfig;
4
+ onSave?: () => void;
5
+ isDirty?: boolean;
6
+ showSave?: boolean;
7
+ }
8
+ export declare function Toolbar({ config, onSave, isDirty, showSave }: ToolbarProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=Toolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAsH7E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,YAAY,+BAgY1E"}
@@ -0,0 +1,204 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { $getSelection, $isRangeSelection, $isElementNode, FORMAT_TEXT_COMMAND, FORMAT_ELEMENT_COMMAND, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, UNDO_COMMAND, REDO_COMMAND, SELECTION_CHANGE_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, COMMAND_PRIORITY_LOW, } from 'lexical';
5
+ import { $setBlocksType } from '@lexical/selection';
6
+ import { $createParagraphNode } from 'lexical';
7
+ import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
8
+ import { $isListNode, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, } from '@lexical/list';
9
+ import { $findMatchingParent } from '@lexical/utils';
10
+ import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconFeather, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconPhoto, IconPilcrow, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
11
+ const INITIAL_STATE = {
12
+ blockType: 'paragraph',
13
+ activeFormats: new Set(),
14
+ elementFormat: 'start',
15
+ listType: null,
16
+ canUndo: false,
17
+ canRedo: false,
18
+ };
19
+ const ICON_SIZE = 17;
20
+ const ICON_STROKE = 1.75;
21
+ const ALIGN_ICONS = {
22
+ start: IconAlignLeft,
23
+ left: IconAlignLeft,
24
+ center: IconAlignCenter,
25
+ end: IconAlignRight,
26
+ right: IconAlignRight,
27
+ justify: IconAlignJustified,
28
+ };
29
+ function ToolbarButton({ icon: Icon, title, active, disabled, onClick, }) {
30
+ return (_jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": active ? 'true' : 'false', disabled: disabled, "aria-pressed": active, "aria-label": title, title: title, onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }) }));
31
+ }
32
+ /** A disabled placeholder for a feature whose config flag is on but that
33
+ * isn't implemented yet (link/image/poetry/font/language-tooling — see
34
+ * docs/lexical-editor-spec.md §13's phasing). Renders so the toolbar's
35
+ * layout is final now and only needs its onClick wired up later. */
36
+ function StubButton({ icon, title }) {
37
+ return _jsx(ToolbarButton, { icon: icon, title: `${title} (coming soon)`, disabled: true, onClick: undefined });
38
+ }
39
+ /** Icon shown to the left of a <select> — native selects can't render icons
40
+ * per-option, so this labels the control itself instead. */
41
+ function SelectIcon({ icon: Icon }) {
42
+ return (_jsx("span", { className: "likhari-toolbar-select-icon", "aria-hidden": "true", children: _jsx(Icon, { size: 15, stroke: ICON_STROKE }) }));
43
+ }
44
+ export function Toolbar({ config, onSave, isDirty, showSave }) {
45
+ const [editor] = useLexicalComposerContext();
46
+ const [state, setState] = useState(INITIAL_STATE);
47
+ const [overflowOpen, setOverflowOpen] = useState(false);
48
+ const overflowRef = useRef(null);
49
+ const updateToolbar = useCallback(() => {
50
+ editor.getEditorState().read(() => {
51
+ const selection = $getSelection();
52
+ if (!$isRangeSelection(selection))
53
+ return;
54
+ const anchorNode = selection.anchor.getNode();
55
+ const element = anchorNode.getKey() === 'root' ? anchorNode : (anchorNode.getTopLevelElement() ?? anchorNode);
56
+ const listParent = $findMatchingParent(anchorNode, $isListNode);
57
+ const listType = listParent && $isListNode(listParent) ? listParent.getListType() : null;
58
+ let blockType = 'paragraph';
59
+ if ($isHeadingNode(element)) {
60
+ blockType = element.getTag();
61
+ }
62
+ else if ($isQuoteNode(element)) {
63
+ blockType = 'quote';
64
+ }
65
+ const activeFormats = new Set();
66
+ ['bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript'].forEach((format) => {
67
+ if (selection.hasFormat(format))
68
+ activeFormats.add(format);
69
+ });
70
+ // Computed eagerly, inside the active read() callback — React calls the
71
+ // setState updater lazily during its own reconciliation, by which point
72
+ // Lexical's read/update context has already closed, so any $-prefixed
73
+ // node method (getFormatType() included) must not be deferred into it.
74
+ const elementFormat = ($isElementNode(element) ? element.getFormatType() : 'start') || 'start';
75
+ setState((s) => ({
76
+ ...s,
77
+ blockType,
78
+ activeFormats,
79
+ elementFormat,
80
+ listType,
81
+ }));
82
+ });
83
+ }, [editor]);
84
+ useEffect(() => {
85
+ return editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
86
+ updateToolbar();
87
+ return false;
88
+ }, COMMAND_PRIORITY_LOW);
89
+ }, [editor, updateToolbar]);
90
+ useEffect(() => {
91
+ return editor.registerCommand(CAN_UNDO_COMMAND, (payload) => {
92
+ setState((s) => ({ ...s, canUndo: payload }));
93
+ return false;
94
+ }, COMMAND_PRIORITY_LOW);
95
+ }, [editor]);
96
+ useEffect(() => {
97
+ return editor.registerCommand(CAN_REDO_COMMAND, (payload) => {
98
+ setState((s) => ({ ...s, canRedo: payload }));
99
+ return false;
100
+ }, COMMAND_PRIORITY_LOW);
101
+ }, [editor]);
102
+ // Close the overflow panel on outside click or Escape.
103
+ useEffect(() => {
104
+ if (!overflowOpen)
105
+ return;
106
+ const handlePointerDown = (e) => {
107
+ if (overflowRef.current && !overflowRef.current.contains(e.target))
108
+ setOverflowOpen(false);
109
+ };
110
+ const handleKeyDown = (e) => {
111
+ if (e.key === 'Escape')
112
+ setOverflowOpen(false);
113
+ };
114
+ document.addEventListener('mousedown', handlePointerDown);
115
+ document.addEventListener('keydown', handleKeyDown);
116
+ return () => {
117
+ document.removeEventListener('mousedown', handlePointerDown);
118
+ document.removeEventListener('keydown', handleKeyDown);
119
+ };
120
+ }, [overflowOpen]);
121
+ const setBlockType = (type) => {
122
+ editor.update(() => {
123
+ const selection = $getSelection();
124
+ if (!$isRangeSelection(selection))
125
+ return;
126
+ if (type === 'paragraph') {
127
+ $setBlocksType(selection, () => $createParagraphNode());
128
+ }
129
+ else if (type === 'quote') {
130
+ $setBlocksType(selection, () => $createQuoteNode());
131
+ }
132
+ else {
133
+ $setBlocksType(selection, () => $createHeadingNode(type));
134
+ }
135
+ });
136
+ };
137
+ const insertList = (type) => {
138
+ const command = type === 'bullet' ? INSERT_UNORDERED_LIST_COMMAND : type === 'number' ? INSERT_ORDERED_LIST_COMMAND : INSERT_CHECK_LIST_COMMAND;
139
+ editor.dispatchCommand(command, undefined);
140
+ };
141
+ /** Handler for the single "Formatting" dropdown — unifies block type and
142
+ * list type into one control (Heading 1-6 / Numbered / Bullet / Task /
143
+ * Quote / Paragraph), matching the requested compact toolbar layout. */
144
+ const applyFormatting = (value) => {
145
+ if (value === 'bullet' || value === 'number' || value === 'check') {
146
+ insertList(value);
147
+ return;
148
+ }
149
+ if (state.listType) {
150
+ editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
151
+ }
152
+ setBlockType(value);
153
+ };
154
+ const formatText = (format) => editor.dispatchCommand(FORMAT_TEXT_COMMAND, format);
155
+ const formatElement = (format) => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, format);
156
+ const applyCaseTransform = (transform) => {
157
+ editor.update(() => {
158
+ const selection = $getSelection();
159
+ if (!$isRangeSelection(selection))
160
+ return;
161
+ const nodes = selection.getNodes();
162
+ for (const node of nodes) {
163
+ if (node.getType() !== 'text')
164
+ continue;
165
+ const textNode = node;
166
+ const text = textNode.getTextContent();
167
+ // Arabic-script (Urdu/Punjabi) has no case concept — skip RTL runs (spec §4.1).
168
+ if (/[؀-ۿ]/.test(text))
169
+ continue;
170
+ const next = transform === 'upper'
171
+ ? text.toUpperCase()
172
+ : transform === 'lower'
173
+ ? text.toLowerCase()
174
+ : text.replace(/\b\w/g, (c) => c.toUpperCase());
175
+ textNode.setTextContent(next);
176
+ }
177
+ });
178
+ };
179
+ const clearFormatting = () => {
180
+ editor.update(() => {
181
+ const selection = $getSelection();
182
+ if (!$isRangeSelection(selection))
183
+ return;
184
+ selection.getNodes().forEach((node) => {
185
+ if (node.getType() !== 'text')
186
+ return;
187
+ const textNode = node;
188
+ textNode.setFormat(0);
189
+ textNode.setStyle('');
190
+ });
191
+ });
192
+ };
193
+ const headingLevels = config.blocks.headingLevels ?? [];
194
+ const fmt = config.formatting;
195
+ const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
196
+ const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
197
+ const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
198
+ const showInsertPoetryGroup = config.links || config.images.linked || config.images.embedded || config.poetry.enabled;
199
+ const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
200
+ const showOverflowMenu = fmt.strikethrough || fmt.superscript || fmt.subscript || fmt.caseTransforms || fmt.clearFormatting || config.indent;
201
+ const formattingValue = state.listType ?? state.blockType;
202
+ const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
203
+ return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": "Formatting", children: [showSave && (_jsxs("button", { type: "button", className: "likhari-save-button", "data-dirty": isDirty ? 'true' : 'false', onClick: onSave, children: [_jsx(IconDeviceFloppy, { size: 16, stroke: ICON_STROKE }), "Save"] })), config.history && (_jsxs("div", { className: "likhari-toolbar-group", children: [_jsx(ToolbarButton, { icon: IconArrowBackUp, title: "Undo", disabled: !state.canUndo, onClick: () => editor.dispatchCommand(UNDO_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconArrowForwardUp, title: "Redo", disabled: !state.canRedo, onClick: () => editor.dispatchCommand(REDO_COMMAND, undefined) })] })), showFormattingGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-select-group", children: [_jsx(SelectIcon, { icon: IconPilcrow }), _jsxs("select", { className: "likhari-toolbar-select", "aria-label": "Formatting", value: formattingValue, onChange: (e) => applyFormatting(e.target.value), children: [_jsx("option", { value: "paragraph", children: "Paragraph" }), headingLevels.map((level) => (_jsxs("option", { value: `h${level}`, children: ["Heading ", level] }, level))), config.lists.numbered && _jsx("option", { value: "number", children: "Numbered list" }), config.lists.bullet && _jsx("option", { value: "bullet", children: "Bullet list" }), config.lists.check && _jsx("option", { value: "check", children: "Task list" }), config.blocks.quote && _jsx("option", { value: "quote", children: "Quote" })] })] })), showInlineGroup && (_jsxs("div", { className: "likhari-toolbar-group", children: [fmt.bold && (_jsx(ToolbarButton, { icon: IconBold, title: "Bold", active: state.activeFormats.has('bold'), onClick: () => formatText('bold') })), fmt.italic && (_jsx(ToolbarButton, { icon: IconItalic, title: "Italic", active: state.activeFormats.has('italic'), onClick: () => formatText('italic') })), fmt.underline && (_jsx(ToolbarButton, { icon: IconUnderline, title: "Underline", active: state.activeFormats.has('underline'), onClick: () => formatText('underline') }))] })), (config.font.family || config.font.size) && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet likhari-toolbar-select-group", children: [config.font.family && (_jsxs(_Fragment, { children: [_jsx(SelectIcon, { icon: IconTypography }), _jsx("select", { className: "likhari-toolbar-select", "aria-label": "Font family", disabled: true, title: "Font family (coming soon)", children: _jsx("option", { children: "Font" }) })] })), config.font.size && (_jsxs(_Fragment, { children: [_jsx(SelectIcon, { icon: IconTextSize }), _jsx("select", { className: "likhari-toolbar-select", "aria-label": "Font size", disabled: true, title: "Font size (coming soon)", children: _jsx("option", { children: "Size" }) })] }))] })), showAlignGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-select-group", children: [_jsx(SelectIcon, { icon: AlignIcon }), _jsxs("select", { className: "likhari-toolbar-select", "aria-label": "Alignment", value: state.elementFormat || 'start', onChange: (e) => formatElement(e.target.value), children: [config.alignment.start && _jsx("option", { value: "start", children: "Align start" }), config.alignment.center && _jsx("option", { value: "center", children: "Align center" }), config.alignment.start && _jsx("option", { value: "end", children: "Align end" }), config.alignment.justify && _jsx("option", { value: "justify", children: "Justify" }), config.alignment.left && _jsx("option", { value: "left", children: "Align left" }), config.alignment.right && _jsx("option", { value: "right", children: "Align right" })] })] })), showInsertPoetryGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.links && _jsx(StubButton, { icon: IconLink, title: "Insert link" }), (config.images.linked || config.images.embedded) && _jsx(StubButton, { icon: IconPhoto, title: "Insert image" }), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: "Poetry blocks" })] })), showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && _jsx(StubButton, { icon: IconWand, title: "Auto-correct" }), config.language.textCleanup && _jsx(StubButton, { icon: IconSparkles, title: "Text cleanup" }), config.language.spellCheck && _jsx(StubButton, { icon: IconAbc, title: "Spell-checker" })] })), showOverflowMenu && (_jsxs("div", { className: "likhari-toolbar-overflow", ref: overflowRef, children: [_jsx(ToolbarButton, { icon: IconDots, title: "More formatting", active: overflowOpen, onClick: () => setOverflowOpen((o) => !o) }), overflowOpen && (_jsxs("div", { className: "likhari-toolbar-overflow-panel", role: "menu", children: [fmt.strikethrough && (_jsx(ToolbarButton, { icon: IconStrikethrough, title: "Strikethrough", active: state.activeFormats.has('strikethrough'), onClick: () => formatText('strikethrough') })), fmt.superscript && (_jsx(ToolbarButton, { icon: IconSuperscript, title: "Superscript", active: state.activeFormats.has('superscript'), onClick: () => formatText('superscript') })), fmt.subscript && (_jsx(ToolbarButton, { icon: IconSubscript, title: "Subscript", active: state.activeFormats.has('subscript'), onClick: () => formatText('subscript') })), fmt.caseTransforms && (_jsxs(_Fragment, { children: [_jsx(ToolbarButton, { icon: IconLetterCaseUpper, title: "UPPERCASE", onClick: () => applyCaseTransform('upper') }), _jsx(ToolbarButton, { icon: IconLetterCaseLower, title: "lowercase", onClick: () => applyCaseTransform('lower') }), _jsx(ToolbarButton, { icon: IconLetterCase, title: "Capitalize", onClick: () => applyCaseTransform('capitalize') })] })), fmt.clearFormatting && _jsx(ToolbarButton, { icon: IconClearFormatting, title: "Clear formatting", onClick: clearFormatting }), config.indent && (_jsxs(_Fragment, { children: [_jsx(ToolbarButton, { icon: IconIndentDecrease, title: "Outdent", onClick: () => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconIndentIncrease, title: "Indent", onClick: () => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined) })] }))] }))] }))] }));
204
+ }
@@ -0,0 +1,4 @@
1
+ export { EditorRoot } from './EditorRoot.js';
2
+ export type { EditorRootProps, EditorRef, EditorInitialContent } from './EditorRoot.js';
3
+ export { defaultMantineTheme } from './theme/mantineTheme.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { EditorRoot } from './EditorRoot.js';
2
+ export { defaultMantineTheme } from './theme/mantineTheme.js';
@@ -0,0 +1,12 @@
1
+ import type { Klass, LexicalNode } from 'lexical';
2
+ /**
3
+ * Node types registered on the editor at construction time. Per
4
+ * editor-architecture-design.md §2, node registration cannot be conditional
5
+ * on EditorFeatureConfig — Lexical throws on an unregistered node type
6
+ * during parseEditorState. This list is Phase 1's set (built-ins only);
7
+ * Phase 3's custom nodes (poetry, footnote, layout, page break, image) join
8
+ * this list unconditionally once implemented — the feature config will then
9
+ * control their toolbar/insert availability, not whether they're registered.
10
+ */
11
+ export declare const EDITOR_NODES: Klass<LexicalNode>[];
12
+ //# sourceMappingURL=nodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAMlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAO5C,CAAC"}
package/dist/nodes.js ADDED
@@ -0,0 +1,21 @@
1
+ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
2
+ import { ListItemNode, ListNode } from '@lexical/list';
3
+ import { LinkNode } from '@lexical/link';
4
+ import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
5
+ /**
6
+ * Node types registered on the editor at construction time. Per
7
+ * editor-architecture-design.md §2, node registration cannot be conditional
8
+ * on EditorFeatureConfig — Lexical throws on an unregistered node type
9
+ * during parseEditorState. This list is Phase 1's set (built-ins only);
10
+ * Phase 3's custom nodes (poetry, footnote, layout, page break, image) join
11
+ * this list unconditionally once implemented — the feature config will then
12
+ * control their toolbar/insert availability, not whether they're registered.
13
+ */
14
+ export const EDITOR_NODES = [
15
+ HeadingNode,
16
+ QuoteNode,
17
+ ListNode,
18
+ ListItemNode,
19
+ LinkNode,
20
+ HorizontalRuleNode,
21
+ ];
@@ -0,0 +1,10 @@
1
+ import { type MantineThemeOverride } from '@mantine/core';
2
+ import '@mantine/core/styles.css';
3
+ import './editor.css';
4
+ export interface EditorThemeProviderProps {
5
+ theme?: MantineThemeOverride;
6
+ colorScheme?: 'light' | 'dark';
7
+ children: React.ReactNode;
8
+ }
9
+ export declare function EditorThemeProvider({ theme, colorScheme, children }: EditorThemeProviderProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=EditorThemeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorThemeProvider.d.ts","sourceRoot":"","sources":["../../src/theme/EditorThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,0BAA0B,CAAC;AAGlC,OAAO,cAAc,CAAC;AAsBtB,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,wBAAwB,+BAa7F"}
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MantineProvider } from '@mantine/core';
3
+ import '@mantine/core/styles.css';
4
+ import { buildThemeCss } from '@inshapardaz/likhari-core';
5
+ import { defaultMantineTheme } from './mantineTheme.js';
6
+ import './editor.css';
7
+ let cssInjected = false;
8
+ // Scopes the --editor-* variables to the wrapper div below, which is what
9
+ // actually carries `data-editor-color-scheme` per instance — buildThemeCss's
10
+ // default (':root') would only work if that attribute were on <html>, which
11
+ // it isn't, so a light/dark toggle would silently do nothing (and any OS
12
+ // dark preference would win unconditionally via the media-query fallback).
13
+ const THEME_SCOPE_SELECTOR = '.likhari-theme-scope';
14
+ /** Injects the --editor-* custom property block into <head> exactly once per page. */
15
+ function useThemeCssInjection() {
16
+ if (typeof document !== 'undefined' && !cssInjected) {
17
+ const style = document.createElement('style');
18
+ style.setAttribute('data-likhari-theme', '');
19
+ style.textContent = buildThemeCss(THEME_SCOPE_SELECTOR);
20
+ document.head.appendChild(style);
21
+ cssInjected = true;
22
+ }
23
+ }
24
+ export function EditorThemeProvider({ theme, colorScheme, children }) {
25
+ useThemeCssInjection();
26
+ return (
27
+ // display: contents keeps this wrapper out of the layout tree — CSS
28
+ // custom properties still cascade to children through it (inheritance
29
+ // doesn't depend on layout), but it doesn't sit between EditorRoot's own
30
+ // element and the host's actual parent, so EditorRoot's `height` prop
31
+ // (e.g. '100%') resolves against the host's container as if this div
32
+ // weren't there at all.
33
+ _jsx("div", { className: "likhari-theme-scope", "data-editor-color-scheme": colorScheme, style: { display: 'contents' }, children: _jsx(MantineProvider, { theme: theme ?? defaultMantineTheme, children: children }) }));
34
+ }
@@ -0,0 +1,265 @@
1
+ /* Canvas + toolbar styling built on the --editor-* custom properties
2
+ injected by <EditorThemeProvider> (packages/core design tokens). */
3
+
4
+ .likhari-root {
5
+ /* Actual height comes from EditorRoot's `height` prop (inline style) —
6
+ this class only defines the internal flex layout that makes the
7
+ toolbar stay fixed-height while the canvas absorbs whatever's left. */
8
+ display: flex;
9
+ flex-direction: column;
10
+ background: var(--editor-paper);
11
+ color: var(--editor-ink);
12
+ border: 1px solid var(--editor-line);
13
+ border-radius: 8px;
14
+ overflow: hidden;
15
+ font-family: 'IBM Plex Sans', system-ui, sans-serif;
16
+ }
17
+
18
+ /* Toolbar — editor-ui-design-spec.md §3.2. flex-shrink: 0 keeps it pinned at
19
+ its own height so only the canvas below scrolls. */
20
+ .likhari-toolbar {
21
+ display: flex;
22
+ flex-shrink: 0;
23
+ align-items: center;
24
+ gap: 8px;
25
+ min-height: 46px;
26
+ padding: 0 8px;
27
+ background: var(--editor-paper-dim);
28
+ border-bottom: 1px solid var(--editor-line);
29
+ flex-wrap: nowrap;
30
+ overflow-x: auto;
31
+ }
32
+
33
+ .likhari-toolbar-group {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 2px;
37
+ padding: 0 6px;
38
+ border-right: 0.5px solid var(--editor-line);
39
+ }
40
+
41
+ .likhari-toolbar-group:last-child {
42
+ border-right: none;
43
+ }
44
+
45
+ .likhari-toolbar-button {
46
+ display: inline-flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ min-width: 30px;
50
+ height: 30px;
51
+ padding: 0 6px;
52
+ border-radius: 6px;
53
+ border: none;
54
+ background: transparent;
55
+ color: var(--editor-ink);
56
+ cursor: pointer;
57
+ }
58
+
59
+ .likhari-toolbar-button svg {
60
+ display: block;
61
+ }
62
+
63
+ .likhari-toolbar-button:hover {
64
+ background: var(--editor-line);
65
+ }
66
+
67
+ .likhari-toolbar-button[data-active='true'] {
68
+ background: var(--editor-accent-soft);
69
+ color: var(--editor-accent);
70
+ }
71
+
72
+ .likhari-toolbar-button:focus-visible {
73
+ outline: 2px solid var(--editor-accent);
74
+ outline-offset: 1px;
75
+ }
76
+
77
+ .likhari-toolbar-button:disabled {
78
+ opacity: 0.4;
79
+ cursor: not-allowed;
80
+ }
81
+
82
+ .likhari-toolbar-select {
83
+ height: 30px;
84
+ max-width: 116px;
85
+ font-size: 12px;
86
+ border-radius: 6px;
87
+ border: 1px solid var(--editor-line-strong);
88
+ background: var(--editor-paper);
89
+ color: var(--editor-ink);
90
+ padding: 0 6px;
91
+ }
92
+
93
+ .likhari-toolbar-select:disabled {
94
+ opacity: 0.4;
95
+ cursor: not-allowed;
96
+ }
97
+
98
+ /* Icon shown to the left of a <select> — native selects can't render an
99
+ icon per-option, so the control itself is labeled this way instead. */
100
+ .likhari-toolbar-select-group {
101
+ gap: 4px;
102
+ }
103
+
104
+ .likhari-toolbar-select-icon {
105
+ display: inline-flex;
106
+ align-items: center;
107
+ color: var(--editor-ink-soft);
108
+ }
109
+
110
+ /* Overflow menu ("⋯") — houses less-frequent formatting (strikethrough,
111
+ super/subscript, case transforms, clear formatting, indent/outdent) so
112
+ the primary row stays compact on small/mobile viewports. */
113
+ .likhari-toolbar-overflow {
114
+ position: relative;
115
+ display: flex;
116
+ align-items: center;
117
+ }
118
+
119
+ .likhari-toolbar-overflow-panel {
120
+ position: absolute;
121
+ top: calc(100% + 4px);
122
+ inset-inline-end: 0;
123
+ z-index: 20;
124
+ display: flex;
125
+ flex-wrap: wrap;
126
+ gap: 2px;
127
+ width: 168px;
128
+ padding: 6px;
129
+ background: var(--editor-paper);
130
+ border: 0.5px solid var(--editor-line);
131
+ border-radius: 8px;
132
+ }
133
+
134
+ /* Responsive collapse — editor-ui-design-spec.md §3.3. These groups hold
135
+ only not-yet-implemented placeholders, so hiding them below the tablet
136
+ breakpoint costs nothing functional while keeping the primary row usable
137
+ on small screens; the toolbar's horizontal scroll (below) remains the
138
+ fallback if the still-visible controls alone overflow a narrow viewport. */
139
+ @media (max-width: 900px) {
140
+ .likhari-toolbar-group--collapse-tablet {
141
+ display: none;
142
+ }
143
+ }
144
+
145
+ .likhari-save-button {
146
+ display: inline-flex;
147
+ align-items: center;
148
+ gap: 6px;
149
+ height: 30px;
150
+ padding: 0 14px;
151
+ border-radius: 6px;
152
+ border: 1px solid var(--editor-accent);
153
+ background: var(--editor-accent);
154
+ color: var(--editor-paper);
155
+ font-size: 12px;
156
+ font-weight: 500;
157
+ cursor: pointer;
158
+ }
159
+
160
+ .likhari-save-button[data-dirty='false'] {
161
+ background: transparent;
162
+ color: var(--editor-accent);
163
+ }
164
+
165
+ /* Canvas — editor-ui-design-spec.md §4. flex: 1 1 auto + min-height: 0 lets
166
+ this pane absorb the root's height and scroll internally instead of the
167
+ editor (and the page around it) growing to fit the content. */
168
+ .likhari-canvas {
169
+ position: relative;
170
+ flex: 1 1 auto;
171
+ min-height: 0;
172
+ overflow-y: auto;
173
+ padding: 20px 24px;
174
+ }
175
+
176
+ .likhari-content-editable {
177
+ /* Fills the canvas so a click anywhere in the visible area — not just on
178
+ existing text — focuses the editor and places the caret, matching how
179
+ a text editor's empty space is expected to behave. */
180
+ height: 100%;
181
+ min-height: 100%;
182
+ outline: none;
183
+ font-family: 'IBM Plex Serif', Georgia, serif;
184
+ font-size: 15px;
185
+ line-height: 1.75;
186
+ }
187
+
188
+ .likhari-content-editable[dir='rtl'] {
189
+ font-family: 'Noto Nastaliq Urdu', serif;
190
+ font-size: 20px;
191
+ line-height: 2.3;
192
+ }
193
+
194
+ .likhari-placeholder {
195
+ position: absolute;
196
+ top: 20px;
197
+ inset-inline-start: 24px;
198
+ color: var(--editor-ink-soft);
199
+ pointer-events: none;
200
+ user-select: none;
201
+ }
202
+
203
+ .likhari-paragraph {
204
+ margin: 0 0 1em 0;
205
+ }
206
+
207
+ .likhari-quote {
208
+ margin: 0 0 1em 0;
209
+ padding-inline-start: 1em;
210
+ border-inline-start: 3px solid var(--editor-line-strong);
211
+ color: var(--editor-ink-soft);
212
+ }
213
+
214
+ .likhari-h1,
215
+ .likhari-h2,
216
+ .likhari-h3,
217
+ .likhari-h4,
218
+ .likhari-h5,
219
+ .likhari-h6 {
220
+ font-weight: 600;
221
+ margin: 0.6em 0 0.4em 0;
222
+ }
223
+
224
+ .likhari-h1 { font-size: 1.8em; }
225
+ .likhari-h2 { font-size: 1.5em; }
226
+ .likhari-h3 { font-size: 1.3em; }
227
+ .likhari-h4 { font-size: 1.2em; }
228
+ .likhari-h5 { font-size: 1.1em; }
229
+ .likhari-h6 { font-size: 1.05em; }
230
+
231
+ .likhari-ul,
232
+ .likhari-ol {
233
+ margin: 0 0 1em 0;
234
+ padding-inline-start: 1.5em;
235
+ }
236
+
237
+ .likhari-listitem {
238
+ margin: 0.2em 0;
239
+ }
240
+
241
+ .likhari-listitem-checked,
242
+ .likhari-listitem-unchecked {
243
+ list-style: none;
244
+ padding-inline-start: 1.5em;
245
+ position: relative;
246
+ }
247
+
248
+ .likhari-link {
249
+ color: var(--editor-accent);
250
+ text-decoration: underline;
251
+ }
252
+
253
+ .likhari-text-bold { font-weight: 600; }
254
+ .likhari-text-italic { font-style: italic; }
255
+ .likhari-text-underline { text-decoration: underline; }
256
+ .likhari-text-strikethrough { text-decoration: line-through; }
257
+ .likhari-text-underline.likhari-text-strikethrough { text-decoration: underline line-through; }
258
+ .likhari-text-subscript { vertical-align: sub; font-size: 0.8em; }
259
+ .likhari-text-superscript { vertical-align: super; font-size: 0.8em; }
260
+
261
+ .likhari-hr {
262
+ border: none;
263
+ border-top: 1px solid var(--editor-line);
264
+ margin: 1.5em 0;
265
+ }
@@ -0,0 +1,8 @@
1
+ import type { EditorThemeClasses } from 'lexical';
2
+ /**
3
+ * Maps Lexical node/format kinds to the CSS classes defined in editor.css.
4
+ * Class names are prefixed `likhari-` to stay collision-free in a host page
5
+ * (the "headless + default theme" requirement, UI spec §10).
6
+ */
7
+ export declare const editorTheme: EditorThemeClasses;
8
+ //# sourceMappingURL=editorTheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorTheme.d.ts","sourceRoot":"","sources":["../../src/theme/editorTheme.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,kBA6BzB,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Maps Lexical node/format kinds to the CSS classes defined in editor.css.
3
+ * Class names are prefixed `likhari-` to stay collision-free in a host page
4
+ * (the "headless + default theme" requirement, UI spec §10).
5
+ */
6
+ export const editorTheme = {
7
+ paragraph: 'likhari-paragraph',
8
+ quote: 'likhari-quote',
9
+ heading: {
10
+ h1: 'likhari-h1',
11
+ h2: 'likhari-h2',
12
+ h3: 'likhari-h3',
13
+ h4: 'likhari-h4',
14
+ h5: 'likhari-h5',
15
+ h6: 'likhari-h6',
16
+ },
17
+ list: {
18
+ listitem: 'likhari-listitem',
19
+ listitemChecked: 'likhari-listitem-checked',
20
+ listitemUnchecked: 'likhari-listitem-unchecked',
21
+ nested: { listitem: 'likhari-nested-listitem' },
22
+ olDepth: ['likhari-ol'],
23
+ ul: 'likhari-ul',
24
+ },
25
+ link: 'likhari-link',
26
+ text: {
27
+ bold: 'likhari-text-bold',
28
+ italic: 'likhari-text-italic',
29
+ underline: 'likhari-text-underline',
30
+ strikethrough: 'likhari-text-strikethrough',
31
+ subscript: 'likhari-text-subscript',
32
+ superscript: 'likhari-text-superscript',
33
+ },
34
+ hr: 'likhari-hr',
35
+ };
@@ -0,0 +1,9 @@
1
+ import { type MantineThemeOverride } from '@mantine/core';
2
+ /**
3
+ * Default Mantine theme (headless mode, UI spec §10) — supplies structure/
4
+ * behavior while the app's own --editor-* CSS variables (theme.css) carry
5
+ * the actual visual tokens, so a host restyling the editor overrides CSS
6
+ * variables rather than fighting Mantine's default visuals.
7
+ */
8
+ export declare const defaultMantineTheme: MantineThemeOverride;
9
+ //# sourceMappingURL=mantineTheme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mantineTheme.d.ts","sourceRoot":"","sources":["../../src/theme/mantineTheme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGvE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,oBAkBhC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { createTheme } from '@mantine/core';
2
+ import { LIGHT_TOKENS } from '@inshapardaz/likhari-core';
3
+ /**
4
+ * Default Mantine theme (headless mode, UI spec §10) — supplies structure/
5
+ * behavior while the app's own --editor-* CSS variables (theme.css) carry
6
+ * the actual visual tokens, so a host restyling the editor overrides CSS
7
+ * variables rather than fighting Mantine's default visuals.
8
+ */
9
+ export const defaultMantineTheme = createTheme({
10
+ fontFamily: `'IBM Plex Sans', system-ui, sans-serif`,
11
+ primaryColor: 'likhariAccent',
12
+ colors: {
13
+ likhariAccent: [
14
+ LIGHT_TOKENS.accentSoft,
15
+ LIGHT_TOKENS.accentSoft,
16
+ LIGHT_TOKENS.accent,
17
+ LIGHT_TOKENS.accent,
18
+ LIGHT_TOKENS.accent,
19
+ LIGHT_TOKENS.accent,
20
+ LIGHT_TOKENS.accent,
21
+ LIGHT_TOKENS.accent,
22
+ LIGHT_TOKENS.accent,
23
+ LIGHT_TOKENS.accent,
24
+ ],
25
+ },
26
+ defaultRadius: 'sm',
27
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@inshapardaz/likhari-react",
3
+ "version": "0.1.0-dev.7",
4
+ "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/inshapardaz/likhari"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./package.json": "./package.json"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "sideEffects": [
27
+ "*.css"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.build.json && node ../../scripts/fix-esm-extensions.mjs ./dist && node ../../scripts/copy-assets.mjs src dist \"**/*.css\"",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "dependencies": {
34
+ "@lexical/link": "^0.20.0",
35
+ "@lexical/list": "^0.20.0",
36
+ "@lexical/react": "^0.20.0",
37
+ "@lexical/rich-text": "^0.20.0",
38
+ "@lexical/selection": "^0.20.0",
39
+ "@lexical/utils": "^0.20.0",
40
+ "@inshapardaz/likhari-converters": "^0.1.0",
41
+ "@inshapardaz/likhari-core": "^0.1.0",
42
+ "@mantine/core": "^7.13.5",
43
+ "@mantine/hooks": "^7.13.5",
44
+ "@tabler/icons-react": "^3.46.0",
45
+ "lexical": "^0.20.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "^18.3.1",
49
+ "react-dom": "^18.3.1"
50
+ }
51
+ }