react-headings 4.0.0-next.1 → 4.0.0-next.3

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
@@ -13,12 +13,12 @@
13
13
 
14
14
  > Never write `h1`–`h6` again.
15
15
 
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.
16
+ Hardcoded heading levels break accessibility and SEO when components are reused. React Headings fixes this by computing each level from your component tree.
17
17
 
18
18
  ## Features
19
19
 
20
20
  - Supports server-side rendering
21
- - 384 bytes minified & brotlied
21
+ - Only 369 bytes minified & brotlied
22
22
  - Typed with TypeScript
23
23
  - Works with any CSS solution (Tailwind, CSS-in-JS, etc.)
24
24
  - Plays nicely with component libraries
@@ -31,37 +31,38 @@ npm install react-headings
31
31
 
32
32
  ## Usage
33
33
 
34
- Use `H` for every heading. Wrap the content that belongs under a heading in `HeadingLevel`: headings inside render one level deeper.
34
+ Use `Heading` for every heading. Wrap the content that belongs under a heading in `HeadingLevel`: headings inside render one level deeper.
35
35
 
36
36
  ```tsx
37
- import { H, HeadingLevel } from "react-headings";
37
+ import { Heading, HeadingLevel } from "react-headings";
38
38
 
39
39
  function Page() {
40
40
  return (
41
41
  <>
42
- <H>Star Wars</H> {/* h1 */}
42
+ <Heading>Star Wars</Heading> {/* h1 */}
43
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>
44
+ <Heading>Characters</Heading> {/* h2 */}
45
+ <HeadingLevel>
46
+ <Section title="Luke" /> {/* h3 */}
47
+ <Section title="Leia" /> {/* h3 */}
48
+ <Section title="Han Solo" /> {/* h3 */}
49
+ </HeadingLevel>
49
50
  </HeadingLevel>
50
51
  </>
51
52
  );
52
53
  }
53
54
 
54
- function Card({ title, children }) {
55
+ function Section({ title, children }) {
55
56
  return (
56
- <article>
57
- <H>{title}</H>
57
+ <section>
58
+ <Heading>{title}</Heading>
58
59
  <HeadingLevel>{children}</HeadingLevel>
59
- </article>
60
+ </section>
60
61
  );
61
62
  }
62
63
  ```
63
64
 
64
- `Card` doesn't know its level: it renders `h2` for Characters, `h3` for each character, and `h1` on its own.
65
+ `Section` doesn't know its level: it renders `h3` here.
65
66
 
66
67
  ### Dialogs
67
68
 
@@ -70,7 +71,7 @@ Portaled elements, like dialogs, aren't part of the page outline. Use `level` to
70
71
  ```tsx
71
72
  <Dialog>
72
73
  <HeadingLevel level={1}>
73
- <H>Delete file?</H> {/* h1 */}
74
+ <Heading>Delete file?</Heading> {/* h1 */}
74
75
  </HeadingLevel>
75
76
  </Dialog>
76
77
  ```
@@ -80,14 +81,14 @@ Portaled elements, like dialogs, aren't part of the page outline. Use `level` to
80
81
  React Headings only picks the tag. Style headings however you like, or use `render` for your own component:
81
82
 
82
83
  ```tsx
83
- <H render={(props, { tag }) => <Text as={tag} size="lg" {...props} />}>
84
+ <Heading render={(props, { tag }) => <Text as={tag} size="lg" {...props} />}>
84
85
  Title
85
- </H>
86
+ </Heading>
86
87
  ```
87
88
 
88
89
  ## API
89
90
 
90
- ### `Heading` / `H`
91
+ ### `Heading`
91
92
 
92
93
  Renders `h1`–`h6` based on the current level: `h1` by default, and never deeper than `h6`.
93
94
 
@@ -97,8 +98,6 @@ Accepts every `h1` prop and a `ref`, plus:
97
98
  | -------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
98
99
  | `render` | `(props, { level, tag }) => ReactElement` | Renders your own element. Spread `props` on it (includes `children` and `ref`). |
99
100
 
100
- `H` is the same component as `Heading`. Pick one name per project.
101
-
102
101
  ### `HeadingLevel`
103
102
 
104
103
  | Prop | Type | Default | Description |
@@ -108,7 +107,7 @@ Accepts every `h1` prop and a `ref`, plus:
108
107
 
109
108
  Pass `offset` or `level`, not both. Nested offsets add up. `offset={0}` adds nothing, which is useful for conditional nesting.
110
109
 
111
- Invalid values never throw: fractions round down, and anything else invalid counts as `offset={0}` or `level={1}`.
110
+ Invalid values never throw: they're ignored with a warning, as if not passed. `null` counts as not passed, without a warning.
112
111
 
113
112
  ### `useHeadingLevel()`
114
113
 
@@ -118,22 +117,11 @@ Returns the `level` (`1`–`6`) and `tag` (`"h1"`–`"h6"`) a `Heading` would re
118
117
 
119
118
  In development, React Headings warns in the console when:
120
119
 
121
- - a level is skipped, like an `h3` with no `h2` above it,
120
+ - a level is skipped,
122
121
  - a level goes past 6,
123
122
  - an `offset` or `level` is invalid.
124
123
 
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`.
126
-
127
- ## Server Components
128
-
129
- Works in Server Components, including Server Components nested inside `HeadingLevel`. Only `useHeadingLevel()` needs a Client Component.
130
-
131
- ## Differences from `headingoffset`
132
-
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.
124
+ Warnings are removed from production builds. Vite and webpack show them automatically in dev mode. In plain Node, run with `--conditions=development`.
137
125
 
138
126
  ## License
139
127
 
package/dist/index.d.ts CHANGED
@@ -25,7 +25,6 @@ export declare const Heading: import("react").ForwardRefExoticComponent<Omit<imp
25
25
  tag: Tag;
26
26
  }) => ReactElement;
27
27
  } & import("react").RefAttributes<HTMLHeadingElement>>;
28
- export { Heading as H };
29
28
  /**
30
29
  * Renders headings inside `offset` levels deeper (default 1), or at `level`
31
30
  * regardless of ancestors.
@@ -38,3 +37,4 @@ export declare function useHeadingLevel(): {
38
37
  level: Level;
39
38
  tag: Tag;
40
39
  };
40
+ export {};
@@ -10,11 +10,11 @@ import {
10
10
  } from "react";
11
11
  import { jsx } from "react/jsx-runtime";
12
12
  var LevelContext = createContext(1);
13
- function getNormalizedOffset(offset) {
14
- return Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;
13
+ function isValidOffset(offset) {
14
+ return Number.isInteger(offset) && offset >= 0;
15
15
  }
16
- function getNormalizedLevel(level) {
17
- return Number.isFinite(level) && level > 1 ? Math.floor(level) : 1;
16
+ function isValidLevel(level) {
17
+ return Number.isInteger(level) && level >= 1;
18
18
  }
19
19
  var Heading = forwardRef(
20
20
  function({ render, ...props }, ref) {
@@ -28,17 +28,13 @@ function HeadingLevel({
28
28
  level,
29
29
  children
30
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(
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(
37
33
  SkippedLevelScope,
38
34
  {
39
35
  firstLevel: isReset ? innerLevel : parentLevel + 1,
40
36
  isIsolated: isReset,
41
- isPassthrough: !isReset && normalizedOffset === 0,
37
+ isPassthrough: !isReset && offset === 0,
42
38
  children
43
39
  }
44
40
  ), /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
@@ -93,15 +89,14 @@ function useHeadingChecks() {
93
89
  );
94
90
  }, [level]), useEffect(() => scopeRegistry?.register(level), [scopeRegistry, level]);
95
91
  }
96
- function useInvalidPropWarning(prop, value, normalizedValue) {
92
+ function useInvalidPropWarning(prop, value, isValid) {
97
93
  useEffect(() => {
98
- value !== normalizedValue && console.warn(
99
- `react-headings: HeadingLevel received invalid ${prop} ${value}, treated as ${normalizedValue}.`
94
+ value != null && !isValid && console.warn(
95
+ `react-headings: HeadingLevel received invalid ${prop} ${value}, ignored.`
100
96
  );
101
- }, [prop, value, normalizedValue]);
97
+ }, [prop, value, isValid]);
102
98
  }
103
99
  export {
104
- Heading as H,
105
100
  Heading,
106
101
  HeadingLevel,
107
102
  useHeadingLevel
package/dist/index.js CHANGED
@@ -10,11 +10,11 @@ import {
10
10
  } from "react";
11
11
  import { jsx } from "react/jsx-runtime";
12
12
  var LevelContext = createContext(1);
13
- function getNormalizedOffset(offset) {
14
- return Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 0;
13
+ function isValidOffset(offset) {
14
+ return Number.isInteger(offset) && offset >= 0;
15
15
  }
16
- function getNormalizedLevel(level) {
17
- return Number.isFinite(level) && level > 1 ? Math.floor(level) : 1;
16
+ function isValidLevel(level) {
17
+ return Number.isInteger(level) && level >= 1;
18
18
  }
19
19
  var Heading = forwardRef(
20
20
  function({ render, ...props }, ref) {
@@ -28,7 +28,7 @@ function HeadingLevel({
28
28
  level,
29
29
  children
30
30
  }) {
31
- let parentLevel = useContext(LevelContext), isReset = level !== void 0, normalizedOffset = getNormalizedOffset(offset), innerLevel = isReset ? getNormalizedLevel(level) : parentLevel + normalizedOffset;
31
+ let parentLevel = useContext(LevelContext), isReset = isValidLevel(level), isOffsetValid = isValidOffset(offset), innerLevel = isReset ? level : parentLevel + (isOffsetValid ? offset : 1);
32
32
  return /* @__PURE__ */ jsx(LevelContext.Provider, { value: innerLevel, children });
33
33
  }
34
34
  function useHeadingLevel() {
@@ -36,7 +36,6 @@ function useHeadingLevel() {
36
36
  return { level, tag: `h${level}` };
37
37
  }
38
38
  export {
39
- Heading as H,
40
39
  Heading,
41
40
  HeadingLevel,
42
41
  useHeadingLevel
package/package.json CHANGED
@@ -1,15 +1,13 @@
1
1
  {
2
2
  "name": "react-headings",
3
- "version": "4.0.0-next.1",
3
+ "version": "4.0.0-next.3",
4
4
  "type": "module",
5
- "description": "Dynamic heading levels for React. A polyfill of the HTML headingoffset proposal.",
5
+ "description": "Dynamic heading levels for React.",
6
6
  "author": "Alex Nault",
7
7
  "keywords": [
8
8
  "react",
9
9
  "heading",
10
10
  "headings",
11
- "headingoffset",
12
- "headingreset",
13
11
  "level",
14
12
  "outline",
15
13
  "accessibility",
@@ -20,6 +18,7 @@
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",
23
22
  "exports": {
24
23
  ".": {
25
24
  "types": "./dist/index.d.ts",