@a3s-lab/office 0.37.1 → 0.37.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.
@@ -4561,6 +4561,7 @@ function isSpreadsheetNativeTextUndoTarget(target) {
4561
4561
  return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable || Boolean(target.closest('[contenteditable="true"]'));
4562
4562
  }
4563
4563
  function isSpreadsheetCellEditingTarget(target) {
4564
+ if (!isSpreadsheetCellEditorTarget(target)) return false;
4564
4565
  if (!(target instanceof Element)) return false;
4565
4566
  const formulaInput = target.closest('.fortune-fx-input');
4566
4567
  if (formulaInput) return true;
@@ -4571,6 +4572,9 @@ function isSpreadsheetCellEditingTarget(target) {
4571
4572
  const zIndex = Number.parseInt(inputBox.style.zIndex || getComputedStyle(inputBox).zIndex, 10);
4572
4573
  return !Number.isFinite(zIndex) || zIndex >= 0;
4573
4574
  }
4575
+ function isSpreadsheetCellEditorTarget(target) {
4576
+ return target instanceof Element && Boolean(target.closest('.fortune-fx-input, .luckysheet-cell-input'));
4577
+ }
4574
4578
  function spreadsheetFormulaBarSelectAllTarget(event) {
4575
4579
  if (event.defaultPrevented || event.repeat || event.altKey || event.shiftKey || !(event.metaKey || event.ctrlKey) || 'a' !== event.key.toLocaleLowerCase() || !(event.target instanceof Element)) return null;
4576
4580
  const formulaBar = event.target.closest('.fortune-fx-input');
@@ -25185,6 +25189,11 @@ function spreadsheetClipboardSnapshotForText(plainText) {
25185
25189
  }
25186
25190
  return createSpreadsheetTextClipboardSnapshot(normalized);
25187
25191
  }
25192
+ function clearRichSpreadsheetClipboard() {
25193
+ richSpreadsheetClipboard = null;
25194
+ browserClipboardWriteRevision += 1;
25195
+ preferLocalSpreadsheetClipboardRead = false;
25196
+ }
25188
25197
  async function readSpreadsheetClipboardText(readSystem, readLocal, timeoutMs = spreadsheetSystemClipboardTimeoutMs, canReadSystem = ()=>true) {
25189
25198
  if (readSystem && await canReadSystem()) try {
25190
25199
  const value = await withSpreadsheetClipboardTimeout(readSystem(), timeoutMs);
@@ -25758,25 +25767,26 @@ function useSpreadsheetClipboard({ clipboard = browserSpreadsheetClipboard, canA
25758
25767
  }, [
25759
25768
  editable
25760
25769
  ]);
25761
- const currentSnapshot = useCallback(()=>{
25770
+ const currentSource = useCallback(()=>{
25762
25771
  if (!editable) return null;
25763
25772
  const selection = getSelection();
25764
25773
  if (!selection) return null;
25765
25774
  const snapshot = captureSpreadsheetClipboardSnapshot(contentRef.current, selection.sheetId, selection.range, selection.plainText);
25766
- return snapshot ? {
25775
+ return {
25767
25776
  selection,
25768
25777
  snapshot
25769
- } : null;
25778
+ };
25770
25779
  }, [
25771
25780
  contentRef,
25772
25781
  editable,
25773
25782
  getSelection
25774
25783
  ]);
25775
25784
  const copy = useCallback((cut)=>{
25776
- const source = currentSnapshot();
25785
+ const source = currentSource();
25777
25786
  if (!source) return false;
25778
- storeRichSpreadsheetClipboard(source.snapshot);
25779
- copySpreadsheetSelection(clipboard, source.snapshot.plainText, cut);
25787
+ if (source.snapshot) storeRichSpreadsheetClipboard(source.snapshot);
25788
+ else clearRichSpreadsheetClipboard();
25789
+ copySpreadsheetSelection(clipboard, source.selection.plainText, cut);
25780
25790
  if (cut && !clearSelection()) {
25781
25791
  showToast('选区已复制,但无法清除原内容。', 'error');
25782
25792
  return false;
@@ -25785,7 +25795,7 @@ function useSpreadsheetClipboard({ clipboard = browserSpreadsheetClipboard, canA
25785
25795
  }, [
25786
25796
  clearSelection,
25787
25797
  clipboard,
25788
- currentSnapshot
25798
+ currentSource
25789
25799
  ]);
25790
25800
  const applySnapshot = useCallback((snapshot, targetSheetId, targetRange, options, notify = true)=>{
25791
25801
  const request = {
@@ -25913,15 +25923,16 @@ function useSpreadsheetClipboard({ clipboard = browserSpreadsheetClipboard, canA
25913
25923
  fallbackFocusTarget
25914
25924
  ]);
25915
25925
  const copyToDataTransfer = useCallback((data, cut)=>{
25916
- const source = currentSnapshot();
25926
+ const source = currentSource();
25917
25927
  if (!source) return false;
25918
- storeRichSpreadsheetClipboard(source.snapshot);
25919
- data.setData('text/plain', source.snapshot.plainText);
25928
+ if (source.snapshot) storeRichSpreadsheetClipboard(source.snapshot);
25929
+ else clearRichSpreadsheetClipboard();
25930
+ data.setData('text/plain', source.selection.plainText);
25920
25931
  if (cut && !clearSelection()) return false;
25921
25932
  return true;
25922
25933
  }, [
25923
25934
  clearSelection,
25924
- currentSnapshot
25935
+ currentSource
25925
25936
  ]);
25926
25937
  return {
25927
25938
  applyDialog,
@@ -28896,6 +28907,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
28896
28907
  const previewRef = useRef(preview);
28897
28908
  const spreadsheetRootRef = useRef(null);
28898
28909
  const spreadsheetCanvasRef = useRef(null);
28910
+ const spreadsheetEditingEscapePendingRef = useRef(false);
28899
28911
  const editorFocusOrigin = useOfficeEditorFocusOrigin();
28900
28912
  const workbookRef = useRef(null);
28901
28913
  const projectedWorkbookSheetsRef = useRef([]);
@@ -28953,6 +28965,32 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
28953
28965
  }, [
28954
28966
  preview
28955
28967
  ]);
28968
+ useEffect(()=>{
28969
+ const container = spreadsheetCanvasRef.current;
28970
+ if (!container || preview) return;
28971
+ const restoreGridFocus = ()=>requestAnimationFrame(()=>focusSpreadsheetGrid(container, {
28972
+ forceCellEditingExit: true
28973
+ }));
28974
+ const handleKeyDown = (event)=>{
28975
+ if ('Escape' !== event.key || !isSpreadsheetCellEditorTarget(event.target)) return;
28976
+ spreadsheetEditingEscapePendingRef.current = true;
28977
+ restoreGridFocus();
28978
+ };
28979
+ const handleKeyUp = (event)=>{
28980
+ if ('Escape' !== event.key || !spreadsheetEditingEscapePendingRef.current) return;
28981
+ spreadsheetEditingEscapePendingRef.current = false;
28982
+ restoreGridFocus();
28983
+ };
28984
+ container.addEventListener('keydown', handleKeyDown, true);
28985
+ container.addEventListener('keyup', handleKeyUp, true);
28986
+ return ()=>{
28987
+ container.removeEventListener('keydown', handleKeyDown, true);
28988
+ container.removeEventListener('keyup', handleKeyUp, true);
28989
+ spreadsheetEditingEscapePendingRef.current = false;
28990
+ };
28991
+ }, [
28992
+ preview
28993
+ ]);
28956
28994
  const spreadsheetZoomRef = useRef(100);
28957
28995
  const [workbookInstance, setWorkbookInstance] = useState(null);
28958
28996
  const bindWorkbookInstance = useCallback((instance)=>{
@@ -29383,6 +29421,20 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
29383
29421
  });
29384
29422
  const currentClipboardSelection = useCallback(()=>{
29385
29423
  if (previewRef.current) return null;
29424
+ if (contextMenu) return {
29425
+ sheetId: contextMenu.selection.sheetId,
29426
+ range: {
29427
+ row: [
29428
+ contextMenu.range.row[0] ?? 0,
29429
+ contextMenu.range.row[1] ?? contextMenu.range.row[0] ?? 0
29430
+ ],
29431
+ column: [
29432
+ contextMenu.range.column[0] ?? 0,
29433
+ contextMenu.range.column[1] ?? contextMenu.range.column[0] ?? 0
29434
+ ]
29435
+ },
29436
+ plainText: contextMenu.selection.clipboard
29437
+ };
29386
29438
  const sheetId = selectionState?.sheetId ?? activeSheetIdRef.current;
29387
29439
  const sheet = contentRef.current.sheets.find((candidate)=>candidate.id === sheetId);
29388
29440
  const selection = selectionState?.selection ?? sheet?.luckysheet_select_save?.at(-1);
@@ -29406,6 +29458,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
29406
29458
  plainText: agentSelection.clipboard
29407
29459
  } : null;
29408
29460
  }, [
29461
+ contextMenu,
29409
29462
  selectionState
29410
29463
  ]);
29411
29464
  const spreadsheetClipboard = useSpreadsheetClipboard({
@@ -29754,9 +29807,6 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
29754
29807
  onZoomIn: ()=>changeSpreadsheetWheelZoom('in'),
29755
29808
  onZoomOut: ()=>changeSpreadsheetWheelZoom('out')
29756
29809
  });
29757
- const handleSpreadsheetEditingEscape = (event)=>{
29758
- if ('Escape' === event.key && isSpreadsheetCellEditingTarget(event.target)) requestAnimationFrame(()=>focusSpreadsheetGrid(spreadsheetCanvasRef.current));
29759
- };
29760
29810
  const handleSpreadsheetKeyDownCapture = (event)=>{
29761
29811
  releaseSpreadsheetSelectionRequest(selectionStateRef.current);
29762
29812
  if ('Alt' === event.key && !event.repeat && event.target instanceof Element && event.target.closest('.fortune-sheet-overlay') && reserveAutoFilterAltKey()) {
@@ -29764,7 +29814,6 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
29764
29814
  event.stopPropagation();
29765
29815
  return;
29766
29816
  }
29767
- handleSpreadsheetEditingEscape(event);
29768
29817
  };
29769
29818
  const handleSpreadsheetPointerDownCapture = (event)=>{
29770
29819
  releaseSpreadsheetSelectionRequest(selectionStateRef.current);
@@ -30135,7 +30184,7 @@ function SpreadsheetEditorSurface({ autoFocus = true, content, collaborationHist
30135
30184
  ]
30136
30185
  });
30137
30186
  }
30138
- function focusSpreadsheetGrid(container, { focusOrigin = document.activeElement, forceInitial = true } = {}) {
30187
+ function focusSpreadsheetGrid(container, { focusOrigin = document.activeElement, forceCellEditingExit = false, forceInitial = true } = {}) {
30139
30188
  if (!container) return;
30140
30189
  spreadsheetFocusCleanups.get(container)?.();
30141
30190
  let remainingFrames = spreadsheetFocusRetryFrames;
@@ -30174,8 +30223,9 @@ function focusSpreadsheetGrid(container, { focusOrigin = document.activeElement,
30174
30223
  const restoreFocus = (force)=>{
30175
30224
  if (focusRestorationStopped) return;
30176
30225
  const activeElement = document.activeElement;
30177
- if (!force && isSpreadsheetCellEditingTarget(activeElement)) return void stopObservingFocusTarget();
30178
- if (!force && activeElement !== document.body && activeElement !== document.documentElement && activeElement !== commandTrigger && activeElement !== lastFocusedTarget) return void stopObservingFocusTarget();
30226
+ const activeCellEditor = isSpreadsheetCellEditorTarget(activeElement);
30227
+ if (!force && !forceCellEditingExit && isSpreadsheetCellEditingTarget(activeElement)) return void stopObservingFocusTarget();
30228
+ if (!force && !(forceCellEditingExit && activeCellEditor) && activeElement !== document.body && activeElement !== document.documentElement && activeElement !== commandTrigger && activeElement !== lastFocusedTarget) return void stopObservingFocusTarget();
30179
30229
  const focusTarget = spreadsheetGridFocusTarget(container);
30180
30230
  if (focusTarget) {
30181
30231
  focusTarget.focus({
@@ -0,0 +1,20 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ /**
3
+ * Keeps controlled TipTap editors from publishing or reconciling content while
4
+ * the browser and ProseMirror still own an IME composition. The zero-delay
5
+ * settlement lets ProseMirror flush WebKit's pending DOM records first; the
6
+ * bounded retries cover runtimes that keep `view.composing` true briefly after
7
+ * `compositionend` without adding work to ordinary keyboard input.
8
+ */
9
+ export declare class ControlledEditorComposition {
10
+ private active;
11
+ private generation;
12
+ private settling;
13
+ private timer;
14
+ start(): void;
15
+ end(editor: Editor, onSettled: (editor: Editor) => void): void;
16
+ isBlocking(editor?: Editor | null): boolean;
17
+ destroy(): void;
18
+ private scheduleSettlement;
19
+ private cancelTimer;
20
+ }
@@ -35,5 +35,6 @@ export declare function spreadsheetContentWithSelection(content: WorkSpreadsheet
35
35
  export declare function spreadsheetContentWithSelections(content: WorkSpreadsheetContent, sheetId: string, selections: readonly Selection[]): WorkSpreadsheetContent;
36
36
  export declare function isSpreadsheetNativeTextUndoTarget(target: EventTarget | null): boolean;
37
37
  export declare function isSpreadsheetCellEditingTarget(target: EventTarget | null): boolean;
38
+ export declare function isSpreadsheetCellEditorTarget(target: EventTarget | null): boolean;
38
39
  export declare function spreadsheetFormulaBarSelectAllTarget(event: KeyboardEvent): HTMLElement | null;
39
40
  export declare function selectSpreadsheetFormulaBarContents(event: KeyboardEvent): boolean;
@@ -17,8 +17,9 @@ export interface SpreadsheetEditorProps {
17
17
  onAgentRequest?: (request: WorkEditorAgentRequest) => void | Promise<void>;
18
18
  }
19
19
  export declare function SpreadsheetEditor(props: SpreadsheetEditorProps): import("react").JSX.Element;
20
- export declare function focusSpreadsheetGrid(container: HTMLElement | null, { focusOrigin, forceInitial, }?: {
20
+ export declare function focusSpreadsheetGrid(container: HTMLElement | null, { focusOrigin, forceCellEditingExit, forceInitial, }?: {
21
21
  focusOrigin?: Element | null;
22
+ forceCellEditingExit?: boolean;
22
23
  forceInitial?: boolean;
23
24
  }): void;
24
25
  export declare function preserveSpreadsheetGridFocus(container: HTMLElement | null, target: EventTarget | null): void;
@@ -10,6 +10,8 @@ export interface UseDocumentReviewConflictsOptions {
10
10
  appliedSourceKeyRef: MutableValue<string>;
11
11
  artifactId?: string;
12
12
  content: WorkDocumentContent;
13
+ controlledUpdateRevision?: number;
14
+ deferControlledUpdates?: boolean;
13
15
  editor: Editor | null;
14
16
  editorInput: WorkDocumentEditorInput;
15
17
  normalizedContent: string;
@@ -17,7 +19,7 @@ export interface UseDocumentReviewConflictsOptions {
17
19
  publishedDocumentRef: MutableValue<Editor['state']['doc'] | null>;
18
20
  reconcileControlledUpdates?: boolean;
19
21
  }
20
- export declare function useDocumentReviewConflicts({ activeConflictsRef, appliedSourceKeyRef, artifactId, content, editor, editorInput, normalizedContent, onReviewConflict, publishedDocumentRef, reconcileControlledUpdates, }: UseDocumentReviewConflictsOptions): {
22
+ export declare function useDocumentReviewConflicts({ activeConflictsRef, appliedSourceKeyRef, artifactId, content, controlledUpdateRevision, deferControlledUpdates, editor, editorInput, normalizedContent, onReviewConflict, publishedDocumentRef, reconcileControlledUpdates, }: UseDocumentReviewConflictsOptions): {
21
23
  dismiss: () => void;
22
24
  visibleConflicts: WorkDocumentReviewConflict[];
23
25
  };
Binary file
@@ -644,6 +644,22 @@ The shell never searches visible labels, scrapes rendered text, or synthesizes
644
644
  clicks to infer product intent. This keeps localized UI copy independent from
645
645
  behavior and gives Worker/WASM operations a stable request contract.
646
646
 
647
+ ## Controlled rich-text IME boundary
648
+
649
+ Document body text, visual Markdown, and Presentation text share one internal
650
+ composition coordinator. `compositionstart` blocks host publication and
651
+ controlled-value reconciliation. `compositionend` schedules settlement after
652
+ the current task so ProseMirror can flush WebKit's pending DOM observer records;
653
+ bounded 20 ms retries cover runtimes that briefly retain `view.composing`.
654
+ Only the settled canonical value is published, and an authoritative host value
655
+ received during composition is reconciled afterward.
656
+
657
+ The coordinator creates no timers or extra renders for ordinary keyboard input.
658
+ Focused component tests cover controlled echo and mid-composition host
659
+ replacement. A dedicated Playwright WebKit gate exercises the browser event
660
+ ordering, asserts zero publications for Pinyin pre-edit text, one publication
661
+ for the committed Chinese value, and the same value after a controlled reopen.
662
+
647
663
  ## Markdown editing flow
648
664
 
649
665
  ```text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a3s-lab/office",
3
- "version": "0.37.1",
3
+ "version": "0.37.2",
4
4
  "description": "Open-source collaborative browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
5
5
  "keywords": [
6
6
  "office",
@@ -86,6 +86,7 @@
86
86
  "playground:bundle-check": "bun scripts/check-playground-bundle.ts",
87
87
  "playground:preview": "rspress preview -c website/rspress.config.ts --host 127.0.0.1 --port 4175",
88
88
  "playground:typecheck": "tsc --noEmit -p playground/tsconfig.json",
89
+ "playground:ime:webkit": "playwright test --config playwright.ime.config.ts",
89
90
  "playground:visual": "playwright test --config playwright.config.ts",
90
91
  "playground:visual:document-emphasis": "playwright test visual-tests/document-emphasis.functional.spec.ts --config playwright.config.ts",
91
92
  "playground:visual:document-index": "playwright test visual-tests/document-index.functional.spec.ts --config playwright.config.ts",