Skip to content

Commit e0ef402

Browse files
feat: tags order
1 parent 36e020f commit e0ef402

File tree

2 files changed

+125
-21
lines changed

2 files changed

+125
-21
lines changed

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,74 @@ The package is distributed using [npm](https://www.npmjs.com/), the node package
1818
npm i --save @lomray/react-head-manager
1919
```
2020

21+
## Usage
22+
```typescript jsx
23+
import { MetaManagerProvider, Manager, Meta } from '@lomray/react-head-manager';
24+
25+
const manager = new Manager();
26+
27+
/**
28+
* Root component container
29+
*/
30+
const App = ({ children }) => {
31+
const [state] = useState();
32+
33+
return (
34+
<MetaManagerProvider manager={manager}>
35+
<MyComponent />
36+
</MetaManagerProvider>
37+
)
38+
}
39+
40+
/**
41+
* Some component
42+
*/
43+
const MyComponent = () => {
44+
return (
45+
<>
46+
<Meta>
47+
<title>Example</title>
48+
<meta name="description" content="Description example" />
49+
<meta name="keywords" content="test,key" />
50+
<body data-id="test" />
51+
</Meta>
52+
<div>Some component....</div>
53+
</>
54+
)
55+
}
56+
```
57+
58+
Change tags order:
59+
```typescript jsx
60+
/**
61+
* Way 1
62+
*/
63+
const manager = new Manager();
64+
manager.setTagsDefinitions({
65+
title: 1, // change order for title tag
66+
"meta[name='viewport']": 2, // change order for meta viewport tag
67+
meta: 100, // change for all meta tags
68+
script: 200, // change for all script tags
69+
});
70+
71+
/**
72+
* Way 2
73+
*/
74+
<Meta>
75+
<title data-order={1}>Example</title>
76+
<meta data-order={3} name="description" content="Description example" />
77+
<meta data-order={2} name="keywords" content="test,key" />
78+
</Meta>
79+
80+
/**
81+
* You can also use both...
82+
*/
83+
```
84+
85+
__NOTE:__ this package use [@lomray/consistent-suspense](https://github.com/Lomray-Software/consistent-suspense) for generate stable id's.
86+
87+
See [demo app](https://github.com/Lomray-Software/vite-template) to more understand.
88+
2189
## Bugs and feature requests
2290

2391
Bug or a feature request, [please open a new issue](https://github.com/Lomray-Software/react-head-manager/issues/new).

src/manager.ts

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ class Manager {
8383
},
8484
};
8585

86+
/**
87+
* System tag attributes
88+
*/
89+
protected reservedAttributes = {
90+
order: 'data-order',
91+
};
92+
8693
/**
8794
* @constructor
8895
*/
@@ -116,14 +123,14 @@ class Manager {
116123
*/
117124
public getTags(): IMetaManagerTags {
118125
const { meta, body, html, containers } = this.tags;
119-
const sortedMeta = new Map(
126+
const sortedTags = new Map(
120127
[...meta.entries()].sort(([, tagA], [, tagB]) => tagA.order - tagB.order),
121128
);
122129

123130
return {
124131
body,
125132
html,
126-
meta: sortedMeta,
133+
meta: sortedTags,
127134
containers,
128135
};
129136
}
@@ -209,6 +216,34 @@ class Manager {
209216
return key.endsWith('-not-unique');
210217
}
211218

219+
/**
220+
* Clone react element
221+
*/
222+
protected cloneElement(element: ReactElement): {
223+
element: ReactElement;
224+
elementProps: Record<string, any>;
225+
} {
226+
const { type } = element;
227+
const props = { ...(element?.props ?? {}) } as Record<string, any>;
228+
229+
// remove system attributes
230+
Object.values(this.reservedAttributes).forEach((attrName) => {
231+
if (props[attrName]) {
232+
delete props[attrName];
233+
}
234+
});
235+
236+
// fix multiple nodes for title
237+
if (type === 'title' && Array.isArray(props.children)) {
238+
props.children = props.children.join('');
239+
}
240+
241+
return {
242+
element: React.createElement(type, props),
243+
elementProps: props,
244+
};
245+
}
246+
212247
/**
213248
* Push react elements to meta state
214249
*/
@@ -224,21 +259,17 @@ class Manager {
224259
return;
225260
}
226261

227-
const { type, props } = child;
228-
const elementProps: Record<string, any> = props ?? {};
262+
const { type } = child;
263+
const { element, elementProps } = this.cloneElement(child);
264+
const elementOrder = elementProps[this.reservedAttributes.order]
265+
? Number(elementProps[this.reservedAttributes.order])
266+
: undefined;
229267

230-
let order = this.tagsDefinitions[type as string]?.order ?? 1000;
268+
let order = elementOrder ?? this.tagsDefinitions[type as string]?.order ?? 1000;
231269
let key = this.tagsDefinitions[type as string]?.key ?? (type as string);
232-
let element = child;
233270

234271
switch (type) {
235272
case 'title':
236-
const { children } = elementProps;
237-
238-
// fix multiple nodes
239-
element = Array.isArray(children)
240-
? React.cloneElement(child, { children: children.join('') })
241-
: child;
242273
break;
243274

244275
case 'meta':
@@ -329,21 +360,26 @@ class Manager {
329360
*/
330361
protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): void {
331362
const tagName = element.tagName.toLowerCase();
363+
const reservedAttributes = Object.values(this.reservedAttributes);
332364

333365
// apply attributes
334366
Object.entries(props).forEach(([name, value]) => {
335-
if (name === 'children') {
336-
element.innerHTML = value;
367+
switch (name) {
368+
case 'children':
369+
element.innerHTML = value;
337370

338-
return;
371+
return;
372+
373+
case 'style':
374+
return Object.entries(value as Record<string, string>).forEach(
375+
([styleName, styleValue]) => {
376+
element['style'][styleName] = styleValue;
377+
},
378+
);
339379
}
340380

341-
if (name === 'style') {
342-
return Object.entries(value as Record<string, string>).forEach(
343-
([styleName, styleValue]) => {
344-
element['style'][styleName] = styleValue;
345-
},
346-
);
381+
if (reservedAttributes[name]) {
382+
return;
347383
}
348384

349385
element.setAttribute(this.replaceAttribute(tagName, name), value as string);

0 commit comments

Comments
 (0)