@portabletext/plugin-list-index 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 - 2026 Sanity.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # `@portabletext/plugin-list-index`
2
+
3
+ A helper plugin for calculating list indices for flat blocks based on `listItem` and `level`.
4
+
5
+ Portable Text has no nested list structure: a list is a run of flat sibling blocks carrying `listItem` and `level` properties. That makes the 1-based position of an item within its list a _derived_ value: same-type items count up across consecutive blocks on the same level, deeper levels restart at 1, and non-list blocks break the sequence. This plugin derives it for you and keeps it correct as the value changes.
6
+
7
+ ```tsx
8
+ import {ListIndexProvider, useListIndex} from '@portabletext/plugin-list-index'
9
+
10
+ function MyEditor() {
11
+ return (
12
+ <EditorProvider initialConfig={...}>
13
+ <ListIndexProvider>
14
+ <PortableTextEditable />
15
+ </ListIndexProvider>
16
+ </EditorProvider>
17
+ )
18
+ }
19
+ ```
20
+
21
+ Typical use: custom text-block renders (`defineTextBlock`) that need to render numbered list markers, since the engine's default list-item wrapping (and the index it computes) does not apply to custom renders. Call `useListIndex` from a component the render returns, not inline in the `render` callback (it is a hook):
22
+
23
+ ```tsx
24
+ function TextBlock(props: TextBlockRenderProps) {
25
+ const listIndex = useListIndex(props.path)
26
+ return <div {...props.attributes}>{props.children}</div>
27
+ }
28
+
29
+ defineTextBlock({type: '*', render: (props) => <TextBlock {...props} />})
30
+ ```
31
+
32
+ The index map is rebuilt at most once per editor operation, regardless of how many components read it, and only for operations that can affect list indices: text insertions/removals and operations nested deeper than the root are skipped. Reads via `useListIndex` re-render only when the index at their own path changes.
33
+
34
+ Because the plugin observes every change source (local edits, remote patches, value sync, normalization), indices are correct on first render and stay correct when collaborators change the document.
@@ -0,0 +1,39 @@
1
+ import {Path} from '@portabletext/editor'
2
+ import {JSX, ReactNode} from 'react'
3
+
4
+ /**
5
+ * Maintains a list index map for the editor and serves it through context.
6
+ * Mount inside `EditorProvider`, wrapping whatever reads the indices:
7
+ *
8
+ * ```tsx
9
+ * <EditorProvider initialConfig={...}>
10
+ * <ListIndexProvider>
11
+ * <PortableTextEditable />
12
+ * </ListIndexProvider>
13
+ * </EditorProvider>
14
+ * ```
15
+ *
16
+ * The map is rebuilt at most once per editor operation, regardless of how
17
+ * many components read it, and only for operations that can affect list
18
+ * indices. Reads via {@link useListIndex} only re-render when the index at
19
+ * their own path changes.
20
+ *
21
+ * @beta
22
+ */
23
+ export declare function ListIndexProvider(props: {
24
+ children?: ReactNode
25
+ }): JSX.Element
26
+
27
+ /**
28
+ * Read the 1-based list index of the block at `path`, or `undefined` when
29
+ * the block is not a list item. Re-renders only when the index at this
30
+ * path changes.
31
+ *
32
+ * `path` is the keyed block path render callbacks receive, e.g.
33
+ * `[{_key: 'b0'}]`.
34
+ *
35
+ * @beta
36
+ */
37
+ export declare function useListIndex(path: Path): number | undefined
38
+
39
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { c } from "react/compiler-runtime";
3
+ import { useEditor } from "@portabletext/editor";
4
+ import { createContext, useEffect, useContext, useSyncExternalStore } from "react";
5
+ import { isKeyedSegment, isTextBlock } from "@portabletext/editor/utils";
6
+ function serializePath(path) {
7
+ return path.reduce((result, segment, index) => isKeyedSegment(segment) ? `${result}[_key=="${segment._key}"]` : `${result}${index === 0 ? "" : "."}${segment}`, "");
8
+ }
9
+ function buildListIndexMap(context) {
10
+ const listIndexMap = /* @__PURE__ */ new Map(), levelIndexMaps = /* @__PURE__ */ new Map();
11
+ let previousListItem;
12
+ for (const block of context.value) {
13
+ if (block === void 0)
14
+ continue;
15
+ if (!isTextBlock(context, block)) {
16
+ levelIndexMaps.clear(), previousListItem = void 0;
17
+ continue;
18
+ }
19
+ if (block.listItem === void 0 || block.level === void 0) {
20
+ levelIndexMaps.clear(), previousListItem = void 0;
21
+ continue;
22
+ }
23
+ if (!previousListItem) {
24
+ const levelIndexMap2 = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
25
+ levelIndexMap2.set(block.level, 1), levelIndexMaps.set(block.listItem, levelIndexMap2), listIndexMap.set(serializePath([{
26
+ _key: block._key
27
+ }]), 1), previousListItem = {
28
+ listItem: block.listItem,
29
+ level: block.level
30
+ };
31
+ continue;
32
+ }
33
+ if (previousListItem.listItem === block.listItem && previousListItem.level < block.level) {
34
+ const levelIndexMap2 = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map();
35
+ levelIndexMap2.set(block.level, 1), levelIndexMaps.set(block.listItem, levelIndexMap2), listIndexMap.set(serializePath([{
36
+ _key: block._key
37
+ }]), 1), previousListItem = {
38
+ listItem: block.listItem,
39
+ level: block.level
40
+ };
41
+ continue;
42
+ }
43
+ levelIndexMaps.forEach((levelIndexMap2, listItem) => {
44
+ if (listItem === block.listItem)
45
+ return;
46
+ const levelsToDelete = [];
47
+ levelIndexMap2.forEach((_, level) => {
48
+ block.level !== void 0 && level >= block.level && levelsToDelete.push(level);
49
+ }), levelsToDelete.forEach((level) => {
50
+ levelIndexMap2.delete(level);
51
+ });
52
+ });
53
+ const levelIndexMap = levelIndexMaps.get(block.listItem) ?? /* @__PURE__ */ new Map(), levelCounter = levelIndexMap.get(block.level) ?? 0;
54
+ levelIndexMap.set(block.level, levelCounter + 1), levelIndexMaps.set(block.listItem, levelIndexMap), listIndexMap.set(serializePath([{
55
+ _key: block._key
56
+ }]), levelCounter + 1), previousListItem = {
57
+ listItem: block.listItem,
58
+ level: block.level
59
+ };
60
+ }
61
+ return listIndexMap;
62
+ }
63
+ function createListIndexStore(editor) {
64
+ let listIndexMap = buildListIndexMap(editor.getSnapshot().context);
65
+ const subscribers = /* @__PURE__ */ new Map();
66
+ let rebuildScheduled = !1, subscribed = !1;
67
+ function scheduleRebuild() {
68
+ rebuildScheduled || (rebuildScheduled = !0, queueMicrotask(() => {
69
+ rebuildScheduled = !1, subscribed && rebuild();
70
+ }));
71
+ }
72
+ function rebuild() {
73
+ const previousListIndexMap = listIndexMap;
74
+ listIndexMap = buildListIndexMap(editor.getSnapshot().context);
75
+ for (const [serializedPath, callbacks] of subscribers)
76
+ if (previousListIndexMap.get(serializedPath) !== listIndexMap.get(serializedPath))
77
+ for (const callback of callbacks)
78
+ callback();
79
+ }
80
+ return {
81
+ get: (serializedPath) => listIndexMap.get(serializedPath),
82
+ subscribeKey: (serializedPath, callback) => {
83
+ let bucket = subscribers.get(serializedPath);
84
+ return bucket === void 0 && (bucket = /* @__PURE__ */ new Set(), subscribers.set(serializedPath, bucket)), bucket.add(callback), () => {
85
+ bucket.delete(callback), bucket.size === 0 && subscribers.delete(serializedPath);
86
+ };
87
+ },
88
+ subscribe: () => {
89
+ subscribed = !0, rebuild();
90
+ const subscription = editor.on("operation", (event) => {
91
+ event.operation.type === "insert.text" || event.operation.type === "remove.text" || event.operation.path.length > 2 || scheduleRebuild();
92
+ });
93
+ return () => {
94
+ subscribed = !1, subscription.unsubscribe();
95
+ };
96
+ }
97
+ };
98
+ }
99
+ const ListIndexContext = createContext(void 0);
100
+ function ListIndexProvider(props) {
101
+ const $ = c(8), editor = useEditor();
102
+ let t0;
103
+ $[0] !== editor ? (t0 = createListIndexStore(editor), $[0] = editor, $[1] = t0) : t0 = $[1];
104
+ const store = t0;
105
+ let t1, t2;
106
+ $[2] !== store ? (t1 = () => store.subscribe(), t2 = [store], $[2] = store, $[3] = t1, $[4] = t2) : (t1 = $[3], t2 = $[4]), useEffect(t1, t2);
107
+ let t3;
108
+ return $[5] !== props.children || $[6] !== store ? (t3 = /* @__PURE__ */ jsx(ListIndexContext.Provider, { value: store, children: props.children }), $[5] = props.children, $[6] = store, $[7] = t3) : t3 = $[7], t3;
109
+ }
110
+ function useListIndex(path) {
111
+ const $ = c(8), store = useContext(ListIndexContext);
112
+ if (store === void 0)
113
+ throw new Error("useListIndex must be used below a <ListIndexProvider>");
114
+ let t0;
115
+ $[0] !== path ? (t0 = serializePath(path), $[0] = path, $[1] = t0) : t0 = $[1];
116
+ const serializedPath = t0;
117
+ let t1;
118
+ $[2] !== serializedPath || $[3] !== store ? (t1 = (callback) => store.subscribeKey(serializedPath, callback), $[2] = serializedPath, $[3] = store, $[4] = t1) : t1 = $[4];
119
+ const subscribe = t1;
120
+ let t2;
121
+ $[5] !== serializedPath || $[6] !== store ? (t2 = () => store.get(serializedPath), $[5] = serializedPath, $[6] = store, $[7] = t2) : t2 = $[7];
122
+ const getSnapshot = t2;
123
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
124
+ }
125
+ export {
126
+ ListIndexProvider,
127
+ useListIndex
128
+ };
129
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/build-list-index-map.ts","../src/plugin.list-index.tsx"],"sourcesContent":["import type {EditorContext, Path} from '@portabletext/editor'\nimport {isKeyedSegment, isTextBlock} from '@portabletext/editor/utils'\n\n/**\n * Serialize a keyed path to a string using Sanity's bracket notation,\n * e.g. `[{_key: 'k0'}]` becomes `[_key==\"k0\"]`.\n *\n * Duplicated from the editor's internal `serializePath` rather than\n * imported: exporting it from core would add permanent public API for what\n * is an implementation detail of this plugin.\n */\nexport function serializePath(path: Path): string {\n return path.reduce<string>((result, segment, index) => {\n if (isKeyedSegment(segment)) {\n return `${result}[_key==\"${segment._key}\"]`\n }\n\n const separator = index === 0 ? '' : '.'\n return `${result}${separator}${segment}`\n }, '')\n}\n\n/**\n * Compute the list index for every list item block in the value.\n *\n * Returns a fresh `Map` keyed by serialized block path with the 1-based\n * index of the block within its list, honoring list type and indentation\n * level: same-type items count up across consecutive blocks on the same\n * level, deeper levels restart at 1, and non-list blocks break the\n * sequence.\n *\n * Duplicated from the editor's internal `buildIndexMaps` (the part that\n * fills `listIndexMap`) rather than imported, for the same reason as\n * `serializePath` above. Keep the list semantics in sync with\n * `packages/editor/src/internal-utils/build-index-maps.ts`.\n */\nexport function buildListIndexMap(\n context: Pick<EditorContext, 'schema' | 'value'>,\n): Map<string, number> {\n const listIndexMap = new Map<string, number>()\n\n // Maps for each list type, keeping track of the current list count for\n // each level.\n const levelIndexMaps = new Map<string, Map<number, number>>()\n\n let previousListItem:\n | {\n listItem: string\n level: number\n }\n | undefined\n\n for (const block of context.value) {\n if (block === undefined) {\n continue\n }\n\n // Clear the state if we encounter a non-text block. Unlike the engine's\n // internal check, `isTextBlock` also requires `children`, which holds\n // for the post-apply snapshot values this plugin reads.\n if (!isTextBlock(context, block)) {\n levelIndexMaps.clear()\n previousListItem = undefined\n\n continue\n }\n\n // Clear the state if we encounter a non-list text block\n if (block.listItem === undefined || block.level === undefined) {\n levelIndexMaps.clear()\n previousListItem = undefined\n\n continue\n }\n\n // If we encounter a new list item, we set the initial index to 1 for the\n // list type on that level.\n if (!previousListItem) {\n const listIndex = 1\n const levelIndexMap =\n levelIndexMaps.get(block.listItem) ?? new Map<number, number>()\n levelIndexMap.set(block.level, listIndex)\n levelIndexMaps.set(block.listItem, levelIndexMap)\n\n listIndexMap.set(serializePath([{_key: block._key}]), listIndex)\n\n previousListItem = {\n listItem: block.listItem,\n level: block.level,\n }\n\n continue\n }\n\n // If the previous list item is of the same type but on a lower level, we\n // need to reset the level index map for that type.\n if (\n previousListItem.listItem === block.listItem &&\n previousListItem.level < block.level\n ) {\n const listIndex = 1\n const levelIndexMap =\n levelIndexMaps.get(block.listItem) ?? new Map<number, number>()\n levelIndexMap.set(block.level, listIndex)\n levelIndexMaps.set(block.listItem, levelIndexMap)\n\n listIndexMap.set(serializePath([{_key: block._key}]), listIndex)\n\n previousListItem = {\n listItem: block.listItem,\n level: block.level,\n }\n\n continue\n }\n\n // Reset other list types at current level and deeper\n levelIndexMaps.forEach((levelIndexMap, listItem) => {\n if (listItem === block.listItem) {\n return\n }\n\n // Reset all levels that are >= current level\n const levelsToDelete: number[] = []\n\n levelIndexMap.forEach((_, level) => {\n if (block.level !== undefined && level >= block.level) {\n levelsToDelete.push(level)\n }\n })\n\n levelsToDelete.forEach((level) => {\n levelIndexMap.delete(level)\n })\n })\n\n const levelIndexMap =\n levelIndexMaps.get(block.listItem) ?? new Map<number, number>()\n const levelCounter = levelIndexMap.get(block.level) ?? 0\n levelIndexMap.set(block.level, levelCounter + 1)\n levelIndexMaps.set(block.listItem, levelIndexMap)\n\n listIndexMap.set(serializePath([{_key: block._key}]), levelCounter + 1)\n\n previousListItem = {\n listItem: block.listItem,\n level: block.level,\n }\n }\n\n return listIndexMap\n}\n","import {useEditor, type Editor, type Path} from '@portabletext/editor'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {buildListIndexMap, serializePath} from './build-list-index-map'\n\n/**\n * One store per editor: a single `operation` subscription maintains the\n * list index map, and per-path subscriber buckets make notification\n * O(changed paths) rather than O(subscribers).\n */\ntype ListIndexStore = {\n get: (serializedPath: string) => number | undefined\n subscribeKey: (serializedPath: string, callback: () => void) => () => void\n /**\n * Starts the `operation` subscription. Returns the unsubscribe.\n */\n subscribe: () => () => void\n}\n\nfunction createListIndexStore(editor: Editor): ListIndexStore {\n let listIndexMap = buildListIndexMap(editor.getSnapshot().context)\n const subscribers = new Map<string, Set<() => void>>()\n let rebuildScheduled = false\n let subscribed = false\n\n function scheduleRebuild() {\n if (rebuildScheduled) {\n return\n }\n\n rebuildScheduled = true\n\n // Coalesce per microtask: bulk transactions (value sync, multi-block\n // inserts) deliver one operation per affected block, and rebuilding per\n // operation would be O(blocks^2). The map has only React consumers and\n // they read post-commit, so deferring to the end of the JS turn is\n // safe; the microtask drains before React's commit.\n queueMicrotask(() => {\n rebuildScheduled = false\n\n if (subscribed) {\n rebuild()\n }\n })\n }\n\n function rebuild() {\n const previousListIndexMap = listIndexMap\n\n // Swap before notifying: `useSyncExternalStore` re-reads the snapshot\n // synchronously on notification and skips the re-render when it reads\n // an unchanged (stale) value.\n listIndexMap = buildListIndexMap(editor.getSnapshot().context)\n\n for (const [serializedPath, callbacks] of subscribers) {\n if (\n previousListIndexMap.get(serializedPath) !==\n listIndexMap.get(serializedPath)\n ) {\n for (const callback of callbacks) {\n callback()\n }\n }\n }\n }\n\n return {\n get: (serializedPath) => listIndexMap.get(serializedPath),\n subscribeKey: (serializedPath, callback) => {\n let bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n bucket = new Set()\n subscribers.set(serializedPath, bucket)\n }\n\n bucket.add(callback)\n\n return () => {\n bucket.delete(callback)\n\n if (bucket.size === 0) {\n subscribers.delete(serializedPath)\n }\n }\n },\n subscribe: () => {\n subscribed = true\n\n // Operations applied between store creation (render) and subscription\n // (effect) are not observed, so reconcile once up front.\n rebuild()\n\n const subscription = editor.on('operation', (event) => {\n if (\n event.operation.type === 'insert.text' ||\n event.operation.type === 'remove.text'\n ) {\n // Inserting and removing text has no effect on list indices so\n // there is no need to rebuild those.\n return\n }\n\n if (event.operation.path.length > 2) {\n // Operations deep inside blocks only modify nested structure and\n // cannot affect root-level list indices.\n return\n }\n\n scheduleRebuild()\n })\n\n return () => {\n subscribed = false\n subscription.unsubscribe()\n }\n },\n }\n}\n\nconst ListIndexContext = createContext<ListIndexStore | undefined>(undefined)\n\n/**\n * Maintains a list index map for the editor and serves it through context.\n * Mount inside `EditorProvider`, wrapping whatever reads the indices:\n *\n * ```tsx\n * <EditorProvider initialConfig={...}>\n * <ListIndexProvider>\n * <PortableTextEditable />\n * </ListIndexProvider>\n * </EditorProvider>\n * ```\n *\n * The map is rebuilt at most once per editor operation, regardless of how\n * many components read it, and only for operations that can affect list\n * indices. Reads via {@link useListIndex} only re-render when the index at\n * their own path changes.\n *\n * @beta\n */\nexport function ListIndexProvider(props: {children?: ReactNode}) {\n const editor = useEditor()\n const store = useMemo(() => createListIndexStore(editor), [editor])\n\n useEffect(() => {\n return store.subscribe()\n }, [store])\n\n return (\n <ListIndexContext.Provider value={store}>\n {props.children}\n </ListIndexContext.Provider>\n )\n}\n\n/**\n * Read the 1-based list index of the block at `path`, or `undefined` when\n * the block is not a list item. Re-renders only when the index at this\n * path changes.\n *\n * `path` is the keyed block path render callbacks receive, e.g.\n * `[{_key: 'b0'}]`.\n *\n * @beta\n */\nexport function useListIndex(path: Path): number | undefined {\n const store = useContext(ListIndexContext)\n\n if (store === undefined) {\n throw new Error('useListIndex must be used below a <ListIndexProvider>')\n }\n\n // Callers typically pass a fresh `path` array every render, so the\n // serialized string, not the array, is what keeps `subscribe` stable\n // below. Memoizing on the array would never hit.\n const serializedPath = serializePath(path)\n\n const subscribe = useCallback(\n (callback: () => void) => store.subscribeKey(serializedPath, callback),\n [store, serializedPath],\n )\n\n const getSnapshot = () => store.get(serializedPath)\n\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n}\n"],"names":["serializePath","path","reduce","result","segment","index","isKeyedSegment","_key","buildListIndexMap","context","listIndexMap","Map","levelIndexMaps","previousListItem","block","value","undefined","isTextBlock","clear","listItem","level","levelIndexMap","get","set","listIndex","forEach","levelsToDelete","_","push","delete","levelCounter","createListIndexStore","editor","getSnapshot","subscribers","rebuildScheduled","subscribed","scheduleRebuild","queueMicrotask","rebuild","previousListIndexMap","serializedPath","callbacks","callback","subscribeKey","bucket","Set","add","size","subscribe","subscription","on","event","operation","type","length","unsubscribe","ListIndexContext","createContext","ListIndexProvider","props","$","_c","useEditor","t0","store","t1","t2","useEffect","t3","children","useListIndex","useContext","Error","useSyncExternalStore"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@portabletext/plugin-list-index",
3
+ "version": "1.0.0",
4
+ "description": "A helper plugin for calculating list indices for flat blocks based on listItem and level",
5
+ "keywords": [
6
+ "portabletext",
7
+ "plugin",
8
+ "list-index",
9
+ "lists"
10
+ ],
11
+ "homepage": "https://portabletext.org",
12
+ "bugs": {
13
+ "url": "https://github.com/portabletext/editor/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/portabletext/editor.git",
18
+ "directory": "packages/plugin-list-index"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Sanity.io <hello@sanity.io>",
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": "./dist/index.js",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "devDependencies": {
34
+ "@sanity/tsconfig": "^2.1.0",
35
+ "@types/react": "^19.2.14",
36
+ "@types/react-dom": "^19.2.3",
37
+ "@vitejs/plugin-react": "^5.2.0",
38
+ "@vitest/browser": "^4.1.8",
39
+ "@vitest/browser-playwright": "^4.1.8",
40
+ "babel-plugin-react-compiler": "^1.0.0",
41
+ "eslint": "^9.39.1",
42
+ "eslint-plugin-react-hooks": "^7.1.1",
43
+ "react": "^19.2.5",
44
+ "react-dom": "^19.2.5",
45
+ "typescript": "5.9.3",
46
+ "typescript-eslint": "^8.48.0",
47
+ "vitest": "^4.1.8",
48
+ "vitest-browser-react": "^2.2.0",
49
+ "@portabletext/editor": "^7.4.0",
50
+ "@portabletext/test": "^1.0.3",
51
+ "@portabletext/schema": "^2.2.0"
52
+ },
53
+ "peerDependencies": {
54
+ "react": "^19.2",
55
+ "@portabletext/editor": "^7.4.0"
56
+ },
57
+ "engines": {
58
+ "node": ">=20.19 <22 || >=22.12"
59
+ },
60
+ "scripts": {
61
+ "build": "pkg-utils build --strict --check --clean",
62
+ "check:lint": "biome lint .",
63
+ "check:react-compiler": "eslint .",
64
+ "check:types": "tsc",
65
+ "check:types:watch": "tsc --watch",
66
+ "clean": "del .turbo && del dist && del node_modules",
67
+ "dev": "pkg-utils watch",
68
+ "lint:fix": "biome lint --write .",
69
+ "test:browser": "vitest --run --project browser",
70
+ "test:browser:chromium": "vitest --run --project \"browser (chromium)\"",
71
+ "test:unit": "vitest --run --project unit",
72
+ "test:watch": "vitest"
73
+ }
74
+ }