@inshapardaz/likhari-react 0.1.32-dev.273 → 0.1.33-dev.276
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA2CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"AA2CA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACjF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgDtF,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAA6D,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAItG,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AA6iBvD,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,2BAA2B,CAAC;IACpC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,gCAAgC;IAChC,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,aAAa,EAAE,CAAC;IACnC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACnE,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC1B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAqB,EAAE,eAAoB,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAgB,EAAE,YAAY,EAAE,SAAiB,EAAE,aAAa,EAAE,eAAuB,EAAE,mBAAmB,EAAE,WAAkC,EAAE,SAAiB,EAAE,MAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BA+rCrV"}
|
|
@@ -65,6 +65,27 @@ const INITIAL_STATE = {
|
|
|
65
65
|
};
|
|
66
66
|
const ICON_SIZE = 17;
|
|
67
67
|
const ICON_STROKE = 1.75;
|
|
68
|
+
/** The CSS Custom Highlight name; editor.css paints it like a native text selection. */
|
|
69
|
+
const SELECTION_HIGHLIGHT = 'likhari-toolbar-selection';
|
|
70
|
+
/** Paints the current native selection through the CSS Custom Highlight API, so it
|
|
71
|
+
* stays visible once a toolbar control (a Select, a Menu, a dialog) takes focus —
|
|
72
|
+
* which, unlike a ToolbarButton's onMouseDown/preventDefault, it must be able to do.
|
|
73
|
+
* The browser clears `window.getSelection()` on blur; this is called at the one
|
|
74
|
+
* moment before that happens (mousedown, or opening a menu/dialog), alongside the
|
|
75
|
+
* Lexical-level selection snapshot these controls already restore from. */
|
|
76
|
+
function paintSelectionHighlight() {
|
|
77
|
+
if (typeof CSS === 'undefined' || !('highlights' in CSS))
|
|
78
|
+
return;
|
|
79
|
+
const domSelection = window.getSelection();
|
|
80
|
+
if (!domSelection || domSelection.isCollapsed || domSelection.rangeCount === 0) {
|
|
81
|
+
CSS.highlights.delete(SELECTION_HIGHLIGHT);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const ranges = [];
|
|
85
|
+
for (let i = 0; i < domSelection.rangeCount; i++)
|
|
86
|
+
ranges.push(domSelection.getRangeAt(i).cloneRange());
|
|
87
|
+
CSS.highlights.set(SELECTION_HIGHLIGHT, new Highlight(...ranges));
|
|
88
|
+
}
|
|
68
89
|
const ALIGN_ICONS_LTR = {
|
|
69
90
|
start: IconAlignLeft,
|
|
70
91
|
left: IconAlignLeft,
|
|
@@ -135,10 +156,17 @@ function setsEqual(a, b) {
|
|
|
135
156
|
/** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
|
|
136
157
|
* labels the control itself, since a dropdown can't show one per option in
|
|
137
158
|
* its closed state. The dropdown portals to <body>, so it is not clipped by
|
|
138
|
-
* the editor's `overflow: hidden` frame.
|
|
139
|
-
|
|
159
|
+
* the editor's `overflow: hidden` frame.
|
|
160
|
+
*
|
|
161
|
+
* Unlike a ToolbarButton, this can't keep focus on the canvas with
|
|
162
|
+
* onMouseDown/preventDefault — a combobox needs focus to open. So picking an
|
|
163
|
+
* option blurs the canvas first, which clears Lexical's selection; `onMouseDown`
|
|
164
|
+
* is the caller's chance to snapshot it (the browser moves focus only after
|
|
165
|
+
* mousedown's listeners run), to restore before applying the change
|
|
166
|
+
* (see Toolbar's `withRefocus`). */
|
|
167
|
+
function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeholder, searchable, renderOption, onChange, onMouseDown, comingSoon, noMatchMessage }) {
|
|
140
168
|
const portalTarget = usePortalTarget();
|
|
141
|
-
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) }));
|
|
169
|
+
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, onMouseDown: onMouseDown, 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) }));
|
|
142
170
|
}
|
|
143
171
|
/** An item in the "..." menu. A mousedown here would blur the canvas, so it is kept. */
|
|
144
172
|
function MoreMenuItem({ icon: Icon, label, active, onClick, }) {
|
|
@@ -345,9 +373,24 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
|
|
|
345
373
|
const selection = $getSelection();
|
|
346
374
|
menuSelectionRef.current = selection ? selection.clone() : null;
|
|
347
375
|
});
|
|
376
|
+
paintSelectionHighlight();
|
|
348
377
|
};
|
|
349
378
|
const snapshotSelectionRef = useRef(snapshotSelection);
|
|
350
379
|
snapshotSelectionRef.current = snapshotSelection;
|
|
380
|
+
// Clears the stand-in highlight once the canvas has a real selection to show again.
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
if (typeof CSS === 'undefined' || !('highlights' in CSS))
|
|
383
|
+
return;
|
|
384
|
+
const clear = () => CSS.highlights.delete(SELECTION_HIGHLIGHT);
|
|
385
|
+
const unregister = editor.registerRootListener((root, previous) => {
|
|
386
|
+
previous?.removeEventListener('focus', clear);
|
|
387
|
+
root?.addEventListener('focus', clear);
|
|
388
|
+
});
|
|
389
|
+
return () => {
|
|
390
|
+
unregister();
|
|
391
|
+
clear();
|
|
392
|
+
};
|
|
393
|
+
}, [editor]);
|
|
351
394
|
const runMenuItem = (action) => () => {
|
|
352
395
|
const saved = menuSelectionRef.current;
|
|
353
396
|
if (saved)
|
|
@@ -610,7 +653,12 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
|
|
|
610
653
|
return true;
|
|
611
654
|
}, COMMAND_PRIORITY_NORMAL);
|
|
612
655
|
}, [editor, config.links, openLinkDialog]);
|
|
656
|
+
// The Select has already blurred the canvas by the time this runs (see
|
|
657
|
+
// ToolbarSelect's doc comment), so the selection snapshotted on its
|
|
658
|
+
// mousedown is restored first, then the canvas regains focus afterward —
|
|
659
|
+
// the combobox otherwise keeps it, so typing after picking would go nowhere.
|
|
613
660
|
const withRefocus = (fn) => (value) => {
|
|
661
|
+
restoreSelection();
|
|
614
662
|
fn(value);
|
|
615
663
|
editor.focus();
|
|
616
664
|
};
|
|
@@ -753,9 +801,9 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
|
|
|
753
801
|
];
|
|
754
802
|
const formattingValue = state.listType ?? state.blockType;
|
|
755
803
|
const AlignIcon = ALIGN_ICONS[state.elementFormat] ?? IconAlignLeft;
|
|
756
|
-
const formatSection = (showFormattingGroup || fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(Fragment, { children: [showFormattingGroup && (_jsx(MantineToolbar.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)) }) })), (fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", 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') })), fmt.strikethrough && (_jsx(ToolbarButton, { icon: IconStrikethrough, title: strings.toolbar.strikethrough, active: state.activeFormats.has('strikethrough'), onClick: () => formatText('strikethrough') })), moreItems.length > 0 && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconDots, title: strings.toolbar.moreFormatting }) }), _jsx(Menu.Dropdown, { children: moreItems.map((item) => (_jsx(MoreMenuItem, { icon: item.icon, label: item.label, active: item.active, onClick: runMenuItem(item.onClick) }, item.key))) })] }))] }))] }, "format"));
|
|
757
|
-
const alignSection = (showAlignGroup || indentItems.length > 0) && (_jsxs(Fragment, { children: [showAlignGroup && (_jsx(MantineToolbar.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)) }) })), indentItems.length > 0 && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: indentItems.map((item) => (_jsx(ToolbarButton, { icon: item.icon, title: item.label, onClick: item.onClick }, item.key))) }))] }, "align"));
|
|
758
|
-
const fontSection = (config.font.family || config.font.size) && (_jsxs(MantineToolbar.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"));
|
|
804
|
+
const formatSection = (showFormattingGroup || fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(Fragment, { children: [showFormattingGroup && (_jsx(MantineToolbar.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), onMouseDown: snapshotSelection, onChange: withRefocus((v) => applyFormatting(v)) }) })), (fmt.bold || fmt.italic || fmt.underline || fmt.strikethrough || moreItems.length > 0) && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", 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') })), fmt.strikethrough && (_jsx(ToolbarButton, { icon: IconStrikethrough, title: strings.toolbar.strikethrough, active: state.activeFormats.has('strikethrough'), onClick: () => formatText('strikethrough') })), moreItems.length > 0 && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 210, closeOnItemClick: true, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconDots, title: strings.toolbar.moreFormatting }) }), _jsx(Menu.Dropdown, { children: moreItems.map((item) => (_jsx(MoreMenuItem, { icon: item.icon, label: item.label, active: item.active, onClick: runMenuItem(item.onClick) }, item.key))) })] }))] }))] }, "format"));
|
|
805
|
+
const alignSection = (showAlignGroup || indentItems.length > 0) && (_jsxs(Fragment, { children: [showAlignGroup && (_jsx(MantineToolbar.Group, { children: _jsx(ToolbarSelect, { icon: AlignIcon, label: strings.toolbar.alignment, width: 138, value: state.elementFormat || 'start', data: alignOptions, renderOption: iconOptionRenderer(ALIGN_ICONS, IconAlignLeft), onMouseDown: snapshotSelection, onChange: withRefocus((v) => formatElement(v)) }) })), indentItems.length > 0 && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: indentItems.map((item) => (_jsx(ToolbarButton, { icon: item.icon, title: item.label, onClick: item.onClick }, item.key))) }))] }, "align"));
|
|
806
|
+
const fontSection = (config.font.family || config.font.size) && (_jsxs(MantineToolbar.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 }), onMouseDown: snapshotSelection, 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) })), onMouseDown: snapshotSelection, onChange: withRefocus((v) => applyFont('font-size', v)) }))] }, "font"));
|
|
759
807
|
const insertSection = showInsertGroup && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [config.links &&
|
|
760
808
|
(state.isLink ? (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.linkOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(LinkMenuItems, { url: state.linkUrl, onEdit: () => {
|
|
761
809
|
restoreSelection();
|
package/dist/theme/editor.css
CHANGED
|
@@ -934,6 +934,13 @@
|
|
|
934
934
|
text-decoration-skip-ink: none;
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
+
/* Stands in for the native text selection while a toolbar control (which must
|
|
938
|
+
take focus, e.g. a Select) has it — otherwise the browser clears it the
|
|
939
|
+
moment focus leaves the canvas, making a selection look lost mid-edit. */
|
|
940
|
+
::highlight(likhari-toolbar-selection) {
|
|
941
|
+
background-color: var(--editor-accent-soft);
|
|
942
|
+
}
|
|
943
|
+
|
|
937
944
|
.likhari-spell-nav {
|
|
938
945
|
display: flex;
|
|
939
946
|
align-items: center;
|