diff --git a/src/index.tsx b/src/index.tsx index 122fe67..9bdc899 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,8 +19,8 @@ declare module "solid-js/web" { } } - interface TagDescription { + key?: string; tag: string; props: Record; setting?: { close?: boolean; escape?: boolean }; @@ -36,31 +36,10 @@ export interface MetaContextType { const cascadingTags = ["title", "meta"]; -// https://html.spec.whatwg.org/multipage/semantics.html#the-title-element -const titleTagProperties: string[] = []; - -const metaTagProperties: string[] = - // https://html.spec.whatwg.org/multipage/semantics.html#the-meta-element - ["name", "http-equiv", "content", "charset", "media"] - // additional properties - .concat(["property"]); - -const getTagKey = (tag: TagDescription, properties: string[]) => { - // pick allowed properties and sort them - const tagProps = Object.fromEntries( - Object.entries(tag.props) - .filter(([k]) => properties.includes(k)) - .sort() - ); - - // treat `property` as `name` for meta tags - if (Object.hasOwn(tagProps, "name") || Object.hasOwn(tagProps, "property")) { - tagProps.name = tagProps.name || tagProps.property; - delete tagProps.property; - } - - // concat tag name and properties as unique key for this tag - return tag.tag + JSON.stringify(tagProps); +const getTagKey = ({ tag, key }: TagDescription) => { + if (tag === "title") return tag; + if (tag === "meta" && key) return tag + "|" + key; + return false; }; function initClientProvider() { @@ -99,8 +78,8 @@ function initClientProvider() { return { addTag(tag: TagDescription) { if (cascadingTags.indexOf(tag.tag) !== -1) { - const properties = tag.tag === "title" ? titleTagProperties : metaTagProperties; - const tagKey = getTagKey(tag, properties); + const tagKey = getTagKey(tag); + if (!tagKey) return -1; // only cascading tags need to be kept as singletons if (!cascadedTagInstances.has(tagKey)) { @@ -150,11 +129,10 @@ function initClientProvider() { return -1; }, removeTag(tag: TagDescription, index: number) { - const properties = tag.tag === "title" ? titleTagProperties : metaTagProperties; - const tagKey = getTagKey(tag, properties); + const tagKey = getTagKey(tag); if (tag.ref) { - const t = cascadedTagInstances.get(tagKey); + const t = tagKey && cascadedTagInstances.get(tagKey); if (t) { if (tag.ref.parentNode) { tag.ref.parentNode.removeChild(tag.ref); @@ -185,11 +163,13 @@ function initServerProvider() { addTag(tagDesc: TagDescription) { // tweak only cascading tags if (cascadingTags.indexOf(tagDesc.tag) !== -1) { - const properties = tagDesc.tag === "title" ? titleTagProperties : metaTagProperties; - const tagDescKey = getTagKey(tagDesc, properties); - const index = tags.findIndex( - prev => prev.tag === tagDesc.tag && getTagKey(prev, properties) === tagDescKey - ); + const tagDescKey = getTagKey(tagDesc); + const index = tags.findIndex(prev => { + if (!tagDescKey) return false; + const prevKey = getTagKey(prev); + if (!prevKey) return false; + return prev.tag === tagDesc.tag && prevKey === tagDescKey; + }); if (index !== -1) { tags.splice(index, 1); } @@ -274,13 +254,20 @@ function renderTags(tags: Array) { .join(""); } +type KeyProp = { + /** + * If set, this element will override previous meta elements with the same `key` value. + * */ + key?: string; +}; + export const Title: Component> = props => MetaTag("title", props, { escape: true, close: true }); export const Style: Component> = props => MetaTag("style", props, { close: true }); -export const Meta: Component> = props => +export const Meta: Component & KeyProp> = props => MetaTag("meta", props); export const Link: Component> = props =>