@stll/folio-react 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
- import { f as containedHandler, u as useDrawerFocus } from "./renderAsync-DqldIlJu.js";
1
+ import { f as containedHandler, u as useDrawerFocus } from "./renderAsync-6eWf0MfZ.js";
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { CheckIcon, MoreVerticalIcon } from "lucide-react";
4
4
  import { useLocale, useTranslations } from "use-intl";
5
+ import { scrollEditorTo } from "@stll/folio-core/paged-layout/editorScrollRoot";
5
6
  import { closestHtmlElement, queryHtmlElement } from "@stll/folio-core/utils/domGuards";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  import { PANEL_METRICS } from "@stll/folio-core/panel-layout";
@@ -369,7 +370,10 @@ const CommentsSidebar = ({ comments, onCommentClick, activeCommentId = null, onC
369
370
  const scrollEl_1 = editorContainerRef?.current;
370
371
  const anchorY = lastKnownCardPositionsRef.current.get(cardId_1) ?? anchorPositions?.get(cardId_1);
371
372
  if (!scrollEl_1 || anchorY === void 0) return;
372
- scrollEl_1.scrollTop = Math.max(0, anchorY - scrollEl_1.clientHeight / 3);
373
+ scrollEditorTo(scrollEl_1, {
374
+ top: Math.max(0, anchorY - scrollEl_1.clientHeight / 3),
375
+ behavior: "instant"
376
+ });
373
377
  };
374
378
  const hasPositions = !isDrawer && cardPositions.size > 0;
375
379
  const avatarStyle = (name, size = 28) => ({
@@ -1,4 +1,4 @@
1
- import { n as DocxEditor$1, t as renderAsync } from "../renderAsync-DqldIlJu.js";
1
+ import { n as DocxEditor$1, t as renderAsync } from "../renderAsync-6eWf0MfZ.js";
2
2
  import { t as messages_exports } from "../messages.js";
3
3
  import { c } from "react-compiler-runtime";
4
4
  import { forwardRef } from "react";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as formatZoom, c as FormattingBar, d as AutocompleteCaretOverlay, i as clampZoom, l as ZoomControl, n as DocxEditor, o as parseZoom, r as ZOOM_PRESETS, s as useWheelZoom, t as renderAsync } from "./renderAsync-DqldIlJu.js";
1
+ import { a as formatZoom, c as FormattingBar, d as AutocompleteCaretOverlay, i as clampZoom, l as ZoomControl, n as DocxEditor, o as parseZoom, r as ZOOM_PRESETS, s as useWheelZoom, t as renderAsync } from "./renderAsync-6eWf0MfZ.js";
2
2
  import { n as FolioUIProvider } from "./folio-ui-DO95voaI.js";
3
3
  import { t as FindReplaceDialog } from "./FindReplaceDialog-BUwFQWaq.js";
4
4
  import { t as FootnotePropertiesDialog } from "./FootnotePropertiesDialog-6XdO0zBH.js";
@@ -14,6 +14,7 @@ import { cloneDocumentWithParagraphPropertySources } from "@stll/folio-core/docx
14
14
  import { normalizeBaseDirection } from "@stll/folio-core/docx/normalizeBaseDirection";
15
15
  import { getCachedNumberingMap } from "@stll/folio-core/docx/numberingParser";
16
16
  import { classifyEditorKeydown, deleteSelectedTable, historyShortcutOwner, isFocusInInputLike, isKeydownInShortcutScope, isMacPlatform } from "@stll/folio-core/managers/editorShortcuts";
17
+ import { getEditorScrollRoot, scrollEditorBy, scrollEditorElementIntoView, scrollEditorTo } from "@stll/folio-core/paged-layout/editorScrollRoot";
17
18
  import { computeScrollPageInfo, updateScrollPageTotal } from "@stll/folio-core/paged-layout/scrollPageInfo";
18
19
  import { NO_LIST_STATE, TextSelection, addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyFormatMarks, applyStyle, applyTableStyle, autoFitContents, captureFormatMarks, clearFormatting, clearTextColor, continueNumbering, createStyleResolver, decreaseIndent, decreaseListLevel, deleteColumn, deleteRow, deleteTable, distributeColumns, extractSelectionState, getDocumentBuiltInStyles, getSelectedText, getTableContext, increaseIndent, increaseListLevel, listStateLevel, mergeCells, removeTabStop, removeTableBorders, restartNumbering, sameListState, schema, selectColumn, selectRow, selectTable, setAlignment, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setFontFamily, setFontSize, setHighlight, setIndentFirstLine, setIndentLeft, setIndentRight, setInsideTableBorders, setLineSpacing, setLtr, setNumberingValue, setOutsideTableBorders, setRowHeight, setRtl, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableProperties, setTextColor, splitCell, toggleBidi, toggleBold, toggleBulletList, toggleHeaderRow, toggleItalic, toggleNoWrap, toggleNumberedList, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline } from "@stll/folio-core/prosemirror";
19
20
  import { acceptAIEditRevision, acceptChange, acceptSuggestion, findAIEditRevisionRange, findChangeAtPosition, findNextChange, findParagraphBoundaryChangeAtPosition, findPreviousChange, findSuggestionRange, getSuggestions, rejectAIEditRevision, rejectChange, rejectSuggestion } from "@stll/folio-core/prosemirror/commands/comments";
@@ -80,7 +81,7 @@ import { onPaintedLayoutChange, readBlockRects } from "@stll/folio-core/paged-la
80
81
  import { LayoutSelectionGate } from "@stll/folio-core/paged-layout/LayoutSelectionGate";
81
82
  import { collectRunFontsAtPmPositions, measureDirectiveGutter, projectRangesToRects } from "@stll/folio-core/paged-layout/rangeProjection";
82
83
  import { isReadOnlyEditKey } from "@stll/folio-core/paged-layout/readOnlyEditAttempt";
83
- import { getPageScrollTarget, prefersReducedMotionBehavior } from "@stll/folio-core/paged-layout/scrollNavigation";
84
+ import { getPageScrollTarget } from "@stll/folio-core/paged-layout/scrollNavigation";
84
85
  import { PAGES_CONTAINER_CLASS, scrollPagesToPmPosition } from "@stll/folio-core/paged-layout/scrollToPmPosition";
85
86
  import { flashParagraphFragmentsByParaId } from "@stll/folio-core/paged-layout/paragraphFlash";
86
87
  import { resizeColumnPair } from "@stll/folio-core/paged-layout/tableColumnResize";
@@ -96,6 +97,7 @@ import { createVisualLineState, handleVisualLineKeyDown } from "@stll/folio-core
96
97
  import { clampRangeToDocSize, resolveFolioAIBlockRange, resolveFolioAITextRange, resolvePassageRange } from "@stll/folio-core/ai-edits/blockRange";
97
98
  import { getFolioParaIdFromBlockId } from "@stll/folio-core/types/block-id";
98
99
  import { getPageTextFromLayout } from "@stll/folio-core/paged-layout/pageText";
100
+ import { allocateCommentId, seedCommentIdAbove } from "@stll/folio-core/prosemirror/commentIdAllocator";
99
101
  import { ContentControlWidgetController } from "@stll/folio-core/controller/contentControlWidgetController";
100
102
  import { createDefaultFindOptions, findInDocument } from "@stll/folio-core/utils/findReplace";
101
103
  import { PANEL_METRICS, computePanelLayout } from "@stll/folio-core/panel-layout";
@@ -853,10 +855,7 @@ const AnonymizationRectsOverlay = ({ groups, onTermClick, selectedCanonical = nu
853
855
  index: nextIndex
854
856
  };
855
857
  const el = spans[nextIndex];
856
- if (el) el.scrollIntoView({
857
- block: "center",
858
- behavior: prefersReducedMotionBehavior()
859
- });
858
+ if (el) scrollEditorElementIntoView(el);
860
859
  }, [selectedCanonical, selectionSeq]);
861
860
  const overlayRef = useRef(null);
862
861
  useEffect(() => {
@@ -2170,18 +2169,6 @@ const TemplateDirectivesOverlay = (t0) => {
2170
2169
  const EDGE_ZONE = 40;
2171
2170
  /** Maximum scroll speed in pixels per frame (~60fps). */
2172
2171
  const MAX_SPEED = 12;
2173
- /**
2174
- * Find the nearest scrollable ancestor.
2175
- */
2176
- function findScrollParent(el) {
2177
- let parent = el.parentElement;
2178
- while (parent && parent !== document.documentElement) {
2179
- const { overflowY } = getComputedStyle(parent);
2180
- if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) return parent;
2181
- parent = parent.parentElement;
2182
- }
2183
- return null;
2184
- }
2185
2172
  function useDragAutoScroll({ pagesContainerRef, onScrollExtendSelection }) {
2186
2173
  const rafIdRef = useRef(null);
2187
2174
  const lastMouseRef = useRef({
@@ -2189,14 +2176,7 @@ function useDragAutoScroll({ pagesContainerRef, onScrollExtendSelection }) {
2189
2176
  y: 0
2190
2177
  });
2191
2178
  const activeRef = useRef(false);
2192
- const scrollParentRef = useRef(null);
2193
- const getScrollParent = useCallback(() => {
2194
- if (scrollParentRef.current) return scrollParentRef.current;
2195
- const pages = pagesContainerRef.current;
2196
- if (!pages) return null;
2197
- scrollParentRef.current = findScrollParent(pages);
2198
- return scrollParentRef.current;
2199
- }, [pagesContainerRef]);
2179
+ const getScrollParent = useCallback(() => getEditorScrollRoot(pagesContainerRef.current), [pagesContainerRef]);
2200
2180
  const stopAutoScroll = useCallback(() => {
2201
2181
  activeRef.current = false;
2202
2182
  if (rafIdRef.current !== null) {
@@ -2219,7 +2199,7 @@ function useDragAutoScroll({ pagesContainerRef, onScrollExtendSelection }) {
2219
2199
  scrollDelta = Math.min(MAX_SPEED, proximity_0 / EDGE_ZONE * MAX_SPEED);
2220
2200
  }
2221
2201
  if (scrollDelta !== 0) {
2222
- container.scrollTop += scrollDelta;
2202
+ scrollEditorBy(container, scrollDelta);
2223
2203
  onScrollExtendSelection(mx, my);
2224
2204
  }
2225
2205
  rafIdRef.current = requestAnimationFrame(tick);
@@ -2841,11 +2821,18 @@ function buildFootnoteRenderItems(pageFootnoteMap, footnoteContentMap, doc) {
2841
2821
  const PagedEditor = forwardRef(function PagedEditor(props, ref) {
2842
2822
  const { document, documentIO, documentIdentity, fonts: hostFonts, styles, theme: _theme, sectionProperties, headerContent, footerContent, firstPageHeaderContent, firstPageFooterContent, headerContentRId, footerContentRId, firstPageHeaderContentRId, firstPageFooterContentRId, readOnly = false, pageGap = 24, zoom = 1, pageRenderer, markupView, showMarginGuides = false, marginGuideColor, onDocumentChange, onReadOnlyEditAttempt, onCopy, onCut, onPaste, onSelectionChange, onSelectionTextChange, onEditorViewReady, externalPlugins = EMPTY_PLUGINS, showTemplateDirectives = true, collaboration, extensionManager, suggestionModeActive = false, suggestionAuthor, onActiveNoteStoryChange, onHeaderFooterDoubleClick, hfEditMode, activeHeaderFooterRId, onBodyClick, className, style, pageEndGutter = 0, sidebarOverlay, scrollContainerRef: scrollContainerRefProp, onHyperlinkClick, onContextMenu, onAnchorPositionsChange, onTotalPagesChange, onError, anchorPositionMode = "comments-and-revisions", onAnonymizationTermClick, selectedAnonymizationCanonical = null, anonymizationSelectionSeq } = props;
2843
2823
  const getScrollContainer = useCallback(() => {
2844
- if (scrollContainerRefProp && typeof scrollContainerRefProp === "object") return scrollContainerRefProp.current;
2845
- return containerRef.current;
2824
+ if (scrollContainerRefProp && typeof scrollContainerRefProp === "object") return getEditorScrollRoot(scrollContainerRefProp.current);
2825
+ return getEditorScrollRoot(containerRef.current);
2846
2826
  }, [scrollContainerRefProp]);
2847
2827
  const containerRef = useRef(null);
2848
2828
  const pagesContainerRef = useRef(null);
2829
+ useLayoutEffect(() => {
2830
+ if (!scrollContainerRefProp || typeof scrollContainerRefProp !== "object") return;
2831
+ const root = scrollContainerRefProp.current;
2832
+ if (!root || root.hasAttribute("data-folio-scroll")) return;
2833
+ root.setAttribute("data-folio-scroll", "");
2834
+ return () => root.removeAttribute("data-folio-scroll");
2835
+ }, [scrollContainerRefProp]);
2849
2836
  const folioEmitterRef = useRef(createFolioEditorEmitter());
2850
2837
  const hiddenPMRef = useRef(null);
2851
2838
  const hfPMsRef = useRef(null);
@@ -4029,10 +4016,8 @@ const PagedEditor = forwardRef(function PagedEditor(props, ref) {
4029
4016
  scrollToPositionImpl(target_0.pmPos);
4030
4017
  return;
4031
4018
  }
4032
- (pagesContainerRef.current?.querySelector(`[data-page-number="${String(target_0.pageIndex + 1)}"]`))?.scrollIntoView({
4033
- block: "center",
4034
- inline: "nearest"
4035
- });
4019
+ const shell = pagesContainerRef.current?.querySelector(`[data-page-number="${String(target_0.pageIndex + 1)}"]`);
4020
+ scrollEditorElementIntoView(shell ?? null);
4036
4021
  }, [layout, scrollToPositionImpl]);
4037
4022
  const scrollToParaIdImpl = useCallback((paraId, options_2) => {
4038
4023
  const state_7 = hiddenPMRef.current?.getState();
@@ -5100,11 +5085,17 @@ const PagedEditor = forwardRef(function PagedEditor(props, ref) {
5100
5085
  if (["PageUp", "PageDown"].includes(e_7.key) && !e_7.metaKey && !e_7.ctrlKey) {}
5101
5086
  if (e_7.key === "Home" && (e_7.metaKey || e_7.ctrlKey)) {
5102
5087
  const sc = getScrollContainer();
5103
- if (sc) sc.scrollTop = 0;
5088
+ if (sc) scrollEditorTo(sc, {
5089
+ top: 0,
5090
+ behavior: "instant"
5091
+ });
5104
5092
  }
5105
5093
  if (e_7.key === "End" && (e_7.metaKey || e_7.ctrlKey)) {
5106
5094
  const sc_0 = getScrollContainer();
5107
- if (sc_0) sc_0.scrollTop = sc_0.scrollHeight;
5095
+ if (sc_0) scrollEditorTo(sc_0, {
5096
+ top: sc_0.scrollHeight,
5097
+ behavior: "instant"
5098
+ });
5108
5099
  }
5109
5100
  }, [
5110
5101
  copySelectionText,
@@ -5451,20 +5442,22 @@ const PagedEditor = forwardRef(function PagedEditor(props, ref) {
5451
5442
  undo() {
5452
5443
  const target_5 = getActiveEditorStory();
5453
5444
  if (target_5.type === "none") return false;
5454
- return undo(target_5.view.state, target_5.view.dispatch);
5445
+ return target_5.type === "body" ? hiddenPMRef.current?.undo() ?? false : undo(target_5.view.state, target_5.view.dispatch);
5455
5446
  },
5456
5447
  redo() {
5457
5448
  const target_6 = getActiveEditorStory();
5458
5449
  if (target_6.type === "none") return false;
5459
- return redo(target_6.view.state, target_6.view.dispatch);
5450
+ return target_6.type === "body" ? hiddenPMRef.current?.redo() ?? false : redo(target_6.view.state, target_6.view.dispatch);
5460
5451
  },
5461
5452
  canUndo() {
5462
5453
  const target_7 = getActiveEditorStory();
5463
- return target_7.type === "none" ? false : undo(target_7.view.state);
5454
+ if (target_7.type === "none") return false;
5455
+ return target_7.type === "body" ? hiddenPMRef.current?.canUndo() ?? false : undo(target_7.view.state);
5464
5456
  },
5465
5457
  canRedo() {
5466
5458
  const target_8 = getActiveEditorStory();
5467
- return target_8.type === "none" ? false : redo(target_8.view.state);
5459
+ if (target_8.type === "none") return false;
5460
+ return target_8.type === "body" ? hiddenPMRef.current?.canRedo() ?? false : redo(target_8.view.state);
5468
5461
  },
5469
5462
  setSelection(anchor_3, head_1) {
5470
5463
  folioEditor.setSelection(anchor_3, head_1);
@@ -5603,6 +5596,7 @@ const PagedEditor = forwardRef(function PagedEditor(props, ref) {
5603
5596
  };
5604
5597
  return /* @__PURE__ */ jsxs("div", {
5605
5598
  ref: containerRef,
5599
+ "data-folio-scroll": scrollContainerRefProp && typeof scrollContainerRefProp === "object" ? void 0 : "",
5606
5600
  className: `folio-root paged-editor ${className ?? ""}`,
5607
5601
  style: {
5608
5602
  ...containerStyles,
@@ -6804,20 +6798,6 @@ const toast = (msg) => {
6804
6798
  };
6805
6799
  /** Stable empty anchor positions Map used as the initial state. */
6806
6800
  const EMPTY_ANCHOR_POSITIONS = /* @__PURE__ */ new Map();
6807
- let nextCommentId = Date.now();
6808
- function allocateCommentId() {
6809
- return nextCommentId++;
6810
- }
6811
- /**
6812
- * Bump the in-process comment-id counter above `id` so a later
6813
- * `allocateCommentId()` can never re-mint an id that was adopted from an
6814
- * external source-of-truth model. No-op when `id` is already below the
6815
- * current counter. Mirrors the allocator-seeding behaviour of upstream
6816
- * docx-editor's `CommentIdAllocator.seedAbove` (05f2ab84).
6817
- */
6818
- function seedCommentIdAbove(id) {
6819
- if (id >= nextCommentId) nextCommentId = id + 1;
6820
- }
6821
6801
  function getCommentAuthorKey(author) {
6822
6802
  const trimmed = author?.trim();
6823
6803
  return trimmed && trimmed.length > 0 ? trimmed : "Unknown";
@@ -7176,10 +7156,7 @@ const readFindSelectionSeed = () => {
7176
7156
  //#region src/components/dialogs/findReplaceUtils.ts
7177
7157
  const scrollToMatch = (containerElement, match) => {
7178
7158
  if (!containerElement) return;
7179
- (containerElement.querySelector(`[data-paragraph-index="${match.paragraphIndex}"]`) ?? containerElement.querySelector(`.layout-paragraph[data-block-id="block-${match.paragraphIndex + 1}"]`) ?? containerElement.querySelectorAll(".layout-paragraph").item(match.paragraphIndex)).scrollIntoView({
7180
- behavior: prefersReducedMotionBehavior(),
7181
- block: "center"
7182
- });
7159
+ scrollEditorElementIntoView(containerElement.querySelector(`[data-paragraph-index="${match.paragraphIndex}"]`) ?? containerElement.querySelector(`.layout-paragraph[data-block-id="block-${match.paragraphIndex + 1}"]`) ?? containerElement.querySelectorAll(".layout-paragraph").item(match.paragraphIndex));
7183
7160
  };
7184
7161
  //#endregion
7185
7162
  //#region src/components/dialogs/useFindReplace.ts
@@ -10083,9 +10060,10 @@ function useContextMenu(t0) {
10083
10060
  * callbacks each render.
10084
10061
  */
10085
10062
  const useDocumentLoader = (t0) => {
10086
- const $ = c(21);
10063
+ const $ = c(22);
10087
10064
  const { documentBuffer, initialDocument, password, history, onError, onCompatibilityChange, onReset, setDocumentLoadState } = t0;
10088
10065
  const originalBufferRef = useRef(null);
10066
+ const originalBufferIdentityRef = useRef(null);
10089
10067
  const [loadedDocumentIdentity, setLoadedDocumentIdentity] = useState("0");
10090
10068
  let t1;
10091
10069
  if ($[0] !== history || $[1] !== onCompatibilityChange || $[2] !== onError || $[3] !== onReset || $[4] !== setDocumentLoadState) {
@@ -10158,26 +10136,33 @@ const useDocumentLoader = (t0) => {
10158
10136
  useEffect(t3, t4);
10159
10137
  let t5;
10160
10138
  let t6;
10161
- if ($[12] !== documentBuffer || $[13] !== history.state) {
10139
+ if ($[12] !== documentBuffer || $[13] !== history.state || $[14] !== loadedDocumentIdentity) {
10162
10140
  t5 = () => {
10141
+ if (originalBufferIdentityRef.current === loadedDocumentIdentity) return;
10142
+ originalBufferIdentityRef.current = loadedDocumentIdentity;
10163
10143
  if (history.state?.originalBuffer) {
10164
10144
  originalBufferRef.current = history.state.originalBuffer;
10165
10145
  return;
10166
10146
  }
10167
- if (documentBuffer instanceof ArrayBuffer) originalBufferRef.current = documentBuffer;
10147
+ originalBufferRef.current = documentBuffer instanceof ArrayBuffer ? documentBuffer : null;
10168
10148
  };
10169
- t6 = [history.state, documentBuffer];
10149
+ t6 = [
10150
+ history.state,
10151
+ documentBuffer,
10152
+ loadedDocumentIdentity
10153
+ ];
10170
10154
  $[12] = documentBuffer;
10171
10155
  $[13] = history.state;
10172
- $[14] = t5;
10173
- $[15] = t6;
10156
+ $[14] = loadedDocumentIdentity;
10157
+ $[15] = t5;
10158
+ $[16] = t6;
10174
10159
  } else {
10175
- t5 = $[14];
10176
- t6 = $[15];
10160
+ t5 = $[15];
10161
+ t6 = $[16];
10177
10162
  }
10178
10163
  useEffect(t5, t6);
10179
10164
  let t7;
10180
- if ($[16] !== api.loadBuffer || $[17] !== api.loadParsedDocument || $[18] !== api.resetForNewDocument || $[19] !== loadedDocumentIdentity) {
10165
+ if ($[17] !== api.loadBuffer || $[18] !== api.loadParsedDocument || $[19] !== api.resetForNewDocument || $[20] !== loadedDocumentIdentity) {
10181
10166
  t7 = {
10182
10167
  loadBuffer: api.loadBuffer,
10183
10168
  loadParsedDocument: api.loadParsedDocument,
@@ -10185,12 +10170,12 @@ const useDocumentLoader = (t0) => {
10185
10170
  originalBufferRef,
10186
10171
  loadedDocumentIdentity
10187
10172
  };
10188
- $[16] = api.loadBuffer;
10189
- $[17] = api.loadParsedDocument;
10190
- $[18] = api.resetForNewDocument;
10191
- $[19] = loadedDocumentIdentity;
10192
- $[20] = t7;
10193
- } else t7 = $[20];
10173
+ $[17] = api.loadBuffer;
10174
+ $[18] = api.loadParsedDocument;
10175
+ $[19] = api.resetForNewDocument;
10176
+ $[20] = loadedDocumentIdentity;
10177
+ $[21] = t7;
10178
+ } else t7 = $[21];
10194
10179
  return t7;
10195
10180
  };
10196
10181
  //#endregion
@@ -11019,7 +11004,10 @@ function useZoomAndPageInfo({ scrollContainerRef, pagedEditorRef, initialZoom })
11019
11004
  if (anchor.zoom === zoom) return;
11020
11005
  const scrollContainer_1 = scrollContainerRef.current;
11021
11006
  if (!scrollContainer_1) return;
11022
- scrollContainer_1.scrollTop = getScrollTopForZoomAnchor(anchor, zoom);
11007
+ scrollEditorTo(scrollContainer_1, {
11008
+ top: getScrollTopForZoomAnchor(anchor, zoom),
11009
+ behavior: "instant"
11010
+ });
11023
11011
  updateScrollPageInfo(scrollContainer_1);
11024
11012
  scheduleScrollPageInfoFade();
11025
11013
  }, [
@@ -12637,15 +12625,25 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
12637
12625
  const sanitizedCommentsProp = useMemo(() => commentsProp !== void 0 ? sanitizeControlledComments(commentsProp) : void 0, [commentsProp]);
12638
12626
  const isControlledComments = sanitizedCommentsProp !== void 0;
12639
12627
  const comments = isControlledComments ? sanitizedCommentsProp : internalComments;
12628
+ useLayoutEffect(() => {
12629
+ for (const { id } of doc?.package.document.comments ?? []) seedCommentIdAbove(id);
12630
+ for (const { id: id_0 } of comments) seedCommentIdAbove(id_0);
12631
+ }, [comments, doc]);
12640
12632
  const commentsDirtyRef = useRef(false);
12641
12633
  const commentsRef = useRef(comments);
12642
12634
  commentsRef.current = comments;
12643
12635
  const readonlyCommentsRef = commentsRef;
12644
12636
  const onCommentsChangeRef = useRef(onCommentsChange);
12645
12637
  onCommentsChangeRef.current = onCommentsChange;
12638
+ const createComment$1 = useCallback((text, author, parentId) => {
12639
+ for (const { id: id_1 } of doc?.package.document.comments ?? []) seedCommentIdAbove(id_1);
12640
+ for (const { id: id_2 } of commentsRef.current) seedCommentIdAbove(id_2);
12641
+ return createComment(text, author, parentId);
12642
+ }, [doc]);
12646
12643
  const setComments = useCallback((next) => {
12647
12644
  const resolved = typeof next === "function" ? next(commentsRef.current) : next;
12648
12645
  if (resolved === commentsRef.current) return;
12646
+ for (const { id: id_3 } of resolved) seedCommentIdAbove(id_3);
12649
12647
  commentsRef.current = resolved;
12650
12648
  if (!isControlledComments) setInternalComments(resolved);
12651
12649
  onCommentsChangeRef.current?.(resolved);
@@ -12693,13 +12691,13 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
12693
12691
  const visibleComments = useMemo(() => {
12694
12692
  const visibleRootIds = /* @__PURE__ */ new Set();
12695
12693
  for (const comment_1 of comments) {
12696
- const parentId = getCommentParentId(comment_1);
12697
- if (parentId === null || parentId === void 0 || !visibleCommentIds.has(comment_1.id)) continue;
12698
- visibleRootIds.add(parentId);
12694
+ const parentId_0 = getCommentParentId(comment_1);
12695
+ if (parentId_0 === null || parentId_0 === void 0 || !visibleCommentIds.has(comment_1.id)) continue;
12696
+ visibleRootIds.add(parentId_0);
12699
12697
  }
12700
12698
  return comments.filter((comment_2) => {
12701
- const parentId_0 = getCommentParentId(comment_2);
12702
- if (parentId_0 !== null && parentId_0 !== void 0) return visibleCommentIds.has(comment_2.id);
12699
+ const parentId_1 = getCommentParentId(comment_2);
12700
+ if (parentId_1 !== null && parentId_1 !== void 0) return visibleCommentIds.has(comment_2.id);
12703
12701
  return visibleCommentIds.has(comment_2.id) || visibleRootIds.has(comment_2.id);
12704
12702
  });
12705
12703
  }, [comments, visibleCommentIds]);
@@ -12712,7 +12710,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
12712
12710
  if (!root) return;
12713
12711
  const nodes = root.querySelectorAll(`.layout-run-text${COMMENT_ANCHOR_SELECTOR}`);
12714
12712
  for (const node of nodes) {
12715
- const commentIds = commentAnchorIds(node).map((id) => Number.parseInt(id, 10));
12713
+ const commentIds = commentAnchorIds(node).map((id_4) => Number.parseInt(id_4, 10));
12716
12714
  if (!commentIds.some((commentId) => commentId === -1 || visibleCommentIds.has(commentId))) {
12717
12715
  node.style.backgroundColor = "transparent";
12718
12716
  node.style.borderBottom = "2px solid transparent";
@@ -12757,6 +12755,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
12757
12755
  return {
12758
12756
  comments,
12759
12757
  setComments,
12758
+ createComment: createComment$1,
12760
12759
  isControlledComments,
12761
12760
  commentsRef: readonlyCommentsRef,
12762
12761
  commentsDirtyRef,
@@ -12794,7 +12793,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
12794
12793
  */
12795
12794
  /** No headings to track while the outline is not shown. */
12796
12795
  const NO_HEADINGS = [];
12797
- const CommentsSidebar = lazy(() => import("./CommentsSidebar-D0TBbrpB.js").then((m) => ({ default: m.CommentsSidebar })));
12796
+ const CommentsSidebar = lazy(() => import("./CommentsSidebar-Bk5pQ_2C.js").then((m) => ({ default: m.CommentsSidebar })));
12798
12797
  const TextContextMenu = lazy(() => import("./TextContextMenu-Bqth8itn.js").then((m) => ({ default: m.TextContextMenu })));
12799
12798
  const loadAttemptSelectiveSave = async () => {
12800
12799
  const { attemptSelectiveSave } = await import("@stll/folio-core/docx/selectiveSave");
@@ -13017,8 +13016,30 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
13017
13016
  if (!onEditorViewReady) return;
13018
13017
  if (lastReportedViewRef.current === view_0) return;
13019
13018
  lastReportedViewRef.current = view_0;
13019
+ if (view_0 && initialScrollTop !== void 0) {
13020
+ const initialScroll = initialScrollRef.current;
13021
+ switch (initialScroll.status) {
13022
+ case "scheduled":
13023
+ cancelAnimationFrame(initialScroll.frame);
13024
+ scrollEditorTo(scrollContainerRef.current, {
13025
+ top: initialScrollTop,
13026
+ behavior: "instant"
13027
+ });
13028
+ initialScrollRef.current = { status: "applied" };
13029
+ break;
13030
+ case "waiting":
13031
+ scrollEditorTo(scrollContainerRef.current, {
13032
+ top: initialScrollTop,
13033
+ behavior: "instant"
13034
+ });
13035
+ initialScrollRef.current = { status: "applied" };
13036
+ break;
13037
+ case "applied": break;
13038
+ default: return initialScroll;
13039
+ }
13040
+ }
13020
13041
  onEditorViewReady(view_0);
13021
- }, [onEditorViewReady]);
13042
+ }, [onEditorViewReady, initialScrollTop]);
13022
13043
  useLayoutEffect(() => {
13023
13044
  if (!onEditorViewReady) return;
13024
13045
  const view_1 = pagedEditorRef.current?.getView() ?? null;
@@ -13077,7 +13098,7 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
13077
13098
  const imageInputRef = useRef(null);
13078
13099
  const editorContentRef = useRef(null);
13079
13100
  const scrollContainerRef = useRef(null);
13080
- const initialScrollAppliedRef = useRef(false);
13101
+ const initialScrollRef = useRef({ status: "waiting" });
13081
13102
  const [widestLaidOutPageWidth, setWidestLaidOutPageWidth] = useState(null);
13082
13103
  const hasDocument = history.state !== null;
13083
13104
  useEffect(() => {
@@ -13118,7 +13139,7 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
13118
13139
  size: 4,
13119
13140
  color: { rgb: "000000" }
13120
13141
  });
13121
- const { comments, setComments, commentsRef, commentsDirtyRef, commentsLoadedRef, showCommentsSidebar, setShowCommentsSidebar, setVisibleCommentAuthors, activeCommentId, setActiveCommentId, isAddingComment, setIsAddingComment, commentSelectionRange, setCommentSelectionRange, addCommentYPosition, setAddCommentYPosition, floatingCommentBtn, setFloatingCommentBtn, commentAuthors, visibleCommentAuthorSet, visibleComments, syncCommentHighlightStyles } = useFolioComments({
13142
+ const { comments, createComment, setComments, commentsRef, commentsDirtyRef, commentsLoadedRef, showCommentsSidebar, setShowCommentsSidebar, setVisibleCommentAuthors, activeCommentId, setActiveCommentId, isAddingComment, setIsAddingComment, commentSelectionRange, setCommentSelectionRange, addCommentYPosition, setAddCommentYPosition, floatingCommentBtn, setFloatingCommentBtn, commentAuthors, visibleCommentAuthorSet, visibleComments, syncCommentHighlightStyles } = useFolioComments({
13122
13143
  doc: history.state,
13123
13144
  autoOpenReviewSidebar,
13124
13145
  anchorPositions,
@@ -13243,11 +13264,14 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
13243
13264
  onError,
13244
13265
  onCompatibilityChange,
13245
13266
  onReset: useCallback(() => {
13246
- initialScrollAppliedRef.current = false;
13247
- if (scrollContainerRef.current) {
13248
- scrollContainerRef.current.scrollTop = 0;
13249
- scrollContainerRef.current.scrollLeft = 0;
13250
- }
13267
+ const initialScroll_0 = initialScrollRef.current;
13268
+ if (initialScroll_0.status === "scheduled") cancelAnimationFrame(initialScroll_0.frame);
13269
+ initialScrollRef.current = { status: "waiting" };
13270
+ if (scrollContainerRef.current) scrollEditorTo(scrollContainerRef.current, {
13271
+ top: 0,
13272
+ left: 0,
13273
+ behavior: "instant"
13274
+ });
13251
13275
  commentsDirtyRef.current = false;
13252
13276
  commentsLoadedRef.current = false;
13253
13277
  setComments([]);
@@ -13303,13 +13327,24 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
13303
13327
  extractTrackedChanges
13304
13328
  ]);
13305
13329
  useEffect(() => {
13306
- if (initialScrollTop === void 0 || state.documentLoad.status !== "ready" || initialScrollAppliedRef.current) return;
13307
- initialScrollAppliedRef.current = true;
13330
+ if (initialScrollTop === void 0 || state.documentLoad.status !== "ready" || initialScrollRef.current.status !== "waiting") return;
13308
13331
  const frame = requestAnimationFrame(() => {
13309
- if (scrollContainerRef.current) scrollContainerRef.current.scrollTop = initialScrollTop;
13332
+ initialScrollRef.current = { status: "applied" };
13333
+ scrollEditorTo(scrollContainerRef.current, {
13334
+ top: initialScrollTop,
13335
+ behavior: "instant"
13336
+ });
13310
13337
  });
13338
+ initialScrollRef.current = {
13339
+ status: "scheduled",
13340
+ frame
13341
+ };
13311
13342
  return () => {
13312
- cancelAnimationFrame(frame);
13343
+ const initialScroll_1 = initialScrollRef.current;
13344
+ if (initialScroll_1.status === "scheduled" && initialScroll_1.frame === frame) {
13345
+ cancelAnimationFrame(frame);
13346
+ initialScrollRef.current = { status: "waiting" };
13347
+ }
13313
13348
  };
13314
13349
  }, [
13315
13350
  initialScrollTop,
@@ -14362,6 +14397,9 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
14362
14397
  let tripwireResult = null;
14363
14398
  let savedBuffer = null;
14364
14399
  try {
14400
+ const view_18 = pagedEditorRef.current?.getView();
14401
+ const editorState = view_18?.state;
14402
+ const baselineBuffer = originalBufferRef.current;
14365
14403
  const current = buildCurrentDocument();
14366
14404
  const doc_1 = current ? withoutUnreferencedNotes(current) : null;
14367
14405
  if (!doc_1) return null;
@@ -14369,18 +14407,13 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
14369
14407
  const flags = resolveSelectiveSaveFlags(featureFlags);
14370
14408
  const useSelectiveForSave = flags.selectiveSave && options?.mode !== FOLIO_DOCX_SERIALIZATION_MODE.full;
14371
14409
  const shouldAttemptSelective = useSelectiveForSave || flags.selectiveSaveTripwire;
14372
- const view_18 = pagedEditorRef.current?.getView();
14373
- const baselineBuffer = originalBufferRef.current;
14374
14410
  let selectiveBuffer = null;
14375
- if (shouldAttemptSelective && view_18 && baselineBuffer) {
14376
- const editorState = view_18.state;
14377
- selectiveBuffer = await (await loadAttemptSelectiveSave())(doc_1, baselineBuffer, {
14378
- changedParaIds: getChangedParagraphIds(editorState),
14379
- structuralChange: hasStructuralChanges(editorState),
14380
- hasUntrackedChanges: hasUntrackedChanges(editorState),
14381
- maxBytes: flags.selectiveSaveMaxBytes
14382
- });
14383
- }
14411
+ if (shouldAttemptSelective && editorState && baselineBuffer) selectiveBuffer = await (await loadAttemptSelectiveSave())(doc_1, baselineBuffer, {
14412
+ changedParaIds: getChangedParagraphIds(editorState),
14413
+ structuralChange: hasStructuralChanges(editorState),
14414
+ hasUntrackedChanges: hasUntrackedChanges(editorState),
14415
+ maxBytes: flags.selectiveSaveMaxBytes
14416
+ });
14384
14417
  let buffer = useSelectiveForSave ? selectiveBuffer : null;
14385
14418
  let fullBuffer = null;
14386
14419
  const repackSourceDoc = baselineBuffer ? {
@@ -14390,8 +14423,8 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
14390
14423
  const repackFull = async () => {
14391
14424
  const repackDocx = await loadRepackDocx();
14392
14425
  const repack = () => repackDocx(repackSourceDoc);
14393
- return view_18 ? repackWithEditorSectionRemovals({
14394
- state: view_18.state,
14426
+ return editorState ? repackWithEditorSectionRemovals({
14427
+ state: editorState,
14395
14428
  document: repackSourceDoc,
14396
14429
  repack
14397
14430
  }) : repack();
@@ -14406,7 +14439,7 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
14406
14439
  const { compareSelectiveVsFull } = await import("@stll/folio-core/docx/selectiveSaveTripwire");
14407
14440
  tripwireResult = await compareSelectiveVsFull(selectiveBuffer, fullBuffer);
14408
14441
  } catch {}
14409
- if (view_18) {
14442
+ if (view_18 && pagedEditorRef.current?.getView() === view_18 && view_18.state.doc === editorState?.doc) {
14410
14443
  originalBufferRef.current = buffer;
14411
14444
  view_18.dispatch(clearTrackedChanges(view_18.state));
14412
14445
  }
@@ -14940,6 +14973,7 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
14940
14973
  loadBuffer,
14941
14974
  updateComments,
14942
14975
  replaceComments,
14976
+ createComment,
14943
14977
  commentsRef,
14944
14978
  commentsDirtyRef,
14945
14979
  getActiveEditorStory,
@@ -15232,7 +15266,11 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
15232
15266
  ]);
15233
15267
  const handleCommentReply = useCallback((id_2, text_6) => {
15234
15268
  updateComments((previous_2) => [...previous_2, createComment(text_6, author, id_2)]);
15235
- }, [author, updateComments]);
15269
+ }, [
15270
+ author,
15271
+ createComment,
15272
+ updateComments
15273
+ ]);
15236
15274
  const handleAddComment = useCallback((addText) => {
15237
15275
  const comment_4 = createComment(addText, author);
15238
15276
  const view_54 = pagedEditorRef.current?.getView();
@@ -15259,6 +15297,7 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
15259
15297
  }, [
15260
15298
  author,
15261
15299
  commentSelectionRange,
15300
+ createComment,
15262
15301
  setActiveCommentId,
15263
15302
  setAddCommentYPosition,
15264
15303
  setCommentSelectionRange,
@@ -15269,7 +15308,11 @@ const DocxEditor = forwardRef(function DocxEditor({ documentBuffer, password, do
15269
15308
  ]);
15270
15309
  const handleTrackedChangeReply = useCallback((revisionId_2, text_7) => {
15271
15310
  updateComments((previous_4) => [...previous_4, createComment(text_7, author, revisionId_2)]);
15272
- }, [author, updateComments]);
15311
+ }, [
15312
+ author,
15313
+ createComment,
15314
+ updateComments
15315
+ ]);
15273
15316
  const handleCancelAddComment = useCallback(() => {
15274
15317
  const view_55 = pagedEditorRef.current?.getView();
15275
15318
  if (view_55 && commentSelectionRange) removePendingCommentMarkRange(view_55, commentSelectionRange);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-react",
3
- "version": "0.24.1",
3
+ "version": "0.24.2",
4
4
  "description": "React editor for folio: a Word-document (.docx) editor for the browser, built on @stll/folio-core and ProseMirror.",
5
5
  "keywords": [
6
6
  "document-editor",
@@ -75,7 +75,7 @@
75
75
  "@fontsource/noto-sans-arabic": "^5.2.7",
76
76
  "@fontsource/source-sans-3": "^5.2.9",
77
77
  "@fontsource/tinos": "^5.2.7",
78
- "@stll/folio-core": "^0.54.0",
78
+ "@stll/folio-core": "^0.54.2",
79
79
  "better-result": "3.0.1",
80
80
  "lucide-react": "1.24.0",
81
81
  "prosemirror-history": "^1.5.0",