1
+ /**
2
+ * Available types for generic elements that don't need special properties
3
+ */
4
+ export type GenericElementType =
5
+ | 'paragraph'
6
+ | 'heading-one'
7
+ | 'heading-two'
8
+ | 'heading-three'
9
+ | 'heading-four'
10
+ | 'heading-five'
11
+ | 'heading-six'
12
+ | 'bulleted-list'
13
+ | 'numbered-list'
14
+ | 'list-item'
15
+ | 'quote'
16
+ | 'code'
17
+ | 'pre'
18
+ | 'var'
19
+ | 'samp'
20
+ | 'div'
21
+ | 'richText'
22
+ | 'br'
23
+ | 'table'
24
+ | 'tbody'
25
+ | 'tr' ;
26
+
27
+ /**
28
+ * Base element properties shared by all element types
29
+ */
30
+ export interface BaseElement {
31
+ children : Node [ ] ;
32
+ class ?: string ; // allow headless CMS to pass CSS classes
33
+ [ key : string ] : unknown ; // custom attributes
34
+ }
35
+
36
+ /**
37
+ * Generic element for types that don't need special properties
38
+ */
39
+ export interface GenericElement extends BaseElement {
40
+ type : GenericElementType ;
41
+ }
42
+
43
+ /**
44
+ * Link element with required href (mapped from url)
45
+ */
46
+ export interface LinkElement extends BaseElement {
47
+ type : 'link' ;
48
+ url : string ; // Required for links - will be mapped to href
49
+ target ?: '_blank' | '_self' | '_parent' | '_top' ;
50
+ rel ?: string ;
51
+ title ?: string ;
52
+ }
53
+
54
+ /**
55
+ * Image element with required src (mapped from url)
56
+ */
57
+ export interface ImageElement extends BaseElement {
58
+ type : 'image' ;
59
+ url : string ; // Required for images - will be mapped to src
60
+ alt ?: string ;
61
+ title ?: string ;
62
+ width ?: number | string ;
63
+ height ?: number | string ;
64
+ loading ?: 'lazy' | 'eager' ;
65
+ }
66
+
67
+ /**
68
+ * Table cell elements with table-specific attributes
69
+ */
70
+ export interface TableCellElement extends BaseElement {
71
+ type : 'td' | 'th' ;
72
+ colspan ?: number ;
73
+ rowspan ?: number ;
74
+ scope ?: 'col' | 'row' | 'colgroup' | 'rowgroup' ;
75
+ }
76
+
77
+ /**
78
+ * Element node (blocks and inline elements) - Discriminated Union
79
+ * Based on Slate.js JSON structure with type-specific properties
80
+ */
81
+ export type Element =
82
+ | GenericElement
83
+ | LinkElement
84
+ | ImageElement
85
+ | TableCellElement ;
86
+
1
87
/**
2
88
* Text node with formatting marks
3
89
* Based on Slate.js JSON structure
@@ -12,18 +98,6 @@ export type Text = {
12
98
[ key : string ] : unknown ; // allow custom marks (e.g., highlight, color)
13
99
} ;
14
100
15
- /**
16
- * Element node (blocks and inline elements)
17
- * Based on Slate.js JSON structure
18
- */
19
- export type Element = {
20
- type : string ; // e.g., 'paragraph', 'heading-one', 'link', 'image'
21
- children : Node [ ] ;
22
- url ?: string ; // common on 'link', 'image', 'video'
23
- class ?: string ; // allow headless CMS to pass CSS classes
24
- [ key : string ] : unknown ; // custom attributes
25
- } ;
26
-
27
101
/**
28
102
* Union type for all possible nodes (text or element)
29
103
* Based on Slate.js JSON structure
@@ -44,25 +118,6 @@ export function isElement(node: Node): node is Element {
44
118
return ! isText ( node ) ;
45
119
}
46
120
47
- /**
48
- * Utility type to extract text content from Slate.js content
49
- */
50
- export type Content = Node [ ] ;
51
-
52
- /**
53
- * Utility type for working with specific element types
54
- */
55
- export type ElementOfType < T extends ElementType > = Element & {
56
- type : T ;
57
- } ;
58
-
59
- /**
60
- * Utility type for working with text nodes with specific marks
61
- */
62
- export type TextWithMark < T extends MarkType > = Text & {
63
- [ K in T ] : true ;
64
- } ;
65
-
66
121
/**
67
122
* Props for element renderer components (framework-agnostic)
68
123
*/
@@ -145,33 +200,9 @@ export interface RichTextPropsBase<
145
200
146
201
/**
147
202
* Available element types in the default implementation
203
+ * Derived from the actual Element discriminated union to ensure consistency
148
204
*/
149
- export type ElementType =
150
- | 'paragraph'
151
- | 'heading-one'
152
- | 'heading-two'
153
- | 'heading-three'
154
- | 'heading-four'
155
- | 'heading-five'
156
- | 'heading-six'
157
- | 'bulleted-list'
158
- | 'numbered-list'
159
- | 'list-item'
160
- | 'table'
161
- | 'tbody'
162
- | 'tr'
163
- | 'td'
164
- | 'th'
165
- | 'quote'
166
- | 'link'
167
- | 'image'
168
- | 'br'
169
- | 'code'
170
- | 'pre'
171
- | 'var'
172
- | 'samp'
173
- | 'div'
174
- | 'richText' ;
205
+ export type ElementType = Element [ 'type' ] ;
175
206
176
207
/**
177
208
* Available text marks in the default implementation
@@ -229,20 +260,34 @@ export function mapAttributes(node: Element): Record<string, unknown> {
229
260
nodeProps . class = node . class ;
230
261
}
231
262
232
- // Map URL-ish attributes based on common element semantics
233
- if ( 'url' in node ) {
234
- switch ( node . type ) {
235
- case 'link' :
236
- nodeProps . href = node . url ;
237
- break ;
238
- case 'image' :
239
- case 'video' :
240
- nodeProps . src = node . url ;
241
- break ;
242
- default :
263
+ // Map URL-ish attributes based on specific element types (type-safe)
264
+ switch ( node . type ) {
265
+ case 'link' :
266
+ nodeProps . href = node . url ;
267
+ if ( node . target ) nodeProps . target = node . target ;
268
+ if ( node . rel ) nodeProps . rel = node . rel ;
269
+ if ( node . title ) nodeProps . title = node . title ;
270
+ break ;
271
+ case 'image' :
272
+ nodeProps . src = node . url ;
273
+ if ( node . alt ) nodeProps . alt = node . alt ;
274
+ if ( node . title ) nodeProps . title = node . title ;
275
+ if ( node . width ) nodeProps . width = node . width ;
276
+ if ( node . height ) nodeProps . height = node . height ;
277
+ if ( node . loading ) nodeProps . loading = node . loading ;
278
+ break ;
279
+ case 'td' :
280
+ case 'th' :
281
+ if ( node . colspan ) nodeProps . colspan = node . colspan ;
282
+ if ( node . rowspan ) nodeProps . rowspan = node . rowspan ;
283
+ if ( node . scope ) nodeProps . scope = node . scope ;
284
+ break ;
285
+ default :
286
+ // For generic elements, check if they have a url and map it as data-url
287
+ if ( 'url' in node && node . url ) {
243
288
nodeProps [ 'data-url' ] = node . url ;
244
- break ;
245
- }
289
+ }
290
+ break ;
246
291
}
247
292
248
293
return nodeProps ;
@@ -274,6 +319,55 @@ export function extractTextContent(children: Node[]): string {
274
319
. join ( '' ) ;
275
320
}
276
321
322
+ /**
323
+ * Creates type-safe element data based on element type and attributes
324
+ * This is a utility function that can be used by framework-specific renderers
325
+ */
326
+ export function createElementData (
327
+ type : string ,
328
+ attributes : Record < string , unknown > = { }
329
+ ) : Element {
330
+ const baseProps = { children : [ ] , ...attributes } ;
331
+
332
+ switch ( type ) {
333
+ case 'link' :
334
+ return {
335
+ type : 'link' ,
336
+ url : ( attributes . url as string ) || ( attributes . href as string ) || '' ,
337
+ target : attributes . target as any ,
338
+ rel : attributes . rel as string ,
339
+ title : attributes . title as string ,
340
+ ...baseProps ,
341
+ } ;
342
+ case 'image' :
343
+ return {
344
+ type : 'image' ,
345
+ url : ( attributes . url as string ) || ( attributes . src as string ) || '' ,
346
+ alt : attributes . alt as string ,
347
+ title : attributes . title as string ,
348
+ width : attributes . width as number | string ,
349
+ height : attributes . height as number | string ,
350
+ loading : attributes . loading as 'lazy' | 'eager' ,
351
+ ...baseProps ,
352
+ } ;
353
+ case 'td' :
354
+ case 'th' :
355
+ return {
356
+ type : type as 'td' | 'th' ,
357
+ colspan : attributes . colspan as number ,
358
+ rowspan : attributes . rowspan as number ,
359
+ scope : attributes . scope as any ,
360
+ ...baseProps ,
361
+ } ;
362
+ default :
363
+ // For generic elements, we need to cast to the proper generic type
364
+ return {
365
+ type : type as any , // Type assertion for generic elements
366
+ ...baseProps ,
367
+ } as Element ;
368
+ }
369
+ }
370
+
277
371
/**
278
372
* Minimal HTML entity decoder to avoid extra deps
279
373
*/
0 commit comments