From bdac040ee759c75c5ddc5890a77dad382d20641b Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 23 Oct 2026 11:57:21 -0600 Subject: [PATCH 1/4] chore: Add missing types for events --- lib/index.d.ts | 11 +++- src/types/core/events.d.ts | 112 +++++++++++++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 5 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index da892f0a9f8..f0fb53d9b79 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -81,15 +81,20 @@ export type { export type { BeforePlotEvent, + ButtonClickedEvent, ClickAnnotationEvent, FrameAnimationEvent, + IcicleClickEvent, + IciclePlotDatum, LegendClickEvent, + LegendTitleClickEvent, PlotCoordinate, PlotDatum, PlotHoverEvent, PlotlyEventName, PlotlyHTMLElement, PlotMouseEvent, + PlotReactEvent, PlotRelayoutEvent, PlotRestyleEvent, PlotRestyleEventUpdate, @@ -97,12 +102,16 @@ export type { PlotScene, PlotSelectedData, PlotSelectionEvent, + PlotUpdateEvent, SelectionRange, SliderChangeEvent, SliderEndEvent, SliderStartEvent, SunburstClickEvent, - SunburstPlotDatum + SunburstPlotDatum, + TreemapClickEvent, + TreemapPlotDatum, + WebGLContextLostEvent } from '../src/types/core/events'; // --------------------------------------------------------------------------- diff --git a/src/types/core/events.d.ts b/src/types/core/events.d.ts index 8023f0dba74..c6f128e63b1 100644 --- a/src/types/core/events.d.ts +++ b/src/types/core/events.d.ts @@ -14,6 +14,7 @@ import type { LayoutAxis, Slider, Transition, + UpdateMenu } from '../generated/schema'; import type { Datum } from '../lib/common'; import type { Config } from './config'; @@ -113,6 +114,8 @@ export interface PlotMouseEvent { xPixel?: number; /** Cursor y position in pixels from the graph div's top-left corner */ yPixel?: number; + /** Category constraints of the hovered or clicked path, set by `parcats` traces only. */ + constraints?: Record; } /** Payload for `plotly_hover` — augments `PlotMouseEvent` with axis values. */ @@ -157,6 +160,14 @@ export type PlotRestyleEvent = [PlotRestyleEventUpdate, number[]]; */ export type PlotRelayoutEvent = LayoutUpdate; +/** Payload for `plotly_update`: the restyle and relayout payloads of one `Plotly.update` call. */ +export interface PlotUpdateEvent { + /** Restyle part of the update: the attribute changes and the affected trace indices. */ + data: PlotRestyleEvent; + /** Relayout part of the update. */ + layout: PlotRelayoutEvent; +} + // --------------------------------------------------------------------------- // 3D scene / annotation / animation / legend events // --------------------------------------------------------------------------- @@ -235,6 +246,45 @@ export interface LegendClickEvent { fullLayout: Partial; } +/** + * Payload for `plotly_legendtitleclick` and `plotly_legendtitledoubleclick`. + * Return `false` from the handler to suppress Plotly's default title click behavior. + */ +export interface LegendTitleClickEvent { + /** The original DOM mouse event. */ + event: MouseEvent; + /** Id of the legend whose title was clicked, for example `'legend2'`. */ + legendId: string; + /** User-supplied data array. */ + data: Data[]; + /** User-supplied layout object. */ + layout: Partial; + /** Fully-resolved data array (defaults applied). */ + fullData: Data[]; + /** Fully-resolved layout object (defaults applied). */ + fullLayout: Partial; +} + +/** Payload for `plotly_react`, emitted after a `Plotly.react` call that left the config unchanged. */ +export interface PlotReactEvent { + /** User-supplied config object. */ + config: Partial; + /** User-supplied data array. */ + data: Data[]; + /** User-supplied layout object. */ + layout: Partial; +} + +/** Payload for `plotly_buttonclicked`, emitted when a button of an updatemenu is clicked. */ +export interface ButtonClickedEvent { + /** The updatemenu that holds the button. */ + menu: UpdateMenu; + /** The clicked button. */ + button: NonNullable[number]; + /** Index of the active button, or `-1` if none is active. */ + active: number; +} + // --------------------------------------------------------------------------- // Slider events // --------------------------------------------------------------------------- @@ -268,11 +318,23 @@ export interface SliderEndEvent { step: SliderStep; } +// --------------------------------------------------------------------------- +// WebGL context events +// --------------------------------------------------------------------------- + +/** Payload for `plotly_webglcontextlost`, emitted when a WebGL context is lost. */ +export interface WebGLContextLostEvent { + /** The original DOM `webglcontextlost` event. */ + event: WebGLContextEvent; + /** Identifier of the regl layer or the gl3d scene that lost its context. */ + layer: string; +} + // --------------------------------------------------------------------------- // Sunburst / before-plot // --------------------------------------------------------------------------- -/** Point payload for `plotly_sunburstclick`. */ +/** Point payload for `plotly_sunburstclick`. Treemap and icicle traces emit the same point shape. */ export interface SunburstPlotDatum { /** Color value associated with the sector. */ color: number; @@ -306,16 +368,28 @@ export interface SunburstPlotDatum { value: number; } -/** Payload for `plotly_sunburstclick`, including the next-level entry. */ +/** Point payload for `plotly_treemapclick`. Same shape as `SunburstPlotDatum`. */ +export type TreemapPlotDatum = SunburstPlotDatum; + +/** Point payload for `plotly_icicleclick`. Same shape as `SunburstPlotDatum`. */ +export type IciclePlotDatum = SunburstPlotDatum; + +/** Payload for `plotly_sunburstclick`. Treemap and icicle traces emit the same payload, including the next-level entry. */ export interface SunburstClickEvent { /** The original DOM mouse event. */ event: MouseEvent; - /** Sector id that becomes the entry on click. */ - nextLevel: string; + /** Sector id that becomes the entry on click. Absent when the click does not change the entry, as on a sunburst root or leaf. */ + nextLevel?: string; /** Clicked sector(s). */ points: SunburstPlotDatum[]; } +/** Payload for `plotly_treemapclick`. Same shape as `SunburstClickEvent`. */ +export type TreemapClickEvent = SunburstClickEvent; + +/** Payload for `plotly_icicleclick`. Same shape as `SunburstClickEvent`. */ +export type IcicleClickEvent = SunburstClickEvent; + /** * Payload for `plotly_beforeplot`. Returning `false` from the handler * cancels the plot operation. @@ -349,12 +423,27 @@ export interface PlotlyHTMLElement extends HTMLElement { on(event: 'plotly_restyle', callback: (data: PlotRestyleEvent) => void): void; /** Subscribe to relayout events (layout updates, including in-progress). */ on(event: 'plotly_relayout' | 'plotly_relayouting', callback: (event: PlotRelayoutEvent) => void): void; + /** Subscribe to update events (`Plotly.update` calls). */ + on(event: 'plotly_update', callback: (event: PlotUpdateEvent) => void): void; + /** Subscribe to WebGL context loss events. */ + on(event: 'plotly_webglcontextlost', callback: (event: WebGLContextLostEvent) => void): void; /** Subscribe to annotation click events. */ on(event: 'plotly_clickannotation', callback: (event: ClickAnnotationEvent) => void): void; /** Subscribe to per-frame animation events. */ on(event: 'plotly_animatingframe', callback: (event: FrameAnimationEvent) => void): void; /** Subscribe to legend click events; return `false` to suppress default toggle. */ on(event: 'plotly_legendclick' | 'plotly_legenddoubleclick', callback: (event: LegendClickEvent) => boolean): void; + /** Subscribe to legend title click events; return `false` to suppress default behavior. */ + on( + event: 'plotly_legendtitleclick' | 'plotly_legendtitledoubleclick', + callback: (event: LegendTitleClickEvent) => boolean + ): void; + /** Subscribe to react events. */ + on(event: 'plotly_react', callback: (event: PlotReactEvent) => void): void; + /** Subscribe to updatemenu button click events. */ + on(event: 'plotly_buttonclicked', callback: (event: ButtonClickedEvent) => void): void; + /** Subscribe to pre-hover events; return `false` to cancel the hover. */ + on(event: 'plotly_beforehover', callback: (event: MouseEvent) => boolean): void; /** Subscribe to slider change events. */ on(event: 'plotly_sliderchange', callback: (event: SliderChangeEvent) => void): void; /** Subscribe to slider end-of-interaction events. */ @@ -363,6 +452,10 @@ export interface PlotlyHTMLElement extends HTMLElement { on(event: 'plotly_sliderstart', callback: (event: SliderStartEvent) => void): void; /** Subscribe to sunburst sector click events. */ on(event: 'plotly_sunburstclick', callback: (event: SunburstClickEvent) => void): void; + /** Subscribe to treemap sector click events. */ + on(event: 'plotly_treemapclick', callback: (event: TreemapClickEvent) => void): void; + /** Subscribe to icicle sector click events. */ + on(event: 'plotly_icicleclick', callback: (event: IcicleClickEvent) => void): void; /** Subscribe to generic plotly events (low-level catch-all). */ on(event: 'plotly_event', callback: (data: any) => void): void; /** Subscribe to pre-plot events; return `false` to cancel plotting. */ @@ -373,13 +466,16 @@ export interface PlotlyHTMLElement extends HTMLElement { | 'plotly_afterexport' | 'plotly_afterplot' | 'plotly_animated' + | 'plotly_animating' | 'plotly_animationinterrupted' | 'plotly_autosize' | 'plotly_beforeexport' | 'plotly_deselect' | 'plotly_doubleclick' + | 'plotly_exportfail' | 'plotly_framework' | 'plotly_redraw' + | 'plotly_transitioned' | 'plotly_transitioning' | 'plotly_transitioninterrupted', callback: () => void @@ -401,6 +497,7 @@ export type PlotlyEventName = | 'plotly_afterexport' | 'plotly_afterplot' | 'plotly_animated' + | 'plotly_animating' | 'plotly_animatingframe' | 'plotly_animationinterrupted' | 'plotly_autosize' @@ -412,10 +509,15 @@ export type PlotlyEventName = | 'plotly_clickannotation' | 'plotly_deselect' | 'plotly_doubleclick' + | 'plotly_exportfail' | 'plotly_framework' | 'plotly_hover' + | 'plotly_icicleclick' | 'plotly_legendclick' | 'plotly_legenddoubleclick' + | 'plotly_legendtitleclick' + | 'plotly_legendtitledoubleclick' + | 'plotly_react' | 'plotly_redraw' | 'plotly_relayout' | 'plotly_relayouting' @@ -426,8 +528,10 @@ export type PlotlyEventName = | 'plotly_sliderend' | 'plotly_sliderstart' | 'plotly_sunburstclick' + | 'plotly_transitioned' | 'plotly_transitioning' | 'plotly_transitioninterrupted' | 'plotly_treemapclick' | 'plotly_unhover' + | 'plotly_update' | 'plotly_webglcontextlost'; From 4393e526cb7d25798f7629bfe12720fb54617bde Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 23 Oct 2026 12:14:28 -0600 Subject: [PATCH 2/4] Rename test file --- test/types/{relayout_event.ts => events.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/types/{relayout_event.ts => events.ts} (100%) diff --git a/test/types/relayout_event.ts b/test/types/events.ts similarity index 100% rename from test/types/relayout_event.ts rename to test/types/events.ts From 830f530f5ae2f5def06bed60115fb0fe5a2069ef Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 23 Oct 2026 12:14:38 -0600 Subject: [PATCH 3/4] Add more event type tests --- test/types/events.ts | 46 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/types/events.ts b/test/types/events.ts index 56d26a90e35..e14ba404306 100644 --- a/test/types/events.ts +++ b/test/types/events.ts @@ -1,11 +1,12 @@ /** - * Consumer-side checks for the `plotly_relayout` event payload. + * Consumer-side checks for the event payload types and the `on()` overloads. * * Nothing here runs. See `update_api.ts`. */ -import type { PlotRelayoutEvent } from '../../lib/index'; +import type { PlotlyHTMLElement, PlotRelayoutEvent, TreemapPlotDatum } from '../../lib/index'; +declare const gd: PlotlyHTMLElement; declare const event: PlotRelayoutEvent; // A range value can be a number or a date string @@ -21,3 +22,44 @@ const autosize: boolean | undefined = event.autosize; // @ts-expect-error unknown top-level key const typo = event.titl; + +gd.on('plotly_update', (event) => { + const attributes: Record = event.data[0]; + const traces: number[] = event.data[1]; + const autosizeInUpdate: boolean | undefined = event.layout.autosize; +}); + +gd.on('plotly_webglcontextlost', (event) => { + const domEvent: WebGLContextEvent = event.event; + const layer: string = event.layer; +}); + +gd.on('plotly_react', (event) => { + const dataLength: number = event.data.length; +}); + +gd.on('plotly_legendtitleclick', (event) => { + const legendId: string = event.legendId; + return false; +}); + +gd.on('plotly_buttonclicked', (event) => { + const active: number = event.active; +}); + +gd.on('plotly_beforehover', (event) => { + const x: number = event.clientX; + return true; +}); + +gd.on('plotly_icicleclick', (event) => { + const nextLevel: string | undefined = event.nextLevel; +}); + +gd.on('plotly_treemapclick', (event) => { + const points: TreemapPlotDatum[] = event.points; +}); + +gd.on('plotly_animating', () => {}); +gd.on('plotly_transitioned', () => {}); +gd.on('plotly_exportfail', () => {}); From 708ef2c29a28008af305499898506e7645d6a5fd Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 23 Oct 2026 12:33:16 -0600 Subject: [PATCH 4/4] Add draftlog --- draftlogs/8145_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/8145_add.md diff --git a/draftlogs/8145_add.md b/draftlogs/8145_add.md new file mode 100644 index 00000000000..d55a68b379b --- /dev/null +++ b/draftlogs/8145_add.md @@ -0,0 +1 @@ + - Add TypeScript declarations for lifecycle and WebGL context events [[#8145](https://github.com/plotly/plotly.js/pull/8145)]