Skip to content

Commit

Permalink
feat: tags order
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewPattell committed Jul 4, 2023
1 parent 36e020f commit e0ef402
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 21 deletions.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<MetaManagerProvider manager={manager}>
<MyComponent />
</MetaManagerProvider>
)
}

/**
* Some component
*/
const MyComponent = () => {
return (
<>
<Meta>
<title>Example</title>
<meta name="description" content="Description example" />
<meta name="keywords" content="test,key" />
<body data-id="test" />
</Meta>
<div>Some component....</div>
</>
)
}
```

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
*/
<Meta>
<title data-order={1}>Example</title>
<meta data-order={3} name="description" content="Description example" />
<meta data-order={2} name="keywords" content="test,key" />
</Meta>

/**
* 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).
Expand Down
78 changes: 57 additions & 21 deletions src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ class Manager {
},
};

/**
* System tag attributes
*/
protected reservedAttributes = {
order: 'data-order',
};

/**
* @constructor
*/
Expand Down Expand Up @@ -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,
};
}
Expand Down Expand Up @@ -209,6 +216,34 @@ class Manager {
return key.endsWith('-not-unique');
}

/**
* Clone react element
*/
protected cloneElement(element: ReactElement): {
element: ReactElement;
elementProps: Record<string, any>;
} {
const { type } = element;
const props = { ...(element?.props ?? {}) } as Record<string, any>;

// 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
*/
Expand All @@ -224,21 +259,17 @@ class Manager {
return;
}

const { type, props } = child;
const elementProps: Record<string, any> = 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':
Expand Down Expand Up @@ -329,21 +360,26 @@ class Manager {
*/
protected applyDomElementAttributes(element: Element, props: Record<string, any> = {}): 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<string, string>).forEach(
([styleName, styleValue]) => {
element['style'][styleName] = styleValue;
},
);
}

if (name === 'style') {
return Object.entries(value as Record<string, string>).forEach(
([styleName, styleValue]) => {
element['style'][styleName] = styleValue;
},
);
if (reservedAttributes[name]) {
return;
}

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

0 comments on commit e0ef402

Please sign in to comment.