react-headings 3.3.4 → 4.0.0-next.1
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 +74 -221
- package/dist/index.d.ts +32 -21
- package/dist/index.development.js +108 -0
- package/dist/index.js +40 -67
- package/package.json +39 -27
package/README.md
CHANGED
|
@@ -9,279 +9,132 @@
|
|
|
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/)
|
|
28
|
-
|
|
29
|
-
## Table of contents
|
|
30
|
-
|
|
31
|
-
- [Demos](#demos)
|
|
32
|
-
- [Highlights](#highlights)
|
|
33
|
-
- [Installation](#installation)
|
|
34
|
-
- [Examples](#examples)
|
|
35
|
-
- [API](#api)
|
|
36
|
-
- [Changelog](#changelog)
|
|
37
|
-
- [Contributing](#contributing)
|
|
14
|
+
> Never write `h1`–`h6` again.
|
|
38
15
|
|
|
39
|
-
|
|
16
|
+
Search engines and screen readers rely on heading levels, but reusable components can't know how deep they'll be rendered. React Headings works out each level from your component tree, like the HTML [`headingoffset`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/headingoffset) proposal, but in any browser today.
|
|
40
17
|
|
|
41
|
-
|
|
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)
|
|
18
|
+
## Features
|
|
44
19
|
|
|
45
|
-
## Highlights
|
|
46
|
-
|
|
47
|
-
- Improves SEO and accessibility
|
|
48
20
|
- Supports server-side rendering
|
|
49
|
-
-
|
|
21
|
+
- 384 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
|
|
32
|
+
## Usage
|
|
87
33
|
|
|
88
|
-
|
|
34
|
+
Use `H` for every heading. Wrap the content that belongs under a heading in `HeadingLevel`: headings inside render one level deeper.
|
|
89
35
|
|
|
90
|
-
```
|
|
91
|
-
import
|
|
92
|
-
import { H, Section } from "react-headings";
|
|
36
|
+
```tsx
|
|
37
|
+
import { H, HeadingLevel } from "react-headings";
|
|
93
38
|
|
|
94
|
-
function
|
|
39
|
+
function Page() {
|
|
95
40
|
return (
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
</>
|
|
103
51
|
);
|
|
104
52
|
}
|
|
105
53
|
|
|
106
|
-
function
|
|
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
|
|
119
|
-
|
|
120
|
-
A heading can be styled like any ordinary `<hx>` element since it accepts all the same props:
|
|
121
|
-
|
|
122
|
-
```jsx
|
|
123
|
-
import React from "react";
|
|
124
|
-
import { H, Section } from "react-headings";
|
|
125
|
-
|
|
126
|
-
function App() {
|
|
54
|
+
function Card({ title, children }) {
|
|
127
55
|
return (
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
56
|
+
<article>
|
|
57
|
+
<H>{title}</H>
|
|
58
|
+
<HeadingLevel>{children}</HeadingLevel>
|
|
59
|
+
</article>
|
|
131
60
|
);
|
|
132
61
|
}
|
|
133
62
|
```
|
|
134
63
|
|
|
135
|
-
|
|
64
|
+
`Card` doesn't know its level: it renders `h2` for Characters, `h3` for each character, and `h1` on its own.
|
|
136
65
|
|
|
137
|
-
|
|
66
|
+
### Dialogs
|
|
138
67
|
|
|
139
|
-
|
|
140
|
-
import React from "react";
|
|
141
|
-
import { H, Section } from "react-headings";
|
|
142
|
-
import MyIcon from "./MyIcon";
|
|
68
|
+
Portaled elements, like dialogs, aren't part of the page outline. Use `level` to start their headings at `h1`:
|
|
143
69
|
|
|
144
|
-
|
|
145
|
-
|
|
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>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
70
|
+
```tsx
|
|
71
|
+
<Dialog>
|
|
72
|
+
<HeadingLevel level={1}>
|
|
73
|
+
<H>Delete file?</H> {/* h1 */}
|
|
74
|
+
</HeadingLevel>
|
|
75
|
+
</Dialog>
|
|
160
76
|
```
|
|
161
77
|
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
Leveraging `Component` and `level` from the context allows the use of component libraries.
|
|
165
|
-
Here's an example with [Material UI](https://material-ui.com/api/typography/):
|
|
166
|
-
|
|
167
|
-
```jsx
|
|
168
|
-
import React from "react";
|
|
169
|
-
import { useLevel } from "react-headings";
|
|
170
|
-
import { Typography } from "@material-ui/core";
|
|
78
|
+
### Styling
|
|
171
79
|
|
|
172
|
-
|
|
173
|
-
const { Component } = useLevel();
|
|
80
|
+
React Headings only picks the tag. Style headings however you like, or use `render` for your own component:
|
|
174
81
|
|
|
175
|
-
|
|
176
|
-
}
|
|
82
|
+
```tsx
|
|
83
|
+
<H render={(props, { tag }) => <Text as={tag} size="lg" {...props} />}>
|
|
84
|
+
Title
|
|
85
|
+
</H>
|
|
177
86
|
```
|
|
178
87
|
|
|
179
88
|
## API
|
|
180
89
|
|
|
181
|
-
###
|
|
182
|
-
|
|
183
|
-
Renders a `<h1>`, `<h2>`, `<h3>`, `<h4>`, `<h5>` or `<h6>` depending on the current level.
|
|
184
|
-
|
|
185
|
-
#### Props
|
|
186
|
-
|
|
187
|
-
| Name | Type | Required | Description |
|
|
188
|
-
| ---------- | ---------- | -------- | --------------------------------------------------------------- |
|
|
189
|
-
| `render` | `function` | No | Override with a custom heading. Has precedence over `children`. |
|
|
190
|
-
| `children` | `node` | No | The content of the heading. Usually the title. |
|
|
191
|
-
|
|
192
|
-
Any other props will be passed to the heading element.
|
|
193
|
-
|
|
194
|
-
#### Example
|
|
195
|
-
|
|
196
|
-
```jsx
|
|
197
|
-
import React from "react";
|
|
198
|
-
import { H } from "react-headings";
|
|
199
|
-
|
|
200
|
-
function Example1() {
|
|
201
|
-
return <H>This is my title</H>;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function Example2() {
|
|
205
|
-
return (
|
|
206
|
-
<H render={({ level, Component }) => <Component>My h{level}</Component>} />
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### `<Section>` component
|
|
90
|
+
### `Heading` / `H`
|
|
212
91
|
|
|
213
|
-
|
|
92
|
+
Renders `h1`–`h6` based on the current level: `h1` by default, and never deeper than `h6`.
|
|
214
93
|
|
|
215
|
-
|
|
94
|
+
Accepts every `h1` prop and a `ref`, plus:
|
|
216
95
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
| `
|
|
220
|
-
| `children` | `node` | No | The content of the new level. |
|
|
96
|
+
| Prop | Type | Description |
|
|
97
|
+
| -------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| `render` | `(props, { level, tag }) => ReactElement` | Renders your own element. Spread `props` on it (includes `children` and `ref`). |
|
|
221
99
|
|
|
222
|
-
|
|
100
|
+
`H` is the same component as `Heading`. Pick one name per project.
|
|
223
101
|
|
|
224
|
-
|
|
225
|
-
import React from "react";
|
|
226
|
-
import { Section, H } from "react-headings";
|
|
227
|
-
|
|
228
|
-
function Example1() {
|
|
229
|
-
return (
|
|
230
|
-
<Section component={<H>This is my title</H>}>
|
|
231
|
-
This is my content
|
|
232
|
-
</Section>
|
|
233
|
-
);
|
|
234
|
-
}
|
|
102
|
+
### `HeadingLevel`
|
|
235
103
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
<div>
|
|
241
|
-
<div>
|
|
242
|
-
<H>This is my title</H>
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
}
|
|
246
|
-
>
|
|
247
|
-
This is my content
|
|
248
|
-
</Section>
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
```
|
|
104
|
+
| Prop | Type | Default | Description |
|
|
105
|
+
| -------- | -------- | ------- | ------------------------------------------------ |
|
|
106
|
+
| `offset` | `number` | `1` | Levels added to headings inside. |
|
|
107
|
+
| `level` | `number` | | Level of headings inside, ignoring parents. |
|
|
252
108
|
|
|
253
|
-
|
|
109
|
+
Pass `offset` or `level`, not both. Nested offsets add up. `offset={0}` adds nothing, which is useful for conditional nesting.
|
|
254
110
|
|
|
255
|
-
|
|
111
|
+
Invalid values never throw: fractions round down, and anything else invalid counts as `offset={0}` or `level={1}`.
|
|
256
112
|
|
|
257
|
-
|
|
113
|
+
### `useHeadingLevel()`
|
|
258
114
|
|
|
259
|
-
|
|
115
|
+
Returns the `level` (`1`–`6`) and `tag` (`"h1"`–`"h6"`) a `Heading` would render here.
|
|
260
116
|
|
|
261
|
-
|
|
117
|
+
## Development warnings
|
|
262
118
|
|
|
263
|
-
|
|
264
|
-
| ----------- | -------------------------------------------------------- | ------------------------------------- |
|
|
265
|
-
| `level` | `1` \| `2` \| `3` \| `4` \| `5` \| `6` | The current level. |
|
|
266
|
-
| `Component` | `"h1"` \| `"h2"` \| `"h3"` \| `"h4"` \| `"h5"` \| `"h6"` | The current component. Same as level. |
|
|
119
|
+
In development, React Headings warns in the console when:
|
|
267
120
|
|
|
268
|
-
|
|
121
|
+
- a level is skipped, like an `h3` with no `h2` above it,
|
|
122
|
+
- a level goes past 6,
|
|
123
|
+
- an `offset` or `level` is invalid.
|
|
269
124
|
|
|
270
|
-
|
|
271
|
-
import React from "react";
|
|
272
|
-
import { useLevel } from "react-headings";
|
|
125
|
+
Warnings are removed from production builds. Vite and webpack show them automatically in dev mode. In plain Node or Jest, run with `--conditions=development`.
|
|
273
126
|
|
|
274
|
-
|
|
275
|
-
const { level, Component } = useLevel();
|
|
127
|
+
## Server Components
|
|
276
128
|
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
```
|
|
129
|
+
Works in Server Components, including Server Components nested inside `HeadingLevel`. Only `useHeadingLevel()` needs a Client Component.
|
|
280
130
|
|
|
281
|
-
##
|
|
131
|
+
## Differences from `headingoffset`
|
|
282
132
|
|
|
283
|
-
|
|
133
|
+
- Levels follow the React tree, not the DOM, so portaled content inherits its level.
|
|
134
|
+
- `Heading` always starts at level 1, while a native `h2` starts at 2.
|
|
135
|
+
- Levels stop at 6, while `headingoffset` goes up to 9 through `aria-level`.
|
|
136
|
+
- The output is a real `h1`–`h6` tag, so CSS like `h2 { … }` matches the level.
|
|
284
137
|
|
|
285
|
-
##
|
|
138
|
+
## License
|
|
286
139
|
|
|
287
|
-
|
|
140
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +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 SectionProps = {
|
|
20
|
-
component: React.ReactNode;
|
|
21
|
-
children?: React.ReactNode;
|
|
22
|
-
};
|
|
33
|
+
export declare function HeadingLevel({ offset, level, children, }: HeadingLevelProps): import("react").JSX.Element;
|
|
23
34
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param component A component containing a heading
|
|
26
|
-
* @param children The children in the next level
|
|
35
|
+
* Returns the level and tag a `Heading` would render at this position.
|
|
27
36
|
*/
|
|
28
|
-
export declare function
|
|
29
|
-
|
|
37
|
+
export declare function useHeadingLevel(): {
|
|
38
|
+
level: Level;
|
|
39
|
+
tag: Tag;
|
|
40
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
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 getNormalizedOffset(offset) {
|
|
14
|
+
return Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;
|
|
15
|
+
}
|
|
16
|
+
function getNormalizedLevel(level) {
|
|
17
|
+
return Number.isFinite(level) && level > 1 ? Math.floor(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 = level !== void 0, normalizedOffset = getNormalizedOffset(offset), innerLevel = isReset ? getNormalizedLevel(level) : parentLevel + normalizedOffset;
|
|
32
|
+
return useInvalidPropWarning(
|
|
33
|
+
isReset ? "level" : "offset",
|
|
34
|
+
isReset ? level : offset,
|
|
35
|
+
isReset ? innerLevel : normalizedOffset
|
|
36
|
+
), children = /* @__PURE__ */ jsx(
|
|
37
|
+
SkippedLevelScope,
|
|
38
|
+
{
|
|
39
|
+
firstLevel: isReset ? innerLevel : parentLevel + 1,
|
|
40
|
+
isIsolated: isReset,
|
|
41
|
+
isPassthrough: !isReset && normalizedOffset === 0,
|
|
42
|
+
children
|
|
43
|
+
}
|
|
44
|
+
), /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
|
|
45
|
+
}
|
|
46
|
+
function useHeadingLevel() {
|
|
47
|
+
let level = Math.min(useContext(LevelContext), 6);
|
|
48
|
+
return { level, tag: `h${level}` };
|
|
49
|
+
}
|
|
50
|
+
var ScopeContext = /* @__PURE__ */ createContext(null);
|
|
51
|
+
function createScopeRegistry(firstLevel, parentRegistry) {
|
|
52
|
+
let headingLevels = /* @__PURE__ */ new Map(), unregisterFromParent, isCheckScheduled = !1, hasWarned = !1;
|
|
53
|
+
function warnIfLevelSkipped() {
|
|
54
|
+
if (isCheckScheduled = !1, hasWarned || headingLevels.size === 0) return;
|
|
55
|
+
let presentLevels = new Set(headingLevels.values()), deepestLevel = Math.max(...presentLevels);
|
|
56
|
+
for (let level = firstLevel; level < deepestLevel; level++)
|
|
57
|
+
if (!presentLevels.has(level)) {
|
|
58
|
+
hasWarned = !0, console.warn(
|
|
59
|
+
`react-headings: Skipped heading level ${level}: found a level ${deepestLevel} heading but no level ${level} heading.`
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function scheduleCheck() {
|
|
65
|
+
isCheckScheduled || (isCheckScheduled = !0, queueMicrotask(warnIfLevelSkipped));
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
register(level) {
|
|
69
|
+
let token = {};
|
|
70
|
+
return headingLevels.set(token, level), headingLevels.size === 1 && (unregisterFromParent = parentRegistry?.register(firstLevel)), scheduleCheck(), () => {
|
|
71
|
+
headingLevels.delete(token), headingLevels.size === 0 && (unregisterFromParent?.(), unregisterFromParent = void 0), scheduleCheck();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function SkippedLevelScope({
|
|
77
|
+
firstLevel,
|
|
78
|
+
isIsolated,
|
|
79
|
+
isPassthrough,
|
|
80
|
+
children
|
|
81
|
+
}) {
|
|
82
|
+
let parentRegistry = useContext(ScopeContext), registry = useMemo(
|
|
83
|
+
() => isPassthrough ? parentRegistry : createScopeRegistry(firstLevel, isIsolated ? null : parentRegistry),
|
|
84
|
+
[firstLevel, isIsolated, isPassthrough, parentRegistry]
|
|
85
|
+
);
|
|
86
|
+
return /* @__PURE__ */ jsx(ScopeContext.Provider, { value: registry, children });
|
|
87
|
+
}
|
|
88
|
+
function useHeadingChecks() {
|
|
89
|
+
let level = useContext(LevelContext), scopeRegistry = useContext(ScopeContext);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
level > 6 && console.warn(
|
|
92
|
+
`react-headings: Heading level ${level} exceeds 6 and was clamped to h6.`
|
|
93
|
+
);
|
|
94
|
+
}, [level]), useEffect(() => scopeRegistry?.register(level), [scopeRegistry, level]);
|
|
95
|
+
}
|
|
96
|
+
function useInvalidPropWarning(prop, value, normalizedValue) {
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
value !== normalizedValue && console.warn(
|
|
99
|
+
`react-headings: HeadingLevel received invalid ${prop} ${value}, treated as ${normalizedValue}.`
|
|
100
|
+
);
|
|
101
|
+
}, [prop, value, normalizedValue]);
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
Heading as H,
|
|
105
|
+
Heading,
|
|
106
|
+
HeadingLevel,
|
|
107
|
+
useHeadingLevel
|
|
108
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,70 +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 getNormalizedOffset(offset) {
|
|
14
|
+
return Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;
|
|
39
15
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
var render = _a.render, props = __rest(_a, ["render"]);
|
|
43
|
-
var context = useLevel();
|
|
44
|
-
if (render) {
|
|
45
|
-
return render(context);
|
|
46
|
-
}
|
|
47
|
-
return react_1.default.createElement(context.Component, __assign({ ref: forwardedRef }, props));
|
|
16
|
+
function getNormalizedLevel(level) {
|
|
17
|
+
return Number.isFinite(level) && level > 1 ? Math.floor(level) : 1;
|
|
48
18
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
level: nextLevel,
|
|
64
|
-
Component: "h".concat(nextLevel),
|
|
65
|
-
};
|
|
66
|
-
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
67
|
-
component,
|
|
68
|
-
react_1.default.createElement(LevelContext.Provider, { value: value }, children)));
|
|
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 = level !== void 0, normalizedOffset = getNormalizedOffset(offset), innerLevel = isReset ? getNormalizedLevel(level) : parentLevel + normalizedOffset;
|
|
32
|
+
return /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
|
|
69
33
|
}
|
|
70
|
-
|
|
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,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-headings",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
3
|
+
"version": "4.0.0-next.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Dynamic heading levels for React. A polyfill of the HTML headingoffset proposal.",
|
|
7
6
|
"author": "Alex Nault",
|
|
8
7
|
"keywords": [
|
|
9
8
|
"react",
|
|
10
9
|
"heading",
|
|
11
|
-
"
|
|
10
|
+
"headings",
|
|
11
|
+
"headingoffset",
|
|
12
|
+
"headingreset",
|
|
12
13
|
"level",
|
|
14
|
+
"outline",
|
|
13
15
|
"accessibility",
|
|
14
16
|
"a11y",
|
|
15
17
|
"wcag",
|
|
16
|
-
"h1"
|
|
17
|
-
"header",
|
|
18
|
-
"title"
|
|
18
|
+
"h1"
|
|
19
19
|
],
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"repository": "https://github.com/alexnault/react-headings",
|
|
22
22
|
"homepage": "https://github.com/alexnault/react-headings#readme",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"development": "./dist/index.development.js",
|
|
27
|
+
"production": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
23
32
|
"scripts": {
|
|
24
|
-
"build": "rm -rf
|
|
33
|
+
"build": "rm -rf dist && node scripts/build.js && tsc -p tsconfig.build.json",
|
|
34
|
+
"typecheck": "tsc",
|
|
25
35
|
"format": "prettier ./ --write",
|
|
26
36
|
"lint": "eslint ./ --max-warnings=0",
|
|
27
37
|
"size": "size-limit",
|
|
@@ -32,26 +42,28 @@
|
|
|
32
42
|
],
|
|
33
43
|
"sideEffects": false,
|
|
34
44
|
"peerDependencies": {
|
|
35
|
-
"react": "^
|
|
45
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
36
46
|
},
|
|
37
47
|
"devDependencies": {
|
|
38
|
-
"@
|
|
39
|
-
"@
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
42
|
-
"@
|
|
43
|
-
"@
|
|
44
|
-
"@vitest/coverage-
|
|
45
|
-
"conventional-changelog-conventionalcommits": "^
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"react
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
49
|
+
"@size-limit/preset-small-lib": "^14.3.0",
|
|
50
|
+
"@testing-library/dom": "^10.4.2",
|
|
51
|
+
"@testing-library/react": "^16.3.3",
|
|
52
|
+
"@types/react": "^19.3.0",
|
|
53
|
+
"@types/react-dom": "^19.3.0",
|
|
54
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
55
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
56
|
+
"esbuild": "^0.28.2",
|
|
57
|
+
"eslint": "^10.14.0",
|
|
58
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
59
|
+
"jsdom": "^30.1.3",
|
|
60
|
+
"prettier": "^3.9.10",
|
|
61
|
+
"react": "^19.3.1",
|
|
62
|
+
"react-dom": "^19.3.1",
|
|
52
63
|
"semantic-release": "^25.0.3",
|
|
53
|
-
"size-limit": "^
|
|
54
|
-
"typescript": "^
|
|
55
|
-
"
|
|
64
|
+
"size-limit": "^14.3.0",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.72.1",
|
|
67
|
+
"vitest": "^5.0.5"
|
|
56
68
|
}
|
|
57
69
|
}
|