@ornery/ui-grid-vanilla 0.1.7 → 0.1.8

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.
package/dist/index.mjs CHANGED
@@ -5,9 +5,11 @@ import { registerRustWasmGridEngine } from "@ornery/ui-grid-core";
5
5
  import {
6
6
  GRID_CORE_CSS,
7
7
  SORT_DIRECTIONS as SORT_DIRECTIONS2,
8
+ buildGridHeaderContext,
8
9
  canGridMoveColumns as canGridMoveColumns2,
9
10
  downloadGridCsvFile,
10
11
  exportCsvRows,
12
+ formatGridHeaderDisplayValue,
11
13
  getCellValue as getCellValue2,
12
14
  sanitizeDownloadFilename
13
15
  } from "@ornery/ui-grid-core";
@@ -198,6 +200,29 @@ var VanillaGridController = class {
198
200
  this.apiRegistered = false;
199
201
  this.refresh();
200
202
  }
203
+ /**
204
+ * Fast-path: swap only the row data and rebuild the pipeline without
205
+ * touching column state. Emits the new snapshot. The element uses this to
206
+ * patch cell content in-place without rebuilding the full shadow DOM.
207
+ */
208
+ refreshData(data) {
209
+ this.options = { ...this.options, data };
210
+ this.pipeline = defaultGridEngine.buildPipeline({
211
+ options: this.options,
212
+ columns: this.visibleColumns,
213
+ activeFilters: this.activeFilters,
214
+ sortState: this.sortState,
215
+ groupByColumns: this.groupByColumns,
216
+ collapsedGroups: this.collapsedGroups,
217
+ hiddenRowReasons: this.hiddenRowReasons,
218
+ expandedRows: this.expandedRows,
219
+ expandedTreeRows: this.expandedTreeRows,
220
+ currentPage: this.currentPage,
221
+ pageSize: this.pageSize,
222
+ rowSize: this.getRowSize()
223
+ });
224
+ this.emit();
225
+ }
201
226
  subscribe(subscriber) {
202
227
  this.subscribers.add(subscriber);
203
228
  subscriber(this.getSnapshot());
@@ -787,6 +812,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
787
812
  snapshot = null;
788
813
  unsubscribe = null;
789
814
  activeOptions = null;
815
+ attributeOptions = {};
816
+ attributeSyncScheduled = false;
790
817
  iconOverrides = {};
791
818
  templateObserver = null;
792
819
  openPinMenuColumn = null;
@@ -801,6 +828,52 @@ var UiGridStandaloneElement = class extends HTMLElement {
801
828
  measuredFilterStickyHeight = 0;
802
829
  stickyHeightRelayoutQueued = false;
803
830
  benchmarkAverage = "\u2014";
831
+ skipNextRender = false;
832
+ dataFrame = null;
833
+ static get observedAttributes() {
834
+ return [
835
+ // Scalar attributes
836
+ "grid-id",
837
+ "title",
838
+ "row-height",
839
+ "header-row-height",
840
+ "viewport-height",
841
+ "pagination-page-size",
842
+ "pagination-current-page",
843
+ "total-items",
844
+ "virtualization-threshold",
845
+ "tree-children-field",
846
+ "tree-indent",
847
+ "expandable-row-height",
848
+ "expandable-row-header-width",
849
+ "empty-message",
850
+ "infinite-scroll-rows-from-end",
851
+ // JSON attributes
852
+ "column-defs",
853
+ "data",
854
+ "grouping",
855
+ "pagination-page-sizes",
856
+ // Boolean flags
857
+ "enable-sorting",
858
+ "enable-filtering",
859
+ "enable-grouping",
860
+ "enable-pinning",
861
+ "enable-column-moving",
862
+ "enable-cell-edit",
863
+ "enable-cell-edit-on-focus",
864
+ "enable-pagination",
865
+ "enable-pagination-controls",
866
+ "use-external-pagination",
867
+ "enable-expandable",
868
+ "enable-tree-view",
869
+ "show-tree-expand-no-children",
870
+ "tree-row-header-always-visible",
871
+ "enable-auto-resize",
872
+ "enable-virtualization",
873
+ "infinite-scroll-up",
874
+ "infinite-scroll-down"
875
+ ];
876
+ }
804
877
  get options() {
805
878
  return this.activeOptions ?? {
806
879
  id: "__ui-grid-pending__",
@@ -813,6 +886,144 @@ var UiGridStandaloneElement = class extends HTMLElement {
813
886
  this.iconOverrides = this.activeOptions.iconOverrides ?? {};
814
887
  this.ensureController(this.buildEffectiveOptions(this.activeOptions));
815
888
  }
889
+ attributeChangedCallback(_name, _oldValue, _newValue) {
890
+ if (!this.attributeSyncScheduled) {
891
+ this.attributeSyncScheduled = true;
892
+ queueMicrotask(() => {
893
+ this.attributeSyncScheduled = false;
894
+ this.syncAttributesToOptions();
895
+ });
896
+ }
897
+ }
898
+ syncAttributesToOptions() {
899
+ this.attributeOptions = {};
900
+ const gridId = this.getAttribute("grid-id");
901
+ if (gridId !== null) this.attributeOptions.id = gridId;
902
+ const title = this.getAttribute("title");
903
+ if (title !== null) this.attributeOptions.title = title;
904
+ const emptyMessage = this.getAttribute("empty-message");
905
+ if (emptyMessage !== null) this.attributeOptions.emptyMessage = emptyMessage;
906
+ const treeChildrenField = this.getAttribute("tree-children-field");
907
+ if (treeChildrenField !== null) this.attributeOptions.treeChildrenField = treeChildrenField;
908
+ const rowHeight = this.parseNumberAttribute("row-height");
909
+ if (rowHeight !== void 0) this.attributeOptions.rowHeight = rowHeight;
910
+ const headerRowHeight = this.parseNumberAttribute("header-row-height");
911
+ if (headerRowHeight !== void 0)
912
+ this.attributeOptions.headerRowHeight = headerRowHeight;
913
+ const viewportHeight = this.parseNumberAttribute("viewport-height");
914
+ if (viewportHeight !== void 0) this.attributeOptions.viewportHeight = viewportHeight;
915
+ const paginationPageSize = this.parseNumberAttribute("pagination-page-size");
916
+ if (paginationPageSize !== void 0)
917
+ this.attributeOptions.paginationPageSize = paginationPageSize;
918
+ const paginationCurrentPage = this.parseNumberAttribute("pagination-current-page");
919
+ if (paginationCurrentPage !== void 0)
920
+ this.attributeOptions.paginationCurrentPage = paginationCurrentPage;
921
+ const totalItems = this.parseNumberAttribute("total-items");
922
+ if (totalItems !== void 0) this.attributeOptions.totalItems = totalItems;
923
+ const virtualizationThreshold = this.parseNumberAttribute("virtualization-threshold");
924
+ if (virtualizationThreshold !== void 0)
925
+ this.attributeOptions.virtualizationThreshold = virtualizationThreshold;
926
+ const treeIndent = this.parseNumberAttribute("tree-indent");
927
+ if (treeIndent !== void 0) this.attributeOptions.treeIndent = treeIndent;
928
+ const expandableRowHeight = this.parseNumberAttribute("expandable-row-height");
929
+ if (expandableRowHeight !== void 0)
930
+ this.attributeOptions.expandableRowHeight = expandableRowHeight;
931
+ const expandableRowHeaderWidth = this.parseNumberAttribute(
932
+ "expandable-row-header-width"
933
+ );
934
+ if (expandableRowHeaderWidth !== void 0)
935
+ this.attributeOptions.expandableRowHeaderWidth = expandableRowHeaderWidth;
936
+ const infiniteScrollRowsFromEnd = this.parseNumberAttribute(
937
+ "infinite-scroll-rows-from-end"
938
+ );
939
+ if (infiniteScrollRowsFromEnd !== void 0)
940
+ this.attributeOptions.infiniteScrollRowsFromEnd = infiniteScrollRowsFromEnd;
941
+ const enableSorting = this.parseBooleanAttribute("enable-sorting");
942
+ if (enableSorting !== void 0) this.attributeOptions.enableSorting = enableSorting;
943
+ const enableFiltering = this.parseBooleanAttribute("enable-filtering");
944
+ if (enableFiltering !== void 0) this.attributeOptions.enableFiltering = enableFiltering;
945
+ const enableGrouping = this.parseBooleanAttribute("enable-grouping");
946
+ if (enableGrouping !== void 0) this.attributeOptions.enableGrouping = enableGrouping;
947
+ const enablePinning = this.parseBooleanAttribute("enable-pinning");
948
+ if (enablePinning !== void 0) this.attributeOptions.enablePinning = enablePinning;
949
+ const enableColumnMoving = this.parseBooleanAttribute("enable-column-moving");
950
+ if (enableColumnMoving !== void 0)
951
+ this.attributeOptions.enableColumnMoving = enableColumnMoving;
952
+ const enableCellEdit = this.parseBooleanAttribute("enable-cell-edit");
953
+ if (enableCellEdit !== void 0) this.attributeOptions.enableCellEdit = enableCellEdit;
954
+ const enableCellEditOnFocus = this.parseBooleanAttribute("enable-cell-edit-on-focus");
955
+ if (enableCellEditOnFocus !== void 0)
956
+ this.attributeOptions.enableCellEditOnFocus = enableCellEditOnFocus;
957
+ const enablePagination = this.parseBooleanAttribute("enable-pagination");
958
+ if (enablePagination !== void 0)
959
+ this.attributeOptions.enablePagination = enablePagination;
960
+ const enablePaginationControls = this.parseBooleanAttribute(
961
+ "enable-pagination-controls"
962
+ );
963
+ if (enablePaginationControls !== void 0)
964
+ this.attributeOptions.enablePaginationControls = enablePaginationControls;
965
+ const useExternalPagination = this.parseBooleanAttribute("use-external-pagination");
966
+ if (useExternalPagination !== void 0)
967
+ this.attributeOptions.useExternalPagination = useExternalPagination;
968
+ const enableExpandable = this.parseBooleanAttribute("enable-expandable");
969
+ if (enableExpandable !== void 0) this.attributeOptions.enableExpandable = enableExpandable;
970
+ const enableTreeView = this.parseBooleanAttribute("enable-tree-view");
971
+ if (enableTreeView !== void 0) this.attributeOptions.enableTreeView = enableTreeView;
972
+ const showTreeExpandNoChildren = this.parseBooleanAttribute(
973
+ "show-tree-expand-no-children"
974
+ );
975
+ if (showTreeExpandNoChildren !== void 0)
976
+ this.attributeOptions.showTreeExpandNoChildren = showTreeExpandNoChildren;
977
+ const treeRowHeaderAlwaysVisible = this.parseBooleanAttribute(
978
+ "tree-row-header-always-visible"
979
+ );
980
+ if (treeRowHeaderAlwaysVisible !== void 0)
981
+ this.attributeOptions.treeRowHeaderAlwaysVisible = treeRowHeaderAlwaysVisible;
982
+ const enableAutoResize = this.parseBooleanAttribute("enable-auto-resize");
983
+ if (enableAutoResize !== void 0) this.attributeOptions.enableAutoResize = enableAutoResize;
984
+ const enableVirtualization = this.parseBooleanAttribute("enable-virtualization");
985
+ if (enableVirtualization !== void 0)
986
+ this.attributeOptions.enableVirtualization = enableVirtualization;
987
+ const infiniteScrollUp = this.parseBooleanAttribute("infinite-scroll-up");
988
+ if (infiniteScrollUp !== void 0) this.attributeOptions.infiniteScrollUp = infiniteScrollUp;
989
+ const infiniteScrollDown = this.parseBooleanAttribute("infinite-scroll-down");
990
+ if (infiniteScrollDown !== void 0)
991
+ this.attributeOptions.infiniteScrollDown = infiniteScrollDown;
992
+ const columnDefs = this.parseJsonAttribute("column-defs");
993
+ if (columnDefs !== void 0) this.attributeOptions.columnDefs = columnDefs;
994
+ const data = this.parseJsonAttribute("data");
995
+ if (data !== void 0) this.attributeOptions.data = data;
996
+ const grouping = this.parseJsonAttribute("grouping");
997
+ if (grouping !== void 0 && grouping !== null)
998
+ this.attributeOptions.grouping = grouping;
999
+ const paginationPageSizes = this.parseJsonAttribute(
1000
+ "pagination-page-sizes"
1001
+ );
1002
+ if (paginationPageSizes !== void 0)
1003
+ this.attributeOptions.paginationPageSizes = paginationPageSizes;
1004
+ if (this.activeOptions) {
1005
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1006
+ }
1007
+ }
1008
+ parseBooleanAttribute(name) {
1009
+ return this.hasAttribute(name) ? true : void 0;
1010
+ }
1011
+ parseNumberAttribute(name) {
1012
+ const raw = this.getAttribute(name);
1013
+ if (raw === null) return void 0;
1014
+ const parsed = Number(raw);
1015
+ return Number.isFinite(parsed) ? parsed : void 0;
1016
+ }
1017
+ parseJsonAttribute(name) {
1018
+ const raw = this.getAttribute(name);
1019
+ if (raw === null) return void 0;
1020
+ try {
1021
+ return JSON.parse(raw);
1022
+ } catch (e) {
1023
+ console.warn(`<ui-grid-element>: invalid JSON in "${name}" attribute`, e);
1024
+ return void 0;
1025
+ }
1026
+ }
816
1027
  get controlIcons() {
817
1028
  return this.iconOverrides;
818
1029
  }
@@ -820,12 +1031,337 @@ var UiGridStandaloneElement = class extends HTMLElement {
820
1031
  this.iconOverrides = { ...value };
821
1032
  this.render();
822
1033
  }
1034
+ // ─────────────────────────────────────────────────────────────────
1035
+ // Individual property accessors (mirrors of HTML attributes)
1036
+ // ─────────────────────────────────────────────────────────────────
1037
+ get gridId() {
1038
+ return this.options.id ?? "";
1039
+ }
1040
+ set gridId(value) {
1041
+ this.activeOptions = { ...this.activeOptions, id: value };
1042
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1043
+ }
1044
+ // @ts-ignore: Intentional override of HTMLElement.title
1045
+ get title() {
1046
+ return this.options.title ?? "";
1047
+ }
1048
+ // @ts-ignore: Intentional override of HTMLElement.title
1049
+ set title(value) {
1050
+ this.activeOptions = { ...this.activeOptions, title: value };
1051
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1052
+ }
1053
+ get rowHeight() {
1054
+ return this.options.rowHeight ?? 40;
1055
+ }
1056
+ set rowHeight(value) {
1057
+ this.activeOptions = { ...this.activeOptions, rowHeight: value };
1058
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1059
+ }
1060
+ get headerRowHeight() {
1061
+ return this.options.headerRowHeight ?? 50;
1062
+ }
1063
+ set headerRowHeight(value) {
1064
+ this.activeOptions = { ...this.activeOptions, headerRowHeight: value };
1065
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1066
+ }
1067
+ get viewportHeight() {
1068
+ return this.options.viewportHeight ?? 560;
1069
+ }
1070
+ set viewportHeight(value) {
1071
+ this.activeOptions = { ...this.activeOptions, viewportHeight: value };
1072
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1073
+ }
1074
+ get paginationPageSize() {
1075
+ return this.options.paginationPageSize ?? 25;
1076
+ }
1077
+ set paginationPageSize(value) {
1078
+ this.activeOptions = { ...this.activeOptions, paginationPageSize: value };
1079
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1080
+ }
1081
+ get paginationCurrentPage() {
1082
+ return this.options.paginationCurrentPage ?? 1;
1083
+ }
1084
+ set paginationCurrentPage(value) {
1085
+ this.activeOptions = { ...this.activeOptions, paginationCurrentPage: value };
1086
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1087
+ }
1088
+ get totalItems() {
1089
+ return this.options.totalItems;
1090
+ }
1091
+ set totalItems(value) {
1092
+ this.activeOptions = { ...this.activeOptions, totalItems: value };
1093
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1094
+ }
1095
+ get virtualizationThreshold() {
1096
+ return this.options.virtualizationThreshold ?? 40;
1097
+ }
1098
+ set virtualizationThreshold(value) {
1099
+ this.activeOptions = { ...this.activeOptions, virtualizationThreshold: value };
1100
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1101
+ }
1102
+ get treeChildrenField() {
1103
+ return this.options.treeChildrenField ?? "children";
1104
+ }
1105
+ set treeChildrenField(value) {
1106
+ this.activeOptions = { ...this.activeOptions, treeChildrenField: value };
1107
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1108
+ }
1109
+ get treeIndent() {
1110
+ return this.options.treeIndent ?? 20;
1111
+ }
1112
+ set treeIndent(value) {
1113
+ this.activeOptions = { ...this.activeOptions, treeIndent: value };
1114
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1115
+ }
1116
+ get expandableRowHeight() {
1117
+ return this.options.expandableRowHeight ?? 150;
1118
+ }
1119
+ set expandableRowHeight(value) {
1120
+ this.activeOptions = { ...this.activeOptions, expandableRowHeight: value };
1121
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1122
+ }
1123
+ get expandableRowHeaderWidth() {
1124
+ return this.options.expandableRowHeaderWidth ?? 40;
1125
+ }
1126
+ set expandableRowHeaderWidth(value) {
1127
+ this.activeOptions = { ...this.activeOptions, expandableRowHeaderWidth: value };
1128
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1129
+ }
1130
+ get emptyMessage() {
1131
+ return this.options.emptyMessage ?? "No data available.";
1132
+ }
1133
+ set emptyMessage(value) {
1134
+ this.activeOptions = { ...this.activeOptions, emptyMessage: value };
1135
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1136
+ }
1137
+ get infiniteScrollRowsFromEnd() {
1138
+ return this.options.infiniteScrollRowsFromEnd ?? 10;
1139
+ }
1140
+ set infiniteScrollRowsFromEnd(value) {
1141
+ this.activeOptions = { ...this.activeOptions, infiniteScrollRowsFromEnd: value };
1142
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1143
+ }
1144
+ get columnDefs() {
1145
+ return this.options.columnDefs;
1146
+ }
1147
+ set columnDefs(value) {
1148
+ this.activeOptions = { ...this.activeOptions, columnDefs: value };
1149
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1150
+ }
1151
+ get data() {
1152
+ return this.options.data;
1153
+ }
1154
+ set data(value) {
1155
+ this.activeOptions = { ...this.activeOptions, data: value };
1156
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1157
+ }
1158
+ get enableSorting() {
1159
+ return this.options.enableSorting ?? true;
1160
+ }
1161
+ set enableSorting(value) {
1162
+ this.activeOptions = { ...this.activeOptions, enableSorting: value };
1163
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1164
+ }
1165
+ get enableFiltering() {
1166
+ return this.options.enableFiltering ?? true;
1167
+ }
1168
+ set enableFiltering(value) {
1169
+ this.activeOptions = { ...this.activeOptions, enableFiltering: value };
1170
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1171
+ }
1172
+ get enableGrouping() {
1173
+ return this.options.enableGrouping ?? true;
1174
+ }
1175
+ set enableGrouping(value) {
1176
+ this.activeOptions = { ...this.activeOptions, enableGrouping: value };
1177
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1178
+ }
1179
+ get enablePinning() {
1180
+ return this.options.enablePinning ?? true;
1181
+ }
1182
+ set enablePinning(value) {
1183
+ this.activeOptions = { ...this.activeOptions, enablePinning: value };
1184
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1185
+ }
1186
+ get enableColumnMoving() {
1187
+ return this.options.enableColumnMoving ?? true;
1188
+ }
1189
+ set enableColumnMoving(value) {
1190
+ this.activeOptions = { ...this.activeOptions, enableColumnMoving: value };
1191
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1192
+ }
1193
+ get enableCellEdit() {
1194
+ return this.options.enableCellEdit ?? false;
1195
+ }
1196
+ set enableCellEdit(value) {
1197
+ this.activeOptions = { ...this.activeOptions, enableCellEdit: value };
1198
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1199
+ }
1200
+ get enableCellEditOnFocus() {
1201
+ return this.options.enableCellEditOnFocus ?? false;
1202
+ }
1203
+ set enableCellEditOnFocus(value) {
1204
+ this.activeOptions = { ...this.activeOptions, enableCellEditOnFocus: value };
1205
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1206
+ }
1207
+ get enablePagination() {
1208
+ return this.options.enablePagination ?? false;
1209
+ }
1210
+ set enablePagination(value) {
1211
+ this.activeOptions = { ...this.activeOptions, enablePagination: value };
1212
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1213
+ }
1214
+ get enablePaginationControls() {
1215
+ return this.options.enablePaginationControls ?? true;
1216
+ }
1217
+ set enablePaginationControls(value) {
1218
+ this.activeOptions = { ...this.activeOptions, enablePaginationControls: value };
1219
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1220
+ }
1221
+ get useExternalPagination() {
1222
+ return this.options.useExternalPagination ?? false;
1223
+ }
1224
+ set useExternalPagination(value) {
1225
+ this.activeOptions = { ...this.activeOptions, useExternalPagination: value };
1226
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1227
+ }
1228
+ get enableExpandable() {
1229
+ return this.options.enableExpandable ?? false;
1230
+ }
1231
+ set enableExpandable(value) {
1232
+ this.activeOptions = { ...this.activeOptions, enableExpandable: value };
1233
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1234
+ }
1235
+ get enableTreeView() {
1236
+ return this.options.enableTreeView ?? false;
1237
+ }
1238
+ set enableTreeView(value) {
1239
+ this.activeOptions = { ...this.activeOptions, enableTreeView: value };
1240
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1241
+ }
1242
+ get showTreeExpandNoChildren() {
1243
+ return this.options.showTreeExpandNoChildren ?? false;
1244
+ }
1245
+ set showTreeExpandNoChildren(value) {
1246
+ this.activeOptions = { ...this.activeOptions, showTreeExpandNoChildren: value };
1247
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1248
+ }
1249
+ get treeRowHeaderAlwaysVisible() {
1250
+ return this.options.treeRowHeaderAlwaysVisible ?? false;
1251
+ }
1252
+ set treeRowHeaderAlwaysVisible(value) {
1253
+ this.activeOptions = { ...this.activeOptions, treeRowHeaderAlwaysVisible: value };
1254
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1255
+ }
1256
+ get enableAutoResize() {
1257
+ return this.options.enableAutoResize ?? false;
1258
+ }
1259
+ set enableAutoResize(value) {
1260
+ this.activeOptions = { ...this.activeOptions, enableAutoResize: value };
1261
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1262
+ }
1263
+ get enableVirtualization() {
1264
+ return this.options.enableVirtualization ?? true;
1265
+ }
1266
+ set enableVirtualization(value) {
1267
+ this.activeOptions = { ...this.activeOptions, enableVirtualization: value };
1268
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1269
+ }
1270
+ get infiniteScrollUp() {
1271
+ return this.options.infiniteScrollUp ?? false;
1272
+ }
1273
+ set infiniteScrollUp(value) {
1274
+ this.activeOptions = { ...this.activeOptions, infiniteScrollUp: value };
1275
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1276
+ }
1277
+ get infiniteScrollDown() {
1278
+ return this.options.infiniteScrollDown ?? false;
1279
+ }
1280
+ set infiniteScrollDown(value) {
1281
+ this.activeOptions = { ...this.activeOptions, infiniteScrollDown: value };
1282
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1283
+ }
1284
+ get grouping() {
1285
+ return this.options.grouping;
1286
+ }
1287
+ set grouping(value) {
1288
+ this.activeOptions = { ...this.activeOptions, grouping: value };
1289
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1290
+ }
1291
+ get paginationPageSizes() {
1292
+ return this.options.paginationPageSizes;
1293
+ }
1294
+ set paginationPageSizes(value) {
1295
+ this.activeOptions = { ...this.activeOptions, paginationPageSizes: value };
1296
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1297
+ }
823
1298
  getState() {
824
1299
  return this.controller?.getState() ?? null;
825
1300
  }
826
1301
  setState(state) {
827
1302
  this.controller?.setState(state);
828
1303
  }
1304
+ /**
1305
+ * Fast-path: update only row data without rebuilding the shadow DOM.
1306
+ * The pipeline is updated synchronously; DOM patching is deferred to the
1307
+ * next animation frame so it never interrupts an in-progress scroll paint.
1308
+ * If multiple ticks arrive before the frame fires, only the latest wins.
1309
+ * If a scroll-triggered full render fires first, the frame is cancelled
1310
+ * (the fresh innerHTML already reflects the latest data).
1311
+ */
1312
+ setData(rows) {
1313
+ const controller = this.controller;
1314
+ if (!controller) {
1315
+ return;
1316
+ }
1317
+ this.skipNextRender = true;
1318
+ controller.refreshData(rows);
1319
+ this.skipNextRender = false;
1320
+ if (this.dataFrame !== null) {
1321
+ cancelAnimationFrame(this.dataFrame);
1322
+ }
1323
+ this.dataFrame = requestAnimationFrame(() => {
1324
+ this.dataFrame = null;
1325
+ this.patchCells();
1326
+ });
1327
+ }
1328
+ patchCells() {
1329
+ const snapshot = this.snapshot;
1330
+ if (!snapshot) {
1331
+ return;
1332
+ }
1333
+ const root = this.shadowRoot;
1334
+ if (!root) {
1335
+ return;
1336
+ }
1337
+ const rowMap = /* @__PURE__ */ new Map();
1338
+ for (const row of snapshot.pipeline.visibleRows) {
1339
+ rowMap.set(row.id, row);
1340
+ }
1341
+ const colMap = /* @__PURE__ */ new Map();
1342
+ for (const col of snapshot.visibleColumns) {
1343
+ colMap.set(col.name, col);
1344
+ }
1345
+ const cells = root.querySelectorAll("[data-row][data-column]");
1346
+ for (const cell of cells) {
1347
+ const rowId = cell.dataset["row"];
1348
+ const colName = cell.dataset["column"];
1349
+ if (!rowId || !colName) continue;
1350
+ const row = rowMap.get(rowId);
1351
+ const column = colMap.get(colName);
1352
+ if (!row || !column) continue;
1353
+ const cellShell = cell.querySelector(".cell-shell");
1354
+ if (!cellShell) continue;
1355
+ const newContent = this.renderCellTemplate(row, column, 0);
1356
+ const toggles = cellShell.querySelectorAll(".row-toggle");
1357
+ if (toggles.length === 0) {
1358
+ cellShell.innerHTML = newContent;
1359
+ } else {
1360
+ const toggleHtml = Array.from(toggles).map((t) => t.outerHTML).join("");
1361
+ cellShell.innerHTML = toggleHtml + newContent;
1362
+ }
1363
+ }
1364
+ }
829
1365
  connectedCallback() {
830
1366
  this.ensureShadowRoot();
831
1367
  this.bindEvents();
@@ -882,16 +1418,17 @@ var UiGridStandaloneElement = class extends HTMLElement {
882
1418
  });
883
1419
  }
884
1420
  buildEffectiveOptions(options) {
1421
+ const merged = {
1422
+ ...this.attributeOptions,
1423
+ ...options
1424
+ };
885
1425
  const hasExpandableSlot = this.getTemplateMarkup("expandable-row") !== null;
886
- if (options.enableExpandable && hasExpandableSlot && !options.expandableRowTemplate) {
887
- return {
888
- ...options,
889
- expandableRowTemplate: {
890
- createEmbeddedView: () => void 0
891
- }
1426
+ if (merged.enableExpandable && hasExpandableSlot && !merged.expandableRowTemplate) {
1427
+ merged.expandableRowTemplate = {
1428
+ createEmbeddedView: () => void 0
892
1429
  };
893
1430
  }
894
- return options;
1431
+ return merged;
895
1432
  }
896
1433
  bindEvents() {
897
1434
  const root = this.ensureShadowRoot();
@@ -1192,14 +1729,63 @@ var UiGridStandaloneElement = class extends HTMLElement {
1192
1729
  }
1193
1730
  this.scrollFrame = requestAnimationFrame(() => {
1194
1731
  this.scrollFrame = null;
1195
- this.render();
1732
+ this.renderVirtualBody();
1196
1733
  });
1197
1734
  },
1198
1735
  true
1199
1736
  );
1200
1737
  root.__uiGridBound = true;
1201
1738
  }
1739
+ /**
1740
+ * Targeted update for virtual scroll: replaces only the rendered rows inside
1741
+ * the existing `.grid-virtual-body` without touching the scroll container.
1742
+ * This preserves momentum/inertia scrolling because the `.grid-table` element
1743
+ * is never destroyed.
1744
+ */
1745
+ renderVirtualBody() {
1746
+ const snapshot = this.snapshot;
1747
+ if (!snapshot?.pipeline.virtualizationEnabled) {
1748
+ this.render();
1749
+ return;
1750
+ }
1751
+ const root = this.shadowRoot;
1752
+ const virtualBody = root?.querySelector(".grid-virtual-body");
1753
+ if (!root || !virtualBody) {
1754
+ this.render();
1755
+ return;
1756
+ }
1757
+ if (this.dataFrame !== null) {
1758
+ cancelAnimationFrame(this.dataFrame);
1759
+ this.dataFrame = null;
1760
+ }
1761
+ const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1762
+ const viewportHeight = snapshot.options.viewportHeight ?? 560;
1763
+ const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
1764
+ const bodyScrollTop = Math.max(0, this.scrollPosition - stickyChromeHeight);
1765
+ const overscan = 4;
1766
+ const startIndex = Math.max(0, Math.floor(bodyScrollTop / snapshot.rowSize) - overscan);
1767
+ this.lastVirtualStartIndex = startIndex;
1768
+ const visibleCount = Math.ceil(bodyViewportHeight / snapshot.rowSize) + overscan * 2;
1769
+ const itemsToRender = snapshot.pipeline.displayItems.slice(
1770
+ startIndex,
1771
+ Math.min(snapshot.pipeline.displayItems.length, startIndex + visibleCount)
1772
+ );
1773
+ const virtualOffset = startIndex * snapshot.rowSize;
1774
+ virtualBody.style.top = `${virtualOffset}px`;
1775
+ virtualBody.innerHTML = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
1776
+ }
1202
1777
  render() {
1778
+ if (this.skipNextRender) {
1779
+ return;
1780
+ }
1781
+ if (this.dataFrame !== null) {
1782
+ cancelAnimationFrame(this.dataFrame);
1783
+ this.dataFrame = null;
1784
+ }
1785
+ if (this.scrollFrame !== null) {
1786
+ cancelAnimationFrame(this.scrollFrame);
1787
+ this.scrollFrame = null;
1788
+ }
1203
1789
  const root = this.ensureShadowRoot();
1204
1790
  const snapshot = this.snapshot;
1205
1791
  const previousGridTable = root.querySelector(".grid-table");
@@ -1225,9 +1811,6 @@ var UiGridStandaloneElement = class extends HTMLElement {
1225
1811
  const headerStickyTop = this.measuredHeaderStickyHeight || options.headerRowHeight || 50;
1226
1812
  const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1227
1813
  const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
1228
- const visibleRowCount = snapshot.pipeline.visibleRows.length;
1229
- const totalRows = options.data.length;
1230
- const pinnedCount = Object.keys(snapshot.pinnedColumns).length;
1231
1814
  let startIndex = 0;
1232
1815
  let itemsToRender = snapshot.pipeline.displayItems;
1233
1816
  let virtualOffset = 0;
@@ -1254,11 +1837,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
1254
1837
  const bodyContent = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
1255
1838
  const body = snapshot.pipeline.displayItems.length > 0 ? virtualizationEnabled ? `<div class="grid-virtual-spacer" style="height:${totalVirtualHeight}px"><div class="body-grid ui-grid-canvas grid-virtual-body" style="grid-template-columns:${templateColumns};top:${virtualOffset}px">${bodyContent}</div></div>` : `<div class="body-grid ui-grid-canvas" style="grid-template-columns:${templateColumns}">${bodyContent}</div>` : `<div class="empty-state ui-grid-no-row-overlay"><strong>${escapeHtml(options.emptyMessage ?? labels.emptyHeading)}</strong><p>${escapeHtml(labels.emptyDescription)}</p></div>`;
1256
1839
  const pagination = paginationEnabled && showPagination ? this.renderPagination(snapshot) : "";
1257
- const hero = `<header class="grid-hero"><div><p class="eyebrow">Vanilla web component</p><h1>${escapeHtml(options.title ?? "UI Grid")}</h1><p class="deck">Framework-neutral custom element with Shadow DOM styling, slot templates, pinning, virtualization, editing, tree view, grouping, and state restore.</p></div><div class="hero-actions"><button type="button" class="action action-secondary" data-action="benchmark">Benchmark</button><button type="button" class="action action-secondary" data-action="export-csv">Export CSV</button><div class="stats-card"><span>${visibleRowCount}</span><small>${escapeHtml(labels.statsVisibleRows)}</small></div></div></header>`;
1258
- const metrics = `<section class="metrics-strip" aria-label="Grid metrics"><article><strong>${snapshot.pipeline.pipelineMs.toFixed(2)} ms</strong><span>pipeline</span></article><article><strong>${virtualizationEnabled ? "On" : "Off"}</strong><span>virtualization</span></article><article><strong>${pinnedCount}</strong><span>pinned columns</span></article><article><strong>${escapeHtml(this.benchmarkAverage)}</strong><span>benchmark avg</span></article></section>`;
1259
- const toolbar = `<div class="grid-toolbar"><div><strong>${visibleRowCount}</strong><span>${escapeHtml(labels.toolbarOf)} ${totalRows} ${escapeHtml(labels.toolbarRows)}</span></div><p>${escapeHtml(options.id)}</p></div>`;
1260
1840
  const gridTableStyle = `${virtualizationEnabled ? `height:${viewportHeight}px;overflow-y:auto;` : ""}--ui-grid-header-sticky-top:${headerStickyTop}px;`;
1261
- root.innerHTML = `<style>${GRID_CORE_CSS}</style><section class="grid-shell ui-grid-shell">${slotRegistry}${hero}${metrics}<section class="grid-frame ui-grid" role="grid" aria-label="${escapeHtml(options.title ?? "Data grid")}">${toolbar}<div class="grid-table ui-grid-contents-wrapper" style="${gridTableStyle}"><div class="header-grid ui-grid-header ui-grid-header-canvas" style="grid-template-columns:${templateColumns}">${header}</div>${filterRow}${body}</div>${pagination}</section></section>`;
1841
+ root.innerHTML = `<style>${GRID_CORE_CSS}</style>${slotRegistry}<section class="grid-frame ui-grid" role="grid" aria-label="${escapeHtml(options.title ?? "Data grid")}"><div class="grid-table ui-grid-contents-wrapper" style="${gridTableStyle}"><div class="header-grid ui-grid-header ui-grid-header-canvas" style="grid-template-columns:${templateColumns}">${header}</div>${filterRow}${body}</div>${pagination}</section>`;
1262
1842
  const gridTable = root.querySelector(".grid-table");
1263
1843
  if (gridTable && this.scrollPosition > 0 && virtualizationEnabled) {
1264
1844
  this.suppressScrollEvent = true;
@@ -1313,7 +1893,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
1313
1893
  const sortIconKey = sortDirection === SORT_DIRECTIONS2.asc ? "sortAsc" : sortDirection === SORT_DIRECTIONS2.desc ? "sortDesc" : "sortNone";
1314
1894
  const draggable = canGridMoveColumns2(options) ? 'draggable="true"' : "";
1315
1895
  const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
1316
- return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${escapeHtml(controller.headerLabel(column))}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span></div>`;
1896
+ const headerValue = escapeHtml(formatGridHeaderDisplayValue(buildGridHeaderContext(column)));
1897
+ return `<div class="${classes}" data-column="${escapeHtml(column.name)}" ${draggable} style="${stickyStyle}"><span class="header-label">${headerValue}</span><span class="header-actions">${sortEnabled ? `<button type="button" class="header-action" data-action="sort" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(sortLabel)}" ${canSort ? "" : "disabled"}>${this.renderControlIcon(sortIconKey)}<span class="sr-only">${escapeHtml(sortLabel)}</span></button>` : ""}${groupingEnabled ? `<button type="button" class="chip-action${controller.isColumnGrouped(column) ? " chip-action-active" : ""}" data-action="group" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(groupingLabel)}" ${canGroup ? "" : "disabled"}>${this.renderControlIcon("group")}<span class="sr-only">${escapeHtml(groupingLabel)}</span></button>` : ""}${canPin ? `<div class="pin-control${this.openPinMenuColumn === column.name ? " pin-control-open" : ""}"><button type="button" class="chip-action pin-trigger${isPinned ? " chip-action-active" : ""}" data-action="pin-trigger" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(pinLabel)}">${this.renderControlIcon("pin")}<span class="sr-only">${escapeHtml(pinLabel)}</span></button><div class="pin-menu" role="menu" aria-label="${escapeHtml(pinLabel)}"><button type="button" class="pin-menu-action" data-action="pin-left" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}">${this.renderIconWithClass("control-icon", "pinLeft")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinLeft ?? "Pin left")}</span></button><button type="button" class="pin-menu-action" data-action="pin-right" data-column="${escapeHtml(column.name)}" aria-label="${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}">${this.renderIconWithClass("control-icon", "pinRight")}<span class="sr-only">${escapeHtml(this.snapshot?.labels.pinRight ?? "Pin right")}</span></button></div></div>` : ""}</span></div>`;
1317
1898
  }
1318
1899
  renderFilterCell(column) {
1319
1900
  const value = this.snapshot?.activeFilters[column.name] ?? "";