@lomray/react-head-manager 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./context.js";
2
+ export { default as TagStatus } from "./tag-status.js";
2
3
  export { default as Manager } from "./manager.js";
package/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export{Meta,MetaManagerProvider,useMetaManager}from"./context.js";export{default as Manager}from"./manager.js";
1
+ export{Meta,MetaManagerProvider,useMetaManager}from"./context.js";export{default as TagStatus}from"./tag-status.js";export{default as Manager}from"./manager.js";
2
2
  //# sourceMappingURL=index.js.map
package/manager.d.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  /// <reference types="node" />
2
2
  import { ReactElement, ReactNode } from 'react';
3
- declare enum TagStatus {
4
- init = "init",
5
- synced = "synced",
6
- drain = "drain"
7
- }
3
+ import TagStatus from "./tag-status.js";
8
4
  interface IMetaManagerTags {
9
- html: Map<string, Record<string, any>>;
10
- body: Map<string, Record<string, any>>;
5
+ html: Map<string, {
6
+ props: Record<string, any>;
7
+ order: number;
8
+ }>;
9
+ body: Map<string, {
10
+ props: Record<string, any>;
11
+ order: number;
12
+ }>;
11
13
  meta: Map<string, {
12
14
  element?: ReactElement;
13
15
  domElement?: HTMLElement;
@@ -74,6 +76,15 @@ declare class Manager {
74
76
  * Remove children, dangerouslySetInnerHTML etc.
75
77
  */
76
78
  static cleanupElementProps(props: Record<string, any>): Record<string, any>;
79
+ /**
80
+ * Sort elements or props
81
+ */
82
+ /**
83
+ * Sort elements or props
84
+ */
85
+ protected sortTags<T extends Map<string, {
86
+ order: number;
87
+ }>>(elements: T): T;
77
88
  /**
78
89
  * Get meta tags
79
90
  */
@@ -126,6 +137,13 @@ declare class Manager {
126
137
  element: ReactElement;
127
138
  elementProps: Record<string, any>;
128
139
  };
140
+ /**
141
+ * Get element order
142
+ */
143
+ /**
144
+ * Get element order
145
+ */
146
+ protected getElementOrder(elementProps: Record<string, any>, type: string, key?: string): number;
129
147
  /**
130
148
  * Push react elements to meta state
131
149
  */
package/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import e from"html-react-parser";import t,{Children as s}from"react";var r;!function(e){e.init="init",e.synced="synced",e.drain="drain"}(r||(r={}));class i{static rootContainerId="root";isServer="undefined"==typeof window;tags;tagsDefinitions={"meta[charset]":{order:10},"meta[name='viewport']":{order:20},title:{order:100},base:{order:110},meta:{order:200},link:{order:300},script:{order:400},noscript:{order:500},style:{order:600}};reservedAttributes={order:"data-order"};syncTimerId=null;constructor(e={}){this.tags={html:new Map(e?.html),body:new Map(e?.body),meta:new Map(e?.meta),containers:new Set(e?.containers)}}setTagsDefinitions(e){this.tagsDefinitions={...this.tagsDefinitions,...e}}static cleanupElementProps(e){const{children:t,dangerouslySetInnerHTML:s,...r}=e;return r}getTags(){const{meta:e,body:t,html:s,containers:r}=this.tags;return{body:t,html:s,meta:new Map([...e.entries()].sort((([,e],[,t])=>e.order-t.order))),containers:r}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:s}=e,r=document.createElement(t);return this.applyDomElementAttributes(r,s),r}buildKeyByProps(e,t={},s=!1){let r="";for(const e of["id","name","href","src"])if(t[e]){r=`[${e}='${t[e]}']`;break}if(r||s||(r=Object.entries(t).map((([e,t])=>"string"==typeof t&&`[${e}]`)).filter(Boolean).join("")),r)return`${e}${r}`}getDefaultKey(e,t,s){return`${e}-${t}-${s}-not-unique`}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){const{type:s}=e,r={...e?.props??{}};return Object.values(this.reservedAttributes).forEach((e=>{r[e]&&delete r[e]})),"title"===s&&Array.isArray(r.children)&&(r.children=r.children.join("")),{element:t.createElement(s,r),elementProps:r}}pushElements(e,t,n=!0,o=r.init){s.forEach(e,((e,s)=>{if(!e||"object"!=typeof e||!("type"in e)||!e.type)return;const{type:a}=e,{element:c,elementProps:d}=this.cloneElement(e);let h=(d[this.reservedAttributes.order]?Number(d[this.reservedAttributes.order]):void 0)??this.tagsDefinitions[a]?.order??1e3,l=this.tagsDefinitions[a]?.key??a;switch(a){case"title":break;case"meta":const{charSet:e,httpEquiv:r}=d;l=e?"meta[charset]":r?"meta[httpEquiv]":this.buildKeyByProps(a,d);break;case"html":case"body":if(!n&&this.tags[a].has(l))return;return void this.tags[a].set(t,i.cleanupElementProps(d));default:l=this.buildKeyByProps(a,d,!0)??this.getDefaultKey(a,t,s)}!n&&this.tags.meta.has(l)||this.isNotUniqueTag(l)&&this.tags.meta.get(l)?.status===r.synced||(this.tagsDefinitions[l]?.order&&(h=this.tagsDefinitions[l]?.order),this.tags.meta.set(l,{element:this.isServer?c:void 0,domElement:t===i.rootContainerId?void 0:this.createDomElement(c),order:h,containerId:t,status:o}))})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t})),{}):{}}applyDomElementAttributes(e,t={}){const s=e.tagName.toLowerCase(),r=Object.values(this.reservedAttributes);Object.entries(t).forEach((([t,i])=>{switch(t){case"children":return void(e.innerHTML=i);case"style":return Object.entries(i).forEach((([t,s])=>{e.style[t]=s}))}r[t]||e.setAttribute(this.replaceAttribute(s,t),i)}))}syncMeta(e){const{meta:t,html:s,body:i}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:s},{name:"body",value:i}]){const s=this.getRootTagProps(t),r=document.querySelector(e);(r?.getAttributeNames()??[]).forEach((e=>{s[e]||r.removeAttribute(e)})),Object.keys(s).length&&r&&this.applyDomElementAttributes(r,s)}if(!t.size||this.isServer)return;const n=document.getElementsByTagName("head")?.[0];let o;t.forEach(((s,i)=>{const{status:a,domElement:c}=s;if(a===r.synced||!c||e&&e!==s.containerId)return void(o=c);if(a===r.drain)return o=c.previousSibling,c?.remove(),void t.delete(i);if(s.status=r.synced,!this.isNotUniqueTag(i)){const e=document.querySelector(i);if(e)return o=c,void e.replaceWith(c)}const d=o&&o.nextSibling;d?n.insertBefore(c,d):n.append(c),o=c}))}pushTags(e,t,s=!0){const i=this.tags.containers.has(t);this.pushElements(e,t,s,i?r.synced:r.init),this.isServer||i||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,s)=>{if(t.containerId===e&&t.containerId!==i.rootContainerId)return this.isNotUniqueTag(s)?(t.domElement?.remove(),void this.tags.meta.delete(s)):void(t.status=r.drain)})),e!==i.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e),this.syncTimerId=setTimeout((()=>this.syncMeta()),500))}analyzeClientHead(){if(this.isServer)return;for(const t of["html","body"]){const s=document.getElementsByTagName(t)?.[0].cloneNode(!1)?.outerHTML;this.pushElements(e(s),i.rootContainerId,!1)}const t=document.getElementsByTagName("head")?.[0],s=e(t?.innerHTML??"");this.pushElements(s,i.rootContainerId,!1,r.synced);const n=[...this.tags.meta.values()];t.childNodes.forEach(((e,t)=>{const s=n[t];s&&(s.domElement=e)}))}}export{i as default};
1
+ import e from"html-react-parser";import t,{Fragment as r,Children as s}from"react";import i from"./tag-status.js";class n{static rootContainerId="root";isServer="undefined"==typeof window;tags;tagsDefinitions={"meta[charset]":{order:10},"meta[name='viewport']":{order:20},title:{order:100},base:{order:110},meta:{order:200},link:{order:300},script:{order:400},noscript:{order:500},style:{order:600}};reservedAttributes={order:"data-order"};syncTimerId=null;constructor(e={}){this.tags={html:new Map(e?.html),body:new Map(e?.body),meta:new Map(e?.meta),containers:new Set(e?.containers)}}setTagsDefinitions(e){this.tagsDefinitions={...this.tagsDefinitions,...e}}static cleanupElementProps(e){const{children:t,dangerouslySetInnerHTML:r,...s}=e;return s}sortTags(e){return new Map([...e.entries()].sort((([,e],[,t])=>e.order-t.order)))}getTags(){const{meta:e,body:t,html:r,containers:s}=this.tags;return{html:this.sortTags(r),body:this.sortTags(t),meta:this.sortTags(e),containers:s}}replaceAttribute(e,t){return"meta"===e&&"httpequiv"===t.toLowerCase()?"http-equiv":t}createDomElement(e){if(this.isServer)return;const{type:t,props:r}=e,s=document.createElement(t);return this.applyDomElementAttributes(s,r),s}buildKeyByProps(e,t={},r=!1){let s="";for(const e of["id","name","href","src"])if(t[e]){s=`[${e}='${t[e]}']`;break}if(s||r||(s=Object.entries(t).map((([e,t])=>"string"==typeof t&&`[${e}]`)).filter(Boolean).join("")),s)return`${e}${s}`}getDefaultKey(e,t,r){return`${e}-${t}-${r}-not-unique`}isNotUniqueTag(e){return e.endsWith("-not-unique")}cloneElement(e){const{type:r}=e,s={...e?.props??{}};return Object.values(this.reservedAttributes).forEach((e=>{s[e]&&delete s[e]})),"title"===r&&Array.isArray(s.children)&&(s.children=s.children.join("")),{element:t.createElement(r,s),elementProps:s}}getElementOrder(e,t,r="unknown"){return(e[this.reservedAttributes.order]?Number(e[this.reservedAttributes.order]):void 0)??this.tagsDefinitions[r]?.order??this.tagsDefinitions[t]?.order??1e3}pushElements(e,t,o=!0,a=i.init){const d=e&&"object"==typeof e&&"type"in e&&e.type===r?e.props.children:e;s.forEach(d,((e,r)=>{if(!e||"object"!=typeof e||!("type"in e)||!e.type||"string"!=typeof e.type)return;const{type:s}=e,{element:d,elementProps:c}=this.cloneElement(e);let h=this.tagsDefinitions[s]?.key??s;switch(s){case"title":break;case"meta":const{charSet:e,httpEquiv:i}=c;h=e?"meta[charset]":i?"meta[httpEquiv]":this.buildKeyByProps(s,c);break;case"html":case"body":if(!o&&this.tags[s].has(h))return;return void this.tags[s].set(t,{props:n.cleanupElementProps(c),order:t===n.rootContainerId?1:this.getElementOrder(c,s,h)});default:h=this.buildKeyByProps(s,c,!0)??this.getDefaultKey(s,t,r)}!o&&this.tags.meta.has(h)||this.isNotUniqueTag(h)&&this.tags.meta.get(h)?.status===i.synced||this.tags.meta.set(h,{element:this.isServer?d:void 0,domElement:t===n.rootContainerId?void 0:this.createDomElement(d),order:this.getElementOrder(c,s,h),containerId:t,status:a})})),this.tags.containers.add(t)}getRootTagProps(e){return e.size?[...e.values()].reduce(((e,t)=>({...e,...t.props})),{}):{}}applyDomElementAttributes(e,t={}){const r=e.tagName.toLowerCase(),s=Object.values(this.reservedAttributes);Object.entries(t).forEach((([t,i])=>{switch(t){case"children":return void(e.innerHTML=i);case"style":return Object.entries(i).forEach((([t,r])=>{e.style[t]=r}))}s.includes(t)||e.setAttribute(this.replaceAttribute(r,t),i)}))}syncMeta(e){const{meta:t,html:r,body:s}=this.getTags();this.syncTimerId&&clearTimeout(this.syncTimerId);for(const{name:e,value:t}of[{name:"html",value:r},{name:"body",value:s}]){const r=this.getRootTagProps(t),s=document.querySelector(e);(s?.getAttributeNames()??[]).forEach((e=>{r[e]||s.removeAttribute(e)})),Object.keys(r).length&&s&&this.applyDomElementAttributes(s,r)}if(!t.size||this.isServer)return;const n=document.getElementsByTagName("head")?.[0];let o;t.forEach(((r,s)=>{const{status:a,domElement:d}=r;if(a===i.synced||!d||e&&e!==r.containerId)return void(o=d);if(a===i.drain)return o=d.previousSibling,d?.remove(),void t.delete(s);if(r.status=i.synced,!this.isNotUniqueTag(s)){const e=document.querySelector(s);if(e)return o=d,void e.replaceWith(d)}const c=o&&o.nextSibling;c?n.insertBefore(d,c):n.append(d),o=d}))}pushTags(e,t,r=!0){const s=this.tags.containers.has(t);this.pushElements(e,t,r,s?i.synced:i.init),this.isServer||s||this.syncMeta()}removeTags(e){this.tags.meta.forEach(((t,r)=>{if(t.containerId===e&&t.containerId!==n.rootContainerId)return this.isNotUniqueTag(r)?(t.domElement?.remove(),void this.tags.meta.delete(r)):void(t.status=i.drain)})),e!==n.rootContainerId&&(this.tags.body.delete(e),this.tags.html.delete(e),this.tags.containers.delete(e),this.syncTimerId=setTimeout((()=>this.syncMeta()),500))}analyzeClientHead(){if(this.isServer)return;for(const t of["html","body"]){const r=document.getElementsByTagName(t)?.[0].cloneNode(!1)?.outerHTML;this.pushElements(e(r),n.rootContainerId,!1)}const t=document.getElementsByTagName("head")?.[0],r=e(t?.innerHTML??"");this.pushElements(r,n.rootContainerId,!1,i.synced);const s=[...this.tags.meta.values()];t.childNodes.forEach(((e,t)=>{const r=s[t];r&&(r.domElement=e)}))}}export{n as default};
2
2
  //# sourceMappingURL=manager.js.map
package/manager.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sources":["../src/manager.ts"],"sourcesContent":["import htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Children } from 'react';\n\nenum TagStatus {\n init = 'init',\n synced = 'synced',\n drain = 'drain',\n}\n\nexport interface IMetaManagerTags {\n html: Map<string, Record<string, any>>; // containerId => styles\n body: Map<string, Record<string, any>>; // containerId => styles\n meta: Map<\n string,\n {\n element?: ReactElement;\n domElement?: HTMLElement;\n order: number;\n containerId: string;\n status: TagStatus;\n }\n >;\n containers: Set<string>;\n}\n\nexport interface IMetaManagerTagsDefinitions {\n [type: string]: {\n key?: string;\n order: number;\n };\n}\n\n/**\n * Meta tags manager\n */\nclass Manager {\n /**\n * Root container id\n */\n public static rootContainerId = 'root';\n\n /**\n * Detect server side\n */\n public isServer = typeof window === 'undefined';\n\n /**\n * Meta tags state\n */\n protected tags: IMetaManagerTags;\n\n /**\n * Tags definitions\n */\n protected tagsDefinitions: IMetaManagerTagsDefinitions = {\n 'meta[charset]': {\n order: 10,\n },\n \"meta[name='viewport']\": {\n order: 20,\n },\n title: {\n order: 100,\n },\n base: {\n order: 110,\n },\n meta: {\n order: 200,\n },\n link: {\n order: 300,\n },\n script: {\n order: 400,\n },\n noscript: {\n order: 500,\n },\n style: {\n order: 600,\n },\n };\n\n /**\n * System tag attributes\n */\n protected reservedAttributes = {\n order: 'data-order',\n };\n\n /**\n * Scheduled synchronization\n */\n protected syncTimerId: null | NodeJS.Timeout = null;\n\n /**\n * @constructor\n */\n constructor(tags: Partial<Manager['tags']> = {}) {\n this.tags = {\n html: new Map(tags?.html),\n body: new Map(tags?.body),\n meta: new Map(tags?.meta),\n containers: new Set(tags?.containers),\n };\n }\n\n /**\n * Set tags definitions\n */\n public setTagsDefinitions(definitions: IMetaManagerTagsDefinitions): void {\n this.tagsDefinitions = { ...this.tagsDefinitions, ...definitions };\n }\n\n /**\n * Remove children, dangerouslySetInnerHTML etc.\n */\n public static cleanupElementProps(props: Record<string, any>): Record<string, any> {\n const { children: _, dangerouslySetInnerHTML: __, ...restProps } = props;\n\n return restProps;\n }\n\n /**\n * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n const sortedTags = new Map(\n [...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n );\n\n return {\n body,\n html,\n meta: sortedTags,\n containers,\n };\n }\n\n /**\n * Replace react attribute to valid DOM attribute\n */\n protected replaceAttribute(tagName: string, attribute: string): string {\n if (tagName === 'meta' && attribute.toLowerCase() === 'httpequiv') {\n return 'http-equiv';\n }\n\n return attribute;\n }\n\n /**\n * Create DOM element from React element\n */\n protected createDomElement(element: ReactElement): HTMLElement | undefined {\n if (this.isServer) {\n return;\n }\n\n const { type, props } = element;\n const domElement = document.createElement(type as string);\n\n this.applyDomElementAttributes(domElement, props as Record<string, string>);\n\n return domElement;\n }\n\n /**\n * Make element key by element props\n */\n protected buildKeyByProps(\n type: string,\n props: Record<string, any> = {},\n isSkipProps = false,\n ): string | undefined {\n let key = '';\n\n // try to build unique key by unique props\n for (const uniqueAttr of ['id', 'name', 'href', 'src']) {\n if (props[uniqueAttr]) {\n key = `[${uniqueAttr}='${props[uniqueAttr] as string}']`;\n\n break;\n }\n }\n\n // try to build by props\n if (!key && !isSkipProps) {\n key = Object.entries(props)\n .map(([k, v]) => {\n if (typeof v === 'string') {\n return `[${k}]`;\n }\n\n return false;\n })\n .filter(Boolean)\n .join('');\n }\n\n if (!key) {\n return undefined;\n }\n\n return `${type}${key}`;\n }\n\n /**\n * Get default tag key for not unique elements\n */\n protected getDefaultKey(type: string, containerId: string, index: number): string {\n return `${type}-${containerId}-${index}-not-unique`;\n }\n\n /**\n * Check if tag is not unique\n */\n protected isNotUniqueTag(key: string): boolean {\n return key.endsWith('-not-unique');\n }\n\n /**\n * Clone react element\n */\n protected cloneElement(element: ReactElement): {\n element: ReactElement;\n elementProps: Record<string, any>;\n } {\n const { type } = element;\n const props = { ...(element?.props ?? {}) } as Record<string, any>;\n\n // remove system attributes\n Object.values(this.reservedAttributes).forEach((attrName) => {\n if (props[attrName]) {\n delete props[attrName];\n }\n });\n\n // fix multiple nodes for title\n if (type === 'title' && Array.isArray(props.children)) {\n props.children = props.children.join('');\n }\n\n return {\n element: React.createElement(type, props),\n elementProps: props,\n };\n }\n\n /**\n * Push react elements to meta state\n */\n protected pushElements(\n elements: ReactNode,\n containerId: string,\n isReplace = true,\n status = TagStatus.init,\n ): void {\n Children.forEach(elements, (child, index) => {\n // skip unsupported elements\n if (!child || typeof child !== 'object' || !('type' in child) || !child.type) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n let order = elementOrder ?? this.tagsDefinitions[type as string]?.order ?? 1000;\n let key = this.tagsDefinitions[type as string]?.key ?? (type as string);\n\n switch (type) {\n case 'title':\n break;\n\n case 'meta':\n const { charSet, httpEquiv } = elementProps;\n\n if (charSet) {\n key = `meta[charset]`;\n } else if (httpEquiv) {\n key = `meta[httpEquiv]`;\n } else {\n key = this.buildKeyByProps(type, elementProps)!;\n }\n\n break;\n\n case 'html':\n case 'body':\n if (!isReplace && this.tags[type].has(key)) {\n return;\n }\n\n this.tags[type].set(containerId, Manager.cleanupElementProps(elementProps));\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type as string, elementProps, true) ??\n this.getDefaultKey(type as string, containerId, index);\n break;\n }\n\n // skip replace existed meta tag (e.g. for push existed tags from parsed html, @see ServerManager)\n if (!isReplace && this.tags.meta.has(key)) {\n return;\n }\n\n // skip push already existed in head not unique tags\n if (this.isNotUniqueTag(key) && this.tags.meta.get(key)?.status === TagStatus.synced) {\n return;\n }\n\n // custom order\n if (this.tagsDefinitions[key]?.order) {\n order = this.tagsDefinitions[key]?.order;\n }\n\n this.tags.meta.set(key, {\n element: this.isServer ? element : undefined, // keep element only for server render\n domElement:\n /**\n * create DOM element only for client side\n * generate DOM element for root container inside @see this.analyzeClientHead\n */\n containerId === Manager.rootContainerId ? undefined : this.createDomElement(element),\n order,\n containerId,\n status,\n });\n });\n\n this.tags.containers.add(containerId);\n }\n\n /**\n * Get html or body props\n */\n public getRootTagProps(props: Manager['tags']['html']): Record<string, any> {\n if (!props.size) {\n return {};\n }\n\n return [...props.values()].reduce(\n (res, val) => ({\n ...res,\n ...val,\n }),\n {},\n );\n }\n\n /**\n * Apply props to dom element\n */\n protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): void {\n const tagName = element.tagName.toLowerCase();\n const reservedAttributes = Object.values(this.reservedAttributes);\n\n // apply attributes\n Object.entries(props).forEach(([name, value]) => {\n switch (name) {\n case 'children':\n element.innerHTML = value;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes[name]) {\n return;\n }\n\n element.setAttribute(this.replaceAttribute(tagName, name), value as string);\n });\n }\n\n /**\n * Synchronize local meta tags with head meta tags\n */\n protected syncMeta(containerId?: string): void {\n const { meta, html, body } = this.getTags();\n\n if (this.syncTimerId) {\n clearTimeout(this.syncTimerId);\n }\n\n // apply html, body props\n for (const { name, value } of [\n { name: 'html', value: html },\n { name: 'body', value: body },\n ]) {\n const tagProps = this.getRootTagProps(value);\n const domElement = document.querySelector(name);\n const existAttributes = domElement?.getAttributeNames() ?? [];\n\n // remove drained props\n existAttributes.forEach((attrName) => {\n if (tagProps[attrName]) {\n return;\n }\n\n domElement!.removeAttribute(attrName);\n });\n\n if (!Object.keys(tagProps).length || !domElement) {\n continue;\n }\n\n this.applyDomElementAttributes(domElement, tagProps);\n }\n\n if (!meta.size || this.isServer) {\n return;\n }\n\n const head = document.getElementsByTagName('head')?.[0];\n let prevElement: HTMLElement | ChildNode | null | undefined;\n\n meta.forEach((metaTag, key) => {\n const { status, domElement } = metaTag;\n\n if (\n status === TagStatus.synced ||\n !domElement ||\n (containerId && containerId !== metaTag.containerId)\n ) {\n prevElement = domElement;\n\n return;\n }\n\n // remove tags\n if (status === TagStatus.drain) {\n prevElement = domElement.previousSibling;\n domElement?.remove();\n meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.synced;\n\n if (!this.isNotUniqueTag(key)) {\n const exist = document.querySelector(key);\n\n if (exist) {\n prevElement = domElement;\n exist.replaceWith(domElement);\n\n return;\n }\n }\n\n const beforeElement = prevElement && prevElement.nextSibling;\n\n if (beforeElement) {\n head.insertBefore(domElement, beforeElement);\n } else {\n head.append(domElement);\n }\n\n prevElement = domElement;\n });\n }\n\n /**\n * Push new meta tags\n */\n public pushTags(elements: ReactNode, containerId: string, isReplace = true): void {\n const isAdded = this.tags.containers.has(containerId);\n\n this.pushElements(\n elements,\n containerId,\n isReplace,\n isAdded ? TagStatus.synced : TagStatus.init,\n );\n\n // skip sync already synced tags\n if (this.isServer || isAdded) {\n return;\n }\n\n this.syncMeta();\n }\n\n /**\n * Remove meta tags\n */\n public removeTags(containerId: string): void {\n this.tags.meta.forEach((metaTag, key) => {\n if (metaTag.containerId !== containerId || metaTag.containerId === Manager.rootContainerId) {\n return;\n }\n\n // remove immediately not unique tags\n if (this.isNotUniqueTag(key)) {\n metaTag.domElement?.remove();\n this.tags.meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.drain;\n });\n\n if (containerId === Manager.rootContainerId) {\n return;\n }\n\n this.tags.body.delete(containerId);\n this.tags.html.delete(containerId);\n this.tags.containers.delete(containerId);\n this.syncTimerId = setTimeout(() => this.syncMeta(), 500);\n }\n\n /**\n * Initial analyze client head meta tags\n */\n public analyzeClientHead(): void {\n if (this.isServer) {\n return;\n }\n\n // parse default attributes for root tags\n for (const tagName of ['html', 'body']) {\n const htmlTag = document.getElementsByTagName(tagName)?.[0].cloneNode(false)?.[\n 'outerHTML'\n ] as string;\n\n this.pushElements(htmlParser(htmlTag), Manager.rootContainerId, false);\n }\n\n // parse default meta tags\n const head = document.getElementsByTagName('head')?.[0];\n const reactElements = htmlParser(head?.innerHTML ?? '');\n\n this.pushElements(reactElements, Manager.rootContainerId, false, TagStatus.synced);\n\n const meta = [...this.tags.meta.values()];\n\n // attach real dom node to virtual tags\n head.childNodes.forEach((node, i) => {\n const existedElem = meta[i];\n\n if (!existedElem) {\n return;\n }\n\n existedElem.domElement = node as HTMLElement;\n });\n }\n}\n\nexport default Manager;\n"],"names":["TagStatus","Manager","static","isServer","window","tags","tagsDefinitions","order","title","base","meta","link","script","noscript","style","reservedAttributes","syncTimerId","constructor","this","html","Map","body","containers","Set","setTagsDefinitions","definitions","props","children","_","dangerouslySetInnerHTML","__","restProps","getTags","entries","sort","tagA","tagB","replaceAttribute","tagName","attribute","toLowerCase","createDomElement","element","type","domElement","document","createElement","applyDomElementAttributes","buildKeyByProps","isSkipProps","key","uniqueAttr","Object","map","k","v","filter","Boolean","join","getDefaultKey","containerId","index","isNotUniqueTag","endsWith","cloneElement","values","forEach","attrName","Array","isArray","React","elementProps","pushElements","elements","isReplace","status","init","Children","child","Number","undefined","charSet","httpEquiv","has","set","cleanupElementProps","get","synced","rootContainerId","add","getRootTagProps","size","reduce","res","val","name","value","innerHTML","styleName","styleValue","setAttribute","syncMeta","clearTimeout","tagProps","querySelector","getAttributeNames","removeAttribute","keys","length","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","pushTags","isAdded","removeTags","setTimeout","analyzeClientHead","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
1
+ {"version":3,"file":"manager.js","sources":["../src/manager.ts"],"sourcesContent":["import htmlParser from 'html-react-parser';\nimport type { ReactElement, ReactNode } from 'react';\nimport React, { Fragment, Children } from 'react';\nimport TagStatus from './tag-status';\n\nexport interface IMetaManagerTags {\n html: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >;\n body: Map<\n string,\n {\n props: Record<string, any>;\n order: number;\n }\n >; // containerId => styles\n meta: Map<\n string,\n {\n element?: ReactElement;\n domElement?: HTMLElement;\n order: number;\n containerId: string;\n status: TagStatus;\n }\n >;\n containers: Set<string>;\n}\n\nexport interface IMetaManagerTagsDefinitions {\n [type: string]: {\n key?: string;\n order: number;\n };\n}\n\n/**\n * Meta tags manager\n */\nclass Manager {\n /**\n * Root container id\n */\n public static rootContainerId = 'root';\n\n /**\n * Detect server side\n */\n public isServer = typeof window === 'undefined';\n\n /**\n * Meta tags state\n */\n protected tags: IMetaManagerTags;\n\n /**\n * Tags definitions\n */\n protected tagsDefinitions: IMetaManagerTagsDefinitions = {\n 'meta[charset]': {\n order: 10,\n },\n \"meta[name='viewport']\": {\n order: 20,\n },\n title: {\n order: 100,\n },\n base: {\n order: 110,\n },\n meta: {\n order: 200,\n },\n link: {\n order: 300,\n },\n script: {\n order: 400,\n },\n noscript: {\n order: 500,\n },\n style: {\n order: 600,\n },\n };\n\n /**\n * System tag attributes\n */\n protected reservedAttributes = {\n order: 'data-order',\n };\n\n /**\n * Scheduled synchronization\n */\n protected syncTimerId: null | NodeJS.Timeout = null;\n\n /**\n * @constructor\n */\n constructor(tags: Partial<Manager['tags']> = {}) {\n this.tags = {\n html: new Map(tags?.html),\n body: new Map(tags?.body),\n meta: new Map(tags?.meta),\n containers: new Set(tags?.containers),\n };\n }\n\n /**\n * Set tags definitions\n */\n public setTagsDefinitions(definitions: IMetaManagerTagsDefinitions): void {\n this.tagsDefinitions = { ...this.tagsDefinitions, ...definitions };\n }\n\n /**\n * Remove children, dangerouslySetInnerHTML etc.\n */\n public static cleanupElementProps(props: Record<string, any>): Record<string, any> {\n const { children: _, dangerouslySetInnerHTML: __, ...restProps } = props;\n\n return restProps;\n }\n\n /**\n * Sort elements or props\n */\n protected sortTags<T extends Map<string, { order: number }>>(elements: T): T {\n return new Map(\n [...elements.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),\n ) as T;\n }\n\n /**\n * Get meta tags\n */\n public getTags(): IMetaManagerTags {\n const { meta, body, html, containers } = this.tags;\n\n return {\n html: this.sortTags(html),\n body: this.sortTags(body),\n meta: this.sortTags(meta),\n containers,\n };\n }\n\n /**\n * Replace react attribute to valid DOM attribute\n */\n protected replaceAttribute(tagName: string, attribute: string): string {\n if (tagName === 'meta' && attribute.toLowerCase() === 'httpequiv') {\n return 'http-equiv';\n }\n\n return attribute;\n }\n\n /**\n * Create DOM element from React element\n */\n protected createDomElement(element: ReactElement): HTMLElement | undefined {\n if (this.isServer) {\n return;\n }\n\n const { type, props } = element;\n const domElement = document.createElement(type as string);\n\n this.applyDomElementAttributes(domElement, props as Record<string, string>);\n\n return domElement;\n }\n\n /**\n * Make element key by element props\n */\n protected buildKeyByProps(\n type: string,\n props: Record<string, any> = {},\n isSkipProps = false,\n ): string | undefined {\n let key = '';\n\n // try to build unique key by unique props\n for (const uniqueAttr of ['id', 'name', 'href', 'src']) {\n if (props[uniqueAttr]) {\n key = `[${uniqueAttr}='${props[uniqueAttr] as string}']`;\n\n break;\n }\n }\n\n // try to build by props\n if (!key && !isSkipProps) {\n key = Object.entries(props)\n .map(([k, v]) => {\n if (typeof v === 'string') {\n return `[${k}]`;\n }\n\n return false;\n })\n .filter(Boolean)\n .join('');\n }\n\n if (!key) {\n return undefined;\n }\n\n return `${type}${key}`;\n }\n\n /**\n * Get default tag key for not unique elements\n */\n protected getDefaultKey(type: string, containerId: string, index: number): string {\n return `${type}-${containerId}-${index}-not-unique`;\n }\n\n /**\n * Check if tag is not unique\n */\n protected isNotUniqueTag(key: string): boolean {\n return key.endsWith('-not-unique');\n }\n\n /**\n * Clone react element\n */\n protected cloneElement(element: ReactElement): {\n element: ReactElement;\n elementProps: Record<string, any>;\n } {\n const { type } = element;\n const props = { ...(element?.props ?? {}) } as Record<string, any>;\n\n // remove system attributes\n Object.values(this.reservedAttributes).forEach((attrName) => {\n if (props[attrName]) {\n delete props[attrName];\n }\n });\n\n // fix multiple nodes for title\n if (type === 'title' && Array.isArray(props.children)) {\n props.children = props.children.join('');\n }\n\n return {\n element: React.createElement(type, props),\n elementProps: props,\n };\n }\n\n /**\n * Get element order\n */\n protected getElementOrder(\n elementProps: Record<string, any>,\n type: string,\n key: string = 'unknown',\n ): number {\n const elementOrder = elementProps[this.reservedAttributes.order]\n ? Number(elementProps[this.reservedAttributes.order])\n : undefined;\n\n return (\n elementOrder ?? this.tagsDefinitions[key]?.order ?? this.tagsDefinitions[type]?.order ?? 1000\n );\n }\n\n /**\n * Push react elements to meta state\n */\n protected pushElements(\n elements: ReactNode,\n containerId: string,\n isReplace = true,\n status = TagStatus.init,\n ): void {\n // unwrap fragment\n const clearElements: ReactNode =\n elements && typeof elements === 'object' && 'type' in elements && elements.type === Fragment\n ? elements.props.children\n : elements;\n\n Children.forEach(clearElements, (child, index) => {\n // skip unsupported elements\n if (\n !child ||\n typeof child !== 'object' ||\n !('type' in child) ||\n !child.type ||\n typeof child.type !== 'string'\n ) {\n return;\n }\n\n const { type } = child;\n const { element, elementProps } = this.cloneElement(child);\n let key = this.tagsDefinitions[type]?.key ?? type;\n\n switch (type) {\n case 'title':\n break;\n\n case 'meta':\n const { charSet, httpEquiv } = elementProps;\n\n if (charSet) {\n key = `meta[charset]`;\n } else if (httpEquiv) {\n key = `meta[httpEquiv]`;\n } else {\n key = this.buildKeyByProps(type, elementProps)!;\n }\n\n break;\n\n case 'html':\n case 'body':\n if (!isReplace && this.tags[type].has(key)) {\n return;\n }\n\n this.tags[type].set(containerId, {\n props: Manager.cleanupElementProps(elementProps),\n order:\n containerId === Manager.rootContainerId\n ? 1\n : this.getElementOrder(elementProps, type, key),\n });\n\n return;\n\n case 'link':\n case 'script':\n case 'noscript':\n case 'style':\n default:\n key =\n this.buildKeyByProps(type, elementProps, true) ??\n this.getDefaultKey(type, containerId, index);\n break;\n }\n\n // skip replace existed meta tag (e.g. for push existed tags from parsed html, @see ServerManager)\n if (!isReplace && this.tags.meta.has(key)) {\n return;\n }\n\n // skip push already existed in head not unique tags\n if (this.isNotUniqueTag(key) && this.tags.meta.get(key)?.status === TagStatus.synced) {\n return;\n }\n\n this.tags.meta.set(key, {\n element: this.isServer ? element : undefined, // keep element only for server render\n domElement:\n /**\n * create DOM element only for client side\n * generate DOM element for root container inside @see this.analyzeClientHead\n */\n containerId === Manager.rootContainerId ? undefined : this.createDomElement(element),\n order: this.getElementOrder(elementProps, type, key),\n containerId,\n status,\n });\n });\n\n this.tags.containers.add(containerId);\n }\n\n /**\n * Get html or body props\n */\n public getRootTagProps(props: Manager['tags']['html']): Record<string, any> {\n if (!props.size) {\n return {};\n }\n\n return [...props.values()].reduce(\n (res, val) => ({\n ...res,\n ...val.props,\n }),\n {},\n );\n }\n\n /**\n * Apply props to dom element\n */\n protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): void {\n const tagName = element.tagName.toLowerCase();\n const reservedAttributes = Object.values(this.reservedAttributes);\n\n // apply attributes\n Object.entries(props).forEach(([name, value]) => {\n switch (name) {\n case 'children':\n element.innerHTML = value;\n\n return;\n\n case 'style':\n return Object.entries(value as Record<string, string>).forEach(\n ([styleName, styleValue]) => {\n // @ts-ignore\n element['style'][styleName] = styleValue;\n },\n );\n }\n\n if (reservedAttributes.includes(name)) {\n return;\n }\n\n element.setAttribute(this.replaceAttribute(tagName, name), value as string);\n });\n }\n\n /**\n * Synchronize local meta tags with head meta tags\n */\n protected syncMeta(containerId?: string): void {\n const { meta, html, body } = this.getTags();\n\n if (this.syncTimerId) {\n clearTimeout(this.syncTimerId);\n }\n\n // apply html, body props\n for (const { name, value } of [\n { name: 'html', value: html },\n { name: 'body', value: body },\n ]) {\n const tagProps = this.getRootTagProps(value);\n const domElement = document.querySelector(name);\n const existAttributes = domElement?.getAttributeNames() ?? [];\n\n // remove drained props\n existAttributes.forEach((attrName) => {\n if (tagProps[attrName]) {\n return;\n }\n\n domElement!.removeAttribute(attrName);\n });\n\n if (!Object.keys(tagProps).length || !domElement) {\n continue;\n }\n\n this.applyDomElementAttributes(domElement, tagProps);\n }\n\n if (!meta.size || this.isServer) {\n return;\n }\n\n const head = document.getElementsByTagName('head')?.[0];\n let prevElement: HTMLElement | ChildNode | null | undefined;\n\n meta.forEach((metaTag, key) => {\n const { status, domElement } = metaTag;\n\n if (\n status === TagStatus.synced ||\n !domElement ||\n (containerId && containerId !== metaTag.containerId)\n ) {\n prevElement = domElement;\n\n return;\n }\n\n // remove tags\n if (status === TagStatus.drain) {\n prevElement = domElement.previousSibling;\n domElement?.remove();\n meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.synced;\n\n if (!this.isNotUniqueTag(key)) {\n const exist = document.querySelector(key);\n\n if (exist) {\n prevElement = domElement;\n exist.replaceWith(domElement);\n\n return;\n }\n }\n\n const beforeElement = prevElement && prevElement.nextSibling;\n\n if (beforeElement) {\n head.insertBefore(domElement, beforeElement);\n } else {\n head.append(domElement);\n }\n\n prevElement = domElement;\n });\n }\n\n /**\n * Push new meta tags\n */\n public pushTags(elements: ReactNode, containerId: string, isReplace = true): void {\n const isAdded = this.tags.containers.has(containerId);\n\n this.pushElements(\n elements,\n containerId,\n isReplace,\n isAdded ? TagStatus.synced : TagStatus.init,\n );\n\n // skip sync already synced tags\n if (this.isServer || isAdded) {\n return;\n }\n\n this.syncMeta();\n }\n\n /**\n * Remove meta tags\n */\n public removeTags(containerId: string): void {\n this.tags.meta.forEach((metaTag, key) => {\n if (metaTag.containerId !== containerId || metaTag.containerId === Manager.rootContainerId) {\n return;\n }\n\n // remove immediately not unique tags\n if (this.isNotUniqueTag(key)) {\n metaTag.domElement?.remove();\n this.tags.meta.delete(key);\n\n return;\n }\n\n metaTag.status = TagStatus.drain;\n });\n\n if (containerId === Manager.rootContainerId) {\n return;\n }\n\n this.tags.body.delete(containerId);\n this.tags.html.delete(containerId);\n this.tags.containers.delete(containerId);\n this.syncTimerId = setTimeout(() => this.syncMeta(), 500);\n }\n\n /**\n * Initial analyze client head meta tags\n */\n public analyzeClientHead(): void {\n if (this.isServer) {\n return;\n }\n\n // parse default attributes for root tags\n for (const tagName of ['html', 'body']) {\n // @ts-ignore\n const htmlTag = document.getElementsByTagName(tagName)?.[0].cloneNode(false)?.[\n 'outerHTML'\n ] as string;\n\n this.pushElements(htmlParser(htmlTag), Manager.rootContainerId, false);\n }\n\n // parse default meta tags\n const head = document.getElementsByTagName('head')?.[0];\n const reactElements = htmlParser(head?.innerHTML ?? '');\n\n this.pushElements(reactElements, Manager.rootContainerId, false, TagStatus.synced);\n\n const meta = [...this.tags.meta.values()];\n\n // attach real dom node to virtual tags\n head.childNodes.forEach((node, i) => {\n const existedElem = meta[i];\n\n if (!existedElem) {\n return;\n }\n\n existedElem.domElement = node as HTMLElement;\n });\n }\n}\n\nexport default Manager;\n"],"names":["Manager","static","isServer","window","tags","tagsDefinitions","order","title","base","meta","link","script","noscript","style","reservedAttributes","syncTimerId","constructor","this","html","Map","body","containers","Set","setTagsDefinitions","definitions","props","children","_","dangerouslySetInnerHTML","__","restProps","sortTags","elements","entries","sort","tagA","tagB","getTags","replaceAttribute","tagName","attribute","toLowerCase","createDomElement","element","type","domElement","document","createElement","applyDomElementAttributes","buildKeyByProps","isSkipProps","key","uniqueAttr","Object","map","k","v","filter","Boolean","join","getDefaultKey","containerId","index","isNotUniqueTag","endsWith","cloneElement","values","forEach","attrName","Array","isArray","React","elementProps","getElementOrder","Number","undefined","pushElements","isReplace","status","TagStatus","init","clearElements","Fragment","Children","child","charSet","httpEquiv","has","set","cleanupElementProps","rootContainerId","get","synced","add","getRootTagProps","size","reduce","res","val","name","value","innerHTML","styleName","styleValue","includes","setAttribute","syncMeta","clearTimeout","tagProps","querySelector","getAttributeNames","removeAttribute","keys","length","head","getElementsByTagName","prevElement","metaTag","drain","previousSibling","remove","delete","exist","replaceWith","beforeElement","nextSibling","insertBefore","append","pushTags","isAdded","removeTags","setTimeout","analyzeClientHead","htmlTag","cloneNode","htmlParser","reactElements","childNodes","node","i","existedElem"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lomray/react-head-manager",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "React meta tags manager with SSR and Suspense support.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -35,38 +35,43 @@
35
35
  "release": "npm run build && cd lib && npm publish",
36
36
  "lint:check": "eslint --ext \".ts,.tsx\" \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
37
37
  "lint:format": "eslint --fix --ext \".ts,.tsx\" \"src/**/*.{ts,tsx,*.ts,*tsx}\"",
38
- "ts:check": "tsc --project ./tsconfig.json --skipLibCheck --noemit"
38
+ "ts:check": "tsc --project ./tsconfig.json --skipLibCheck --noemit",
39
+ "test": "vitest run"
39
40
  },
40
41
  "dependencies": {
41
- "html-react-parser": "^5.0.11"
42
+ "html-react-parser": "^5.1.4"
42
43
  },
43
44
  "devDependencies": {
44
- "@commitlint/cli": "^18.4.4",
45
- "@commitlint/config-conventional": "^18.4.4",
45
+ "@commitlint/cli": "18.6.0",
46
+ "@commitlint/config-conventional": "18.6.0",
46
47
  "@lomray/eslint-config": "^4.0.1",
47
48
  "@lomray/prettier-config": "^2.0.1",
48
- "@rollup/plugin-json": "^6.1.0",
49
49
  "@rollup/plugin-terser": "^0.4.4",
50
- "@types/react": "^18.2.47",
51
- "@types/react-dom": "^18.2.18",
52
- "@typescript-eslint/eslint-plugin": "^6.18.1",
53
- "@zerollup/ts-transform-paths": "^1.7.18",
50
+ "@types/react": "^18.2.55",
51
+ "@types/react-dom": "^18.2.19",
52
+ "@types/sinon": "^17.0.3",
53
+ "@types/sinon-chai": "^3.2.12",
54
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
55
+ "@vitest/coverage-v8": "^1.2.2",
54
56
  "eslint": "^8.56.0",
55
57
  "eslint-config-prettier": "^9.1.0",
56
58
  "eslint-plugin-import": "^2.29.1",
57
59
  "eslint-plugin-jsx-a11y": "^6.8.0",
58
60
  "eslint-plugin-prettier": "^5.1.3",
59
- "husky": "^8.0.3",
60
- "lint-staged": "^15.2.0",
61
- "prettier": "^3.1.1",
61
+ "husky": "^9.0.10",
62
+ "jsdom": "^24.0.0",
63
+ "lint-staged": "^15.2.2",
64
+ "prettier": "^3.2.5",
62
65
  "react": "^18.2.0",
63
- "rollup": "^4.9.4",
66
+ "rollup": "^4.10.0",
64
67
  "rollup-plugin-copy": "^3.5.0",
65
- "rollup-plugin-folder-input": "^1.0.1",
66
68
  "rollup-plugin-peer-deps-external": "^2.2.4",
67
69
  "rollup-plugin-ts": "^3.4.5",
68
70
  "semantic-release": "^21.1.2",
69
- "typescript": "^4.9.5"
71
+ "sinon": "^17.0.1",
72
+ "sinon-chai": "^3.7.0",
73
+ "typescript": "^5.3.3",
74
+ "vitest": "^1.2.2"
70
75
  },
71
76
  "peerDependencies": {
72
77
  "@lomray/consistent-suspense": ">=2.0.1",
@@ -0,0 +1,6 @@
1
+ declare enum TagStatus {
2
+ init = "init",
3
+ synced = "synced",
4
+ drain = "drain"
5
+ }
6
+ export { TagStatus as default };
package/tag-status.js ADDED
@@ -0,0 +1,2 @@
1
+ var n;!function(n){n.init="init",n.synced="synced",n.drain="drain"}(n||(n={}));var i=n;export{i as default};
2
+ //# sourceMappingURL=tag-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-status.js","sources":["../src/tag-status.ts"],"sourcesContent":["enum TagStatus {\n init = 'init',\n synced = 'synced',\n drain = 'drain',\n}\n\nexport default TagStatus;\n"],"names":["TagStatus","TagStatus$1"],"mappings":"AAAA,IAAKA,GAAL,SAAKA,GACHA,EAAA,KAAA,OACAA,EAAA,OAAA,SACAA,EAAA,MAAA,OACD,CAJD,CAAKA,IAAAA,EAIJ,CAAA,IAED,IAAAC,EAAeD"}