@stll/folio-react 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { t as containedHandler } from "./contained-handler-4uiqUVRn.js";
2
- import { i as cn, n as FolioUIProvider, r as useFolioUI, t as DEFAULT_COMPONENTS } from "./folio-ui-o_rftArH.js";
2
+ import { i as cn, n as FolioUIProvider, r as useFolioUI, t as DEFAULT_COMPONENTS } from "./folio-ui-CA59webC.js";
3
3
  import { i as findInDocument, o as scrollToMatch, t as useFindReplace$1 } from "./useFindReplace-MZ8wi31A.js";
4
+ import { n as getFolioMessages } from "./messages-heGr9XLo.js";
4
5
  import * as React$1 from "react";
5
- import { Component, Suspense, cloneElement, createContext, isValidElement, lazy, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
+ import React, { Component, Suspense, cloneElement, createContext, isValidElement, lazy, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
7
  import { AlertCircleIcon, AlertTriangleIcon, AlignCenterIcon, AlignJustifyIcon, AlignLeftIcon, AlignRightIcon, BaselineIcon, BoldIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardCopyIcon, EyeIcon, FileIcon, GlobeIcon, ImageIcon, IndentDecreaseIcon, IndentIncreaseIcon, InfoIcon, ItalicIcon, LinkIcon, ListIcon, ListOrderedIcon, MoreHorizontalIcon, PaintbrushIcon, PenLineIcon, PencilIcon, PilcrowIcon, Redo2Icon, RulerIcon, SeparatorHorizontalIcon, SquarePenIcon, StickyNoteIcon, TableIcon, TableOfContentsIcon, TypeIcon, UnderlineIcon, Undo2Icon, UnlinkIcon, XIcon } from "lucide-react";
7
- import { useTranslations } from "use-intl";
8
+ import { IntlProvider, useTranslations } from "use-intl";
8
9
  import { applyFolioAIEditOperations, applyFolioAIEditOperations as applyFolioAIEditOperations$1, createFolioAIEditSnapshot, createFolioAIEditSnapshot as createFolioAIEditSnapshot$1, diffWordSegments, hashFolioAIBlockText, normalizeFolioAIBlockText } from "@stll/folio-core/ai-edits";
9
10
  import { normalizeBaseDirection } from "@stll/folio-core/docx/normalizeBaseDirection";
10
11
  import { getCachedNumberingMap } from "@stll/folio-core/docx/numberingParser";
@@ -66,16 +67,17 @@ import { collectRunFontsAtPmPositions, measureContentLeft, projectRangesToRects
66
67
  import { isReadOnlyEditKey } from "@stll/folio-core/paged-layout/readOnlyEditAttempt";
67
68
  import { getPageScrollTarget, prefersReducedMotionBehavior } from "@stll/folio-core/paged-layout/scrollNavigation";
68
69
  import { PAGES_CONTAINER_CLASS, scrollFolioPositionIntoView, scrollPagesToPmPosition } from "@stll/folio-core/paged-layout/scrollToPmPosition";
70
+ import { flashParagraphFragmentsByParaId } from "@stll/folio-core/paged-layout/paragraphFlash";
69
71
  import { DEFAULT_PAGE_HEIGHT_PX, getMargins, getPageSize, twipsToPixels as twipsToPixels$1 } from "@stll/folio-core/paged-layout/sectionGeometry";
70
72
  import { resizeColumnPair } from "@stll/folio-core/paged-layout/tableColumnResize";
71
73
  import { tableInsertButtonOffset } from "@stll/folio-core/paged-layout/tableInsertButtonGeometry";
72
74
  import { getTransactionDirtyRange } from "@stll/folio-core/paged-layout/transactionDirtyRange";
75
+ import { findParagraphByParaId, findStartPosForParaId } from "@stll/folio-core/prosemirror/utils/findParagraphByParaId";
73
76
  import { expectFontFamilyMarkAttrs, expectImageAttrs, expectTableAttrs, expectTableCellAttrs, mergeImageAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs } from "@stll/folio-core/prosemirror/attrs";
74
77
  import { extractEmbeddedFonts } from "@stll/folio-core/fonts/embeddedFonts";
75
78
  import { panic } from "better-result";
76
79
  import { collectRemoteSelections, createHiddenEditorManager, createHiddenEditorState } from "@stll/folio-core/controller/hiddenEditorManager";
77
80
  import { createFolioAIEditSnapshot as createFolioAIEditSnapshot$2 } from "@stll/folio-core/ai-edits/snapshot";
78
- import { findParagraphByParaId } from "@stll/folio-core/prosemirror/utils/findParagraphByParaId";
79
81
  import { deriveBlockId, getFolioParaIdFromBlockId, getFolioParaIdFromBlockId as getFolioParaIdFromBlockId$1, getSequentialFolioBlockIdIndex, isFolioBlockId, isSequentialFolioBlockId } from "@stll/folio-core/types/block-id";
80
82
  import { CONTENT_CONTROL_WIDGET_EVENT_NAME, dispatchDatePick, dispatchDropdownPick } from "@stll/folio-core/prosemirror/plugins/contentControlWidgets";
81
83
  import { ErrorManager } from "@stll/folio-core/managers/ErrorManager";
@@ -91,6 +93,7 @@ import { applyImagePosition, applyImageProperties, applyImageTransform, applyIma
91
93
  import { classifyEditorKeydown, deleteSelectedTable, isFocusInInputLike, isMacPlatform } from "@stll/folio-core/managers/editorShortcuts";
92
94
  import { getScrollTopForZoomAnchor, getViewportCenterZoomAnchorForZoomChange } from "@stll/folio-core/paged-layout/zoomScrollAnchor";
93
95
  import { redo, undo } from "prosemirror-history";
96
+ import { createRoot } from "react-dom/client";
94
97
  import { createEmptyDocument } from "@stll/folio-core/utils/createDocument";
95
98
  import { createDocx } from "@stll/folio-core/docx/rezip";
96
99
  import { DEFAULT_AI_SUGGESTION_PRESETS } from "@stll/folio-core/ai-suggestions/types";
@@ -3569,6 +3572,24 @@ function PagedEditor(props) {
3569
3572
  inline: "nearest"
3570
3573
  });
3571
3574
  }, [layout, scrollToPositionImpl]);
3575
+ const scrollToParaIdImpl = useCallback((paraId, options) => {
3576
+ const state = hiddenPMRef.current?.getState();
3577
+ if (!state) return false;
3578
+ const startPos = findStartPosForParaId(state.doc, paraId);
3579
+ if (startPos == null || startPos < 0) return false;
3580
+ scrollToPositionImpl(startPos);
3581
+ requestAnimationFrame(() => {
3582
+ if (options?.highlight) {
3583
+ const pages = pagesContainerRef.current;
3584
+ if (pages) flashParagraphFragmentsByParaId(pages, paraId, options.highlight);
3585
+ }
3586
+ const targetNode = state.doc.nodeAt(startPos);
3587
+ const inner = targetNode?.isTextblock === true ? Math.min(startPos + 1 + targetNode.content.size, state.doc.content.size) : Math.min(startPos + 1, state.doc.content.size);
3588
+ hiddenPMRef.current?.setSelection(inner);
3589
+ hiddenPMRef.current?.focus();
3590
+ });
3591
+ return true;
3592
+ }, [scrollToPositionImpl]);
3572
3593
  const focusHiddenEditor = useCallback(() => {
3573
3594
  if (readOnly) {
3574
3595
  containerRef.current?.focus({ preventScroll: true });
@@ -4989,6 +5010,7 @@ function PagedEditor(props) {
4989
5010
  },
4990
5011
  scrollToPosition: scrollToPositionImpl,
4991
5012
  scrollToPage: scrollToPageImpl,
5013
+ scrollToParaId: scrollToParaIdImpl,
4992
5014
  getPageNumberForPmPos(pmPos) {
4993
5015
  const container = pagesContainerRef.current;
4994
5016
  if (!container) return null;
@@ -5028,6 +5050,7 @@ function PagedEditor(props) {
5028
5050
  ensureHiddenEditorView,
5029
5051
  folioEditor,
5030
5052
  scrollToPageImpl,
5053
+ scrollToParaIdImpl,
5031
5054
  scrollToPositionImpl
5032
5055
  ]);
5033
5056
  useEffect(() => {
@@ -6238,30 +6261,34 @@ function ContentControlWidgetsOverlay({ getEditorView }) {
6238
6261
  if (liveView && value.length > 0) dispatchDatePick(liveView, open.pmPos, value);
6239
6262
  close();
6240
6263
  };
6241
- return createPortal(/* @__PURE__ */ jsxs("div", {
6242
- role: "dialog",
6243
- "aria-label": open.kind === "dropdown" ? t("contentControlDropdownAriaLabel") : t("contentControlDateAriaLabel"),
6244
- style,
6245
- className: "bg-popover text-popover-foreground min-w-[10rem] rounded-md border p-1 shadow-md",
6246
- children: [open.kind === "dropdown" && /* @__PURE__ */ jsx("div", {
6247
- role: "menu",
6248
- className: "flex flex-col",
6249
- children: open.items.length === 0 ? /* @__PURE__ */ jsx("div", {
6250
- className: "text-muted-foreground px-2 py-1 text-sm",
6251
- children: t("contentControlDropdownNoOptions")
6252
- }) : open.items.map((item) => /* @__PURE__ */ jsx(MenuItem, {
6253
- onClick: () => onDropdownPick(item.value),
6254
- children: item.displayText
6255
- }, `${item.value}::${item.displayText}`))
6256
- }), open.kind === "date" && /* @__PURE__ */ jsx(DatePickerPopover, {
6257
- clearLabel: t("clearDate"),
6258
- defaultOpen: true,
6259
- onChange: (value) => {
6260
- if (value) onDatePick(value);
6261
- },
6262
- showIcon: false,
6263
- value: null
6264
- })]
6264
+ return createPortal(/* @__PURE__ */ jsx("div", {
6265
+ className: "folio-root",
6266
+ style: { display: "contents" },
6267
+ children: /* @__PURE__ */ jsxs("div", {
6268
+ role: "dialog",
6269
+ "aria-label": open.kind === "dropdown" ? t("contentControlDropdownAriaLabel") : t("contentControlDateAriaLabel"),
6270
+ style,
6271
+ className: "bg-popover text-popover-foreground min-w-[10rem] rounded-md border p-1 shadow-md",
6272
+ children: [open.kind === "dropdown" && /* @__PURE__ */ jsx("div", {
6273
+ role: "menu",
6274
+ className: "flex flex-col",
6275
+ children: open.items.length === 0 ? /* @__PURE__ */ jsx("div", {
6276
+ className: "text-muted-foreground px-2 py-1 text-sm",
6277
+ children: t("contentControlDropdownNoOptions")
6278
+ }) : open.items.map((item) => /* @__PURE__ */ jsx(MenuItem, {
6279
+ onClick: () => onDropdownPick(item.value),
6280
+ children: item.displayText
6281
+ }, `${item.value}::${item.displayText}`))
6282
+ }), open.kind === "date" && /* @__PURE__ */ jsx(DatePickerPopover, {
6283
+ clearLabel: t("clearDate"),
6284
+ defaultOpen: true,
6285
+ onChange: (value) => {
6286
+ if (value) onDatePick(value);
6287
+ },
6288
+ showIcon: false,
6289
+ value: null
6290
+ })]
6291
+ })
6265
6292
  }), document.body);
6266
6293
  }
6267
6294
  //#endregion
@@ -6345,12 +6372,12 @@ const DocumentOutline = ({ headings, scrollContainerRef, topOffset, docSize, onH
6345
6372
  };
6346
6373
  //#endregion
6347
6374
  //#region src/components/DocxEditorDialogs.tsx
6348
- const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-CG1VKA_g.js").then((m) => ({ default: m.FindReplaceDialog })));
6349
- const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-mQMnGfHu.js").then((m) => ({ default: m.TablePropertiesDialog })));
6350
- const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-BQnOnDf4.js").then((m) => ({ default: m.ImagePositionDialog })));
6351
- const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-Dn4N7qZv.js").then((m) => ({ default: m.ImagePropertiesDialog })));
6352
- const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-MuW4D3gt.js").then((m) => ({ default: m.FootnotePropertiesDialog })));
6353
- const PageSetupDialog = lazy(() => import("./PageSetupDialog-CxJUEkoC.js").then((m) => ({ default: m.PageSetupDialog })));
6375
+ const FindReplaceDialog = lazy(() => import("./FindReplaceDialog-EFjmqEbg.js").then((m) => ({ default: m.FindReplaceDialog })));
6376
+ const TablePropertiesDialog = lazy(() => import("./TablePropertiesDialog-ZmzjSqME.js").then((m) => ({ default: m.TablePropertiesDialog })));
6377
+ const ImagePositionDialog = lazy(() => import("./ImagePositionDialog-FGLCOf0Q.js").then((m) => ({ default: m.ImagePositionDialog })));
6378
+ const ImagePropertiesDialog = lazy(() => import("./ImagePropertiesDialog-Baokui4M.js").then((m) => ({ default: m.ImagePropertiesDialog })));
6379
+ const FootnotePropertiesDialog = lazy(() => import("./FootnotePropertiesDialog-VfF7SQo2.js").then((m) => ({ default: m.FootnotePropertiesDialog })));
6380
+ const PageSetupDialog = lazy(() => import("./PageSetupDialog-Df2HjHiu.js").then((m) => ({ default: m.PageSetupDialog })));
6354
6381
  function DocxEditorDialogs({ findReplace, tableProperties, imagePosition, imageProperties, pageSetup, footnoteProperties }) {
6355
6382
  return /* @__PURE__ */ jsxs(Suspense, {
6356
6383
  fallback: null,
@@ -6672,7 +6699,7 @@ function ToolbarButton({ active = false, disabled = false, title, onClick, onDou
6672
6699
  };
6673
6700
  const button = /* @__PURE__ */ jsx("button", {
6674
6701
  type: "button",
6675
- className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md", "transition-colors duration-100", "-webkit-font-smoothing-antialiased", "text-[var(--doc-text-muted)] hover:bg-[var(--doc-primary-light)] hover:text-[var(--doc-text)]", active && "bg-[var(--doc-primary-light)] text-[var(--doc-text)] hover:bg-[var(--doc-primary-light)]", disabled && "cursor-not-allowed text-[var(--doc-text-subtle)] opacity-[0.16] disabled:hover:bg-transparent disabled:hover:text-[var(--doc-text-subtle)]", className),
6702
+ className: cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md", "transition-colors duration-100", "antialiased", "text-[var(--doc-text-muted)] hover:bg-[var(--doc-primary-light)] hover:text-[var(--doc-text)]", active && "bg-[var(--doc-primary-light)] text-[var(--doc-text)] hover:bg-[var(--doc-primary-light)]", disabled && "cursor-not-allowed text-[var(--doc-text-subtle)] opacity-[0.16] disabled:hover:bg-transparent disabled:hover:text-[var(--doc-text-subtle)]", className),
6676
6703
  onMouseDown: handleMouseDown,
6677
6704
  onClick: disabled ? void 0 : onClick,
6678
6705
  onDoubleClick: disabled ? void 0 : onDoubleClick,
@@ -9691,21 +9718,32 @@ const getBuiltinTableStyle = (styleId) => BUILTIN_STYLES.find((s) => s.id === st
9691
9718
  * (`replaceComments`/`updateComments`) stay in the component because they
9692
9719
  * depend on its document-build pipeline.
9693
9720
  */
9694
- function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef }) {
9721
+ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorContentRef, commentsProp, onCommentsChange }) {
9695
9722
  const [showCommentsSidebar, setShowCommentsSidebar] = useState(false);
9696
9723
  const [visibleCommentAuthors, setVisibleCommentAuthors] = useState(null);
9697
9724
  const [activeCommentId, setActiveCommentId] = useState(null);
9698
- const [comments, setComments] = useState([]);
9725
+ const [internalComments, setInternalComments] = useState([]);
9726
+ const isControlledComments = commentsProp !== void 0;
9727
+ const comments = isControlledComments ? commentsProp : internalComments;
9699
9728
  const commentsDirtyRef = useRef(false);
9700
- const commentsRef = useRef([]);
9729
+ const commentsRef = useRef(comments);
9701
9730
  commentsRef.current = comments;
9731
+ const onCommentsChangeRef = useRef(onCommentsChange);
9732
+ onCommentsChangeRef.current = onCommentsChange;
9733
+ const setComments = useCallback((next) => {
9734
+ const resolved = typeof next === "function" ? next(commentsRef.current) : next;
9735
+ if (resolved === commentsRef.current) return;
9736
+ commentsRef.current = resolved;
9737
+ if (!isControlledComments) setInternalComments(resolved);
9738
+ onCommentsChangeRef.current?.(resolved);
9739
+ }, [isControlledComments]);
9702
9740
  const [isAddingComment, setIsAddingComment] = useState(false);
9703
9741
  const [commentSelectionRange, setCommentSelectionRange] = useState(null);
9704
9742
  const [addCommentYPosition, setAddCommentYPosition] = useState(null);
9705
9743
  const [floatingCommentBtn, setFloatingCommentBtn] = useState(null);
9706
9744
  const commentsLoadedRef = useRef(false);
9707
9745
  useEffect(() => {
9708
- if (commentsLoadedRef.current) return;
9746
+ if (isControlledComments || commentsLoadedRef.current) return;
9709
9747
  if (!doc) return;
9710
9748
  const bodyComments = doc.package.document.comments;
9711
9749
  if (bodyComments && bodyComments.length > 0) {
@@ -9715,7 +9753,12 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
9715
9753
  if (autoOpenReviewSidebar) setShowCommentsSidebar(true);
9716
9754
  commentsLoadedRef.current = true;
9717
9755
  }
9718
- }, [autoOpenReviewSidebar, doc]);
9756
+ }, [
9757
+ autoOpenReviewSidebar,
9758
+ doc,
9759
+ isControlledComments,
9760
+ setComments
9761
+ ]);
9719
9762
  const commentAuthors = useMemo(() => {
9720
9763
  const seen = /* @__PURE__ */ new Set();
9721
9764
  const authors = [];
@@ -9801,6 +9844,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
9801
9844
  return {
9802
9845
  comments,
9803
9846
  setComments,
9847
+ isControlledComments,
9804
9848
  commentsRef,
9805
9849
  commentsDirtyRef,
9806
9850
  commentsLoadedRef,
@@ -9836,7 +9880,7 @@ function useFolioComments({ doc, autoOpenReviewSidebar, anchorPositions, editorC
9836
9880
  * - Loading states
9837
9881
  */
9838
9882
  const CommentsSidebar = lazy(() => import("./CommentsSidebar-BsYOInaX.js").then((m) => ({ default: m.CommentsSidebar })));
9839
- const TextContextMenu = lazy(() => import("./TextContextMenu-DzTbp2sn.js").then((m) => ({ default: m.TextContextMenu })));
9883
+ const TextContextMenu = lazy(() => import("./TextContextMenu-CG0V37wi.js").then((m) => ({ default: m.TextContextMenu })));
9840
9884
  const loadAttemptSelectiveSave = async () => {
9841
9885
  const { attemptSelectiveSave } = await import("@stll/folio-core/docx/selectiveSave");
9842
9886
  return attemptSelectiveSave;
@@ -9915,7 +9959,7 @@ function areActiveTrackedChangesEqual(a, b) {
9915
9959
  /**
9916
9960
  * DocxEditor - Complete DOCX editor component
9917
9961
  */
9918
- function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKey, onSave, author = "User", onChange, onSelectionChange, onSelectionTextChange, onError, onFontsLoaded: onFontsLoadedCallback, fontFamilies, fonts, theme, showToolbar = true, showZoomControl = true, showReviewControls = true, showHeaderFooterEditing = true, showMarginGuides: _showMarginGuides = false, marginGuideColor: _marginGuideColor, showRuler: showRulerProp = false, rulerUnit = "inch", initialZoom = 1, enableWheelZoom = true, readOnly: readOnlyProp = false, autoOpenReviewSidebar = true, components, toolbarExtra, className = "", style, placeholder, loadingIndicator, preserveDocumentWhileLoading = false, initialScrollTop, onScrollTopChange, showOutline: showOutlineProp = true, onPrint, onInsertImage, onInsertTable, showTableInsert = true, onInsertPageBreak, onInsertTOC, onCopy: _onCopy, onCut: _onCut, onPaste: _onPaste, mode: modeProp, onModeChange, onReadonlyEditAttempt, onCompatibilityChange, onEditorViewReady, onAnonymizationMatchesChange, onAnonymizationTermClick, selectedAnonymizationCanonical = null, anonymizationSelectionSeq, showTemplateDirectives = false, onSlashMenuChange, onSlashMenuKeyAction, customContextMenuItems, onCustomContextAction, collaboration, featureFlags, onSelectiveSaveTripwire }) {
9962
+ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKey, onSave, author = "User", onChange, onSelectionChange, onSelectionTextChange, onError, onFontsLoaded: onFontsLoadedCallback, fontFamilies, fonts, theme, showToolbar = true, showZoomControl = true, showReviewControls = true, showHeaderFooterEditing = true, showMarginGuides: _showMarginGuides = false, marginGuideColor: _marginGuideColor, showRuler: showRulerProp = false, rulerUnit = "inch", initialZoom = 1, enableWheelZoom = true, readOnly: readOnlyProp = false, autoOpenReviewSidebar = true, components, toolbarExtra, className = "", style, placeholder, loadingIndicator, preserveDocumentWhileLoading = false, initialScrollTop, onScrollTopChange, showOutline: showOutlineProp = true, onPrint, onInsertImage, onInsertTable, showTableInsert = true, onInsertPageBreak, onInsertTOC, onCopy: _onCopy, onCut: _onCut, onPaste: _onPaste, mode: modeProp, onModeChange, onReadonlyEditAttempt, onCompatibilityChange, onEditorViewReady, onAnonymizationMatchesChange, onAnonymizationTermClick, selectedAnonymizationCanonical = null, anonymizationSelectionSeq, showTemplateDirectives = false, onSlashMenuChange, onSlashMenuKeyAction, customContextMenuItems, onCustomContextAction, comments: commentsProp, onCommentsChange, collaboration, featureFlags, onSelectiveSaveTripwire }) {
9919
9963
  const t = useTranslations("folio");
9920
9964
  useEffect(() => {
9921
9965
  const onClipboardReadError = () => {
@@ -10129,7 +10173,9 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
10129
10173
  doc: history.state,
10130
10174
  autoOpenReviewSidebar,
10131
10175
  anchorPositions,
10132
- editorContentRef
10176
+ editorContentRef,
10177
+ commentsProp,
10178
+ onCommentsChange
10133
10179
  });
10134
10180
  const styleResolverCacheRef = useRef(null);
10135
10181
  const getCachedStyleResolver = useCallback((styles) => {
@@ -11390,6 +11436,7 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
11390
11436
  scrollToPage: (pageNumber) => {
11391
11437
  pagedEditorRef.current?.scrollToPage(pageNumber);
11392
11438
  },
11439
+ scrollToParaId: (paraId, options) => pagedEditorRef.current?.scrollToParaId(paraId, options) ?? false,
11393
11440
  openPrintPreview: handleDirectPrint,
11394
11441
  print: handleDirectPrint,
11395
11442
  loadDocument: loadParsedDocument,
@@ -12318,4 +12365,100 @@ function DocxEditor({ ref, documentBuffer, document: initialDocument, documentKe
12318
12365
  });
12319
12366
  }
12320
12367
  //#endregion
12321
- export { AutocompleteCaretOverlay, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DocxEditor, FolioUIProvider, FormattingBar, ZOOM_PRESETS, ZoomControl, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applySuggestions, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clampZoom, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, finishAutocompleteSuggestion, formatZoom, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getFolioCaretViewportRect, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, insertImageFromFile, insertPageBreakInView, insertTableInView, insertTableOfContentsInView, isFolioBlockId, isSequentialFolioBlockId, isSuggestionStale, normalizeFolioAIBlockText, parseZoom, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult, useWheelZoom };
12368
+ //#region src/renderAsync.tsx
12369
+ /**
12370
+ * Imperative API for mounting a DOCX editor into a DOM element.
12371
+ *
12372
+ * Returns a framework-agnostic {@link EditorHandle} extended with zoom and
12373
+ * scroll helpers. Wraps the editor in `IntlProvider` so `use-intl` works
12374
+ * without extra host setup.
12375
+ *
12376
+ * ```ts
12377
+ * import { renderAsync } from "@stll/folio-react";
12378
+ * import "@stll/folio-react/standalone.css";
12379
+ *
12380
+ * const editor = await renderAsync(buffer, document.getElementById("host")!, {
12381
+ * readOnly: false,
12382
+ * showToolbar: true,
12383
+ * });
12384
+ *
12385
+ * const blob = await editor.save();
12386
+ * editor.destroy();
12387
+ * ```
12388
+ */
12389
+ /**
12390
+ * Render a DOCX editor into a container element.
12391
+ *
12392
+ * Resolves once the document has parsed and the first `onChange` fires.
12393
+ * Rejects if `onError` runs before that milestone.
12394
+ */
12395
+ const renderAsync = (input, container, options = {}) => {
12396
+ const { locale = "en", ...editorOptions } = options;
12397
+ return new Promise((resolve, reject) => {
12398
+ const ref = React.createRef();
12399
+ let root = null;
12400
+ let settled = false;
12401
+ try {
12402
+ root = createRoot(container);
12403
+ } catch (error) {
12404
+ reject(error);
12405
+ return;
12406
+ }
12407
+ const handle = {
12408
+ save: async () => {
12409
+ const buffer = await (ref.current?.save() ?? Promise.resolve(null));
12410
+ if (!buffer) return null;
12411
+ return new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
12412
+ },
12413
+ getDocument: () => ref.current?.getDocument() ?? null,
12414
+ focus: () => {
12415
+ ref.current?.focus();
12416
+ },
12417
+ setZoom: (zoom) => {
12418
+ ref.current?.setZoom(zoom);
12419
+ },
12420
+ scrollToPosition: (pmPos) => {
12421
+ ref.current?.getEditorRef()?.scrollToPosition(pmPos);
12422
+ },
12423
+ scrollToPage: (pageNumber) => {
12424
+ ref.current?.scrollToPage(pageNumber);
12425
+ },
12426
+ destroy: () => {
12427
+ if (!settled) {
12428
+ settled = true;
12429
+ reject(/* @__PURE__ */ new Error("Editor was destroyed before mounting completed."));
12430
+ }
12431
+ root?.unmount();
12432
+ root = null;
12433
+ }
12434
+ };
12435
+ const element = /* @__PURE__ */ jsx(IntlProvider, {
12436
+ locale,
12437
+ messages: getFolioMessages(locale),
12438
+ children: /* @__PURE__ */ jsx(DocxEditor, {
12439
+ ...editorOptions,
12440
+ documentBuffer: input,
12441
+ onError: (error) => {
12442
+ editorOptions["onError"]?.(error);
12443
+ if (!settled) {
12444
+ settled = true;
12445
+ root?.unmount();
12446
+ root = null;
12447
+ reject(error);
12448
+ }
12449
+ },
12450
+ onChange: (doc) => {
12451
+ editorOptions["onChange"]?.(doc);
12452
+ if (!settled) {
12453
+ settled = true;
12454
+ resolve(handle);
12455
+ }
12456
+ },
12457
+ ref
12458
+ })
12459
+ });
12460
+ root.render(element);
12461
+ });
12462
+ };
12463
+ //#endregion
12464
+ export { AutocompleteCaretOverlay, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DocxEditor, FolioUIProvider, FormattingBar, ZOOM_PRESETS, ZoomControl, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applySuggestions, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clampZoom, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, finishAutocompleteSuggestion, formatZoom, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getFolioCaretViewportRect, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, insertImageFromFile, insertPageBreakInView, insertTableInView, insertTableOfContentsInView, isFolioBlockId, isSequentialFolioBlockId, isSuggestionStale, normalizeFolioAIBlockText, parseZoom, renderAsync, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult, useWheelZoom };