@quillmark/wasm 0.110.0 → 0.112.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.
@@ -279,6 +279,10 @@ export class Document {
279
279
  * Throws on an out-of-range card, a field that is not richtext, a malformed
280
280
  * bundle, or an op that applies out of bounds; the value is unchanged on a
281
281
  * failed apply.
282
+ *
283
+ * Each text-moving channel rebases the marks already in the field, by the
284
+ * rule on `ChangeBundle`; `mapMarks` answers where they land, so a caller
285
+ * building `markOps` need not predict it.
282
286
  * @param {Addr | string} addr
283
287
  * @param {ChangeBundle} bundle
284
288
  */
@@ -1406,15 +1410,22 @@ export class LiveSession {
1406
1410
  * `FieldRegion.rect`, so from a canvas click use
1407
1411
  * `x = clickPx.x / renderScale`, `y = pageHeightPt - clickPx.y / renderScale`.
1408
1412
  * Unlike `regions()`, *every* placement answers, not just the first.
1413
+ *
1414
+ * `tolPt` is how far off the ink a click still counts, in the same points,
1415
+ * and defaults to `0` — exact. Convert the pointer slack a surface wants
1416
+ * from CSS pixels at the scale it drew the page (`slackPx / renderScale`),
1417
+ * so it stays the same size under the cursor as the page zooms. The
1418
+ * nearest placement answers, so raising it only fills a miss.
1409
1419
  * @param {number} page
1410
1420
  * @param {number} x
1411
1421
  * @param {number} y
1422
+ * @param {number | null} [tol_pt]
1412
1423
  * @returns {string | undefined}
1413
1424
  */
1414
- fieldAt(page, x, y) {
1425
+ fieldAt(page, x, y, tol_pt) {
1415
1426
  try {
1416
1427
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1417
- wasm.livesession_fieldAt(retptr, this.__wbg_ptr, page, x, y);
1428
+ wasm.livesession_fieldAt(retptr, this.__wbg_ptr, page, x, y, isLikeNone(tol_pt) ? 0x100000001 : Math.fround(tol_pt));
1418
1429
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1419
1430
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1420
1431
  let v1;
@@ -1533,16 +1544,21 @@ export class LiveSession {
1533
1544
  /**
1534
1545
  * A point → **content position**: the field *and* a USV offset into its
1535
1546
  * `Content`, for placing a caret or mapping a selection into the content
1536
- * model, or `undefined` off all content ink. `x`/`y` are PDF points,
1537
- * bottom-left origin, as in `fieldAt`. The offset is cluster-exact and
1538
- * degrades to the containing segment's start on origin-less ink.
1547
+ * model, or `undefined` off all content ink. `x`/`y`/`tolPt` are PDF
1548
+ * points, bottom-left origin, as in `fieldAt`. The offset is cluster-exact
1549
+ * and degrades to the containing segment's start on origin-less ink.
1550
+ *
1551
+ * `tolPt` earns the most here: the leading between two lines lies inside a
1552
+ * paragraph and on no glyph, and under `tolPt` such a point takes the
1553
+ * nearer line.
1539
1554
  * @param {number} page
1540
1555
  * @param {number} x
1541
1556
  * @param {number} y
1557
+ * @param {number | null} [tol_pt]
1542
1558
  * @returns {ContentHit | undefined}
1543
1559
  */
1544
- positionAt(page, x, y) {
1545
- const ret = wasm.livesession_positionAt(this.__wbg_ptr, page, x, y);
1560
+ positionAt(page, x, y, tol_pt) {
1561
+ const ret = wasm.livesession_positionAt(this.__wbg_ptr, page, x, y, isLikeNone(tol_pt) ? 0x100000001 : Math.fround(tol_pt));
1546
1562
  return takeObject(ret);
1547
1563
  }
1548
1564
  /**
@@ -2154,11 +2170,48 @@ export function importMarkdown(markdown) {
2154
2170
  }
2155
2171
  }
2156
2172
 
2173
+ /**
2174
+ * Where `bundle`'s text-moving channels (`delta`, then `islandOps`, then
2175
+ * `lineOps`) leave `content`'s marks: the final-text coordinates the bundle's
2176
+ * `markOps` are written in, under the rebase rule stated on `ChangeBundle`.
2177
+ * The document-free read an editor diffs against to decide which `markOps` to
2178
+ * emit, rather than reproducing that rule in its own language.
2179
+ *
2180
+ * `bundle.markOps` are ignored. The answer is normalized, as the store's is:
2181
+ * marks a text move drops (out of range, zero-width formatting) are absent,
2182
+ * and same-kind runs a move left adjacent arrive already unioned, so a bundle
2183
+ * carrying no `markOps` names the marks the field will hold.
2184
+ * Throws on a non-content `content`, a malformed bundle, or an op that applies
2185
+ * out of bounds: `applyChange`'s errors on the same ops.
2186
+ * @param {Content} content
2187
+ * @param {ChangeBundle} bundle
2188
+ * @returns {ContentMark[]}
2189
+ */
2190
+ export function mapMarks(content, bundle) {
2191
+ try {
2192
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2193
+ wasm.mapMarks(retptr, addHeapObject(content), addHeapObject(bundle));
2194
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2195
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2196
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
2197
+ if (r2) {
2198
+ throw takeObject(r1);
2199
+ }
2200
+ return takeObject(r0);
2201
+ } finally {
2202
+ wasm.__wbindgen_add_to_stack_pointer(16);
2203
+ }
2204
+ }
2205
+
2157
2206
  /**
2158
2207
  * Map a base content position (a USV index into `Content.text`, not a UTF-16
2159
2208
  * offset) through a `delta` to its new position, holding a caret stable across
2160
2209
  * a `revise`. `assoc` decides the side of a same-position insertion (`"after"`
2161
2210
  * moves past it). Throws on a malformed `delta`.
2211
+ *
2212
+ * This maps a position the *caller* holds. For the marks already in a field,
2213
+ * `mapMarks` applies the store's own assoc rule across every channel of a
2214
+ * `ChangeBundle`.
2162
2215
  * @param {Delta} delta
2163
2216
  * @param {number} pos
2164
2217
  * @param {Assoc} assoc
Binary file
@@ -64,18 +64,19 @@ export const exportMarkdown: (a: number, b: number) => void;
64
64
  export const formatDocPath: (a: number, b: number) => void;
65
65
  export const importMarkdown: (a: number, b: number, c: number) => void;
66
66
  export const livesession_backendId: (a: number, b: number) => void;
67
- export const livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number) => void;
67
+ export const livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
68
68
  export const livesession_fieldBoxes: (a: number, b: number, c: number, d: number) => void;
69
69
  export const livesession_locate: (a: number, b: number, c: number, d: number) => number;
70
70
  export const livesession_pageCount: (a: number) => number;
71
71
  export const livesession_pageSize: (a: number, b: number, c: number) => void;
72
72
  export const livesession_paint: (a: number, b: number, c: number, d: number, e: number) => void;
73
- export const livesession_positionAt: (a: number, b: number, c: number, d: number) => number;
73
+ export const livesession_positionAt: (a: number, b: number, c: number, d: number, e: number) => number;
74
74
  export const livesession_regions: (a: number, b: number) => void;
75
75
  export const livesession_render: (a: number, b: number, c: number) => void;
76
76
  export const livesession_supportsCanvas: (a: number) => number;
77
77
  export const livesession_update: (a: number, b: number, c: number) => void;
78
78
  export const livesession_warnings: (a: number, b: number) => void;
79
+ export const mapMarks: (a: number, b: number, c: number) => void;
79
80
  export const mapPos: (a: number, b: number, c: number, d: number) => void;
80
81
  export const parseDocPath: (a: number, b: number, c: number) => void;
81
82
  export const quill_backendId: (a: number, b: number) => void;
@@ -74,8 +74,9 @@ export interface Content {
74
74
 
75
75
  /** One `\n`-separated segment of `Content.text`, in order. `kind` is an open set:
76
76
  * an unknown role round-trips with opaque `attrs` and renders as a paragraph.
77
- * The open arm blocks discriminant narrowing, so read `level`/`lang` behind a
78
- * check of the arm you want. */
77
+ * Every role spells its payload in `attrs`, known or not, so promoting one moves
78
+ * no bytes. The open arm blocks discriminant narrowing, so read
79
+ * `attrs.level`/`attrs.lang` behind a check of the arm you want. */
79
80
  export type ContentLine = {
80
81
  containers: ContentContainer[];
81
82
  /** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
@@ -85,11 +86,11 @@ export type ContentLine = {
85
86
  /** A line's block role, shared by `ContentLine` and the `setKind` op. */
86
87
  export type ContentLineKind =
87
88
  | { kind: "para" }
88
- | { kind: "heading"; level: number }
89
- | { kind: "code"; lang?: string }
89
+ | { kind: "heading"; attrs: { level: number } }
90
+ | { kind: "code"; attrs?: { lang?: string } }
90
91
  | { kind: "island" }
91
92
  | { kind: "rule" }
92
- | { kind: string; attrs: unknown };
93
+ | { kind: string; attrs?: unknown };
93
94
 
94
95
  /** An ancestor block a line nests inside, outermost first. Open like
95
96
  * `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
@@ -118,22 +119,26 @@ export type ContentLineKind =
118
119
  * Content parsed from a stored document is the one shape that arrives without
119
120
  * it — storage omits a zero — and needs a cast. */
120
121
  export type ContentContainer =
121
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance: number }
122
+ | {
123
+ container: "list_item";
124
+ attrs: { ordered: boolean; start: number; ordinal: number };
125
+ instance: number;
126
+ }
122
127
  | { container: "quote"; instance: number }
123
- | { container: string; attrs: unknown; instance: number };
128
+ | { container: string; attrs?: unknown; instance: number };
124
129
 
125
130
  /** A mark over char range `[start, end)` into `Content.text`. The open `type`
126
131
  * arm blocks discriminant narrowing, so read a payload-carrying arm behind its
127
- * guard: `isLinkMark` (`url`) / `isAnchorMark` (`id`), from
132
+ * guard: `isLinkMark` (`attrs.url`) / `isAnchorMark` (`attrs.id`), from
128
133
  * `@quillmark/wasm/runtime`. An `anchor`'s `id` is a caller-supplied opaque
129
134
  * handle, unique per `Content` and invariant while the mark lives (positions
130
135
  * rebase, the id never does); it has no markdown projection and survives only
131
136
  * through the edit lane. */
132
137
  export type ContentMark = { start: number; end: number } & (
133
138
  | { type: "strong" | "emph" | "underline" | "strike" | "code" }
134
- | { type: "link"; url: string }
135
- | { type: "anchor"; id: string }
136
- | { type: string; attrs: unknown }
139
+ | { type: "link"; attrs: { url: string } }
140
+ | { type: "anchor"; attrs: { id: string } }
141
+ | { type: string; attrs?: unknown }
137
142
  );
138
143
 
139
144
  /** A cell in a `TableProps`. `marks` rides the prose `ContentMark` shape, but
@@ -290,6 +295,18 @@ export type IslandOp =
290
295
  * Within each channel ops apply in sequence against the state the earlier ones
291
296
  * left: an island `insert`'s `at` counts earlier ops' slots, and `lineOps`
292
297
  * positions and indices renumber through earlier `split`/`join`.
298
+ *
299
+ * **Mark rebase.** `delta`, `islandOps` and `lineOps` each move text, and each
300
+ * rebases the marks already in the field by one rule: a range mark's `start`
301
+ * takes assoc `after` and its `end` `before`, so an insertion at either edge
302
+ * grows text *outside* the span; a **zero-width** mark takes `before`, so an
303
+ * insertion at its own position leaves it put. That last case is the one
304
+ * position where the two assocs differ, and where an anchor most often sits.
305
+ *
306
+ * `markOps` name the result, so a caller emitting them predicts this rebase.
307
+ * `mapMarks(content, bundle)` runs it instead: pass the bundle's text-moving
308
+ * channels, diff the marks it returns against the ones you intend, and emit
309
+ * only the difference. Reproducing the rule by hand is a second copy to drift.
293
310
  */
294
311
  export interface ChangeBundle {
295
312
  delta?: Delta;
@@ -548,7 +565,7 @@ export interface ContentHit {
548
565
  */
549
566
  field: string;
550
567
  /**
551
- * USV offset into the field\'s `Content`.
568
+ * USV offset into the field's `Content`.
552
569
  */
553
570
  pos: number;
554
571
  /**
@@ -565,12 +582,12 @@ export interface ContentHit {
565
582
  * (paragraph, heading, whole code fence) and per page each touches, a scalar
566
583
  * referenced at several plate sites surfaces each site, and tracked content
567
584
  * plus a `field:`-bound widget yields both. Group by `field`. The whole-field
568
- * highlight is the union of a page\'s `span`-bearing rects, so inter-paragraph
585
+ * highlight is the union of a page's `span`-bearing rects, so inter-paragraph
569
586
  * whitespace stays uncovered; `LiveSession.fieldBoxes(field)` owns that union.
570
587
  */
571
588
  export interface FieldRegion {
572
589
  /**
573
- * Canonical `DocPath` field address (e.g. `\"cards.indorsement[1].from\"`):
590
+ * Canonical `DocPath` field address (e.g. `"cards.indorsement[1].from"`):
574
591
  * the grammar `parseDocPath` reads and `Diagnostic.path` carries. Feed it
575
592
  * back to `fieldBoxes` / `locate`.
576
593
  */
@@ -584,7 +601,7 @@ export interface FieldRegion {
584
601
  */
585
602
  rect: [number, number, number, number];
586
603
  /**
587
- * The slice this box covers: USV `[start, end)` into the field\'s `Content`
604
+ * The slice this box covers: USV `[start, end)` into the field's `Content`
588
605
  * for one content segment, `undefined` for a scalar site or widget.
589
606
  */
590
607
  span?: [number, number];
@@ -599,7 +616,7 @@ export interface Diagnostic {
599
616
  message: string;
600
617
  location?: Location;
601
618
  /**
602
- * Document-model path anchor (e.g. `\"cards.indorsement[0].signature_block\"`),
619
+ * Document-model path anchor (e.g. `"cards.indorsement[0].signature_block"`),
603
620
  * set on schema validation diagnostics and `undefined` otherwise.
604
621
  */
605
622
  path?: string;
@@ -720,6 +737,10 @@ export class Document {
720
737
  * Throws on an out-of-range card, a field that is not richtext, a malformed
721
738
  * bundle, or an op that applies out of bounds; the value is unchanged on a
722
739
  * failed apply.
740
+ *
741
+ * Each text-moving channel rebases the marks already in the field, by the
742
+ * rule on `ChangeBundle`; `mapMarks` answers where they land, so a caller
743
+ * building `markOps` need not predict it.
723
744
  */
724
745
  applyChange(addr: Addr | string, bundle: ChangeBundle): void;
725
746
  /**
@@ -1056,8 +1077,14 @@ export class LiveSession {
1056
1077
  * `FieldRegion.rect`, so from a canvas click use
1057
1078
  * `x = clickPx.x / renderScale`, `y = pageHeightPt - clickPx.y / renderScale`.
1058
1079
  * Unlike `regions()`, *every* placement answers, not just the first.
1080
+ *
1081
+ * `tolPt` is how far off the ink a click still counts, in the same points,
1082
+ * and defaults to `0` — exact. Convert the pointer slack a surface wants
1083
+ * from CSS pixels at the scale it drew the page (`slackPx / renderScale`),
1084
+ * so it stays the same size under the cursor as the page zooms. The
1085
+ * nearest placement answers, so raising it only fills a miss.
1059
1086
  */
1060
- fieldAt(page: number, x: number, y: number): string | undefined;
1087
+ fieldAt(page: number, x: number, y: number, tol_pt?: number | null): string | undefined;
1061
1088
  /**
1062
1089
  * The whole-field highlight boxes for `field`: one union rect per page over
1063
1090
  * the field's `span`-bearing content segments, the union `regions()` leaves
@@ -1097,11 +1124,15 @@ export class LiveSession {
1097
1124
  /**
1098
1125
  * A point → **content position**: the field *and* a USV offset into its
1099
1126
  * `Content`, for placing a caret or mapping a selection into the content
1100
- * model, or `undefined` off all content ink. `x`/`y` are PDF points,
1101
- * bottom-left origin, as in `fieldAt`. The offset is cluster-exact and
1102
- * degrades to the containing segment's start on origin-less ink.
1127
+ * model, or `undefined` off all content ink. `x`/`y`/`tolPt` are PDF
1128
+ * points, bottom-left origin, as in `fieldAt`. The offset is cluster-exact
1129
+ * and degrades to the containing segment's start on origin-less ink.
1130
+ *
1131
+ * `tolPt` earns the most here: the leading between two lines lies inside a
1132
+ * paragraph and on no glyph, and under `tolPt` such a point takes the
1133
+ * nearer line.
1103
1134
  */
1104
- positionAt(page: number, x: number, y: number): ContentHit | undefined;
1135
+ positionAt(page: number, x: number, y: number, tol_pt?: number | null): ContentHit | undefined;
1105
1136
  /**
1106
1137
  * Schema-field geometry for this compiled session: each content field's
1107
1138
  * **first placement** (one region per page it touches) plus widget and
@@ -1296,11 +1327,31 @@ export function formatDocPath(segs: DocPathSeg[]): string;
1296
1327
  */
1297
1328
  export function importMarkdown(markdown: string): Content;
1298
1329
 
1330
+ /**
1331
+ * Where `bundle`'s text-moving channels (`delta`, then `islandOps`, then
1332
+ * `lineOps`) leave `content`'s marks: the final-text coordinates the bundle's
1333
+ * `markOps` are written in, under the rebase rule stated on `ChangeBundle`.
1334
+ * The document-free read an editor diffs against to decide which `markOps` to
1335
+ * emit, rather than reproducing that rule in its own language.
1336
+ *
1337
+ * `bundle.markOps` are ignored. The answer is normalized, as the store's is:
1338
+ * marks a text move drops (out of range, zero-width formatting) are absent,
1339
+ * and same-kind runs a move left adjacent arrive already unioned, so a bundle
1340
+ * carrying no `markOps` names the marks the field will hold.
1341
+ * Throws on a non-content `content`, a malformed bundle, or an op that applies
1342
+ * out of bounds: `applyChange`'s errors on the same ops.
1343
+ */
1344
+ export function mapMarks(content: Content, bundle: ChangeBundle): ContentMark[];
1345
+
1299
1346
  /**
1300
1347
  * Map a base content position (a USV index into `Content.text`, not a UTF-16
1301
1348
  * offset) through a `delta` to its new position, holding a caret stable across
1302
1349
  * a `revise`. `assoc` decides the side of a same-position insertion (`"after"`
1303
1350
  * moves past it). Throws on a malformed `delta`.
1351
+ *
1352
+ * This maps a position the *caller* holds. For the marks already in a field,
1353
+ * `mapMarks` applies the store's own assoc rule across every channel of a
1354
+ * `ChangeBundle`.
1304
1355
  */
1305
1356
  export function mapPos(delta: Delta, pos: number, assoc: Assoc): number;
1306
1357
 
@@ -1394,18 +1445,19 @@ export interface InitOutput {
1394
1445
  readonly formatDocPath: (a: number, b: number) => void;
1395
1446
  readonly importMarkdown: (a: number, b: number, c: number) => void;
1396
1447
  readonly livesession_backendId: (a: number, b: number) => void;
1397
- readonly livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number) => void;
1448
+ readonly livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
1398
1449
  readonly livesession_fieldBoxes: (a: number, b: number, c: number, d: number) => void;
1399
1450
  readonly livesession_locate: (a: number, b: number, c: number, d: number) => number;
1400
1451
  readonly livesession_pageCount: (a: number) => number;
1401
1452
  readonly livesession_pageSize: (a: number, b: number, c: number) => void;
1402
1453
  readonly livesession_paint: (a: number, b: number, c: number, d: number, e: number) => void;
1403
- readonly livesession_positionAt: (a: number, b: number, c: number, d: number) => number;
1454
+ readonly livesession_positionAt: (a: number, b: number, c: number, d: number, e: number) => number;
1404
1455
  readonly livesession_regions: (a: number, b: number) => void;
1405
1456
  readonly livesession_render: (a: number, b: number, c: number) => void;
1406
1457
  readonly livesession_supportsCanvas: (a: number) => number;
1407
1458
  readonly livesession_update: (a: number, b: number, c: number) => void;
1408
1459
  readonly livesession_warnings: (a: number, b: number) => void;
1460
+ readonly mapMarks: (a: number, b: number, c: number) => void;
1409
1461
  readonly mapPos: (a: number, b: number, c: number, d: number) => void;
1410
1462
  readonly parseDocPath: (a: number, b: number, c: number) => void;
1411
1463
  readonly quill_backendId: (a: number, b: number) => void;
@@ -279,6 +279,10 @@ export class Document {
279
279
  * Throws on an out-of-range card, a field that is not richtext, a malformed
280
280
  * bundle, or an op that applies out of bounds; the value is unchanged on a
281
281
  * failed apply.
282
+ *
283
+ * Each text-moving channel rebases the marks already in the field, by the
284
+ * rule on `ChangeBundle`; `mapMarks` answers where they land, so a caller
285
+ * building `markOps` need not predict it.
282
286
  * @param {Addr | string} addr
283
287
  * @param {ChangeBundle} bundle
284
288
  */
@@ -1406,15 +1410,22 @@ export class LiveSession {
1406
1410
  * `FieldRegion.rect`, so from a canvas click use
1407
1411
  * `x = clickPx.x / renderScale`, `y = pageHeightPt - clickPx.y / renderScale`.
1408
1412
  * Unlike `regions()`, *every* placement answers, not just the first.
1413
+ *
1414
+ * `tolPt` is how far off the ink a click still counts, in the same points,
1415
+ * and defaults to `0` — exact. Convert the pointer slack a surface wants
1416
+ * from CSS pixels at the scale it drew the page (`slackPx / renderScale`),
1417
+ * so it stays the same size under the cursor as the page zooms. The
1418
+ * nearest placement answers, so raising it only fills a miss.
1409
1419
  * @param {number} page
1410
1420
  * @param {number} x
1411
1421
  * @param {number} y
1422
+ * @param {number | null} [tol_pt]
1412
1423
  * @returns {string | undefined}
1413
1424
  */
1414
- fieldAt(page, x, y) {
1425
+ fieldAt(page, x, y, tol_pt) {
1415
1426
  try {
1416
1427
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1417
- wasm.livesession_fieldAt(retptr, this.__wbg_ptr, page, x, y);
1428
+ wasm.livesession_fieldAt(retptr, this.__wbg_ptr, page, x, y, isLikeNone(tol_pt) ? 0x100000001 : Math.fround(tol_pt));
1418
1429
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1419
1430
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1420
1431
  let v1;
@@ -1533,16 +1544,21 @@ export class LiveSession {
1533
1544
  /**
1534
1545
  * A point → **content position**: the field *and* a USV offset into its
1535
1546
  * `Content`, for placing a caret or mapping a selection into the content
1536
- * model, or `undefined` off all content ink. `x`/`y` are PDF points,
1537
- * bottom-left origin, as in `fieldAt`. The offset is cluster-exact and
1538
- * degrades to the containing segment's start on origin-less ink.
1547
+ * model, or `undefined` off all content ink. `x`/`y`/`tolPt` are PDF
1548
+ * points, bottom-left origin, as in `fieldAt`. The offset is cluster-exact
1549
+ * and degrades to the containing segment's start on origin-less ink.
1550
+ *
1551
+ * `tolPt` earns the most here: the leading between two lines lies inside a
1552
+ * paragraph and on no glyph, and under `tolPt` such a point takes the
1553
+ * nearer line.
1539
1554
  * @param {number} page
1540
1555
  * @param {number} x
1541
1556
  * @param {number} y
1557
+ * @param {number | null} [tol_pt]
1542
1558
  * @returns {ContentHit | undefined}
1543
1559
  */
1544
- positionAt(page, x, y) {
1545
- const ret = wasm.livesession_positionAt(this.__wbg_ptr, page, x, y);
1560
+ positionAt(page, x, y, tol_pt) {
1561
+ const ret = wasm.livesession_positionAt(this.__wbg_ptr, page, x, y, isLikeNone(tol_pt) ? 0x100000001 : Math.fround(tol_pt));
1546
1562
  return takeObject(ret);
1547
1563
  }
1548
1564
  /**
@@ -2154,11 +2170,48 @@ export function importMarkdown(markdown) {
2154
2170
  }
2155
2171
  }
2156
2172
 
2173
+ /**
2174
+ * Where `bundle`'s text-moving channels (`delta`, then `islandOps`, then
2175
+ * `lineOps`) leave `content`'s marks: the final-text coordinates the bundle's
2176
+ * `markOps` are written in, under the rebase rule stated on `ChangeBundle`.
2177
+ * The document-free read an editor diffs against to decide which `markOps` to
2178
+ * emit, rather than reproducing that rule in its own language.
2179
+ *
2180
+ * `bundle.markOps` are ignored. The answer is normalized, as the store's is:
2181
+ * marks a text move drops (out of range, zero-width formatting) are absent,
2182
+ * and same-kind runs a move left adjacent arrive already unioned, so a bundle
2183
+ * carrying no `markOps` names the marks the field will hold.
2184
+ * Throws on a non-content `content`, a malformed bundle, or an op that applies
2185
+ * out of bounds: `applyChange`'s errors on the same ops.
2186
+ * @param {Content} content
2187
+ * @param {ChangeBundle} bundle
2188
+ * @returns {ContentMark[]}
2189
+ */
2190
+ export function mapMarks(content, bundle) {
2191
+ try {
2192
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
2193
+ wasm.mapMarks(retptr, addHeapObject(content), addHeapObject(bundle));
2194
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
2195
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
2196
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
2197
+ if (r2) {
2198
+ throw takeObject(r1);
2199
+ }
2200
+ return takeObject(r0);
2201
+ } finally {
2202
+ wasm.__wbindgen_add_to_stack_pointer(16);
2203
+ }
2204
+ }
2205
+
2157
2206
  /**
2158
2207
  * Map a base content position (a USV index into `Content.text`, not a UTF-16
2159
2208
  * offset) through a `delta` to its new position, holding a caret stable across
2160
2209
  * a `revise`. `assoc` decides the side of a same-position insertion (`"after"`
2161
2210
  * moves past it). Throws on a malformed `delta`.
2211
+ *
2212
+ * This maps a position the *caller* holds. For the marks already in a field,
2213
+ * `mapMarks` applies the store's own assoc rule across every channel of a
2214
+ * `ChangeBundle`.
2162
2215
  * @param {Delta} delta
2163
2216
  * @param {number} pos
2164
2217
  * @param {Assoc} assoc
Binary file
@@ -64,18 +64,19 @@ export const exportMarkdown: (a: number, b: number) => void;
64
64
  export const formatDocPath: (a: number, b: number) => void;
65
65
  export const importMarkdown: (a: number, b: number, c: number) => void;
66
66
  export const livesession_backendId: (a: number, b: number) => void;
67
- export const livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number) => void;
67
+ export const livesession_fieldAt: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
68
68
  export const livesession_fieldBoxes: (a: number, b: number, c: number, d: number) => void;
69
69
  export const livesession_locate: (a: number, b: number, c: number, d: number) => number;
70
70
  export const livesession_pageCount: (a: number) => number;
71
71
  export const livesession_pageSize: (a: number, b: number, c: number) => void;
72
72
  export const livesession_paint: (a: number, b: number, c: number, d: number, e: number) => void;
73
- export const livesession_positionAt: (a: number, b: number, c: number, d: number) => number;
73
+ export const livesession_positionAt: (a: number, b: number, c: number, d: number, e: number) => number;
74
74
  export const livesession_regions: (a: number, b: number) => void;
75
75
  export const livesession_render: (a: number, b: number, c: number) => void;
76
76
  export const livesession_supportsCanvas: (a: number) => number;
77
77
  export const livesession_update: (a: number, b: number, c: number) => void;
78
78
  export const livesession_warnings: (a: number, b: number) => void;
79
+ export const mapMarks: (a: number, b: number, c: number) => void;
79
80
  export const mapPos: (a: number, b: number, c: number, d: number) => void;
80
81
  export const parseDocPath: (a: number, b: number, c: number) => void;
81
82
  export const quill_backendId: (a: number, b: number) => void;
package/core/wasm.d.ts CHANGED
@@ -74,8 +74,9 @@ export interface Content {
74
74
 
75
75
  /** One `\n`-separated segment of `Content.text`, in order. `kind` is an open set:
76
76
  * an unknown role round-trips with opaque `attrs` and renders as a paragraph.
77
- * The open arm blocks discriminant narrowing, so read `level`/`lang` behind a
78
- * check of the arm you want. */
77
+ * Every role spells its payload in `attrs`, known or not, so promoting one moves
78
+ * no bytes. The open arm blocks discriminant narrowing, so read
79
+ * `attrs.level`/`attrs.lang` behind a check of the arm you want. */
79
80
  export type ContentLine = {
80
81
  containers: ContentContainer[];
81
82
  /** A within-block hard line break rather than a new block. Omitted (false) in the common case. */
@@ -85,11 +86,11 @@ export type ContentLine = {
85
86
  /** A line's block role, shared by `ContentLine` and the `setKind` op. */
86
87
  export type ContentLineKind =
87
88
  | { kind: "para" }
88
- | { kind: "heading"; level: number }
89
- | { kind: "code"; lang?: string }
89
+ | { kind: "heading"; attrs: { level: number } }
90
+ | { kind: "code"; attrs?: { lang?: string } }
90
91
  | { kind: "island" }
91
92
  | { kind: "rule" }
92
- | { kind: string; attrs: unknown };
93
+ | { kind: string; attrs?: unknown };
93
94
 
94
95
  /** An ancestor block a line nests inside, outermost first. Open like
95
96
  * `ContentLine.kind`: an unrecognized container round-trips with opaque `attrs`
@@ -118,22 +119,26 @@ export type ContentLineKind =
118
119
  * Content parsed from a stored document is the one shape that arrives without
119
120
  * it — storage omits a zero — and needs a cast. */
120
121
  export type ContentContainer =
121
- | { container: "list_item"; ordered: boolean; start: number; ordinal: number; instance: number }
122
+ | {
123
+ container: "list_item";
124
+ attrs: { ordered: boolean; start: number; ordinal: number };
125
+ instance: number;
126
+ }
122
127
  | { container: "quote"; instance: number }
123
- | { container: string; attrs: unknown; instance: number };
128
+ | { container: string; attrs?: unknown; instance: number };
124
129
 
125
130
  /** A mark over char range `[start, end)` into `Content.text`. The open `type`
126
131
  * arm blocks discriminant narrowing, so read a payload-carrying arm behind its
127
- * guard: `isLinkMark` (`url`) / `isAnchorMark` (`id`), from
132
+ * guard: `isLinkMark` (`attrs.url`) / `isAnchorMark` (`attrs.id`), from
128
133
  * `@quillmark/wasm/runtime`. An `anchor`'s `id` is a caller-supplied opaque
129
134
  * handle, unique per `Content` and invariant while the mark lives (positions
130
135
  * rebase, the id never does); it has no markdown projection and survives only
131
136
  * through the edit lane. */
132
137
  export type ContentMark = { start: number; end: number } & (
133
138
  | { type: "strong" | "emph" | "underline" | "strike" | "code" }
134
- | { type: "link"; url: string }
135
- | { type: "anchor"; id: string }
136
- | { type: string; attrs: unknown }
139
+ | { type: "link"; attrs: { url: string } }
140
+ | { type: "anchor"; attrs: { id: string } }
141
+ | { type: string; attrs?: unknown }
137
142
  );
138
143
 
139
144
  /** A cell in a `TableProps`. `marks` rides the prose `ContentMark` shape, but
@@ -290,6 +295,18 @@ export type IslandOp =
290
295
  * Within each channel ops apply in sequence against the state the earlier ones
291
296
  * left: an island `insert`'s `at` counts earlier ops' slots, and `lineOps`
292
297
  * positions and indices renumber through earlier `split`/`join`.
298
+ *
299
+ * **Mark rebase.** `delta`, `islandOps` and `lineOps` each move text, and each
300
+ * rebases the marks already in the field by one rule: a range mark's `start`
301
+ * takes assoc `after` and its `end` `before`, so an insertion at either edge
302
+ * grows text *outside* the span; a **zero-width** mark takes `before`, so an
303
+ * insertion at its own position leaves it put. That last case is the one
304
+ * position where the two assocs differ, and where an anchor most often sits.
305
+ *
306
+ * `markOps` name the result, so a caller emitting them predicts this rebase.
307
+ * `mapMarks(content, bundle)` runs it instead: pass the bundle's text-moving
308
+ * channels, diff the marks it returns against the ones you intend, and emit
309
+ * only the difference. Reproducing the rule by hand is a second copy to drift.
293
310
  */
294
311
  export interface ChangeBundle {
295
312
  delta?: Delta;
@@ -491,7 +508,7 @@ export interface Diagnostic {
491
508
  message: string;
492
509
  location?: Location;
493
510
  /**
494
- * Document-model path anchor (e.g. `\"cards.indorsement[0].signature_block\"`),
511
+ * Document-model path anchor (e.g. `"cards.indorsement[0].signature_block"`),
495
512
  * set on schema validation diagnostics and `undefined` otherwise.
496
513
  */
497
514
  path?: string;
@@ -536,6 +553,10 @@ export class Document {
536
553
  * Throws on an out-of-range card, a field that is not richtext, a malformed
537
554
  * bundle, or an op that applies out of bounds; the value is unchanged on a
538
555
  * failed apply.
556
+ *
557
+ * Each text-moving channel rebases the marks already in the field, by the
558
+ * rule on `ChangeBundle`; `mapMarks` answers where they land, so a caller
559
+ * building `markOps` need not predict it.
539
560
  */
540
561
  applyChange(addr: Addr | string, bundle: ChangeBundle): void;
541
562
  /**
@@ -975,11 +996,31 @@ export function formatDocPath(segs: DocPathSeg[]): string;
975
996
  */
976
997
  export function importMarkdown(markdown: string): Content;
977
998
 
999
+ /**
1000
+ * Where `bundle`'s text-moving channels (`delta`, then `islandOps`, then
1001
+ * `lineOps`) leave `content`'s marks: the final-text coordinates the bundle's
1002
+ * `markOps` are written in, under the rebase rule stated on `ChangeBundle`.
1003
+ * The document-free read an editor diffs against to decide which `markOps` to
1004
+ * emit, rather than reproducing that rule in its own language.
1005
+ *
1006
+ * `bundle.markOps` are ignored. The answer is normalized, as the store's is:
1007
+ * marks a text move drops (out of range, zero-width formatting) are absent,
1008
+ * and same-kind runs a move left adjacent arrive already unioned, so a bundle
1009
+ * carrying no `markOps` names the marks the field will hold.
1010
+ * Throws on a non-content `content`, a malformed bundle, or an op that applies
1011
+ * out of bounds: `applyChange`'s errors on the same ops.
1012
+ */
1013
+ export function mapMarks(content: Content, bundle: ChangeBundle): ContentMark[];
1014
+
978
1015
  /**
979
1016
  * Map a base content position (a USV index into `Content.text`, not a UTF-16
980
1017
  * offset) through a `delta` to its new position, holding a caret stable across
981
1018
  * a `revise`. `assoc` decides the side of a same-position insertion (`"after"`
982
1019
  * moves past it). Throws on a malformed `delta`.
1020
+ *
1021
+ * This maps a position the *caller* holds. For the marks already in a field,
1022
+ * `mapMarks` applies the store's own assoc rule across every channel of a
1023
+ * `ChangeBundle`.
983
1024
  */
984
1025
  export function mapPos(delta: Delta, pos: number, assoc: Assoc): number;
985
1026
 
@@ -1070,6 +1111,7 @@ export interface InitOutput {
1070
1111
  readonly exportMarkdown: (a: number, b: number) => void;
1071
1112
  readonly formatDocPath: (a: number, b: number) => void;
1072
1113
  readonly importMarkdown: (a: number, b: number, c: number) => void;
1114
+ readonly mapMarks: (a: number, b: number, c: number) => void;
1073
1115
  readonly mapPos: (a: number, b: number, c: number, d: number) => void;
1074
1116
  readonly parseDocPath: (a: number, b: number, c: number) => void;
1075
1117
  readonly quill_backendId: (a: number, b: number) => void;