@web-ppt/editor 0.5.0-beta.1
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/CHANGELOG.md +274 -0
- package/LICENSE +21 -0
- package/README.md +473 -0
- package/dist/adapter-format-painter.d.ts +15 -0
- package/dist/adapter-link.d.ts +3 -0
- package/dist/adapter-options.d.ts +6 -0
- package/dist/adapter-recovery.d.ts +14 -0
- package/dist/adapter-selection-pane.d.ts +22 -0
- package/dist/adapter-text-search.d.ts +28 -0
- package/dist/animation-preview.d.ts +26 -0
- package/dist/dom-identity.d.ts +17 -0
- package/dist/dom-patch.d.ts +9 -0
- package/dist/editor-keyboard.d.ts +13 -0
- package/dist/editor.js +6332 -0
- package/dist/element-clipboard.d.ts +23 -0
- package/dist/format-painter-types.d.ts +32 -0
- package/dist/format-painter-view.d.ts +17 -0
- package/dist/format-painter.d.ts +21 -0
- package/dist/framework-adapter-binding.d.ts +4 -0
- package/dist/framework-adapter-state.d.ts +9 -0
- package/dist/framework-adapter-types.d.ts +114 -0
- package/dist/framework-adapter.d.ts +2 -0
- package/dist/image-crop-gesture.d.ts +40 -0
- package/dist/image-insertion.d.ts +46 -0
- package/dist/index.d.ts +28 -0
- package/dist/indexeddb-recovery-store.d.ts +30 -0
- package/dist/keyboard-context.d.ts +8 -0
- package/dist/keyboard-delete.d.ts +7 -0
- package/dist/keyboard-history.d.ts +7 -0
- package/dist/keyboard-layer.d.ts +7 -0
- package/dist/keyboard-nudge.d.ts +10 -0
- package/dist/keyboard-owner.d.ts +5 -0
- package/dist/marquee-gesture.d.ts +31 -0
- package/dist/move-gesture.d.ts +34 -0
- package/dist/placeholder-overlay.d.ts +3 -0
- package/dist/pointer-gesture.d.ts +37 -0
- package/dist/recovery-session.d.ts +24 -0
- package/dist/recovery-store.d.ts +64 -0
- package/dist/resize-geometry.d.ts +19 -0
- package/dist/resize-gesture.d.ts +30 -0
- package/dist/rotation-geometry.d.ts +9 -0
- package/dist/rotation-gesture.d.ts +33 -0
- package/dist/selection-body-properties.d.ts +3 -0
- package/dist/selection-combine.d.ts +10 -0
- package/dist/selection-handles.d.ts +3 -0
- package/dist/selection-hit.d.ts +8 -0
- package/dist/selection-overlay.d.ts +13 -0
- package/dist/selection-pane-types.d.ts +18 -0
- package/dist/selection-pane.d.ts +3 -0
- package/dist/session-state.d.ts +20 -0
- package/dist/session.d.ts +24 -0
- package/dist/slide-dom-renderer.d.ts +22 -0
- package/dist/slide-dom-update.d.ts +13 -0
- package/dist/slide-editor-commands.d.ts +76 -0
- package/dist/slide-editor-dom.d.ts +2 -0
- package/dist/slide-editor-events.d.ts +20 -0
- package/dist/slide-editor-keyboard-events.d.ts +39 -0
- package/dist/slide-editor-types.d.ts +90 -0
- package/dist/slide-editor.d.ts +3 -0
- package/dist/slide-link-controller.d.ts +26 -0
- package/dist/slide-pointer-controller.d.ts +43 -0
- package/dist/snap-guides.d.ts +3 -0
- package/dist/snap.d.ts +60 -0
- package/dist/source-fingerprint.d.ts +9 -0
- package/dist/table-insertion.d.ts +7 -0
- package/dist/text-autofit.d.ts +18 -0
- package/dist/text-clipboard-controller.d.ts +32 -0
- package/dist/text-clipboard.d.ts +7 -0
- package/dist/text-dom.d.ts +30 -0
- package/dist/text-editor-context.d.ts +13 -0
- package/dist/text-editor-target.d.ts +10 -0
- package/dist/text-editor-types.d.ts +30 -0
- package/dist/text-editor-view.d.ts +4 -0
- package/dist/text-editor.d.ts +51 -0
- package/dist/text-input-plan.d.ts +20 -0
- package/dist/text-search-highlight.d.ts +18 -0
- package/dist/text-search-index.d.ts +11 -0
- package/dist/text-search-types.d.ts +41 -0
- package/dist/text-search-view.d.ts +33 -0
- package/dist/text-search.d.ts +46 -0
- package/dist/transform-frame.d.ts +14 -0
- package/dist/transition-preview.d.ts +24 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
# @web-ppt/editor
|
|
2
|
+
|
|
3
|
+
**English** · [简体中文](https://github.com/unStone/web-ppt/blob/master/packages/editor/README-zh-CN.md)
|
|
4
|
+
|
|
5
|
+
The framework-agnostic browser DOM layer for [Web-PPT](https://github.com/unStone/web-ppt). It opens a
|
|
6
|
+
source file once, owns its parsed resources and headless editing model, and mounts one or more high-fidelity
|
|
7
|
+
slide views without uploading the file.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @web-ppt/core @web-ppt/edit-core @web-ppt/viewer-core @web-ppt/editor
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { openEditor } from '@web-ppt/editor';
|
|
15
|
+
|
|
16
|
+
const session = await openEditor(file);
|
|
17
|
+
const view = session.mount(container, {
|
|
18
|
+
mode: 'edit', zoom: 1, textMode: 'auto', snapping: true,
|
|
19
|
+
snapMargins: { left: 24, right: 24, top: 24, bottom: 24 }, // optional slide px
|
|
20
|
+
});
|
|
21
|
+
const selectionPane = session.mountSelectionPane(paneContainer, {
|
|
22
|
+
mode: 'edit', slideId: view.slideId,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const slideId = session.editor.doc.slideOrder[0];
|
|
26
|
+
const elementId = session.editor.doc.slides[slideId].children[0];
|
|
27
|
+
session.editor.exec({ type: 'SetXfrm', id: elementId, x: 120 });
|
|
28
|
+
session.editor.exec({ type: 'AlignElements', ids: [elementId], edge: 'center' });
|
|
29
|
+
session.editor.exec({
|
|
30
|
+
type: 'AddShape', slideId, preset: 'roundRect',
|
|
31
|
+
rect: { x: 360, y: 180, w: 280, h: 160 },
|
|
32
|
+
});
|
|
33
|
+
const layoutId = session.editor.doc.layoutOrder[0];
|
|
34
|
+
const added = session.editor.exec({ type: 'AddSlide', layoutId, at: { after: slideId } });
|
|
35
|
+
view.setSlide([...added.createdSlides][0]);
|
|
36
|
+
session.editor.exec({ type: 'MoveSlide', id: view.slideId, at: { after: null } });
|
|
37
|
+
const duplicated = session.editor.exec({ type: 'DuplicateSlide', id: view.slideId });
|
|
38
|
+
view.setSlide([...duplicated.createdSlides][0]);
|
|
39
|
+
|
|
40
|
+
await view.insertImage(imageFile, { rect: { x: 420, y: 180, w: 320, h: 220 } });
|
|
41
|
+
// Or from a toolbar click: const imageId = await view.chooseImage();
|
|
42
|
+
const tableId = view.insertTable(3, 4); // rows, columns; replaces a selected empty content placeholder
|
|
43
|
+
|
|
44
|
+
view.setMode('view'); // same static preview DOM; interaction layers are hidden
|
|
45
|
+
view.setMode('edit');
|
|
46
|
+
view.setSlide(slideId);
|
|
47
|
+
view.setZoom(1.5);
|
|
48
|
+
view.setSnapping(false); // can be changed without remounting
|
|
49
|
+
selectionPane.setSlide(slideId);
|
|
50
|
+
|
|
51
|
+
notesTextarea.value = view.queryNotes().value;
|
|
52
|
+
notesTextarea.addEventListener('input', () => view.setNotes(notesTextarea.value));
|
|
53
|
+
// View mode can still queryNotes(), but setNotes() returns false without mutating the shared model.
|
|
54
|
+
|
|
55
|
+
const transition = view.queryTransition();
|
|
56
|
+
await view.previewTransition(); // preview the effective value in view or edit mode
|
|
57
|
+
await view.previewTransition({ type: 'push', dir: 'r', durationMs: 500 }); // preview before commit
|
|
58
|
+
view.setTransition({ type: 'morph', durationMs: 900, morphBy: 'byWord' });
|
|
59
|
+
view.setTransition(null); // restore the parsed source; { type: 'none' } explicitly disables it
|
|
60
|
+
|
|
61
|
+
const animations = view.queryAnimations(); // targets are stable ElementIds, never OOXML spids
|
|
62
|
+
// When sourceReadonly is true, offer an explicit whole-timeline replacement instead of partial editing.
|
|
63
|
+
await view.previewAnimations(); // plays the complete effective timeline in view/edit mode
|
|
64
|
+
await view.previewAnimations([{ // validate and audition a draft without changing history
|
|
65
|
+
target: targetId, kind: 'entrance', effect: 'fly', dir: 'r', trigger: 'click',
|
|
66
|
+
delayMs: 0, durationMs: 500,
|
|
67
|
+
}]);
|
|
68
|
+
view.setAnimations(animations.value); // edit mode only; [] removes the timeline
|
|
69
|
+
view.setAnimations(null); // restore the untouched parsed source
|
|
70
|
+
|
|
71
|
+
const bytes = await session.editor.save();
|
|
72
|
+
view.destroy(); // destroys only this mounted view
|
|
73
|
+
selectionPane.destroy();
|
|
74
|
+
session.dispose(); // destroys remaining views and releases ZIP bytes / blob URLs
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Autosave and crash recovery
|
|
78
|
+
|
|
79
|
+
Storage is opt-in. Once `recovery` is provided, the source is read once, identified by a full-byte SHA-256, and
|
|
80
|
+
parsed from those same bytes. Transactions are appended in asynchronous batches, so canvas commits never wait for
|
|
81
|
+
IndexedDB.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createIndexedDbRecoveryStore, openEditor } from '@web-ppt/editor';
|
|
85
|
+
|
|
86
|
+
const recoveryStore = createIndexedDbRecoveryStore({ namespace: 'my-editor' });
|
|
87
|
+
const session = await openEditor(file, {
|
|
88
|
+
recovery: {
|
|
89
|
+
store: recoveryStore,
|
|
90
|
+
decide: async (candidate) => showRecoveryDialog({
|
|
91
|
+
changedAt: new Date(candidate.updatedAt),
|
|
92
|
+
actions: ['restore', 'discard', 'cancel'],
|
|
93
|
+
}),
|
|
94
|
+
onError: (error) => showAutosaveWarning(error),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await session.recovery?.flush(); // await queued frames before a critical navigation
|
|
99
|
+
session.dispose();
|
|
100
|
+
await recoveryStore.close(); // share one store across sessions; close it at app shutdown
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Equal bytes across `File`, `Blob`, `ArrayBuffer`, and `Uint8Array` resolve to one journal; equal names or sizes do
|
|
104
|
+
not. `restore` reuses the stored `idPrefix` before any DOM is visible, while `discard` atomically replaces the old
|
|
105
|
+
journal with an empty, new-generation reservation. Late writes from an older tab are rejected instead of reviving
|
|
106
|
+
discarded edits. `cancel` neither parses nor mounts nor changes the journal. A clean tail is reset without prompting.
|
|
107
|
+
Edits made after a save retain the complete chain from the original source.
|
|
108
|
+
|
|
109
|
+
Defaults compact more than 64 chunks back to 32 and retain at most 20 journals, 16MB, or 30 days. `stats()`,
|
|
110
|
+
`cleanup()`, and factory options expose those controls. Compaction regroups patch frames unchanged and coalesces only
|
|
111
|
+
consecutive metadata-only frames, preserving the final selection/savepoint state. Append/quota failures never roll back committed edits; they surface through
|
|
112
|
+
`session.recovery.error`, `flush()` rejection, and `onError` without an unhandled rejection.
|
|
113
|
+
|
|
114
|
+
A custom `RecoveryStore` implements `load`, atomic `reset`, atomic `append`, and `remove`. It must persist and compare
|
|
115
|
+
the supplied `epoch`; this is the generation guard that prevents a stale session from appending after discard. Its
|
|
116
|
+
`reset` must also reject before commit when `request.signal` is aborted, so a superseded open cannot overwrite the
|
|
117
|
+
latest reservation.
|
|
118
|
+
|
|
119
|
+
You may still supply `idPrefix + recoveryFrames` from `@web-ppt/edit-core` for custom persistence, but not together
|
|
120
|
+
with `recovery`. Framework adapters use O(1) reference identity for both stores and manual logs rather than scanning
|
|
121
|
+
them on every render.
|
|
122
|
+
|
|
123
|
+
## Framework adapter contract
|
|
124
|
+
|
|
125
|
+
`createWebPptAdapter()` is the lifecycle boundary used by `@web-ppt/react` and `@web-ppt/vue`; Svelte, Web
|
|
126
|
+
Components, and other hosts can bind it directly. It is safe to import during SSR and in a Worker because no DOM or
|
|
127
|
+
IndexedDB global is touched until a session or store is explicitly created; `attach()` is the first DOM boundary.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { applyWebPptAdapterBinding, createWebPptAdapter } from '@web-ppt/editor';
|
|
131
|
+
|
|
132
|
+
const adapter = createWebPptAdapter({ onError: console.error });
|
|
133
|
+
adapter.attach(container);
|
|
134
|
+
adapter.attachSelectionPane(paneContainer);
|
|
135
|
+
await applyWebPptAdapterBinding(adapter, {
|
|
136
|
+
source: file, mode: 'edit', slideId, zoom: 1,
|
|
137
|
+
onViewChange: (state) => routeTo(state.slideId),
|
|
138
|
+
});
|
|
139
|
+
const bytes = await adapter.save();
|
|
140
|
+
adapter.dispose();
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The adapter owns sessions opened from `source` and atomically releases them on replacement or disposal. To share
|
|
144
|
+
an existing session across several adapters, pass `{ session, sessionOwnership: 'external' }`; each adapter then
|
|
145
|
+
destroys only its view and the caller remains responsible for `session.dispose()`. Concurrent stale opens dispose
|
|
146
|
+
their late result. `snapshot` and `subscribe()` expose idle/opening/recovering/ready/error state, progress, the
|
|
147
|
+
recovery candidate, session, view, mode, stable slide id, and zoom without introducing a second presentation model.
|
|
148
|
+
When persistence is enabled, `onRecovery(candidate)` returns `restore`, `discard`, or `cancel`; React and Vue pass
|
|
149
|
+
through this same callback.
|
|
150
|
+
|
|
151
|
+
### Session format painter
|
|
152
|
+
|
|
153
|
+
The format painter is one session controller shared by every mounted view and framework adapter. Select exactly one
|
|
154
|
+
shape/image/group or a text range, then start a one-shot or continuous painter:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
view.startFormatPainter(); // exits after the first successful target
|
|
158
|
+
view.startFormatPainter({ continuous: true }); // keeps the stable source across slides/views
|
|
159
|
+
session.formatPainter.cancel();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`Escape`, switching any view to `view` mode, deleting the source, or disposing the session cancels it. A target
|
|
163
|
+
click applies one atomic `ApplyFormat` command before selecting that target; incompatible targets dispatch
|
|
164
|
+
`webpptformaterror` and retain the active source. Active roots expose `data-format-painter="single|continuous"`
|
|
165
|
+
and `data-format-painter-source`, so products can supply their own cursor and toolbar styling without owning state.
|
|
166
|
+
`adapter.startFormatPainter()` / `cancelFormatPainter()` map to the same controller, while
|
|
167
|
+
`snapshot.formatPainter` exposes `active`, `mode`, `source`, and `readonly` to React, Vue, Svelte, and Web Components.
|
|
168
|
+
|
|
169
|
+
### Session find and replace
|
|
170
|
+
|
|
171
|
+
Every mounted view and adapter also consumes one lazy `session.textSearch` controller. View mode can search,
|
|
172
|
+
navigate, and highlight; edit mode additionally enables replacement. The first non-empty query builds per-page
|
|
173
|
+
results, later document transactions evict only dirty or removed pages, and page moves only reorder cached results.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
session.textSearch.open({
|
|
177
|
+
mode: 'replace', query: 'draft', replacement: 'final',
|
|
178
|
+
scope: { kind: 'document' }, matchCase: false, wholeWord: true,
|
|
179
|
+
});
|
|
180
|
+
session.textSearch.next();
|
|
181
|
+
session.textSearch.replaceCurrent();
|
|
182
|
+
|
|
183
|
+
adapter.openTextSearch({ mode: 'find', query: 'owner' });
|
|
184
|
+
adapter.setTextSearchReplacement('team');
|
|
185
|
+
adapter.replaceAllText(); // false/0 in view mode; snapshot.textSearch.canReplace explains why
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Focused views use `Ctrl/Cmd+F`, `Ctrl/Cmd+H`, `Enter`, `Shift+Enter`, and `Escape`. Only the view receiving a
|
|
189
|
+
cross-page navigation event switches slides; other views keep their local page. Roots expose
|
|
190
|
+
`data-text-search-*`, matched elements expose `data-ppt-search-matches` / `data-ppt-search-current`, and the
|
|
191
|
+
current text range exposes `data-ppt-search-current-range`. Exact range boxes live in an overlay rather than a
|
|
192
|
+
fourth render layer. Products own the search bar and ARIA controls. The Chromium contract measures 200-page
|
|
193
|
+
initial index / cold query p95 at 1.8 / 0.5ms and 60-element navigation / replacement feedback p95 at 0.1 / 0.9ms.
|
|
194
|
+
|
|
195
|
+
`mountSelectionPane()` is the single accessible DOM implementation for the object catalog. It presents the current
|
|
196
|
+
slide in topmost-first tree order, keeps duplicate OOXML names distinct through stable element ids, and supports
|
|
197
|
+
group navigation, rename, lock, and session-only hide. Arrow keys, Home/End, Left/Right, Enter, Space, and F2 work
|
|
198
|
+
without a pointer. Lock and hide inherit through groups; showing a child removes its own `visibility` declaration
|
|
199
|
+
instead of writing `visible`, so a hidden ancestor cannot be bypassed. Geometry and text edits retain existing row
|
|
200
|
+
DOM identities; a 60-object lock/unlock round trip measures 0.4ms p95 in the Chromium contract.
|
|
201
|
+
|
|
202
|
+
Every host maps the same four operations; no rendering or editor state is reimplemented:
|
|
203
|
+
|
|
204
|
+
| Host | Create / update / destroy mapping |
|
|
205
|
+
|---|---|
|
|
206
|
+
| React | effect creates adapter → props call `applyWebPptAdapterBinding` → cleanup calls `dispose` |
|
|
207
|
+
| Vue | `onMounted` creates → reactive effect applies binding → `onBeforeUnmount` disposes |
|
|
208
|
+
| Svelte | `onMount` creates and returns `dispose`; `$effect` applies the controlled binding |
|
|
209
|
+
| Web Component | `connectedCallback` creates/attaches; attributes apply binding; `disconnectedCallback` disposes |
|
|
210
|
+
|
|
211
|
+
For a reconnectable custom element, create a fresh adapter on every `connectedCallback` rather than retaining a
|
|
212
|
+
disposed controller. Container mounting remains `adapter.attach(this)`, so Shadow DOM is optional and product CSS
|
|
213
|
+
or toolbars stay outside the contract.
|
|
214
|
+
|
|
215
|
+
`SlideEditor` owns three stacked layers: the existing SVG preview, an SVG interaction overlay, and an HTML
|
|
216
|
+
text overlay. A headless `Editor` transaction replaces only the dirty element's markup and defs partition;
|
|
217
|
+
unchanged sibling DOM nodes keep their identity. The view falls back to one full render only when more than eight
|
|
218
|
+
partitions and over 30% of the slide's top-level elements are dirty, avoiding ratio distortion on small slides.
|
|
219
|
+
Stable `data-edit-id` values are assigned to top-level and nested group nodes, so DOM hit testing never depends
|
|
220
|
+
on part-local OOXML ids.
|
|
221
|
+
|
|
222
|
+
In edit mode, pointer selection uses the browser's native SVG hit testing. A click inside a group selects its
|
|
223
|
+
outermost group; double-click enters one group level and `Escape` leaves one level. `Alt`+click cycles through
|
|
224
|
+
overlapping candidates in `elementsFromPoint` z-order. Locked, user-hidden, and non-editable branches are
|
|
225
|
+
skipped. View mode does not intercept pointer events or mutate the shared headless selection. Selection changes
|
|
226
|
+
replace only the interaction overlay, leaving the static preview DOM untouched.
|
|
227
|
+
|
|
228
|
+
`Shift`, `Ctrl`, or macOS `Command`+click toggles a direct selectable child in the current slide or entered
|
|
229
|
+
group. Combining the selection modifier with `Alt` still reaches an unselected object behind the current stack.
|
|
230
|
+
A modified marquee previews and commits the symmetric difference between the valid prior selection and fully
|
|
231
|
+
contained objects; a modified blank click preserves the selection. Results follow paint order, create no history,
|
|
232
|
+
and never rebuild the static preview.
|
|
233
|
+
|
|
234
|
+
When an edit view has focus, arrow keys nudge in slide space by `1px`; `Shift`+arrow nudges by `10px`.
|
|
235
|
+
Multi-selections and elements inside rotated, flipped, non-uniformly scaled groups receive the same world-space
|
|
236
|
+
delta. Auto-repeat from one physical hold is one undo unit, while a press after key-up starts another. A locked,
|
|
237
|
+
hidden, non-editable, or off-page member rejects the whole operation. View mode, active pointer gestures, and
|
|
238
|
+
form/contenteditable controls in either regular or Shadow DOM keep ownership of their arrow keys.
|
|
239
|
+
|
|
240
|
+
`Tab` and `Shift`+`Tab` cycle forward or backward through the direct selectable children of the current slide
|
|
241
|
+
or entered group, in paint order and with wrap-around. A selection owned by another mounted page starts at this
|
|
242
|
+
view's first or last candidate. The traversal changes selection only: it creates no history entry and preserves
|
|
243
|
+
the static preview DOM. Form and contenteditable controls, including those in Shadow DOM, keep native Tab focus.
|
|
244
|
+
|
|
245
|
+
After a table cell is opened in the same text editor, `Tab` and `Shift`+`Tab` move only between visible merge-start
|
|
246
|
+
cells. `Tab` in the final cell runs one `InsertRow` transaction and enters the first cell of the new blank row.
|
|
247
|
+
Table style, frame height, static preview, selection box, and caret update together without losing focus. View mode
|
|
248
|
+
never emits the structural command.
|
|
249
|
+
|
|
250
|
+
`Ctrl/Cmd+Z` undoes; `Ctrl/Cmd+Shift+Z` or `Ctrl/Cmd+Y` redoes. If the restored selection belongs to another
|
|
251
|
+
slide, only the edit view that received the shortcut reveals that slide; other shared views stay put. Active
|
|
252
|
+
pointer previews and regular or Shadow DOM text controls retain keyboard ownership, while single-element history
|
|
253
|
+
still replaces only that element's DOM partition.
|
|
254
|
+
|
|
255
|
+
`Delete` or `Backspace` removes the current element selection as one undo unit. Groups are recursive; frame-only
|
|
256
|
+
objects such as charts, SmartArt, and OLE lose their outer frame without garbage-collecting potentially shared
|
|
257
|
+
relationships or media. A populated placeholder clears its text on the first deletion and removes its frame on
|
|
258
|
+
the second. Delete and undo remove or reinsert only the affected markup/defs partitions in stable z-order, so
|
|
259
|
+
untouched sibling DOM identities survive. Form controls, contenteditable or Shadow DOM text, and active pointer
|
|
260
|
+
gestures keep native keyboard ownership.
|
|
261
|
+
|
|
262
|
+
`Ctrl/Cmd+]` moves forward, `Ctrl/Cmd+Shift+]` brings to front, `Ctrl/Cmd+[` moves backward, and
|
|
263
|
+
`Ctrl/Cmd+Shift+[` sends to back. A multi-selection moves as one undo unit without reversing its internal order;
|
|
264
|
+
group children and frame-only objects use the same semantics, and boundary operations create no empty history.
|
|
265
|
+
Views move existing markup partitions in place, preserving defs, hyperlink wrappers, untouched siblings, and
|
|
266
|
+
node identities in every shared view.
|
|
267
|
+
|
|
268
|
+
Focused edit views support native `Ctrl/Cmd+C`, `X`, and `V` through synchronous `ClipboardEvent` handling.
|
|
269
|
+
The view writes `application/x-web-ppt-elements+json` and `text/plain`, never calls permission-gated
|
|
270
|
+
`navigator.clipboard`, and pastes multiple element trees as one undo unit. `Ctrl/Cmd+D` duplicates the current
|
|
271
|
+
selection by 10 slide pixels without changing the system clipboard. View mode, active pointer gestures,
|
|
272
|
+
text/table selections, and form/contenteditable descendants retain browser ownership. Small pastes insert only
|
|
273
|
+
new markup/defs partitions; large batches may use the existing bounded full-slide fallback.
|
|
274
|
+
|
|
275
|
+
Links keep PowerPoint's edit/view distinction. Edit-mode clicks only select; `Ctrl/Cmd+Enter` or
|
|
276
|
+
`view.followLink()` follows the current single element/text link. In view mode, internal links are focusable with
|
|
277
|
+
`Tab`, activate with `Enter`, and route through the view's stable `SlideId` state; safe external links open with
|
|
278
|
+
`noopener,noreferrer`. A framework can intercept both through one callback and return `true` when its router handled
|
|
279
|
+
the target:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const view = session.mount(container, {
|
|
283
|
+
mode: 'edit',
|
|
284
|
+
onLinkFollow(target, context) {
|
|
285
|
+
if (target.kind === 'slide') {
|
|
286
|
+
appRouter.openSlide(target.slideId);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
view.followLink({ kind: 'external', href: 'https://example.com/docs' });
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The callback receives only `LinkTarget` (`external` or stable `slide`), never a raw page index or OOXML action.
|
|
295
|
+
View mode installs no edit pointer/keyboard/clipboard listeners; mode switches and `destroy()` release their
|
|
296
|
+
listeners symmetrically, so multiple React/Vue/Svelte/Web Component/vanilla views keep local navigation state.
|
|
297
|
+
|
|
298
|
+
Double-clicking an editable shape opens the HTML text layer with native selection and IME composition. Selected
|
|
299
|
+
text responds to `Ctrl/Cmd+B`, `I`, and `U` as one undo unit; the equivalent `beforeinput` format events are also
|
|
300
|
+
handled. At a collapsed caret these shortcuts update view-local pending typing style, and the next insertion plus
|
|
301
|
+
its format commit as one history unit without creating a zero-width model run. The live DOM range is published as
|
|
302
|
+
`session.editor.selection`; non-collapsed ranges can use headless `SetRunProps` / `queryRunProps` directly. A mounted
|
|
303
|
+
toolbar uses the view seam below so collapsed-caret typing style stays with the owning input view. Every mounted
|
|
304
|
+
view refreshes while the active browser range is preserved. Switching to view mode closes the input layer and keeps
|
|
305
|
+
the high-fidelity static preview.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const unregister = view.registerTextUi(toolbarElement);
|
|
309
|
+
boldButton.addEventListener('pointerdown', (event) => {
|
|
310
|
+
event.preventDefault(); // keep native selection/focus stable
|
|
311
|
+
const state = view.queryRunProps();
|
|
312
|
+
if (state) view.setRunProps({ b: state.b.mixed || state.b.value !== true });
|
|
313
|
+
});
|
|
314
|
+
centerButton.addEventListener('pointerdown', (event) => {
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
const state = view.queryParaProps();
|
|
317
|
+
if (state) view.setParaProps({ align: 'center' });
|
|
318
|
+
});
|
|
319
|
+
// unregister() when this toolbar no longer belongs to the view.
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Paragraph controls use the same live DOM Range and remain framework-neutral. `setParaProps` formats every touched
|
|
323
|
+
paragraph—including the current paragraph at a collapsed caret—in one undo unit, then restores the browser Range.
|
|
324
|
+
`queryParaProps` returns per-property mixed state for alignment, effective line height, spacing, margin, and indent.
|
|
325
|
+
The command refreshes all views sharing the session; an external toolbar registered with `registerTextUi` does not
|
|
326
|
+
steal focus or accidentally close text editing.
|
|
327
|
+
|
|
328
|
+
Text-mode `Ctrl/Cmd+C`, `X`, and `V` use the same synchronous browser clipboard events as native editors. Copy and
|
|
329
|
+
cut publish sanitized `text/plain` plus `text/html`; default paste keeps only font, size, bold, italic, underline,
|
|
330
|
+
and strike-through, while `Ctrl/Cmd+Shift+V` ignores HTML. Blocks become PPT paragraphs and `<br>` remains a hard
|
|
331
|
+
line break. External HTML is parsed in a detached tree and never injected into the live editor; scripts, style
|
|
332
|
+
sheets, link targets, image sources, hidden metadata, and unsupported CSS are discarded. If sanitized HTML text
|
|
333
|
+
does not equal `text/plain`, formatting is dropped instead of guessing offsets. A paste or cut is one undo unit.
|
|
334
|
+
Image-only paste on the canvas routes through the same `AddImage` command and creates one undo unit. Text/table
|
|
335
|
+
selections still retain their native paste ownership.
|
|
336
|
+
|
|
337
|
+
Product toolbars stay outside the base DOM package. Their six alignment actions call the headless
|
|
338
|
+
`AlignElements` command directly; the mounted view synchronously patches only elements that actually moved and
|
|
339
|
+
refreshes the interaction frame. This keeps the same integration surface for React, Vue, Web Components, and
|
|
340
|
+
vanilla applications without putting a framework runtime into the editor package.
|
|
341
|
+
|
|
342
|
+
Fill and outline controls use the same external-toolbar seam. The editor entry re-exports
|
|
343
|
+
`queryElementFill`, `queryElementStroke`, and `SHAPE_PATTERN_PRESETS`, so an adapter can derive mixed/effective
|
|
344
|
+
state and submit JSON commands without reading SVG or importing editor internals. Every mounted edit and view
|
|
345
|
+
surface updates the target markup/defs partition; unchanged siblings and the page SVG keep their DOM identity.
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
import { openEditor, queryElementFill } from '@web-ppt/editor';
|
|
349
|
+
|
|
350
|
+
const state = queryElementFill(session.editor.doc, selectedIds);
|
|
351
|
+
session.editor.exec({
|
|
352
|
+
type: 'SetFill', id: selectedIds[0],
|
|
353
|
+
fill: { type: 'solid', color: state.mixed ? '#2563EB' : '#0EA5E9' },
|
|
354
|
+
});
|
|
355
|
+
session.editor.exec({
|
|
356
|
+
type: 'SetStroke', id: selectedIds[0],
|
|
357
|
+
stroke: { color: '#0F172A', width: 2, dash: null },
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Page-sidebars use the re-exported `querySlideBackground` / `querySlideHidden` functions and submit
|
|
362
|
+
`SetBackground` / `SetHidden` through the same headless editor. Multiple mounted edit/view surfaces showing the
|
|
363
|
+
target page receive the new background synchronously; surfaces on other pages retain their SVG identity.
|
|
364
|
+
For local files, call `await view.setBackgroundImage(file, options)` or `view.chooseBackgroundImage()`; use
|
|
365
|
+
`view.setBackgroundCrop(cropOrNull)` for an existing or inherited image background. These methods are available to
|
|
366
|
+
React, Vue, Web Component, and vanilla toolbars without exposing OPC relationships or media hashes.
|
|
367
|
+
|
|
368
|
+
Shape palettes use the same framework-neutral seam: call `session.editor.exec({ type: 'AddShape', ... })`.
|
|
369
|
+
Every mounted view inserts the new SVG partition synchronously, the edit view shows its selection frame, and a
|
|
370
|
+
double-click opens the existing text editor. View mode exposes no creation gesture; product code decides when to
|
|
371
|
+
offer the command without importing DOM internals.
|
|
372
|
+
|
|
373
|
+
Image buttons can call `view.chooseImage()` for the built-in local file input or `view.insertImage(file, options)`
|
|
374
|
+
when a React, Vue, Web Component, or vanilla toolbar already owns a `File`/`Blob`. PNG, JPEG, GIF, and WebP are
|
|
375
|
+
recognized from their bytes rather than the filename or browser MIME. Files stay local; the default 5MB limit
|
|
376
|
+
keeps the insertion inside the standard 8MB undo budget and can be changed explicitly. While bytes are read the
|
|
377
|
+
view exposes `aria-busy="true"`; failures reject the promise and dispatch `webpptimageerror`. Double-clicking an
|
|
378
|
+
empty picture placeholder uses the same path and replaces that placeholder plus the image in one undo unit.
|
|
379
|
+
|
|
380
|
+
Table pickers call synchronous `view.insertTable(rows, cols, options?)`. An explicit `rect` is used as-is; without
|
|
381
|
+
one, the view replaces the selected empty content placeholder or computes a centered size from the grid. The result
|
|
382
|
+
is a native DrawingML table whose cells open in the existing text editor and whose final-cell `Tab` uses the existing
|
|
383
|
+
append-row path. The method returns the new stable element id, while view mode rejects creation.
|
|
384
|
+
|
|
385
|
+
Page navigators use the equivalent `AddSlide` seam shown above. The headless result identifies the new page;
|
|
386
|
+
each mounted edit or view surface switches with its existing `setSlide` method, so a toolbar never mutates DOM
|
|
387
|
+
or scans for generated ids. An edit surface draws empty layout placeholders only in its interaction layer and
|
|
388
|
+
double-click opens the existing text editor; view surfaces and exported/saved output contain no helper UI.
|
|
389
|
+
|
|
390
|
+
Reordering uses the adjacent `MoveSlide { id, at: { after } }` seam. Subscribe to `movedSlides`, then read the
|
|
391
|
+
final `session.editor.doc.slideOrder`; mounted view/edit canvases keep their stable `slideId` and only derived
|
|
392
|
+
page-number or relative-link partitions are refreshed. Framework adapters never need a shadow page-order model.
|
|
393
|
+
|
|
394
|
+
Deletion uses `session.editor.exec({ type: 'RemoveSlide', id })`. All mounted view/edit canvases showing that page
|
|
395
|
+
close active input and switch to the public `removedSlideFallbacks` target; canvases on surviving pages keep their
|
|
396
|
+
stable `slideId` and SVG root. A React, Vue, Svelte, Web Component, or vanilla navigator can consume the same map.
|
|
397
|
+
Disable the command when `session.editor.doc.slideOrder.length === 1`.
|
|
398
|
+
|
|
399
|
+
Duplication uses `DuplicateSlide { id }` and reports the new stable page through `createdSlides`. Existing canvases
|
|
400
|
+
stay on their current pages and preserve active input; a React/Vue/Svelte/Web Component adapter can switch or mount
|
|
401
|
+
another canvas explicitly with that returned id. The copy is inserted after its source; compose `MoveSlide` when the
|
|
402
|
+
user chooses another destination.
|
|
403
|
+
|
|
404
|
+
The interaction SVG draws one exact oriented bounding box for a single selection and the world-space AABB union
|
|
405
|
+
for a multi-selection. It adds eight resize handles and one rotation handle; their stroke and size stay constant
|
|
406
|
+
in screen pixels at every view zoom. Rotated/flipped elements and nested groups use the same transform order as
|
|
407
|
+
the core renderer.
|
|
408
|
+
|
|
409
|
+
Custom adapters can use the same pure coordinate seam without mounting another view:
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import {
|
|
413
|
+
elementFrameToSlidePoint, screenToSlidePoint,
|
|
414
|
+
slideToElementFramePoint, slideToElementParentPoint,
|
|
415
|
+
} from '@web-ppt/editor';
|
|
416
|
+
|
|
417
|
+
const slidePoint = screenToSlidePoint(
|
|
418
|
+
{ x: event.clientX, y: event.clientY },
|
|
419
|
+
{ left: canvasRect.left, top: canvasRect.top, zoom: view.zoom },
|
|
420
|
+
);
|
|
421
|
+
const localPoint = slideToElementFramePoint(session.editor.doc, elementId, slidePoint);
|
|
422
|
+
const parentPoint = slideToElementParentPoint(session.editor.doc, elementId, slidePoint);
|
|
423
|
+
const origin = elementFrameToSlidePoint(session.editor.doc, elementId, { x: 0, y: 0 });
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
These functions are DOM-free and include every ancestor group's rotation, flip, child offset, and child scale.
|
|
427
|
+
Use `localPoint` for geometry and `parentPoint` for `x` / `y` movement.
|
|
428
|
+
|
|
429
|
+
Dragging an element selects and moves it in one gesture; dragging any member of a multi-selection preserves and
|
|
430
|
+
moves the whole selection. A 3-screen-pixel threshold separates clicks from moves. The view captures the primary
|
|
431
|
+
pointer, coalesces preview updates with `requestAnimationFrame`, and translates temporary SVG wrappers plus the
|
|
432
|
+
interaction overlay—no model, defs, or static element identity changes during the gesture. Pointer-up removes the
|
|
433
|
+
ghosts before committing one `SetXfrm` transaction. `Escape`, pointer cancellation/loss, page or mode changes,
|
|
434
|
+
and view destruction restore the original DOM without history. Nested rotated/flipped groups are converted into
|
|
435
|
+
each element's parent coordinate space.
|
|
436
|
+
|
|
437
|
+
Movement snaps within a six-screen-pixel threshold to the slide center/edges and direct siblings' edges/centers;
|
|
438
|
+
equal gaps are shown as paired bidirectional spacing arrows. Optional `snapMargins` add four host-defined slide
|
|
439
|
+
guides without guessing a document margin. Candidates are resolved independently per axis in stable priority
|
|
440
|
+
order, so overlapping choices do not jitter with element traversal order. Hold `Ctrl` to disable snapping during
|
|
441
|
+
the current gesture, or use `snapping: false` / `view.setSnapping(false)` for the whole view. Guides exist only in
|
|
442
|
+
the interaction SVG and every cancellation path removes them without changing the model.
|
|
443
|
+
|
|
444
|
+
The eight resize handles have a 4-screen-pixel outward hit margin. Corners resize both axes, edge handles resize
|
|
445
|
+
one axis, `Shift` preserves aspect ratio, and `Alt` keeps the center fixed; modifiers can be pressed or released
|
|
446
|
+
during the gesture. Crossing the opposite anchor normalizes positive dimensions, swaps the active handle without
|
|
447
|
+
a visual jump, and toggles `flipH` / `flipV`. Single rotated/flipped and nested elements keep the opposite anchor
|
|
448
|
+
fixed in their parent space; multi-selections resize from their shared world-space AABB. Preview frames reuse the
|
|
449
|
+
same pointer-capture/rAF lifecycle as movement and touch only temporary wrappers plus the interaction overlay.
|
|
450
|
+
Pointer-up commits every selected root in one undo unit.
|
|
451
|
+
|
|
452
|
+
The rotation handle has the same four-screen-pixel hit margin. A single selection converts pointer angles into
|
|
453
|
+
the element's parent space through its own flip and every rotated/flipped ancestor; a multi-selection rotates all
|
|
454
|
+
selected roots around the shared AABB center. Angles accumulate continuously across ±180°, `Shift` can snap to
|
|
455
|
+
15° at any point in the gesture, and a live value appears beside a single selection. Preview still touches only
|
|
456
|
+
ghost wrappers; pointer-up writes OOXML's 1/60000-degree value in one transaction, while every cancellation path
|
|
457
|
+
commits nothing.
|
|
458
|
+
|
|
459
|
+
`textMode: 'auto'` is the default. It reuses `viewer-core`'s runtime probe and switches affected Safari/iOS
|
|
460
|
+
engines to native SVG text when they fail to scale `foreignObject`. The out-of-SVG `contenteditable` then consumes
|
|
461
|
+
the same absolute engine line boxes from core. Explicit `html` and `svg` modes are also available. Soft wraps stay
|
|
462
|
+
out of the model while hard breaks, empty paragraphs, RTL, vertical text, columns, and math remain editable at
|
|
463
|
+
source UTF-16 positions. Full-slide renders, incremental patches, and the active editor use one text mode.
|
|
464
|
+
|
|
465
|
+
One session can mount several views (for example, the main canvas and a thumbnail). Destroying a view never
|
|
466
|
+
releases shared resources; disposing the session destroys every remaining view and is idempotent. React, Vue,
|
|
467
|
+
Svelte, Web Components, and plain DOM adapters all use the same `openEditor` / `mount` seam—none of their
|
|
468
|
+
runtimes are dependencies of this package.
|
|
469
|
+
|
|
470
|
+
The published entry measures 40.61 KB gzip. `@web-ppt/core`, `@web-ppt/edit-core`, and
|
|
471
|
+
`@web-ppt/viewer-core` are peer dependencies.
|
|
472
|
+
|
|
473
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EditorSession } from './session';
|
|
2
|
+
import type { EditorMode } from './slide-editor-types';
|
|
3
|
+
import type { WebPptFormatPainterState } from './framework-adapter-types';
|
|
4
|
+
import type { FormatPainterStartOptions } from './format-painter-types';
|
|
5
|
+
/** adapter 只桥接会话控制器;状态机仍由 EditorSession 独占。 */
|
|
6
|
+
export declare class AdapterFormatPainterBinding {
|
|
7
|
+
private session;
|
|
8
|
+
private unsubscribe;
|
|
9
|
+
private previous;
|
|
10
|
+
bind(session: EditorSession, changed: () => void): void;
|
|
11
|
+
release(): void;
|
|
12
|
+
start(options: FormatPainterStartOptions): boolean;
|
|
13
|
+
cancel(): void;
|
|
14
|
+
state(ready: boolean, mode: EditorMode): WebPptFormatPainterState;
|
|
15
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { LinkTarget } from '@web-ppt/edit-core';
|
|
2
|
+
import type { LinkFollowContext, LinkFollowHandler } from './slide-editor-types';
|
|
3
|
+
export declare function followAdapterLink(target: LinkTarget, context: LinkFollowContext, handler: LinkFollowHandler | undefined, setSlide: (slideId: string) => void, report: (error: unknown) => void): boolean | void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpenEditorOptions } from './session';
|
|
2
|
+
import type { SlideEditorOptions } from './slide-editor-types';
|
|
3
|
+
import type { WebPptViewOptions } from './framework-adapter-types';
|
|
4
|
+
export declare function openOptionsKey(options: OpenEditorOptions | undefined): string;
|
|
5
|
+
export declare function validateViewOptions(options: WebPptViewOptions): void;
|
|
6
|
+
export declare function sameMargins(left: SlideEditorOptions['snapMargins'], right: SlideEditorOptions['snapMargins']): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OpenEditorOptions } from './session';
|
|
2
|
+
import type { RecoveryCandidate, RecoveryDecisionHandler } from './recovery-store';
|
|
3
|
+
interface AdapterRecoveryHooks {
|
|
4
|
+
active(): boolean;
|
|
5
|
+
errorActive(): boolean;
|
|
6
|
+
decision(): RecoveryDecisionHandler | undefined;
|
|
7
|
+
recovering(candidate: RecoveryCandidate): void;
|
|
8
|
+
opening(): void;
|
|
9
|
+
errorHandler(): ((error: unknown) => void) | undefined;
|
|
10
|
+
error(error: unknown): void;
|
|
11
|
+
}
|
|
12
|
+
/** 只编排宿主决策;指纹、日志和回放仍由 openEditor 独占。 */
|
|
13
|
+
export declare function bindAdapterRecovery(options: OpenEditorOptions | undefined, signal: AbortSignal, hooks: AdapterRecoveryHooks): OpenEditorOptions | undefined;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SlideId } from '@web-ppt/edit-core';
|
|
2
|
+
import type { EditorSession } from './session';
|
|
3
|
+
import type { SelectionPane } from './selection-pane-types';
|
|
4
|
+
import type { EditorMode } from './slide-editor-types';
|
|
5
|
+
interface PaneViewState {
|
|
6
|
+
readonly mode?: EditorMode;
|
|
7
|
+
readonly slideId?: SlideId;
|
|
8
|
+
}
|
|
9
|
+
/** adapter 的第二个 DOM 资源独立收口,文件替换时可先完整挂载再原子切换所有权。 */
|
|
10
|
+
export declare class AdapterSelectionPaneBinding {
|
|
11
|
+
private currentContainer;
|
|
12
|
+
private currentPane;
|
|
13
|
+
get pane(): SelectionPane | null;
|
|
14
|
+
attach(container: HTMLElement | null, session: EditorSession | null, view: PaneViewState, fallbackSlide: SlideId | null, onError: (error: unknown) => void): void;
|
|
15
|
+
prepare(session: EditorSession, view: PaneViewState, fallbackSlide: SlideId | null, onError: (error: unknown) => void): SelectionPane | null;
|
|
16
|
+
commit(next: SelectionPane | null): void;
|
|
17
|
+
sync(session: EditorSession | null, view: PaneViewState): void;
|
|
18
|
+
release(): void;
|
|
19
|
+
dispose(): void;
|
|
20
|
+
private mount;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TextSearchMatch } from '@web-ppt/edit-core';
|
|
2
|
+
import type { EditorSession } from './session';
|
|
3
|
+
import type { EditorMode } from './slide-editor-types';
|
|
4
|
+
import type { TextSearchOpenOptions, TextSearchOptions } from './text-search-types';
|
|
5
|
+
import type { WebPptTextSearchState } from './framework-adapter-types';
|
|
6
|
+
/** adapter 仅转发会话搜索;React/Vue/Svelte 不应各自维护第二份状态机。 */
|
|
7
|
+
export declare class AdapterTextSearchBinding {
|
|
8
|
+
private readonly reveal;
|
|
9
|
+
private session;
|
|
10
|
+
private unsubscribe;
|
|
11
|
+
private source;
|
|
12
|
+
private previous;
|
|
13
|
+
constructor(reveal: (match: TextSearchMatch) => void);
|
|
14
|
+
bind(session: EditorSession, changed: () => void): void;
|
|
15
|
+
release(): void;
|
|
16
|
+
open(options: TextSearchOpenOptions): void;
|
|
17
|
+
close(): void;
|
|
18
|
+
setQuery(query: string): void;
|
|
19
|
+
setReplacement(replacement: string): void;
|
|
20
|
+
setOptions(options: Partial<TextSearchOptions>): void;
|
|
21
|
+
next(): TextSearchMatch | null;
|
|
22
|
+
previousMatch(): TextSearchMatch | null;
|
|
23
|
+
replaceCurrent(): boolean;
|
|
24
|
+
replaceAll(): number;
|
|
25
|
+
state(ready: boolean, mode: EditorMode): WebPptTextSearchState;
|
|
26
|
+
private navigate;
|
|
27
|
+
private revealCurrent;
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnimStep } from '@web-ppt/core';
|
|
2
|
+
interface AnimationPreviewOptions {
|
|
3
|
+
readonly layer: HTMLElement;
|
|
4
|
+
readonly chrome?: readonly (HTMLElement | SVGElement)[];
|
|
5
|
+
current(): readonly AnimStep[] | undefined;
|
|
6
|
+
destroyed(): boolean;
|
|
7
|
+
}
|
|
8
|
+
/** 一键预览自动跑完整条时间线,但只拥有自己创建的 WAAPI 与临时内联样式。 */
|
|
9
|
+
export declare class AnimationPreviewController {
|
|
10
|
+
private readonly options;
|
|
11
|
+
private generation;
|
|
12
|
+
private playback;
|
|
13
|
+
private styles;
|
|
14
|
+
private chromeVisibility;
|
|
15
|
+
constructor(options: AnimationPreviewOptions);
|
|
16
|
+
preview(input?: readonly AnimStep[]): Promise<boolean>;
|
|
17
|
+
cancel(): void;
|
|
18
|
+
private releasePreview;
|
|
19
|
+
private releasePlayback;
|
|
20
|
+
private captureStyles;
|
|
21
|
+
private restoreStyles;
|
|
22
|
+
private applyVisibility;
|
|
23
|
+
private hideChrome;
|
|
24
|
+
private restoreChrome;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EditDoc, ElementId, SlideId } from '@web-ppt/edit-core';
|
|
2
|
+
export declare function elementIds(doc: EditDoc, roots: readonly ElementId[]): ElementId[];
|
|
3
|
+
export declare function bindElementIdentities(root: ParentNode, doc: EditDoc, roots: readonly ElementId[]): void;
|
|
4
|
+
export declare function bindSlideIdentities(root: ParentNode, doc: EditDoc, slideId: SlideId): void;
|
|
5
|
+
export declare function findElementPartition(root: ParentNode, id: ElementId): SVGElement | null;
|
|
6
|
+
/**
|
|
7
|
+
* visibility 会被后代的 visible 覆盖;显示元素时必须删除声明,让祖先隐藏继续继承。
|
|
8
|
+
*/
|
|
9
|
+
export declare function syncElementVisibility(root: ParentNode, doc: EditDoc, id: ElementId): boolean;
|
|
10
|
+
export declare function syncElementTreeVisibility(root: ParentNode, doc: EditDoc, id: ElementId): void;
|
|
11
|
+
export declare function syncSlideVisibility(root: ParentNode, doc: EditDoc, slideId: SlideId): void;
|
|
12
|
+
export declare function touchedElementPartitions(doc: EditDoc, slideId: SlideId, touched: ReadonlySet<ElementId>): {
|
|
13
|
+
ids: ElementId[];
|
|
14
|
+
topLevelCount: number;
|
|
15
|
+
};
|
|
16
|
+
/** 百分比在小页面上会把一次局部修改误判成整页重绘;只有真实批量提交才考虑换整页。 */
|
|
17
|
+
export declare function shouldRenderWholeSlide(partitionCount: number, topLevelCount: number, slideTopLevelCount: number): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Editor, ElementId } from '@web-ppt/edit-core';
|
|
2
|
+
/** markup 与它引用的 defs 在同一同步提交中换代,浏览器没有机会绘制半个状态。 */
|
|
3
|
+
export declare function patchElement(staticLayer: HTMLElement, editor: Editor, id: ElementId, idPrefix: string, textMode: 'html' | 'svg'): boolean;
|
|
4
|
+
/** 删除结构节点时只移除其 markup/defs 分区,未触碰兄弟必须保留 DOM 身份。 */
|
|
5
|
+
export declare function removeElementPartition(staticLayer: HTMLElement, id: ElementId): boolean;
|
|
6
|
+
/** 撤销删除时以模型兄弟 z 序寻找稳定锚点;空父容器交给整页回退。 */
|
|
7
|
+
export declare function insertElementPartition(staticLayer: HTMLElement, editor: Editor, id: ElementId, idPrefix: string, textMode: 'html' | 'svg'): boolean;
|
|
8
|
+
/** 按模型目标序倒序移动受影响分区;锚点标记保证末元素不会越过非元素装饰节点。 */
|
|
9
|
+
export declare function reorderElementPartitions(staticLayer: HTMLElement, editor: Editor, ids: ReadonlySet<ElementId>): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { KeyboardControllerOptions } from './keyboard-context';
|
|
2
|
+
/** 画布键盘路由只组合命令,元素变换与选择范围仍留在各自模块。 */
|
|
3
|
+
export declare class EditorKeyboardController {
|
|
4
|
+
private readonly options;
|
|
5
|
+
private readonly history;
|
|
6
|
+
private readonly deletion;
|
|
7
|
+
private readonly layer;
|
|
8
|
+
private readonly nudge;
|
|
9
|
+
constructor(options: KeyboardControllerOptions);
|
|
10
|
+
keyDown(event: KeyboardEvent): boolean;
|
|
11
|
+
keyUp(event: KeyboardEvent): boolean;
|
|
12
|
+
breakSequence(): void;
|
|
13
|
+
}
|