fumadocs-core 14.0.2 → 14.1.0

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.
@@ -0,0 +1,59 @@
1
+ // src/server/shiki.ts
2
+ import {
3
+ getSingletonHighlighter
4
+ } from "shiki";
5
+ import { toJsxRuntime } from "hast-util-to-jsx-runtime";
6
+ import { Fragment } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { createOnigurumaEngine } from "shiki/engine/oniguruma";
9
+ function createStyleTransformer() {
10
+ return {
11
+ name: "rehype-code:styles",
12
+ line(hast) {
13
+ if (hast.children.length === 0) {
14
+ hast.children.push({
15
+ type: "text",
16
+ value: " "
17
+ });
18
+ }
19
+ }
20
+ };
21
+ }
22
+ var defaultThemes = {
23
+ light: "github-light",
24
+ dark: "github-dark"
25
+ };
26
+ async function highlight(code, options) {
27
+ const { lang, components, engine, ...rest } = options;
28
+ let themes = { themes: defaultThemes };
29
+ if ("theme" in options && options.theme) {
30
+ themes = { theme: options.theme };
31
+ } else if ("themes" in options && options.themes) {
32
+ themes = { themes: options.themes };
33
+ }
34
+ const highlighter = await getSingletonHighlighter({
35
+ langs: [lang],
36
+ engine: engine ?? createOnigurumaEngine(() => import("shiki/wasm")),
37
+ themes: "theme" in themes ? [themes.theme] : Object.values(themes.themes).filter((v) => v !== void 0)
38
+ });
39
+ const hast = highlighter.codeToHast(code, {
40
+ lang,
41
+ ...rest,
42
+ ...themes,
43
+ transformers: [createStyleTransformer(), ...rest.transformers ?? []],
44
+ defaultColor: "themes" in themes ? false : void 0
45
+ });
46
+ return toJsxRuntime(hast, {
47
+ jsx,
48
+ jsxs,
49
+ development: false,
50
+ components,
51
+ Fragment
52
+ });
53
+ }
54
+
55
+ export {
56
+ createStyleTransformer,
57
+ defaultThemes,
58
+ highlight
59
+ };
@@ -0,0 +1,19 @@
1
+ // src/utils/use-on-change.ts
2
+ import { useState } from "react";
3
+ function isDifferent(a, b) {
4
+ if (Array.isArray(a) && Array.isArray(b)) {
5
+ return b.length !== a.length || a.some((v, i) => isDifferent(v, b[i]));
6
+ }
7
+ return a !== b;
8
+ }
9
+ function useOnChange(value, onChange, isUpdated = isDifferent) {
10
+ const [prev, setPrev] = useState(value);
11
+ if (isUpdated(prev, value)) {
12
+ onChange(value, prev);
13
+ setPrev(value);
14
+ }
15
+ }
16
+
17
+ export {
18
+ useOnChange
19
+ };
@@ -2,6 +2,10 @@ import {
2
2
  flattenNode,
3
3
  remarkHeading
4
4
  } from "../chunk-4MNUWZIW.js";
5
+ import {
6
+ createStyleTransformer,
7
+ defaultThemes
8
+ } from "../chunk-7CSWJQ5H.js";
5
9
  import {
6
10
  slash
7
11
  } from "../chunk-UWEEHUJV.js";
@@ -14,11 +18,254 @@ import {
14
18
 
15
19
  // src/mdx-plugins/rehype-code.ts
16
20
  import rehypeShikiFromHighlighter from "@shikijs/rehype/core";
17
- import {
18
- transformerNotationDiff,
19
- transformerNotationHighlight,
20
- transformerNotationWordHighlight
21
- } from "@shikijs/transformers";
21
+
22
+ // ../../node_modules/.pnpm/shiki-transformers@1.0.0_shiki@1.22.2/node_modules/shiki-transformers/dist/index.js
23
+ var matchers = [
24
+ [/^(<!--)(.+)(-->)$/, true],
25
+ [/^(\/\*)(.+)(\*\/)$/, true],
26
+ [/^(\/\/|["']|;{1,2}|%{1,2}|--|#)(.+)$/, false]
27
+ ];
28
+ function parseComments(lines, jsx) {
29
+ const out = [];
30
+ for (const line of lines) {
31
+ const elements = line.children;
32
+ const start = jsx ? elements.length - 2 : elements.length - 1;
33
+ for (let i = Math.max(start, 0); i < elements.length; i++) {
34
+ const token = elements[i];
35
+ if (token.type !== "element")
36
+ continue;
37
+ const isLast = i === elements.length - 1;
38
+ const match = matchToken(token, isLast);
39
+ if (!match) continue;
40
+ if (jsx && !isLast && i !== 0) {
41
+ const left = elements[i - 1];
42
+ const right = elements[i + 1];
43
+ out.push({
44
+ info: match,
45
+ line,
46
+ token,
47
+ jsxIntercept: isValue(left, "{") && isValue(right, "}")
48
+ });
49
+ } else {
50
+ out.push({
51
+ info: match,
52
+ line,
53
+ token,
54
+ jsxIntercept: false
55
+ });
56
+ }
57
+ }
58
+ }
59
+ return out;
60
+ }
61
+ function isValue(element, value) {
62
+ if (element.type !== "element") return false;
63
+ const text = element.children[0];
64
+ if (text.type !== "text")
65
+ return false;
66
+ return text.value.trim() === value;
67
+ }
68
+ function matchToken(token, last) {
69
+ const text = token.children[0];
70
+ if (text.type !== "text")
71
+ return;
72
+ for (const [matcher, lastOnly] of matchers) {
73
+ if (!lastOnly && !last) continue;
74
+ let trimmed = text.value.trimStart();
75
+ const spaceFront = text.value.length - trimmed.length;
76
+ trimmed = trimmed.trimEnd();
77
+ const spaceEnd = text.value.length - trimmed.length - spaceFront;
78
+ const result = matcher.exec(trimmed);
79
+ if (!result) continue;
80
+ return [
81
+ " ".repeat(spaceFront) + result[1],
82
+ result[2],
83
+ result[3] ? result[3] + " ".repeat(spaceEnd) : void 0
84
+ ];
85
+ }
86
+ }
87
+ function createCommentNotationTransformer(name, regex, onMatch) {
88
+ return {
89
+ name,
90
+ code(code) {
91
+ const lines = code.children.filter((i) => i.type === "element");
92
+ const linesToRemove = [];
93
+ code.data ??= {};
94
+ const data = code.data;
95
+ const parsed = data._shiki_notation ??= parseComments(lines, ["jsx", "tsx"].includes(this.options.lang));
96
+ for (const comment of parsed) {
97
+ if (comment.info[1].length === 0) continue;
98
+ const isLineCommentOnly = comment.line.children.length === (comment.jsxIntercept ? 3 : 1);
99
+ let lineIdx = lines.indexOf(comment.line);
100
+ if (isLineCommentOnly) lineIdx++;
101
+ comment.info[1] = comment.info[1].replace(regex, (...match) => {
102
+ if (onMatch.call(this, match, comment.line, comment.token, lines, lineIdx)) {
103
+ return "";
104
+ }
105
+ return match[0];
106
+ });
107
+ const isEmpty = comment.info[1].trim().length === 0;
108
+ if (isEmpty) comment.info[1] = "";
109
+ if (isEmpty && isLineCommentOnly) {
110
+ linesToRemove.push(comment.line);
111
+ } else if (isEmpty && comment.jsxIntercept) {
112
+ comment.line.children.splice(comment.line.children.indexOf(comment.token) - 1, 3);
113
+ } else if (isEmpty) {
114
+ comment.line.children.splice(comment.line.children.indexOf(comment.token), 1);
115
+ } else {
116
+ const head = comment.token.children[0];
117
+ if (head.type === "text") {
118
+ head.value = comment.info.join("");
119
+ }
120
+ }
121
+ }
122
+ for (const line of linesToRemove)
123
+ code.children.splice(code.children.indexOf(line), 1);
124
+ }
125
+ };
126
+ }
127
+ function escapeRegExp(str) {
128
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
129
+ }
130
+ function transformerNotationMap(options = {}, name = "@shikijs/transformers:notation-map") {
131
+ const {
132
+ classMap = {},
133
+ classActivePre = void 0
134
+ } = options;
135
+ return createCommentNotationTransformer(
136
+ name,
137
+ new RegExp(`\\s*\\[!code (${Object.keys(classMap).map(escapeRegExp).join("|")})(:\\d+)?\\]`),
138
+ function([_, match, range = ":1"], _line, _comment, lines, index) {
139
+ const lineNum = Number.parseInt(range.slice(1), 10);
140
+ lines.slice(index, index + lineNum).forEach((line) => {
141
+ this.addClassToHast(line, classMap[match]);
142
+ });
143
+ if (classActivePre)
144
+ this.addClassToHast(this.pre, classActivePre);
145
+ return true;
146
+ }
147
+ );
148
+ }
149
+ function transformerNotationDiff(options = {}) {
150
+ const {
151
+ classLineAdd = "diff add",
152
+ classLineRemove = "diff remove",
153
+ classActivePre = "has-diff"
154
+ } = options;
155
+ return transformerNotationMap(
156
+ {
157
+ classMap: {
158
+ "++": classLineAdd,
159
+ "--": classLineRemove
160
+ },
161
+ classActivePre
162
+ },
163
+ "@shikijs/transformers:notation-diff"
164
+ );
165
+ }
166
+ function transformerNotationHighlight(options = {}) {
167
+ const {
168
+ classActiveLine = "highlighted",
169
+ classActivePre = "has-highlighted"
170
+ } = options;
171
+ return transformerNotationMap(
172
+ {
173
+ classMap: {
174
+ highlight: classActiveLine,
175
+ hl: classActiveLine
176
+ },
177
+ classActivePre
178
+ },
179
+ "@shikijs/transformers:notation-highlight"
180
+ );
181
+ }
182
+ function highlightWordInLine(line, ignoredElement, word, className) {
183
+ const content = getTextContent(line);
184
+ let index = content.indexOf(word);
185
+ while (index !== -1) {
186
+ highlightRange.call(this, line.children, ignoredElement, index, word.length, className);
187
+ index = content.indexOf(word, index + 1);
188
+ }
189
+ }
190
+ function getTextContent(element) {
191
+ if (element.type === "text")
192
+ return element.value;
193
+ if (element.type === "element" && element.tagName === "span")
194
+ return element.children.map(getTextContent).join("");
195
+ return "";
196
+ }
197
+ function highlightRange(elements, ignoredElement, index, len, className) {
198
+ let currentIdx = 0;
199
+ for (let i = 0; i < elements.length; i++) {
200
+ const element = elements[i];
201
+ if (element.type !== "element" || element.tagName !== "span" || element === ignoredElement)
202
+ continue;
203
+ const textNode = element.children[0];
204
+ if (textNode.type !== "text")
205
+ continue;
206
+ if (hasOverlap([currentIdx, currentIdx + textNode.value.length - 1], [index, index + len])) {
207
+ const start = Math.max(0, index - currentIdx);
208
+ const length = len - Math.max(0, currentIdx - index);
209
+ if (length === 0)
210
+ continue;
211
+ const separated = separateToken(element, textNode, start, length);
212
+ this.addClassToHast(separated[1], className);
213
+ const output = separated.filter(Boolean);
214
+ elements.splice(i, 1, ...output);
215
+ i += output.length - 1;
216
+ }
217
+ currentIdx += textNode.value.length;
218
+ }
219
+ }
220
+ function hasOverlap(range1, range2) {
221
+ return range1[0] <= range2[1] && range1[1] >= range2[0];
222
+ }
223
+ function separateToken(span, textNode, index, len) {
224
+ const text = textNode.value;
225
+ const createNode = (value) => inheritElement(span, {
226
+ children: [
227
+ {
228
+ type: "text",
229
+ value
230
+ }
231
+ ]
232
+ });
233
+ return [
234
+ index > 0 ? createNode(text.slice(0, index)) : void 0,
235
+ createNode(text.slice(index, index + len)),
236
+ index + len < text.length ? createNode(text.slice(index + len)) : void 0
237
+ ];
238
+ }
239
+ function inheritElement(original, overrides) {
240
+ return {
241
+ ...original,
242
+ properties: {
243
+ ...original.properties
244
+ },
245
+ ...overrides
246
+ };
247
+ }
248
+ function transformerNotationWordHighlight(options = {}) {
249
+ const {
250
+ classActiveWord = "highlighted-word",
251
+ classActivePre = void 0
252
+ } = options;
253
+ return createCommentNotationTransformer(
254
+ "@shikijs/transformers:notation-highlight-word",
255
+ /\s*\[!code word:((?:\\.|[^:\]])+)(:\d+)?\]/,
256
+ function([_, word, range], _line, comment, lines, index) {
257
+ const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
258
+ word = word.replace(/\\(.)/g, "$1");
259
+ lines.slice(index, index + lineNum).forEach((line) => highlightWordInLine.call(this, line, comment, word, classActiveWord));
260
+ if (classActivePre)
261
+ this.addClassToHast(this.pre, classActivePre);
262
+ return true;
263
+ }
264
+ );
265
+ }
266
+ var symbol = Symbol("highlighted-lines");
267
+
268
+ // src/mdx-plugins/rehype-code.ts
22
269
  import {
23
270
  getSingletonHighlighter,
24
271
  bundledLanguages
@@ -185,14 +432,12 @@ var metaValues = [
185
432
  }
186
433
  ];
187
434
  var rehypeCodeDefaultOptions = {
188
- themes: {
189
- light: "github-light",
190
- dark: "github-dark"
191
- },
435
+ themes: defaultThemes,
436
+ defaultColor: false,
192
437
  defaultLanguage: "plaintext",
193
438
  experimentalJSEngine: false,
194
- defaultColor: false,
195
439
  transformers: [
440
+ createStyleTransformer(),
196
441
  transformerNotationHighlight(),
197
442
  transformerNotationWordHighlight(),
198
443
  transformerNotationDiff()
@@ -229,14 +474,6 @@ function rehypeCode(options = {}) {
229
474
  meta.__raw = codeOptions.filterMetaString(meta.__raw ?? "");
230
475
  }
231
476
  return code.replace(/\n$/, "");
232
- },
233
- line(hast) {
234
- if (hast.children.length === 0) {
235
- hast.children.push({
236
- type: "text",
237
- value: " "
238
- });
239
- }
240
477
  }
241
478
  },
242
479
  ...codeOptions.transformers
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useOnChange
3
- } from "../chunk-I5BWASD6.js";
3
+ } from "../chunk-EMWGTXSW.js";
4
4
  import "../chunk-MLKGABMK.js";
5
5
 
6
6
  // src/search/client.ts
@@ -5,8 +5,12 @@ export { S as SortedResult } from '../types-Ch8gnVgO.js';
5
5
  import { Metadata } from 'next';
6
6
  import { NextRequest } from 'next/server';
7
7
  import { LoaderOutput, LoaderConfig, InferPageType } from '../source/index.js';
8
+ export { H as HighlightOptions, c as createStyleTransformer, d as defaultThemes, h as highlight } from '../shiki-FJwEmGMA.js';
8
9
  import 'react';
9
10
  import '../config-inq6kP6y.js';
11
+ import 'shiki';
12
+ import 'shiki/themes';
13
+ import 'hast-util-to-jsx-runtime';
10
14
 
11
15
  /**
12
16
  * Flatten tree to an array of page nodes
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  remarkHeading
3
3
  } from "../chunk-4MNUWZIW.js";
4
+ import {
5
+ createStyleTransformer,
6
+ defaultThemes,
7
+ highlight
8
+ } from "../chunk-7CSWJQ5H.js";
4
9
  import "../chunk-MLKGABMK.js";
5
10
 
6
11
  // src/server/get-toc.ts
@@ -147,9 +152,12 @@ function createMetadataImage(options) {
147
152
  export {
148
153
  page_tree_exports as PageTree,
149
154
  createMetadataImage,
155
+ createStyleTransformer,
156
+ defaultThemes,
150
157
  findNeighbour,
151
158
  flattenTree,
152
159
  getGithubLastEdit,
153
160
  getTableOfContents,
161
+ highlight,
154
162
  separatePageTree
155
163
  };
@@ -0,0 +1,16 @@
1
+ import { ShikiTransformer, CodeToHastOptionsCommon, BundledLanguage, HighlighterCoreOptions, CodeOptionsThemes, CodeOptionsMeta } from 'shiki';
2
+ import { BundledTheme } from 'shiki/themes';
3
+ import { Components } from 'hast-util-to-jsx-runtime';
4
+ import { ReactNode } from 'react';
5
+
6
+ declare function createStyleTransformer(): ShikiTransformer;
7
+ declare const defaultThemes: {
8
+ light: string;
9
+ dark: string;
10
+ };
11
+ type HighlightOptions = CodeToHastOptionsCommon<BundledLanguage> & Pick<HighlighterCoreOptions, 'engine'> & Partial<CodeOptionsThemes<BundledTheme>> & CodeOptionsMeta & {
12
+ components?: Partial<Components>;
13
+ };
14
+ declare function highlight(code: string, options: HighlightOptions): Promise<ReactNode>;
15
+
16
+ export { type HighlightOptions as H, createStyleTransformer as c, defaultThemes as d, highlight as h };
package/dist/sidebar.js CHANGED
@@ -3,7 +3,6 @@ import "./chunk-MLKGABMK.js";
3
3
 
4
4
  // src/sidebar.tsx
5
5
  import {
6
- useCallback,
7
6
  createContext,
8
7
  useContext,
9
8
  useEffect,
@@ -38,9 +37,9 @@ function SidebarTrigger({
38
37
  {
39
38
  "aria-label": "Toggle Sidebar",
40
39
  "data-open": open,
41
- onClick: useCallback(() => {
40
+ onClick: () => {
42
41
  setOpen(!open);
43
- }, [open, setOpen]),
42
+ },
44
43
  ...props
45
44
  }
46
45
  );
package/dist/toc.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  useOnChange
4
- } from "./chunk-I5BWASD6.js";
4
+ } from "./chunk-EMWGTXSW.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
 
7
7
  // src/toc.tsx
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useOnChange
3
- } from "../chunk-I5BWASD6.js";
3
+ } from "../chunk-EMWGTXSW.js";
4
4
  import "../chunk-MLKGABMK.js";
5
5
  export {
6
6
  useOnChange
@@ -0,0 +1,11 @@
1
+ import { ReactNode, DependencyList } from 'react';
2
+ import { H as HighlightOptions } from '../shiki-FJwEmGMA.js';
3
+ import 'shiki';
4
+ import 'shiki/themes';
5
+ import 'hast-util-to-jsx-runtime';
6
+
7
+ declare function useShiki(code: string, options: HighlightOptions & {
8
+ defaultValue?: ReactNode;
9
+ }, deps?: DependencyList): ReactNode;
10
+
11
+ export { useShiki };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import {
3
+ highlight
4
+ } from "../chunk-7CSWJQ5H.js";
5
+ import "../chunk-MLKGABMK.js";
6
+
7
+ // src/utils/use-shiki.tsx
8
+ import {
9
+ useEffect,
10
+ useState
11
+ } from "react";
12
+ import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
13
+ import { jsx } from "react/jsx-runtime";
14
+ var jsEngine;
15
+ function useShiki(code, options, deps) {
16
+ const [out, setOut] = useState(() => {
17
+ if (options.defaultValue) return options.defaultValue;
18
+ const { pre: Pre = "pre", code: Code = "code" } = options.components ?? {};
19
+ return /* @__PURE__ */ jsx(Pre, { children: /* @__PURE__ */ jsx(Code, { children: code }) });
20
+ });
21
+ if (!options.engine && !jsEngine) {
22
+ jsEngine = createJavaScriptRegexEngine();
23
+ }
24
+ useEffect(
25
+ () => {
26
+ void highlight(code, {
27
+ ...options,
28
+ engine: options.engine ?? jsEngine
29
+ }).then(setOut);
30
+ },
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- custom deps
32
+ deps ?? [code, options.lang]
33
+ );
34
+ return out;
35
+ }
36
+ export {
37
+ useShiki
38
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-core",
3
- "version": "14.0.2",
3
+ "version": "14.1.0",
4
4
  "description": "The library for building a documentation website in Next.js",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -48,6 +48,10 @@
48
48
  "import": "./dist/utils/use-on-change.js",
49
49
  "types": "./dist/utils/use-on-change.d.ts"
50
50
  },
51
+ "./utils/use-shiki": {
52
+ "import": "./dist/utils/use-shiki.js",
53
+ "types": "./dist/utils/use-shiki.d.ts"
54
+ },
51
55
  "./link": {
52
56
  "import": "./dist/link.js",
53
57
  "types": "./dist/link.d.ts"
@@ -65,19 +69,19 @@
65
69
  "dist/*"
66
70
  ],
67
71
  "dependencies": {
68
- "@formatjs/intl-localematcher": "^0.5.5",
72
+ "@formatjs/intl-localematcher": "^0.5.6",
69
73
  "@orama/orama": "^3.0.1",
70
- "@shikijs/rehype": "^1.22.0",
71
- "@shikijs/transformers": "^1.22.0",
74
+ "@shikijs/rehype": "^1.22.2",
72
75
  "github-slugger": "^2.0.0",
73
76
  "hast-util-to-estree": "^3.1.0",
77
+ "hast-util-to-jsx-runtime": "^2.3.2",
74
78
  "image-size": "^1.1.1",
75
79
  "negotiator": "^1.0.0",
76
80
  "react-remove-scroll": "^2.6.0",
77
81
  "remark": "^15.0.0",
78
82
  "remark-gfm": "^4.0.0",
79
83
  "scroll-into-view-if-needed": "^3.1.0",
80
- "shiki": "^1.22.0",
84
+ "shiki": "^1.22.2",
81
85
  "unist-util-visit": "^5.0.0"
82
86
  },
83
87
  "devDependencies": {
@@ -87,8 +91,8 @@
87
91
  "@types/hast": "^3.0.4",
88
92
  "@types/mdast": "^4.0.3",
89
93
  "@types/negotiator": "^0.6.3",
90
- "@types/node": "22.7.8",
91
- "@types/react": "^18.3.11",
94
+ "@types/node": "22.8.1",
95
+ "@types/react": "^18.3.12",
92
96
  "@types/react-dom": "^18.3.1",
93
97
  "algoliasearch": "4.24.0",
94
98
  "mdast-util-mdx-jsx": "^3.1.3",
@@ -96,16 +100,31 @@
96
100
  "next": "^15.0.0",
97
101
  "remark-mdx": "^3.1.0",
98
102
  "remark-rehype": "^11.1.1",
103
+ "shiki-transformers": "^1.0.0",
99
104
  "unified": "^11.0.5",
100
- "eslint-config-custom": "0.0.0",
101
- "tsconfig": "0.0.0"
105
+ "tsconfig": "0.0.0",
106
+ "eslint-config-custom": "0.0.0"
102
107
  },
103
- "optionalDependencies": {
108
+ "peerDependencies": {
104
109
  "algoliasearch": "4.24.0",
105
- "next": "15.0.0",
110
+ "next": "14.x.x || 15.x.x",
106
111
  "react": ">= 18",
107
112
  "react-dom": ">= 18"
108
113
  },
114
+ "peerDependenciesMeta": {
115
+ "algoliasearch": {
116
+ "optional": true
117
+ },
118
+ "next": {
119
+ "optional": true
120
+ },
121
+ "react": {
122
+ "optional": true
123
+ },
124
+ "react-dom": {
125
+ "optional": true
126
+ }
127
+ },
109
128
  "publishConfig": {
110
129
  "access": "public"
111
130
  },
@@ -1,13 +0,0 @@
1
- // src/utils/use-on-change.ts
2
- import { useState } from "react";
3
- function useOnChange(value, onChange, isUpdated = (prev, current) => prev !== current) {
4
- const [prev, setPrev] = useState(value);
5
- if (isUpdated(prev, value)) {
6
- onChange(value, prev);
7
- setPrev(value);
8
- }
9
- }
10
-
11
- export {
12
- useOnChange
13
- };