svedit 0.12.0 → 0.13.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 (86) hide show
  1. package/README.md +33 -21
  2. package/dist/Command.svelte.d.ts +38 -30
  3. package/dist/Command.svelte.d.ts.map +1 -1
  4. package/dist/Command.svelte.js +271 -326
  5. package/dist/CustomProperty.svelte +3 -5
  6. package/dist/CustomProperty.svelte.d.ts +3 -6
  7. package/dist/CustomProperty.svelte.d.ts.map +1 -1
  8. package/dist/KeyMapper.svelte.d.ts +19 -6
  9. package/dist/KeyMapper.svelte.d.ts.map +1 -1
  10. package/dist/KeyMapper.svelte.js +95 -112
  11. package/dist/Node.svelte +16 -13
  12. package/dist/Node.svelte.d.ts +3 -5
  13. package/dist/Node.svelte.d.ts.map +1 -1
  14. package/dist/NodeArrayProperty.svelte +39 -22
  15. package/dist/NodeArrayProperty.svelte.d.ts +3 -5
  16. package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
  17. package/dist/NodeCaret.svelte +1 -1
  18. package/dist/NodeCaret.svelte.d.ts +6 -14
  19. package/dist/NodeCaret.svelte.d.ts.map +1 -1
  20. package/dist/NodeGap.svelte +12 -6
  21. package/dist/NodeGap.svelte.d.ts +7 -14
  22. package/dist/NodeGap.svelte.d.ts.map +1 -1
  23. package/dist/NodeGapMarkers.svelte +19 -10
  24. package/dist/NodeGapMarkers.svelte.d.ts +6 -11
  25. package/dist/NodeGapMarkers.svelte.d.ts.map +1 -1
  26. package/dist/NodeSelectionMarkers.svelte +20 -6
  27. package/dist/NodeSelectionMarkers.svelte.d.ts +2 -5
  28. package/dist/NodeSelectionMarkers.svelte.d.ts.map +1 -1
  29. package/dist/Session.svelte.d.ts +81 -129
  30. package/dist/Session.svelte.d.ts.map +1 -1
  31. package/dist/Session.svelte.js +542 -648
  32. package/dist/Svedit.svelte +144 -138
  33. package/dist/Svedit.svelte.d.ts +30 -10
  34. package/dist/Svedit.svelte.d.ts.map +1 -1
  35. package/dist/TextProperty.svelte +32 -18
  36. package/dist/TextProperty.svelte.d.ts +3 -5
  37. package/dist/TextProperty.svelte.d.ts.map +1 -1
  38. package/dist/Transaction.svelte.d.ts +64 -127
  39. package/dist/Transaction.svelte.d.ts.map +1 -1
  40. package/dist/Transaction.svelte.js +839 -1001
  41. package/dist/UnknownNode.svelte +4 -3
  42. package/dist/UnknownNode.svelte.d.ts +5 -9
  43. package/dist/UnknownNode.svelte.d.ts.map +1 -1
  44. package/dist/doc_utils.d.ts +67 -155
  45. package/dist/doc_utils.d.ts.map +1 -1
  46. package/dist/doc_utils.js +619 -863
  47. package/dist/index.d.ts +20 -17
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1 -17
  50. package/dist/node_visibility.svelte.d.ts +110 -9
  51. package/dist/node_visibility.svelte.d.ts.map +1 -1
  52. package/dist/node_visibility.svelte.js +473 -544
  53. package/dist/paste_utils.d.ts +11 -44
  54. package/dist/paste_utils.d.ts.map +1 -1
  55. package/dist/paste_utils.js +111 -152
  56. package/dist/transforms.svelte.d.ts +9 -7
  57. package/dist/transforms.svelte.d.ts.map +1 -1
  58. package/dist/transforms.svelte.js +125 -150
  59. package/dist/types.d.ts +277 -309
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +1 -0
  62. package/dist/utils.d.ts +40 -119
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +346 -473
  65. package/package.json +69 -70
  66. package/src/lib/{Command.svelte.js → Command.svelte.ts} +29 -17
  67. package/src/lib/CustomProperty.svelte +3 -5
  68. package/src/lib/{KeyMapper.svelte.js → KeyMapper.svelte.ts} +28 -11
  69. package/src/lib/Node.svelte +16 -13
  70. package/src/lib/NodeArrayProperty.svelte +39 -22
  71. package/src/lib/NodeCaret.svelte +1 -1
  72. package/src/lib/NodeGap.svelte +12 -6
  73. package/src/lib/NodeGapMarkers.svelte +19 -10
  74. package/src/lib/NodeSelectionMarkers.svelte +20 -6
  75. package/src/lib/{Session.svelte.js → Session.svelte.ts} +130 -127
  76. package/src/lib/Svedit.svelte +144 -138
  77. package/src/lib/TextProperty.svelte +32 -18
  78. package/src/lib/{Transaction.svelte.js → Transaction.svelte.ts} +139 -143
  79. package/src/lib/UnknownNode.svelte +4 -3
  80. package/src/lib/{doc_utils.js → doc_utils.ts} +247 -206
  81. package/src/lib/{index.js → index.ts} +47 -0
  82. package/src/lib/{node_visibility.svelte.js → node_visibility.svelte.ts} +68 -92
  83. package/src/lib/{paste_utils.js → paste_utils.ts} +47 -58
  84. package/src/lib/{transforms.svelte.js → transforms.svelte.ts} +39 -21
  85. package/src/lib/{types.d.ts → types.ts} +143 -66
  86. package/src/lib/{utils.js → utils.ts} +110 -129
@@ -1,7 +1,3 @@
1
- /**
2
- * @import { NodeId, Selection, Document, DocumentSchema, DocumentPath, Attachment, Mark, Annotation } from './types.d.js'
3
- */
4
-
5
1
  import {
6
2
  get_char_length,
7
3
  char_slice,
@@ -32,15 +28,28 @@ import {
32
28
  get_selected_range_types,
33
29
  validate_selection
34
30
  } from './doc_utils.js';
31
+ import type { SelectedAttachment } from './doc_utils.js';
32
+ import type {
33
+ NodeId,
34
+ NodeKind,
35
+ Selection,
36
+ Document,
37
+ DocumentSchema,
38
+ DocumentNode,
39
+ DocumentPath,
40
+ Attachment,
41
+ Mark,
42
+ Annotation,
43
+ DocumentOperation,
44
+ DynamicRecord,
45
+ Inspection,
46
+ SessionConfig
47
+ } from './types.js';
35
48
 
36
49
  /**
37
50
  * Check whether a selection-relative range fits within a container length.
38
- *
39
- * @param {Attachment} range
40
- * @param {number} length
41
- * @returns {boolean}
42
51
  */
43
- function is_range_within_bounds(range, length) {
52
+ function is_range_within_bounds(range: Attachment, length: number): boolean {
44
53
  return (
45
54
  Number.isInteger(range.start_offset) &&
46
55
  Number.isInteger(range.end_offset) &&
@@ -66,39 +75,52 @@ function is_range_within_bounds(range, length) {
66
75
  * ```
67
76
  */
68
77
  export default class Transaction {
78
+ schema: DocumentSchema;
79
+ doc: Document;
80
+ // NOTE: Typed non-null for historical reasons — most methods assume a
81
+ // selection is present. A null selection is cast here and would surface at
82
+ // runtime exactly as before the TypeScript conversion.
83
+ selection: Selection;
84
+ config: SessionConfig;
85
+ ops: DocumentOperation[];
86
+ inverse_ops: DocumentOperation[];
87
+ selection_before: Selection | null;
88
+ created_node_ids: NodeId[];
89
+ modified_node_ids: NodeId[];
90
+ deleted_node_ids: NodeId[];
91
+ changed_node_types: boolean;
92
+
69
93
  /**
70
94
  * Creates a new Transaction with the given state.
71
95
  *
72
- * @param {DocumentSchema} schema - The document schema
73
- * @param {Document} doc - The document state {document_id, nodes}
74
- * @param {Selection | null} selection - The current selection
75
- * @param {object} config - The document config (including generate_id)
96
+ * @param schema - The document schema
97
+ * @param doc - The document state {document_id, nodes}
98
+ * @param selection - The current selection
99
+ * @param config - The document config (including generate_id)
76
100
  */
77
- constructor(schema, doc, selection, config) {
101
+ constructor(
102
+ schema: DocumentSchema,
103
+ doc: Document,
104
+ selection: Selection | null,
105
+ config: SessionConfig
106
+ ) {
78
107
  this.schema = schema;
79
108
  // The transaction works on a draft: one shallow copy of the nodes map
80
109
  // up front, then ops mutate the draft in place (copying node objects
81
110
  // on write). This keeps the original document untouched and node
82
111
  // identity stable for unchanged nodes, without the previous
83
112
  // one-full-map-copy-per-op cost.
84
- /** @type {object} */
85
113
  this.doc = create_document_draft(doc);
86
- /** @type {Selection} */
87
- this.selection = selection;
114
+ this.selection = selection as Selection;
88
115
  this.config = config;
89
116
  // Here we track the ops during the transaction
90
- /** @type {Record<string, any>[]} */
91
117
  this.ops = [];
92
- /** @type {Record<string, any>[]} */
93
118
  this.inverse_ops = [];
94
119
  // Remember the selection before the transaction started
95
120
  this.selection_before = selection;
96
121
 
97
- /** @type {NodeId[]} */
98
122
  this.created_node_ids = [];
99
- /** @type {NodeId[]} */
100
123
  this.modified_node_ids = [];
101
- /** @type {NodeId[]} */
102
124
  this.deleted_node_ids = [];
103
125
  // True when any op set a node's 'type' property — referrers must be
104
126
  // re-validated against type constraints in that case.
@@ -108,50 +130,39 @@ export default class Transaction {
108
130
  /**
109
131
  * Gets a value from the document at the specified path.
110
132
  *
111
- * @param {DocumentPath|string} path - The path to the value in the document, or a string node ID
112
- * @returns {any} The value at the specified path
133
+ * @param path - The path to the value in the document, or a string node ID
134
+ * @returns The value at the specified path
113
135
  */
114
- get(path) {
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ get(path: DocumentPath | string): any {
115
138
  return doc_get(this.schema, this.doc, path);
116
139
  }
117
140
 
118
141
  /**
119
142
  * Gets the type of a property from the schema.
120
- *
121
- * @param {string} type - The node type
122
- * @param {string} property - The property name
123
- * @returns {string} The property type
124
143
  */
125
- property_type(type, property) {
144
+ property_type(type: string, property: string): string {
126
145
  return doc_property_type(this.schema, type, property);
127
146
  }
128
147
 
129
148
  /**
130
- * Determines the kind of a node ('block', 'text', 'mark', or 'annotation').
131
- *
132
- * @param {any} node - The node to check
133
- * @returns {'block'|'text'|'mark'|'annotation'} The node kind
149
+ * Determines the kind of a node ('document', 'block', 'text', 'mark', or 'annotation').
134
150
  */
135
- kind(node) {
151
+ kind(node: DocumentNode): NodeKind {
136
152
  return doc_kind(this.schema, node);
137
153
  }
138
154
 
139
155
  /**
140
156
  * Inspects a path to get metadata about the value at that location.
141
- *
142
- * @param {DocumentPath} path - The path to inspect
143
- * @returns {{kind: 'property'|'node', [key: string]: any}} Metadata about the path
144
157
  */
145
- inspect(path) {
158
+ inspect(path: DocumentPath): Inspection {
146
159
  return doc_inspect(this.schema, this.doc, path);
147
160
  }
148
161
 
149
162
  /**
150
163
  * Generates a new unique ID using the config's generate_id function.
151
- *
152
- * @returns {string} A new unique ID
153
164
  */
154
- generate_id() {
165
+ generate_id(): string {
155
166
  const id = this.config?.generate_id ? this.config.generate_id() : `node_${crypto.randomUUID()}`;
156
167
  if (!is_id_valid(id)) {
157
168
  throw new Error(
@@ -164,29 +175,23 @@ export default class Transaction {
164
175
  /**
165
176
  * Validates a node against the document schema.
166
177
  *
167
- * @param {any} node - The node to validate
168
178
  * @throws {Error} Throws if the node is invalid
169
179
  */
170
- validate_node(node) {
180
+ validate_node(node: DocumentNode): void {
171
181
  validate_node(node, this.schema, this.doc.nodes, { require_references: false });
172
182
  }
173
183
 
174
184
  /**
175
185
  * Gets all nodes referenced by a given node (recursively).
176
- *
177
- * @param {NodeId} node_id - The node ID to get references for
178
- * @returns {NodeId[]} Array of referenced node IDs
179
186
  */
180
- get_referenced_nodes(node_id) {
187
+ get_referenced_nodes(node_id: NodeId): NodeId[] {
181
188
  return traverse_ids(node_id, this.schema, this.doc.nodes).slice(0, -1);
182
189
  }
183
190
 
184
191
  /**
185
192
  * Gets the available mark types for the current selection.
186
- *
187
- * @returns {string[]} Array of available mark type names
188
193
  */
189
- get available_mark_types() {
194
+ get available_mark_types(): string[] {
190
195
  if (this.selection?.type !== 'text' && this.selection?.type !== 'node') return [];
191
196
  const property_definition = this.inspect(this.selection.path);
192
197
  return property_definition.mark_types || [];
@@ -194,10 +199,8 @@ export default class Transaction {
194
199
 
195
200
  /**
196
201
  * Gets the available annotation types for the current selection.
197
- *
198
- * @returns {string[]} Array of available annotation type names
199
202
  */
200
- get available_annotation_types() {
203
+ get available_annotation_types(): string[] {
201
204
  if (this.selection?.type !== 'text' && this.selection?.type !== 'node') return [];
202
205
  const property_definition = this.inspect(this.selection.path);
203
206
  return property_definition.annotation_types || [];
@@ -205,46 +208,34 @@ export default class Transaction {
205
208
 
206
209
  /**
207
210
  * Returns marks touched by the current selection.
208
- *
209
- * @returns {Array<Mark & { index: number, node: any }>}
210
211
  */
211
- get selected_marks() {
212
+ get selected_marks(): SelectedAttachment[] {
212
213
  return get_selected_marks(this.schema, this.doc, this.selection);
213
214
  }
214
215
 
215
- get active_mark() {
216
+ get active_mark(): SelectedAttachment | null {
216
217
  return this.selected_marks.length === 1 ? this.selected_marks[0] : null;
217
218
  }
218
219
 
219
220
  /**
220
221
  * Returns annotations touched by the current selection.
221
- *
222
- * @returns {Array<Annotation & { index: number, node: any }>}
223
222
  */
224
- get selected_annotations() {
223
+ get selected_annotations(): SelectedAttachment[] {
225
224
  return get_selected_annotations(this.schema, this.doc, this.selection);
226
225
  }
227
226
 
228
- get active_annotation() {
227
+ get active_annotation(): SelectedAttachment | null {
229
228
  return this.selected_annotations.length === 1 ? this.selected_annotations[0] : null;
230
229
  }
231
230
 
232
231
  /**
233
232
  * Applies an operation to the document (internal).
234
- *
235
- * @param {Array} op - The operation to apply
236
- * @private
237
233
  */
238
- _apply_op(op) {
234
+ private _apply_op(op: DocumentOperation): void {
239
235
  apply_op_to_draft(this.doc, op);
240
236
  }
241
237
 
242
- /**
243
- * @param {NodeId[]} node_ids
244
- * @param {NodeId} node_id
245
- * @private
246
- */
247
- _track_node_id(node_ids, node_id) {
238
+ private _track_node_id(node_ids: NodeId[], node_id: NodeId): void {
248
239
  if (!node_ids.includes(node_id)) node_ids.push(node_id);
249
240
  }
250
241
 
@@ -254,9 +245,9 @@ export default class Transaction {
254
245
  * This is the core operation for modifying document properties. It records
255
246
  * both the forward operation and its inverse for undo support.
256
247
  *
257
- * @param {DocumentPath} path - Array path to the property (e.g., ["node_1", "title"])
258
- * @param {any} value - The new value to set
259
- * @returns {Transaction} This transaction instance for method chaining
248
+ * @param path - Array path to the property (e.g., ["node_1", "title"])
249
+ * @param value - The new value to set
250
+ * @returns This transaction instance for method chaining
260
251
  *
261
252
  * @example
262
253
  * ```js
@@ -264,7 +255,8 @@ export default class Transaction {
264
255
  * tr.set(["page_1", "body", "0", "description"], {content: "Hello world", marks: [], annotations: []});
265
256
  * ```
266
257
  */
267
- set(path, value) {
258
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
259
+ set(path: DocumentPath, value: any): this {
268
260
  const path_info = this.inspect(path);
269
261
  if (path_info?.kind !== 'property') {
270
262
  throw new Error(
@@ -286,8 +278,7 @@ export default class Transaction {
286
278
 
287
279
  // Collect node IDs that might need to be deleted after the set operation
288
280
  const prop_type = this.property_type(node.type, property_key_str);
289
- /** @type {NodeId[]} */
290
- let removed_node_ids = [];
281
+ let removed_node_ids: NodeId[] = [];
291
282
 
292
283
  if (prop_type === 'node' && typeof previous_value === 'string' && previous_value !== value) {
293
284
  removed_node_ids = [previous_value];
@@ -297,10 +288,10 @@ export default class Transaction {
297
288
  const next_node_ids = new Set(value.nodes);
298
289
 
299
290
  // Only include node IDs that were in previous_value but are not in the new value
300
- removed_node_ids = previous_node_ids.filter((id) => !next_node_ids.has(id));
291
+ removed_node_ids = previous_node_ids.filter((id: NodeId) => !next_node_ids.has(id));
301
292
  }
302
293
 
303
- const op = ['set', normalized_path, value];
294
+ const op: DocumentOperation = ['set', normalized_path, value];
304
295
  this.ops.push(op);
305
296
  this.inverse_ops.push(['set', normalized_path, previous_value]);
306
297
  this._apply_op(op);
@@ -324,15 +315,15 @@ export default class Transaction {
324
315
  // Takes a subgraph and constructs new nodes from it
325
316
  // NOTE: all ids will be mapped to new unique ids.
326
317
  // NOTE: Omitted properties will be populated with default values.
327
- build(node_id, nodes) {
318
+ build(node_id: NodeId, nodes: Record<string, DocumentNode>): NodeId {
328
319
  const depth_first_nodes = traverse(node_id, this.schema, nodes);
329
320
  // This maps original ids to newly generated ids
330
- const id_map = {};
321
+ const id_map: Record<string, string> = {};
331
322
 
332
323
  for (const node of depth_first_nodes) {
333
324
  const new_id = this.generate_id();
334
325
  id_map[node.id] = new_id;
335
- let new_node = { ...node, id: new_id };
326
+ let new_node: DocumentNode = { ...node, id: new_id };
336
327
  const node_schema = this.schema[node.type];
337
328
 
338
329
  // Update all property references to use new IDs
@@ -340,14 +331,14 @@ export default class Transaction {
340
331
  const prop_type = property_definition.type;
341
332
  const value = new_node[property_name];
342
333
 
343
- const remap_ranges = (ranges) =>
334
+ const remap_ranges = (ranges: Attachment[] | undefined) =>
344
335
  (ranges ?? []).map(({ start_offset, end_offset, node_id }) => {
345
336
  return { start_offset, end_offset, node_id: id_map[node_id] || node_id };
346
337
  });
347
338
 
348
339
  if (prop_type === 'node_array' && value && typeof value === 'object') {
349
340
  new_node[property_name] = {
350
- nodes: value.nodes.map((ref_id) => id_map[ref_id]),
341
+ nodes: value.nodes.map((ref_id: NodeId) => id_map[ref_id]),
351
342
  marks: remap_ranges(value.marks),
352
343
  annotations: remap_ranges(value.annotations)
353
344
  };
@@ -366,7 +357,7 @@ export default class Transaction {
366
357
  this.create(new_node);
367
358
  }
368
359
 
369
- return id_map[depth_first_nodes.at(-1).id];
360
+ return id_map[depth_first_nodes.at(-1)!.id];
370
361
  }
371
362
 
372
363
  /**
@@ -375,8 +366,8 @@ export default class Transaction {
375
366
  * The node must have a valid id and must not already exist in the document.
376
367
  * Omitted properties with schema defaults are filled before validation.
377
368
  *
378
- * @param {any} node - The node object to create (must include id, type, and required properties)
379
- * @returns {Transaction} This transaction instance for method chaining
369
+ * @param node - The node object to create (must include id, type, and required properties)
370
+ * @returns This transaction instance for method chaining
380
371
  * @throws {Error} If the node ID is invalid or if the node already exists
381
372
  *
382
373
  * @example
@@ -388,7 +379,7 @@ export default class Transaction {
388
379
  * });
389
380
  * ```
390
381
  */
391
- create(node) {
382
+ create(node: DocumentNode): this {
392
383
  const node_with_defaults = fill_node_defaults(node, this.schema);
393
384
 
394
385
  // Validate node against schema
@@ -398,7 +389,7 @@ export default class Transaction {
398
389
  throw new Error('Node with id ' + node_with_defaults.id + ' already exists');
399
390
  }
400
391
 
401
- const op = ['create', node_with_defaults];
392
+ const op: DocumentOperation = ['create', node_with_defaults];
402
393
  this.ops.push(op);
403
394
  this.inverse_ops.push(['delete', node_with_defaults.id]);
404
395
  this._apply_op(op);
@@ -418,15 +409,15 @@ export default class Transaction {
418
409
  * For ref-count-aware cleanup, see how `set` calls
419
410
  * `_cascade_delete_unreferenced_nodes`.
420
411
  *
421
- * @param {any} id - The ID of the node to delete
422
- * @returns {Transaction} This transaction instance for method chaining
412
+ * @param id - The ID of the node to delete
413
+ * @returns This transaction instance for method chaining
423
414
  *
424
415
  * @example
425
416
  * ```js
426
417
  * tr.delete('node_123');
427
418
  * ```
428
419
  */
429
- delete(id) {
420
+ delete(id: NodeId): this {
430
421
  const previous_value = this.get(id);
431
422
  if (!previous_value) {
432
423
  console.warn(`Deletion of node ${id} skipped, as it does not exist.`);
@@ -434,7 +425,7 @@ export default class Transaction {
434
425
  }
435
426
  // Get nodes referenced by this node BEFORE deleting it.
436
427
  const referenced_nodes = this.get_referenced_nodes(id);
437
- const op = ['delete', id];
428
+ const op: DocumentOperation = ['delete', id];
438
429
  this.ops.push(op);
439
430
  this.inverse_ops.push(['create', previous_value]);
440
431
  this._apply_op(op);
@@ -447,11 +438,11 @@ export default class Transaction {
447
438
  /**
448
439
  * Sets the document selection.
449
440
  *
450
- * @param {Selection} selection - The new selection object
451
- * @returns {Transaction} This transaction instance for method chaining
441
+ * @param selection - The new selection object
442
+ * @returns This transaction instance for method chaining
452
443
  * @throws {Error} Throws if the selection is invalid or out of bounds
453
444
  */
454
- set_selection(selection) {
445
+ set_selection(selection: Selection): this {
455
446
  this._validate_selection(selection);
456
447
  this.selection = selection;
457
448
  return this;
@@ -460,11 +451,9 @@ export default class Transaction {
460
451
  /**
461
452
  * Validates a selection against the current document state.
462
453
  *
463
- * @param {Selection} selection - The selection to validate
464
454
  * @throws {Error} Throws if the selection is invalid
465
- * @private
466
455
  */
467
- _validate_selection(selection) {
456
+ private _validate_selection(selection: Selection | null): void {
468
457
  validate_selection(selection, this);
469
458
  }
470
459
 
@@ -474,9 +463,9 @@ export default class Transaction {
474
463
  * Marks are mutually exclusive. Whole marks are toggled, removed, or
475
464
  * switched; ranges are never truncated or expanded by this command.
476
465
  *
477
- * @param {any} mark_type - The type of mark (e.g., 'link', 'strong', 'emphasis')
478
- * @param {any} mark_properties - Additional data for the mark (e.g., href for links)
479
- * @returns {Transaction} This transaction instance for method chaining
466
+ * @param mark_type - The type of mark (e.g., 'link', 'strong', 'emphasis')
467
+ * @param mark_properties - Additional data for the mark (e.g., href for links)
468
+ * @returns This transaction instance for method chaining
480
469
  *
481
470
  * @example
482
471
  * ```js
@@ -487,7 +476,7 @@ export default class Transaction {
487
476
  * tr.toggle_mark('emphasis', {});
488
477
  * ```
489
478
  */
490
- toggle_mark(mark_type, mark_properties) {
479
+ toggle_mark(mark_type: string, mark_properties?: DynamicRecord): this {
491
480
  if (this.selection.type !== 'text' && this.selection.type !== 'node') return this;
492
481
  if (this.selection.type === 'node' && is_selection_collapsed(this.selection)) return this;
493
482
  if (!this.available_mark_types.includes(mark_type)) {
@@ -535,7 +524,7 @@ export default class Transaction {
535
524
  const selected_indices = new Set(selected_marks.map(({ index }) => index));
536
525
  const removed_node_ids = selected_marks.map(({ node_id }) => node_id);
537
526
  annotated_value.marks = annotated_value.marks.filter(
538
- (_, index) => !selected_indices.has(index)
527
+ (_: Attachment, index: number) => !selected_indices.has(index)
539
528
  );
540
529
  this.set(this.selection.path, annotated_value);
541
530
  this._cascade_delete_unreferenced_nodes(removed_node_ids);
@@ -569,9 +558,9 @@ export default class Transaction {
569
558
  * same-type annotations may still overlap when created through lower-level
570
559
  * APIs.
571
560
  *
572
- * @param {any} annotation_type - The type of annotation (e.g., 'comment', 'marker')
573
- * @param {any} annotation_properties - Additional data for the annotation
574
- * @returns {Transaction} This transaction instance for method chaining
561
+ * @param annotation_type - The type of annotation (e.g., 'comment', 'marker')
562
+ * @param annotation_properties - Additional data for the annotation
563
+ * @returns This transaction instance for method chaining
575
564
  *
576
565
  * @example
577
566
  * ```js
@@ -579,7 +568,7 @@ export default class Transaction {
579
568
  * tr.toggle_annotation('comment', { text: 'Please review' });
580
569
  * ```
581
570
  */
582
- toggle_annotation(annotation_type, annotation_properties) {
571
+ toggle_annotation(annotation_type: string, annotation_properties?: DynamicRecord): this {
583
572
  if (this.selection.type !== 'text' && this.selection.type !== 'node') return this;
584
573
  if (this.selection.type === 'node' && is_selection_collapsed(this.selection)) return this;
585
574
  if (!this.available_annotation_types.includes(annotation_type)) {
@@ -621,7 +610,7 @@ export default class Transaction {
621
610
  const selected_indices = new Set(selected_annotations.map(({ index }) => index));
622
611
  const removed_node_ids = selected_annotations.map(({ node_id }) => node_id);
623
612
  annotated_value.annotations = annotated_value.annotations.filter(
624
- (_, index) => !selected_indices.has(index)
613
+ (_: Attachment, index: number) => !selected_indices.has(index)
625
614
  );
626
615
  this.set(this.selection.path, annotated_value);
627
616
  this._cascade_delete_unreferenced_nodes(removed_node_ids);
@@ -637,10 +626,10 @@ export default class Transaction {
637
626
  * - For collapsed selections: Deletes the previous character/node (backward) or next character/node (forward)
638
627
  * - For property selections: Delegates to the config's handle_property_deletion hook
639
628
  *
640
- * @param {'backward' | 'forward'} [direction] - Direction of deletion for collapsed selections
641
- * @returns {Transaction} This transaction instance for method chaining
629
+ * @param direction - Direction of deletion for collapsed selections
630
+ * @returns This transaction instance for method chaining
642
631
  */
643
- delete_selection(direction = 'backward') {
632
+ delete_selection(direction: 'backward' | 'forward' = 'backward'): this {
644
633
  if (!this.selection) return this;
645
634
 
646
635
  if (this.selection.type === 'property') {
@@ -653,7 +642,7 @@ export default class Transaction {
653
642
  // Get the start and end indices for the selection
654
643
  let start = Math.min(this.selection.anchor_offset, this.selection.focus_offset);
655
644
  let end = Math.max(this.selection.anchor_offset, this.selection.focus_offset);
656
- let length;
645
+ let length = 0;
657
646
 
658
647
  if (this.selection?.type === 'text') {
659
648
  const text_content = this.get(this.selection.path).content;
@@ -675,7 +664,7 @@ export default class Transaction {
675
664
  return this;
676
665
  } else if (direction === 'forward' && end === length) {
677
666
  // At end of text - try to join with next text node
678
- const node_index = parseInt(String(this.selection.path.at(-2)), 10);
667
+ const node_index = this.selection.path.at(-2) as number;
679
668
  const successor_node = this.get([...this.selection.path.slice(0, -2), node_index + 1]);
680
669
  // Check if next node is a text node
681
670
  if (successor_node && this.kind(successor_node) === 'text') {
@@ -724,7 +713,7 @@ export default class Transaction {
724
713
  };
725
714
  } else if (this.selection.type === 'text') {
726
715
  const path = this.selection.path;
727
- let text = structuredClone($state.snapshot(this.get(path)));
716
+ const text = structuredClone($state.snapshot(this.get(path)));
728
717
 
729
718
  // Update the text content using character-based operations
730
719
  const original_text = text.content;
@@ -763,13 +752,18 @@ export default class Transaction {
763
752
  * If the selection is expanded (not collapsed), first deletes the selected nodes
764
753
  * before inserting the new ones.
765
754
  *
766
- * @param {NodeId[]} node_ids - Array of node IDs to insert
767
- * @param {Array<Mark>} [marks] - Selection-relative marks to restore
768
- * @param {Array<Annotation>} [annotations] - Selection-relative annotations to restore
769
- * @param {Record<NodeId, any>} [nodes] - Node graph for restored marks and annotations
770
- * @returns {Transaction} This transaction instance for method chaining
755
+ * @param node_ids - Array of node IDs to insert
756
+ * @param marks - Selection-relative marks to restore
757
+ * @param annotations - Selection-relative annotations to restore
758
+ * @param nodes - Node graph for restored marks and annotations
759
+ * @returns This transaction instance for method chaining
771
760
  */
772
- insert_nodes(node_ids, marks = [], annotations = [], nodes = {}) {
761
+ insert_nodes(
762
+ node_ids: NodeId[],
763
+ marks: Array<Mark> = [],
764
+ annotations: Array<Annotation> = [],
765
+ nodes: Record<NodeId, DocumentNode> = {}
766
+ ): this {
773
767
  if (this.selection.type !== 'node') return this;
774
768
 
775
769
  // Unless caret is collapsed, delete the selected nodes as a first step
@@ -781,7 +775,7 @@ export default class Transaction {
781
775
  const current_value = structuredClone($state.snapshot(this.get(path)));
782
776
  const node_array = [...current_value.nodes];
783
777
 
784
- let start = Math.min(this.selection.anchor_offset, this.selection.focus_offset);
778
+ const start = Math.min(this.selection.anchor_offset, this.selection.focus_offset);
785
779
 
786
780
  let next_marks = adjust_ranges_for_insertion(current_value.marks, start, node_ids.length);
787
781
  let next_annotations = adjust_ranges_for_insertion(
@@ -816,7 +810,7 @@ export default class Transaction {
816
810
  node_id: new_mark_node_id
817
811
  };
818
812
  })
819
- .filter(Boolean);
813
+ .filter((mark): mark is Attachment => mark !== null);
820
814
  const combined_marks = next_marks.concat(restored_marks);
821
815
  if (are_ranges_exclusive(combined_marks)) {
822
816
  next_marks = combined_marks;
@@ -841,7 +835,7 @@ export default class Transaction {
841
835
  node_id: new_annotation_node_id
842
836
  };
843
837
  })
844
- .filter(Boolean);
838
+ .filter((annotation): annotation is Attachment => annotation !== null);
845
839
  next_annotations = next_annotations.concat(restored_annotations);
846
840
 
847
841
  this.set(path, { nodes: node_array, marks: next_marks, annotations: next_annotations });
@@ -856,13 +850,18 @@ export default class Transaction {
856
850
  * - Shifting ranges that come after the insertion point
857
851
  * - Optionally applying new marks and annotations to the inserted text
858
852
  *
859
- * @param {string} replaced_text - The text to insert
860
- * @param {Array<Mark>} [marks] - Optional marks to apply to the inserted text
861
- * @param {Array<Annotation>} [annotations] - Optional annotations to apply to the inserted text
862
- * @param {object} [nodes] - Optional node definitions for mark and annotation nodes
863
- * @returns {Transaction} This transaction instance for method chaining
853
+ * @param replaced_text - The text to insert
854
+ * @param marks - Optional marks to apply to the inserted text
855
+ * @param annotations - Optional annotations to apply to the inserted text
856
+ * @param nodes - Optional node definitions for mark and annotation nodes
857
+ * @returns This transaction instance for method chaining
864
858
  */
865
- insert_text(replaced_text, marks = [], annotations = [], nodes = {}) {
859
+ insert_text(
860
+ replaced_text: string,
861
+ marks: Array<Mark> = [],
862
+ annotations: Array<Annotation> = [],
863
+ nodes: Record<NodeId, DocumentNode> = {}
864
+ ): this {
866
865
  if (this.selection?.type !== 'text') return this;
867
866
 
868
867
  // Unless selection is collapsed, delete the selected content
@@ -872,7 +871,7 @@ export default class Transaction {
872
871
  }
873
872
 
874
873
  const text_value = structuredClone($state.snapshot(this.get(this.selection.path)));
875
- const range = get_selection_range(this.selection);
874
+ const range = get_selection_range(this.selection)!;
876
875
 
877
876
  // Transform the plain text string using character-based operations
878
877
  const current_text = text_value.content;
@@ -893,9 +892,8 @@ export default class Transaction {
893
892
  this.set(this.selection.path, text_value); // this will update the current state and create a history entry
894
893
 
895
894
  // Setting the selection automatically triggers a re-render of the corresponding DOMSelection.
896
- /** @type {Selection} */
897
- const new_selection = {
898
- type: /** @type {const} */ ('text'),
895
+ const new_selection: Selection = {
896
+ type: 'text',
899
897
  path: this.selection.path,
900
898
  anchor_offset: range.start_offset + get_char_length(replaced_text),
901
899
  focus_offset: range.start_offset + get_char_length(replaced_text)
@@ -922,7 +920,7 @@ export default class Transaction {
922
920
  }
923
921
  return null;
924
922
  })
925
- .filter(Boolean);
923
+ .filter((mark): mark is Attachment => mark !== null);
926
924
  const combined_marks = text_value.marks.concat(restored_marks);
927
925
  if (are_ranges_exclusive(combined_marks)) {
928
926
  next_text.marks = combined_marks;
@@ -950,7 +948,7 @@ export default class Transaction {
950
948
  }
951
949
  return null;
952
950
  })
953
- .filter(Boolean);
951
+ .filter((annotation): annotation is Attachment => annotation !== null);
954
952
  if (restored_annotations.length > 0) {
955
953
  next_text.annotations = text_value.annotations.concat(restored_annotations);
956
954
  text_changed = true;
@@ -971,10 +969,9 @@ export default class Transaction {
971
969
  * references are removed. Uses reference counting to determine which
972
970
  * nodes are safe to delete.
973
971
  *
974
- * @param {NodeId[]} potentially_orphaned_nodes - Array of node IDs to check
975
- * @private
972
+ * @param potentially_orphaned_nodes - Array of node IDs to check
976
973
  */
977
- _cascade_delete_unreferenced_nodes(potentially_orphaned_nodes) {
974
+ private _cascade_delete_unreferenced_nodes(potentially_orphaned_nodes: NodeId[]): void {
978
975
  if (potentially_orphaned_nodes.length === 0) return;
979
976
 
980
977
  // Build reference counts in ONE full-document scan, then maintain them
@@ -984,8 +981,7 @@ export default class Transaction {
984
981
  // O(M·N) instead of O(N + M).
985
982
  const ref_counts = build_reference_counts(this.schema, this.doc);
986
983
 
987
- /** @type {Record<NodeId, boolean>} */
988
- const nodes_to_delete = {};
984
+ const nodes_to_delete: Record<NodeId, boolean> = {};
989
985
  const to_check = [...potentially_orphaned_nodes];
990
986
 
991
987
  while (to_check.length > 0) {
@@ -1013,7 +1009,7 @@ export default class Transaction {
1013
1009
  for (const node_id of Object.keys(nodes_to_delete)) {
1014
1010
  const previous_value = this.get([node_id]);
1015
1011
  if (previous_value) {
1016
- const op = ['delete', node_id];
1012
+ const op: DocumentOperation = ['delete', node_id];
1017
1013
  this.ops.push(op);
1018
1014
  this.inverse_ops.push(['create', previous_value]);
1019
1015
  this._apply_op(op);