@ornery/ui-grid-vanilla 0.1.7-hotfix-1 → 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.js CHANGED
@@ -158,6 +158,29 @@ var VanillaGridController = class {
158
158
  this.apiRegistered = false;
159
159
  this.refresh();
160
160
  }
161
+ /**
162
+ * Fast-path: swap only the row data and rebuild the pipeline without
163
+ * touching column state. Emits the new snapshot. The element uses this to
164
+ * patch cell content in-place without rebuilding the full shadow DOM.
165
+ */
166
+ refreshData(data) {
167
+ this.options = { ...this.options, data };
168
+ this.pipeline = import_ui_grid_core.defaultGridEngine.buildPipeline({
169
+ options: this.options,
170
+ columns: this.visibleColumns,
171
+ activeFilters: this.activeFilters,
172
+ sortState: this.sortState,
173
+ groupByColumns: this.groupByColumns,
174
+ collapsedGroups: this.collapsedGroups,
175
+ hiddenRowReasons: this.hiddenRowReasons,
176
+ expandedRows: this.expandedRows,
177
+ expandedTreeRows: this.expandedTreeRows,
178
+ currentPage: this.currentPage,
179
+ pageSize: this.pageSize,
180
+ rowSize: this.getRowSize()
181
+ });
182
+ this.emit();
183
+ }
161
184
  subscribe(subscriber) {
162
185
  this.subscribers.add(subscriber);
163
186
  subscriber(this.getSnapshot());
@@ -747,6 +770,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
747
770
  snapshot = null;
748
771
  unsubscribe = null;
749
772
  activeOptions = null;
773
+ attributeOptions = {};
774
+ attributeSyncScheduled = false;
750
775
  iconOverrides = {};
751
776
  templateObserver = null;
752
777
  openPinMenuColumn = null;
@@ -761,6 +786,52 @@ var UiGridStandaloneElement = class extends HTMLElement {
761
786
  measuredFilterStickyHeight = 0;
762
787
  stickyHeightRelayoutQueued = false;
763
788
  benchmarkAverage = "\u2014";
789
+ skipNextRender = false;
790
+ dataFrame = null;
791
+ static get observedAttributes() {
792
+ return [
793
+ // Scalar attributes
794
+ "grid-id",
795
+ "title",
796
+ "row-height",
797
+ "header-row-height",
798
+ "viewport-height",
799
+ "pagination-page-size",
800
+ "pagination-current-page",
801
+ "total-items",
802
+ "virtualization-threshold",
803
+ "tree-children-field",
804
+ "tree-indent",
805
+ "expandable-row-height",
806
+ "expandable-row-header-width",
807
+ "empty-message",
808
+ "infinite-scroll-rows-from-end",
809
+ // JSON attributes
810
+ "column-defs",
811
+ "data",
812
+ "grouping",
813
+ "pagination-page-sizes",
814
+ // Boolean flags
815
+ "enable-sorting",
816
+ "enable-filtering",
817
+ "enable-grouping",
818
+ "enable-pinning",
819
+ "enable-column-moving",
820
+ "enable-cell-edit",
821
+ "enable-cell-edit-on-focus",
822
+ "enable-pagination",
823
+ "enable-pagination-controls",
824
+ "use-external-pagination",
825
+ "enable-expandable",
826
+ "enable-tree-view",
827
+ "show-tree-expand-no-children",
828
+ "tree-row-header-always-visible",
829
+ "enable-auto-resize",
830
+ "enable-virtualization",
831
+ "infinite-scroll-up",
832
+ "infinite-scroll-down"
833
+ ];
834
+ }
764
835
  get options() {
765
836
  return this.activeOptions ?? {
766
837
  id: "__ui-grid-pending__",
@@ -773,6 +844,144 @@ var UiGridStandaloneElement = class extends HTMLElement {
773
844
  this.iconOverrides = this.activeOptions.iconOverrides ?? {};
774
845
  this.ensureController(this.buildEffectiveOptions(this.activeOptions));
775
846
  }
847
+ attributeChangedCallback(_name, _oldValue, _newValue) {
848
+ if (!this.attributeSyncScheduled) {
849
+ this.attributeSyncScheduled = true;
850
+ queueMicrotask(() => {
851
+ this.attributeSyncScheduled = false;
852
+ this.syncAttributesToOptions();
853
+ });
854
+ }
855
+ }
856
+ syncAttributesToOptions() {
857
+ this.attributeOptions = {};
858
+ const gridId = this.getAttribute("grid-id");
859
+ if (gridId !== null) this.attributeOptions.id = gridId;
860
+ const title = this.getAttribute("title");
861
+ if (title !== null) this.attributeOptions.title = title;
862
+ const emptyMessage = this.getAttribute("empty-message");
863
+ if (emptyMessage !== null) this.attributeOptions.emptyMessage = emptyMessage;
864
+ const treeChildrenField = this.getAttribute("tree-children-field");
865
+ if (treeChildrenField !== null) this.attributeOptions.treeChildrenField = treeChildrenField;
866
+ const rowHeight = this.parseNumberAttribute("row-height");
867
+ if (rowHeight !== void 0) this.attributeOptions.rowHeight = rowHeight;
868
+ const headerRowHeight = this.parseNumberAttribute("header-row-height");
869
+ if (headerRowHeight !== void 0)
870
+ this.attributeOptions.headerRowHeight = headerRowHeight;
871
+ const viewportHeight = this.parseNumberAttribute("viewport-height");
872
+ if (viewportHeight !== void 0) this.attributeOptions.viewportHeight = viewportHeight;
873
+ const paginationPageSize = this.parseNumberAttribute("pagination-page-size");
874
+ if (paginationPageSize !== void 0)
875
+ this.attributeOptions.paginationPageSize = paginationPageSize;
876
+ const paginationCurrentPage = this.parseNumberAttribute("pagination-current-page");
877
+ if (paginationCurrentPage !== void 0)
878
+ this.attributeOptions.paginationCurrentPage = paginationCurrentPage;
879
+ const totalItems = this.parseNumberAttribute("total-items");
880
+ if (totalItems !== void 0) this.attributeOptions.totalItems = totalItems;
881
+ const virtualizationThreshold = this.parseNumberAttribute("virtualization-threshold");
882
+ if (virtualizationThreshold !== void 0)
883
+ this.attributeOptions.virtualizationThreshold = virtualizationThreshold;
884
+ const treeIndent = this.parseNumberAttribute("tree-indent");
885
+ if (treeIndent !== void 0) this.attributeOptions.treeIndent = treeIndent;
886
+ const expandableRowHeight = this.parseNumberAttribute("expandable-row-height");
887
+ if (expandableRowHeight !== void 0)
888
+ this.attributeOptions.expandableRowHeight = expandableRowHeight;
889
+ const expandableRowHeaderWidth = this.parseNumberAttribute(
890
+ "expandable-row-header-width"
891
+ );
892
+ if (expandableRowHeaderWidth !== void 0)
893
+ this.attributeOptions.expandableRowHeaderWidth = expandableRowHeaderWidth;
894
+ const infiniteScrollRowsFromEnd = this.parseNumberAttribute(
895
+ "infinite-scroll-rows-from-end"
896
+ );
897
+ if (infiniteScrollRowsFromEnd !== void 0)
898
+ this.attributeOptions.infiniteScrollRowsFromEnd = infiniteScrollRowsFromEnd;
899
+ const enableSorting = this.parseBooleanAttribute("enable-sorting");
900
+ if (enableSorting !== void 0) this.attributeOptions.enableSorting = enableSorting;
901
+ const enableFiltering = this.parseBooleanAttribute("enable-filtering");
902
+ if (enableFiltering !== void 0) this.attributeOptions.enableFiltering = enableFiltering;
903
+ const enableGrouping = this.parseBooleanAttribute("enable-grouping");
904
+ if (enableGrouping !== void 0) this.attributeOptions.enableGrouping = enableGrouping;
905
+ const enablePinning = this.parseBooleanAttribute("enable-pinning");
906
+ if (enablePinning !== void 0) this.attributeOptions.enablePinning = enablePinning;
907
+ const enableColumnMoving = this.parseBooleanAttribute("enable-column-moving");
908
+ if (enableColumnMoving !== void 0)
909
+ this.attributeOptions.enableColumnMoving = enableColumnMoving;
910
+ const enableCellEdit = this.parseBooleanAttribute("enable-cell-edit");
911
+ if (enableCellEdit !== void 0) this.attributeOptions.enableCellEdit = enableCellEdit;
912
+ const enableCellEditOnFocus = this.parseBooleanAttribute("enable-cell-edit-on-focus");
913
+ if (enableCellEditOnFocus !== void 0)
914
+ this.attributeOptions.enableCellEditOnFocus = enableCellEditOnFocus;
915
+ const enablePagination = this.parseBooleanAttribute("enable-pagination");
916
+ if (enablePagination !== void 0)
917
+ this.attributeOptions.enablePagination = enablePagination;
918
+ const enablePaginationControls = this.parseBooleanAttribute(
919
+ "enable-pagination-controls"
920
+ );
921
+ if (enablePaginationControls !== void 0)
922
+ this.attributeOptions.enablePaginationControls = enablePaginationControls;
923
+ const useExternalPagination = this.parseBooleanAttribute("use-external-pagination");
924
+ if (useExternalPagination !== void 0)
925
+ this.attributeOptions.useExternalPagination = useExternalPagination;
926
+ const enableExpandable = this.parseBooleanAttribute("enable-expandable");
927
+ if (enableExpandable !== void 0) this.attributeOptions.enableExpandable = enableExpandable;
928
+ const enableTreeView = this.parseBooleanAttribute("enable-tree-view");
929
+ if (enableTreeView !== void 0) this.attributeOptions.enableTreeView = enableTreeView;
930
+ const showTreeExpandNoChildren = this.parseBooleanAttribute(
931
+ "show-tree-expand-no-children"
932
+ );
933
+ if (showTreeExpandNoChildren !== void 0)
934
+ this.attributeOptions.showTreeExpandNoChildren = showTreeExpandNoChildren;
935
+ const treeRowHeaderAlwaysVisible = this.parseBooleanAttribute(
936
+ "tree-row-header-always-visible"
937
+ );
938
+ if (treeRowHeaderAlwaysVisible !== void 0)
939
+ this.attributeOptions.treeRowHeaderAlwaysVisible = treeRowHeaderAlwaysVisible;
940
+ const enableAutoResize = this.parseBooleanAttribute("enable-auto-resize");
941
+ if (enableAutoResize !== void 0) this.attributeOptions.enableAutoResize = enableAutoResize;
942
+ const enableVirtualization = this.parseBooleanAttribute("enable-virtualization");
943
+ if (enableVirtualization !== void 0)
944
+ this.attributeOptions.enableVirtualization = enableVirtualization;
945
+ const infiniteScrollUp = this.parseBooleanAttribute("infinite-scroll-up");
946
+ if (infiniteScrollUp !== void 0) this.attributeOptions.infiniteScrollUp = infiniteScrollUp;
947
+ const infiniteScrollDown = this.parseBooleanAttribute("infinite-scroll-down");
948
+ if (infiniteScrollDown !== void 0)
949
+ this.attributeOptions.infiniteScrollDown = infiniteScrollDown;
950
+ const columnDefs = this.parseJsonAttribute("column-defs");
951
+ if (columnDefs !== void 0) this.attributeOptions.columnDefs = columnDefs;
952
+ const data = this.parseJsonAttribute("data");
953
+ if (data !== void 0) this.attributeOptions.data = data;
954
+ const grouping = this.parseJsonAttribute("grouping");
955
+ if (grouping !== void 0 && grouping !== null)
956
+ this.attributeOptions.grouping = grouping;
957
+ const paginationPageSizes = this.parseJsonAttribute(
958
+ "pagination-page-sizes"
959
+ );
960
+ if (paginationPageSizes !== void 0)
961
+ this.attributeOptions.paginationPageSizes = paginationPageSizes;
962
+ if (this.activeOptions) {
963
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
964
+ }
965
+ }
966
+ parseBooleanAttribute(name) {
967
+ return this.hasAttribute(name) ? true : void 0;
968
+ }
969
+ parseNumberAttribute(name) {
970
+ const raw = this.getAttribute(name);
971
+ if (raw === null) return void 0;
972
+ const parsed = Number(raw);
973
+ return Number.isFinite(parsed) ? parsed : void 0;
974
+ }
975
+ parseJsonAttribute(name) {
976
+ const raw = this.getAttribute(name);
977
+ if (raw === null) return void 0;
978
+ try {
979
+ return JSON.parse(raw);
980
+ } catch (e) {
981
+ console.warn(`<ui-grid-element>: invalid JSON in "${name}" attribute`, e);
982
+ return void 0;
983
+ }
984
+ }
776
985
  get controlIcons() {
777
986
  return this.iconOverrides;
778
987
  }
@@ -780,12 +989,337 @@ var UiGridStandaloneElement = class extends HTMLElement {
780
989
  this.iconOverrides = { ...value };
781
990
  this.render();
782
991
  }
992
+ // ─────────────────────────────────────────────────────────────────
993
+ // Individual property accessors (mirrors of HTML attributes)
994
+ // ─────────────────────────────────────────────────────────────────
995
+ get gridId() {
996
+ return this.options.id ?? "";
997
+ }
998
+ set gridId(value) {
999
+ this.activeOptions = { ...this.activeOptions, id: value };
1000
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1001
+ }
1002
+ // @ts-ignore: Intentional override of HTMLElement.title
1003
+ get title() {
1004
+ return this.options.title ?? "";
1005
+ }
1006
+ // @ts-ignore: Intentional override of HTMLElement.title
1007
+ set title(value) {
1008
+ this.activeOptions = { ...this.activeOptions, title: value };
1009
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1010
+ }
1011
+ get rowHeight() {
1012
+ return this.options.rowHeight ?? 40;
1013
+ }
1014
+ set rowHeight(value) {
1015
+ this.activeOptions = { ...this.activeOptions, rowHeight: value };
1016
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1017
+ }
1018
+ get headerRowHeight() {
1019
+ return this.options.headerRowHeight ?? 50;
1020
+ }
1021
+ set headerRowHeight(value) {
1022
+ this.activeOptions = { ...this.activeOptions, headerRowHeight: value };
1023
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1024
+ }
1025
+ get viewportHeight() {
1026
+ return this.options.viewportHeight ?? 560;
1027
+ }
1028
+ set viewportHeight(value) {
1029
+ this.activeOptions = { ...this.activeOptions, viewportHeight: value };
1030
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1031
+ }
1032
+ get paginationPageSize() {
1033
+ return this.options.paginationPageSize ?? 25;
1034
+ }
1035
+ set paginationPageSize(value) {
1036
+ this.activeOptions = { ...this.activeOptions, paginationPageSize: value };
1037
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1038
+ }
1039
+ get paginationCurrentPage() {
1040
+ return this.options.paginationCurrentPage ?? 1;
1041
+ }
1042
+ set paginationCurrentPage(value) {
1043
+ this.activeOptions = { ...this.activeOptions, paginationCurrentPage: value };
1044
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1045
+ }
1046
+ get totalItems() {
1047
+ return this.options.totalItems;
1048
+ }
1049
+ set totalItems(value) {
1050
+ this.activeOptions = { ...this.activeOptions, totalItems: value };
1051
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1052
+ }
1053
+ get virtualizationThreshold() {
1054
+ return this.options.virtualizationThreshold ?? 40;
1055
+ }
1056
+ set virtualizationThreshold(value) {
1057
+ this.activeOptions = { ...this.activeOptions, virtualizationThreshold: value };
1058
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1059
+ }
1060
+ get treeChildrenField() {
1061
+ return this.options.treeChildrenField ?? "children";
1062
+ }
1063
+ set treeChildrenField(value) {
1064
+ this.activeOptions = { ...this.activeOptions, treeChildrenField: value };
1065
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1066
+ }
1067
+ get treeIndent() {
1068
+ return this.options.treeIndent ?? 20;
1069
+ }
1070
+ set treeIndent(value) {
1071
+ this.activeOptions = { ...this.activeOptions, treeIndent: value };
1072
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1073
+ }
1074
+ get expandableRowHeight() {
1075
+ return this.options.expandableRowHeight ?? 150;
1076
+ }
1077
+ set expandableRowHeight(value) {
1078
+ this.activeOptions = { ...this.activeOptions, expandableRowHeight: value };
1079
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1080
+ }
1081
+ get expandableRowHeaderWidth() {
1082
+ return this.options.expandableRowHeaderWidth ?? 40;
1083
+ }
1084
+ set expandableRowHeaderWidth(value) {
1085
+ this.activeOptions = { ...this.activeOptions, expandableRowHeaderWidth: value };
1086
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1087
+ }
1088
+ get emptyMessage() {
1089
+ return this.options.emptyMessage ?? "No data available.";
1090
+ }
1091
+ set emptyMessage(value) {
1092
+ this.activeOptions = { ...this.activeOptions, emptyMessage: value };
1093
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1094
+ }
1095
+ get infiniteScrollRowsFromEnd() {
1096
+ return this.options.infiniteScrollRowsFromEnd ?? 10;
1097
+ }
1098
+ set infiniteScrollRowsFromEnd(value) {
1099
+ this.activeOptions = { ...this.activeOptions, infiniteScrollRowsFromEnd: value };
1100
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1101
+ }
1102
+ get columnDefs() {
1103
+ return this.options.columnDefs;
1104
+ }
1105
+ set columnDefs(value) {
1106
+ this.activeOptions = { ...this.activeOptions, columnDefs: value };
1107
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1108
+ }
1109
+ get data() {
1110
+ return this.options.data;
1111
+ }
1112
+ set data(value) {
1113
+ this.activeOptions = { ...this.activeOptions, data: value };
1114
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1115
+ }
1116
+ get enableSorting() {
1117
+ return this.options.enableSorting ?? true;
1118
+ }
1119
+ set enableSorting(value) {
1120
+ this.activeOptions = { ...this.activeOptions, enableSorting: value };
1121
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1122
+ }
1123
+ get enableFiltering() {
1124
+ return this.options.enableFiltering ?? true;
1125
+ }
1126
+ set enableFiltering(value) {
1127
+ this.activeOptions = { ...this.activeOptions, enableFiltering: value };
1128
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1129
+ }
1130
+ get enableGrouping() {
1131
+ return this.options.enableGrouping ?? true;
1132
+ }
1133
+ set enableGrouping(value) {
1134
+ this.activeOptions = { ...this.activeOptions, enableGrouping: value };
1135
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1136
+ }
1137
+ get enablePinning() {
1138
+ return this.options.enablePinning ?? true;
1139
+ }
1140
+ set enablePinning(value) {
1141
+ this.activeOptions = { ...this.activeOptions, enablePinning: value };
1142
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1143
+ }
1144
+ get enableColumnMoving() {
1145
+ return this.options.enableColumnMoving ?? true;
1146
+ }
1147
+ set enableColumnMoving(value) {
1148
+ this.activeOptions = { ...this.activeOptions, enableColumnMoving: value };
1149
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1150
+ }
1151
+ get enableCellEdit() {
1152
+ return this.options.enableCellEdit ?? false;
1153
+ }
1154
+ set enableCellEdit(value) {
1155
+ this.activeOptions = { ...this.activeOptions, enableCellEdit: value };
1156
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1157
+ }
1158
+ get enableCellEditOnFocus() {
1159
+ return this.options.enableCellEditOnFocus ?? false;
1160
+ }
1161
+ set enableCellEditOnFocus(value) {
1162
+ this.activeOptions = { ...this.activeOptions, enableCellEditOnFocus: value };
1163
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1164
+ }
1165
+ get enablePagination() {
1166
+ return this.options.enablePagination ?? false;
1167
+ }
1168
+ set enablePagination(value) {
1169
+ this.activeOptions = { ...this.activeOptions, enablePagination: value };
1170
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1171
+ }
1172
+ get enablePaginationControls() {
1173
+ return this.options.enablePaginationControls ?? true;
1174
+ }
1175
+ set enablePaginationControls(value) {
1176
+ this.activeOptions = { ...this.activeOptions, enablePaginationControls: value };
1177
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1178
+ }
1179
+ get useExternalPagination() {
1180
+ return this.options.useExternalPagination ?? false;
1181
+ }
1182
+ set useExternalPagination(value) {
1183
+ this.activeOptions = { ...this.activeOptions, useExternalPagination: value };
1184
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1185
+ }
1186
+ get enableExpandable() {
1187
+ return this.options.enableExpandable ?? false;
1188
+ }
1189
+ set enableExpandable(value) {
1190
+ this.activeOptions = { ...this.activeOptions, enableExpandable: value };
1191
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1192
+ }
1193
+ get enableTreeView() {
1194
+ return this.options.enableTreeView ?? false;
1195
+ }
1196
+ set enableTreeView(value) {
1197
+ this.activeOptions = { ...this.activeOptions, enableTreeView: value };
1198
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1199
+ }
1200
+ get showTreeExpandNoChildren() {
1201
+ return this.options.showTreeExpandNoChildren ?? false;
1202
+ }
1203
+ set showTreeExpandNoChildren(value) {
1204
+ this.activeOptions = { ...this.activeOptions, showTreeExpandNoChildren: value };
1205
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1206
+ }
1207
+ get treeRowHeaderAlwaysVisible() {
1208
+ return this.options.treeRowHeaderAlwaysVisible ?? false;
1209
+ }
1210
+ set treeRowHeaderAlwaysVisible(value) {
1211
+ this.activeOptions = { ...this.activeOptions, treeRowHeaderAlwaysVisible: value };
1212
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1213
+ }
1214
+ get enableAutoResize() {
1215
+ return this.options.enableAutoResize ?? false;
1216
+ }
1217
+ set enableAutoResize(value) {
1218
+ this.activeOptions = { ...this.activeOptions, enableAutoResize: value };
1219
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1220
+ }
1221
+ get enableVirtualization() {
1222
+ return this.options.enableVirtualization ?? true;
1223
+ }
1224
+ set enableVirtualization(value) {
1225
+ this.activeOptions = { ...this.activeOptions, enableVirtualization: value };
1226
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1227
+ }
1228
+ get infiniteScrollUp() {
1229
+ return this.options.infiniteScrollUp ?? false;
1230
+ }
1231
+ set infiniteScrollUp(value) {
1232
+ this.activeOptions = { ...this.activeOptions, infiniteScrollUp: value };
1233
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1234
+ }
1235
+ get infiniteScrollDown() {
1236
+ return this.options.infiniteScrollDown ?? false;
1237
+ }
1238
+ set infiniteScrollDown(value) {
1239
+ this.activeOptions = { ...this.activeOptions, infiniteScrollDown: value };
1240
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1241
+ }
1242
+ get grouping() {
1243
+ return this.options.grouping;
1244
+ }
1245
+ set grouping(value) {
1246
+ this.activeOptions = { ...this.activeOptions, grouping: value };
1247
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1248
+ }
1249
+ get paginationPageSizes() {
1250
+ return this.options.paginationPageSizes;
1251
+ }
1252
+ set paginationPageSizes(value) {
1253
+ this.activeOptions = { ...this.activeOptions, paginationPageSizes: value };
1254
+ this.ensureController(this.buildEffectiveOptions(this.activeOptions));
1255
+ }
783
1256
  getState() {
784
1257
  return this.controller?.getState() ?? null;
785
1258
  }
786
1259
  setState(state) {
787
1260
  this.controller?.setState(state);
788
1261
  }
1262
+ /**
1263
+ * Fast-path: update only row data without rebuilding the shadow DOM.
1264
+ * The pipeline is updated synchronously; DOM patching is deferred to the
1265
+ * next animation frame so it never interrupts an in-progress scroll paint.
1266
+ * If multiple ticks arrive before the frame fires, only the latest wins.
1267
+ * If a scroll-triggered full render fires first, the frame is cancelled
1268
+ * (the fresh innerHTML already reflects the latest data).
1269
+ */
1270
+ setData(rows) {
1271
+ const controller = this.controller;
1272
+ if (!controller) {
1273
+ return;
1274
+ }
1275
+ this.skipNextRender = true;
1276
+ controller.refreshData(rows);
1277
+ this.skipNextRender = false;
1278
+ if (this.dataFrame !== null) {
1279
+ cancelAnimationFrame(this.dataFrame);
1280
+ }
1281
+ this.dataFrame = requestAnimationFrame(() => {
1282
+ this.dataFrame = null;
1283
+ this.patchCells();
1284
+ });
1285
+ }
1286
+ patchCells() {
1287
+ const snapshot = this.snapshot;
1288
+ if (!snapshot) {
1289
+ return;
1290
+ }
1291
+ const root = this.shadowRoot;
1292
+ if (!root) {
1293
+ return;
1294
+ }
1295
+ const rowMap = /* @__PURE__ */ new Map();
1296
+ for (const row of snapshot.pipeline.visibleRows) {
1297
+ rowMap.set(row.id, row);
1298
+ }
1299
+ const colMap = /* @__PURE__ */ new Map();
1300
+ for (const col of snapshot.visibleColumns) {
1301
+ colMap.set(col.name, col);
1302
+ }
1303
+ const cells = root.querySelectorAll("[data-row][data-column]");
1304
+ for (const cell of cells) {
1305
+ const rowId = cell.dataset["row"];
1306
+ const colName = cell.dataset["column"];
1307
+ if (!rowId || !colName) continue;
1308
+ const row = rowMap.get(rowId);
1309
+ const column = colMap.get(colName);
1310
+ if (!row || !column) continue;
1311
+ const cellShell = cell.querySelector(".cell-shell");
1312
+ if (!cellShell) continue;
1313
+ const newContent = this.renderCellTemplate(row, column, 0);
1314
+ const toggles = cellShell.querySelectorAll(".row-toggle");
1315
+ if (toggles.length === 0) {
1316
+ cellShell.innerHTML = newContent;
1317
+ } else {
1318
+ const toggleHtml = Array.from(toggles).map((t) => t.outerHTML).join("");
1319
+ cellShell.innerHTML = toggleHtml + newContent;
1320
+ }
1321
+ }
1322
+ }
789
1323
  connectedCallback() {
790
1324
  this.ensureShadowRoot();
791
1325
  this.bindEvents();
@@ -842,16 +1376,17 @@ var UiGridStandaloneElement = class extends HTMLElement {
842
1376
  });
843
1377
  }
844
1378
  buildEffectiveOptions(options) {
1379
+ const merged = {
1380
+ ...this.attributeOptions,
1381
+ ...options
1382
+ };
845
1383
  const hasExpandableSlot = this.getTemplateMarkup("expandable-row") !== null;
846
- if (options.enableExpandable && hasExpandableSlot && !options.expandableRowTemplate) {
847
- return {
848
- ...options,
849
- expandableRowTemplate: {
850
- createEmbeddedView: () => void 0
851
- }
1384
+ if (merged.enableExpandable && hasExpandableSlot && !merged.expandableRowTemplate) {
1385
+ merged.expandableRowTemplate = {
1386
+ createEmbeddedView: () => void 0
852
1387
  };
853
1388
  }
854
- return options;
1389
+ return merged;
855
1390
  }
856
1391
  bindEvents() {
857
1392
  const root = this.ensureShadowRoot();
@@ -1152,14 +1687,63 @@ var UiGridStandaloneElement = class extends HTMLElement {
1152
1687
  }
1153
1688
  this.scrollFrame = requestAnimationFrame(() => {
1154
1689
  this.scrollFrame = null;
1155
- this.render();
1690
+ this.renderVirtualBody();
1156
1691
  });
1157
1692
  },
1158
1693
  true
1159
1694
  );
1160
1695
  root.__uiGridBound = true;
1161
1696
  }
1697
+ /**
1698
+ * Targeted update for virtual scroll: replaces only the rendered rows inside
1699
+ * the existing `.grid-virtual-body` without touching the scroll container.
1700
+ * This preserves momentum/inertia scrolling because the `.grid-table` element
1701
+ * is never destroyed.
1702
+ */
1703
+ renderVirtualBody() {
1704
+ const snapshot = this.snapshot;
1705
+ if (!snapshot?.pipeline.virtualizationEnabled) {
1706
+ this.render();
1707
+ return;
1708
+ }
1709
+ const root = this.shadowRoot;
1710
+ const virtualBody = root?.querySelector(".grid-virtual-body");
1711
+ if (!root || !virtualBody) {
1712
+ this.render();
1713
+ return;
1714
+ }
1715
+ if (this.dataFrame !== null) {
1716
+ cancelAnimationFrame(this.dataFrame);
1717
+ this.dataFrame = null;
1718
+ }
1719
+ const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1720
+ const viewportHeight = snapshot.options.viewportHeight ?? 560;
1721
+ const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
1722
+ const bodyScrollTop = Math.max(0, this.scrollPosition - stickyChromeHeight);
1723
+ const overscan = 4;
1724
+ const startIndex = Math.max(0, Math.floor(bodyScrollTop / snapshot.rowSize) - overscan);
1725
+ this.lastVirtualStartIndex = startIndex;
1726
+ const visibleCount = Math.ceil(bodyViewportHeight / snapshot.rowSize) + overscan * 2;
1727
+ const itemsToRender = snapshot.pipeline.displayItems.slice(
1728
+ startIndex,
1729
+ Math.min(snapshot.pipeline.displayItems.length, startIndex + visibleCount)
1730
+ );
1731
+ const virtualOffset = startIndex * snapshot.rowSize;
1732
+ virtualBody.style.top = `${virtualOffset}px`;
1733
+ virtualBody.innerHTML = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
1734
+ }
1162
1735
  render() {
1736
+ if (this.skipNextRender) {
1737
+ return;
1738
+ }
1739
+ if (this.dataFrame !== null) {
1740
+ cancelAnimationFrame(this.dataFrame);
1741
+ this.dataFrame = null;
1742
+ }
1743
+ if (this.scrollFrame !== null) {
1744
+ cancelAnimationFrame(this.scrollFrame);
1745
+ this.scrollFrame = null;
1746
+ }
1163
1747
  const root = this.ensureShadowRoot();
1164
1748
  const snapshot = this.snapshot;
1165
1749
  const previousGridTable = root.querySelector(".grid-table");
@@ -1185,9 +1769,6 @@ var UiGridStandaloneElement = class extends HTMLElement {
1185
1769
  const headerStickyTop = this.measuredHeaderStickyHeight || options.headerRowHeight || 50;
1186
1770
  const stickyChromeHeight = this.measuredHeaderStickyHeight + this.measuredFilterStickyHeight;
1187
1771
  const bodyViewportHeight = Math.max(snapshot.rowSize, viewportHeight - stickyChromeHeight);
1188
- const visibleRowCount = snapshot.pipeline.visibleRows.length;
1189
- const totalRows = options.data.length;
1190
- const pinnedCount = Object.keys(snapshot.pinnedColumns).length;
1191
1772
  let startIndex = 0;
1192
1773
  let itemsToRender = snapshot.pipeline.displayItems;
1193
1774
  let virtualOffset = 0;
@@ -1214,11 +1795,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
1214
1795
  const bodyContent = itemsToRender.map((item, index) => this.renderDisplayItem(item, startIndex + index)).join("");
1215
1796
  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>`;
1216
1797
  const pagination = paginationEnabled && showPagination ? this.renderPagination(snapshot) : "";
1217
- 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>`;
1218
- 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>`;
1219
- 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>`;
1220
1798
  const gridTableStyle = `${virtualizationEnabled ? `height:${viewportHeight}px;overflow-y:auto;` : ""}--ui-grid-header-sticky-top:${headerStickyTop}px;`;
1221
- root.innerHTML = `<style>${import_ui_grid_core2.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>`;
1799
+ root.innerHTML = `<style>${import_ui_grid_core2.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>`;
1222
1800
  const gridTable = root.querySelector(".grid-table");
1223
1801
  if (gridTable && this.scrollPosition > 0 && virtualizationEnabled) {
1224
1802
  this.suppressScrollEvent = true;
@@ -1273,7 +1851,8 @@ var UiGridStandaloneElement = class extends HTMLElement {
1273
1851
  const sortIconKey = sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.asc ? "sortAsc" : sortDirection === import_ui_grid_core2.SORT_DIRECTIONS.desc ? "sortDesc" : "sortNone";
1274
1852
  const draggable = (0, import_ui_grid_core2.canGridMoveColumns)(options) ? 'draggable="true"' : "";
1275
1853
  const pinLabel = isPinned ? this.snapshot?.labels.unpin ?? "Unpin" : this.snapshot?.labels.pinColumn ?? "Pin";
1276
- 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>`;
1854
+ const headerValue = escapeHtml((0, import_ui_grid_core2.formatGridHeaderDisplayValue)((0, import_ui_grid_core2.buildGridHeaderContext)(column)));
1855
+ 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>`;
1277
1856
  }
1278
1857
  renderFilterCell(column) {
1279
1858
  const value = this.snapshot?.activeFilters[column.name] ?? "";