@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.
- package/README.md +282 -1549
- package/dist/0~9073.js +2 -2
- package/dist/0~controlled-editor-composition.js +54 -0
- package/dist/0~document-editor.js +60 -10
- package/dist/0~markdown-editor.js +62 -21
- package/dist/0~presentation-editor.js +57 -31
- package/dist/0~spreadsheet-editor.js +68 -18
- package/dist/internal/features/work/editors/controlled-editor-composition.d.ts +20 -0
- package/dist/internal/features/work/editors/spreadsheet-editor-support.d.ts +1 -0
- package/dist/internal/features/work/editors/spreadsheet-editor.d.ts +2 -1
- package/dist/internal/features/work/editors/use-document-review-conflicts.d.ts +3 -1
- package/dist/office-kernel.wasm +0 -0
- package/docs/latest/en/browser-editor-architecture.md +16 -0
- package/package.json +2 -1
|
@@ -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
|
|
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
|
|
25775
|
+
return {
|
|
25767
25776
|
selection,
|
|
25768
25777
|
snapshot
|
|
25769
|
-
}
|
|
25778
|
+
};
|
|
25770
25779
|
}, [
|
|
25771
25780
|
contentRef,
|
|
25772
25781
|
editable,
|
|
25773
25782
|
getSelection
|
|
25774
25783
|
]);
|
|
25775
25784
|
const copy = useCallback((cut)=>{
|
|
25776
|
-
const source =
|
|
25785
|
+
const source = currentSource();
|
|
25777
25786
|
if (!source) return false;
|
|
25778
|
-
storeRichSpreadsheetClipboard(source.snapshot);
|
|
25779
|
-
|
|
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
|
-
|
|
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 =
|
|
25926
|
+
const source = currentSource();
|
|
25917
25927
|
if (!source) return false;
|
|
25918
|
-
storeRichSpreadsheetClipboard(source.snapshot);
|
|
25919
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30178
|
-
if (!force &&
|
|
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
|
};
|
package/dist/office-kernel.wasm
CHANGED
|
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.
|
|
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",
|