Skip to content

Commit

Permalink
feat(joint-react): adding external store
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelgja committed Feb 5, 2025
1 parent de3b197 commit ef39d8d
Show file tree
Hide file tree
Showing 24 changed files with 561 additions and 537 deletions.
11 changes: 6 additions & 5 deletions packages/joint-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
"react-dom": "^19.0.0"
},
"devDependencies": {
"storybook": "^8.5.3",
"@chromatic-com/storybook": "^3.2.4",
"@eslint-react/eslint-plugin": "1.15.1",
"@eslint/compat": "^1.1.1",
"@eslint/js": "^9.8.0",
"@eslint/js": "9.19.0",
"@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-onboarding": "^8.4.7",
Expand All @@ -36,14 +36,13 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/react-hooks": "^8.0.1",
"@types/eslint": "^9.6.0",
"@types/eslint": "9.6.1",
"@types/jest": "29.5.14",
"@types/react": "19.0.8",
"@types/react-dom": "^19.0.3",
"@types/react-test-renderer": "19.0.0",
"@typescript-eslint/eslint-plugin": "8.21.0",
"@typescript-eslint/parser": "8.21.0",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "9.19.0",
"eslint-plugin-depend": "0.12.0",
"eslint-plugin-jest": "^28.8.3",
Expand All @@ -55,18 +54,20 @@
"eslint-plugin-sonarjs": "3.0.1",
"eslint-plugin-tailwindcss": "^3.17.5",
"eslint-plugin-unicorn": "56.0.1",
"@vitejs/plugin-react": "^4.3.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5",
"react-test-renderer": "19.0.0",
"storybook": "^8.5.3",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "5.7.3",
"typescript-eslint": "8.21.0",
"vitest": "^3.0.4"
},
"dependencies": {
"@joint/core": "4.1.2"
"@joint/core": "*"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render } from '@testing-library/react'
import { GraphProvider } from '../graph-provider'
import { dia } from '@joint/core'
import { GraphContext } from '../../context/graph-context'
import type { GraphStore } from '../../hooks/use-create-graph-store'

describe('graph-provider', () => {
it('should render children and match snapshot', () => {
Expand All @@ -16,7 +17,7 @@ describe('graph-provider', () => {
})

it('should provide a graph instance in context', () => {
let contextGraph: dia.Graph | undefined
let contextGraph: GraphStore | undefined
function TestComponent() {
contextGraph = React.useContext(GraphContext)
return null
Expand Down
24 changes: 2 additions & 22 deletions packages/joint-react/src/components/__tests__/paper.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* eslint-disable react-perf/jsx-no-new-array-as-prop */
import { render, waitFor } from '@testing-library/react'
import { GraphProvider } from '../graph-provider'
import { PaperProvider } from '../paper-provider'
Expand All @@ -7,27 +7,7 @@ import { Paper } from '../paper'
describe('paper', () => {
it('should render paper component without errors', async () => {
const { asFragment, getByText } = render(
<GraphProvider
cells={[
{
id: '1',
type: 'standard.Rectangle',
position: { x: 10, y: 100 },
attrs: { label: { text: 'testing-rectangle1' } },
},
{
id: '2',
type: 'standard.Rectangle',
position: { x: 10, y: 100 },
attrs: { label: { text: 'testing-rectangle2' } },
},
{
type: 'standard.Link',
source: { id: '1' },
target: { id: '2' },
},
]}
>
<GraphProvider>
<PaperProvider>
<Paper />
</PaperProvider>
Expand Down
20 changes: 7 additions & 13 deletions packages/joint-react/src/components/graph-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useId, useMemo, useState } from 'react'
import { GraphContext } from '../context/graph-context'
import { dia, shapes } from '@joint/core'
import type { dia } from '@joint/core'
import { useCreateGraphStore } from '../hooks/use-create-graph-store'

export interface GraphProps {
/**
Expand All @@ -25,24 +25,18 @@ export interface GraphProps {
*/
readonly cellModel?: typeof dia.Cell
/**
* Callback to handle cells change.
* This handle events directly from the graph instance.
* This event is not triggered when cells are changed via React state.
* Initial cells to be added to graph
*/
readonly onCellsChange?: (cells: Array<dia.Cell>) => void
readonly cells?: Array<dia.Cell | dia.Cell.JSON>
}

/**
* GraphProvider component creates a graph instance and provides it to its children via context.
* It also handles updates to the graph when cells change via React state or JointJS events.
*/
export function GraphProvider(props: GraphProps) {
const { children, cellNamespace = shapes, cellModel, onCellsChange } = props
const { children, ...rest } = props
const graphStore = useCreateGraphStore(rest)

const [graphValue] = useState(() => {
const graph = props.graph ?? new dia.Graph({}, { cellNamespace, cellModel })
return graph
})

return <GraphContext.Provider value={graphValue}>{children}</GraphContext.Provider>
return <GraphContext.Provider value={graphStore}>{children}</GraphContext.Provider>
}
31 changes: 11 additions & 20 deletions packages/joint-react/src/components/paper-portal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { memo, type ReactNode } from 'react'
import type { PaperElement } from './paper'
import type { dia } from '@joint/core'
import { type ReactNode } from 'react'
import { createPortal } from 'react-dom'
import typedMemo from '../utils/typed-memo'
import type { BaseCell, RequiredCell } from '../types/cell.types'

export interface PaperPortalProps extends PaperElement {
export interface PaperPortalProps<T extends RequiredCell = BaseCell> {
/**
* A function that renders the element. It is called every time the element is rendered.
*/
renderElement: (element: dia.Cell) => ReactNode
/**
* Internal version of the paper element.
*/
version: number
readonly renderElement: (element: T) => ReactNode
readonly portalHtmlElement: HTMLElement
}

/**
Expand All @@ -20,16 +17,10 @@ export interface PaperPortalProps extends PaperElement {
* It takes a renderElement function, a cell, and a containerElement as props.
* The renderElement function is called with the cell as an argument and its return value is rendered inside the containerElement.
*/
function Component(props: Readonly<PaperPortalProps>) {
const { renderElement, cell, containerElement } = props
// useEffect(() => {
// cell.on('change', () => {})
// return () => {
// cell.off('change')
// }
// }, [])

return createPortal(renderElement(cell), containerElement)
function Component<T extends RequiredCell = BaseCell>(props: PaperPortalProps<T> & T) {
const { renderElement, portalHtmlElement, ...rest } = props
const cell = rest as unknown as T
return createPortal(renderElement(cell), portalHtmlElement)
}

export const PaperPortal = memo(Component)
export const PaperPortal = typedMemo(Component)
8 changes: 4 additions & 4 deletions packages/joint-react/src/components/paper-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { PaperOptions } from '../utils/create-paper'
import { createPaper } from '../utils/create-paper'
import { PaperContext } from '../context/paper-context'
import type { dia } from '@joint/core'
import { useGraph } from '../hooks/use-graph'
import { useGraphStore } from '../hooks/use-graph-store'

export interface PaperProviderProps extends PaperOptions {
readonly children: ReactNode
Expand All @@ -17,12 +17,12 @@ export interface PaperProviderProps extends PaperOptions {
* @see https://docs.jointjs.com/api/dia/Paper
*/
export function PaperProvider({ children, ...paperOptions }: Readonly<PaperProviderProps>) {
const graph = useGraph()
if (!graph) {
const graphStore = useGraphStore()
if (!graphStore) {
throw new Error('PaperProvider must be used within a GraphProvider')
}

const [paper] = useState<dia.Paper>(() => createPaper(graph, paperOptions))
const [paper] = useState<dia.Paper>(() => createPaper(graphStore.graph, paperOptions))

// Remove the paper when the component is unmounted.
useEffect(() => {
Expand Down
Loading

0 comments on commit ef39d8d

Please sign in to comment.