basalt-ui 2.6.0 → 2.7.0

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.
Files changed (39) hide show
  1. package/AGENTS.md +15 -15
  2. package/CHANGELOG.md +24 -0
  3. package/MIGRATING.md +50 -12
  4. package/agent/rules/basalt-batteries.md +2 -2
  5. package/agent/rules/basalt-controls.md +1 -1
  6. package/agent/skills/basalt-app/SKILL.md +2 -3
  7. package/agent/skills/basalt-charts/SKILL.md +1 -1
  8. package/agent/skills/basalt-design/SKILL.md +1 -1
  9. package/configs/oxlint-plugin.js +48 -0
  10. package/configs/oxlint-plugin.test.ts +82 -0
  11. package/dist/charts/Bars.d.ts +1 -1
  12. package/dist/charts/Bars.d.ts.map +1 -1
  13. package/dist/charts/Bars.js +9 -2
  14. package/dist/charts/legend.d.ts.map +1 -1
  15. package/dist/charts/legend.js +9 -1
  16. package/dist/controls/form-layout.d.ts +15 -6
  17. package/dist/controls/form-layout.d.ts.map +1 -1
  18. package/dist/forms/field.d.ts +42 -2
  19. package/dist/forms/field.d.ts.map +1 -1
  20. package/dist/forms/field.js +2 -2
  21. package/dist/forms/index.d.ts +4 -5
  22. package/dist/forms/index.d.ts.map +1 -1
  23. package/dist/surfaces.d.ts +1 -1
  24. package/dist/surfaces.js +1 -1
  25. package/dist/table/data-table.d.ts +33 -0
  26. package/dist/table/data-table.d.ts.map +1 -1
  27. package/dist/table/data-table.js +100 -17
  28. package/dist/telemetry.d.ts +5 -0
  29. package/dist/telemetry.d.ts.map +1 -1
  30. package/llms.txt +343 -11
  31. package/package.json +1 -1
  32. package/src/charts/Bars.tsx +13 -3
  33. package/src/charts/legend.tsx +9 -2
  34. package/src/controls/form-layout.tsx +15 -6
  35. package/src/forms/field.ts +78 -3
  36. package/src/forms/index.ts +4 -5
  37. package/src/surfaces.ts +1 -1
  38. package/src/table/data-table.tsx +171 -17
  39. package/src/telemetry.ts +5 -0
@@ -15,7 +15,28 @@
15
15
  * which was the whole reason for the rename in the first place. Write
16
16
  * `key={fieldKey(form, path)} {...inputProps(form, path)}`.
17
17
  */
18
- import type { LooseKeys, UseFormReturnType } from '@mantine/form';
18
+ import type { GetInputPropsReturnType, LooseKeys, UseFormReturnType } from '@mantine/form';
19
+ import type { ReactNode } from 'react';
20
+ /**
21
+ * What `inputProps` returns: Mantine's shape with `error` typed (`any` upstream). `error` is always
22
+ * present — Mantine sets the key on every call, `undefined` on a clean field — so it is `ReactNode`,
23
+ * not optional. It is the one typed way to read a field error: `form.errors` is an index signature
24
+ * (dot access is TS4111), and a raw control inside `FormRow` takes
25
+ * `<FormRow error={inputProps(form, path).error}>`. A Mantine input spread with `inputProps` already
26
+ * renders it; passing it to `FormRow` as well renders it twice.
27
+ */
28
+ export type InputProps = Omit<GetInputPropsReturnType, 'error'> & {
29
+ error: ReactNode;
30
+ };
31
+ type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
32
+ type AllDigits<S extends string> = S extends `${Digit}${infer Rest}` ? Rest extends '' ? true : AllDigits<Rest> : false;
33
+ /** A canonical array index: digits only, no sign, no decimal point, no exponent, no leading zero. */
34
+ type IsIndex<S extends string> = S extends '0' ? true : S extends `0${string}` ? false : AllDigits<S>;
35
+ /** One path segment applied to a value; `never` when the segment does not exist there. */
36
+ type Child<T, K extends string> = NonNullable<T> extends infer N ? N extends readonly (infer V)[] ? IsIndex<K> extends true ? V : never : K extends keyof N ? N[K] : never : never;
37
+ type ValueAt<T, P extends string> = P extends `${infer Head}.${infer Rest}` ? ValueAt<Child<T, Head>, Rest> : Child<T, P>;
38
+ /** `Path` if the value at that dot path is a (possibly optional/nullable) boolean, else `never`. */
39
+ type BooleanAt<Values, Path extends string> = [ValueAt<Values, Path>] extends [never] ? never : [NonNullable<ValueAt<Values, Path>>] extends [boolean] ? Path : never;
19
40
  /**
20
41
  * The spread half: `form.getInputProps(path)`, and nothing else.
21
42
  *
@@ -31,7 +52,25 @@ import type { LooseKeys, UseFormReturnType } from '@mantine/form';
31
52
  * return <TextInput key={fieldKey(form, 'email')} {...inputProps(form, 'email')} label="Email" />
32
53
  * }
33
54
  */
34
- export declare function inputProps<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path): ReturnType<UseFormReturnType<Values>['getInputProps']>;
55
+ export declare function inputProps<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path): InputProps;
56
+ /**
57
+ * The boolean overload: `{ type: 'checkbox' }` binds a boolean field to Mantine `Checkbox` and
58
+ * `Switch`. It returns `onChange` plus `defaultChecked` in `useBasaltForm`'s default `uncontrolled`
59
+ * mode, or `checked` under `mode: 'controlled'` (Mantine decides; there is no `value`).
60
+ *
61
+ * Only a path whose value is a boolean (optional/nullable included) type-checks; a string path is a
62
+ * type error, not a silently wrong binding. Two limits of that check:
63
+ * - The path must be a literal. A `string`-typed path is rejected here (use a literal, or the
64
+ * one-argument form), so the path argument does not autocomplete.
65
+ * - On a union parent, a key present on several members is accepted only when every member types it
66
+ * as boolean; array segments must be canonical indices (`flags.0`, never `flags.1.5` or `flags.01`).
67
+ *
68
+ * @example
69
+ * <Switch key={fieldKey(form, 'active')} {...inputProps(form, 'active', { type: 'checkbox' })} />
70
+ */
71
+ export declare function inputProps<Values extends Record<string, unknown>, Path extends string>(form: UseFormReturnType<Values>, path: BooleanAt<Values, Path>, options: {
72
+ type: 'checkbox';
73
+ }): InputProps;
35
74
  /**
36
75
  * The key half: `form.key(path)`, as a JSX `key` attribute — never spread.
37
76
  *
@@ -43,4 +82,5 @@ export declare function inputProps<Values extends Record<string, unknown>, Path
43
82
  * <TextInput key={fieldKey(form, 'email')} {...inputProps(form, 'email')} />
44
83
  */
45
84
  export declare function fieldKey<Values extends Record<string, unknown>, Path extends LooseKeys<Values>>(form: UseFormReturnType<Values>, path: Path): string;
85
+ export {};
46
86
  //# sourceMappingURL=field.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAIjE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS,SAAS,CAAC,MAAM,CAAC,EAC/F,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,IAAI,EAAE,IAAI,GACT,UAAU,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAExD;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,SAAS,SAAS,CAAC,MAAM,CAAC,EAC7F,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAC/B,IAAI,EAAE,IAAI,GACT,MAAM,CAER"}
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/forms/field.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- function inputProps(form, path) {
2
- return form.getInputProps(path);
1
+ function inputProps(form, path, options) {
2
+ return form.getInputProps(path, options);
3
3
  }
4
4
  function fieldKey(form, path) {
5
5
  return form.key(path);
@@ -6,14 +6,13 @@
6
6
  * `basalt-ui`. Same reason `./charts` and `./tokens` stay off the root barrel, one optional peer
7
7
  * over. Import everything below from `basalt-ui/forms`.
8
8
  *
9
- * Four pieces: the form (`useBasaltForm` + `inputProps`/`fieldKey`), the submit lifecycle
10
- * (`useFormSubmit` + `FormStateProvider`), the list field (`useFieldArray`) and the draft
11
- * (`useFormDraft`). The layout (`FormSection` / `FormRow` / `FormGroup` / `FormActions`) lives in
12
- * `basalt-ui/controls` (it needs no `@mantine/form`); `useForm`/`schemaResolver` come from
13
- * `@mantine/form` directly. Both stopped being re-exported here in 1.39.0.
9
+ * One piece: the form (`useBasaltForm` + `inputProps`/`fieldKey`). The layout (FormRow /
10
+ * FormGroup) lives in `basalt-ui/controls` (it needs no `@mantine/form`); the form hook and
11
+ * schema resolvers come from `@mantine/form` directly.
14
12
  *
15
13
  * Optional peer: @mantine/form ^9.3.0 — install with: bun add @mantine/form
16
14
  */
17
15
  export { useBasaltForm } from './create-form.js';
18
16
  export { inputProps, fieldKey } from './field.js';
17
+ export type { InputProps } from './field.js';
19
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/forms/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
@@ -297,7 +297,7 @@ export declare const SURFACES: {
297
297
  readonly guardKinds: readonly ["raw-selection-control"];
298
298
  readonly pluginRules: readonly ["hand-rolled-filter", "control-outside-home", "bound-control-outside-home", "control-size-literal", "responsive-twin"];
299
299
  readonly advisoryLaws: readonly ["C1 as a cross-file law (a control placed in one file, its home declared in another)", "hand-rolled section headings (argo writes `<Text fw={600} size=\"sm\">` + children, which no AST heuristic matches without false positives)", "C11 — a table/list stating its count when it is not a BasaltDataTable", "C12 — one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"];
300
- readonly description: "The control tier (docs/CONTROLS-SPEC.md §3): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle — no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction), and the form layout FormSection/FormRow/FormGroup/FormActions (moved here from ./forms in 1.36.0 — FormRow is law C1’s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop (useFilterSurface): pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3). PanelRow is that row primitive (label above, hint, mono readout, an `end` control on the label line) and SliderControl is the one bound control with no pill form at all; the counts/max props on MultiSelectFilter render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size=\"ctl\" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.";
300
+ readonly description: "The control tier (docs/CONTROLS-SPEC.md §3). Exports: `FilterSet` (nowrap row + measured +N fold + the mobile Filters (n) sheet), `RangeFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter` (each bound to a FieldHandle — no value/onChange/size, law C2/C5; `NumberFilter` is the field.number lane, a radio list over `options` or a stepper without), `ViewTabs`, `SyncButton` with the `BarAction`/`GlobalAction` types, and the form layout `FormRow`/`FormGroup` (moved here from ./forms in 1.36.0 — `FormRow` is law C1’s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop: pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body — where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md §3); the counts/max props on `MultiSelectFilter` render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size=\"ctl\" internally. Resolves and renders with NO @mantine/dates installed — the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.";
301
301
  readonly optionalPeers: readonly [];
302
302
  readonly globs: {
303
303
  readonly shipped: readonly [];
package/dist/surfaces.js CHANGED
@@ -298,7 +298,7 @@ const SURFACES = {
298
298
  "C11 \u2014 a table/list stating its count when it is not a BasaltDataTable",
299
299
  "C12 \u2014 one shape for refresh/sync (SyncButton); only the alias table sees a renamed copy"
300
300
  ],
301
- description: 'The control tier (docs/CONTROLS-SPEC.md \xA73): FilterSet (nowrap row + measured +N fold + the mobile Filters (n) sheet), RangeFilter/CompareFilter/SelectFilter/MultiSelectFilter/NumberFilter/SearchFilter/ToggleFilter (each bound to a FieldHandle \u2014 no value/onChange/size, law C2/C5; NumberFilter is the field.number lane, a radio list over `options` or a stepper without), ViewTabs, the action/sync family (ActionGroup, OverflowMenu, SyncButton, BarAction/GlobalAction), and the form layout FormSection/FormRow/FormGroup/FormActions (moved here from ./forms in 1.36.0 \u2014 FormRow is law C1\u2019s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop (useFilterSurface): pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body \u2014 where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md \xA73). PanelRow is that row primitive (label above, hint, mono readout, an `end` control on the label line) and SliderControl is the one bound control with no pill form at all; the counts/max props on MultiSelectFilter render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed \u2014 the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
301
+ description: 'The control tier (docs/CONTROLS-SPEC.md \xA73). Exports: `FilterSet` (nowrap row + measured +N fold + the mobile Filters (n) sheet), `RangeFilter`/`SelectFilter`/`MultiSelectFilter`/`NumberFilter`/`SearchFilter`/`ToggleFilter` (each bound to a FieldHandle \u2014 no value/onChange/size, law C2/C5; `NumberFilter` is the field.number lane, a radio list over `options` or a stepper without), `ViewTabs`, `SyncButton` with the `BarAction`/`GlobalAction` types, and the form layout `FormRow`/`FormGroup` (moved here from ./forms in 1.36.0 \u2014 `FormRow` is law C1\u2019s form row and needs no @mantine/form). THREE surfaces, picked by the home and never by a prop: pill in a PageBar row, sheet in the mobile Filters (n) drawer, panel in a PageAside body \u2014 where every control renders an inspector/facet ROW instead of a chip (docs/ASIDE-SPEC.md \xA73); the counts/max props on `MultiSelectFilter` render the Foundry facet list there. Every control owns its own desktop/mobile swap in CSS (C9) and renders size="ctl" internally. Resolves and renders with NO @mantine/dates installed \u2014 the custom date picker is injected through RangeFilter.customPicker from ./controls-dates.',
302
302
  optionalPeers: [],
303
303
  // `@mantine/dates` is an optional peer basaltViteConfig pre-bundles the whole @mantine scope
304
304
  // for — a consumer without the peer (linewatch) must never resolve it through this surface.
@@ -51,6 +51,19 @@ export type DataTableColumnPinning<T = unknown> = {
51
51
  start?: DataTableColumnId<T>[];
52
52
  end?: DataTableColumnId<T>[];
53
53
  };
54
+ /** One per-row action: `BarActionItem`'s vocabulary with the row handed to the handler. */
55
+ export type DataTableRowAction<T = unknown> = {
56
+ key: string;
57
+ label: string;
58
+ /** A lone action with an icon renders as that icon button; anything else folds into the menu. */
59
+ icon?: ReactNode;
60
+ onClick: (row: T) => void;
61
+ danger?: boolean;
62
+ /** A disabled action renders inert and never fires. */
63
+ disabled?: (row: T) => boolean;
64
+ /** A loading action shows the busy state (icon button) or sits inert (menu) and never fires. */
65
+ loading?: (row: T) => boolean;
66
+ };
54
67
  /**
55
68
  * The four boxes `BasaltDataTable` paints: `root` is the outer fragment's replacement wrapper,
56
69
  * `toolbar` is the search/facets/actions row, `table` is whichever body renders — the Mantine
@@ -316,6 +329,26 @@ export type BasaltDataTableProps<T extends RowData> = BasaltProps & SlotStylesPr
316
329
  * ]}
317
330
  */
318
331
  bulkActions?: (rows: T[]) => BarAction[];
332
+ /**
333
+ * Per-row actions as typed data. The table renders them itself in a trailing, pinned,
334
+ * header-less column: an icon button for one action that carries an `icon`, an overflow menu
335
+ * for anything else. Clicking either never fires `onRowActivate`. Never put a `Menu` or
336
+ * `ActionIcon` in a `display` / `cell` renderer (`basalt/control-outside-home`).
337
+ *
338
+ * Pass a STABLE reference — a module constant or a `useMemo` — because the column is rebuilt
339
+ * whenever the array identity changes. A `renderCard` projection owns its own card, so the
340
+ * column renders in the table body only.
341
+ *
342
+ * @example
343
+ * const ROW_ACTIONS: DataTableRowAction<Row>[] = [
344
+ * { key: 'edit', label: 'Edit', icon: <IconEdit />, onClick: (row) => edit(row) },
345
+ * { key: 'delete', label: 'Delete', danger: true, onClick: (row) => remove(row) },
346
+ * ]
347
+ * <BasaltDataTable rowActions={ROW_ACTIONS} rowLabel={(row) => row.name} … />
348
+ */
349
+ rowActions?: DataTableRowAction<T>[];
350
+ /** Names a row for assistive tech in its action controls; default `Row N actions`. */
351
+ rowLabel?: (row: T) => string;
319
352
  /**
320
353
  * Enables the bottom pagination bar ("Showing X–Y of N", rows-per-page `Select`, Mantine
321
354
  * `Pagination`) wired to TanStack's `pagination` state. Client-side by default — see
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/table/data-table.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../src/table/data-table.tsx"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import {
3
+ ActionIcon,
3
4
  Box,
4
5
  Checkbox,
5
6
  Group,
@@ -21,10 +22,11 @@ import { HIT_COARSE } from "../common/hit-floor.js";
21
22
  import { useMeasuredWidths } from "../common/use-measured-widths.js";
22
23
  import { assertRequiredProps, useValidateProps } from "../common/validate.js";
23
24
  import { FilterSet } from "../controls/index.js";
24
- import { ActionGroup, BarActionSlot } from "../controls/actions.js";
25
+ import { ActionGroup, BarActionSlot, OverflowMenu } from "../controls/actions.js";
25
26
  import { EnumFilter } from "../controls/enum-filter.js";
26
27
  import { MultiSelectFilter } from "../controls/multi-select-filter.js";
27
28
  import { CtlSlot } from "../theme/index.js";
29
+ import { IconSlot } from "../theme/icon-slot.js";
28
30
  import { alpha, VX } from "../tokens/index.js";
29
31
  import { WidgetHeader } from "../dashboard/index.js";
30
32
  import { ErrorState } from "../dashboard/query-state.js";
@@ -148,6 +150,74 @@ function selectionColumn() {
148
150
  )
149
151
  };
150
152
  }
153
+ const ACTIONS_COLUMN_ID = "__basalt_actions__";
154
+ const ACTIONS_COLUMN_SIZE = 56;
155
+ function stopsRowActivation(cell) {
156
+ if (cell.column.id === ACTIONS_COLUMN_ID) return true;
157
+ return cell.column.id === SELECT_COLUMN_ID && cell.row.getCanSelect();
158
+ }
159
+ function RowActionCell({
160
+ actions,
161
+ row,
162
+ rowIndex,
163
+ rowLabel
164
+ }) {
165
+ const named = rowLabel?.(row);
166
+ const [only] = actions;
167
+ if (actions.length === 1 && only?.icon !== void 0) {
168
+ const inert = only.disabled?.(row) === true || only.loading?.(row) === true;
169
+ return /* @__PURE__ */ jsx(
170
+ ActionIcon,
171
+ {
172
+ variant: "subtle",
173
+ size: "ctl",
174
+ "aria-label": named === void 0 ? only.label : `${only.label}: ${named}`,
175
+ disabled: only.disabled?.(row) === true,
176
+ loading: only.loading?.(row) === true,
177
+ ...only.danger === true && { color: "red" },
178
+ onClick: () => {
179
+ if (!inert) only.onClick(row);
180
+ },
181
+ children: /* @__PURE__ */ jsx(IconSlot, { children: only.icon })
182
+ }
183
+ );
184
+ }
185
+ const items = actions.map((action) => ({
186
+ key: action.key,
187
+ label: action.label,
188
+ ...action.icon !== void 0 && { icon: action.icon },
189
+ ...action.danger === true && { danger: true },
190
+ // The menu has no busy state of its own, so loading reads as inert there.
191
+ disabled: action.disabled?.(row) === true || action.loading?.(row) === true,
192
+ onClick: () => action.onClick(row)
193
+ }));
194
+ return /* @__PURE__ */ jsx(
195
+ OverflowMenu,
196
+ {
197
+ actions: items,
198
+ trigger: "kebab",
199
+ label: named === void 0 ? `Row ${rowIndex + 1} actions` : `Actions for ${named}`
200
+ }
201
+ );
202
+ }
203
+ function rowActionsColumn(actions, rowLabel) {
204
+ return {
205
+ id: ACTIONS_COLUMN_ID,
206
+ size: ACTIONS_COLUMN_SIZE,
207
+ enableSorting: false,
208
+ enableHiding: false,
209
+ header: () => /* @__PURE__ */ jsx("span", { className: classes.srOnly, children: "Actions" }),
210
+ cell: ({ row }) => /* @__PURE__ */ jsx(
211
+ RowActionCell,
212
+ {
213
+ actions,
214
+ row: row.original,
215
+ rowIndex: row.index,
216
+ rowLabel
217
+ }
218
+ )
219
+ };
220
+ }
151
221
  function BulkActionBar({ count, actions }) {
152
222
  return /* @__PURE__ */ jsxs(Group, { className: classes.bulkBar, gap: "xs", align: "center", wrap: "wrap", mb: "xs", children: [
153
223
  /* @__PURE__ */ jsxs(Text, { className: classes.bulkCount, children: [
@@ -409,6 +479,8 @@ function BasaltDataTable(props) {
409
479
  enableRowSelection = false,
410
480
  getRowId,
411
481
  bulkActions,
482
+ rowActions,
483
+ rowLabel,
412
484
  enablePagination = false,
413
485
  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
414
486
  manualPagination = false,
@@ -461,9 +533,14 @@ function BasaltDataTable(props) {
461
533
  return { ...column, filterFn };
462
534
  });
463
535
  }, [columns, facets]);
536
+ const hasRowActions = rowActions !== void 0 && rowActions.length > 0;
464
537
  const tableColumns = useMemo(
465
- () => enableRowSelection ? [selectionColumn(), ...facetColumns] : facetColumns,
466
- [facetColumns, enableRowSelection]
538
+ () => [
539
+ ...enableRowSelection ? [selectionColumn()] : [],
540
+ ...facetColumns,
541
+ ...hasRowActions ? [rowActionsColumn(rowActions, rowLabel)] : []
542
+ ],
543
+ [facetColumns, enableRowSelection, hasRowActions, rowActions, rowLabel]
467
544
  );
468
545
  const hasFacets = Boolean(facets && facets.length > 0);
469
546
  const breaches = manualPaginationBreaches({
@@ -512,6 +589,11 @@ function BasaltDataTable(props) {
512
589
  );
513
590
  const hasFolded = foldedColumns.size > 0;
514
591
  const foldRows = useRowDisclosure({ data, getRowId });
592
+ const pinningActive = enablePinning || hasRowActions;
593
+ const tablePinning = useMemo(
594
+ () => hasRowActions ? { ...columnPinning, end: [...columnPinning.end, ACTIONS_COLUMN_ID] } : columnPinning,
595
+ [columnPinning, hasRowActions]
596
+ );
515
597
  const table = useTable({
516
598
  features: basaltTableFeatures,
517
599
  data,
@@ -520,7 +602,7 @@ function BasaltDataTable(props) {
520
602
  sorting: tableState.sorting,
521
603
  globalFilter,
522
604
  columnFilters: tableState.columnFilters,
523
- columnPinning,
605
+ columnPinning: tablePinning,
524
606
  columnVisibility,
525
607
  ...enablePagination && { pagination: tableState.pagination },
526
608
  ...enableRowSelection && { rowSelection: tableState.rowSelection }
@@ -549,7 +631,7 @@ function BasaltDataTable(props) {
549
631
  ...manualPagination && pageCount !== void 0 && { pageCount }
550
632
  },
551
633
  enableSorting: sortingEnabled,
552
- enableColumnPinning: enablePinning
634
+ enableColumnPinning: pinningActive
553
635
  });
554
636
  const showSearch = enableGlobalFilter && filteringEnabled;
555
637
  const showFacets = hasFacets && filteringEnabled;
@@ -557,7 +639,7 @@ function BasaltDataTable(props) {
557
639
  const branch = query === void 0 ? void 0 : dataQueryBranch("BasaltDataTable", query);
558
640
  const showSkeleton = branch === void 0 ? isLoading : branch === "pending";
559
641
  const queryError = branch === "error" && query !== void 0 ? query : void 0;
560
- const columnCount = columns.length + (enableRowSelection ? 1 : 0) + (hasFolded ? 1 : 0) - foldedColumns.size;
642
+ const columnCount = columns.length + (enableRowSelection ? 1 : 0) + (hasRowActions ? 1 : 0) + (hasFolded ? 1 : 0) - foldedColumns.size;
561
643
  const selectedModelRows = enableRowSelection ? table.getSelectedRowModel().rows : void 0;
562
644
  const selectedRows = useMemo(
563
645
  () => selectedModelRows?.map((row) => row.original) ?? [],
@@ -567,7 +649,7 @@ function BasaltDataTable(props) {
567
649
  () => bulkActions === void 0 || selectedRows.length === 0 ? void 0 : bulkActions(selectedRows),
568
650
  [bulkActions, selectedRows]
569
651
  );
570
- const headerGroups = enablePinning ? getOrderedHeaderGroups(table) : table.getHeaderGroups();
652
+ const headerGroups = pinningActive ? getOrderedHeaderGroups(table) : table.getHeaderGroups();
571
653
  const rows = table.getRowModel().rows;
572
654
  const pageStickyHeader = !scrolls && stickyHeader === true;
573
655
  const { wrapperRef: containmentRef, contained } = useMeasuredContainment(
@@ -610,14 +692,15 @@ function BasaltDataTable(props) {
610
692
  const canSort = sortingEnabled && header.column.getCanSort();
611
693
  const sorted = header.column.getIsSorted();
612
694
  const toggleSorting = header.column.getToggleSortingHandler();
613
- const pinnedStyle = enablePinning ? getPinnedCellStyle(table, header.column) : void 0;
695
+ const pinnedStyle = pinningActive ? getPinnedCellStyle(table, header.column) : void 0;
614
696
  const align = resolveAlign(header.column);
615
697
  const isSelectColumn = header.column.id === SELECT_COLUMN_ID;
698
+ const isFoldCandidate = !isSelectColumn && header.column.id !== ACTIONS_COLUMN_ID;
616
699
  return /* @__PURE__ */ jsxs(
617
700
  Table.Th,
618
701
  {
619
702
  ...isSelectColumn && { className: classes.selectCell },
620
- ...!isSelectColumn && { [FOLD_ID_ATTR]: header.column.id },
703
+ ...isFoldCandidate && { [FOLD_ID_ATTR]: header.column.id },
621
704
  onClick: canSort ? toggleSorting : void 0,
622
705
  onKeyDown: canSort ? (event) => {
623
706
  if (event.key !== "Enter" && event.key !== " ") return;
@@ -641,7 +724,7 @@ function BasaltDataTable(props) {
641
724
  })
642
725
  ] }, headerGroup.id)) }),
643
726
  /* @__PURE__ */ jsx(Table.Tbody, { children: queryError !== void 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: errorNode }) }) : showSkeleton ? Array.from({ length: skeletonRows }, (_, rowIndex) => /* @__PURE__ */ jsx(Table.Tr, { children: Array.from({ length: columnCount }, (__, colIndex) => /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Skeleton, { height: 16, radius: "sm" }) }, `skeleton-${rowIndex}-${colIndex}`)) }, `skeleton-${rowIndex}`)) : rows.length === 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: columnCount, children: emptyNode }) }) : rows.map((row) => {
644
- const cells = enablePinning ? [
727
+ const cells = pinningActive ? [
645
728
  ...row.getStartVisibleCells(),
646
729
  ...row.getCenterVisibleCells(),
647
730
  ...row.getEndVisibleCells()
@@ -663,18 +746,18 @@ function BasaltDataTable(props) {
663
746
  }
664
747
  ) }),
665
748
  cells.map((cell) => {
666
- const pinnedStyle = enablePinning ? getPinnedCellStyle(table, cell.column) : void 0;
749
+ const pinnedStyle = pinningActive ? getPinnedCellStyle(table, cell.column) : void 0;
667
750
  const align = resolveAlign(cell.column);
668
751
  return /* @__PURE__ */ jsx(
669
752
  Table.Td,
670
753
  {
671
754
  ...cell.column.id === SELECT_COLUMN_ID && {
672
- className: classes.selectCell,
673
- // The label's ::after fills the cell, so a tap on it bubbles to the row —
674
- // stop it only where a tick is possible; a disabled row's tap is a row tap.
675
- ...cell.row.getCanSelect() && {
676
- onClick: (event) => event.stopPropagation()
677
- }
755
+ className: classes.selectCell
756
+ },
757
+ ...stopsRowActivation(cell) && {
758
+ // The label's ::after fills the select cell, so a tap on it bubbles to the
759
+ // row; the actions menu portals out but React bubbles through the portal.
760
+ onClick: (event) => event.stopPropagation()
678
761
  },
679
762
  style: {
680
763
  ...typeof cell.getValue() === "number" && cell.column.columnDef.meta?.numeral !== false ? NUMERIC_CELL_STYLE : void 0,
@@ -90,6 +90,11 @@ export type ChatTurnTrace = {
90
90
  * else. A sink without the hook, or no sink, changes nothing on the wire. basalt has no OTel
91
91
  * dependency; the hook is where the consumer's SDK does the work.
92
92
  *
93
+ * A fetch instrumentation (OTel web, HyperDX) overwrites the returned `traceparent` on same-origin
94
+ * requests with its own fetch span, so the API span lands in a separate trace. Either exclude the
95
+ * chat URL from the instrumentation (`ignoreUrls` in HyperDX and in OTel's fetch instrumentation),
96
+ * or make the turn span the active context around the request.
97
+ *
93
98
  * @example
94
99
  * export const telemetry: Telemetry = Object.assign(onEvent, {
95
100
  * startChatTurn({ threadId }) {
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}