@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.
package/core/wasm.js CHANGED
@@ -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
  */
@@ -1749,11 +1753,48 @@ export function importMarkdown(markdown) {
1749
1753
  }
1750
1754
  }
1751
1755
 
1756
+ /**
1757
+ * Where `bundle`'s text-moving channels (`delta`, then `islandOps`, then
1758
+ * `lineOps`) leave `content`'s marks: the final-text coordinates the bundle's
1759
+ * `markOps` are written in, under the rebase rule stated on `ChangeBundle`.
1760
+ * The document-free read an editor diffs against to decide which `markOps` to
1761
+ * emit, rather than reproducing that rule in its own language.
1762
+ *
1763
+ * `bundle.markOps` are ignored. The answer is normalized, as the store's is:
1764
+ * marks a text move drops (out of range, zero-width formatting) are absent,
1765
+ * and same-kind runs a move left adjacent arrive already unioned, so a bundle
1766
+ * carrying no `markOps` names the marks the field will hold.
1767
+ * Throws on a non-content `content`, a malformed bundle, or an op that applies
1768
+ * out of bounds: `applyChange`'s errors on the same ops.
1769
+ * @param {Content} content
1770
+ * @param {ChangeBundle} bundle
1771
+ * @returns {ContentMark[]}
1772
+ */
1773
+ export function mapMarks(content, bundle) {
1774
+ try {
1775
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
1776
+ wasm.mapMarks(retptr, addHeapObject(content), addHeapObject(bundle));
1777
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
1778
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
1779
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
1780
+ if (r2) {
1781
+ throw takeObject(r1);
1782
+ }
1783
+ return takeObject(r0);
1784
+ } finally {
1785
+ wasm.__wbindgen_add_to_stack_pointer(16);
1786
+ }
1787
+ }
1788
+
1752
1789
  /**
1753
1790
  * Map a base content position (a USV index into `Content.text`, not a UTF-16
1754
1791
  * offset) through a `delta` to its new position, holding a caret stable across
1755
1792
  * a `revise`. `assoc` decides the side of a same-position insertion (`"after"`
1756
1793
  * moves past it). Throws on a malformed `delta`.
1794
+ *
1795
+ * This maps a position the *caller* holds. For the marks already in a field,
1796
+ * `mapMarks` applies the store's own assoc rule across every channel of a
1797
+ * `ChangeBundle`.
1757
1798
  * @param {Delta} delta
1758
1799
  * @param {number} pos
1759
1800
  * @param {Assoc} assoc
package/core/wasm_bg.wasm CHANGED
Binary file
@@ -61,6 +61,7 @@ export const document_warnings: (a: number, b: number) => void;
61
61
  export const exportMarkdown: (a: number, b: number) => void;
62
62
  export const formatDocPath: (a: number, b: number) => void;
63
63
  export const importMarkdown: (a: number, b: number, c: number) => void;
64
+ export const mapMarks: (a: number, b: number, c: number) => void;
64
65
  export const mapPos: (a: number, b: number, c: number, d: number) => void;
65
66
  export const parseDocPath: (a: number, b: number, c: number) => void;
66
67
  export const quill_backendId: (a: number, b: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.110.0",
3
+ "version": "0.112.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -24,6 +24,7 @@ import type {
24
24
  exportMarkdown,
25
25
  rebase,
26
26
  mapPos,
27
+ mapMarks,
27
28
  parseDocPath,
28
29
  formatDocPath
29
30
  } from '../core/wasm.js';
@@ -42,6 +43,7 @@ export interface CoreSurface {
42
43
  exportMarkdown: typeof exportMarkdown;
43
44
  rebase: typeof rebase;
44
45
  mapPos: typeof mapPos;
46
+ mapMarks: typeof mapMarks;
45
47
  parseDocPath: typeof parseDocPath;
46
48
  formatDocPath: typeof formatDocPath;
47
49
  }
@@ -217,34 +219,32 @@ export declare function isImageIsland(
217
219
  island: ContentIsland
218
220
  ): island is ContentIsland & { type: 'image'; props: ImageProps };
219
221
 
220
- /** Narrow a {@link ContentMark} to the `link` arm (carries `url`). */
222
+ /** Narrow a {@link ContentMark} to the `link` arm (carries `attrs.url`). */
221
223
  export declare function isLinkMark(
222
224
  mark: ContentMark
223
- ): mark is ContentMark & { type: 'link'; url: string };
225
+ ): mark is ContentMark & { type: 'link'; attrs: { url: string } };
224
226
 
225
- /** Narrow a {@link ContentMark} to the `anchor` arm (carries `id`). */
227
+ /** Narrow a {@link ContentMark} to the `anchor` arm (carries `attrs.id`). */
226
228
  export declare function isAnchorMark(
227
229
  mark: ContentMark
228
- ): mark is ContentMark & { type: 'anchor'; id: string };
230
+ ): mark is ContentMark & { type: 'anchor'; attrs: { id: string } };
229
231
 
230
- /** Narrow a {@link ContentLine} to the `heading` arm (carries `level`). */
232
+ /** Narrow a {@link ContentLine} to the `heading` arm (carries `attrs.level`). */
231
233
  export declare function isHeadingLine(
232
234
  line: ContentLine
233
- ): line is ContentLine & { kind: 'heading'; level: number };
235
+ ): line is ContentLine & { kind: 'heading'; attrs: { level: number } };
234
236
 
235
- /** Narrow a {@link ContentLine} to the `code` arm (carries `lang`). */
237
+ /** Narrow a {@link ContentLine} to the `code` arm (carries `attrs.lang`). */
236
238
  export declare function isCodeLine(
237
239
  line: ContentLine
238
- ): line is ContentLine & { kind: 'code'; lang?: string };
240
+ ): line is ContentLine & { kind: 'code'; attrs?: { lang?: string } };
239
241
 
240
242
  /** Narrow a {@link ContentContainer} to the `list_item` arm (carries its shape). */
241
243
  export declare function isListItemContainer(
242
244
  container: ContentContainer
243
245
  ): container is ContentContainer & {
244
246
  container: 'list_item';
245
- ordered: boolean;
246
- start: number;
247
- ordinal: number;
247
+ attrs: { ordered: boolean; start: number; ordinal: number };
248
248
  instance: number;
249
249
  };
250
250
 
@@ -255,8 +255,9 @@ export declare function isListItemContainer(
255
255
  // built-in names by hand re-couples to a closed set.
256
256
  //
257
257
  // They classify unknown TAGS, not unknown payloads on known tags: a future
258
- // `kind: "footnote"` carrying a sibling `ref` loses `ref` at any consumer that
259
- // predates it, with or without these.
258
+ // `kind: "footnote"` carrying an `attrs.ref` loses `ref` at any consumer that
259
+ // predates it, with or without these. The spelling needs no classifying: a
260
+ // payload rides `attrs` whether or not this build knows the name.
260
261
 
261
262
  /** True when this build does not know `line.kind`: the open arm, carrying opaque `attrs`. */
262
263
  export declare function isUnknownLine(
@@ -610,13 +611,24 @@ export declare class LiveSession {
610
611
  * transform documented there: `x = clickPx.x / renderScale`,
611
612
  * `y = pageHeightPt - clickPx.y / renderScale`. Unlike {@link regions},
612
613
  * *every* placement answers, not just the first.
614
+ *
615
+ * `tolPt` is how far off the ink a click still counts, in the same points,
616
+ * and defaults to `0` — exact. It is pointer slack, so derive it from the
617
+ * scale the page was drawn at (`slackPx / renderScale`) rather than fixing a
618
+ * value in points, which shrinks under the cursor as the page zooms out. The
619
+ * nearest placement answers and containment is distance zero, so raising
620
+ * `tolPt` only ever fills a miss.
613
621
  */
614
- fieldAt(page: number, x: number, y: number): string | undefined;
622
+ fieldAt(page: number, x: number, y: number, tolPt?: number): string | undefined;
615
623
  /**
616
624
  * Fine-grained click → content position (caret placement). Same PDF-point
617
- * space as {@link fieldAt}; `undefined` off all content ink.
625
+ * space as {@link fieldAt}; `undefined` past `tolPt` from all content ink.
626
+ *
627
+ * `tolPt` buys the most here: the leading between two lines lies inside a
628
+ * paragraph and on no glyph, and under `tolPt` a point there takes the line
629
+ * it is nearer.
618
630
  */
619
- positionAt(page: number, x: number, y: number): ContentHit | undefined;
631
+ positionAt(page: number, x: number, y: number, tolPt?: number): ContentHit | undefined;
620
632
  /**
621
633
  * Content position → caret rect: reverse of {@link positionAt}. `field` is a
622
634
  * canonical `DocPath` address (`parseDocPath`-routable), as {@link regions} keys.
@@ -40,7 +40,7 @@ import initCore, { Quill, Document } from '../core/wasm.js';
40
40
  // itself), a `node:fs` read under Node, whose `fetch` rejects `file:` URLs.
41
41
  // Resolution-time, so `node:fs` never enters a browser graph.
42
42
  import { toModuleSource } from '#quillmark-env';
43
- import { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js';
43
+ import { importMarkdown, exportMarkdown, rebase, mapPos, mapMarks } from '../core/wasm.js';
44
44
  import { parseDocPath, formatDocPath } from '../core/wasm.js';
45
45
 
46
46
  // ── Initialization ──────────────────────────────────────────────────────────
@@ -90,6 +90,7 @@ const CORE_SURFACE = Object.freeze({
90
90
  exportMarkdown,
91
91
  rebase,
92
92
  mapPos,
93
+ mapMarks,
93
94
  parseDocPath,
94
95
  formatDocPath
95
96
  });
@@ -397,12 +398,12 @@ for (const name of /** @type {const} */ (['validate', 'resolve', 'conform'])) {
397
398
  // leaving `props` / the mark payload / `level` opaque at every consumer. These
398
399
  // are the checked narrowing path: on the true branch the payload's pinned shape
399
400
  // is asserted. Only the payload-carrying arms get a guard: an island always
400
- // carries `props`, a `link` mark carries `url`, an `anchor` mark carries `id`, a
401
- // `heading` line carries `level` and a `code` line `lang`, a `list_item`
402
- // container its shape; the payload-free arms (`strong`/`emph`/`underline`/
403
- // `strike`/`code` marks, `para`/`island`/`rule` lines, `quote`) narrow to
404
- // nothing. An unrecognized discriminant fails every guard and keeps its opaque
405
- // `attrs`/`props`.
401
+ // carries `props`, and a `link`/`anchor` mark, a `heading`/`code` line and a
402
+ // `list_item` container each carry their payload in `attrs`; the payload-free
403
+ // arms (`strong`/`emph`/`underline`/`strike`/`code` marks, `para`/`island`/
404
+ // `rule` lines, `quote`) omit `attrs` and narrow to nothing. An unrecognized
405
+ // discriminant fails every guard and carries the same `attrs` a known one
406
+ // would.
406
407
 
407
408
  /**
408
409
  * @param {import('../core/wasm.js').ContentIsland} island
@@ -422,7 +423,7 @@ export function isImageIsland(island) {
422
423
 
423
424
  /**
424
425
  * @param {import('../core/wasm.js').ContentMark} mark
425
- * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'link'; url: string }}
426
+ * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'link'; attrs: { url: string } }}
426
427
  */
427
428
  export function isLinkMark(mark) {
428
429
  return mark.type === 'link';
@@ -430,7 +431,7 @@ export function isLinkMark(mark) {
430
431
 
431
432
  /**
432
433
  * @param {import('../core/wasm.js').ContentMark} mark
433
- * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'anchor'; id: string }}
434
+ * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'anchor'; attrs: { id: string } }}
434
435
  */
435
436
  export function isAnchorMark(mark) {
436
437
  return mark.type === 'anchor';
@@ -438,7 +439,7 @@ export function isAnchorMark(mark) {
438
439
 
439
440
  /**
440
441
  * @param {import('../core/wasm.js').ContentLine} line
441
- * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'heading'; level: number }}
442
+ * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'heading'; attrs: { level: number } }}
442
443
  */
443
444
  export function isHeadingLine(line) {
444
445
  return line.kind === 'heading';
@@ -446,7 +447,7 @@ export function isHeadingLine(line) {
446
447
 
447
448
  /**
448
449
  * @param {import('../core/wasm.js').ContentLine} line
449
- * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'code'; lang?: string }}
450
+ * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'code'; attrs?: { lang?: string } }}
450
451
  */
451
452
  export function isCodeLine(line) {
452
453
  return line.kind === 'code';
@@ -454,7 +455,7 @@ export function isCodeLine(line) {
454
455
 
455
456
  /**
456
457
  * @param {import('../core/wasm.js').ContentContainer} container
457
- * @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; ordered: boolean; start: number; ordinal: number; instance?: number }}
458
+ * @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; attrs: { ordered: boolean; start: number; ordinal: number }; instance?: number }}
458
459
  */
459
460
  export function isListItemContainer(container) {
460
461
  return container.container === 'list_item';
@@ -516,12 +517,13 @@ export function isUnknownIsland(island) {
516
517
  // form cannot tell a list ending beside another from one list of two items, so
517
518
  // an omitted discriminator welds them and nothing reports it.
518
519
  //
519
- // WELD_KEYS is the rule `Container::same_weld` owns upstream: which fields two
520
- // adjacent runs must share for the markdown projection to read them as one, and
521
- // therefore for the canonical form to have to spend a discriminator. `start` is
522
- // not among them, since CommonMark reads only a list's first number. A table
523
- // rather than a switch, so `tests/known_names_drift.rs` can pin it against the
524
- // Rust predicate.
520
+ // WELD_KEYS is the rule `Container::same_weld` owns upstream: which `attrs`
521
+ // entries two adjacent runs must share for the markdown projection to read them
522
+ // as one, and therefore for the canonical form to have to spend a
523
+ // discriminator. `start` is not among them, since CommonMark reads only a
524
+ // list's first number — a subset, which is why a built-in needs an entry rather
525
+ // than the unknown branch's whole-bag compare. A table rather than a switch, so
526
+ // `tests/known_names_drift.rs` can pin it against the Rust predicate.
525
527
 
526
528
  const WELD_KEYS = { list_item: ['ordered'], quote: [] };
527
529
 
@@ -548,7 +550,7 @@ function weldsWith(a, b) {
548
550
  // `hasOwn`, so a tag colliding with an `Object.prototype` member reaches the
549
551
  // unknown branch rather than a function.
550
552
  if (!Object.hasOwn(WELD_KEYS, a.container)) return sameJson(a.attrs, b.attrs);
551
- return WELD_KEYS[a.container].every((k) => a[k] === b[k]);
553
+ return WELD_KEYS[a.container].every((k) => a.attrs?.[k] === b.attrs?.[k]);
552
554
  }
553
555
 
554
556
  /**
@@ -961,20 +963,22 @@ export class LiveSession {
961
963
  * @param {number} page
962
964
  * @param {number} x
963
965
  * @param {number} y
966
+ * @param {number} [tolPt]
964
967
  * @returns {string | undefined}
965
968
  */
966
- fieldAt(page, x, y) {
967
- return this.#inner.fieldAt(page, x, y);
969
+ fieldAt(page, x, y, tolPt) {
970
+ return this.#inner.fieldAt(page, x, y, tolPt);
968
971
  }
969
972
 
970
973
  /**
971
974
  * @param {number} page
972
975
  * @param {number} x
973
976
  * @param {number} y
977
+ * @param {number} [tolPt]
974
978
  * @returns {import('./runtime.d.ts').ContentHit | undefined}
975
979
  */
976
- positionAt(page, x, y) {
977
- return this.#inner.positionAt(page, x, y);
980
+ positionAt(page, x, y, tolPt) {
981
+ return this.#inner.positionAt(page, x, y, tolPt);
978
982
  }
979
983
 
980
984
  /**