diff --git a/@plotly/dash-generator-test-component-typescript/generator.test.ts b/@plotly/dash-generator-test-component-typescript/generator.test.ts index 7b81fa07a3..9c8e721371 100644 --- a/@plotly/dash-generator-test-component-typescript/generator.test.ts +++ b/@plotly/dash-generator-test-component-typescript/generator.test.ts @@ -9,7 +9,7 @@ function getMetadata() { [ path.resolve(__dirname, '..', '..', 'dash', 'extract-meta.js'), '""', // ignore pattern - '""', // reserved keywords + '^_.*$', // reserved keywords path.join(__dirname, 'src', 'components') ], { @@ -103,6 +103,10 @@ describe('Test Typescript component metadata generation', () => { `${componentName} setProps func`, testTypeFactory('setProps', 'func') ); + test( + `${componentName} tuple tuple`, + testTypeFactory('a_tuple', 'tuple') + ) }); describe('Test prop attributes', () => { @@ -230,6 +234,24 @@ describe('Test Typescript component metadata generation', () => { 'name' ], metadata)).toBe('any') } + ); + + test( + 'Tuple elements', () => { + const tuplePath: (string|number)[] = [ + 'TypeScriptComponent', + 'props', + 'a_tuple', + 'type', + 'elements' + ] + expect( + R.path(tuplePath.concat(0, 'name'), metadata) + ).toBe('number'); + expect( + R.path(tuplePath.concat(1, 'name'), metadata) + ).toBe('string'); + } ) }); diff --git a/@plotly/dash-generator-test-component-typescript/src/props.ts b/@plotly/dash-generator-test-component-typescript/src/props.ts index 3912f9ddf1..7e6445a30c 100644 --- a/@plotly/dash-generator-test-component-typescript/src/props.ts +++ b/@plotly/dash-generator-test-component-typescript/src/props.ts @@ -40,8 +40,9 @@ export type TypescriptComponentProps = { setProps?: (props: Record) => void; className?: string; style?: any; - nested?: Nested; + + a_tuple?: [number, string]; }; export type WrappedHTMLProps = { diff --git a/CHANGELOG.md b/CHANGELOG.md index 7db41c1467..bacc69fb2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `dash` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). +## UNRELEASED + +### Fixed + +- [#2257](https://github.com/plotly/dash/pull/2257) Fix tuple types in the TypeScript component generator. + ## [2.6.2] - 2022-09-23 ### Fixed diff --git a/dash/development/_py_components_generation.py b/dash/development/_py_components_generation.py index 3b6ce744e9..6e7c49ecdf 100644 --- a/dash/development/_py_components_generation.py +++ b/dash/development/_py_components_generation.py @@ -570,6 +570,10 @@ def array_of(): ) return "list" + def tuple_of(): + elements = [js_to_py_type(element) for element in type_object["elements"]] + return f"list of {len(elements)} elements: [{', '.join(elements)}]" + return dict( array=lambda: "list", bool=lambda: "boolean", @@ -601,6 +605,7 @@ def array_of(): shape=shape_or_exact, # React's PropTypes.exact exact=shape_or_exact, + tuple=tuple_of, ) diff --git a/dash/extract-meta.js b/dash/extract-meta.js index 9ec1fd405b..f0767a340b 100755 --- a/dash/extract-meta.js +++ b/dash/extract-meta.js @@ -293,7 +293,7 @@ function gatherComponents(sources, components = {}) { const getPropType = (propType, propObj, parentType = null) => { // Types can get namespace prefixes or not. let name = checker.typeToString(propType).replace(/^React\./, ''); - let value; + let value, elements; const raw = name; const newParentType = (parentType || []).concat(raw) @@ -311,13 +311,13 @@ function gatherComponents(sources, components = {}) { // Shapes & array support. if (!PRIMITIVES.concat('enum', 'func', 'union').includes(name)) { if ( - name.includes('[]') || - name.includes('Array') || - name === 'tuple' + // Excluding object with arrays in the raw. + (name.includes('[]') && name.endsWith("]")) || + name.includes('Array') ) { name = 'arrayOf'; const replaced = raw.replace('[]', ''); - if (unionSupport.includes('replaced')) { + if (unionSupport.includes(replaced)) { // Simple types are easier. value = { name: getPropTypeName(replaced), @@ -336,6 +336,14 @@ function gatherComponents(sources, components = {}) { name = 'array'; } } + } else if ( + name === 'tuple' || + (name.startsWith('[') && name.endsWith(']')) + ) { + name = 'tuple'; + elements = propType.resolvedTypeArguments.map( + t => getPropType(t, propObj, newParentType) + ); } else if ( BANNED_TYPES.includes(name) || (parentType && parentType.includes(name)) @@ -369,6 +377,7 @@ function gatherComponents(sources, components = {}) { return { name, value, + elements, raw }; };