@inshapardaz/likhari-react 0.1.31 → 0.1.32-dev.273

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.
@@ -11,6 +11,12 @@ export interface ToolbarStyle {
11
11
  bordered?: boolean;
12
12
  /** `light` tints the active button, `filled` fills it with the accent. Default `light`. */
13
13
  variant?: 'light' | 'filled';
14
+ /**
15
+ * Move the lowest-priority groups into a "..." menu once the toolbar no
16
+ * longer fits its width (UI spec §3.3). `false` keeps every group inline
17
+ * and lets the toolbar wrap onto as many rows as it needs. Default `true`.
18
+ */
19
+ overflow?: boolean;
14
20
  }
15
21
  export interface ToolbarProps {
16
22
  config: ResolvedEditorFeatureConfig;
@@ -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;AAsgBvD,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B;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,+BAkiCrV"}
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;AA8gBvD,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,+BAuqCrV"}
@@ -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, useRef, useState } from 'react';
2
+ import { Fragment, forwardRef, useCallback, useEffect, 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';
@@ -123,6 +123,15 @@ function withDividers(sections) {
123
123
  .filter((section) => Boolean(section))
124
124
  .flatMap((section, index) => (index === 0 ? [section] : [_jsx(MantineToolbar.Divider, {}, `divider-${index}`), section]));
125
125
  }
126
+ const EMPTY_OVERFLOW = new Set();
127
+ function setsEqual(a, b) {
128
+ if (a.size !== b.size)
129
+ return false;
130
+ for (const v of a)
131
+ if (!b.has(v))
132
+ return false;
133
+ return true;
134
+ }
126
135
  /** Mantine Select (combobox) for the toolbar's dropdown controls. The icon
127
136
  * labels the control itself, since a dropdown can't show one per option in
128
137
  * its closed state. The dropdown portals to <body>, so it is not clipped by
@@ -179,6 +188,7 @@ function PoetryMenuItems({ strings, layout, scale, centered, onAction, }) {
179
188
  export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurusStores = [], onAddAutoCorrect, onSave, isDirty, showSave, findOpen = false, onToggleFind, spellOpen = false, onToggleSpell, autoCorrectOpen = false, onToggleAutoCorrect, fontOptions = DEFAULT_FONT_OPTIONS, direction = 'ltr', locale = 'en', drafts }) {
180
189
  const bordered = toolbarStyle?.bordered ?? true;
181
190
  const variant = toolbarStyle?.variant ?? 'light';
191
+ const overflowEnabled = toolbarStyle?.overflow ?? true;
182
192
  const [editor] = useLexicalComposerContext();
183
193
  const [state, setState] = useState(INITIAL_STATE);
184
194
  const strings = useStrings(locale);
@@ -186,6 +196,17 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
186
196
  const [draftsOpen, setDraftsOpen] = useState(false);
187
197
  const rtl = direction === 'rtl';
188
198
  const ALIGN_ICONS = rtl ? ALIGN_ICONS_RTL : ALIGN_ICONS_LTR;
199
+ // Responsive "priority+" overflow (UI spec §3.3): the toolbar's outer
200
+ // container and a hidden nowrap clone (every movable group forced inline)
201
+ // are both measured; whenever the clone's natural width exceeds the
202
+ // container's available width, the lowest-priority movable groups move
203
+ // into the "..." menu, lowest-priority first, until what remains inline
204
+ // fits (or nothing is left to move). Font and insert are overflow-menu
205
+ // items per spec §3.1's closing note, and tools (autocorrect, spellcheck,
206
+ // join lines) is lower priority still, so it is first to move.
207
+ const toolbarContainerRef = useRef(null);
208
+ const toolbarMeasureRef = useRef(null);
209
+ const [overflowGroupKeys, setOverflowGroupKeys] = useState(EMPTY_OVERFLOW);
189
210
  const updateToolbar = useCallback(() => {
190
211
  editor.getEditorState().read(() => {
191
212
  const selection = $getSelection();
@@ -506,7 +527,7 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
506
527
  }
507
528
  if (point)
508
529
  word = $wordAtDomPoint(point.node, point.offset);
509
- if (!cell && !couplet) {
530
+ if (!cell && !couplet && config.joinLines) {
510
531
  menu.text = true;
511
532
  menu.canJoin = $canJoinLines();
512
533
  }
@@ -538,7 +559,7 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
538
559
  previous?.removeEventListener('contextmenu', onContextMenu);
539
560
  root?.addEventListener('contextmenu', onContextMenu);
540
561
  });
541
- }, [editor, config.links, config.tables, config.poetry.enabled, config.language.spellCheck, config.language.thesaurus, thesaurusStores]);
562
+ }, [editor, config.links, config.tables, config.poetry.enabled, config.language.spellCheck, config.language.thesaurus, config.joinLines, thesaurusStores]);
542
563
  // Cut and copy put the selected text on the clipboard as plain text.
543
564
  const copyOrCut = (cut) => async () => {
544
565
  restoreSelection();
@@ -746,18 +767,72 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
746
767
  }, { discrete: true });
747
768
  editor.focus();
748
769
  };
749
- 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 })), _jsx(ToolbarButton, { icon: IconArrowsJoin, title: strings.toolbar.joinLines, disabled: !state.canJoinLines, onClick: joinLines })] }, "tools"));
770
+ 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"));
750
771
  const endSection = (drafts || config.findReplace) && (_jsxs(MantineToolbar.Group, { className: "likhari-toolbar-cluster", style: { marginInlineStart: 'auto' }, 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 }))] }, "end"));
751
772
  const startSections = [
752
773
  showSave && (_jsx(MantineToolbar.Group, { className: "likhari-toolbar-cluster", children: _jsx(ToolbarButton, { icon: IconDeviceFloppy, title: strings.toolbar.save, dirty: Boolean(isDirty), onClick: onSave }) }, "save")),
753
774
  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")),
754
775
  formatSection,
755
776
  alignSection,
756
- fontSection,
757
- insertSection,
758
- toolsSection,
759
777
  ];
760
- return (_jsxs(MantineToolbar, { "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), endSection, _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
778
+ // Lowest priority first: the first to move into the "..." menu once the
779
+ // toolbar stops fitting its available width.
780
+ const movableSections = [
781
+ { key: 'tools', node: toolsSection },
782
+ { key: 'font', node: fontSection },
783
+ { key: 'insert', node: insertSection },
784
+ ].filter((section) => Boolean(section.node));
785
+ const movableGroupKeys = useMemo(() => movableSections.map((section) => section.key).join(','), [movableSections]);
786
+ // Re-measure whenever the container is resized, or whenever the set of
787
+ // movable sections (and so the hidden clone's content) changes.
788
+ useLayoutEffect(() => {
789
+ if (!overflowEnabled) {
790
+ setOverflowGroupKeys((prev) => (prev.size === 0 ? prev : EMPTY_OVERFLOW));
791
+ return;
792
+ }
793
+ const container = toolbarContainerRef.current;
794
+ const measure = toolbarMeasureRef.current;
795
+ if (!container || !measure)
796
+ return;
797
+ // Rough width of the "..." button itself (icon button + its gap) —
798
+ // only charged against the budget once something has actually moved
799
+ // into it, since it isn't rendered at all otherwise.
800
+ const OVERFLOW_BUTTON_WIDTH = 38;
801
+ const recompute = () => {
802
+ const available = container.clientWidth;
803
+ const total = measure.scrollWidth;
804
+ if (total <= available) {
805
+ setOverflowGroupKeys((prev) => (prev.size === 0 ? prev : EMPTY_OVERFLOW));
806
+ return;
807
+ }
808
+ const widths = new Map();
809
+ measure.querySelectorAll('[data-group-key]').forEach((el) => {
810
+ widths.set(el.dataset.groupKey, el.getBoundingClientRect().width);
811
+ });
812
+ let remaining = total;
813
+ const next = new Set();
814
+ for (const key of movableGroupKeys.split(',').filter(Boolean)) {
815
+ if (remaining <= available)
816
+ break;
817
+ const w = widths.get(key);
818
+ if (w === undefined)
819
+ continue;
820
+ remaining -= w;
821
+ next.add(key);
822
+ }
823
+ if (next.size > 0)
824
+ remaining += OVERFLOW_BUTTON_WIDTH;
825
+ setOverflowGroupKeys((prev) => (setsEqual(prev, next) ? prev : next));
826
+ };
827
+ recompute();
828
+ const ro = new ResizeObserver(recompute);
829
+ ro.observe(container);
830
+ ro.observe(measure);
831
+ return () => ro.disconnect();
832
+ });
833
+ const visibleMovable = movableSections.filter((section) => !overflowGroupKeys.has(section.key));
834
+ const hiddenMovable = movableSections.filter((section) => overflowGroupKeys.has(section.key));
835
+ 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, { position: "bottom-end", withinPortal: true, portalProps: { target: portalTarget }, shadow: "sm", width: "max-content", closeOnItemClick: false, onOpen: snapshotSelection, 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)) }) })] })] })), endSection, _jsxs(Menu, { opened: contextMenu !== null, onChange: (opened) => {
761
836
  if (!opened)
762
837
  setContextMenu(null);
763
838
  }, 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: () => {
@@ -787,5 +862,5 @@ export function Toolbar({ config, toolbarStyle, dictionaryStores = [], thesaurus
787
862
  openLinkDialog();
788
863
  }, 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(() => {
789
864
  $joinSelectedLines();
790
- }), 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 })] })] }), (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 })] }));
865
+ }), 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 })] }));
791
866
  }
@@ -19,15 +19,25 @@
19
19
  its own height so only the canvas below scrolls. */
20
20
  .likhari-toolbar {
21
21
  display: flex;
22
+ /* Containing block for the hidden width-measuring clone (Toolbar.tsx):
23
+ without this, its position: absolute would resolve against the
24
+ viewport instead, and its intentionally-unwrapped (possibly very wide)
25
+ content could inflate the host page's scroll area instead of being
26
+ clipped by .likhari-root's overflow: hidden. */
27
+ position: relative;
22
28
  flex-shrink: 0;
23
29
  align-items: center;
24
30
  gap: 8px;
25
31
  min-height: 46px;
26
32
  padding: 4px 8px;
27
33
  background: var(--editor-paper-dim);
28
- /* Wrap instead of scroll: an overflow container would clip menus and grow
29
- a scrollbar. Dropdowns (Select/Menu) portal out, so nothing needs to
30
- escape this box. */
34
+ /* Wrap instead of scroll, once the responsive overflow (UI spec §3.3) has
35
+ already moved the lowest-priority groups into the "..." menu: an
36
+ overflow container would clip menus and grow a scrollbar, where wrapping
37
+ to a second row keeps every control reachable without one. This is a
38
+ deliberate departure from the spec's literal "horizontal scroll"
39
+ fallback — it only applies in the edge case where even the essential
40
+ groups (§3.3's mobile tier) don't fit a very narrow viewport. */
31
41
  flex-wrap: wrap;
32
42
  row-gap: 4px;
33
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inshapardaz/likhari-react",
3
- "version": "0.1.31",
3
+ "version": "0.1.32-dev.273",
4
4
  "description": "React implementation of the Likhari editor, built on @lexical/react.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "prepublishOnly": "npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@inshapardaz/likhari-converters": "^0.1.0",
37
- "@inshapardaz/likhari-core": "^0.1.0",
36
+ "@inshapardaz/likhari-converters": "*",
37
+ "@inshapardaz/likhari-core": "*",
38
38
  "@lexical/link": "^0.20.0",
39
39
  "@lexical/list": "^0.20.0",
40
40
  "@lexical/react": "^0.20.0",