From f4fe890b2672854bd980f2c898f3ece0432a35be Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 2 Sep 2026 14:57:31 -0600 Subject: [PATCH 1/4] fix: Handle regex values when generating types --- lib/index.d.ts | 11 ++-- src/types/core/layout.d.ts | 19 +----- src/types/lib/common.d.ts | 46 ++++++++++++++ tasks/generate_schema_types.mjs | 109 ++++++++++++++++++++++++++++---- 4 files changed, 150 insertions(+), 35 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index e17f8128ec8..583bd7811a3 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -27,6 +27,8 @@ export as namespace Plotly; // --------------------------------------------------------------------------- export type { + AxisName, + CartesianSubplotId, Color, ColorScale, Datum, @@ -36,7 +38,9 @@ export type { MarkerSymbol, TypedArray, XAnchor, - YAnchor + XAxisName, + YAnchor, + YAxisName } from '../src/types/lib/common'; // --------------------------------------------------------------------------- @@ -50,15 +54,12 @@ export type * from '../src/types/generated/schema'; // --------------------------------------------------------------------------- export type { - AxisName, ButtonClickEvent, Icon, ModeBarButton, ModeBarButtonAny, ModeBarDefaultButtons, - Template, - XAxisName, - YAxisName + Template } from '../src/types/core/layout'; // --------------------------------------------------------------------------- diff --git a/src/types/core/layout.d.ts b/src/types/core/layout.d.ts index ab8b3c73e9f..15577369194 100644 --- a/src/types/core/layout.d.ts +++ b/src/types/core/layout.d.ts @@ -12,24 +12,10 @@ import type { Data, Layout, TraceType } from '../generated/schema'; import type { PlotlyHTMLElement } from './events'; // --------------------------------------------------------------------------- -// Axis name types (template literal utilities — not in schema) +// Axis name types // --------------------------------------------------------------------------- -/** - * Numeric axis suffix plus the optional ` domain` qualifier. The suffix is - * empty for the first axis (`x` / `y`) and `2` through `99` otherwise. - */ -type xYAxisNames = `${ - | '' - | `${2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` - | `${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}${0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`}${'' | ' domain'}`; - -/** Any valid x-axis reference: `'x'`, `'x2'`, …, `'x99'`, optionally `' domain'`. */ -export type XAxisName = `x${xYAxisNames}`; -/** Any valid y-axis reference: `'y'`, `'y2'`, …, `'y99'`, optionally `' domain'`. */ -export type YAxisName = `y${xYAxisNames}`; -/** Any valid axis reference (x or y, numbered or not, domain-qualified or not). */ -export type AxisName = XAxisName | YAxisName; +export type { AxisName, CartesianSubplotId, XAxisName, YAxisName } from '../lib/common'; // --------------------------------------------------------------------------- // ModeBar / Icon (behavioral types — not in schema) @@ -169,4 +155,3 @@ export interface Template { /** Template layout defaults. */ layout?: Partial | undefined; } - diff --git a/src/types/lib/common.d.ts b/src/types/lib/common.d.ts index 14c54e0001e..daa58c1e058 100644 --- a/src/types/lib/common.d.ts +++ b/src/types/lib/common.d.ts @@ -64,6 +64,52 @@ export type XAnchor = 'auto' | 'left' | 'center' | 'right'; /** Vertical anchor position for components (legend, annotation, etc.). */ export type YAnchor = 'auto' | 'top' | 'middle' | 'bottom'; +// --------------------------------------------------------------------------- +// Axis and subplot identifiers +// +// The schema states these as regexes, which no TypeScript type can express +// exactly. The template literal types below enumerate the accepted strings +// instead, so they are bounded where the schema is not. See the digit-tier +// note on `AxisNumber`. +// +// tasks/generate_schema_types.mjs maps each schema regex onto one of these +// types through its REGEX_VALUE_TYPES table. +// --------------------------------------------------------------------------- + +type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; +type NonZeroDigit = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; + +/** + * Numeric axis suffix. Empty for the first axis (`x` / `y`), then `2` through + * `999`. There is no `1` suffix — the first axis is unnumbered. + * + * The schema regex accepts any number of digits. This type stops at three + * because a template literal union has to be finite, so charts with 1000 or + * more axes of one letter cannot be typed. + */ +type AxisNumber = '' | `${Exclude}` | `${NonZeroDigit}${Digit}` | `${NonZeroDigit}${Digit}${Digit}`; + +/** + * Two-digit variant of `AxisNumber`, capped at `99`. + * + * Used only where the suffix appears twice in one identifier. Three digits + * squared exceeds the TypeScript union size limit. + */ +type ShortAxisNumber = '' | `${Exclude}` | `${NonZeroDigit}${Digit}`; + +/** Any valid x-axis reference: `'x'`, `'x2'`, …, optionally `' domain'`. */ +export type XAxisName = `x${AxisNumber}${'' | ' domain'}`; +/** Any valid y-axis reference: `'y'`, `'y2'`, …, optionally `' domain'`. */ +export type YAxisName = `y${AxisNumber}${'' | ' domain'}`; +/** Any valid axis reference (x or y, numbered or not, domain-qualified or not). */ +export type AxisName = XAxisName | YAxisName; + +/** + * A cartesian subplot id pairing an x and a y axis, such as `'xy'` or + * `'x3y2'`. Unlike `XAxisName`, no `' domain'` qualifier is permitted. + */ +export type CartesianSubplotId = `x${ShortAxisNumber}y${ShortAxisNumber}`; + // --------------------------------------------------------------------------- // Error bars // --------------------------------------------------------------------------- diff --git a/tasks/generate_schema_types.mjs b/tasks/generate_schema_types.mjs index d70970488e7..a91fd6ddb6d 100644 --- a/tasks/generate_schema_types.mjs +++ b/tasks/generate_schema_types.mjs @@ -194,6 +194,53 @@ function serializeValue(v) { return String(v); // numbers, booleans } +/** + * Schema `values` entries wrapped in `/.../` are regexes, not literals. The + * enumerated `validateFunction` in src/lib/coerce.js unwraps and tests them. + * Each one maps to a template literal type in src/types/lib/common.d.ts that + * accepts the same strings. + * + * A regex stands in for a value set that the static schema cannot list, + * because the real set depends on how many axes a figure declares. + */ +const REGEX_VALUE_TYPES = new Map([ + ['/^x([2-9]|[1-9][0-9]+)?( domain)?$/', 'XAxisName'], + ['/^y([2-9]|[1-9][0-9]+)?( domain)?$/', 'YAxisName'], + ['/^x([2-9]|[1-9][0-9]+)?y([2-9]|[1-9][0-9]+)?$/', 'CartesianSubplotId'] +]); + +/** Test whether a schema value uses the `/.../` regex convention. */ +function isRegexValue(v) { + return typeof v === 'string' && v.length > 1 && v.startsWith('/') && v.endsWith('/'); +} + +/** + * Serialize one `values` entry from a string or enumerated attribute. + * + * Plain values become string literals. Regex values become the named type + * that accepts the same strings. + * + * @param {string|number|boolean} v - The schema value + * @param {string} attrPath - Dotted attribute path, used in the error message + * @returns {string} + * @throws If `v` is a regex with no entry in `REGEX_VALUE_TYPES`. Emitting the + * regex source as a string literal would produce a type that only accepts + * the pattern text, so failing here is deliberate. + */ +function enumValueToTS(v, attrPath) { + if (!isRegexValue(v)) return serializeValue(v); + + const typeName = REGEX_VALUE_TYPES.get(v); + if (!typeName) { + throw new Error( + `Unmapped regex value '${v}' at '${attrPath}'. Add an entry to REGEX_VALUE_TYPES ` + + `pointing at a template literal type in src/types/lib/common.d.ts that accepts ` + + `the same strings.` + ); + } + return typeName; +} + /** * Try to match a values array to a known common type. * Returns the type name string or null. @@ -215,32 +262,42 @@ function matchCommonType(values) { * When all elements share the same valType we can emit a precise tuple * or array type instead of `unknown[]`. */ -function infoArrayToTS(attr) { +function infoArrayToTS(attr, attrPath) { if (!attr.items) return 'any[]'; // items can be an array of item descriptors or a single descriptor // (single descriptor = freeLength homogeneous array) if (!Array.isArray(attr.items)) { - const elemType = simpleValType(attr.items.valType); - return `${elemType}[]`; + return `${asArrayElement(simpleValType(attr.items, attrPath))}[]`; } - const elemTypes = attr.items.map((item) => simpleValType(item.valType)); + const elemTypes = attr.items.map((item) => simpleValType(item, attrPath)); if (attr.freeLength) { // Variable-length — use array of the union of element types const unique = [...new Set(elemTypes)]; const union = unique.length === 1 ? unique[0] : unique.join(' | '); - return `${union}[]`; + return `${asArrayElement(union)}[]`; } // Fixed-length — emit tuple return `[${elemTypes.join(', ')}]`; } -/** Map a valType string to a simple TS type (no arrayOk handling). */ -function simpleValType(valType) { - switch (valType) { +/** Parenthesize a union so that appending `[]` binds to the whole union. */ +function asArrayElement(type) { + return type.includes('|') ? `(${type})` : type; +} + +/** + * Map an info_array item descriptor to a simple TS type (no arrayOk handling). + * + * @param {object} item - The item descriptor, carrying at least `valType` + * @param {string} attrPath - Dotted path of the owning attribute + * @returns {string} + */ +function simpleValType(item, attrPath) { + switch (item.valType) { case 'number': case 'integer': return 'number'; @@ -251,6 +308,9 @@ function simpleValType(valType) { return 'boolean'; case 'color': return 'Color'; + case 'enumerated': + if (!Array.isArray(item.values)) return 'any'; + return item.values.map((v) => enumValueToTS(v, attrPath)).join(' | '); default: return 'any'; } @@ -368,7 +428,7 @@ function valTypeToTS(attr, attrPath) { base = common; break; } - base = attr.values.map(serializeValue).join(' | '); + base = attr.values.map((v) => enumValueToTS(v, attrPath)).join(' | '); } else { base = 'string'; } @@ -405,7 +465,7 @@ function valTypeToTS(attr, attrPath) { base = common; break; } - base = attr.values.map(serializeValue).join(' | '); + base = attr.values.map((v) => enumValueToTS(v, attrPath)).join(' | '); } else { base = 'any'; } @@ -432,7 +492,7 @@ function valTypeToTS(attr, attrPath) { } case 'info_array': - return infoArrayToTS(attr); + return infoArrayToTS(attr, attrPath); case 'any': return 'any'; @@ -1080,7 +1140,16 @@ export function generateSchemaTypes(schema, outputPath) { ' * Do not edit by hand — run `npm run schema` to regenerate.', ' */', '', - "import type { Color, ColorScale, Datum, MarkerSymbol, TypedArray } from '../lib/common';", + 'import type {', + ' CartesianSubplotId,', + ' Color,', + ' ColorScale,', + ' Datum,', + ' MarkerSymbol,', + ' TypedArray,', + ' XAxisName,', + ' YAxisName', + "} from '../lib/common';", '' ]; @@ -1265,7 +1334,21 @@ export function generateSchemaTypes(schema, outputPath) { } } - fs.writeFileSync(outputPath, chunks.join('\n')); + const output = chunks.join('\n'); + + // Backstop for the `/.../` regex convention. `enumValueToTS` already fails + // on an unmapped regex and names the attribute, but it only sees `values` + // entries. This catches a regex that reaches the output by another route. + const leakedRegexes = output.match(/'\/\^[^']*'/g); + if (leakedRegexes) { + throw new Error( + `Generated types contain regex string literals: ${[...new Set(leakedRegexes)].join(', ')}. ` + + `A schema regex reached the output as a literal type, which only accepts the pattern ` + + `text itself. Map it in REGEX_VALUE_TYPES.` + ); + } + + fs.writeFileSync(outputPath, output); const sharedCount = sharedList.length; const layoutCount = subplotGroups.size + arrayItems.size + 1; // +1 for Layout itself From b91a20595d63a5e0566ce5f728db6086e15058a0 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 2 Sep 2026 14:58:19 -0600 Subject: [PATCH 2/4] Update types --- src/types/generated/schema.d.ts | 45 ++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/types/generated/schema.d.ts b/src/types/generated/schema.d.ts index 2c08fa0db33..1dba468a9ec 100644 --- a/src/types/generated/schema.d.ts +++ b/src/types/generated/schema.d.ts @@ -3,7 +3,16 @@ * Do not edit by hand — run `npm run schema` to regenerate. */ -import type { Color, ColorScale, Datum, MarkerSymbol, TypedArray } from '../lib/common'; +import type { + CartesianSubplotId, + Color, + ColorScale, + Datum, + MarkerSymbol, + TypedArray, + XAxisName, + YAxisName +} from '../lib/common'; // --------------------------------------------------------------------------- // Common enum types — value sets discovered from the schema @@ -12125,7 +12134,7 @@ export interface GeoLayout { export interface LayoutAxis { /** If set to an opposite-letter axis id (e.g. `x2`, `y`), this axis is bound to the corresponding opposite-letter axis. If set to *free*, this axis' position is determined by `position`. */ - anchor?: 'free' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + anchor?: 'free' | XAxisName | YAxisName; /** * Determines whether long tick labels automatically grow the figure margins. * @default false @@ -12245,7 +12254,7 @@ export interface LayoutAxis { */ linewidth?: number; /** If set to another axis id (e.g. `x2`, `y`), the range of this axis will match the range of the corresponding axis in data-coordinates space. Moreover, matching axes share auto-range values, category lists and histogram auto-bins. Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint is currently forbidden. Moreover, note that matching axes must have the same `type`. */ - matches?: '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + matches?: XAxisName | YAxisName; /** * Determines the maximum range of this axis. * Setting this also sets: ^autorange = false @@ -12340,7 +12349,7 @@ export interface LayoutAxis { */ nticks?: number; /** If set a same-letter axis id, this axis is overlaid on top of the corresponding same-letter axis, with traces and axes visible for both axes. If *false*, this axis does not overlay any same-letter axes. In this case, for axes with overlapping domains only the highest-numbered axis will be visible. */ - overlaying?: 'free' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + overlaying?: 'free' | XAxisName | YAxisName; /** * Sets the position of this axis in the plotting space (in normalized coordinates). Only has an effect if `anchor` is set to *free*. * @default 0 @@ -12502,7 +12511,7 @@ export interface LayoutAxis { }; }; /** If set to another axis id (e.g. `x2`, `y`), the range of this axis changes together with the range of the corresponding axis such that the scale of pixels per unit is in a constant ratio. Both axes are still zoomable, but when you zoom one, the other will zoom the same amount, keeping a fixed midpoint. `constrain` and `constraintoward` determine how we enforce the constraint. You can chain these, ie `yaxis: {scaleanchor: *x*}, xaxis2: {scaleanchor: *y*}` but you can only link axes of the same `type`. The linked axis can have the opposite letter (to constrain the aspect ratio) or the same letter (to match scales across subplots). Loops (`yaxis: {scaleanchor: *x*}, xaxis: {scaleanchor: *y*}` or longer) are redundant and the last constraint encountered will be ignored to avoid possible inconsistent constraints via `scaleratio`. Note that setting axes simultaneously in both a `scaleanchor` and a `matches` constraint is currently forbidden. Setting `false` allows to remove a default constraint (occasionally, you may need to prevent a default `scaleanchor` constraint from being applied, eg. when having an image trace `yaxis: {scaleanchor: "x"}` is set automatically in order for pixels to be rendered as squares, setting `yaxis: {scaleanchor: false}` allows to remove the constraint). */ - scaleanchor?: '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/' | false; + scaleanchor?: XAxisName | YAxisName | false; /** * If this axis is linked to another by `scaleanchor`, this determines the pixel to unit scale ratio. For example, if this value is 10, then every unit on this axis spans 10 times the number of pixels as a unit on the linked axis. Use this for example to create an elevation profile where the vertical scale is exaggerated a fixed amount with respect to the horizontal. * @default 1 @@ -15174,14 +15183,14 @@ export interface Annotation { * Indicates in what coordinates the tail of the annotation (ax,ay) is specified. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to a x coordinate. If set to *paper*, the `x` position refers to the distance from the left of the plotting area in normalized coordinates where *0* (*1*) corresponds to the left (right). If set to a x axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the left of the domain of that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. In order for absolute positioning of the arrow to work, *axref* must be exactly the same as *xref*, otherwise *axref* will revert to *pixel* (explained next). For relative positioning, *axref* can be set to *pixel*, in which case the *ax* value is specified in pixels relative to *x*. Absolute positioning is useful for trendline annotations which should continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. * @default 'pixel' */ - axref?: 'pixel' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/'; + axref?: 'pixel' | XAxisName; /** Sets the y component of the arrow tail about the arrow head. If `ayref` is `pixel`, a positive (negative) component corresponds to an arrow pointing from bottom to top (top to bottom). If `ayref` is not `pixel` and is exactly the same as `yref`, this is an absolute value on that axis, like `y`, specified in the same coordinates as `yref`. */ ay?: any; /** * Indicates in what coordinates the tail of the annotation (ax,ay) is specified. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plotting area in normalized coordinates where *0* (*1*) corresponds to the bottom (top). If set to a y axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the bottom of the domain of that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. In order for absolute positioning of the arrow to work, *ayref* must be exactly the same as *yref*, otherwise *ayref* will revert to *pixel* (explained next). For relative positioning, *ayref* can be set to *pixel*, in which case the *ay* value is specified in pixels relative to *y*. Absolute positioning is useful for trendline annotations which should continue to indicate the correct trend when zoomed. Relative positioning is useful for specifying the text offset for an annotated point. * @default 'pixel' */ - ayref?: 'pixel' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + ayref?: 'pixel' | YAxisName; /** * Sets the background color of the annotation. * @default 'rgba(0,0,0,0)' @@ -15299,7 +15308,7 @@ export interface Annotation { /** Toggle this annotation when clicking a data point whose `x` value is `xclick` rather than the annotation's `x` value. */ xclick?: any; /** Sets the annotation's x coordinate axis. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to a x coordinate. If set to *paper*, the `x` position refers to the distance from the left of the plotting area in normalized coordinates where *0* (*1*) corresponds to the left (right). If set to a x axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the left of the domain of that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. */ - xref?: 'paper' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/'; + xref?: 'paper' | XAxisName; /** * Shifts the position of the whole annotation and arrow to the right (positive) or left (negative) by this many pixels. * @default 0 @@ -15315,7 +15324,7 @@ export interface Annotation { /** Toggle this annotation when clicking a data point whose `y` value is `yclick` rather than the annotation's `y` value. */ yclick?: any; /** Sets the annotation's y coordinate axis. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plotting area in normalized coordinates where *0* (*1*) corresponds to the bottom (top). If set to a y axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the bottom of the domain of that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. */ - yref?: 'paper' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + yref?: 'paper' | YAxisName; /** * Shifts the position of the whole annotation and arrow up (positive) or down (negative) by this many pixels. * @default 0 @@ -15375,7 +15384,7 @@ export interface LayoutImage { * Sets the images's x coordinate axis. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to a x coordinate. If set to *paper*, the `x` position refers to the distance from the left of the plotting area in normalized coordinates where *0* (*1*) corresponds to the left (right). If set to a x axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the left of the domain of that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. * @default 'paper' */ - xref?: 'paper' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/'; + xref?: 'paper' | XAxisName; /** * Sets the image's y position. When `yref` is set to `paper`, units are sized relative to the plot height. See `yref` for more info * @default 0 @@ -15390,7 +15399,7 @@ export interface LayoutImage { * Sets the images's y coordinate axis. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plotting area in normalized coordinates where *0* (*1*) corresponds to the bottom (top). If set to a y axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the bottom of the domain of that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. * @default 'paper' */ - yref?: 'paper' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + yref?: 'paper' | YAxisName; } export interface LayoutSelection { @@ -15428,13 +15437,13 @@ export interface LayoutSelection { /** Sets the selection's end x position. */ x1?: any; /** Sets the selection's x coordinate axis. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to a x coordinate. If set to *paper*, the `x` position refers to the distance from the left of the plotting area in normalized coordinates where *0* (*1*) corresponds to the left (right). If set to a x axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the left of the domain of that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. */ - xref?: 'paper' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/'; + xref?: 'paper' | XAxisName; /** Sets the selection's starting y position. */ y0?: any; /** Sets the selection's end y position. */ y1?: any; /** Sets the selection's x coordinate axis. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plotting area in normalized coordinates where *0* (*1*) corresponds to the bottom (top). If set to a y axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the bottom of the domain of that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. */ - yref?: 'paper' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/'; + yref?: 'paper' | YAxisName; } export interface Shape { @@ -15567,7 +15576,7 @@ export interface Shape { /** Only relevant in conjunction with `xsizemode` set to *pixel*. Specifies the anchor point on the x axis to which `x0`, `x1` and x coordinates within `path` are relative to. E.g. useful to attach a pixel sized shape to a certain data value. No effect when `xsizemode` not set to *pixel*. */ xanchor?: any; /** Sets the shape's x coordinate axis. If set to a x axis id (e.g. *x* or *x2*), the `x` position refers to a x coordinate. If set to *paper*, the `x` position refers to the distance from the left of the plotting area in normalized coordinates where *0* (*1*) corresponds to the left (right). If set to a x axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the left of the domain of that axis: e.g., *x2 domain* refers to the domain of the second x axis and a x position of 0.5 refers to the point between the left and the right of the domain of the second x axis. If an array of axis IDs is provided, each `x` value will refer to the corresponding axis, e.g., ['x', 'x2'] for a rectangle, line, or circle means `x0` uses the `x` axis and `x1` uses the `x2` axis. Path shapes using an array should have one entry for each x coordinate in the string. */ - xref?: 'paper' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/' | ('paper' | '/^x([2-9]|[1-9][0-9]+)?( domain)?$/')[]; + xref?: 'paper' | XAxisName | ('paper' | XAxisName)[]; /** * Sets the shapes's sizing mode along the x axis. If set to *scaled*, `x0`, `x1` and x coordinates within `path` refer to data values on the x axis or a fraction of the plot area's width (`xref` set to *paper*). If set to *pixel*, `xanchor` specifies the x position in terms of data or plot fraction but `x0`, `x1` and x coordinates within `path` are pixels relative to `xanchor`. This way, the shape can have a fixed width while maintaining a position relative to data or plot fraction. Note: `xsizemode` *pixel* is not supported when `xref` is an array. * @default 'scaled' @@ -15592,7 +15601,7 @@ export interface Shape { /** Only relevant in conjunction with `ysizemode` set to *pixel*. Specifies the anchor point on the y axis to which `y0`, `y1` and y coordinates within `path` are relative to. E.g. useful to attach a pixel sized shape to a certain data value. No effect when `ysizemode` not set to *pixel*. */ yanchor?: any; /** Sets the shape's y coordinate axis. If set to a y axis id (e.g. *y* or *y2*), the `y` position refers to a y coordinate. If set to *paper*, the `y` position refers to the distance from the bottom of the plotting area in normalized coordinates where *0* (*1*) corresponds to the bottom (top). If set to a y axis ID followed by *domain* (separated by a space), the position behaves like for *paper*, but refers to the distance in fractions of the domain length from the bottom of the domain of that axis: e.g., *y2 domain* refers to the domain of the second y axis and a y position of 0.5 refers to the point between the bottom and the top of the domain of the second y axis. If an array of axis IDs is provided, each `y` value will refer to the corresponding axis, e.g., ['y', 'y2'] for a rectangle, line, or circle means `y0` uses the `y` axis and `y1` uses the `y2` axis. Path shapes using an array should have one entry for each y coordinate in the string. */ - yref?: 'paper' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/' | ('paper' | '/^y([2-9]|[1-9][0-9]+)?( domain)?$/')[]; + yref?: 'paper' | YAxisName | ('paper' | YAxisName)[]; /** * Sets the shapes's sizing mode along the y axis. If set to *scaled*, `y0`, `y1` and y coordinates within `path` refer to data values on the y axis or a fraction of the plot area's height (`yref` set to *paper*). If set to *pixel*, `yanchor` specifies the y position in terms of data or plot fraction but `y0`, `y1` and y coordinates within `path` are pixels relative to `yanchor`. This way, the shape can have a fixed height while maintaining a position relative to data or plot fraction. Note: `ysizemode` *pixel* is not supported when `yref` is an array. * @default 'scaled' @@ -16025,9 +16034,9 @@ export interface Layout { */ rows?: number; /** Used for freeform grids, where some axes may be shared across subplots but others are not. Each entry should be a cartesian subplot id, like *xy* or *x3y2*, or ** to leave that cell empty. You may reuse x axes within the same column, and y axes within the same row. Non-cartesian subplots and traces that support `domain` can place themselves in this grid separately using the `gridcell` attribute. */ - subplots?: any[]; + subplots?: (CartesianSubplotId | '')[]; /** Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an x axis id like *x*, *x2*, etc., or ** to not put an x axis in that column. Entries other than ** must be unique. Ignored if `subplots` is present. If missing but `yaxes` is present, will generate consecutive IDs. */ - xaxes?: any[]; + xaxes?: (XAxisName | '')[]; /** * Horizontal space between grid cells, expressed as a fraction of the total width available to one cell. Defaults to 0.1 for coupled-axes grids and 0.2 for independent grids. * Range: [0, 1] @@ -16039,7 +16048,7 @@ export interface Layout { */ xside?: 'bottom' | 'bottom plot' | 'top plot' | 'top'; /** Used with `yaxes` when the x and y axes are shared across columns and rows. Each entry should be an y axis id like *y*, *y2*, etc., or ** to not put a y axis in that row. Entries other than ** must be unique. Ignored if `subplots` is present. If missing but `xaxes` is present, will generate consecutive IDs. */ - yaxes?: any[]; + yaxes?: (YAxisName | '')[]; /** * Vertical space between grid cells, expressed as a fraction of the total height available to one cell. Defaults to 0.1 for coupled-axes grids and 0.3 for independent grids. * Range: [0, 1] From 1c3c5ca9ee56b3b7aa747895a27192d90d3a96ae Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 2 Sep 2026 15:08:12 -0600 Subject: [PATCH 3/4] Update doc --- src/types/ARCHITECTURE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/ARCHITECTURE.md b/src/types/ARCHITECTURE.md index 06d6064a0ee..9463d954d61 100644 --- a/src/types/ARCHITECTURE.md +++ b/src/types/ARCHITECTURE.md @@ -111,11 +111,11 @@ src/types/ │ ├── data.internal.d.ts # CalcData, FullData │ ├── events.d.ts # PlotMouseEvent, PlotlyHTMLElement, etc. │ ├── graph-div.internal.d.ts # GraphDiv, GraphContext -│ ├── layout.d.ts # AxisName, ModeBar behavioral types, Template +│ ├── layout.d.ts # ModeBar behavioral types, Template │ └── layout.internal.d.ts # FullLayout, LayoutSize, SubplotInfo │ ├── lib/ # primitives + the schema-extraction machinery -│ ├── common.d.ts # Color, Datum, TypedArray, MarkerSymbol, ... +│ ├── common.d.ts # Color, Datum, TypedArray, AxisName, ... │ └── attributes.d.ts # AttributeMap, AttrInfo (compile-time validation) │ └── generated/ # machine-generated types From b3257caf8798e50b225bdfde50ac6647d043ab71 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 2 Sep 2026 15:22:15 -0600 Subject: [PATCH 4/4] Add draftlog --- draftlogs/8010_fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/8010_fix.md diff --git a/draftlogs/8010_fix.md b/draftlogs/8010_fix.md new file mode 100644 index 00000000000..3edd85ca072 --- /dev/null +++ b/draftlogs/8010_fix.md @@ -0,0 +1 @@ +- Handle regex enum values when generating schema types [[#8010](https://github.com/plotly/plotly.js/pull/8010)]