@pixi/react
Advanced tools
Comparing version 8.0.0-dev.0aaf87a to 8.0.0-dev.2cf4a08
@@ -1,18 +0,15 @@ | ||
/** @typedef {import('pixi.js').Container} Container */ | ||
/** @typedef {import('../types/HostContainer.js').HostContainer} HostContainer */ | ||
'use strict'; | ||
/** | ||
* Adds elements to our scene and attaches geometry and material to meshes. | ||
* | ||
* @param {HostContainer & Container} parentInstance | ||
* @param {HostContainer & Container} child | ||
*/ | ||
export function appendChild(parentInstance, child) | ||
{ | ||
if (!child) | ||
{ | ||
return; | ||
} | ||
var log = require('./log.js'); | ||
parentInstance.addChild(child); | ||
"use strict"; | ||
function appendChild(parentInstance, childInstance) { | ||
log.log("info", "lifecycle::appendChild"); | ||
if (!childInstance) { | ||
return; | ||
} | ||
parentInstance.addChild(childInstance); | ||
} | ||
exports.appendChild = appendChild; | ||
//# sourceMappingURL=appendChild.js.map |
@@ -1,85 +0,112 @@ | ||
import { Graphics } from 'pixi.js'; | ||
import { pruneKeys } from './pruneKeys.js'; | ||
'use strict'; | ||
/** | ||
* Apply properties to Pixi.js instance. | ||
* | ||
* @param {{ [key: string]: any }} instance An instance? | ||
* @param {{ [key: string]: any }} newProps New props. | ||
* @param {{ [key: string]: any }} [oldProps] Old props. | ||
*/ | ||
export function applyProps(instance, newProps, oldProps = {}) | ||
{ | ||
// Filter identical props, event handlers, and reserved keys | ||
const identicalProps = Object | ||
.keys(newProps) | ||
.filter((key) => newProps[key] === oldProps[key]); | ||
var pixi_js = require('pixi.js'); | ||
var EventPropNames = require('../constants/EventPropNames.js'); | ||
var diffProps = require('./diffProps.js'); | ||
var isDiffSet = require('./isDiffSet.js'); | ||
var isReadOnlyProperty = require('./isReadOnlyProperty.js'); | ||
var log = require('./log.js'); | ||
if ((instance instanceof Graphics) && !identicalProps.includes('draw')) | ||
{ | ||
newProps.draw?.(instance); | ||
"use strict"; | ||
const DEFAULT = "__default"; | ||
const DEFAULTS_CONTAINERS = /* @__PURE__ */ new Map(); | ||
const PIXI_EVENT_PROP_NAME_ERROR_HAS_BEEN_SHOWN = {}; | ||
function applyProps(instance, data) { | ||
const localState = instance.__pixireact; | ||
const { | ||
__pixireact, | ||
...instanceProps | ||
} = instance; | ||
let typedData; | ||
if (isDiffSet.isDiffSet(data)) { | ||
typedData = /** @type {DiffSet} */ | ||
data; | ||
} else { | ||
typedData = diffProps.diffProps( | ||
/** @type {InstanceProps} */ | ||
data, | ||
instanceProps | ||
); | ||
} | ||
const { changes } = typedData; | ||
let changeIndex = 0; | ||
while (changeIndex < changes.length) { | ||
const change = changes[changeIndex]; | ||
let hasError = false; | ||
let key = ( | ||
/** @type {keyof Instance} */ | ||
change[0] | ||
); | ||
let value = change[1]; | ||
const isEvent = change[2]; | ||
const keys = ( | ||
/** @type {*} */ | ||
change[3] | ||
); | ||
let currentInstance = instance; | ||
let targetProp = currentInstance[key]; | ||
if (key === "draw" && typeof value === "function") { | ||
if (instance instanceof pixi_js.Graphics) { | ||
value(instance); | ||
} else { | ||
hasError = true; | ||
log.log("warn", `The \`draw\` prop was used on a \`${instance.type}\` component, but it's only valid on \`graphics\` components.`); | ||
} | ||
} | ||
const handlers = Object.keys(newProps).filter((key) => | ||
{ | ||
const isFunction = typeof newProps[key] === 'function'; | ||
return isFunction && key.startsWith('on'); | ||
}); | ||
const props = pruneKeys(newProps, [ | ||
...identicalProps, | ||
...handlers, | ||
'children', | ||
'draw', | ||
'key', | ||
'ref', | ||
]); | ||
// Mutate our Pixi.js element | ||
if (Object.keys(props).length) | ||
{ | ||
Object.entries(props).forEach(([key, value]) => | ||
{ | ||
// const target = instance[key] | ||
// const isColor = target instanceof THREE.Color | ||
// // Prefer to use properties' copy and set methods | ||
// // otherwise, mutate the property directly | ||
// if (target?.set) { | ||
// if (target.constructor.name === value.constructor.name) { | ||
// target.copy(value) | ||
// } else if (Array.isArray(value)) { | ||
// target.set(...value) | ||
// } else if (!isColor && target?.setScalar) { | ||
// // Allow shorthand like scale={1} | ||
// target.setScalar(value) | ||
// } else { | ||
// target.set(value) | ||
// } | ||
// // Auto-convert sRGB colors | ||
// if (isColor) { | ||
// target.convertSRGBToLinear() | ||
// } | ||
// } else { | ||
// instance[key] = value | ||
// } | ||
instance[key] = value; | ||
}); | ||
if (key in EventPropNames.PixiToReactEventPropNames) { | ||
const typedKey = ( | ||
/** @type {keyof PixiToReactEventPropNames} */ | ||
key | ||
); | ||
hasError = true; | ||
if (!PIXI_EVENT_PROP_NAME_ERROR_HAS_BEEN_SHOWN[key]) { | ||
PIXI_EVENT_PROP_NAME_ERROR_HAS_BEEN_SHOWN[key] = true; | ||
log.log("warn", `Event names must be pascal case; instead of \`${key}\`, you probably want \`${EventPropNames.PixiToReactEventPropNames[typedKey]}\`.`); | ||
} | ||
} | ||
// Collect event handlers. | ||
// We put this on an invalid prop so Pixi.js doesn't serialize handlers | ||
// if you do ref.current.clone() or ref.current.toJSON() | ||
if (handlers.length) | ||
{ | ||
instance.__handlers = handlers.reduce( | ||
(acc, key) => ({ | ||
...acc, | ||
[key]: newProps[key], | ||
}), | ||
{}, | ||
if (!hasError) { | ||
if (keys.length) { | ||
targetProp = keys.reduce((accumulator, key2) => accumulator[key2], currentInstance); | ||
if (!(targetProp && targetProp.set)) { | ||
const [name, ...reverseEntries] = keys.reverse(); | ||
currentInstance = reverseEntries.reverse().reduce((accumulator, key2) => accumulator[key2], currentInstance); | ||
key = name; | ||
} | ||
} | ||
if (value === `${DEFAULT}remove`) { | ||
if (currentInstance instanceof pixi_js.Container) { | ||
let ctor = DEFAULTS_CONTAINERS.get(currentInstance.constructor); | ||
if (!ctor) { | ||
ctor = /** @type {*} */ | ||
currentInstance.constructor; | ||
ctor = new ctor(); | ||
DEFAULTS_CONTAINERS.set(currentInstance.constructor, ctor); | ||
} | ||
value = ctor[key]; | ||
} else { | ||
value = 0; | ||
} | ||
} | ||
if (isEvent && localState) { | ||
const typedKey = ( | ||
/** @type {keyof ReactToPixiEventPropNames} */ | ||
key | ||
); | ||
const pixiKey = EventPropNames.ReactToPixiEventPropNames[typedKey]; | ||
if (value) { | ||
currentInstance[pixiKey] = /** @type {(event: FederatedPointerEvent | FederatedWheelEvent) => void} */ | ||
value; | ||
} else { | ||
delete currentInstance[pixiKey]; | ||
} | ||
} else if (!isReadOnlyProperty.isReadOnlyProperty(currentInstance, key)) { | ||
currentInstance[key] = value; | ||
} | ||
} | ||
changeIndex += 1; | ||
} | ||
return instance; | ||
} | ||
exports.applyProps = applyProps; | ||
//# sourceMappingURL=applyProps.js.map |
@@ -1,14 +0,14 @@ | ||
/** | ||
* Converts a string to PascalCase. | ||
* | ||
* @template {string} S | ||
* @param {S} string The string to be converted. | ||
* @returns {S} The converted string. | ||
*/ | ||
export function convertStringToPascalCase(string) | ||
{ | ||
const firstChar = string.charAt(0); | ||
const rest = string.substring(1); | ||
'use strict'; | ||
return /** @type {S} */ (`${firstChar.toUpperCase()}${rest}`); | ||
"use strict"; | ||
function convertStringToPascalCase(string) { | ||
const firstChar = string.charAt(0); | ||
const rest = string.substring(1); | ||
return ( | ||
/** @type {Capitalize<S>} */ | ||
`${firstChar.toUpperCase()}${rest}` | ||
); | ||
} | ||
exports.convertStringToPascalCase = convertStringToPascalCase; | ||
//# sourceMappingURL=convertStringToPascalCase.js.map |
@@ -1,38 +0,40 @@ | ||
import * as PIXI from 'pixi.js'; | ||
import { applyProps } from './applyProps.js'; | ||
import { convertStringToPascalCase } from './convertStringToPascalCase.js'; | ||
'use strict'; | ||
/** @typedef {import('../types/PixiElements.js').PixiElements} PixiElements */ | ||
var PixiReactIgnoredProps = require('../constants/PixiReactIgnoredProps.js'); | ||
var applyProps = require('./applyProps.js'); | ||
var catalogue = require('./catalogue.js'); | ||
var convertStringToPascalCase = require('./convertStringToPascalCase.js'); | ||
var gentleCloneProps = require('./gentleCloneProps.js'); | ||
var log = require('./log.js'); | ||
var prepareInstance = require('./prepareInstance.js'); | ||
/** | ||
* @param {keyof PixiElements} type | ||
* @param {Record<string, unknown>} props | ||
* @returns | ||
*/ | ||
export function createInstance(type, props) | ||
{ | ||
const { args } = props; | ||
"use strict"; | ||
function createInstance(type, props, root) { | ||
log.log("info", "lifecycle::createInstance"); | ||
const parsedType = type.startsWith("pixi") ? type.replace(/^pixi([A-Z])/, (_fullMatch, firstCharacter) => firstCharacter.toLowerCase()) : type; | ||
const name = convertStringToPascalCase.convertStringToPascalCase(parsedType); | ||
const PixiComponent = ( | ||
/** @type {new (...args: any[]) => any} */ | ||
catalogue.catalogue[name] | ||
); | ||
if (!PixiComponent) { | ||
throw new Error(`${name} is not part of the PIXI namespace! Did you forget to extend?`); | ||
} | ||
const pixiProps = gentleCloneProps.gentleCloneProps(props, PixiReactIgnoredProps.PixiReactIgnoredProps); | ||
let component; | ||
if (name === "Application") { | ||
component = new PixiComponent(); | ||
component.init(pixiProps); | ||
} else { | ||
component = new PixiComponent(pixiProps); | ||
} | ||
const instance = prepareInstance.prepareInstance(component, { | ||
root, | ||
type: parsedType | ||
}); | ||
applyProps.applyProps(instance, props); | ||
return instance; | ||
} | ||
// Convert lowercase primitive to PascalCase | ||
const name = convertStringToPascalCase(type); | ||
// Get class from Pixi.js namespace | ||
const TARGET = /** @type {new (...args: any[]) => any} */ (PIXI[name]); | ||
let instance; | ||
// Create instance | ||
if (Array.isArray(args)) | ||
{ | ||
instance = new TARGET(...args); | ||
} | ||
else | ||
{ | ||
instance = new TARGET(args); | ||
} | ||
// Set initial props | ||
applyProps(instance, props); | ||
return instance; | ||
} | ||
exports.createInstance = createInstance; | ||
//# sourceMappingURL=createInstance.js.map |
@@ -1,4 +0,12 @@ | ||
export function createTextInstance() | ||
{ | ||
console.warn('Text is not currently supported. Please use a `<text>` component.'); | ||
'use strict'; | ||
var log = require('./log.js'); | ||
"use strict"; | ||
function createTextInstance(_text, _rootContainer, _hostContext, _internalHandle) { | ||
log.log("info", "lifecycle::createTextInstance"); | ||
throw new Error("Text instances are not yet supported. Please use a `<text>` component."); | ||
} | ||
exports.createTextInstance = createTextInstance; | ||
//# sourceMappingURL=createTextInstance.js.map |
@@ -1,21 +0,12 @@ | ||
/** @typedef {import('pixi.js').Container} Container */ | ||
/** @typedef {import('../types/HostContainer.js').HostContainer} HostContainer */ | ||
'use strict'; | ||
/** | ||
* Removes elements from our scene and disposes of them. | ||
* | ||
* @param {HostContainer & Container} _container Unused. | ||
* @param {HostContainer & Container} child The child to be removed. | ||
*/ | ||
export function removeChild(_container, child) | ||
{ | ||
if (!child) | ||
{ | ||
return; | ||
} | ||
var log = require('./log.js'); | ||
if (child.destroy) | ||
{ | ||
child.destroy(); | ||
} | ||
"use strict"; | ||
function removeChild(_parentInstance, childInstance) { | ||
log.log("info", "lifecycle::removeChild"); | ||
childInstance.destroy(); | ||
} | ||
exports.removeChild = removeChild; | ||
//# sourceMappingURL=removeChild.js.map |
@@ -1,56 +0,42 @@ | ||
import { Assets } from 'pixi.js'; | ||
import { | ||
useEffect, | ||
useState, | ||
} from 'react'; | ||
'use strict'; | ||
/** | ||
* @typedef {import('pixi.js').ProgressCallback} ProgressCallback | ||
* @typedef {import('pixi.js').Texture} Texture | ||
* @typedef {import('pixi.js').UnresolvedAsset} UnresolvedAsset | ||
*/ | ||
var pixi_js = require('pixi.js'); | ||
var getAssetKeyFromOptions = require('../helpers/getAssetKeyFromOptions.js'); | ||
/** | ||
* Loads assets, returning a hash of assets once they're loaded. | ||
* | ||
* @param {UnresolvedAsset} options Asset options. | ||
* @param {ProgressCallback} [onProgress] A function to be called when the asset loader reports loading progress. | ||
* @returns {null | Texture} A hash of textures, keyed by their URLs. This will be `null` until the assets are loaded. | ||
*/ | ||
export function useAsset(options, onProgress) | ||
{ | ||
const [isLoaded, setIsLoaded] = useState(false); | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [texture, setTexture] = useState(null); | ||
"use strict"; | ||
const errorCache = /* @__PURE__ */ new Map(); | ||
function useAsset(options, onProgress) { | ||
if (typeof window === "undefined") { | ||
throw Object.assign(Error("`useAsset` will only run on the client."), { | ||
digest: "BAILOUT_TO_CLIENT_SIDE_RENDERING" | ||
}); | ||
} | ||
const { | ||
maxRetries = 3, | ||
retryOnFailure = true | ||
} = typeof options !== "string" ? options : {}; | ||
const assetKey = getAssetKeyFromOptions.getAssetKeyFromOptions(options); | ||
if (!pixi_js.Cache.has(assetKey)) { | ||
let state = errorCache.get(options); | ||
if (state && (!retryOnFailure || state.retries > maxRetries)) { | ||
throw state.error; | ||
} | ||
throw pixi_js.Assets.load(options, onProgress).catch((error) => { | ||
if (!state) { | ||
state = { | ||
error, | ||
retries: 0 | ||
}; | ||
} | ||
errorCache.set(options, { | ||
...state, | ||
error, | ||
retries: state.retries + 1 | ||
}); | ||
}); | ||
} | ||
return pixi_js.Assets.get(assetKey); | ||
} | ||
useEffect(() => | ||
{ | ||
setIsLoaded(false); | ||
}, [options]); | ||
useEffect(() => | ||
{ | ||
if (!isLoaded && !isLoading) | ||
{ | ||
setIsLoading(true); | ||
const assetKey = /** @type {string} */ (options.alias ?? options.src); | ||
Assets.add(options); | ||
Assets | ||
.load(assetKey, onProgress) | ||
.then((texture) => | ||
{ | ||
setTexture(texture); | ||
setIsLoaded(true); | ||
setIsLoading(false); | ||
}) | ||
.catch(() => setIsLoading(false)); | ||
} | ||
}, [ | ||
isLoaded, | ||
isLoading, | ||
]); | ||
return texture; | ||
} | ||
exports.useAsset = useAsset; | ||
//# sourceMappingURL=useAsset.js.map |
@@ -1,2 +0,21 @@ | ||
export { useAsset } from './hooks/useAsset.js'; | ||
export { render } from './render.js'; | ||
'use strict'; | ||
var Application = require('./components/Application.js'); | ||
var createRoot = require('./core/createRoot.js'); | ||
var extend = require('./helpers/extend.js'); | ||
var useApp = require('./hooks/useApp.js'); | ||
var useAsset = require('./hooks/useAsset.js'); | ||
var useExtend = require('./hooks/useExtend.js'); | ||
var useTick = require('./hooks/useTick.js'); | ||
require('./global.js'); | ||
"use strict"; | ||
exports.Application = Application.Application; | ||
exports.createRoot = createRoot.createRoot; | ||
exports.extend = extend.extend; | ||
exports.useApp = useApp.useApp; | ||
exports.useAsset = useAsset.useAsset; | ||
exports.useExtend = useExtend.useExtend; | ||
exports.useTick = useTick.useTick; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@pixi/react", | ||
"version": "8.0.0-dev.0aaf87a", | ||
"version": "8.0.0-dev.2cf4a08", | ||
"description": "Write PixiJS applications using React declarative style.", | ||
@@ -32,9 +32,10 @@ "keywords": [ | ||
"module": "lib/index.mjs", | ||
"types": "lib/index.d.ts", | ||
"types": "types/index.d.ts", | ||
"files": [ | ||
"lib/", | ||
"types/" | ||
"lib", | ||
"dist", | ||
"types" | ||
], | ||
"scripts": { | ||
"build": "tsc", | ||
"build": "rimraf dist lib types && npm run lint:fix && rollup -c && tsc", | ||
"clean": "xs clean", | ||
@@ -62,13 +63,19 @@ "docs": "xs docs", | ||
"dependencies": { | ||
"react-reconciler": "^0.29.2", | ||
"statery": "^0.7.1" | ||
"react-reconciler": "^0.29.2" | ||
}, | ||
"devDependencies": { | ||
"@pixi/extension-scripts": "^2.4.1", | ||
"@rollup/plugin-commonjs": "^25.0.8", | ||
"@rollup/plugin-json": "^6.1.0", | ||
"@rollup/plugin-node-resolve": "^15.2.3", | ||
"@types/eslint": "^8.56.10", | ||
"@types/react": "^18.3.2", | ||
"@types/react-reconciler": "^0.28.8", | ||
"husky": "^8.0.0", | ||
"pixi.js": "v8.1.5-dev.1ebdfc5", | ||
"pixi.js": "8.2.1", | ||
"react": "^18.3.1", | ||
"react-dom": "^18.3.1", | ||
"rollup": "^4.18.0", | ||
"rollup-plugin-esbuild": "^6.1.1", | ||
"rollup-plugin-sourcemaps": "^0.6.3", | ||
"typescript": "^5.4.5", | ||
@@ -78,3 +85,3 @@ "vitest": "^1.6.0" | ||
"peerDependencies": { | ||
"pixi.js": "^8.0.0", | ||
"pixi.js": "^8.2.1", | ||
"react": ">=18.0.0", | ||
@@ -81,0 +88,0 @@ "react-dom": ">=18.0.0" |
346
README.md
@@ -22,3 +22,3 @@ <p align="center"> | ||
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="license" /> | ||
<img src="https://img.shields.io/badge/react-latest-ff69b4.svg" alt="react version" /> | ||
<img src="https://img.shields.io/badge/react-v18.0.0-ff69b4.svg" alt="react version" /> | ||
</p> | ||
@@ -29,1 +29,345 @@ | ||
Pixi React is an open-source, production-ready library to render high performant PixiJS applications in React. | ||
## Features | ||
- React v17 and v18 support | ||
- PixiJS v8 support | ||
## Getting Started | ||
### Quick Start | ||
If you want to start a new React project from scratch then we recommend [Create React App](https://github.com/facebook/create-react-app), but Pixi React should work with any React application (Remix, Next.js, etc). | ||
To add to an existing React application, just install the dependencies: | ||
#### Install Pixi React Dependencies | ||
```bash | ||
npm install pixi.js@^8.2.1 @pixi/react | ||
``` | ||
#### Pixie React Usage | ||
```jsx | ||
import { | ||
Application, | ||
extend, | ||
} from '@pixi/react' | ||
import { | ||
Container, | ||
Graphics, | ||
} from 'pixi.js' | ||
import { useCallback } from 'react' | ||
extend({ | ||
Container, | ||
Graphics, | ||
}) | ||
const MyComponent = () => { | ||
const drawCallback = useCallback(graphics => { | ||
graphics.clear() | ||
graphics.setFillStyle({ color: 'red' }) | ||
graphics.rect(0, 0, 100, 100) | ||
graphics.fill() | ||
}, []) | ||
return ( | ||
<Application> | ||
<container x={100} y={100}> | ||
<graphics draw={drawCallback} /> | ||
</container> | ||
</Application> | ||
) | ||
} | ||
``` | ||
## Docs | ||
### `extend` | ||
One of the most important concepts to understand with Pixi React v7 is `extend`. Normally, Pixi React would have to import all pf Pixi.js to be able to provide the full library as JSX components. Instead, we use an internal catalogue of components populated by the `extend` API. This allows you to define exactly which parts of Pixi.js you want to import, keeping your bundle sizes small. | ||
To allow Pixi React to use a Pixi.js component, pass it to the `extend` API: | ||
```jsx | ||
import { Container } from 'pixi.js' | ||
import { extend } from '@pixi/react' | ||
extend({ Container }) | ||
const MyComponent = () => ( | ||
<container /> | ||
) | ||
``` | ||
> [!CAUTION] | ||
> Attempting to use components that haven't been passed to the `extend` API **will result in errors**. | ||
### Components | ||
#### `<Application>` | ||
The `<Application>` component is used to wrap your Pixi React app. The `<Application>` component can take [all props that can be set](https://pixijs.download/release/docs/app.ApplicationOptions.html) on [`PIXI.Application`](https://pixijs.download/release/docs/app.Application.html). | ||
##### Example Usage | ||
```jsx | ||
import { Application } from '@pixi/react' | ||
const MyComponent = () => { | ||
return ( | ||
<Application | ||
autoStart | ||
sharedTicker /> | ||
) | ||
} | ||
``` | ||
###### `defaultTextStyle` | ||
`defaultTextStyle` is a convenience property. Whatever is passed will automatically be assigned to Pixi.js's[`TextStyle.defaultTextStyle`](https://pixijs.download/release/docs/text.TextStyle.html#defaultTextStyle). | ||
> [!NOTE] | ||
> This property **is not retroactive**. It will only apply to text components created after `defaultTextStyle` is set. Any text components created before setting `defaultTextStyle` will retain the base styles they had before `defaultTextStyle` was changed. | ||
###### `resizeTo` | ||
The `<Application>` component supports the `resizeTo` property, with some additional functionality: it can accept any HTML element **or** it can take a React `ref` directly. | ||
```jsx | ||
import { Application } from '@pixi/react' | ||
import { useRef } from 'react' | ||
const MyComponent = () => { | ||
const parentRef = useRef(null) | ||
return ( | ||
<div ref={parentRef}> | ||
<Application resizeTo={parentRef} /> | ||
</div> | ||
) | ||
} | ||
``` | ||
#### Pixi Components | ||
All other Pixi React components should be included in your IDE's intellisense/autocomplete once you've installed/imported `@pixi/react`. If it's exported from Pixi.js, it's supported as a component in Pixi React. The only difference is that Pixi React components will always start with lowercase characters. Here's a selection of commonly used components: | ||
```jsx | ||
<container /> | ||
<graphics /> | ||
<sprite /> | ||
<animatedSprite /> | ||
<text /> | ||
<htmlText /> | ||
``` | ||
##### `<graphics>` | ||
The `graphics` component has a special `draw` property. `draw` takes a callback which receives the `Graphics` context, allowing drawing to happen on every tick. | ||
```jsx | ||
const MyComponent = () => { | ||
return ( | ||
<graphics draw={graphics => { | ||
graphics.clear() | ||
graphics.setFillStyle({ color: 'red' }) | ||
graphics.rect(0, 0, 100, 100) | ||
graphics.fill() | ||
}} /> | ||
) | ||
} | ||
``` | ||
> [!IMPORTANT] | ||
> You may run into some components that conflict with others. For example, the `<text>` component conflicts with the `<text>` component that's built-in to React for use in SVGs. To address this issue, all components are available with the `pixi` prefix. For example, you can replace the `<text>` component with the `<pixiText>` component. It will have the same functionality with none of the collisions. | ||
#### Custom Components | ||
Pixi React supports custom components via the `extend` API. For example, you can create a `<viewport>` component using the [`pixi-viewport`](https://github.com/davidfig/pixi-viewport) library: | ||
```jsx | ||
import { extend } from '@pixi/react' | ||
import { Viewport } from 'pixi-viewport' | ||
extend({ viewport }) | ||
const MyComponent = () => { | ||
<viewport> | ||
<container /> | ||
</viewport> | ||
} | ||
``` | ||
##### For Typescript Users | ||
If you're using Typescript, this new `<viewport>` component will throw type errors. Pixi React exports a `PixiReactNode` type that can be used to solve this. You'll need to pass the `Viewport` into `PixiReactNode` and inject it into JSX: | ||
```ts | ||
import type { PixiReactNode } from '@pixi/react' | ||
import type { Viewport } from 'pixi-viewport' | ||
declare global { | ||
namespace JSX { | ||
interface IntrinsicElements { | ||
viewport: PixiReactNode<typeof Viewport>; | ||
} | ||
} | ||
} | ||
``` | ||
### Hooks | ||
#### `useApp` | ||
`useApp` allows access to the parent `PIXI.Application` created by the `<Application>` component. This hook _will not work_ outside of an `<Application>` component. Additionally, the parent application is passed via [React Context](https://react.dev/reference/react/useContext). This means `useApp` will only work appropriately in _child components_, and not directly in the component that contains the `<Application>` component. | ||
For example, the following example `useApp` **will not** be able to access the parent application: | ||
```jsx | ||
import { | ||
Application, | ||
useApp, | ||
} from '@pixi/react' | ||
const ParentComponent = () => { | ||
// This will cause an invariant violation. | ||
const app = useApp() | ||
return ( | ||
<Application /> | ||
) | ||
} | ||
``` | ||
Here's a working example where `useApp` **will** be able to access the parent application: | ||
```jsx | ||
import { | ||
Application, | ||
useApp, | ||
} from '@pixi/react' | ||
const ChildComponent = () => { | ||
const app = useApp() | ||
console.log(app) | ||
return ( | ||
<container /> | ||
) | ||
} | ||
const ParentComponent = () => ( | ||
<Application> | ||
<ChildComponent /> | ||
</Application> | ||
) | ||
``` | ||
#### `useAsset` | ||
The `useAsset` hook wraps the functionality of [Pixi's Asset loader](https://pixijs.download/release/docs/assets.Assets.html) and cache into a convenient React hook. The hook can accept either an [`UnresolvedAsset`](https://pixijs.download/release/docs/assets.html#UnresolvedAsset) or a url. | ||
```jsx | ||
import { useAsset } from '@pixi/react' | ||
const MyComponent = () => { | ||
const bunnyTexture = useAsset('https://pixijs.com/assets/bunny.png') | ||
const bunnyTexture2 = useAsset({ | ||
alias: 'bunny', | ||
src: 'https://pixijs.com/assets/bunny.png', | ||
}) | ||
return ( | ||
<container> | ||
<sprite texture={bunnyTexture}> | ||
<sprite texture={bunnyTexture2}> | ||
</container> | ||
) | ||
} | ||
``` | ||
##### Tracking Progress | ||
`useAsset` can optionally accept a [`ProgressCallback`](https://pixijs.download/release/docs/assets.html#ProgressCallback) as a second argument. This callback will be called by the asset loader as the asset is loaded. | ||
```jsx | ||
const bunnyTexture = useAsset('https://pixijs.com/assets/bunny.png', progress => { | ||
console.log(`We have achieved ${progress * 100}% bunny.`) | ||
}) | ||
``` | ||
> [!TIP] | ||
> The `useAsset` hook also supports [React Suspense](https://react.dev/reference/react/Suspense)! If given a suspense boundary, it's possible to prevent components from rendering until they've finished loading their assets: | ||
> ```jsx | ||
> import { | ||
> Application, | ||
> useAsset, | ||
> } from '@pixi/react' | ||
> | ||
> import { Suspense } from 'react'; | ||
> | ||
> const BunnySprite = () => { | ||
> const bunnyTexture = useAsset('https://pixijs.com/assets/bunny.png') | ||
> | ||
> return ( | ||
> <sprite texture={bunnyTexture} /> | ||
> ) | ||
> } | ||
> | ||
> const LoadingText = () => ( | ||
> <pixiText text={'Loading...'} /> | ||
> ) | ||
> | ||
> const MyApp = () => ( | ||
> <Application> | ||
> <Suspense fallback={<LoadingText />}> | ||
> <BunnySprite /> | ||
> </Suspense> | ||
> </Application> | ||
> ) | ||
> ``` | ||
#### `useExtend` | ||
`useExtend` allows the `extend` API to be used as a React hook. Additionally, the `useExtend` hook is memoised, while the `extend` function is not. | ||
```jsx | ||
import { Container } from 'pixi.js' | ||
import { useExtend } from '@pixi/react' | ||
const MyComponent = () => { | ||
useExtend({ Container }) | ||
return ( | ||
<container /> | ||
) | ||
} | ||
``` | ||
#### `useTick` | ||
`useTick` allows a callback to be attached to the [`Ticker`](https://pixijs.download/release/docs/ticker.Ticker.html) on the parent application. | ||
```jsx | ||
import { useTick } from '@pixi/react' | ||
const MyComponent = () => { | ||
useTick(() => console.log('This will be logged on every tick')) | ||
} | ||
``` | ||
`useTick` optionally takes a boolean as a second argument. Setting this boolean to `false` will cause the callback to be disabled until the argument is set to true again. | ||
```jsx | ||
import { useState } from 'react' | ||
import { useTick } from '@pixi/react' | ||
const MyComponent = () => { | ||
const [isEnabled, setIsEnabled] = useState(false) | ||
useTick(() => console.log('This will be logged on every tick as long as `isEnabled` is `true`'), ) | ||
return ( | ||
<sprite onClick={setIsEnabled(previousState => !previousState)}> | ||
) | ||
} | ||
``` |
@@ -1,14 +0,9 @@ | ||
import type { PixiElementsImpl } from '../lib/types/PixiElementsImpl'; | ||
import type { NamespacedPixiElements } from './typedefs/NamespacedPixiElements.ts'; | ||
import type { PixiElements } from './typedefs/PixiElements.ts'; | ||
export type { PixiReactNode } from './typedefs/PixiReactNode.ts'; | ||
declare global { | ||
namespace React.JSX { | ||
interface IntrinsicElements extends PixiElementsImpl { | ||
namespace JSX { | ||
interface IntrinsicElements extends PixiElements, NamespacedPixiElements { | ||
} | ||
} | ||
namespace PixiMixins { | ||
interface Container { | ||
__handlers: Record<string, (...args: any[]) => any>; | ||
busy: boolean; | ||
} | ||
} | ||
} | ||
export {}; |
@@ -1,11 +0,9 @@ | ||
/** @typedef {import('pixi.js').Container} Container */ | ||
/** @typedef {import('../types/HostContainer.js').HostContainer} HostContainer */ | ||
/** @typedef {import('../typedefs/Instance.ts').Instance} Instance */ | ||
/** | ||
* Adds elements to our scene and attaches geometry and material to meshes. | ||
* Adds elements to our application. | ||
* | ||
* @param {HostContainer & Container} parentInstance | ||
* @param {HostContainer & Container} child | ||
* @param {Instance} parentInstance | ||
* @param {Instance | null} childInstance | ||
*/ | ||
export function appendChild(parentInstance: HostContainer & Container, child: HostContainer & Container): void; | ||
export type Container = import('pixi.js').Container; | ||
export type HostContainer = import('../types/HostContainer.js').HostContainer; | ||
export function appendChild(parentInstance: Instance, childInstance: Instance | null): void; | ||
export type Instance = import('../typedefs/Instance.ts').Instance; |
/** | ||
* Apply properties to Pixi.js instance. | ||
* | ||
* @param {{ [key: string]: any }} instance An instance? | ||
* @param {{ [key: string]: any }} newProps New props. | ||
* @param {{ [key: string]: any }} [oldProps] Old props. | ||
* @param {MaybeInstance} instance An instance? | ||
* @param {InstanceProps | DiffSet} data New props. | ||
*/ | ||
export function applyProps(instance: { | ||
[key: string]: any; | ||
}, newProps: { | ||
[key: string]: any; | ||
}, oldProps?: { | ||
[key: string]: any; | ||
} | undefined): void; | ||
export function applyProps(instance: MaybeInstance, data: InstanceProps | DiffSet): import("../typedefs/MaybeInstance.ts").MaybeInstance; | ||
export type FederatedPointerEvent = import('pixi.js').FederatedPointerEvent; | ||
export type FederatedWheelEvent = import('pixi.js').FederatedWheelEvent; | ||
export type DiffSet = import('../typedefs/DiffSet.ts').DiffSet; | ||
export type Instance = import('../typedefs/Instance.ts').Instance; | ||
export type InstanceProps = import('../typedefs/InstanceProps.ts').InstanceProps; | ||
export type MaybeInstance = import('../typedefs/MaybeInstance.ts').MaybeInstance; |
@@ -6,4 +6,4 @@ /** | ||
* @param {S} string The string to be converted. | ||
* @returns {S} The converted string. | ||
* @returns {Capitalize<S>} The converted string. | ||
*/ | ||
export function convertStringToPascalCase<S extends string>(string: S): S; | ||
export function convertStringToPascalCase<S extends string>(string: S): Capitalize<S>; |
@@ -1,8 +0,13 @@ | ||
/** @typedef {import('../types/PixiElements.js').PixiElements} PixiElements */ | ||
/** @typedef {import('../typedefs/HostConfig.ts').HostConfig} HostConfig */ | ||
/** @typedef {import('../typedefs/Instance.ts').Instance} Instance */ | ||
/** @typedef {import('../typedefs/InstanceProps.ts').InstanceProps} InstanceProps */ | ||
/** | ||
* @param {keyof PixiElements} type | ||
* @param {Record<string, unknown>} props | ||
* @returns | ||
* @param {HostConfig['type']} type | ||
* @param {InstanceProps} props | ||
* @param {Instance} root | ||
* @returns {Instance} | ||
*/ | ||
export function createInstance(type: keyof PixiElements, props: Record<string, unknown>): any; | ||
export type PixiElements = import('../types/PixiElements.js').PixiElements; | ||
export function createInstance(type: HostConfig['type'], props: InstanceProps, root: Instance): Instance; | ||
export type HostConfig = import('../typedefs/HostConfig.ts').HostConfig; | ||
export type Instance = import('../typedefs/Instance.ts').Instance; | ||
export type InstanceProps = import('../typedefs/InstanceProps.ts').InstanceProps; |
@@ -1,1 +0,12 @@ | ||
export function createTextInstance(): void; | ||
/** @typedef {import('../typedefs/Instance.ts').Instance} Instance */ | ||
/** | ||
* text: string, rootContainer: Instance, hostContext: null, internalHandle: any | ||
* @param {string} _text Unused. | ||
* @param {Instance} _rootContainer Unused. | ||
* @param {null} _hostContext Unused. | ||
* @param {any} _internalHandle Unused. | ||
* @throws {Error} Always throws, because we don't support this (yet). | ||
* @returns {Instance} | ||
*/ | ||
export function createTextInstance(_text: string, _rootContainer: Instance, _hostContext: null, _internalHandle: any): Instance; | ||
export type Instance = import('../typedefs/Instance.ts').Instance; |
@@ -1,11 +0,9 @@ | ||
/** @typedef {import('pixi.js').Container} Container */ | ||
/** @typedef {import('../types/HostContainer.js').HostContainer} HostContainer */ | ||
/** @typedef {import('../typedefs/Instance.ts').Instance} Instance */ | ||
/** | ||
* Removes elements from our scene and disposes of them. | ||
* | ||
* @param {HostContainer & Container} _container Unused. | ||
* @param {HostContainer & Container} child The child to be removed. | ||
* @param {Instance} _parentInstance The parent instance. | ||
* @param {Instance} childInstance The child instance to be removed. | ||
*/ | ||
export function removeChild(_container: HostContainer & Container, child: HostContainer & Container): void; | ||
export type Container = import('pixi.js').Container; | ||
export type HostContainer = import('../types/HostContainer.js').HostContainer; | ||
export function removeChild(_parentInstance: Instance, childInstance: Instance): void; | ||
export type Instance = import('../typedefs/Instance.ts').Instance; |
/** | ||
* @typedef {import('pixi.js').ProgressCallback} ProgressCallback | ||
* @typedef {import('pixi.js').Texture} Texture | ||
* @typedef {import('pixi.js').UnresolvedAsset} UnresolvedAsset | ||
*/ | ||
/** | ||
* Loads assets, returning a hash of assets once they're loaded. | ||
* | ||
* @param {UnresolvedAsset} options Asset options. | ||
* @param {ProgressCallback} [onProgress] A function to be called when the asset loader reports loading progress. | ||
* @returns {null | Texture} A hash of textures, keyed by their URLs. This will be `null` until the assets are loaded. | ||
* @template T | ||
* @param {(import('pixi.js').UnresolvedAsset<T> & AssetRetryOptions) | string} options Asset options. | ||
* @param {import('pixi.js').ProgressCallback} [onProgress] A function to be called when the asset loader reports loading progress. | ||
* @returns {T} | ||
*/ | ||
export function useAsset(options: UnresolvedAsset, onProgress?: import("pixi.js").ProgressCallback | undefined): null | Texture; | ||
export type ProgressCallback = import('pixi.js').ProgressCallback; | ||
export type Texture = import('pixi.js').Texture; | ||
export type UnresolvedAsset = import('pixi.js').UnresolvedAsset; | ||
export function useAsset<T>(options: string | (Pick<import("pixi.js").ResolvedAsset<T>, "data" | "format" | "loadParser"> & { | ||
[key: string]: any; | ||
alias?: import("pixi.js").ArrayOr<string> | undefined; | ||
src?: import("pixi.js").AssetSrc | undefined; | ||
} & import("../typedefs/AssetRetryOptions.ts").AssetRetryOptions), onProgress?: import("pixi.js").ProgressCallback | undefined): T; | ||
export type AssetRetryOptions = import('../typedefs/AssetRetryOptions.ts').AssetRetryOptions; | ||
export type AssetRetryState = import('../typedefs/AssetRetryState.ts').AssetRetryState; |
@@ -0,2 +1,8 @@ | ||
export { Application } from "./components/Application.js"; | ||
export { createRoot } from "./core/createRoot.js"; | ||
export { extend } from "./helpers/extend.js"; | ||
export { useApp } from "./hooks/useApp.js"; | ||
export { useAsset } from "./hooks/useAsset.js"; | ||
export { render } from "./render.js"; | ||
export { useExtend } from "./hooks/useExtend.js"; | ||
export { useTick } from "./hooks/useTick.js"; | ||
export * from "./global.js"; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
521804
4
319
5328
372
1
16
6
- Removedstatery@^0.7.1
- Removedstatery@0.7.1(transitive)