@shadow-garden/bapbong-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/index.cjs +6694 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6655 -0
- package/dist/lib/bapbong-editor.d.ts +207 -0
- package/dist/lib/bapbong-editor.d.ts.map +1 -0
- package/dist/lib/built-in-plugins.d.ts +14 -0
- package/dist/lib/built-in-plugins.d.ts.map +1 -0
- package/dist/lib/find-plugin.d.ts +32 -0
- package/dist/lib/find-plugin.d.ts.map +1 -0
- package/dist/lib/hyperlink-plugin.d.ts +12 -0
- package/dist/lib/hyperlink-plugin.d.ts.map +1 -0
- package/dist/lib/image-resize-plugin.d.ts +38 -0
- package/dist/lib/image-resize-plugin.d.ts.map +1 -0
- package/dist/lib/table-resize-plugin.d.ts +13 -0
- package/dist/lib/table-resize-plugin.d.ts.map +1 -0
- package/dist/lib/table-selection-plugin.d.ts +36 -0
- package/dist/lib/table-selection-plugin.d.ts.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Schema } from 'prosemirror-model';
|
|
2
|
+
import { type EditorState, type Transaction } from '@shadow-garden/bapbong-input-bridge';
|
|
3
|
+
import type { CaretRect, Command as EditorCommand, EditorChange, EditorPlugin, MeasureMetrics, MeasureText, PagePoint } from '@shadow-garden/bapbong-contracts';
|
|
4
|
+
/** A section boundary surfaced for UI markers. `index` is the break index
|
|
5
|
+
* passed to `removeSectionBreak`; `rect` is a full-page-width line (page-local)
|
|
6
|
+
* at the boundary, ready for `pageToCanvas` — null if off the current layout. */
|
|
7
|
+
export interface SectionBoundary {
|
|
8
|
+
index: number;
|
|
9
|
+
newPage: boolean;
|
|
10
|
+
columns: number;
|
|
11
|
+
pos: number;
|
|
12
|
+
rect: {
|
|
13
|
+
pageIndex: number;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
export type { EditorChange, EditorPlugin, PluginContext } from '@shadow-garden/bapbong-contracts';
|
|
20
|
+
export { RenderCore } from '@shadow-garden/bapbong-view';
|
|
21
|
+
import { Collection } from '@shadow-garden/bapbong-contracts';
|
|
22
|
+
import type { FindPlugin } from './find-plugin';
|
|
23
|
+
import type { TableSelectionPlugin } from './table-selection-plugin';
|
|
24
|
+
export type { TableSelectionPlugin, CellBlock, SelectedCell } from './table-selection-plugin';
|
|
25
|
+
export type { FindPlugin, FindState } from './find-plugin';
|
|
26
|
+
export interface BapbongEditorOptions {
|
|
27
|
+
/** The scroll viewport the page stack lives in (used for virtualization and
|
|
28
|
+
* scroll-into-view). Defaults to `stack.closest('.canvas-wrap')`. */
|
|
29
|
+
viewport?: HTMLElement;
|
|
30
|
+
/** Editor plugins. Their lifecycle/event hooks are invoked by the core; they
|
|
31
|
+
* reach back through the PluginContext handed to `setup`. */
|
|
32
|
+
plugins?: EditorPlugin[];
|
|
33
|
+
/** Engine-independent text measurer (e.g. font-file metrics). Defaults to a
|
|
34
|
+
* canvas-backed one; inject to make wrapping/pagination deterministic across
|
|
35
|
+
* WebView engines. Pair with {@link measureMetrics}. */
|
|
36
|
+
measureText?: MeasureText;
|
|
37
|
+
/** Vertical-metrics provider paired with {@link measureText}. */
|
|
38
|
+
measureMetrics?: MeasureMetrics;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The framework-agnostic core of bapbong: it owns the render → edit loop
|
|
42
|
+
* (import → layout → paint → input → selection → export) and the per-page
|
|
43
|
+
* canvas stack, with no UI chrome of its own. A host wires comment UI / toolbars
|
|
44
|
+
* around it via `onChange`, `caretRect`, `pageToCanvas`, `dispatch`, etc.
|
|
45
|
+
*
|
|
46
|
+
* The render half (layout/paint/scroll/zoom/geometry) lives in a shared
|
|
47
|
+
* {@link RenderCore} (the same one the read-only `BapbongView` uses); this class
|
|
48
|
+
* adds the editing half — a hidden ProseMirror editor as the IME / keyboard
|
|
49
|
+
* sink, pointer-driven caret/selection, plugins, commands and clipboard — and
|
|
50
|
+
* pushes a fresh doc + caret overlay to the core on every transaction.
|
|
51
|
+
*/
|
|
52
|
+
export declare class BapbongEditor {
|
|
53
|
+
private readonly stack;
|
|
54
|
+
private readonly core;
|
|
55
|
+
private bridge;
|
|
56
|
+
private lastCaret;
|
|
57
|
+
private lastSelection;
|
|
58
|
+
private caretVisible;
|
|
59
|
+
private blinkTimer;
|
|
60
|
+
private dragAnchor;
|
|
61
|
+
private dragHead;
|
|
62
|
+
private readonly changeListeners;
|
|
63
|
+
private readonly caretPickListeners;
|
|
64
|
+
/** Plugin registry keyed by name — built-ins (internal) + host (external). */
|
|
65
|
+
private readonly plugins;
|
|
66
|
+
private readonly pluginTeardowns;
|
|
67
|
+
private readonly pluginCtx;
|
|
68
|
+
private pointerPlugins;
|
|
69
|
+
private readonly offFonts;
|
|
70
|
+
private guideEl;
|
|
71
|
+
private readonly highlightEls;
|
|
72
|
+
private actionEl;
|
|
73
|
+
private actionHandler;
|
|
74
|
+
private frameEl;
|
|
75
|
+
/** Headless editor commands keyed by name — the surface a toolbar/menubar
|
|
76
|
+
* renders and dispatches against (`editor.commands.get('bold')?.run(...)`).
|
|
77
|
+
* Built from the shared command layer; the same ops run on a backend.
|
|
78
|
+
* (Plugin-contributed commands are an additive follow-up.) */
|
|
79
|
+
readonly commands: Collection<EditorCommand>;
|
|
80
|
+
constructor(stack: HTMLElement, opts?: BapbongEditorOptions);
|
|
81
|
+
/** The controlled surface handed to each plugin (live state + geometry). */
|
|
82
|
+
private makePluginContext;
|
|
83
|
+
/** Current editor state (doc + selection). Throws before a document loads. */
|
|
84
|
+
get state(): EditorState;
|
|
85
|
+
/** Number of laid-out pages (0 before the first document). */
|
|
86
|
+
get pageCount(): number;
|
|
87
|
+
/** The document schema in use (model's base + plugin schema contributions).
|
|
88
|
+
* Hosts serialize/parse comment bodies, previews, etc. against this. */
|
|
89
|
+
get schema(): Schema;
|
|
90
|
+
/** Built-in find-and-replace (always registered; the host renders the bar).
|
|
91
|
+
* Cast is safe by construction — createBuiltins() always registers it. */
|
|
92
|
+
get find(): FindPlugin;
|
|
93
|
+
/** Built-in table cell-range selection (drag across cells). The host opens
|
|
94
|
+
* cell properties from its action icon / right-click via `onAction`. */
|
|
95
|
+
get tableSelection(): TableSelectionPlugin;
|
|
96
|
+
/** Import a .docx, lay it out, and paint the first frame. Resolves with the
|
|
97
|
+
* imported page-chrome keys (the rest of the import rides on the doc model
|
|
98
|
+
* exposed via `state`). */
|
|
99
|
+
loadDocx(bytes: ArrayBuffer): Promise<{
|
|
100
|
+
headerKeys: string[];
|
|
101
|
+
footerKeys: string[];
|
|
102
|
+
}>;
|
|
103
|
+
/** Export the (edited) document back to .docx bytes, carrying the imported
|
|
104
|
+
* source package so unmodelled parts survive the round-trip. */
|
|
105
|
+
exportDocx(): Promise<Uint8Array>;
|
|
106
|
+
/** Apply a transaction (the host builds comment/edit transactions against
|
|
107
|
+
* `state` and dispatches them here). */
|
|
108
|
+
dispatch(tr: Transaction): void;
|
|
109
|
+
/** Subscribe to layout/paint cycles. Returns an unsubscribe fn. */
|
|
110
|
+
onChange(cb: (c: EditorChange) => void): () => void;
|
|
111
|
+
/** Subscribe to caret placement (a pointerdown that placed the caret). The
|
|
112
|
+
* host uses this to e.g. select the comment whose range was clicked. */
|
|
113
|
+
onCaretPick(cb: (pos: number) => void): () => void;
|
|
114
|
+
/** Caret geometry at a doc position (page-local), or null. */
|
|
115
|
+
caretRect(pos: number): CaretRect | null;
|
|
116
|
+
/** Map a page-local point to container (canvas-stack) coordinates, or null. */
|
|
117
|
+
pageToCanvas(p: PagePoint): {
|
|
118
|
+
x: number;
|
|
119
|
+
y: number;
|
|
120
|
+
} | null;
|
|
121
|
+
/** The document's section boundaries (one per section break), for a host to
|
|
122
|
+
* draw clickable markers. `pos` is the doc position at the start of the
|
|
123
|
+
* section after the break — feed it to `caretRect` to place the marker. */
|
|
124
|
+
sectionBoundaries(): SectionBoundary[];
|
|
125
|
+
/** Move the selection (caret if `to` omitted) and anchor the IME. */
|
|
126
|
+
setSelection(from: number, to?: number): void;
|
|
127
|
+
/** Select the word at a doc position. */
|
|
128
|
+
selectWordAt(pos: number): void;
|
|
129
|
+
/** Scroll the viewport so the caret at `pos` sits `topMargin` px from the top. */
|
|
130
|
+
scrollToPos(pos: number, topMargin?: number): void;
|
|
131
|
+
/** Set the zoom factor (1 = 100%) and repaint at the new scale. */
|
|
132
|
+
setZoom(zoom: number): void;
|
|
133
|
+
/** The current zoom factor (1 = 100%). */
|
|
134
|
+
getZoom(): number;
|
|
135
|
+
/** Print the whole document — renders every page (not just the visible ones)
|
|
136
|
+
* and prints one image per sheet. */
|
|
137
|
+
print(): Promise<void>;
|
|
138
|
+
/** Focus the hidden ProseMirror editor (keyboard/IME sink). */
|
|
139
|
+
focus(): void;
|
|
140
|
+
/** Copy the selection to the clipboard (PM serializes the slice). */
|
|
141
|
+
copy(): boolean;
|
|
142
|
+
/** Cut the selection to the clipboard. */
|
|
143
|
+
cut(): boolean;
|
|
144
|
+
/** Paste clipboard content (HTML → parsed by the schema, else plain text). */
|
|
145
|
+
paste(): Promise<void>;
|
|
146
|
+
/** Paste clipboard text as plain text (no formatting). */
|
|
147
|
+
pasteText(): Promise<void>;
|
|
148
|
+
destroy(): void;
|
|
149
|
+
/** Mount the hidden ProseMirror editor on a fresh doc and run the first paint. */
|
|
150
|
+
private mount;
|
|
151
|
+
/** Bridge update hook: relayout (only when the doc changed) → paint → place
|
|
152
|
+
* IME → notify. */
|
|
153
|
+
private refresh;
|
|
154
|
+
/** Compute the caret/selection overlay from `state`, paint it (full content
|
|
155
|
+
* when `contentDirty`, else overlay-only), anchor the IME, and notify. */
|
|
156
|
+
private render;
|
|
157
|
+
/** The caret/selection overlay in the current blink phase. */
|
|
158
|
+
private currentOverlay;
|
|
159
|
+
/** Each plugin's doc-range decorations (the core resolves them to rects). */
|
|
160
|
+
private pluginDecorations;
|
|
161
|
+
private emitChange;
|
|
162
|
+
/** Redraw caret/selection in the current blink phase (overlay-only). */
|
|
163
|
+
private repaintOverlay;
|
|
164
|
+
private stopBlink;
|
|
165
|
+
/** Show the caret solid now, then blink while idle. */
|
|
166
|
+
private restartBlink;
|
|
167
|
+
/** ArrowUp/ArrowDown against the canvas layout (the hidden DOM's own line
|
|
168
|
+
* wrapping is meaningless). With `extend`, Shift+arrow grows the selection. */
|
|
169
|
+
private verticalCmd;
|
|
170
|
+
private onPointerDown;
|
|
171
|
+
/** Offer a key to plugins (before the hidden editor's keymaps). Only when
|
|
172
|
+
* the event targets this editor (the canvas stack / hidden bridge) or has
|
|
173
|
+
* no specific target (body — where focus lands after a claimed pointer
|
|
174
|
+
* gesture), so plugins never steal keys from other inputs on the page. */
|
|
175
|
+
private onKeyDown;
|
|
176
|
+
private onPointerMove;
|
|
177
|
+
/** Overlay-only repaint of the in-progress drag selection. */
|
|
178
|
+
private paintDragSelection;
|
|
179
|
+
private onPointerUp;
|
|
180
|
+
private onContextMenu;
|
|
181
|
+
private onDblClick;
|
|
182
|
+
/** Offer a pointer event to plugins; returns true if one claimed it. `pos` is
|
|
183
|
+
* resolved for down/up/contextmenu only (move fires on every hover). */
|
|
184
|
+
private offerPointer;
|
|
185
|
+
/** Position (or hide, with null) the transient vertical drag guide. Lives in
|
|
186
|
+
* the canvas stack so it scrolls/zooms with the pages. */
|
|
187
|
+
private setGuide;
|
|
188
|
+
/** Position (or hide, with null) the object-selection frame: border, 8
|
|
189
|
+
* resize handles, and a rotate knob above the top edge — all one absolutely
|
|
190
|
+
* positioned container rotated around its center, so a drag updates plain
|
|
191
|
+
* DOM (no canvas repaint). Handle geometry stays constant-size on screen. */
|
|
192
|
+
private setFrame;
|
|
193
|
+
/** Fill (or clear, with null) translucent highlight rects — e.g. a selected
|
|
194
|
+
* table-cell block. Reuses a pool of divs in the canvas stack. */
|
|
195
|
+
private setHighlight;
|
|
196
|
+
/** Show (or hide, with null `at`) a small action button straddling a page
|
|
197
|
+
* point — a touch-friendly trigger. Stops pointer propagation so clicking it
|
|
198
|
+
* doesn't reset the selection underneath. */
|
|
199
|
+
private setActionButton;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Compose the document schema from a base plus each plugin's `schema`
|
|
203
|
+
* contribution (extra marks/nodes appended). Returns `null` when no plugin
|
|
204
|
+
* contributes anything, so callers can keep using the base schema unchanged.
|
|
205
|
+
*/
|
|
206
|
+
export declare function composeSchema(base: Schema, plugins: Iterable<EditorPlugin>): Schema | null;
|
|
207
|
+
//# sourceMappingURL=bapbong-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bapbong-editor.d.ts","sourceRoot":"","sources":["../../src/lib/bapbong-editor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Built-in ("internal") plugins — shipped with the editor, no install needed
|
|
4
|
+
* (unlike external `@shadow-garden/bapbong-*` plugins the host passes via
|
|
5
|
+
* `{ plugins }`). The editor registers these (keyed by `name`) ahead of the
|
|
6
|
+
* host's plugins and exposes the ones with a richer API as typed handles
|
|
7
|
+
* (e.g. `editor.find`).
|
|
8
|
+
*
|
|
9
|
+
* Add a new internal plugin by importing its factory and returning it here.
|
|
10
|
+
* This is a FACTORY, not a shared array: each plugin holds per-editor state, so
|
|
11
|
+
* every editor needs its own fresh instances.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createBuiltins(): EditorPlugin[];
|
|
14
|
+
//# sourceMappingURL=built-in-plugins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in-plugins.d.ts","sourceRoot":"","sources":["../../src/lib/built-in-plugins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAOrE;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,IAAI,YAAY,EAAE,CAK/C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
export interface FindState {
|
|
3
|
+
query: string;
|
|
4
|
+
/** Total matches. */
|
|
5
|
+
count: number;
|
|
6
|
+
/** 1-based index of the active match (0 when there are none). */
|
|
7
|
+
active: number;
|
|
8
|
+
}
|
|
9
|
+
export interface FindPlugin extends EditorPlugin {
|
|
10
|
+
setQuery(q: string): void;
|
|
11
|
+
next(): void;
|
|
12
|
+
prev(): void;
|
|
13
|
+
clear(): void;
|
|
14
|
+
/** Replace the active match with `text` (one undoable transaction). */
|
|
15
|
+
replaceCurrent(text: string): void;
|
|
16
|
+
/** Replace every match with `text` (one undoable transaction). */
|
|
17
|
+
replaceAll(text: string): void;
|
|
18
|
+
/** Subscribe to query/count/active changes (for the host's find bar). */
|
|
19
|
+
onState(cb: (s: FindState) => void): () => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Find-and-replace as a built-in ("internal") editor plugin: the editor
|
|
23
|
+
* instantiates it and exposes it as `editor.find`. Highlighting rides the
|
|
24
|
+
* decoration pipeline (read side); replace dispatches transactions (write side)
|
|
25
|
+
* — exercising both halves of the plugin contract. Matches are recomputed only
|
|
26
|
+
* when the query or the doc changes (cached for cheap per-scroll repaints).
|
|
27
|
+
*
|
|
28
|
+
* Matching is case-insensitive, within a single text node (cross-node matches
|
|
29
|
+
* are out of scope for v1). The host owns the find-bar UI.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findPlugin(): FindPlugin;
|
|
32
|
+
//# sourceMappingURL=find-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-plugin.d.ts","sourceRoot":"","sources":["../../src/lib/find-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,kCAAkC,CAAC;AAM1C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,IAAI,IAAI,CAAC;IACb,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,uEAAuE;IACvE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kEAAkE;IAClE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yEAAyE;IACzE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAuGvC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Internal plugin: **Ctrl/Cmd-click a hyperlink to open it** (the Word / Google
|
|
4
|
+
* Docs convention — a plain click still places the caret so links stay
|
|
5
|
+
* editable). Claims the press when a modified primary click lands on a `link`
|
|
6
|
+
* mark, so the editor skips caret placement and the URL opens in a new tab.
|
|
7
|
+
*
|
|
8
|
+
* Pairs with the export hyperlink round-trip (M6) and the accessible mirror
|
|
9
|
+
* (M10), whose `<a href>` elements expose the same links to assistive tech.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hyperlinkPlugin(): EditorPlugin;
|
|
12
|
+
//# sourceMappingURL=hyperlink-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hyperlink-plugin.d.ts","sourceRoot":"","sources":["../../src/lib/hyperlink-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,kCAAkC,CAAC;AAuBpF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAgB9C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
type Handle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
|
|
3
|
+
interface Rect {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
/** `point` mapped into the frame's unrotated local space (inverse-rotate
|
|
10
|
+
* around the rect center). Handle/knob hit-tests run in this space. */
|
|
11
|
+
export declare function toLocal(x: number, y: number, rect: Rect, rotation: number): {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
/** Resize cursor for a handle, accounting for the frame's rotation: each
|
|
16
|
+
* handle points at a compass angle (n = 0°, e = 90°, …); adding the rotation
|
|
17
|
+
* and quantizing to 45° picks among the four bidirectional resize cursors. */
|
|
18
|
+
export declare function cursorFor(handle: Handle, rotation: number): string;
|
|
19
|
+
/** Snap: within SNAP_TOL of a cardinal angle → that angle; with Shift → 15°
|
|
20
|
+
* steps. Result normalized to [0, 360). */
|
|
21
|
+
export declare function snapAngle(deg: number, shift: boolean): number;
|
|
22
|
+
/** New rect for dragging `handle` by (dx, dy) from `base`: the opposite
|
|
23
|
+
* edge/corner stays anchored; corners keep the aspect ratio (edges are free)
|
|
24
|
+
* unless Shift flips the mode; both dimensions clamp to MIN_SIZE. */
|
|
25
|
+
export declare function resizeRect(base: Rect, handle: Handle, dx: number, dy: number, shift: boolean): Rect;
|
|
26
|
+
/**
|
|
27
|
+
* Internal plugin: click an image to select it — a frame with 8 resize handles
|
|
28
|
+
* and a rotate knob appears (DOM overlay; the content canvas is untouched).
|
|
29
|
+
* Clicking elsewhere deselects. After a doc change the selection survives only
|
|
30
|
+
* if the same position still holds an image (a resize commit does; edits that
|
|
31
|
+
* shift positions drop it), and the frame re-anchors to the fresh layout.
|
|
32
|
+
*
|
|
33
|
+
* Resize / rotate drags land in later steps (M16 R3/R5); this step is the
|
|
34
|
+
* selection lifecycle the drags build on.
|
|
35
|
+
*/
|
|
36
|
+
export declare function imageResizePlugin(): EditorPlugin;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=image-resize-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-resize-plugin.d.ts","sourceRoot":"","sources":["../../src/lib/image-resize-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAMb,MAAM,kCAAkC,CAAC;AAkB1C,KAAK,MAAM,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAEhE,UAAU,IAAI;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAqBD;wEACwE;AACxE,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAQpG;AAED;;+EAE+E;AAC/E,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIlE;AAED;4CAC4C;AAC5C,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAU7D;AAiBD;;sEAEsE;AACtE,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAsBnG;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,YAAY,CAyNhD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Internal plugin: drag a table column's interior border to resize it. Hovering
|
|
4
|
+
* a border shows a `col-resize` cursor; during the drag only a vertical guide
|
|
5
|
+
* follows the cursor (no re-layout); on drop it rewrites just the two adjacent
|
|
6
|
+
* columns' `colwidth` (left +Δ, right −Δ) in one transaction — so the table's
|
|
7
|
+
* total width stays fixed and the change is a single undo step.
|
|
8
|
+
*
|
|
9
|
+
* Built on the editor's pointer hook + `ctx.layout`/`setCursor`/`setGuide` — no
|
|
10
|
+
* UI, no schema; geometry + one transaction.
|
|
11
|
+
*/
|
|
12
|
+
export declare function tableResizePlugin(): EditorPlugin;
|
|
13
|
+
//# sourceMappingURL=table-resize-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-resize-plugin.d.ts","sourceRoot":"","sources":["../../src/lib/table-resize-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAMb,MAAM,kCAAkC,CAAC;AAgE1C;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,YAAY,CA2EhD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { EditorPlugin } from '@shadow-garden/bapbong-contracts';
|
|
2
|
+
/** A selected cell + its position within the block grid (0-based). */
|
|
3
|
+
export interface SelectedCell {
|
|
4
|
+
pos: number;
|
|
5
|
+
row: number;
|
|
6
|
+
col: number;
|
|
7
|
+
}
|
|
8
|
+
/** The selected rectangular cell block as a grid. */
|
|
9
|
+
export interface CellBlock {
|
|
10
|
+
cells: SelectedCell[];
|
|
11
|
+
rows: number;
|
|
12
|
+
cols: number;
|
|
13
|
+
}
|
|
14
|
+
/** Richer handle the editor exposes as `editor.tableSelection`. */
|
|
15
|
+
export interface TableSelectionPlugin extends EditorPlugin {
|
|
16
|
+
/** Subscribe to the action trigger (icon tap / menu); receives the selected
|
|
17
|
+
* block (cells + grid size). Returns an unsubscribe. */
|
|
18
|
+
onAction(cb: (block: CellBlock) => void): () => void;
|
|
19
|
+
/** The current selected block, or null when there's no selection. */
|
|
20
|
+
block(): CellBlock | null;
|
|
21
|
+
/** Clear the block selection + its overlay. */
|
|
22
|
+
clear(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Internal plugin: drag across table cells to select a rectangular block. A
|
|
26
|
+
* drag within one cell stays text selection; crossing into another cell of the
|
|
27
|
+
* same table claims the drag, collapses the text selection, paints the block as
|
|
28
|
+
* a translucent highlight, and (on release) shows a small action button at the
|
|
29
|
+
* block's top-right — a touch-friendly trigger to open cell properties. The
|
|
30
|
+
* host subscribes via `onAction` and reads the selected block via `block()`.
|
|
31
|
+
*
|
|
32
|
+
* Pointer-event based (mouse/touch/pen). The selection is editor overlay state
|
|
33
|
+
* (bapbong's table schema has no ProseMirror CellSelection).
|
|
34
|
+
*/
|
|
35
|
+
export declare function tableSelectionPlugin(): TableSelectionPlugin;
|
|
36
|
+
//# sourceMappingURL=table-selection-plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-selection-plugin.d.ts","sourceRoot":"","sources":["../../src/lib/table-selection-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAQb,MAAM,kCAAkC,CAAC;AAK1C,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AACD,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD;6DACyD;IACzD,QAAQ,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACrD,qEAAqE;IACrE,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC;IAC1B,+CAA+C;IAC/C,KAAK,IAAI,IAAI,CAAC;CACf;AA8CD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,IAAI,oBAAoB,CA0G3D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shadow-garden/bapbong-editor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "bapbong — canvas-rendered DOCX editor for the browser (work in progress)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/shadowgarden-app/bapbong.git",
|
|
9
|
+
"directory": "packages/editor"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": "./package.json",
|
|
17
|
+
".": {
|
|
18
|
+
"@shadow-garden/source": "./src/index.ts",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"!**/*.tsbuildinfo"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"nx": {
|
|
33
|
+
"tags": [
|
|
34
|
+
"scope:app"
|
|
35
|
+
],
|
|
36
|
+
"targets": {
|
|
37
|
+
"build": {
|
|
38
|
+
"executor": "@nx/esbuild:esbuild",
|
|
39
|
+
"outputs": [
|
|
40
|
+
"{options.outputPath}"
|
|
41
|
+
],
|
|
42
|
+
"options": {
|
|
43
|
+
"outputPath": "packages/editor/dist",
|
|
44
|
+
"main": "packages/editor/src/index.ts",
|
|
45
|
+
"tsConfig": "packages/editor/tsconfig.lib.json",
|
|
46
|
+
"format": [
|
|
47
|
+
"esm",
|
|
48
|
+
"cjs"
|
|
49
|
+
],
|
|
50
|
+
"declarationRootDir": "packages/editor/src",
|
|
51
|
+
"generatePackageJson": false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"prosemirror-model": "^1.25.7",
|
|
58
|
+
"@shadow-garden/bapbong-commands": "^0.1.0",
|
|
59
|
+
"@shadow-garden/bapbong-contracts": "^0.1.0",
|
|
60
|
+
"@shadow-garden/bapbong-selection": "^0.1.0",
|
|
61
|
+
"@shadow-garden/bapbong-model": "^0.1.0",
|
|
62
|
+
"@shadow-garden/bapbong-input-bridge": "^0.1.0",
|
|
63
|
+
"@shadow-garden/bapbong-view": "^0.1.0"
|
|
64
|
+
}
|
|
65
|
+
}
|