@sofereditor/react 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,32 @@
1
- import { RefObject, ReactNode, JSX } from 'react';
2
- import { EditorDocument, EditorHistory, SerializedDocument, Selection, MarkName, MarkAttrs, BlockType, BlockAttrs, ListKind, PageSettings, TableLocation, TableRect, ImageEmbed, SerializedBlock, DeltaOp } from '@sofereditor/core';
1
+ import { RefObject, JSX, ReactNode } from 'react';
2
+ import { EditorDocument, EditorHistory, SerializedDocument, Selection, MarkName, MarkAttrs, BlockType, BlockAttrs, AlignValue, ListKind, PageSettings, TableLocation, TableRect, ImageEmbed, EmbedLoc, SerializedBlock, DeltaOp, Position } from '@sofereditor/core';
3
+
4
+ /**
5
+ * Subconjunto mínimo da Awareness do y-protocols que o overlay usa. Tipar assim
6
+ * evita que `@sofereditor/react` dependa de `y-protocols` — o objeto vem de fora
7
+ * (de `useCollab().binding.awareness`) e é estruturalmente compatível.
8
+ */
9
+ interface AwarenessLike {
10
+ clientID: number;
11
+ getStates(): Map<number, Record<string, unknown>>;
12
+ on(event: "change", cb: () => void): void;
13
+ off(event: "change", cb: () => void): void;
14
+ setLocalStateField(field: string, value: unknown): void;
15
+ }
16
+ interface RemoteCursorsOverlayProps {
17
+ rootRef: RefObject<HTMLDivElement>;
18
+ doc: EditorDocument;
19
+ awareness: AwarenessLike;
20
+ /** Muda a cada edição/repaginação → re-medir os carets remotos. */
21
+ revision?: unknown;
22
+ }
23
+ /**
24
+ * Desenha o caret + seleção + nome de cada usuário remoto sobre o conteúdo
25
+ * paginado. Posições remotas chegam via awareness (campo `cursor`, codificado
26
+ * com Y.RelativePosition pelo core) e são resolvidas para coordenadas DOM com
27
+ * o mesmo bridge da seleção local — ciente de fragmentação por página.
28
+ */
29
+ declare function RemoteCursorsOverlay({ rootRef, doc, awareness, revision, }: RemoteCursorsOverlayProps): JSX.Element | null;
3
30
 
4
31
  /**
5
32
  * Sub-phase 3.2 — page layout with intra-paragraph fragmentation.
@@ -27,9 +54,16 @@ interface PageGeometry {
27
54
  marginBottom: number;
28
55
  marginLeft: number;
29
56
  marginRight: number;
57
+ /**
58
+ * Espaço adicional somado ao marginTop só na 1ª página. Útil para clientes
59
+ * que rendem um header fixo (timbrado, identificação) na pág 1 via
60
+ * `renderPageHeader` e não querem que esse espaço seja reservado nas demais.
61
+ * Quando omitido, todas as páginas têm a mesma `contentHeight`.
62
+ */
63
+ firstPageExtraTop?: number;
30
64
  }
31
65
  declare const A4_PAGE: PageGeometry;
32
- declare function contentHeight(g: PageGeometry): number;
66
+ declare function contentHeight(g: PageGeometry, pageIndex?: number): number;
33
67
  declare function contentWidth(g: PageGeometry): number;
34
68
  interface BlockFragment {
35
69
  index: number;
@@ -44,12 +78,22 @@ interface TableRowFragment {
44
78
  /** First row NOT included (exclusive). */
45
79
  rowEnd: number;
46
80
  }
81
+ interface ListItemRangeFragment {
82
+ /** 0, 1, 2... sequential fragment id within the list group. */
83
+ index: number;
84
+ /** First top-level item (level 0) included (inclusive). */
85
+ itemStart: number;
86
+ /** First top-level item (level 0) NOT included (exclusive). */
87
+ itemEnd: number;
88
+ }
47
89
  interface PageSlot {
48
90
  topLevelIndex: number;
49
91
  /** Set when this slot represents a paragraph/heading/blockquote line-level fragment. */
50
92
  fragment?: BlockFragment;
51
93
  /** Set when this slot is a row-range fragment of a table (Sub-phase 4.6). */
52
94
  tableFragment?: TableRowFragment;
95
+ /** Set when this slot is an item-range fragment of an ordered/bullet list. */
96
+ listFragment?: ListItemRangeFragment;
53
97
  }
54
98
  interface PageEntry {
55
99
  slots: PageSlot[];
@@ -58,7 +102,19 @@ interface PageLayout {
58
102
  pages: PageEntry[];
59
103
  }
60
104
  declare function defaultPageLayout(topLevelCount: number): PageLayout;
61
- declare function usePageLayout(rootRef: RefObject<HTMLElement>, geometry: PageGeometry, topLevelCount: number, deps: ReadonlyArray<unknown>): PageLayout;
105
+ type PaginationPhase = "measure" | "stable";
106
+ interface PageLayoutResult {
107
+ layout: PageLayout;
108
+ /**
109
+ * "measure" between a deps change and the post-commit measurement; "stable"
110
+ * once the layout matches the rendered DOM. Consumers can hide the caret /
111
+ * suppress visible flashes during "measure" (the DOM is briefly in the
112
+ * unfragmented intermediate state and the native caret may jump to the start
113
+ * of the line before useLayoutEffect re-applies the model selection).
114
+ */
115
+ phase: PaginationPhase;
116
+ }
117
+ declare function usePageLayout(rootRef: RefObject<HTMLElement>, geometry: PageGeometry, topLevelCount: number, deps: ReadonlyArray<unknown>): PageLayoutResult;
62
118
 
63
119
  interface UseEditorOptions {
64
120
  document?: EditorDocument;
@@ -76,6 +132,8 @@ type PendingMarks = Partial<Record<MarkName, MarkAttrs[MarkName] | null>>;
76
132
  interface LinkRequest {
77
133
  initialHref: string;
78
134
  resolve: (href: string | null) => void;
135
+ /** Model selection captured when the link was requested (bug #6). */
136
+ selection: Selection;
79
137
  }
80
138
  interface PageConfigRequest {
81
139
  initial: PageSettings;
@@ -116,6 +174,8 @@ interface UseEditorResult {
116
174
  getBlockAttr: <K extends keyof BlockAttrs>(key: K) => BlockAttrs[K] | "mixed" | undefined;
117
175
  setBlockType: (type: BlockType, attrs?: BlockAttrs) => void;
118
176
  setBlockAttr: <K extends keyof BlockAttrs>(key: K, value: BlockAttrs[K] | null) => void;
177
+ setAlign: (value: AlignValue) => void;
178
+ getAlign: () => AlignValue | "mixed" | undefined;
119
179
  isListActive: (kind: ListKind) => boolean;
120
180
  toggleList: (kind: ListKind) => void;
121
181
  indentList: () => void;
@@ -173,6 +233,11 @@ interface UseEditorResult {
173
233
  mergeSelection: () => boolean;
174
234
  insertImage: (embed: ImageEmbed) => void;
175
235
  setImageAttrs: (blockIndex: number, offset: number, partial: Partial<ImageEmbed>, cellIndex?: number, newOffset?: number) => void;
236
+ /**
237
+ * Move a floating image embed from one anchor to another (re-anchor across
238
+ * blocks/pages) in a single transaction. No-op for inline embeds.
239
+ */
240
+ moveEmbedAnchor: (from: EmbedLoc, to: EmbedLoc, newOffsetX: number, newOffsetY: number) => void;
176
241
  /** Read a File (image) and insert it inline at the caret as a data-URL embed. */
177
242
  insertImageFromFile: (file: File) => Promise<void>;
178
243
  /**
@@ -215,8 +280,14 @@ interface EditorProps {
215
280
  * `"<n> / <total>"` centered. Pass `null` to omit.
216
281
  */
217
282
  renderPageFooter?: PageRenderProp | null;
283
+ /**
284
+ * Awareness do Yjs (de `useCollab().binding.awareness`). Quando presente,
285
+ * o editor publica a seleção local e desenha os cursores/seleções dos outros
286
+ * usuários (presença colaborativa). Omitir = sem cursores remotos.
287
+ */
288
+ awareness?: AwarenessLike;
218
289
  }
219
- declare function Editor({ editor: providedEditor, className, pageGeometry, renderPageHeader, renderPageFooter, }: EditorProps): JSX.Element;
290
+ declare function Editor({ editor: providedEditor, className, pageGeometry, renderPageHeader, renderPageFooter, awareness, }: EditorProps): JSX.Element;
220
291
 
221
292
  /**
222
293
  * Modal dialog for entering a link URL. Replaces the bare `window.prompt`
@@ -339,6 +410,11 @@ declare function getCellElement(root: HTMLElement, blockIndex: number, cellIndex
339
410
  */
340
411
  declare function textOffsetWithin(container: HTMLElement, node: Node, offsetInNode: number): number;
341
412
  declare function readDomSelection(root: HTMLElement): Selection | null;
413
+ interface DomPoint {
414
+ node: Node;
415
+ offset: number;
416
+ }
417
+ declare function locatePoint(root: HTMLElement, pos: Position): DomPoint | null;
342
418
  declare function selectionsEqual(a: Selection, b: Selection): boolean;
343
419
  /**
344
420
  * `true` when the selection is a rectangular table-cell range — both endpoints
@@ -350,4 +426,4 @@ declare function selectionsEqual(a: Selection, b: Selection): boolean;
350
426
  declare function isTableRectSelection(sel: Selection): boolean;
351
427
  declare function applyDomSelection(root: HTMLElement, modelSel: Selection): void;
352
428
 
353
- export { A4_PAGE, type BlockFragment, type CaptionAlign, Editor, type EditorProps, EditorProvider, type EditorProviderProps, ImageCaptionDialog, type ImageCaptionRequest, type ImageCaptionResult, LinkDialog, type LinkRequest, NodeView, type NodeViewFragment, PageConfigDialog, type PageConfigRequest, type PageEntry, type PageGeometry, type PageLayout, type PageRenderContext, type PageRenderProp, type PageSlot, type PendingMarks, type TableRowFragment, Toolbar, type ToolbarProps, type UseEditorOptions, type UseEditorResult, applyDomSelection, contentHeight, contentWidth, defaultPageLayout, deltaLength, findBlockIndex, getBlockElement, getCellElement, getFragmentForOffset, isTableRectSelection, readDomSelection, renderInline, selectionsEqual, sliceDelta, textOffsetWithin, useEditor, useEditorContext, usePageLayout, usePageSettings };
429
+ export { A4_PAGE, type BlockFragment, type CaptionAlign, Editor, type EditorProps, EditorProvider, type EditorProviderProps, ImageCaptionDialog, type ImageCaptionRequest, type ImageCaptionResult, LinkDialog, type LinkRequest, NodeView, type NodeViewFragment, PageConfigDialog, type PageConfigRequest, type PageEntry, type PageGeometry, type PageLayout, type PageLayoutResult, type PageRenderContext, type PageRenderProp, type PageSlot, type PaginationPhase, type PendingMarks, RemoteCursorsOverlay, type TableRowFragment, Toolbar, type ToolbarProps, type UseEditorOptions, type UseEditorResult, applyDomSelection, contentHeight, contentWidth, defaultPageLayout, deltaLength, findBlockIndex, getBlockElement, getCellElement, getFragmentForOffset, isTableRectSelection, locatePoint, readDomSelection, renderInline, selectionsEqual, sliceDelta, textOffsetWithin, useEditor, useEditorContext, usePageLayout, usePageSettings };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,32 @@
1
- import { RefObject, ReactNode, JSX } from 'react';
2
- import { EditorDocument, EditorHistory, SerializedDocument, Selection, MarkName, MarkAttrs, BlockType, BlockAttrs, ListKind, PageSettings, TableLocation, TableRect, ImageEmbed, SerializedBlock, DeltaOp } from '@sofereditor/core';
1
+ import { RefObject, JSX, ReactNode } from 'react';
2
+ import { EditorDocument, EditorHistory, SerializedDocument, Selection, MarkName, MarkAttrs, BlockType, BlockAttrs, AlignValue, ListKind, PageSettings, TableLocation, TableRect, ImageEmbed, EmbedLoc, SerializedBlock, DeltaOp, Position } from '@sofereditor/core';
3
+
4
+ /**
5
+ * Subconjunto mínimo da Awareness do y-protocols que o overlay usa. Tipar assim
6
+ * evita que `@sofereditor/react` dependa de `y-protocols` — o objeto vem de fora
7
+ * (de `useCollab().binding.awareness`) e é estruturalmente compatível.
8
+ */
9
+ interface AwarenessLike {
10
+ clientID: number;
11
+ getStates(): Map<number, Record<string, unknown>>;
12
+ on(event: "change", cb: () => void): void;
13
+ off(event: "change", cb: () => void): void;
14
+ setLocalStateField(field: string, value: unknown): void;
15
+ }
16
+ interface RemoteCursorsOverlayProps {
17
+ rootRef: RefObject<HTMLDivElement>;
18
+ doc: EditorDocument;
19
+ awareness: AwarenessLike;
20
+ /** Muda a cada edição/repaginação → re-medir os carets remotos. */
21
+ revision?: unknown;
22
+ }
23
+ /**
24
+ * Desenha o caret + seleção + nome de cada usuário remoto sobre o conteúdo
25
+ * paginado. Posições remotas chegam via awareness (campo `cursor`, codificado
26
+ * com Y.RelativePosition pelo core) e são resolvidas para coordenadas DOM com
27
+ * o mesmo bridge da seleção local — ciente de fragmentação por página.
28
+ */
29
+ declare function RemoteCursorsOverlay({ rootRef, doc, awareness, revision, }: RemoteCursorsOverlayProps): JSX.Element | null;
3
30
 
4
31
  /**
5
32
  * Sub-phase 3.2 — page layout with intra-paragraph fragmentation.
@@ -27,9 +54,16 @@ interface PageGeometry {
27
54
  marginBottom: number;
28
55
  marginLeft: number;
29
56
  marginRight: number;
57
+ /**
58
+ * Espaço adicional somado ao marginTop só na 1ª página. Útil para clientes
59
+ * que rendem um header fixo (timbrado, identificação) na pág 1 via
60
+ * `renderPageHeader` e não querem que esse espaço seja reservado nas demais.
61
+ * Quando omitido, todas as páginas têm a mesma `contentHeight`.
62
+ */
63
+ firstPageExtraTop?: number;
30
64
  }
31
65
  declare const A4_PAGE: PageGeometry;
32
- declare function contentHeight(g: PageGeometry): number;
66
+ declare function contentHeight(g: PageGeometry, pageIndex?: number): number;
33
67
  declare function contentWidth(g: PageGeometry): number;
34
68
  interface BlockFragment {
35
69
  index: number;
@@ -44,12 +78,22 @@ interface TableRowFragment {
44
78
  /** First row NOT included (exclusive). */
45
79
  rowEnd: number;
46
80
  }
81
+ interface ListItemRangeFragment {
82
+ /** 0, 1, 2... sequential fragment id within the list group. */
83
+ index: number;
84
+ /** First top-level item (level 0) included (inclusive). */
85
+ itemStart: number;
86
+ /** First top-level item (level 0) NOT included (exclusive). */
87
+ itemEnd: number;
88
+ }
47
89
  interface PageSlot {
48
90
  topLevelIndex: number;
49
91
  /** Set when this slot represents a paragraph/heading/blockquote line-level fragment. */
50
92
  fragment?: BlockFragment;
51
93
  /** Set when this slot is a row-range fragment of a table (Sub-phase 4.6). */
52
94
  tableFragment?: TableRowFragment;
95
+ /** Set when this slot is an item-range fragment of an ordered/bullet list. */
96
+ listFragment?: ListItemRangeFragment;
53
97
  }
54
98
  interface PageEntry {
55
99
  slots: PageSlot[];
@@ -58,7 +102,19 @@ interface PageLayout {
58
102
  pages: PageEntry[];
59
103
  }
60
104
  declare function defaultPageLayout(topLevelCount: number): PageLayout;
61
- declare function usePageLayout(rootRef: RefObject<HTMLElement>, geometry: PageGeometry, topLevelCount: number, deps: ReadonlyArray<unknown>): PageLayout;
105
+ type PaginationPhase = "measure" | "stable";
106
+ interface PageLayoutResult {
107
+ layout: PageLayout;
108
+ /**
109
+ * "measure" between a deps change and the post-commit measurement; "stable"
110
+ * once the layout matches the rendered DOM. Consumers can hide the caret /
111
+ * suppress visible flashes during "measure" (the DOM is briefly in the
112
+ * unfragmented intermediate state and the native caret may jump to the start
113
+ * of the line before useLayoutEffect re-applies the model selection).
114
+ */
115
+ phase: PaginationPhase;
116
+ }
117
+ declare function usePageLayout(rootRef: RefObject<HTMLElement>, geometry: PageGeometry, topLevelCount: number, deps: ReadonlyArray<unknown>): PageLayoutResult;
62
118
 
63
119
  interface UseEditorOptions {
64
120
  document?: EditorDocument;
@@ -76,6 +132,8 @@ type PendingMarks = Partial<Record<MarkName, MarkAttrs[MarkName] | null>>;
76
132
  interface LinkRequest {
77
133
  initialHref: string;
78
134
  resolve: (href: string | null) => void;
135
+ /** Model selection captured when the link was requested (bug #6). */
136
+ selection: Selection;
79
137
  }
80
138
  interface PageConfigRequest {
81
139
  initial: PageSettings;
@@ -116,6 +174,8 @@ interface UseEditorResult {
116
174
  getBlockAttr: <K extends keyof BlockAttrs>(key: K) => BlockAttrs[K] | "mixed" | undefined;
117
175
  setBlockType: (type: BlockType, attrs?: BlockAttrs) => void;
118
176
  setBlockAttr: <K extends keyof BlockAttrs>(key: K, value: BlockAttrs[K] | null) => void;
177
+ setAlign: (value: AlignValue) => void;
178
+ getAlign: () => AlignValue | "mixed" | undefined;
119
179
  isListActive: (kind: ListKind) => boolean;
120
180
  toggleList: (kind: ListKind) => void;
121
181
  indentList: () => void;
@@ -173,6 +233,11 @@ interface UseEditorResult {
173
233
  mergeSelection: () => boolean;
174
234
  insertImage: (embed: ImageEmbed) => void;
175
235
  setImageAttrs: (blockIndex: number, offset: number, partial: Partial<ImageEmbed>, cellIndex?: number, newOffset?: number) => void;
236
+ /**
237
+ * Move a floating image embed from one anchor to another (re-anchor across
238
+ * blocks/pages) in a single transaction. No-op for inline embeds.
239
+ */
240
+ moveEmbedAnchor: (from: EmbedLoc, to: EmbedLoc, newOffsetX: number, newOffsetY: number) => void;
176
241
  /** Read a File (image) and insert it inline at the caret as a data-URL embed. */
177
242
  insertImageFromFile: (file: File) => Promise<void>;
178
243
  /**
@@ -215,8 +280,14 @@ interface EditorProps {
215
280
  * `"<n> / <total>"` centered. Pass `null` to omit.
216
281
  */
217
282
  renderPageFooter?: PageRenderProp | null;
283
+ /**
284
+ * Awareness do Yjs (de `useCollab().binding.awareness`). Quando presente,
285
+ * o editor publica a seleção local e desenha os cursores/seleções dos outros
286
+ * usuários (presença colaborativa). Omitir = sem cursores remotos.
287
+ */
288
+ awareness?: AwarenessLike;
218
289
  }
219
- declare function Editor({ editor: providedEditor, className, pageGeometry, renderPageHeader, renderPageFooter, }: EditorProps): JSX.Element;
290
+ declare function Editor({ editor: providedEditor, className, pageGeometry, renderPageHeader, renderPageFooter, awareness, }: EditorProps): JSX.Element;
220
291
 
221
292
  /**
222
293
  * Modal dialog for entering a link URL. Replaces the bare `window.prompt`
@@ -339,6 +410,11 @@ declare function getCellElement(root: HTMLElement, blockIndex: number, cellIndex
339
410
  */
340
411
  declare function textOffsetWithin(container: HTMLElement, node: Node, offsetInNode: number): number;
341
412
  declare function readDomSelection(root: HTMLElement): Selection | null;
413
+ interface DomPoint {
414
+ node: Node;
415
+ offset: number;
416
+ }
417
+ declare function locatePoint(root: HTMLElement, pos: Position): DomPoint | null;
342
418
  declare function selectionsEqual(a: Selection, b: Selection): boolean;
343
419
  /**
344
420
  * `true` when the selection is a rectangular table-cell range — both endpoints
@@ -350,4 +426,4 @@ declare function selectionsEqual(a: Selection, b: Selection): boolean;
350
426
  declare function isTableRectSelection(sel: Selection): boolean;
351
427
  declare function applyDomSelection(root: HTMLElement, modelSel: Selection): void;
352
428
 
353
- export { A4_PAGE, type BlockFragment, type CaptionAlign, Editor, type EditorProps, EditorProvider, type EditorProviderProps, ImageCaptionDialog, type ImageCaptionRequest, type ImageCaptionResult, LinkDialog, type LinkRequest, NodeView, type NodeViewFragment, PageConfigDialog, type PageConfigRequest, type PageEntry, type PageGeometry, type PageLayout, type PageRenderContext, type PageRenderProp, type PageSlot, type PendingMarks, type TableRowFragment, Toolbar, type ToolbarProps, type UseEditorOptions, type UseEditorResult, applyDomSelection, contentHeight, contentWidth, defaultPageLayout, deltaLength, findBlockIndex, getBlockElement, getCellElement, getFragmentForOffset, isTableRectSelection, readDomSelection, renderInline, selectionsEqual, sliceDelta, textOffsetWithin, useEditor, useEditorContext, usePageLayout, usePageSettings };
429
+ export { A4_PAGE, type BlockFragment, type CaptionAlign, Editor, type EditorProps, EditorProvider, type EditorProviderProps, ImageCaptionDialog, type ImageCaptionRequest, type ImageCaptionResult, LinkDialog, type LinkRequest, NodeView, type NodeViewFragment, PageConfigDialog, type PageConfigRequest, type PageEntry, type PageGeometry, type PageLayout, type PageLayoutResult, type PageRenderContext, type PageRenderProp, type PageSlot, type PaginationPhase, type PendingMarks, RemoteCursorsOverlay, type TableRowFragment, Toolbar, type ToolbarProps, type UseEditorOptions, type UseEditorResult, applyDomSelection, contentHeight, contentWidth, defaultPageLayout, deltaLength, findBlockIndex, getBlockElement, getCellElement, getFragmentForOffset, isTableRectSelection, locatePoint, readDomSelection, renderInline, selectionsEqual, sliceDelta, textOffsetWithin, useEditor, useEditorContext, usePageLayout, usePageSettings };