@sobree/core 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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
  4. package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
  5. package/dist/blob/cache.d.ts +69 -0
  6. package/dist/blob/fetch.d.ts +18 -0
  7. package/dist/blob/hash.d.ts +13 -0
  8. package/dist/blob/index.d.ts +33 -0
  9. package/dist/blob/memory.d.ts +2 -0
  10. package/dist/blob/types.d.ts +80 -0
  11. package/dist/createSobree.d.ts +132 -0
  12. package/dist/doc/api.d.ts +132 -0
  13. package/dist/doc/builders.d.ts +42 -0
  14. package/dist/doc/pageSetupBridge.d.ts +26 -0
  15. package/dist/doc/parts.d.ts +18 -0
  16. package/dist/doc/runs.d.ts +47 -0
  17. package/dist/doc/styles.d.ts +19 -0
  18. package/dist/doc/types.d.ts +800 -0
  19. package/dist/doc/walk.d.ts +30 -0
  20. package/dist/docx/export/contentTypes.d.ts +35 -0
  21. package/dist/docx/export/context.d.ts +59 -0
  22. package/dist/docx/export/document.d.ts +19 -0
  23. package/dist/docx/export/drawings.d.ts +10 -0
  24. package/dist/docx/export/headers.d.ts +19 -0
  25. package/dist/docx/export/index.d.ts +14 -0
  26. package/dist/docx/export/runs.d.ts +8 -0
  27. package/dist/docx/export/styles.d.ts +8 -0
  28. package/dist/docx/export/zip.d.ts +13 -0
  29. package/dist/docx/import/anchoredFrames.d.ts +34 -0
  30. package/dist/docx/import/comments.d.ts +3 -0
  31. package/dist/docx/import/document.d.ts +57 -0
  32. package/dist/docx/import/flowFrames.d.ts +11 -0
  33. package/dist/docx/import/footnotes.d.ts +3 -0
  34. package/dist/docx/import/headers.d.ts +50 -0
  35. package/dist/docx/import/index.d.ts +12 -0
  36. package/dist/docx/import/inlineFrames.d.ts +62 -0
  37. package/dist/docx/import/numbering.d.ts +2 -0
  38. package/dist/docx/import/paragraph.d.ts +24 -0
  39. package/dist/docx/import/paragraphs.d.ts +27 -0
  40. package/dist/docx/import/rels.d.ts +5 -0
  41. package/dist/docx/import/runs.d.ts +64 -0
  42. package/dist/docx/import/settings.d.ts +48 -0
  43. package/dist/docx/import/styles.d.ts +3 -0
  44. package/dist/docx/import/tables.d.ts +12 -0
  45. package/dist/docx/import/unzip.d.ts +13 -0
  46. package/dist/docx/shared/namespaces.d.ts +31 -0
  47. package/dist/docx/shared/pageSize.d.ts +27 -0
  48. package/dist/docx/shared/shading.d.ts +2 -0
  49. package/dist/docx/shared/units.d.ts +35 -0
  50. package/dist/docx/shared/xml.d.ts +29 -0
  51. package/dist/docx/types.d.ts +98 -0
  52. package/dist/editor/index.d.ts +1078 -0
  53. package/dist/editor/internal/blockRegistry.d.ts +91 -0
  54. package/dist/editor/internal/mutations.d.ts +63 -0
  55. package/dist/editor/internal/positionMap.d.ts +35 -0
  56. package/dist/editor/table.d.ts +96 -0
  57. package/dist/editor/view/docRenderer/anchorLayer.d.ts +26 -0
  58. package/dist/editor/view/docRenderer/block.d.ts +13 -0
  59. package/dist/editor/view/docRenderer/fontFallback.d.ts +28 -0
  60. package/dist/editor/view/docRenderer/index.d.ts +18 -0
  61. package/dist/editor/view/docRenderer/inline.d.ts +15 -0
  62. package/dist/editor/view/docRenderer/inlineFrame.d.ts +4 -0
  63. package/dist/editor/view/docRenderer/lists.d.ts +28 -0
  64. package/dist/editor/view/docRenderer/paragraph.d.ts +2 -0
  65. package/dist/editor/view/docRenderer/properties.d.ts +2 -0
  66. package/dist/editor/view/docRenderer/table.d.ts +15 -0
  67. package/dist/editor/view/docRenderer/units.d.ts +48 -0
  68. package/dist/editor/view/docSerialize/block.d.ts +14 -0
  69. package/dist/editor/view/docSerialize/index.d.ts +8 -0
  70. package/dist/editor/view/docSerialize/inline.d.ts +11 -0
  71. package/dist/editor/view/docSerialize/table.d.ts +12 -0
  72. package/dist/editor/view/imageResize.d.ts +16 -0
  73. package/dist/embed/floatingCorner.d.ts +44 -0
  74. package/dist/embed/viewport.d.ts +133 -0
  75. package/dist/fonts/embedAPI.d.ts +33 -0
  76. package/dist/fonts/emit.d.ts +24 -0
  77. package/dist/fonts/fontFaceRegistry.d.ts +20 -0
  78. package/dist/fonts/fsType.d.ts +36 -0
  79. package/dist/fonts/index.d.ts +19 -0
  80. package/dist/fonts/liveness.d.ts +2 -0
  81. package/dist/fonts/odttf.d.ts +33 -0
  82. package/dist/fonts/parse.d.ts +29 -0
  83. package/dist/fonts/types.d.ts +52 -0
  84. package/dist/headless.d.ts +168 -0
  85. package/dist/history/history.d.ts +100 -0
  86. package/dist/history/index.d.ts +4 -0
  87. package/dist/history/types.d.ts +54 -0
  88. package/dist/index.css +1 -0
  89. package/dist/index.d.ts +52 -0
  90. package/dist/index.js +10561 -0
  91. package/dist/index.js.map +1 -0
  92. package/dist/markdown/parse.d.ts +6 -0
  93. package/dist/pagination/cost.d.ts +32 -0
  94. package/dist/pagination/index.d.ts +2 -0
  95. package/dist/pagination/paginate.d.ts +10 -0
  96. package/dist/pagination/postConditions.d.ts +10 -0
  97. package/dist/pagination/types.d.ts +94 -0
  98. package/dist/paperStack/pageSetup.d.ts +42 -0
  99. package/dist/paperStack/paginationAdapter/buildItems.d.ts +19 -0
  100. package/dist/paperStack/paginationAdapter/distribute.d.ts +23 -0
  101. package/dist/paperStack/paginationAdapter/index.d.ts +18 -0
  102. package/dist/paperStack/paginationAdapter/paragraphLines.d.ts +23 -0
  103. package/dist/paperStack/paginationAdapter/splitList.d.ts +19 -0
  104. package/dist/paperStack/paginationAdapter/splitParagraph.d.ts +21 -0
  105. package/dist/paperStack/paginationAdapter/types.d.ts +30 -0
  106. package/dist/paperStack/paper.d.ts +107 -0
  107. package/dist/paperStack/paperStack.d.ts +245 -0
  108. package/dist/plugin.d.ts +24 -0
  109. package/dist/plugins/marks.d.ts +49 -0
  110. package/dist/plugins/sections.d.ts +15 -0
  111. package/dist/presence/attach.d.ts +48 -0
  112. package/dist/presence/awareness.d.ts +28 -0
  113. package/dist/presence/index.d.ts +19 -0
  114. package/dist/presence/overlay.d.ts +28 -0
  115. package/dist/presence/state.d.ts +36 -0
  116. package/dist/sobree.d.ts +211 -0
  117. package/dist/tokens.css +144 -0
  118. package/dist/util/selection.d.ts +13 -0
  119. package/dist/ydoc/apply.d.ts +68 -0
  120. package/dist/ydoc/index.d.ts +18 -0
  121. package/dist/ydoc/project.d.ts +41 -0
  122. package/dist/ydoc/runs.d.ts +51 -0
  123. package/dist/ydoc/schema.d.ts +123 -0
  124. package/dist/ydoc/seed.d.ts +45 -0
  125. package/dist/ydoc/textDiff.d.ts +59 -0
  126. package/dist/zoneEdit/index.d.ts +22 -0
  127. package/package.json +61 -0
@@ -0,0 +1,245 @@
1
+ import { PageSetup } from './pageSetup';
2
+ import { AnchoredFrame, Block, NamedStyle, NumberingDefinition, SectionProperties } from '../doc/types';
3
+ /**
4
+ * Sourced from `SobreeDocument`: the rich AST + the dependencies
5
+ * `renderBlocks` needs (numbering, named styles, embedded media bytes).
6
+ * When set, every page renders headers/footers from this AST and
7
+ * ignores the `PageSetup.header.default` / `footer.default` string
8
+ * templates. Cleared (set back to `null`) when the doc has no
9
+ * header/footer parts — falls back to the legacy text path.
10
+ */
11
+ export interface RichZonesSource {
12
+ headerFooterBodies: Record<string, Block[]>;
13
+ numbering: readonly NumberingDefinition[];
14
+ styles: readonly NamedStyle[];
15
+ rawParts: Record<string, Uint8Array>;
16
+ }
17
+ /**
18
+ * Stack of Paper elements. The stack's root is the single contentEditable
19
+ * region; each paper's header/footer is contentEditable=false. Blocks of
20
+ * editor content are physically distributed across the papers'
21
+ * `.paper-content` elements. Editing naturally crosses page boundaries
22
+ * because the whole stack is one editable region.
23
+ *
24
+ * Call `repaginate()` after content changes or after page setup changes.
25
+ */
26
+ export declare class PaperStack {
27
+ readonly root: HTMLElement;
28
+ private papers;
29
+ private setup;
30
+ /**
31
+ * Layout-side zoom tier currently applied to an ancestor (via the Viewport's
32
+ * CSS `zoom`). Measurements from offsetHeight/offsetTop come back in zoomed
33
+ * pixels, so the page budget must scale the same way.
34
+ */
35
+ private renderTier;
36
+ /** Optional observer fired after every successful repagination. */
37
+ private paginateListeners;
38
+ /**
39
+ * Per-section property overrides. When set, each Paper applies the
40
+ * section's settings (currently just `vAlign`) based on the
41
+ * `data-section-index` stamped on its first block. Sobree provides
42
+ * this from the live document; absent → all pages use `setup`.
43
+ */
44
+ private sections;
45
+ /**
46
+ * Optional rich-AST source for header/footer rendering. When present,
47
+ * each paper's header/footer is rendered from the matching part body
48
+ * (resolved through `sections[i].headerRefs` / `footerRefs`) instead
49
+ * of from the `PageSetup` string template. Set via `setRichZones`.
50
+ */
51
+ private richZones;
52
+ /**
53
+ * Document's floating-layer frames (the new architecture replacing
54
+ * the lifter + framePictures hacks). When set, each paper is painted
55
+ * with the subset of frames whose anchor resolves to that page;
56
+ * paper.setAnchoredFrames does the per-page filtering and DOM swap.
57
+ * `null` → no floating layer at all (skeleton state during Phase B).
58
+ */
59
+ private anchoredFrames;
60
+ /** Reused blob-URL cache across renders so the same image isn't re-uploaded. */
61
+ private readonly anchorPictureUrlCache;
62
+ constructor(container: HTMLElement, setup: PageSetup);
63
+ /**
64
+ * Provide the document's sections for per-page property application.
65
+ * Re-applied immediately so a setSections call without a content
66
+ * change still updates papers (e.g. user edits a section's vAlign in
67
+ * the future Page Setup section selector).
68
+ */
69
+ setSections(sections: readonly SectionProperties[]): void;
70
+ /**
71
+ * Install or clear the rich AST source for header/footer rendering.
72
+ * Re-renders zones immediately so the swap is visible without waiting
73
+ * for the next paginate. Pass `null` to revert to the legacy text-
74
+ * template path (no rich body available).
75
+ */
76
+ setRichZones(source: RichZonesSource | null): void;
77
+ /**
78
+ * Install or clear the document's floating-layer frames. Each frame's
79
+ * `anchor.paragraphIndex` (when set) decides which page receives it
80
+ * after the next repaginate; frames without a paragraph index land
81
+ * on the first page of their section. Pass `null` to clear.
82
+ *
83
+ * Call after `setRichZones`/`updateBodyBlocks` so the next render
84
+ * picks up the new floating content alongside body flow.
85
+ */
86
+ setAnchoredFrames(frames: readonly AnchoredFrame[] | null): void;
87
+ getSetup(): PageSetup;
88
+ getPageCount(): number;
89
+ onPaginate(cb: (pageCount: number) => void): () => void;
90
+ /** First paper's content — initial render target for a new editor. */
91
+ get primaryContent(): HTMLElement;
92
+ /** First paper card — for viewport fit-to-page calculations. */
93
+ get firstPaper(): HTMLElement;
94
+ /** First paper's row wrapper (paper + comments sidebar) — for fit-to-width. */
95
+ get firstPaperRow(): HTMLElement;
96
+ /** All content hosts in document order — for serialization. */
97
+ get contentHosts(): HTMLElement[];
98
+ updateSetup(setup: PageSetup): void;
99
+ /**
100
+ * Called by the Viewport when the layout-zoom tier changes. In Chromium,
101
+ * CSS `zoom` doesn't change `offsetHeight`/`offsetTop`, so measurements
102
+ * stay logical and the page budget is unaffected. We still re-paginate
103
+ * defensively — tier changes re-render text at a new resolution, which
104
+ * can nudge line wrapping at sub-pixel boundaries.
105
+ */
106
+ setRenderTier(tier: number): void;
107
+ /**
108
+ * Redistribute blocks across papers so no paper overflows. Creates or
109
+ * removes papers as needed.
110
+ *
111
+ * Delegates to the pure paginator in `src/pagination/` via the DOM
112
+ * adapter. Before paginating, blocks are **consolidated** into the first
113
+ * paper's content so their `offsetTop` values share one coordinate system,
114
+ * and consecutive `<p>` fragments sharing a `data-pag-pid` (previous-split
115
+ * siblings of the same logical paragraph) are **merged** back into a
116
+ * single paragraph — so each repagination starts from the clean logical
117
+ * flow instead of accumulating inter-fragment margins.
118
+ */
119
+ repaginate(): void;
120
+ /**
121
+ * Per-page footnote pinning. After body pagination completes, scan
122
+ * each paper for footnote references (`<sup class="sobree-footnote-ref">`)
123
+ * and populate that paper's `.paper-footnotes` zone with the cited
124
+ * bodies — matching Word's "footnote bodies render at the bottom of
125
+ * the page where they're referenced" behaviour.
126
+ *
127
+ * Footnote bodies are sourced from two places:
128
+ * 1. The doc-end `<aside class="sobree-footnotes">` the renderer
129
+ * initially appends to paper[0].content. We harvest the `<li>`s
130
+ * out of it before the aside itself gets re-paginated as a block.
131
+ * 2. Any footnote zones already populated from a previous repaginate
132
+ * pass — those bodies stay in scope across iterations.
133
+ *
134
+ * Caveat: body budget is NOT reduced for footnote-zone height. On
135
+ * pages with many footnotes, the footnote zone may extend past the
136
+ * page bottom into the footer area. True budget-reservation is its
137
+ * own paginator feature.
138
+ */
139
+ private distributeFootnotes;
140
+ /**
141
+ * One round of consolidate → merge → paginate → distribute.
142
+ * Re-entrant: each call re-collects blocks from every paper, so the
143
+ * iterative loop in `repaginate` can call this multiple times with
144
+ * shrinking budgets to absorb post-split slippage.
145
+ */
146
+ private runPaginationOnce;
147
+ /**
148
+ * Build a `pageHeights[]` array from observed footnote-zone heights.
149
+ * Entry `i` = `baselineBudgetPx - footnoteZoneHeight(i)`. Pages
150
+ * without footnotes get the full baseline. Returned array trims
151
+ * trailing entries equal to the baseline, so consumers can detect
152
+ * "no per-page overrides needed" via `length === 0`.
153
+ */
154
+ private computePageHeights;
155
+ /**
156
+ * Largest content overflow across all papers, in CSS px. Zero means
157
+ * every paper fits within its content area. Used by the iterative
158
+ * `repaginate` to detect post-split slippage that needs another
159
+ * pagination pass with a tighter budget.
160
+ */
161
+ private maxPaperOverflowPx;
162
+ /**
163
+ * For every Paper, look at its first content block's `data-section-index`
164
+ * and apply that section's overrides. Falls back silently when no
165
+ * sections were provided or a paper is empty.
166
+ */
167
+ private applyPerSectionSettings;
168
+ private emitPaginate;
169
+ destroy(): void;
170
+ private pageContentHeightPx;
171
+ private collectAllBlocks;
172
+ private ensurePaperCount;
173
+ private renderAllZones;
174
+ /**
175
+ * Filter the document's `anchoredFrames` per paper and ask each Paper
176
+ * to swap its anchor-layer DOM accordingly. A frame's destination
177
+ * page is determined by its anchor:
178
+ * - `paragraphIndex` set → page that ended up containing that
179
+ * body block (looked up by `data-block-index` stamped on
180
+ * rendered children).
181
+ * - paragraphIndex absent → first paper of the frame's section.
182
+ *
183
+ * Cheap and idempotent; safe to call after any layout change.
184
+ */
185
+ private paintAnchorLayers;
186
+ /**
187
+ * Walk every paper's content children once and record, for each
188
+ * `data-block-index="N"` element, the paper index that holds it.
189
+ * The renderer stamps that attribute when emitting body paragraphs;
190
+ * this is the only piece of mutual knowledge between body flow and
191
+ * the floating layer needed for paragraph anchoring.
192
+ */
193
+ private buildParagraphPageIndex;
194
+ /**
195
+ * Section index a paper belongs to. Read off the first content
196
+ * block's `data-section-index` (stamped by `renderBlocks`). Empty
197
+ * papers and the no-sections case fall back to 0.
198
+ */
199
+ private sectionIndexForPage;
200
+ /**
201
+ * True when this paper is the first page of its section. Used to
202
+ * select the `first`-typed header/footer ref when the section has
203
+ * `titlePage = true`.
204
+ */
205
+ private isFirstPaperOfSection;
206
+ /**
207
+ * Resolve which header/footer Block[] applies to a paper. Walks the
208
+ * section's refs by type and looks up the corresponding part body in
209
+ * `richZones.headerFooterBodies`. Returns `null` when no rich body
210
+ * applies (no refs, or `richZones` not set).
211
+ */
212
+ private pickRichZone;
213
+ }
214
+ /**
215
+ * Walk the paginator's output back-to-front, collapsing trailing pages
216
+ * whose blocks are all visually empty into the previous page.
217
+ *
218
+ * "Visually empty" = an empty paragraph: a `<p>` (or list-item `<li>`)
219
+ * with no text content and no embedded image / table / break. We keep
220
+ * the empty blocks (round-trip fidelity demands every paragraph mark
221
+ * stays in the AST) — we just stop reserving a fresh page for them.
222
+ *
223
+ * Stops collapsing the moment a page contains any non-empty block, so
224
+ * a real "last page with just a signature line" still gets its own
225
+ * page. Idempotent; safe to call on a single-page result.
226
+ */
227
+ /**
228
+ * Walk paginator output forward, absorbing pages whose total content
229
+ * height is ≤ 15% of `budgetPx` (the page-content budget) into the
230
+ * previous page. These tiny tail-pages are paginator widows — usually
231
+ * a 1-line overflow or an LRPB-induced sub-section that didn't have
232
+ * room. Both Word and LO tolerate a bit of bottom-margin spill in
233
+ * exchange for not spending an entire fresh page on the runt.
234
+ *
235
+ * Safety: only absorbs pages whose content height is smaller than the
236
+ * previous page's slack PLUS the widow tolerance (~20% of budget).
237
+ * If absorbing would visibly push content far past the page bottom,
238
+ * we leave the runt page alone — better an underfilled page than
239
+ * unreadable overflow.
240
+ *
241
+ * Run AFTER `collapseTrailingEmptyPages` so trailing whitespace is
242
+ * already merged and we're working on the real content layout.
243
+ */
244
+ export declare function collapseUnderfilledPages(pages: readonly HTMLElement[][], budgetPx: number): HTMLElement[][];
245
+ export declare function collapseTrailingEmptyPages(pages: readonly HTMLElement[][]): HTMLElement[][];
@@ -0,0 +1,24 @@
1
+ import { Editor } from './editor';
2
+ import { Viewport } from './embed/viewport';
3
+ import { Sobree } from './sobree';
4
+ export interface PluginContext {
5
+ /** The framework-free editor kernel. */
6
+ editor: Editor;
7
+ /** The Sobree façade — paper stack, page setup, mode, headers/footers. */
8
+ sobree: Sobree;
9
+ /** The viewport — pannable / zoomable stage. */
10
+ viewport: Viewport;
11
+ /** The element `createSobree()` was mounted into. */
12
+ host: HTMLElement;
13
+ }
14
+ export interface SobreePluginInstance {
15
+ /** Tear down everything `setup` allocated. Called on createSobree's
16
+ * destroy, in reverse-of-setup order. */
17
+ destroy(): void;
18
+ }
19
+ export interface SobreePlugin {
20
+ /** Diagnostic name surfaced in setup-failure logs. Optional. */
21
+ name?: string;
22
+ /** Mount the plugin against the editor surface. Returns a destroyer. */
23
+ setup(ctx: PluginContext): SobreePluginInstance;
24
+ }
@@ -0,0 +1,49 @@
1
+ import { Range as ApiRange } from '../doc/api';
2
+ import { RunProperties } from '../doc/types';
3
+ import { Editor, WrapTag } from '../editor';
4
+ /**
5
+ * Mark toggle helpers — shared by the floating toolbar's mark buttons
6
+ * and the keyboard plugin (Ctrl+B / I / U / …).
7
+ *
8
+ * "Mark" = a boolean / enum run property that maps 1:1 to a `wrapRange`
9
+ * tag: `strong`, `em`, `u`, `s`, `sup`, `sub`. (`mark`/highlight is
10
+ * passthrough — not a toggle, since highlight is colour-valued.)
11
+ */
12
+ /** Tag → run property key for toggle detection. */
13
+ export declare const MARK_PROP: Record<string, keyof RunProperties>;
14
+ /** Expected "on" value for each mark. */
15
+ export declare const MARK_ON: Record<string, RunProperties[keyof RunProperties]>;
16
+ export type ToggleableMark = "strong" | "em" | "u" | "s" | "sup" | "sub";
17
+ /**
18
+ * Standard mark commands registered on every Editor's command bus.
19
+ * Owned by the marks module so a single source of truth backs the
20
+ * keyboard plugin (`@sobree/keyboard`), the floating toolbar
21
+ * (`@sobree/block-tools`), and any custom UI / agent dispatch.
22
+ */
23
+ export interface MarkCommandDef {
24
+ name: string;
25
+ title: string;
26
+ tag: ToggleableMark;
27
+ }
28
+ export declare const MARK_COMMAND_DEFS: readonly MarkCommandDef[];
29
+ /**
30
+ * Apply or clear a mark across `range` based on its current state. If
31
+ * every text run in the range already carries the mark's "on" value,
32
+ * the call clears it; otherwise the mark is applied.
33
+ *
34
+ * `mark` (highlight) is delegated straight to `wrapRange` — it's not a
35
+ * toggle since highlight is a colour, not a boolean.
36
+ */
37
+ export declare function toggleMark(editor: Editor, range: ApiRange, tag: WrapTag): void;
38
+ /**
39
+ * True when every non-empty text run inside `range` has the mark's "on"
40
+ * value. Walks hyperlink children. Multi-block ranges always read as
41
+ * inactive so a click sets the mark before clearing it.
42
+ */
43
+ export declare function isMarkActive(editor: Editor, range: ApiRange, tag: string): boolean;
44
+ /**
45
+ * Pick the right range to operate on: live DOM selection if any,
46
+ * otherwise the full extent of the block at the caret. Matches the
47
+ * "press Ctrl+B with caret only → bold the whole paragraph" UX.
48
+ */
49
+ export declare function rangeAtSelection(editor: Editor): ApiRange | null;
@@ -0,0 +1,15 @@
1
+ import { Editor } from '../editor';
2
+ /**
3
+ * Section commands plugin.
4
+ *
5
+ * Registers `section.insertBreakAfter` — inserts a `SectionBreak` block
6
+ * after the caret's current block. The new section inherits the
7
+ * previous section's properties (cloned so later edits don't bleed
8
+ * back) so visual continuity is preserved until the user edits the
9
+ * new section in Page Setup.
10
+ *
11
+ * The keyboard plugin's Ctrl+Shift+Enter dispatches this command, the
12
+ * change-type popover does too, and a future Insert menu would as well
13
+ * — same dispatch path for every trigger.
14
+ */
15
+ export declare function attachSections(editor: Editor): () => void;
@@ -0,0 +1,48 @@
1
+ import { Editor } from '../editor';
2
+ import { AwarenessLike } from './awareness';
3
+ import { PresenceState, PresenceUser } from './state';
4
+ export interface AttachPresenceOptions {
5
+ /** This peer's identity. Published in the local state's `user` field. */
6
+ user: PresenceUser;
7
+ /**
8
+ * Called whenever any peer's presence (including this one) changes.
9
+ * The map is `clientID → PresenceState` for every peer currently
10
+ * publishing valid Sobree presence. The local peer's clientID
11
+ * (matches `awareness.clientID` / `editor.ydoc.clientID`) is
12
+ * included so callers can render their own caret too if they want.
13
+ */
14
+ onChange?: (peers: Map<number, PresenceState>) => void;
15
+ /**
16
+ * Default to `true` — publish the local user's selection on every
17
+ * `editor.on("selection")` event. Pass `false` if the caller wants
18
+ * to manage own-selection publishing manually.
19
+ */
20
+ publishOwnSelection?: boolean;
21
+ }
22
+ export interface PresenceHandle {
23
+ /** Snapshot of every peer's current state, keyed by clientID. */
24
+ getPeers(): Map<number, PresenceState>;
25
+ /**
26
+ * Push a manual state update. Useful when the local user changes
27
+ * name / color, or to explicitly clear the selection on focus loss.
28
+ */
29
+ setLocalState(state: Partial<PresenceState>): void;
30
+ /** Tear down: unsubscribe from awareness + editor events. Clears
31
+ * the local published state so peers see the user leave. */
32
+ destroy(): void;
33
+ }
34
+ /**
35
+ * Wire an `Awareness` instance into the editor.
36
+ *
37
+ * - Publishes the local user's `user` + `selection` state on every
38
+ * selection change.
39
+ * - Subscribes to remote peers' state via awareness `"change"`
40
+ * events; surfaces them via `onChange(peers)` and `getPeers()`.
41
+ *
42
+ * Does NOT render an overlay — pass the peers to your own renderer,
43
+ * or wire `attachPresenceOverlay` from this package for the default
44
+ * caret + range-highlight rendering.
45
+ *
46
+ * Returns a `PresenceHandle` with manual-update and teardown.
47
+ */
48
+ export declare function attachPresence(editor: Editor, awareness: AwarenessLike, opts: AttachPresenceOptions): PresenceHandle;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Minimal awareness interface — a structural subset of
3
+ * `y-protocols/awareness`'s `Awareness` class. We don't depend on
4
+ * `y-protocols` from `@sobree/core` (it's an optional concern); the
5
+ * interface here is structurally compatible so an `Awareness`
6
+ * instance passes typecheck without any glue.
7
+ *
8
+ * The shape:
9
+ *
10
+ * - `clientID` — the local peer's id (matches `ydoc.clientID`)
11
+ * - `setLocalState(state | null)` — publish your own state
12
+ * - `setLocalStateField(field, value)` — patch one field
13
+ * - `getStates()` — read every peer's state as `Map<clientID, state>`
14
+ * - `on("change", cb) / off("change", cb)` — subscribe to changes
15
+ */
16
+ export interface AwarenessLike {
17
+ readonly clientID: number;
18
+ setLocalState(state: Record<string, unknown> | null): void;
19
+ setLocalStateField(field: string, value: unknown): void;
20
+ getStates(): Map<number, Record<string, unknown>>;
21
+ on(event: "change" | "update", cb: (changes: AwarenessChanges, origin: unknown) => void): void;
22
+ off(event: "change" | "update", cb: (changes: AwarenessChanges, origin: unknown) => void): void;
23
+ }
24
+ export interface AwarenessChanges {
25
+ added: number[];
26
+ updated: number[];
27
+ removed: number[];
28
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Presence — remote peer cursors and selection highlights.
3
+ *
4
+ * Builds on the Y.Doc backing in `@sobree/core`. Pass an `Awareness`
5
+ * instance (from `y-protocols/awareness`, surfaced via
6
+ * `@sobree/collab-providers` handles) and a user identity; pluck
7
+ * remote peers via `getPeers()` or render the default overlay.
8
+ *
9
+ * See `concepts/architecture` ("Y.Doc store") for the bigger picture
10
+ * and `api/create-sobree` ("Y.Doc + collaboration") for the
11
+ * multi-phase roadmap.
12
+ */
13
+ export { attachPresence } from './attach';
14
+ export type { AttachPresenceOptions, PresenceHandle, } from './attach';
15
+ export { attachPresenceOverlay } from './overlay';
16
+ export type { AttachPresenceOverlayOptions, PresenceOverlayHandle, } from './overlay';
17
+ export { isPresenceState, presenceSelectionFromEditor, } from './state';
18
+ export type { PresenceState, PresenceSelection, PresenceUser, } from './state';
19
+ export type { AwarenessLike, AwarenessChanges } from './awareness';
@@ -0,0 +1,28 @@
1
+ import { Editor } from '../editor';
2
+ import { AwarenessLike } from './awareness';
3
+ import { PresenceHandle, AttachPresenceOptions } from './attach';
4
+ export interface AttachPresenceOverlayOptions extends AttachPresenceOptions {
5
+ /**
6
+ * Element to render the overlay into. Should be an ancestor of the
7
+ * paper stack and `position: relative` (or `position: absolute`).
8
+ * The factory adds `position: relative` if `static`. Most embedders
9
+ * pass `createSobree(...).viewport.slot`.
10
+ */
11
+ container: HTMLElement;
12
+ /**
13
+ * Element whose `data-block-id` lookup resolves to block DOM
14
+ * elements. Default: `container`. Pass a different host if your
15
+ * paper stack is layered separately.
16
+ */
17
+ blockHost?: HTMLElement;
18
+ }
19
+ export interface PresenceOverlayHandle {
20
+ /** Underlying presence handle — read peers, push manual updates. */
21
+ readonly presence: PresenceHandle;
22
+ /** Force re-render (e.g. after a layout-affecting change the
23
+ * overlay didn't observe directly). */
24
+ refresh(): void;
25
+ /** Tear down DOM + listeners. */
26
+ destroy(): void;
27
+ }
28
+ export declare function attachPresenceOverlay(editor: Editor, awareness: AwarenessLike, opts: AttachPresenceOverlayOptions): PresenceOverlayHandle;
@@ -0,0 +1,36 @@
1
+ import { Selection as EditorSelection } from '../doc/api';
2
+ /** Per-peer state published to other peers via Yjs awareness. */
3
+ export interface PresenceState {
4
+ user: PresenceUser;
5
+ /**
6
+ * Where this peer's cursor / range sits. `null` if focus left the
7
+ * editor. Position is identified by `blockId` (stable across the
8
+ * Y.Doc — see `BlockRegistry`) + character offsets, so it survives
9
+ * re-pagination and structural mutations.
10
+ */
11
+ selection: PresenceSelection | null;
12
+ }
13
+ export interface PresenceUser {
14
+ /** Stable peer id (a UUID, an auth user id, etc). Random per page-load
15
+ * is fine if you don't have auth. */
16
+ id: string;
17
+ /** Display name for overlays / tooltips. */
18
+ name: string;
19
+ /** CSS color for the caret / range highlight (e.g. "#f59e0b"). */
20
+ color: string;
21
+ }
22
+ export interface PresenceSelection {
23
+ /** BlockRegistry id of the focused block. Both `anchor` and `focus`
24
+ * point inside this block (cross-block selections are clipped to
25
+ * the focus block for the overlay). */
26
+ blockId: string;
27
+ /** Caret position when anchor === focus; range start otherwise. */
28
+ anchor: number;
29
+ /** Caret position when anchor === focus; range end otherwise. */
30
+ focus: number;
31
+ }
32
+ /** Convenience view: the editor's live `Selection` collapsed to a
33
+ * `PresenceSelection` referencing the focused block by id. */
34
+ export declare function presenceSelectionFromEditor(sel: EditorSelection): PresenceSelection | null;
35
+ /** Type guard — narrows an unknown awareness state value to PresenceState. */
36
+ export declare function isPresenceState(value: unknown): value is PresenceState;