@xenosystem/blocks 0.13.19 → 0.13.21

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.
@@ -194,8 +194,8 @@ function moveCell(from, delta, bounds) {
194
194
  }
195
195
 
196
196
  // src/data/table/numeric.ts
197
- function parseNumeric(text5) {
198
- const trimmed = String(text5).trim();
197
+ function parseNumeric(text6) {
198
+ const trimmed = String(text6).trim();
199
199
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
200
200
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
201
201
  if (!match) return { ok: false, reason: "nan" };
@@ -276,6 +276,18 @@ var TableController = class {
276
276
  }
277
277
  /* ── Subscription ──────────────────────────────────────────────────────── */
278
278
  /** Subscribe to state changes. Returns an unsubscribe function. */
279
+ /** The scroller's measured viewport — the view state windows rows AND columns from it (`table/Scroller` reports it). */
280
+ viewport = { width: 0, height: 0, scrollTop: 0, scrollLeft: 0 };
281
+ /** The scroller reports its viewport; the view state windows the grid from it. */
282
+ setViewport(viewport) {
283
+ const v = this.viewport;
284
+ if (v.width === viewport.width && v.height === viewport.height && v.scrollTop === viewport.scrollTop && v.scrollLeft === viewport.scrollLeft) return;
285
+ this.viewport = { ...viewport };
286
+ this.notify();
287
+ }
288
+ getViewport() {
289
+ return this.viewport;
290
+ }
279
291
  subscribe = (listener) => {
280
292
  this.listeners.add(listener);
281
293
  return () => this.listeners.delete(listener);
@@ -549,11 +561,11 @@ var TableController = class {
549
561
  return true;
550
562
  }
551
563
  /** Update the in-flight draft. Emits `editPreview` only when `livePreview` is on. */
552
- updateDraft(text5) {
564
+ updateDraft(text6) {
553
565
  if (!this.editing) return;
554
- this.editing = { ...this.editing, draft: text5 };
566
+ this.editing = { ...this.editing, draft: text6 };
555
567
  if (this.livePreview) {
556
- const parsed = this.parseDraft(this.editing.columnId, text5);
568
+ const parsed = this.parseDraft(this.editing.columnId, text6);
557
569
  if (parsed.ok) {
558
570
  this.host.emit("editPreview", this.buildEdit(this.editing, parsed.value));
559
571
  }
@@ -605,12 +617,12 @@ var TableController = class {
605
617
  };
606
618
  }
607
619
  /** Parse a draft according to the column's declared type. */
608
- parseDraft(columnId, text5) {
620
+ parseDraft(columnId, text6) {
609
621
  const column = this.columnById(columnId);
610
- const trimmed = text5.trim();
622
+ const trimmed = text6.trim();
611
623
  if (column?.type === "number" || column?.type === "integer" || column?.type === "duration") {
612
624
  if (trimmed === "") return { ok: true, value: null };
613
- const parsed = parseNumeric(text5);
625
+ const parsed = parseNumeric(text6);
614
626
  if (!parsed.ok) return { ok: false, reason: parsed.reason };
615
627
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOfColumn(column)) };
616
628
  }
@@ -627,7 +639,7 @@ var TableController = class {
627
639
  value: trimmed === "" ? [] : trimmed.split(",").map((part) => part.trim()).filter(Boolean)
628
640
  };
629
641
  }
630
- return { ok: true, value: trimmed === "" ? null : text5 };
642
+ return { ok: true, value: trimmed === "" ? null : text6 };
631
643
  }
632
644
  rowById(rowId) {
633
645
  return this.result?.rows.find((r) => r.id === rowId);
@@ -929,19 +941,33 @@ var tableManifest = {
929
941
  };
930
942
 
931
943
  // src/data/table/react/TablePanelView.tsx
932
- import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
944
+ import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
933
945
  import {
934
946
  Badge,
947
+ Column,
935
948
  EmptyState,
936
949
  ErrorState,
950
+ GridCell,
951
+ GridHeader,
952
+ GridHeaderCell,
953
+ GridRow,
954
+ GridSpacer,
937
955
  IconButton,
956
+ Input,
938
957
  LoadingState,
958
+ PivotCell,
959
+ PivotRow,
960
+ PivotTable,
939
961
  ScrollArea,
940
962
  SegmentedControl,
941
963
  StatusBar,
964
+ Text,
942
965
  Toolbar,
943
- ToolbarGroup
966
+ ToolbarGroup,
967
+ VirtualWindow
944
968
  } from "@xenosystem/components/chrome";
969
+
970
+ // src/data/table/declaration.ts
945
971
  import { alignmentFor, formatValue as formatValue2 } from "@xenosystem/data-core";
946
972
 
947
973
  // src/data/table/virtualize.ts
@@ -968,448 +994,439 @@ function safe(n, fallback) {
968
994
  return typeof n === "number" && Number.isFinite(n) ? n : fallback;
969
995
  }
970
996
 
971
- // src/data/table/react/TablePanelView.tsx
972
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
973
- function TablePanelView({
974
- controller,
975
- showRowNumbers = false,
976
- rowHeight = 24,
977
- showFooter = true,
978
- zebra = false
979
- }) {
980
- const state = useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
981
- const scrollRef = useRef(null);
982
- const [viewport, setViewport] = useState({ width: 0, height: 0, scrollTop: 0, scrollLeft: 0 });
983
- useEffect(() => {
984
- const element = scrollRef.current;
985
- if (!element || typeof ResizeObserver === "undefined") return;
986
- const observer = new ResizeObserver(() => {
987
- setViewport((v) => ({ ...v, width: element.clientWidth, height: element.clientHeight }));
988
- });
989
- observer.observe(element);
990
- setViewport((v) => ({ ...v, width: element.clientWidth, height: element.clientHeight }));
991
- return () => observer.disconnect();
992
- }, []);
993
- const onScroll = useCallback((event) => {
994
- const el = event.currentTarget;
995
- setViewport((v) => ({ ...v, scrollTop: el.scrollTop, scrollLeft: el.scrollLeft }));
996
- }, []);
997
- const columns = useMemo(() => controller.resolvedColumns(), [controller, state]);
998
- const result = state.result;
999
- if (!result || result.status === "idle") {
1000
- return /* @__PURE__ */ jsx(Frame, { controller, state, children: /* @__PURE__ */ jsx(
1001
- EmptyState,
1002
- {
1003
- title: "No data",
1004
- hint: "Wire a source into this table, or refresh to run the current query."
1005
- }
1006
- ) });
1007
- }
1008
- if (result.status === "error") {
1009
- return /* @__PURE__ */ jsx(Frame, { controller, state, children: /* @__PURE__ */ jsx(
1010
- ErrorState,
1011
- {
1012
- title: result.error?.code ? `Query failed (${result.error.code})` : "Query failed",
1013
- message: result.error?.message,
1014
- onRetry: result.error?.retryable === false ? void 0 : () => controller.refresh()
1015
- }
1016
- ) });
1017
- }
1018
- if (result.status === "loading" && result.rows.length === 0) {
1019
- return /* @__PURE__ */ jsx(Frame, { controller, state, children: /* @__PURE__ */ jsx(LoadingState, { label: "Running query\u2026" }) });
1020
- }
1021
- const body = state.viewMode === "pivot" ? /* @__PURE__ */ jsx(PivotBody, { controller, columns, groups: result.groups ?? [] }) : result.rows.length === 0 ? /* @__PURE__ */ jsx(
1022
- EmptyState,
1023
- {
1024
- title: "No rows",
1025
- hint: "Every row was filtered out. Clear a filter, or widen the time window."
1026
- }
1027
- ) : /* @__PURE__ */ jsx(
1028
- GridBody,
1029
- {
1030
- controller,
1031
- columns,
1032
- rowHeight,
1033
- zebra,
1034
- showRowNumbers,
1035
- viewport,
1036
- scrollRef,
1037
- onScroll
1038
- }
1039
- );
1040
- return /* @__PURE__ */ jsx(Frame, { controller, state, showFooter, children: body });
1041
- }
1042
- function Frame({
1043
- controller,
1044
- state,
1045
- children,
1046
- showFooter = true
1047
- }) {
997
+ // src/data/table/declaration.ts
998
+ var GUTTER_WIDTH = 44;
999
+ var MODES = [{ value: "grid", label: "Grid" }, { value: "pivot", label: "Pivot" }];
1000
+ function tableViewState(c, options = {}) {
1001
+ const { rowHeight = 24, showFooter = true, zebra = false, showRowNumbers = false } = options;
1002
+ const state = c.getState();
1048
1003
  const result = state.result;
1049
1004
  const page = result?.page;
1005
+ const viewport = c.getViewport();
1006
+ const idle = !result || result.status === "idle";
1007
+ const failed = !idle && result.status === "error";
1008
+ const loading = !idle && !failed && result.status === "loading" && result.rows.length === 0;
1009
+ const active = !idle && !failed && !loading;
1010
+ const pivot = active && state.viewMode === "pivot";
1011
+ const groups = result?.groups ?? [];
1012
+ const pivotEmpty = pivot && groups.length === 0;
1013
+ const noRows = active && !pivot && result.rows.length === 0;
1014
+ const grid = active && !pivot && result.rows.length > 0;
1015
+ const columns = c.resolvedColumns();
1016
+ const sorts = c.effectiveQuery().sorts ?? [];
1017
+ const header = (col) => {
1018
+ const sort = sorts.find((s) => s.columnId === col.column.id);
1019
+ return {
1020
+ key: col.column.id,
1021
+ columnId: col.column.id,
1022
+ name: col.column.name,
1023
+ width: col.width,
1024
+ frozen: col.frozen,
1025
+ offset: col.offset,
1026
+ sort: sort ? sort.direction : null,
1027
+ sortAsc: sort?.direction === "asc",
1028
+ sortDesc: sort?.direction === "desc",
1029
+ sortable: col.column.sortable !== false,
1030
+ title: col.column.description ?? col.column.name
1031
+ };
1032
+ };
1033
+ const rowWindow = computeWindow({ scrollTop: viewport.scrollTop, viewportHeight: viewport.height || rowHeight * 20, rowHeight, rowCount: result?.rows.length ?? 0 });
1034
+ const colWindow = computeColumnWindow(columns, viewport.scrollLeft, viewport.width || 800);
1035
+ const frozen = columns.filter((col) => col.frozen);
1036
+ const visible = columns.slice(colWindow.startIndex, colWindow.endIndex).filter((col) => !col.frozen);
1037
+ const width = totalWidth(columns);
1038
+ const editing = state.editing;
1039
+ const cell = (rowId, values, col) => {
1040
+ const isEditing = editing?.rowId === rowId && editing.columnId === col.column.id;
1041
+ return {
1042
+ key: col.column.id,
1043
+ rowId,
1044
+ columnId: col.column.id,
1045
+ width: col.width,
1046
+ align: alignmentFor(col.column),
1047
+ frozen: col.frozen,
1048
+ offset: col.offset,
1049
+ editing: isEditing,
1050
+ draft: isEditing ? editing.draft : "",
1051
+ text: isEditing ? "" : formatValue2(values[col.column.id] ?? null, col.column)
1052
+ };
1053
+ };
1054
+ const rows = grid ? result.rows.slice(rowWindow.startIndex, rowWindow.endIndex).map((row, i) => {
1055
+ const rowIndex = rowWindow.startIndex + i;
1056
+ return {
1057
+ key: row.id,
1058
+ id: row.id,
1059
+ height: rowHeight,
1060
+ width,
1061
+ selected: state.selection.includes(row.id),
1062
+ zebra: zebra && rowIndex % 2 === 1,
1063
+ hasGutter: showRowNumbers,
1064
+ ordinal: String((page?.offset ?? 0) + rowIndex + 1),
1065
+ gutterWidth: GUTTER_WIDTH,
1066
+ frozenCells: frozen.map((col) => cell(row.id, row.values, col)),
1067
+ cells: visible.map((col) => cell(row.id, row.values, col))
1068
+ };
1069
+ }) : [];
1070
+ const pivotRows = [];
1071
+ if (pivot && !pivotEmpty) {
1072
+ const walk = (group, depth) => {
1073
+ pivotRows.push({
1074
+ key: group.key.join("/"),
1075
+ label: group.label || "\u2014",
1076
+ depth,
1077
+ total: false,
1078
+ cells: columns.map((col) => {
1079
+ const v = group.aggregates?.[col.column.id];
1080
+ return { key: col.column.id, text: v === null || v === void 0 ? "" : formatValue2(v, col.column) };
1081
+ })
1082
+ });
1083
+ for (const child of group.children ?? []) walk(child, depth + 1);
1084
+ };
1085
+ for (const group of groups) walk(group, 0);
1086
+ const totals = result?.totals;
1087
+ if (totals) pivotRows.push({ key: "__total", label: "Total", depth: 0, total: true, cells: columns.map((col) => ({ key: col.column.id, text: formatValue2(totals[col.column.id] ?? null, col.column) })) });
1088
+ }
1050
1089
  const total = page?.total;
1051
1090
  const from = page ? page.offset + 1 : 0;
1052
1091
  const to = page ? page.offset + (result?.rows.length ?? 0) : 0;
1053
- return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
1054
- /* @__PURE__ */ jsx(
1055
- Toolbar,
1092
+ return {
1093
+ modes: MODES,
1094
+ mode: state.viewMode === "pivot" ? "pivot" : "grid",
1095
+ partial: result?.status === "partial",
1096
+ loadingBadge: result?.status !== "partial" && state.pending,
1097
+ prevDisabled: (page?.offset ?? 0) === 0,
1098
+ nextDisabled: !page?.hasMore,
1099
+ idle,
1100
+ failed,
1101
+ failedTitle: failed && result.error?.code ? `Query failed (${result.error.code})` : "Query failed",
1102
+ failedMessage: failed ? result.error?.message : void 0,
1103
+ retryable: failed && result.error?.retryable !== false,
1104
+ failedNoRetry: failed && result.error?.retryable === false,
1105
+ loading,
1106
+ pivot,
1107
+ pivotEmpty,
1108
+ noRows,
1109
+ grid,
1110
+ width,
1111
+ headerHeight: rowHeight,
1112
+ frozenHeaders: grid ? frozen.map(header) : [],
1113
+ headers: grid ? visible.map(header) : [],
1114
+ spacerWidth: colWindow.offsetLeft,
1115
+ totalHeight: rowWindow.totalHeight,
1116
+ offsetTop: rowWindow.offsetTop,
1117
+ rows,
1118
+ pivotHeads: pivot && !pivotEmpty ? columns.map((col) => ({ key: col.column.id, name: col.column.name })) : [],
1119
+ pivotRows,
1120
+ footerShown: showFooter,
1121
+ countLabel: total === void 0 ? `${to - from + 1} rows` : `${from}\u2013${to} of ${total}`,
1122
+ selectedLabel: state.selection.length > 0 ? `${state.selection.length} selected` : void 0
1123
+ };
1124
+ }
1125
+ function applyTableUi(c, intent) {
1126
+ switch (intent.kind) {
1127
+ case "mode":
1128
+ c.setViewMode(intent.mode);
1129
+ break;
1130
+ case "prev":
1131
+ c.stepPage(-1);
1132
+ break;
1133
+ case "next":
1134
+ c.stepPage(1);
1135
+ break;
1136
+ case "refresh":
1137
+ c.refresh();
1138
+ break;
1139
+ case "sort":
1140
+ c.toggleSort(intent.columnId, !!intent.shiftKey);
1141
+ break;
1142
+ case "rowClick":
1143
+ c.select([intent.id], !!intent.ctrlKey || !!intent.metaKey);
1144
+ break;
1145
+ case "rowDouble":
1146
+ c.activateRow(intent.id);
1147
+ break;
1148
+ case "cellDouble":
1149
+ if (!c.beginEdit(intent.rowId, intent.columnId)) c.activateRow(intent.rowId, intent.columnId);
1150
+ break;
1151
+ case "draft":
1152
+ c.updateDraft(intent.value);
1153
+ break;
1154
+ case "editKey": {
1155
+ if (intent.key === "Enter") c.commitEdit();
1156
+ else if (intent.key === "Escape") c.cancelEdit();
1157
+ break;
1158
+ }
1159
+ case "commitEdit":
1160
+ c.commitEdit();
1161
+ break;
1162
+ }
1163
+ }
1164
+ var tableViewManifest = {
1165
+ id: tableManifest.id,
1166
+ version: "1.0.0",
1167
+ title: "Table",
1168
+ category: "data",
1169
+ layout: { slot: "center" },
1170
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The TableViewModel the runtime derives from the controller on every draw." }],
1171
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, mode?, columnId?, id?, rowId?, value?, key?, shiftKey?, ctrlKey?, metaKey? } \u2014 applied by the runtime to the controller." }],
1172
+ commands: []
1173
+ };
1174
+ var EMPTY = {
1175
+ modes: MODES,
1176
+ mode: "grid",
1177
+ partial: false,
1178
+ loadingBadge: false,
1179
+ prevDisabled: true,
1180
+ nextDisabled: true,
1181
+ idle: true,
1182
+ failed: false,
1183
+ failedTitle: "",
1184
+ retryable: false,
1185
+ failedNoRetry: false,
1186
+ loading: false,
1187
+ pivot: false,
1188
+ pivotEmpty: false,
1189
+ noRows: false,
1190
+ grid: false,
1191
+ width: 0,
1192
+ headerHeight: 24,
1193
+ frozenHeaders: [],
1194
+ headers: [],
1195
+ spacerWidth: 0,
1196
+ totalHeight: 0,
1197
+ offsetTop: 0,
1198
+ rows: [],
1199
+ pivotHeads: [],
1200
+ pivotRows: [],
1201
+ footerShown: true,
1202
+ countLabel: "0 rows"
1203
+ };
1204
+ var BEHAVIOURS = {
1205
+ "data/tablePanelView": (inputs) => inputs.view ?? EMPTY
1206
+ };
1207
+ var text = (b) => ({ component: "text", props: { text: b } });
1208
+ var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
1209
+ var HEADER_CELL = (list) => ({
1210
+ component: "chrome/GridHeaderCell",
1211
+ each: { input: list, key: "key" },
1212
+ props: {
1213
+ width: { item: "width" },
1214
+ frozen: { item: "frozen" },
1215
+ offset: { item: "offset" },
1216
+ sort: { item: "sort" },
1217
+ sortable: { item: "sortable" },
1218
+ title: { item: "title" },
1219
+ sortIcon: { node: { component: "fragment", children: [{ component: "icons/SortAsc", when: { item: "sortAsc" } }, { component: "icons/SortDesc", when: { item: "sortDesc" } }] } }
1220
+ },
1221
+ children: [text({ item: "name" })],
1222
+ on: { onClick: ui("sort", { columnId: { item: "columnId" }, shiftKey: { arg: "shiftKey" } }) }
1223
+ });
1224
+ var CELL = (list) => ({
1225
+ component: "chrome/GridCell",
1226
+ each: { item: list, key: "key" },
1227
+ props: { width: { item: "width" }, align: { item: "align" }, frozen: { item: "frozen" }, offset: { item: "offset" }, editing: { item: "editing" } },
1228
+ children: [
1229
+ { component: "chrome/Input", when: { item: "editing" }, props: { autoFocus: { value: true }, value: { item: "draft" }, label: { value: "Cell value" } }, on: { onChange: ui("draft", { value: { arg: "" } }), onBlur: ui("commitEdit"), onKeyDown: ui("editKey", { key: { arg: "key" } }) } },
1230
+ { component: "text", when: { not: { item: "editing" } }, props: { text: { item: "text" } } }
1231
+ ],
1232
+ on: { onDoubleClick: ui("cellDouble", { rowId: { item: "rowId" }, columnId: { item: "columnId" } }) }
1233
+ });
1234
+ var TABLE_DECLARATION = {
1235
+ format: "xeno.block@1",
1236
+ manifest: tableViewManifest,
1237
+ behaviour: "data/tablePanelView",
1238
+ composition: {
1239
+ component: "chrome/Column",
1240
+ children: [
1056
1241
  {
1057
- left: /* @__PURE__ */ jsxs(Fragment, { children: [
1058
- /* @__PURE__ */ jsx(
1059
- SegmentedControl,
1060
- {
1061
- size: "sm",
1062
- label: "View mode",
1063
- options: [
1064
- { value: "grid", label: "Grid" },
1065
- { value: "pivot", label: "Pivot" }
1066
- ],
1067
- value: state.viewMode === "pivot" ? "pivot" : "grid",
1068
- onChange: (mode) => controller.setViewMode(mode)
1069
- }
1070
- ),
1071
- result?.status === "partial" ? /* @__PURE__ */ jsx(Badge, { tone: "warning", children: "Partial" }) : state.pending ? /* @__PURE__ */ jsx(Badge, { children: "Loading" }) : null
1072
- ] }),
1073
- right: /* @__PURE__ */ jsxs(ToolbarGroup, { end: true, children: [
1074
- /* @__PURE__ */ jsx(
1075
- IconButton,
1076
- {
1077
- icon: /* @__PURE__ */ jsx(ChevronGlyph, { direction: "left" }),
1078
- label: "Previous page",
1079
- disabled: (page?.offset ?? 0) === 0,
1080
- onClick: () => controller.stepPage(-1)
1081
- }
1082
- ),
1083
- /* @__PURE__ */ jsx(
1084
- IconButton,
1085
- {
1086
- icon: /* @__PURE__ */ jsx(ChevronGlyph, { direction: "right" }),
1087
- label: "Next page",
1088
- disabled: !page?.hasMore,
1089
- onClick: () => controller.stepPage(1)
1242
+ component: "chrome/Toolbar",
1243
+ props: {
1244
+ left: {
1245
+ node: {
1246
+ component: "fragment",
1247
+ children: [
1248
+ { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "View mode" }, options: { input: "view.modes" }, value: { input: "view.mode" } }, on: { onChange: ui("mode", { mode: { arg: "" } }) } },
1249
+ { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" } }, children: [text({ value: "Partial" })] },
1250
+ { component: "chrome/Badge", when: { input: "view.loadingBadge" }, children: [text({ value: "Loading" })] }
1251
+ ]
1090
1252
  }
1091
- ),
1092
- /* @__PURE__ */ jsx(
1093
- IconButton,
1094
- {
1095
- icon: /* @__PURE__ */ jsx(RefreshGlyph, {}),
1096
- label: "Refresh",
1097
- onClick: () => controller.refresh()
1253
+ },
1254
+ right: {
1255
+ node: {
1256
+ component: "chrome/ToolbarGroup",
1257
+ props: { end: { value: true } },
1258
+ children: [
1259
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Chevron", props: { direction: { value: "left" } } } }, label: { value: "Previous page" }, disabled: { input: "view.prevDisabled" } }, on: { onClick: ui("prev") } },
1260
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Chevron", props: { direction: { value: "right" } } } }, label: { value: "Next page" }, disabled: { input: "view.nextDisabled" } }, on: { onClick: ui("next") } },
1261
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Refresh" } }, label: { value: "Refresh" } }, on: { onClick: ui("refresh") } }
1262
+ ]
1098
1263
  }
1099
- )
1100
- ] })
1101
- }
1102
- ),
1103
- children,
1104
- showFooter ? /* @__PURE__ */ jsx(
1105
- StatusBar,
1264
+ }
1265
+ }
1266
+ },
1267
+ { component: "chrome/EmptyState", when: { input: "view.idle" }, props: { title: { value: "No data" }, hint: { value: "Wire a source into this table, or refresh to run the current query." } } },
1268
+ { component: "chrome/ErrorState", when: { input: "view.retryable" }, props: { title: { input: "view.failedTitle" }, message: { input: "view.failedMessage" } }, on: { onRetry: ui("refresh") } },
1269
+ // a failure the host marked non-retryable offers no retry — the handler is absent, not disabled
1270
+ { component: "chrome/ErrorState", when: { input: "view.failedNoRetry" }, props: { title: { input: "view.failedTitle" }, message: { input: "view.failedMessage" } } },
1271
+ { component: "chrome/LoadingState", when: { input: "view.loading" }, props: { label: { value: "Running query\u2026" } } },
1272
+ { component: "chrome/EmptyState", when: { input: "view.pivotEmpty" }, props: { title: { value: "No cross-tab" }, hint: { value: "Set a pivot spec \u2014 rows, columns and at least one measure \u2014 to build a cross-tab." } } },
1273
+ { component: "chrome/EmptyState", when: { input: "view.noRows" }, props: { title: { value: "No rows" }, hint: { value: "Every row was filtered out. Clear a filter, or widen the time window." } } },
1106
1274
  {
1107
- left: total === void 0 ? `${to - from + 1} rows` : `${from}\u2013${to} of ${total}`,
1108
- right: state.selection.length > 0 ? `${state.selection.length} selected` : void 0
1109
- }
1110
- ) : null
1111
- ] });
1112
- }
1113
- function GridBody({
1114
- controller,
1115
- columns,
1116
- rowHeight,
1117
- zebra,
1118
- showRowNumbers,
1119
- viewport,
1120
- scrollRef,
1121
- onScroll
1122
- }) {
1123
- const state = controller.getState();
1124
- const result = state.result;
1125
- if (!result) return null;
1126
- const rowWindow = computeWindow({
1127
- scrollTop: viewport.scrollTop,
1128
- viewportHeight: viewport.height || rowHeight * 20,
1129
- rowHeight,
1130
- rowCount: result.rows.length
1131
- });
1132
- const colWindow = computeColumnWindow(columns, viewport.scrollLeft, viewport.width || 800);
1133
- const frozen = columns.filter((c) => c.frozen);
1134
- const visible = columns.slice(colWindow.startIndex, colWindow.endIndex);
1135
- const width = totalWidth(columns);
1136
- const sorts = controller.effectiveQuery().sorts ?? [];
1137
- return /* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsxs(
1138
- "div",
1139
- {
1140
- ref: scrollRef,
1141
- onScroll,
1142
- style: { position: "relative", height: "100%", overflow: "auto" },
1143
- children: [
1144
- /* @__PURE__ */ jsxs(
1145
- "div",
1275
+ component: "chrome/ScrollArea",
1276
+ when: { input: "view.pivot" },
1277
+ children: [
1146
1278
  {
1147
- style: {
1148
- position: "sticky",
1149
- top: 0,
1150
- zIndex: 2,
1151
- display: "flex",
1152
- width,
1153
- height: rowHeight,
1154
- background: "var(--xeno-panel-header)"
1155
- },
1279
+ component: "chrome/PivotTable",
1280
+ when: { not: { input: "view.pivotEmpty" } },
1156
1281
  children: [
1157
- frozen.map((c) => /* @__PURE__ */ jsx(HeaderCell, { resolved: c, controller, sorts, sticky: true }, c.column.id)),
1158
- /* @__PURE__ */ jsx("div", { style: { width: colWindow.offsetLeft, flex: "0 0 auto" } }),
1159
- visible.filter((c) => !c.frozen).map((c) => /* @__PURE__ */ jsx(HeaderCell, { resolved: c, controller, sorts }, c.column.id))
1282
+ { component: "chrome/PivotRow", props: { head: { value: true } }, children: [{ component: "chrome/PivotCell", props: { head: { value: true } }, children: [text({ value: "\xA0" })] }, { component: "chrome/PivotCell", each: { input: "view.pivotHeads", key: "key" }, children: [text({ item: "name" })] }] },
1283
+ {
1284
+ component: "chrome/PivotRow",
1285
+ each: { input: "view.pivotRows", key: "key" },
1286
+ props: { total: { item: "total" } },
1287
+ children: [
1288
+ { component: "chrome/PivotCell", props: { head: { value: true }, depth: { item: "depth" } }, children: [text({ item: "label" })] },
1289
+ { component: "chrome/PivotCell", each: { item: "cells", key: "key" }, props: { numeric: { value: true } }, children: [text({ item: "text" })] }
1290
+ ]
1291
+ }
1160
1292
  ]
1161
1293
  }
1162
- ),
1163
- /* @__PURE__ */ jsx("div", { style: { height: rowWindow.totalHeight, width, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { transform: `translateY(${rowWindow.offsetTop}px)` }, children: result.rows.slice(rowWindow.startIndex, rowWindow.endIndex).map((row, i) => {
1164
- const rowIndex = rowWindow.startIndex + i;
1165
- const selected = state.selection.includes(row.id);
1166
- return /* @__PURE__ */ jsxs(
1167
- "div",
1168
- {
1169
- role: "row",
1170
- "aria-selected": selected,
1171
- onClick: (e) => controller.select([row.id], e.ctrlKey || e.metaKey),
1172
- onDoubleClick: () => controller.activateRow(row.id),
1173
- style: {
1174
- display: "flex",
1175
- height: rowHeight,
1176
- width,
1177
- background: selected ? "var(--xeno-border)" : zebra && rowIndex % 2 === 1 ? "var(--xeno-border-subtle)" : "transparent"
1178
- },
1179
- children: [
1180
- showRowNumbers ? /* @__PURE__ */ jsx(
1181
- "div",
1182
- {
1183
- role: "gridcell",
1184
- "aria-hidden": true,
1185
- style: {
1186
- width: 44,
1187
- flex: "0 0 44px",
1188
- textAlign: "right",
1189
- paddingRight: 6,
1190
- opacity: 0.35,
1191
- fontVariantNumeric: "tabular-nums",
1192
- userSelect: "none",
1193
- alignSelf: "center"
1194
- },
1195
- children: (result.page?.offset ?? 0) + rowIndex + 1
1196
- }
1197
- ) : null,
1198
- frozen.map((c) => /* @__PURE__ */ jsx(Cell, { controller, row, resolved: c, sticky: true }, c.column.id)),
1199
- /* @__PURE__ */ jsx("div", { style: { width: colWindow.offsetLeft, flex: "0 0 auto" } }),
1200
- visible.filter((c) => !c.frozen).map((c) => /* @__PURE__ */ jsx(Cell, { controller, row, resolved: c }, c.column.id))
1201
- ]
1202
- },
1203
- row.id
1204
- );
1205
- }) }) })
1206
- ]
1207
- }
1208
- ) });
1209
- }
1210
- function HeaderCell({
1211
- resolved,
1212
- controller,
1213
- sorts,
1214
- sticky
1215
- }) {
1216
- const sort = sorts.find((s) => s.columnId === resolved.column.id);
1217
- return /* @__PURE__ */ jsxs(
1218
- "div",
1219
- {
1220
- role: "columnheader",
1221
- "aria-sort": sort ? sort.direction === "asc" ? "ascending" : "descending" : "none",
1222
- onClick: (e) => controller.toggleSort(resolved.column.id, e.shiftKey),
1223
- title: resolved.column.description ?? resolved.column.name,
1224
- style: {
1225
- width: resolved.width,
1226
- flex: "0 0 auto",
1227
- display: "flex",
1228
- alignItems: "center",
1229
- gap: 4,
1230
- padding: "0 6px",
1231
- boxSizing: "border-box",
1232
- fontSize: 9,
1233
- fontWeight: 600,
1234
- letterSpacing: "0.06em",
1235
- textTransform: "uppercase",
1236
- color: "var(--xeno-muted)",
1237
- cursor: resolved.column.sortable === false ? "default" : "pointer",
1238
- position: sticky ? "sticky" : void 0,
1239
- left: sticky ? resolved.offset : void 0,
1240
- zIndex: sticky ? 1 : void 0,
1241
- background: sticky ? "var(--xeno-panel-header)" : void 0,
1242
- borderRight: "1px solid var(--xeno-border)",
1243
- userSelect: "none"
1294
+ ]
1244
1295
  },
1245
- children: [
1246
- /* @__PURE__ */ jsx(
1247
- "span",
1296
+ {
1297
+ component: "table/Scroller",
1298
+ when: { input: "view.grid" },
1299
+ children: [
1300
+ { component: "chrome/GridHeader", props: { width: { input: "view.width" }, height: { input: "view.headerHeight" } }, children: [HEADER_CELL("view.frozenHeaders"), { component: "chrome/GridSpacer", props: { width: { input: "view.spacerWidth" } } }, HEADER_CELL("view.headers")] },
1248
1301
  {
1249
- style: { flex: "1 1 auto", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
1250
- children: resolved.column.name
1302
+ component: "chrome/VirtualWindow",
1303
+ props: { totalHeight: { input: "view.totalHeight" }, offsetTop: { input: "view.offsetTop" } },
1304
+ children: [
1305
+ {
1306
+ component: "chrome/GridRow",
1307
+ each: { input: "view.rows", key: "key" },
1308
+ props: { height: { item: "height" }, width: { item: "width" }, selected: { item: "selected" }, zebra: { item: "zebra" } },
1309
+ on: { onClick: ui("rowClick", { id: { item: "id" }, ctrlKey: { arg: "ctrlKey" }, metaKey: { arg: "metaKey" } }), onDoubleClick: ui("rowDouble", { id: { item: "id" } }) },
1310
+ children: [
1311
+ { component: "chrome/GridCell", when: { item: "hasGutter" }, props: { width: { item: "gutterWidth" }, gutter: { value: true } }, children: [text({ item: "ordinal" })] },
1312
+ CELL("frozenCells"),
1313
+ { component: "chrome/GridSpacer", props: { width: { input: "view.spacerWidth" } } },
1314
+ CELL("cells")
1315
+ ]
1316
+ }
1317
+ ]
1251
1318
  }
1252
- ),
1253
- sort ? sort.direction === "asc" ? /* @__PURE__ */ jsx(SortAscGlyph, {}) : /* @__PURE__ */ jsx(SortDescGlyph, {}) : null
1254
- ]
1255
- }
1256
- );
1257
- }
1258
- function Cell({
1259
- controller,
1260
- row,
1261
- resolved,
1262
- sticky
1263
- }) {
1264
- const state = controller.getState();
1265
- const column = resolved.column;
1266
- const editing = state.editing?.rowId === row.id && state.editing.columnId === column.id ? state.editing : null;
1267
- const style = {
1268
- width: resolved.width,
1269
- flex: "0 0 auto",
1270
- display: "flex",
1271
- alignItems: "center",
1272
- justifyContent: alignmentFor(column) === "right" ? "flex-end" : alignmentFor(column) === "center" ? "center" : "flex-start",
1273
- padding: "0 6px",
1274
- boxSizing: "border-box",
1275
- fontSize: 11,
1276
- color: "var(--xeno-text)",
1277
- fontVariantNumeric: alignmentFor(column) === "right" ? "tabular-nums" : void 0,
1278
- overflow: "hidden",
1279
- whiteSpace: "nowrap",
1280
- textOverflow: "ellipsis",
1281
- position: sticky ? "sticky" : void 0,
1282
- left: sticky ? resolved.offset : void 0,
1283
- zIndex: sticky ? 1 : void 0,
1284
- background: sticky ? "var(--xeno-canvas)" : void 0,
1285
- borderRight: "1px solid var(--xeno-border)"
1286
- };
1287
- if (editing) {
1288
- return /* @__PURE__ */ jsx("div", { style, children: /* @__PURE__ */ jsx(
1289
- "input",
1319
+ ]
1320
+ },
1290
1321
  {
1291
- autoFocus: true,
1292
- value: editing.draft,
1293
- onChange: (e) => controller.updateDraft(e.target.value),
1294
- onBlur: () => controller.commitEdit(),
1295
- onFocus: (e) => e.currentTarget.select(),
1296
- onKeyDown: (e) => {
1297
- if (e.key === "Enter") {
1298
- e.preventDefault();
1299
- controller.commitEdit();
1300
- } else if (e.key === "Escape") {
1301
- e.preventDefault();
1302
- controller.cancelEdit();
1303
- }
1304
- },
1305
- style: {
1306
- width: "100%",
1307
- height: "100%",
1308
- border: "none",
1309
- outline: "none",
1310
- background: "var(--xeno-canvas)",
1311
- color: "inherit",
1312
- font: "inherit",
1313
- textAlign: alignmentFor(column) === "right" ? "right" : "left",
1314
- padding: 0
1322
+ component: "chrome/StatusBar",
1323
+ when: { input: "view.footerShown" },
1324
+ props: {
1325
+ left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text({ input: "view.countLabel" })] } },
1326
+ right: { node: { component: "chrome/Text", when: { input: "view.selectedLabel" }, props: { role: { value: "meta" } }, children: [text({ input: "view.selectedLabel" })] } }
1315
1327
  }
1316
1328
  }
1317
- ) });
1329
+ ]
1318
1330
  }
1319
- return /* @__PURE__ */ jsx(
1320
- "div",
1331
+ };
1332
+
1333
+ // src/data/table/react/TablePanelView.tsx
1334
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1335
+ function TablePanelView({ controller, ...options }) {
1336
+ useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
1337
+ const view = tableViewState(controller, options);
1338
+ const ui6 = (intent) => applyTableUi(controller, intent);
1339
+ const header = (h) => /* @__PURE__ */ jsx(
1340
+ GridHeaderCell,
1321
1341
  {
1322
- role: "gridcell",
1323
- style,
1324
- onDoubleClick: (e) => {
1325
- e.stopPropagation();
1326
- if (!controller.beginEdit(row.id, column.id)) controller.activateRow(row.id, column.id);
1327
- },
1328
- children: formatValue2(row.values[column.id] ?? null, column)
1329
- }
1342
+ width: h.width,
1343
+ frozen: h.frozen,
1344
+ offset: h.offset,
1345
+ sort: h.sort,
1346
+ sortable: h.sortable,
1347
+ title: h.title,
1348
+ sortIcon: /* @__PURE__ */ jsxs(Fragment, { children: [
1349
+ h.sortAsc ? /* @__PURE__ */ jsx(SortAscGlyph, {}) : null,
1350
+ h.sortDesc ? /* @__PURE__ */ jsx(SortDescGlyph, {}) : null
1351
+ ] }),
1352
+ onClick: (e) => ui6({ kind: "sort", columnId: h.columnId, shiftKey: e.shiftKey }),
1353
+ children: h.name
1354
+ },
1355
+ h.key
1330
1356
  );
1331
- }
1332
- function PivotBody({
1333
- controller,
1334
- columns,
1335
- groups
1336
- }) {
1337
- const state = controller.getState();
1338
- const totals = state.result?.totals;
1339
- if (groups.length === 0) {
1340
- return /* @__PURE__ */ jsx(
1341
- EmptyState,
1357
+ const cell = (c) => /* @__PURE__ */ jsxs(GridCell, { width: c.width, align: c.align, frozen: c.frozen, offset: c.offset, editing: c.editing, onDoubleClick: () => ui6({ kind: "cellDouble", rowId: c.rowId, columnId: c.columnId }), children: [
1358
+ c.editing ? /* @__PURE__ */ jsx(Input, { autoFocus: true, value: c.draft, label: "Cell value", onChange: (value) => ui6({ kind: "draft", value }), onBlur: () => ui6({ kind: "commitEdit" }), onKeyDown: (e) => ui6({ kind: "editKey", key: e.key }) }) : null,
1359
+ !c.editing ? c.text : null
1360
+ ] }, c.key);
1361
+ return /* @__PURE__ */ jsxs(Column, { children: [
1362
+ /* @__PURE__ */ jsx(
1363
+ Toolbar,
1342
1364
  {
1343
- title: "No cross-tab",
1344
- hint: "Set a pivot spec \u2014 rows, columns and at least one measure \u2014 to build a cross-tab."
1365
+ left: /* @__PURE__ */ jsxs(Fragment, { children: [
1366
+ /* @__PURE__ */ jsx(SegmentedControl, { size: "sm", label: "View mode", options: view.modes, value: view.mode, onChange: (mode) => ui6({ kind: "mode", mode }) }),
1367
+ view.partial ? /* @__PURE__ */ jsx(Badge, { tone: "warning", children: "Partial" }) : null,
1368
+ view.loadingBadge ? /* @__PURE__ */ jsx(Badge, { children: "Loading" }) : null
1369
+ ] }),
1370
+ right: /* @__PURE__ */ jsxs(ToolbarGroup, { end: true, children: [
1371
+ /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(ChevronGlyph, { direction: "left" }), label: "Previous page", disabled: view.prevDisabled, onClick: () => ui6({ kind: "prev" }) }),
1372
+ /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(ChevronGlyph, { direction: "right" }), label: "Next page", disabled: view.nextDisabled, onClick: () => ui6({ kind: "next" }) }),
1373
+ /* @__PURE__ */ jsx(IconButton, { icon: /* @__PURE__ */ jsx(RefreshGlyph, {}), label: "Refresh", onClick: () => ui6({ kind: "refresh" }) })
1374
+ ] })
1345
1375
  }
1346
- );
1347
- }
1348
- return /* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsxs("div", { style: { display: "table", width: "100%", fontSize: 11 }, children: [
1349
- /* @__PURE__ */ jsxs("div", { style: { display: "table-row", position: "sticky", top: 0 }, children: [
1350
- /* @__PURE__ */ jsx(PivotHeadCell, { children: "\xA0" }),
1351
- columns.map((c) => /* @__PURE__ */ jsx(PivotHeadCell, { children: c.column.name }, c.column.id))
1352
- ] }),
1353
- groups.map((group) => /* @__PURE__ */ jsx(PivotRow, { group, columns, depth: 0 }, group.key.join("/"))),
1354
- totals ? /* @__PURE__ */ jsxs("div", { style: { display: "table-row", fontWeight: 600 }, children: [
1355
- /* @__PURE__ */ jsx(PivotCell, { head: true, children: "Total" }),
1356
- columns.map((c) => /* @__PURE__ */ jsx(PivotCell, { numeric: true, children: formatValue2(totals[c.column.id] ?? null, c.column) }, c.column.id))
1357
- ] }) : null
1358
- ] }) });
1359
- }
1360
- function PivotRow({
1361
- group,
1362
- columns,
1363
- depth
1364
- }) {
1365
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1366
- /* @__PURE__ */ jsxs("div", { style: { display: "table-row" }, children: [
1367
- /* @__PURE__ */ jsx(PivotCell, { head: true, indent: depth, children: group.label || "\u2014" }),
1368
- columns.map((c) => /* @__PURE__ */ jsx(PivotCell, { numeric: true, children: group.aggregates?.[c.column.id] === null || group.aggregates?.[c.column.id] === void 0 ? "" : formatValue2(group.aggregates[c.column.id] ?? null, c.column) }, c.column.id))
1369
- ] }),
1370
- group.children?.map((child) => /* @__PURE__ */ jsx(PivotRow, { group: child, columns, depth: depth + 1 }, child.key.join("/")))
1376
+ ),
1377
+ view.idle ? /* @__PURE__ */ jsx(EmptyState, { title: "No data", hint: "Wire a source into this table, or refresh to run the current query." }) : null,
1378
+ view.retryable ? /* @__PURE__ */ jsx(ErrorState, { title: view.failedTitle, message: view.failedMessage, onRetry: () => ui6({ kind: "refresh" }) }) : null,
1379
+ view.failedNoRetry ? /* @__PURE__ */ jsx(ErrorState, { title: view.failedTitle, message: view.failedMessage }) : null,
1380
+ view.loading ? /* @__PURE__ */ jsx(LoadingState, { label: "Running query\u2026" }) : null,
1381
+ view.pivotEmpty ? /* @__PURE__ */ jsx(EmptyState, { title: "No cross-tab", hint: "Set a pivot spec \u2014 rows, columns and at least one measure \u2014 to build a cross-tab." }) : null,
1382
+ view.noRows ? /* @__PURE__ */ jsx(EmptyState, { title: "No rows", hint: "Every row was filtered out. Clear a filter, or widen the time window." }) : null,
1383
+ view.pivot ? /* @__PURE__ */ jsx(ScrollArea, { children: !view.pivotEmpty ? /* @__PURE__ */ jsxs(PivotTable, { children: [
1384
+ /* @__PURE__ */ jsxs(PivotRow, { head: true, children: [
1385
+ /* @__PURE__ */ jsx(PivotCell, { head: true, children: "\xA0" }),
1386
+ view.pivotHeads.map((h) => /* @__PURE__ */ jsx(PivotCell, { children: h.name }, h.key))
1387
+ ] }),
1388
+ view.pivotRows.map((r) => /* @__PURE__ */ jsxs(PivotRow, { total: r.total, children: [
1389
+ /* @__PURE__ */ jsx(PivotCell, { head: true, depth: r.depth, children: r.label }),
1390
+ r.cells.map((c) => /* @__PURE__ */ jsx(PivotCell, { numeric: true, children: c.text }, c.key))
1391
+ ] }, r.key))
1392
+ ] }) : null }) : null,
1393
+ view.grid ? /* @__PURE__ */ jsxs(TableScroller, { controller, children: [
1394
+ /* @__PURE__ */ jsxs(GridHeader, { width: view.width, height: view.headerHeight, children: [
1395
+ view.frozenHeaders.map(header),
1396
+ /* @__PURE__ */ jsx(GridSpacer, { width: view.spacerWidth }),
1397
+ view.headers.map(header)
1398
+ ] }),
1399
+ /* @__PURE__ */ jsx(VirtualWindow, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((r) => /* @__PURE__ */ jsxs(GridRow, { height: r.height, width: r.width, selected: r.selected, zebra: r.zebra, onClick: (e) => ui6({ kind: "rowClick", id: r.id, ctrlKey: e.ctrlKey, metaKey: e.metaKey }), onDoubleClick: () => ui6({ kind: "rowDouble", id: r.id }), children: [
1400
+ r.hasGutter ? /* @__PURE__ */ jsx(GridCell, { width: r.gutterWidth, gutter: true, children: r.ordinal }) : null,
1401
+ r.frozenCells.map(cell),
1402
+ /* @__PURE__ */ jsx(GridSpacer, { width: view.spacerWidth }),
1403
+ r.cells.map(cell)
1404
+ ] }, r.key)) })
1405
+ ] }) : null,
1406
+ view.footerShown ? /* @__PURE__ */ jsx(StatusBar, { left: /* @__PURE__ */ jsx(Text, { role: "number", children: view.countLabel }), right: view.selectedLabel ? /* @__PURE__ */ jsx(Text, { role: "meta", children: view.selectedLabel }) : null }) : null
1371
1407
  ] });
1372
1408
  }
1373
- function PivotHeadCell({ children }) {
1374
- return /* @__PURE__ */ jsx(
1375
- "div",
1376
- {
1377
- style: {
1378
- display: "table-cell",
1379
- padding: "2px 6px",
1380
- fontSize: 9,
1381
- fontWeight: 600,
1382
- letterSpacing: "0.06em",
1383
- textTransform: "uppercase",
1384
- color: "var(--xeno-muted)",
1385
- background: "var(--xeno-panel-header)",
1386
- textAlign: "right"
1387
- },
1388
- children
1389
- }
1390
- );
1391
- }
1392
- function PivotCell({
1393
- children,
1394
- head,
1395
- numeric,
1396
- indent = 0
1397
- }) {
1398
- return /* @__PURE__ */ jsx(
1399
- "div",
1400
- {
1401
- style: {
1402
- display: "table-cell",
1403
- padding: "2px 6px",
1404
- paddingLeft: head ? 6 + indent * 12 : 6,
1405
- textAlign: numeric ? "right" : "left",
1406
- fontVariantNumeric: numeric ? "tabular-nums" : void 0,
1407
- color: head ? "var(--xeno-text)" : "var(--xeno-text)",
1408
- borderTop: "1px solid var(--xeno-border)"
1409
- },
1410
- children
1411
- }
1412
- );
1409
+ function TableScroller({ controller, children }) {
1410
+ const scrollRef = useRef(null);
1411
+ const report = useCallback((el) => controller.setViewport({ width: el.clientWidth, height: el.clientHeight, scrollTop: el.scrollTop, scrollLeft: el.scrollLeft }), [controller]);
1412
+ useEffect(() => {
1413
+ const el = scrollRef.current;
1414
+ if (!el) return;
1415
+ report(el);
1416
+ const RO = globalThis.ResizeObserver;
1417
+ if (!RO) return;
1418
+ const ro = new RO(() => report(el));
1419
+ ro.observe(el);
1420
+ return () => ro.disconnect();
1421
+ }, [report]);
1422
+ const onScroll = useCallback((e) => report(e.currentTarget), [report]);
1423
+ return /* @__PURE__ */ jsx(ScrollArea, { ref: scrollRef, onScroll, children });
1424
+ }
1425
+ var TABLE_RUNTIME_UNITS = ["table/Scroller"];
1426
+ function tableRuntimeComponents(controller) {
1427
+ return {
1428
+ "table/Scroller": ({ children }) => /* @__PURE__ */ jsx(TableScroller, { controller, children })
1429
+ };
1413
1430
  }
1414
1431
 
1415
1432
  // src/data/table/panel.ts
@@ -2201,7 +2218,7 @@ var chartManifest = {
2201
2218
 
2202
2219
  // src/data/chart/react/ChartPanelView.tsx
2203
2220
  import { useEffect as useEffect2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
2204
- import { Badge as Badge2, Column, EmptyState as EmptyState2, ErrorState as ErrorState2, IconButton as IconButton2, PlotSurface, SegmentedControl as SegmentedControl2, StatusBar as StatusBar2, Toolbar as Toolbar2, ToolbarGroup as ToolbarGroup2 } from "@xenosystem/components/chrome";
2221
+ import { Badge as Badge2, Column as Column2, EmptyState as EmptyState2, ErrorState as ErrorState2, IconButton as IconButton2, PlotSurface, SegmentedControl as SegmentedControl2, StatusBar as StatusBar2, Toolbar as Toolbar2, ToolbarGroup as ToolbarGroup2 } from "@xenosystem/components/chrome";
2205
2222
 
2206
2223
  // src/data/chart/renderer.ts
2207
2224
  var renderer = null;
@@ -2302,13 +2319,13 @@ var chartViewManifest = {
2302
2319
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind: type | export, \u2026 } \u2014 applied by the runtime to the controller." }],
2303
2320
  commands: []
2304
2321
  };
2305
- var EMPTY = { types: [], type: "bar", partial: false, aggregating: false, errored: false, empty: true, emptyHint: "Wire a result into this chart to plot it.", unplottable: false, noRenderer: false, plot: false };
2306
- var BEHAVIOURS = {
2307
- "data/chartPanelView": (inputs) => inputs.view ?? EMPTY
2322
+ var EMPTY2 = { types: [], type: "bar", partial: false, aggregating: false, errored: false, empty: true, emptyHint: "Wire a result into this chart to plot it.", unplottable: false, noRenderer: false, plot: false };
2323
+ var BEHAVIOURS2 = {
2324
+ "data/chartPanelView": (inputs) => inputs.view ?? EMPTY2
2308
2325
  };
2309
2326
  var CHART_RUNTIME_UNITS = ["chart/Plot"];
2310
- var text = (b) => ({ component: "text", props: { text: b } });
2311
- var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
2327
+ var text2 = (b) => ({ component: "text", props: { text: b } });
2328
+ var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
2312
2329
  var CHART_DECLARATION = {
2313
2330
  format: "xeno.block@1",
2314
2331
  manifest: chartViewManifest,
@@ -2319,15 +2336,15 @@ var CHART_DECLARATION = {
2319
2336
  {
2320
2337
  component: "chrome/Toolbar",
2321
2338
  props: {
2322
- left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Chart type" }, options: { input: "view.types" }, value: { input: "view.type" } }, on: { onChange: ui("type", { type: { arg: "" } }) } } },
2339
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Chart type" }, options: { input: "view.types" }, value: { input: "view.type" } }, on: { onChange: ui2("type", { type: { arg: "" } }) } } },
2323
2340
  right: {
2324
2341
  node: {
2325
2342
  component: "chrome/ToolbarGroup",
2326
2343
  props: { end: { value: true } },
2327
2344
  children: [
2328
- { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { value: "Some measures have not synced yet." } }, children: [text({ value: "partial" })] },
2329
- { component: "chrome/Badge", when: { input: "view.aggregating" }, props: { tone: { value: "warning" }, title: { input: "view.aggregatingHint" } }, children: [text({ value: "aggregating" })] },
2330
- { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Download" } }, label: { value: "Export chart" } }, on: { onClick: ui("export") } }
2345
+ { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { value: "Some measures have not synced yet." } }, children: [text2({ value: "partial" })] },
2346
+ { component: "chrome/Badge", when: { input: "view.aggregating" }, props: { tone: { value: "warning" }, title: { input: "view.aggregatingHint" } }, children: [text2({ value: "aggregating" })] },
2347
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Download" } }, label: { value: "Export chart" } }, on: { onClick: ui2("export") } }
2331
2348
  ]
2332
2349
  }
2333
2350
  }
@@ -2374,16 +2391,16 @@ function chartRuntimeComponents(controller, options) {
2374
2391
  function ChartPanelView({ controller, options, emptyHint }) {
2375
2392
  useSyncExternalStore2(controller.subscribe, controller.getState, controller.getState);
2376
2393
  const view = chartViewState(controller, { emptyHint });
2377
- const ui5 = (intent) => applyChartUi(controller, intent);
2378
- return /* @__PURE__ */ jsxs2(Column, { children: [
2394
+ const ui6 = (intent) => applyChartUi(controller, intent);
2395
+ return /* @__PURE__ */ jsxs2(Column2, { children: [
2379
2396
  /* @__PURE__ */ jsx2(
2380
2397
  Toolbar2,
2381
2398
  {
2382
- left: /* @__PURE__ */ jsx2(SegmentedControl2, { size: "sm", label: "Chart type", options: view.types, value: view.type, onChange: (type) => ui5({ kind: "type", type }) }),
2399
+ left: /* @__PURE__ */ jsx2(SegmentedControl2, { size: "sm", label: "Chart type", options: view.types, value: view.type, onChange: (type) => ui6({ kind: "type", type }) }),
2383
2400
  right: /* @__PURE__ */ jsxs2(ToolbarGroup2, { end: true, children: [
2384
2401
  view.partial ? /* @__PURE__ */ jsx2(Badge2, { tone: "warning", title: "Some measures have not synced yet.", children: "partial" }) : null,
2385
2402
  view.aggregating ? /* @__PURE__ */ jsx2(Badge2, { tone: "warning", title: view.aggregatingHint, children: "aggregating" }) : null,
2386
- /* @__PURE__ */ jsx2(IconButton2, { icon: /* @__PURE__ */ jsx2(DownloadGlyph, {}), label: "Export chart", onClick: () => ui5({ kind: "export" }) })
2403
+ /* @__PURE__ */ jsx2(IconButton2, { icon: /* @__PURE__ */ jsx2(DownloadGlyph, {}), label: "Export chart", onClick: () => ui6({ kind: "export" }) })
2387
2404
  ] })
2388
2405
  }
2389
2406
  ),
@@ -2937,7 +2954,7 @@ var metricsManifest = {
2937
2954
  import { useSyncExternalStore as useSyncExternalStore3 } from "react";
2938
2955
  import {
2939
2956
  Badge as Badge3,
2940
- Column as Column2,
2957
+ Column as Column3,
2941
2958
  Delta,
2942
2959
  EmptyState as EmptyState3,
2943
2960
  ErrorState as ErrorState3,
@@ -2947,7 +2964,7 @@ import {
2947
2964
  Stack,
2948
2965
  StatTile,
2949
2966
  StatusBar as StatusBar3,
2950
- Text,
2967
+ Text as Text2,
2951
2968
  TileGrid,
2952
2969
  Toolbar as Toolbar3
2953
2970
  } from "@xenosystem/components/chrome";
@@ -3019,13 +3036,13 @@ var metricsViewManifest = {
3019
3036
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind: range | activate, \u2026 } \u2014 applied by the runtime to the controller." }],
3020
3037
  commands: []
3021
3038
  };
3022
- var EMPTY2 = { hasRanges: false, ranges: [], rangeId: "", partial: false, partialHint: "", errored: false, empty: true, emptyHint: "Configure a metric, or wire a result into this panel.", ready: false, layout: "grid", columns: 4, tiles: [], countLabel: "0 metrics" };
3023
- var BEHAVIOURS2 = {
3024
- "data/metricsPanelView": (inputs) => inputs.view ?? EMPTY2
3039
+ var EMPTY3 = { hasRanges: false, ranges: [], rangeId: "", partial: false, partialHint: "", errored: false, empty: true, emptyHint: "Configure a metric, or wire a result into this panel.", ready: false, layout: "grid", columns: 4, tiles: [], countLabel: "0 metrics" };
3040
+ var BEHAVIOURS3 = {
3041
+ "data/metricsPanelView": (inputs) => inputs.view ?? EMPTY3
3025
3042
  };
3026
3043
  var METRICS_RUNTIME_UNITS = [];
3027
- var text2 = (b) => ({ component: "text", props: { text: b } });
3028
- var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3044
+ var text3 = (b) => ({ component: "text", props: { text: b } });
3045
+ var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3029
3046
  var METRICS_DECLARATION = {
3030
3047
  format: "xeno.block@1",
3031
3048
  manifest: metricsViewManifest,
@@ -3037,8 +3054,8 @@ var METRICS_DECLARATION = {
3037
3054
  component: "chrome/Toolbar",
3038
3055
  when: { input: "view.hasRanges" },
3039
3056
  props: {
3040
- left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Range" }, options: { input: "view.ranges" }, value: { input: "view.rangeId" } }, on: { onChange: ui2("range", { rangeId: { arg: "" } }) } } },
3041
- right: { node: { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { input: "view.partialHint" } }, children: [text2({ value: "partial" })] } }
3057
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Range" }, options: { input: "view.ranges" }, value: { input: "view.rangeId" } }, on: { onChange: ui3("range", { rangeId: { arg: "" } }) } } },
3058
+ right: { node: { component: "chrome/Badge", when: { input: "view.partial" }, props: { tone: { value: "warning" }, title: { input: "view.partialHint" } }, children: [text3({ value: "partial" })] } }
3042
3059
  }
3043
3060
  },
3044
3061
  { component: "chrome/ErrorState", when: { input: "view.errored" }, props: { title: { value: "Could not load metrics" }, message: { input: "view.errorMessage" } } },
@@ -3058,20 +3075,20 @@ var METRICS_DECLARATION = {
3058
3075
  title: { item: "title" },
3059
3076
  label: { item: "label" },
3060
3077
  value: { item: "value" },
3061
- icon: { node: { component: "chrome/Badge", when: { item: "hasLevel" }, props: { tone: { item: "tone" } }, children: [text2({ item: "level" })] } },
3078
+ icon: { node: { component: "chrome/Badge", when: { item: "hasLevel" }, props: { tone: { item: "tone" } }, children: [text3({ item: "level" })] } },
3062
3079
  sub: {
3063
3080
  node: {
3064
3081
  component: "chrome/Stack",
3065
3082
  props: { direction: { value: "row" }, pad: { value: "none" } },
3066
3083
  children: [
3067
3084
  { component: "chrome/Delta", when: { item: "hasDelta" }, props: { value: { item: "delta" }, tone: { item: "deltaTone" } } },
3068
- { component: "chrome/Text", when: { item: "partial" }, props: { role: { value: "meta" } }, children: [text2({ input: "view.partialHint" })] },
3085
+ { component: "chrome/Text", when: { item: "partial" }, props: { role: { value: "meta" } }, children: [text3({ input: "view.partialHint" })] },
3069
3086
  { component: "chrome/Sparkline", when: { item: "hasSpark" }, props: { data: { item: "spark" }, label: { item: "sparkLabel" } } }
3070
3087
  ]
3071
3088
  }
3072
3089
  }
3073
3090
  },
3074
- on: { onActivate: ui2("activate", { metricId: { item: "id" } }) }
3091
+ on: { onActivate: ui3("activate", { metricId: { item: "id" } }) }
3075
3092
  }
3076
3093
  ]
3077
3094
  }
@@ -3087,12 +3104,12 @@ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3087
3104
  function MetricsPanelView({ controller, ...options }) {
3088
3105
  useSyncExternalStore3(controller.subscribe, controller.getState, controller.getState);
3089
3106
  const view = metricsViewState(controller, options);
3090
- const ui5 = (intent) => applyMetricsUi(controller, intent);
3091
- return /* @__PURE__ */ jsxs3(Column2, { children: [
3107
+ const ui6 = (intent) => applyMetricsUi(controller, intent);
3108
+ return /* @__PURE__ */ jsxs3(Column3, { children: [
3092
3109
  view.hasRanges ? /* @__PURE__ */ jsx3(
3093
3110
  Toolbar3,
3094
3111
  {
3095
- left: /* @__PURE__ */ jsx3(SegmentedControl3, { size: "sm", label: "Range", options: view.ranges, value: view.rangeId, onChange: (rangeId) => ui5({ kind: "range", rangeId }) }),
3112
+ left: /* @__PURE__ */ jsx3(SegmentedControl3, { size: "sm", label: "Range", options: view.ranges, value: view.rangeId, onChange: (rangeId) => ui6({ kind: "range", rangeId }) }),
3096
3113
  right: view.partial ? /* @__PURE__ */ jsx3(Badge3, { tone: "warning", title: view.partialHint, children: "partial" }) : null
3097
3114
  }
3098
3115
  ) : null,
@@ -3107,10 +3124,10 @@ function MetricsPanelView({ controller, ...options }) {
3107
3124
  icon: tile.hasLevel ? /* @__PURE__ */ jsx3(Badge3, { tone: tile.tone, children: tile.level }) : null,
3108
3125
  sub: /* @__PURE__ */ jsxs3(Stack, { direction: "row", pad: "none", children: [
3109
3126
  tile.hasDelta ? /* @__PURE__ */ jsx3(Delta, { value: tile.delta, tone: tile.deltaTone }) : null,
3110
- tile.partial ? /* @__PURE__ */ jsx3(Text, { role: "meta", children: view.partialHint }) : null,
3127
+ tile.partial ? /* @__PURE__ */ jsx3(Text2, { role: "meta", children: view.partialHint }) : null,
3111
3128
  tile.hasSpark ? /* @__PURE__ */ jsx3(Sparkline, { data: tile.spark, label: tile.sparkLabel }) : null
3112
3129
  ] }),
3113
- onActivate: () => ui5({ kind: "activate", metricId: tile.id })
3130
+ onActivate: () => ui6({ kind: "activate", metricId: tile.id })
3114
3131
  },
3115
3132
  tile.id
3116
3133
  )) }) }) : null,
@@ -3312,7 +3329,7 @@ function emptyFilterGroup() {
3312
3329
  function offerableFilterOps(column, sourceOps = []) {
3313
3330
  const common = ["eq", "ne", "isEmpty", "isNotEmpty", "in", "notIn"];
3314
3331
  const ordered = ["gt", "gte", "lt", "lte", "between"];
3315
- const text5 = ["contains", "notContains", "startsWith", "endsWith"];
3332
+ const text6 = ["contains", "notContains", "startsWith", "endsWith"];
3316
3333
  const temporal = ["relative"];
3317
3334
  let ops = [...common];
3318
3335
  switch (column?.type) {
@@ -3330,10 +3347,10 @@ function offerableFilterOps(column, sourceOps = []) {
3330
3347
  case "url":
3331
3348
  case "select":
3332
3349
  case "multiselect":
3333
- ops = [...ops, ...text5];
3350
+ ops = [...ops, ...text6];
3334
3351
  break;
3335
3352
  default:
3336
- ops = [...ops, ...text5, ...ordered];
3353
+ ops = [...ops, ...text6, ...ordered];
3337
3354
  }
3338
3355
  if (sourceOps.length === 0) return ops;
3339
3356
  return ops.filter((op) => sourceOps.includes(op));
@@ -3964,7 +3981,7 @@ var fieldsManifest = {
3964
3981
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
3965
3982
  import {
3966
3983
  Badge as Badge4,
3967
- Column as Column3,
3984
+ Column as Column4,
3968
3985
  EmptyState as EmptyState4,
3969
3986
  IconButton as IconButton3,
3970
3987
  Note,
@@ -4099,13 +4116,13 @@ var fieldsViewManifest = {
4099
4116
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
4100
4117
  commands: []
4101
4118
  };
4102
- var EMPTY3 = { hasColumns: false, noColumns: true, sampled: false, manualApply: false, applyDisabled: true, columns: [], shelves: [], filters: [], filterCount: 0, hasFilters: false, noFilters: true, combinator: "and", countLabel: "0 fields" };
4103
- var BEHAVIOURS3 = {
4104
- "data/fieldsPanelView": (inputs) => inputs.view ?? EMPTY3
4119
+ var EMPTY4 = { hasColumns: false, noColumns: true, sampled: false, manualApply: false, applyDisabled: true, columns: [], shelves: [], filters: [], filterCount: 0, hasFilters: false, noFilters: true, combinator: "and", countLabel: "0 fields" };
4120
+ var BEHAVIOURS4 = {
4121
+ "data/fieldsPanelView": (inputs) => inputs.view ?? EMPTY4
4105
4122
  };
4106
4123
  var FIELDS_RUNTIME_UNITS = [];
4107
- var text3 = (b) => ({ component: "text", props: { text: b } });
4108
- var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4124
+ var text4 = (b) => ({ component: "text", props: { text: b } });
4125
+ var ui4 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4109
4126
  var removeButton = (label, on) => ({
4110
4127
  component: "chrome/IconButton",
4111
4128
  props: { size: { value: "sm" }, icon: { node: { component: "icons/Close", props: { size: { value: 11 } } } }, label },
@@ -4126,14 +4143,14 @@ var FIELDS_DECLARATION = {
4126
4143
  {
4127
4144
  component: "chrome/Toolbar",
4128
4145
  props: {
4129
- left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text3({ value: "sampled schema" })] } },
4146
+ left: { node: { component: "chrome/Badge", when: { input: "view.sampled" }, props: { tone: { value: "warning" }, title: { input: "view.sampleWarning" } }, children: [text4({ value: "sampled schema" })] } },
4130
4147
  right: {
4131
4148
  node: {
4132
4149
  component: "chrome/ToolbarGroup",
4133
4150
  props: { end: { value: true } },
4134
4151
  children: [
4135
- { component: "chrome/TextButton", on: { onClick: ui3("autoAssign") }, children: [text3({ value: "Auto-assign" })] },
4136
- { component: "chrome/TextButton", when: { input: "view.manualApply" }, props: { strong: { value: true }, disabled: { input: "view.applyDisabled" } }, on: { onClick: ui3("apply") }, children: [text3({ value: "Apply" })] }
4152
+ { component: "chrome/TextButton", on: { onClick: ui4("autoAssign") }, children: [text4({ value: "Auto-assign" })] },
4153
+ { component: "chrome/TextButton", when: { input: "view.manualApply" }, props: { strong: { value: true }, disabled: { input: "view.applyDisabled" } }, on: { onClick: ui4("apply") }, children: [text4({ value: "Apply" })] }
4137
4154
  ]
4138
4155
  }
4139
4156
  }
@@ -4153,8 +4170,8 @@ var FIELDS_DECLARATION = {
4153
4170
  {
4154
4171
  component: "chrome/Row",
4155
4172
  each: { input: "view.columns", key: "id" },
4156
- props: { label: { item: "name" }, meta: { item: "role" }, selected: { item: "focused" }, trailing: { node: { component: "chrome/Badge", when: { item: "stat" }, props: { title: { item: "statTitle" } }, children: [text3({ item: "stat" })] } } },
4157
- on: { onClick: ui3("focus", { columnId: { item: "id" } }) }
4173
+ props: { label: { item: "name" }, meta: { item: "role" }, selected: { item: "focused" }, trailing: { node: { component: "chrome/Badge", when: { item: "stat" }, props: { title: { item: "statTitle" } }, children: [text4({ item: "stat" })] } } },
4174
+ on: { onClick: ui4("focus", { columnId: { item: "id" } }) }
4158
4175
  }
4159
4176
  ]
4160
4177
  }
@@ -4163,9 +4180,9 @@ var FIELDS_DECLARATION = {
4163
4180
  {
4164
4181
  component: "chrome/Section",
4165
4182
  each: { input: "view.shelves", key: "id" },
4166
- props: { title: { item: "label" }, flush: { value: true }, badge: { node: { component: "chrome/Badge", when: { item: "hasEntries" }, children: [text3({ item: "count" })] } } },
4183
+ props: { title: { item: "label" }, flush: { value: true }, badge: { node: { component: "chrome/Badge", when: { item: "hasEntries" }, children: [text4({ item: "count" })] } } },
4167
4184
  children: [
4168
- { component: "chrome/Note", when: { item: "empty" }, children: [text3({ item: "hint" })] },
4185
+ { component: "chrome/Note", when: { item: "empty" }, children: [text4({ item: "hint" })] },
4169
4186
  {
4170
4187
  component: "chrome/RowList",
4171
4188
  when: { item: "hasEntries" },
@@ -4180,13 +4197,13 @@ var FIELDS_DECLARATION = {
4180
4197
  node: {
4181
4198
  component: "fragment",
4182
4199
  children: [
4183
- { component: "chrome/Select", when: { item: "aggregated" }, props: { size: { value: "sm" }, label: { value: "Aggregate" }, options: { item: "aggregates" }, value: { item: "fn" } }, on: { onChange: ui3("aggregate", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" }, fn: { arg: "" } }) } },
4184
- { component: "chrome/TextButton", when: { item: "sortable" }, on: { onClick: ui3("direction", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } }) }, children: [text3({ item: "direction" })] },
4185
- { component: "chrome/Badge", when: { item: "granularity" }, children: [text3({ item: "granularity" })] }
4200
+ { component: "chrome/Select", when: { item: "aggregated" }, props: { size: { value: "sm" }, label: { value: "Aggregate" }, options: { item: "aggregates" }, value: { item: "fn" } }, on: { onChange: ui4("aggregate", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" }, fn: { arg: "" } }) } },
4201
+ { component: "chrome/TextButton", when: { item: "sortable" }, on: { onClick: ui4("direction", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } }) }, children: [text4({ item: "direction" })] },
4202
+ { component: "chrome/Badge", when: { item: "granularity" }, children: [text4({ item: "granularity" })] }
4186
4203
  ]
4187
4204
  }
4188
4205
  },
4189
- trailing: { node: removeButton({ item: "removeLabel" }, ui3("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4206
+ trailing: { node: removeButton({ item: "removeLabel" }, ui4("remove", { shelfId: { item: "shelfId" }, columnId: { item: "columnId" } })) }
4190
4207
  }
4191
4208
  }
4192
4209
  ]
@@ -4198,11 +4215,11 @@ var FIELDS_DECLARATION = {
4198
4215
  props: {
4199
4216
  title: { value: "Filters" },
4200
4217
  flush: { value: true },
4201
- badge: { node: { component: "chrome/Badge", when: { input: "view.hasFilters" }, children: [text3({ input: "view.filterCount" })] } },
4202
- actions: { node: { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "icons/Plus", props: { size: { value: 11 } } } }, label: { value: "Add filter" } }, on: { onClick: ui3("addFilter") } } }
4218
+ badge: { node: { component: "chrome/Badge", when: { input: "view.hasFilters" }, children: [text4({ input: "view.filterCount" })] } },
4219
+ actions: { node: { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "icons/Plus", props: { size: { value: 11 } } } }, label: { value: "Add filter" } }, on: { onClick: ui4("addFilter") } } }
4203
4220
  },
4204
4221
  children: [
4205
- { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text3({ value: "No filters. Everything passes." })] },
4222
+ { component: "chrome/Note", when: { input: "view.noFilters" }, children: [text4({ value: "No filters. Everything passes." })] },
4206
4223
  {
4207
4224
  component: "chrome/RowList",
4208
4225
  when: { input: "view.hasFilters" },
@@ -4212,8 +4229,8 @@ var FIELDS_DECLARATION = {
4212
4229
  each: { input: "view.filters", key: "key" },
4213
4230
  props: {
4214
4231
  label: { item: "label" },
4215
- meta: { node: { component: "chrome/Select", when: { item: "leaf" }, props: { size: { value: "sm" }, label: { value: "Operator" }, options: { item: "ops" }, value: { item: "op" } }, on: { onChange: ui3("filterOp", { index: { item: "index" }, op: { arg: "" } }) } } },
4216
- trailing: { node: removeButton({ item: "removeLabel" }, ui3("removeFilter", { index: { item: "index" } })) }
4232
+ meta: { node: { component: "chrome/Select", when: { item: "leaf" }, props: { size: { value: "sm" }, label: { value: "Operator" }, options: { item: "ops" }, value: { item: "op" } }, on: { onChange: ui4("filterOp", { index: { item: "index" }, op: { arg: "" } }) } } },
4233
+ trailing: { node: removeButton({ item: "removeLabel" }, ui4("removeFilter", { index: { item: "index" } })) }
4217
4234
  }
4218
4235
  }
4219
4236
  ]
@@ -4222,8 +4239,8 @@ var FIELDS_DECLARATION = {
4222
4239
  component: "chrome/Toolbar",
4223
4240
  props: {
4224
4241
  divided: { value: false },
4225
- left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Combinator" }, options: { value: COMBINATORS }, value: { input: "view.combinator" } }, on: { onChange: ui3("combinator", { combinator: { arg: "" } }) } } },
4226
- right: { node: { component: "chrome/TextButton", on: { onClick: ui3("addGroup") }, children: [text3({ value: "+ group" })] } }
4242
+ left: { node: { component: "chrome/SegmentedControl", props: { size: { value: "sm" }, label: { value: "Combinator" }, options: { value: COMBINATORS }, value: { input: "view.combinator" } }, on: { onChange: ui4("combinator", { combinator: { arg: "" } }) } } },
4243
+ right: { node: { component: "chrome/TextButton", on: { onClick: ui4("addGroup") }, children: [text4({ value: "+ group" })] } }
4227
4244
  }
4228
4245
  }
4229
4246
  ]
@@ -4245,18 +4262,18 @@ var COMBINATORS2 = [{ value: "and", label: "All" }, { value: "or", label: "Any"
4245
4262
  function FieldsPanelView({ controller, showStats, manualApply }) {
4246
4263
  useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
4247
4264
  const view = fieldsViewState(controller, { showStats, manualApply });
4248
- const ui5 = (intent) => applyFieldsUi(controller, intent);
4265
+ const ui6 = (intent) => applyFieldsUi(controller, intent);
4249
4266
  if (view.noColumns) {
4250
4267
  return /* @__PURE__ */ jsx4(EmptyState4, { title: "No fields", hint: "Connect a source \u2014 its columns appear here, ready to drop onto a shelf." });
4251
4268
  }
4252
- return /* @__PURE__ */ jsxs4(Column3, { children: [
4269
+ return /* @__PURE__ */ jsxs4(Column4, { children: [
4253
4270
  /* @__PURE__ */ jsx4(
4254
4271
  Toolbar4,
4255
4272
  {
4256
4273
  left: view.sampled ? /* @__PURE__ */ jsx4(Badge4, { tone: "warning", title: view.sampleWarning, children: "sampled schema" }) : null,
4257
4274
  right: /* @__PURE__ */ jsxs4(ToolbarGroup3, { end: true, children: [
4258
- /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui5({ kind: "autoAssign" }), children: "Auto-assign" }),
4259
- view.manualApply ? /* @__PURE__ */ jsx4(TextButton, { strong: true, disabled: view.applyDisabled, onClick: () => ui5({ kind: "apply" }), children: "Apply" }) : null
4275
+ /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui6({ kind: "autoAssign" }), children: "Auto-assign" }),
4276
+ view.manualApply ? /* @__PURE__ */ jsx4(TextButton, { strong: true, disabled: view.applyDisabled, onClick: () => ui6({ kind: "apply" }), children: "Apply" }) : null
4260
4277
  ] })
4261
4278
  }
4262
4279
  ),
@@ -4268,7 +4285,7 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4268
4285
  meta: column.role,
4269
4286
  selected: column.focused,
4270
4287
  trailing: column.stat ? /* @__PURE__ */ jsx4(Badge4, { title: column.statTitle, children: column.stat }) : null,
4271
- onClick: () => ui5({ kind: "focus", columnId: column.id })
4288
+ onClick: () => ui6({ kind: "focus", columnId: column.id })
4272
4289
  },
4273
4290
  column.id
4274
4291
  )) }) }),
@@ -4280,11 +4297,11 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4280
4297
  label: entry.label,
4281
4298
  dimmed: entry.suggested,
4282
4299
  meta: /* @__PURE__ */ jsxs4(Fragment2, { children: [
4283
- entry.aggregated ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Aggregate", options: entry.aggregates, value: entry.fn, onChange: (fn) => ui5({ kind: "aggregate", shelfId: entry.shelfId, columnId: entry.columnId, fn }) }) : null,
4284
- entry.sortable ? /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui5({ kind: "direction", shelfId: entry.shelfId, columnId: entry.columnId }), children: entry.direction }) : null,
4300
+ entry.aggregated ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Aggregate", options: entry.aggregates, value: entry.fn, onChange: (fn) => ui6({ kind: "aggregate", shelfId: entry.shelfId, columnId: entry.columnId, fn }) }) : null,
4301
+ entry.sortable ? /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui6({ kind: "direction", shelfId: entry.shelfId, columnId: entry.columnId }), children: entry.direction }) : null,
4285
4302
  entry.granularity ? /* @__PURE__ */ jsx4(Badge4, { children: entry.granularity }) : null
4286
4303
  ] }),
4287
- trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: entry.removeLabel, onClick: () => ui5({ kind: "remove", shelfId: entry.shelfId, columnId: entry.columnId }) })
4304
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: entry.removeLabel, onClick: () => ui6({ kind: "remove", shelfId: entry.shelfId, columnId: entry.columnId }) })
4288
4305
  },
4289
4306
  entry.columnId
4290
4307
  )) }) : null
@@ -4295,15 +4312,15 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4295
4312
  title: "Filters",
4296
4313
  flush: true,
4297
4314
  badge: view.hasFilters ? /* @__PURE__ */ jsx4(Badge4, { children: view.filterCount }) : null,
4298
- actions: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PlusGlyph, { size: 11 }), label: "Add filter", onClick: () => ui5({ kind: "addFilter" }) }),
4315
+ actions: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PlusGlyph, { size: 11 }), label: "Add filter", onClick: () => ui6({ kind: "addFilter" }) }),
4299
4316
  children: [
4300
4317
  view.noFilters ? /* @__PURE__ */ jsx4(Note, { children: "No filters. Everything passes." }) : null,
4301
4318
  view.hasFilters ? /* @__PURE__ */ jsx4(RowList, { children: view.filters.map((filter) => /* @__PURE__ */ jsx4(
4302
4319
  Row,
4303
4320
  {
4304
4321
  label: filter.label,
4305
- meta: filter.leaf ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Operator", options: filter.ops, value: filter.op, onChange: (op) => ui5({ kind: "filterOp", index: filter.index, op }) }) : null,
4306
- trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: filter.removeLabel, onClick: () => ui5({ kind: "removeFilter", index: filter.index }) })
4322
+ meta: filter.leaf ? /* @__PURE__ */ jsx4(Select, { size: "sm", label: "Operator", options: filter.ops, value: filter.op, onChange: (op) => ui6({ kind: "filterOp", index: filter.index, op }) }) : null,
4323
+ trailing: /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CloseGlyph, { size: 11 }), label: filter.removeLabel, onClick: () => ui6({ kind: "removeFilter", index: filter.index }) })
4307
4324
  },
4308
4325
  filter.key
4309
4326
  )) }) : null,
@@ -4311,8 +4328,8 @@ function FieldsPanelView({ controller, showStats, manualApply }) {
4311
4328
  Toolbar4,
4312
4329
  {
4313
4330
  divided: false,
4314
- left: /* @__PURE__ */ jsx4(SegmentedControl4, { size: "sm", label: "Combinator", options: COMBINATORS2, value: view.combinator, onChange: (combinator) => ui5({ kind: "combinator", combinator }) }),
4315
- right: /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui5({ kind: "addGroup" }), children: "+ group" })
4331
+ left: /* @__PURE__ */ jsx4(SegmentedControl4, { size: "sm", label: "Combinator", options: COMBINATORS2, value: view.combinator, onChange: (combinator) => ui6({ kind: "combinator", combinator }) }),
4332
+ right: /* @__PURE__ */ jsx4(TextButton, { onClick: () => ui6({ kind: "addGroup" }), children: "+ group" })
4316
4333
  }
4317
4334
  )
4318
4335
  ]
@@ -5463,10 +5480,10 @@ function parseJsonPath(path) {
5463
5480
  }
5464
5481
  return steps;
5465
5482
  }
5466
- function findBracketEnd(text5, start) {
5483
+ function findBracketEnd(text6, start) {
5467
5484
  let quote = null;
5468
- for (let i = start + 1; i < text5.length; i += 1) {
5469
- const char = text5[i];
5485
+ for (let i = start + 1; i < text6.length; i += 1) {
5486
+ const char = text6[i];
5470
5487
  if (quote) {
5471
5488
  if (char === "\\") i += 1;
5472
5489
  else if (char === quote) quote = null;
@@ -5890,9 +5907,9 @@ function firstGraphqlError(errors) {
5890
5907
  }
5891
5908
 
5892
5909
  // src/data/connectors/executors/csv.ts
5893
- function sniffDelimiter(text5) {
5910
+ function sniffDelimiter(text6) {
5894
5911
  const candidates = [",", ";", " ", "|"];
5895
- const line = firstLogicalLine(text5);
5912
+ const line = firstLogicalLine(text6);
5896
5913
  let best = ",";
5897
5914
  let bestCount = 0;
5898
5915
  for (const candidate of candidates) {
@@ -5914,21 +5931,21 @@ function sniffDelimiter(text5) {
5914
5931
  }
5915
5932
  return best;
5916
5933
  }
5917
- function firstLogicalLine(text5) {
5934
+ function firstLogicalLine(text6) {
5918
5935
  let inQuotes = false;
5919
- for (let i = 0; i < text5.length; i += 1) {
5920
- const char = text5[i];
5936
+ for (let i = 0; i < text6.length; i += 1) {
5937
+ const char = text6[i];
5921
5938
  if (char === '"') {
5922
- if (inQuotes && text5[i + 1] === '"') i += 1;
5939
+ if (inQuotes && text6[i + 1] === '"') i += 1;
5923
5940
  else inQuotes = !inQuotes;
5924
5941
  } else if ((char === "\n" || char === "\r") && !inQuotes) {
5925
- return text5.slice(0, i);
5942
+ return text6.slice(0, i);
5926
5943
  }
5927
5944
  }
5928
- return text5;
5945
+ return text6;
5929
5946
  }
5930
- function parseCsv(text5, options = {}) {
5931
- const source = text5.charCodeAt(0) === 65279 ? text5.slice(1) : text5;
5947
+ function parseCsv(text6, options = {}) {
5948
+ const source = text6.charCodeAt(0) === 65279 ? text6.slice(1) : text6;
5932
5949
  const delimiter = options.delimiter && options.delimiter.length > 0 ? options.delimiter : sniffDelimiter(source);
5933
5950
  const quote = options.quote && options.quote.length > 0 ? options.quote : '"';
5934
5951
  const hasHeader = options.hasHeader ?? true;
@@ -5963,17 +5980,17 @@ function dedupe(names) {
5963
5980
  return count === 0 ? name : `${name}_${count + 1}`;
5964
5981
  });
5965
5982
  }
5966
- function scanRecords(text5, delimiter, quote) {
5983
+ function scanRecords(text6, delimiter, quote) {
5967
5984
  const records = [];
5968
5985
  let fields = [];
5969
5986
  let field = "";
5970
5987
  let inQuotes = false;
5971
5988
  let i = 0;
5972
- while (i < text5.length) {
5973
- const char = text5[i];
5989
+ while (i < text6.length) {
5990
+ const char = text6[i];
5974
5991
  if (inQuotes) {
5975
5992
  if (char === quote) {
5976
- if (text5[i + 1] === quote) {
5993
+ if (text6[i + 1] === quote) {
5977
5994
  field += quote;
5978
5995
  i += 2;
5979
5996
  continue;
@@ -5991,7 +6008,7 @@ function scanRecords(text5, delimiter, quote) {
5991
6008
  i += 1;
5992
6009
  continue;
5993
6010
  }
5994
- if (text5.startsWith(delimiter, i)) {
6011
+ if (text6.startsWith(delimiter, i)) {
5995
6012
  fields.push(field);
5996
6013
  field = "";
5997
6014
  i += delimiter.length;
@@ -6002,7 +6019,7 @@ function scanRecords(text5, delimiter, quote) {
6002
6019
  records.push(fields);
6003
6020
  fields = [];
6004
6021
  field = "";
6005
- i += char === "\r" && text5[i + 1] === "\n" ? 2 : 1;
6022
+ i += char === "\r" && text6[i + 1] === "\n" ? 2 : 1;
6006
6023
  continue;
6007
6024
  }
6008
6025
  field += char;
@@ -6572,12 +6589,12 @@ import { useSyncExternalStore as useSyncExternalStore5 } from "react";
6572
6589
  import {
6573
6590
  Badge as Badge5,
6574
6591
  Code,
6575
- Column as Column4,
6592
+ Column as Column5,
6576
6593
  EmptyState as EmptyState5,
6577
6594
  Field,
6578
6595
  FieldGroup,
6579
6596
  IconButton as IconButton4,
6580
- Input,
6597
+ Input as Input2,
6581
6598
  Link,
6582
6599
  Note as Note2,
6583
6600
  Row as Row2,
@@ -6585,7 +6602,7 @@ import {
6585
6602
  ScrollArea as ScrollArea4,
6586
6603
  Select as Select2,
6587
6604
  StatusBar as StatusBar5,
6588
- Text as Text2,
6605
+ Text as Text3,
6589
6606
  TextButton as TextButton2,
6590
6607
  Toggle,
6591
6608
  Toolbar as Toolbar5,
@@ -6738,13 +6755,13 @@ var connectorsViewManifest = {
6738
6755
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
6739
6756
  commands: []
6740
6757
  };
6741
- var EMPTY4 = { listing: true, sources: [], empty: true, emptyHint: "Paste CSV or JSON to start with an inline source \u2014 no setup needed.", listed: false, countLabel: "0 sources", form: false, formTitle: "", formBroken: false, formReady: false, fields: [], submitDisabled: true };
6742
- var BEHAVIOURS4 = {
6743
- "data/connectorsPanelView": (inputs) => inputs.view ?? EMPTY4
6758
+ var EMPTY5 = { listing: true, sources: [], empty: true, emptyHint: "Paste CSV or JSON to start with an inline source \u2014 no setup needed.", listed: false, countLabel: "0 sources", form: false, formTitle: "", formBroken: false, formReady: false, fields: [], submitDisabled: true };
6759
+ var BEHAVIOURS5 = {
6760
+ "data/connectorsPanelView": (inputs) => inputs.view ?? EMPTY5
6744
6761
  };
6745
6762
  var CONNECTORS_RUNTIME_UNITS = [];
6746
- var text4 = (b) => ({ component: "text", props: { text: b } });
6747
- var ui4 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
6763
+ var text5 = (b) => ({ component: "text", props: { text: b } });
6764
+ var ui5 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
6748
6765
  var CONNECTORS_DECLARATION = {
6749
6766
  format: "xeno.block@1",
6750
6767
  manifest: connectorsViewManifest,
@@ -6756,7 +6773,7 @@ var CONNECTORS_DECLARATION = {
6756
6773
  component: "chrome/Column",
6757
6774
  when: { input: "view.listing" },
6758
6775
  children: [
6759
- { component: "chrome/Toolbar", props: { right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/IconButton", props: { icon: { node: { component: "icons/Refresh" } }, label: { value: "Refresh all" } }, on: { onClick: ui4("refreshAll") } }] } } } },
6776
+ { component: "chrome/Toolbar", props: { right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/IconButton", props: { icon: { node: { component: "icons/Refresh" } }, label: { value: "Refresh all" } }, on: { onClick: ui5("refreshAll") } }] } } } },
6760
6777
  { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No sources" }, hint: { input: "view.emptyHint" } } },
6761
6778
  {
6762
6779
  component: "chrome/ScrollArea",
@@ -6769,8 +6786,8 @@ var CONNECTORS_DECLARATION = {
6769
6786
  {
6770
6787
  component: "chrome/Row",
6771
6788
  each: { input: "view.sources", key: "id" },
6772
- props: { icon: { node: { component: "icons/Plug" } }, label: { item: "name" }, meta: { item: "kind" }, selected: { item: "selected" }, disabled: { item: "disabled" }, title: { item: "title" }, trailing: { node: { component: "chrome/Badge", props: { tone: { item: "tone" } }, children: [text4({ item: "health" })] } } },
6773
- on: { onClick: ui4("select", { id: { item: "id" } }) }
6789
+ props: { icon: { node: { component: "icons/Plug" } }, label: { item: "name" }, meta: { item: "kind" }, selected: { item: "selected" }, disabled: { item: "disabled" }, title: { item: "title" }, trailing: { node: { component: "chrome/Badge", props: { tone: { item: "tone" } }, children: [text5({ item: "health" })] } } },
6790
+ on: { onClick: ui5("select", { id: { item: "id" } }) }
6774
6791
  }
6775
6792
  ]
6776
6793
  }
@@ -6783,25 +6800,25 @@ var CONNECTORS_DECLARATION = {
6783
6800
  component: "chrome/Column",
6784
6801
  when: { input: "view.form" },
6785
6802
  children: [
6786
- { component: "chrome/Toolbar", props: { left: { node: { component: "chrome/Text", props: { role: { value: "title" } }, children: [text4({ input: "view.formTitle" })] } } } },
6803
+ { component: "chrome/Toolbar", props: { left: { node: { component: "chrome/Text", props: { role: { value: "title" } }, children: [text5({ input: "view.formTitle" })] } } } },
6787
6804
  { component: "chrome/EmptyState", when: { input: "view.formBroken" }, props: { title: { value: "This connection cannot be set up" }, hint: { input: "view.formProblem" } } },
6788
6805
  {
6789
6806
  component: "chrome/Toolbar",
6790
6807
  when: { input: "view.formBroken" },
6791
- props: { divided: { value: false }, right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/TextButton", on: { onClick: ui4("cancel") }, children: [text4({ value: "Close" })] }] } } }
6808
+ props: { divided: { value: false }, right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/TextButton", on: { onClick: ui5("cancel") }, children: [text5({ value: "Close" })] }] } } }
6792
6809
  },
6793
6810
  {
6794
6811
  component: "chrome/ScrollArea",
6795
6812
  when: { input: "view.formReady" },
6796
6813
  children: [
6797
- { component: "chrome/Note", when: { input: "view.formMessage" }, children: [text4({ input: "view.formMessage" })] },
6814
+ { component: "chrome/Note", when: { input: "view.formMessage" }, children: [text5({ input: "view.formMessage" })] },
6798
6815
  {
6799
6816
  component: "fragment",
6800
6817
  when: { input: "view.redirectUri" },
6801
6818
  children: [
6802
- { component: "chrome/Note", children: [text4({ value: "Add this exact redirect URI to the provider:" })] },
6819
+ { component: "chrome/Note", children: [text5({ value: "Add this exact redirect URI to the provider:" })] },
6803
6820
  // VERBATIM. Rendering the real string is what prevents a redirect-URI mismatch.
6804
- { component: "chrome/Code", props: { variant: { value: "flush" } }, children: [text4({ input: "view.redirectUri" })] }
6821
+ { component: "chrome/Code", props: { variant: { value: "flush" } }, children: [text5({ input: "view.redirectUri" })] }
6805
6822
  ]
6806
6823
  },
6807
6824
  {
@@ -6812,14 +6829,14 @@ var CONNECTORS_DECLARATION = {
6812
6829
  each: { input: "view.fields", key: "key" },
6813
6830
  props: { label: { item: "label" }, hint: { item: "hint" } },
6814
6831
  children: [
6815
- { component: "chrome/Input", when: { item: "isText" }, props: { type: { item: "inputType" }, value: { item: "text" }, placeholder: { item: "placeholder" }, autoComplete: { value: "off" }, label: { item: "label" } }, on: { onChange: ui4("field", { key: { item: "key" }, value: { arg: "" } }) } },
6816
- { component: "chrome/Select", when: { item: "isSelect" }, props: { options: { item: "options" }, value: { item: "text" }, placeholder: { value: "" }, label: { item: "label" } }, on: { onChange: ui4("field", { key: { item: "key" }, value: { arg: "" } }) } },
6817
- { component: "chrome/Toggle", when: { item: "isBoolean" }, props: { checked: { item: "checked" }, label: { item: "label" } }, on: { onChange: ui4("field", { key: { item: "key" }, value: { arg: "" } }) } }
6832
+ { component: "chrome/Input", when: { item: "isText" }, props: { type: { item: "inputType" }, value: { item: "text" }, placeholder: { item: "placeholder" }, autoComplete: { value: "off" }, label: { item: "label" } }, on: { onChange: ui5("field", { key: { item: "key" }, value: { arg: "" } }) } },
6833
+ { component: "chrome/Select", when: { item: "isSelect" }, props: { options: { item: "options" }, value: { item: "text" }, placeholder: { value: "" }, label: { item: "label" } }, on: { onChange: ui5("field", { key: { item: "key" }, value: { arg: "" } }) } },
6834
+ { component: "chrome/Toggle", when: { item: "isBoolean" }, props: { checked: { item: "checked" }, label: { item: "label" } }, on: { onChange: ui5("field", { key: { item: "key" }, value: { arg: "" } }) } }
6818
6835
  ]
6819
6836
  }
6820
6837
  ]
6821
6838
  },
6822
- { component: "chrome/Note", when: { input: "view.docsUrl" }, children: [{ component: "chrome/Link", props: { href: { input: "view.docsUrl" } }, children: [text4({ value: "Where do I get this?" })] }] }
6839
+ { component: "chrome/Note", when: { input: "view.docsUrl" }, children: [{ component: "chrome/Link", props: { href: { input: "view.docsUrl" } }, children: [text5({ value: "Where do I get this?" })] }] }
6823
6840
  ]
6824
6841
  },
6825
6842
  {
@@ -6832,8 +6849,8 @@ var CONNECTORS_DECLARATION = {
6832
6849
  component: "chrome/ToolbarGroup",
6833
6850
  props: { end: { value: true } },
6834
6851
  children: [
6835
- { component: "chrome/TextButton", on: { onClick: ui4("cancel") }, children: [text4({ value: "Cancel" })] },
6836
- { component: "chrome/TextButton", props: { strong: { value: true }, disabled: { input: "view.submitDisabled" } }, on: { onClick: ui4("submit") }, children: [text4({ value: "Connect" })] }
6852
+ { component: "chrome/TextButton", on: { onClick: ui5("cancel") }, children: [text5({ value: "Cancel" })] },
6853
+ { component: "chrome/TextButton", props: { strong: { value: true }, disabled: { input: "view.submitDisabled" } }, on: { onClick: ui5("submit") }, children: [text5({ value: "Connect" })] }
6837
6854
  ]
6838
6855
  }
6839
6856
  }
@@ -6852,10 +6869,10 @@ var RefreshGlyph2 = ICONS["icons/Refresh"];
6852
6869
  function ConnectorsPanelView({ controller, emptyHint }) {
6853
6870
  useSyncExternalStore5(controller.subscribe, controller.getState, controller.getState);
6854
6871
  const view = connectorsViewState(controller, { emptyHint });
6855
- const ui5 = (intent) => applyConnectorsUi(controller, intent);
6872
+ const ui6 = (intent) => applyConnectorsUi(controller, intent);
6856
6873
  return /* @__PURE__ */ jsxs5(Fragment3, { children: [
6857
- view.listing ? /* @__PURE__ */ jsxs5(Column4, { children: [
6858
- /* @__PURE__ */ jsx5(Toolbar5, { right: /* @__PURE__ */ jsx5(ToolbarGroup4, { end: true, children: /* @__PURE__ */ jsx5(IconButton4, { icon: /* @__PURE__ */ jsx5(RefreshGlyph2, {}), label: "Refresh all", onClick: () => ui5({ kind: "refreshAll" }) }) }) }),
6874
+ view.listing ? /* @__PURE__ */ jsxs5(Column5, { children: [
6875
+ /* @__PURE__ */ jsx5(Toolbar5, { right: /* @__PURE__ */ jsx5(ToolbarGroup4, { end: true, children: /* @__PURE__ */ jsx5(IconButton4, { icon: /* @__PURE__ */ jsx5(RefreshGlyph2, {}), label: "Refresh all", onClick: () => ui6({ kind: "refreshAll" }) }) }) }),
6859
6876
  view.empty ? /* @__PURE__ */ jsx5(EmptyState5, { title: "No sources", hint: view.emptyHint }) : null,
6860
6877
  view.listed ? /* @__PURE__ */ jsx5(ScrollArea4, { children: /* @__PURE__ */ jsx5(RowList2, { role: "listbox", "aria-label": "Data sources", children: view.sources.map((source) => /* @__PURE__ */ jsx5(
6861
6878
  Row2,
@@ -6867,16 +6884,16 @@ function ConnectorsPanelView({ controller, emptyHint }) {
6867
6884
  disabled: source.disabled,
6868
6885
  title: source.title,
6869
6886
  trailing: /* @__PURE__ */ jsx5(Badge5, { tone: source.tone, children: source.health }),
6870
- onClick: () => ui5({ kind: "select", id: source.id })
6887
+ onClick: () => ui6({ kind: "select", id: source.id })
6871
6888
  },
6872
6889
  source.id
6873
6890
  )) }) }) : null,
6874
6891
  /* @__PURE__ */ jsx5(StatusBar5, { left: view.countLabel, right: view.busyLabel })
6875
6892
  ] }) : null,
6876
- view.form ? /* @__PURE__ */ jsxs5(Column4, { children: [
6877
- /* @__PURE__ */ jsx5(Toolbar5, { left: /* @__PURE__ */ jsx5(Text2, { role: "title", children: view.formTitle }) }),
6893
+ view.form ? /* @__PURE__ */ jsxs5(Column5, { children: [
6894
+ /* @__PURE__ */ jsx5(Toolbar5, { left: /* @__PURE__ */ jsx5(Text3, { role: "title", children: view.formTitle }) }),
6878
6895
  view.formBroken ? /* @__PURE__ */ jsx5(EmptyState5, { title: "This connection cannot be set up", hint: view.formProblem }) : null,
6879
- view.formBroken ? /* @__PURE__ */ jsx5(Toolbar5, { divided: false, right: /* @__PURE__ */ jsx5(ToolbarGroup4, { end: true, children: /* @__PURE__ */ jsx5(TextButton2, { onClick: () => ui5({ kind: "cancel" }), children: "Close" }) }) }) : null,
6896
+ view.formBroken ? /* @__PURE__ */ jsx5(Toolbar5, { divided: false, right: /* @__PURE__ */ jsx5(ToolbarGroup4, { end: true, children: /* @__PURE__ */ jsx5(TextButton2, { onClick: () => ui6({ kind: "cancel" }), children: "Close" }) }) }) : null,
6880
6897
  view.formReady ? /* @__PURE__ */ jsxs5(ScrollArea4, { children: [
6881
6898
  view.formMessage ? /* @__PURE__ */ jsx5(Note2, { children: view.formMessage }) : null,
6882
6899
  view.redirectUri ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
@@ -6884,9 +6901,9 @@ function ConnectorsPanelView({ controller, emptyHint }) {
6884
6901
  /* @__PURE__ */ jsx5(Code, { variant: "flush", children: view.redirectUri })
6885
6902
  ] }) : null,
6886
6903
  /* @__PURE__ */ jsx5(FieldGroup, { children: view.fields.map((field) => /* @__PURE__ */ jsxs5(Field, { label: field.label, hint: field.hint, children: [
6887
- field.isText ? /* @__PURE__ */ jsx5(Input, { type: field.inputType, value: field.text, placeholder: field.placeholder, autoComplete: "off", label: field.label, onChange: (value) => ui5({ kind: "field", key: field.key, value }) }) : null,
6888
- field.isSelect ? /* @__PURE__ */ jsx5(Select2, { options: field.options, value: field.text, placeholder: "", label: field.label, onChange: (value) => ui5({ kind: "field", key: field.key, value }) }) : null,
6889
- field.isBoolean ? /* @__PURE__ */ jsx5(Toggle, { checked: field.checked, label: field.label, onChange: (value) => ui5({ kind: "field", key: field.key, value }) }) : null
6904
+ field.isText ? /* @__PURE__ */ jsx5(Input2, { type: field.inputType, value: field.text, placeholder: field.placeholder, autoComplete: "off", label: field.label, onChange: (value) => ui6({ kind: "field", key: field.key, value }) }) : null,
6905
+ field.isSelect ? /* @__PURE__ */ jsx5(Select2, { options: field.options, value: field.text, placeholder: "", label: field.label, onChange: (value) => ui6({ kind: "field", key: field.key, value }) }) : null,
6906
+ field.isBoolean ? /* @__PURE__ */ jsx5(Toggle, { checked: field.checked, label: field.label, onChange: (value) => ui6({ kind: "field", key: field.key, value }) }) : null
6890
6907
  ] }, field.key)) }),
6891
6908
  view.docsUrl ? /* @__PURE__ */ jsx5(Note2, { children: /* @__PURE__ */ jsx5(Link, { href: view.docsUrl, children: "Where do I get this?" }) }) : null
6892
6909
  ] }) : null,
@@ -6895,8 +6912,8 @@ function ConnectorsPanelView({ controller, emptyHint }) {
6895
6912
  {
6896
6913
  divided: false,
6897
6914
  right: /* @__PURE__ */ jsxs5(ToolbarGroup4, { end: true, children: [
6898
- /* @__PURE__ */ jsx5(TextButton2, { onClick: () => ui5({ kind: "cancel" }), children: "Cancel" }),
6899
- /* @__PURE__ */ jsx5(TextButton2, { strong: true, disabled: view.submitDisabled, onClick: () => ui5({ kind: "submit" }), children: "Connect" })
6915
+ /* @__PURE__ */ jsx5(TextButton2, { onClick: () => ui6({ kind: "cancel" }), children: "Cancel" }),
6916
+ /* @__PURE__ */ jsx5(TextButton2, { strong: true, disabled: view.submitDisabled, onClick: () => ui6({ kind: "submit" }), children: "Connect" })
6900
6917
  ] })
6901
6918
  }
6902
6919
  ) : null
@@ -7218,14 +7235,14 @@ export {
7218
7235
  AGENT_HIDDEN_COMMANDS,
7219
7236
  ALL_AGGREGATES,
7220
7237
  ALL_CHART_TYPES,
7221
- BEHAVIOURS as CHART_BEHAVIOURS,
7238
+ BEHAVIOURS2 as CHART_BEHAVIOURS,
7222
7239
  CHART_DECLARATION,
7223
7240
  CHART_EMPTY_FILL,
7224
7241
  CHART_PALETTE_STEPS,
7225
7242
  CHART_PANEL_ID,
7226
7243
  CHART_RUNTIME_UNITS,
7227
7244
  CHART_TYPE_LABELS,
7228
- BEHAVIOURS4 as CONNECTORS_BEHAVIOURS,
7245
+ BEHAVIOURS5 as CONNECTORS_BEHAVIOURS,
7229
7246
  CONNECTORS_DECLARATION,
7230
7247
  CONNECTORS_PANEL_ID,
7231
7248
  CONNECTORS_RUNTIME_UNITS,
@@ -7241,14 +7258,14 @@ export {
7241
7258
  DEFAULT_RANGES,
7242
7259
  EMPTY_LAYOUT,
7243
7260
  EXPIRY_SKEW_MS,
7244
- BEHAVIOURS3 as FIELDS_BEHAVIOURS,
7261
+ BEHAVIOURS4 as FIELDS_BEHAVIOURS,
7245
7262
  FIELDS_DECLARATION,
7246
7263
  FIELDS_PANEL_ID,
7247
7264
  FIELDS_RUNTIME_UNITS,
7248
7265
  FieldsController,
7249
7266
  FieldsPanelView,
7250
7267
  IMPLEMENTED_VIEW_MODES,
7251
- BEHAVIOURS2 as METRICS_BEHAVIOURS,
7268
+ BEHAVIOURS3 as METRICS_BEHAVIOURS,
7252
7269
  METRICS_DECLARATION,
7253
7270
  METRICS_PANEL_ID,
7254
7271
  METRICS_RUNTIME_UNITS,
@@ -7259,14 +7276,19 @@ export {
7259
7276
  MetricsPanelView,
7260
7277
  SECRET_HEADER_NAMES,
7261
7278
  SHELF_PRESETS,
7279
+ BEHAVIOURS as TABLE_BEHAVIOURS,
7280
+ TABLE_DECLARATION,
7262
7281
  TABLE_PANEL_ID,
7282
+ TABLE_RUNTIME_UNITS,
7263
7283
  TableController,
7264
7284
  TablePanelView,
7285
+ TableScroller,
7265
7286
  applyChartUi,
7266
7287
  applyConnectorsUi,
7267
7288
  applyFieldsUi,
7268
7289
  applyMetricsUi,
7269
7290
  applyPrecision,
7291
+ applyTableUi,
7270
7292
  base64Url,
7271
7293
  breachSideOf,
7272
7294
  browserFetch,
@@ -7372,6 +7394,9 @@ export {
7372
7394
  suggestedShelfFor,
7373
7395
  tableManifest,
7374
7396
  tablePanel,
7397
+ tableRuntimeComponents,
7398
+ tableViewManifest,
7399
+ tableViewState,
7375
7400
  toChartJsConfig,
7376
7401
  toExecuteError,
7377
7402
  totalWidth,