From 46b8998f6ebbfa7fbf6d0ee84345ca808e6155b5 Mon Sep 17 00:00:00 2001 From: Norman Fomferra Date: Tue, 10 Dec 2024 08:44:22 +0100 Subject: [PATCH] respecting multiple signal syntax forms --- .../vega/hooks/useSignalListeners.test.ts | 38 ++++++++++++++++--- .../plugins/vega/hooks/useSignalListeners.ts | 35 +++++++++++------ 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.test.ts b/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.test.ts index fc2f92ec..b6d38713 100644 --- a/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.test.ts +++ b/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.test.ts @@ -24,14 +24,27 @@ const chartWithSelect: TopLevelSpec = { ...chart, params: [ { - name: "points", - select: { type: "point", fields: ["x", "a"], on: "click" }, + name: "sel_point", + select: "point", + }, + { + name: "sel_interval", + select: "interval", + }, + { + name: "sel_point_a", + select: { on: "click", type: "point", fields: ["x", "a"] }, + }, + // Event not supported yet + { + name: "sel_interval_b", + select: { on: "wheel", type: "interval", fields: ["x", "b"] }, }, ], }; describe("useSignalListeners", () => { - it("should return a stable record", () => { + it("should return a stable record wo signals", () => { const { result, rerender } = renderHook(() => useSignalListeners(chart, "VegaChart", "my_chart", () => {}), ); @@ -43,6 +56,19 @@ describe("useSignalListeners", () => { expect(signalHandlers1).toBe(signalHandlers1); }); + it("should support different signal types", () => { + const { result } = renderHook(() => + useSignalListeners(chartWithSelect, "VegaChart", "my_chart", () => {}), + ); + const signalHandlers = result.current; + expect(signalHandlers).toBeDefined(); + expect(signalHandlers["sel_point"]).toBeTypeOf("function"); + expect(signalHandlers["sel_interval"]).toBeTypeOf("function"); + expect(signalHandlers["sel_point_a"]).toBeTypeOf("function"); + // "wheel" not supported + expect(signalHandlers["sel_point_b"]).toBeUndefined(); + }); + it("should call onChange", () => { const { recordedEvents, onChange } = createChangeHandler(); const { result } = renderHook(() => @@ -50,16 +76,16 @@ describe("useSignalListeners", () => { ); const signalHandlers = result.current; expect(signalHandlers).toBeDefined(); - const signalHandler = signalHandlers["points"]; + const signalHandler = signalHandlers["sel_point_a"]; expect(signalHandler).toBeTypeOf("function"); act(() => { - signalHandler("points", [1, 2, 3]); + signalHandler("sel_point_a", [1, 2, 3]); }); expect(recordedEvents.length).toBe(1); expect(recordedEvents[0]).toEqual({ componentType: "VegaChart", id: "my_chart", - property: "points", + property: "sel_point_a", value: [1, 2, 3], }); }); diff --git a/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.ts b/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.ts index 4b2b326c..125621f6 100644 --- a/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.ts +++ b/chartlets.js/packages/lib/src/plugins/vega/hooks/useSignalListeners.ts @@ -13,15 +13,18 @@ type SignalHandler = (signalName: string, signalValue: unknown) => void; * only interested in extracting the handlers, the following * properties are required. */ -type SelectionParameter = { name: string; select: { on: string } }; +type SelectionParameter = { + name: string; + select: "point" | "interval" | { type: "point" | "interval"; on: string }; +}; const isSelectionParameter = (param: unknown): param is SelectionParameter => isObject(param) && - "name" in param && - "select" in param && - isObject(param.select) && - param.select?.on !== null && - isString(param.select.on); + (param.select === "point" || + param.select === "interval" || + (isObject(param.select) && + (param.select.type === "point" || param.select.type === "interval") && + isString(param.select.on))); export function useSignalListeners( chart: TopLevelSpec | null | undefined, @@ -36,16 +39,23 @@ export function useSignalListeners( * have so that we can create those listeners with the `name` specified in * the event-listener object. */ - const signalNames = useMemo((): Record => { - const signalNames: Record = {}; + const signalNames = useMemo(() => { + const signalNames: [string, string][] = []; if (!chart || !chart.params) { return signalNames; } return chart.params .filter(isSelectionParameter) - .reduce((paramNames, param) => { - paramNames[param.select.on] = param.name; - return paramNames; + .reduce((signalNames, param) => { + // https://vega.github.io/vega-lite/docs/parameter.html#select + if (param.select === "point") { + signalNames.push(["click", param.name]); + } else if (param.select === "interval") { + signalNames.push(["drag", param.name]); + } else { + signalNames.push([param.select.on, param.name]); + } + return signalNames; }, signalNames); }, [chart]); @@ -74,10 +84,11 @@ export function useSignalListeners( */ const signalHandlers: Record = { click: handleClickSignal, + drag: handleClickSignal, }; const signalListeners: Record = {}; - Object.entries(signalNames).forEach(([event, signalName]) => { + signalNames.forEach(([event, signalName]) => { if (signalHandlers[event]) { signalListeners[signalName] = signalHandlers[event]; } else {