Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 8 additions & 82 deletions lib/ArcSegment.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import React, { useEffect, useImperativeHandle, useMemo } from 'react';
import React from 'react';
import Two from 'two.js';
import { useTwo } from './Context';

import type { ArcSegment as Instance } from 'two.js/src/shapes/arc-segment';
import { PathProps } from './Path';
import { type EventHandlers } from './Properties';
import { EVENT_HANDLER_NAMES } from './Events';
import { useTwoObject } from './useTwoObject';

export type ArcSegmentProps =
| PathProps
| 'startAngle'
| 'endAngle'
| 'innerRadius'
| 'outerRadius';

type ComponentProps = React.PropsWithChildren<
{
[K in Extract<ArcSegmentProps, keyof Instance>]?: Instance[K];
Expand All @@ -26,84 +25,11 @@ type ComponentProps = React.PropsWithChildren<
export type RefArcSegment = Instance;

export const ArcSegment = React.forwardRef<Instance, ComponentProps>(
({ x, y, resolution, ...props }, forwardedRef) => {
const { parent, registerEventShape, unregisterEventShape } = useTwo();

// Create the instance synchronously so it's available for refs immediately
const arcSegment = useMemo(
() => new Two.ArcSegment(0, 0, 0, 0, 0, 0, resolution),
[resolution]
);

// Extract event handlers from props
const { eventHandlers, shapeProps } = useMemo(() => {
const eventHandlers: Partial<EventHandlers> = {};
const shapeProps: Record<string, unknown> = {};

for (const key in props) {
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
// An explicitly `undefined` handler means "not interactive", so it
// must not count toward the registered handler set.
const handler = props[key as keyof EventHandlers];
if (handler !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventHandlers[key as keyof EventHandlers] = handler as any;
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shapeProps[key] = (props as any)[key];
}
}

return { eventHandlers, shapeProps };
}, [props]);

useEffect(() => {
if (parent) {
parent.add(arcSegment);
return () => {
parent.remove(arcSegment);
};
}
}, [parent, arcSegment]);

useEffect(() => {
// Update position
if (typeof x === 'number') arcSegment.translation.x = x;
if (typeof y === 'number') arcSegment.translation.y = y;

// Update other properties (excluding event handlers)
for (const key in shapeProps) {
if (key in arcSegment) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(arcSegment as any)[key] = (shapeProps as any)[key];
}
}
}, [shapeProps, arcSegment, x, y]);

// Unregister on unmount only
useEffect(() => {
return () => {
unregisterEventShape(arcSegment);
};
}, [arcSegment, unregisterEventShape]);

// Register / update event handlers
useEffect(() => {
if (Object.keys(eventHandlers).length > 0) {
registerEventShape(arcSegment, eventHandlers, parent ?? undefined);
} else {
unregisterEventShape(arcSegment);
}
}, [
arcSegment,
registerEventShape,
unregisterEventShape,
parent,
eventHandlers,
]);

useImperativeHandle(forwardedRef, () => arcSegment, [arcSegment]);
(props, forwardedRef) => {
useTwoObject(props, forwardedRef, {
factory: (p) => new Two.ArcSegment(0, 0, 0, 0, 0, 0, p.resolution),
constructionProps: ['resolution'],
});

return <></>;
}
Expand Down
90 changes: 7 additions & 83 deletions lib/Circle.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React, { useEffect, useImperativeHandle, useMemo } from 'react';
import React from 'react';
import Two from 'two.js';
import { useTwo } from './Context';

import type { Circle as Instance } from 'two.js/src/shapes/circle';
import { PathProps } from './Path';
import { type EventHandlers } from './Properties';
import { EVENT_HANDLER_NAMES } from './Events';
import { useTwoObject } from './useTwoObject';

export type CircleProps = PathProps | 'radius';
type ComponentProps = React.PropsWithChildren<
Expand All @@ -21,85 +19,11 @@ type ComponentProps = React.PropsWithChildren<
export type RefCircle = Instance;

export const Circle = React.forwardRef<Instance, ComponentProps>(
({ x, y, resolution, ...props }, forwardedRef) => {
const { parent, registerEventShape, unregisterEventShape } = useTwo();

// Create the instance synchronously so it's available for refs immediately
const circle = useMemo(
() => new Two.Circle(0, 0, 0, resolution),
[resolution]
);

// Extract event handlers from props
const { eventHandlers, shapeProps } = useMemo(() => {
const eventHandlers: Partial<EventHandlers> = {};
const shapeProps: Record<string, unknown> = {};

for (const key in props) {
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
// An explicitly `undefined` handler means "not interactive", so it
// must not count toward the registered handler set.
const handler = props[key as keyof EventHandlers];
if (handler !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventHandlers[key as keyof EventHandlers] = handler as any;
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shapeProps[key] = (props as any)[key];
}
}

return { eventHandlers, shapeProps };
}, [props]);

useEffect(() => {
// Update position
if (typeof x === 'number') circle.translation.x = x;
if (typeof y === 'number') circle.translation.y = y;

// Update other properties (excluding event handlers)
for (const key in shapeProps) {
if (key in circle) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(circle as any)[key] = (shapeProps as any)[key];
}
}
}, [circle, shapeProps, x, y]);

useEffect(() => {
if (parent) {
parent.add(circle);

return () => {
parent.remove(circle);
};
}
}, [parent, circle]);

// Unregister on unmount only
useEffect(() => {
return () => {
unregisterEventShape(circle);
};
}, [circle, unregisterEventShape]);

// Register / update event handlers
useEffect(() => {
if (Object.keys(eventHandlers).length > 0) {
registerEventShape(circle, eventHandlers, parent ?? undefined);
} else {
unregisterEventShape(circle);
}
}, [
circle,
registerEventShape,
unregisterEventShape,
parent,
eventHandlers,
]);

useImperativeHandle(forwardedRef, () => circle, [circle]);
(props, forwardedRef) => {
useTwoObject(props, forwardedRef, {
factory: (p) => new Two.Circle(0, 0, 0, p.resolution),
constructionProps: ['resolution'],
});

return <></>;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ export interface TwoCoreContextValue {

export interface TwoParentContextValue {
parent: Group | null;
attachChild?: (child: Shape | Group) => void;
detachChild?: (child: Shape | Group) => void;
registerChildOrder?: (child: Shape | Group) => void;
}

export const ChildSlotContext = createContext<number | null>(null);

export interface TwoSizeContextValue {
width: number;
height: number;
Expand Down
92 changes: 8 additions & 84 deletions lib/Ellipse.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import React, { useEffect, useImperativeHandle, useMemo } from 'react';
import React from 'react';
import Two from 'two.js';
import { useTwo } from './Context';

import type { Ellipse as Instance } from 'two.js/src/shapes/ellipse';
import { PathProps } from './Path';
import { type EventHandlers } from './Properties';
import { EVENT_HANDLER_NAMES } from './Events';
import { useTwoObject } from './useTwoObject';

export type EllipseProps = PathProps | 'width' | 'height';
type ComponentProps = React.PropsWithChildren<
Expand All @@ -20,86 +18,12 @@ type ComponentProps = React.PropsWithChildren<

export type RefEllipse = Instance;

export const Ellipse = React.forwardRef<Instance | null, ComponentProps>(
({ x, y, resolution, ...props }, forwardedRef) => {
const { parent, registerEventShape, unregisterEventShape } = useTwo();

// Create the instance synchronously so it's available for refs immediately
const ellipse = useMemo(
() => new Two.Ellipse(0, 0, 0, 0, resolution),
[resolution]
);

// Extract event handlers from props
const { eventHandlers, shapeProps } = useMemo(() => {
const eventHandlers: Partial<EventHandlers> = {};
const shapeProps: Record<string, unknown> = {};

for (const key in props) {
if (EVENT_HANDLER_NAMES.includes(key as keyof EventHandlers)) {
// An explicitly `undefined` handler means "not interactive", so it
// must not count toward the registered handler set.
const handler = props[key as keyof EventHandlers];
if (handler !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventHandlers[key as keyof EventHandlers] = handler as any;
}
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shapeProps[key] = (props as any)[key];
}
}

return { eventHandlers, shapeProps };
}, [props]);

useEffect(() => {
if (parent) {
parent.add(ellipse);

return () => {
parent.remove(ellipse);
};
}
}, [parent, ellipse]);

useEffect(() => {
// Update position
if (typeof x === 'number') ellipse.translation.x = x;
if (typeof y === 'number') ellipse.translation.y = y;

// Update other properties (excluding event handlers)
for (const key in shapeProps) {
if (key in ellipse) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(ellipse as any)[key] = (shapeProps as any)[key];
}
}
}, [ellipse, x, y, shapeProps]);

// Unregister on unmount only
useEffect(() => {
return () => {
unregisterEventShape(ellipse);
};
}, [ellipse, unregisterEventShape]);

// Register / update event handlers
useEffect(() => {
if (Object.keys(eventHandlers).length > 0) {
registerEventShape(ellipse, eventHandlers, parent ?? undefined);
} else {
unregisterEventShape(ellipse);
}
}, [
ellipse,
registerEventShape,
unregisterEventShape,
parent,
eventHandlers,
]);

useImperativeHandle(forwardedRef, () => ellipse, [ellipse]);
export const Ellipse = React.forwardRef<Instance, ComponentProps>(
(props, forwardedRef) => {
useTwoObject(props, forwardedRef, {
factory: (p) => new Two.Ellipse(0, 0, 0, 0, p.resolution),
constructionProps: ['resolution'],
});

return <></>;
}
Expand Down
Loading
Loading