Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/components/Application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import { roots } from '../core/roots';
import { processUnmountQueue } from '../helpers/processUnmountQueue';
import { queueForUnmount } from '../helpers/queueForUnmount';
import { isHTMLElement } from '../helpers/typeChecks';
import { unqueueForUnmount } from '../helpers/unqueueForUnmount';
import { useIsomorphicLayoutEffect } from '../hooks/useIsomorphicLayoutEffect';
import { type ApplicationProps } from '../typedefs/ApplicationProps';
Expand Down Expand Up @@ -68,7 +69,7 @@
{
if ('current' in resizeTo)
{
if (resizeTo.current instanceof HTMLElement)
if (isHTMLElement(resizeTo.current))
{
application.resizeTo = resizeTo.current;
}
Expand All @@ -93,7 +94,7 @@
applicationRef.current = application;
updateResizeTo();
onInit?.(application);
}, [onInit]);

Check warning on line 97 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useCallback has a missing dependency: 'updateResizeTo'. Either include it or remove the dependency array

useIsomorphicLayoutEffect(() =>
{
Expand Down Expand Up @@ -143,7 +144,7 @@
// @ts-expect-error The value of `children` is fine, but `PixiReactChildNode` doesn't strictly adhere to the `ReactNode` structure.
root.render((<Bridge>{children}</Bridge>), applicationProps);
}
}, [

Check warning on line 147 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useIsomorphicLayoutEffect has a missing dependency: 'Bridge'. Either include it or remove the dependency array
applicationProps,
children,
handleInit,
Expand All @@ -153,7 +154,7 @@
useIsomorphicLayoutEffect(() =>
{
updateResizeTo();
}, [resizeTo]);

Check warning on line 157 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useIsomorphicLayoutEffect has a missing dependency: 'updateResizeTo'. Either include it or remove the dependency array

useIsomorphicLayoutEffect(() =>
{
Expand Down
3 changes: 2 additions & 1 deletion src/core/createRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ContextProvider } from '../components/Context';
import { isReadOnlyProperty } from '../helpers/isReadOnlyProperty';
import { log } from '../helpers/log';
import { prepareInstance } from '../helpers/prepareInstance';
import { isHTMLCanvasElement } from '../helpers/typeChecks';
import { type ApplicationState } from '../typedefs/ApplicationState';
import { type CreateRootOptions } from '../typedefs/CreateRootOptions';
import { type HostConfig } from '../typedefs/HostConfig';
Expand Down Expand Up @@ -58,7 +59,7 @@ export function createRoot(
{
let canvas;

if (target instanceof HTMLCanvasElement)
if (isHTMLCanvasElement(target))
{
canvas = target;
}
Expand Down
9 changes: 3 additions & 6 deletions src/helpers/appendChild.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import {
Container,
Filter,
} from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { attach } from './attach';
import { log } from './log';
import { isContainer, isFilter } from './typeChecks';

/** Adds elements to our application. */
export function appendChild(
Expand All @@ -19,11 +16,11 @@ export function appendChild(
return;
}

if (childNode instanceof Container)
if (isContainer(childNode))
{
parentNode.addChild(childNode);
}
else if (childNode instanceof Filter)
else if (isFilter(childNode))
{
attach(parentNode, childNode);
}
Expand Down
9 changes: 3 additions & 6 deletions src/helpers/applyProps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import {
Container,
Graphics,
} from 'pixi.js';
import {
type FederatedPointerEvent,
type FederatedWheelEvent,
Expand All @@ -21,6 +17,7 @@ import { diffProps } from './diffProps';
import { isDiffSet } from './isDiffSet';
import { isReadOnlyProperty } from './isReadOnlyProperty';
import { log } from './log';
import { isContainer, isGraphics } from './typeChecks';

const DEFAULT = '__default';
const DEFAULTS_CONTAINERS = new Map();
Expand Down Expand Up @@ -85,7 +82,7 @@ export function applyProps(

if ((key as string === 'draw') && (typeof value === 'function'))
{
if (instance instanceof Graphics)
if (isGraphics(instance))
{
value(instance);
}
Expand Down Expand Up @@ -135,7 +132,7 @@ export function applyProps(
// For removed props, try to set default values, if possible
if (value === `${DEFAULT}remove`)
{
if (currentInstance instanceof Container)
if (isContainer(currentInstance))
{
// create a blank slate of the instance and copy the particular parameter.
let ctor = DEFAULTS_CONTAINERS.get(currentInstance.constructor);
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/attach.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Filter } from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { isFilter } from './typeChecks';

export function attach(
parentInstance: HostConfig['containerInstance'],
childInstance: HostConfig['instance'],
targetIndex?: number
)
{
if (childInstance instanceof Filter)
if (isFilter(childInstance))
{
(childInstance as unknown as HostConfig['filterInstance']).__pixireact.parent = parentInstance;

Expand Down
4 changes: 2 additions & 2 deletions src/helpers/detach.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Filter } from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { isFilter } from './typeChecks';

export function detach(
childInstance: HostConfig['instance'],
)
{
if (childInstance instanceof Filter)
if (isFilter(childInstance))
{
const parentInstance = childInstance.__pixireact.parent as HostConfig['instance'];

Expand Down
9 changes: 3 additions & 6 deletions src/helpers/hideInstance.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import {
Container,
Filter,
} from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { isContainer, isFilter } from './typeChecks';

export function hideInstance(
instance: HostConfig['instance']
)
{
if (instance instanceof Container)
if (isContainer(instance))
{
instance.visible = false;
}
else if (instance instanceof Filter)
else if (isFilter(instance))
{
instance.enabled = false;
}
Expand Down
10 changes: 4 additions & 6 deletions src/helpers/insertBefore.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {
Container,
Filter,
} from 'pixi.js';
import { type Container, type Filter } from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { attach } from './attach';
import { detach } from './detach';
import { invariant } from './invariant';
import { log } from './log';
import { isContainer, isFilter } from './typeChecks';

export function insertBefore(
parentInstance: HostConfig['containerInstance'],
Expand All @@ -18,7 +16,7 @@ export function insertBefore(

invariant(childInstance !== beforeChildInstance, 'Cannot insert node before itself');

if (childInstance instanceof Container)
if (isContainer(childInstance))
{
const childContainerInstance = childInstance as HostConfig['containerInstance'];
const childContainer = childInstance as unknown as Container;
Expand All @@ -33,7 +31,7 @@ export function insertBefore(

parentInstance.addChildAt(childContainer, index);
}
else if (childInstance instanceof Filter)
else if (isFilter(childInstance))
{
const childFilterInstance = childInstance;
const instanceState = childFilterInstance.__pixireact;
Expand Down
3 changes: 2 additions & 1 deletion src/helpers/log.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { store } from '../store';
import { isFunction } from './typeChecks';

export type LogType = 'error' | 'info' | 'log' | 'warn';

Expand All @@ -12,7 +13,7 @@ export function log(logType: LogType, ...args: any[])
// eslint-disable-next-line no-console
const logMethod = console[logType];

if (!(logMethod instanceof Function))
if (!isFunction(logMethod))
{
console.warn(`Attempted to create an invalid log type: "${logType}"`);

Expand Down
4 changes: 2 additions & 2 deletions src/helpers/removeChild.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Filter } from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { detach } from './detach';
import { log } from './log';
import { isFilter } from './typeChecks';

/** Removes elements from our scene and disposes of them. */
export function removeChild(
Expand All @@ -11,7 +11,7 @@ export function removeChild(
{
log('info', 'lifecycle::removeChild');

if (childInstance instanceof Filter)
if (isFilter(childInstance))
{
detach(childInstance);
}
Expand Down
135 changes: 135 additions & 0 deletions src/helpers/typeChecks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Application, Container, Filter, Graphics } from 'pixi.js';

/**
* Cross-realm compatible type checking utilities.
* These functions work across different JavaScript realms/contexts
* where instanceof checks may fail.
*/

/**
* Checks if an object is a PixiJS Container across realms.
* Uses constructor name and key properties as fallback to instanceof.
*/
export function isContainer(obj: any): obj is Container
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (obj instanceof Container) return true;

// Fallback: check constructor name and key properties
const constructorName = obj.constructor?.name;

return constructorName === 'Container'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return constructorName === 'Container'
const validConstructors = new Set([
'Container',
'Sprite',
'Graphics',
'Text',
'BitmapText',
'SimpleMesh',
'NineSlicePlane',
]);
const constructorName = obj?.constructor?.name;
return (
validConstructors.has(constructorName) ||
(
typeof obj?.addChild === 'function' &&
typeof obj?.removeChild === 'function' &&
typeof obj?.getChildIndex === 'function' &&
'children' in obj
)
);

|| constructorName === 'Sprite'
|| constructorName === 'Graphics'
|| constructorName === 'Text'
|| constructorName === 'BitmapText'
|| constructorName === 'SimpleMesh'
|| constructorName === 'NineSlicePlane'
|| (typeof obj.addChild === 'function'
&& typeof obj.removeChild === 'function'
&& typeof obj.getChildIndex === 'function'
&& 'children' in obj);
}

/**
* Checks if an object is a PixiJS Graphics across realms.
*/
export function isGraphics(obj: any): obj is Graphics
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (obj instanceof Graphics) return true;

// Fallback: check constructor name and key methods
const constructorName = obj.constructor?.name;

return constructorName === 'Graphics'
|| (typeof obj.clear === 'function'
&& typeof obj.rect === 'function'
&& typeof obj.fill === 'function');
}

/**
* Checks if an object is a PixiJS Filter across realms.
*/
export function isFilter(obj: any): obj is Filter
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (obj instanceof Filter) return true;

// Fallback: check constructor name and key properties
const constructorName = obj.constructor?.name;

return constructorName === 'Filter'
|| constructorName?.endsWith('Filter')
|| ('enabled' in obj
&& 'resolution' in obj
&& typeof obj.apply === 'function');
}

/**
* Checks if an object is a PixiJS Application across realms.
*/
export function isApplication(obj: any): obj is Application
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (obj instanceof Application) return true;

// Fallback: check constructor name and key properties
const constructorName = obj.constructor?.name;

return constructorName === 'Application'
|| ('stage' in obj
&& 'renderer' in obj
&& 'ticker' in obj
&& typeof obj.init === 'function');
}

/**
* Checks if an object is an HTMLCanvasElement across realms.
*/
export function isHTMLCanvasElement(obj: any): obj is HTMLCanvasElement
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (typeof HTMLCanvasElement !== 'undefined' && obj instanceof HTMLCanvasElement) return true;

// Fallback: check node properties and methods
return isHTMLElement(obj)
&& obj.nodeName === 'CANVAS'
&& typeof (obj as HTMLCanvasElement).getContext === 'function'
&& typeof (obj as HTMLCanvasElement).toDataURL === 'function';
}

/**
* Checks if an object is an HTMLElement across realms.
*/
export function isHTMLElement(obj: any): obj is HTMLElement
{
if (!obj || typeof obj !== 'object') return false;

// Primary check: instanceof (works in same realm)
if (typeof HTMLElement !== 'undefined' && obj instanceof HTMLElement) return true;

// Fallback: check node properties
return obj.nodeType === Node.ELEMENT_NODE || (
obj.nodeType === Node.TEXT_NODE
&& obj.parentElement?.nodeType === Node.ELEMENT_NODE
);
}

/**
* Checks if an object is a Function across realms.
*/
export function isFunction(obj: any): obj is (...args: any[]) => any
{
return typeof obj === 'function';
}
9 changes: 3 additions & 6 deletions src/helpers/unhideInstance.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import {
Container,
Filter,
} from 'pixi.js';
import { type HostConfig } from '../typedefs/HostConfig';
import { isContainer, isFilter } from './typeChecks';

export function unhideInstance(
instance: HostConfig['instance'],
)
{
if (instance instanceof Container)
if (isContainer(instance))
{
instance.visible = true;
}
else if (instance instanceof Filter)
else if (isFilter(instance))
{
instance.enabled = true;
}
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useApplication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Application } from 'pixi.js';
import { useContext } from 'react';
import { Context } from '../components/Context';
import { invariant } from '../helpers/invariant';
import { isApplication } from '../helpers/typeChecks';

/**
* @description Retrieves the nearest Pixi.js Application from the Pixi React context.
Expand All @@ -11,7 +11,7 @@ export function useApplication()
const appContext = useContext(Context);

invariant(
appContext.app instanceof Application,
isApplication(appContext.app),
'No Context found with `%s`. Make sure to wrap component with `%s`',
'Application',
'AppProvider'
Expand Down
Loading