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);