react-headings 3.4.0 → 4.0.0-next.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/README.md +67 -250
- package/dist/index.d.ts +31 -32
- package/dist/index.development.js +104 -0
- package/dist/index.js +40 -77
- package/package.json +38 -27
package/README.md
CHANGED
|
@@ -9,304 +9,121 @@
|
|
|
9
9
|
<a aria-label="NPM version" href="https://www.npmjs.com/package/react-headings">
|
|
10
10
|
<img alt="" src="https://img.shields.io/npm/v/react-headings?style=for-the-badge">
|
|
11
11
|
</a>
|
|
12
|
-
<a aria-label="Size" href="https://bundlephobia.com/package/react-headings">
|
|
13
|
-
<img alt="" src="https://img.shields.io/bundlephobia/minzip/react-headings?style=for-the-badge">
|
|
14
|
-
</a>
|
|
15
|
-
<a aria-label="Monthly downloads" href="https://www.npmjs.com/package/react-headings">
|
|
16
|
-
<img alt="" src="https://img.shields.io/npm/dm/react-headings?style=for-the-badge">
|
|
17
|
-
</a>
|
|
18
12
|
</p>
|
|
19
13
|
|
|
20
|
-
> Never
|
|
21
|
-
|
|
22
|
-
React-headings maintains the proper hierarchy of headings for improved accessibility and SEO, no matter the component structure, while you keep full control of what's rendered.
|
|
23
|
-
|
|
24
|
-
References:
|
|
25
|
-
|
|
26
|
-
- [WCAG 2.0 technique H69](https://www.w3.org/TR/WCAG20-TECHS/H69.html)
|
|
27
|
-
- [Lighthouse SEO heading order audit](https://web.dev/heading-order/)
|
|
14
|
+
> Never write `h1`–`h6` again.
|
|
28
15
|
|
|
29
|
-
|
|
16
|
+
Hardcoded heading levels break accessibility and SEO when components are reused. React Headings fixes this by computing each level from your component tree.
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
- [Highlights](#highlights)
|
|
33
|
-
- [Installation](#installation)
|
|
34
|
-
- [Examples](#examples)
|
|
35
|
-
- [API](#api)
|
|
36
|
-
- [Changelog](#changelog)
|
|
37
|
-
- [Contributing](#contributing)
|
|
18
|
+
## Features
|
|
38
19
|
|
|
39
|
-
## Demos
|
|
40
|
-
|
|
41
|
-
- [Minimal](https://codesandbox.io/s/react-headings-minimal-4temt?file=/src/Demo.js)
|
|
42
|
-
- [Custom component](https://codesandbox.io/s/react-headings-custom-component-l4bjb?file=/src/Demo.js)
|
|
43
|
-
- [Advanced structure](https://codesandbox.io/s/react-headings-advanced-structure-uxk4p?file=/src/Demo.js)
|
|
44
|
-
|
|
45
|
-
## Highlights
|
|
46
|
-
|
|
47
|
-
- Improves SEO and accessibility
|
|
48
20
|
- Supports server-side rendering
|
|
49
|
-
-
|
|
21
|
+
- 370 bytes minified & brotlied
|
|
50
22
|
- Typed with TypeScript
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
- Plays nicely with component libraries (Material UI, etc.)
|
|
54
|
-
- Follows [semantic versioning](https://semver.org/)
|
|
23
|
+
- Works with any CSS solution (Tailwind, CSS-in-JS, etc.)
|
|
24
|
+
- Plays nicely with component libraries
|
|
55
25
|
|
|
56
|
-
##
|
|
26
|
+
## Install
|
|
57
27
|
|
|
58
|
-
```
|
|
28
|
+
```sh
|
|
59
29
|
npm install react-headings
|
|
60
30
|
```
|
|
61
31
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
### Basic usage
|
|
65
|
-
|
|
66
|
-
```jsx
|
|
67
|
-
import React from "react";
|
|
68
|
-
import { H, Section } from "react-headings";
|
|
69
|
-
|
|
70
|
-
function App() {
|
|
71
|
-
return (
|
|
72
|
-
<Section component={<H>My hx</H>}>
|
|
73
|
-
<div>...</div>
|
|
74
|
-
<div>...</div>
|
|
75
|
-
<div>...</div>
|
|
76
|
-
<Section component={<H>My hx+1</H>}>
|
|
77
|
-
<div>...</div>
|
|
78
|
-
<div>...</div>
|
|
79
|
-
<div>...</div>
|
|
80
|
-
</Section>
|
|
81
|
-
</Section>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Advanced structure
|
|
87
|
-
|
|
88
|
-
Child components inherit the current level of their parent:
|
|
89
|
-
|
|
90
|
-
```jsx
|
|
91
|
-
import React from "react";
|
|
92
|
-
import { H, Section } from "react-headings";
|
|
93
|
-
|
|
94
|
-
function ParentComponent() {
|
|
95
|
-
return (
|
|
96
|
-
<Section component={<H>My hx</H>}>
|
|
97
|
-
<Section component={<H>My hx+1</H>}>
|
|
98
|
-
<Section component={<H>My hx+2</H>}>
|
|
99
|
-
<ChildComponent />
|
|
100
|
-
</Section>
|
|
101
|
-
</Section>
|
|
102
|
-
</Section>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function ChildComponent() {
|
|
107
|
-
return (
|
|
108
|
-
<Section component={<H>My hy</H>}>
|
|
109
|
-
{/* The following heading would be a <h5> in the current context */}
|
|
110
|
-
<Section component={<H>My hy+1</H>}>
|
|
111
|
-
<p>...</p>
|
|
112
|
-
</Section>
|
|
113
|
-
</Section>
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Styling
|
|
32
|
+
## Usage
|
|
119
33
|
|
|
120
|
-
|
|
34
|
+
Use `H` for every heading. Wrap the content that belongs under a heading in `HeadingLevel`: headings inside render one level deeper.
|
|
121
35
|
|
|
122
|
-
```
|
|
123
|
-
import
|
|
124
|
-
import { H, Section } from "react-headings";
|
|
36
|
+
```tsx
|
|
37
|
+
import { H, HeadingLevel } from "react-headings";
|
|
125
38
|
|
|
126
|
-
function
|
|
39
|
+
function Page() {
|
|
127
40
|
return (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
41
|
+
<>
|
|
42
|
+
<H>Star Wars</H> {/* h1 */}
|
|
43
|
+
<HeadingLevel>
|
|
44
|
+
<Card title="Characters"> {/* h2 */}
|
|
45
|
+
<Card title="Luke" /> {/* h3 */}
|
|
46
|
+
<Card title="Leia" /> {/* h3 */}
|
|
47
|
+
<Card title="Han Solo" /> {/* h3 */}
|
|
48
|
+
</Card>
|
|
49
|
+
</HeadingLevel>
|
|
50
|
+
</>
|
|
131
51
|
);
|
|
132
52
|
}
|
|
133
|
-
```
|
|
134
53
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
A heading can be as complex as we want:
|
|
138
|
-
|
|
139
|
-
```jsx
|
|
140
|
-
import React from "react";
|
|
141
|
-
import { H, Section } from "react-headings";
|
|
142
|
-
import MyIcon from "./MyIcon";
|
|
143
|
-
|
|
144
|
-
function App() {
|
|
54
|
+
function Card({ title, children }) {
|
|
145
55
|
return (
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<H className="my-heading">My hx</H>
|
|
151
|
-
</div>
|
|
152
|
-
}
|
|
153
|
-
>
|
|
154
|
-
<div>...</div>
|
|
155
|
-
<div>...</div>
|
|
156
|
-
<div>...</div>
|
|
157
|
-
</Section>
|
|
56
|
+
<article>
|
|
57
|
+
<H>{title}</H>
|
|
58
|
+
<HeadingLevel>{children}</HeadingLevel>
|
|
59
|
+
</article>
|
|
158
60
|
);
|
|
159
61
|
}
|
|
160
62
|
```
|
|
161
63
|
|
|
162
|
-
|
|
64
|
+
`Card` doesn't know its level: it renders `h2` for Characters, `h3` for each character, and `h1` on its own.
|
|
163
65
|
|
|
164
|
-
|
|
165
|
-
Nested sections continue from that level.
|
|
66
|
+
### Dialogs
|
|
166
67
|
|
|
167
|
-
|
|
168
|
-
import React from "react";
|
|
169
|
-
import { Section, H } from "react-headings";
|
|
68
|
+
Portaled elements, like dialogs, aren't part of the page outline. Use `level` to start their headings at `h1`:
|
|
170
69
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
My dialog content
|
|
178
|
-
</Section>
|
|
179
|
-
</Dialog>
|
|
180
|
-
</Section>
|
|
181
|
-
</Section>
|
|
182
|
-
);
|
|
183
|
-
}
|
|
70
|
+
```tsx
|
|
71
|
+
<Dialog>
|
|
72
|
+
<HeadingLevel level={1}>
|
|
73
|
+
<H>Delete file?</H> {/* h1 */}
|
|
74
|
+
</HeadingLevel>
|
|
75
|
+
</Dialog>
|
|
184
76
|
```
|
|
185
77
|
|
|
186
|
-
###
|
|
187
|
-
|
|
188
|
-
Leveraging `Component` and `level` from the context allows the use of component libraries.
|
|
189
|
-
Here's an example with [Material UI](https://material-ui.com/api/typography/):
|
|
190
|
-
|
|
191
|
-
```jsx
|
|
192
|
-
import React from "react";
|
|
193
|
-
import { useLevel } from "react-headings";
|
|
194
|
-
import { Typography } from "@material-ui/core";
|
|
78
|
+
### Styling
|
|
195
79
|
|
|
196
|
-
|
|
197
|
-
const { Component } = useLevel();
|
|
80
|
+
React Headings only picks the tag. Style headings however you like, or use `render` for your own component:
|
|
198
81
|
|
|
199
|
-
|
|
200
|
-
}
|
|
82
|
+
```tsx
|
|
83
|
+
<H render={(props, { tag }) => <Text as={tag} size="lg" {...props} />}>
|
|
84
|
+
Title
|
|
85
|
+
</H>
|
|
201
86
|
```
|
|
202
87
|
|
|
203
88
|
## API
|
|
204
89
|
|
|
205
|
-
###
|
|
206
|
-
|
|
207
|
-
Renders a `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>` or `<h6>` depending on the current level.
|
|
208
|
-
|
|
209
|
-
#### Props
|
|
210
|
-
|
|
211
|
-
| Name | Type | Required | Description |
|
|
212
|
-
| ---------- | ---------- | -------- | --------------------------------------------------------------- |
|
|
213
|
-
| `render` | `function` | No | Override with a custom heading. Has precedence over `children`. |
|
|
214
|
-
| `children` | `node` | No | The content of the heading. Usually the title. |
|
|
215
|
-
|
|
216
|
-
Any other props will be passed to the heading element.
|
|
217
|
-
|
|
218
|
-
#### Example
|
|
219
|
-
|
|
220
|
-
```jsx
|
|
221
|
-
import React from "react";
|
|
222
|
-
import { H } from "react-headings";
|
|
223
|
-
|
|
224
|
-
function Example1() {
|
|
225
|
-
return <H>This is my title</H>;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function Example2() {
|
|
229
|
-
return (
|
|
230
|
-
<H render={({ level, Component }) => <Component>My h{level}</Component>} />
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### `<Section>` component
|
|
90
|
+
### `Heading` / `H`
|
|
236
91
|
|
|
237
|
-
|
|
92
|
+
Renders `h1`–`h6` based on the current level: `h1` by default, and never deeper than `h6`.
|
|
238
93
|
|
|
239
|
-
|
|
94
|
+
Accepts every `h1` prop and a `ref`, plus:
|
|
240
95
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
| `
|
|
244
|
-
| `children` | `node` | No | The content of the new level. |
|
|
245
|
-
| `level` | `1-6` | No | Renders `component` at this level instead of the current one. |
|
|
96
|
+
| Prop | Type | Description |
|
|
97
|
+
| -------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `render` | `(props, { level, tag }) => ReactElement` | Renders your own element. Spread `props` on it (includes `children` and `ref`). |
|
|
246
99
|
|
|
247
|
-
|
|
100
|
+
`H` is the same component as `Heading`. Pick one name per project.
|
|
248
101
|
|
|
249
|
-
|
|
250
|
-
import React from "react";
|
|
251
|
-
import { Section, H } from "react-headings";
|
|
102
|
+
### `HeadingLevel`
|
|
252
103
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
</Section>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
104
|
+
| Prop | Type | Default | Description |
|
|
105
|
+
| -------- | -------- | ------- | ------------------------------------------------ |
|
|
106
|
+
| `offset` | `number` | `1` | Levels added to headings inside. |
|
|
107
|
+
| `level` | `number` | | Level of headings inside, ignoring parents. |
|
|
260
108
|
|
|
261
|
-
|
|
262
|
-
return (
|
|
263
|
-
<Section
|
|
264
|
-
component={
|
|
265
|
-
<div>
|
|
266
|
-
<div>
|
|
267
|
-
<H>This is my title</H>
|
|
268
|
-
</div>
|
|
269
|
-
</div>
|
|
270
|
-
}
|
|
271
|
-
>
|
|
272
|
-
This is my content
|
|
273
|
-
</Section>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### `useLevel` hook
|
|
279
|
-
|
|
280
|
-
Returns an object containing the current `level` and current `Component`.
|
|
281
|
-
|
|
282
|
-
#### Arguments
|
|
109
|
+
Pass `offset` or `level`, not both. Nested offsets add up. `offset={0}` adds nothing, which is useful for conditional nesting.
|
|
283
110
|
|
|
284
|
-
|
|
111
|
+
Invalid values never throw: they're ignored with a warning, as if not passed. `null` counts as not passed, without a warning.
|
|
285
112
|
|
|
286
|
-
|
|
113
|
+
### `useHeadingLevel()`
|
|
287
114
|
|
|
288
|
-
|
|
289
|
-
| ----------- | -------------------------------------------------------- | ------------------------------------- |
|
|
290
|
-
| `level` | `1` \| `2` \| `3` \| `4` \| `5` \| `6` | The current level. |
|
|
291
|
-
| `Component` | `"h1"` \| `"h2"` \| `"h3"` \| `"h4"` \| `"h5"` \| `"h6"` | The current component. Same as level. |
|
|
115
|
+
Returns the `level` (`1`–`6`) and `tag` (`"h1"`–`"h6"`) a `Heading` would render here.
|
|
292
116
|
|
|
293
|
-
|
|
117
|
+
## Development warnings
|
|
294
118
|
|
|
295
|
-
|
|
296
|
-
import React from "react";
|
|
297
|
-
import { useLevel } from "react-headings";
|
|
298
|
-
|
|
299
|
-
function Example(props) {
|
|
300
|
-
const { level, Component } = useLevel();
|
|
301
|
-
|
|
302
|
-
return <Component {...props}>This is a h{level}</Component>;
|
|
303
|
-
}
|
|
304
|
-
```
|
|
119
|
+
In development, React Headings warns in the console when:
|
|
305
120
|
|
|
306
|
-
|
|
121
|
+
- a level is skipped,
|
|
122
|
+
- a level goes past 6,
|
|
123
|
+
- an `offset` or `level` is invalid.
|
|
307
124
|
|
|
308
|
-
|
|
125
|
+
Warnings are removed from production builds. Vite and webpack show them automatically in dev mode. In plain Node, run with `--conditions=development`.
|
|
309
126
|
|
|
310
|
-
##
|
|
127
|
+
## License
|
|
311
128
|
|
|
312
|
-
|
|
129
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ComponentPropsWithoutRef, ComponentPropsWithRef, ReactElement, ReactNode } from "react";
|
|
2
2
|
type Level = 1 | 2 | 3 | 4 | 5 | 6;
|
|
3
|
-
type
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
type Tag = `h${Level}`;
|
|
4
|
+
export type HeadingProps = ComponentPropsWithoutRef<"h1"> & {
|
|
5
|
+
render?: (props: ComponentPropsWithRef<"h1">, heading: {
|
|
6
|
+
level: Level;
|
|
7
|
+
tag: Tag;
|
|
8
|
+
}) => ReactElement;
|
|
7
9
|
};
|
|
10
|
+
export type HeadingLevelProps = {
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
} & ({
|
|
13
|
+
offset?: number;
|
|
14
|
+
level?: never;
|
|
15
|
+
} | {
|
|
16
|
+
level: number;
|
|
17
|
+
offset?: never;
|
|
18
|
+
});
|
|
8
19
|
/**
|
|
9
|
-
*
|
|
20
|
+
* Renders an `h1`–`h6` according to the current level.
|
|
10
21
|
*/
|
|
11
|
-
export declare
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
export declare const Heading: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & {
|
|
23
|
+
render?: (props: ComponentPropsWithRef<"h1">, heading: {
|
|
24
|
+
level: Level;
|
|
25
|
+
tag: Tag;
|
|
26
|
+
}) => ReactElement;
|
|
27
|
+
} & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
28
|
+
export { Heading as H };
|
|
15
29
|
/**
|
|
16
|
-
* Renders
|
|
30
|
+
* Renders headings inside `offset` levels deeper (default 1), or at `level`
|
|
31
|
+
* regardless of ancestors.
|
|
17
32
|
*/
|
|
18
|
-
export declare
|
|
19
|
-
type LevelProps = {
|
|
20
|
-
level?: Level;
|
|
21
|
-
children?: React.ReactNode;
|
|
22
|
-
};
|
|
33
|
+
export declare function HeadingLevel({ offset, level, children, }: HeadingLevelProps): import("react").JSX.Element;
|
|
23
34
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param level The level to render `children` at
|
|
26
|
-
* @param children The children to render
|
|
35
|
+
* Returns the level and tag a `Heading` would render at this position.
|
|
27
36
|
*/
|
|
28
|
-
declare function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
children?: React.ReactNode;
|
|
32
|
-
level?: Level;
|
|
37
|
+
export declare function useHeadingLevel(): {
|
|
38
|
+
level: Level;
|
|
39
|
+
tag: Tag;
|
|
33
40
|
};
|
|
34
|
-
/**
|
|
35
|
-
* Renders `component` in the current level and `children` in the next level.
|
|
36
|
-
* @param component A component containing a heading
|
|
37
|
-
* @param children The children in the next level
|
|
38
|
-
* @param level A level to render `component` at instead of the current one (e.g. for dialogs and portals)
|
|
39
|
-
*/
|
|
40
|
-
export declare function Section({ component, children, level, }: SectionProps): JSX.Element;
|
|
41
|
-
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/index.tsx
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
forwardRef,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo
|
|
10
|
+
} from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
var LevelContext = createContext(1);
|
|
13
|
+
function isValidOffset(offset) {
|
|
14
|
+
return Number.isInteger(offset) && offset >= 0;
|
|
15
|
+
}
|
|
16
|
+
function isValidLevel(level) {
|
|
17
|
+
return Number.isInteger(level) && level >= 1;
|
|
18
|
+
}
|
|
19
|
+
var Heading = forwardRef(
|
|
20
|
+
function({ render, ...props }, ref) {
|
|
21
|
+
let { level, tag } = useHeadingLevel();
|
|
22
|
+
return useHeadingChecks(), render ? render({ ...props, ref }, { level, tag }) : /* @__PURE__ */ jsx(tag, { ref, ...props });
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
Heading.displayName = "Heading";
|
|
26
|
+
function HeadingLevel({
|
|
27
|
+
offset = 1,
|
|
28
|
+
level,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
let parentLevel = useContext(LevelContext), isReset = isValidLevel(level), isOffsetValid = isValidOffset(offset), innerLevel = isReset ? level : parentLevel + (isOffsetValid ? offset : 1);
|
|
32
|
+
return useInvalidPropWarning("offset", offset, isOffsetValid), useInvalidPropWarning("level", level, isReset), children = /* @__PURE__ */ jsx(
|
|
33
|
+
SkippedLevelScope,
|
|
34
|
+
{
|
|
35
|
+
firstLevel: isReset ? innerLevel : parentLevel + 1,
|
|
36
|
+
isIsolated: isReset,
|
|
37
|
+
isPassthrough: !isReset && offset === 0,
|
|
38
|
+
children
|
|
39
|
+
}
|
|
40
|
+
), /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
|
|
41
|
+
}
|
|
42
|
+
function useHeadingLevel() {
|
|
43
|
+
let level = Math.min(useContext(LevelContext), 6);
|
|
44
|
+
return { level, tag: `h${level}` };
|
|
45
|
+
}
|
|
46
|
+
var ScopeContext = /* @__PURE__ */ createContext(null);
|
|
47
|
+
function createScopeRegistry(firstLevel, parentRegistry) {
|
|
48
|
+
let headingLevels = /* @__PURE__ */ new Map(), unregisterFromParent, isCheckScheduled = !1, hasWarned = !1;
|
|
49
|
+
function warnIfLevelSkipped() {
|
|
50
|
+
if (isCheckScheduled = !1, hasWarned || headingLevels.size === 0) return;
|
|
51
|
+
let presentLevels = new Set(headingLevels.values()), deepestLevel = Math.max(...presentLevels);
|
|
52
|
+
for (let level = firstLevel; level < deepestLevel; level++)
|
|
53
|
+
if (!presentLevels.has(level)) {
|
|
54
|
+
hasWarned = !0, console.warn(
|
|
55
|
+
`react-headings: Skipped heading level ${level}: found a level ${deepestLevel} heading but no level ${level} heading.`
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function scheduleCheck() {
|
|
61
|
+
isCheckScheduled || (isCheckScheduled = !0, queueMicrotask(warnIfLevelSkipped));
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
register(level) {
|
|
65
|
+
let token = {};
|
|
66
|
+
return headingLevels.set(token, level), headingLevels.size === 1 && (unregisterFromParent = parentRegistry?.register(firstLevel)), scheduleCheck(), () => {
|
|
67
|
+
headingLevels.delete(token), headingLevels.size === 0 && (unregisterFromParent?.(), unregisterFromParent = void 0), scheduleCheck();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function SkippedLevelScope({
|
|
73
|
+
firstLevel,
|
|
74
|
+
isIsolated,
|
|
75
|
+
isPassthrough,
|
|
76
|
+
children
|
|
77
|
+
}) {
|
|
78
|
+
let parentRegistry = useContext(ScopeContext), registry = useMemo(
|
|
79
|
+
() => isPassthrough ? parentRegistry : createScopeRegistry(firstLevel, isIsolated ? null : parentRegistry),
|
|
80
|
+
[firstLevel, isIsolated, isPassthrough, parentRegistry]
|
|
81
|
+
);
|
|
82
|
+
return /* @__PURE__ */ jsx(ScopeContext.Provider, { value: registry, children });
|
|
83
|
+
}
|
|
84
|
+
function useHeadingChecks() {
|
|
85
|
+
let level = useContext(LevelContext), scopeRegistry = useContext(ScopeContext);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
level > 6 && console.warn(
|
|
88
|
+
`react-headings: Heading level ${level} exceeds 6 and was clamped to h6.`
|
|
89
|
+
);
|
|
90
|
+
}, [level]), useEffect(() => scopeRegistry?.register(level), [scopeRegistry, level]);
|
|
91
|
+
}
|
|
92
|
+
function useInvalidPropWarning(prop, value, isValid) {
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
value != null && !isValid && console.warn(
|
|
95
|
+
`react-headings: HeadingLevel received invalid ${prop} ${value}, ignored.`
|
|
96
|
+
);
|
|
97
|
+
}, [prop, value, isValid]);
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
Heading as H,
|
|
101
|
+
Heading,
|
|
102
|
+
HeadingLevel,
|
|
103
|
+
useHeadingLevel
|
|
104
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,80 +1,43 @@
|
|
|
1
|
-
"use
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
16
|
-
t[p] = s[p];
|
|
17
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
18
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
19
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
20
|
-
t[p[i]] = s[p[i]];
|
|
21
|
-
}
|
|
22
|
-
return t;
|
|
23
|
-
};
|
|
24
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
25
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
26
|
-
};
|
|
27
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
-
exports.Section = exports.H = exports.useLevel = void 0;
|
|
29
|
-
var react_1 = __importDefault(require("react"));
|
|
30
|
-
var LevelContext = react_1.default.createContext({
|
|
31
|
-
level: 1,
|
|
32
|
-
Component: "h1",
|
|
33
|
-
});
|
|
34
|
-
/**
|
|
35
|
-
* Returns the current heading and level.
|
|
36
|
-
*/
|
|
37
|
-
function useLevel() {
|
|
38
|
-
return react_1.default.useContext(LevelContext);
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/index.tsx
|
|
4
|
+
import {
|
|
5
|
+
createContext,
|
|
6
|
+
forwardRef,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo
|
|
10
|
+
} from "react";
|
|
11
|
+
import { jsx } from "react/jsx-runtime";
|
|
12
|
+
var LevelContext = createContext(1);
|
|
13
|
+
function isValidOffset(offset) {
|
|
14
|
+
return Number.isInteger(offset) && offset >= 0;
|
|
39
15
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* Renders a dynamic HTML heading (h1, h2, etc.) or custom component according to the current level.
|
|
43
|
-
*/
|
|
44
|
-
exports.H = react_1.default.forwardRef(function H(_a, forwardedRef) {
|
|
45
|
-
var render = _a.render, props = __rest(_a, ["render"]);
|
|
46
|
-
var context = useLevel();
|
|
47
|
-
if (render) {
|
|
48
|
-
return render(context);
|
|
49
|
-
}
|
|
50
|
-
return react_1.default.createElement(context.Component, __assign({ ref: forwardedRef }, props));
|
|
51
|
-
});
|
|
52
|
-
/**
|
|
53
|
-
* Renders `children` 1 level down, or at `level` when provided.
|
|
54
|
-
* @param level The level to render `children` at
|
|
55
|
-
* @param children The children to render
|
|
56
|
-
*/
|
|
57
|
-
function Level(_a) {
|
|
58
|
-
var desiredLevel = _a.level, children = _a.children;
|
|
59
|
-
var currentLevel = useLevel().level;
|
|
60
|
-
var level = desiredLevel !== null && desiredLevel !== void 0 ? desiredLevel : Math.min(currentLevel + 1, 6);
|
|
61
|
-
var value = {
|
|
62
|
-
level: level,
|
|
63
|
-
Component: "h".concat(level),
|
|
64
|
-
};
|
|
65
|
-
return (react_1.default.createElement(LevelContext.Provider, { value: value }, children));
|
|
16
|
+
function isValidLevel(level) {
|
|
17
|
+
return Number.isInteger(level) && level >= 1;
|
|
66
18
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
19
|
+
var Heading = forwardRef(
|
|
20
|
+
function({ render, ...props }, ref) {
|
|
21
|
+
let { level, tag } = useHeadingLevel();
|
|
22
|
+
return render ? render({ ...props, ref }, { level, tag }) : /* @__PURE__ */ jsx(tag, { ref, ...props });
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
Heading.displayName = "Heading";
|
|
26
|
+
function HeadingLevel({
|
|
27
|
+
offset = 1,
|
|
28
|
+
level,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
let parentLevel = useContext(LevelContext), isReset = isValidLevel(level), isOffsetValid = isValidOffset(offset), innerLevel = isReset ? level : parentLevel + (isOffsetValid ? offset : 1);
|
|
32
|
+
return /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
|
|
79
33
|
}
|
|
80
|
-
|
|
34
|
+
function useHeadingLevel() {
|
|
35
|
+
let level = Math.min(useContext(LevelContext), 6);
|
|
36
|
+
return { level, tag: `h${level}` };
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
Heading as H,
|
|
40
|
+
Heading,
|
|
41
|
+
HeadingLevel,
|
|
42
|
+
useHeadingLevel
|
|
43
|
+
};
|
package/package.json
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-headings",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
3
|
+
"version": "4.0.0-next.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Dynamic heading levels for React.",
|
|
7
6
|
"author": "Alex Nault",
|
|
8
7
|
"keywords": [
|
|
9
8
|
"react",
|
|
10
9
|
"heading",
|
|
11
|
-
"
|
|
10
|
+
"headings",
|
|
12
11
|
"level",
|
|
12
|
+
"outline",
|
|
13
13
|
"accessibility",
|
|
14
14
|
"a11y",
|
|
15
15
|
"wcag",
|
|
16
|
-
"h1"
|
|
17
|
-
"header",
|
|
18
|
-
"title"
|
|
16
|
+
"h1"
|
|
19
17
|
],
|
|
20
18
|
"license": "MIT",
|
|
21
19
|
"repository": "https://github.com/alexnault/react-headings",
|
|
22
20
|
"homepage": "https://github.com/alexnault/react-headings#readme",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"development": "./dist/index.development.js",
|
|
26
|
+
"production": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
23
31
|
"scripts": {
|
|
24
|
-
"build": "rm -rf
|
|
32
|
+
"build": "rm -rf dist && node scripts/build.js && tsc -p tsconfig.build.json",
|
|
33
|
+
"typecheck": "tsc",
|
|
25
34
|
"format": "prettier ./ --write",
|
|
26
35
|
"lint": "eslint ./ --max-warnings=0",
|
|
27
36
|
"size": "size-limit",
|
|
@@ -32,26 +41,28 @@
|
|
|
32
41
|
],
|
|
33
42
|
"sideEffects": false,
|
|
34
43
|
"peerDependencies": {
|
|
35
|
-
"react": "^
|
|
44
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
36
45
|
},
|
|
37
46
|
"devDependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@vitest/coverage-
|
|
45
|
-
"conventional-changelog-conventionalcommits": "^
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"react
|
|
47
|
+
"@eslint/js": "^10.0.1",
|
|
48
|
+
"@size-limit/preset-small-lib": "^14.3.0",
|
|
49
|
+
"@testing-library/dom": "^10.4.2",
|
|
50
|
+
"@testing-library/react": "^16.3.3",
|
|
51
|
+
"@types/react": "^19.3.0",
|
|
52
|
+
"@types/react-dom": "^19.3.0",
|
|
53
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
54
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
55
|
+
"esbuild": "^0.28.2",
|
|
56
|
+
"eslint": "^10.14.0",
|
|
57
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
58
|
+
"jsdom": "^30.1.3",
|
|
59
|
+
"prettier": "^3.9.10",
|
|
60
|
+
"react": "^19.3.1",
|
|
61
|
+
"react-dom": "^19.3.1",
|
|
52
62
|
"semantic-release": "^25.0.3",
|
|
53
|
-
"size-limit": "^
|
|
54
|
-
"typescript": "^
|
|
55
|
-
"
|
|
63
|
+
"size-limit": "^14.3.0",
|
|
64
|
+
"typescript": "^5.9.3",
|
|
65
|
+
"typescript-eslint": "^8.72.1",
|
|
66
|
+
"vitest": "^5.0.5"
|
|
56
67
|
}
|
|
57
68
|
}
|