import { createSignal , createContext , createComputed , onMount , onCleanup , useContext , Component , JSX } from "solid-js" ; import { isServer , Show , Portal , Dynamic } from "solid-js/web" ; interface TagDescription { tag : string ; props : Record < string , unknown > ; } interface MetaContextType { addClientTag : ( tag : string , name : string ) => number ; shouldRenderTag : ( tag : string , index : number ) => boolean ; removeClientTag : ( tag : string , index : number ) => void ; addServerTag ? : ( tagDesc : TagDescription ) => void ; } const MetaContext = createContext < MetaContextType > ( ) ; const cascadingTags = [ "title" , "meta" ] ; const MetaProvider = props => { const indices = new Map ( ) , [ tags , setTags ] = createSignal < { [ k ] : ( string | null ) [ ] ; } > ( { } ) ; onMount ( ( ) => { const ssrTags = document . head . querySelectorAll ( `[data-sm=""]` ) ; Array . prototype . forEach . call ( ssrTags , ( ssrTag ) => ssrTag . parentNode ! . removeChild ( ssrTag ) ) ; } ) ; const actions = { addClientTag : ( tag , name ) => { if ( cascadingTags . indexOf ( tag ) !== - 1 ) { setTags ( tags => { const names = tags [ tag ] || [ ] ; return { ... tags , [ tag ] : [ ... names , name ] } ; } ) ; const index = indices . has ( tag ) ? indices . get ( tag ) + 1 : 0 ; indices . set ( tag , index ) ; return index ; } return - 1 ; } , shouldRenderTag : ( tag , index ) => { if ( cascadingTags . indexOf ( tag ) !== - 1 ) { const names = tags ( ) [ tag ] ; return names && names . lastIndexOf ( names [ index ] ) === index ; } return true ; } , removeClientTag : ( tag , index ) => { setTags ( tags => { const names = tags [ tag ] ; if ( names ) { names [ index ] = null ; return { ... tags , [ tag ] : names } ; } return tags ; } ) ; } } ; if ( isServer ) { actions . addServerTag = ( tagDesc ) => { const { tags = [ ] } = props ; if ( cascadingTags . indexOf ( tagDesc . tag ) !== - 1 ) { const index = tags . findIndex ( prev => { const prevName = prev . props . name || prev . props . property ; const nextName = tagDesc . props . name || tagDesc . props . property ; return prev . tag === tagDesc . tag && prevName === nextName ; } ) ; if ( index !== - 1 ) { tags . splice ( index , 1 ) ; } } tags . push ( tagDesc ) ; } ; if ( Array . isArray ( props . tags ) === false ) { throw Error ( "tags array should be passed to <MetaProvider /> in node" ) ; } } return < MetaContext . Provider value = { actions } > { props . children } < /MetaContext.Provider>; } ; const MetaTag = ( tag , props ) => { const c = useContext ( MetaContext ) ; if ( ! c ) throw new Error ( "<MetaProvider /> should be in the tree" ) ; const { addClientTag , removeClientTag , addServerTag , shouldRenderTag } = c ; let index = - 1 ; createComputed ( ( ) => { index = addClientTag ( tag , props . name || props . property ) ; onCleanup ( ( ) => removeClientTag ( tag , index ) ) ; } ) ; if ( isServer ) { addServerTag ! ( { tag , props } ) ; return null ; } return ( < Show when = { shouldRenderTag ( tag , index ) } > < Portal mount = { document . head } > < Dynamic component = { tag } { ... props } /> < /Portal> < /Show>); } ; export { MetaProvider } ; export function renderTags ( tags ) { return tags . map ( tag => { const keys = Object . keys ( tag . props ) ; const props = keys . map ( k => ( k === "children" ? "" : ` ${ k }="${ tag . props [ k ] }"` ) ) . join ( "" ) ; return tag . props . children ? `<${ tag . tag } data-sm=""${ props }>${ Array . isArray ( tag . props . children ) ? tag . props . children . join ( "" ) : tag . props . children }</${ tag . tag }>` : `<${ tag . tag } data-sm=""${ props }/>` ; } ) . join ( "" ) ; } export const Title = props => MetaTag ( "title" , props ) ; export const Style = props => MetaTag ( "style" , props ) ; export const Meta = props => MetaTag ( "meta" , props ) ; export const Link = props => MetaTag ( "link" , props ) ; export const Base = props => MetaTag ( "base" , props ) ;