@inshapardaz/likhari-react 0.1.6-dev.68 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/EditorRoot.d.ts +1 -25
  2. package/dist/EditorRoot.d.ts.map +1 -1
  3. package/dist/EditorRoot.js +5 -24
  4. package/dist/components/LinkDialog.d.ts.map +1 -1
  5. package/dist/components/LinkDialog.js +3 -7
  6. package/dist/components/Toolbar.d.ts +1 -4
  7. package/dist/components/Toolbar.d.ts.map +1 -1
  8. package/dist/components/Toolbar.js +31 -215
  9. package/dist/index.d.ts +0 -4
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +0 -2
  12. package/dist/nodes.d.ts.map +1 -1
  13. package/dist/nodes.js +0 -2
  14. package/dist/theme/EditorThemeProvider.d.ts +1 -17
  15. package/dist/theme/EditorThemeProvider.d.ts.map +1 -1
  16. package/dist/theme/EditorThemeProvider.js +2 -24
  17. package/dist/theme/editor.css +0 -125
  18. package/dist/theme/editorTheme.d.ts.map +1 -1
  19. package/dist/theme/editorTheme.js +0 -1
  20. package/package.json +1 -2
  21. package/dist/PortalTargetContext.d.ts +0 -16
  22. package/dist/PortalTargetContext.d.ts.map +0 -1
  23. package/dist/PortalTargetContext.js +0 -18
  24. package/dist/i18n/index.d.ts +0 -4
  25. package/dist/i18n/index.d.ts.map +0 -1
  26. package/dist/i18n/index.js +0 -2
  27. package/dist/i18n/strings.d.ts +0 -158
  28. package/dist/i18n/strings.d.ts.map +0 -1
  29. package/dist/i18n/strings.js +0 -425
  30. package/dist/i18n/useStrings.d.ts +0 -10
  31. package/dist/i18n/useStrings.d.ts.map +0 -1
  32. package/dist/i18n/useStrings.js +0 -14
  33. package/dist/image/ImageComponent.d.ts +0 -23
  34. package/dist/image/ImageComponent.d.ts.map +0 -1
  35. package/dist/image/ImageComponent.js +0 -171
  36. package/dist/image/ImageCropDialog.d.ts +0 -22
  37. package/dist/image/ImageCropDialog.d.ts.map +0 -1
  38. package/dist/image/ImageCropDialog.js +0 -120
  39. package/dist/image/ImageCropper.d.ts +0 -15
  40. package/dist/image/ImageCropper.d.ts.map +0 -1
  41. package/dist/image/ImageCropper.js +0 -120
  42. package/dist/image/ImageDialog.d.ts +0 -37
  43. package/dist/image/ImageDialog.d.ts.map +0 -1
  44. package/dist/image/ImageDialog.js +0 -176
  45. package/dist/image/ImageNode.d.ts +0 -64
  46. package/dist/image/ImageNode.d.ts.map +0 -1
  47. package/dist/image/ImageNode.js +0 -133
  48. package/dist/image/ImageOptionsContext.d.ts +0 -24
  49. package/dist/image/ImageOptionsContext.d.ts.map +0 -1
  50. package/dist/image/ImageOptionsContext.js +0 -10
  51. package/dist/image/cropMath.d.ts +0 -35
  52. package/dist/image/cropMath.d.ts.map +0 -1
  53. package/dist/image/cropMath.js +0 -92
  54. package/dist/image/imageEdit.d.ts +0 -49
  55. package/dist/image/imageEdit.d.ts.map +0 -1
  56. package/dist/image/imageEdit.js +0 -132
  57. package/dist/image/imageUrl.d.ts +0 -14
  58. package/dist/image/imageUrl.d.ts.map +0 -1
  59. package/dist/image/imageUrl.js +0 -27
  60. package/dist/theme/accentColor.d.ts +0 -43
  61. package/dist/theme/accentColor.d.ts.map +0 -1
  62. package/dist/theme/accentColor.js +0 -84
@@ -3,7 +3,6 @@ import type { SerializedEditorState } from 'lexical';
3
3
  import { type FormatId } from '@inshapardaz/likhari-converters';
4
4
  import { type EditorFeatureConfig, type FeatureConfigPresetName } from '@inshapardaz/likhari-core';
5
5
  import { type FontOption } from './fonts/index.js';
6
- import { type Locale } from './i18n/index.js';
7
6
  export interface EditorInitialContent {
8
7
  format: Extract<FormatId, 'lexical-json' | 'plain-text'>;
9
8
  value: string;
@@ -15,15 +14,7 @@ export interface EditorRootProps {
15
14
  featurePreset?: FeatureConfigPresetName;
16
15
  theme?: MantineThemeOverride;
17
16
  colorScheme?: 'light' | 'dark';
18
- /**
19
- * A CSS color (e.g. `'#2B6E6E'`) overriding the editor's default accent —
20
- * both Mantine's own primary color (buttons, portaled dropdowns' active-item
21
- * highlight, etc.) and the `--editor-accent` / `--editor-accent-soft` CSS
22
- * variables the toolbar/canvas use directly. Omit to use the built-in
23
- * default tokens (packages/core/src/theme/tokens.ts).
24
- */
25
- accentColor?: string;
26
- locale?: Locale;
17
+ locale?: 'en' | 'ur' | 'pa-shahmukhi';
27
18
  placeholder?: string;
28
19
  /**
29
20
  * CSS height for the editor box (toolbar + canvas together) — accepts any
@@ -47,21 +38,6 @@ export interface EditorRootProps {
47
38
  * the unsaved-changes state) or to hide it while keeping `onSave`.
48
39
  */
49
40
  showSave?: boolean;
50
- /**
51
- * Stores an uploaded image and returns the URL to reference it by. Without
52
- * it, uploaded images are embedded in the document as base64 data URIs —
53
- * simple, but they bloat the document (see `images.maxSizeMB`).
54
- */
55
- onImageUpload?: (file: File) => Promise<string>;
56
- /**
57
- * Downloads a linked image's bytes when converting it to an embedded copy
58
- * or applying a crop/rotate/flip. Without it, the browser fetches the URL
59
- * directly, which fails for any image server that doesn't send CORS
60
- * headers — that restriction can't be worked around from the browser
61
- * alone. Provide this to route the request through your own backend (or a
62
- * CORS proxy) instead.
63
- */
64
- fetchImage?: (url: string) => Promise<Blob>;
65
41
  /**
66
42
  * Entries for the toolbar's font-family dropdown. Defaults to
67
43
  * `DEFAULT_FONT_OPTIONS` (generic Latin faces plus the Urdu/Arabic-script
@@ -1 +1 @@
1
- {"version":3,"file":"EditorRoot.d.ts","sourceRoot":"","sources":["../src/EditorRoot.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAU1D,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;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAIjE,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEnE,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;;;;;;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;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,uGAkKrB,CAAC"}
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;AAKzH,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAGjE,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;;;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;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,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,uGA6HrB,CAAC"}
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
2
+ import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
3
3
  import { LexicalComposer } from '@lexical/react/LexicalComposer';
4
4
  import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
5
5
  import { ContentEditable } from '@lexical/react/LexicalContentEditable';
6
6
  import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
7
7
  import { ListPlugin } from '@lexical/react/LexicalListPlugin';
8
- import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
9
8
  import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
10
9
  import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
11
10
  import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
@@ -17,33 +16,20 @@ import { EditorThemeProvider } from './theme/EditorThemeProvider.js';
17
16
  import { Toolbar } from './components/Toolbar.js';
18
17
  import { injectUrduWebFontsCss } from './fonts/index.js';
19
18
  import { LinkPastePlugin } from './plugins/LinkPastePlugin.js';
20
- import { ImageOptionsContext } from './image/ImageOptionsContext.js';
21
- import { PortalTargetContext } from './PortalTargetContext.js';
22
- import { UiStringsContext, getStrings } from './i18n/index.js';
23
19
  function initialEditorStateJson(initialContent) {
24
20
  if (!initialContent)
25
21
  return undefined;
26
22
  const state = defaultFormatRegistry.parse(initialContent.format, initialContent.value);
27
23
  return JSON.stringify(state);
28
24
  }
29
- export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, accentColor, locale = 'en', placeholder, height = '480px', onChange, onSave, showSave = Boolean(onSave), onImageUpload, fetchImage, fontOptions, }, ref) {
25
+ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialContent, featureConfig, featurePreset, theme, colorScheme, locale = 'en', placeholder = 'Start writing…', height = '480px', onChange, onSave, showSave = Boolean(onSave), fontOptions, }, ref) {
30
26
  const config = useMemo(() => resolveFeatureConfig(featureConfig, featurePreset), [featureConfig, featurePreset]);
31
- const strings = useMemo(() => getStrings(locale), [locale]);
32
- const resolvedPlaceholder = placeholder ?? strings.editor.placeholder;
33
27
  // The urdu-web-fonts stylesheets are needed for the font dropdown and for
34
28
  // RTL content, whose canvas font (editor.css) is one of those families.
35
29
  useEffect(() => {
36
30
  if (config.font.family || locale !== 'en')
37
31
  injectUrduWebFontsCss();
38
32
  }, [config.font.family, locale]);
39
- const imageOptions = useMemo(() => ({
40
- allowLinked: config.images.linked,
41
- allowEmbedded: config.images.embedded,
42
- allowCaption: config.images.caption,
43
- maxSizeMB: config.images.maxSizeMB,
44
- onImageUpload,
45
- fetchImage,
46
- }), [config.images.linked, config.images.embedded, config.images.caption, config.images.maxSizeMB, onImageUpload, fetchImage]);
47
33
  const editorStateRef = useRef(null);
48
34
  const lastSavedJsonRef = useRef(null);
49
35
  const [isDirty, setIsDirty] = useState(false);
@@ -82,12 +68,12 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
82
68
  return true;
83
69
  if (typeof window === 'undefined')
84
70
  return true;
85
- return window.confirm(strings.editor.confirmDiscard);
71
+ return window.confirm('You have unsaved changes. Discard them?');
86
72
  },
87
73
  focus() {
88
74
  rootElementRef.current?.focus();
89
75
  },
90
- }), [isDirty, strings]);
76
+ }), [isDirty]);
91
77
  const rootElementRef = useRef(null);
92
78
  const handleChange = (state) => {
93
79
  editorStateRef.current = state;
@@ -106,10 +92,5 @@ export const EditorRoot = forwardRef(function EditorRoot({ documentId, initialCo
106
92
  onSave?.(content, format);
107
93
  };
108
94
  const dir = locale === 'en' ? 'ltr' : 'rtl';
109
- // A stable per-instance id for the portal-anchor div below, so multiple
110
- // EditorRoot instances on one page each get their own portal target
111
- // rather than colliding on a shared one.
112
- const portalTargetId = useId();
113
- const portalTargetSelector = `#${CSS.escape(portalTargetId)}`;
114
- 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: _jsx(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 }), _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, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }) })] }) }));
95
+ 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: handleSave, isDirty: isDirty, showSave: showSave, fontOptions: fontOptions, direction: dir }), _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, {}), config.links && _jsx(LinkPastePlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange })] }) }) }));
115
96
  });
@@ -1 +1 @@
1
- {"version":3,"file":"LinkDialog.d.ts","sourceRoot":"","sources":["../../src/components/LinkDialog.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxD,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;gDACgD;AAChD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,+BAyE7G"}
1
+ {"version":3,"file":"LinkDialog.d.ts","sourceRoot":"","sources":["../../src/components/LinkDialog.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxD,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;gDACgD;AAChD,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,eAAe,+BAgE7G"}
@@ -2,13 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
3
  import { Button, Group, Modal, TextInput } from '@mantine/core';
4
4
  import { normalizeLinkUrl } from '../utils/linkUrl.js';
5
- import { useUiStrings } from '../i18n/useStrings.js';
6
- import { usePortalTarget } from '../PortalTargetContext.js';
7
5
  /** Insert/edit-link dialog (UI spec §6). Mantine Modal supplies focus
8
6
  * trapping, Escape-to-close and the overlay. */
9
7
  export function LinkDialog({ opened, initialUrl, showTextField, onSubmit, onRemove, onClose }) {
10
- const strings = useUiStrings();
11
- const portalTarget = usePortalTarget();
12
8
  const [url, setUrl] = useState(initialUrl);
13
9
  const [text, setText] = useState('');
14
10
  const [error, setError] = useState(null);
@@ -23,13 +19,13 @@ export function LinkDialog({ opened, initialUrl, showTextField, onSubmit, onRemo
23
19
  e.preventDefault();
24
20
  const normalized = normalizeLinkUrl(url);
25
21
  if (!normalized) {
26
- setError(strings.linkDialog.invalidUrl);
22
+ setError('Enter a valid http(s), mailto, tel or relative URL');
27
23
  return;
28
24
  }
29
25
  onSubmit({ url: normalized, text: text.trim() || normalized });
30
26
  };
31
- return (_jsx(Modal, { opened: opened, onClose: onClose, title: onRemove ? strings.linkDialog.titleEdit : strings.linkDialog.titleInsert, centered: true, size: "sm", portalProps: { target: portalTarget }, children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TextInput, { label: strings.linkDialog.urlLabel, placeholder: strings.linkDialog.urlPlaceholder, value: url, onChange: (e) => {
27
+ return (_jsx(Modal, { opened: opened, onClose: onClose, title: onRemove ? 'Edit link' : 'Insert link', centered: true, size: "sm", children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx(TextInput, { label: "URL", placeholder: "https://example.com", value: url, onChange: (e) => {
32
28
  setUrl(e.currentTarget.value);
33
29
  setError(null);
34
- }, error: error, "data-autofocus": true }), showTextField && (_jsx(TextInput, { mt: "sm", label: strings.linkDialog.textLabel, placeholder: strings.linkDialog.textPlaceholder, value: text, onChange: (e) => setText(e.currentTarget.value) })), _jsxs(Group, { justify: "space-between", mt: "md", children: [onRemove ? (_jsx(Button, { variant: "subtle", color: "red", onClick: onRemove, children: strings.link.removeLink })) : (_jsx("span", {})), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { variant: "default", onClick: onClose, children: strings.common.cancel }), _jsx(Button, { type: "submit", children: strings.linkDialog.apply })] })] })] }) }));
30
+ }, error: error, "data-autofocus": true }), showTextField && (_jsx(TextInput, { mt: "sm", label: "Text", placeholder: "Link text (defaults to the URL)", value: text, onChange: (e) => setText(e.currentTarget.value) })), _jsxs(Group, { justify: "space-between", mt: "md", children: [onRemove ? (_jsx(Button, { variant: "subtle", color: "red", onClick: onRemove, children: "Remove link" })) : (_jsx("span", {})), _jsxs(Group, { gap: "xs", children: [_jsx(Button, { variant: "default", onClick: onClose, children: "Cancel" }), _jsx(Button, { type: "submit", children: "Apply" })] })] })] }) }));
35
31
  }
@@ -1,6 +1,5 @@
1
1
  import type { ResolvedEditorFeatureConfig } from '@inshapardaz/likhari-core';
2
2
  import { type FontOption } from '../fonts/index.js';
3
- import type { Locale } from '../i18n/strings.js';
4
3
  export interface ToolbarProps {
5
4
  config: ResolvedEditorFeatureConfig;
6
5
  onSave?: () => void;
@@ -10,8 +9,6 @@ export interface ToolbarProps {
10
9
  fontOptions?: FontOption[];
11
10
  /** Text direction of the canvas; picks which default font and size are pre-selected. */
12
11
  direction?: 'ltr' | 'rtl';
13
- /** UI locale — picks the translated strings for labels, tooltips and menu items. */
14
- locale?: Locale;
15
12
  }
16
- export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction, locale }: ToolbarProps): import("react").JSX.Element;
13
+ export declare function Toolbar({ config, onSave, isDirty, showSave, fontOptions, direction }: ToolbarProps): import("react").JSX.Element;
17
14
  //# sourceMappingURL=Toolbar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA4CA,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;AAsRvD,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;CACjB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,EAAE,YAAY,+BA4wBhJ"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAmPtG,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;CAC3B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAkC,EAAE,SAAiB,EAAE,EAAE,YAAY,+BAqmBjI"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { Menu, Select } from '@mantine/core';
4
4
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
5
5
  import { $getSelection, $isRangeSelection, $isElementNode, $setSelection, $getRoot, $getNearestNodeFromDOMNode, $createTextNode, KEY_MODIFIER_COMMAND, COMMAND_PRIORITY_NORMAL, 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';
@@ -7,16 +7,12 @@ import { $getSelectionStyleValueForProperty, $patchStyleText, $setBlocksType } f
7
7
  import { $createParagraphNode } from 'lexical';
8
8
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
9
9
  import { $isListNode, INSERT_CHECK_LIST_COMMAND, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, } from '@lexical/list';
10
- import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
11
- import { ImageDialog } from '../image/ImageDialog.js';
12
- import { $createImageNode } from '../image/ImageNode.js';
10
+ import { $findMatchingParent } from '@lexical/utils';
13
11
  import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
14
12
  import { LinkDialog } from './LinkDialog.js';
15
13
  import { normalizeLinkUrl } from '../utils/linkUrl.js';
16
14
  import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fonts/index.js';
17
15
  import { setStyleProperty } from '../utils/style.js';
18
- import { useStrings } from '../i18n/useStrings.js';
19
- import { usePortalTarget } from '../PortalTargetContext.js';
20
16
  import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconExternalLink, IconFeather, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconPencil, IconPhoto, IconPilcrow, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
21
17
  /** Sentinel for "no inline style": Lexical returns the default it is given when
22
18
  * the selection has none, so this tells "unstyled" (show the canvas default)
@@ -51,30 +47,15 @@ function ToolbarButton({ icon: Icon, title, active, dirty, disabled, onClick, })
51
47
  * isn't implemented yet (link/image/poetry/font/language-tooling — see
52
48
  * docs/lexical-editor-spec.md §13's phasing). Renders so the toolbar's
53
49
  * layout is final now and only needs its onClick wired up later. */
54
- function StubButton({ icon, title, comingSoon }) {
55
- return _jsx(ToolbarButton, { icon: icon, title: comingSoon(title), disabled: true, onClick: undefined });
50
+ function StubButton({ icon, title }) {
51
+ return _jsx(ToolbarButton, { icon: icon, title: `${title} (coming soon)`, disabled: true, onClick: undefined });
56
52
  }
57
53
  /** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
58
54
  * labels the control itself, since a dropdown can't show one per option in
59
55
  * its closed state. The dropdown portals to <body>, so it is not clipped by
60
56
  * the editor's `overflow: hidden` frame. */
61
- function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange, comingSoon, noMatchMessage }) {
62
- const portalTarget = usePortalTarget();
63
- return (_jsx(Select, { size: "xs", w: width, "aria-label": label, title: disabled && comingSoon ? comingSoon(label) : label, data: data, value: value, placeholder: placeholder, disabled: disabled, searchable: searchable, renderOption: renderOption, nothingFoundMessage: searchable ? noMatchMessage : undefined, allowDeselect: false, leftSection: _jsx(Icon, { size: 15, stroke: ICON_STROKE }), comboboxProps: { withinPortal: true, position: 'bottom-start', middlewares: { flip: true, shift: true }, portalProps: { target: portalTarget } }, classNames: { input: 'likhari-toolbar-select-input', dropdown: 'likhari-toolbar-select-dropdown' }, onChange: (v) => v && onChange?.(v) }));
64
- }
65
- const EMPTY_OVERFLOW = new Set();
66
- function setsEqual(a, b) {
67
- if (a.size !== b.size)
68
- return false;
69
- for (const v of a)
70
- if (!b.has(v))
71
- return false;
72
- return true;
73
- }
74
- /** Renders a movable group's items as normal inline toolbar buttons (used
75
- * both in the real toolbar and in the hidden width-measuring clone). */
76
- function MovableGroupButtons({ group }) {
77
- return (_jsx("div", { className: "likhari-toolbar-group", "data-group-key": group.key, children: group.items.map((item) => (_jsx(ToolbarButton, { icon: item.icon, title: item.label, active: item.active, onClick: item.onClick }, item.key))) }));
57
+ function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange }) {
58
+ return (_jsx(Select, { size: "xs", w: width, "aria-label": label, title: disabled ? `${label} (coming soon)` : label, data: data, value: value, placeholder: placeholder, disabled: disabled, searchable: searchable, renderOption: renderOption, nothingFoundMessage: searchable ? 'No match' : undefined, allowDeselect: false, leftSection: _jsx(Icon, { size: 15, stroke: ICON_STROKE }), comboboxProps: { withinPortal: true, position: 'bottom-start', middlewares: { flip: true, shift: true } }, classNames: { input: 'likhari-toolbar-select-input', dropdown: 'likhari-toolbar-select-dropdown' }, onChange: (v) => v && onChange?.(v) }));
78
59
  }
79
60
  function OverflowItem({ icon: Icon, label, active, onClick, }) {
80
61
  return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', className: "likhari-overflow-item",
@@ -83,27 +64,16 @@ function OverflowItem({ icon: Icon, label, active, onClick, }) {
83
64
  }
84
65
  /** The link menu's contents — shared by the toolbar button and the right-click
85
66
  * menu: the URL (opens in a new tab), Edit link, Remove link. */
86
- function LinkMenuItems({ url, onEdit, onRemove, strings }) {
67
+ function LinkMenuItems({ url, onEdit, onRemove }) {
87
68
  // Only offer the link as clickable if it's a URL the editor would have accepted.
88
69
  const safeUrl = normalizeLinkUrl(url);
89
- return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.link.menuLabel }), safeUrl ? (_jsx(Menu.Item, { component: "a", href: safeUrl, target: "_blank", rel: "noopener noreferrer", leftSection: _jsx(IconExternalLink, { size: ICON_SIZE, stroke: ICON_STROKE }), title: url, className: "likhari-link-menu-url", children: url })) : (
70
+ return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: "Link" }), safeUrl ? (_jsx(Menu.Item, { component: "a", href: safeUrl, target: "_blank", rel: "noopener noreferrer", leftSection: _jsx(IconExternalLink, { size: ICON_SIZE, stroke: ICON_STROKE }), title: url, className: "likhari-link-menu-url", children: url })) : (
90
71
  // Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
91
- _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 })] }));
72
+ _jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url || '(no URL)' })), _jsx(Menu.Item, { leftSection: _jsx(IconPencil, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onEdit, children: "Edit link" }), _jsx(Menu.Item, { color: "red", leftSection: _jsx(IconLinkOff, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onRemove, children: "Remove link" })] }));
92
73
  }
93
- export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en' }) {
74
+ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr' }) {
94
75
  const [editor] = useLexicalComposerContext();
95
76
  const [state, setState] = useState(INITIAL_STATE);
96
- const strings = useStrings(locale);
97
- const portalTarget = usePortalTarget();
98
- // Responsive "priority+" overflow: the toolbar's outer container and a
99
- // hidden nowrap clone (rendered with every movable group forced inline)
100
- // are both measured; whenever the clone's natural width exceeds the
101
- // container's available width, the lowest-priority movable groups are
102
- // moved into the "..." menu, lowest-priority first, until what remains
103
- // inline fits (or there's nothing left to move).
104
- const toolbarContainerRef = useRef(null);
105
- const toolbarMeasureRef = useRef(null);
106
- const [overflowGroupKeys, setOverflowGroupKeys] = useState(EMPTY_OVERFLOW);
107
77
  const updateToolbar = useCallback(() => {
108
78
  editor.getEditorState().read(() => {
109
79
  const selection = $getSelection();
@@ -225,25 +195,6 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
225
195
  action();
226
196
  editor.focus();
227
197
  };
228
- const [imageDialogOpen, setImageDialogOpen] = useState(false);
229
- const openImageDialog = () => {
230
- // Snapshot first: the dialog's focus trap makes Lexical drop the selection.
231
- snapshotSelection();
232
- setImageDialogOpen(true);
233
- };
234
- const closeImageDialog = () => {
235
- setImageDialogOpen(false);
236
- editor.focus();
237
- };
238
- const insertImage = (image) => {
239
- const saved = menuSelectionRef.current;
240
- if (saved)
241
- editor.update(() => $setSelection(saved.clone()), { discrete: true });
242
- editor.update(() => {
243
- $insertNodeToNearestRoot($createImageNode(image));
244
- });
245
- closeImageDialog();
246
- };
247
198
  /** Applies a font property per `config.font.scope`: to the selection, to
248
199
  * every text node in the document, or (for 'both') to the selection when
249
200
  * there is a range and to the whole document when there isn't. */
@@ -399,112 +350,7 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
399
350
  const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
400
351
  const showStubInsertGroup = config.images.linked || config.images.embedded || config.poetry.enabled;
401
352
  const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
402
- // The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
403
- // as normal inline toolbar groups whenever there's room, and only move
404
- // into the "..." overflow menu when the measured toolbar content doesn't
405
- // fit the available width (see the ResizeObserver effect below) — this
406
- // replaces the old always-on overflow menu, which was driven purely by
407
- // these config flags regardless of actual space.
408
- const scriptCleanupItems = [];
409
- if (fmt.strikethrough) {
410
- scriptCleanupItems.push({
411
- key: 'strikethrough',
412
- icon: IconStrikethrough,
413
- label: strings.toolbar.strikethrough,
414
- active: state.activeFormats.has('strikethrough'),
415
- onClick: () => formatText('strikethrough'),
416
- });
417
- }
418
- if (fmt.superscript) {
419
- scriptCleanupItems.push({
420
- key: 'superscript',
421
- icon: IconSuperscript,
422
- label: strings.toolbar.superscript,
423
- active: state.activeFormats.has('superscript'),
424
- onClick: () => formatText('superscript'),
425
- });
426
- }
427
- if (fmt.subscript) {
428
- scriptCleanupItems.push({
429
- key: 'subscript',
430
- icon: IconSubscript,
431
- label: strings.toolbar.subscript,
432
- active: state.activeFormats.has('subscript'),
433
- onClick: () => formatText('subscript'),
434
- });
435
- }
436
- if (fmt.caseTransforms) {
437
- scriptCleanupItems.push({ key: 'upper', icon: IconLetterCaseUpper, label: strings.toolbar.uppercase, onClick: () => applyCaseTransform('upper') }, { key: 'lower', icon: IconLetterCaseLower, label: strings.toolbar.lowercase, onClick: () => applyCaseTransform('lower') }, { key: 'capitalize', icon: IconLetterCase, label: strings.toolbar.capitalize, onClick: () => applyCaseTransform('capitalize') });
438
- }
439
- if (fmt.clearFormatting) {
440
- scriptCleanupItems.push({ key: 'clear', icon: IconClearFormatting, label: strings.toolbar.clearFormatting, onClick: clearFormatting });
441
- }
442
- const indentItems = config.indent
443
- ? [
444
- {
445
- key: 'outdent',
446
- icon: IconIndentDecrease,
447
- label: strings.toolbar.outdent,
448
- onClick: () => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined),
449
- },
450
- {
451
- key: 'indent',
452
- icon: IconIndentIncrease,
453
- label: strings.toolbar.indent,
454
- onClick: () => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined),
455
- },
456
- ]
457
- : [];
458
- // Low to high priority: the first group here is the first one pushed into
459
- // overflow once content stops fitting (UI spec §3.3).
460
- const movableGroups = [
461
- { key: 'scriptCleanup', items: scriptCleanupItems },
462
- { key: 'indentOutdent', items: indentItems },
463
- ].filter((g) => g.items.length > 0);
464
- const movableGroupKeys = useMemo(() => movableGroups.map((g) => g.key).join(','), [movableGroups]);
465
- // Re-measure whenever the container is resized, or whenever the set of
466
- // movable groups (and so the hidden clone's content) changes.
467
- useLayoutEffect(() => {
468
- const container = toolbarContainerRef.current;
469
- const measure = toolbarMeasureRef.current;
470
- if (!container || !measure)
471
- return;
472
- // Rough width of the "..." button itself (icon button + its gap) —
473
- // only charged against the budget once something has actually moved
474
- // into it, since it isn't rendered at all otherwise.
475
- const OVERFLOW_BUTTON_WIDTH = 38;
476
- const recompute = () => {
477
- const available = container.clientWidth;
478
- const total = measure.scrollWidth;
479
- if (total <= available) {
480
- setOverflowGroupKeys((prev) => (prev.size === 0 ? prev : EMPTY_OVERFLOW));
481
- return;
482
- }
483
- const widths = new Map();
484
- measure.querySelectorAll('[data-group-key]').forEach((el) => {
485
- widths.set(el.dataset.groupKey, el.getBoundingClientRect().width);
486
- });
487
- let remaining = total;
488
- const next = new Set();
489
- for (const key of movableGroupKeys.split(',').filter(Boolean)) {
490
- if (remaining <= available)
491
- break;
492
- const w = widths.get(key);
493
- if (w === undefined)
494
- continue;
495
- remaining -= w;
496
- next.add(key);
497
- }
498
- if (next.size > 0)
499
- remaining += OVERFLOW_BUTTON_WIDTH;
500
- setOverflowGroupKeys((prev) => (setsEqual(prev, next) ? prev : next));
501
- };
502
- recompute();
503
- const ro = new ResizeObserver(recompute);
504
- ro.observe(container);
505
- ro.observe(measure);
506
- return () => ro.disconnect();
507
- });
353
+ const showOverflowMenu = fmt.strikethrough || fmt.superscript || fmt.subscript || fmt.caseTransforms || fmt.clearFormatting || config.indent;
508
354
  // Grouped for the dropdown when options declare groups (Latin / Urdu).
509
355
  const fontData = (() => {
510
356
  const data = [];
@@ -530,63 +376,33 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
530
376
  const shownFontFamily = state.fontFamily === UNSET_STYLE ? canvasDefaults.family : state.fontFamily;
531
377
  const shownFontSize = state.fontSize === UNSET_STYLE ? canvasDefaults.size : state.fontSize;
532
378
  const formattingOptions = [
533
- { value: 'paragraph', label: strings.toolbar.formattingOptions.paragraph },
534
- ...headingLevels.map((level) => ({ value: `h${level}`, label: strings.toolbar.formattingOptions.heading(level) })),
535
- ...(config.lists.numbered ? [{ value: 'number', label: strings.toolbar.formattingOptions.numberedList }] : []),
536
- ...(config.lists.bullet ? [{ value: 'bullet', label: strings.toolbar.formattingOptions.bulletList }] : []),
537
- ...(config.lists.check ? [{ value: 'check', label: strings.toolbar.formattingOptions.taskList }] : []),
538
- ...(config.blocks.quote ? [{ value: 'quote', label: strings.toolbar.formattingOptions.quote }] : []),
379
+ { value: 'paragraph', label: 'Paragraph' },
380
+ ...headingLevels.map((level) => ({ value: `h${level}`, label: `Heading ${level}` })),
381
+ ...(config.lists.numbered ? [{ value: 'number', label: 'Numbered list' }] : []),
382
+ ...(config.lists.bullet ? [{ value: 'bullet', label: 'Bullet list' }] : []),
383
+ ...(config.lists.check ? [{ value: 'check', label: 'Task list' }] : []),
384
+ ...(config.blocks.quote ? [{ value: 'quote', label: 'Quote' }] : []),
539
385
  ];
540
386
  const alignOptions = [
541
- ...(config.alignment.start ? [{ value: 'start', label: strings.toolbar.alignOptions.start }] : []),
542
- ...(config.alignment.center ? [{ value: 'center', label: strings.toolbar.alignOptions.center }] : []),
543
- ...(config.alignment.start ? [{ value: 'end', label: strings.toolbar.alignOptions.end }] : []),
544
- ...(config.alignment.justify ? [{ value: 'justify', label: strings.toolbar.alignOptions.justify }] : []),
545
- ...(config.alignment.left ? [{ value: 'left', label: strings.toolbar.alignOptions.left }] : []),
546
- ...(config.alignment.right ? [{ value: 'right', label: strings.toolbar.alignOptions.right }] : []),
387
+ ...(config.alignment.start ? [{ value: 'start', label: 'Align start' }] : []),
388
+ ...(config.alignment.center ? [{ value: 'center', label: 'Align center' }] : []),
389
+ ...(config.alignment.start ? [{ value: 'end', label: 'Align end' }] : []),
390
+ ...(config.alignment.justify ? [{ value: 'justify', label: 'Justify' }] : []),
391
+ ...(config.alignment.left ? [{ value: 'left', label: 'Align left' }] : []),
392
+ ...(config.alignment.right ? [{ value: 'right', label: 'Align right' }] : []),
547
393
  ];
548
394
  const formattingValue = state.listType ?? state.blockType;
549
395
  const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
550
- // The toolbar's "always inline" groups (UI spec §3.3's Desktop tier) —
551
- // built as an array so the exact same elements can be rendered both in
552
- // the visible toolbar and in the hidden width-measuring clone below,
553
- // without duplicating the JSX. The link right-click context menu is kept
554
- // out of this list: it's a zero-size, fixed-position anchor, not part of
555
- // the toolbar's own layout width.
556
- const fixedGroups = [
557
- showSave && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) }, "save")),
558
- config.history && (_jsxs("div", { className: "likhari-toolbar-group", children: [_jsx(ToolbarButton, { icon: IconArrowBackUp, title: strings.toolbar.undo, disabled: !state.canUndo, onClick: () => editor.dispatchCommand(UNDO_COMMAND, undefined) }), _jsx(ToolbarButton, { icon: IconArrowForwardUp, title: strings.toolbar.redo, disabled: !state.canRedo, onClick: () => editor.dispatchCommand(REDO_COMMAND, undefined) })] }, "history")),
559
- showFormattingGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: IconPilcrow, label: strings.toolbar.formattingLabel, width: 148, value: formattingValue, data: formattingOptions, comingSoon: strings.toolbar.comingSoon, onChange: withRefocus((v) => applyFormatting(v)) }) }, "formatting")),
560
- showInlineGroup && (_jsxs("div", { className: "likhari-toolbar-group", children: [fmt.bold && (_jsx(ToolbarButton, { icon: IconBold, title: strings.toolbar.bold, active: state.activeFormats.has('bold'), onClick: () => formatText('bold') })), fmt.italic && (_jsx(ToolbarButton, { icon: IconItalic, title: strings.toolbar.italic, active: state.activeFormats.has('italic'), onClick: () => formatText('italic') })), fmt.underline && (_jsx(ToolbarButton, { icon: IconUnderline, title: strings.toolbar.underline, active: state.activeFormats.has('underline'), onClick: () => formatText('underline') }))] }, "inline")),
561
- (config.font.family || config.font.size) && (_jsxs("div", { className: "likhari-toolbar-group", children: [config.font.family && (_jsx(ToolbarSelect, { icon: IconTypography, label: strings.toolbar.fontFamily, width: 150, value: fontOptions.some((f) => f.family === shownFontFamily) ? shownFontFamily : null, placeholder: strings.toolbar.fontFamilyPlaceholder, data: fontData, searchable: true, noMatchMessage: strings.toolbar.noMatch, renderOption: ({ option }) => _jsx("span", { style: { fontFamily: option.value }, children: option.label }), onChange: withRefocus((v) => applyFont('font-family', v)) })), config.font.size && (_jsx(ToolbarSelect, { icon: IconTextSize, label: strings.toolbar.fontSize, width: 84, value: FONT_SIZES_PX.some((px) => `${px}px` === shownFontSize) ? shownFontSize : null, placeholder: strings.toolbar.fontSizePlaceholder, data: FONT_SIZES_PX.map((px) => ({ value: `${px}px`, label: String(px) })), onChange: withRefocus((v) => applyFont('font-size', v)) }))] }, "font")),
562
- showAlignGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: AlignIcon, label: strings.toolbar.alignment, width: 138, value: state.elementFormat || 'start', data: alignOptions, onChange: withRefocus((v) => formatElement(v)) }) }, "align")),
563
- config.links && (_jsx("div", { className: "likhari-toolbar-group", children: state.isLink ? (
564
- // Caret is in a link: the button opens a menu to see, edit or remove it.
565
- _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.linkOptions, title: strings.toolbar.linkOptions, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconLink, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: state.linkUrl, onEdit: () => {
566
- restoreSelection();
567
- openLinkDialog();
568
- }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
569
- 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.poetry.enabled && _jsx(StubButton, { icon: IconFeather, title: strings.toolbar.poetryBlocks, comingSoon: strings.toolbar.comingSoon })] }, "stubInsert")),
570
- 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")),
571
- ];
572
- return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
573
- .filter((group) => !overflowGroupKeys.has(group.key))
574
- .map((group) => (_jsx(MovableGroupButtons, { group: group }, group.key))), overflowGroupKeys.size > 0 && (_jsxs(Menu, { position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "aria-label": strings.toolbar.moreFormatting, title: strings.toolbar.moreFormatting, onMouseDown: (e) => e.preventDefault(), children: _jsx(IconDots, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: movableGroups
575
- .filter((group) => overflowGroupKeys.has(group.key))
576
- .flatMap((group) => group.items.map((item) => (_jsx(OverflowItem, { icon: item.icon, label: item.label, active: item.active, onClick: runOverflowAction(item.onClick) }, item.key)))) })] })), config.links && (_jsxs(Menu, { opened: linkContext !== null, onChange: (opened) => {
396
+ return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": "Formatting", children: [showSave && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: "Save", dirty: Boolean(isDirty), onClick: onSave }) })), 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 && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: IconPilcrow, label: "Formatting", width: 148, value: formattingValue, data: formattingOptions, onChange: withRefocus((v) => applyFormatting(v)) }) })), 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", children: [config.font.family && (_jsx(ToolbarSelect, { icon: IconTypography, label: "Font family", width: 150, value: fontOptions.some((f) => f.family === shownFontFamily) ? shownFontFamily : null, placeholder: "Font", data: fontData, searchable: true, renderOption: ({ option }) => _jsx("span", { style: { fontFamily: option.value }, children: option.label }), onChange: withRefocus((v) => applyFont('font-family', v)) })), config.font.size && (_jsx(ToolbarSelect, { icon: IconTextSize, label: "Font size", width: 84, value: FONT_SIZES_PX.some((px) => `${px}px` === shownFontSize) ? shownFontSize : null, placeholder: "Size", data: FONT_SIZES_PX.map((px) => ({ value: `${px}px`, label: String(px) })), onChange: withRefocus((v) => applyFont('font-size', v)) }))] })), showAlignGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: AlignIcon, label: "Alignment", width: 138, value: state.elementFormat || 'start', data: alignOptions, onChange: withRefocus((v) => formatElement(v)) }) })), config.links && (_jsx("div", { className: "likhari-toolbar-group", children: state.isLink ? (
397
+ // Caret is in a link: the button opens a menu to see, edit or remove it.
398
+ _jsxs(Menu, { position: "bottom-start", withinPortal: true, 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": "Link options", title: "Link options", onMouseDown: (e) => e.preventDefault(), children: _jsx(IconLink, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: state.linkUrl, onEdit: () => {
399
+ restoreSelection();
400
+ openLinkDialog();
401
+ }, onRemove: removeLink }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: "Insert link (Ctrl+K)", onClick: openLinkDialog })) })), config.links && (_jsxs(Menu, { opened: linkContext !== null, onChange: (opened) => {
577
402
  if (!opened)
578
403
  setLinkContext(null);
579
- }, 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: () => {
404
+ }, position: "bottom-start", withinPortal: true, 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: () => {
580
405
  restoreSelection();
581
406
  openLinkDialog();
582
- }, onRemove: removeLink, strings: strings }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
583
- position: 'absolute',
584
- visibility: 'hidden',
585
- pointerEvents: 'none',
586
- top: 0,
587
- left: 0,
588
- display: 'flex',
589
- flexWrap: 'nowrap',
590
- gap: 8,
591
- }, 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 }))] }));
407
+ }, onRemove: removeLink }) })] })), showStubInsertGroup && (_jsxs("div", { className: "likhari-toolbar-group likhari-toolbar-group--collapse-tablet", children: [(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(Menu, { position: "bottom-end", withinPortal: true, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx("button", { type: "button", className: "likhari-toolbar-button", "aria-label": "More formatting", title: "More formatting", onMouseDown: (e) => e.preventDefault(), children: _jsx(IconDots, { size: ICON_SIZE, stroke: ICON_STROKE }) }) }), _jsxs(Menu.Dropdown, { children: [fmt.strikethrough && (_jsx(OverflowItem, { icon: IconStrikethrough, label: "Strikethrough", active: state.activeFormats.has('strikethrough'), onClick: runOverflowAction(() => formatText('strikethrough')) })), fmt.superscript && (_jsx(OverflowItem, { icon: IconSuperscript, label: "Superscript", active: state.activeFormats.has('superscript'), onClick: runOverflowAction(() => formatText('superscript')) })), fmt.subscript && (_jsx(OverflowItem, { icon: IconSubscript, label: "Subscript", active: state.activeFormats.has('subscript'), onClick: runOverflowAction(() => formatText('subscript')) })), fmt.caseTransforms && (_jsxs(_Fragment, { children: [_jsx(OverflowItem, { icon: IconLetterCaseUpper, label: "UPPERCASE", onClick: runOverflowAction(() => applyCaseTransform('upper')) }), _jsx(OverflowItem, { icon: IconLetterCaseLower, label: "lowercase", onClick: runOverflowAction(() => applyCaseTransform('lower')) }), _jsx(OverflowItem, { icon: IconLetterCase, label: "Capitalize", onClick: runOverflowAction(() => applyCaseTransform('capitalize')) })] })), fmt.clearFormatting && _jsx(OverflowItem, { icon: IconClearFormatting, label: "Clear formatting", onClick: runOverflowAction(clearFormatting) }), config.indent && (_jsxs(_Fragment, { children: [_jsx(OverflowItem, { icon: IconIndentDecrease, label: "Outdent", onClick: runOverflowAction(() => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined)) }), _jsx(OverflowItem, { icon: IconIndentIncrease, label: "Indent", onClick: runOverflowAction(() => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined)) })] }))] })] })), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog }))] }));
592
408
  }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  export { EditorRoot } from './EditorRoot.js';
2
2
  export type { EditorRootProps, EditorRef, EditorInitialContent } from './EditorRoot.js';
3
3
  export { defaultMantineTheme } from './theme/mantineTheme.js';
4
- export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
5
- export type { ImagePayload, ImageLinkType, SerializedImageNode } from './image/ImageNode.js';
6
4
  export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
7
5
  export type { FontOption } from './fonts/index.js';
8
- export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
9
- export type { Locale, Strings } from './i18n/index.js';
10
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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;AAC3D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACzF,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC"}
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;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,3 @@
1
1
  export { EditorRoot } from './EditorRoot.js';
2
2
  export { defaultMantineTheme } from './theme/mantineTheme.js';
3
- export { ImageNode, $createImageNode, $isImageNode } from './image/ImageNode.js';
4
3
  export { DEFAULT_FONT_OPTIONS, URDU_WEB_FONT_OPTIONS, FONT_SIZES_PX } from './fonts/index.js';
5
- export { STRINGS, getStrings, useStrings, useUiStrings, UiStringsContext } from './i18n/index.js';
@@ -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;AAMlD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,EAO5C,CAAC"}
package/dist/nodes.js CHANGED
@@ -2,7 +2,6 @@ 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 { ImageNode } from './image/ImageNode.js';
6
5
  /**
7
6
  * Node types registered on the editor at construction time. Per
8
7
  * editor-architecture-design.md §2, node registration cannot be conditional
@@ -19,5 +18,4 @@ export const EDITOR_NODES = [
19
18
  ListItemNode,
20
19
  LinkNode,
21
20
  HorizontalRuleNode,
22
- ImageNode,
23
21
  ];