Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a5f85a7
Fix TypeScript typing for filterColor shader hook
Kathrina-dev Mar 15, 2026
ea3b6b4
Removed formatted code
Kathrina-dev Mar 16, 2026
9de7f94
fix: typescript generation script for typedef constants
Kathrina-dev Mar 16, 2026
fdf4b55
added condition for typedefs in p5Constants
Kathrina-dev Mar 16, 2026
ce9ef51
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 16, 2026
36f70e2
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 17, 2026
c42ef81
fixed typescript JSDoc generator to handle unions
Kathrina-dev Mar 17, 2026
c809894
removed auto-formatting
Kathrina-dev Mar 17, 2026
51a03f0
added useful comments
Kathrina-dev Mar 17, 2026
ca4c1a4
added useful comments
Kathrina-dev Mar 17, 2026
94ae3d4
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 20, 2026
27b0d88
revert back to original structure
Kathrina-dev Mar 20, 2026
f29c5b8
added new function to handle typedef properties
Kathrina-dev Mar 20, 2026
825d0cb
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 23, 2026
bc62e8c
added condition to allow filterColorHook to pass through p5Constants
Kathrina-dev Mar 23, 2026
6ce04fc
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Mar 26, 2026
4e64619
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Apr 3, 2026
17281cf
Merge branch 'dev-2.0' into fix-filterColor-object-type
Kathrina-dev Apr 7, 2026
1219e71
added new typing for objects in p5.strands.js
Kathrina-dev Apr 7, 2026
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
39 changes: 32 additions & 7 deletions src/strands/p5.strands.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ if (typeof p5 !== "undefined") {

/* ------------------------------------------------------------- */
/**
* @property {Object} worldInputs
* @typedef {Object} WorldInputsHook
*/
/**
* @property {WorldInputsHook} worldInputs
* @beta
* @description
* A shader hook block that modifies the world-space properties of each vertex in a shader. This hook can be used inside <a href="#/p5/buildColorShader">`buildColorShader()`</a> and similar shader <a href="#/p5.Shader/modify">`modify()`</a> calls to customize vertex positions, normals, texture coordinates, and colors before rendering. Modifications happen between the `.begin()` and `.end()` methods of the hook. "World space" refers to the coordinate system of the 3D scene, before any camera or projection transformations are applied.
Expand Down Expand Up @@ -259,7 +262,10 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} combineColors
* @typedef {Object} CombineColorsHook
*/
/**
* @property {CombineColorsHook} combineColors
* @beta
* @description
* A shader hook block that modifies how color components are combined in the fragment shader. This hook can be used inside <a href="#/p5/buildMaterialShader">`buildMaterialShader()`</a> and similar shader <a href="#/p5.Shader/modify">`modify()`</a> calls to control the final color output of a material. Modifications happen between the `.begin()` and `.end()` methods of the hook.
Expand Down Expand Up @@ -592,7 +598,10 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} pixelInputs
* @typedef {Object} PixelInputsHook
*/
/**
* @property {PixelInputsHook} pixelInputs
* @beta
* @description
* A shader hook block that modifies the properties of each pixel before the final color is calculated. This hook can be used inside <a href="#/p5/buildMaterialShader">`buildMaterialShader()`</a> and similar shader <a href="#/p5.Shader/modify">`modify()`</a> calls to adjust per-pixel data before lighting is applied. Modifications happen between the `.begin()` and `.end()` methods of the hook.
Expand Down Expand Up @@ -763,8 +772,18 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} filterColor
* @beta
* @typedef {Object} FilterColorHook
* @property {any} texCoord
* @property {any} canvasSize
* @property {any} texelSize
* @property {any} canvasContent
* @property {function(): undefined} begin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should these be : void too or is there a reason for them to be : undefined?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

When I tried using void, the generated p5.d.ts ended up showing any instead. I tried using different ways to define a function but it still kept showing any. So I've defined it as undefined which showed up correctly while generating p5.js.d.ts

* @property {function(): undefined} end
* @property {function(color: any): void} set
*/

/**
* @property {FilterColorHook} filterColor
* @description
* A shader hook block that sets the color for each pixel in a filter shader. This hook can be used inside <a href="#/p5/buildFilterShader">`buildFilterShader()`</a> to control the output color for each pixel.
*
Expand Down Expand Up @@ -808,7 +827,10 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} objectInputs
* @typedef {Object} ObjectInputsHook
*/
/**
* @property {ObjectInputsHook} objectInputs
* @beta
* @description
* A shader hook block to modify the properties of each vertex before any transformations are applied. This hook can be used inside <a href="#/p5/buildMaterialShader">`buildMaterialShader()`</a> and similar shader <a href="#/p5.Shader/modify">`modify()`</a> calls to customize vertex positions, normals, texture coordinates, and colors before rendering. Modifications happen between the `.begin()` and `.end()` methods of the hook. "Object space" refers to the coordinate system of the 3D scene before any transformations, cameras, or projection transformations are applied.
Expand Down Expand Up @@ -850,7 +872,10 @@ if (typeof p5 !== "undefined") {
*/

/**
* @property {Object} cameraInputs
* @typedef {Object} CameraInputsHook
*/
/**
* @property {CameraInputsHook} cameraInputs
* @beta
* @description
* A shader hook block that adjusts vertex properties from the perspective of the camera. This hook can be used inside <a href="#/p5/buildMaterialShader">`buildMaterialShader()`</a> and similar shader <a href="#/p5.Shader/modify">`modify()`</a> calls to customize vertex positions, normals, texture coordinates, and colors before rendering. "Camera space" refers to the coordinate system of the 3D scene after transformations have been applied, seen relative to the camera.
Expand Down
1 change: 0 additions & 1 deletion utils/patch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,3 @@ export function applyPatches() {
}
}
}

145 changes: 139 additions & 6 deletions utils/typescript.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ allRawData.forEach(entry => {
if (entry.kind === 'constant' || entry.kind === 'typedef') {
constantsLookup.add(entry.name);
if (entry.kind === 'typedef') {
typedefs[entry.name] = entry.type;
// Store the full entry so we have access to both .type and .properties
typedefs[entry.name] = entry;
}
}
});
Expand Down Expand Up @@ -242,15 +243,29 @@ function convertTypeToTypeScript(typeNode, options = {}) {
}
}

// Check if this is a p5 constant - use typeof since they're defined as values
// Check if this is a p5 constant/typedef
if (constantsLookup.has(typeName)) {
const typedefEntry = typedefs[typeName];

// Use interface name for object-shaped typedefs in all contexts
if (typedefEntry && hasTypedefProperties(typedefEntry)) {
if (inGlobalMode) {
return `P5.${typeName}`;
} else if (isInsideNamespace) {
return typeName;
} else {
return `p5.${typeName}`;
}
}

// Fallback to typeof or primitive resolution for alias-style typedefs
if (inGlobalMode) {
return `typeof P5.${typeName}`;
} else if (typedefs[typeName]) {
} else if (typedefEntry) {
if (isConstantDef) {
return convertTypeToTypeScript(typedefs[typeName], options);
return convertTypeToTypeScript(typedefEntry.type, options);
} else {
return `typeof p5.${typeName}`
return `typeof p5.${typeName}`;
}
} else {
return `Symbol`;
Expand Down Expand Up @@ -330,6 +345,105 @@ function convertTypeToTypeScript(typeNode, options = {}) {
}
}

// Check if typedef represents a real object shape
function hasTypedefProperties(typedefEntry) {
if (!Array.isArray(typedefEntry.properties) || typedefEntry.properties.length === 0) {
return false;
}
// Reject self-referential single-property typedefs
if (
typedefEntry.properties.length === 1 &&
typedefEntry.properties[0].name === typedefEntry.name
) {
return false;
}
return true;
}

// Convert JSDoc FunctionType into a TypeScript function signature string
function convertFunctionTypeForInterface(typeNode, options) {
const params = (typeNode.params || [])
.map((param, i) => {
let typeObj;
let paramName;
if (param.type === 'ParameterType') {
typeObj = param.expression;
paramName = param.name ?? `p${i}`;
} else if (typeof param.type === 'object' && param.type !== null) {
typeObj = param.type;
paramName = param.name ?? `p${i}`;
} else {
// param itself is a plain type node
typeObj = param;
paramName = `p${i}`;
}
const paramType = convertTypeToTypeScript(typeObj, options);
return `${paramName}: ${paramType}`;
})
.join(', ');

const returnType = typeNode.result
? convertTypeToTypeScript(typeNode.result, options)
: 'void';

// Normalise 'undefined' return to 'void' for idiomatic TypeScript
const normalisedReturn = returnType === 'undefined' ? 'void' : returnType;

return `(${params}) => ${normalisedReturn}`;
}

// Generate a TypeScript interface from a typedef with @property fields
function generateTypedefInterface(name, typedefEntry, options = {}, indent = 2) {
const pad = ' '.repeat(indent);
const innerPad = ' '.repeat(indent + 2);
let output = '';

if (typedefEntry.description) {
const descStr = typeof typedefEntry.description === 'string'
? typedefEntry.description
: descriptionStringForTypeScript(typedefEntry.description);
if (descStr) {
output += `${pad}/**\n`;
output += formatJSDocComment(descStr, indent) + '\n';
output += `${pad} */\n`;
}
}

output += `${pad}interface ${name} {\n`;

for (const prop of typedefEntry.properties) {
// Each prop: { name, type, description, optional }
const propName = prop.name;
const rawType = prop.type;
const isOptional = prop.optional || rawType?.type === 'OptionalType';
const optMark = isOptional ? '?' : '';

if (prop.description) {
const propDescStr = typeof prop.description === 'string'
? prop.description.trim()
: descriptionStringForTypeScript(prop.description);
if (propDescStr) {
output += `${innerPad}/** ${propDescStr} */\n`;
}
}

if (rawType?.type === 'FunctionType') {
// Render FunctionType properties as method signatures instead of arrow properties
const sig = convertFunctionTypeForInterface(rawType, options);
const arrowIdx = sig.lastIndexOf('=>');
const paramsPart = sig.substring(0, arrowIdx).trim();
const retPart = sig.substring(arrowIdx + 2).trim();
output += `${innerPad}${propName}${paramsPart}: ${retPart};\n`;
} else {
const tsType = rawType ? convertTypeToTypeScript(rawType, options) : 'any';
output += `${innerPad}${propName}${optMark}: ${tsType};\n`;
}
}

output += `${pad}}\n\n`;
return output;
}

// Strategy for TypeScript output
const typescriptStrategy = {
shouldSkipEntry: (entry, context) => {
Expand Down Expand Up @@ -606,6 +720,10 @@ function generateTypeDefinitions() {
if (seenConstants.has(item.name)) {
return false;
}
// Skip typedefs that have real object shapes
if (typedefs[item.name] && hasTypedefProperties(typedefs[item.name])) {
return false;
}
seenConstants.add(item.name);
return true;
}
Expand Down Expand Up @@ -667,13 +785,20 @@ function generateTypeDefinitions() {

output += '\n';


p5Constants.forEach(constant => {
output += `${mutableProperties.has(constant.name) ? 'let' : 'const'} ${constant.name}: typeof __${constant.name};\n`;
});

output += '\n';

// Emit interfaces for typedefs that define object shapes
const namespaceOptions = { isInsideNamespace: true };
for (const [name, typedefEntry] of Object.entries(typedefs)) {
if (hasTypedefProperties(typedefEntry)) {
output += generateTypedefInterface(name, typedefEntry, namespaceOptions, 2);
}
}

// Generate other classes in namespace
Object.values(processed.classes).forEach(classData => {
if (classData.name !== 'p5') {
Expand Down Expand Up @@ -750,6 +875,14 @@ p5: P5;

globalDefinitions += '\n';

// Mirror typedef interfaces for global-mode usage
const globalNamespaceOptions = { isInsideNamespace: true, inGlobalMode: true };
for (const [name, typedefEntry] of Object.entries(typedefs)) {
if (hasTypedefProperties(typedefEntry)) {
globalDefinitions += generateTypedefInterface(name, typedefEntry, globalNamespaceOptions, 2);
}
}

// Add all real classes as both types and constructors
Object.values(processed.classes).forEach(classData => {
if (classData.name !== 'p5') {
Expand Down