svedit 0.5.0 → 0.6.1

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 (62) hide show
  1. package/README.md +761 -120
  2. package/dist/AnnotatedTextProperty.svelte +129 -76
  3. package/dist/AnnotatedTextProperty.svelte.d.ts.map +1 -1
  4. package/dist/Command.svelte.d.ts +118 -0
  5. package/dist/Command.svelte.d.ts.map +1 -0
  6. package/dist/Command.svelte.js +314 -0
  7. package/dist/CustomProperty.svelte +31 -42
  8. package/dist/CustomProperty.svelte.d.ts.map +1 -1
  9. package/dist/KeyMapper.svelte.d.ts +36 -0
  10. package/dist/KeyMapper.svelte.d.ts.map +1 -0
  11. package/dist/KeyMapper.svelte.js +141 -0
  12. package/dist/Node.svelte +33 -35
  13. package/dist/Node.svelte.d.ts.map +1 -1
  14. package/dist/NodeArrayProperty.svelte +40 -37
  15. package/dist/NodeArrayProperty.svelte.d.ts.map +1 -1
  16. package/dist/{Document.svelte.d.ts → Session.svelte.d.ts} +76 -83
  17. package/dist/Session.svelte.d.ts.map +1 -0
  18. package/dist/Session.svelte.js +594 -0
  19. package/dist/Svedit.svelte +1338 -1512
  20. package/dist/Svedit.svelte.d.ts.map +1 -1
  21. package/dist/Transaction.svelte.d.ts +114 -40
  22. package/dist/Transaction.svelte.d.ts.map +1 -1
  23. package/dist/Transaction.svelte.js +845 -661
  24. package/dist/UnknownNode.svelte +6 -6
  25. package/dist/UnknownNode.svelte.d.ts.map +1 -1
  26. package/dist/doc_utils.d.ts +127 -0
  27. package/dist/doc_utils.d.ts.map +1 -0
  28. package/dist/doc_utils.js +482 -0
  29. package/dist/index.d.ts +7 -3
  30. package/dist/index.js +20 -4
  31. package/dist/{commands.svelte.d.ts → transforms.svelte.d.ts} +1 -2
  32. package/dist/transforms.svelte.d.ts.map +1 -0
  33. package/dist/transforms.svelte.js +155 -0
  34. package/dist/types.d.ts +256 -110
  35. package/dist/{util.d.ts → utils.d.ts} +22 -2
  36. package/dist/utils.d.ts.map +1 -0
  37. package/dist/utils.js +338 -0
  38. package/package.json +20 -18
  39. package/src/lib/AnnotatedTextProperty.svelte +129 -76
  40. package/src/lib/Command.svelte.js +314 -0
  41. package/src/lib/CustomProperty.svelte +31 -42
  42. package/src/lib/KeyMapper.svelte.js +141 -0
  43. package/src/lib/Node.svelte +33 -35
  44. package/src/lib/NodeArrayProperty.svelte +40 -37
  45. package/src/lib/Session.svelte.js +594 -0
  46. package/src/lib/Svedit.svelte +1338 -1512
  47. package/src/lib/Transaction.svelte.js +845 -661
  48. package/src/lib/UnknownNode.svelte +6 -6
  49. package/src/lib/doc_utils.js +482 -0
  50. package/src/lib/index.js +20 -4
  51. package/src/lib/transforms.svelte.js +155 -0
  52. package/src/lib/types.d.ts +256 -110
  53. package/src/lib/utils.js +338 -0
  54. package/dist/Document.svelte.d.ts.map +0 -1
  55. package/dist/Document.svelte.js +0 -833
  56. package/dist/commands.svelte.d.ts.map +0 -1
  57. package/dist/commands.svelte.js +0 -266
  58. package/dist/util.d.ts.map +0 -1
  59. package/dist/util.js +0 -269
  60. package/src/lib/Document.svelte.js +0 -833
  61. package/src/lib/commands.svelte.js +0 -266
  62. package/src/lib/util.js +0 -269
@@ -1,12 +1,12 @@
1
1
  <script>
2
- import { getContext } from 'svelte';
3
- import Node from './Node.svelte';
2
+ import { getContext } from 'svelte';
3
+ import Node from './Node.svelte';
4
4
 
5
- const svedit = getContext('svedit');
6
- let { path } = $props();
7
- let node = $derived(svedit.doc.get(path));
5
+ const svedit = getContext('svedit');
6
+ let { path } = $props();
7
+ let node = $derived(svedit.session.get(path));
8
8
  </script>
9
9
 
10
10
  <Node {path}>
11
- Unknown: {node.type}.
11
+ Unknown: {node.type}.
12
12
  </Node>
@@ -1 +1 @@
1
- {"version":3,"file":"UnknownNode.svelte.d.ts","sourceRoot":"","sources":["../src/lib/UnknownNode.svelte.js"],"names":[],"mappings":";;;;;AAqBA;UAV2B,GAAG;WAU4B;wBAVvC;IAAE,IAAI,EAAE,GAAG,CAAA;CAAE"}
1
+ {"version":3,"file":"UnknownNode.svelte.d.ts","sourceRoot":"","sources":["../src/lib/UnknownNode.svelte.js"],"names":[],"mappings":";;;;;AAqBA;UAV0B,GAAG;WAU6B;wBAVxC;IAAE,IAAI,EAAE,GAAG,CAAA;CAAE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Identity function — keeps schema at runtime & makes IDE infer types.
3
+ * Similar to your define_schema pattern but for document schemas.
4
+ *
5
+ * @template {Record<string, NodeSchema>} S
6
+ * @param {S} schema - The document schema to validate
7
+ * @returns {S} The same schema, but with type information preserved
8
+ */
9
+ export function define_document_schema<S extends Record<string, NodeSchema>>(schema: S): S;
10
+ /**
11
+ * Check if a string represents a valid primitive type.
12
+ * @param {string} type - The type string to check
13
+ * @returns {type is PrimitiveType} True if it's a valid primitive type
14
+ */
15
+ export function is_primitive_type(type: string): type is PrimitiveType;
16
+ /**
17
+ * Get the default node type for a property that references nodes.
18
+ * @param {NodeProperty | NodeArrayProperty} property_definition - The property definition
19
+ * @returns {string | null} The default node type, or null if none specified
20
+ */
21
+ export function get_default_node_type(property_definition: NodeProperty | NodeArrayProperty): string | null;
22
+ /**
23
+ * Validate a document schema to ensure all referenced node types exist.
24
+ * @param {DocumentSchema} document_schema - The document schema to validate
25
+ * @throws {Error} Throws if the document schema is invalid
26
+ */
27
+ export function validate_document_schema(document_schema: DocumentSchema): void;
28
+ /**
29
+ * Validate a node against its schema.
30
+ * @param {any} node - The node to validate
31
+ * @param {DocumentSchema} schema - The document schema
32
+ * @param {Record<string, any>} all_nodes - All nodes in the document to check references
33
+ * @throws {Error} Throws if the node is invalid
34
+ */
35
+ export function validate_node(node: any, schema: DocumentSchema, all_nodes?: Record<string, any>): void;
36
+ /**
37
+ * Gets a value from the document at the specified path.
38
+ *
39
+ * @param {DocumentSchema} schema - The document schema
40
+ * @param {Document} doc - The document containing nodes
41
+ * @param {DocumentPath|string} path - Array path to the value, or a string node ID
42
+ * @returns {any} The value at the specified path
43
+ */
44
+ export function get(schema: DocumentSchema, doc: Document, path: DocumentPath | string): any;
45
+ /**
46
+ * Gets the type of a property from the schema.
47
+ *
48
+ * @param {object} schema - The document schema
49
+ * @param {string} type - The node type
50
+ * @param {string} property - The property name
51
+ * @returns {string} The property type
52
+ */
53
+ export function property_type(schema: object, type: string, property: string): string;
54
+ /**
55
+ * Determines the kind of a node ('block', 'text', or 'annotation').
56
+ *
57
+ * @param {object} schema - The document schema
58
+ * @param {any} node - The node to check
59
+ * @returns {'block'|'text'|'annotation'} The node kind
60
+ */
61
+ export function kind(schema: object, node: any): "block" | "text" | "annotation";
62
+ /**
63
+ * Inspects a path to get metadata about the value at that location.
64
+ *
65
+ * @param {DocumentSchema} schema - The document schema
66
+ * @param {Document} doc - The document containing nodes
67
+ * @param {DocumentPath} path - The path to inspect
68
+ * @returns {{kind: 'property'|'node', [key: string]: any}} Metadata about the path
69
+ */
70
+ export function inspect(schema: DocumentSchema, doc: Document, path: DocumentPath): {
71
+ kind: "property" | "node";
72
+ [key: string]: any;
73
+ };
74
+ /**
75
+ * Applies an operation to a document and returns the new document.
76
+ * Uses copy-on-write semantics.
77
+ *
78
+ * @param {Document} doc - The document to apply the operation to
79
+ * @param {Array} op - The operation to apply [type, ...args]
80
+ * @returns {Document} The new document with the operation applied
81
+ */
82
+ export function apply_op(doc: Document, op: any[]): Document;
83
+ /**
84
+ * Counts how many times a node is referenced in the document.
85
+ *
86
+ * @param {DocumentSchema} schema - The document schema
87
+ * @param {Document} doc - The document containing nodes
88
+ * @param {NodeId} node_id - The node ID to count references for
89
+ * @returns {number} The number of references
90
+ */
91
+ export function count_references(schema: DocumentSchema, doc: Document, node_id: NodeId): number;
92
+ /**
93
+ * Gets the currently active annotation at the selection, optionally filtered by type.
94
+ *
95
+ * An annotation is considered "active" if it overlaps with the current selection in any way:
96
+ * - Annotation contains the selection start
97
+ * - Annotation contains the selection end
98
+ * - Selection fully contains the annotation
99
+ *
100
+ * @param {DocumentSchema} schema - The document schema
101
+ * @param {Document} doc - The document containing nodes
102
+ * @param {Selection} [selection] - The current selection
103
+ * @param {string} [annotation_type] - Optional annotation type to filter by
104
+ * @returns {Annotation | null} The active annotation, or null if none found
105
+ */
106
+ export function get_active_annotation(schema: DocumentSchema, doc: Document, selection?: Selection, annotation_type?: string): Annotation | null;
107
+ /**
108
+ * Counts references to a node, excluding nodes marked for deletion.
109
+ *
110
+ * @param {DocumentSchema} schema - The document schema
111
+ * @param {Document} doc - The document containing nodes
112
+ * @param {NodeId} target_node_id - The node ID to count references for
113
+ * @param {Record<NodeId, boolean>} nodes_to_delete - Nodes marked for deletion
114
+ * @returns {number} The number of references excluding deleted nodes
115
+ */
116
+ export function count_references_excluding_deleted(schema: DocumentSchema, doc: Document, target_node_id: NodeId, nodes_to_delete: Record<NodeId, boolean>): number;
117
+ import type { NodeSchema } from './types';
118
+ import type { PrimitiveType } from './types';
119
+ import type { NodeProperty } from './types';
120
+ import type { NodeArrayProperty } from './types';
121
+ import type { DocumentSchema } from './types';
122
+ import type { Document } from './types';
123
+ import type { DocumentPath } from './types';
124
+ import type { NodeId } from './types';
125
+ import type { Selection } from './types';
126
+ import type { Annotation } from './types';
127
+ //# sourceMappingURL=doc_utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc_utils.d.ts","sourceRoot":"","sources":["../src/lib/doc_utils.js"],"names":[],"mappings":"AAWA;;;;;;;GAOG;AACH,uCAJ0C,CAAC,SAA7B,MAAM,CAAC,MAAM,EAAE,UAAU,CAAE,UAC9B,CAAC,GACC,CAAC,CAIb;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,IAAI,IAAI,aAAa,CAejC;AAED;;;;GAIG;AACH,2DAHW,YAAY,GAAG,iBAAiB,GAC9B,MAAM,GAAG,IAAI,CAWzB;AAED;;;;GAIG;AACH,0DAHW,cAAc,QAmBxB;AAgDD;;;;;;GAMG;AACH,oCALW,GAAG,UACH,cAAc,cACd,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QA6D7B;AAED;;;;;;;GAOG;AACH,4BALW,cAAc,OACd,QAAQ,QACR,YAAY,GAAC,MAAM,GACjB,GAAG,CA4Ef;AAED;;;;;;;GAOG;AACH,sCALW,MAAM,QACN,MAAM,YACN,MAAM,GACJ,MAAM,CAclB;AAED;;;;;;GAMG;AACH,6BAJW,MAAM,QACN,GAAG,GACD,OAAO,GAAC,MAAM,GAAC,YAAY,CAIvC;AAED;;;;;;;GAOG;AACH,gCALW,cAAc,OACd,QAAQ,QACR,YAAY,GACV;IAAC,IAAI,EAAE,UAAU,GAAC,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAoBzD;AAED;;;;;;;GAOG;AACH,8BAJW,QAAQ,cAEN,QAAQ,CAkCpB;AAED;;;;;;;GAOG;AACH,yCALW,cAAc,OACd,QAAQ,WACR,MAAM,GACJ,MAAM,CAoBlB;AAED;;;;;;;;;;;;;GAaG;AACH,8CANW,cAAc,OACd,QAAQ,cACR,SAAS,oBACT,MAAM,GACJ,UAAU,GAAG,IAAI,CAwB7B;AAED;;;;;;;;GAQG;AACH,2DANW,cAAc,OACd,QAAQ,kBACR,MAAM,mBACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrB,MAAM,CA0BlB;gCA3dqJ,SAAS;mCAAT,SAAS;kCAAT,SAAS;uCAAT,SAAS;oCAAT,SAAS;8BAAT,SAAS;kCAAT,SAAS;4BAAT,SAAS;+BAAT,SAAS;gCAAT,SAAS"}
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Shared document utilities used by both Session and Transaction.
3
+ *
4
+ * These functions operate on the core document state (schema, doc, selection, config)
5
+ * without any history management or transaction tracking.
6
+ *
7
+ * @import { NodeId, DocumentPath, PrimitiveType, NodeProperty, NodeArrayProperty, NodeSchema, DocumentSchema, Selection, Annotation, Document } from './types'
8
+ */
9
+
10
+ import { get_selection_range } from './utils.js';
11
+
12
+ /**
13
+ * Identity function — keeps schema at runtime & makes IDE infer types.
14
+ * Similar to your define_schema pattern but for document schemas.
15
+ *
16
+ * @template {Record<string, NodeSchema>} S
17
+ * @param {S} schema - The document schema to validate
18
+ * @returns {S} The same schema, but with type information preserved
19
+ */
20
+ export function define_document_schema(schema) {
21
+ return schema;
22
+ }
23
+
24
+ /**
25
+ * Check if a string represents a valid primitive type.
26
+ * @param {string} type - The type string to check
27
+ * @returns {type is PrimitiveType} True if it's a valid primitive type
28
+ */
29
+ export function is_primitive_type(type) {
30
+ return [
31
+ 'string',
32
+ 'number',
33
+ 'boolean',
34
+ 'integer',
35
+ 'datetime',
36
+ 'annotated_text',
37
+ 'string_array',
38
+ 'number_array',
39
+ 'boolean_array',
40
+ 'integer_array'
41
+ ].includes(type);
42
+ }
43
+
44
+ /**
45
+ * Get the default node type for a property that references nodes.
46
+ * @param {NodeProperty | NodeArrayProperty} property_definition - The property definition
47
+ * @returns {string | null} The default node type, or null if none specified
48
+ */
49
+ export function get_default_node_type(property_definition) {
50
+ if (!property_definition || !property_definition.node_types) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ property_definition.default_node_type ||
56
+ (property_definition.node_types.length === 1 ? property_definition.node_types[0] : null)
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Validate a document schema to ensure all referenced node types exist.
62
+ * @param {DocumentSchema} document_schema - The document schema to validate
63
+ * @throws {Error} Throws if the document schema is invalid
64
+ */
65
+ export function validate_document_schema(document_schema) {
66
+ // Check that all referenced node types exist
67
+ for (const [node_type, node_schema] of Object.entries(document_schema)) {
68
+ for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
69
+ if (prop_def.type === 'node' || prop_def.type === 'node_array') {
70
+ const missing_types = prop_def.node_types.filter(
71
+ (ref_type) => !(ref_type in document_schema)
72
+ );
73
+ if (missing_types.length > 0) {
74
+ throw new Error(
75
+ `Node type "${node_type}" property "${prop_name}" references unknown node types: ${missing_types.join(', ')}. Available node types: ${Object.keys(document_schema).join(', ')}`
76
+ );
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Validate a primitive value against its schema type.
85
+ * @param {PrimitiveType} type - The expected type
86
+ * @param {any} value - The value to validate
87
+ * @returns {boolean} True if the value matches the type
88
+ */
89
+ function validate_primitive_value(type, value) {
90
+ switch (type) {
91
+ case 'string':
92
+ return typeof value === 'string';
93
+ case 'number':
94
+ return typeof value === 'number' && !isNaN(value);
95
+ case 'boolean':
96
+ return typeof value === 'boolean';
97
+ case 'integer':
98
+ return Number.isInteger(value);
99
+ case 'datetime':
100
+ return typeof value === 'string' && !isNaN(Date.parse(value));
101
+ case 'annotated_text':
102
+ return (
103
+ typeof value === 'object' &&
104
+ value !== null &&
105
+ typeof value.text === 'string' &&
106
+ Array.isArray(value.annotations)
107
+ );
108
+ case 'string_array':
109
+ return Array.isArray(value) && value.every((v) => typeof v === 'string');
110
+ case 'number_array':
111
+ return Array.isArray(value) && value.every((v) => typeof v === 'number' && !isNaN(v));
112
+ case 'boolean_array':
113
+ return Array.isArray(value) && value.every((v) => typeof v === 'boolean');
114
+ case 'integer_array':
115
+ return Array.isArray(value) && value.every((v) => Number.isInteger(v));
116
+ default:
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * @param {String} id
123
+ * @returns {boolean}
124
+ */
125
+ function is_id_valid(id) {
126
+ return typeof id === 'string' && id.length > 0;
127
+ }
128
+
129
+ /**
130
+ * Validate a node against its schema.
131
+ * @param {any} node - The node to validate
132
+ * @param {DocumentSchema} schema - The document schema
133
+ * @param {Record<string, any>} all_nodes - All nodes in the document to check references
134
+ * @throws {Error} Throws if the node is invalid
135
+ */
136
+ export function validate_node(node, schema, all_nodes = {}) {
137
+ if (!is_id_valid(node.id)) {
138
+ throw new Error(`Node ${node.id} has an invalid id.`);
139
+ }
140
+
141
+ if (!node.type || !schema[node.type]) {
142
+ throw new Error(`Node ${node.id} has an invalid type: ${node.type}`);
143
+ }
144
+
145
+ const node_schema = schema[node.type];
146
+
147
+ for (const [prop_name, prop_def] of Object.entries(node_schema.properties)) {
148
+ const value = node[prop_name];
149
+
150
+ // Check primitive types
151
+ if (is_primitive_type(prop_def.type)) {
152
+ if (!validate_primitive_value(prop_def.type, value)) {
153
+ throw new Error(
154
+ `Node ${node.id} has an invalid property: ${prop_name} must be of type ${prop_def.type}.`
155
+ );
156
+ }
157
+ }
158
+ // Check node references
159
+ if (prop_def.type === 'node') {
160
+ if (!is_id_valid(value)) {
161
+ throw new Error(
162
+ `Node ${node.id} has an invalid property: ${prop_name} must be a valid node id.`
163
+ );
164
+ }
165
+ // Check if referenced node exists and is of allowed type
166
+ const referenced_node = all_nodes[value];
167
+ if (referenced_node && !prop_def.node_types.includes(referenced_node.type)) {
168
+ throw new Error(
169
+ `Node ${node.id} property ${prop_name} references node ${value} of type ${referenced_node.type}, but only types [${/** @type {NodeProperty} */ (prop_def).node_types.join(', ')}] are allowed.`
170
+ );
171
+ }
172
+ }
173
+ // Check node arrays
174
+ else if (prop_def.type === 'node_array') {
175
+ if (
176
+ !Array.isArray(value) ||
177
+ !value.every((id) => typeof id === 'string' && is_id_valid(id))
178
+ ) {
179
+ throw new Error(
180
+ `Node ${node.id} has an invalid property: ${prop_name} must be an array of node ids.`
181
+ );
182
+ }
183
+ // Check if all referenced nodes are of allowed types
184
+ for (const ref_id of value) {
185
+ const referenced_node = all_nodes[ref_id];
186
+ if (referenced_node && !prop_def.node_types.includes(referenced_node.type)) {
187
+ throw new Error(
188
+ `Node ${node.id} property ${prop_name} references node ${ref_id} of type ${referenced_node.type}, but only types [${prop_def.node_types.join(', ')}] are allowed.`
189
+ );
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Gets a value from the document at the specified path.
198
+ *
199
+ * @param {DocumentSchema} schema - The document schema
200
+ * @param {Document} doc - The document containing nodes
201
+ * @param {DocumentPath|string} path - Array path to the value, or a string node ID
202
+ * @returns {any} The value at the specified path
203
+ */
204
+ export function get(schema, doc, path) {
205
+ if (typeof path === 'string') {
206
+ path = [path];
207
+ }
208
+ if (!(Array.isArray(path) && path.length >= 1)) {
209
+ throw new Error(`Invalid path provided ${JSON.stringify(path)}`);
210
+ }
211
+
212
+ let val = doc.nodes[path[0]];
213
+ let val_type = 'node';
214
+
215
+ for (let i = 1; i < path.length; i++) {
216
+ const path_segment = path[i];
217
+ const path_segment_str = String(path_segment);
218
+ if (val_type === 'node') {
219
+ if (property_type(schema, val.type, path_segment_str) === 'node_array') {
220
+ val = val[path_segment];
221
+ val_type = 'node_array';
222
+ } else if (property_type(schema, val.type, path_segment_str) === 'annotated_text') {
223
+ val = val[path_segment];
224
+ val_type = 'annotated_text';
225
+ } else if (property_type(schema, val.type, path_segment_str) === 'node') {
226
+ val = doc.nodes[val[path_segment]];
227
+ val_type = 'node';
228
+ } else if (
229
+ ['string_array', 'integer_array'].includes(
230
+ property_type(schema, val.type, path_segment_str)
231
+ )
232
+ ) {
233
+ val = val[path_segment];
234
+ val_type = 'value_array';
235
+ } else {
236
+ val = val[path_segment];
237
+ val_type = 'value';
238
+ }
239
+ } else if (val_type === 'node_array') {
240
+ val = doc.nodes[val[path_segment]];
241
+ val_type = 'node';
242
+ } else if (val_type === 'value_array') {
243
+ val = val[path_segment];
244
+ val_type = 'value';
245
+ } else if (val_type === 'annotated_text') {
246
+ if (path_segment === 'text') {
247
+ val = val.text;
248
+ val_type = 'value';
249
+ } else if (path_segment === 'annotations') {
250
+ val = val.annotations;
251
+ val_type = 'annotation_array';
252
+ } else {
253
+ throw new Error(
254
+ `Invalid path segment "${path_segment}" for annotated_text. Use "text" or "annotations".`
255
+ );
256
+ }
257
+ } else if (val_type === 'annotation_array') {
258
+ val = val[path_segment];
259
+ val_type = 'annotation';
260
+ } else if (val_type === 'annotation') {
261
+ if (path_segment === 'node_id') {
262
+ val = doc.nodes[val.node_id];
263
+ val_type = 'node';
264
+ } else if (path_segment === 'start_offset') {
265
+ val = val.start_offset;
266
+ val_type = 'value';
267
+ } else if (path_segment === 'end_offset') {
268
+ val = val.end_offset;
269
+ val_type = 'value';
270
+ } else {
271
+ throw new Error(
272
+ `Invalid path segment "${path_segment}" for annotation. Use "start_offset", "end_offset", or "node_id".`
273
+ );
274
+ }
275
+ }
276
+ }
277
+ return val;
278
+ }
279
+
280
+ /**
281
+ * Gets the type of a property from the schema.
282
+ *
283
+ * @param {object} schema - The document schema
284
+ * @param {string} type - The node type
285
+ * @param {string} property - The property name
286
+ * @returns {string} The property type
287
+ */
288
+ export function property_type(schema, type, property) {
289
+ if (typeof type !== 'string') throw new Error(`Invalid type ${type} provided`);
290
+ if (typeof property !== 'string') throw new Error(`Invalid property ${property} provided`);
291
+
292
+ if (property === 'type') return 'string';
293
+ if (property === 'id') return 'string';
294
+
295
+ if (!schema[type]) throw new Error(`Type ${type} not found in schema`);
296
+ if (!schema[type].properties[property])
297
+ throw new Error(`Property ${property} not found in type ${type}`);
298
+
299
+ return schema[type].properties[property].type;
300
+ }
301
+
302
+ /**
303
+ * Determines the kind of a node ('block', 'text', or 'annotation').
304
+ *
305
+ * @param {object} schema - The document schema
306
+ * @param {any} node - The node to check
307
+ * @returns {'block'|'text'|'annotation'} The node kind
308
+ */
309
+ export function kind(schema, node) {
310
+ return schema[node.type].kind;
311
+ }
312
+
313
+ /**
314
+ * Inspects a path to get metadata about the value at that location.
315
+ *
316
+ * @param {DocumentSchema} schema - The document schema
317
+ * @param {Document} doc - The document containing nodes
318
+ * @param {DocumentPath} path - The path to inspect
319
+ * @returns {{kind: 'property'|'node', [key: string]: any}} Metadata about the path
320
+ */
321
+ export function inspect(schema, doc, path) {
322
+ const parent = path.length > 1 ? get(schema, doc, path.slice(0, -1)) : undefined;
323
+ if (parent?.type) {
324
+ const property_name = path.at(-1);
325
+ return {
326
+ kind: 'property',
327
+ name: property_name,
328
+ ...schema[parent.type].properties[property_name]
329
+ };
330
+ } else {
331
+ const node = get(schema, doc, path);
332
+ return {
333
+ kind: 'node',
334
+ id: node.id,
335
+ type: node.type,
336
+ properties: schema[node.type]
337
+ };
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Applies an operation to a document and returns the new document.
343
+ * Uses copy-on-write semantics.
344
+ *
345
+ * @param {Document} doc - The document to apply the operation to
346
+ * @param {Array} op - The operation to apply [type, ...args]
347
+ * @returns {Document} The new document with the operation applied
348
+ */
349
+ export function apply_op(doc, op) {
350
+ const [type, ...args] = op;
351
+ if (type === 'set') {
352
+ const [node_id, property] = args[0];
353
+ const value = structuredClone(args[1]);
354
+ return {
355
+ ...doc,
356
+ nodes: {
357
+ ...doc.nodes,
358
+ [node_id]: {
359
+ ...doc.nodes[node_id],
360
+ [property]: value
361
+ }
362
+ }
363
+ };
364
+ } else if (type === 'create') {
365
+ return {
366
+ ...doc,
367
+ nodes: {
368
+ ...doc.nodes,
369
+ [args[0].id]: structuredClone(args[0])
370
+ }
371
+ };
372
+ } else if (type === 'delete') {
373
+ // eslint-disable-next-line
374
+ const { [args[0]]: _removed, ...remaining_nodes } = doc.nodes;
375
+ return {
376
+ ...doc,
377
+ nodes: remaining_nodes
378
+ };
379
+ }
380
+ return doc;
381
+ }
382
+
383
+ /**
384
+ * Counts how many times a node is referenced in the document.
385
+ *
386
+ * @param {DocumentSchema} schema - The document schema
387
+ * @param {Document} doc - The document containing nodes
388
+ * @param {NodeId} node_id - The node ID to count references for
389
+ * @returns {number} The number of references
390
+ */
391
+ export function count_references(schema, doc, node_id) {
392
+ let count = 0;
393
+
394
+ for (const node of Object.values(doc.nodes)) {
395
+ for (const [property, value] of Object.entries(node)) {
396
+ if (property === 'id' || property === 'type') continue;
397
+
398
+ const prop_type = property_type(schema, node.type, property);
399
+
400
+ if (prop_type === 'node_array' && Array.isArray(value)) {
401
+ count += value.filter((id) => id === node_id).length;
402
+ } else if (prop_type === 'node' && value === node_id) {
403
+ count += 1;
404
+ }
405
+ }
406
+ }
407
+
408
+ return count;
409
+ }
410
+
411
+ /**
412
+ * Gets the currently active annotation at the selection, optionally filtered by type.
413
+ *
414
+ * An annotation is considered "active" if it overlaps with the current selection in any way:
415
+ * - Annotation contains the selection start
416
+ * - Annotation contains the selection end
417
+ * - Selection fully contains the annotation
418
+ *
419
+ * @param {DocumentSchema} schema - The document schema
420
+ * @param {Document} doc - The document containing nodes
421
+ * @param {Selection} [selection] - The current selection
422
+ * @param {string} [annotation_type] - Optional annotation type to filter by
423
+ * @returns {Annotation | null} The active annotation, or null if none found
424
+ */
425
+ export function get_active_annotation(schema, doc, selection, annotation_type) {
426
+ if (selection?.type !== 'text') return null;
427
+ const range = get_selection_range(selection);
428
+ if (!range) return null;
429
+
430
+ const annotated_text = get(schema, doc, selection.path);
431
+ const annotations = annotated_text.annotations;
432
+
433
+ const active_annotation =
434
+ annotations.find(
435
+ (/** @type {Annotation} */ { start_offset, end_offset }) =>
436
+ (start_offset <= range.start_offset && end_offset > range.start_offset) ||
437
+ (start_offset < range.end_offset && end_offset >= range.end_offset) ||
438
+ (start_offset >= range.start_offset && end_offset <= range.end_offset)
439
+ ) || null;
440
+
441
+ if (annotation_type && active_annotation) {
442
+ const annotation_node = get(schema, doc, [active_annotation.node_id]);
443
+ return annotation_node?.type === annotation_type ? active_annotation : null;
444
+ } else {
445
+ return active_annotation;
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Counts references to a node, excluding nodes marked for deletion.
451
+ *
452
+ * @param {DocumentSchema} schema - The document schema
453
+ * @param {Document} doc - The document containing nodes
454
+ * @param {NodeId} target_node_id - The node ID to count references for
455
+ * @param {Record<NodeId, boolean>} nodes_to_delete - Nodes marked for deletion
456
+ * @returns {number} The number of references excluding deleted nodes
457
+ */
458
+ export function count_references_excluding_deleted(schema, doc, target_node_id, nodes_to_delete) {
459
+ let count = 0;
460
+
461
+ for (const node of Object.values(doc.nodes)) {
462
+ if (nodes_to_delete[node.id]) continue;
463
+
464
+ for (const [property, value] of Object.entries(node)) {
465
+ if (property === 'id' || property === 'type') continue;
466
+
467
+ const prop_type = property_type(schema, node.type, property);
468
+
469
+ if (prop_type === 'node_array' && Array.isArray(value)) {
470
+ count += value.filter((id) => id === target_node_id).length;
471
+ } else if (prop_type === 'node' && value === target_node_id) {
472
+ count += 1;
473
+ } else if (prop_type === 'annotated_text' && value && value.annotations) {
474
+ count += value.annotations.filter(
475
+ (annotation) => annotation.node_id === target_node_id
476
+ ).length;
477
+ }
478
+ }
479
+ }
480
+
481
+ return count;
482
+ }
package/dist/index.d.ts CHANGED
@@ -3,8 +3,12 @@ export { default as AnnotatedTextProperty } from "./AnnotatedTextProperty.svelte
3
3
  export { default as CustomProperty } from "./CustomProperty.svelte";
4
4
  export { default as Node } from "./Node.svelte";
5
5
  export { default as NodeArrayProperty } from "./NodeArrayProperty.svelte";
6
+ export { default as Session } from "./Session.svelte.js";
6
7
  export { default as Transaction } from "./Transaction.svelte.js";
7
- export * from "./commands.svelte.js";
8
- export * from "./util.js";
9
- export { default as Document, define_document_schema } from "./Document.svelte.js";
8
+ export { default as Command } from "./Command.svelte.js";
9
+ export * from "./Command.svelte.js";
10
+ export * from "./transforms.svelte.js";
11
+ export * from "./utils.js";
12
+ export { define_document_schema, is_primitive_type, get_default_node_type, validate_document_schema, validate_node } from "./doc_utils.js";
13
+ export { KeyMapper, define_keymap } from "./KeyMapper.svelte.js";
10
14
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -6,9 +6,25 @@ export { default as Node } from './Node.svelte';
6
6
  export { default as NodeArrayProperty } from './NodeArrayProperty.svelte';
7
7
 
8
8
  // Core classes and utilities
9
- export { default as Document, define_document_schema } from './Document.svelte.js';
9
+ export { default as Session } from './Session.svelte.js';
10
10
  export { default as Transaction } from './Transaction.svelte.js';
11
11
 
12
- // Commands and utilities
13
- export * from './commands.svelte.js';
14
- export * from './util.js';
12
+ // Document utilities
13
+ export {
14
+ define_document_schema,
15
+ is_primitive_type,
16
+ get_default_node_type,
17
+ validate_document_schema,
18
+ validate_node
19
+ } from './doc_utils.js';
20
+
21
+ // Command system
22
+ export { default as Command } from './Command.svelte.js';
23
+ export * from './Command.svelte.js';
24
+
25
+ // Keyboard handling
26
+ export { KeyMapper, define_keymap } from './KeyMapper.svelte.js';
27
+
28
+ // Transforms and utilities
29
+ export * from './transforms.svelte.js';
30
+ export * from './utils.js';
@@ -1,5 +1,4 @@
1
1
  export function break_text_node(tr: any): boolean;
2
2
  export function join_text_node(tr: any): boolean;
3
3
  export function insert_default_node(tr: any): boolean;
4
- export function select_all(tr: any): boolean;
5
- //# sourceMappingURL=commands.svelte.d.ts.map
4
+ //# sourceMappingURL=transforms.svelte.d.ts.map