@a3s-lab/office 0.2.2 → 0.3.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.
Files changed (27) hide show
  1. package/README.md +14 -1
  2. package/dist/0~4595.js +117 -11
  3. package/dist/0~5050.js +124 -113
  4. package/dist/0~9512.js +1 -0
  5. package/dist/0~document-editor.js +2836 -1429
  6. package/dist/0~work-document-extensions.js +2 -2
  7. package/dist/internal/design-system/primitives/navigation/tabs.d.ts +3 -1
  8. package/dist/internal/features/work/editors/document-command-catalog.d.ts +527 -0
  9. package/dist/internal/features/work/editors/document-editor-support.d.ts +7 -3
  10. package/dist/internal/features/work/editors/document-editor.d.ts +1 -1
  11. package/dist/internal/features/work/editors/document-format-clipboard.d.ts +6 -0
  12. package/dist/internal/features/work/editors/document-format-tools.d.ts +4 -0
  13. package/dist/internal/features/work/editors/document-layout-panel.d.ts +4 -1
  14. package/dist/internal/features/work/editors/document-page-chrome-wps-shortcuts.d.ts +2 -0
  15. package/dist/internal/features/work/editors/document-page-layout-ribbon.d.ts +14 -0
  16. package/dist/internal/features/work/editors/document-review-navigation.d.ts +9 -0
  17. package/dist/internal/features/work/editors/document-statistics-dialog.d.ts +7 -0
  18. package/dist/internal/features/work/editors/document-status-bar.d.ts +2 -1
  19. package/dist/internal/features/work/editors/document-toolbar.d.ts +12 -42
  20. package/dist/internal/features/work/editors/document-wps-shortcuts.d.ts +12 -0
  21. package/dist/internal/features/work/editors/document-zoom.d.ts +17 -0
  22. package/dist/internal/features/work/editors/work-office-chrome.d.ts +22 -3
  23. package/dist/internal/features/work/editors/work-office-ribbon-overflow.d.ts +2 -0
  24. package/dist/office-kernel.wasm +0 -0
  25. package/dist/styles.css +213 -1
  26. package/docs/latest/en/browser-editor-architecture.md +33 -0
  27. package/package.json +11 -1
@@ -1,54 +1,20 @@
1
1
  import type { Editor } from '@tiptap/core';
2
2
  import type { WorkDocumentCaptionKind } from '../work-document-captions';
3
3
  import type { WorkDocumentFieldKind } from '../work-document-fields';
4
- import type { WorkDocumentNoteKind } from '../work-document-notes';
5
4
  import type { WorkDocumentLayoutFont } from '../work-document-fonts';
6
- import { type DocumentPageChromeEditingPart } from './document-page-chrome-ribbon';
5
+ import type { WorkDocumentNoteKind } from '../work-document-notes';
6
+ import type { WorkDocumentSectionLayout } from '../work-types';
7
+ import { type DocumentRibbonTabId } from './document-command-catalog';
7
8
  import type { DocumentFindReplaceMode } from './document-find-replace-panel';
9
+ import type { DocumentLayoutPanelTab } from './document-layout-panel';
10
+ import { type DocumentPageChromeEditingPart } from './document-page-chrome-ribbon';
11
+ import { type DocumentZoomFit } from './document-zoom';
8
12
  import { type WorkOfficeFileAction } from './work-office-chrome';
9
- declare const documentRibbonTabs: readonly [{
10
- readonly id: "home";
11
- readonly label: "开始";
12
- }, {
13
- readonly id: "insert";
14
- readonly label: "插入";
15
- }, {
16
- readonly id: "page";
17
- readonly label: "页面布局";
18
- readonly compactLabel: "布局";
19
- }, {
20
- readonly id: "references";
21
- readonly label: "引用";
22
- }, {
23
- readonly id: "review";
24
- readonly label: "审阅";
25
- }, {
26
- readonly id: "view";
27
- readonly label: "视图";
28
- }];
29
- declare const documentPictureRibbonTab: {
30
- readonly id: "picture";
31
- readonly label: "图片";
32
- };
33
- declare const documentTableRibbonTabs: readonly [{
34
- readonly id: "tableDesign";
35
- readonly label: "表格设计";
36
- readonly compactLabel: "设计";
37
- }, {
38
- readonly id: "tableLayout";
39
- readonly label: "表格布局";
40
- readonly compactLabel: "布局";
41
- }];
42
- declare const documentPageChromeRibbonTab: {
43
- readonly id: "pageChrome";
44
- readonly label: "页眉和页脚";
45
- readonly compactLabel: "页眉页脚";
46
- };
47
- type DocumentRibbonTabId = (typeof documentRibbonTabs)[number]['id'] | typeof documentPictureRibbonTab.id | (typeof documentTableRibbonTabs)[number]['id'] | typeof documentPageChromeRibbonTab.id;
48
13
  export type DocumentViewMode = 'page' | 'web';
49
14
  interface DocumentToolbarProps {
50
15
  editor: Editor;
51
16
  layoutOpen: boolean;
17
+ layout: WorkDocumentSectionLayout;
52
18
  layoutFonts?: readonly WorkDocumentLayoutFont[];
53
19
  navigationOpen: boolean;
54
20
  pageColor: string;
@@ -65,6 +31,8 @@ interface DocumentToolbarProps {
65
31
  onClosePageChrome: () => void;
66
32
  onTogglePageChromePageNumber: () => void;
67
33
  onToggleLayout: () => void;
34
+ onLayoutChange: (layout: WorkDocumentSectionLayout) => void;
35
+ onOpenLayout: (target: DocumentLayoutPanelTab) => void;
68
36
  onToggleNavigation: () => void;
69
37
  onTogglePageNumbers: () => void;
70
38
  onToggleRulers: () => void;
@@ -72,6 +40,7 @@ interface DocumentToolbarProps {
72
40
  onToggleSpellcheck: () => void;
73
41
  onViewModeChange: (mode: DocumentViewMode) => void;
74
42
  onZoomChange: (zoom: number) => void;
43
+ onZoomFit: (fit: DocumentZoomFit) => void;
75
44
  onInsertSection: () => void;
76
45
  onInsertNote: (kind: WorkDocumentNoteKind) => void;
77
46
  onInsertCaption: (kind: WorkDocumentCaptionKind) => void;
@@ -94,7 +63,8 @@ interface DocumentToolbarProps {
94
63
  onRibbonTabChange?: (tab: DocumentRibbonTabId) => boolean | undefined | Promise<boolean | undefined>;
95
64
  onToggleTrackChanges: () => void;
96
65
  onToggleChanges: () => void;
66
+ onOpenWordCount: () => void;
97
67
  onOpenFindReplace: (mode: DocumentFindReplaceMode) => void;
98
68
  }
99
- export declare function DocumentToolbar({ editor, layoutOpen, layoutFonts, navigationOpen, pageColor, showPageNumbers, showRulers, spellcheckEnabled, viewMode, zoom, pageChromeEditor, pageChromeEditingPart, pageChromeShowPageNumber, onRequestImage, onPageChromeEditingPartChange, onClosePageChrome, onTogglePageChromePageNumber, onToggleLayout, onToggleNavigation, onTogglePageNumbers, onToggleRulers, onPageColorChange, onToggleSpellcheck, onViewModeChange, onZoomChange, onInsertSection, onInsertNote, onInsertCaption, onInsertCrossReference, citationsOpen, citationSourceCount, onToggleCitations, onInsertField, onRefreshFields, canInsertComment, onInsertComment, commentsOpen, commentCount, onToggleComments, trackChanges, changesOpen, changeCount, findReplaceMode, fileActions, onRibbonTabChange, onToggleTrackChanges, onToggleChanges, onOpenFindReplace, }: DocumentToolbarProps): import("react").JSX.Element;
69
+ export declare function DocumentToolbar({ editor, layoutOpen, layout, layoutFonts, navigationOpen, pageColor, showPageNumbers, showRulers, spellcheckEnabled, viewMode, zoom, pageChromeEditor, pageChromeEditingPart, pageChromeShowPageNumber, onRequestImage, onPageChromeEditingPartChange, onClosePageChrome, onTogglePageChromePageNumber, onToggleLayout, onLayoutChange, onOpenLayout, onToggleNavigation, onTogglePageNumbers, onToggleRulers, onPageColorChange, onToggleSpellcheck, onViewModeChange, onZoomChange, onZoomFit, onInsertSection, onInsertNote, onInsertCaption, onInsertCrossReference, citationsOpen, citationSourceCount, onToggleCitations, onInsertField, onRefreshFields, canInsertComment, onInsertComment, commentsOpen, commentCount, onToggleComments, trackChanges, changesOpen, changeCount, findReplaceMode, fileActions, onRibbonTabChange, onToggleTrackChanges, onToggleChanges, onOpenWordCount, onOpenFindReplace, }: DocumentToolbarProps): import("react").JSX.Element;
100
70
  export {};
@@ -0,0 +1,12 @@
1
+ import type { Editor } from '@tiptap/core';
2
+ export interface DocumentWpsShortcutCallbacks {
3
+ canInsertComment: boolean;
4
+ canRefreshFields: boolean;
5
+ onInsertComment: () => void;
6
+ onOpenWordCount: () => void;
7
+ onRefreshFields: () => void;
8
+ onToggleSpellcheck: () => void;
9
+ onToggleTrackChanges: () => void;
10
+ }
11
+ export type DocumentWpsShortcutEvent = Pick<KeyboardEvent, 'altKey' | 'code' | 'ctrlKey' | 'key' | 'metaKey' | 'shiftKey'>;
12
+ export declare function runDocumentWpsShortcut(editor: Editor, event: DocumentWpsShortcutEvent, callbacks: DocumentWpsShortcutCallbacks): boolean;
@@ -0,0 +1,17 @@
1
+ export declare const MIN_DOCUMENT_ZOOM = 50;
2
+ export declare const MAX_DOCUMENT_ZOOM = 200;
3
+ export type DocumentZoomFit = 'page' | 'width';
4
+ export interface DocumentZoomFitMetrics {
5
+ pageHeight: number;
6
+ pageWidth: number;
7
+ viewportHeight: number;
8
+ viewportWidth: number;
9
+ viewportPadding: {
10
+ top: number;
11
+ right: number;
12
+ bottom: number;
13
+ left: number;
14
+ };
15
+ }
16
+ export declare function clampDocumentZoom(zoom: number): number;
17
+ export declare function documentZoomForFit(fit: DocumentZoomFit, metrics: DocumentZoomFitMetrics): number;
@@ -3,6 +3,7 @@ export interface WorkOfficeRibbonTab<T extends string> {
3
3
  id: T;
4
4
  label: string;
5
5
  compactLabel?: string;
6
+ contextual?: boolean;
6
7
  }
7
8
  export interface WorkOfficeFileAction {
8
9
  id: string;
@@ -13,7 +14,16 @@ export interface WorkOfficeFileAction {
13
14
  separatorBefore?: boolean;
14
15
  onSelect: () => void | Promise<void>;
15
16
  }
16
- export declare function WorkOfficeRibbon<T extends string>({ ariaLabel, tabs, defaultTab, activeTab, onTabChange, panels, fileActions, className, toolbarClassName, }: {
17
+ export interface WorkOfficeQuickAccessAction {
18
+ id: string;
19
+ label: string;
20
+ icon: ReactNode;
21
+ shortcut?: string;
22
+ ariaKeyShortcuts?: string;
23
+ disabled?: boolean;
24
+ onSelect: () => void | Promise<void>;
25
+ }
26
+ export declare function WorkOfficeRibbon<T extends string>({ ariaLabel, tabs, defaultTab, activeTab, onTabChange, panels, fileActions, quickAccessActions, adaptive, collapsible, collapsed: controlledCollapsed, defaultCollapsed, onCollapsedChange, className, toolbarClassName, }: {
17
27
  ariaLabel: string;
18
28
  tabs: readonly WorkOfficeRibbonTab<T>[];
19
29
  defaultTab: T;
@@ -21,6 +31,12 @@ export declare function WorkOfficeRibbon<T extends string>({ ariaLabel, tabs, de
21
31
  onTabChange?: (tab: T) => void;
22
32
  panels: Record<T, ReactNode>;
23
33
  fileActions?: readonly WorkOfficeFileAction[];
34
+ quickAccessActions?: readonly WorkOfficeQuickAccessAction[];
35
+ adaptive?: boolean;
36
+ collapsible?: boolean;
37
+ collapsed?: boolean;
38
+ defaultCollapsed?: boolean;
39
+ onCollapsedChange?: (collapsed: boolean) => void;
24
40
  className?: string;
25
41
  toolbarClassName?: string;
26
42
  }): import("react").JSX.Element;
@@ -31,8 +47,9 @@ export declare function WorkOfficePreviewBar({ ariaLabel, label, detail, fileAct
31
47
  fileActions?: readonly WorkOfficeFileAction[];
32
48
  className?: string;
33
49
  }): import("react").JSX.Element;
34
- export declare function WorkOfficeRibbonGroup({ label, children, }: {
50
+ export declare function WorkOfficeRibbonGroup({ label, priority, children, }: {
35
51
  label: string;
52
+ priority?: 'high' | 'normal' | 'low';
36
53
  children: ReactNode;
37
54
  }): import("react").JSX.Element;
38
55
  export declare function WorkOfficeRibbonButton({ label, visibleLabel, badge, active, displayLabel, className, title, children, ...props }: Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label' | 'children'> & {
@@ -43,9 +60,11 @@ export declare function WorkOfficeRibbonButton({ label, visibleLabel, badge, act
43
60
  displayLabel?: boolean;
44
61
  children: ReactNode;
45
62
  }): import("react").JSX.Element;
46
- export declare function WorkOfficeStatusBar({ children, controls, className, }: {
63
+ export declare function WorkOfficeStatusBar({ ariaLabel, children, controls, controlsLabel, className, }: {
64
+ ariaLabel?: string;
47
65
  children: ReactNode;
48
66
  controls?: ReactNode;
67
+ controlsLabel?: string;
49
68
  className?: string;
50
69
  }): import("react").JSX.Element;
51
70
  export declare function WorkOfficeZoomControls({ zoom, minimum, maximum, step, decreaseLabel, increaseLabel, outputLabel, sliderLabel, onChange, }: {
@@ -1,4 +1,6 @@
1
1
  export declare const WORK_OFFICE_RIBBON_SCROLL_INSET = 34;
2
+ export type WorkOfficeRibbonDensity = 'comfortable' | 'compact-low' | 'compact-normal';
3
+ export declare function calculateRibbonDensity(clientWidth: number): WorkOfficeRibbonDensity;
2
4
  export interface WorkOfficeRibbonItemGeometry {
3
5
  left: number;
4
6
  right: number;
Binary file
package/dist/styles.css CHANGED
@@ -5275,6 +5275,7 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5275
5275
  flex: none;
5276
5276
  min-width: 0;
5277
5277
  display: flex;
5278
+ position: relative;
5278
5279
  box-shadow: 0 2px 7px #181f2e0a;
5279
5280
  }
5280
5281
 
@@ -5375,6 +5376,17 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5375
5376
  font-size: 12px;
5376
5377
  }
5377
5378
 
5379
+ .work-office-ribbon-tabs > button[data-contextual="true"] {
5380
+ background: color-mix(in srgb, var(--work-office-accent) 7%, transparent);
5381
+ box-shadow: inset 0 2px
5382
+ color-mix(in srgb, var(--work-office-accent) 48%, transparent);
5383
+ color: color-mix(in srgb, var(--work-office-accent) 82%, var(--a3s-ink));
5384
+ }
5385
+
5386
+ .work-office-ribbon-tabs > button[data-contextual="true"]:hover:not(:disabled) {
5387
+ background: color-mix(in srgb, var(--work-office-accent) 11%, transparent);
5388
+ }
5389
+
5378
5390
  .work-office-ribbon-tabs > button:after {
5379
5391
  left: 4px;
5380
5392
  right: 4px;
@@ -5401,6 +5413,11 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5401
5413
  background: var(--work-office-accent);
5402
5414
  }
5403
5415
 
5416
+ .work-office-ribbon[data-contextual-active="true"] > .work-office-ribbon-panel {
5417
+ box-shadow: inset 0 1px
5418
+ color-mix(in srgb, var(--work-office-accent) 28%, transparent);
5419
+ }
5420
+
5404
5421
  .work-office-ribbon-tab-navigation {
5405
5422
  border-left: 1px solid var(--a3s-line);
5406
5423
  background: var(--a3s-panel);
@@ -5443,6 +5460,57 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5443
5460
  opacity: .28;
5444
5461
  }
5445
5462
 
5463
+ .work-office-quick-access {
5464
+ border-right: 1px solid var(--a3s-line);
5465
+ flex: none;
5466
+ align-items: center;
5467
+ gap: 1px;
5468
+ height: 36px;
5469
+ padding: 0 5px;
5470
+ display: flex;
5471
+ }
5472
+
5473
+ .work-office-quick-access > button, .work-office-ribbon-collapse {
5474
+ width: 26px;
5475
+ height: 26px;
5476
+ color: var(--a3s-muted);
5477
+ cursor: pointer;
5478
+ background: none;
5479
+ border: 0;
5480
+ border-radius: 5px;
5481
+ outline: 0;
5482
+ place-items: center;
5483
+ padding: 0;
5484
+ display: grid;
5485
+ }
5486
+
5487
+ .work-office-quick-access > button:hover:not(:disabled), .work-office-quick-access > button:focus-visible, .work-office-ribbon-collapse:hover, .work-office-ribbon-collapse:focus-visible {
5488
+ background: color-mix(in srgb,
5489
+ var(--work-office-accent) 8%,
5490
+ var(--a3s-panel));
5491
+ color: var(--work-office-accent);
5492
+ }
5493
+
5494
+ .work-office-quick-access > button:focus-visible, .work-office-ribbon-collapse:focus-visible {
5495
+ box-shadow: inset 0 0 0 1px var(--work-office-accent);
5496
+ }
5497
+
5498
+ .work-office-quick-access > button:disabled {
5499
+ cursor: default;
5500
+ opacity: .34;
5501
+ }
5502
+
5503
+ .work-office-quick-access > button > span {
5504
+ place-items: center;
5505
+ display: grid;
5506
+ }
5507
+
5508
+ .work-office-ribbon-collapse {
5509
+ flex: 0 0 26px;
5510
+ align-self: center;
5511
+ margin: 0 5px 0 2px;
5512
+ }
5513
+
5446
5514
  .work-office-file-menu {
5447
5515
  border-right: 1px solid var(--a3s-line);
5448
5516
  flex: none;
@@ -5552,6 +5620,22 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5552
5620
  position: relative;
5553
5621
  }
5554
5622
 
5623
+ .work-office-ribbon-panel[hidden] {
5624
+ display: none;
5625
+ }
5626
+
5627
+ .work-office-ribbon[data-collapsed="true"] .work-office-ribbon-panel:not([hidden]) {
5628
+ z-index: 10;
5629
+ border-top: 1px solid var(--a3s-line);
5630
+ border-bottom: 1px solid var(--a3s-line);
5631
+ background: var(--a3s-panel);
5632
+ position: absolute;
5633
+ top: 36px;
5634
+ left: 0;
5635
+ right: 0;
5636
+ box-shadow: 0 12px 26px #181f2e26;
5637
+ }
5638
+
5555
5639
  .work-office-ribbon-panel[data-empty="true"] {
5556
5640
  display: none;
5557
5641
  }
@@ -5664,6 +5748,25 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5664
5748
  overflow: hidden;
5665
5749
  }
5666
5750
 
5751
+ .work-office-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"], .work-office-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) {
5752
+ padding-left: 4px;
5753
+ padding-right: 4px;
5754
+ }
5755
+
5756
+ .work-office-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"] > div, .work-office-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) > div {
5757
+ gap: 0;
5758
+ }
5759
+
5760
+ .work-office-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"] button.with-label, .work-office-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) button.with-label {
5761
+ width: 38px;
5762
+ min-width: 38px;
5763
+ padding-inline: 4px;
5764
+ }
5765
+
5766
+ .work-office-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"] button.with-label > span, .work-office-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) button.with-label > span {
5767
+ display: none;
5768
+ }
5769
+
5667
5770
  .work-office-ribbon .work-office-toolbar button.with-label {
5668
5771
  flex-direction: column;
5669
5772
  gap: 3px;
@@ -5757,6 +5860,15 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
5757
5860
  display: grid;
5758
5861
  }
5759
5862
 
5863
+ .work-office-status button.work-office-status-text-button {
5864
+ width: auto;
5865
+ min-width: 0;
5866
+ color: inherit;
5867
+ font: inherit;
5868
+ white-space: nowrap;
5869
+ padding: 0 5px;
5870
+ }
5871
+
5760
5872
  .work-office-status button:hover:not(:disabled), .work-office-status button[aria-pressed="true"] {
5761
5873
  background: var(--a3s-panel-soft);
5762
5874
  color: var(--a3s-ink);
@@ -6691,6 +6803,31 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
6691
6803
  width: 82px;
6692
6804
  }
6693
6805
 
6806
+ .work-document-ribbon .work-office-ribbon-group > .work-document-page-setup-choice {
6807
+ align-content: center;
6808
+ gap: 2px;
6809
+ width: 84px;
6810
+ padding-inline: 1px;
6811
+ display: grid;
6812
+ }
6813
+
6814
+ .work-document-page-setup-choice > span {
6815
+ color: var(--a3s-faint);
6816
+ text-overflow: ellipsis;
6817
+ white-space: nowrap;
6818
+ padding-inline: 2px;
6819
+ font-size: 8px;
6820
+ line-height: 10px;
6821
+ overflow: hidden;
6822
+ }
6823
+
6824
+ .work-document-ribbon .work-document-page-setup-choice .work-office-select > button[role="combobox"] {
6825
+ gap: 4px;
6826
+ min-width: 0;
6827
+ height: 28px;
6828
+ padding-inline: 6px 5px;
6829
+ }
6830
+
6694
6831
  .work-document-selection-toolbar {
6695
6832
  z-index: 70;
6696
6833
  box-sizing: border-box;
@@ -8856,6 +8993,14 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
8856
8993
  display: none;
8857
8994
  }
8858
8995
 
8996
+ .work-document-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"] .work-document-style-gallery, .work-document-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) .work-document-style-gallery {
8997
+ display: none;
8998
+ }
8999
+
9000
+ .work-document-ribbon .work-office-toolbar[data-density="compact-low"] .work-office-ribbon-group[data-priority="low"] .work-document-style-select, .work-document-ribbon .work-office-toolbar[data-density="compact-normal"] .work-office-ribbon-group:not([data-priority="high"]) .work-document-style-select {
9001
+ display: block;
9002
+ }
9003
+
8859
9004
  @media (width <= 900px) {
8860
9005
  .work-document-style-gallery {
8861
9006
  display: none;
@@ -9377,6 +9522,10 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
9377
9522
  position: relative;
9378
9523
  }
9379
9524
 
9525
+ .work-document-editable .ProseMirror[data-document-format-painter="true"] {
9526
+ cursor: copy;
9527
+ }
9528
+
9380
9529
  .work-document-page.letter {
9381
9530
  width: 816px;
9382
9531
  min-height: 1056px;
@@ -10066,6 +10215,38 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
10066
10215
  box-shadow: 0 8px 30px #1e263712;
10067
10216
  }
10068
10217
 
10218
+ .work-document-statistics {
10219
+ border: 1px solid var(--a3s-line);
10220
+ border-radius: 7px;
10221
+ margin: 0;
10222
+ display: grid;
10223
+ overflow: hidden;
10224
+ }
10225
+
10226
+ .work-document-statistics > div {
10227
+ background: var(--a3s-panel);
10228
+ grid-template-columns: minmax(0, 1fr) auto;
10229
+ gap: 24px;
10230
+ padding: 9px 12px;
10231
+ font-size: 12px;
10232
+ display: grid;
10233
+ }
10234
+
10235
+ .work-document-statistics > div + div {
10236
+ border-top: 1px solid var(--a3s-line);
10237
+ }
10238
+
10239
+ .work-document-statistics dt {
10240
+ color: var(--a3s-muted);
10241
+ }
10242
+
10243
+ .work-document-statistics dd {
10244
+ color: var(--a3s-ink);
10245
+ font-variant-numeric: tabular-nums;
10246
+ margin: 0;
10247
+ font-weight: 620;
10248
+ }
10249
+
10069
10250
  @media (width <= 1080px) {
10070
10251
  .work-export-button {
10071
10252
  padding: 0 9px;
@@ -10092,7 +10273,7 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
10092
10273
  }
10093
10274
 
10094
10275
  @media (width <= 760px) {
10095
- .work-document-footer .work-office-status-info [aria-label="分节状态"], .work-document-footer .work-office-status-info button {
10276
+ .work-document-footer .work-office-status-info [data-document-status-item="section"], .work-document-footer .work-office-status-info [data-document-status-item="spellcheck"] {
10096
10277
  display: none;
10097
10278
  }
10098
10279
 
@@ -10101,6 +10282,37 @@ html[data-ds-resizing="horizontal"], html[data-ds-resizing="horizontal"] * {
10101
10282
  }
10102
10283
  }
10103
10284
 
10285
+ @media (width <= 520px) {
10286
+ .work-document-footer.work-office-status {
10287
+ flex-basis: 40px;
10288
+ gap: 6px;
10289
+ height: 40px;
10290
+ padding-inline: 8px;
10291
+ }
10292
+
10293
+ .work-document-footer .work-office-status-info {
10294
+ gap: 8px;
10295
+ }
10296
+
10297
+ .work-document-footer .work-office-status-info [data-document-status-item="word-count"], .work-document-footer .work-office-status-view [data-document-status-control="view-mode"] {
10298
+ display: none;
10299
+ }
10300
+
10301
+ .work-document-footer .work-office-status-view {
10302
+ gap: 2px;
10303
+ }
10304
+
10305
+ .work-document-footer.work-office-status button {
10306
+ width: 36px;
10307
+ height: 36px;
10308
+ }
10309
+
10310
+ .work-document-footer .work-office-status-slider {
10311
+ width: 48px;
10312
+ margin-inline: 0;
10313
+ }
10314
+ }
10315
+
10104
10316
  .work-document-layout-panel > legend {
10105
10317
  float: none;
10106
10318
  color: var(--a3s-ink);
@@ -32,6 +32,39 @@ model:
32
32
  from host-page controls while its own shortcuts remain deterministic.
33
33
  Successful object and grid commands restore focus to the latest controlled
34
34
  selection before the next keystroke.
35
+ Writer keeps tab, group, label, and shortcut metadata in one internal
36
+ command catalog. Undo and redo live in a keyboard-described quick-access
37
+ toolbar. The ribbon measures its own toolbar instead of the browser viewport
38
+ and compacts low-priority, then normal-priority groups before exposing group
39
+ paging. It can remain collapsed; activating a tab temporarily overlays that
40
+ tab's commands without moving the document, and an outside pointer action
41
+ returns to the compact row. Writer routes its catalogued WPS font-size,
42
+ alignment, line-spacing, heading, spelling, field-refresh, comment, and
43
+ track-changes shortcuts only while the document surface owns the event. The
44
+ status route uses the same catalog for WPS word count, opening live page,
45
+ word, character, and paragraph details from either `Ctrl+Shift+G` or the
46
+ visible count. Its local formatting clipboard powers WPS copy-format and
47
+ paste-format shortcuts
48
+ plus a one-shot format painter without requiring browser clipboard access;
49
+ paste replaces only direct character and paragraph formatting, preserving
50
+ links, comments, and revision semantics in one document transaction.
51
+ Header and footer TipTap surfaces register the same WPS alignment and
52
+ copy-format shortcuts. Cross-surface paste retains compatible marks and
53
+ paragraph attributes while falling back from unsupported heading nodes.
54
+ Writer orders Insert commands as Pages, Table, Illustrations, Links, Header
55
+ and Footer, and Text. Page Layout keeps direct margin, orientation, paper,
56
+ and equal-column presets in the ribbon; custom margins and advanced columns
57
+ open the corresponding controlled Page Setup tab instead of duplicating a
58
+ second settings model. References separates footnotes, captions, citations
59
+ and bibliography, and field updates; Review derives previous, next, accept,
60
+ and reject availability from the live revision ranges and moves the
61
+ selection to the next resolvable change. View keeps document modes before
62
+ display controls and calculates One Page and Page Width zoom from the live
63
+ page metrics and scroll viewport. Picture, Table, and Header and Footer tabs
64
+ carry explicit contextual semantics and disappear with their selection. The
65
+ labelled status surface provides arrow-key traversal across view and zoom
66
+ controls; compact Web widths remove secondary section, proofing, citation,
67
+ and save items in stages while retaining physical-page and zoom feedback.
35
68
  2. Each editor owns its selection model and exposes typed commands to the
36
69
  shell. A command never searches rendered text or infers intent from labels.
37
70
  3. Interactive editing stays on the main thread. Presentation keeps object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a3s-lab/office",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Open-source browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
5
5
  "keywords": [
6
6
  "office",
@@ -90,6 +90,16 @@
90
90
  "test:e2e:fixtures": "bun scripts/create-e2e-fixtures.ts",
91
91
  "test:e2e:wps-layout": "bun run test:e2e:fixtures && a3s-test run tests/e2e/word-wps-layout-parity.acl --json && a3s-test run tests/e2e/word-wps-font-grid-parity.acl --json",
92
92
  "test:e2e:wps-layout:check": "bun run test:e2e:fixtures && a3s-test check tests/e2e/word-wps-layout-parity.acl --json && a3s-test check tests/e2e/word-wps-font-grid-parity.acl --json",
93
+ "test:e2e:writer-ribbon": "a3s-test run tests/e2e/word-ribbon-collapse.acl --json",
94
+ "test:e2e:writer-ribbon:check": "a3s-test check tests/e2e/word-ribbon-collapse.acl --json",
95
+ "test:e2e:writer-shortcuts": "a3s-test run tests/e2e/word-wps-shortcuts.acl --json",
96
+ "test:e2e:writer-shortcuts:check": "a3s-test check tests/e2e/word-wps-shortcuts.acl --json",
97
+ "test:e2e:writer-layout": "a3s-test run tests/e2e/word-insert-page-layout.acl --json",
98
+ "test:e2e:writer-layout:check": "a3s-test check tests/e2e/word-insert-page-layout.acl --json",
99
+ "test:e2e:writer-review-view": "a3s-test run tests/e2e/word-review-view.acl --json",
100
+ "test:e2e:writer-review-view:check": "a3s-test check tests/e2e/word-review-view.acl --json",
101
+ "test:e2e:writer-status": "a3s-test run tests/e2e/word-status-bar.acl --json",
102
+ "test:e2e:writer-status:check": "a3s-test check tests/e2e/word-status-bar.acl --json",
93
103
  "test:wps-layout": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/test-wps-layout-parity.ps1",
94
104
  "test:watch": "rstest --watch",
95
105
  "typecheck": "tsc --noEmit"