@lightdash/cli 2.290.0 → 2.291.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -96,9 +96,13 @@ turn missing values into zeros, or sample away facts without disclosure.
96
96
  Honor saved options and pivoted results. Empty, invalid, and zero-only states
97
97
  explain the relevant condition in `var(--ll-text-muted)`.
98
98
 
99
- Preserve native **View underlying data** and **Drill into** on eligible
100
- marks, retaining original result-row references through transforms. Marks are
101
- inspectable by pointer and keyboard. Decorative overlays use
99
+ Never hand-build a data-point action menu when
100
+ `useVizContext().pointMenu.enabled` — call `pointMenu.open({ x, y, row, metric })`
101
+ and let Lightdash render it; an in-viz menu is only the fallback for hosts that
102
+ predate the capability. Preserve native **View underlying data** and **Drill
103
+ into** on eligible marks (via `pointMenu`, or the `underlyingData`/`drillDown`
104
+ fallback), retaining original result-row references through transforms. Marks
105
+ are inspectable by pointer and keyboard. Decorative overlays use
102
106
  `pointer-events: none`. Scope SVG gradient/mask/pattern IDs uniquely.
103
107
  Reduced motion disables reveal effects; animation is never the mechanism
104
108
  that makes data visible.
@@ -11,6 +11,8 @@ You are editing a Lightdash **custom chart type** (a reusable visualization, the
11
11
 
12
12
  Read `.claude/skills/reusable-visualization` before editing. It defines everything the component may do: `useVizContext()` is the only channel to the host (data, options, resolved colours), the component runs no query, owns no explore, and never fetches anything itself. App-level SDK APIs (query builder, `useLightdash`, filters, `externalFetch`) do not apply here and must not be introduced.
13
13
 
14
+ Never hand-build a data-point action menu when `useVizContext().pointMenu.enabled` — call `pointMenu.open({ x, y, row, metric })` and let Lightdash render it; an in-viz menu is only the fallback for hosts that predate the capability.
15
+
14
16
  ## The declaration lives in lightdash-app.yml — keep it in lockstep
15
17
 
16
18
  In the in-product builder the declaration is emitted by the generation run; **locally it is the `vizSchema` block in this folder's `lightdash-app.yml`**, and upload round-trips it to the server (without it the chart type never appears in the explorer's chart type picker).
@@ -35,6 +35,7 @@ const {
35
35
  colorPalette,
36
36
  pivotDetails,
37
37
  ready,
38
+ pointMenu,
38
39
  underlyingData,
39
40
  drillDown,
40
41
  } = context;
@@ -57,9 +58,13 @@ const {
57
58
  - `pivotDetails` — the complete backend-pivot layout, or `null` for ordinary rows. See
58
59
  "Backend-pivoted results" below.
59
60
  - `ready` — false until the first context arrives.
60
- - `underlyingData` — host-mediated access to the raw rows behind a clicked data point.
61
- See "Data-point actions" below.
62
- - `drillDown` — host-mediated drill on a clicked data point. See "Data-point
61
+ - `pointMenu` — host-rendered data-point action menu for a clicked mark; this is
62
+ the current data-point action contract. See "Data-point actions" below.
63
+ - `underlyingData` — host-mediated access to the raw rows behind a clicked data point;
64
+ the fallback per-action dialog for hosts that predate `pointMenu`. See
65
+ "Data-point actions" below.
66
+ - `drillDown` — host-mediated drill on a clicked data point; the fallback
67
+ per-action dialog for hosts that predate `pointMenu`. See "Data-point
63
68
  actions" below.
64
69
 
65
70
  Read all of them. Pass the complete `context` to the colour helpers; they preserve model
@@ -169,18 +174,46 @@ declared series fields builds its label from each matching `pivotValues` entry i
169
174
  field order. Backend-pivoted rows do not have safe one-source-row provenance, so the host
170
175
  sets `underlyingData.enabled` to false for them.
171
176
 
172
- ## Data-point actions: underlying data and drill-down
177
+ ## Data-point actions: pointMenu, underlying data, and drill-down
173
178
 
174
- Every chart whose marks satisfy the provenance rule below MUST wire the data-point
175
- action menu. This is part of the component contract, not an optional nicety — a
176
- "keep it minimal" prompt does not waive it. The flags decide at RUNTIME whether each
177
- item shows; you always write the wiring, and it costs nothing visually when disabled.
179
+ Every chart whose marks satisfy the provenance rule below MUST wire the standard
180
+ data-point action. This is part of the component contract, not an optional nicety —
181
+ a "keep it minimal" prompt does not waive it. `pointMenu.enabled` (or, on the
182
+ fallback path, `underlyingData.enabled` and `drillDown.enabled`) decides at RUNTIME
183
+ whether anything shows; you always write the wiring, and it costs nothing visually
184
+ when disabled.
178
185
 
179
- When `underlyingData.enabled` is true, viewers get the standard Lightdash action on
180
- chart marks: click a data point → small action menu → "View underlying data" → the
181
- standard Lightdash dialog listing the raw rows behind that point, with its Download
182
- button. When it is false (host too old, viewer lacks permission, embed), render no menu
183
- item — never a disabled one.
186
+ ### `pointMenu`: the current contract
187
+
188
+ The current contract is a single call on click: `pointMenu.open({ x, y, row, metric,
189
+ fieldId? })`, with `x`/`y` taken from the click event's `clientX`/`clientY`. Lightdash
190
+ renders the entire menu — copy value, view underlying data, drill, dashboard
191
+ cross-filtering — over the viz and owns all subsequent UI; the component renders no
192
+ menu of its own. `pointMenu.open` resolves `{ shown: boolean }`; `shown: false` means
193
+ the host had no applicable action for that point, and the component still renders
194
+ nothing extra.
195
+
196
+ ```tsx
197
+ onClick={(event, datum) => {
198
+ if (pointMenu.enabled) {
199
+ pointMenu
200
+ .open({
201
+ x: event.clientX,
202
+ y: event.clientY,
203
+ row: datum.sourceRow,
204
+ metric: 'value',
205
+ })
206
+ .catch(() => {});
207
+ return;
208
+ }
209
+ // Fallback for hosts that predate the point menu:
210
+ openLocalActionMenu(event, datum);
211
+ }}
212
+ ```
213
+
214
+ Only when `pointMenu.enabled` is false (a host that predates the capability) does the
215
+ component fall back to building its own in-viz action menu from `underlyingData.open`
216
+ and `drillDown.open`, as described in the rest of this section.
184
217
 
185
218
  Provenance is the contract: **every interactive datum keeps a reference to its
186
219
  untransformed source row.** When mapping `rows` into chart data, carry the row:
@@ -189,13 +222,25 @@ untransformed source row.** When mapping `rows` into chart data, carry the row:
189
222
  const data = rows.map((row) => ({
190
223
  label: getFormatted(row, catField),
191
224
  value: Number(getRaw(row, valField) ?? 0),
192
- sourceRow: row, // ← required for underlying data
225
+ sourceRow: row, // ← required for the data-point action
193
226
  }));
194
227
  ```
195
228
 
196
229
  Only attach the action where one mark maps to exactly ONE source row and ONE metric-slot
197
230
  field. A mark that aggregates several rows (a binned bucket, a "top N + other" slice)
198
- gets no underlying-data item.
231
+ gets no data-point action.
232
+
233
+ ### Fallback: the in-viz action menu
234
+
235
+ Everything through "Open the menu at the data point" below applies only when
236
+ `pointMenu.enabled` is false. It calls the two per-action APIs directly and builds
237
+ its own accessible in-viz menu instead of delegating to a Lightdash-rendered one.
238
+
239
+ When `underlyingData.enabled` is true, viewers get the fallback action on chart
240
+ marks: click a data point → small action menu → "View underlying data" → the
241
+ standard Lightdash dialog listing the raw rows behind that point, with its Download
242
+ button. When it is false (host too old, viewer lacks permission, embed), render no menu
243
+ item — never a disabled one.
199
244
 
200
245
  On click, send the intent and render nothing else. Lightdash opens the dialog outside
201
246
  the viz and owns its table, loading, error, sorting, and download controls:
@@ -280,7 +325,7 @@ const registerMark = (key, node) => {
280
325
  if (node) markElements.current.set(key, node);
281
326
  else markElements.current.delete(key);
282
327
  };
283
- const openPointMenu = (datum, anchor) => {
328
+ const openFallbackMenu = (datum, anchor) => {
284
329
  if (!underlyingData.enabled && !drillDown.enabled) return;
285
330
  const rect = anchor.activator.getBoundingClientRect();
286
331
  setTooltipVisible(false);
@@ -318,8 +363,9 @@ const openPointMenu = (datum, anchor) => {
318
363
 
319
364
  ### Interaction hygiene
320
365
 
321
- One floating surface at a time, and no leftover emphasis — native Lightdash
322
- charts show a menu OR a tooltip, never both, and clicking leaves no mark
366
+ These rules apply on both paths — the host-rendered `pointMenu` and the fallback
367
+ in-viz menu. One floating surface at a time, and no leftover emphasis — native
368
+ Lightdash charts show a menu OR a tooltip, never both, and clicking leaves no mark
323
369
  highlighted:
324
370
 
325
371
  - **Opening the action menu closes the tooltip.** Drive tooltip visibility
@@ -545,9 +591,10 @@ matters. Keep that help reusable across queries: describe the chart contract rat
545
591
  specific dataset.
546
592
 
547
593
  Finally, if any mark maps to exactly one source row, the data-point action
548
- menu is wired: each interactive datum carries `sourceRow`, the underlying-data
549
- action is gated on `underlyingData.enabled`, and the drill action is gated on
550
- `drillDown.enabled`. Omitting the menu on a chart whose marks satisfy
594
+ is wired: each interactive datum carries `sourceRow`, and the click handler calls
595
+ `pointMenu.open({ x, y, row, metric })` when `pointMenu.enabled`, falling back to
596
+ an in-viz menu — gated on `underlyingData.enabled` and `drillDown.enabled` — only
597
+ when it is not. Omitting the action on a chart whose marks satisfy
551
598
  provenance is a defect, not a simplification. Then click a mark mentally:
552
599
  the tooltip closes, nothing stays highlighted, and only the menu remains —
553
600
  one floating surface at a time.
@@ -28,6 +28,7 @@ Users describe features by what they see in the Lightdash editor. Translate:
28
28
  | external API data | `external-fetch` | app code opt-in |
29
29
  | runs inside a dashboard tile | `viz-context` | required — see below |
30
30
  | reusable chart/table with pivoted results | `viz-pivoted-results` | required — see below |
31
+ | "Data point actions" — native context menu on a data-point click (copy, view underlying data, drill, cross-filter; viz only) | `point-action-menu` | app code opt-in |
31
32
  | "view underlying data", raw rows behind a point (viz only) | `viz-underlying-data` | app code opt-in |
32
33
  | Lightdash-owned "View underlying data" dialog (viz only) | `viz-host-underlying-data` | app code opt-in |
33
34
  | light/dark mode, "matches my Lightdash theme" | `follow-host-theme` | CSS tokens — see below |
@@ -106,10 +107,23 @@ order, `sortBy` describes result ordering, `totalColumnCount` exposes truncation
106
107
 
107
108
  - `drill-down`: `drillDown(...)` derives a more detailed query from a clicked
108
109
  result row — wire it to click handlers on charts/rows.
109
- - `viz-drill-down`: in a reusable visualization, the data-point action menu
110
- offers "Drill into …" when `useVizContext().drillDown.enabled`; selection
111
- calls `drillDown.open({ row, metric })` and Lightdash opens its drill
112
- dialog. Distinct from `drill-down`, which is the full-app query helper.
110
+ - `point-action-menu` (vizs only): the primary data-point-action contract. On a
111
+ data-point click, when `useVizContext().pointMenu.enabled`, call
112
+ `pointMenu.open({ x: event.clientX, y: event.clientY, row: datum.sourceRow, metric: '<field name>' })`
113
+ and render no menu of your own — Lightdash renders the entire native menu
114
+ (copy value, view underlying data, drill, dashboard cross-filtering) and
115
+ owns all subsequent UI. `viz-drill-down` and
116
+ `viz-underlying-data`/`viz-host-underlying-data` below are the
117
+ `pointMenu.enabled === false` fallback for older hosts, not a separate
118
+ feature to wire in addition.
119
+ - `viz-drill-down`: in a reusable visualization, when
120
+ `useVizContext().pointMenu.enabled`, call `pointMenu.open({ x, y, row, metric })`
121
+ and render no menu of your own — Lightdash's menu offers "Drill into …".
122
+ Only when `pointMenu.enabled` is false, fall back to a local data-point
123
+ action menu gated on `drillDown.enabled` and call
124
+ `drillDown.open({ row, metric })` on selection. Either way Lightdash opens
125
+ its drill dialog. Distinct from `drill-down`, which is the full-app query
126
+ helper.
113
127
  - `url-state`: `useUrlState(...)` syncs a piece of app state into the page URL
114
128
  so views can be shared and restored.
115
129
  - `gsheet-export`: `exportToSheets(...)` sends tabular results to a new
@@ -137,11 +151,13 @@ order, `sortBy` describes result ordering, `totalColumnCount` exposes truncation
137
151
  visible — never mount all tabs unconditionally (interactive loads must stay
138
152
  lazy).
139
153
  - `viz-underlying-data` and `viz-host-underlying-data` (vizs only): keep the
140
- untransformed `sourceRow` on each interactive datum, gate a data-point action menu on
141
- `useVizContext().underlyingData.enabled`, and call
142
- `underlyingData.open({ row, metric })`. Lightdash renders the dialog and
143
- owns its table, loading, error, and download controls. Full contract in the
144
- `reusable-visualization` skill.
154
+ untransformed `sourceRow` on each interactive datum. When
155
+ `useVizContext().pointMenu.enabled`, call `pointMenu.open({ x, y, row, metric })`
156
+ and render no menu of your own. Only when `pointMenu.enabled` is false, fall
157
+ back to a local data-point action menu gated on `underlyingData.enabled` and
158
+ call `underlyingData.open({ row, metric })` on selection. Either way,
159
+ Lightdash renders the dialog and owns its table, loading, error, and
160
+ download controls. Full contract in the `reusable-visualization` skill.
145
161
 
146
162
  ## After a template upgrade
147
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/cli",
3
- "version": "2.290.0",
3
+ "version": "2.291.1",
4
4
  "description": "Lightdash CLI tool",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,8 +22,8 @@
22
22
  "@actions/core": "1.11.1",
23
23
  "@aws-sdk/credential-providers": "3.995.0",
24
24
  "@casl/ability": "6.8.0",
25
- "@lightdash/common": "2.290.0",
26
- "@lightdash/warehouses": "2.290.0",
25
+ "@lightdash/common": "2.291.1",
26
+ "@lightdash/warehouses": "2.291.1",
27
27
  "@sidvind/better-ajv-errors": "7.0.0",
28
28
  "@types/columnify": "1.5.1",
29
29
  "ajv": "8.18.0",