@inshapardaz/likhari-react 0.1.6 → 0.1.7-dev.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/EditorRoot.d.ts +25 -1
- package/dist/EditorRoot.d.ts.map +1 -1
- package/dist/EditorRoot.js +24 -5
- package/dist/PortalTargetContext.d.ts +16 -0
- package/dist/PortalTargetContext.d.ts.map +1 -0
- package/dist/PortalTargetContext.js +18 -0
- package/dist/components/LinkDialog.d.ts.map +1 -1
- package/dist/components/LinkDialog.js +7 -3
- package/dist/components/Toolbar.d.ts +4 -1
- package/dist/components/Toolbar.d.ts.map +1 -1
- package/dist/components/Toolbar.js +246 -33
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/strings.d.ts +158 -0
- package/dist/i18n/strings.d.ts.map +1 -0
- package/dist/i18n/strings.js +425 -0
- package/dist/i18n/useStrings.d.ts +10 -0
- package/dist/i18n/useStrings.d.ts.map +1 -0
- package/dist/i18n/useStrings.js +14 -0
- package/dist/image/ImageComponent.d.ts +23 -0
- package/dist/image/ImageComponent.d.ts.map +1 -0
- package/dist/image/ImageComponent.js +171 -0
- package/dist/image/ImageCropDialog.d.ts +22 -0
- package/dist/image/ImageCropDialog.d.ts.map +1 -0
- package/dist/image/ImageCropDialog.js +120 -0
- package/dist/image/ImageCropper.d.ts +15 -0
- package/dist/image/ImageCropper.d.ts.map +1 -0
- package/dist/image/ImageCropper.js +120 -0
- package/dist/image/ImageDialog.d.ts +37 -0
- package/dist/image/ImageDialog.d.ts.map +1 -0
- package/dist/image/ImageDialog.js +176 -0
- package/dist/image/ImageNode.d.ts +64 -0
- package/dist/image/ImageNode.d.ts.map +1 -0
- package/dist/image/ImageNode.js +133 -0
- package/dist/image/ImageOptionsContext.d.ts +24 -0
- package/dist/image/ImageOptionsContext.d.ts.map +1 -0
- package/dist/image/ImageOptionsContext.js +10 -0
- package/dist/image/cropMath.d.ts +35 -0
- package/dist/image/cropMath.d.ts.map +1 -0
- package/dist/image/cropMath.js +92 -0
- package/dist/image/imageEdit.d.ts +49 -0
- package/dist/image/imageEdit.d.ts.map +1 -0
- package/dist/image/imageEdit.js +132 -0
- package/dist/image/imageUrl.d.ts +14 -0
- package/dist/image/imageUrl.d.ts.map +1 -0
- package/dist/image/imageUrl.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +2 -0
- package/dist/theme/EditorThemeProvider.d.ts +17 -2
- package/dist/theme/EditorThemeProvider.d.ts.map +1 -1
- package/dist/theme/EditorThemeProvider.js +27 -3
- package/dist/theme/accentColor.d.ts +43 -0
- package/dist/theme/accentColor.d.ts.map +1 -0
- package/dist/theme/accentColor.js +84 -0
- package/dist/theme/editor.css +125 -0
- package/dist/theme/editorTheme.d.ts.map +1 -1
- package/dist/theme/editorTheme.js +1 -0
- package/package.json +9 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, 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,13 +7,17 @@ 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 } from '@lexical/utils';
|
|
10
|
+
import { $findMatchingParent, $insertNodeToNearestRoot } from '@lexical/utils';
|
|
11
|
+
import { ImageDialog } from '../image/ImageDialog.js';
|
|
12
|
+
import { $createImageNode } from '../image/ImageNode.js';
|
|
11
13
|
import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link';
|
|
12
14
|
import { LinkDialog } from './LinkDialog.js';
|
|
13
15
|
import { normalizeLinkUrl } from '../utils/linkUrl.js';
|
|
14
16
|
import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX } from '../fonts/index.js';
|
|
15
17
|
import { setStyleProperty } from '../utils/style.js';
|
|
16
|
-
import {
|
|
18
|
+
import { useStrings } from '../i18n/useStrings.js';
|
|
19
|
+
import { usePortalTarget } from '../PortalTargetContext.js';
|
|
20
|
+
import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
|
|
17
21
|
/** Sentinel for "no inline style": Lexical returns the default it is given when
|
|
18
22
|
* the selection has none, so this tells "unstyled" (show the canvas default)
|
|
19
23
|
* apart from "styled with a value the dropdown doesn't list" (show nothing). */
|
|
@@ -40,6 +44,35 @@ const ALIGN_ICONS = {
|
|
|
40
44
|
right: IconAlignRight,
|
|
41
45
|
justify: IconAlignJustified,
|
|
42
46
|
};
|
|
47
|
+
const HEADING_ICONS = {
|
|
48
|
+
1: IconH1,
|
|
49
|
+
2: IconH2,
|
|
50
|
+
3: IconH3,
|
|
51
|
+
4: IconH4,
|
|
52
|
+
5: IconH5,
|
|
53
|
+
6: IconH6,
|
|
54
|
+
};
|
|
55
|
+
const FORMATTING_ICONS = {
|
|
56
|
+
paragraph: IconPilcrow,
|
|
57
|
+
h1: HEADING_ICONS[1],
|
|
58
|
+
h2: HEADING_ICONS[2],
|
|
59
|
+
h3: HEADING_ICONS[3],
|
|
60
|
+
h4: HEADING_ICONS[4],
|
|
61
|
+
h5: HEADING_ICONS[5],
|
|
62
|
+
h6: HEADING_ICONS[6],
|
|
63
|
+
number: IconListNumbers,
|
|
64
|
+
bullet: IconList,
|
|
65
|
+
check: IconListCheck,
|
|
66
|
+
quote: IconQuote,
|
|
67
|
+
};
|
|
68
|
+
/** Shared option renderer for the formatting/alignment dropdowns: an icon
|
|
69
|
+
* ahead of the label, looked up by the option's own value. */
|
|
70
|
+
function iconOptionRenderer(icons, fallback) {
|
|
71
|
+
return ({ option }) => {
|
|
72
|
+
const Icon = icons[option.value] ?? fallback;
|
|
73
|
+
return (_jsxs("span", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(Icon, { size: 16, stroke: ICON_STROKE }), option.label] }));
|
|
74
|
+
};
|
|
75
|
+
}
|
|
43
76
|
function ToolbarButton({ icon: Icon, title, active, dirty, disabled, onClick, }) {
|
|
44
77
|
return (_jsx("button", { type: "button", className: "likhari-toolbar-button", "data-active": active ? 'true' : 'false', "data-dirty": dirty === undefined ? undefined : dirty ? 'true' : 'false', disabled: disabled, "aria-pressed": active, "aria-label": title, title: title, onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }) }));
|
|
45
78
|
}
|
|
@@ -47,15 +80,30 @@ function ToolbarButton({ icon: Icon, title, active, dirty, disabled, onClick, })
|
|
|
47
80
|
* isn't implemented yet (link/image/poetry/font/language-tooling — see
|
|
48
81
|
* docs/lexical-editor-spec.md §13's phasing). Renders so the toolbar's
|
|
49
82
|
* layout is final now and only needs its onClick wired up later. */
|
|
50
|
-
function StubButton({ icon, title }) {
|
|
51
|
-
return _jsx(ToolbarButton, { icon: icon, title:
|
|
83
|
+
function StubButton({ icon, title, comingSoon }) {
|
|
84
|
+
return _jsx(ToolbarButton, { icon: icon, title: comingSoon(title), disabled: true, onClick: undefined });
|
|
52
85
|
}
|
|
53
86
|
/** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
|
|
54
87
|
* labels the control itself, since a dropdown can't show one per option in
|
|
55
88
|
* its closed state. The dropdown portals to <body>, so it is not clipped by
|
|
56
89
|
* the editor's `overflow: hidden` frame. */
|
|
57
|
-
function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange }) {
|
|
58
|
-
|
|
90
|
+
function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange, comingSoon, noMatchMessage }) {
|
|
91
|
+
const portalTarget = usePortalTarget();
|
|
92
|
+
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) }));
|
|
93
|
+
}
|
|
94
|
+
const EMPTY_OVERFLOW = new Set();
|
|
95
|
+
function setsEqual(a, b) {
|
|
96
|
+
if (a.size !== b.size)
|
|
97
|
+
return false;
|
|
98
|
+
for (const v of a)
|
|
99
|
+
if (!b.has(v))
|
|
100
|
+
return false;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
/** Renders a movable group's items as normal inline toolbar buttons (used
|
|
104
|
+
* both in the real toolbar and in the hidden width-measuring clone). */
|
|
105
|
+
function MovableGroupButtons({ group }) {
|
|
106
|
+
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))) }));
|
|
59
107
|
}
|
|
60
108
|
function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
61
109
|
return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', className: "likhari-overflow-item",
|
|
@@ -64,16 +112,27 @@ function OverflowItem({ icon: Icon, label, active, onClick, }) {
|
|
|
64
112
|
}
|
|
65
113
|
/** The link menu's contents — shared by the toolbar button and the right-click
|
|
66
114
|
* menu: the URL (opens in a new tab), Edit link, Remove link. */
|
|
67
|
-
function LinkMenuItems({ url, onEdit, onRemove }) {
|
|
115
|
+
function LinkMenuItems({ url, onEdit, onRemove, strings }) {
|
|
68
116
|
// Only offer the link as clickable if it's a URL the editor would have accepted.
|
|
69
117
|
const safeUrl = normalizeLinkUrl(url);
|
|
70
|
-
return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children:
|
|
118
|
+
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 })) : (
|
|
71
119
|
// Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
|
|
72
|
-
_jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url ||
|
|
120
|
+
_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 })] }));
|
|
73
121
|
}
|
|
74
|
-
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr' }) {
|
|
122
|
+
export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en' }) {
|
|
75
123
|
const [editor] = useLexicalComposerContext();
|
|
76
124
|
const [state, setState] = useState(INITIAL_STATE);
|
|
125
|
+
const strings = useStrings(locale);
|
|
126
|
+
const portalTarget = usePortalTarget();
|
|
127
|
+
// Responsive "priority+" overflow: the toolbar's outer container and a
|
|
128
|
+
// hidden nowrap clone (rendered with every movable group forced inline)
|
|
129
|
+
// are both measured; whenever the clone's natural width exceeds the
|
|
130
|
+
// container's available width, the lowest-priority movable groups are
|
|
131
|
+
// moved into the "..." menu, lowest-priority first, until what remains
|
|
132
|
+
// inline fits (or there's nothing left to move).
|
|
133
|
+
const toolbarContainerRef = useRef(null);
|
|
134
|
+
const toolbarMeasureRef = useRef(null);
|
|
135
|
+
const [overflowGroupKeys, setOverflowGroupKeys] = useState(EMPTY_OVERFLOW);
|
|
77
136
|
const updateToolbar = useCallback(() => {
|
|
78
137
|
editor.getEditorState().read(() => {
|
|
79
138
|
const selection = $getSelection();
|
|
@@ -195,6 +254,25 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
195
254
|
action();
|
|
196
255
|
editor.focus();
|
|
197
256
|
};
|
|
257
|
+
const [imageDialogOpen, setImageDialogOpen] = useState(false);
|
|
258
|
+
const openImageDialog = () => {
|
|
259
|
+
// Snapshot first: the dialog's focus trap makes Lexical drop the selection.
|
|
260
|
+
snapshotSelection();
|
|
261
|
+
setImageDialogOpen(true);
|
|
262
|
+
};
|
|
263
|
+
const closeImageDialog = () => {
|
|
264
|
+
setImageDialogOpen(false);
|
|
265
|
+
editor.focus();
|
|
266
|
+
};
|
|
267
|
+
const insertImage = (image) => {
|
|
268
|
+
const saved = menuSelectionRef.current;
|
|
269
|
+
if (saved)
|
|
270
|
+
editor.update(() => $setSelection(saved.clone()), { discrete: true });
|
|
271
|
+
editor.update(() => {
|
|
272
|
+
$insertNodeToNearestRoot($createImageNode(image));
|
|
273
|
+
});
|
|
274
|
+
closeImageDialog();
|
|
275
|
+
};
|
|
198
276
|
/** Applies a font property per `config.font.scope`: to the selection, to
|
|
199
277
|
* every text node in the document, or (for 'both') to the selection when
|
|
200
278
|
* there is a range and to the whole document when there isn't. */
|
|
@@ -350,7 +428,112 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
350
428
|
const showAlignGroup = config.alignment.start || config.alignment.center || config.alignment.justify || config.alignment.left || config.alignment.right;
|
|
351
429
|
const showStubInsertGroup = config.images.linked || config.images.embedded || config.poetry.enabled;
|
|
352
430
|
const showLanguageGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck;
|
|
353
|
-
|
|
431
|
+
// The "script & cleanup" and "indent/outdent" groups (UI spec §3.3) render
|
|
432
|
+
// as normal inline toolbar groups whenever there's room, and only move
|
|
433
|
+
// into the "..." overflow menu when the measured toolbar content doesn't
|
|
434
|
+
// fit the available width (see the ResizeObserver effect below) — this
|
|
435
|
+
// replaces the old always-on overflow menu, which was driven purely by
|
|
436
|
+
// these config flags regardless of actual space.
|
|
437
|
+
const scriptCleanupItems = [];
|
|
438
|
+
if (fmt.strikethrough) {
|
|
439
|
+
scriptCleanupItems.push({
|
|
440
|
+
key: 'strikethrough',
|
|
441
|
+
icon: IconStrikethrough,
|
|
442
|
+
label: strings.toolbar.strikethrough,
|
|
443
|
+
active: state.activeFormats.has('strikethrough'),
|
|
444
|
+
onClick: () => formatText('strikethrough'),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (fmt.superscript) {
|
|
448
|
+
scriptCleanupItems.push({
|
|
449
|
+
key: 'superscript',
|
|
450
|
+
icon: IconSuperscript,
|
|
451
|
+
label: strings.toolbar.superscript,
|
|
452
|
+
active: state.activeFormats.has('superscript'),
|
|
453
|
+
onClick: () => formatText('superscript'),
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
if (fmt.subscript) {
|
|
457
|
+
scriptCleanupItems.push({
|
|
458
|
+
key: 'subscript',
|
|
459
|
+
icon: IconSubscript,
|
|
460
|
+
label: strings.toolbar.subscript,
|
|
461
|
+
active: state.activeFormats.has('subscript'),
|
|
462
|
+
onClick: () => formatText('subscript'),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (fmt.caseTransforms) {
|
|
466
|
+
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') });
|
|
467
|
+
}
|
|
468
|
+
if (fmt.clearFormatting) {
|
|
469
|
+
scriptCleanupItems.push({ key: 'clear', icon: IconClearFormatting, label: strings.toolbar.clearFormatting, onClick: clearFormatting });
|
|
470
|
+
}
|
|
471
|
+
const indentItems = config.indent
|
|
472
|
+
? [
|
|
473
|
+
{
|
|
474
|
+
key: 'outdent',
|
|
475
|
+
icon: IconIndentDecrease,
|
|
476
|
+
label: strings.toolbar.outdent,
|
|
477
|
+
onClick: () => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined),
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
key: 'indent',
|
|
481
|
+
icon: IconIndentIncrease,
|
|
482
|
+
label: strings.toolbar.indent,
|
|
483
|
+
onClick: () => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined),
|
|
484
|
+
},
|
|
485
|
+
]
|
|
486
|
+
: [];
|
|
487
|
+
// Low to high priority: the first group here is the first one pushed into
|
|
488
|
+
// overflow once content stops fitting (UI spec §3.3).
|
|
489
|
+
const movableGroups = [
|
|
490
|
+
{ key: 'scriptCleanup', items: scriptCleanupItems },
|
|
491
|
+
{ key: 'indentOutdent', items: indentItems },
|
|
492
|
+
].filter((g) => g.items.length > 0);
|
|
493
|
+
const movableGroupKeys = useMemo(() => movableGroups.map((g) => g.key).join(','), [movableGroups]);
|
|
494
|
+
// Re-measure whenever the container is resized, or whenever the set of
|
|
495
|
+
// movable groups (and so the hidden clone's content) changes.
|
|
496
|
+
useLayoutEffect(() => {
|
|
497
|
+
const container = toolbarContainerRef.current;
|
|
498
|
+
const measure = toolbarMeasureRef.current;
|
|
499
|
+
if (!container || !measure)
|
|
500
|
+
return;
|
|
501
|
+
// Rough width of the "..." button itself (icon button + its gap) —
|
|
502
|
+
// only charged against the budget once something has actually moved
|
|
503
|
+
// into it, since it isn't rendered at all otherwise.
|
|
504
|
+
const OVERFLOW_BUTTON_WIDTH = 38;
|
|
505
|
+
const recompute = () => {
|
|
506
|
+
const available = container.clientWidth;
|
|
507
|
+
const total = measure.scrollWidth;
|
|
508
|
+
if (total <= available) {
|
|
509
|
+
setOverflowGroupKeys((prev) => (prev.size === 0 ? prev : EMPTY_OVERFLOW));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const widths = new Map();
|
|
513
|
+
measure.querySelectorAll('[data-group-key]').forEach((el) => {
|
|
514
|
+
widths.set(el.dataset.groupKey, el.getBoundingClientRect().width);
|
|
515
|
+
});
|
|
516
|
+
let remaining = total;
|
|
517
|
+
const next = new Set();
|
|
518
|
+
for (const key of movableGroupKeys.split(',').filter(Boolean)) {
|
|
519
|
+
if (remaining <= available)
|
|
520
|
+
break;
|
|
521
|
+
const w = widths.get(key);
|
|
522
|
+
if (w === undefined)
|
|
523
|
+
continue;
|
|
524
|
+
remaining -= w;
|
|
525
|
+
next.add(key);
|
|
526
|
+
}
|
|
527
|
+
if (next.size > 0)
|
|
528
|
+
remaining += OVERFLOW_BUTTON_WIDTH;
|
|
529
|
+
setOverflowGroupKeys((prev) => (setsEqual(prev, next) ? prev : next));
|
|
530
|
+
};
|
|
531
|
+
recompute();
|
|
532
|
+
const ro = new ResizeObserver(recompute);
|
|
533
|
+
ro.observe(container);
|
|
534
|
+
ro.observe(measure);
|
|
535
|
+
return () => ro.disconnect();
|
|
536
|
+
});
|
|
354
537
|
// Grouped for the dropdown when options declare groups (Latin / Urdu).
|
|
355
538
|
const fontData = (() => {
|
|
356
539
|
const data = [];
|
|
@@ -376,33 +559,63 @@ export function Toolbar({ config, onSave, isDirty, showSave, fontOptions = DEFAU
|
|
|
376
559
|
const shownFontFamily = state.fontFamily === UNSET_STYLE ? canvasDefaults.family : state.fontFamily;
|
|
377
560
|
const shownFontSize = state.fontSize === UNSET_STYLE ? canvasDefaults.size : state.fontSize;
|
|
378
561
|
const formattingOptions = [
|
|
379
|
-
{ value: 'paragraph', label:
|
|
380
|
-
...headingLevels.map((level) => ({ value: `h${level}`, label:
|
|
381
|
-
...(config.lists.numbered ? [{ value: 'number', label:
|
|
382
|
-
...(config.lists.bullet ? [{ value: 'bullet', label:
|
|
383
|
-
...(config.lists.check ? [{ value: 'check', label:
|
|
384
|
-
...(config.blocks.quote ? [{ value: 'quote', label:
|
|
562
|
+
{ value: 'paragraph', label: strings.toolbar.formattingOptions.paragraph },
|
|
563
|
+
...headingLevels.map((level) => ({ value: `h${level}`, label: strings.toolbar.formattingOptions.heading(level) })),
|
|
564
|
+
...(config.lists.numbered ? [{ value: 'number', label: strings.toolbar.formattingOptions.numberedList }] : []),
|
|
565
|
+
...(config.lists.bullet ? [{ value: 'bullet', label: strings.toolbar.formattingOptions.bulletList }] : []),
|
|
566
|
+
...(config.lists.check ? [{ value: 'check', label: strings.toolbar.formattingOptions.taskList }] : []),
|
|
567
|
+
...(config.blocks.quote ? [{ value: 'quote', label: strings.toolbar.formattingOptions.quote }] : []),
|
|
385
568
|
];
|
|
386
569
|
const alignOptions = [
|
|
387
|
-
...(config.alignment.start ? [{ value: 'start', label:
|
|
388
|
-
...(config.alignment.center ? [{ value: 'center', label:
|
|
389
|
-
...(config.alignment.start ? [{ value: 'end', label:
|
|
390
|
-
...(config.alignment.justify ? [{ value: 'justify', label:
|
|
391
|
-
...(config.alignment.left ? [{ value: 'left', label:
|
|
392
|
-
...(config.alignment.right ? [{ value: 'right', label:
|
|
570
|
+
...(config.alignment.start ? [{ value: 'start', label: strings.toolbar.alignOptions.start }] : []),
|
|
571
|
+
...(config.alignment.center ? [{ value: 'center', label: strings.toolbar.alignOptions.center }] : []),
|
|
572
|
+
...(config.alignment.start ? [{ value: 'end', label: strings.toolbar.alignOptions.end }] : []),
|
|
573
|
+
...(config.alignment.justify ? [{ value: 'justify', label: strings.toolbar.alignOptions.justify }] : []),
|
|
574
|
+
...(config.alignment.left ? [{ value: 'left', label: strings.toolbar.alignOptions.left }] : []),
|
|
575
|
+
...(config.alignment.right ? [{ value: 'right', label: strings.toolbar.alignOptions.right }] : []),
|
|
393
576
|
];
|
|
394
577
|
const formattingValue = state.listType ?? state.blockType;
|
|
395
578
|
const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
579
|
+
// The toolbar's "always inline" groups (UI spec §3.3's Desktop tier) —
|
|
580
|
+
// built as an array so the exact same elements can be rendered both in
|
|
581
|
+
// the visible toolbar and in the hidden width-measuring clone below,
|
|
582
|
+
// without duplicating the JSX. The link right-click context menu is kept
|
|
583
|
+
// out of this list: it's a zero-size, fixed-position anchor, not part of
|
|
584
|
+
// the toolbar's own layout width.
|
|
585
|
+
const fixedGroups = [
|
|
586
|
+
showSave && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) }, "save")),
|
|
587
|
+
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")),
|
|
588
|
+
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, renderOption: iconOptionRenderer(FORMATTING_ICONS, IconPilcrow), onChange: withRefocus((v) => applyFormatting(v)) }) }, "formatting")),
|
|
589
|
+
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")),
|
|
590
|
+
(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")),
|
|
591
|
+
showAlignGroup && (_jsx("div", { className: "likhari-toolbar-group", children: _jsx(ToolbarSelect, { icon: AlignIcon, label: strings.toolbar.alignment, width: 138, value: state.elementFormat || 'start', data: alignOptions, renderOption: iconOptionRenderer(ALIGN_ICONS, IconAlignLeft), onChange: withRefocus((v) => formatElement(v)) }) }, "align")),
|
|
592
|
+
config.links && (_jsx("div", { className: "likhari-toolbar-group", children: state.isLink ? (
|
|
593
|
+
// Caret is in a link: the button opens a menu to see, edit or remove it.
|
|
594
|
+
_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: () => {
|
|
595
|
+
restoreSelection();
|
|
596
|
+
openLinkDialog();
|
|
597
|
+
}, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog })) }, "link")),
|
|
598
|
+
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")),
|
|
599
|
+
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")),
|
|
600
|
+
];
|
|
601
|
+
return (_jsxs("div", { className: "likhari-toolbar", role: "toolbar", "aria-label": strings.toolbar.ariaLabel, ref: toolbarContainerRef, children: [fixedGroups, movableGroups
|
|
602
|
+
.filter((group) => !overflowGroupKeys.has(group.key))
|
|
603
|
+
.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
|
|
604
|
+
.filter((group) => overflowGroupKeys.has(group.key))
|
|
605
|
+
.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) => {
|
|
402
606
|
if (!opened)
|
|
403
607
|
setLinkContext(null);
|
|
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: () => {
|
|
608
|
+
}, 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: () => {
|
|
405
609
|
restoreSelection();
|
|
406
610
|
openLinkDialog();
|
|
407
|
-
}, onRemove: removeLink
|
|
611
|
+
}, onRemove: removeLink, strings: strings }) })] })), _jsxs("div", { "aria-hidden": "true", ref: toolbarMeasureRef, style: {
|
|
612
|
+
position: 'absolute',
|
|
613
|
+
visibility: 'hidden',
|
|
614
|
+
pointerEvents: 'none',
|
|
615
|
+
top: 0,
|
|
616
|
+
left: 0,
|
|
617
|
+
display: 'flex',
|
|
618
|
+
flexWrap: 'nowrap',
|
|
619
|
+
gap: 8,
|
|
620
|
+
}, 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 }))] }));
|
|
408
621
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI localization strings for the editor's own chrome (toolbar, dialogs,
|
|
3
|
+
* menus, placeholders) — see docs/lexical-editor-spec.md §3.2. This is
|
|
4
|
+
* separate from content direction/fonts (handled in EditorRoot) and from
|
|
5
|
+
* Markdown/HTML/plain-text format translation, which is out of scope here.
|
|
6
|
+
*/
|
|
7
|
+
/** Re-exported from EditorRoot's existing prop type so there is a single
|
|
8
|
+
* source of truth for which locales the editor knows about. */
|
|
9
|
+
export type Locale = 'en' | 'ur' | 'pa-shahmukhi';
|
|
10
|
+
export interface Strings {
|
|
11
|
+
editor: {
|
|
12
|
+
/** Default placeholder shown in an empty editor (overridden by an explicit `placeholder` prop). */
|
|
13
|
+
placeholder: string;
|
|
14
|
+
/** aria-label on the contenteditable itself. */
|
|
15
|
+
contentLabel: string;
|
|
16
|
+
/** window.confirm() prompt shown by EditorRef.confirmDiscard() when there are unsaved changes. */
|
|
17
|
+
confirmDiscard: string;
|
|
18
|
+
};
|
|
19
|
+
common: {
|
|
20
|
+
cancel: string;
|
|
21
|
+
save: string;
|
|
22
|
+
insert: string;
|
|
23
|
+
};
|
|
24
|
+
toolbar: {
|
|
25
|
+
/** aria-label on the toolbar's own role="toolbar" container. */
|
|
26
|
+
ariaLabel: string;
|
|
27
|
+
save: string;
|
|
28
|
+
undo: string;
|
|
29
|
+
redo: string;
|
|
30
|
+
formattingLabel: string;
|
|
31
|
+
bold: string;
|
|
32
|
+
italic: string;
|
|
33
|
+
underline: string;
|
|
34
|
+
fontFamily: string;
|
|
35
|
+
fontFamilyPlaceholder: string;
|
|
36
|
+
fontSize: string;
|
|
37
|
+
fontSizePlaceholder: string;
|
|
38
|
+
noMatch: string;
|
|
39
|
+
alignment: string;
|
|
40
|
+
linkOptions: string;
|
|
41
|
+
insertLink: string;
|
|
42
|
+
insertImage: string;
|
|
43
|
+
poetryBlocks: string;
|
|
44
|
+
comingSoon: (label: string) => string;
|
|
45
|
+
autocorrect: string;
|
|
46
|
+
textCleanup: string;
|
|
47
|
+
spellChecker: string;
|
|
48
|
+
moreFormatting: string;
|
|
49
|
+
strikethrough: string;
|
|
50
|
+
superscript: string;
|
|
51
|
+
subscript: string;
|
|
52
|
+
uppercase: string;
|
|
53
|
+
lowercase: string;
|
|
54
|
+
capitalize: string;
|
|
55
|
+
clearFormatting: string;
|
|
56
|
+
outdent: string;
|
|
57
|
+
indent: string;
|
|
58
|
+
formattingOptions: {
|
|
59
|
+
paragraph: string;
|
|
60
|
+
heading: (level: number) => string;
|
|
61
|
+
numberedList: string;
|
|
62
|
+
bulletList: string;
|
|
63
|
+
taskList: string;
|
|
64
|
+
quote: string;
|
|
65
|
+
};
|
|
66
|
+
alignOptions: {
|
|
67
|
+
start: string;
|
|
68
|
+
center: string;
|
|
69
|
+
end: string;
|
|
70
|
+
justify: string;
|
|
71
|
+
left: string;
|
|
72
|
+
right: string;
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
link: {
|
|
76
|
+
menuLabel: string;
|
|
77
|
+
noUrl: string;
|
|
78
|
+
editLink: string;
|
|
79
|
+
removeLink: string;
|
|
80
|
+
};
|
|
81
|
+
linkDialog: {
|
|
82
|
+
titleInsert: string;
|
|
83
|
+
titleEdit: string;
|
|
84
|
+
urlLabel: string;
|
|
85
|
+
urlPlaceholder: string;
|
|
86
|
+
textLabel: string;
|
|
87
|
+
textPlaceholder: string;
|
|
88
|
+
invalidUrl: string;
|
|
89
|
+
apply: string;
|
|
90
|
+
};
|
|
91
|
+
imageDialog: {
|
|
92
|
+
titleInsert: string;
|
|
93
|
+
titleEdit: string;
|
|
94
|
+
currentImage: string;
|
|
95
|
+
fromUrl: string;
|
|
96
|
+
upload: string;
|
|
97
|
+
embeddedInDocument: string;
|
|
98
|
+
linkedFrom: (src: string) => string;
|
|
99
|
+
convertButton: string;
|
|
100
|
+
convertHint: string;
|
|
101
|
+
imageUrlLabel: string;
|
|
102
|
+
imageUrlPlaceholder: string;
|
|
103
|
+
embedCopyLabel: string;
|
|
104
|
+
embedCopyDescription: string;
|
|
105
|
+
convertedFromUrl: string;
|
|
106
|
+
fileLabel: string;
|
|
107
|
+
filePlaceholder: string;
|
|
108
|
+
fileDescription: (maxSizeMB: number, storedInDocument: boolean) => string;
|
|
109
|
+
altTextLabel: string;
|
|
110
|
+
altTextDescription: string;
|
|
111
|
+
captionLabel: string;
|
|
112
|
+
captionDescription: string;
|
|
113
|
+
errors: {
|
|
114
|
+
enterUrl: string;
|
|
115
|
+
chooseFile: string;
|
|
116
|
+
noImage: string;
|
|
117
|
+
uploadUnusable: string;
|
|
118
|
+
couldNotSave: string;
|
|
119
|
+
couldNotConvert: string;
|
|
120
|
+
couldNotReadFile: string;
|
|
121
|
+
largerThan: (maxSizeMB: number) => string;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
imageCropDialog: {
|
|
125
|
+
title: string;
|
|
126
|
+
sizeLabel: string;
|
|
127
|
+
widthLabel: string;
|
|
128
|
+
heightLabel: string;
|
|
129
|
+
autoPlaceholder: string;
|
|
130
|
+
keepProportions: string;
|
|
131
|
+
originalSize: string;
|
|
132
|
+
dragHint: (natural: {
|
|
133
|
+
width: number;
|
|
134
|
+
height: number;
|
|
135
|
+
} | null) => string;
|
|
136
|
+
errors: {
|
|
137
|
+
uploadUnusable: string;
|
|
138
|
+
couldNotSave: string;
|
|
139
|
+
largerThan: (maxSizeMB: number) => string;
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
imageMenu: {
|
|
143
|
+
editImage: string;
|
|
144
|
+
convertToEmbedded: string;
|
|
145
|
+
cropResize: string;
|
|
146
|
+
addCaption: string;
|
|
147
|
+
editCaption: string;
|
|
148
|
+
removeCaption: string;
|
|
149
|
+
deleteImage: string;
|
|
150
|
+
resizeImage: string;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export declare const STRINGS: Record<Locale, Strings>;
|
|
154
|
+
/** Returns the strings dictionary for `locale`, defensively falling back to
|
|
155
|
+
* `en` for any locale not present in `STRINGS` (e.g. a future locale value
|
|
156
|
+
* passed by a caller on an older type definition). */
|
|
157
|
+
export declare function getStrings(locale: Locale): Strings;
|
|
158
|
+
//# sourceMappingURL=strings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
|