@@ -246,16 +246,11 @@ export const patchSlotInsertAdjacentElement = (HostElementPrototype: HTMLElement
246
246
247
247
/**
248
248
* Patches the text content of an unnamed slotted node inside a scoped component
249
+ *
249
250
* @param hostElementPrototype the `Element` to be patched
250
251
*/
251
252
export const patchTextContent = ( hostElementPrototype : HTMLElement ) : void => {
252
- let descriptor = globalThis . Node && Object . getOwnPropertyDescriptor ( Node . prototype , 'textContent' ) ;
253
-
254
- if ( ! descriptor ) {
255
- // for mock-doc
256
- descriptor = Object . getOwnPropertyDescriptor ( hostElementPrototype , 'textContent' ) ;
257
- }
258
- if ( descriptor ) Object . defineProperty ( hostElementPrototype , '__textContent' , descriptor ) ;
253
+ patchHostOriginalAccessor ( 'textContent' , hostElementPrototype ) ;
259
254
260
255
Object . defineProperty ( hostElementPrototype , 'textContent' , {
261
256
get : function ( ) {
@@ -282,20 +277,7 @@ export const patchChildSlotNodes = (elm: HTMLElement) => {
282
277
}
283
278
}
284
279
285
- let childNodesFn = globalThis . Node && Object . getOwnPropertyDescriptor ( Node . prototype , 'childNodes' ) ;
286
- if ( ! childNodesFn ) {
287
- // for mock-doc
288
- childNodesFn = Object . getOwnPropertyDescriptor ( elm , 'childNodes' ) ;
289
- }
290
- if ( childNodesFn ) Object . defineProperty ( elm , '__childNodes' , childNodesFn ) ;
291
-
292
- let childrenFn = Object . getOwnPropertyDescriptor ( Element . prototype , 'children' ) ;
293
- if ( ! childrenFn ) {
294
- // for mock-doc
295
- childrenFn = Object . getOwnPropertyDescriptor ( elm , 'children' ) ;
296
- }
297
- if ( childrenFn ) Object . defineProperty ( elm , '__children' , childrenFn ) ;
298
-
280
+ patchHostOriginalAccessor ( 'children' , elm ) ;
299
281
Object . defineProperty ( elm , 'children' , {
300
282
get ( ) {
301
283
return this . childNodes . filter ( ( n : any ) => n . nodeType === 1 ) ;
@@ -308,8 +290,21 @@ export const patchChildSlotNodes = (elm: HTMLElement) => {
308
290
} ,
309
291
} ) ;
310
292
311
- if ( ! childNodesFn ) return ;
293
+ patchHostOriginalAccessor ( 'firstChild' , elm ) ;
294
+ Object . defineProperty ( elm , 'firstChild' , {
295
+ get ( ) {
296
+ return this . childNodes [ 0 ] ;
297
+ } ,
298
+ } ) ;
299
+
300
+ patchHostOriginalAccessor ( 'lastChild' , elm ) ;
301
+ Object . defineProperty ( elm , 'lastChild' , {
302
+ get ( ) {
303
+ return this . childNodes [ this . childNodes . length - 1 ] ;
304
+ } ,
305
+ } ) ;
312
306
307
+ patchHostOriginalAccessor ( 'childNodes' , elm ) ;
313
308
Object . defineProperty ( elm , 'childNodes' , {
314
309
get ( ) {
315
310
if (
@@ -327,12 +322,163 @@ export const patchChildSlotNodes = (elm: HTMLElement) => {
327
322
} ) ;
328
323
} ;
329
324
325
+ /// SLOTTED NODES ///
326
+
327
+ /**
328
+ * Patches sibling accessors of a 'slotted' node within a non-shadow component.
329
+ * Meaning whilst stepping through a non-shadow element's nodes, only the mock 'lightDOM' nodes are returned.
330
+ * Especially relevant when rendering components via SSR... Frameworks will often try to reconcile their
331
+ * VDOM with the real DOM by stepping through nodes with 'nextSibling' et al.
332
+ * - `nextSibling`
333
+ * - `nextElementSibling`
334
+ * - `previousSibling`
335
+ * - `previousElementSibling`
336
+ *
337
+ * @param node the slotted node to be patched
338
+ */
339
+ export const patchNextPrev = ( node : Node ) => {
340
+ if ( ! node || ( node as any ) . __nextSibling || ! globalThis . Node ) return ;
341
+
342
+ patchNextSibling ( node ) ;
343
+ patchPreviousSibling ( node ) ;
344
+
345
+ if ( node . nodeType === Node . ELEMENT_NODE ) {
346
+ patchNextElementSibling ( node as Element ) ;
347
+ patchPreviousElementSibling ( node as Element ) ;
348
+ }
349
+ } ;
350
+
351
+ /**
352
+ * Patches the `nextSibling` accessor of a non-shadow slotted node
353
+ *
354
+ * @param node the slotted node to be patched
355
+ * Required during during testing / mock environnement.
356
+ */
357
+ const patchNextSibling = ( node : Node ) => {
358
+ // already been patched? return
359
+ if ( ! node || ( node as any ) . __nextSibling ) return ;
360
+
361
+ patchHostOriginalAccessor ( 'nextSibling' , node ) ;
362
+ Object . defineProperty ( node , 'nextSibling' , {
363
+ get : function ( ) {
364
+ const parentNodes = this [ 's-ol' ] ?. parentNode . childNodes ;
365
+ const index = parentNodes ?. indexOf ( this ) ;
366
+ if ( parentNodes && index > - 1 ) {
367
+ return parentNodes [ index + 1 ] ;
368
+ }
369
+ return this . __nextSibling ;
370
+ } ,
371
+ } ) ;
372
+ } ;
373
+
374
+ /**
375
+ * Patches the `nextElementSibling` accessor of a non-shadow slotted node
376
+ *
377
+ * @param element the slotted element node to be patched
378
+ * Required during during testing / mock environnement.
379
+ */
380
+ const patchNextElementSibling = ( element : Element ) => {
381
+ if ( ! element || ( element as any ) . __nextElementSibling ) return ;
382
+
383
+ patchHostOriginalAccessor ( 'nextElementSibling' , element ) ;
384
+ Object . defineProperty ( element , 'nextElementSibling' , {
385
+ get : function ( ) {
386
+ const parentEles = this [ 's-ol' ] ?. parentNode . children ;
387
+ const index = parentEles ?. indexOf ( this ) ;
388
+ if ( parentEles && index > - 1 ) {
389
+ return parentEles [ index + 1 ] ;
390
+ }
391
+ return this . __nextElementSibling ;
392
+ } ,
393
+ } ) ;
394
+ } ;
395
+
396
+ /**
397
+ * Patches the `previousSibling` accessor of a non-shadow slotted node
398
+ *
399
+ * @param node the slotted node to be patched
400
+ * Required during during testing / mock environnement.
401
+ */
402
+ const patchPreviousSibling = ( node : Node ) => {
403
+ if ( ! node || ( node as any ) . __previousSibling ) return ;
404
+
405
+ patchHostOriginalAccessor ( 'previousSibling' , node ) ;
406
+ Object . defineProperty ( node , 'previousSibling' , {
407
+ get : function ( ) {
408
+ const parentNodes = this [ 's-ol' ] ?. parentNode . childNodes ;
409
+ const index = parentNodes ?. indexOf ( this ) ;
410
+ if ( parentNodes && index > - 1 ) {
411
+ return parentNodes [ index - 1 ] ;
412
+ }
413
+ return this . __previousSibling ;
414
+ } ,
415
+ } ) ;
416
+ } ;
417
+
418
+ /**
419
+ * Patches the `previousElementSibling` accessor of a non-shadow slotted node
420
+ *
421
+ * @param element the slotted element node to be patched
422
+ * Required during during testing / mock environnement.
423
+ */
424
+ const patchPreviousElementSibling = ( element : Element ) => {
425
+ if ( ! element || ( element as any ) . __previousElementSibling ) return ;
426
+
427
+ patchHostOriginalAccessor ( 'previousElementSibling' , element ) ;
428
+ Object . defineProperty ( element , 'previousElementSibling' , {
429
+ get : function ( ) {
430
+ const parentNodes = this [ 's-ol' ] ?. parentNode . children ;
431
+ const index = parentNodes ?. indexOf ( this ) ;
432
+
433
+ if ( parentNodes && index > - 1 ) {
434
+ return parentNodes [ index - 1 ] ;
435
+ }
436
+ return this . __previousElementSibling ;
437
+ } ,
438
+ } ) ;
439
+ } ;
440
+
330
441
/// UTILS ///
331
442
443
+ const validElementPatches = [ 'children' , 'nextElementSibling' , 'previousElementSibling' ] as const ;
444
+ const validNodesPatches = [
445
+ 'childNodes' ,
446
+ 'firstChild' ,
447
+ 'lastChild' ,
448
+ 'nextSibling' ,
449
+ 'previousSibling' ,
450
+ 'textContent' ,
451
+ ] as const ;
452
+
453
+ /**
454
+ * Patches a node or element; making it's original accessor method available under a new name.
455
+ * e.g. `nextSibling` -> `__nextSibling`
456
+ *
457
+ * @param accessorName - the name of the accessor to patch
458
+ * @param node - the node to patch
459
+ */
460
+ function patchHostOriginalAccessor (
461
+ accessorName : ( typeof validElementPatches ) [ number ] | ( typeof validNodesPatches ) [ number ] ,
462
+ node : Node ,
463
+ ) {
464
+ let accessor ;
465
+ if ( validElementPatches . includes ( accessorName as any ) ) {
466
+ accessor = Object . getOwnPropertyDescriptor ( Element . prototype , accessorName ) ;
467
+ } else if ( validNodesPatches . includes ( accessorName as any ) ) {
468
+ accessor = Object . getOwnPropertyDescriptor ( Node . prototype , accessorName ) ;
469
+ }
470
+ if ( ! accessor ) {
471
+ // for mock-doc
472
+ accessor = Object . getOwnPropertyDescriptor ( node , accessorName ) ;
473
+ }
474
+ if ( accessor ) Object . defineProperty ( node , '__' + accessorName , accessor ) ;
475
+ }
476
+
332
477
/**
333
478
* Creates an empty text node to act as a forwarding address to a slotted node:
334
479
* 1) When non-shadow components re-render, they need a place to temporarily put 'lightDOM' elements.
335
480
* 2) Patched dom methods and accessors use this node to calculate what 'lightDOM' nodes are in the host.
481
+ *
336
482
* @param newChild a node that's going to be added to the component
337
483
* @param slotNode the slot node that the node will be added to
338
484
* @param prepend move the slotted location node to the beginning of the host
@@ -387,6 +533,7 @@ export const addSlotRelocateNode = (
387
533
* Get's the child nodes of a component that are actually slotted.
388
534
* This is only required until all patches are unified
389
535
* either under 'experimentalSlotFixes' or on by default
536
+ *
390
537
* @param childNodes all 'internal' child nodes of the component
391
538
* @returns An array of slotted reference nodes.
392
539
*/
@@ -406,6 +553,7 @@ const getSlotName = (node: d.RenderNode) =>
406
553
407
554
/**
408
555
* Recursively searches a series of child nodes for a slot with the provided name.
556
+ *
409
557
* @param childNodes the nodes to search for a slot with a specific name.
410
558
* @param slotName the name of the slot to match on.
411
559
* @param hostName the host name of the slot to match on.
0 commit comments