@inshapardaz/likhari-react 0.1.9 → 0.1.11-dev.102

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.
@@ -8,7 +8,7 @@ import { type Locale } from './i18n/index.js';
8
8
  /** What happens when the user is about to leave with unsaved changes. */
9
9
  export type NavigationGuardMode = 'confirm' | 'save-draft' | 'off';
10
10
  export interface EditorInitialContent {
11
- format: Extract<FormatId, 'lexical-json' | 'plain-text'>;
11
+ format: FormatId;
12
12
  value: string;
13
13
  }
14
14
  export interface EditorRootProps {
@@ -1 +1 @@
1
- {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY1D,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEjF,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAGjE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAYhF,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMnE,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,CAAC;AAEnE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,QAAQ,CAAC;IACjB,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;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3E,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;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,uGAqXrB,CAAC"}
@@ -7,9 +7,11 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
7
7
  import { ListPlugin } from '@lexical/react/LexicalListPlugin';
8
8
  import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
9
9
  import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
10
+ import { TablePlugin } from '@lexical/react/LexicalTablePlugin';
10
11
  import { HorizontalRulePlugin } from '@lexical/react/LexicalHorizontalRulePlugin';
11
12
  import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
12
13
  import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
14
+ import { EditorInstancePlugin } from './plugins/EditorInstancePlugin.js';
13
15
  import { defaultFormatRegistry } from '@inshapardaz/likhari-converters';
14
16
  import { resolveFeatureConfig } from '@inshapardaz/likhari-core';
15
17
  import { EDITOR_NODES } from './nodes.js';
@@ -18,6 +20,7 @@ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
18
20
  import { Toolbar } from './components/Toolbar.js';
19
21
  import { injectUrduWebFontsCss } from './fonts/index.js';
20
22
  import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
23
+ import { PageBreakPlugin } from './plugins/PageBreakPlugin.js';
21
24
  import { DraftRestore } from './components/DraftRestore.js';
22
25
  import { LeaveDialog } from './components/LeaveDialog.js';
23
26
  import { clearDraft, createDraftId, hasDraftContent, pruneDrafts, writeDraft, } from './persistence/draftStorage.js';
@@ -198,13 +201,20 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
198
201
  return defaultFormatRegistry.serialize(format, json);
199
202
  },
200
203
  setContent(value, format) {
201
- // Applied on next render via editorState prop is not supported for
202
- // an already-mounted LexicalComposer; hosts needing this before
203
- // Phase 2's controlled-mode support should remount with a new
204
- // `initialContent`/`documentId` in the meantime.
205
- void value;
206
- void format;
207
- throw new Error('@inshapardaz/likhari-react: EditorRef.setContent is not implemented yet — controlled mode is Phase 2 work (docs/editor-architecture-design.md §5).');
204
+ const editor = editorInstanceRef.current;
205
+ if (!editor)
206
+ return;
207
+ const parsed = editor.parseEditorState(JSON.stringify(defaultFormatRegistry.parse(format, value)));
208
+ // The host's value is the new baseline: clean, and no stale draft of
209
+ // whatever it replaces should be offered back later.
210
+ lastSavedJsonRef.current = JSON.stringify(parsed.toJSON());
211
+ if (autosaveTimerRef.current) {
212
+ clearTimeout(autosaveTimerRef.current);
213
+ autosaveTimerRef.current = null;
214
+ }
215
+ pendingDraftRef.current = null;
216
+ editor.setEditorState(parsed);
217
+ setIsDirty(false);
208
218
  },
209
219
  hasUnsavedChanges() {
210
220
  return isDirty;
@@ -217,6 +227,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
217
227
  },
218
228
  }), [isDirty]);
219
229
  const rootElementRef = useRef(null);
230
+ const editorInstanceRef = useRef(null);
220
231
  const handleChange = (state) => {
221
232
  editorStateRef.current = state;
222
233
  const json = JSON.stringify(state.toJSON());
@@ -266,7 +277,7 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
266
277
  const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
267
278
  return (_jsx(EditorThemeProvider, { theme: theme, colorScheme: colorScheme, accentColor: accentColor, scopeElementId: portalTargetId, children: _jsxs("div", { className: "likhari-root", dir: dir, ref: rootElementRef, "data-document-id": documentId, style: { height: typeof height === 'number' ? `${height}px` : height }, children: [_jsx("div", { id: portalTargetId, className: "likhari-portal-target" }), _jsx(PortalTargetContext.Provider, { value: portalTargetSelector, children: _jsxs(UiStringsContext.Provider, { value: strings, children: [_jsx(ImageOptionsContext.Provider, { value: imageOptions, children: _jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Toolbar, { config: config, onSave: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir, locale: locale, drafts: autosave
268
279
  ? { currentId: draftId, maxBytes: autosaveMaxBytes, adoptOnRestore: !documentId, onRestored: handleDraftRestored }
269
- : undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
280
+ : undefined }), documentId && (_jsx(DraftRestore, { draftId: documentId, mode: restoreDraft, initialJson: initialContentJsonRef.current, locale: locale, onRestored: handleDraftRestored })), _jsx("div", { className: "likhari-canvas", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "likhari-content-editable", dir: dir, "aria-label": strings.editor.contentLabel }), placeholder: _jsx("div", { className: "likhari-placeholder", children: resolvedPlaceholder }), ErrorBoundary: LexicalErrorBoundary }) }), config.history && _jsx(HistoryPlugin, {}), (config.lists.bullet || config.lists.numbered || config.lists.check) && _jsx(ListPlugin, {}), config.lists.check && _jsx(CheckListPlugin, {}), config.links && _jsx(LinkPlugin, {}), config.links && _jsx(LinkPastePlugin, {}), config.blocks.pageBreak && _jsx(PageBreakPlugin, {}), config.tables && _jsx(TablePlugin, { hasCellMerge: false, hasTabHandler: true }), config.blocks.horizontalRule && _jsx(HorizontalRulePlugin, {}), _jsx(EditorInstancePlugin, { instanceRef: editorInstanceRef }), _jsx(OnChangePlugin, { onChange: handleChange })] }) }), _jsx(LeaveDialog, { opened: leavePrompt !== null, canSave: Boolean(onSave), canSaveDraft: autosave || navigationGuard === 'save-draft', draftFailed: leavePrompt?.draftFailed ?? false, onSave: () => {
270
281
  handleSave();
271
282
  settleLeave(true);
272
283
  }, onSaveDraft: () => {
@@ -0,0 +1,30 @@
1
+ import { type ReactElement } from 'react';
2
+ import { DecoratorNode, type DOMConversionMap, type DOMExportOutput, type EditorConfig, type LexicalCommand, type LexicalNode, type SerializedLexicalNode } from 'lexical';
3
+ export declare const INSERT_PAGE_BREAK_COMMAND: LexicalCommand<void>;
4
+ export type SerializedPageBreakNode = SerializedLexicalNode;
5
+ /**
6
+ * A manual page break — a print/paginated-export concept with no native
7
+ * Lexical node (lexical-editor-spec.md §4.3/§4.6). Renders as a visible
8
+ * dashed marker while editing, and as `page-break-after: always` both live
9
+ * (a print stylesheet, editor.css) and in exported HTML (`exportDOM`).
10
+ *
11
+ * Mirrors @lexical/react's HorizontalRuleNode pattern: `createDOM` returns
12
+ * the actual visible element (the click target `PageBreakComponent` wires
13
+ * selection/delete against), rather than `decorate()` rendering visuals.
14
+ */
15
+ export declare class PageBreakNode extends DecoratorNode<ReactElement> {
16
+ static getType(): string;
17
+ static clone(node: PageBreakNode): PageBreakNode;
18
+ static importJSON(): PageBreakNode;
19
+ exportJSON(): SerializedPageBreakNode;
20
+ static importDOM(): DOMConversionMap | null;
21
+ exportDOM(): DOMExportOutput;
22
+ createDOM(config: EditorConfig): HTMLElement;
23
+ getTextContent(): string;
24
+ isInline(): false;
25
+ updateDOM(): boolean;
26
+ decorate(): import("react").JSX.Element;
27
+ }
28
+ export declare function $createPageBreakNode(): PageBreakNode;
29
+ export declare function $isPageBreakNode(node: LexicalNode | null | undefined): node is PageBreakNode;
30
+ //# sourceMappingURL=PageBreakNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageBreakNode.d.ts","sourceRoot":"","sources":["../../src/blocks/PageBreakNode.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAKlE,OAAO,EAOL,aAAa,EAGb,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,qBAAqB,EAC3B,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,yBAAyB,EAAE,cAAc,CAAC,IAAI,CAA8C,CAAC;AAE1G,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AA6D5D;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,aAAa,CAAC,YAAY,CAAC;IAC5D,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa;IAIhD,MAAM,CAAC,UAAU,IAAI,aAAa;IAIlC,UAAU,IAAI,uBAAuB;IAIrC,MAAM,CAAC,SAAS,IAAI,gBAAgB,GAAG,IAAI;IAS3C,SAAS,IAAI,eAAe;IAO5B,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,cAAc,IAAI,MAAM;IAIxB,QAAQ,IAAI,KAAK;IAIjB,SAAS,IAAI,OAAO;IAIpB,QAAQ;CAGT;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAEpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,IAAI,aAAa,CAE5F"}
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect } from 'react';
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
5
+ import { addClassNamesToElement, mergeRegister, removeClassNamesFromElement } from '@lexical/utils';
6
+ import { useUiStrings } from '../i18n/useStrings.js';
7
+ import { $applyNodeReplacement, $getSelection, $isNodeSelection, CLICK_COMMAND, COMMAND_PRIORITY_LOW, createCommand, DecoratorNode, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, } from 'lexical';
8
+ export const INSERT_PAGE_BREAK_COMMAND = createCommand('INSERT_PAGE_BREAK_COMMAND');
9
+ const SELECTED_CLASS_NAME = 'likhari-page-break-selected';
10
+ function PageBreakComponent({ nodeKey }) {
11
+ const [editor] = useLexicalComposerContext();
12
+ const strings = useUiStrings();
13
+ const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
14
+ const onDelete = useCallback((event) => {
15
+ const selection = $getSelection();
16
+ if (isSelected && $isNodeSelection(selection)) {
17
+ event.preventDefault();
18
+ editor.update(() => {
19
+ selection.getNodes().forEach((node) => {
20
+ if ($isPageBreakNode(node))
21
+ node.remove();
22
+ });
23
+ });
24
+ }
25
+ return false;
26
+ }, [editor, isSelected]);
27
+ useEffect(() => {
28
+ return mergeRegister(editor.registerCommand(CLICK_COMMAND, (event) => {
29
+ const element = editor.getElementByKey(nodeKey);
30
+ // The marker is made of child spans (the label and two lines), so a
31
+ // click almost never lands on the outer element itself.
32
+ if (!element || !element.contains(event.target))
33
+ return false;
34
+ if (!event.shiftKey)
35
+ clearSelection();
36
+ setSelected(!isSelected);
37
+ return true;
38
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_DELETE_COMMAND, onDelete, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_BACKSPACE_COMMAND, onDelete, COMMAND_PRIORITY_LOW));
39
+ }, [clearSelection, editor, isSelected, nodeKey, onDelete, setSelected]);
40
+ useEffect(() => {
41
+ const element = editor.getElementByKey(nodeKey);
42
+ if (!element)
43
+ return;
44
+ if (isSelected)
45
+ addClassNamesToElement(element, SELECTED_CLASS_NAME);
46
+ else
47
+ removeClassNamesFromElement(element, SELECTED_CLASS_NAME);
48
+ }, [editor, isSelected, nodeKey]);
49
+ return (_jsxs(_Fragment, { children: [_jsx("span", { className: "likhari-page-break-line", "aria-hidden": "true" }), _jsx("span", { className: "likhari-page-break-label", children: strings.editor.pageBreakLabel }), _jsx("span", { className: "likhari-page-break-line", "aria-hidden": "true" })] }));
50
+ }
51
+ /**
52
+ * A manual page break — a print/paginated-export concept with no native
53
+ * Lexical node (lexical-editor-spec.md §4.3/§4.6). Renders as a visible
54
+ * dashed marker while editing, and as `page-break-after: always` both live
55
+ * (a print stylesheet, editor.css) and in exported HTML (`exportDOM`).
56
+ *
57
+ * Mirrors @lexical/react's HorizontalRuleNode pattern: `createDOM` returns
58
+ * the actual visible element (the click target `PageBreakComponent` wires
59
+ * selection/delete against), rather than `decorate()` rendering visuals.
60
+ */
61
+ export class PageBreakNode extends DecoratorNode {
62
+ static getType() {
63
+ return 'page-break';
64
+ }
65
+ static clone(node) {
66
+ return new PageBreakNode(node.__key);
67
+ }
68
+ static importJSON() {
69
+ return $createPageBreakNode();
70
+ }
71
+ exportJSON() {
72
+ return { type: 'page-break', version: 1 };
73
+ }
74
+ static importDOM() {
75
+ return {
76
+ div: (domNode) => {
77
+ if (!domNode.hasAttribute('data-likhari-page-break'))
78
+ return null;
79
+ return { conversion: () => ({ node: $createPageBreakNode() }), priority: 2 };
80
+ },
81
+ };
82
+ }
83
+ exportDOM() {
84
+ const element = document.createElement('div');
85
+ element.setAttribute('data-likhari-page-break', '');
86
+ element.style.pageBreakAfter = 'always';
87
+ return { element };
88
+ }
89
+ createDOM(config) {
90
+ const element = document.createElement('div');
91
+ addClassNamesToElement(element, config.theme.pageBreak ?? 'likhari-page-break');
92
+ return element;
93
+ }
94
+ getTextContent() {
95
+ return '\n';
96
+ }
97
+ isInline() {
98
+ return false;
99
+ }
100
+ updateDOM() {
101
+ return false;
102
+ }
103
+ decorate() {
104
+ return _jsx(PageBreakComponent, { nodeKey: this.__key });
105
+ }
106
+ }
107
+ export function $createPageBreakNode() {
108
+ return $applyNodeReplacement(new PageBreakNode());
109
+ }
110
+ export function $isPageBreakNode(node) {
111
+ return node instanceof PageBreakNode;
112
+ }
@@ -0,0 +1,17 @@
1
+ export interface TableDialogValue {
2
+ rows: number;
3
+ columns: number;
4
+ headerRow: boolean;
5
+ }
6
+ export interface TableDialogProps {
7
+ opened: boolean;
8
+ onSubmit: (value: TableDialogValue) => void;
9
+ onClose: () => void;
10
+ }
11
+ /**
12
+ * Insert-table dialog: rows x columns and an optional header row. Cell
13
+ * merge/split is intentionally not offered anywhere in this dialog or the
14
+ * table it creates — see the comment on `TablePlugin` in `EditorRoot.tsx`.
15
+ */
16
+ export declare function TableDialog({ opened, onSubmit, onClose }: TableDialogProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=TableDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableDialog.d.ts","sourceRoot":"","sources":["../../src/components/TableDialog.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAOD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,gBAAgB,+BAiD1E"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Button, Checkbox, Group, Modal, NumberInput } from '@mantine/core';
4
+ import { useUiStrings } from '../i18n/useStrings.js';
5
+ import { usePortalTarget } from '../PortalTargetContext.js';
6
+ const MIN_DIMENSION = 1;
7
+ const MAX_DIMENSION = 20;
8
+ const DEFAULT_ROWS = 3;
9
+ const DEFAULT_COLUMNS = 3;
10
+ /**
11
+ * Insert-table dialog: rows x columns and an optional header row. Cell
12
+ * merge/split is intentionally not offered anywhere in this dialog or the
13
+ * table it creates — see the comment on `TablePlugin` in `EditorRoot.tsx`.
14
+ */
15
+ export function TableDialog({ opened, onSubmit, onClose }) {
16
+ const strings = useUiStrings();
17
+ const portalTarget = usePortalTarget();
18
+ const [rows, setRows] = useState(DEFAULT_ROWS);
19
+ const [columns, setColumns] = useState(DEFAULT_COLUMNS);
20
+ const [headerRow, setHeaderRow] = useState(true);
21
+ useEffect(() => {
22
+ if (!opened)
23
+ return;
24
+ setRows(DEFAULT_ROWS);
25
+ setColumns(DEFAULT_COLUMNS);
26
+ setHeaderRow(true);
27
+ }, [opened]);
28
+ const handleSubmit = (e) => {
29
+ e.preventDefault();
30
+ onSubmit({ rows, columns, headerRow });
31
+ };
32
+ return (_jsx(Modal, { opened: opened, onClose: onClose, title: strings.tableDialog.title, centered: true, size: "sm", portalProps: { target: portalTarget }, children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(Group, { grow: true, align: "flex-end", children: [_jsx(NumberInput, { label: strings.tableDialog.rows, min: MIN_DIMENSION, max: MAX_DIMENSION, value: rows, onChange: (v) => setRows(typeof v === 'number' ? v : DEFAULT_ROWS), "data-autofocus": true }), _jsx(NumberInput, { label: strings.tableDialog.columns, min: MIN_DIMENSION, max: MAX_DIMENSION, value: columns, onChange: (v) => setColumns(typeof v === 'number' ? v : DEFAULT_COLUMNS) })] }), _jsx(Checkbox, { mt: "sm", label: strings.tableDialog.headerRow, checked: headerRow, onChange: (e) => setHeaderRow(e.currentTarget.checked) }), _jsxs(Group, { justify: "flex-end", gap: "xs", mt: "md", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", children: strings.common.insert })] })] }) }));
33
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA4CA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAuUvD,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;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BA4xBxJ"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AAuDA,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA+YvD,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;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAg7BxJ"}
@@ -11,6 +11,10 @@ import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
11
11
  import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode';
12
12
  import { ImageDialog } from '../image/ImageDialog.js';
13
13
  import { $createImageNode } from '../image/ImageNode.js';
14
+ import { INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
15
+ import { TableDialog } from './TableDialog.js';
16
+ import { $getTableCellNodeFromLexicalNode, $isTableSelection, INSERT_TABLE_COMMAND } from '@lexical/table';
17
+ import { $deleteTable, $deleteTableColumns, $deleteTableRows, $getTableSelectionSize, $insertTableColumns, $insertTableRows, } from '../table/tableActions.js';
14
18
  import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
15
19
  import { LinkDialog } from './LinkDialog.js';
16
20
  import { DraftsDialog } from './DraftsDialog.js';
@@ -19,7 +23,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fo
19
23
  import { setStyleProperty } from '../utils/style.js';
20
24
  import { useStrings } from '../i18n/useStrings.js';
21
25
  import { usePortalTarget } from '../PortalTargetContext.js';
22
- import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
26
+ import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconTable, IconTableMinus, IconTableOptions, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
23
27
  /** Sentinel for "no inline style": Lexical returns the default it is given when
24
28
  * the selection has none, so this tells "unstyled" (show the canvas default)
25
29
  * apart from "styled with a value the dropdown doesn't list" (show nothing). */
@@ -33,6 +37,9 @@ const INITIAL_STATE = {
33
37
  fontSize: UNSET_STYLE,
34
38
  isLink: false,
35
39
  linkUrl: '',
40
+ inTable: false,
41
+ tableRows: 1,
42
+ tableColumns: 1,
36
43
  canUndo: false,
37
44
  canRedo: false,
38
45
  };
@@ -121,6 +128,21 @@ function LinkMenuItems({ url, onEdit, onRemove, strings }) {
121
128
  // Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
122
129
  _jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url || strings.link.noUrl })), _jsx(Menu.Item, { leftSection: _jsx(IconPencil, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onEdit, children: strings.link.editLink }), _jsx(Menu.Item, { color: "red", leftSection: _jsx(IconLinkOff, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onRemove, children: strings.link.removeLink })] }));
123
130
  }
131
+ /** The table actions menu's contents — shared by the toolbar button and the
132
+ * right-click menu. Each acts on the selected cell, or on every row/column a
133
+ * multi-cell selection covers. */
134
+ function TableMenuItems({ strings, direction, rows, columns, onAction, }) {
135
+ const t = strings.tableMenu;
136
+ // "Before" is the start side, which is on the right in an RTL table.
137
+ const rtl = direction === 'rtl';
138
+ const manyRows = rows > 1;
139
+ const manyColumns = columns > 1;
140
+ const item = (icon, label, action, color) => {
141
+ const Icon = icon;
142
+ return (_jsx(Menu.Item, { color: color, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
143
+ };
144
+ return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), _jsx(Menu.Divider, {}), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
145
+ }
124
146
  export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
125
147
  const [editor] = useLexicalComposerContext();
126
148
  const [state, setState] = useState(INITIAL_STATE);
@@ -139,9 +161,17 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
139
161
  const updateToolbar = useCallback(() => {
140
162
  editor.getEditorState().read(() => {
141
163
  const selection = $getSelection();
164
+ // A selection of several table cells isn't a range selection.
165
+ if ($isTableSelection(selection)) {
166
+ const { rows, columns } = $getTableSelectionSize();
167
+ setState((s) => ({ ...s, inTable: true, tableRows: rows, tableColumns: columns }));
168
+ return;
169
+ }
142
170
  if (!$isRangeSelection(selection))
143
171
  return;
144
172
  const anchorNode = selection.anchor.getNode();
173
+ const inTable = $getTableCellNodeFromLexicalNode(anchorNode) !== null;
174
+ const tableSize = inTable ? $getTableSelectionSize() : { rows: 1, columns: 1 };
145
175
  const element = anchorNode.getKey() === 'root' ? anchorNode : (anchorNode.getTopLevelElement() ?? anchorNode);
146
176
  const listParent = $findMatchingParent(anchorNode, $isListNode);
147
177
  const listType = listParent && $isListNode(listParent) ? listParent.getListType() : null;
@@ -177,6 +207,9 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
177
207
  fontSize,
178
208
  isLink,
179
209
  linkUrl,
210
+ inTable,
211
+ tableRows: tableSize.rows,
212
+ tableColumns: tableSize.columns,
180
213
  }));
181
214
  });
182
215
  }, [editor]);
@@ -276,6 +309,26 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
276
309
  });
277
310
  closeImageDialog();
278
311
  };
312
+ const [tableDialogOpen, setTableDialogOpen] = useState(false);
313
+ const openTableDialog = () => {
314
+ snapshotSelection();
315
+ setTableDialogOpen(true);
316
+ };
317
+ const closeTableDialog = () => {
318
+ setTableDialogOpen(false);
319
+ editor.focus();
320
+ };
321
+ const insertTable = ({ rows, columns, headerRow }) => {
322
+ const saved = menuSelectionRef.current;
323
+ if (saved)
324
+ editor.update(() => $setSelection(saved.clone()), { discrete: true });
325
+ editor.dispatchCommand(INSERT_TABLE_COMMAND, {
326
+ rows: String(rows),
327
+ columns: String(columns),
328
+ includeHeaders: { rows: headerRow, columns: false },
329
+ });
330
+ closeTableDialog();
331
+ };
279
332
  /** Applies a font property per `config.font.scope`: to the selection, to
280
333
  * every text node in the document, or (for 'both') to the selection when
281
334
  * there is a range and to the whole document when there isn't. */
@@ -369,6 +422,50 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
369
422
  root?.addEventListener('contextmenu', onContextMenu);
370
423
  });
371
424
  }, [editor, config.links]);
425
+ // Table actions. The menu takes focus, so the selection is snapshotted when it
426
+ // opens and restored before an action runs.
427
+ const runTableAction = (action) => () => {
428
+ restoreSelection();
429
+ editor.update(action);
430
+ editor.focus();
431
+ };
432
+ // Right-clicking a table cell opens the same actions at the pointer.
433
+ const [tableContext, setTableContext] = useState(null);
434
+ useEffect(() => {
435
+ if (!config.tables)
436
+ return;
437
+ const onContextMenu = (event) => {
438
+ if (event.defaultPrevented)
439
+ return; // a link inside the cell already took it
440
+ const cellElement = event.target?.closest?.('td, th');
441
+ if (!cellElement)
442
+ return;
443
+ let handled = false;
444
+ editor.update(() => {
445
+ const node = $getNearestNodeFromDOMNode(cellElement);
446
+ const cell = node ? $getTableCellNodeFromLexicalNode(node) : null;
447
+ if (!cell)
448
+ return;
449
+ handled = true;
450
+ const selection = $getSelection();
451
+ // Keep a multi-cell selection that includes this cell, so "insert
452
+ // rows" / "delete columns" act on everything selected.
453
+ const insideSelection = $isTableSelection(selection) && selection.getNodes().some((n) => n.getKey() === cell.getKey());
454
+ if (!insideSelection)
455
+ cell.selectEnd();
456
+ }, { discrete: true });
457
+ if (!handled)
458
+ return;
459
+ event.preventDefault();
460
+ snapshotSelectionRef.current();
461
+ const size = editor.getEditorState().read($getTableSelectionSize);
462
+ setTableContext({ x: event.clientX, y: event.clientY, rows: size.rows, columns: size.columns });
463
+ };
464
+ return editor.registerRootListener((root, previous) => {
465
+ previous?.removeEventListener('contextmenu', onContextMenu);
466
+ root?.addEventListener('contextmenu', onContextMenu);
467
+ });
468
+ }, [editor, config.tables]);
372
469
  // Ctrl/Cmd+K opens the link dialog.
373
470
  useEffect(() => {
374
471
  if (!config.links)
@@ -429,7 +526,12 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
429
526
  const showFormattingGroup = headingLevels.length > 0 || config.lists.bullet || config.lists.numbered || config.lists.check || config.blocks.quote;
430
527
  const showInlineGroup = fmt.bold || fmt.italic || fmt.underline;
431
528
  const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
432
- const showStubInsertGroup = config.images.linked || config.images.embedded || config.blocks.horizontalRule || config.poetry.enabled;
529
+ const showStubInsertGroup = config.images.linked ||
530
+ config.images.embedded ||
531
+ config.tables ||
532
+ config.blocks.horizontalRule ||
533
+ config.blocks.pageBreak ||
534
+ config.poetry.enabled;
433
535
  const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
434
536
  // The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
435
537
  // as normal inline toolbar groups whenever there's room, and only move
@@ -599,7 +701,8 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
599
701
  restoreSelection();
600
702
  openLinkDialog();
601
703
  }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
602
- showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
704
+ showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
705
+ config.tables && state.inTable && (_jsx("div", { className: "likhari-toolbar-group", children: _jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": "true", "aria-pressed": "true", "aria-haspopup": "menu", "aria-label": strings.toolbar.tableOptions, title: strings.toolbar.tableOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconTableOptions, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, onAction: runTableAction }) })] }) }, "tableActions")),
603
706
  showLanguageGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [config.language.autocorrect && _jsx(StubButton, { icon: IconWand, title: strings.toolbar.autocorrect, comingSoon: strings.toolbar.comingSoon }), config.language.textCleanup && _jsx(StubButton, { icon: IconSparkles, title: strings.toolbar.textCleanup, comingSoon: strings.toolbar.comingSoon }), config.language.spellCheck && _jsx(StubButton, { icon: IconAbc, title: strings.toolbar.spellChecker, comingSoon: strings.toolbar.comingSoon })] }, "language")),
604
707
  ];
605
708
  return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
@@ -612,7 +715,10 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
612
715
  }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: linkContext?.x ?? -9999, top: linkContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: linkContext?.url ?? '', onEdit: () => {
613
716
  restoreSelection();
614
717
  openLinkDialog();
615
- }, onRemove: removeLink, strings: strings }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
718
+ }, onRemove: removeLink, strings: strings }) })] })), config.tables && (_jsxs(Menu, { opened: tableContext !== null, onChange: (opened) => {
719
+ if (!opened)
720
+ setTableContext(null);
721
+ }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: tableContext?.x ?? -9999, top: tableContext?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: tableContext?.rows ?? 1, columns: tableContext?.columns ?? 1, onAction: runTableAction }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
616
722
  position: 'absolute',
617
723
  visibility: 'hidden',
618
724
  pointerEvents: 'none',
@@ -621,5 +727,5 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
621
727
  display: 'flex',
622
728
  flexWrap: 'nowrap',
623
729
  gap: 8,
624
- }, children: [fixedGroups, movableGroups.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key)))] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
730
+ }, children: [fixedGroups, movableGroups.map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key)))] }), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
625
731
  }
@@ -13,6 +13,8 @@ export interface Strings {
13
13
  placeholder: string;
14
14
  /** aria-label on the contenteditable itself. */
15
15
  contentLabel: string;
16
+ /** Label shown on a page-break marker's dashed line while editing. */
17
+ pageBreakLabel: string;
16
18
  };
17
19
  common: {
18
20
  cancel: string;
@@ -38,8 +40,12 @@ export interface Strings {
38
40
  noMatch: string;
39
41
  alignment: string;
40
42
  linkOptions: string;
43
+ /** Toolbar button (shown while the caret is in a table) that opens the table actions menu. */
44
+ tableOptions: string;
41
45
  insertLink: string;
42
46
  insertImage: string;
47
+ insertPageBreak: string;
48
+ insertTable: string;
43
49
  insertHorizontalRule: string;
44
50
  poetryBlocks: string;
45
51
  comingSoon: (label: string) => string;
@@ -108,6 +114,24 @@ export interface Strings {
108
114
  delete: string;
109
115
  close: string;
110
116
  };
117
+ /** The table actions menu (toolbar button and right-click on a cell). */
118
+ tableMenu: {
119
+ menuLabel: string;
120
+ insertRowBefore: string;
121
+ insertRowAfter: string;
122
+ insertColumnBefore: string;
123
+ insertColumnAfter: string;
124
+ deleteRow: string;
125
+ deleteColumn: string;
126
+ deleteTable: string;
127
+ /** Plural forms, used when the selection spans several rows or columns. */
128
+ insertRowsBefore: string;
129
+ insertRowsAfter: string;
130
+ insertColumnsBefore: string;
131
+ insertColumnsAfter: string;
132
+ deleteRows: string;
133
+ deleteColumns: string;
134
+ };
111
135
  link: {
112
136
  menuLabel: string;
113
137
  noUrl: string;
@@ -124,6 +148,12 @@ export interface Strings {
124
148
  invalidUrl: string;
125
149
  apply: string;
126
150
  };
151
+ tableDialog: {
152
+ title: string;
153
+ rows: string;
154
+ columns: string;
155
+ headerRow: string;
156
+ };
127
157
  imageDialog: {
128
158
  titleInsert: string;
129
159
  titleEdit: string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ const en = {
8
8
  editor: {
9
9
  placeholder: 'Start writing…',
10
10
  contentLabel: 'Editor content',
11
+ pageBreakLabel: 'Page break',
11
12
  },
12
13
  common: {
13
14
  cancel: 'Cancel',
@@ -31,8 +32,11 @@ const en = {
31
32
  noMatch: 'No match',
32
33
  alignment: 'Alignment',
33
34
  linkOptions: 'Link options',
35
+ tableOptions: 'Table options',
34
36
  insertLink: 'Insert link (Ctrl+K)',
35
37
  insertImage: 'Insert image',
38
+ insertPageBreak: 'Insert page break',
39
+ insertTable: 'Insert table',
36
40
  insertHorizontalRule: 'Insert horizontal rule',
37
41
  poetryBlocks: 'Poetry blocks',
38
42
  comingSoon: (label) => `${label} (coming soon)`,
@@ -96,6 +100,22 @@ const en = {
96
100
  delete: 'Delete',
97
101
  close: 'Close',
98
102
  },
103
+ tableMenu: {
104
+ menuLabel: 'Table',
105
+ insertRowBefore: 'Insert row before',
106
+ insertRowAfter: 'Insert row after',
107
+ insertColumnBefore: 'Insert column before',
108
+ insertColumnAfter: 'Insert column after',
109
+ deleteRow: 'Delete row',
110
+ deleteColumn: 'Delete column',
111
+ deleteTable: 'Delete table',
112
+ insertRowsBefore: 'Insert rows before',
113
+ insertRowsAfter: 'Insert rows after',
114
+ insertColumnsBefore: 'Insert columns before',
115
+ insertColumnsAfter: 'Insert columns after',
116
+ deleteRows: 'Delete rows',
117
+ deleteColumns: 'Delete columns',
118
+ },
99
119
  link: {
100
120
  menuLabel: 'Link',
101
121
  noUrl: '(no URL)',
@@ -112,6 +132,12 @@ const en = {
112
132
  invalidUrl: 'Enter a valid http(s), mailto, tel or relative URL',
113
133
  apply: 'Apply',
114
134
  },
135
+ tableDialog: {
136
+ title: 'Insert table',
137
+ rows: 'Rows',
138
+ columns: 'Columns',
139
+ headerRow: 'Header row',
140
+ },
115
141
  imageDialog: {
116
142
  titleInsert: 'Insert image',
117
143
  titleEdit: 'Edit image',
@@ -175,6 +201,7 @@ const ur = {
175
201
  editor: {
176
202
  placeholder: 'لکھنا شروع کریں…',
177
203
  contentLabel: 'ایڈیٹر کا مواد',
204
+ pageBreakLabel: 'صفحے کی تقسیم',
178
205
  },
179
206
  common: {
180
207
  cancel: 'منسوخ کریں',
@@ -198,8 +225,11 @@ const ur = {
198
225
  noMatch: 'کوئی مماثلت نہیں',
199
226
  alignment: 'سیدھ',
200
227
  linkOptions: 'لنک کے اختیارات',
228
+ tableOptions: 'جدول کے اختیارات',
201
229
  insertLink: 'لنک شامل کریں (Ctrl+K)',
202
230
  insertImage: 'تصویر شامل کریں',
231
+ insertPageBreak: 'صفحے کی تقسیم شامل کریں',
232
+ insertTable: 'جدول شامل کریں',
203
233
  insertHorizontalRule: 'افقی لکیر شامل کریں',
204
234
  poetryBlocks: 'شاعری کے بلاکس',
205
235
  comingSoon: (label) => `${label} (جلد آ رہا ہے)`,
@@ -263,6 +293,22 @@ const ur = {
263
293
  delete: 'حذف کریں',
264
294
  close: 'بند کریں',
265
295
  },
296
+ tableMenu: {
297
+ menuLabel: 'جدول',
298
+ insertRowBefore: 'پہلے قطار شامل کریں',
299
+ insertRowAfter: 'بعد میں قطار شامل کریں',
300
+ insertColumnBefore: 'پہلے کالم شامل کریں',
301
+ insertColumnAfter: 'بعد میں کالم شامل کریں',
302
+ deleteRow: 'قطار حذف کریں',
303
+ deleteColumn: 'کالم حذف کریں',
304
+ deleteTable: 'جدول حذف کریں',
305
+ insertRowsBefore: 'پہلے قطاریں شامل کریں',
306
+ insertRowsAfter: 'بعد میں قطاریں شامل کریں',
307
+ insertColumnsBefore: 'پہلے کالمز شامل کریں',
308
+ insertColumnsAfter: 'بعد میں کالمز شامل کریں',
309
+ deleteRows: 'قطاریں حذف کریں',
310
+ deleteColumns: 'کالمز حذف کریں',
311
+ },
266
312
  link: {
267
313
  menuLabel: 'لنک',
268
314
  noUrl: '(کوئی یو آر ایل نہیں)',
@@ -279,6 +325,12 @@ const ur = {
279
325
  invalidUrl: 'براہِ کرم ایک درست http(s)، mailto، tel یا رشتہ دار یو آر ایل درج کریں',
280
326
  apply: 'لاگو کریں',
281
327
  },
328
+ tableDialog: {
329
+ title: 'جدول شامل کریں',
330
+ rows: 'قطاریں',
331
+ columns: 'کالم',
332
+ headerRow: 'سرِ فہرست قطار',
333
+ },
282
334
  imageDialog: {
283
335
  titleInsert: 'تصویر شامل کریں',
284
336
  titleEdit: 'تصویر میں ترمیم کریں',
@@ -342,6 +394,7 @@ const paShahmukhi = {
342
394
  editor: {
343
395
  placeholder: 'لکھنا شروع کرو…',
344
396
  contentLabel: 'ایڈیٹر دی سامگری',
397
+ pageBreakLabel: 'صفحے دی ونڈ',
345
398
  },
346
399
  common: {
347
400
  cancel: 'رد کرو',
@@ -365,8 +418,11 @@ const paShahmukhi = {
365
418
  noMatch: 'کوئی نئیں ملیا',
366
419
  alignment: 'سیدھ',
367
420
  linkOptions: 'لنک دے اختیار',
421
+ tableOptions: 'ٹیبل دے اختیار',
368
422
  insertLink: 'لنک پاؤ (Ctrl+K)',
369
423
  insertImage: 'تصویر پاؤ',
424
+ insertPageBreak: 'صفحے دی ونڈ پاؤ',
425
+ insertTable: 'ٹیبل پاؤ',
370
426
  insertHorizontalRule: 'لیٹی لکیر پاؤ',
371
427
  poetryBlocks: 'شاعری بلاک',
372
428
  comingSoon: (label) => `${label} (چھیتی آ رہا اے)`,
@@ -430,6 +486,22 @@ const paShahmukhi = {
430
486
  delete: 'مٹاؤ',
431
487
  close: 'بند کرو',
432
488
  },
489
+ tableMenu: {
490
+ menuLabel: 'ٹیبل',
491
+ insertRowBefore: 'پہلاں قطار پاؤ',
492
+ insertRowAfter: 'بعد وچ قطار پاؤ',
493
+ insertColumnBefore: 'پہلاں کالم پاؤ',
494
+ insertColumnAfter: 'بعد وچ کالم پاؤ',
495
+ deleteRow: 'قطار مٹاؤ',
496
+ deleteColumn: 'کالم مٹاؤ',
497
+ deleteTable: 'ٹیبل مٹاؤ',
498
+ insertRowsBefore: 'پہلاں قطاراں پاؤ',
499
+ insertRowsAfter: 'بعد وچ قطاراں پاؤ',
500
+ insertColumnsBefore: 'پہلاں کالماں پاؤ',
501
+ insertColumnsAfter: 'بعد وچ کالماں پاؤ',
502
+ deleteRows: 'قطاراں مٹاؤ',
503
+ deleteColumns: 'کالماں مٹاؤ',
504
+ },
433
505
  link: {
434
506
  menuLabel: 'لنک',
435
507
  noUrl: '(کوئی یو آر ایل نئیں)',
@@ -446,6 +518,12 @@ const paShahmukhi = {
446
518
  invalidUrl: 'کِرپا کر کے صحیح http(s)، mailto، tel یا نسبتی یو آر ایل پاؤ',
447
519
  apply: 'لاگو کرو',
448
520
  },
521
+ tableDialog: {
522
+ title: 'ٹیبل پاؤ',
523
+ rows: 'قطاراں',
524
+ columns: 'کالم',
525
+ headerRow: 'سرکڑی قطار',
526
+ },
449
527
  imageDialog: {
450
528
  titleInsert: 'تصویر پاؤ',
451
529
  titleEdit: 'تصویر وچ تبدیلی کرو',
@@ -1 +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;AAOlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAQ5C,CAAC"}
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;AASlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAY5C,CAAC"}
package/dist/nodes.js CHANGED
@@ -2,7 +2,9 @@ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
2
2
  import { ListItemNode, ListNode } from '@lexical/list';
3
3
  import { LinkNode } from '@lexical/link';
4
4
  import { HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
5
+ import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
5
6
  import { ImageNode } from './image/ImageNode.js';
7
+ import { PageBreakNode } from './blocks/PageBreakNode.js';
6
8
  /**
7
9
  * Node types registered on the editor at construction time. Per
8
10
  * editor-architecture-design.md §2, node registration cannot be conditional
@@ -19,5 +21,9 @@ export const EDITOR_NODES = [
19
21
  ListItemNode,
20
22
  LinkNode,
21
23
  HorizontalRuleNode,
24
+ TableNode,
25
+ TableRowNode,
26
+ TableCellNode,
22
27
  ImageNode,
28
+ PageBreakNode,
23
29
  ];
@@ -0,0 +1,7 @@
1
+ import { type MutableRefObject } from 'react';
2
+ import type { LexicalEditor } from 'lexical';
3
+ /** Hands the composer's editor to the component that owns the imperative ref. */
4
+ export declare function EditorInstancePlugin({ instanceRef }: {
5
+ instanceRef: MutableRefObject<LexicalEditor | null>;
6
+ }): null;
7
+ //# sourceMappingURL=EditorInstancePlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorInstancePlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/EditorInstancePlugin.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,EAAE,gBAAgB,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;CAAE,GAAG,IAAI,CASnH"}
@@ -0,0 +1,13 @@
1
+ import { useEffect } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ /** Hands the composer's editor to the component that owns the imperative ref. */
4
+ export function EditorInstancePlugin({ instanceRef }) {
5
+ const [editor] = useLexicalComposerContext();
6
+ useEffect(() => {
7
+ instanceRef.current = editor;
8
+ return () => {
9
+ instanceRef.current = null;
10
+ };
11
+ }, [editor, instanceRef]);
12
+ return null;
13
+ }
@@ -0,0 +1,3 @@
1
+ /** Registers INSERT_PAGE_BREAK_COMMAND — mirrors @lexical/react's own HorizontalRulePlugin. */
2
+ export declare function PageBreakPlugin(): null;
3
+ //# sourceMappingURL=PageBreakPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageBreakPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/PageBreakPlugin.tsx"],"names":[],"mappings":"AAMA,+FAA+F;AAC/F,wBAAgB,eAAe,SAiB9B"}
@@ -0,0 +1,19 @@
1
+ import { useEffect } from 'react';
2
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
3
+ import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical';
4
+ import { $insertNodeToNearestRoot } from '@lexical/utils';
5
+ import { $createPageBreakNode, INSERT_PAGE_BREAK_COMMAND } from '../blocks/PageBreakNode.js';
6
+ /** Registers INSERT_PAGE_BREAK_COMMAND — mirrors @lexical/react's own HorizontalRulePlugin. */
7
+ export function PageBreakPlugin() {
8
+ const [editor] = useLexicalComposerContext();
9
+ useEffect(() => {
10
+ return editor.registerCommand(INSERT_PAGE_BREAK_COMMAND, () => {
11
+ const selection = $getSelection();
12
+ if (!$isRangeSelection(selection))
13
+ return false;
14
+ $insertNodeToNearestRoot($createPageBreakNode());
15
+ return true;
16
+ }, COMMAND_PRIORITY_EDITOR);
17
+ }, [editor]);
18
+ return null;
19
+ }
@@ -0,0 +1,21 @@
1
+ /** How many rows and columns the selection spans (1 × 1 for a caret in a cell, 0 × 0 outside a table). */
2
+ export declare function $getTableSelectionSize(): {
3
+ rows: number;
4
+ columns: number;
5
+ };
6
+ /**
7
+ * Inserts as many rows as the selection spans, before its first row or after
8
+ * its last. Lexical's own insert works from the selection's focus cell and adds
9
+ * one row, so with several rows selected it would land mid-selection.
10
+ */
11
+ export declare function $insertTableRows(insertAfter: boolean): void;
12
+ /** Like $insertTableRows, for columns. "Before" is the column index below the
13
+ * first selected one (the right-hand side in a right-to-left table). */
14
+ export declare function $insertTableColumns(insertAfter: boolean): void;
15
+ /** Deletes every row the selection touches (Lexical handles a multi-cell selection). */
16
+ export declare function $deleteTableRows(): void;
17
+ /** Deletes every column the selection touches. */
18
+ export declare function $deleteTableColumns(): void;
19
+ /** Removes the whole table the selection is in. */
20
+ export declare function $deleteTable(): void;
21
+ //# sourceMappingURL=tableActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tableActions.d.ts","sourceRoot":"","sources":["../../src/table/tableActions.ts"],"names":[],"mappings":"AA2BA,0GAA0G;AAC1G,wBAAgB,sBAAsB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAS1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAS3D;AAED;wEACwE;AACxE,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,GAAG,IAAI,CAQ9D;AAED,wFAAwF;AACxF,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,kDAAkD;AAClD,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,mDAAmD;AACnD,wBAAgB,YAAY,IAAI,IAAI,CAInC"}
@@ -0,0 +1,71 @@
1
+ import { $deleteTableColumn__EXPERIMENTAL, $deleteTableRow__EXPERIMENTAL, $getTableCellNodeFromLexicalNode, $getTableColumnIndexFromTableCellNode, $getTableNodeFromLexicalNodeOrThrow, $getTableRowIndexFromTableCellNode, $insertTableColumn__EXPERIMENTAL, $insertTableRow__EXPERIMENTAL, $isTableCellNode, $isTableSelection, } from '@lexical/table';
2
+ import { $getSelection, $isRangeSelection } from 'lexical';
3
+ /** Table cells the selection covers: the caret's own cell, or every cell of a
4
+ * multi-cell selection. Empty when the selection isn't in a table. */
5
+ function $selectedCells() {
6
+ const selection = $getSelection();
7
+ if ($isTableSelection(selection))
8
+ return selection.getNodes().filter($isTableCellNode);
9
+ if ($isRangeSelection(selection)) {
10
+ const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
11
+ return cell ? [cell] : [];
12
+ }
13
+ return [];
14
+ }
15
+ /** How many rows and columns the selection spans (1 × 1 for a caret in a cell, 0 × 0 outside a table). */
16
+ export function $getTableSelectionSize() {
17
+ const cells = $selectedCells();
18
+ if (cells.length === 0)
19
+ return { rows: 0, columns: 0 };
20
+ const rows = cells.map($getTableRowIndexFromTableCellNode);
21
+ const columns = cells.map($getTableColumnIndexFromTableCellNode);
22
+ return {
23
+ rows: Math.max(...rows) - Math.min(...rows) + 1,
24
+ columns: Math.max(...columns) - Math.min(...columns) + 1,
25
+ };
26
+ }
27
+ /**
28
+ * Inserts as many rows as the selection spans, before its first row or after
29
+ * its last. Lexical's own insert works from the selection's focus cell and adds
30
+ * one row, so with several rows selected it would land mid-selection.
31
+ */
32
+ export function $insertTableRows(insertAfter) {
33
+ const cells = $selectedCells();
34
+ if (cells.length === 0)
35
+ return;
36
+ const rows = cells.map($getTableRowIndexFromTableCellNode);
37
+ const first = Math.min(...rows);
38
+ const last = Math.max(...rows);
39
+ // Park the caret in a cell on the edge row; every insert then lands right next to it.
40
+ cells[rows.indexOf(insertAfter ? last : first)].selectEnd();
41
+ for (let i = 0; i <= last - first; i++)
42
+ $insertTableRow__EXPERIMENTAL(insertAfter);
43
+ }
44
+ /** Like $insertTableRows, for columns. "Before" is the column index below the
45
+ * first selected one (the right-hand side in a right-to-left table). */
46
+ export function $insertTableColumns(insertAfter) {
47
+ const cells = $selectedCells();
48
+ if (cells.length === 0)
49
+ return;
50
+ const columns = cells.map($getTableColumnIndexFromTableCellNode);
51
+ const first = Math.min(...columns);
52
+ const last = Math.max(...columns);
53
+ cells[columns.indexOf(insertAfter ? last : first)].selectEnd();
54
+ for (let i = 0; i <= last - first; i++)
55
+ $insertTableColumn__EXPERIMENTAL(insertAfter);
56
+ }
57
+ /** Deletes every row the selection touches (Lexical handles a multi-cell selection). */
58
+ export function $deleteTableRows() {
59
+ $deleteTableRow__EXPERIMENTAL();
60
+ }
61
+ /** Deletes every column the selection touches. */
62
+ export function $deleteTableColumns() {
63
+ $deleteTableColumn__EXPERIMENTAL();
64
+ }
65
+ /** Removes the whole table the selection is in. */
66
+ export function $deleteTable() {
67
+ const selection = $getSelection();
68
+ const anchor = $isRangeSelection(selection) || $isTableSelection(selection) ? selection.anchor.getNode() : null;
69
+ if (anchor)
70
+ $getTableNodeFromLexicalNodeOrThrow(anchor).remove();
71
+ }
@@ -51,6 +51,12 @@
51
51
  pointer-events: none;
52
52
  }
53
53
 
54
+ /* pointer-events is inherited, so without this every menu, dropdown and dialog
55
+ portaled into the (pointer-transparent) anchor would be unclickable. */
56
+ .likhari-portal-target > * {
57
+ pointer-events: auto;
58
+ }
59
+
54
60
  .likhari-toolbar-group {
55
61
  display: flex;
56
62
  align-items: center;
@@ -253,6 +259,76 @@
253
259
  margin: 1.5em 0;
254
260
  }
255
261
 
262
+ /* Page break — a print/export concept with no visual meaning while editing
263
+ on screen, so it gets its own clearly-labeled dashed marker rather than
264
+ masquerading as a horizontal rule. The actual page-break behavior only
265
+ takes effect when printing (lexical-editor-spec.md §4.3/§4.6). */
266
+ .likhari-page-break {
267
+ display: flex;
268
+ align-items: center;
269
+ gap: 8px;
270
+ margin: 1.5em 0;
271
+ font-size: 12px;
272
+ cursor: default;
273
+ }
274
+
275
+ .likhari-page-break-line {
276
+ flex: 1 1 auto;
277
+ border-top: 1px dashed var(--editor-line);
278
+ }
279
+
280
+ .likhari-page-break-label {
281
+ color: var(--editor-ink-soft);
282
+ white-space: nowrap;
283
+ }
284
+
285
+ .likhari-page-break-selected {
286
+ outline: 2px solid var(--editor-accent);
287
+ outline-offset: 2px;
288
+ border-radius: 4px;
289
+ }
290
+
291
+ @media print {
292
+ .likhari-page-break {
293
+ page-break-after: always;
294
+ /* Hide the on-screen marker itself when actually printing — the
295
+ browser's own pagination (triggered by page-break-after) is what
296
+ matters there, not the editing-time label. */
297
+ visibility: hidden;
298
+ }
299
+ }
300
+
301
+ /* Tables — cell merge/split is deliberately out of scope for now (a flagged
302
+ open risk, lexical-editor-spec.md §12 item 3, needing its own scope
303
+ decision); TablePlugin is configured with hasCellMerge={false} to keep
304
+ every table a plain grid until that's resolved. */
305
+ .likhari-table {
306
+ border-collapse: collapse;
307
+ margin: 1em 0;
308
+ width: 100%;
309
+ }
310
+
311
+ .likhari-table-row {
312
+ border-bottom: 1px solid var(--editor-line);
313
+ }
314
+
315
+ .likhari-table-cell {
316
+ border: 1px solid var(--editor-line);
317
+ padding: 6px 10px;
318
+ min-width: 72px;
319
+ vertical-align: top;
320
+ }
321
+
322
+ .likhari-table-cell-header {
323
+ background: var(--editor-paper-dim);
324
+ font-weight: 600;
325
+ text-align: start;
326
+ }
327
+
328
+ .likhari-table-cell-selected {
329
+ background: var(--editor-accent-soft);
330
+ }
331
+
256
332
  /* Images — block-level figure; the caption is static text (edited in the
257
333
  image's dialog), and a selected image gets a corner resize handle. */
258
334
  .likhari-image-block {
@@ -1 +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,kBA8BzB,CAAC"}
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,kBAoCzB,CAAC"}
@@ -33,4 +33,10 @@ export const editorTheme = {
33
33
  },
34
34
  hr: 'likhari-hr',
35
35
  image: 'likhari-image-block',
36
+ pageBreak: 'likhari-page-break',
37
+ table: 'likhari-table',
38
+ tableRow: 'likhari-table-row',
39
+ tableCell: 'likhari-table-cell',
40
+ tableCellHeader: 'likhari-table-cell-header',
41
+ tableCellSelected: 'likhari-table-cell-selected',
36
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.9",
3
+ "version": "0.1.11-dev.102",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,6 +38,7 @@
38
38
  "@lexical/react": "^0.20.0",
39
39
  "@lexical/rich-text": "^0.20.0",
40
40
  "@lexical/selection": "^0.20.0",
41
+ "@lexical/table": "^0.20.0",
41
42
  "@lexical/utils": "^0.20.0",
42
43
  "@inshapardaz/likhari-converters": "^0.1.0",
43
44
  "@inshapardaz/likhari-core": "^0.1.0",