@inshapardaz/likhari-react 0.1.47 → 0.1.48-dev.356

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.
@@ -7,6 +7,12 @@ import { type FontMode, type FontOption } from '../fonts/index.js';
7
7
  import type { Locale } from '../i18n/strings.js';
8
8
  /** Appearance of the toolbar. The accent colour comes from the editor's `accentColor`. */
9
9
  export interface ToolbarStyle {
10
+ /**
11
+ * `classic` is the single row of command groups (UI spec §3.1); `ribbon` puts the same
12
+ * commands on tabs (Home, Insert, Tools, View) with Table, Poetry and Link tabs that
13
+ * appear while the caret is in that kind of content (UI spec §3.4). Default `classic`.
14
+ */
15
+ layout?: 'classic' | 'ribbon';
10
16
  /** Outline the toolbar and each button cluster. Default true. */
11
17
  bordered?: boolean;
12
18
  /** `light` tints the active button, `filled` fills it with the accent. Default `light`. */
@@ -1 +1 @@
1
- {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../../src/components/Toolbar.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { Fragment, forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Menu, Select, Toolbar as MantineToolbar } from '@mantine/core';
4
4
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
5
5
  import { $getSelection, $isRangeSelection, $isElementNode, $setSelection, $getRoot, $selectAll, $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';
@@ -36,7 +36,7 @@ import { CANVAS_FONT_DEFAULTS, DEFAULT_FONT_OPTIONS, FONT_SIZES_PX, ZOOM_LEVELS_
36
36
  import { setStyleProperty } from '../utils/style.js';
37
37
  import { useStrings } from '../i18n/useStrings.js';
38
38
  import { usePortalTarget } from '../PortalTargetContext.js';
39
- import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconArrowsJoin, IconBold, IconClearFormatting, IconDeviceFloppy, IconDots, IconHelpCircle, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconClipboard, IconCopy, IconCut, IconSearch, IconSelectAll, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconArrowsShuffle, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconMinus, IconPlus, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
39
+ import { IconAbc, IconAlignCenter, IconAlignJustified, IconAlignLeft, IconAlignRight, IconArrowBackUp, IconArrowForwardUp, IconArrowsJoin, IconBold, IconClearFormatting, IconDeviceFloppy, IconBook, IconChevronDown, IconChevronUp, IconDots, IconHelpCircle, IconHistory, IconExternalLink, IconFeather, IconH1, IconH2, IconH3, IconH4, IconH5, IconH6, IconIndentDecrease, IconIndentIncrease, IconItalic, IconLetterCase, IconLetterCaseLower, IconLetterCaseUpper, IconLink, IconLinkOff, IconList, IconListCheck, IconListNumbers, IconPageBreak, IconPencil, IconPhoto, IconPilcrow, IconQuote, IconSeparatorHorizontal, IconSparkles, IconStrikethrough, IconSubscript, IconSuperscript, IconArrowMergeBoth, IconArrowsSplit2, IconColumnInsertLeft, IconColumnInsertRight, IconColumnRemove, IconColumns, IconClipboard, IconCopy, IconCut, IconSearch, IconSelectAll, IconColumns1, IconColumns2, IconNumber1Small, IconRowInsertBottom, IconRowInsertTop, IconRowRemove, IconArrowsMaximize, IconArrowsMinimize, IconArrowsShuffle, IconTable, IconTableMinus, IconTrash, IconTableOptions, IconMinus, IconPlus, IconTextSize, IconTypography, IconUnderline, IconWand, } from '@tabler/icons-react';
40
40
  /** Sentinel for "no inline style": Lexical returns the default it is given when
41
41
  * the selection has none, so this tells "unstyled" (show the canvas default)
42
42
  * apart from "styled with a value the dropdown doesn't list" (show nothing). */
@@ -65,6 +65,23 @@ const INITIAL_STATE = {
65
65
  };
66
66
  const ICON_SIZE = 17;
67
67
  const ICON_STROKE = 1.75;
68
+ /** The right-click menu: compact items, and a gap kept between it and the edge of the window. */
69
+ const CONTEXT_MENU_WIDTH = 220;
70
+ const CONTEXT_MENU_VIEWPORT_PADDING = 8;
71
+ /** `likhari-context-menu` (editor.css) shrinks the icons to match the smaller text. */
72
+ const CONTEXT_MENU_CLASS_NAMES = { dropdown: 'likhari-context-menu' };
73
+ /** The submenus: opened to the side, flipped or shifted to stay in the window, and positioned against the window (`fixed`) so
74
+ * the menu's own scrolling does not clip them. */
75
+ const CONTEXT_SUBMENU_PROPS = {
76
+ floatingStrategy: 'fixed',
77
+ middlewares: { flip: true, shift: { crossAxis: true, padding: CONTEXT_MENU_VIEWPORT_PADDING }, size: { padding: CONTEXT_MENU_VIEWPORT_PADDING } },
78
+ };
79
+ const CONTEXT_MENU_STYLES = {
80
+ dropdown: { overflowY: 'auto', padding: 4 },
81
+ item: { fontSize: 'var(--mantine-font-size-xs)', padding: '4px 8px', minHeight: 0 },
82
+ label: { fontSize: 'var(--mantine-font-size-xs)', padding: '2px 8px' },
83
+ divider: { margin: '3px 0' },
84
+ };
68
85
  /** The CSS Custom Highlight name; editor.css paints it like a native text selection. */
69
86
  const SELECTION_HIGHLIGHT = 'likhari-toolbar-selection';
70
87
  /** Paints the current native selection through the CSS Custom Highlight API, so it
@@ -172,27 +189,34 @@ function ToolbarSelect({ icon: Icon, label, value, data, width, disabled, placeh
172
189
  function MoreMenuItem({ icon: Icon, label, active, onClick, }) {
173
190
  return (_jsx(Menu.Item, { leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), "data-active": active ? 'true' : 'false', onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: label }));
174
191
  }
175
- function LinkMenuItems({ url, onEdit, onRemove, strings }) {
192
+ /** One command, as a menu item or as a ribbon button (the label becomes its tooltip and name). */
193
+ function CommandItem({ mode, icon: Icon, label, onClick, disabled, color, }) {
194
+ if (mode === 'ribbon') {
195
+ return _jsx(ToolbarButton, { icon: Icon, title: label, disabled: disabled, "data-danger": color === 'red' ? 'true' : undefined, onClick: onClick });
196
+ }
197
+ return (_jsx(Menu.Item, { color: color, disabled: disabled, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onClick, children: label }));
198
+ }
199
+ /** A group heading in a menu; the ribbon's tab and a submenu's parent item already name the group, so they show nothing. */
200
+ const ItemLabel = ({ mode, children }) => (mode === 'menu' ? _jsx(Menu.Label, { children: children }) : null);
201
+ const ItemDivider = ({ mode }) => (mode === 'ribbon' ? _jsx(MantineToolbar.Divider, {}) : _jsx(Menu.Divider, {}));
202
+ function LinkMenuItems({ url, onEdit, onRemove, strings, mode = 'menu' }) {
176
203
  // Only offer the link as clickable if it's a URL the editor would have accepted.
177
204
  const safeUrl = normalizeLinkUrl(url);
178
- 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 })) : (
205
+ return (_jsxs(_Fragment, { children: [_jsx(ItemLabel, { mode: mode, children: strings.link.menuLabel }), mode === 'ribbon' ? (safeUrl && (_jsx(MantineToolbar.Toggle, { component: "a", href: safeUrl, target: "_blank", rel: "noopener noreferrer", "aria-label": url, title: url, children: _jsx(IconExternalLink, { size: ICON_SIZE, stroke: ICON_STROKE }) }))) : 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 })) : (
179
206
  // Not a URL the editor would accept (e.g. loaded from a document): show it, don't make it clickable.
180
- _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 })] }));
207
+ _jsx(Menu.Item, { disabled: true, title: url, className: "likhari-link-menu-url", children: url || strings.link.noUrl })), _jsx(CommandItem, { mode: mode, icon: IconPencil, label: strings.link.editLink, onClick: onEdit }), _jsx(CommandItem, { mode: mode, icon: IconLinkOff, label: strings.link.removeLink, onClick: onRemove, color: "red" })] }));
181
208
  }
182
209
  /** The table actions menu's contents — shared by the toolbar button and the
183
210
  * right-click menu. Each acts on the selected cell, or on every row/column a
184
211
  * multi-cell selection covers. */
185
- function TableMenuItems({ strings, direction, rows, columns, canMergeCells, canUnmergeCell, onAction, }) {
212
+ function TableMenuItems({ strings, direction, rows, columns, canMergeCells, canUnmergeCell, onAction, mode = 'menu', }) {
186
213
  const t = strings.tableMenu;
187
214
  // "Before" is the start side, which is on the right in an RTL table.
188
215
  const rtl = direction === 'rtl';
189
216
  const manyRows = rows > 1;
190
217
  const manyColumns = columns > 1;
191
- const item = (icon, label, action, color) => {
192
- const Icon = icon;
193
- return (_jsx(Menu.Item, { color: color, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
194
- };
195
- return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), (canMergeCells || canUnmergeCell) && _jsx(Menu.Divider, {}), canMergeCells && item(IconArrowMergeBoth, t.mergeCells, $mergeTableCells), canUnmergeCell && item(IconArrowsSplit2, t.unmergeCell, $unmergeTableCell), _jsx(Menu.Divider, {}), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
218
+ const item = (icon, label, action, color) => (_jsx(CommandItem, { mode: mode, icon: icon, label: label, onClick: onAction(action), color: color }));
219
+ return (_jsxs(_Fragment, { children: [_jsx(ItemLabel, { mode: mode, children: t.menuLabel }), item(IconRowInsertTop, manyRows ? t.insertRowsBefore : t.insertRowBefore, () => $insertTableRows(false)), item(IconRowInsertBottom, manyRows ? t.insertRowsAfter : t.insertRowAfter, () => $insertTableRows(true)), item(rtl ? IconColumnInsertRight : IconColumnInsertLeft, manyColumns ? t.insertColumnsBefore : t.insertColumnBefore, () => $insertTableColumns(false)), item(rtl ? IconColumnInsertLeft : IconColumnInsertRight, manyColumns ? t.insertColumnsAfter : t.insertColumnAfter, () => $insertTableColumns(true)), (canMergeCells || canUnmergeCell) && _jsx(ItemDivider, { mode: mode }), canMergeCells && item(IconArrowMergeBoth, t.mergeCells, $mergeTableCells), canUnmergeCell && item(IconArrowsSplit2, t.unmergeCell, $unmergeTableCell), _jsx(ItemDivider, { mode: mode }), item(IconRowRemove, manyRows ? t.deleteRows : t.deleteRow, $deleteTableRows), item(IconColumnRemove, manyColumns ? t.deleteColumns : t.deleteColumn, $deleteTableColumns), item(IconTableMinus, t.deleteTable, $deleteTable, 'red')] }));
196
220
  }
197
221
  /** Whether a one-notch step from `value` stays on the scale — a step that
198
222
  * would fall off either end is disabled rather than silently clamped. */
@@ -200,18 +224,15 @@ function canStep(scale, value, step) {
200
224
  const next = scale.indexOf(value) + step;
201
225
  return next >= 0 && next < scale.length;
202
226
  }
203
- function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
227
+ function PoetryMenuItems({ strings, layout, scale, centered, onAction, mode = 'menu', }) {
204
228
  const t = strings.poetryMenu;
205
- const item = (icon, label, action, active = false, color) => {
206
- const Icon = icon;
207
- return (_jsx(Menu.Item, { color: color, disabled: active, leftSection: _jsx(Icon, { size: ICON_SIZE, stroke: ICON_STROKE }), onMouseDown: (e) => e.preventDefault(), onClick: onAction(action), children: label }));
208
- };
229
+ const item = (icon, label, action, active = false, color) => (_jsx(CommandItem, { mode: mode, icon: icon, label: label, onClick: onAction(action), disabled: active, color: color }));
209
230
  const setLayout = (next) => () => {
210
231
  const node = $getPoetryBlockFromSelection();
211
232
  if (node)
212
233
  $setPoetryLayout(node, next);
213
234
  };
214
- return (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), item(IconArrowsShuffle, t.staggered, setLayout('staggered'), layout === 'staggered'), _jsx(Menu.Divider, {}), _jsx(Menu.Divider, {}), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1), !canStep(POETRY_SPACINGS, scale.spacing, -1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1), !canStep(POETRY_SPACINGS, scale.spacing, 1)), layout === 'staggered' && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCouplets, () => $adjustPoetryStagger(-1), !canStep(POETRY_STAGGERS, scale.stagger, -1)), item(IconArrowsMaximize, t.widerCouplets, () => $adjustPoetryStagger(1), !canStep(POETRY_STAGGERS, scale.stagger, 1))] })), layout === 'two-column' && (_jsxs(_Fragment, { children: [item(IconAlignCenter, centered ? t.uncenterCouplet : t.centerCouplet, () => $setCoupletCentered(!centered)), centered && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCenter, () => $adjustPoetryCenterWidth(-1), !canStep(POETRY_CENTER_WIDTHS, scale.centerWidth, -1)), item(IconArrowsMaximize, t.widerCenter, () => $adjustPoetryCenterWidth(1), !canStep(POETRY_CENTER_WIDTHS, scale.centerWidth, 1))] })), item(IconColumns2, t.narrowerGutter, () => $adjustPoetryGutter(-1), !canStep(POETRY_GUTTERS, scale.gutter, -1)), item(IconColumns2, t.widerGutter, () => $adjustPoetryGutter(1), !canStep(POETRY_GUTTERS, scale.gutter, 1))] })), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red'), item(IconTrash, t.deletePoetry, $deletePoetryBlock, false, 'red')] }));
235
+ return (_jsxs(_Fragment, { children: [_jsx(ItemLabel, { mode: mode, children: t.menuLabel }), item(IconColumns1, t.singleColumn, setLayout('single'), layout === 'single'), item(IconColumns2, t.twoColumn, setLayout('two-column'), layout === 'two-column'), item(IconArrowsShuffle, t.staggered, setLayout('staggered'), layout === 'staggered'), _jsx(ItemDivider, { mode: mode }), _jsx(ItemDivider, { mode: mode }), item(IconRowInsertTop, t.insertCoupletBefore, () => $insertCoupletRelativeToSelection('before')), item(IconRowInsertBottom, t.insertCoupletAfter, () => $insertCoupletRelativeToSelection('after')), item(IconArrowsMinimize, t.tighterSpacing, () => $adjustPoetrySpacing(-1), !canStep(POETRY_SPACINGS, scale.spacing, -1)), item(IconArrowsMaximize, t.looserSpacing, () => $adjustPoetrySpacing(1), !canStep(POETRY_SPACINGS, scale.spacing, 1)), layout === 'staggered' && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCouplets, () => $adjustPoetryStagger(-1), !canStep(POETRY_STAGGERS, scale.stagger, -1)), item(IconArrowsMaximize, t.widerCouplets, () => $adjustPoetryStagger(1), !canStep(POETRY_STAGGERS, scale.stagger, 1))] })), layout === 'two-column' && (_jsxs(_Fragment, { children: [item(IconAlignCenter, centered ? t.uncenterCouplet : t.centerCouplet, () => $setCoupletCentered(!centered)), centered && (_jsxs(_Fragment, { children: [item(IconArrowsMinimize, t.narrowerCenter, () => $adjustPoetryCenterWidth(-1), !canStep(POETRY_CENTER_WIDTHS, scale.centerWidth, -1)), item(IconArrowsMaximize, t.widerCenter, () => $adjustPoetryCenterWidth(1), !canStep(POETRY_CENTER_WIDTHS, scale.centerWidth, 1))] })), item(IconColumns2, t.narrowerGutter, () => $adjustPoetryGutter(-1), !canStep(POETRY_GUTTERS, scale.gutter, -1)), item(IconColumns2, t.widerGutter, () => $adjustPoetryGutter(1), !canStep(POETRY_GUTTERS, scale.gutter, 1))] })), item(IconTrash, t.deleteCouplet, $deletePoetryCouplet, false, 'red'), item(IconTrash, t.deletePoetry, $deletePoetryBlock, false, 'red')] }));
215
236
  }
216
237
  /** `docs/markdown-dialect.md` at the repository's default branch — the Markdown
217
238
  * format this editor reads and writes, including the poetry container syntax. */
@@ -220,6 +241,7 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
220
241
  const bordered = toolbarStyle?.bordered ?? true;
221
242
  const variant = toolbarStyle?.variant ?? 'light';
222
243
  const overflowEnabled = toolbarStyle?.overflow ?? true;
244
+ const ribbon = toolbarStyle?.layout === 'ribbon';
223
245
  const [editor] = useLexicalComposerContext();
224
246
  const [state, setState] = useState(INITIAL_STATE);
225
247
  const strings = useStrings(locale);
@@ -559,6 +581,34 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
559
581
  editor.focus();
560
582
  };
561
583
  const [contextMenu, setContextMenu] = useState(null);
584
+ const [ribbonTab, setRibbonTab] = useState('home');
585
+ const ribbonId = useId();
586
+ // Collapsed: only the tabs show. Clicking a tab then "peeks" at its commands in a panel over the canvas.
587
+ const [ribbonCollapsed, setRibbonCollapsed] = useState(false);
588
+ const [ribbonPeek, setRibbonPeek] = useState(false);
589
+ const ribbonRootRef = useRef(null);
590
+ useEffect(() => {
591
+ if (!ribbonPeek)
592
+ return;
593
+ // A click outside the ribbon closes the peek, but not one in a menu or dropdown it opened: those
594
+ // render in the editor's portal anchor, outside the ribbon's own DOM.
595
+ const onPointerDown = (event) => {
596
+ const target = event.target;
597
+ if (target && (ribbonRootRef.current?.contains(target) || target.closest?.('.likhari-portal-target')))
598
+ return;
599
+ setRibbonPeek(false);
600
+ };
601
+ const onKeyDown = (event) => {
602
+ if (event.key === 'Escape')
603
+ setRibbonPeek(false);
604
+ };
605
+ document.addEventListener('pointerdown', onPointerDown, true);
606
+ document.addEventListener('keydown', onKeyDown);
607
+ return () => {
608
+ document.removeEventListener('pointerdown', onPointerDown, true);
609
+ document.removeEventListener('keydown', onKeyDown);
610
+ };
611
+ }, [ribbonPeek]);
562
612
  useEffect(() => {
563
613
  const onContextMenu = async (event) => {
564
614
  event.preventDefault();
@@ -622,6 +672,14 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
622
672
  }
623
673
  }
624
674
  }
675
+ // A misspelled word's menu is its spelling fixes and the clipboard, nothing else: no synonyms to look up either.
676
+ if (menu.spelling) {
677
+ menu.thesaurus = undefined;
678
+ menu.link = undefined;
679
+ menu.table = undefined;
680
+ menu.poetry = undefined;
681
+ menu.text = undefined;
682
+ }
625
683
  setContextMenu(menu);
626
684
  if (word && menu.thesaurus) {
627
685
  // Synonyms can take a while (the first WordNet lookup downloads the database), so the menu
@@ -847,17 +905,22 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
847
905
  editor.focus();
848
906
  }
849
907
  : withRefocus((v) => applyFont('font-family', v)) })), config.font.size && !uniform && (_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"));
850
- const insertSection = showInsertGroup && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [config.links &&
851
- (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: () => {
908
+ /** The insert commands. `contextual` also puts the link, table and poetry option menus among them (the classic
909
+ * toolbar); the ribbon shows those on their own tabs instead. */
910
+ const insertButtons = (contextual) => (_jsxs(_Fragment, { children: [config.links &&
911
+ (contextual && 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: () => {
852
912
  restoreSelection();
853
913
  openLinkDialog();
854
- }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog }))), (config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), config.tables && state.inTable && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconTableOptions, title: strings.toolbar.tableOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout }) })), config.poetry.enabled && state.inPoetry && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.poetryOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] }))] }, "insert"));
914
+ }, onRemove: removeLink, strings: strings }) })] })) : (_jsx(ToolbarButton, { icon: IconLink, title: strings.toolbar.insertLink, onClick: openLinkDialog }))), (config.images.linked || config.images.embedded) && (_jsx(ToolbarButton, { icon: IconPhoto, title: strings.toolbar.insertImage, onClick: openImageDialog })), config.blocks.horizontalRule && (_jsx(ToolbarButton, { icon: IconSeparatorHorizontal, title: strings.toolbar.insertHorizontalRule, onClick: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined) })), config.tables && _jsx(ToolbarButton, { icon: IconTable, title: strings.toolbar.insertTable, onClick: openTableDialog }), contextual && config.tables && state.inTable && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 240, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconTableOptions, title: strings.toolbar.tableOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(TableMenuItems, { strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), config.columns && _jsx(ToolbarButton, { icon: IconColumns, title: strings.toolbar.insertColumns, onClick: openLayoutDialog }), config.footnotes && (_jsx(ToolbarButton, { icon: IconNumber1Small, title: strings.toolbar.insertFootnote, onClick: () => editor.dispatchCommand(INSERT_FOOTNOTE_COMMAND, undefined) })), config.blocks.pageBreak && (_jsx(ToolbarButton, { icon: IconPageBreak, title: strings.toolbar.insertPageBreak, onClick: () => editor.dispatchCommand(INSERT_PAGE_BREAK_COMMAND, undefined) })), config.poetry.enabled && locale !== 'en' && (_jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.insertPoetryCouplet, onClick: () => editor.dispatchCommand(INSERT_POETRY_COUPLET_COMMAND, { layout: config.poetry.defaultLayout }) })), contextual && config.poetry.enabled && state.inPoetry && (_jsxs(Menu, { position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 200, onOpen: snapshotSelection, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconFeather, title: strings.toolbar.poetryOptions, active: true, "aria-haspopup": "menu" }) }), _jsx(Menu.Dropdown, { children: _jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] }))] }));
915
+ const insertSection = showInsertGroup && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: insertButtons(true) }, "insert"));
855
916
  const joinLines = () => {
856
917
  editor.update(() => {
857
918
  $joinSelectedLines();
858
919
  }, { discrete: true });
859
920
  editor.focus();
860
921
  };
922
+ // The tools group is always built, even when every command in it is off, so the ribbon asks first.
923
+ const hasToolsGroup = config.language.autocorrect || config.language.textCleanup || config.language.spellCheck || config.joinLines;
861
924
  const toolsSection = (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [config.language.autocorrect && (_jsx(ToolbarButton, { icon: IconWand, title: strings.toolbar.autocorrect, active: autoCorrectOpen, onClick: onToggleAutoCorrect })), config.language.textCleanup && (_jsx(ToolbarButton, { icon: IconSparkles, title: strings.autoCorrect.correctDocument, onClick: () => editor.dispatchCommand(CORRECT_DOCUMENT_COMMAND, undefined) })), config.language.spellCheck && (_jsx(ToolbarButton, { icon: IconAbc, title: strings.toolbar.spellChecker, active: spellOpen, onClick: onToggleSpell })), config.joinLines && (_jsx(ToolbarButton, { icon: IconArrowsJoin, title: strings.toolbar.joinLines, disabled: !state.canJoinLines, onClick: joinLines }))] }, "tools"));
862
925
  // Uniform mode's zoom: [-] 100% [+], stepping through the preset levels; the
863
926
  // percentage resets to 100%. Always laid out left-to-right, like a number.
@@ -866,13 +929,18 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
866
929
  const zoomInTo = ZOOM_LEVELS_PERCENT.find((level) => level > zoom);
867
930
  const hasEndUtilities = Boolean(drafts || config.findReplace || helpUrl !== false);
868
931
  const zoomGroup = showZoom && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", role: "group", "aria-label": strings.toolbar.zoom, dir: "ltr", children: [_jsx(ToolbarButton, { icon: IconMinus, title: strings.toolbar.zoomOut, disabled: zoomOutTo === undefined, onClick: () => zoomOutTo !== undefined && onZoomChange?.(zoomOutTo) }), _jsxs(MantineToolbar.Toggle, { title: strings.toolbar.zoomReset, "aria-label": `${strings.toolbar.zoomReset} (${zoom}%)`, onMouseDown: (e) => e.preventDefault(), onClick: () => onZoomChange?.(100), style: { width: 'auto', minWidth: 52, padding: '0 6px', fontSize: 12 }, children: [zoom, "%"] }), _jsx(ToolbarButton, { icon: IconPlus, title: strings.toolbar.zoomIn, disabled: zoomInTo === undefined, onClick: () => zoomInTo !== undefined && onZoomChange?.(zoomInTo) })] }, "zoom"));
932
+ const draftsButton = drafts && _jsx(ToolbarButton, { icon: IconHistory, title: strings.toolbar.drafts, onClick: () => setDraftsOpen(true) });
933
+ const findButton = config.findReplace && (_jsx(ToolbarButton, { icon: IconSearch, title: strings.findReplace.toggle, active: findOpen, onClick: onToggleFind }));
934
+ const helpButton = helpUrl !== false && (_jsx(MantineToolbar.Toggle, { component: "a", href: helpUrl ?? DEFAULT_HELP_URL, target: "_blank", rel: "noopener noreferrer", "aria-label": strings.toolbar.help, title: strings.toolbar.help, children: _jsx(IconHelpCircle, { size: ICON_SIZE, stroke: ICON_STROKE }) }));
869
935
  // Drafts, search and help: the far-end group (see `endMovable` below).
870
- const utilitiesGroup = hasEndUtilities && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [drafts && _jsx(ToolbarButton, { icon: IconHistory, title: strings.toolbar.drafts, onClick: () => setDraftsOpen(true) }), config.findReplace && (_jsx(ToolbarButton, { icon: IconSearch, title: strings.findReplace.toggle, active: findOpen, onClick: onToggleFind })), helpUrl !== false && (_jsx(MantineToolbar.Toggle, { component: "a", href: helpUrl ?? DEFAULT_HELP_URL, target: "_blank", rel: "noopener noreferrer", "aria-label": strings.toolbar.help, title: strings.toolbar.help, children: _jsx(IconHelpCircle, { size: ICON_SIZE, stroke: ICON_STROKE }) }))] }, "end"));
936
+ const utilitiesGroup = hasEndUtilities && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: [draftsButton, findButton, helpButton] }, "end"));
937
+ const saveGroup = showSave && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty),
938
+ // Nothing to save until the content has changed since it was loaded or last saved.
939
+ disabled: !isDirty, onClick: onSave }) }, "save"));
940
+ const historyGroup = config.history && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", 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"));
871
941
  const startSections = [
872
- showSave && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty),
873
- // Nothing to save until the content has changed since it was loaded or last saved.
874
- disabled: !isDirty, onClick: onSave }) }, "save")),
875
- config.history && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", 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")),
942
+ saveGroup,
943
+ historyGroup,
876
944
  formatSection,
877
945
  alignSection,
878
946
  ];
@@ -942,35 +1010,136 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
942
1010
  const visibleEnd = endMovable.filter(isVisible);
943
1011
  // Shown in the menu in toolbar order: the inline sections, then the far-end ones.
944
1012
  const hiddenMovable = [...mainMovable, ...endMovable].filter((section) => !isVisible(section));
945
- return (_jsxs(MantineToolbar, { ref: toolbarContainerRef, "aria-label": strings.toolbar.ariaLabel, className: bordered ? 'likhari-toolbar' : 'likhari-toolbar likhari-toolbar--plain', withBorder: bordered, variant: variant, color: "var(--editor-accent)", children: [withDividers(startSections), visibleMovable.map((section) => (_jsxs(Fragment, { children: [_jsx(MantineToolbar.Divider, {}), section.node] }, section.key))), hiddenMovable.length > 0 && (_jsxs(_Fragment, { children: [_jsx(MantineToolbar.Divider, {}), _jsxs(Menu, { opened: overflowMenuOpen, onChange: handleOverflowMenuChange, position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: "max-content", closeOnItemClick: false, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconDots, title: strings.toolbar.moreFormatting }) }), _jsx(Menu.Dropdown, { p: 6, children: _jsx(MantineToolbar, { withBorder: false, variant: variant, color: "var(--editor-accent)", style: { maxWidth: 320 }, children: withDividers(hiddenMovable.map((section) => section.node)) }) })] })] })), _jsx("span", { "aria-hidden": "true", style: { flex: '1 1 auto' } }), visibleEnd.map((section) => section.node), _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
946
- if (!opened)
947
- setContextMenu(null);
948
- }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: 260, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: contextMenu?.x ?? -9999, top: contextMenu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [contextMenu?.spelling && (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.contextMenu.spelling }), contextMenu.spelling.suggestions.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.contextMenu.noSuggestions }), contextMenu.spelling.suggestions.map((suggestion) => (_jsx(Menu.Item, { onClick: () => {
949
- const match = contextMenu.spelling.match;
950
- editor.update(() => $replaceMatch(match, suggestion), { discrete: true });
951
- editor.focus();
952
- }, children: suggestion }, suggestion))), _jsx(Menu.Item, { onClick: () => {
953
- ignoreWord(contextMenu.spelling.language, contextMenu.spelling.word);
954
- setContextMenu(null);
955
- }, children: strings.contextMenu.ignore }), _jsx(Menu.Item, { onClick: () => {
956
- const { language, word } = contextMenu.spelling;
957
- // Without a writable store, the word is ignored for this session instead.
958
- void addUserWord(dictionaryStores, language, word).then((added) => {
959
- if (!added)
960
- ignoreWord(language, word);
961
- });
962
- setContextMenu(null);
963
- }, children: strings.contextMenu.addToDictionary }), onAddAutoCorrect && config.language.autocorrect && (_jsx(Menu.Item, { onClick: () => {
964
- onAddAutoCorrect(contextMenu.spelling.word, contextMenu.spelling.language);
965
- setContextMenu(null);
966
- }, children: strings.contextMenu.addToAutoCorrect })), _jsx(Menu.Divider, {})] })), contextMenu?.thesaurus && (_jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.thesaurus.synonyms }), contextMenu.thesaurus.items === undefined && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.loading }), contextMenu.thesaurus.items?.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.none }), contextMenu.thesaurus.items?.map((synonym) => (_jsx(Menu.Item, { onClick: () => {
967
- const match = contextMenu.thesaurus.match;
968
- editor.update(() => $replaceMatch(match, synonym), { discrete: true });
969
- editor.focus();
970
- }, children: synonym }, synonym))), _jsx(Menu.Divider, {})] })), contextMenu?.link !== undefined && (_jsxs(_Fragment, { children: [_jsx(LinkMenuItems, { url: contextMenu.link, onEdit: () => {
971
- restoreSelection();
972
- openLinkDialog();
973
- }, onRemove: removeLink, strings: strings }), _jsx(Menu.Divider, {})] })), contextMenu?.table && (_jsxs(_Fragment, { children: [_jsx(TableMenuItems, { strings: strings, direction: direction, rows: contextMenu.table.rows, columns: contextMenu.table.columns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), contextMenu?.poetry && (_jsxs(_Fragment, { children: [_jsx(PoetryMenuItems, { strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }), _jsx(Menu.Divider, {})] })), contextMenu?.text && (_jsxs(_Fragment, { children: [_jsx(Menu.Item, { leftSection: _jsx(IconArrowsJoin, { size: ICON_SIZE, stroke: ICON_STROKE }), disabled: !contextMenu.canJoin, onClick: runMenuAction(() => {
974
- $joinSelectedLines();
975
- }), children: strings.contextMenu.joinLines }), _jsx(Menu.Divider, {})] })), _jsx(Menu.Item, { leftSection: _jsx(IconCut, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(true)(), children: strings.contextMenu.cut }), _jsx(Menu.Item, { leftSection: _jsx(IconCopy, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void copyOrCut(false)(), children: strings.contextMenu.copy }), _jsx(Menu.Item, { leftSection: _jsx(IconClipboard, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: () => void paste(), children: strings.contextMenu.paste }), _jsx(Menu.Item, { leftSection: _jsx(IconSelectAll, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: runMenuAction(() => $selectAll()), children: strings.contextMenu.selectAll })] })] }), overflowEnabled && (_jsxs(MantineToolbar, { "aria-hidden": "true", ref: toolbarMeasureRef, withBorder: bordered, variant: variant, color: "var(--editor-accent)", style: { position: 'absolute', visibility: 'hidden', pointerEvents: 'none', top: 0, left: 0, flexWrap: 'nowrap', width: 'max-content' }, children: [withDividers(startSections), movableSections.map((section) => (_jsxs(Fragment, { children: [_jsx(MantineToolbar.Divider, {}), _jsx("span", { "data-group-key": section.key, style: { display: 'inline-flex' }, children: section.node })] }, section.key)))] })), (config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), config.columns && _jsx(LayoutDialog, { opened: layoutDialogOpen, onSubmit: insertLayout, onClose: closeLayoutDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
1013
+ // The right-click menu and the dialogs are the same in both layouts.
1014
+ const contextMenuEl = (_jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
1015
+ if (!opened)
1016
+ setContextMenu(null);
1017
+ }, position: "bottom-start", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: CONTEXT_MENU_WIDTH,
1018
+ // Stays inside the visible area: flips above the click or shifts sideways when it fits that
1019
+ // way, and otherwise is cut to the room there is and scrolls (a long menu in a short window).
1020
+ middlewares: { flip: true, shift: true, size: { padding: CONTEXT_MENU_VIEWPORT_PADDING } },
1021
+ // Positioned against the window, not the editor: the menu then takes no room in the editor's layout (so it can
1022
+ // never grow a scroll bar there or make the text jump) and is not cut off at the editor's edge. No open
1023
+ // animation either, as its transform would make the submenus position against the menu instead.
1024
+ floatingStrategy: "fixed", transitionProps: { duration: 0 }, styles: CONTEXT_MENU_STYLES, classNames: CONTEXT_MENU_CLASS_NAMES, children: [_jsx(Menu.Target, { children: _jsx("span", { "aria-hidden": "true", style: { position: 'fixed', left: contextMenu?.x ?? -9999, top: contextMenu?.y ?? -9999, width: 1, height: 1, pointerEvents: 'none' } }) }), _jsxs(Menu.Dropdown, { children: [contextMenu?.spelling ? (
1025
+ // A misspelled word gets its fixes and the clipboard row, and nothing else.
1026
+ _jsxs(_Fragment, { children: [_jsx(Menu.Label, { children: strings.contextMenu.spelling }), contextMenu.spelling.suggestions.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.contextMenu.noSuggestions }), contextMenu.spelling.suggestions.map((suggestion) => (_jsx(Menu.Item, { onClick: () => {
1027
+ const match = contextMenu.spelling.match;
1028
+ editor.update(() => $replaceMatch(match, suggestion), { discrete: true });
1029
+ editor.focus();
1030
+ }, children: suggestion }, suggestion))), _jsx(Menu.Item, { onClick: () => {
1031
+ ignoreWord(contextMenu.spelling.language, contextMenu.spelling.word);
1032
+ setContextMenu(null);
1033
+ }, children: strings.contextMenu.ignore }), _jsx(Menu.Item, { onClick: () => {
1034
+ const { language, word } = contextMenu.spelling;
1035
+ // Without a writable store, the word is ignored for this session instead.
1036
+ void addUserWord(dictionaryStores, language, word).then((added) => {
1037
+ if (!added)
1038
+ ignoreWord(language, word);
1039
+ });
1040
+ setContextMenu(null);
1041
+ }, children: strings.contextMenu.addToDictionary }), onAddAutoCorrect && config.language.autocorrect && (_jsx(Menu.Item, { onClick: () => {
1042
+ onAddAutoCorrect(contextMenu.spelling.word, contextMenu.spelling.language);
1043
+ setContextMenu(null);
1044
+ }, children: strings.contextMenu.addToAutoCorrect })), _jsx(Menu.Divider, {})] })) : (
1045
+ // Everything that applies to what is under the pointer, each kind of content behind its own submenu.
1046
+ _jsxs(_Fragment, { children: [contextMenu?.thesaurus && (_jsxs(Menu.Sub, { ...CONTEXT_SUBMENU_PROPS, children: [_jsx(Menu.Sub.Target, { children: _jsx(Menu.Sub.Item, { leftSection: _jsx(IconBook, { size: ICON_SIZE, stroke: ICON_STROKE }), children: strings.thesaurus.synonyms }) }), _jsxs(Menu.Sub.Dropdown, { children: [contextMenu.thesaurus.items === undefined && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.loading }), contextMenu.thesaurus.items?.length === 0 && _jsx(Menu.Item, { disabled: true, children: strings.thesaurus.none }), contextMenu.thesaurus.items?.map((synonym) => (_jsx(Menu.Item, { onClick: () => {
1047
+ const match = contextMenu.thesaurus.match;
1048
+ editor.update(() => $replaceMatch(match, synonym), { discrete: true });
1049
+ editor.focus();
1050
+ }, children: synonym }, synonym)))] })] })), contextMenu?.link !== undefined && (_jsxs(Menu.Sub, { ...CONTEXT_SUBMENU_PROPS, children: [_jsx(Menu.Sub.Target, { children: _jsx(Menu.Sub.Item, { leftSection: _jsx(IconLink, { size: ICON_SIZE, stroke: ICON_STROKE }), children: strings.link.menuLabel }) }), _jsx(Menu.Sub.Dropdown, { children: _jsx(LinkMenuItems, { mode: "submenu", url: contextMenu.link, onEdit: () => {
1051
+ restoreSelection();
1052
+ openLinkDialog();
1053
+ }, onRemove: removeLink, strings: strings }) })] })), contextMenu?.table && (_jsxs(Menu.Sub, { ...CONTEXT_SUBMENU_PROPS, children: [_jsx(Menu.Sub.Target, { children: _jsx(Menu.Sub.Item, { leftSection: _jsx(IconTable, { size: ICON_SIZE, stroke: ICON_STROKE }), children: strings.tableMenu.menuLabel }) }), _jsx(Menu.Sub.Dropdown, { children: _jsx(TableMenuItems, { mode: "submenu", strings: strings, direction: direction, rows: contextMenu.table.rows, columns: contextMenu.table.columns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction }) })] })), contextMenu?.poetry && (_jsxs(Menu.Sub, { ...CONTEXT_SUBMENU_PROPS, children: [_jsx(Menu.Sub.Target, { children: _jsx(Menu.Sub.Item, { leftSection: _jsx(IconFeather, { size: ICON_SIZE, stroke: ICON_STROKE }), children: strings.poetryMenu.menuLabel }) }), _jsx(Menu.Sub.Dropdown, { children: _jsx(PoetryMenuItems, { mode: "submenu", strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction }) })] })), contextMenu?.text && (_jsx(Menu.Item, { leftSection: _jsx(IconArrowsJoin, { size: ICON_SIZE, stroke: ICON_STROKE }), disabled: !contextMenu.canJoin, onClick: runMenuAction(() => {
1054
+ $joinSelectedLines();
1055
+ }), children: strings.contextMenu.joinLines })), (contextMenu?.thesaurus || contextMenu?.link !== undefined || contextMenu?.table || contextMenu?.poetry || contextMenu?.text) && (_jsx(Menu.Divider, {}))] })), _jsxs("div", { className: "likhari-context-row", role: "group", children: [_jsx(Menu.Item, { "aria-label": strings.contextMenu.cut, title: strings.contextMenu.cut, onClick: () => void copyOrCut(true)(), children: _jsx(IconCut, { size: ICON_SIZE, stroke: ICON_STROKE }) }), _jsx(Menu.Item, { "aria-label": strings.contextMenu.copy, title: strings.contextMenu.copy, onClick: () => void copyOrCut(false)(), children: _jsx(IconCopy, { size: ICON_SIZE, stroke: ICON_STROKE }) }), _jsx(Menu.Item, { "aria-label": strings.contextMenu.paste, title: strings.contextMenu.paste, onClick: () => void paste(), children: _jsx(IconClipboard, { size: ICON_SIZE, stroke: ICON_STROKE }) })] }), !contextMenu?.spelling && (_jsxs(_Fragment, { children: [_jsx(Menu.Divider, {}), _jsx(Menu.Item, { leftSection: _jsx(IconSelectAll, { size: ICON_SIZE, stroke: ICON_STROKE }), onClick: runMenuAction(() => $selectAll()), children: strings.contextMenu.selectAll })] }))] })] }));
1056
+ const dialogsEl = (_jsxs(_Fragment, { children: [(config.images.linked || config.images.embedded) && (_jsx(ImageDialog, { mode: "insert", opened: imageDialogOpen, onSubmit: insertImage, onClose: closeImageDialog })), config.tables && _jsx(TableDialog, { opened: tableDialogOpen, onSubmit: insertTable, onClose: closeTableDialog }), config.columns && _jsx(LayoutDialog, { opened: layoutDialogOpen, onSubmit: insertLayout, onClose: closeLayoutDialog }), config.links && (_jsx(LinkDialog, { opened: linkDialogOpen, initialUrl: state.linkUrl, showTextField: linkNeedsText, onSubmit: applyLink, onRemove: state.isLink ? removeLink : undefined, onClose: closeLinkDialog })), drafts && _jsx(DraftsDialog, { opened: draftsOpen, locale: locale, onClose: () => setDraftsOpen(false), ...drafts })] }));
1057
+ // The ribbon layout (UI spec §3.4): the same command groups as above, on tabs.
1058
+ if (ribbon) {
1059
+ const rt = strings.toolbar.ribbon;
1060
+ const cluster = (key, children) => (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: children }, key));
1061
+ const groupsByTab = {
1062
+ home: [formatSection, alignSection, fontSection],
1063
+ insert: [showInsertGroup && cluster('insert', insertButtons(false))],
1064
+ tools: [hasToolsGroup && toolsSection],
1065
+ table: [
1066
+ config.tables &&
1067
+ state.inTable &&
1068
+ cluster('table', _jsx(TableMenuItems, { mode: "ribbon", strings: strings, direction: direction, rows: state.tableRows, columns: state.tableColumns, canMergeCells: state.canMergeCells, canUnmergeCell: state.canUnmergeCell, onAction: runMenuAction })),
1069
+ ],
1070
+ poetry: [
1071
+ config.poetry.enabled &&
1072
+ state.inPoetry &&
1073
+ cluster('poetry', _jsx(PoetryMenuItems, { mode: "ribbon", strings: strings, layout: state.poetryLayout, scale: state.poetryScale, centered: state.poetryCentered, onAction: runMenuAction })),
1074
+ ],
1075
+ link: [
1076
+ config.links &&
1077
+ state.isLink &&
1078
+ cluster('link', _jsx(LinkMenuItems, { mode: "ribbon", url: state.linkUrl, onEdit: () => {
1079
+ restoreSelection();
1080
+ openLinkDialog();
1081
+ }, onRemove: removeLink, strings: strings })),
1082
+ ],
1083
+ };
1084
+ const CONTEXTUAL = ['table', 'poetry', 'link'];
1085
+ // A tab with nothing to show (its features are off, or the caret is not in that content) is left out.
1086
+ const tabs = ['home', 'insert', 'tools', ...CONTEXTUAL]
1087
+ .map((id) => ({ id, label: rt[id], groups: groupsByTab[id].filter(Boolean) }))
1088
+ .filter((tab) => tab.groups.length > 0);
1089
+ const activeTab = tabs.find((tab) => tab.id === ribbonTab) ?? tabs[0];
1090
+ // A context tab that went away (the caret left the table) must not come back selected on its own later.
1091
+ if (activeTab && activeTab.id !== ribbonTab)
1092
+ setRibbonTab(activeTab.id);
1093
+ const panelVisible = !ribbonCollapsed || ribbonPeek;
1094
+ const toggleCollapsed = () => {
1095
+ setRibbonCollapsed((collapsed) => !collapsed);
1096
+ setRibbonPeek(false);
1097
+ };
1098
+ const tabDomId = (id) => `${ribbonId}-tab-${id}`;
1099
+ const onTabKeyDown = (event) => {
1100
+ if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key) || tabs.length === 0)
1101
+ return;
1102
+ const index = Math.max(0, tabs.findIndex((tab) => tab.id === activeTab?.id));
1103
+ const forward = event.key === (direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight');
1104
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : (index + (forward ? 1 : -1) + tabs.length) % tabs.length;
1105
+ event.preventDefault();
1106
+ setRibbonTab(tabs[next].id);
1107
+ event.currentTarget.querySelectorAll('[role="tab"]')[next]?.focus();
1108
+ };
1109
+ // Save, undo and redo come before the tabs; drafts, search and help come after them. Neither is on a tab,
1110
+ // so both stay in reach whichever tab is open (and when there are no tabs).
1111
+ const quickAccess = [saveGroup, historyGroup].filter(Boolean);
1112
+ const utilities = (draftsButton || findButton || helpButton) &&
1113
+ cluster('utilities', _jsxs(_Fragment, { children: [draftsButton, findButton, helpButton] }));
1114
+ // The collapse button belongs to the row, not to a tab, and is only offered when there are tabs to hide the commands of.
1115
+ const collapseGroup = tabs.length > 0 &&
1116
+ cluster('collapse', _jsx(ToolbarButton, { icon: ribbonCollapsed ? IconChevronDown : IconChevronUp, title: ribbonCollapsed ? rt.expand : rt.collapse, "aria-expanded": !ribbonCollapsed, onClick: toggleCollapsed }));
1117
+ // Zoom (uniform font mode) is a view setting, not a tab: it leads the document tools.
1118
+ const utilityBar = [zoomGroup, utilities, collapseGroup].filter(Boolean);
1119
+ return (_jsxs("div", { ref: ribbonRootRef, className: ['likhari-ribbon', bordered ? '' : 'likhari-ribbon--plain', ribbonCollapsed ? 'likhari-ribbon--collapsed' : ''].filter(Boolean).join(' '), onKeyDown: (event) => {
1120
+ // Ctrl+F1 collapses and expands the ribbon, as in office suites.
1121
+ if (event.key === 'F1' && event.ctrlKey && tabs.length > 0) {
1122
+ event.preventDefault();
1123
+ toggleCollapsed();
1124
+ }
1125
+ }, children: [_jsxs("div", { className: "likhari-ribbon-top", children: [quickAccess.length > 0 && (_jsx(MantineToolbar, { "aria-label": rt.quickAccess, className: "likhari-ribbon-quick", withBorder: false, variant: variant, color: "var(--editor-accent)", children: withDividers(quickAccess) })), tabs.length > 0 && (_jsx("div", { role: "tablist", "aria-label": rt.tabsLabel, className: "likhari-ribbon-tabs", onKeyDown: onTabKeyDown, children: tabs.map((tab) => {
1126
+ // While the ribbon is collapsed a tab only counts as selected when its panel is peeking open.
1127
+ const selected = panelVisible && tab.id === activeTab?.id;
1128
+ return (_jsx("button", { type: "button", role: "tab", id: tabDomId(tab.id), "aria-selected": selected, "aria-controls": selected ? `${ribbonId}-panel` : undefined, tabIndex: tab.id === activeTab?.id ? 0 : -1, className: "likhari-ribbon-tab", "data-contextual": CONTEXTUAL.includes(tab.id) ? 'true' : undefined,
1129
+ // Keep the editor's selection: a mousedown here would blur the canvas.
1130
+ onMouseDown: (e) => e.preventDefault(), onClick: () => {
1131
+ setRibbonTab(tab.id);
1132
+ // Collapsed: a tab opens its commands over the canvas, and clicking it again closes them.
1133
+ if (ribbonCollapsed)
1134
+ setRibbonPeek(!(ribbonPeek && tab.id === activeTab?.id));
1135
+ }, onDoubleClick: toggleCollapsed, children: tab.label }, tab.id));
1136
+ }) })), utilityBar.length > 0 && (_jsx(MantineToolbar, { "aria-label": rt.utilities, className: "likhari-ribbon-utilities", withBorder: false, variant: variant, color: "var(--editor-accent)", children: withDividers(utilityBar) }))] }), panelVisible && activeTab && (_jsx("div", { role: "tabpanel", id: `${ribbonId}-panel`, "aria-labelledby": tabDomId(activeTab.id), className: ribbonCollapsed ? 'likhari-ribbon-panel-wrap likhari-ribbon-panel-wrap--peek' : 'likhari-ribbon-panel-wrap', onClick: (event) => {
1137
+ // A command chosen from the peeking panel closes it, as in office suites. A button that opens a
1138
+ // menu (More formatting) keeps it open, so the menu is not pulled away from under the pointer.
1139
+ const button = event.target.closest('button, a');
1140
+ if (ribbonCollapsed && button && !button.hasAttribute('aria-haspopup'))
1141
+ setRibbonPeek(false);
1142
+ }, children: _jsx(MantineToolbar, { "aria-label": strings.toolbar.ariaLabel, className: bordered ? 'likhari-toolbar likhari-ribbon-panel' : 'likhari-toolbar likhari-toolbar--plain likhari-ribbon-panel', withBorder: bordered, variant: variant, color: "var(--editor-accent)", children: withDividers(activeTab.groups) }) })), contextMenuEl, dialogsEl] }));
1143
+ }
1144
+ return (_jsxs(MantineToolbar, { ref: toolbarContainerRef, "aria-label": strings.toolbar.ariaLabel, className: bordered ? 'likhari-toolbar' : 'likhari-toolbar likhari-toolbar--plain', withBorder: bordered, variant: variant, color: "var(--editor-accent)", children: [withDividers(startSections), visibleMovable.map((section) => (_jsxs(Fragment, { children: [_jsx(MantineToolbar.Divider, {}), section.node] }, section.key))), hiddenMovable.length > 0 && (_jsxs(_Fragment, { children: [_jsx(MantineToolbar.Divider, {}), _jsxs(Menu, { opened: overflowMenuOpen, onChange: handleOverflowMenuChange, position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: "max-content", closeOnItemClick: false, children: [_jsx(Menu.Target, { children: _jsx(ToolbarButton, { icon: IconDots, title: strings.toolbar.moreFormatting }) }), _jsx(Menu.Dropdown, { p: 6, children: _jsx(MantineToolbar, { withBorder: false, variant: variant, color: "var(--editor-accent)", style: { maxWidth: 320 }, children: withDividers(hiddenMovable.map((section) => section.node)) }) })] })] })), _jsx("span", { "aria-hidden": "true", style: { flex: '1 1 auto' } }), visibleEnd.map((section) => section.node), contextMenuEl, overflowEnabled && (_jsxs(MantineToolbar, { "aria-hidden": "true", ref: toolbarMeasureRef, withBorder: bordered, variant: variant, color: "var(--editor-accent)", style: { position: 'absolute', visibility: 'hidden', pointerEvents: 'none', top: 0, left: 0, flexWrap: 'nowrap', width: 'max-content' }, children: [withDividers(startSections), movableSections.map((section) => (_jsxs(Fragment, { children: [_jsx(MantineToolbar.Divider, {}), _jsx("span", { "data-group-key": section.key, style: { display: 'inline-flex' }, children: section.node })] }, section.key)))] })), dialogsEl] }));
976
1145
  }
@@ -74,6 +74,25 @@ export interface Strings {
74
74
  clearFormatting: string;
75
75
  outdent: string;
76
76
  indent: string;
77
+ /** The ribbon layout (`toolbarStyle.layout: 'ribbon'`): its tab strip and tabs. */
78
+ ribbon: {
79
+ /** aria-label on the tab strip. */
80
+ tabsLabel: string;
81
+ /** aria-label on the group before the tabs that holds save, undo and redo. */
82
+ quickAccess: string;
83
+ /** aria-label on the group after the tabs that holds drafts, search and help. */
84
+ utilities: string;
85
+ /** The button that hides the command row, leaving only the tabs, and the one that brings it back. */
86
+ collapse: string;
87
+ expand: string;
88
+ home: string;
89
+ insert: string;
90
+ tools: string;
91
+ /** Contextual tabs, shown only while the caret is in that kind of content. */
92
+ table: string;
93
+ poetry: string;
94
+ link: string;
95
+ };
77
96
  formattingOptions: {
78
97
  paragraph: string;
79
98
  heading: (level: number) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/i18n/strings.ts"],"names":[],"mappings":"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"}
@@ -62,6 +62,19 @@ const en = {
62
62
  clearFormatting: 'Clear formatting',
63
63
  outdent: 'Outdent',
64
64
  indent: 'Indent',
65
+ ribbon: {
66
+ tabsLabel: 'Ribbon tabs',
67
+ quickAccess: 'Quick access',
68
+ utilities: 'Document tools',
69
+ collapse: 'Collapse the ribbon',
70
+ expand: 'Expand the ribbon',
71
+ home: 'Home',
72
+ insert: 'Insert',
73
+ tools: 'Tools',
74
+ table: 'Table',
75
+ poetry: 'Poetry',
76
+ link: 'Link',
77
+ },
65
78
  formattingOptions: {
66
79
  paragraph: 'Paragraph',
67
80
  heading: (level) => `Heading ${level}`,
@@ -347,6 +360,19 @@ const ur = {
347
360
  clearFormatting: 'فارمیٹنگ ختم کریں',
348
361
  outdent: 'حاشیہ کم کریں',
349
362
  indent: 'حاشیہ بڑھائیں',
363
+ ribbon: {
364
+ tabsLabel: 'ربن کے ٹیب',
365
+ quickAccess: 'فوری رسائی',
366
+ utilities: 'دستاویز کے اوزار',
367
+ collapse: 'ربن چھپائیں',
368
+ expand: 'ربن دکھائیں',
369
+ home: 'ہوم',
370
+ insert: 'شامل کریں',
371
+ tools: 'اوزار',
372
+ table: 'جدول',
373
+ poetry: 'شاعری',
374
+ link: 'لنک',
375
+ },
350
376
  formattingOptions: {
351
377
  paragraph: 'پیراگراف',
352
378
  heading: (level) => `عنوان ${level}`,
@@ -632,6 +658,19 @@ const paShahmukhi = {
632
658
  clearFormatting: 'فارمیٹنگ ہٹاؤ',
633
659
  outdent: 'حاشیہ گھٹاؤ',
634
660
  indent: 'حاشیہ ودھاؤ',
661
+ ribbon: {
662
+ tabsLabel: 'ربن دے ٹیب',
663
+ quickAccess: 'جھٹ رسائی',
664
+ utilities: 'دستاویز دے اوزار',
665
+ collapse: 'ربن لکو',
666
+ expand: 'ربن وکھاؤ',
667
+ home: 'ہوم',
668
+ insert: 'شامل کرو',
669
+ tools: 'اوزار',
670
+ table: 'جدول',
671
+ poetry: 'شاعری',
672
+ link: 'لنک',
673
+ },
635
674
  formattingOptions: {
636
675
  paragraph: 'پیراگراف',
637
676
  heading: (level) => `سرلیکھ ${level}`,
@@ -1119,3 +1119,239 @@
1119
1119
  background: var(--editor-accent-soft);
1120
1120
  }
1121
1121
 
1122
+
1123
+ /* Ribbon layout (toolbarStyle.layout: 'ribbon', UI spec §3.4): a strip of tabs over one row of the active
1124
+ tab's command groups, with save, undo and redo before the tabs and drafts, search and help after them. */
1125
+ .likhari-ribbon {
1126
+ display: flex;
1127
+ flex-direction: column;
1128
+ flex-shrink: 0;
1129
+ /* The containing block, and a stacking level above the canvas, for the panel that peeks out when collapsed. */
1130
+ position: relative;
1131
+ z-index: 5;
1132
+ background: var(--editor-paper-dim);
1133
+ /* Lets the row below react to the editor's own width, not the window's. */
1134
+ container-type: inline-size;
1135
+ }
1136
+
1137
+ /* Collapsed: only the tab row stays, closed off with a line where the command row was. */
1138
+ .likhari-ribbon--collapsed .likhari-ribbon-top {
1139
+ padding-bottom: 2px;
1140
+ border-bottom: 1px solid var(--editor-line);
1141
+ }
1142
+
1143
+ /* In a narrow editor the save/undo group and the document tools take all the width and squeeze the tabs to
1144
+ nothing, so the tabs drop to a line of their own (scrolling sideways if need be). */
1145
+ @container (max-width: 560px) {
1146
+ .likhari-ribbon-top {
1147
+ flex-wrap: wrap;
1148
+ row-gap: 0;
1149
+ }
1150
+
1151
+ .likhari-ribbon-tabs {
1152
+ order: 3;
1153
+ flex: 1 1 100%;
1154
+ }
1155
+ }
1156
+
1157
+ /* A collapsed tab's commands open over the canvas instead of pushing it down, so the page does not jump. */
1158
+ .likhari-ribbon-panel-wrap--peek {
1159
+ position: absolute;
1160
+ inset-inline: 0;
1161
+ top: 100%;
1162
+ z-index: 30;
1163
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.16);
1164
+ }
1165
+
1166
+ .likhari-ribbon-top {
1167
+ display: flex;
1168
+ align-items: center;
1169
+ justify-content: space-between;
1170
+ gap: 8px;
1171
+ padding: 2px 8px 0;
1172
+ }
1173
+
1174
+ .likhari-ribbon-tabs {
1175
+ display: flex;
1176
+ gap: 2px;
1177
+ min-width: 0;
1178
+ /* Narrow screens scroll the tabs sideways rather than wrapping them. */
1179
+ overflow-x: auto;
1180
+ scrollbar-width: none;
1181
+ }
1182
+
1183
+ .likhari-ribbon-tab {
1184
+ appearance: none;
1185
+ flex: 0 0 auto;
1186
+ margin: 0;
1187
+ padding: 6px 12px;
1188
+ border: 0;
1189
+ border-bottom: 2px solid transparent;
1190
+ border-radius: 4px 4px 0 0;
1191
+ background: transparent;
1192
+ color: var(--editor-ink-soft);
1193
+ font: inherit;
1194
+ font-size: 13px;
1195
+ line-height: 1.2;
1196
+ white-space: nowrap;
1197
+ cursor: pointer;
1198
+ }
1199
+
1200
+ .likhari-ribbon-tab:hover {
1201
+ background: var(--editor-accent-soft);
1202
+ color: var(--editor-ink);
1203
+ }
1204
+
1205
+ .likhari-ribbon-tab[aria-selected='true'] {
1206
+ border-bottom-color: var(--editor-accent);
1207
+ color: var(--editor-accent);
1208
+ font-weight: 600;
1209
+ }
1210
+
1211
+ /* A context tab (Table, Poetry, Link) is tinted so it reads as appearing because of where the caret is. */
1212
+ .likhari-ribbon-tab[data-contextual] {
1213
+ background: var(--editor-accent-soft);
1214
+ color: var(--editor-accent);
1215
+ }
1216
+
1217
+ .likhari-ribbon-tab:focus-visible {
1218
+ outline: 2px solid var(--editor-accent);
1219
+ outline-offset: -2px;
1220
+ }
1221
+
1222
+ .likhari-ribbon-quick,
1223
+ .likhari-ribbon-utilities {
1224
+ display: flex;
1225
+ flex: 0 0 auto;
1226
+ flex-wrap: nowrap;
1227
+ align-items: center;
1228
+ gap: 8px;
1229
+ min-height: 0;
1230
+ padding: 0;
1231
+ background: transparent;
1232
+ }
1233
+
1234
+ /* Drafts, search and help at the end of the row, also when there are no tabs to push them there. */
1235
+ .likhari-ribbon-utilities {
1236
+ margin-inline-start: auto;
1237
+ }
1238
+
1239
+ /* The panel scrolls sideways when its groups do not fit, instead of wrapping onto a second row. */
1240
+ .likhari-ribbon-panel {
1241
+ flex-wrap: nowrap;
1242
+ overflow-x: auto;
1243
+ }
1244
+
1245
+ .likhari-ribbon-panel > * {
1246
+ flex: 0 0 auto;
1247
+ }
1248
+
1249
+ /* A destructive command (delete table, delete couplet) on the ribbon, as red as it is in the menus. */
1250
+ .likhari-ribbon-panel [data-danger='true'] {
1251
+ color: var(--mantine-color-red-6, #e03131);
1252
+ }
1253
+
1254
+ /* The buttons on the tab row, outside every tab (save, undo, redo; drafts, search, help): flat icons with no
1255
+ border or background at rest. Hover tints them; "on" and "unsaved" are shown by colour and a mark instead
1256
+ of a fill, so the row stays light next to the tabs. */
1257
+ .likhari-ribbon-quick .likhari-toolbar-cluster,
1258
+ .likhari-ribbon-utilities .likhari-toolbar-cluster {
1259
+ border: none;
1260
+ border-radius: 0;
1261
+ background: transparent;
1262
+ overflow: visible;
1263
+ }
1264
+
1265
+ .likhari-ribbon-quick [data-toolbar-toggle],
1266
+ .likhari-ribbon-utilities [data-toolbar-toggle] {
1267
+ position: relative;
1268
+ border: none;
1269
+ border-radius: 6px;
1270
+ background: transparent !important;
1271
+ color: var(--editor-ink-soft);
1272
+ }
1273
+
1274
+ .likhari-ribbon-quick .likhari-toolbar-cluster [data-toolbar-toggle] + [data-toolbar-toggle],
1275
+ .likhari-ribbon-utilities .likhari-toolbar-cluster [data-toolbar-toggle] + [data-toolbar-toggle] {
1276
+ border-inline-start: none;
1277
+ }
1278
+
1279
+ .likhari-ribbon-quick [data-toolbar-toggle]:hover:not(:disabled):not([data-disabled]),
1280
+ .likhari-ribbon-utilities [data-toolbar-toggle]:hover:not(:disabled):not([data-disabled]) {
1281
+ background: var(--editor-accent-soft) !important;
1282
+ color: var(--editor-ink);
1283
+ }
1284
+
1285
+ .likhari-ribbon-quick [data-toolbar-toggle]:disabled,
1286
+ .likhari-ribbon-quick [data-toolbar-toggle][data-disabled],
1287
+ .likhari-ribbon-utilities [data-toolbar-toggle]:disabled,
1288
+ .likhari-ribbon-utilities [data-toolbar-toggle][data-disabled] {
1289
+ color: var(--editor-ink-soft);
1290
+ opacity: 0.45;
1291
+ }
1292
+
1293
+ /* A button that is on (find and replace open): accent icon with a short bar under it. */
1294
+ .likhari-ribbon-quick [data-toolbar-toggle][aria-pressed='true'],
1295
+ .likhari-ribbon-utilities [data-toolbar-toggle][aria-pressed='true'] {
1296
+ color: var(--editor-accent);
1297
+ }
1298
+
1299
+ .likhari-ribbon-quick [data-toolbar-toggle][aria-pressed='true']::after,
1300
+ .likhari-ribbon-utilities [data-toolbar-toggle][aria-pressed='true']::after {
1301
+ content: '';
1302
+ position: absolute;
1303
+ inset-inline: 7px;
1304
+ bottom: 2px;
1305
+ height: 2px;
1306
+ border-radius: 1px;
1307
+ background: var(--editor-accent);
1308
+ }
1309
+
1310
+ /* Save with unsaved changes: accent icon and a dot, not the filled button the classic toolbar uses. */
1311
+ .likhari-ribbon-quick [data-dirty='true'] {
1312
+ color: var(--editor-accent);
1313
+ }
1314
+
1315
+ .likhari-ribbon-quick [data-dirty='true']::after {
1316
+ content: '';
1317
+ position: absolute;
1318
+ top: 4px;
1319
+ inset-inline-end: 4px;
1320
+ width: 7px;
1321
+ height: 7px;
1322
+ border-radius: 50%;
1323
+ background: var(--editor-accent);
1324
+ }
1325
+
1326
+ .likhari-ribbon-quick [data-dirty='true']:hover,
1327
+ .likhari-ribbon-quick [data-dirty='false'] {
1328
+ color: var(--editor-accent);
1329
+ }
1330
+
1331
+ .likhari-ribbon-quick [data-dirty='false']:disabled,
1332
+ .likhari-ribbon-quick [data-dirty='false'][data-disabled] {
1333
+ color: var(--editor-ink-soft);
1334
+ }
1335
+
1336
+ /* The right-click menu is compact: its icons follow the smaller item text (sizes set in Toolbar.tsx). */
1337
+ .likhari-context-menu svg {
1338
+ width: 14px;
1339
+ height: 14px;
1340
+ }
1341
+
1342
+ /* Cut, copy and paste as one row of icon buttons in the right-click menu. */
1343
+ .likhari-context-row {
1344
+ display: flex;
1345
+ gap: 2px;
1346
+ }
1347
+
1348
+ .likhari-context-row [data-menu-item] {
1349
+ flex: 1 1 0;
1350
+ justify-content: center;
1351
+ padding-block: 5px;
1352
+ }
1353
+
1354
+ .likhari-context-row [data-menu-item] > * {
1355
+ display: flex;
1356
+ justify-content: center;
1357
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.47",
3
+ "version": "0.1.48-dev.356",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {