@portabletext/plugin-dnd 1.0.28 → 1.0.30

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/dist/index.d.ts CHANGED
@@ -1,7 +1,14 @@
1
- import {JSX} from 'react'
2
- import type {Path} from '@portabletext/editor'
3
- import {ReactNode} from 'react'
4
-
1
+ import { ReactNode } from "react";
2
+ import { Path } from "@portabletext/editor";
3
+ /**
4
+ * The block-relative position a drop would land at.
5
+ *
6
+ * @beta
7
+ */
8
+ type DropPosition = {
9
+ path: Path;
10
+ position: 'start' | 'end';
11
+ };
5
12
  /**
6
13
  * Tracks the drop position during drag and drop and serves it through
7
14
  * context. Mount inside `EditorProvider`, wrapping whatever reads the
@@ -20,18 +27,9 @@ import {ReactNode} from 'react'
20
27
  *
21
28
  * @beta
22
29
  */
23
- export declare function DndProvider(props: {children?: ReactNode}): JSX.Element
24
-
25
- /**
26
- * The block-relative position a drop would land at.
27
- *
28
- * @beta
29
- */
30
- export declare type DropPosition = {
31
- path: Path
32
- position: 'start' | 'end'
33
- }
34
-
30
+ declare function DndProvider(props: {
31
+ children?: ReactNode;
32
+ }): import("react").JSX.Element;
35
33
  /**
36
34
  * Read where a drop would land relative to the block at `path`: `'start'`,
37
35
  * `'end'`, or `undefined` when no drag is hovering this block. Re-renders
@@ -42,8 +40,6 @@ export declare type DropPosition = {
42
40
  *
43
41
  * @beta
44
42
  */
45
- export declare function useDropPosition(
46
- path: Path,
47
- ): DropPosition['position'] | undefined
48
-
49
- export {}
43
+ declare function useDropPosition(path: Path): DropPosition['position'] | undefined;
44
+ export { DndProvider, type DropPosition, useDropPosition };
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/plugin.dnd.tsx"],"mappings":";;;;;;;KA8BY;EACV,MAAM;EACN;;;;;;;;;;;;;;;;;;;;iBAwMc,YAAY;EAAQ,WAAW;oBAAU,IAAA;;;;;;;;;;;iBAiCzC,gBACd,MAAM,OACL"}
package/dist/index.js CHANGED
@@ -1,210 +1,229 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
1
  import { c } from "react/compiler-runtime";
3
2
  import { defineBehavior, effect, forward } from "@portabletext/editor/behaviors";
4
3
  import { BehaviorPlugin } from "@portabletext/editor/plugins";
5
- import { getFocusInlineObject, isSelectionCollapsed, getFocusTextBlock, getFocusSpan, getFragment, isSelectionExpanded, getSelectionStartBlock, getSelectionEndBlock, isOverlappingSelection, getFocusBlock, getSelectedBlocks, isSelectingEntireBlocks } from "@portabletext/editor/selectors";
4
+ import { getFocusBlock, getFocusInlineObject, getFocusSpan, getFocusTextBlock, getFragment, getSelectedBlocks, getSelectionEndBlock, getSelectionStartBlock, isOverlappingSelection, isSelectingEntireBlocks, isSelectionCollapsed, isSelectionExpanded } from "@portabletext/editor/selectors";
6
5
  import { getBlockEndPoint, getBlockStartPoint, isKeyedSegment } from "@portabletext/editor/utils";
7
- import { createContext, useEffect, useContext, useSyncExternalStore } from "react";
8
- function getDragSelection({
9
- eventSelection,
10
- snapshot
11
- }) {
12
- let dragSelection = eventSelection;
13
- if (getFocusInlineObject({
14
- ...snapshot,
15
- context: {
16
- ...snapshot.context,
17
- selection: eventSelection
18
- }
19
- }))
20
- return dragSelection;
21
- const draggingCollapsedSelection = isSelectionCollapsed({
22
- ...snapshot,
23
- context: {
24
- ...snapshot.context,
25
- selection: eventSelection
26
- }
27
- }), draggedTextBlock = getFocusTextBlock({
28
- ...snapshot,
29
- context: {
30
- ...snapshot.context,
31
- selection: eventSelection
32
- }
33
- }), draggedSpan = getFocusSpan({
34
- ...snapshot,
35
- context: {
36
- ...snapshot.context,
37
- selection: eventSelection
38
- }
39
- });
40
- draggingCollapsedSelection && draggedTextBlock && draggedSpan && (dragSelection = {
41
- anchor: getBlockStartPoint({
42
- context: snapshot.context,
43
- block: draggedTextBlock
44
- }),
45
- focus: getBlockEndPoint({
46
- context: snapshot.context,
47
- block: draggedTextBlock
48
- })
49
- });
50
- const selectedBlocks = getFragment(snapshot);
51
- if (snapshot.context.selection && isSelectionExpanded(snapshot) && selectedBlocks.length > 1) {
52
- const selectionStartBlock = getSelectionStartBlock(snapshot), selectionEndBlock = getSelectionEndBlock(snapshot);
53
- if (!selectionStartBlock || !selectionEndBlock)
54
- return dragSelection;
55
- const selectionStartPoint = getBlockStartPoint({
56
- context: snapshot.context,
57
- block: selectionStartBlock
58
- }), selectionEndPoint = getBlockEndPoint({
59
- context: snapshot.context,
60
- block: selectionEndBlock
61
- });
62
- isOverlappingSelection(eventSelection)({
63
- ...snapshot,
64
- context: {
65
- ...snapshot.context,
66
- selection: {
67
- anchor: selectionStartPoint,
68
- focus: selectionEndPoint
69
- }
70
- }
71
- }) && (dragSelection = {
72
- anchor: selectionStartPoint,
73
- focus: selectionEndPoint
74
- });
75
- }
76
- return dragSelection;
6
+ import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ /**
9
+ * Given the current editor `snapshot` and an `eventSelection` representing
10
+ * where the drag event originates from, calculate the selection in the
11
+ * editor that should be dragged.
12
+ *
13
+ * Duplicated from the editor's internal `getDragSelection` rather than
14
+ * imported: exporting it from core would add permanent public API for what
15
+ * is an implementation detail of this plugin. Built on public selectors
16
+ * only. Keep in sync with
17
+ * `packages/editor/src/selectors/drag-selection.ts`.
18
+ */
19
+ function getDragSelection({ eventSelection, snapshot }) {
20
+ let dragSelection = eventSelection;
21
+ if (getFocusInlineObject({
22
+ ...snapshot,
23
+ context: {
24
+ ...snapshot.context,
25
+ selection: eventSelection
26
+ }
27
+ })) return dragSelection;
28
+ let draggingCollapsedSelection = isSelectionCollapsed({
29
+ ...snapshot,
30
+ context: {
31
+ ...snapshot.context,
32
+ selection: eventSelection
33
+ }
34
+ }), draggedTextBlock = getFocusTextBlock({
35
+ ...snapshot,
36
+ context: {
37
+ ...snapshot.context,
38
+ selection: eventSelection
39
+ }
40
+ }), draggedSpan = getFocusSpan({
41
+ ...snapshot,
42
+ context: {
43
+ ...snapshot.context,
44
+ selection: eventSelection
45
+ }
46
+ });
47
+ draggingCollapsedSelection && draggedTextBlock && draggedSpan && (dragSelection = {
48
+ anchor: getBlockStartPoint({
49
+ context: snapshot.context,
50
+ block: draggedTextBlock
51
+ }),
52
+ focus: getBlockEndPoint({
53
+ context: snapshot.context,
54
+ block: draggedTextBlock
55
+ })
56
+ });
57
+ let selectedBlocks = getFragment(snapshot);
58
+ if (snapshot.context.selection && isSelectionExpanded(snapshot) && selectedBlocks.length > 1) {
59
+ let selectionStartBlock = getSelectionStartBlock(snapshot), selectionEndBlock = getSelectionEndBlock(snapshot);
60
+ if (!selectionStartBlock || !selectionEndBlock) return dragSelection;
61
+ let selectionStartPoint = getBlockStartPoint({
62
+ context: snapshot.context,
63
+ block: selectionStartBlock
64
+ }), selectionEndPoint = getBlockEndPoint({
65
+ context: snapshot.context,
66
+ block: selectionEndBlock
67
+ });
68
+ isOverlappingSelection(eventSelection)({
69
+ ...snapshot,
70
+ context: {
71
+ ...snapshot.context,
72
+ selection: {
73
+ anchor: selectionStartPoint,
74
+ focus: selectionEndPoint
75
+ }
76
+ }
77
+ }) && (dragSelection = {
78
+ anchor: selectionStartPoint,
79
+ focus: selectionEndPoint
80
+ });
81
+ }
82
+ return dragSelection;
77
83
  }
78
84
  function createDropPositionStore() {
79
- let current;
80
- const subscribers = /* @__PURE__ */ new Map();
81
- function notify(serializedPath) {
82
- if (serializedPath === void 0)
83
- return;
84
- const bucket = subscribers.get(serializedPath);
85
- if (bucket !== void 0)
86
- for (const callback of bucket)
87
- callback();
88
- }
89
- return {
90
- get: (serializedPath) => current?.serializedPath === serializedPath ? current.position : void 0,
91
- subscribeKey: (serializedPath, callback) => {
92
- let bucket = subscribers.get(serializedPath);
93
- return bucket === void 0 && (bucket = /* @__PURE__ */ new Set(), subscribers.set(serializedPath, bucket)), bucket.add(callback), () => {
94
- bucket.delete(callback), bucket.size === 0 && subscribers.delete(serializedPath);
95
- };
96
- },
97
- set: (next) => {
98
- const previous = current;
99
- current = next ? {
100
- serializedPath: serializePath(next.path),
101
- position: next.position
102
- } : void 0, !(previous?.serializedPath === current?.serializedPath && previous?.position === current?.position) && (previous?.serializedPath !== current?.serializedPath && notify(previous?.serializedPath), notify(current?.serializedPath));
103
- }
104
- };
85
+ let current, subscribers = /* @__PURE__ */ new Map();
86
+ function notify(serializedPath) {
87
+ if (serializedPath === void 0) return;
88
+ let bucket = subscribers.get(serializedPath);
89
+ if (bucket !== void 0) for (let callback of bucket) callback();
90
+ }
91
+ return {
92
+ get: (serializedPath) => current?.serializedPath === serializedPath ? current.position : void 0,
93
+ subscribeKey: (serializedPath, callback) => {
94
+ let bucket = subscribers.get(serializedPath);
95
+ return bucket === void 0 && (bucket = /* @__PURE__ */ new Set(), subscribers.set(serializedPath, bucket)), bucket.add(callback), () => {
96
+ bucket.delete(callback), bucket.size === 0 && subscribers.delete(serializedPath);
97
+ };
98
+ },
99
+ set: (next) => {
100
+ let previous = current;
101
+ current = next ? {
102
+ serializedPath: serializePath(next.path),
103
+ position: next.position
104
+ } : void 0, (previous?.serializedPath !== current?.serializedPath || previous?.position !== current?.position) && (previous?.serializedPath !== current?.serializedPath && notify(previous?.serializedPath), notify(current?.serializedPath));
105
+ }
106
+ };
105
107
  }
108
+ /**
109
+ * The behaviors observe the public `drag.*` events and `forward` every
110
+ * event they handle: consumer behaviors run before the editor's own drag
111
+ * handling, so omitting the forward would swallow the event and break the
112
+ * drag itself. (The editor's internal drop-position tracking gets away
113
+ * without forwarding because it registers below core priority.)
114
+ */
106
115
  function createDropPositionBehaviors(setDropPosition) {
107
- return [defineBehavior({
108
- on: "drag.dragover",
109
- guard: ({
110
- snapshot,
111
- event
112
- }) => {
113
- const dropFocusBlock = getFocusBlock({
114
- ...snapshot,
115
- context: {
116
- ...snapshot.context,
117
- selection: event.position.selection
118
- }
119
- });
120
- if (!dropFocusBlock)
121
- return !1;
122
- const dragOrigin = event.dragOrigin;
123
- if (!dragOrigin)
124
- return !1;
125
- const dragSelection = getDragSelection({
126
- eventSelection: dragOrigin.selection,
127
- snapshot
128
- });
129
- return getSelectedBlocks({
130
- ...snapshot,
131
- context: {
132
- ...snapshot.context,
133
- selection: dragSelection
134
- }
135
- }).some((draggedBlock) => draggedBlock.node._key === dropFocusBlock.node._key) || !isSelectingEntireBlocks({
136
- ...snapshot,
137
- context: {
138
- ...snapshot.context,
139
- selection: dragSelection
140
- }
141
- }) ? !1 : {
142
- dropFocusBlock
143
- };
144
- },
145
- actions: [({
146
- event
147
- }, {
148
- dropFocusBlock
149
- }) => [effect(() => {
150
- setDropPosition({
151
- path: dropFocusBlock.path,
152
- position: event.position.block
153
- });
154
- }), forward(event)]]
155
- }), defineBehavior({
156
- on: "drag.*",
157
- guard: ({
158
- event
159
- }) => event.type !== "drag.dragover",
160
- actions: [({
161
- event
162
- }) => [effect(() => {
163
- setDropPosition(void 0);
164
- }), forward(event)]]
165
- })];
116
+ return [defineBehavior({
117
+ on: "drag.dragover",
118
+ guard: ({ snapshot, event }) => {
119
+ let dropFocusBlock = getFocusBlock({
120
+ ...snapshot,
121
+ context: {
122
+ ...snapshot.context,
123
+ selection: event.position.selection
124
+ }
125
+ });
126
+ if (!dropFocusBlock) return !1;
127
+ let dragOrigin = event.dragOrigin;
128
+ if (!dragOrigin) return !1;
129
+ let dragSelection = getDragSelection({
130
+ eventSelection: dragOrigin.selection,
131
+ snapshot
132
+ });
133
+ return getSelectedBlocks({
134
+ ...snapshot,
135
+ context: {
136
+ ...snapshot.context,
137
+ selection: dragSelection
138
+ }
139
+ }).some((draggedBlock) => draggedBlock.node._key === dropFocusBlock.node._key) || !isSelectingEntireBlocks({
140
+ ...snapshot,
141
+ context: {
142
+ ...snapshot.context,
143
+ selection: dragSelection
144
+ }
145
+ }) ? !1 : { dropFocusBlock };
146
+ },
147
+ actions: [({ event }, { dropFocusBlock }) => [effect(() => {
148
+ setDropPosition({
149
+ path: dropFocusBlock.path,
150
+ position: event.position.block
151
+ });
152
+ }), forward(event)]]
153
+ }), defineBehavior({
154
+ on: "drag.*",
155
+ guard: ({ event }) => event.type !== "drag.dragover",
156
+ actions: [({ event }) => [effect(() => {
157
+ setDropPosition(void 0);
158
+ }), forward(event)]]
159
+ })];
166
160
  }
167
161
  const DndContext = createContext(void 0);
162
+ /**
163
+ * Tracks the drop position during drag and drop and serves it through
164
+ * context. Mount inside `EditorProvider`, wrapping whatever reads the
165
+ * position:
166
+ *
167
+ * ```tsx
168
+ * <EditorProvider initialConfig={...}>
169
+ * <DndProvider>
170
+ * <PortableTextEditable />
171
+ * </DndProvider>
172
+ * </EditorProvider>
173
+ * ```
174
+ *
175
+ * Reads via {@link useDropPosition} only re-render when the drop position
176
+ * at their own path changes.
177
+ *
178
+ * @beta
179
+ */
168
180
  function DndProvider(props) {
169
- const $ = c(7);
170
- let t0;
171
- $[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t0 = createDropPositionStore(), $[0] = t0) : t0 = $[0];
172
- const store = t0;
173
- let t1;
174
- $[1] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t1 = createDropPositionBehaviors(store.set), $[1] = t1) : t1 = $[1];
175
- const behaviors = t1;
176
- let t2, t3;
177
- $[2] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t2 = () => () => {
178
- store.set(void 0);
179
- }, t3 = [store], $[2] = t2, $[3] = t3) : (t2 = $[2], t3 = $[3]), useEffect(t2, t3);
180
- let t4;
181
- $[4] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel") ? (t4 = /* @__PURE__ */ jsx(BehaviorPlugin, { behaviors }), $[4] = t4) : t4 = $[4];
182
- let t5;
183
- return $[5] !== props.children ? (t5 = /* @__PURE__ */ jsxs(DndContext.Provider, { value: store, children: [
184
- t4,
185
- props.children
186
- ] }), $[5] = props.children, $[6] = t5) : t5 = $[6], t5;
181
+ let $ = c(7), t0;
182
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = createDropPositionStore(), $[0] = t0) : t0 = $[0];
183
+ let store = t0, t1;
184
+ $[1] === Symbol.for("react.memo_cache_sentinel") ? (t1 = createDropPositionBehaviors(store.set), $[1] = t1) : t1 = $[1];
185
+ let behaviors = t1, t2, t3;
186
+ $[2] === Symbol.for("react.memo_cache_sentinel") ? (t2 = () => () => {
187
+ store.set(void 0);
188
+ }, t3 = [store], $[2] = t2, $[3] = t3) : (t2 = $[2], t3 = $[3]), useEffect(t2, t3);
189
+ let t4;
190
+ $[4] === Symbol.for("react.memo_cache_sentinel") ? (t4 = /* @__PURE__ */ jsx(BehaviorPlugin, { behaviors }), $[4] = t4) : t4 = $[4];
191
+ let t5;
192
+ return $[5] === props.children ? t5 = $[6] : (t5 = /* @__PURE__ */ jsxs(DndContext.Provider, {
193
+ value: store,
194
+ children: [t4, props.children]
195
+ }), $[5] = props.children, $[6] = t5), t5;
187
196
  }
197
+ /**
198
+ * Read where a drop would land relative to the block at `path`: `'start'`,
199
+ * `'end'`, or `undefined` when no drag is hovering this block. Re-renders
200
+ * only when the position at this path changes.
201
+ *
202
+ * `path` is the keyed block path render callbacks receive, e.g.
203
+ * `[{_key: 'b0'}]`.
204
+ *
205
+ * @beta
206
+ */
188
207
  function useDropPosition(path) {
189
- const $ = c(8), store = useContext(DndContext);
190
- if (store === void 0)
191
- throw new Error("useDropPosition must be used below a <DndProvider>");
192
- let t0;
193
- $[0] !== path ? (t0 = serializePath(path), $[0] = path, $[1] = t0) : t0 = $[1];
194
- const serializedPath = t0;
195
- let t1;
196
- $[2] !== serializedPath || $[3] !== store ? (t1 = (callback) => store.subscribeKey(serializedPath, callback), $[2] = serializedPath, $[3] = store, $[4] = t1) : t1 = $[4];
197
- const subscribe = t1;
198
- let t2;
199
- $[5] !== serializedPath || $[6] !== store ? (t2 = () => store.get(serializedPath), $[5] = serializedPath, $[6] = store, $[7] = t2) : t2 = $[7];
200
- const getSnapshot = t2;
201
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
208
+ let $ = c(8), store = useContext(DndContext);
209
+ if (store === void 0) throw Error("useDropPosition must be used below a <DndProvider>");
210
+ let t0;
211
+ $[0] === path ? t0 = $[1] : (t0 = serializePath(path), $[0] = path, $[1] = t0);
212
+ let serializedPath = t0, t1;
213
+ $[2] !== serializedPath || $[3] !== store ? (t1 = (callback) => store.subscribeKey(serializedPath, callback), $[2] = serializedPath, $[3] = store, $[4] = t1) : t1 = $[4];
214
+ let subscribe = t1, t2;
215
+ $[5] !== serializedPath || $[6] !== store ? (t2 = () => store.get(serializedPath), $[5] = serializedPath, $[6] = store, $[7] = t2) : t2 = $[7];
216
+ let getSnapshot = t2;
217
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
202
218
  }
219
+ /**
220
+ * Serialize a keyed path to a string using Sanity's bracket notation.
221
+ * Duplicated from the editor's internal `serializePath`; see
222
+ * `drag-selection.ts` for the duplication rationale.
223
+ */
203
224
  function serializePath(path) {
204
- return path.reduce((result, segment, index) => isKeyedSegment(segment) ? `${result}[_key=="${segment._key}"]` : `${result}${index === 0 ? "" : "."}${segment}`, "");
225
+ return path.reduce((result, segment, index) => isKeyedSegment(segment) ? `${result}[_key=="${segment._key}"]` : `${result}${index === 0 ? "" : "."}${segment}`, "");
205
226
  }
206
- export {
207
- DndProvider,
208
- useDropPosition
209
- };
210
- //# sourceMappingURL=index.js.map
227
+ export { DndProvider, useDropPosition };
228
+
229
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/drag-selection.ts","../src/plugin.dnd.tsx"],"sourcesContent":["import type {EditorSelection, EditorSnapshot} from '@portabletext/editor'\nimport {\n getFocusInlineObject,\n getFocusSpan,\n getFocusTextBlock,\n getFragment,\n getSelectionEndBlock,\n getSelectionStartBlock,\n isOverlappingSelection,\n isSelectionCollapsed,\n isSelectionExpanded,\n} from '@portabletext/editor/selectors'\nimport {getBlockEndPoint, getBlockStartPoint} from '@portabletext/editor/utils'\n\n/**\n * Given the current editor `snapshot` and an `eventSelection` representing\n * where the drag event originates from, calculate the selection in the\n * editor that should be dragged.\n *\n * Duplicated from the editor's internal `getDragSelection` rather than\n * imported: exporting it from core would add permanent public API for what\n * is an implementation detail of this plugin. Built on public selectors\n * only. Keep in sync with\n * `packages/editor/src/selectors/drag-selection.ts`.\n */\nexport function getDragSelection({\n eventSelection,\n snapshot,\n}: {\n eventSelection: NonNullable<EditorSelection>\n snapshot: EditorSnapshot\n}): NonNullable<EditorSelection> {\n let dragSelection = eventSelection\n\n const draggedInlineObject = getFocusInlineObject({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggedInlineObject) {\n return dragSelection\n }\n\n const draggingCollapsedSelection = isSelectionCollapsed({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedSpan = getFocusSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggingCollapsedSelection && draggedTextBlock && draggedSpan) {\n // Looks like we are dragging an empty span\n // Let's drag the entire block instead\n dragSelection = {\n anchor: getBlockStartPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n focus: getBlockEndPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n }\n }\n\n const selectedBlocks = getFragment(snapshot)\n\n if (\n snapshot.context.selection &&\n isSelectionExpanded(snapshot) &&\n selectedBlocks.length > 1\n ) {\n const selectionStartBlock = getSelectionStartBlock(snapshot)\n const selectionEndBlock = getSelectionEndBlock(snapshot)\n\n if (!selectionStartBlock || !selectionEndBlock) {\n return dragSelection\n }\n\n const selectionStartPoint = getBlockStartPoint({\n context: snapshot.context,\n block: selectionStartBlock,\n })\n const selectionEndPoint = getBlockEndPoint({\n context: snapshot.context,\n block: selectionEndBlock,\n })\n\n const eventSelectionInsideBlocks = isOverlappingSelection(eventSelection)({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {anchor: selectionStartPoint, focus: selectionEndPoint},\n },\n })\n\n if (eventSelectionInsideBlocks) {\n dragSelection = {\n anchor: selectionStartPoint,\n focus: selectionEndPoint,\n }\n }\n }\n\n return dragSelection\n}\n","import type {Path} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n type Behavior,\n} from '@portabletext/editor/behaviors'\nimport {BehaviorPlugin} from '@portabletext/editor/plugins'\nimport {\n getFocusBlock,\n getSelectedBlocks,\n isSelectingEntireBlocks,\n} from '@portabletext/editor/selectors'\nimport {isKeyedSegment} from '@portabletext/editor/utils'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {getDragSelection} from './drag-selection'\n\n/**\n * The block-relative position a drop would land at.\n *\n * @beta\n */\nexport type DropPosition = {\n path: Path\n position: 'start' | 'end'\n}\n\n/**\n * One store per editor: the drag behaviors below maintain the current drop\n * position, and per-path subscriber buckets make notification O(changed\n * paths) rather than O(subscribers). `dragover` fires at mousemove\n * frequency, so only the block losing and the block gaining the indicator\n * re-render.\n */\ntype DropPositionStore = {\n get: (serializedPath: string) => DropPosition['position'] | undefined\n subscribeKey: (serializedPath: string, callback: () => void) => () => void\n set: (next: DropPosition | undefined) => void\n}\n\nfunction createDropPositionStore(): DropPositionStore {\n let current:\n | {serializedPath: string; position: DropPosition['position']}\n | undefined\n const subscribers = new Map<string, Set<() => void>>()\n\n function notify(serializedPath: string | undefined) {\n if (serializedPath === undefined) {\n return\n }\n\n const bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n return\n }\n\n for (const callback of bucket) {\n callback()\n }\n }\n\n return {\n get: (serializedPath) =>\n current?.serializedPath === serializedPath ? current.position : undefined,\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 set: (next) => {\n const previous = current\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 current = next\n ? {serializedPath: serializePath(next.path), position: next.position}\n : undefined\n\n if (\n previous?.serializedPath === current?.serializedPath &&\n previous?.position === current?.position\n ) {\n return\n }\n\n if (previous?.serializedPath !== current?.serializedPath) {\n notify(previous?.serializedPath)\n }\n\n notify(current?.serializedPath)\n },\n }\n}\n\n/**\n * The behaviors observe the public `drag.*` events and `forward` every\n * event they handle: consumer behaviors run before the editor's own drag\n * handling, so omitting the forward would swallow the event and break the\n * drag itself. (The editor's internal drop-position tracking gets away\n * without forwarding because it registers below core priority.)\n */\nfunction createDropPositionBehaviors(\n setDropPosition: (next: DropPosition | undefined) => void,\n): Array<Behavior> {\n return [\n defineBehavior({\n on: 'drag.dragover',\n guard: ({snapshot, event}) => {\n const dropFocusBlock = getFocusBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n })\n\n if (!dropFocusBlock) {\n return false\n }\n\n const dragOrigin = event.dragOrigin\n\n if (!dragOrigin) {\n return false\n }\n\n const dragSelection = getDragSelection({\n eventSelection: dragOrigin.selection,\n snapshot,\n })\n\n const draggedBlocks = getSelectedBlocks({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n })\n\n if (\n draggedBlocks.some(\n (draggedBlock) =>\n draggedBlock.node._key === dropFocusBlock.node._key,\n )\n ) {\n return false\n }\n\n const draggingEntireBlocks = isSelectingEntireBlocks({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n })\n\n if (!draggingEntireBlocks) {\n return false\n }\n\n return {dropFocusBlock}\n },\n actions: [\n ({event}, {dropFocusBlock}) => [\n effect(() => {\n setDropPosition({\n path: dropFocusBlock.path,\n position: event.position.block,\n })\n }),\n forward(event),\n ],\n ],\n }),\n defineBehavior({\n on: 'drag.*',\n guard: ({event}) => event.type !== 'drag.dragover',\n actions: [\n ({event}) => [\n effect(() => {\n setDropPosition(undefined)\n }),\n forward(event),\n ],\n ],\n }),\n ]\n}\n\nconst DndContext = createContext<DropPositionStore | undefined>(undefined)\n\n/**\n * Tracks the drop position during drag and drop and serves it through\n * context. Mount inside `EditorProvider`, wrapping whatever reads the\n * position:\n *\n * ```tsx\n * <EditorProvider initialConfig={...}>\n * <DndProvider>\n * <PortableTextEditable />\n * </DndProvider>\n * </EditorProvider>\n * ```\n *\n * Reads via {@link useDropPosition} only re-render when the drop position\n * at their own path changes.\n *\n * @beta\n */\nexport function DndProvider(props: {children?: ReactNode}) {\n const store = useMemo(() => createDropPositionStore(), [])\n const behaviors = useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )\n\n useEffect(() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }, [store])\n\n return (\n <DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>\n )\n}\n\n/**\n * Read where a drop would land relative to the block at `path`: `'start'`,\n * `'end'`, or `undefined` when no drag is hovering this block. Re-renders\n * only when the position at this path changes.\n *\n * `path` is the keyed block path render callbacks receive, e.g.\n * `[{_key: 'b0'}]`.\n *\n * @beta\n */\nexport function useDropPosition(\n path: Path,\n): DropPosition['position'] | undefined {\n const store = useContext(DndContext)\n\n if (store === undefined) {\n throw new Error('useDropPosition must be used below a <DndProvider>')\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.\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\n/**\n * Serialize a keyed path to a string using Sanity's bracket notation.\n * Duplicated from the editor's internal `serializePath`; see\n * `drag-selection.ts` for the duplication rationale.\n */\nfunction 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"],"names":["getDragSelection","eventSelection","snapshot","dragSelection","getFocusInlineObject","context","selection","draggingCollapsedSelection","isSelectionCollapsed","draggedTextBlock","getFocusTextBlock","draggedSpan","getFocusSpan","anchor","getBlockStartPoint","block","focus","getBlockEndPoint","selectedBlocks","getFragment","isSelectionExpanded","length","selectionStartBlock","getSelectionStartBlock","selectionEndBlock","getSelectionEndBlock","selectionStartPoint","selectionEndPoint","isOverlappingSelection","createDropPositionStore","current","subscribers","Map","notify","serializedPath","undefined","bucket","get","callback","position","subscribeKey","Set","set","add","delete","size","next","previous","serializePath","path","createDropPositionBehaviors","setDropPosition","defineBehavior","on","guard","event","dropFocusBlock","getFocusBlock","dragOrigin","getSelectedBlocks","some","draggedBlock","node","_key","isSelectingEntireBlocks","actions","effect","forward","type","DndContext","createContext","DndProvider","props","$","_c","t0","Symbol","for","store","t1","behaviors","t2","t3","useEffect","t4","t5","children","useDropPosition","useContext","Error","subscribe","getSnapshot","useSyncExternalStore","reduce","result","segment","index","isKeyedSegment"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["EditorSelection","EditorSnapshot","getFocusInlineObject","getFocusSpan","getFocusTextBlock","getFragment","getSelectionEndBlock","getSelectionStartBlock","isOverlappingSelection","isSelectionCollapsed","isSelectionExpanded","getBlockEndPoint","getBlockStartPoint","getDragSelection","eventSelection","snapshot","NonNullable","dragSelection","draggedInlineObject","context","selection","draggingCollapsedSelection","draggedTextBlock","draggedSpan","anchor","block","focus","selectedBlocks","length","selectionStartBlock","selectionEndBlock","selectionStartPoint","selectionEndPoint","eventSelectionInsideBlocks","Path","defineBehavior","effect","forward","Behavior","BehaviorPlugin","getFocusBlock","getSelectedBlocks","isSelectingEntireBlocks","isKeyedSegment","createContext","useCallback","useContext","useEffect","useMemo","useSyncExternalStore","ReactNode","getDragSelection","DropPosition","path","position","DropPositionStore","get","serializedPath","subscribeKey","callback","set","next","createDropPositionStore","current","subscribers","Map","Set","notify","undefined","bucket","add","delete","size","previous","serializePath","createDropPositionBehaviors","setDropPosition","Array","on","guard","snapshot","event","dropFocusBlock","context","selection","dragOrigin","dragSelection","eventSelection","draggedBlocks","some","draggedBlock","node","_key","draggingEntireBlocks","actions","block","type","DndContext","DndProvider","props","$","_c","t0","Symbol","for","store","t1","behaviors","t2","t3","t4","t5","children","useDropPosition","Error","subscribe","getSnapshot","reduce","result","segment","index","separator"],"sources":["../src/drag-selection.ts","../src/plugin.dnd.tsx"],"sourcesContent":["import type {EditorSelection, EditorSnapshot} from '@portabletext/editor'\nimport {\n getFocusInlineObject,\n getFocusSpan,\n getFocusTextBlock,\n getFragment,\n getSelectionEndBlock,\n getSelectionStartBlock,\n isOverlappingSelection,\n isSelectionCollapsed,\n isSelectionExpanded,\n} from '@portabletext/editor/selectors'\nimport {getBlockEndPoint, getBlockStartPoint} from '@portabletext/editor/utils'\n\n/**\n * Given the current editor `snapshot` and an `eventSelection` representing\n * where the drag event originates from, calculate the selection in the\n * editor that should be dragged.\n *\n * Duplicated from the editor's internal `getDragSelection` rather than\n * imported: exporting it from core would add permanent public API for what\n * is an implementation detail of this plugin. Built on public selectors\n * only. Keep in sync with\n * `packages/editor/src/selectors/drag-selection.ts`.\n */\nexport function getDragSelection({\n eventSelection,\n snapshot,\n}: {\n eventSelection: NonNullable<EditorSelection>\n snapshot: EditorSnapshot\n}): NonNullable<EditorSelection> {\n let dragSelection = eventSelection\n\n const draggedInlineObject = getFocusInlineObject({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggedInlineObject) {\n return dragSelection\n }\n\n const draggingCollapsedSelection = isSelectionCollapsed({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedTextBlock = getFocusTextBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n const draggedSpan = getFocusSpan({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: eventSelection,\n },\n })\n\n if (draggingCollapsedSelection && draggedTextBlock && draggedSpan) {\n // Looks like we are dragging an empty span\n // Let's drag the entire block instead\n dragSelection = {\n anchor: getBlockStartPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n focus: getBlockEndPoint({\n context: snapshot.context,\n block: draggedTextBlock,\n }),\n }\n }\n\n const selectedBlocks = getFragment(snapshot)\n\n if (\n snapshot.context.selection &&\n isSelectionExpanded(snapshot) &&\n selectedBlocks.length > 1\n ) {\n const selectionStartBlock = getSelectionStartBlock(snapshot)\n const selectionEndBlock = getSelectionEndBlock(snapshot)\n\n if (!selectionStartBlock || !selectionEndBlock) {\n return dragSelection\n }\n\n const selectionStartPoint = getBlockStartPoint({\n context: snapshot.context,\n block: selectionStartBlock,\n })\n const selectionEndPoint = getBlockEndPoint({\n context: snapshot.context,\n block: selectionEndBlock,\n })\n\n const eventSelectionInsideBlocks = isOverlappingSelection(eventSelection)({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: {anchor: selectionStartPoint, focus: selectionEndPoint},\n },\n })\n\n if (eventSelectionInsideBlocks) {\n dragSelection = {\n anchor: selectionStartPoint,\n focus: selectionEndPoint,\n }\n }\n }\n\n return dragSelection\n}\n","import type {Path} from '@portabletext/editor'\nimport {\n defineBehavior,\n effect,\n forward,\n type Behavior,\n} from '@portabletext/editor/behaviors'\nimport {BehaviorPlugin} from '@portabletext/editor/plugins'\nimport {\n getFocusBlock,\n getSelectedBlocks,\n isSelectingEntireBlocks,\n} from '@portabletext/editor/selectors'\nimport {isKeyedSegment} from '@portabletext/editor/utils'\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from 'react'\nimport {getDragSelection} from './drag-selection'\n\n/**\n * The block-relative position a drop would land at.\n *\n * @beta\n */\nexport type DropPosition = {\n path: Path\n position: 'start' | 'end'\n}\n\n/**\n * One store per editor: the drag behaviors below maintain the current drop\n * position, and per-path subscriber buckets make notification O(changed\n * paths) rather than O(subscribers). `dragover` fires at mousemove\n * frequency, so only the block losing and the block gaining the indicator\n * re-render.\n */\ntype DropPositionStore = {\n get: (serializedPath: string) => DropPosition['position'] | undefined\n subscribeKey: (serializedPath: string, callback: () => void) => () => void\n set: (next: DropPosition | undefined) => void\n}\n\nfunction createDropPositionStore(): DropPositionStore {\n let current:\n | {serializedPath: string; position: DropPosition['position']}\n | undefined\n const subscribers = new Map<string, Set<() => void>>()\n\n function notify(serializedPath: string | undefined) {\n if (serializedPath === undefined) {\n return\n }\n\n const bucket = subscribers.get(serializedPath)\n\n if (bucket === undefined) {\n return\n }\n\n for (const callback of bucket) {\n callback()\n }\n }\n\n return {\n get: (serializedPath) =>\n current?.serializedPath === serializedPath ? current.position : undefined,\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 set: (next) => {\n const previous = current\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 current = next\n ? {serializedPath: serializePath(next.path), position: next.position}\n : undefined\n\n if (\n previous?.serializedPath === current?.serializedPath &&\n previous?.position === current?.position\n ) {\n return\n }\n\n if (previous?.serializedPath !== current?.serializedPath) {\n notify(previous?.serializedPath)\n }\n\n notify(current?.serializedPath)\n },\n }\n}\n\n/**\n * The behaviors observe the public `drag.*` events and `forward` every\n * event they handle: consumer behaviors run before the editor's own drag\n * handling, so omitting the forward would swallow the event and break the\n * drag itself. (The editor's internal drop-position tracking gets away\n * without forwarding because it registers below core priority.)\n */\nfunction createDropPositionBehaviors(\n setDropPosition: (next: DropPosition | undefined) => void,\n): Array<Behavior> {\n return [\n defineBehavior({\n on: 'drag.dragover',\n guard: ({snapshot, event}) => {\n const dropFocusBlock = getFocusBlock({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: event.position.selection,\n },\n })\n\n if (!dropFocusBlock) {\n return false\n }\n\n const dragOrigin = event.dragOrigin\n\n if (!dragOrigin) {\n return false\n }\n\n const dragSelection = getDragSelection({\n eventSelection: dragOrigin.selection,\n snapshot,\n })\n\n const draggedBlocks = getSelectedBlocks({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n })\n\n if (\n draggedBlocks.some(\n (draggedBlock) =>\n draggedBlock.node._key === dropFocusBlock.node._key,\n )\n ) {\n return false\n }\n\n const draggingEntireBlocks = isSelectingEntireBlocks({\n ...snapshot,\n context: {\n ...snapshot.context,\n selection: dragSelection,\n },\n })\n\n if (!draggingEntireBlocks) {\n return false\n }\n\n return {dropFocusBlock}\n },\n actions: [\n ({event}, {dropFocusBlock}) => [\n effect(() => {\n setDropPosition({\n path: dropFocusBlock.path,\n position: event.position.block,\n })\n }),\n forward(event),\n ],\n ],\n }),\n defineBehavior({\n on: 'drag.*',\n guard: ({event}) => event.type !== 'drag.dragover',\n actions: [\n ({event}) => [\n effect(() => {\n setDropPosition(undefined)\n }),\n forward(event),\n ],\n ],\n }),\n ]\n}\n\nconst DndContext = createContext<DropPositionStore | undefined>(undefined)\n\n/**\n * Tracks the drop position during drag and drop and serves it through\n * context. Mount inside `EditorProvider`, wrapping whatever reads the\n * position:\n *\n * ```tsx\n * <EditorProvider initialConfig={...}>\n * <DndProvider>\n * <PortableTextEditable />\n * </DndProvider>\n * </EditorProvider>\n * ```\n *\n * Reads via {@link useDropPosition} only re-render when the drop position\n * at their own path changes.\n *\n * @beta\n */\nexport function DndProvider(props: {children?: ReactNode}) {\n const store = useMemo(() => createDropPositionStore(), [])\n const behaviors = useMemo(\n () => createDropPositionBehaviors(store.set),\n [store],\n )\n\n useEffect(() => {\n return () => {\n // A drag in progress when the provider unmounts never delivers its\n // clearing event.\n store.set(undefined)\n }\n }, [store])\n\n return (\n <DndContext.Provider value={store}>\n <BehaviorPlugin behaviors={behaviors} />\n {props.children}\n </DndContext.Provider>\n )\n}\n\n/**\n * Read where a drop would land relative to the block at `path`: `'start'`,\n * `'end'`, or `undefined` when no drag is hovering this block. Re-renders\n * only when the position at this path changes.\n *\n * `path` is the keyed block path render callbacks receive, e.g.\n * `[{_key: 'b0'}]`.\n *\n * @beta\n */\nexport function useDropPosition(\n path: Path,\n): DropPosition['position'] | undefined {\n const store = useContext(DndContext)\n\n if (store === undefined) {\n throw new Error('useDropPosition must be used below a <DndProvider>')\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.\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\n/**\n * Serialize a keyed path to a string using Sanity's bracket notation.\n * Duplicated from the editor's internal `serializePath`; see\n * `drag-selection.ts` for the duplication rationale.\n */\nfunction 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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portabletext/plugin-dnd",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "Track the drop position during drag and drop for custom drop indicators",
5
5
  "keywords": [
6
6
  "dnd",
@@ -26,40 +26,42 @@
26
26
  "type": "module",
27
27
  "sideEffects": false,
28
28
  "main": "./dist/index.js",
29
+ "module": "./dist/index.js",
29
30
  "types": "./dist/index.d.ts",
30
31
  "exports": {
31
32
  ".": "./dist/index.js",
32
33
  "./package.json": "./package.json"
33
34
  },
34
35
  "devDependencies": {
35
- "@sanity/pkg-utils": "^10.8.2",
36
+ "@rolldown/plugin-babel": "^0.2.3",
37
+ "@sanity/pkg-utils": "^12.1.0",
36
38
  "@sanity/tsconfig": "^2.1.0",
37
39
  "@types/react": "^19.2.17",
38
40
  "@types/react-dom": "^19.2.3",
39
- "@vitejs/plugin-react": "^5.2.0",
41
+ "@vitejs/plugin-react": "^6.0.5",
40
42
  "@vitest/browser": "^4.1.10",
41
43
  "@vitest/browser-playwright": "^4.1.10",
42
44
  "babel-plugin-react-compiler": "^1.0.0",
43
45
  "eslint": "^9.39.4",
44
46
  "eslint-plugin-react-hooks": "^7.1.1",
45
- "react": "^19.2.7",
47
+ "react": "^19.2.8",
46
48
  "typescript": "6.0.3",
47
49
  "typescript-eslint": "^8.62.0",
48
- "vite": "^7.3.5",
50
+ "vite": "^8.2.0",
49
51
  "vitest": "^4.1.10",
50
- "@portabletext/editor": "^7.10.16",
51
- "@portabletext/schema": "^2.2.3",
52
- "@portabletext/test": "^1.0.4"
52
+ "@portabletext/editor": "^7.10.18",
53
+ "@portabletext/schema": "^2.2.4",
54
+ "@portabletext/test": "^1.0.5"
53
55
  },
54
56
  "peerDependencies": {
55
57
  "react": "^19.2",
56
- "@portabletext/editor": "^7.10.16"
58
+ "@portabletext/editor": "^7.10.18"
57
59
  },
58
60
  "engines": {
59
61
  "node": ">=20.19 <22 || >=22.12"
60
62
  },
61
63
  "scripts": {
62
- "build": "pkg-utils build --strict --check --clean",
64
+ "build": "pkg-utils build --strict --check",
63
65
  "check:lint": "biome lint .",
64
66
  "check:react-compiler": "eslint .",
65
67
  "check:types": "tsc",