mathbox-react 0.0.9-dev → 0.0.10
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/build/cjs/index.js +11 -2
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/types/components/components.d.ts +2 -1
- package/build/cjs/types/components/{Cartesian.spec.d.ts → components.spec.d.ts} +0 -0
- package/build/cjs/types/components/util.d.ts +38 -0
- package/build/esm/index.js +11 -2
- package/build/esm/index.js.map +1 -1
- package/build/esm/types/components/components.d.ts +2 -1
- package/build/esm/types/components/{Cartesian.spec.d.ts → components.spec.d.ts} +0 -0
- package/build/esm/types/components/util.d.ts +38 -0
- package/build/index.d.ts +4 -1
- package/package.json +7 -3
- package/src/components/Mathbox.tsx +3 -2
- package/src/components/{Cartesian.spec.tsx → components.spec.tsx} +75 -3
- package/src/components/components.tsx +24 -5
- package/src/components/util.ts +106 -0
- package/build/cjs/types/components/hooks.d.ts +0 -23
- package/build/esm/types/components/hooks.d.ts +0 -23
- package/src/components/hooks.ts +0 -78
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Props, NodeType, MathboxSelection } from "mathbox";
|
|
3
3
|
import { WithChildren } from "./types";
|
|
4
|
-
|
|
4
|
+
import { ParentNodeTypes } from "./util";
|
|
5
|
+
declare type MathboxComponent<T extends NodeType> = React.ForwardRefExoticComponent<(T extends ParentNodeTypes ? WithChildren<Props[T]> : Props[T]) & React.RefAttributes<MathboxSelection<T>>>;
|
|
5
6
|
/**
|
|
6
7
|
* Component wrapper for mathbox [`area`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#dataarea).
|
|
7
8
|
*/
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MathboxSelection, NodeType } from "mathbox";
|
|
2
|
+
export declare const isRootDestroyed: (selection: MathboxSelection) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Check that parent is actually the selection's parent.
|
|
5
|
+
* The mathbox components occasionally render with a selection value whose
|
|
6
|
+
* parent does not match the parent specified in MathboxContext.
|
|
7
|
+
*
|
|
8
|
+
* I believe this fundamentally this is happening because
|
|
9
|
+
* - we store parent value in context...
|
|
10
|
+
* - we update the context parent value using useEffect hooks..
|
|
11
|
+
* - useEffect hooks are called children before parents.
|
|
12
|
+
*
|
|
13
|
+
* One wonders whether we really need to be using useEffect hooks for the
|
|
14
|
+
* mathbox components, or if we can just put all the node creation/removal stuff
|
|
15
|
+
* in a render function. Hooks seem nice for dependencies, but mathbox is pretty
|
|
16
|
+
* smart about diffing out the actual changes.
|
|
17
|
+
*/
|
|
18
|
+
export declare const isSelectionParent: (selection: MathboxSelection, parent: MathboxSelection) => boolean;
|
|
19
|
+
declare const CAN_HAVE_CHILDREN: readonly ["view", "cartesian", "cartesian4", "polar", "spherical", "stereographic", "stereographic4", "transform", "transform4", "vertex", "fragment", "layer", "mask", "group", "inherit", "root", "unit", "rtt", "clock", "now", "move", "present", "reveal", "slide"];
|
|
20
|
+
export declare type ParentNodeTypes = typeof CAN_HAVE_CHILDREN[number];
|
|
21
|
+
export declare const canNodeHaveChildren: (type: NodeType) => boolean;
|
|
22
|
+
export declare const capitalize: (s: string) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Returns the same reference to `value` as long as the the given values are
|
|
25
|
+
* deep equal.
|
|
26
|
+
*
|
|
27
|
+
* This is, in general, not a great idea. See
|
|
28
|
+
* https://github.com/facebook/react/issues/14476#issuecomment-471199055
|
|
29
|
+
*
|
|
30
|
+
* This should work OK for mathbox options because the objects in question are
|
|
31
|
+
* either shallow, or are deep equal at a fairly shallow level.
|
|
32
|
+
* E.g., OrbitControls may not be a shall object, but if it's not, it will at
|
|
33
|
+
* least be deep equal between renders.
|
|
34
|
+
*
|
|
35
|
+
* But should work OK for mathbox options.
|
|
36
|
+
*/
|
|
37
|
+
export declare const useDeepCompareMemo: <T>(value: T, initial: T) => T;
|
|
38
|
+
export {};
|
package/build/index.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ declare type WithChildren<T> = {
|
|
|
5
5
|
children?: ReactNode | ReactNode[];
|
|
6
6
|
} & T;
|
|
7
7
|
|
|
8
|
-
declare
|
|
8
|
+
declare const CAN_HAVE_CHILDREN: readonly ["view", "cartesian", "cartesian4", "polar", "spherical", "stereographic", "stereographic4", "transform", "transform4", "vertex", "fragment", "layer", "mask", "group", "inherit", "root", "unit", "rtt", "clock", "now", "move", "present", "reveal", "slide"];
|
|
9
|
+
declare type ParentNodeTypes = typeof CAN_HAVE_CHILDREN[number];
|
|
10
|
+
|
|
11
|
+
declare type MathboxComponent<T extends NodeType> = React.ForwardRefExoticComponent<(T extends ParentNodeTypes ? WithChildren<Props[T]> : Props[T]) & React.RefAttributes<MathboxSelection<T>>>;
|
|
9
12
|
/**
|
|
10
13
|
* Component wrapper for mathbox [`area`](https://gitgud.io/unconed/mathbox/-/blob/master/docs/primitives.md#dataarea).
|
|
11
14
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mathbox-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "React wrapper for Mathbox",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Chris Chudzicki",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@storybook/testing-library": "^0.0.9",
|
|
32
32
|
"@testing-library/react": "^12.1.3",
|
|
33
33
|
"@types/jest": "^27.4.1",
|
|
34
|
+
"@types/lodash": "^4.14.181",
|
|
34
35
|
"@types/react": "^17.0.39",
|
|
35
36
|
"@types/three": "^0.137.0",
|
|
36
37
|
"babel-jest": "^27.5.1",
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
"eslint-import-resolver-typescript": "^2.5.0",
|
|
43
44
|
"eslint-plugin-prettier": "^4.0.0",
|
|
44
45
|
"jest": "^27.5.1",
|
|
45
|
-
"mathbox": "^2.1.
|
|
46
|
+
"mathbox": "^2.1.4",
|
|
46
47
|
"prettier": "^2.5.1",
|
|
47
48
|
"react": "^17.0.2",
|
|
48
49
|
"react-dom": "^17.0.2",
|
|
@@ -55,8 +56,11 @@
|
|
|
55
56
|
"typescript": "^4.5.5"
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|
|
58
|
-
"mathbox": "^2.1.
|
|
59
|
+
"mathbox": "^2.1.4",
|
|
59
60
|
"react": "^17.0.2 || ^18.0.0",
|
|
60
61
|
"three": ">=0.118.0"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"lodash": "^4.17.21"
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -3,12 +3,12 @@ import React, {
|
|
|
3
3
|
useState,
|
|
4
4
|
forwardRef,
|
|
5
5
|
useImperativeHandle,
|
|
6
|
-
useMemo,
|
|
7
6
|
} from "react"
|
|
8
7
|
import { Color } from "three"
|
|
9
8
|
import { mathBox, RootProps, MathboxSelection, MathBoxOptions } from "mathbox"
|
|
10
9
|
import MathboxAPIContext from "./MathboxNodeContext"
|
|
11
10
|
import { WithChildren } from "./types"
|
|
11
|
+
import { useDeepCompareMemo } from "./util"
|
|
12
12
|
|
|
13
13
|
type Props = WithChildren<
|
|
14
14
|
{
|
|
@@ -22,7 +22,7 @@ const Mathbox = (
|
|
|
22
22
|
ref: React.Ref<MathboxSelection<"root"> | null>
|
|
23
23
|
) => {
|
|
24
24
|
const { container, children, options, ...rootProps } = props
|
|
25
|
-
const mathboxOptions =
|
|
25
|
+
const mathboxOptions = useDeepCompareMemo(options ?? {}, {})
|
|
26
26
|
const [selection, setSelection] = useState<MathboxSelection<"root"> | null>(
|
|
27
27
|
null
|
|
28
28
|
)
|
|
@@ -39,6 +39,7 @@ const Mathbox = (
|
|
|
39
39
|
* TODO: Should Mathbox component allow setting these more easily?
|
|
40
40
|
*/
|
|
41
41
|
mathbox.three.renderer.setClearColor(new Color(0xffffff), 1.0)
|
|
42
|
+
mathbox.three.camera.position.set(1, 1, 2)
|
|
42
43
|
return () => {
|
|
43
44
|
mathbox.select("*").remove()
|
|
44
45
|
mathbox.three.destroy()
|
|
@@ -3,7 +3,7 @@ import { render, act } from "@testing-library/react"
|
|
|
3
3
|
import { MathboxSelection } from "mathbox"
|
|
4
4
|
import ContainedMathbox from "./ContainedMathbox"
|
|
5
5
|
import Mathbox from "./Mathbox"
|
|
6
|
-
import { Cartesian, Grid } from "./components"
|
|
6
|
+
import { Cartesian, Grid, Voxel } from "./components"
|
|
7
7
|
import { MathboxRef } from "./types"
|
|
8
8
|
|
|
9
9
|
function assertNotNil<T>(value: T): asserts value is NonNullable<T> {
|
|
@@ -27,6 +27,24 @@ const assertSelectionsEqual = (s1: MathboxSelection, s2: MathboxSelection) => {
|
|
|
27
27
|
})
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
describe("ContainedMathbox", () => {
|
|
31
|
+
it("makes a new mathbox instance only if the options have changed", () => {
|
|
32
|
+
const mbRef: MathboxRef<"root"> = { current: null }
|
|
33
|
+
const { rerender } = render(<ContainedMathbox options={{}} ref={mbRef} />)
|
|
34
|
+
|
|
35
|
+
const mb0 = mbRef.current
|
|
36
|
+
assertNotNil(mb0)
|
|
37
|
+
rerender(<ContainedMathbox options={{}} ref={mbRef} />)
|
|
38
|
+
const mb1 = mbRef.current
|
|
39
|
+
expect(mb1).toBe(mb0)
|
|
40
|
+
assertNotNil(mb1)
|
|
41
|
+
rerender(<ContainedMathbox options={{ plugins: ["core"] }} ref={mbRef} />)
|
|
42
|
+
const mb2 = mbRef.current
|
|
43
|
+
assertNotNil(mb2)
|
|
44
|
+
expect(mb2).not.toBe(mb1)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
30
48
|
describe("Cartesian", () => {
|
|
31
49
|
it("exposes Mathbox instance via ref", () => {
|
|
32
50
|
const mbRef: MathboxRef<"root"> = { current: null }
|
|
@@ -145,7 +163,7 @@ describe("Cartesian", () => {
|
|
|
145
163
|
* When re-rendered, this will create a new mathBox since the options
|
|
146
164
|
* object prop will have changed.
|
|
147
165
|
*/
|
|
148
|
-
const options2 = {}
|
|
166
|
+
const options2 = { plugins: ["core"] }
|
|
149
167
|
expect(options1).not.toBe(options2)
|
|
150
168
|
|
|
151
169
|
rerender(
|
|
@@ -163,7 +181,7 @@ describe("Cartesian", () => {
|
|
|
163
181
|
assertNotNil(cartesian2)
|
|
164
182
|
assertNotNil(grid2)
|
|
165
183
|
|
|
166
|
-
//
|
|
184
|
+
// The options have changed (via deep equal) so a new instance is created
|
|
167
185
|
expect(mb1).not.toBe(mb2)
|
|
168
186
|
|
|
169
187
|
expect(cartesian2).toHaveLength(1)
|
|
@@ -201,4 +219,58 @@ describe("Cartesian", () => {
|
|
|
201
219
|
expect(mbRef.current.three.element).toBe(containerDiv)
|
|
202
220
|
expect(mbRef.current.select("cartesian").length).toBe(1)
|
|
203
221
|
})
|
|
222
|
+
|
|
223
|
+
it("Can add and remove children without error", async () => {
|
|
224
|
+
const mbRef: MathboxRef<"root"> = { current: null }
|
|
225
|
+
|
|
226
|
+
const { rerender } = render(
|
|
227
|
+
<ContainedMathbox ref={mbRef} options={{}}>
|
|
228
|
+
<Cartesian>
|
|
229
|
+
<Grid />
|
|
230
|
+
</Cartesian>
|
|
231
|
+
</ContainedMathbox>
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
assertNotNil(mbRef.current)
|
|
235
|
+
expect(mbRef.current.select("grid").length).toBe(1)
|
|
236
|
+
|
|
237
|
+
rerender(
|
|
238
|
+
<ContainedMathbox ref={mbRef} options={{}}>
|
|
239
|
+
<Cartesian>
|
|
240
|
+
<Grid />
|
|
241
|
+
<Grid />
|
|
242
|
+
</Cartesian>
|
|
243
|
+
</ContainedMathbox>
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
assertNotNil(mbRef.current)
|
|
247
|
+
expect(mbRef.current.select("grid").length).toBe(2)
|
|
248
|
+
|
|
249
|
+
rerender(
|
|
250
|
+
<ContainedMathbox ref={mbRef} options={{}}>
|
|
251
|
+
<Cartesian />
|
|
252
|
+
</ContainedMathbox>
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assertNotNil(mbRef.current)
|
|
256
|
+
expect(mbRef.current.select("grid").length).toBe(0)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe("<Voxel />", () => {
|
|
261
|
+
it("throws a runtime error if it has children", () => {
|
|
262
|
+
const willThrow = () =>
|
|
263
|
+
render(
|
|
264
|
+
<ContainedMathbox>
|
|
265
|
+
<Cartesian>
|
|
266
|
+
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
|
267
|
+
{/* @ts-ignore */}
|
|
268
|
+
<Voxel>
|
|
269
|
+
<Grid />
|
|
270
|
+
</Voxel>
|
|
271
|
+
</Cartesian>
|
|
272
|
+
</ContainedMathbox>
|
|
273
|
+
)
|
|
274
|
+
expect(willThrow).toThrow("Component <Voxel /> cannot have children.")
|
|
275
|
+
})
|
|
204
276
|
})
|
|
@@ -9,14 +9,24 @@ import React, {
|
|
|
9
9
|
import { Props, NodeType, MathboxSelection } from "mathbox"
|
|
10
10
|
import MathboxAPIContext from "./MathboxNodeContext"
|
|
11
11
|
import { WithChildren } from "./types"
|
|
12
|
+
import {
|
|
13
|
+
isRootDestroyed,
|
|
14
|
+
isSelectionParent,
|
|
15
|
+
canNodeHaveChildren,
|
|
16
|
+
ParentNodeTypes,
|
|
17
|
+
capitalize,
|
|
18
|
+
} from "./util"
|
|
12
19
|
|
|
13
20
|
type MathboxComponent<T extends NodeType> = React.ForwardRefExoticComponent<
|
|
14
|
-
WithChildren<Props[T]>
|
|
21
|
+
(T extends ParentNodeTypes ? WithChildren<Props[T]> : Props[T]) &
|
|
22
|
+
React.RefAttributes<MathboxSelection<T>>
|
|
15
23
|
>
|
|
16
24
|
|
|
17
25
|
const mathboxComponentFactory = <T extends NodeType>(
|
|
18
26
|
type: T
|
|
19
27
|
): MathboxComponent<T> => {
|
|
28
|
+
const canHaveChildren = canNodeHaveChildren(type)
|
|
29
|
+
const componentName = capitalize(type)
|
|
20
30
|
const Comp = (
|
|
21
31
|
props: WithChildren<Props[T]>,
|
|
22
32
|
ref: React.Ref<MathboxSelection<T> | null>
|
|
@@ -28,6 +38,7 @@ const mathboxComponentFactory = <T extends NodeType>(
|
|
|
28
38
|
() => () => {
|
|
29
39
|
if (selection.current) {
|
|
30
40
|
selection.current.remove()
|
|
41
|
+
selection.current = null
|
|
31
42
|
}
|
|
32
43
|
},
|
|
33
44
|
[]
|
|
@@ -37,16 +48,19 @@ const mathboxComponentFactory = <T extends NodeType>(
|
|
|
37
48
|
const { children, ...others } = props
|
|
38
49
|
useEffect(() => {
|
|
39
50
|
if (!parent) return
|
|
51
|
+
if (isRootDestroyed(parent)) {
|
|
52
|
+
forceUpdate()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
40
55
|
if (selection.current) {
|
|
41
|
-
|
|
42
|
-
if (selection.current._up[0] !== parent[0]) {
|
|
56
|
+
if (!isSelectionParent(selection.current, parent)) {
|
|
43
57
|
selection.current.remove()
|
|
44
58
|
selection.current = null
|
|
45
59
|
forceUpdate()
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
|
-
|
|
49
62
|
if (!selection.current) {
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
50
64
|
// @ts-ignore
|
|
51
65
|
selection.current = parent[type](others)
|
|
52
66
|
forceUpdate()
|
|
@@ -54,7 +68,12 @@ const mathboxComponentFactory = <T extends NodeType>(
|
|
|
54
68
|
selection.current.set(others)
|
|
55
69
|
}
|
|
56
70
|
}, [parent, others])
|
|
57
|
-
|
|
71
|
+
if (!canHaveChildren) {
|
|
72
|
+
if (props.children) {
|
|
73
|
+
throw new Error(`Component <${componentName} /> cannot have children.`)
|
|
74
|
+
}
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
58
77
|
return (
|
|
59
78
|
<MathboxAPIContext.Provider value={selection.current}>
|
|
60
79
|
{props.children}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
|
+
import { isEqual } from "lodash"
|
|
3
|
+
import { useRef, useMemo } from "react"
|
|
4
|
+
import { MathboxSelection, NodeType } from "mathbox"
|
|
5
|
+
|
|
6
|
+
type WithPrivateUp = MathboxSelection & { _up?: WithPrivateUp }
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get root node given any node in Mathbox tree
|
|
10
|
+
*/
|
|
11
|
+
const getRoot = (selection: MathboxSelection) => {
|
|
12
|
+
let current = selection as WithPrivateUp
|
|
13
|
+
while (current._up) {
|
|
14
|
+
current = current._up
|
|
15
|
+
}
|
|
16
|
+
return current
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const isRootDestroyed = (selection: MathboxSelection) => {
|
|
20
|
+
const root = getRoot(selection)
|
|
21
|
+
return root.three === undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check that parent is actually the selection's parent.
|
|
26
|
+
* The mathbox components occasionally render with a selection value whose
|
|
27
|
+
* parent does not match the parent specified in MathboxContext.
|
|
28
|
+
*
|
|
29
|
+
* I believe this fundamentally this is happening because
|
|
30
|
+
* - we store parent value in context...
|
|
31
|
+
* - we update the context parent value using useEffect hooks..
|
|
32
|
+
* - useEffect hooks are called children before parents.
|
|
33
|
+
*
|
|
34
|
+
* One wonders whether we really need to be using useEffect hooks for the
|
|
35
|
+
* mathbox components, or if we can just put all the node creation/removal stuff
|
|
36
|
+
* in a render function. Hooks seem nice for dependencies, but mathbox is pretty
|
|
37
|
+
* smart about diffing out the actual changes.
|
|
38
|
+
*/
|
|
39
|
+
export const isSelectionParent = (
|
|
40
|
+
selection: MathboxSelection,
|
|
41
|
+
parent: MathboxSelection
|
|
42
|
+
) => {
|
|
43
|
+
const selectionParentNode = (selection as WithPrivateUp)._up?.[0]
|
|
44
|
+
return selectionParentNode === parent[0]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const CAN_HAVE_CHILDREN = [
|
|
48
|
+
"view",
|
|
49
|
+
"cartesian",
|
|
50
|
+
"cartesian4",
|
|
51
|
+
"polar",
|
|
52
|
+
"spherical",
|
|
53
|
+
"stereographic",
|
|
54
|
+
"stereographic4",
|
|
55
|
+
"transform",
|
|
56
|
+
"transform4",
|
|
57
|
+
"vertex",
|
|
58
|
+
"fragment",
|
|
59
|
+
"layer",
|
|
60
|
+
"mask",
|
|
61
|
+
"group",
|
|
62
|
+
"inherit",
|
|
63
|
+
"root",
|
|
64
|
+
"unit",
|
|
65
|
+
"rtt",
|
|
66
|
+
"clock",
|
|
67
|
+
"now",
|
|
68
|
+
"move",
|
|
69
|
+
"present",
|
|
70
|
+
"reveal",
|
|
71
|
+
"slide",
|
|
72
|
+
] as const
|
|
73
|
+
|
|
74
|
+
export type ParentNodeTypes = typeof CAN_HAVE_CHILDREN[number]
|
|
75
|
+
|
|
76
|
+
export const canNodeHaveChildren = (type: NodeType) =>
|
|
77
|
+
(CAN_HAVE_CHILDREN as readonly string[]).includes(type)
|
|
78
|
+
|
|
79
|
+
export const capitalize = (s: string) => {
|
|
80
|
+
if (!s) return ""
|
|
81
|
+
return s[0].toLocaleUpperCase() + s.slice(1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns the same reference to `value` as long as the the given values are
|
|
86
|
+
* deep equal.
|
|
87
|
+
*
|
|
88
|
+
* This is, in general, not a great idea. See
|
|
89
|
+
* https://github.com/facebook/react/issues/14476#issuecomment-471199055
|
|
90
|
+
*
|
|
91
|
+
* This should work OK for mathbox options because the objects in question are
|
|
92
|
+
* either shallow, or are deep equal at a fairly shallow level.
|
|
93
|
+
* E.g., OrbitControls may not be a shall object, but if it's not, it will at
|
|
94
|
+
* least be deep equal between renders.
|
|
95
|
+
*
|
|
96
|
+
* But should work OK for mathbox options.
|
|
97
|
+
*/
|
|
98
|
+
export const useDeepCompareMemo = <T>(value: T, initial: T): T => {
|
|
99
|
+
const oldValue = useRef<T>(initial)
|
|
100
|
+
const memoOptions = useMemo(() => {
|
|
101
|
+
if (isEqual(value, oldValue.current)) return oldValue.current
|
|
102
|
+
oldValue.current = value
|
|
103
|
+
return value
|
|
104
|
+
}, [value])
|
|
105
|
+
return memoOptions
|
|
106
|
+
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { MathboxSelection, Props } from "mathbox";
|
|
3
|
-
import type { WithChildren } from "./types";
|
|
4
|
-
/**
|
|
5
|
-
* Check that parent is actually the selection's parent.
|
|
6
|
-
* The mathbox components occasionally render with a selection value whose
|
|
7
|
-
* parent does not match the parent specified in MathboxContext.
|
|
8
|
-
*
|
|
9
|
-
* I believe this fundamentally this is happening because
|
|
10
|
-
* - we store parent value in context...
|
|
11
|
-
* - we update the context parent value using useEffect hooks..
|
|
12
|
-
* - useEffect hooks are called children before parents.
|
|
13
|
-
*
|
|
14
|
-
* One wonders whether we really need to be using useEffect hooks for the
|
|
15
|
-
* mathbox components, or if we can just put all the node creation/removal stuff
|
|
16
|
-
* in a render function. Hooks seem nice for dependencies, but mathbox is pretty
|
|
17
|
-
* smart about diffing out the actual changes.
|
|
18
|
-
*/
|
|
19
|
-
export declare const isSelectionParent: (selection: MathboxSelection, parent: MathboxSelection) => boolean;
|
|
20
|
-
export declare const useMathboxAPI: <T extends keyof Props>(name: T, props: WithChildren<Props[T]>, ref: React.Ref<MathboxSelection<T> | null>) => {
|
|
21
|
-
selection: MathboxSelection<T> | null;
|
|
22
|
-
parent: MathboxSelection<keyof Props> | null;
|
|
23
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import type { MathboxSelection, Props } from "mathbox";
|
|
3
|
-
import type { WithChildren } from "./types";
|
|
4
|
-
/**
|
|
5
|
-
* Check that parent is actually the selection's parent.
|
|
6
|
-
* The mathbox components occasionally render with a selection value whose
|
|
7
|
-
* parent does not match the parent specified in MathboxContext.
|
|
8
|
-
*
|
|
9
|
-
* I believe this fundamentally this is happening because
|
|
10
|
-
* - we store parent value in context...
|
|
11
|
-
* - we update the context parent value using useEffect hooks..
|
|
12
|
-
* - useEffect hooks are called children before parents.
|
|
13
|
-
*
|
|
14
|
-
* One wonders whether we really need to be using useEffect hooks for the
|
|
15
|
-
* mathbox components, or if we can just put all the node creation/removal stuff
|
|
16
|
-
* in a render function. Hooks seem nice for dependencies, but mathbox is pretty
|
|
17
|
-
* smart about diffing out the actual changes.
|
|
18
|
-
*/
|
|
19
|
-
export declare const isSelectionParent: (selection: MathboxSelection, parent: MathboxSelection) => boolean;
|
|
20
|
-
export declare const useMathboxAPI: <T extends keyof Props>(name: T, props: WithChildren<Props[T]>, ref: React.Ref<MathboxSelection<T> | null>) => {
|
|
21
|
-
selection: MathboxSelection<T> | null;
|
|
22
|
-
parent: MathboxSelection<keyof Props> | null;
|
|
23
|
-
};
|
package/src/components/hooks.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useContext,
|
|
3
|
-
useEffect,
|
|
4
|
-
useState,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
} from "react"
|
|
7
|
-
import type { MathboxSelection, NodeType, Props } from "mathbox"
|
|
8
|
-
import MathboxAPIContext from "./MathboxNodeContext"
|
|
9
|
-
import type { WithChildren } from "./types"
|
|
10
|
-
|
|
11
|
-
type WithPrivateUp = MathboxSelection & { _up: WithPrivateUp | null }
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check that parent is actually the selection's parent.
|
|
15
|
-
* The mathbox components occasionally render with a selection value whose
|
|
16
|
-
* parent does not match the parent specified in MathboxContext.
|
|
17
|
-
*
|
|
18
|
-
* I believe this fundamentally this is happening because
|
|
19
|
-
* - we store parent value in context...
|
|
20
|
-
* - we update the context parent value using useEffect hooks..
|
|
21
|
-
* - useEffect hooks are called children before parents.
|
|
22
|
-
*
|
|
23
|
-
* One wonders whether we really need to be using useEffect hooks for the
|
|
24
|
-
* mathbox components, or if we can just put all the node creation/removal stuff
|
|
25
|
-
* in a render function. Hooks seem nice for dependencies, but mathbox is pretty
|
|
26
|
-
* smart about diffing out the actual changes.
|
|
27
|
-
*/
|
|
28
|
-
export const isSelectionParent = (
|
|
29
|
-
selection: MathboxSelection,
|
|
30
|
-
parent: MathboxSelection
|
|
31
|
-
) => {
|
|
32
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
33
|
-
const selectionParentNode = (selection as WithPrivateUp)._up?.[0]
|
|
34
|
-
return selectionParentNode === parent[0]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const useMathboxAPI = <T extends NodeType>(
|
|
38
|
-
name: T,
|
|
39
|
-
props: WithChildren<Props[T]>,
|
|
40
|
-
ref: React.Ref<MathboxSelection<T> | null>
|
|
41
|
-
) => {
|
|
42
|
-
const parent = useContext(MathboxAPIContext)
|
|
43
|
-
const [selection, setSelection] = useState<MathboxSelection<T> | null>(null)
|
|
44
|
-
|
|
45
|
-
useEffect(
|
|
46
|
-
() => () => {
|
|
47
|
-
if (selection) {
|
|
48
|
-
selection.remove()
|
|
49
|
-
setSelection(null)
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
[selection, parent]
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
const { children, ...mbProps } = props
|
|
57
|
-
if (!parent) return
|
|
58
|
-
if (!selection || !isSelectionParent(selection, parent)) {
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
60
|
-
// @ts-ignore
|
|
61
|
-
const thisNode: MathboxSelection<T> = parent[name](mbProps)
|
|
62
|
-
setSelection(thisNode)
|
|
63
|
-
} else {
|
|
64
|
-
/**
|
|
65
|
-
* Set all the props anew. It's MathBox's responsibility to diff out the
|
|
66
|
-
* unchanged props if it wants to do that optimization.
|
|
67
|
-
*
|
|
68
|
-
* (In fact, Mathbox delegates that optimization to Threestrap through
|
|
69
|
-
* some rather byzantine inheritance.)
|
|
70
|
-
*/
|
|
71
|
-
selection.set(mbProps)
|
|
72
|
-
}
|
|
73
|
-
}, [parent, selection, setSelection, props, name])
|
|
74
|
-
|
|
75
|
-
useImperativeHandle(ref, () => selection, [selection])
|
|
76
|
-
|
|
77
|
-
return { selection, parent }
|
|
78
|
-
}
|