@revisium/schema-toolkit-ui 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2944,6 +2944,7 @@ const PlusButton = ({ onClick, tooltip, disabled, dataTestId }) => {
2944
2944
  variant: "ghost",
2945
2945
  color: "gray.400",
2946
2946
  disabled,
2947
+ focusRing: "none",
2947
2948
  _hover: {
2948
2949
  bg: "gray.100",
2949
2950
  color: "gray.600"
@@ -7031,12 +7032,32 @@ function selectDefaultColumns(columns, maxVisible = 4) {
7031
7032
  //#endregion
7032
7033
  //#region src/table-editor/Columns/model/ColumnsModel.ts
7033
7034
  const DEFAULT_COLUMN_WIDTH = 150;
7035
+ const DEFAULT_ID_COLUMN_WIDTH = 240;
7036
+ function fieldToCssVar(field) {
7037
+ return `--cw-${field.replaceAll(".", "-")}`;
7038
+ }
7039
+ function isValidPinZoneOrder(fields, pins) {
7040
+ let zone = "left";
7041
+ for (const field of fields) {
7042
+ const pin = pins.get(field);
7043
+ if (zone === "left") {
7044
+ if (pin === "right") zone = "right";
7045
+ else if (pin === void 0) zone = "none";
7046
+ } else if (zone === "none") {
7047
+ if (pin === "left") return false;
7048
+ if (pin === "right") zone = "right";
7049
+ } else if (pin !== "right") return false;
7050
+ }
7051
+ return true;
7052
+ }
7034
7053
  var ColumnsModel = class {
7035
7054
  _allColumns = [];
7036
7055
  _visibleFields = [];
7037
7056
  _columnWidths = mobx.observable.map();
7038
7057
  _pinnedColumns = mobx.observable.map();
7039
7058
  _onChange = null;
7059
+ _isResizing = false;
7060
+ _wrapperElement = null;
7040
7061
  constructor() {
7041
7062
  (0, mobx.makeAutoObservable)(this, {}, { autoBind: true });
7042
7063
  }
@@ -7195,17 +7216,36 @@ var ColumnsModel = class {
7195
7216
  this._visibleFields = fields;
7196
7217
  this._notifyChange();
7197
7218
  }
7219
+ get isResizing() {
7220
+ return this._isResizing;
7221
+ }
7222
+ setWrapperElement(el) {
7223
+ this._wrapperElement = el;
7224
+ }
7198
7225
  setColumnWidth(field, width) {
7226
+ this._isResizing = true;
7199
7227
  this._columnWidths.set(field, width);
7228
+ if (this._wrapperElement) this._wrapperElement.style.setProperty(fieldToCssVar(field), `${width}px`);
7200
7229
  }
7201
7230
  commitColumnWidth() {
7231
+ this._isResizing = false;
7202
7232
  this._notifyChange();
7203
7233
  }
7204
7234
  getColumnWidth(field) {
7205
7235
  return this._columnWidths.get(field);
7206
7236
  }
7207
7237
  resolveColumnWidth(field) {
7208
- return this._columnWidths.get(field) ?? DEFAULT_COLUMN_WIDTH;
7238
+ return this._resolveWidthUntracked(field);
7239
+ }
7240
+ get columnWidthCssVars() {
7241
+ if (this._isResizing) return this._buildCssVarsUntracked();
7242
+ return this._buildCssVarsTracked();
7243
+ }
7244
+ getWidthCssVarsDuringResize() {
7245
+ return this._buildCssVarsUntracked();
7246
+ }
7247
+ columnWidthCssVar(field) {
7248
+ return `var(${fieldToCssVar(field)}, ${field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH}px)`;
7209
7249
  }
7210
7250
  getPinState(field) {
7211
7251
  return this._pinnedColumns.get(field);
@@ -7263,7 +7303,7 @@ var ColumnsModel = class {
7263
7303
  let offset = selectionWidth;
7264
7304
  for (const f of this._visibleFields) {
7265
7305
  if (f === field) break;
7266
- if (this._pinnedColumns.get(f) === "left") offset += this.resolveColumnWidth(f);
7306
+ if (this._pinnedColumns.get(f) === "left") offset += this._resolveWidthUntracked(f);
7267
7307
  }
7268
7308
  return offset;
7269
7309
  }
@@ -7276,10 +7316,37 @@ var ColumnsModel = class {
7276
7316
  found = true;
7277
7317
  continue;
7278
7318
  }
7279
- if (found && this._pinnedColumns.get(f) === "right") offset += this.resolveColumnWidth(f);
7319
+ if (found && this._pinnedColumns.get(f) === "right") offset += this._resolveWidthUntracked(f);
7280
7320
  }
7281
7321
  return offset;
7282
7322
  }
7323
+ getColumnStickyLeftCss(field, selectionWidth) {
7324
+ if (this._pinnedColumns.get(field) !== "left") return;
7325
+ const parts = [];
7326
+ if (selectionWidth > 0) parts.push(`${selectionWidth}px`);
7327
+ for (const f of this._visibleFields) {
7328
+ if (f === field) break;
7329
+ if (this._pinnedColumns.get(f) === "left") parts.push(this.columnWidthCssVar(f));
7330
+ }
7331
+ if (parts.length === 0) return "0px";
7332
+ if (parts.length === 1) return parts[0];
7333
+ return `calc(${parts.join(" + ")})`;
7334
+ }
7335
+ getColumnStickyRightCss(field) {
7336
+ if (this._pinnedColumns.get(field) !== "right") return;
7337
+ const parts = [];
7338
+ let found = false;
7339
+ for (const f of this._visibleFields) {
7340
+ if (f === field) {
7341
+ found = true;
7342
+ continue;
7343
+ }
7344
+ if (found && this._pinnedColumns.get(f) === "right") parts.push(this.columnWidthCssVar(f));
7345
+ }
7346
+ if (parts.length === 0) return "0px";
7347
+ if (parts.length === 1) return parts[0];
7348
+ return `calc(${parts.join(" + ")})`;
7349
+ }
7283
7350
  isStickyLeftBoundary(field) {
7284
7351
  if (this._pinnedColumns.get(field) !== "left") return false;
7285
7352
  const index = this.getColumnIndex(field);
@@ -7315,16 +7382,20 @@ var ColumnsModel = class {
7315
7382
  applyViewColumns(viewColumns) {
7316
7383
  const lookup = this._columnLookup;
7317
7384
  const fields = [];
7318
- this._columnWidths.clear();
7319
- this._pinnedColumns.clear();
7385
+ const widths = /* @__PURE__ */ new Map();
7386
+ const pins = /* @__PURE__ */ new Map();
7320
7387
  for (const vc of viewColumns) {
7321
7388
  const field = this._fromViewField(vc.field);
7322
7389
  if (lookup.has(field)) {
7323
7390
  fields.push(field);
7324
- if (vc.width !== void 0) this._columnWidths.set(field, vc.width);
7325
- if (vc.pinned !== void 0) this._pinnedColumns.set(field, vc.pinned);
7391
+ if (vc.width !== void 0) widths.set(field, vc.width);
7392
+ if (vc.pinned !== void 0) pins.set(field, vc.pinned);
7326
7393
  }
7327
7394
  }
7395
+ this._columnWidths.clear();
7396
+ this._pinnedColumns.clear();
7397
+ for (const [field, width] of widths) this._columnWidths.set(field, width);
7398
+ if (isValidPinZoneOrder(fields, pins)) for (const [field, side] of pins) this._pinnedColumns.set(field, side);
7328
7399
  if (fields.length === 0) this._visibleFields = selectDefaultColumns(this._allColumns).map((col) => col.field);
7329
7400
  else this._visibleFields = fields;
7330
7401
  }
@@ -7394,6 +7465,27 @@ var ColumnsModel = class {
7394
7465
  if (viewField.startsWith("data.")) return viewField.slice(5);
7395
7466
  return viewField;
7396
7467
  }
7468
+ _resolveWidthUntracked(field) {
7469
+ return (0, mobx.untracked)(() => this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH));
7470
+ }
7471
+ _buildCssVarsTracked() {
7472
+ const vars = {};
7473
+ for (const field of this._visibleFields) {
7474
+ const width = this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH);
7475
+ vars[fieldToCssVar(field)] = `${width}px`;
7476
+ }
7477
+ return vars;
7478
+ }
7479
+ _buildCssVarsUntracked() {
7480
+ return (0, mobx.untracked)(() => {
7481
+ const vars = {};
7482
+ for (const field of this._visibleFields) {
7483
+ const width = this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH);
7484
+ vars[fieldToCssVar(field)] = `${width}px`;
7485
+ }
7486
+ return vars;
7487
+ });
7488
+ }
7397
7489
  _notifyChange() {
7398
7490
  if (this._onChange) this._onChange();
7399
7491
  }
@@ -9808,15 +9900,6 @@ function cellAt(ctx, colIndex, rowIndex) {
9808
9900
  field
9809
9901
  };
9810
9902
  }
9811
- function cellPosition(ctx, cell) {
9812
- const colIndex = ctx.columns.indexOf(cell.field);
9813
- const rowIndex = ctx.rowIds.indexOf(cell.rowId);
9814
- if (colIndex === -1 || rowIndex === -1) return null;
9815
- return {
9816
- colIndex,
9817
- rowIndex
9818
- };
9819
- }
9820
9903
  function createConfig() {
9821
9904
  return {
9822
9905
  initial: "idle",
@@ -9980,11 +10063,37 @@ function createConfig() {
9980
10063
 
9981
10064
  //#endregion
9982
10065
  //#region src/table-editor/Table/model/CellFSM.ts
10066
+ const READONLY_TOAST_THROTTLE_MS = 2e3;
10067
+ function buildIndexMap(arr) {
10068
+ const map = /* @__PURE__ */ new Map();
10069
+ arr.forEach((value, i) => {
10070
+ map.set(value, i);
10071
+ });
10072
+ return map;
10073
+ }
10074
+ function selectedRangeEquals(a, b) {
10075
+ if (a === b) return true;
10076
+ if (!a || !b) return false;
10077
+ return a.startCol === b.startCol && a.endCol === b.endCol && a.startRow === b.startRow && a.endRow === b.endRow;
10078
+ }
9983
10079
  var CellFSM = class {
9984
10080
  _fsm;
10081
+ _onReadonlyEditAttempt = null;
10082
+ _lastReadonlyToastAt = 0;
10083
+ _columnIndexMap = /* @__PURE__ */ new Map();
10084
+ _rowIndexMap = /* @__PURE__ */ new Map();
9985
10085
  constructor() {
9986
10086
  this._fsm = new ObservableFSM(createConfig());
9987
- (0, mobx.makeAutoObservable)(this, {}, { autoBind: true });
10087
+ (0, mobx.makeAutoObservable)(this, { selectedRange: (0, mobx.computed)({ equals: selectedRangeEquals }) }, { autoBind: true });
10088
+ }
10089
+ setOnReadonlyEditAttempt(cb) {
10090
+ this._onReadonlyEditAttempt = cb;
10091
+ }
10092
+ notifyReadonlyEditAttempt() {
10093
+ const now = Date.now();
10094
+ if (now - this._lastReadonlyToastAt < READONLY_TOAST_THROTTLE_MS) return;
10095
+ this._lastReadonlyToastAt = now;
10096
+ this._onReadonlyEditAttempt?.();
9988
10097
  }
9989
10098
  get state() {
9990
10099
  return this._fsm.state;
@@ -10007,8 +10116,14 @@ var CellFSM = class {
10007
10116
  get navigationVersion() {
10008
10117
  return this._fsm.context.navigationVersion;
10009
10118
  }
10119
+ get columnIndexMap() {
10120
+ return this._columnIndexMap;
10121
+ }
10122
+ get rowIndexMap() {
10123
+ return this._rowIndexMap;
10124
+ }
10010
10125
  get hasSelection() {
10011
- return this.getSelectedRange() !== null;
10126
+ return this.selectedRange !== null;
10012
10127
  }
10013
10128
  isCellFocused(rowId, field) {
10014
10129
  const cell = this._fsm.context.focusedCell;
@@ -10024,54 +10139,53 @@ var CellFSM = class {
10024
10139
  return this._fsm.matches("editing") && this.isCellFocused(rowId, field);
10025
10140
  }
10026
10141
  isCellInSelection(rowId, field) {
10027
- const range = this.getSelectedRange();
10142
+ const range = this.selectedRange;
10028
10143
  if (!range) return false;
10029
- const pos = this._getCellPosition(rowId, field);
10030
- if (!pos) return false;
10031
- return pos.colIndex >= range.startCol && pos.colIndex <= range.endCol && pos.rowIndex >= range.startRow && pos.rowIndex <= range.endRow;
10144
+ const colIndex = this._columnIndexMap.get(field);
10145
+ const rowIndex = this._rowIndexMap.get(rowId);
10146
+ if (colIndex === void 0 || rowIndex === void 0) return false;
10147
+ return colIndex >= range.startCol && colIndex <= range.endCol && rowIndex >= range.startRow && rowIndex <= range.endRow;
10032
10148
  }
10033
10149
  getCellSelectionEdges(rowId, field) {
10034
- const range = this.getSelectedRange();
10150
+ const range = this.selectedRange;
10035
10151
  if (!range) return null;
10036
- const pos = this._getCellPosition(rowId, field);
10037
- if (!pos) return null;
10038
- if (pos.colIndex < range.startCol || pos.colIndex > range.endCol || pos.rowIndex < range.startRow || pos.rowIndex > range.endRow) return null;
10152
+ const colIndex = this._columnIndexMap.get(field);
10153
+ const rowIndex = this._rowIndexMap.get(rowId);
10154
+ if (colIndex === void 0 || rowIndex === void 0) return null;
10155
+ if (colIndex < range.startCol || colIndex > range.endCol || rowIndex < range.startRow || rowIndex > range.endRow) return null;
10039
10156
  return {
10040
- top: pos.rowIndex === range.startRow,
10041
- bottom: pos.rowIndex === range.endRow,
10042
- left: pos.colIndex === range.startCol,
10043
- right: pos.colIndex === range.endCol
10157
+ top: rowIndex === range.startRow,
10158
+ bottom: rowIndex === range.endRow,
10159
+ left: colIndex === range.startCol,
10160
+ right: colIndex === range.endCol
10044
10161
  };
10045
10162
  }
10046
- _getCellPosition(rowId, field) {
10047
- const ctx = this._fsm.context;
10048
- const colIndex = ctx.columns.indexOf(field);
10049
- const rowIndex = ctx.rowIds.indexOf(rowId);
10050
- if (colIndex === -1 || rowIndex === -1) return null;
10051
- return {
10052
- colIndex,
10053
- rowIndex
10054
- };
10055
- }
10056
- getSelectedRange() {
10163
+ get selectedRange() {
10057
10164
  const ctx = this._fsm.context;
10058
10165
  if (!ctx.anchorCell || !ctx.focusedCell) return null;
10059
10166
  if (ctx.anchorCell.rowId === ctx.focusedCell.rowId && ctx.anchorCell.field === ctx.focusedCell.field) return null;
10060
- const anchorPos = cellPosition(ctx, ctx.anchorCell);
10061
- const focusPos = cellPosition(ctx, ctx.focusedCell);
10062
- if (!anchorPos || !focusPos) return null;
10167
+ const anchorCol = this._columnIndexMap.get(ctx.anchorCell.field);
10168
+ const anchorRow = this._rowIndexMap.get(ctx.anchorCell.rowId);
10169
+ const focusCol = this._columnIndexMap.get(ctx.focusedCell.field);
10170
+ const focusRow = this._rowIndexMap.get(ctx.focusedCell.rowId);
10171
+ if (anchorCol === void 0 || anchorRow === void 0 || focusCol === void 0 || focusRow === void 0) return null;
10063
10172
  return {
10064
- startCol: Math.min(anchorPos.colIndex, focusPos.colIndex),
10065
- endCol: Math.max(anchorPos.colIndex, focusPos.colIndex),
10066
- startRow: Math.min(anchorPos.rowIndex, focusPos.rowIndex),
10067
- endRow: Math.max(anchorPos.rowIndex, focusPos.rowIndex)
10173
+ startCol: Math.min(anchorCol, focusCol),
10174
+ endCol: Math.max(anchorCol, focusCol),
10175
+ startRow: Math.min(anchorRow, focusRow),
10176
+ endRow: Math.max(anchorRow, focusRow)
10068
10177
  };
10069
10178
  }
10179
+ getSelectedRange() {
10180
+ return this.selectedRange;
10181
+ }
10070
10182
  setNavigationContext(columns, rowIds) {
10071
10183
  Object.assign(this._fsm.context, {
10072
10184
  columns,
10073
10185
  rowIds
10074
10186
  });
10187
+ this._columnIndexMap = buildIndexMap(columns);
10188
+ this._rowIndexMap = buildIndexMap(rowIds);
10075
10189
  }
10076
10190
  updateNavigationContext(columns, rowIds) {
10077
10191
  const ctx = this._fsm.context;
@@ -10079,8 +10193,10 @@ var CellFSM = class {
10079
10193
  ctx.columns = columns;
10080
10194
  ctx.rowIds = rowIds;
10081
10195
  ctx.navigationVersion++;
10196
+ this._columnIndexMap = buildIndexMap(columns);
10197
+ this._rowIndexMap = buildIndexMap(rowIds);
10082
10198
  if (!focused) return;
10083
- if (columns.includes(focused.field) && rowIds.includes(focused.rowId)) ctx.anchorCell = null;
10199
+ if (this._columnIndexMap.has(focused.field) && this._rowIndexMap.has(focused.rowId)) ctx.anchorCell = null;
10084
10200
  else this.blur();
10085
10201
  }
10086
10202
  focusCell(cell) {
@@ -10364,6 +10480,9 @@ var CellVM = class {
10364
10480
  const newValue = node.getPlainValue();
10365
10481
  if (newValue !== previousValue) this._onCommit?.(this._rowId, this._column.field, newValue, previousValue);
10366
10482
  }
10483
+ notifyReadonlyEditAttempt() {
10484
+ this._cellFSM.notifyReadonlyEditAttempt();
10485
+ }
10367
10486
  blur() {
10368
10487
  this._cellFSM.blur();
10369
10488
  }
@@ -10735,6 +10854,242 @@ const CellContextMenu = (0, mobx_react_lite.observer)(({ cell, onEditPointerDown
10735
10854
  }) }) });
10736
10855
  });
10737
10856
 
10857
+ //#endregion
10858
+ //#region src/table-editor/Table/ui/Cell/cellStyles.ts
10859
+ const SELECTION_BORDER_COLOR = "#3b82f6";
10860
+ function getCellState(cell) {
10861
+ if (cell.isReadOnly) {
10862
+ if (cell.isFocused && !cell.isInSelection) return "readonlyFocused";
10863
+ if (cell.isInSelection) return "selected";
10864
+ return "readonly";
10865
+ }
10866
+ if (cell.isEditing) return "editing";
10867
+ if (cell.isFocused && !cell.isInSelection) return "focused";
10868
+ if (cell.isInSelection) return "selected";
10869
+ return "display";
10870
+ }
10871
+ function isPrintableKey(e) {
10872
+ if (e.ctrlKey || e.metaKey || e.altKey) return false;
10873
+ return e.key.length === 1;
10874
+ }
10875
+
10876
+ //#endregion
10877
+ //#region src/table-editor/Table/ui/Cell/cellCss.ts
10878
+ const CLS = "cw";
10879
+ const CLS_DISPLAY = `${CLS} cw-display`;
10880
+ const CLS_READONLY = `${CLS} cw-readonly`;
10881
+ const CLS_FOCUSED = `${CLS} cw-focused`;
10882
+ const CLS_EDITING = `${CLS} cw-editing`;
10883
+ const CLS_READONLY_FOCUSED = `${CLS} cw-readonlyFocused`;
10884
+ const CLS_SELECTED = `${CLS} cw-selected`;
10885
+ const CLS_ANCHOR = "cw-anchor";
10886
+ const STATE_CLASS = {
10887
+ display: CLS_DISPLAY,
10888
+ readonly: CLS_READONLY,
10889
+ focused: CLS_FOCUSED,
10890
+ editing: CLS_EDITING,
10891
+ readonlyFocused: CLS_READONLY_FOCUSED,
10892
+ selected: CLS_SELECTED
10893
+ };
10894
+ const CELL_STYLE_ID = "cell-wrapper-styles";
10895
+ function ensureCellStyles() {
10896
+ if (typeof document === "undefined") return;
10897
+ if (document.getElementById(CELL_STYLE_ID)) return;
10898
+ const style = document.createElement("style");
10899
+ style.id = CELL_STYLE_ID;
10900
+ style.textContent = [
10901
+ ".cw{height:40px;padding:0 8px;position:relative;overflow:hidden;cursor:cell;box-shadow:var(--cw-shadow,none)}",
10902
+ ".cw:focus,.cw:focus-visible{outline:none;box-shadow:var(--cw-shadow,none)}",
10903
+ ".cw-display:hover,.cw-readonly:hover{background-color:var(--chakra-colors-gray-50);box-shadow:inset 0 -1px 0 0 #ededed}",
10904
+ ".cw-focused{background-color:var(--chakra-colors-blue-50)}",
10905
+ ".cw-focused::before,.cw-editing::before,.cw-readonlyFocused::before,.cw-anchor::before{content:\"\";position:absolute;inset:1px;border-radius:1px;pointer-events:none}",
10906
+ ".cw-focused::before{border:2px solid var(--chakra-colors-blue-400)}",
10907
+ ".cw-editing{cursor:text;background-color:white;z-index:1}",
10908
+ ".cw-editing::before{border:2px solid var(--chakra-colors-blue-500)}",
10909
+ ".cw-readonlyFocused{background-color:var(--chakra-colors-gray-50)}",
10910
+ ".cw-readonlyFocused::before{border:2px solid var(--chakra-colors-gray-400)}",
10911
+ ".cw-selected{background-color:var(--chakra-colors-blue-100);user-select:none}",
10912
+ ".cw-anchor::before{border:2px solid var(--chakra-colors-blue-400)}"
10913
+ ].join("");
10914
+ document.head.appendChild(style);
10915
+ }
10916
+ const INNER_STYLE = {
10917
+ display: "flex",
10918
+ alignItems: "center",
10919
+ height: "100%",
10920
+ width: "100%",
10921
+ minWidth: 0,
10922
+ overflow: "hidden"
10923
+ };
10924
+ function buildSelectionBoxShadow(edges) {
10925
+ const shadows = [];
10926
+ if (edges.top) shadows.push(`inset 0 2px 0 0 ${SELECTION_BORDER_COLOR}`);
10927
+ if (edges.bottom) shadows.push(`inset 0 -2px 0 0 ${SELECTION_BORDER_COLOR}`);
10928
+ if (edges.left) shadows.push(`inset 2px 0 0 0 ${SELECTION_BORDER_COLOR}`);
10929
+ if (edges.right) shadows.push(`inset -2px 0 0 0 ${SELECTION_BORDER_COLOR}`);
10930
+ return shadows.length > 0 ? shadows.join(", ") : null;
10931
+ }
10932
+
10933
+ //#endregion
10934
+ //#region src/table-editor/Table/ui/Cell/useCellContextMenu.ts
10935
+ let pendingContextMenu = null;
10936
+ function clearPendingContextMenu() {
10937
+ const pending = pendingContextMenu;
10938
+ pendingContextMenu = null;
10939
+ return pending;
10940
+ }
10941
+ function setPendingContextMenu(value) {
10942
+ pendingContextMenu = value;
10943
+ }
10944
+ const cellMenuRegistry = /* @__PURE__ */ new WeakMap();
10945
+ function useCellContextMenu(cell, cellRef, deferredEdit) {
10946
+ const [menuAnchor, setMenuAnchor] = (0, react.useState)(null);
10947
+ const menuOpen = menuAnchor !== null;
10948
+ const openContextMenuAt = (0, react.useCallback)((clientX, clientY) => {
10949
+ if (!cell.isFocused && !cell.isInSelection) cell.focus();
10950
+ setMenuAnchor(new DOMRect(clientX, clientY, 0, 0));
10951
+ }, [cell]);
10952
+ (0, react.useEffect)(() => {
10953
+ const el = cellRef.current;
10954
+ if (el) cellMenuRegistry.set(el, openContextMenuAt);
10955
+ return () => {
10956
+ if (el) cellMenuRegistry.delete(el);
10957
+ };
10958
+ }, [cellRef, openContextMenuAt]);
10959
+ const menuCloseRef = (0, react.useRef)(null);
10960
+ const handleMenuClose = (0, react.useCallback)(() => {
10961
+ const pending = clearPendingContextMenu();
10962
+ const didTriggerEdit = deferredEdit.triggerIfRequested();
10963
+ if (!pending && !didTriggerEdit && !cell.isEditing) if (cell.isAnchor || cell.isFocused && !cell.hasRangeSelection) cellRef.current?.focus();
10964
+ else ((cellRef.current?.closest("[data-testid=\"table-widget\"]"))?.querySelector("[data-testid^=\"cell-\"][tabindex=\"0\"]"))?.focus();
10965
+ setMenuAnchor(null);
10966
+ if (pending) {
10967
+ const openFn = cellMenuRegistry.get(pending.target);
10968
+ if (openFn) setTimeout(() => {
10969
+ openFn(pending.clientX, pending.clientY);
10970
+ }, 0);
10971
+ }
10972
+ }, [
10973
+ cell,
10974
+ cellRef,
10975
+ deferredEdit
10976
+ ]);
10977
+ menuCloseRef.current = handleMenuClose;
10978
+ return {
10979
+ menuAnchor,
10980
+ menuOpen,
10981
+ openContextMenuAt,
10982
+ handleMenuClose,
10983
+ menuCloseRef
10984
+ };
10985
+ }
10986
+
10987
+ //#endregion
10988
+ //#region src/table-editor/Table/ui/Cell/useCellFocus.ts
10989
+ function useCellFocus(cellRef, state, isAnchorInRange, navVersion) {
10990
+ (0, react.useEffect)(() => {
10991
+ if (!cellRef.current) return;
10992
+ if (state === "focused" || state === "readonlyFocused" || isAnchorInRange) cellRef.current.focus();
10993
+ else if (state === "display" || state === "readonly" || state === "selected") cellRef.current.blur();
10994
+ }, [
10995
+ cellRef,
10996
+ state,
10997
+ isAnchorInRange,
10998
+ navVersion
10999
+ ]);
11000
+ }
11001
+
11002
+ //#endregion
11003
+ //#region src/table-editor/Table/ui/Cell/useCellKeyboard.ts
11004
+ function handleArrowKey(cell, e, shiftAction, moveAction) {
11005
+ e.preventDefault();
11006
+ if (e.shiftKey) shiftAction();
11007
+ else if (cell.hasRangeSelection) cell.focus();
11008
+ else moveAction();
11009
+ }
11010
+ function handleEditableKeys(cell, e, callbacks) {
11011
+ if (cell.isReadOnly) {
11012
+ if (e.key === "Enter" || e.key === "Delete" || e.key === "Backspace" || isPrintableKey(e)) {
11013
+ e.preventDefault();
11014
+ cell.notifyReadonlyEditAttempt();
11015
+ }
11016
+ return;
11017
+ }
11018
+ const hasRange = cell.hasRangeSelection;
11019
+ if (!hasRange && e.key === "Enter") {
11020
+ e.preventDefault();
11021
+ if (callbacks.onStartEdit) callbacks.onStartEdit();
11022
+ else if (callbacks.onDoubleClick) callbacks.onDoubleClick();
11023
+ } else if (!hasRange && (e.key === "Delete" || e.key === "Backspace") && callbacks.onDelete) {
11024
+ e.preventDefault();
11025
+ callbacks.onDelete();
11026
+ } else if (isPrintableKey(e) && callbacks.onTypeChar) {
11027
+ e.preventDefault();
11028
+ callbacks.onTypeChar(e.key);
11029
+ }
11030
+ }
11031
+ function useCellKeyboard(cell, state, isAnchorInRange, menuOpen, menuCloseRef, callbacks) {
11032
+ const { onStartEdit, onDoubleClick, onTypeChar, onDelete } = callbacks;
11033
+ return {
11034
+ handleKeyDown: (0, react.useCallback)((e) => {
11035
+ if (menuOpen && e.key === "Escape") {
11036
+ e.preventDefault();
11037
+ menuCloseRef.current?.();
11038
+ return;
11039
+ }
11040
+ if (!(state === "focused" || state === "readonlyFocused" || isAnchorInRange)) return;
11041
+ const isMod = e.ctrlKey || e.metaKey;
11042
+ if (isMod && e.key === "c" && !cell.hasRangeSelection) {
11043
+ e.preventDefault();
11044
+ cell.copyToClipboard();
11045
+ return;
11046
+ }
11047
+ if (isMod && e.key === "v") return;
11048
+ const arrow = {
11049
+ ArrowUp: [cell.shiftMoveUp, cell.moveUp],
11050
+ ArrowDown: [cell.shiftMoveDown, cell.moveDown],
11051
+ ArrowLeft: [cell.shiftMoveLeft, cell.moveLeft],
11052
+ ArrowRight: [cell.shiftMoveRight, cell.moveRight]
11053
+ }[e.key];
11054
+ if (e.key === "Escape") {
11055
+ e.preventDefault();
11056
+ if (cell.hasRangeSelection) cell.focus();
11057
+ else cell.blur();
11058
+ } else if (arrow) handleArrowKey(cell, e, arrow[0], arrow[1]);
11059
+ else if (e.key === "Tab") {
11060
+ e.preventDefault();
11061
+ cell.handleTab(e.shiftKey);
11062
+ } else handleEditableKeys(cell, e, {
11063
+ onStartEdit,
11064
+ onDoubleClick,
11065
+ onTypeChar,
11066
+ onDelete
11067
+ });
11068
+ }, [
11069
+ state,
11070
+ cell,
11071
+ isAnchorInRange,
11072
+ menuOpen,
11073
+ menuCloseRef,
11074
+ onStartEdit,
11075
+ onDoubleClick,
11076
+ onTypeChar,
11077
+ onDelete
11078
+ ]),
11079
+ handleDoubleClick: (0, react.useCallback)((e) => {
11080
+ if (state === "readonly" || state === "readonlyFocused") {
11081
+ cell.notifyReadonlyEditAttempt();
11082
+ return;
11083
+ }
11084
+ onDoubleClick?.(e.clientX);
11085
+ }, [
11086
+ state,
11087
+ cell,
11088
+ onDoubleClick
11089
+ ])
11090
+ };
11091
+ }
11092
+
10738
11093
  //#endregion
10739
11094
  //#region src/table-editor/Table/ui/Cell/useDeferredMenuEdit.ts
10740
11095
  /**
@@ -10789,282 +11144,117 @@ function useDeferredMenuEdit(editFn) {
10789
11144
  };
10790
11145
  }
10791
11146
 
10792
- //#endregion
10793
- //#region src/table-editor/Table/ui/Cell/cellStyles.ts
10794
- const FOCUS_RING_RESET = {
10795
- outline: "none",
10796
- boxShadow: "none"
10797
- };
10798
- const RANGE_BORDER_COLOR = "blue.400";
10799
- function buildSelectionBorderStyle(edges) {
10800
- return {
10801
- content: "\"\"",
10802
- position: "absolute",
10803
- top: edges.top ? "-1px" : 0,
10804
- bottom: edges.bottom ? "-1px" : 0,
10805
- left: edges.left ? "-1px" : 0,
10806
- right: edges.right ? "-1px" : 0,
10807
- borderTop: edges.top ? "2px solid" : "none",
10808
- borderBottom: edges.bottom ? "2px solid" : "none",
10809
- borderLeft: edges.left ? "2px solid" : "none",
10810
- borderRight: edges.right ? "2px solid" : "none",
10811
- borderColor: RANGE_BORDER_COLOR,
10812
- pointerEvents: "none",
10813
- zIndex: 2
10814
- };
10815
- }
10816
- const stateStyles = {
10817
- display: {
10818
- cursor: "cell",
10819
- _hover: { bg: "gray.50" }
10820
- },
10821
- focused: {
10822
- cursor: "cell",
10823
- bg: "blue.50",
10824
- _before: {
10825
- content: "\"\"",
10826
- position: "absolute",
10827
- inset: "1px",
10828
- border: "2px solid",
10829
- borderColor: "blue.400",
10830
- borderRadius: "1px",
10831
- pointerEvents: "none"
10832
- },
10833
- _focus: FOCUS_RING_RESET,
10834
- _focusVisible: FOCUS_RING_RESET
10835
- },
10836
- editing: {
10837
- cursor: "text",
10838
- bg: "white",
10839
- zIndex: 1,
10840
- _before: {
10841
- content: "\"\"",
10842
- position: "absolute",
10843
- inset: "1px",
10844
- border: "2px solid",
10845
- borderColor: "blue.500",
10846
- borderRadius: "1px",
10847
- pointerEvents: "none"
10848
- },
10849
- _focus: FOCUS_RING_RESET,
10850
- _focusVisible: FOCUS_RING_RESET
10851
- },
10852
- readonly: {
10853
- cursor: "default",
10854
- color: "gray.500"
10855
- },
10856
- readonlyFocused: {
10857
- cursor: "default",
10858
- bg: "gray.50",
10859
- _before: {
10860
- content: "\"\"",
10861
- position: "absolute",
10862
- inset: "1px",
10863
- border: "2px solid",
10864
- borderColor: "gray.400",
10865
- borderRadius: "1px",
10866
- pointerEvents: "none"
10867
- },
10868
- _focus: FOCUS_RING_RESET,
10869
- _focusVisible: FOCUS_RING_RESET
10870
- },
10871
- selected: {
10872
- cursor: "cell",
10873
- bg: "blue.100"
10874
- }
10875
- };
10876
- function getCellState(cell) {
10877
- if (cell.isReadOnly) {
10878
- if (cell.isFocused && !cell.isInSelection) return "readonlyFocused";
10879
- if (cell.isInSelection) return "selected";
10880
- return "readonly";
10881
- }
10882
- if (cell.isEditing) return "editing";
10883
- if (cell.isFocused && !cell.isInSelection) return "focused";
10884
- if (cell.isInSelection) return "selected";
10885
- return "display";
10886
- }
10887
- function isPrintableKey(e) {
10888
- if (e.ctrlKey || e.metaKey || e.altKey) return false;
10889
- return e.key.length === 1;
10890
- }
10891
-
10892
11147
  //#endregion
10893
11148
  //#region src/table-editor/Table/ui/Cell/CellWrapper.tsx
10894
- function handleArrowKey(cell, e, shiftAction, moveAction) {
10895
- e.preventDefault();
10896
- if (e.shiftKey) shiftAction();
10897
- else if (cell.hasRangeSelection) cell.focus();
10898
- else moveAction();
10899
- }
10900
- function handleEditableKeys(cell, e, onStartEdit, onDoubleClick, onTypeChar, onDelete) {
10901
- if (cell.isReadOnly) return;
10902
- const hasRange = cell.hasRangeSelection;
10903
- if (!hasRange && e.key === "Enter") {
10904
- e.preventDefault();
10905
- if (onStartEdit) onStartEdit();
10906
- else if (onDoubleClick) onDoubleClick();
10907
- } else if (!hasRange && (e.key === "Delete" || e.key === "Backspace") && onDelete) {
10908
- e.preventDefault();
10909
- onDelete();
10910
- } else if (isPrintableKey(e) && onTypeChar) {
10911
- e.preventDefault();
10912
- onTypeChar(e.key);
10913
- }
10914
- }
11149
+ const LazyContextMenu = (0, mobx_react_lite.observer)(({ cell, cellRef, anchorRect, onClose, onEditPointerDown }) => {
11150
+ const handleOpenChange = (0, react.useCallback)((details) => {
11151
+ if (!details.open) onClose();
11152
+ }, [onClose]);
11153
+ const handleInteractOutside = (0, react.useCallback)((e) => {
11154
+ const originalEvent = e.detail?.originalEvent;
11155
+ if (!originalEvent || originalEvent.button !== 2) return;
11156
+ const targetCell = originalEvent.target?.closest("[data-testid^=\"cell-\"]");
11157
+ if (targetCell) setPendingContextMenu({
11158
+ target: targetCell,
11159
+ clientX: originalEvent.clientX,
11160
+ clientY: originalEvent.clientY
11161
+ });
11162
+ }, []);
11163
+ const getAnchorRect = (0, react.useCallback)(() => {
11164
+ return anchorRect ?? (cellRef.current?.getBoundingClientRect() || null);
11165
+ }, [anchorRect, cellRef]);
11166
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Menu.Root, {
11167
+ open: true,
11168
+ onOpenChange: handleOpenChange,
11169
+ onInteractOutside: handleInteractOutside,
11170
+ positioning: {
11171
+ placement: "bottom-start",
11172
+ getAnchorRect
11173
+ },
11174
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellContextMenu, {
11175
+ cell,
11176
+ onEditPointerDown
11177
+ })
11178
+ });
11179
+ });
10915
11180
  const CellWrapper = (0, mobx_react_lite.observer)(({ cell, children, onDoubleClick, onStartEdit, onTypeChar, onDelete }) => {
11181
+ ensureCellStyles();
10916
11182
  const cellRef = (0, react.useRef)(null);
10917
- const menuOpenRef = (0, react.useRef)(false);
10918
- const deferredEdit = useDeferredMenuEdit(onStartEdit ?? onDoubleClick);
10919
11183
  const state = getCellState(cell);
10920
11184
  const selectionEdges = cell.selectionEdges;
10921
11185
  const isAnchorInRange = cell.isAnchor && cell.isInSelection;
10922
11186
  const navVersion = cell.navigationVersion;
10923
- (0, react.useEffect)(() => {
10924
- if (!cellRef.current) return;
10925
- if (state === "focused" || state === "readonlyFocused" || isAnchorInRange) cellRef.current.focus();
10926
- else if (state === "display" || state === "readonly" || state === "selected") cellRef.current.blur();
10927
- }, [
10928
- state,
10929
- isAnchorInRange,
10930
- navVersion
10931
- ]);
10932
- const handleClick = (0, react.useCallback)((e) => {
10933
- if (state === "editing") return;
10934
- if (e.shiftKey) cell.selectTo();
10935
- else cell.focus();
10936
- }, [state, cell]);
11187
+ const isActive = state === "focused" || state === "editing" || state === "readonlyFocused" || isAnchorInRange;
11188
+ const deferredEdit = useDeferredMenuEdit(onStartEdit ?? onDoubleClick);
11189
+ const { menuAnchor, menuOpen, openContextMenuAt, handleMenuClose, menuCloseRef } = useCellContextMenu(cell, cellRef, deferredEdit);
11190
+ useCellFocus(cellRef, state, isAnchorInRange, navVersion);
11191
+ const { handleKeyDown, handleDoubleClick } = useCellKeyboard(cell, state, isAnchorInRange, menuOpen, menuCloseRef, {
11192
+ onStartEdit,
11193
+ onDoubleClick,
11194
+ onTypeChar,
11195
+ onDelete
11196
+ });
10937
11197
  const handleMouseDown = (0, react.useCallback)((e) => {
10938
11198
  if (e.detail === 2 && state !== "readonly" && state !== "readonlyFocused") e.preventDefault();
11199
+ if (e.button === 2) {
11200
+ openContextMenuAt(e.clientX, e.clientY);
11201
+ return;
11202
+ }
10939
11203
  if (!e.shiftKey && e.button === 0 && state !== "editing") {
10940
11204
  e.preventDefault();
10941
11205
  cell.dragStart();
10942
11206
  cellRef.current?.focus();
10943
11207
  }
10944
- }, [state, cell]);
10945
- const handleMouseEnter = (0, react.useCallback)((e) => {
10946
- if (e.buttons === 1) cell.dragExtend();
10947
- }, [cell]);
10948
- const handleDoubleClick = (0, react.useCallback)((e) => {
10949
- if (state !== "readonly" && state !== "readonlyFocused") onDoubleClick?.(e.clientX);
10950
- }, [state, onDoubleClick]);
10951
- const handleKeyDown = (0, react.useCallback)((e) => {
10952
- if (!(state === "focused" || state === "readonlyFocused" || isAnchorInRange)) return;
10953
- const isMod = e.ctrlKey || e.metaKey;
10954
- if (isMod && e.key === "c" && !cell.hasRangeSelection) {
10955
- e.preventDefault();
10956
- cell.copyToClipboard();
10957
- return;
10958
- }
10959
- if (isMod && e.key === "v") return;
10960
- if (e.key === "Escape") {
10961
- e.preventDefault();
10962
- if (cell.hasRangeSelection) cell.focus();
10963
- else cell.blur();
10964
- } else if (e.key === "ArrowUp") handleArrowKey(cell, e, cell.shiftMoveUp, cell.moveUp);
10965
- else if (e.key === "ArrowDown") handleArrowKey(cell, e, cell.shiftMoveDown, cell.moveDown);
10966
- else if (e.key === "ArrowLeft") handleArrowKey(cell, e, cell.shiftMoveLeft, cell.moveLeft);
10967
- else if (e.key === "ArrowRight") handleArrowKey(cell, e, cell.shiftMoveRight, cell.moveRight);
10968
- else if (e.key === "Tab") {
10969
- e.preventDefault();
10970
- cell.handleTab(e.shiftKey);
10971
- } else handleEditableKeys(cell, e, onStartEdit, onDoubleClick, onTypeChar, onDelete);
10972
11208
  }, [
10973
11209
  state,
10974
11210
  cell,
10975
- isAnchorInRange,
10976
- onStartEdit,
10977
- onDoubleClick,
10978
- onTypeChar,
10979
- onDelete
11211
+ openContextMenuAt
10980
11212
  ]);
10981
- const afterStyle = selectionEdges ? buildSelectionBorderStyle(selectionEdges) : void 0;
10982
- const needsFocus = state === "focused" || state === "editing" || state === "readonlyFocused" || isAnchorInRange;
11213
+ const handleClick = (0, react.useCallback)((e) => {
11214
+ if (state === "editing") return;
11215
+ if (e.shiftKey) cell.selectTo();
11216
+ else cell.focus();
11217
+ }, [state, cell]);
11218
+ const handleMouseEnter = (0, react.useCallback)((e) => {
11219
+ if (e.buttons === 1) cell.dragExtend();
11220
+ }, [cell]);
10983
11221
  const handleBlur = (0, react.useCallback)((e) => {
10984
11222
  if (!cell.isFocused || cell.isEditing) return;
10985
- if (menuOpenRef.current) return;
11223
+ if (menuOpen) return;
10986
11224
  if (e.relatedTarget?.closest("[data-testid^=\"cell-\"]")) return;
10987
11225
  cell.blur();
10988
- }, [cell]);
10989
- const handleContextMenu = (0, react.useCallback)(() => {
10990
- if (!cell.isFocused && !cell.isInSelection) cell.focus();
10991
- }, [cell]);
10992
- const handleMenuOpenChange = (0, react.useCallback)((details) => {
10993
- menuOpenRef.current = details.open;
10994
- if (details.open) return;
10995
- if (deferredEdit.triggerIfRequested()) return;
10996
- if (cell.isEditing) return;
10997
- if (cell.isAnchor || cell.isFocused && !cell.hasRangeSelection) cellRef.current?.focus();
10998
- else ((cellRef.current?.closest("[data-testid=\"table-widget\"]"))?.querySelector("[data-testid^=\"cell-\"][tabindex=\"0\"]"))?.focus();
10999
- }, [cell, deferredEdit]);
11000
- const extraStyles = {};
11001
- if (isAnchorInRange) {
11002
- extraStyles._before = {
11003
- content: "\"\"",
11004
- position: "absolute",
11005
- inset: "1px",
11006
- border: "2px solid",
11007
- borderColor: "blue.400",
11008
- borderRadius: "1px",
11009
- pointerEvents: "none",
11010
- zIndex: 3
11011
- };
11012
- extraStyles._focus = FOCUS_RING_RESET;
11013
- extraStyles._focusVisible = FOCUS_RING_RESET;
11014
- }
11015
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Menu.Root, {
11016
- onOpenChange: handleMenuOpenChange,
11017
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Menu.ContextTrigger, {
11018
- asChild: true,
11019
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
11020
- ref: cellRef,
11021
- height: "40px",
11022
- px: "8px",
11023
- position: "relative",
11024
- overflow: selectionEdges ? "visible" : "hidden",
11025
- onClick: handleClick,
11026
- onMouseDown: handleMouseDown,
11027
- onMouseEnter: handleMouseEnter,
11028
- onDoubleClick: handleDoubleClick,
11029
- onKeyDown: handleKeyDown,
11030
- onBlur: handleBlur,
11031
- onContextMenu: handleContextMenu,
11032
- tabIndex: needsFocus ? 0 : -1,
11033
- userSelect: state === "selected" ? "none" : void 0,
11034
- "data-testid": `cell-${cell.rowId}-${cell.field}`,
11035
- ...stateStyles[state],
11036
- ...extraStyles,
11037
- _after: afterStyle,
11038
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
11039
- display: "flex",
11040
- alignItems: "center",
11041
- height: "100%",
11042
- width: "100%",
11043
- minWidth: 0,
11044
- overflow: "hidden",
11045
- children
11046
- }), state === "readonlyFocused" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
11047
- position: "absolute",
11048
- top: "1px",
11049
- right: "4px",
11050
- fontSize: "9px",
11051
- lineHeight: "1",
11052
- color: "gray.500",
11053
- bg: "gray.100",
11054
- px: "3px",
11055
- py: "1px",
11056
- borderRadius: "2px",
11057
- userSelect: "none",
11058
- pointerEvents: "none",
11059
- children: "readonly"
11060
- })]
11061
- })
11062
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellContextMenu, {
11063
- cell,
11064
- onEditPointerDown: deferredEdit.requestEdit
11065
- })]
11066
- });
11226
+ }, [cell, menuOpen]);
11227
+ const selectionShadow = selectionEdges ? buildSelectionBoxShadow(selectionEdges) : null;
11228
+ let className = STATE_CLASS[state];
11229
+ if (isAnchorInRange) className += ` ${CLS_ANCHOR}`;
11230
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11231
+ ref: cellRef,
11232
+ className,
11233
+ style: selectionShadow ? { "--cw-shadow": selectionShadow } : void 0,
11234
+ tabIndex: isActive ? 0 : -1,
11235
+ onClick: handleClick,
11236
+ onMouseDown: handleMouseDown,
11237
+ onMouseEnter: handleMouseEnter,
11238
+ onDoubleClick: isActive ? handleDoubleClick : void 0,
11239
+ onKeyDown: isActive ? handleKeyDown : void 0,
11240
+ onBlur: isActive ? handleBlur : void 0,
11241
+ onContextMenu: preventContextMenu,
11242
+ "data-testid": `cell-${cell.rowId}-${cell.field}`,
11243
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11244
+ style: INNER_STYLE,
11245
+ children
11246
+ })
11247
+ }), menuOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LazyContextMenu, {
11248
+ cell,
11249
+ cellRef,
11250
+ anchorRect: menuAnchor,
11251
+ onClose: handleMenuClose,
11252
+ onEditPointerDown: deferredEdit.requestEdit
11253
+ })] });
11067
11254
  });
11255
+ function preventContextMenu(e) {
11256
+ e.preventDefault();
11257
+ }
11068
11258
 
11069
11259
  //#endregion
11070
11260
  //#region src/table-editor/Table/ui/Cell/useTextareaCell.ts
@@ -11341,7 +11531,6 @@ const ForeignKeyCell = (0, mobx_react_lite.observer)(({ cell, onSearchForeignKey
11341
11531
  textOverflow: "ellipsis",
11342
11532
  overflow: "hidden",
11343
11533
  fontWeight: "300",
11344
- color: "blue.500",
11345
11534
  flex: 1,
11346
11535
  minWidth: 0,
11347
11536
  children: cell.displayValue
@@ -11549,8 +11738,8 @@ const ReadonlyCell = (0, mobx_react_lite.observer)(({ cell }) => {
11549
11738
  whiteSpace: "nowrap",
11550
11739
  textOverflow: "ellipsis",
11551
11740
  overflow: "hidden",
11552
- fontWeight: "300",
11553
- color: "gray.400",
11741
+ fontSize: "14px",
11742
+ color: "text/primary",
11554
11743
  children: cell.displayValue
11555
11744
  })
11556
11745
  });
@@ -11698,6 +11887,30 @@ function clearRange(range, rows, cols) {
11698
11887
  }
11699
11888
  }
11700
11889
 
11890
+ //#endregion
11891
+ //#region src/table-editor/Table/ui/borderConstants.ts
11892
+ const CELL_BORDER_COLOR = "#ededed";
11893
+ const BOTTOM_BORDER_SHADOW = `inset 0 -1px 0 0 ${CELL_BORDER_COLOR}`;
11894
+ function buildAddColumnShadowCss() {
11895
+ return { "&::after": {
11896
+ content: "\"\"",
11897
+ position: "absolute",
11898
+ top: 0,
11899
+ bottom: 0,
11900
+ width: "8px",
11901
+ left: "-8px",
11902
+ pointerEvents: "none",
11903
+ transition: "opacity 0.15s",
11904
+ opacity: "var(--shadow-right-opacity, 0)",
11905
+ boxShadow: "inset -8px 0 12px -8px rgba(0,0,0,0.1)"
11906
+ } };
11907
+ }
11908
+ function adjustRightOffsetCss(rightCss, addColOffset) {
11909
+ if (addColOffset <= 0) return rightCss;
11910
+ if (rightCss === "0px") return `${addColOffset}px`;
11911
+ return `calc(${rightCss} + ${addColOffset}px)`;
11912
+ }
11913
+
11701
11914
  //#endregion
11702
11915
  //#region src/table-editor/Table/ui/ResizeHandle.tsx
11703
11916
  const MIN_COLUMN_WIDTH = 40;
@@ -11724,7 +11937,7 @@ const ResizeHandle = (0, mobx_react_lite.observer)(({ field, columnsModel }) =>
11724
11937
  onMouseDown: (0, react.useCallback)((e) => {
11725
11938
  e.preventDefault();
11726
11939
  e.stopPropagation();
11727
- const currentWidth = columnsModel.getColumnWidth(field) ?? 150;
11940
+ const currentWidth = columnsModel.resolveColumnWidth(field);
11728
11941
  startXRef.current = e.clientX;
11729
11942
  startWidthRef.current = currentWidth;
11730
11943
  setIsResizing(true);
@@ -12083,7 +12296,8 @@ const ColumnHeaderMenu = (0, mobx_react_lite.observer)(({ column, columnsModel,
12083
12296
 
12084
12297
  //#endregion
12085
12298
  //#region src/table-editor/Table/ui/Header/ColumnHeader.tsx
12086
- function buildHeaderShadowCss(position, showShadow) {
12299
+ function buildHeaderShadowCss(position) {
12300
+ const cssVar = position.side === "left" ? "var(--shadow-left-opacity, 0)" : "var(--shadow-right-opacity, 0)";
12087
12301
  return { "&::after": {
12088
12302
  content: "\"\"",
12089
12303
  position: "absolute",
@@ -12092,7 +12306,7 @@ function buildHeaderShadowCss(position, showShadow) {
12092
12306
  width: "8px",
12093
12307
  pointerEvents: "none",
12094
12308
  transition: "opacity 0.15s",
12095
- opacity: showShadow ? 1 : 0,
12309
+ opacity: cssVar,
12096
12310
  ...position.side === "left" ? {
12097
12311
  right: "-8px",
12098
12312
  boxShadow: "inset 8px 0 12px -8px rgba(0,0,0,0.1)"
@@ -12102,22 +12316,19 @@ function buildHeaderShadowCss(position, showShadow) {
12102
12316
  }
12103
12317
  } };
12104
12318
  }
12105
- const BOTTOM_BORDER_SHADOW$2 = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12106
12319
  function getHeaderBoxShadow(stickyPosition) {
12107
- if (!stickyPosition) return BOTTOM_BORDER_SHADOW$2;
12108
- return `${BOTTOM_BORDER_SHADOW$2}, ${stickyPosition.side === "left" ? "inset -1px 0 0 0 var(--chakra-colors-gray-100)" : "inset 1px 0 0 0 var(--chakra-colors-gray-100)"}`;
12320
+ if (!stickyPosition) return BOTTOM_BORDER_SHADOW;
12321
+ return `${BOTTOM_BORDER_SHADOW}, ${stickyPosition.side === "left" ? `inset -1px 0 0 0 ${CELL_BORDER_COLOR}` : `inset 1px 0 0 0 ${CELL_BORDER_COLOR}`}`;
12109
12322
  }
12110
- const ColumnHeader = (0, mobx_react_lite.observer)(({ column, columnsModel, sortModel, filterModel, onCopyPath, stickyPosition, showLeftShadow, showRightShadow }) => {
12323
+ const ColumnHeader = (0, mobx_react_lite.observer)(({ column, columnsModel, sortModel, filterModel, onCopyPath, stickyPosition }) => {
12111
12324
  const [isMenuOpen, setIsMenuOpen] = (0, react.useState)(false);
12112
- const width = columnsModel.getColumnWidth(column.field);
12113
- const w = width ? `${width}px` : "150px";
12325
+ const w = columnsModel.columnWidthCssVar(column.field);
12114
12326
  const isSticky = Boolean(stickyPosition);
12115
- const showShadow = stickyPosition?.isBoundary ? stickyPosition.side === "left" && Boolean(showLeftShadow) || stickyPosition.side === "right" && Boolean(showRightShadow) : false;
12116
12327
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
12117
12328
  as: "th",
12118
12329
  position: isSticky ? "sticky" : "relative",
12119
- left: stickyPosition?.side === "left" ? `${stickyPosition.offset}px` : void 0,
12120
- right: stickyPosition?.side === "right" ? `${stickyPosition.offset}px` : void 0,
12330
+ left: stickyPosition?.side === "left" ? stickyPosition.offsetCss : void 0,
12331
+ right: stickyPosition?.side === "right" ? stickyPosition.offsetCss : void 0,
12121
12332
  zIndex: isSticky ? 2 : void 0,
12122
12333
  width: w,
12123
12334
  minWidth: w,
@@ -12128,7 +12339,7 @@ const ColumnHeader = (0, mobx_react_lite.observer)(({ column, columnsModel, sort
12128
12339
  textAlign: "left",
12129
12340
  fontWeight: "normal",
12130
12341
  p: 0,
12131
- css: stickyPosition?.isBoundary ? buildHeaderShadowCss(stickyPosition, showShadow) : void 0,
12342
+ css: stickyPosition?.isBoundary ? buildHeaderShadowCss(stickyPosition) : void 0,
12132
12343
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Menu.Root, {
12133
12344
  positioning: { placement: "bottom-end" },
12134
12345
  lazyMount: true,
@@ -12224,6 +12435,7 @@ const AddColumnButton = (0, mobx_react_lite.observer)(({ columnsModel }) => {
12224
12435
  bg: "gray.100",
12225
12436
  color: "gray.600"
12226
12437
  },
12438
+ focusRing: "none",
12227
12439
  "data-testid": "add-column-button",
12228
12440
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_icons_pi.PiPlus, {})
12229
12441
  })
@@ -12269,8 +12481,7 @@ const AddColumnButton = (0, mobx_react_lite.observer)(({ columnsModel }) => {
12269
12481
  //#region src/table-editor/Table/ui/HeaderRow.tsx
12270
12482
  const SELECTION_COLUMN_WIDTH$1 = 40;
12271
12483
  const ADD_COLUMN_BUTTON_WIDTH$1 = 40;
12272
- const BOTTOM_BORDER_SHADOW$1 = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12273
- const HeaderRow = (0, mobx_react_lite.observer)(({ columnsModel, sortModel, filterModel, onCopyPath, showSelection, scrollShadow }) => {
12484
+ const HeaderRow = (0, mobx_react_lite.observer)(({ columnsModel, sortModel, filterModel, onCopyPath, showSelection }) => {
12274
12485
  const selectionWidth = showSelection ? SELECTION_COLUMN_WIDTH$1 : 0;
12275
12486
  const addColumnStickyRight = columnsModel.hasHiddenColumns;
12276
12487
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
@@ -12287,7 +12498,7 @@ const HeaderRow = (0, mobx_react_lite.observer)(({ columnsModel, sortModel, filt
12287
12498
  position: "sticky",
12288
12499
  left: 0,
12289
12500
  zIndex: 2,
12290
- boxShadow: BOTTOM_BORDER_SHADOW$1
12501
+ boxShadow: BOTTOM_BORDER_SHADOW
12291
12502
  }),
12292
12503
  columnsModel.visibleColumns.map((col) => {
12293
12504
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColumnHeader, {
@@ -12296,42 +12507,42 @@ const HeaderRow = (0, mobx_react_lite.observer)(({ columnsModel, sortModel, filt
12296
12507
  sortModel,
12297
12508
  filterModel,
12298
12509
  onCopyPath,
12299
- stickyPosition: getStickyPosition(col.field, columnsModel, selectionWidth, addColumnStickyRight),
12300
- showLeftShadow: scrollShadow?.showLeftShadow,
12301
- showRightShadow: scrollShadow?.showRightShadow
12510
+ stickyPosition: getStickyPosition(col.field, columnsModel, selectionWidth, addColumnStickyRight)
12302
12511
  }, col.field);
12303
12512
  }),
12304
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12513
+ addColumnStickyRight ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12305
12514
  as: "th",
12306
12515
  width: "100%",
12516
+ minWidth: `${ADD_COLUMN_BUTTON_WIDTH$1}px`,
12307
12517
  bg: "white",
12308
12518
  p: 0,
12309
- boxShadow: BOTTOM_BORDER_SHADOW$1
12310
- }),
12311
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12519
+ position: "sticky",
12520
+ right: 0,
12521
+ zIndex: 2,
12522
+ boxShadow: BOTTOM_BORDER_SHADOW,
12523
+ css: columnsModel.pinnedRightCount === 0 ? buildAddColumnShadowCss() : void 0,
12524
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddColumnButton, { columnsModel })
12525
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12312
12526
  as: "th",
12527
+ width: "100%",
12313
12528
  bg: "white",
12314
12529
  p: 0,
12315
- position: addColumnStickyRight ? "sticky" : void 0,
12316
- right: addColumnStickyRight ? 0 : void 0,
12317
- zIndex: addColumnStickyRight ? 2 : void 0,
12318
- boxShadow: BOTTOM_BORDER_SHADOW$1,
12319
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddColumnButton, { columnsModel })
12530
+ boxShadow: BOTTOM_BORDER_SHADOW
12320
12531
  })
12321
12532
  ]
12322
12533
  });
12323
12534
  });
12324
12535
  function getStickyPosition(field, columnsModel, selectionWidth, addColumnStickyRight) {
12325
- const leftOffset = columnsModel.getColumnStickyLeft(field, selectionWidth);
12326
- if (leftOffset !== void 0) return {
12536
+ const leftCss = columnsModel.getColumnStickyLeftCss(field, selectionWidth);
12537
+ if (leftCss !== void 0) return {
12327
12538
  side: "left",
12328
- offset: leftOffset,
12539
+ offsetCss: leftCss,
12329
12540
  isBoundary: columnsModel.isStickyLeftBoundary(field)
12330
12541
  };
12331
- const rightBase = columnsModel.getColumnStickyRight(field);
12332
- if (rightBase !== void 0) return {
12542
+ const rightCss = columnsModel.getColumnStickyRightCss(field);
12543
+ if (rightCss !== void 0) return {
12333
12544
  side: "right",
12334
- offset: rightBase + (addColumnStickyRight ? ADD_COLUMN_BUTTON_WIDTH$1 : 0),
12545
+ offsetCss: adjustRightOffsetCss(rightCss, addColumnStickyRight ? ADD_COLUMN_BUTTON_WIDTH$1 : 0),
12335
12546
  isBoundary: columnsModel.isStickyRightBoundary(field)
12336
12547
  };
12337
12548
  }
@@ -12466,7 +12677,7 @@ const SelectionCheckboxCell = ({ rowId, isSelected, onToggleSelection }) => {
12466
12677
  left: 0,
12467
12678
  zIndex: 1,
12468
12679
  bg: "white",
12469
- boxShadow: "inset 0 -1px 0 0 var(--chakra-colors-gray-100), inset -1px 0 0 0 var(--chakra-colors-gray-100)",
12680
+ boxShadow: `inset 0 -1px 0 0 ${CELL_BORDER_COLOR}, inset -1px 0 0 0 ${CELL_BORDER_COLOR}`,
12470
12681
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Flex, {
12471
12682
  alignItems: "center",
12472
12683
  justifyContent: "center",
@@ -12486,7 +12697,8 @@ const SelectionCheckboxCell = ({ rowId, isSelected, onToggleSelection }) => {
12486
12697
  //#region src/table-editor/Table/ui/DataRow.tsx
12487
12698
  const SELECTION_COLUMN_WIDTH = 40;
12488
12699
  const ADD_COLUMN_BUTTON_WIDTH = 40;
12489
- function buildShadowCss(side, showShadow) {
12700
+ function buildShadowCss(side) {
12701
+ const cssVar = side === "left" ? "var(--shadow-left-opacity, 0)" : "var(--shadow-right-opacity, 0)";
12490
12702
  return { "&::after": {
12491
12703
  content: "\"\"",
12492
12704
  position: "absolute",
@@ -12495,7 +12707,7 @@ function buildShadowCss(side, showShadow) {
12495
12707
  width: "8px",
12496
12708
  pointerEvents: "none",
12497
12709
  transition: "opacity 0.15s",
12498
- opacity: showShadow ? 1 : 0,
12710
+ opacity: cssVar,
12499
12711
  ...side === "left" ? {
12500
12712
  right: "-8px",
12501
12713
  boxShadow: "inset 8px 0 12px -8px rgba(0,0,0,0.1)"
@@ -12505,7 +12717,7 @@ function buildShadowCss(side, showShadow) {
12505
12717
  }
12506
12718
  } };
12507
12719
  }
12508
- function buildCellCss(isBoundary, boundarySide, showShadow, hasRowActions, isFirstColumn) {
12720
+ function buildCellCss(isBoundary, boundarySide, hasRowActions, isFirstColumn) {
12509
12721
  const needsHover = isFirstColumn && hasRowActions;
12510
12722
  const hoverCss = needsHover ? {
12511
12723
  "& .row-action-buttons": { opacity: 0 },
@@ -12516,34 +12728,33 @@ function buildCellCss(isBoundary, boundarySide, showShadow, hasRowActions, isFir
12516
12728
  } : {};
12517
12729
  if (isBoundary) return {
12518
12730
  ...hoverCss,
12519
- ...buildShadowCss(boundarySide, showShadow)
12731
+ ...buildShadowCss(boundarySide)
12520
12732
  };
12521
12733
  if (needsHover) return hoverCss;
12522
12734
  }
12523
- function computeStickyProps(col, columnsModel, selectionWidth, addColOffset, showLeftShadow, showRightShadow) {
12524
- const leftOffset = columnsModel.getColumnStickyLeft(col.field, selectionWidth);
12525
- const rightBase = columnsModel.getColumnStickyRight(col.field);
12526
- const isStickyLeft = leftOffset !== void 0;
12527
- const isStickyRight = rightBase !== void 0;
12735
+ function computeStickyProps(col, columnsModel, selectionWidth, addColOffset) {
12736
+ const leftCss = columnsModel.getColumnStickyLeftCss(col.field, selectionWidth);
12737
+ const rightCss = columnsModel.getColumnStickyRightCss(col.field);
12738
+ const isStickyLeft = leftCss !== void 0;
12739
+ const isStickyRight = rightCss !== void 0;
12528
12740
  const isSticky = isStickyLeft || isStickyRight;
12529
12741
  const isLeftBoundary = columnsModel.isStickyLeftBoundary(col.field);
12530
12742
  const isRightBoundary = columnsModel.isStickyRightBoundary(col.field);
12743
+ const finalRightCss = isStickyRight && rightCss !== void 0 ? adjustRightOffsetCss(rightCss, addColOffset) : rightCss;
12531
12744
  return {
12532
12745
  isSticky,
12533
12746
  isBoundary: isLeftBoundary || isRightBoundary,
12534
12747
  boundarySide: isStickyLeft ? "left" : "right",
12535
- showShadow: isLeftBoundary && Boolean(showLeftShadow) || isRightBoundary && Boolean(showRightShadow),
12536
- leftOffset: isStickyLeft ? leftOffset : void 0,
12537
- rightOffset: isStickyRight ? rightBase + addColOffset : void 0,
12538
- colWidth: isSticky ? `${columnsModel.resolveColumnWidth(col.field)}px` : void 0
12748
+ leftCss: isStickyLeft ? leftCss : void 0,
12749
+ rightCss: isStickyRight ? finalRightCss : void 0,
12750
+ colWidth: isSticky ? columnsModel.columnWidthCssVar(col.field) : void 0
12539
12751
  };
12540
12752
  }
12541
- const BOTTOM_BORDER_SHADOW = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12542
12753
  function getCellBoxShadow(isSticky, side) {
12543
12754
  if (!isSticky) return BOTTOM_BORDER_SHADOW;
12544
- return `${BOTTOM_BORDER_SHADOW}, ${side === "left" ? "inset -1px 0 0 0 var(--chakra-colors-gray-100)" : "inset 1px 0 0 0 var(--chakra-colors-gray-100)"}`;
12755
+ return `${BOTTOM_BORDER_SHADOW}, ${side === "left" ? `inset -1px 0 0 0 ${CELL_BORDER_COLOR}` : `inset 1px 0 0 0 ${CELL_BORDER_COLOR}`}`;
12545
12756
  }
12546
- const DataRow = (0, mobx_react_lite.observer)(({ row, columnsModel, showSelection, scrollShadow, onSearchForeignKey, onUploadFile, onOpenFile, onOpenRow, onSelectRow, onDuplicateRow, onDeleteRow }) => {
12757
+ const DataRow = (0, mobx_react_lite.observer)(({ row, columnsModel, showSelection, onSearchForeignKey, onUploadFile, onOpenFile, onOpenRow, onSelectRow, onDuplicateRow, onDeleteRow }) => {
12547
12758
  const hasRowActions = Boolean(onOpenRow || onSelectRow || onDuplicateRow || onDeleteRow);
12548
12759
  const selectionWidth = showSelection ? SELECTION_COLUMN_WIDTH : 0;
12549
12760
  const addColumnStickyRight = columnsModel.hasHiddenColumns;
@@ -12558,7 +12769,7 @@ const DataRow = (0, mobx_react_lite.observer)(({ row, columnsModel, showSelectio
12558
12769
  const cellVM = row.getCellVM(col);
12559
12770
  const isFirstColumn = index === 0;
12560
12771
  const showOverlay = isFirstColumn && hasRowActions && !cellVM.isEditing;
12561
- const sticky = computeStickyProps(col, columnsModel, selectionWidth, addColOffset, scrollShadow?.showLeftShadow, scrollShadow?.showRightShadow);
12772
+ const sticky = computeStickyProps(col, columnsModel, selectionWidth, addColOffset);
12562
12773
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
12563
12774
  as: "td",
12564
12775
  width: sticky.colWidth,
@@ -12566,15 +12777,15 @@ const DataRow = (0, mobx_react_lite.observer)(({ row, columnsModel, showSelectio
12566
12777
  maxWidth: sticky.isSticky ? sticky.colWidth : "0",
12567
12778
  overflow: sticky.isBoundary ? "visible" : "hidden",
12568
12779
  borderRight: sticky.isSticky ? void 0 : "1px solid",
12569
- borderColor: sticky.isSticky ? void 0 : "gray.100",
12780
+ borderColor: sticky.isSticky ? void 0 : CELL_BORDER_COLOR,
12570
12781
  p: 0,
12571
12782
  position: sticky.isSticky ? "sticky" : "relative",
12572
- left: sticky.leftOffset !== void 0 ? `${sticky.leftOffset}px` : void 0,
12573
- right: sticky.rightOffset !== void 0 ? `${sticky.rightOffset}px` : void 0,
12783
+ left: sticky.leftCss,
12784
+ right: sticky.rightCss,
12574
12785
  zIndex: sticky.isSticky ? 1 : void 0,
12575
12786
  bg: sticky.isSticky ? "white" : void 0,
12576
12787
  boxShadow: getCellBoxShadow(sticky.isSticky, sticky.boundarySide),
12577
- css: buildCellCss(sticky.isBoundary, sticky.boundarySide, sticky.showShadow, hasRowActions, isFirstColumn),
12788
+ css: buildCellCss(sticky.isBoundary, sticky.boundarySide, hasRowActions, isFirstColumn),
12578
12789
  children: [sticky.isBoundary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12579
12790
  overflow: "hidden",
12580
12791
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellRenderer, {
@@ -12597,23 +12808,22 @@ const DataRow = (0, mobx_react_lite.observer)(({ row, columnsModel, showSelectio
12597
12808
  })]
12598
12809
  }, col.field);
12599
12810
  }),
12600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12811
+ addColumnStickyRight ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12601
12812
  as: "td",
12602
12813
  width: "100%",
12603
- p: 0,
12604
- boxShadow: BOTTOM_BORDER_SHADOW
12605
- }),
12606
- addColumnStickyRight && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12607
- as: "td",
12608
- width: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12609
12814
  minWidth: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12610
- maxWidth: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12611
12815
  p: 0,
12612
12816
  position: "sticky",
12613
12817
  right: 0,
12614
12818
  zIndex: 1,
12615
12819
  bg: "white",
12616
- boxShadow: `${BOTTOM_BORDER_SHADOW}, inset 1px 0 0 0 var(--chakra-colors-gray-100)`
12820
+ boxShadow: BOTTOM_BORDER_SHADOW,
12821
+ css: columnsModel.pinnedRightCount === 0 ? buildAddColumnShadowCss() : void 0
12822
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_chakra_ui_react.Box, {
12823
+ as: "td",
12824
+ width: "100%",
12825
+ p: 0,
12826
+ boxShadow: BOTTOM_BORDER_SHADOW
12617
12827
  })
12618
12828
  ] });
12619
12829
  });
@@ -12739,19 +12949,61 @@ const TableRowComponent = ({ "data-index": index, context, style, children, ...p
12739
12949
 
12740
12950
  //#endregion
12741
12951
  //#region src/table-editor/Table/ui/hooks/useScrollShadow.ts
12952
+ const INITIAL_SHADOW = {
12953
+ left: false,
12954
+ right: false
12955
+ };
12742
12956
  var ScrollShadowModel = class {
12743
- showLeftShadow = false;
12744
- showRightShadow = false;
12745
- constructor() {
12746
- (0, mobx.makeAutoObservable)(this);
12957
+ _left = false;
12958
+ _right = false;
12959
+ _paused = false;
12960
+ _dirty = false;
12961
+ _onChange = null;
12962
+ get showLeftShadow() {
12963
+ return this._left;
12964
+ }
12965
+ get showRightShadow() {
12966
+ return this._right;
12967
+ }
12968
+ setOnChange(cb) {
12969
+ this._onChange = cb;
12970
+ }
12971
+ pause() {
12972
+ this._paused = true;
12973
+ this._dirty = false;
12974
+ }
12975
+ resume() {
12976
+ this._paused = false;
12977
+ if (this._dirty) {
12978
+ this._dirty = false;
12979
+ this._notify();
12980
+ }
12747
12981
  }
12748
12982
  update(left, right) {
12749
- this.showLeftShadow = left;
12750
- this.showRightShadow = right;
12983
+ if (this._left === left && this._right === right) return;
12984
+ this._left = left;
12985
+ this._right = right;
12986
+ if (this._paused) {
12987
+ this._dirty = true;
12988
+ return;
12989
+ }
12990
+ this._notify();
12751
12991
  }
12752
12992
  reset() {
12753
- this.showLeftShadow = false;
12754
- this.showRightShadow = false;
12993
+ if (!this._left && !this._right) return;
12994
+ this._left = false;
12995
+ this._right = false;
12996
+ if (this._paused) {
12997
+ this._dirty = true;
12998
+ return;
12999
+ }
13000
+ this._notify();
13001
+ }
13002
+ _notify() {
13003
+ if (this._onChange) this._onChange({
13004
+ left: this._left,
13005
+ right: this._right
13006
+ });
12755
13007
  }
12756
13008
  };
12757
13009
  function getScrollElement(target) {
@@ -12762,8 +13014,20 @@ function useScrollShadow() {
12762
13014
  const modelRef = (0, react.useRef)(null);
12763
13015
  if (!modelRef.current) modelRef.current = new ScrollShadowModel();
12764
13016
  const model = modelRef.current;
13017
+ const [shadow, setShadow] = (0, react.useState)(INITIAL_SHADOW);
13018
+ model.setOnChange(setShadow);
13019
+ (0, react.useEffect)(() => {
13020
+ return () => {
13021
+ model.setOnChange(null);
13022
+ };
13023
+ }, [model]);
13024
+ const shadowCssVars = {
13025
+ "--shadow-left-opacity": shadow.left ? "1" : "0",
13026
+ "--shadow-right-opacity": shadow.right ? "1" : "0"
13027
+ };
12765
13028
  const targetRef = (0, react.useRef)(null);
12766
13029
  const rafRef = (0, react.useRef)(0);
13030
+ const roRef = (0, react.useRef)(null);
12767
13031
  const update = (0, react.useCallback)(() => {
12768
13032
  const target = targetRef.current;
12769
13033
  if (!target) return;
@@ -12771,9 +13035,7 @@ function useScrollShadow() {
12771
13035
  if (!el) return;
12772
13036
  const scrollLeft = el.scrollLeft;
12773
13037
  const maxScroll = el.scrollWidth - el.clientWidth;
12774
- (0, mobx.runInAction)(() => {
12775
- model.update(scrollLeft > 0, maxScroll > 1 && scrollLeft < maxScroll - 1);
12776
- });
13038
+ model.update(scrollLeft > 0, maxScroll > 1 && scrollLeft < maxScroll - 1);
12777
13039
  }, [model]);
12778
13040
  const handleScroll = (0, react.useCallback)(() => {
12779
13041
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
@@ -12782,15 +13044,24 @@ function useScrollShadow() {
12782
13044
  const setScrollerRef = (0, react.useCallback)((el) => {
12783
13045
  const prev = targetRef.current;
12784
13046
  if (prev) prev.removeEventListener("scroll", handleScroll);
13047
+ if (roRef.current) {
13048
+ roRef.current.disconnect();
13049
+ roRef.current = null;
13050
+ }
12785
13051
  if (el) {
12786
13052
  targetRef.current = el;
12787
13053
  el.addEventListener("scroll", handleScroll, { passive: true });
12788
13054
  rafRef.current = requestAnimationFrame(update);
13055
+ const scrollEl = getScrollElement(el);
13056
+ if (scrollEl) {
13057
+ const ro = new ResizeObserver(handleScroll);
13058
+ const table = scrollEl.querySelector("table");
13059
+ if (table) ro.observe(table);
13060
+ roRef.current = ro;
13061
+ }
12789
13062
  } else {
12790
13063
  targetRef.current = null;
12791
- (0, mobx.runInAction)(() => {
12792
- model.reset();
12793
- });
13064
+ model.reset();
12794
13065
  }
12795
13066
  }, [
12796
13067
  handleScroll,
@@ -12801,11 +13072,13 @@ function useScrollShadow() {
12801
13072
  return () => {
12802
13073
  const prev = targetRef.current;
12803
13074
  if (prev) prev.removeEventListener("scroll", handleScroll);
13075
+ if (roRef.current) roRef.current.disconnect();
12804
13076
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
12805
13077
  };
12806
13078
  }, [handleScroll]);
12807
13079
  return {
12808
- model,
13080
+ shadowCssVars,
13081
+ shadowModel: model,
12809
13082
  setScrollerRef
12810
13083
  };
12811
13084
  }
@@ -12820,7 +13093,28 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
12820
13093
  const showSelection = selection.isSelectionMode;
12821
13094
  const allRowIds = rows.map((r) => r.rowId);
12822
13095
  const [deleteConfirm, setDeleteConfirm] = (0, react.useState)(null);
12823
- const { model: scrollShadow, setScrollerRef } = useScrollShadow();
13096
+ const { shadowCssVars, shadowModel, setScrollerRef } = useScrollShadow();
13097
+ const isResizing = columnsModel.isResizing;
13098
+ (0, react.useEffect)(() => {
13099
+ if (isResizing) shadowModel.pause();
13100
+ else shadowModel.resume();
13101
+ }, [isResizing, shadowModel]);
13102
+ const [wrapperEl, setWrapperEl] = (0, react.useState)(null);
13103
+ const wrapperRefCallback = (0, react.useCallback)((el) => {
13104
+ setWrapperEl(el);
13105
+ columnsModel.setWrapperElement(el);
13106
+ }, [columnsModel]);
13107
+ (0, react.useEffect)(() => {
13108
+ if (!useWindowScrollProp || !wrapperEl) return;
13109
+ setScrollerRef(wrapperEl);
13110
+ return () => {
13111
+ setScrollerRef(null);
13112
+ };
13113
+ }, [
13114
+ useWindowScrollProp,
13115
+ wrapperEl,
13116
+ setScrollerRef
13117
+ ]);
12824
13118
  const handleSelectRow = (0, react.useCallback)((rowId) => {
12825
13119
  selection.enterSelectionMode(rowId);
12826
13120
  }, [selection]);
@@ -12918,7 +13212,6 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
12918
13212
  row,
12919
13213
  columnsModel,
12920
13214
  showSelection,
12921
- scrollShadow,
12922
13215
  onSearchForeignKey,
12923
13216
  onUploadFile,
12924
13217
  onOpenFile,
@@ -12929,7 +13222,6 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
12929
13222
  }), [
12930
13223
  columnsModel,
12931
13224
  showSelection,
12932
- scrollShadow,
12933
13225
  onSearchForeignKey,
12934
13226
  onUploadFile,
12935
13227
  onOpenFile,
@@ -12946,18 +13238,16 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
12946
13238
  sortModel,
12947
13239
  filterModel,
12948
13240
  onCopyPath,
12949
- showSelection,
12950
- scrollShadow
13241
+ showSelection
12951
13242
  }), [
12952
13243
  columnsModel,
12953
13244
  sortModel,
12954
13245
  filterModel,
12955
13246
  onCopyPath,
12956
- showSelection,
12957
- scrollShadow
13247
+ showSelection
12958
13248
  ]);
12959
13249
  const virtuosoContext = (0, react.useMemo)(() => ({ rows }), [rows]);
12960
- const totalColumns = columnsModel.visibleColumns.length + (showSelection ? 4 : 3);
13250
+ const totalColumns = columnsModel.visibleColumns.length + (showSelection ? 2 : 1);
12961
13251
  const LoadingMoreFooter = (0, react.useCallback)(() => isLoadingMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tfoot", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
12962
13252
  colSpan: totalColumns,
12963
13253
  style: {
@@ -12984,10 +13274,17 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
12984
13274
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CellContextActionsContext.Provider, {
12985
13275
  value: cellFSM ? contextActions : null,
12986
13276
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
13277
+ ref: wrapperRefCallback,
12987
13278
  position: "relative",
12988
13279
  height: useWindowScrollProp ? void 0 : "100%",
13280
+ overflowX: "auto",
13281
+ overflowY: useWindowScrollProp ? "clip" : void 0,
12989
13282
  "data-testid": "table-widget",
12990
13283
  onKeyDown: handleKeyDown,
13284
+ style: {
13285
+ ...columnsModel.columnWidthCssVars,
13286
+ ...shadowCssVars
13287
+ },
12991
13288
  children: [rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {
12992
13289
  style: {
12993
13290
  width: "max-content",
@@ -13022,7 +13319,7 @@ const TableWidget = (0, mobx_react_lite.observer)(({ rows, columnsModel, cellFSM
13022
13319
  fixedHeaderContent,
13023
13320
  itemContent,
13024
13321
  components: tableComponents,
13025
- scrollerRef: setScrollerRef
13322
+ scrollerRef: useWindowScrollProp ? void 0 : setScrollerRef
13026
13323
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectionToolbar, {
13027
13324
  selection,
13028
13325
  allRowIds,
@@ -13315,6 +13612,7 @@ var TableEditorCore = class {
13315
13612
  this.sorts.setOnApply((_sorts) => this._handleSortApply());
13316
13613
  this.viewBadge.setOnSave(() => this._handleViewSave());
13317
13614
  this.viewBadge.setOnRevert(() => this._handleViewRevert());
13615
+ this.cellFSM.setOnReadonlyEditAttempt(this._callbacks.onReadonlyEditAttempt ?? null);
13318
13616
  (0, mobx.makeAutoObservable)(this, {}, { autoBind: true });
13319
13617
  queueMicrotask(() => void this._bootstrap());
13320
13618
  }
@@ -13342,17 +13640,14 @@ var TableEditorCore = class {
13342
13640
  getViewState() {
13343
13641
  return {
13344
13642
  columns: this.columns.serializeToViewColumns(),
13345
- filters: this.filters.hasActiveFilters ? JSON.stringify(this.filters.serializeRootGroup()) : null,
13643
+ filters: null,
13346
13644
  sorts: this.sorts.serializeToViewSorts(),
13347
- search: this.search.debouncedQuery
13645
+ search: ""
13348
13646
  };
13349
13647
  }
13350
13648
  applyViewState(state) {
13351
13649
  this.columns.applyViewColumns(state.columns);
13352
13650
  this.sorts.applyViewSorts(state.sorts);
13353
- if (state.filters) this.filters.applySnapshot(state.filters);
13354
- else this.filters.clearAll();
13355
- this.search.setQuery(state.search);
13356
13651
  }
13357
13652
  async loadMore() {
13358
13653
  if (!this._hasNextPage || this._isLoadingMore) return;
@@ -13495,12 +13790,9 @@ var TableEditorCore = class {
13495
13790
  this.cellFSM.updateNavigationContext(this.columns.visibleColumns.map((c) => c.field), this.cellFSM.rowIds);
13496
13791
  this._checkViewChanges();
13497
13792
  }
13498
- _handleFilterChange() {
13499
- this._checkViewChanges();
13500
- }
13793
+ _handleFilterChange() {}
13501
13794
  _handleFilterApply() {
13502
13795
  this._reloadRows();
13503
- this._checkViewChanges();
13504
13796
  }
13505
13797
  _handleSortChange() {
13506
13798
  this._checkViewChanges();
@@ -13511,7 +13803,6 @@ var TableEditorCore = class {
13511
13803
  }
13512
13804
  _handleSearch(_query) {
13513
13805
  this._reloadRows();
13514
- this._checkViewChanges();
13515
13806
  }
13516
13807
  async _handleViewSave() {
13517
13808
  try {
@@ -13758,14 +14049,21 @@ const TableEditor = (0, mobx_react_lite.observer)(({ viewModel, useWindowScroll
13758
14049
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Box, {
13759
14050
  display: "flex",
13760
14051
  flexDirection: "column",
13761
- height: "100%",
14052
+ height: useWindowScroll ? void 0 : "100%",
14053
+ flex: useWindowScroll ? 1 : void 0,
13762
14054
  children: [
13763
14055
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_chakra_ui_react.Flex, {
13764
14056
  px: 3,
13765
- pt: 2,
13766
- mb: "48px",
14057
+ pt: "32px",
14058
+ pb: "48px",
13767
14059
  alignItems: "center",
13768
14060
  justifyContent: "space-between",
14061
+ ...useWindowScroll && {
14062
+ position: "sticky",
14063
+ top: 0,
14064
+ zIndex: 3,
14065
+ bg: "white"
14066
+ },
13769
14067
  children: [breadcrumbs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Breadcrumbs, {
13770
14068
  segments: breadcrumbs,
13771
14069
  highlightLast: false,
@@ -13819,6 +14117,12 @@ const TableEditor = (0, mobx_react_lite.observer)(({ viewModel, useWindowScroll
13819
14117
  px: 3,
13820
14118
  py: 2,
13821
14119
  justifyContent: "space-between",
14120
+ ...useWindowScroll && {
14121
+ position: "sticky",
14122
+ bottom: 0,
14123
+ bg: "white",
14124
+ zIndex: 3
14125
+ },
13822
14126
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RowCountWidget, { model: viewModel.rowCount }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewSettingsBadge, { model: viewModel.viewBadge })]
13823
14127
  })
13824
14128
  ]