-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathtypes.ts
More file actions
651 lines (578 loc) · 16.6 KB
/
Copy pathtypes.ts
File metadata and controls
651 lines (578 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
import type Session from './Session.svelte.js';
import type { VisibilityRegistryApi } from './node_visibility.svelte.js';
// ===== SVELTE TYPE IMPORTS =====
/**
* Import Svelte's Snippet type for properly typing children in components
*/
import type { Snippet } from 'svelte';
// ===== SELECTION TYPE DEFINITIONS =====
/**
* A unique node identifier.
*
* Node ids must be valid Svedit path string segments: they must start with
* a letter or underscore, contain only letters, numbers, underscores, or
* dashes, and must not contain `__`.
*/
export type NodeId = string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type DynamicRecord = Record<string, any>;
export type SessionConfig = DynamicRecord;
export type CommandRegistry = DynamicRecord;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type DocumentOperation = [string, ...any[]];
/**
* Array of IDs, property names (strings), or indexes (integers) that identify a node or property in the document.
* String segments must follow the same path segment rules as NodeId.
*/
export type DocumentPath = Array<string | number>;
/**
* Text selection within a text property
*/
export type TextSelection = {
type: 'text';
path: DocumentPath;
anchor_offset: number;
focus_offset: number;
};
/**
* Node selection within a node array
*/
export type NodeSelection = {
type: 'node';
path: DocumentPath;
anchor_offset: number;
focus_offset: number;
};
/**
* Property selection within a node
*/
export type PropertySelection = {
type: 'property';
path: DocumentPath;
};
/**
* Union type for all possible selection types
*/
export type Selection = TextSelection | NodeSelection | PropertySelection;
/**
* Represents only the range (no direction and payload) of a NodeSelection or TextSelection
*/
export type SelectionRange = {
start_offset: number;
end_offset: number;
};
// ===== SCHEMA TYPE DEFINITIONS =====
/**
* Basic scalar types supported by the schema system.
*/
export type ScalarType = 'string' | 'number' | 'boolean' | 'integer' | 'datetime';
/**
* Array types for collections of scalar values.
*/
export type ArrayType = 'string_array' | 'number_array' | 'boolean_array' | 'integer_array';
/**
* Special types for rich content.
*/
export type RichType = 'text';
/**
* Node reference types for linking to other nodes.
*/
export type ReferenceType = 'node' | 'node_array';
/**
* All primitive types that can be used in property definitions.
*/
export type PrimitiveType = ScalarType | ArrayType | RichType;
/**
* All possible property types in schemas.
*/
export type PropertyType = PrimitiveType | ReferenceType;
// ===== SCHEMA-DERIVED NODE TYPES =====
/**
* Maps a schema property definition to the runtime value type it stores.
*/
export type PropertyValue<P extends PropertyDefinition> = P extends {
type: 'string';
values: readonly (infer Value extends string)[];
}
? Value
: P extends { type: 'string' }
? string
: P extends { type: 'number' }
? number
: P extends { type: 'integer' }
? number
: P extends { type: 'boolean' }
? boolean
: P extends { type: 'datetime' }
? string
: P extends { type: 'string_array' }
? string[]
: P extends { type: 'number_array' }
? number[]
: P extends { type: 'integer_array' }
? number[]
: P extends { type: 'boolean_array' }
? boolean[]
: P extends { type: 'text' }
? Text
: P extends { type: 'node' }
? NodeId
: P extends { type: 'node_array' }
? NodeArray
: never;
/**
* The runtime shape of a node of a specific type, derived from the schema.
*
* Falls back to the untyped DocumentNode when the schema is not a concrete
* schema literal (e.g. the DocumentSchema default of Session).
*/
export type NodeOfType<S extends DocumentSchema, T extends keyof S> = string extends keyof S
? DocumentNode
: {
id: NodeId;
type: T;
} & {
[K in keyof S[T]['properties']]: PropertyValue<S[T]['properties'][K]>;
};
/**
* Discriminated union of all node types in a schema. Narrow with
* `node.type === '...'` to get exact property types.
*
* Falls back to the untyped DocumentNode when the schema is not a concrete
* schema literal (e.g. the DocumentSchema default of Session).
*/
export type AnyNode<S extends DocumentSchema> = string extends keyof S
? DocumentNode
: {
[T in keyof S]: NodeOfType<S, T>;
}[keyof S];
/**
* Convenience map from node type name to its runtime node shape,
* e.g. `NodeMap<typeof document_schema>['story']`.
*/
export type NodeMap<S extends DocumentSchema> = {
[T in keyof S]: NodeOfType<S, T>;
};
/**
* Assert that a node is of the given type. Narrows the static type and
* checks at runtime — for call sites that know what they expect.
*/
/**
* A property that stores text with optional marks and annotations and required allow_newlines setting.
*/
export type TextProperty = {
type: 'text';
mark_types?: string[];
annotation_types?: string[];
allow_newlines: boolean;
};
/**
* A property that stores a string value.
*/
export type StringProperty = {
type: 'string';
default?: string;
values?: readonly string[];
};
/**
* A property that stores a number value.
*/
export type NumberProperty = {
type: 'number';
default?: number;
};
/**
* A property that stores a boolean value.
*/
export type BooleanProperty = {
type: 'boolean';
default?: boolean;
};
/**
* A property that stores an integer value.
*/
export type IntegerProperty = {
type: 'integer';
default?: number;
min?: number;
max?: number;
};
/**
* A property that stores a datetime value.
*/
export type DatetimeProperty = {
type: 'datetime';
default?: string;
};
/**
* A property that stores an array of strings.
*/
export type StringArrayProperty = {
type: 'string_array';
default?: string[];
};
/**
* A property that stores an array of numbers.
*/
export type NumberArrayProperty = {
type: 'number_array';
default?: number[];
};
/**
* A property that stores an array of booleans.
*/
export type BooleanArrayProperty = {
type: 'boolean_array';
default?: boolean[];
};
/**
* A property that stores an array of integers.
*/
export type IntegerArrayProperty = {
type: 'integer_array';
default?: number[];
};
/**
* A property that stores a primitive value (excluding text).
*/
export type PrimitiveProperty =
| StringProperty
| NumberProperty
| BooleanProperty
| IntegerProperty
| DatetimeProperty
| StringArrayProperty
| NumberArrayProperty
| BooleanArrayProperty
| IntegerArrayProperty;
/**
* A property that stores a reference to a single node.
*/
export type NodeProperty = {
type: 'node';
node_types: string[];
default_node_type?: string;
};
/**
* A property that stores an array of node references.
*/
export type NodeArrayProperty = {
type: 'node_array';
node_types: string[];
mark_types?: string[];
annotation_types?: string[];
default_node_type?: string;
};
/**
* Union type for all possible property definitions.
*/
export type PropertyDefinition =
PrimitiveProperty | TextProperty | NodeProperty | NodeArrayProperty;
/**
* Node kind values for different types of content nodes
*/
export type NodeKind = 'document' | 'block' | 'text' | 'mark' | 'annotation';
/**
* Schema for text nodes - must have a content property of type text.
* Use define_document_schema to also check that content is the only text property.
*/
export type TextNodeSchema = {
kind: 'text';
properties: {
content: TextProperty;
} & Record<string, PropertyDefinition>;
};
export type TextPropertyNames<Properties> = {
[PropertyName in keyof Properties]: Properties[PropertyName] extends { type: 'text' }
? PropertyName
: never;
}[keyof Properties];
export type TextNodeSchemaError<Message extends string> = {
[SchemaError in `Svedit schema error: ${Message}`]: never;
};
export type TextNodeMissingContentError =
TextNodeSchemaError<'Text node schemas must define a "content" property of type text.'>;
export type TextNodeExtraTextPropertyError<ExtraProperty extends string> =
TextNodeSchemaError<`Text node schemas must not define text property "${ExtraProperty}". Use "content" as the only text property.`>;
export type ValidateTextNodeSchema<Schema> = Schema extends {
kind: 'text';
properties: infer Properties;
}
? string extends keyof Properties
? Schema
: Properties extends { content: TextProperty }
? Exclude<TextPropertyNames<Properties>, 'content'> extends infer ExtraProperties
? [ExtraProperties] extends [never]
? Schema
: TextNodeExtraTextPropertyError<Extract<ExtraProperties, string>>
: never
: TextNodeMissingContentError
: Schema;
export type ValidateDocumentSchema<Schema extends Record<string, NodeSchema>> = {
[NodeType in keyof Schema]: ValidateTextNodeSchema<Schema[NodeType]>;
};
/**
* Schema for non-text nodes
*/
export type NonTextNodeSchema = {
kind: 'document' | 'block' | 'mark' | 'annotation';
properties: Record<string, PropertyDefinition>;
};
/**
* A node schema defines the structure of a specific node type.
* Contains a kind and properties object that maps property names to their definitions.
*/
export type NodeSchema = TextNodeSchema | NonTextNodeSchema;
/**
* A document schema defines all node types available in a document.
* Maps node type names to their schemas.
*/
export type DocumentSchema = Record<string, NodeSchema>;
/**
* A node in the document.
* Must have id and type properties, with other properties defined by the schema.
*/
export type DocumentNode = {
id: string;
type: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
export type Inspection = {
kind: 'property' | 'node';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
};
/**
* The document format - an object with document_id and nodes.
* The nodes object maps node IDs to their node data.
*/
export type Document = {
document_id: string;
nodes: {
[key: string]: DocumentNode;
};
};
/** Converts a document node to HTML, with access to recursive exporters. */
export type NodeHtmlExporter<S extends DocumentSchema = DocumentSchema> = (
node: DocumentNode,
session: Session<S>,
html_exporters: Record<string, NodeHtmlExporter<S>>
) => string;
/**
* Props for the TextProperty component
*/
export type TextPropertyProps = {
/** The full path to the property */
path: DocumentPath;
/** Optional custom HTML tag */
tag?: string;
/** The `class` attribute on the content element */
class?: string;
/** A placeholder to be rendered for empty content */
placeholder?: string;
/** Rest props to spread onto the rendered element (e.g. href, target, etc.) */
[key: string]: unknown;
};
/**
* Props for the CustomProperty component
*/
export type CustomPropertyProps = {
/** The full path to the property */
path: DocumentPath;
/** Optional custom HTML tag */
tag?: string;
/** The `class` attribute on the content element */
class?: string;
/** The content of the custom property (e.g. an image) */
children: Snippet;
/** Rest props to spread onto the rendered element */
[key: string]: unknown;
};
/**
* Props for the NodeArray component
*/
export type NodeArrayPropertyProps = {
/** The full path to the property */
path: DocumentPath;
/** Optional custom HTML tag */
tag?: string;
/** The `class` attribute on the container element */
class?: string;
/** Rest props to spread onto the rendered element (e.g. href, target, etc.) */
[key: string]: unknown;
};
/**
* Props passed to the consumer's `system_components.node_gap_tools`
* component. It renders inside the active node gap marker (the one carrying
* the caret), so it inherits the `--row` orientation variable and
* appears/disappears together with the caret. The marker has
* pointer-events: none — interactive tools must set pointer-events: auto.
*/
export type NodeGapToolsProps = {
/** The full path to the node_array the gap belongs to */
path: DocumentPath;
/** The insertion offset of the gap (0..count) */
offset: number;
/** True for the gap before the first node */
is_first: boolean;
/** True for the gap after the last node */
is_last: boolean;
};
/**
* Props for the Node component
*/
export type NodeProps = {
/** The full path to the node */
path: DocumentPath;
/** The single node-array mark wrapping this node, if any */
mark?: NodeArrayAttachmentContext | null;
/** All node-array annotations covering this node */
annotations?: Array<NodeArrayAttachmentContext>;
/** Optional custom HTML tag */
tag?: string;
/** Optional string of CSS classes */
class?: string;
/** The type-specific content of the node */
children: Snippet;
/** Rest props to spread onto the rendered element (e.g. href, target, etc.) */
[key: string]: unknown;
};
/**
* Props for the Svedit component
*/
export type SveditProps<S extends DocumentSchema = DocumentSchema> = {
/** The schema-typed session instance */
session: Session<S>;
/** Determines wether the document should be editable or read-only. */
editable?: boolean;
/** The path to the root element (e.g. ['page_1']) */
path: DocumentPath;
/** The `class` attribute on the canvas element */
class?: string;
/** The `autocapitalize` attribute on the canvas element */
autocapitalize?: 'on' | 'off';
/** The `spellcheck` attribute on the canvas element */
spellcheck?: 'true' | 'false';
};
/**
* Context provided by the Svedit component to commands and descendant components.
*/
export type SveditContext<S extends DocumentSchema = DocumentSchema> = {
session: Session<S>;
editable: boolean;
is_composing: boolean;
canvas_el: HTMLElement | undefined;
canvas_focused: boolean;
focus_canvas: () => void;
visibility_registry?: VisibilityRegistryApi;
};
/** Internal descendant context after Svedit's visibility registry is installed. */
export type SveditRenderContext<S extends DocumentSchema = DocumentSchema> = SveditContext<S> & {
visibility_registry: VisibilityRegistryApi;
};
/**
* A range with an attached payload node, used for both marks and annotations.
*/
export type Attachment = {
start_offset: number;
end_offset: number;
node_id: NodeId;
};
/**
* A content-level range (e.g. strong, emphasis, link, section).
* Marks are mutually exclusive within a property and render in-place.
*/
export type Mark = Attachment;
/**
* A metadata/overlay range (e.g. comment, marker).
* Annotations may overlap and are data-only.
*/
export type Annotation = Attachment;
/**
* Represents text content with marks and annotations.
*/
export type Text = {
content: string;
marks: Array<Mark>;
annotations: Array<Annotation>;
};
/**
* Represents a node array with nodes, marks and annotations.
*/
export type NodeArray = {
nodes: Array<NodeId>;
marks: Array<Mark>;
annotations: Array<Annotation>;
};
/**
* Attachment context passed to a node rendered inside a node array.
* It is the flattened mark or annotation attachment, enriched with the resolved
* payload node, its index in the parent attachment array, and this child node's
* position in the attachment range.
*/
export type NodeArrayAttachmentContext = {
start_offset: number;
end_offset: number;
node_id: NodeId;
index: number;
node: DocumentNode;
is_start: boolean;
is_middle: boolean;
is_end: boolean;
};
/** Internal context shared by a node array and its rendered child nodes. */
export type NodeArrayRenderContext = {
length: number;
mark_for: (node_index: number) => NodeArrayAttachmentContext | null;
annotations_for: (node_index: number) => NodeArrayAttachmentContext[];
};
/**
* Represents a selection highlight fragment for unmarked text selections
*/
export type SelectionHighlightFragment = {
type: 'selection_highlight';
content: string;
};
/**
* Represents a mark fragment in text content
*/
export type MarkFragment = {
type: 'mark';
/** NodeId that has mark type and details */
node: DocumentNode;
/** The text content of the mark */
content: string;
/** Index of the mark in the original array */
mark_index: number;
};
/**
* Represents a fragment of text content
*/
export type Fragment = string | MarkFragment | SelectionHighlightFragment;
/**
* Represents a node array fragment for plain nodes
*/
export type NodeArrayPlainFragment = {
type: 'nodes';
nodes: Array<NodeId>;
start_index: number;
};
/**
* Represents a mark fragment in node array content
*/
export type NodeArrayMarkFragment = {
type: 'mark';
/** NodeId that has mark type and details */
node: DocumentNode;
/** The nodes wrapped by the mark */
nodes: Array<NodeId>;
/** Start index in the original nodes array */
start_index: number;
/** Index of the mark in the original array */
mark_index: number;
};
/**
* Represents a fragment of node array content
*/
export type NodeArrayFragment = NodeArrayPlainFragment | NodeArrayMarkFragment;