From e0ef4022c965297cda323a5cd06eb6a5a478bef9 Mon Sep 17 00:00:00 2001 From: Mikhail Yarmaliuk Date: Tue, 4 Jul 2023 19:41:11 +0200 Subject: [PATCH] feat: tags order --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++ src/manager.ts | 78 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 125 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 54e9fbb..ab69b58 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,74 @@ The package is distributed using [npm](https://www.npmjs.com/), the node package npm i --save @lomray/react-head-manager ``` +## Usage +```typescript jsx +import { MetaManagerProvider, Manager, Meta } from '@lomray/react-head-manager'; + +const manager = new Manager(); + +/** + * Root component container + */ +const App = ({ children }) => { + const [state] = useState(); + + return ( + + + + ) +} + +/** + * Some component + */ +const MyComponent = () => { + return ( + <> + + Example + + + + +
Some component....
+ + ) +} +``` + +Change tags order: +```typescript jsx +/** + * Way 1 + */ +const manager = new Manager(); +manager.setTagsDefinitions({ + title: 1, // change order for title tag + "meta[name='viewport']": 2, // change order for meta viewport tag + meta: 100, // change for all meta tags + script: 200, // change for all script tags +}); + +/** + * Way 2 + */ + + Example + + + + +/** + * You can also use both... + */ +``` + +__NOTE:__ this package use [@lomray/consistent-suspense](https://github.com/Lomray-Software/consistent-suspense) for generate stable id's. + +See [demo app](https://github.com/Lomray-Software/vite-template) to more understand. + ## Bugs and feature requests Bug or a feature request, [please open a new issue](https://github.com/Lomray-Software/react-head-manager/issues/new). diff --git a/src/manager.ts b/src/manager.ts index 6098df9..362ea3f 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -83,6 +83,13 @@ class Manager { }, }; + /** + * System tag attributes + */ + protected reservedAttributes = { + order: 'data-order', + }; + /** * @constructor */ @@ -116,14 +123,14 @@ class Manager { */ public getTags(): IMetaManagerTags { const { meta, body, html, containers } = this.tags; - const sortedMeta = new Map( + const sortedTags = new Map( [...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order), ); return { body, html, - meta: sortedMeta, + meta: sortedTags, containers, }; } @@ -209,6 +216,34 @@ class Manager { return key.endsWith('-not-unique'); } + /** + * Clone react element + */ + protected cloneElement(element: ReactElement): { + element: ReactElement; + elementProps: Record; + } { + const { type } = element; + const props = { ...(element?.props ?? {}) } as Record; + + // remove system attributes + Object.values(this.reservedAttributes).forEach((attrName) => { + if (props[attrName]) { + delete props[attrName]; + } + }); + + // fix multiple nodes for title + if (type === 'title' && Array.isArray(props.children)) { + props.children = props.children.join(''); + } + + return { + element: React.createElement(type, props), + elementProps: props, + }; + } + /** * Push react elements to meta state */ @@ -224,21 +259,17 @@ class Manager { return; } - const { type, props } = child; - const elementProps: Record = props ?? {}; + const { type } = child; + const { element, elementProps } = this.cloneElement(child); + const elementOrder = elementProps[this.reservedAttributes.order] + ? Number(elementProps[this.reservedAttributes.order]) + : undefined; - let order = this.tagsDefinitions[type as string]?.order ?? 1000; + let order = elementOrder ?? this.tagsDefinitions[type as string]?.order ?? 1000; let key = this.tagsDefinitions[type as string]?.key ?? (type as string); - let element = child; switch (type) { case 'title': - const { children } = elementProps; - - // fix multiple nodes - element = Array.isArray(children) - ? React.cloneElement(child, { children: children.join('') }) - : child; break; case 'meta': @@ -329,21 +360,26 @@ class Manager { */ protected applyDomElementAttributes(element: Element, props: Record = {}): void { const tagName = element.tagName.toLowerCase(); + const reservedAttributes = Object.values(this.reservedAttributes); // apply attributes Object.entries(props).forEach(([name, value]) => { - if (name === 'children') { - element.innerHTML = value; + switch (name) { + case 'children': + element.innerHTML = value; - return; + return; + + case 'style': + return Object.entries(value as Record).forEach( + ([styleName, styleValue]) => { + element['style'][styleName] = styleValue; + }, + ); } - if (name === 'style') { - return Object.entries(value as Record).forEach( - ([styleName, styleValue]) => { - element['style'][styleName] = styleValue; - }, - ); + if (reservedAttributes[name]) { + return; } element.setAttribute(this.replaceAttribute(tagName, name), value as string);