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 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 worry about using the wrong heading level (`h1`, `h2`, etc.) in complex React apps!
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
- ## Table of contents
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
- - [Demos](#demos)
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
- - Under 1 kB minified & gzipped
21
+ - 370 bytes minified & brotlied
50
22
  - Typed with TypeScript
51
- - Fully tested
52
- - Works with any CSS solutions (Tailwind, CSS-in-JS, etc.)
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
- ## Installation
26
+ ## Install
57
27
 
58
- ```bash
28
+ ```sh
59
29
  npm install react-headings
60
30
  ```
61
31
 
62
- ## Examples
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
- A heading can be styled like any ordinary `<hx>` element since it accepts all the same props:
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
- ```jsx
123
- import React from "react";
124
- import { H, Section } from "react-headings";
36
+ ```tsx
37
+ import { H, HeadingLevel } from "react-headings";
125
38
 
126
- function App() {
39
+ function Page() {
127
40
  return (
128
- <Section component={<H className="my-class">My hx</H>}>
129
- ...
130
- </Section>
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
- ### Custom heading
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
- <Section
147
- component={
148
- <div className="my-div">
149
- <MyIcon className="my-icon" />
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
- ### Dialogs and portals
64
+ `Card` doesn't know its level: it renders `h2` for Characters, `h3` for each character, and `h1` on its own.
163
65
 
164
- Content rendered elsewhere in the DOM (e.g. dialogs portalled to the end of `<body>`) can reset the heading hierarchy with the `level` prop.
165
- Nested sections continue from that level.
66
+ ### Dialogs
166
67
 
167
- ```jsx
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
- function App() {
172
- return (
173
- <Section component={<H>My H1</H>}>
174
- <Section component={<H>My H2</H>}>
175
- <Dialog>
176
- <Section level={1} component={<H>My dialog title (H1)</H>}>
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
- ### Using component libraries
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
- function MyHeading(props) {
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
- return <Typography component={Component} {...props} />;
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
- ### `<H>` component
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
- Creates a new section (a heading and its level).
92
+ Renders `h1`–`h6` based on the current level: `h1` by default, and never deeper than `h6`.
238
93
 
239
- #### Props
94
+ Accepts every `h1` prop and a `ref`, plus:
240
95
 
241
- | Name | Type | Required | Description |
242
- | ----------- | ------ | -------- | ------------------------------------------------------------------------------- |
243
- | `component` | `node` | Yes | The heading component. Can be anything but best used in combination with `<H>`. |
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
- #### Example
100
+ `H` is the same component as `Heading`. Pick one name per project.
248
101
 
249
- ```jsx
250
- import React from "react";
251
- import { Section, H } from "react-headings";
102
+ ### `HeadingLevel`
252
103
 
253
- function Example1() {
254
- return (
255
- <Section component={<H>This is my title</H>}>
256
- This is my content
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
- function Example2() {
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
- None
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
- #### Returns
113
+ ### `useHeadingLevel()`
287
114
 
288
- | Name | Type | Description |
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
- #### Example
117
+ ## Development warnings
294
118
 
295
- ```jsx
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
- ## Changelog
121
+ - a level is skipped,
122
+ - a level goes past 6,
123
+ - an `offset` or `level` is invalid.
307
124
 
308
- For a list of changes and releases, see the [changelog](https://github.com/alexnault/react-headings/releases).
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
- ## Contributing
127
+ ## License
311
128
 
312
- Found a bug, have a question or looking to improve react-headings? Open an [issue](https://github.com/alexnault/react-headings/issues/new), start a [discussion](https://github.com/alexnault/react-headings/discussions/new) or submit a [PR](https://github.com/alexnault/react-headings/fork)!
129
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,41 +1,40 @@
1
- import React from "react";
1
+ import type { ComponentPropsWithoutRef, ComponentPropsWithRef, ReactElement, ReactNode } from "react";
2
2
  type Level = 1 | 2 | 3 | 4 | 5 | 6;
3
- type Heading = `h${Level}`;
4
- type LevelContextValue = {
5
- level: Level;
6
- Component: Heading;
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
- * Returns the current heading and level.
20
+ * Renders an `h1`–`h6` according to the current level.
10
21
  */
11
- export declare function useLevel(): LevelContextValue;
12
- type HProps = React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement> & {
13
- render?: (context: LevelContextValue) => React.ReactElement;
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 a dynamic HTML heading (h1, h2, etc.) or custom component according to the current level.
30
+ * Renders headings inside `offset` levels deeper (default 1), or at `level`
31
+ * regardless of ancestors.
17
32
  */
18
- export declare const H: React.ForwardRefExoticComponent<Omit<HProps, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
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
- * Renders `children` 1 level down, or at `level` when provided.
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 Level({ level: desiredLevel, children }: LevelProps): JSX.Element;
29
- type SectionProps = {
30
- component: React.ReactNode;
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 strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
- var __rest = (this && this.__rest) || function (s, e) {
14
- var t = {};
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
- exports.useLevel = useLevel;
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
- * Renders `component` in the current level and `children` in the next level.
69
- * @param component A component containing a heading
70
- * @param children The children in the next level
71
- * @param level A level to render `component` at instead of the current one (e.g. for dialogs and portals)
72
- */
73
- function Section(_a) {
74
- var component = _a.component, children = _a.children, level = _a.level;
75
- var content = (react_1.default.createElement(react_1.default.Fragment, null,
76
- component,
77
- react_1.default.createElement(Level, null, children)));
78
- return level ? react_1.default.createElement(Level, { level: level }, content) : content;
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
- exports.Section = Section;
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": "3.4.0",
4
- "description": "HTML headings with auto-incrementing levels for WCAG compliance.",
5
- "main": "dist/index.js",
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
- "auto",
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 ./dist && tsc",
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": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
44
+ "react": "^18.0.0 || ^19.0.0"
36
45
  },
37
46
  "devDependencies": {
38
- "@size-limit/preset-small-lib": "^7.0.8",
39
- "@testing-library/react": "^13.4.0",
40
- "@types/react": "^18.3.28",
41
- "@types/react-dom": "^18.3.7",
42
- "@typescript-eslint/eslint-plugin": "^4.33.0",
43
- "@typescript-eslint/parser": "^4.33.0",
44
- "@vitest/coverage-c8": "^0.30.1",
45
- "conventional-changelog-conventionalcommits": "^8.0.0",
46
- "eslint": "^7.32.0",
47
- "eslint-config-prettier": "^8.10.2",
48
- "jsdom": "^21.1.2",
49
- "prettier": "^2.8.8",
50
- "react": "^18.3.1",
51
- "react-dom": "^18.3.1",
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": "^7.0.8",
54
- "typescript": "^4.9.5",
55
- "vitest": "^0.30.1"
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
  }