@@ -10,7 +10,11 @@ import { regex_valid_component_name } from '../phases/1-parse/state/element.js';
10
10
import { analyze_component } from '../phases/2-analyze/index.js' ;
11
11
import { get_rune } from '../phases/scope.js' ;
12
12
import { reset , reset_warning_filter } from '../state.js' ;
13
- import { extract_identifiers , extract_all_identifiers_from_expression } from '../utils/ast.js' ;
13
+ import {
14
+ extract_identifiers ,
15
+ extract_all_identifiers_from_expression ,
16
+ is_text_attribute
17
+ } from '../utils/ast.js' ;
14
18
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js' ;
15
19
import { validate_component_options } from '../validate-options.js' ;
16
20
import { is_svg , is_void } from '../../utils.js' ;
@@ -711,7 +715,8 @@ const template = {
711
715
Identifier ( node , { state, path } ) {
712
716
handle_identifier ( node , state , path ) ;
713
717
} ,
714
- RegularElement ( node , { state, next } ) {
718
+ RegularElement ( node , { state, path, next } ) {
719
+ migrate_slot_usage ( node , path , state ) ;
715
720
handle_events ( node , state ) ;
716
721
// Strip off any namespace from the beginning of the node name.
717
722
const node_name = node . name . replace ( / [ a - z A - Z - ] * : / g, '' ) ;
@@ -724,7 +729,9 @@ const template = {
724
729
}
725
730
next ( ) ;
726
731
} ,
727
- SvelteElement ( node , { state, next } ) {
732
+ SvelteElement ( node , { state, path, next } ) {
733
+ migrate_slot_usage ( node , path , state ) ;
734
+
728
735
if ( node . tag . type === 'Literal' ) {
729
736
let is_static = true ;
730
737
@@ -748,9 +755,15 @@ const template = {
748
755
handle_events ( node , state ) ;
749
756
next ( ) ;
750
757
} ,
758
+ Component ( node , { state, path, next } ) {
759
+ next ( ) ;
760
+ migrate_slot_usage ( node , path , state ) ;
761
+ } ,
751
762
SvelteComponent ( node , { state, next, path } ) {
752
763
next ( ) ;
753
764
765
+ migrate_slot_usage ( node , path , state ) ;
766
+
754
767
let expression = state . str
755
768
. snip (
756
769
/** @type {number } */ ( node . expression . start ) ,
@@ -789,7 +802,7 @@ const template = {
789
802
state . str . original . lastIndexOf ( '\n' , position ) + 1 ,
790
803
position
791
804
) ;
792
- state . str . prependLeft (
805
+ state . str . appendRight (
793
806
position ,
794
807
`{@const ${ expression } = ${ current_expression } }\n${ indent } `
795
808
) ;
@@ -816,6 +829,10 @@ const template = {
816
829
const end_pos = state . str . original . indexOf ( '}' , node . expression . end ) + 1 ;
817
830
state . str . remove ( this_pos , end_pos ) ;
818
831
} ,
832
+ SvelteFragment ( node , { state, path, next } ) {
833
+ migrate_slot_usage ( node , path , state ) ;
834
+ next ( ) ;
835
+ } ,
819
836
SvelteWindow ( node , { state, next } ) {
820
837
handle_events ( node , state ) ;
821
838
next ( ) ;
@@ -828,7 +845,9 @@ const template = {
828
845
handle_events ( node , state ) ;
829
846
next ( ) ;
830
847
} ,
831
- SlotElement ( node , { state, next, visit } ) {
848
+ SlotElement ( node , { state, path, next, visit } ) {
849
+ migrate_slot_usage ( node , path , state ) ;
850
+
832
851
if ( state . analysis . custom_element ) return ;
833
852
let name = 'children' ;
834
853
let slot_name = 'default' ;
@@ -915,6 +934,129 @@ const template = {
915
934
}
916
935
} ;
917
936
937
+ /**
938
+ * @param {AST.RegularElement | AST.SvelteElement | AST.SvelteComponent | AST.Component | AST.SlotElement | AST.SvelteFragment } node
939
+ * @param {SvelteNode[] } path
940
+ * @param {State } state
941
+ */
942
+ function migrate_slot_usage ( node , path , state ) {
943
+ const parent = path . at ( - 2 ) ;
944
+ // Bail on custom element slot usage
945
+ if (
946
+ parent ?. type !== 'Component' &&
947
+ parent ?. type !== 'SvelteComponent' &&
948
+ node . type !== 'Component' &&
949
+ node . type !== 'SvelteComponent'
950
+ ) {
951
+ return ;
952
+ }
953
+
954
+ let snippet_name = 'children' ;
955
+ let snippet_props = [ ] ;
956
+
957
+ for ( let attribute of node . attributes ) {
958
+ if (
959
+ attribute . type === 'Attribute' &&
960
+ attribute . name === 'slot' &&
961
+ is_text_attribute ( attribute )
962
+ ) {
963
+ snippet_name = attribute . value [ 0 ] . data ;
964
+ state . str . remove ( attribute . start , attribute . end ) ;
965
+ }
966
+ if ( attribute . type === 'LetDirective' ) {
967
+ snippet_props . push (
968
+ attribute . name +
969
+ ( attribute . expression
970
+ ? `: ${ state . str . original . substring ( /** @type {number } */ ( attribute . expression . start ) , /** @type {number } */ ( attribute . expression . end ) ) } `
971
+ : '' )
972
+ ) ;
973
+ state . str . remove ( attribute . start , attribute . end ) ;
974
+ }
975
+ }
976
+
977
+ if ( node . type === 'SvelteFragment' && node . fragment . nodes . length > 0 ) {
978
+ // remove node itself, keep content
979
+ state . str . remove ( node . start , node . fragment . nodes [ 0 ] . start ) ;
980
+ state . str . remove ( node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end , node . end ) ;
981
+ }
982
+
983
+ const props = snippet_props . length > 0 ? `{ ${ snippet_props . join ( ', ' ) } }` : '' ;
984
+
985
+ if ( snippet_name === 'children' && node . type !== 'SvelteFragment' ) {
986
+ if ( snippet_props . length === 0 ) return ; // nothing to do
987
+
988
+ let inner_start = 0 ;
989
+ let inner_end = 0 ;
990
+ for ( let i = 0 ; i < node . fragment . nodes . length ; i ++ ) {
991
+ const inner = node . fragment . nodes [ i ] ;
992
+ const is_empty_text = inner . type === 'Text' && ! inner . data . trim ( ) ;
993
+
994
+ if (
995
+ ( inner . type === 'RegularElement' ||
996
+ inner . type === 'SvelteElement' ||
997
+ inner . type === 'Component' ||
998
+ inner . type === 'SvelteComponent' ||
999
+ inner . type === 'SlotElement' ||
1000
+ inner . type === 'SvelteFragment' ) &&
1001
+ inner . attributes . some ( ( attr ) => attr . type === 'Attribute' && attr . name === 'slot' )
1002
+ ) {
1003
+ if ( inner_start && ! inner_end ) {
1004
+ // End of default slot content
1005
+ inner_end = inner . start ;
1006
+ }
1007
+ } else if ( ! inner_start && ! is_empty_text ) {
1008
+ // Start of default slot content
1009
+ inner_start = inner . start ;
1010
+ } else if ( inner_end && ! is_empty_text ) {
1011
+ // There was default slot content before, then some named slot content, now some default slot content again.
1012
+ // We're moving the last character back by one to avoid the closing {/snippet} tag inserted afterwards
1013
+ // to come before the opening {#snippet} tag of the named slot.
1014
+ state . str . update ( inner_end - 1 , inner_end , '' ) ;
1015
+ state . str . prependLeft ( inner_end - 1 , state . str . original [ inner_end - 1 ] ) ;
1016
+ state . str . move ( inner . start , inner . end , inner_end - 1 ) ;
1017
+ }
1018
+ }
1019
+
1020
+ if ( ! inner_end ) {
1021
+ inner_end = node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end ;
1022
+ }
1023
+
1024
+ state . str . appendLeft (
1025
+ inner_start ,
1026
+ `{#snippet ${ snippet_name } (${ props } )}\n${ state . indent . repeat ( path . length ) } `
1027
+ ) ;
1028
+ state . str . indent ( state . indent , {
1029
+ exclude : [
1030
+ [ 0 , inner_start ] ,
1031
+ [ inner_end , state . str . original . length ]
1032
+ ]
1033
+ } ) ;
1034
+ if ( inner_end < node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end ) {
1035
+ // Named slots coming afterwards
1036
+ state . str . prependLeft ( inner_end , `{/snippet}\n${ state . indent . repeat ( path . length ) } ` ) ;
1037
+ } else {
1038
+ // No named slots coming afterwards
1039
+ state . str . prependLeft (
1040
+ inner_end ,
1041
+ `${ state . indent . repeat ( path . length ) } {/snippet}\n${ state . indent . repeat ( path . length - 1 ) } `
1042
+ ) ;
1043
+ }
1044
+ } else {
1045
+ // Named slot or `svelte:fragment`: wrap element itself in a snippet
1046
+ state . str . prependLeft (
1047
+ node . start ,
1048
+ `{#snippet ${ snippet_name } (${ props } )}\n${ state . indent . repeat ( path . length - 2 ) } `
1049
+ ) ;
1050
+ state . str . indent ( state . indent , {
1051
+ exclude : [
1052
+ [ 0 , node . start ] ,
1053
+ [ node . end , state . str . original . length ]
1054
+ ]
1055
+ } ) ;
1056
+ state . str . appendLeft ( node . end , `\n${ state . indent . repeat ( path . length - 2 ) } {/snippet}` ) ;
1057
+ }
1058
+ }
1059
+
918
1060
/**
919
1061
* @param {VariableDeclarator } declarator
920
1062
* @param {MagicString } str
0 commit comments