@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as mobx from "mobx";
2
- import { comparer, computed, makeAutoObservable, observable, reaction, runInAction } from "mobx";
2
+ import { comparer, computed, makeAutoObservable, observable, reaction, runInAction, untracked } from "mobx";
3
3
  import { FIELD_NAME_ERROR_MESSAGE, SchemaParser, SystemSchemaIds, arr, createRowModel, createTableModel, fileSchema, generateDefaultValue, isForeignKeyValueNode, isValidFieldName, jsonPointerToSimplePath, num, obj, rowCreatedAtSchema, rowCreatedIdSchema, rowHashSchema, rowIdSchema, rowPublishedAtSchema, rowSchemaHashSchema, rowUpdatedAtSchema, rowVersionIdSchema, str, validateFormulaAgainstSchema } from "@revisium/schema-toolkit";
4
4
  import { createMobxProvider, setReactivityProvider } from "@revisium/schema-toolkit/core";
5
5
  import { ActionBar, Badge, Box, Button, Checkbox, Flex, HStack, HoverCard, Icon, IconButton, Image, Input, Kbd, Menu, Popover, Portal, SegmentGroup, Spinner, Text, Textarea, Tooltip as Tooltip$1, VStack } from "@chakra-ui/react";
@@ -2918,6 +2918,7 @@ const PlusButton = ({ onClick, tooltip, disabled, dataTestId }) => {
2918
2918
  variant: "ghost",
2919
2919
  color: "gray.400",
2920
2920
  disabled,
2921
+ focusRing: "none",
2921
2922
  _hover: {
2922
2923
  bg: "gray.100",
2923
2924
  color: "gray.600"
@@ -7005,12 +7006,32 @@ function selectDefaultColumns(columns, maxVisible = 4) {
7005
7006
  //#endregion
7006
7007
  //#region src/table-editor/Columns/model/ColumnsModel.ts
7007
7008
  const DEFAULT_COLUMN_WIDTH = 150;
7009
+ const DEFAULT_ID_COLUMN_WIDTH = 240;
7010
+ function fieldToCssVar(field) {
7011
+ return `--cw-${field.replaceAll(".", "-")}`;
7012
+ }
7013
+ function isValidPinZoneOrder(fields, pins) {
7014
+ let zone = "left";
7015
+ for (const field of fields) {
7016
+ const pin = pins.get(field);
7017
+ if (zone === "left") {
7018
+ if (pin === "right") zone = "right";
7019
+ else if (pin === void 0) zone = "none";
7020
+ } else if (zone === "none") {
7021
+ if (pin === "left") return false;
7022
+ if (pin === "right") zone = "right";
7023
+ } else if (pin !== "right") return false;
7024
+ }
7025
+ return true;
7026
+ }
7008
7027
  var ColumnsModel = class {
7009
7028
  _allColumns = [];
7010
7029
  _visibleFields = [];
7011
7030
  _columnWidths = observable.map();
7012
7031
  _pinnedColumns = observable.map();
7013
7032
  _onChange = null;
7033
+ _isResizing = false;
7034
+ _wrapperElement = null;
7014
7035
  constructor() {
7015
7036
  makeAutoObservable(this, {}, { autoBind: true });
7016
7037
  }
@@ -7169,17 +7190,36 @@ var ColumnsModel = class {
7169
7190
  this._visibleFields = fields;
7170
7191
  this._notifyChange();
7171
7192
  }
7193
+ get isResizing() {
7194
+ return this._isResizing;
7195
+ }
7196
+ setWrapperElement(el) {
7197
+ this._wrapperElement = el;
7198
+ }
7172
7199
  setColumnWidth(field, width) {
7200
+ this._isResizing = true;
7173
7201
  this._columnWidths.set(field, width);
7202
+ if (this._wrapperElement) this._wrapperElement.style.setProperty(fieldToCssVar(field), `${width}px`);
7174
7203
  }
7175
7204
  commitColumnWidth() {
7205
+ this._isResizing = false;
7176
7206
  this._notifyChange();
7177
7207
  }
7178
7208
  getColumnWidth(field) {
7179
7209
  return this._columnWidths.get(field);
7180
7210
  }
7181
7211
  resolveColumnWidth(field) {
7182
- return this._columnWidths.get(field) ?? DEFAULT_COLUMN_WIDTH;
7212
+ return this._resolveWidthUntracked(field);
7213
+ }
7214
+ get columnWidthCssVars() {
7215
+ if (this._isResizing) return this._buildCssVarsUntracked();
7216
+ return this._buildCssVarsTracked();
7217
+ }
7218
+ getWidthCssVarsDuringResize() {
7219
+ return this._buildCssVarsUntracked();
7220
+ }
7221
+ columnWidthCssVar(field) {
7222
+ return `var(${fieldToCssVar(field)}, ${field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH}px)`;
7183
7223
  }
7184
7224
  getPinState(field) {
7185
7225
  return this._pinnedColumns.get(field);
@@ -7237,7 +7277,7 @@ var ColumnsModel = class {
7237
7277
  let offset = selectionWidth;
7238
7278
  for (const f of this._visibleFields) {
7239
7279
  if (f === field) break;
7240
- if (this._pinnedColumns.get(f) === "left") offset += this.resolveColumnWidth(f);
7280
+ if (this._pinnedColumns.get(f) === "left") offset += this._resolveWidthUntracked(f);
7241
7281
  }
7242
7282
  return offset;
7243
7283
  }
@@ -7250,10 +7290,37 @@ var ColumnsModel = class {
7250
7290
  found = true;
7251
7291
  continue;
7252
7292
  }
7253
- if (found && this._pinnedColumns.get(f) === "right") offset += this.resolveColumnWidth(f);
7293
+ if (found && this._pinnedColumns.get(f) === "right") offset += this._resolveWidthUntracked(f);
7254
7294
  }
7255
7295
  return offset;
7256
7296
  }
7297
+ getColumnStickyLeftCss(field, selectionWidth) {
7298
+ if (this._pinnedColumns.get(field) !== "left") return;
7299
+ const parts = [];
7300
+ if (selectionWidth > 0) parts.push(`${selectionWidth}px`);
7301
+ for (const f of this._visibleFields) {
7302
+ if (f === field) break;
7303
+ if (this._pinnedColumns.get(f) === "left") parts.push(this.columnWidthCssVar(f));
7304
+ }
7305
+ if (parts.length === 0) return "0px";
7306
+ if (parts.length === 1) return parts[0];
7307
+ return `calc(${parts.join(" + ")})`;
7308
+ }
7309
+ getColumnStickyRightCss(field) {
7310
+ if (this._pinnedColumns.get(field) !== "right") return;
7311
+ const parts = [];
7312
+ let found = false;
7313
+ for (const f of this._visibleFields) {
7314
+ if (f === field) {
7315
+ found = true;
7316
+ continue;
7317
+ }
7318
+ if (found && this._pinnedColumns.get(f) === "right") parts.push(this.columnWidthCssVar(f));
7319
+ }
7320
+ if (parts.length === 0) return "0px";
7321
+ if (parts.length === 1) return parts[0];
7322
+ return `calc(${parts.join(" + ")})`;
7323
+ }
7257
7324
  isStickyLeftBoundary(field) {
7258
7325
  if (this._pinnedColumns.get(field) !== "left") return false;
7259
7326
  const index = this.getColumnIndex(field);
@@ -7289,16 +7356,20 @@ var ColumnsModel = class {
7289
7356
  applyViewColumns(viewColumns) {
7290
7357
  const lookup = this._columnLookup;
7291
7358
  const fields = [];
7292
- this._columnWidths.clear();
7293
- this._pinnedColumns.clear();
7359
+ const widths = /* @__PURE__ */ new Map();
7360
+ const pins = /* @__PURE__ */ new Map();
7294
7361
  for (const vc of viewColumns) {
7295
7362
  const field = this._fromViewField(vc.field);
7296
7363
  if (lookup.has(field)) {
7297
7364
  fields.push(field);
7298
- if (vc.width !== void 0) this._columnWidths.set(field, vc.width);
7299
- if (vc.pinned !== void 0) this._pinnedColumns.set(field, vc.pinned);
7365
+ if (vc.width !== void 0) widths.set(field, vc.width);
7366
+ if (vc.pinned !== void 0) pins.set(field, vc.pinned);
7300
7367
  }
7301
7368
  }
7369
+ this._columnWidths.clear();
7370
+ this._pinnedColumns.clear();
7371
+ for (const [field, width] of widths) this._columnWidths.set(field, width);
7372
+ if (isValidPinZoneOrder(fields, pins)) for (const [field, side] of pins) this._pinnedColumns.set(field, side);
7302
7373
  if (fields.length === 0) this._visibleFields = selectDefaultColumns(this._allColumns).map((col) => col.field);
7303
7374
  else this._visibleFields = fields;
7304
7375
  }
@@ -7368,6 +7439,27 @@ var ColumnsModel = class {
7368
7439
  if (viewField.startsWith("data.")) return viewField.slice(5);
7369
7440
  return viewField;
7370
7441
  }
7442
+ _resolveWidthUntracked(field) {
7443
+ return untracked(() => this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH));
7444
+ }
7445
+ _buildCssVarsTracked() {
7446
+ const vars = {};
7447
+ for (const field of this._visibleFields) {
7448
+ const width = this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH);
7449
+ vars[fieldToCssVar(field)] = `${width}px`;
7450
+ }
7451
+ return vars;
7452
+ }
7453
+ _buildCssVarsUntracked() {
7454
+ return untracked(() => {
7455
+ const vars = {};
7456
+ for (const field of this._visibleFields) {
7457
+ const width = this._columnWidths.get(field) ?? (field === "id" ? DEFAULT_ID_COLUMN_WIDTH : DEFAULT_COLUMN_WIDTH);
7458
+ vars[fieldToCssVar(field)] = `${width}px`;
7459
+ }
7460
+ return vars;
7461
+ });
7462
+ }
7371
7463
  _notifyChange() {
7372
7464
  if (this._onChange) this._onChange();
7373
7465
  }
@@ -9782,15 +9874,6 @@ function cellAt(ctx, colIndex, rowIndex) {
9782
9874
  field
9783
9875
  };
9784
9876
  }
9785
- function cellPosition(ctx, cell) {
9786
- const colIndex = ctx.columns.indexOf(cell.field);
9787
- const rowIndex = ctx.rowIds.indexOf(cell.rowId);
9788
- if (colIndex === -1 || rowIndex === -1) return null;
9789
- return {
9790
- colIndex,
9791
- rowIndex
9792
- };
9793
- }
9794
9877
  function createConfig() {
9795
9878
  return {
9796
9879
  initial: "idle",
@@ -9954,11 +10037,37 @@ function createConfig() {
9954
10037
 
9955
10038
  //#endregion
9956
10039
  //#region src/table-editor/Table/model/CellFSM.ts
10040
+ const READONLY_TOAST_THROTTLE_MS = 2e3;
10041
+ function buildIndexMap(arr) {
10042
+ const map = /* @__PURE__ */ new Map();
10043
+ arr.forEach((value, i) => {
10044
+ map.set(value, i);
10045
+ });
10046
+ return map;
10047
+ }
10048
+ function selectedRangeEquals(a, b) {
10049
+ if (a === b) return true;
10050
+ if (!a || !b) return false;
10051
+ return a.startCol === b.startCol && a.endCol === b.endCol && a.startRow === b.startRow && a.endRow === b.endRow;
10052
+ }
9957
10053
  var CellFSM = class {
9958
10054
  _fsm;
10055
+ _onReadonlyEditAttempt = null;
10056
+ _lastReadonlyToastAt = 0;
10057
+ _columnIndexMap = /* @__PURE__ */ new Map();
10058
+ _rowIndexMap = /* @__PURE__ */ new Map();
9959
10059
  constructor() {
9960
10060
  this._fsm = new ObservableFSM(createConfig());
9961
- makeAutoObservable(this, {}, { autoBind: true });
10061
+ makeAutoObservable(this, { selectedRange: computed({ equals: selectedRangeEquals }) }, { autoBind: true });
10062
+ }
10063
+ setOnReadonlyEditAttempt(cb) {
10064
+ this._onReadonlyEditAttempt = cb;
10065
+ }
10066
+ notifyReadonlyEditAttempt() {
10067
+ const now = Date.now();
10068
+ if (now - this._lastReadonlyToastAt < READONLY_TOAST_THROTTLE_MS) return;
10069
+ this._lastReadonlyToastAt = now;
10070
+ this._onReadonlyEditAttempt?.();
9962
10071
  }
9963
10072
  get state() {
9964
10073
  return this._fsm.state;
@@ -9981,8 +10090,14 @@ var CellFSM = class {
9981
10090
  get navigationVersion() {
9982
10091
  return this._fsm.context.navigationVersion;
9983
10092
  }
10093
+ get columnIndexMap() {
10094
+ return this._columnIndexMap;
10095
+ }
10096
+ get rowIndexMap() {
10097
+ return this._rowIndexMap;
10098
+ }
9984
10099
  get hasSelection() {
9985
- return this.getSelectedRange() !== null;
10100
+ return this.selectedRange !== null;
9986
10101
  }
9987
10102
  isCellFocused(rowId, field) {
9988
10103
  const cell = this._fsm.context.focusedCell;
@@ -9998,54 +10113,53 @@ var CellFSM = class {
9998
10113
  return this._fsm.matches("editing") && this.isCellFocused(rowId, field);
9999
10114
  }
10000
10115
  isCellInSelection(rowId, field) {
10001
- const range = this.getSelectedRange();
10116
+ const range = this.selectedRange;
10002
10117
  if (!range) return false;
10003
- const pos = this._getCellPosition(rowId, field);
10004
- if (!pos) return false;
10005
- return pos.colIndex >= range.startCol && pos.colIndex <= range.endCol && pos.rowIndex >= range.startRow && pos.rowIndex <= range.endRow;
10118
+ const colIndex = this._columnIndexMap.get(field);
10119
+ const rowIndex = this._rowIndexMap.get(rowId);
10120
+ if (colIndex === void 0 || rowIndex === void 0) return false;
10121
+ return colIndex >= range.startCol && colIndex <= range.endCol && rowIndex >= range.startRow && rowIndex <= range.endRow;
10006
10122
  }
10007
10123
  getCellSelectionEdges(rowId, field) {
10008
- const range = this.getSelectedRange();
10124
+ const range = this.selectedRange;
10009
10125
  if (!range) return null;
10010
- const pos = this._getCellPosition(rowId, field);
10011
- if (!pos) return null;
10012
- if (pos.colIndex < range.startCol || pos.colIndex > range.endCol || pos.rowIndex < range.startRow || pos.rowIndex > range.endRow) return null;
10126
+ const colIndex = this._columnIndexMap.get(field);
10127
+ const rowIndex = this._rowIndexMap.get(rowId);
10128
+ if (colIndex === void 0 || rowIndex === void 0) return null;
10129
+ if (colIndex < range.startCol || colIndex > range.endCol || rowIndex < range.startRow || rowIndex > range.endRow) return null;
10013
10130
  return {
10014
- top: pos.rowIndex === range.startRow,
10015
- bottom: pos.rowIndex === range.endRow,
10016
- left: pos.colIndex === range.startCol,
10017
- right: pos.colIndex === range.endCol
10131
+ top: rowIndex === range.startRow,
10132
+ bottom: rowIndex === range.endRow,
10133
+ left: colIndex === range.startCol,
10134
+ right: colIndex === range.endCol
10018
10135
  };
10019
10136
  }
10020
- _getCellPosition(rowId, field) {
10021
- const ctx = this._fsm.context;
10022
- const colIndex = ctx.columns.indexOf(field);
10023
- const rowIndex = ctx.rowIds.indexOf(rowId);
10024
- if (colIndex === -1 || rowIndex === -1) return null;
10025
- return {
10026
- colIndex,
10027
- rowIndex
10028
- };
10029
- }
10030
- getSelectedRange() {
10137
+ get selectedRange() {
10031
10138
  const ctx = this._fsm.context;
10032
10139
  if (!ctx.anchorCell || !ctx.focusedCell) return null;
10033
10140
  if (ctx.anchorCell.rowId === ctx.focusedCell.rowId && ctx.anchorCell.field === ctx.focusedCell.field) return null;
10034
- const anchorPos = cellPosition(ctx, ctx.anchorCell);
10035
- const focusPos = cellPosition(ctx, ctx.focusedCell);
10036
- if (!anchorPos || !focusPos) return null;
10141
+ const anchorCol = this._columnIndexMap.get(ctx.anchorCell.field);
10142
+ const anchorRow = this._rowIndexMap.get(ctx.anchorCell.rowId);
10143
+ const focusCol = this._columnIndexMap.get(ctx.focusedCell.field);
10144
+ const focusRow = this._rowIndexMap.get(ctx.focusedCell.rowId);
10145
+ if (anchorCol === void 0 || anchorRow === void 0 || focusCol === void 0 || focusRow === void 0) return null;
10037
10146
  return {
10038
- startCol: Math.min(anchorPos.colIndex, focusPos.colIndex),
10039
- endCol: Math.max(anchorPos.colIndex, focusPos.colIndex),
10040
- startRow: Math.min(anchorPos.rowIndex, focusPos.rowIndex),
10041
- endRow: Math.max(anchorPos.rowIndex, focusPos.rowIndex)
10147
+ startCol: Math.min(anchorCol, focusCol),
10148
+ endCol: Math.max(anchorCol, focusCol),
10149
+ startRow: Math.min(anchorRow, focusRow),
10150
+ endRow: Math.max(anchorRow, focusRow)
10042
10151
  };
10043
10152
  }
10153
+ getSelectedRange() {
10154
+ return this.selectedRange;
10155
+ }
10044
10156
  setNavigationContext(columns, rowIds) {
10045
10157
  Object.assign(this._fsm.context, {
10046
10158
  columns,
10047
10159
  rowIds
10048
10160
  });
10161
+ this._columnIndexMap = buildIndexMap(columns);
10162
+ this._rowIndexMap = buildIndexMap(rowIds);
10049
10163
  }
10050
10164
  updateNavigationContext(columns, rowIds) {
10051
10165
  const ctx = this._fsm.context;
@@ -10053,8 +10167,10 @@ var CellFSM = class {
10053
10167
  ctx.columns = columns;
10054
10168
  ctx.rowIds = rowIds;
10055
10169
  ctx.navigationVersion++;
10170
+ this._columnIndexMap = buildIndexMap(columns);
10171
+ this._rowIndexMap = buildIndexMap(rowIds);
10056
10172
  if (!focused) return;
10057
- if (columns.includes(focused.field) && rowIds.includes(focused.rowId)) ctx.anchorCell = null;
10173
+ if (this._columnIndexMap.has(focused.field) && this._rowIndexMap.has(focused.rowId)) ctx.anchorCell = null;
10058
10174
  else this.blur();
10059
10175
  }
10060
10176
  focusCell(cell) {
@@ -10338,6 +10454,9 @@ var CellVM = class {
10338
10454
  const newValue = node.getPlainValue();
10339
10455
  if (newValue !== previousValue) this._onCommit?.(this._rowId, this._column.field, newValue, previousValue);
10340
10456
  }
10457
+ notifyReadonlyEditAttempt() {
10458
+ this._cellFSM.notifyReadonlyEditAttempt();
10459
+ }
10341
10460
  blur() {
10342
10461
  this._cellFSM.blur();
10343
10462
  }
@@ -10709,6 +10828,242 @@ const CellContextMenu = observer(({ cell, onEditPointerDown }) => {
10709
10828
  }) }) });
10710
10829
  });
10711
10830
 
10831
+ //#endregion
10832
+ //#region src/table-editor/Table/ui/Cell/cellStyles.ts
10833
+ const SELECTION_BORDER_COLOR = "#3b82f6";
10834
+ function getCellState(cell) {
10835
+ if (cell.isReadOnly) {
10836
+ if (cell.isFocused && !cell.isInSelection) return "readonlyFocused";
10837
+ if (cell.isInSelection) return "selected";
10838
+ return "readonly";
10839
+ }
10840
+ if (cell.isEditing) return "editing";
10841
+ if (cell.isFocused && !cell.isInSelection) return "focused";
10842
+ if (cell.isInSelection) return "selected";
10843
+ return "display";
10844
+ }
10845
+ function isPrintableKey(e) {
10846
+ if (e.ctrlKey || e.metaKey || e.altKey) return false;
10847
+ return e.key.length === 1;
10848
+ }
10849
+
10850
+ //#endregion
10851
+ //#region src/table-editor/Table/ui/Cell/cellCss.ts
10852
+ const CLS = "cw";
10853
+ const CLS_DISPLAY = `${CLS} cw-display`;
10854
+ const CLS_READONLY = `${CLS} cw-readonly`;
10855
+ const CLS_FOCUSED = `${CLS} cw-focused`;
10856
+ const CLS_EDITING = `${CLS} cw-editing`;
10857
+ const CLS_READONLY_FOCUSED = `${CLS} cw-readonlyFocused`;
10858
+ const CLS_SELECTED = `${CLS} cw-selected`;
10859
+ const CLS_ANCHOR = "cw-anchor";
10860
+ const STATE_CLASS = {
10861
+ display: CLS_DISPLAY,
10862
+ readonly: CLS_READONLY,
10863
+ focused: CLS_FOCUSED,
10864
+ editing: CLS_EDITING,
10865
+ readonlyFocused: CLS_READONLY_FOCUSED,
10866
+ selected: CLS_SELECTED
10867
+ };
10868
+ const CELL_STYLE_ID = "cell-wrapper-styles";
10869
+ function ensureCellStyles() {
10870
+ if (typeof document === "undefined") return;
10871
+ if (document.getElementById(CELL_STYLE_ID)) return;
10872
+ const style = document.createElement("style");
10873
+ style.id = CELL_STYLE_ID;
10874
+ style.textContent = [
10875
+ ".cw{height:40px;padding:0 8px;position:relative;overflow:hidden;cursor:cell;box-shadow:var(--cw-shadow,none)}",
10876
+ ".cw:focus,.cw:focus-visible{outline:none;box-shadow:var(--cw-shadow,none)}",
10877
+ ".cw-display:hover,.cw-readonly:hover{background-color:var(--chakra-colors-gray-50);box-shadow:inset 0 -1px 0 0 #ededed}",
10878
+ ".cw-focused{background-color:var(--chakra-colors-blue-50)}",
10879
+ ".cw-focused::before,.cw-editing::before,.cw-readonlyFocused::before,.cw-anchor::before{content:\"\";position:absolute;inset:1px;border-radius:1px;pointer-events:none}",
10880
+ ".cw-focused::before{border:2px solid var(--chakra-colors-blue-400)}",
10881
+ ".cw-editing{cursor:text;background-color:white;z-index:1}",
10882
+ ".cw-editing::before{border:2px solid var(--chakra-colors-blue-500)}",
10883
+ ".cw-readonlyFocused{background-color:var(--chakra-colors-gray-50)}",
10884
+ ".cw-readonlyFocused::before{border:2px solid var(--chakra-colors-gray-400)}",
10885
+ ".cw-selected{background-color:var(--chakra-colors-blue-100);user-select:none}",
10886
+ ".cw-anchor::before{border:2px solid var(--chakra-colors-blue-400)}"
10887
+ ].join("");
10888
+ document.head.appendChild(style);
10889
+ }
10890
+ const INNER_STYLE = {
10891
+ display: "flex",
10892
+ alignItems: "center",
10893
+ height: "100%",
10894
+ width: "100%",
10895
+ minWidth: 0,
10896
+ overflow: "hidden"
10897
+ };
10898
+ function buildSelectionBoxShadow(edges) {
10899
+ const shadows = [];
10900
+ if (edges.top) shadows.push(`inset 0 2px 0 0 ${SELECTION_BORDER_COLOR}`);
10901
+ if (edges.bottom) shadows.push(`inset 0 -2px 0 0 ${SELECTION_BORDER_COLOR}`);
10902
+ if (edges.left) shadows.push(`inset 2px 0 0 0 ${SELECTION_BORDER_COLOR}`);
10903
+ if (edges.right) shadows.push(`inset -2px 0 0 0 ${SELECTION_BORDER_COLOR}`);
10904
+ return shadows.length > 0 ? shadows.join(", ") : null;
10905
+ }
10906
+
10907
+ //#endregion
10908
+ //#region src/table-editor/Table/ui/Cell/useCellContextMenu.ts
10909
+ let pendingContextMenu = null;
10910
+ function clearPendingContextMenu() {
10911
+ const pending = pendingContextMenu;
10912
+ pendingContextMenu = null;
10913
+ return pending;
10914
+ }
10915
+ function setPendingContextMenu(value) {
10916
+ pendingContextMenu = value;
10917
+ }
10918
+ const cellMenuRegistry = /* @__PURE__ */ new WeakMap();
10919
+ function useCellContextMenu(cell, cellRef, deferredEdit) {
10920
+ const [menuAnchor, setMenuAnchor] = useState(null);
10921
+ const menuOpen = menuAnchor !== null;
10922
+ const openContextMenuAt = useCallback((clientX, clientY) => {
10923
+ if (!cell.isFocused && !cell.isInSelection) cell.focus();
10924
+ setMenuAnchor(new DOMRect(clientX, clientY, 0, 0));
10925
+ }, [cell]);
10926
+ useEffect(() => {
10927
+ const el = cellRef.current;
10928
+ if (el) cellMenuRegistry.set(el, openContextMenuAt);
10929
+ return () => {
10930
+ if (el) cellMenuRegistry.delete(el);
10931
+ };
10932
+ }, [cellRef, openContextMenuAt]);
10933
+ const menuCloseRef = useRef(null);
10934
+ const handleMenuClose = useCallback(() => {
10935
+ const pending = clearPendingContextMenu();
10936
+ const didTriggerEdit = deferredEdit.triggerIfRequested();
10937
+ if (!pending && !didTriggerEdit && !cell.isEditing) if (cell.isAnchor || cell.isFocused && !cell.hasRangeSelection) cellRef.current?.focus();
10938
+ else ((cellRef.current?.closest("[data-testid=\"table-widget\"]"))?.querySelector("[data-testid^=\"cell-\"][tabindex=\"0\"]"))?.focus();
10939
+ setMenuAnchor(null);
10940
+ if (pending) {
10941
+ const openFn = cellMenuRegistry.get(pending.target);
10942
+ if (openFn) setTimeout(() => {
10943
+ openFn(pending.clientX, pending.clientY);
10944
+ }, 0);
10945
+ }
10946
+ }, [
10947
+ cell,
10948
+ cellRef,
10949
+ deferredEdit
10950
+ ]);
10951
+ menuCloseRef.current = handleMenuClose;
10952
+ return {
10953
+ menuAnchor,
10954
+ menuOpen,
10955
+ openContextMenuAt,
10956
+ handleMenuClose,
10957
+ menuCloseRef
10958
+ };
10959
+ }
10960
+
10961
+ //#endregion
10962
+ //#region src/table-editor/Table/ui/Cell/useCellFocus.ts
10963
+ function useCellFocus(cellRef, state, isAnchorInRange, navVersion) {
10964
+ useEffect(() => {
10965
+ if (!cellRef.current) return;
10966
+ if (state === "focused" || state === "readonlyFocused" || isAnchorInRange) cellRef.current.focus();
10967
+ else if (state === "display" || state === "readonly" || state === "selected") cellRef.current.blur();
10968
+ }, [
10969
+ cellRef,
10970
+ state,
10971
+ isAnchorInRange,
10972
+ navVersion
10973
+ ]);
10974
+ }
10975
+
10976
+ //#endregion
10977
+ //#region src/table-editor/Table/ui/Cell/useCellKeyboard.ts
10978
+ function handleArrowKey(cell, e, shiftAction, moveAction) {
10979
+ e.preventDefault();
10980
+ if (e.shiftKey) shiftAction();
10981
+ else if (cell.hasRangeSelection) cell.focus();
10982
+ else moveAction();
10983
+ }
10984
+ function handleEditableKeys(cell, e, callbacks) {
10985
+ if (cell.isReadOnly) {
10986
+ if (e.key === "Enter" || e.key === "Delete" || e.key === "Backspace" || isPrintableKey(e)) {
10987
+ e.preventDefault();
10988
+ cell.notifyReadonlyEditAttempt();
10989
+ }
10990
+ return;
10991
+ }
10992
+ const hasRange = cell.hasRangeSelection;
10993
+ if (!hasRange && e.key === "Enter") {
10994
+ e.preventDefault();
10995
+ if (callbacks.onStartEdit) callbacks.onStartEdit();
10996
+ else if (callbacks.onDoubleClick) callbacks.onDoubleClick();
10997
+ } else if (!hasRange && (e.key === "Delete" || e.key === "Backspace") && callbacks.onDelete) {
10998
+ e.preventDefault();
10999
+ callbacks.onDelete();
11000
+ } else if (isPrintableKey(e) && callbacks.onTypeChar) {
11001
+ e.preventDefault();
11002
+ callbacks.onTypeChar(e.key);
11003
+ }
11004
+ }
11005
+ function useCellKeyboard(cell, state, isAnchorInRange, menuOpen, menuCloseRef, callbacks) {
11006
+ const { onStartEdit, onDoubleClick, onTypeChar, onDelete } = callbacks;
11007
+ return {
11008
+ handleKeyDown: useCallback((e) => {
11009
+ if (menuOpen && e.key === "Escape") {
11010
+ e.preventDefault();
11011
+ menuCloseRef.current?.();
11012
+ return;
11013
+ }
11014
+ if (!(state === "focused" || state === "readonlyFocused" || isAnchorInRange)) return;
11015
+ const isMod = e.ctrlKey || e.metaKey;
11016
+ if (isMod && e.key === "c" && !cell.hasRangeSelection) {
11017
+ e.preventDefault();
11018
+ cell.copyToClipboard();
11019
+ return;
11020
+ }
11021
+ if (isMod && e.key === "v") return;
11022
+ const arrow = {
11023
+ ArrowUp: [cell.shiftMoveUp, cell.moveUp],
11024
+ ArrowDown: [cell.shiftMoveDown, cell.moveDown],
11025
+ ArrowLeft: [cell.shiftMoveLeft, cell.moveLeft],
11026
+ ArrowRight: [cell.shiftMoveRight, cell.moveRight]
11027
+ }[e.key];
11028
+ if (e.key === "Escape") {
11029
+ e.preventDefault();
11030
+ if (cell.hasRangeSelection) cell.focus();
11031
+ else cell.blur();
11032
+ } else if (arrow) handleArrowKey(cell, e, arrow[0], arrow[1]);
11033
+ else if (e.key === "Tab") {
11034
+ e.preventDefault();
11035
+ cell.handleTab(e.shiftKey);
11036
+ } else handleEditableKeys(cell, e, {
11037
+ onStartEdit,
11038
+ onDoubleClick,
11039
+ onTypeChar,
11040
+ onDelete
11041
+ });
11042
+ }, [
11043
+ state,
11044
+ cell,
11045
+ isAnchorInRange,
11046
+ menuOpen,
11047
+ menuCloseRef,
11048
+ onStartEdit,
11049
+ onDoubleClick,
11050
+ onTypeChar,
11051
+ onDelete
11052
+ ]),
11053
+ handleDoubleClick: useCallback((e) => {
11054
+ if (state === "readonly" || state === "readonlyFocused") {
11055
+ cell.notifyReadonlyEditAttempt();
11056
+ return;
11057
+ }
11058
+ onDoubleClick?.(e.clientX);
11059
+ }, [
11060
+ state,
11061
+ cell,
11062
+ onDoubleClick
11063
+ ])
11064
+ };
11065
+ }
11066
+
10712
11067
  //#endregion
10713
11068
  //#region src/table-editor/Table/ui/Cell/useDeferredMenuEdit.ts
10714
11069
  /**
@@ -10763,282 +11118,117 @@ function useDeferredMenuEdit(editFn) {
10763
11118
  };
10764
11119
  }
10765
11120
 
10766
- //#endregion
10767
- //#region src/table-editor/Table/ui/Cell/cellStyles.ts
10768
- const FOCUS_RING_RESET = {
10769
- outline: "none",
10770
- boxShadow: "none"
10771
- };
10772
- const RANGE_BORDER_COLOR = "blue.400";
10773
- function buildSelectionBorderStyle(edges) {
10774
- return {
10775
- content: "\"\"",
10776
- position: "absolute",
10777
- top: edges.top ? "-1px" : 0,
10778
- bottom: edges.bottom ? "-1px" : 0,
10779
- left: edges.left ? "-1px" : 0,
10780
- right: edges.right ? "-1px" : 0,
10781
- borderTop: edges.top ? "2px solid" : "none",
10782
- borderBottom: edges.bottom ? "2px solid" : "none",
10783
- borderLeft: edges.left ? "2px solid" : "none",
10784
- borderRight: edges.right ? "2px solid" : "none",
10785
- borderColor: RANGE_BORDER_COLOR,
10786
- pointerEvents: "none",
10787
- zIndex: 2
10788
- };
10789
- }
10790
- const stateStyles = {
10791
- display: {
10792
- cursor: "cell",
10793
- _hover: { bg: "gray.50" }
10794
- },
10795
- focused: {
10796
- cursor: "cell",
10797
- bg: "blue.50",
10798
- _before: {
10799
- content: "\"\"",
10800
- position: "absolute",
10801
- inset: "1px",
10802
- border: "2px solid",
10803
- borderColor: "blue.400",
10804
- borderRadius: "1px",
10805
- pointerEvents: "none"
10806
- },
10807
- _focus: FOCUS_RING_RESET,
10808
- _focusVisible: FOCUS_RING_RESET
10809
- },
10810
- editing: {
10811
- cursor: "text",
10812
- bg: "white",
10813
- zIndex: 1,
10814
- _before: {
10815
- content: "\"\"",
10816
- position: "absolute",
10817
- inset: "1px",
10818
- border: "2px solid",
10819
- borderColor: "blue.500",
10820
- borderRadius: "1px",
10821
- pointerEvents: "none"
10822
- },
10823
- _focus: FOCUS_RING_RESET,
10824
- _focusVisible: FOCUS_RING_RESET
10825
- },
10826
- readonly: {
10827
- cursor: "default",
10828
- color: "gray.500"
10829
- },
10830
- readonlyFocused: {
10831
- cursor: "default",
10832
- bg: "gray.50",
10833
- _before: {
10834
- content: "\"\"",
10835
- position: "absolute",
10836
- inset: "1px",
10837
- border: "2px solid",
10838
- borderColor: "gray.400",
10839
- borderRadius: "1px",
10840
- pointerEvents: "none"
10841
- },
10842
- _focus: FOCUS_RING_RESET,
10843
- _focusVisible: FOCUS_RING_RESET
10844
- },
10845
- selected: {
10846
- cursor: "cell",
10847
- bg: "blue.100"
10848
- }
10849
- };
10850
- function getCellState(cell) {
10851
- if (cell.isReadOnly) {
10852
- if (cell.isFocused && !cell.isInSelection) return "readonlyFocused";
10853
- if (cell.isInSelection) return "selected";
10854
- return "readonly";
10855
- }
10856
- if (cell.isEditing) return "editing";
10857
- if (cell.isFocused && !cell.isInSelection) return "focused";
10858
- if (cell.isInSelection) return "selected";
10859
- return "display";
10860
- }
10861
- function isPrintableKey(e) {
10862
- if (e.ctrlKey || e.metaKey || e.altKey) return false;
10863
- return e.key.length === 1;
10864
- }
10865
-
10866
11121
  //#endregion
10867
11122
  //#region src/table-editor/Table/ui/Cell/CellWrapper.tsx
10868
- function handleArrowKey(cell, e, shiftAction, moveAction) {
10869
- e.preventDefault();
10870
- if (e.shiftKey) shiftAction();
10871
- else if (cell.hasRangeSelection) cell.focus();
10872
- else moveAction();
10873
- }
10874
- function handleEditableKeys(cell, e, onStartEdit, onDoubleClick, onTypeChar, onDelete) {
10875
- if (cell.isReadOnly) return;
10876
- const hasRange = cell.hasRangeSelection;
10877
- if (!hasRange && e.key === "Enter") {
10878
- e.preventDefault();
10879
- if (onStartEdit) onStartEdit();
10880
- else if (onDoubleClick) onDoubleClick();
10881
- } else if (!hasRange && (e.key === "Delete" || e.key === "Backspace") && onDelete) {
10882
- e.preventDefault();
10883
- onDelete();
10884
- } else if (isPrintableKey(e) && onTypeChar) {
10885
- e.preventDefault();
10886
- onTypeChar(e.key);
10887
- }
10888
- }
11123
+ const LazyContextMenu = observer(({ cell, cellRef, anchorRect, onClose, onEditPointerDown }) => {
11124
+ const handleOpenChange = useCallback((details) => {
11125
+ if (!details.open) onClose();
11126
+ }, [onClose]);
11127
+ const handleInteractOutside = useCallback((e) => {
11128
+ const originalEvent = e.detail?.originalEvent;
11129
+ if (!originalEvent || originalEvent.button !== 2) return;
11130
+ const targetCell = originalEvent.target?.closest("[data-testid^=\"cell-\"]");
11131
+ if (targetCell) setPendingContextMenu({
11132
+ target: targetCell,
11133
+ clientX: originalEvent.clientX,
11134
+ clientY: originalEvent.clientY
11135
+ });
11136
+ }, []);
11137
+ const getAnchorRect = useCallback(() => {
11138
+ return anchorRect ?? (cellRef.current?.getBoundingClientRect() || null);
11139
+ }, [anchorRect, cellRef]);
11140
+ return /* @__PURE__ */ jsx(Menu.Root, {
11141
+ open: true,
11142
+ onOpenChange: handleOpenChange,
11143
+ onInteractOutside: handleInteractOutside,
11144
+ positioning: {
11145
+ placement: "bottom-start",
11146
+ getAnchorRect
11147
+ },
11148
+ children: /* @__PURE__ */ jsx(CellContextMenu, {
11149
+ cell,
11150
+ onEditPointerDown
11151
+ })
11152
+ });
11153
+ });
10889
11154
  const CellWrapper = observer(({ cell, children, onDoubleClick, onStartEdit, onTypeChar, onDelete }) => {
11155
+ ensureCellStyles();
10890
11156
  const cellRef = useRef(null);
10891
- const menuOpenRef = useRef(false);
10892
- const deferredEdit = useDeferredMenuEdit(onStartEdit ?? onDoubleClick);
10893
11157
  const state = getCellState(cell);
10894
11158
  const selectionEdges = cell.selectionEdges;
10895
11159
  const isAnchorInRange = cell.isAnchor && cell.isInSelection;
10896
11160
  const navVersion = cell.navigationVersion;
10897
- useEffect(() => {
10898
- if (!cellRef.current) return;
10899
- if (state === "focused" || state === "readonlyFocused" || isAnchorInRange) cellRef.current.focus();
10900
- else if (state === "display" || state === "readonly" || state === "selected") cellRef.current.blur();
10901
- }, [
10902
- state,
10903
- isAnchorInRange,
10904
- navVersion
10905
- ]);
10906
- const handleClick = useCallback((e) => {
10907
- if (state === "editing") return;
10908
- if (e.shiftKey) cell.selectTo();
10909
- else cell.focus();
10910
- }, [state, cell]);
11161
+ const isActive = state === "focused" || state === "editing" || state === "readonlyFocused" || isAnchorInRange;
11162
+ const deferredEdit = useDeferredMenuEdit(onStartEdit ?? onDoubleClick);
11163
+ const { menuAnchor, menuOpen, openContextMenuAt, handleMenuClose, menuCloseRef } = useCellContextMenu(cell, cellRef, deferredEdit);
11164
+ useCellFocus(cellRef, state, isAnchorInRange, navVersion);
11165
+ const { handleKeyDown, handleDoubleClick } = useCellKeyboard(cell, state, isAnchorInRange, menuOpen, menuCloseRef, {
11166
+ onStartEdit,
11167
+ onDoubleClick,
11168
+ onTypeChar,
11169
+ onDelete
11170
+ });
10911
11171
  const handleMouseDown = useCallback((e) => {
10912
11172
  if (e.detail === 2 && state !== "readonly" && state !== "readonlyFocused") e.preventDefault();
11173
+ if (e.button === 2) {
11174
+ openContextMenuAt(e.clientX, e.clientY);
11175
+ return;
11176
+ }
10913
11177
  if (!e.shiftKey && e.button === 0 && state !== "editing") {
10914
11178
  e.preventDefault();
10915
11179
  cell.dragStart();
10916
11180
  cellRef.current?.focus();
10917
11181
  }
10918
- }, [state, cell]);
10919
- const handleMouseEnter = useCallback((e) => {
10920
- if (e.buttons === 1) cell.dragExtend();
10921
- }, [cell]);
10922
- const handleDoubleClick = useCallback((e) => {
10923
- if (state !== "readonly" && state !== "readonlyFocused") onDoubleClick?.(e.clientX);
10924
- }, [state, onDoubleClick]);
10925
- const handleKeyDown = useCallback((e) => {
10926
- if (!(state === "focused" || state === "readonlyFocused" || isAnchorInRange)) return;
10927
- const isMod = e.ctrlKey || e.metaKey;
10928
- if (isMod && e.key === "c" && !cell.hasRangeSelection) {
10929
- e.preventDefault();
10930
- cell.copyToClipboard();
10931
- return;
10932
- }
10933
- if (isMod && e.key === "v") return;
10934
- if (e.key === "Escape") {
10935
- e.preventDefault();
10936
- if (cell.hasRangeSelection) cell.focus();
10937
- else cell.blur();
10938
- } else if (e.key === "ArrowUp") handleArrowKey(cell, e, cell.shiftMoveUp, cell.moveUp);
10939
- else if (e.key === "ArrowDown") handleArrowKey(cell, e, cell.shiftMoveDown, cell.moveDown);
10940
- else if (e.key === "ArrowLeft") handleArrowKey(cell, e, cell.shiftMoveLeft, cell.moveLeft);
10941
- else if (e.key === "ArrowRight") handleArrowKey(cell, e, cell.shiftMoveRight, cell.moveRight);
10942
- else if (e.key === "Tab") {
10943
- e.preventDefault();
10944
- cell.handleTab(e.shiftKey);
10945
- } else handleEditableKeys(cell, e, onStartEdit, onDoubleClick, onTypeChar, onDelete);
10946
11182
  }, [
10947
11183
  state,
10948
11184
  cell,
10949
- isAnchorInRange,
10950
- onStartEdit,
10951
- onDoubleClick,
10952
- onTypeChar,
10953
- onDelete
11185
+ openContextMenuAt
10954
11186
  ]);
10955
- const afterStyle = selectionEdges ? buildSelectionBorderStyle(selectionEdges) : void 0;
10956
- const needsFocus = state === "focused" || state === "editing" || state === "readonlyFocused" || isAnchorInRange;
11187
+ const handleClick = useCallback((e) => {
11188
+ if (state === "editing") return;
11189
+ if (e.shiftKey) cell.selectTo();
11190
+ else cell.focus();
11191
+ }, [state, cell]);
11192
+ const handleMouseEnter = useCallback((e) => {
11193
+ if (e.buttons === 1) cell.dragExtend();
11194
+ }, [cell]);
10957
11195
  const handleBlur = useCallback((e) => {
10958
11196
  if (!cell.isFocused || cell.isEditing) return;
10959
- if (menuOpenRef.current) return;
11197
+ if (menuOpen) return;
10960
11198
  if (e.relatedTarget?.closest("[data-testid^=\"cell-\"]")) return;
10961
11199
  cell.blur();
10962
- }, [cell]);
10963
- const handleContextMenu = useCallback(() => {
10964
- if (!cell.isFocused && !cell.isInSelection) cell.focus();
10965
- }, [cell]);
10966
- const handleMenuOpenChange = useCallback((details) => {
10967
- menuOpenRef.current = details.open;
10968
- if (details.open) return;
10969
- if (deferredEdit.triggerIfRequested()) return;
10970
- if (cell.isEditing) return;
10971
- if (cell.isAnchor || cell.isFocused && !cell.hasRangeSelection) cellRef.current?.focus();
10972
- else ((cellRef.current?.closest("[data-testid=\"table-widget\"]"))?.querySelector("[data-testid^=\"cell-\"][tabindex=\"0\"]"))?.focus();
10973
- }, [cell, deferredEdit]);
10974
- const extraStyles = {};
10975
- if (isAnchorInRange) {
10976
- extraStyles._before = {
10977
- content: "\"\"",
10978
- position: "absolute",
10979
- inset: "1px",
10980
- border: "2px solid",
10981
- borderColor: "blue.400",
10982
- borderRadius: "1px",
10983
- pointerEvents: "none",
10984
- zIndex: 3
10985
- };
10986
- extraStyles._focus = FOCUS_RING_RESET;
10987
- extraStyles._focusVisible = FOCUS_RING_RESET;
10988
- }
10989
- return /* @__PURE__ */ jsxs(Menu.Root, {
10990
- onOpenChange: handleMenuOpenChange,
10991
- children: [/* @__PURE__ */ jsx(Menu.ContextTrigger, {
10992
- asChild: true,
10993
- children: /* @__PURE__ */ jsxs(Box, {
10994
- ref: cellRef,
10995
- height: "40px",
10996
- px: "8px",
10997
- position: "relative",
10998
- overflow: selectionEdges ? "visible" : "hidden",
10999
- onClick: handleClick,
11000
- onMouseDown: handleMouseDown,
11001
- onMouseEnter: handleMouseEnter,
11002
- onDoubleClick: handleDoubleClick,
11003
- onKeyDown: handleKeyDown,
11004
- onBlur: handleBlur,
11005
- onContextMenu: handleContextMenu,
11006
- tabIndex: needsFocus ? 0 : -1,
11007
- userSelect: state === "selected" ? "none" : void 0,
11008
- "data-testid": `cell-${cell.rowId}-${cell.field}`,
11009
- ...stateStyles[state],
11010
- ...extraStyles,
11011
- _after: afterStyle,
11012
- children: [/* @__PURE__ */ jsx(Box, {
11013
- display: "flex",
11014
- alignItems: "center",
11015
- height: "100%",
11016
- width: "100%",
11017
- minWidth: 0,
11018
- overflow: "hidden",
11019
- children
11020
- }), state === "readonlyFocused" && /* @__PURE__ */ jsx(Box, {
11021
- position: "absolute",
11022
- top: "1px",
11023
- right: "4px",
11024
- fontSize: "9px",
11025
- lineHeight: "1",
11026
- color: "gray.500",
11027
- bg: "gray.100",
11028
- px: "3px",
11029
- py: "1px",
11030
- borderRadius: "2px",
11031
- userSelect: "none",
11032
- pointerEvents: "none",
11033
- children: "readonly"
11034
- })]
11035
- })
11036
- }), /* @__PURE__ */ jsx(CellContextMenu, {
11037
- cell,
11038
- onEditPointerDown: deferredEdit.requestEdit
11039
- })]
11040
- });
11200
+ }, [cell, menuOpen]);
11201
+ const selectionShadow = selectionEdges ? buildSelectionBoxShadow(selectionEdges) : null;
11202
+ let className = STATE_CLASS[state];
11203
+ if (isAnchorInRange) className += ` ${CLS_ANCHOR}`;
11204
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
11205
+ ref: cellRef,
11206
+ className,
11207
+ style: selectionShadow ? { "--cw-shadow": selectionShadow } : void 0,
11208
+ tabIndex: isActive ? 0 : -1,
11209
+ onClick: handleClick,
11210
+ onMouseDown: handleMouseDown,
11211
+ onMouseEnter: handleMouseEnter,
11212
+ onDoubleClick: isActive ? handleDoubleClick : void 0,
11213
+ onKeyDown: isActive ? handleKeyDown : void 0,
11214
+ onBlur: isActive ? handleBlur : void 0,
11215
+ onContextMenu: preventContextMenu,
11216
+ "data-testid": `cell-${cell.rowId}-${cell.field}`,
11217
+ children: /* @__PURE__ */ jsx("div", {
11218
+ style: INNER_STYLE,
11219
+ children
11220
+ })
11221
+ }), menuOpen && /* @__PURE__ */ jsx(LazyContextMenu, {
11222
+ cell,
11223
+ cellRef,
11224
+ anchorRect: menuAnchor,
11225
+ onClose: handleMenuClose,
11226
+ onEditPointerDown: deferredEdit.requestEdit
11227
+ })] });
11041
11228
  });
11229
+ function preventContextMenu(e) {
11230
+ e.preventDefault();
11231
+ }
11042
11232
 
11043
11233
  //#endregion
11044
11234
  //#region src/table-editor/Table/ui/Cell/useTextareaCell.ts
@@ -11315,7 +11505,6 @@ const ForeignKeyCell = observer(({ cell, onSearchForeignKey }) => {
11315
11505
  textOverflow: "ellipsis",
11316
11506
  overflow: "hidden",
11317
11507
  fontWeight: "300",
11318
- color: "blue.500",
11319
11508
  flex: 1,
11320
11509
  minWidth: 0,
11321
11510
  children: cell.displayValue
@@ -11523,8 +11712,8 @@ const ReadonlyCell = observer(({ cell }) => {
11523
11712
  whiteSpace: "nowrap",
11524
11713
  textOverflow: "ellipsis",
11525
11714
  overflow: "hidden",
11526
- fontWeight: "300",
11527
- color: "gray.400",
11715
+ fontSize: "14px",
11716
+ color: "text/primary",
11528
11717
  children: cell.displayValue
11529
11718
  })
11530
11719
  });
@@ -11672,6 +11861,30 @@ function clearRange(range, rows, cols) {
11672
11861
  }
11673
11862
  }
11674
11863
 
11864
+ //#endregion
11865
+ //#region src/table-editor/Table/ui/borderConstants.ts
11866
+ const CELL_BORDER_COLOR = "#ededed";
11867
+ const BOTTOM_BORDER_SHADOW = `inset 0 -1px 0 0 ${CELL_BORDER_COLOR}`;
11868
+ function buildAddColumnShadowCss() {
11869
+ return { "&::after": {
11870
+ content: "\"\"",
11871
+ position: "absolute",
11872
+ top: 0,
11873
+ bottom: 0,
11874
+ width: "8px",
11875
+ left: "-8px",
11876
+ pointerEvents: "none",
11877
+ transition: "opacity 0.15s",
11878
+ opacity: "var(--shadow-right-opacity, 0)",
11879
+ boxShadow: "inset -8px 0 12px -8px rgba(0,0,0,0.1)"
11880
+ } };
11881
+ }
11882
+ function adjustRightOffsetCss(rightCss, addColOffset) {
11883
+ if (addColOffset <= 0) return rightCss;
11884
+ if (rightCss === "0px") return `${addColOffset}px`;
11885
+ return `calc(${rightCss} + ${addColOffset}px)`;
11886
+ }
11887
+
11675
11888
  //#endregion
11676
11889
  //#region src/table-editor/Table/ui/ResizeHandle.tsx
11677
11890
  const MIN_COLUMN_WIDTH = 40;
@@ -11698,7 +11911,7 @@ const ResizeHandle = observer(({ field, columnsModel }) => {
11698
11911
  onMouseDown: useCallback((e) => {
11699
11912
  e.preventDefault();
11700
11913
  e.stopPropagation();
11701
- const currentWidth = columnsModel.getColumnWidth(field) ?? 150;
11914
+ const currentWidth = columnsModel.resolveColumnWidth(field);
11702
11915
  startXRef.current = e.clientX;
11703
11916
  startWidthRef.current = currentWidth;
11704
11917
  setIsResizing(true);
@@ -12057,7 +12270,8 @@ const ColumnHeaderMenu = observer(({ column, columnsModel, sortModel, filterMode
12057
12270
 
12058
12271
  //#endregion
12059
12272
  //#region src/table-editor/Table/ui/Header/ColumnHeader.tsx
12060
- function buildHeaderShadowCss(position, showShadow) {
12273
+ function buildHeaderShadowCss(position) {
12274
+ const cssVar = position.side === "left" ? "var(--shadow-left-opacity, 0)" : "var(--shadow-right-opacity, 0)";
12061
12275
  return { "&::after": {
12062
12276
  content: "\"\"",
12063
12277
  position: "absolute",
@@ -12066,7 +12280,7 @@ function buildHeaderShadowCss(position, showShadow) {
12066
12280
  width: "8px",
12067
12281
  pointerEvents: "none",
12068
12282
  transition: "opacity 0.15s",
12069
- opacity: showShadow ? 1 : 0,
12283
+ opacity: cssVar,
12070
12284
  ...position.side === "left" ? {
12071
12285
  right: "-8px",
12072
12286
  boxShadow: "inset 8px 0 12px -8px rgba(0,0,0,0.1)"
@@ -12076,22 +12290,19 @@ function buildHeaderShadowCss(position, showShadow) {
12076
12290
  }
12077
12291
  } };
12078
12292
  }
12079
- const BOTTOM_BORDER_SHADOW$2 = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12080
12293
  function getHeaderBoxShadow(stickyPosition) {
12081
- if (!stickyPosition) return BOTTOM_BORDER_SHADOW$2;
12082
- 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)"}`;
12294
+ if (!stickyPosition) return BOTTOM_BORDER_SHADOW;
12295
+ return `${BOTTOM_BORDER_SHADOW}, ${stickyPosition.side === "left" ? `inset -1px 0 0 0 ${CELL_BORDER_COLOR}` : `inset 1px 0 0 0 ${CELL_BORDER_COLOR}`}`;
12083
12296
  }
12084
- const ColumnHeader = observer(({ column, columnsModel, sortModel, filterModel, onCopyPath, stickyPosition, showLeftShadow, showRightShadow }) => {
12297
+ const ColumnHeader = observer(({ column, columnsModel, sortModel, filterModel, onCopyPath, stickyPosition }) => {
12085
12298
  const [isMenuOpen, setIsMenuOpen] = useState(false);
12086
- const width = columnsModel.getColumnWidth(column.field);
12087
- const w = width ? `${width}px` : "150px";
12299
+ const w = columnsModel.columnWidthCssVar(column.field);
12088
12300
  const isSticky = Boolean(stickyPosition);
12089
- const showShadow = stickyPosition?.isBoundary ? stickyPosition.side === "left" && Boolean(showLeftShadow) || stickyPosition.side === "right" && Boolean(showRightShadow) : false;
12090
12301
  return /* @__PURE__ */ jsxs(Box, {
12091
12302
  as: "th",
12092
12303
  position: isSticky ? "sticky" : "relative",
12093
- left: stickyPosition?.side === "left" ? `${stickyPosition.offset}px` : void 0,
12094
- right: stickyPosition?.side === "right" ? `${stickyPosition.offset}px` : void 0,
12304
+ left: stickyPosition?.side === "left" ? stickyPosition.offsetCss : void 0,
12305
+ right: stickyPosition?.side === "right" ? stickyPosition.offsetCss : void 0,
12095
12306
  zIndex: isSticky ? 2 : void 0,
12096
12307
  width: w,
12097
12308
  minWidth: w,
@@ -12102,7 +12313,7 @@ const ColumnHeader = observer(({ column, columnsModel, sortModel, filterModel, o
12102
12313
  textAlign: "left",
12103
12314
  fontWeight: "normal",
12104
12315
  p: 0,
12105
- css: stickyPosition?.isBoundary ? buildHeaderShadowCss(stickyPosition, showShadow) : void 0,
12316
+ css: stickyPosition?.isBoundary ? buildHeaderShadowCss(stickyPosition) : void 0,
12106
12317
  children: [/* @__PURE__ */ jsxs(Menu.Root, {
12107
12318
  positioning: { placement: "bottom-end" },
12108
12319
  lazyMount: true,
@@ -12198,6 +12409,7 @@ const AddColumnButton = observer(({ columnsModel }) => {
12198
12409
  bg: "gray.100",
12199
12410
  color: "gray.600"
12200
12411
  },
12412
+ focusRing: "none",
12201
12413
  "data-testid": "add-column-button",
12202
12414
  children: /* @__PURE__ */ jsx(PiPlus, {})
12203
12415
  })
@@ -12243,8 +12455,7 @@ const AddColumnButton = observer(({ columnsModel }) => {
12243
12455
  //#region src/table-editor/Table/ui/HeaderRow.tsx
12244
12456
  const SELECTION_COLUMN_WIDTH$1 = 40;
12245
12457
  const ADD_COLUMN_BUTTON_WIDTH$1 = 40;
12246
- const BOTTOM_BORDER_SHADOW$1 = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12247
- const HeaderRow = observer(({ columnsModel, sortModel, filterModel, onCopyPath, showSelection, scrollShadow }) => {
12458
+ const HeaderRow = observer(({ columnsModel, sortModel, filterModel, onCopyPath, showSelection }) => {
12248
12459
  const selectionWidth = showSelection ? SELECTION_COLUMN_WIDTH$1 : 0;
12249
12460
  const addColumnStickyRight = columnsModel.hasHiddenColumns;
12250
12461
  return /* @__PURE__ */ jsxs(Box, {
@@ -12261,7 +12472,7 @@ const HeaderRow = observer(({ columnsModel, sortModel, filterModel, onCopyPath,
12261
12472
  position: "sticky",
12262
12473
  left: 0,
12263
12474
  zIndex: 2,
12264
- boxShadow: BOTTOM_BORDER_SHADOW$1
12475
+ boxShadow: BOTTOM_BORDER_SHADOW
12265
12476
  }),
12266
12477
  columnsModel.visibleColumns.map((col) => {
12267
12478
  return /* @__PURE__ */ jsx(ColumnHeader, {
@@ -12270,42 +12481,42 @@ const HeaderRow = observer(({ columnsModel, sortModel, filterModel, onCopyPath,
12270
12481
  sortModel,
12271
12482
  filterModel,
12272
12483
  onCopyPath,
12273
- stickyPosition: getStickyPosition(col.field, columnsModel, selectionWidth, addColumnStickyRight),
12274
- showLeftShadow: scrollShadow?.showLeftShadow,
12275
- showRightShadow: scrollShadow?.showRightShadow
12484
+ stickyPosition: getStickyPosition(col.field, columnsModel, selectionWidth, addColumnStickyRight)
12276
12485
  }, col.field);
12277
12486
  }),
12278
- /* @__PURE__ */ jsx(Box, {
12487
+ addColumnStickyRight ? /* @__PURE__ */ jsx(Box, {
12279
12488
  as: "th",
12280
12489
  width: "100%",
12490
+ minWidth: `${ADD_COLUMN_BUTTON_WIDTH$1}px`,
12281
12491
  bg: "white",
12282
12492
  p: 0,
12283
- boxShadow: BOTTOM_BORDER_SHADOW$1
12284
- }),
12285
- /* @__PURE__ */ jsx(Box, {
12493
+ position: "sticky",
12494
+ right: 0,
12495
+ zIndex: 2,
12496
+ boxShadow: BOTTOM_BORDER_SHADOW,
12497
+ css: columnsModel.pinnedRightCount === 0 ? buildAddColumnShadowCss() : void 0,
12498
+ children: /* @__PURE__ */ jsx(AddColumnButton, { columnsModel })
12499
+ }) : /* @__PURE__ */ jsx(Box, {
12286
12500
  as: "th",
12501
+ width: "100%",
12287
12502
  bg: "white",
12288
12503
  p: 0,
12289
- position: addColumnStickyRight ? "sticky" : void 0,
12290
- right: addColumnStickyRight ? 0 : void 0,
12291
- zIndex: addColumnStickyRight ? 2 : void 0,
12292
- boxShadow: BOTTOM_BORDER_SHADOW$1,
12293
- children: /* @__PURE__ */ jsx(AddColumnButton, { columnsModel })
12504
+ boxShadow: BOTTOM_BORDER_SHADOW
12294
12505
  })
12295
12506
  ]
12296
12507
  });
12297
12508
  });
12298
12509
  function getStickyPosition(field, columnsModel, selectionWidth, addColumnStickyRight) {
12299
- const leftOffset = columnsModel.getColumnStickyLeft(field, selectionWidth);
12300
- if (leftOffset !== void 0) return {
12510
+ const leftCss = columnsModel.getColumnStickyLeftCss(field, selectionWidth);
12511
+ if (leftCss !== void 0) return {
12301
12512
  side: "left",
12302
- offset: leftOffset,
12513
+ offsetCss: leftCss,
12303
12514
  isBoundary: columnsModel.isStickyLeftBoundary(field)
12304
12515
  };
12305
- const rightBase = columnsModel.getColumnStickyRight(field);
12306
- if (rightBase !== void 0) return {
12516
+ const rightCss = columnsModel.getColumnStickyRightCss(field);
12517
+ if (rightCss !== void 0) return {
12307
12518
  side: "right",
12308
- offset: rightBase + (addColumnStickyRight ? ADD_COLUMN_BUTTON_WIDTH$1 : 0),
12519
+ offsetCss: adjustRightOffsetCss(rightCss, addColumnStickyRight ? ADD_COLUMN_BUTTON_WIDTH$1 : 0),
12309
12520
  isBoundary: columnsModel.isStickyRightBoundary(field)
12310
12521
  };
12311
12522
  }
@@ -12440,7 +12651,7 @@ const SelectionCheckboxCell = ({ rowId, isSelected, onToggleSelection }) => {
12440
12651
  left: 0,
12441
12652
  zIndex: 1,
12442
12653
  bg: "white",
12443
- boxShadow: "inset 0 -1px 0 0 var(--chakra-colors-gray-100), inset -1px 0 0 0 var(--chakra-colors-gray-100)",
12654
+ boxShadow: `inset 0 -1px 0 0 ${CELL_BORDER_COLOR}, inset -1px 0 0 0 ${CELL_BORDER_COLOR}`,
12444
12655
  children: /* @__PURE__ */ jsx(Flex, {
12445
12656
  alignItems: "center",
12446
12657
  justifyContent: "center",
@@ -12460,7 +12671,8 @@ const SelectionCheckboxCell = ({ rowId, isSelected, onToggleSelection }) => {
12460
12671
  //#region src/table-editor/Table/ui/DataRow.tsx
12461
12672
  const SELECTION_COLUMN_WIDTH = 40;
12462
12673
  const ADD_COLUMN_BUTTON_WIDTH = 40;
12463
- function buildShadowCss(side, showShadow) {
12674
+ function buildShadowCss(side) {
12675
+ const cssVar = side === "left" ? "var(--shadow-left-opacity, 0)" : "var(--shadow-right-opacity, 0)";
12464
12676
  return { "&::after": {
12465
12677
  content: "\"\"",
12466
12678
  position: "absolute",
@@ -12469,7 +12681,7 @@ function buildShadowCss(side, showShadow) {
12469
12681
  width: "8px",
12470
12682
  pointerEvents: "none",
12471
12683
  transition: "opacity 0.15s",
12472
- opacity: showShadow ? 1 : 0,
12684
+ opacity: cssVar,
12473
12685
  ...side === "left" ? {
12474
12686
  right: "-8px",
12475
12687
  boxShadow: "inset 8px 0 12px -8px rgba(0,0,0,0.1)"
@@ -12479,7 +12691,7 @@ function buildShadowCss(side, showShadow) {
12479
12691
  }
12480
12692
  } };
12481
12693
  }
12482
- function buildCellCss(isBoundary, boundarySide, showShadow, hasRowActions, isFirstColumn) {
12694
+ function buildCellCss(isBoundary, boundarySide, hasRowActions, isFirstColumn) {
12483
12695
  const needsHover = isFirstColumn && hasRowActions;
12484
12696
  const hoverCss = needsHover ? {
12485
12697
  "& .row-action-buttons": { opacity: 0 },
@@ -12490,34 +12702,33 @@ function buildCellCss(isBoundary, boundarySide, showShadow, hasRowActions, isFir
12490
12702
  } : {};
12491
12703
  if (isBoundary) return {
12492
12704
  ...hoverCss,
12493
- ...buildShadowCss(boundarySide, showShadow)
12705
+ ...buildShadowCss(boundarySide)
12494
12706
  };
12495
12707
  if (needsHover) return hoverCss;
12496
12708
  }
12497
- function computeStickyProps(col, columnsModel, selectionWidth, addColOffset, showLeftShadow, showRightShadow) {
12498
- const leftOffset = columnsModel.getColumnStickyLeft(col.field, selectionWidth);
12499
- const rightBase = columnsModel.getColumnStickyRight(col.field);
12500
- const isStickyLeft = leftOffset !== void 0;
12501
- const isStickyRight = rightBase !== void 0;
12709
+ function computeStickyProps(col, columnsModel, selectionWidth, addColOffset) {
12710
+ const leftCss = columnsModel.getColumnStickyLeftCss(col.field, selectionWidth);
12711
+ const rightCss = columnsModel.getColumnStickyRightCss(col.field);
12712
+ const isStickyLeft = leftCss !== void 0;
12713
+ const isStickyRight = rightCss !== void 0;
12502
12714
  const isSticky = isStickyLeft || isStickyRight;
12503
12715
  const isLeftBoundary = columnsModel.isStickyLeftBoundary(col.field);
12504
12716
  const isRightBoundary = columnsModel.isStickyRightBoundary(col.field);
12717
+ const finalRightCss = isStickyRight && rightCss !== void 0 ? adjustRightOffsetCss(rightCss, addColOffset) : rightCss;
12505
12718
  return {
12506
12719
  isSticky,
12507
12720
  isBoundary: isLeftBoundary || isRightBoundary,
12508
12721
  boundarySide: isStickyLeft ? "left" : "right",
12509
- showShadow: isLeftBoundary && Boolean(showLeftShadow) || isRightBoundary && Boolean(showRightShadow),
12510
- leftOffset: isStickyLeft ? leftOffset : void 0,
12511
- rightOffset: isStickyRight ? rightBase + addColOffset : void 0,
12512
- colWidth: isSticky ? `${columnsModel.resolveColumnWidth(col.field)}px` : void 0
12722
+ leftCss: isStickyLeft ? leftCss : void 0,
12723
+ rightCss: isStickyRight ? finalRightCss : void 0,
12724
+ colWidth: isSticky ? columnsModel.columnWidthCssVar(col.field) : void 0
12513
12725
  };
12514
12726
  }
12515
- const BOTTOM_BORDER_SHADOW = "inset 0 -1px 0 0 var(--chakra-colors-gray-100)";
12516
12727
  function getCellBoxShadow(isSticky, side) {
12517
12728
  if (!isSticky) return BOTTOM_BORDER_SHADOW;
12518
- 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)"}`;
12729
+ return `${BOTTOM_BORDER_SHADOW}, ${side === "left" ? `inset -1px 0 0 0 ${CELL_BORDER_COLOR}` : `inset 1px 0 0 0 ${CELL_BORDER_COLOR}`}`;
12519
12730
  }
12520
- const DataRow = observer(({ row, columnsModel, showSelection, scrollShadow, onSearchForeignKey, onUploadFile, onOpenFile, onOpenRow, onSelectRow, onDuplicateRow, onDeleteRow }) => {
12731
+ const DataRow = observer(({ row, columnsModel, showSelection, onSearchForeignKey, onUploadFile, onOpenFile, onOpenRow, onSelectRow, onDuplicateRow, onDeleteRow }) => {
12521
12732
  const hasRowActions = Boolean(onOpenRow || onSelectRow || onDuplicateRow || onDeleteRow);
12522
12733
  const selectionWidth = showSelection ? SELECTION_COLUMN_WIDTH : 0;
12523
12734
  const addColumnStickyRight = columnsModel.hasHiddenColumns;
@@ -12532,7 +12743,7 @@ const DataRow = observer(({ row, columnsModel, showSelection, scrollShadow, onSe
12532
12743
  const cellVM = row.getCellVM(col);
12533
12744
  const isFirstColumn = index === 0;
12534
12745
  const showOverlay = isFirstColumn && hasRowActions && !cellVM.isEditing;
12535
- const sticky = computeStickyProps(col, columnsModel, selectionWidth, addColOffset, scrollShadow?.showLeftShadow, scrollShadow?.showRightShadow);
12746
+ const sticky = computeStickyProps(col, columnsModel, selectionWidth, addColOffset);
12536
12747
  return /* @__PURE__ */ jsxs(Box, {
12537
12748
  as: "td",
12538
12749
  width: sticky.colWidth,
@@ -12540,15 +12751,15 @@ const DataRow = observer(({ row, columnsModel, showSelection, scrollShadow, onSe
12540
12751
  maxWidth: sticky.isSticky ? sticky.colWidth : "0",
12541
12752
  overflow: sticky.isBoundary ? "visible" : "hidden",
12542
12753
  borderRight: sticky.isSticky ? void 0 : "1px solid",
12543
- borderColor: sticky.isSticky ? void 0 : "gray.100",
12754
+ borderColor: sticky.isSticky ? void 0 : CELL_BORDER_COLOR,
12544
12755
  p: 0,
12545
12756
  position: sticky.isSticky ? "sticky" : "relative",
12546
- left: sticky.leftOffset !== void 0 ? `${sticky.leftOffset}px` : void 0,
12547
- right: sticky.rightOffset !== void 0 ? `${sticky.rightOffset}px` : void 0,
12757
+ left: sticky.leftCss,
12758
+ right: sticky.rightCss,
12548
12759
  zIndex: sticky.isSticky ? 1 : void 0,
12549
12760
  bg: sticky.isSticky ? "white" : void 0,
12550
12761
  boxShadow: getCellBoxShadow(sticky.isSticky, sticky.boundarySide),
12551
- css: buildCellCss(sticky.isBoundary, sticky.boundarySide, sticky.showShadow, hasRowActions, isFirstColumn),
12762
+ css: buildCellCss(sticky.isBoundary, sticky.boundarySide, hasRowActions, isFirstColumn),
12552
12763
  children: [sticky.isBoundary ? /* @__PURE__ */ jsx(Box, {
12553
12764
  overflow: "hidden",
12554
12765
  children: /* @__PURE__ */ jsx(CellRenderer, {
@@ -12571,23 +12782,22 @@ const DataRow = observer(({ row, columnsModel, showSelection, scrollShadow, onSe
12571
12782
  })]
12572
12783
  }, col.field);
12573
12784
  }),
12574
- /* @__PURE__ */ jsx(Box, {
12785
+ addColumnStickyRight ? /* @__PURE__ */ jsx(Box, {
12575
12786
  as: "td",
12576
12787
  width: "100%",
12577
- p: 0,
12578
- boxShadow: BOTTOM_BORDER_SHADOW
12579
- }),
12580
- addColumnStickyRight && /* @__PURE__ */ jsx(Box, {
12581
- as: "td",
12582
- width: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12583
12788
  minWidth: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12584
- maxWidth: `${ADD_COLUMN_BUTTON_WIDTH}px`,
12585
12789
  p: 0,
12586
12790
  position: "sticky",
12587
12791
  right: 0,
12588
12792
  zIndex: 1,
12589
12793
  bg: "white",
12590
- boxShadow: `${BOTTOM_BORDER_SHADOW}, inset 1px 0 0 0 var(--chakra-colors-gray-100)`
12794
+ boxShadow: BOTTOM_BORDER_SHADOW,
12795
+ css: columnsModel.pinnedRightCount === 0 ? buildAddColumnShadowCss() : void 0
12796
+ }) : /* @__PURE__ */ jsx(Box, {
12797
+ as: "td",
12798
+ width: "100%",
12799
+ p: 0,
12800
+ boxShadow: BOTTOM_BORDER_SHADOW
12591
12801
  })
12592
12802
  ] });
12593
12803
  });
@@ -12713,19 +12923,61 @@ const TableRowComponent = ({ "data-index": index, context, style, children, ...p
12713
12923
 
12714
12924
  //#endregion
12715
12925
  //#region src/table-editor/Table/ui/hooks/useScrollShadow.ts
12926
+ const INITIAL_SHADOW = {
12927
+ left: false,
12928
+ right: false
12929
+ };
12716
12930
  var ScrollShadowModel = class {
12717
- showLeftShadow = false;
12718
- showRightShadow = false;
12719
- constructor() {
12720
- makeAutoObservable(this);
12931
+ _left = false;
12932
+ _right = false;
12933
+ _paused = false;
12934
+ _dirty = false;
12935
+ _onChange = null;
12936
+ get showLeftShadow() {
12937
+ return this._left;
12938
+ }
12939
+ get showRightShadow() {
12940
+ return this._right;
12941
+ }
12942
+ setOnChange(cb) {
12943
+ this._onChange = cb;
12944
+ }
12945
+ pause() {
12946
+ this._paused = true;
12947
+ this._dirty = false;
12948
+ }
12949
+ resume() {
12950
+ this._paused = false;
12951
+ if (this._dirty) {
12952
+ this._dirty = false;
12953
+ this._notify();
12954
+ }
12721
12955
  }
12722
12956
  update(left, right) {
12723
- this.showLeftShadow = left;
12724
- this.showRightShadow = right;
12957
+ if (this._left === left && this._right === right) return;
12958
+ this._left = left;
12959
+ this._right = right;
12960
+ if (this._paused) {
12961
+ this._dirty = true;
12962
+ return;
12963
+ }
12964
+ this._notify();
12725
12965
  }
12726
12966
  reset() {
12727
- this.showLeftShadow = false;
12728
- this.showRightShadow = false;
12967
+ if (!this._left && !this._right) return;
12968
+ this._left = false;
12969
+ this._right = false;
12970
+ if (this._paused) {
12971
+ this._dirty = true;
12972
+ return;
12973
+ }
12974
+ this._notify();
12975
+ }
12976
+ _notify() {
12977
+ if (this._onChange) this._onChange({
12978
+ left: this._left,
12979
+ right: this._right
12980
+ });
12729
12981
  }
12730
12982
  };
12731
12983
  function getScrollElement(target) {
@@ -12736,8 +12988,20 @@ function useScrollShadow() {
12736
12988
  const modelRef = useRef(null);
12737
12989
  if (!modelRef.current) modelRef.current = new ScrollShadowModel();
12738
12990
  const model = modelRef.current;
12991
+ const [shadow, setShadow] = useState(INITIAL_SHADOW);
12992
+ model.setOnChange(setShadow);
12993
+ useEffect(() => {
12994
+ return () => {
12995
+ model.setOnChange(null);
12996
+ };
12997
+ }, [model]);
12998
+ const shadowCssVars = {
12999
+ "--shadow-left-opacity": shadow.left ? "1" : "0",
13000
+ "--shadow-right-opacity": shadow.right ? "1" : "0"
13001
+ };
12739
13002
  const targetRef = useRef(null);
12740
13003
  const rafRef = useRef(0);
13004
+ const roRef = useRef(null);
12741
13005
  const update = useCallback(() => {
12742
13006
  const target = targetRef.current;
12743
13007
  if (!target) return;
@@ -12745,9 +13009,7 @@ function useScrollShadow() {
12745
13009
  if (!el) return;
12746
13010
  const scrollLeft = el.scrollLeft;
12747
13011
  const maxScroll = el.scrollWidth - el.clientWidth;
12748
- runInAction(() => {
12749
- model.update(scrollLeft > 0, maxScroll > 1 && scrollLeft < maxScroll - 1);
12750
- });
13012
+ model.update(scrollLeft > 0, maxScroll > 1 && scrollLeft < maxScroll - 1);
12751
13013
  }, [model]);
12752
13014
  const handleScroll = useCallback(() => {
12753
13015
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
@@ -12756,15 +13018,24 @@ function useScrollShadow() {
12756
13018
  const setScrollerRef = useCallback((el) => {
12757
13019
  const prev = targetRef.current;
12758
13020
  if (prev) prev.removeEventListener("scroll", handleScroll);
13021
+ if (roRef.current) {
13022
+ roRef.current.disconnect();
13023
+ roRef.current = null;
13024
+ }
12759
13025
  if (el) {
12760
13026
  targetRef.current = el;
12761
13027
  el.addEventListener("scroll", handleScroll, { passive: true });
12762
13028
  rafRef.current = requestAnimationFrame(update);
13029
+ const scrollEl = getScrollElement(el);
13030
+ if (scrollEl) {
13031
+ const ro = new ResizeObserver(handleScroll);
13032
+ const table = scrollEl.querySelector("table");
13033
+ if (table) ro.observe(table);
13034
+ roRef.current = ro;
13035
+ }
12763
13036
  } else {
12764
13037
  targetRef.current = null;
12765
- runInAction(() => {
12766
- model.reset();
12767
- });
13038
+ model.reset();
12768
13039
  }
12769
13040
  }, [
12770
13041
  handleScroll,
@@ -12775,11 +13046,13 @@ function useScrollShadow() {
12775
13046
  return () => {
12776
13047
  const prev = targetRef.current;
12777
13048
  if (prev) prev.removeEventListener("scroll", handleScroll);
13049
+ if (roRef.current) roRef.current.disconnect();
12778
13050
  if (rafRef.current) cancelAnimationFrame(rafRef.current);
12779
13051
  };
12780
13052
  }, [handleScroll]);
12781
13053
  return {
12782
- model,
13054
+ shadowCssVars,
13055
+ shadowModel: model,
12783
13056
  setScrollerRef
12784
13057
  };
12785
13058
  }
@@ -12794,7 +13067,28 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12794
13067
  const showSelection = selection.isSelectionMode;
12795
13068
  const allRowIds = rows.map((r) => r.rowId);
12796
13069
  const [deleteConfirm, setDeleteConfirm] = useState(null);
12797
- const { model: scrollShadow, setScrollerRef } = useScrollShadow();
13070
+ const { shadowCssVars, shadowModel, setScrollerRef } = useScrollShadow();
13071
+ const isResizing = columnsModel.isResizing;
13072
+ useEffect(() => {
13073
+ if (isResizing) shadowModel.pause();
13074
+ else shadowModel.resume();
13075
+ }, [isResizing, shadowModel]);
13076
+ const [wrapperEl, setWrapperEl] = useState(null);
13077
+ const wrapperRefCallback = useCallback((el) => {
13078
+ setWrapperEl(el);
13079
+ columnsModel.setWrapperElement(el);
13080
+ }, [columnsModel]);
13081
+ useEffect(() => {
13082
+ if (!useWindowScrollProp || !wrapperEl) return;
13083
+ setScrollerRef(wrapperEl);
13084
+ return () => {
13085
+ setScrollerRef(null);
13086
+ };
13087
+ }, [
13088
+ useWindowScrollProp,
13089
+ wrapperEl,
13090
+ setScrollerRef
13091
+ ]);
12798
13092
  const handleSelectRow = useCallback((rowId) => {
12799
13093
  selection.enterSelectionMode(rowId);
12800
13094
  }, [selection]);
@@ -12892,7 +13186,6 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12892
13186
  row,
12893
13187
  columnsModel,
12894
13188
  showSelection,
12895
- scrollShadow,
12896
13189
  onSearchForeignKey,
12897
13190
  onUploadFile,
12898
13191
  onOpenFile,
@@ -12903,7 +13196,6 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12903
13196
  }), [
12904
13197
  columnsModel,
12905
13198
  showSelection,
12906
- scrollShadow,
12907
13199
  onSearchForeignKey,
12908
13200
  onUploadFile,
12909
13201
  onOpenFile,
@@ -12920,18 +13212,16 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12920
13212
  sortModel,
12921
13213
  filterModel,
12922
13214
  onCopyPath,
12923
- showSelection,
12924
- scrollShadow
13215
+ showSelection
12925
13216
  }), [
12926
13217
  columnsModel,
12927
13218
  sortModel,
12928
13219
  filterModel,
12929
13220
  onCopyPath,
12930
- showSelection,
12931
- scrollShadow
13221
+ showSelection
12932
13222
  ]);
12933
13223
  const virtuosoContext = useMemo(() => ({ rows }), [rows]);
12934
- const totalColumns = columnsModel.visibleColumns.length + (showSelection ? 4 : 3);
13224
+ const totalColumns = columnsModel.visibleColumns.length + (showSelection ? 2 : 1);
12935
13225
  const LoadingMoreFooter = useCallback(() => isLoadingMore ? /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
12936
13226
  colSpan: totalColumns,
12937
13227
  style: {
@@ -12958,10 +13248,17 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12958
13248
  return /* @__PURE__ */ jsxs(CellContextActionsContext.Provider, {
12959
13249
  value: cellFSM ? contextActions : null,
12960
13250
  children: [/* @__PURE__ */ jsxs(Box, {
13251
+ ref: wrapperRefCallback,
12961
13252
  position: "relative",
12962
13253
  height: useWindowScrollProp ? void 0 : "100%",
13254
+ overflowX: "auto",
13255
+ overflowY: useWindowScrollProp ? "clip" : void 0,
12963
13256
  "data-testid": "table-widget",
12964
13257
  onKeyDown: handleKeyDown,
13258
+ style: {
13259
+ ...columnsModel.columnWidthCssVars,
13260
+ ...shadowCssVars
13261
+ },
12965
13262
  children: [rows.length === 0 ? /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx("table", {
12966
13263
  style: {
12967
13264
  width: "max-content",
@@ -12996,7 +13293,7 @@ const TableWidget = observer(({ rows, columnsModel, cellFSM, selection, sortMode
12996
13293
  fixedHeaderContent,
12997
13294
  itemContent,
12998
13295
  components: tableComponents,
12999
- scrollerRef: setScrollerRef
13296
+ scrollerRef: useWindowScrollProp ? void 0 : setScrollerRef
13000
13297
  }), /* @__PURE__ */ jsx(SelectionToolbar, {
13001
13298
  selection,
13002
13299
  allRowIds,
@@ -13289,6 +13586,7 @@ var TableEditorCore = class {
13289
13586
  this.sorts.setOnApply((_sorts) => this._handleSortApply());
13290
13587
  this.viewBadge.setOnSave(() => this._handleViewSave());
13291
13588
  this.viewBadge.setOnRevert(() => this._handleViewRevert());
13589
+ this.cellFSM.setOnReadonlyEditAttempt(this._callbacks.onReadonlyEditAttempt ?? null);
13292
13590
  makeAutoObservable(this, {}, { autoBind: true });
13293
13591
  queueMicrotask(() => void this._bootstrap());
13294
13592
  }
@@ -13316,17 +13614,14 @@ var TableEditorCore = class {
13316
13614
  getViewState() {
13317
13615
  return {
13318
13616
  columns: this.columns.serializeToViewColumns(),
13319
- filters: this.filters.hasActiveFilters ? JSON.stringify(this.filters.serializeRootGroup()) : null,
13617
+ filters: null,
13320
13618
  sorts: this.sorts.serializeToViewSorts(),
13321
- search: this.search.debouncedQuery
13619
+ search: ""
13322
13620
  };
13323
13621
  }
13324
13622
  applyViewState(state) {
13325
13623
  this.columns.applyViewColumns(state.columns);
13326
13624
  this.sorts.applyViewSorts(state.sorts);
13327
- if (state.filters) this.filters.applySnapshot(state.filters);
13328
- else this.filters.clearAll();
13329
- this.search.setQuery(state.search);
13330
13625
  }
13331
13626
  async loadMore() {
13332
13627
  if (!this._hasNextPage || this._isLoadingMore) return;
@@ -13469,12 +13764,9 @@ var TableEditorCore = class {
13469
13764
  this.cellFSM.updateNavigationContext(this.columns.visibleColumns.map((c) => c.field), this.cellFSM.rowIds);
13470
13765
  this._checkViewChanges();
13471
13766
  }
13472
- _handleFilterChange() {
13473
- this._checkViewChanges();
13474
- }
13767
+ _handleFilterChange() {}
13475
13768
  _handleFilterApply() {
13476
13769
  this._reloadRows();
13477
- this._checkViewChanges();
13478
13770
  }
13479
13771
  _handleSortChange() {
13480
13772
  this._checkViewChanges();
@@ -13485,7 +13777,6 @@ var TableEditorCore = class {
13485
13777
  }
13486
13778
  _handleSearch(_query) {
13487
13779
  this._reloadRows();
13488
- this._checkViewChanges();
13489
13780
  }
13490
13781
  async _handleViewSave() {
13491
13782
  try {
@@ -13732,14 +14023,21 @@ const TableEditor = observer(({ viewModel, useWindowScroll }) => {
13732
14023
  return /* @__PURE__ */ jsxs(Box, {
13733
14024
  display: "flex",
13734
14025
  flexDirection: "column",
13735
- height: "100%",
14026
+ height: useWindowScroll ? void 0 : "100%",
14027
+ flex: useWindowScroll ? 1 : void 0,
13736
14028
  children: [
13737
14029
  /* @__PURE__ */ jsxs(Flex, {
13738
14030
  px: 3,
13739
- pt: 2,
13740
- mb: "48px",
14031
+ pt: "32px",
14032
+ pb: "48px",
13741
14033
  alignItems: "center",
13742
14034
  justifyContent: "space-between",
14035
+ ...useWindowScroll && {
14036
+ position: "sticky",
14037
+ top: 0,
14038
+ zIndex: 3,
14039
+ bg: "white"
14040
+ },
13743
14041
  children: [breadcrumbs.length > 0 && /* @__PURE__ */ jsx(Breadcrumbs, {
13744
14042
  segments: breadcrumbs,
13745
14043
  highlightLast: false,
@@ -13793,6 +14091,12 @@ const TableEditor = observer(({ viewModel, useWindowScroll }) => {
13793
14091
  px: 3,
13794
14092
  py: 2,
13795
14093
  justifyContent: "space-between",
14094
+ ...useWindowScroll && {
14095
+ position: "sticky",
14096
+ bottom: 0,
14097
+ bg: "white",
14098
+ zIndex: 3
14099
+ },
13796
14100
  children: [/* @__PURE__ */ jsx(RowCountWidget, { model: viewModel.rowCount }), /* @__PURE__ */ jsx(ViewSettingsBadge, { model: viewModel.viewBadge })]
13797
14101
  })
13798
14102
  ]