socialcalc-ai 1.0.2 → 1.0.3

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/README.md CHANGED
@@ -532,6 +532,11 @@ import {
532
532
 
533
533
  ## 📦 Release Notes
534
534
 
535
+ ### v1.0.3
536
+ - **Node.js ESM & Runtime Compatibility**: Enhanced UMD root resolution across all core modules to support `globalThis`, eliminating undefined root errors in Node.js ES module loaders.
537
+ - **TypeScript Declarations**: Bundled official type declarations (`index.d.ts`) covering all core exports, plugin modules, and React/Ionic UI modal components.
538
+ - **Packaging Refinements**: Added `.npmignore` to exclude test files, scratch scripts, and developer metadata from distributed tarballs.
539
+
535
540
  ### v1.0.2
536
541
  - **Cell Edit Modal Fix**: Fixed cell click interceptor and mouse delegates so clicking any cell reliably triggers the `CellEditModal` bottom sheet.
537
542
  - **Scroll Distortion Fix**: Resolved table border collapse and column misalignment when scrolling past multi-row (`rowspan`) and multi-column (`colspan`) blocks.
package/core/constants.js CHANGED
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcConstants = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Initialize SocialCalc namespace if not exists
17
17
  var SocialCalc;
package/core/core.js CHANGED
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcCore = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Get SocialCalc namespace
17
17
  var SocialCalc;
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcFormatNumber = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Get SocialCalc namespace
17
17
  var SocialCalc;
package/core/formula.js CHANGED
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcFormula = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Get SocialCalc namespace
17
17
  var SocialCalc;
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcSpreadsheetControl = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Get SocialCalc namespace
17
17
  var SocialCalc;
@@ -11,7 +11,7 @@
11
11
  } else {
12
12
  root.SocialCalcTableEditor = factory();
13
13
  }
14
- })(typeof self !== "undefined" ? self : this, function () {
14
+ })(typeof globalThis !== "undefined" ? globalThis : (typeof self !== "undefined" ? self : this), function () {
15
15
 
16
16
  // Get SocialCalc namespace
17
17
  var SocialCalc;
@@ -3783,8 +3783,23 @@
3783
3783
  return;
3784
3784
  }
3785
3785
 
3786
- // Do a gross move and render
3786
+ // Fast micro-DOM update for 1-4 row vertical scrolls (smooth touch & momentum)
3787
+ if (hamount == 0 && Math.abs(vamount) >= 1 && Math.abs(vamount) <= 4) {
3788
+ var stepDir = vamount > 0 ? 1 : -1;
3789
+ var steps = Math.abs(vamount);
3790
+ for (var s = 0; s < steps; s++) {
3791
+ if (stepDir > 0) {
3792
+ editor.ScrollTableUpOneRow();
3793
+ } else {
3794
+ editor.ScrollTableDownOneRow();
3795
+ }
3796
+ }
3797
+ if (editor.ecell) editor.SetECellHeaders("selected");
3798
+ editor.SchedulePositionCalculations();
3799
+ return;
3800
+ }
3787
3801
 
3802
+ // Do a gross move and render for large jumps or horizontal scrolls
3788
3803
  if (vamount != 0 || hamount != 0) {
3789
3804
  context.rowpanes[vplen - 1].first += vamount;
3790
3805
  context.rowpanes[vplen - 1].last += vamount;
@@ -3873,13 +3888,252 @@
3873
3888
  };
3874
3889
 
3875
3890
  SocialCalc.ScrollTableUpOneRow = function (editor) {
3876
- SocialCalc.ScrollRelativeBoth(editor, 1, 0);
3877
- return editor.fullgrid;
3891
+ var toprow,
3892
+ rowpane,
3893
+ rownum,
3894
+ colnum,
3895
+ colpane,
3896
+ cell,
3897
+ oldrownum,
3898
+ maxspan,
3899
+ newbottomrow,
3900
+ newrow,
3901
+ oldchild,
3902
+ bottomrownum;
3903
+ var rowneedsrefresh = {};
3904
+
3905
+ var context = editor.context;
3906
+ var sheetobj = context.sheetobj;
3907
+ var tableobj = editor.fullgrid;
3908
+
3909
+ var tbodyobj = tableobj.lastChild;
3910
+ if (!tbodyobj || !tbodyobj.childNodes) return editor.fullgrid;
3911
+
3912
+ toprow = context.showRCHeaders ? 2 : 1;
3913
+ for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) {
3914
+ toprow +=
3915
+ context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row
3916
+ }
3917
+
3918
+ if (toprow >= tbodyobj.childNodes.length) return editor.fullgrid;
3919
+ tbodyobj.removeChild(tbodyobj.childNodes[toprow]);
3920
+
3921
+ context.rowpanes[rowpane].first++;
3922
+ context.rowpanes[rowpane].last++;
3923
+ editor.FitToEditTable();
3924
+ context.CalculateColWidthData();
3925
+
3926
+ newbottomrow = context.RenderRow(context.rowpanes[rowpane].last, rowpane);
3927
+ if (newbottomrow) {
3928
+ tbodyobj.appendChild(newbottomrow);
3929
+ }
3930
+
3931
+ // If scrolled off a row with starting rowspans OR continuation spans, refresh rows for largest span
3932
+ var maxrowspan = 1;
3933
+ oldrownum = context.rowpanes[rowpane].first - 1;
3934
+
3935
+ for (colpane = 0; colpane < context.colpanes.length; colpane++) {
3936
+ for (
3937
+ colnum = context.colpanes[colpane].first;
3938
+ colnum <= context.colpanes[colpane].last;
3939
+ colnum++
3940
+ ) {
3941
+ var coord = SocialCalc.crToCoord(colnum, oldrownum);
3942
+ if (context.cellskip && context.cellskip[coord]) {
3943
+ // Cell was covered by a span starting above oldrownum
3944
+ var originCoord = context.cellskip[coord];
3945
+ var originCell = sheetobj.cells[originCoord];
3946
+ var originCR = context.coordToCR ? context.coordToCR[originCoord] : null;
3947
+ if (originCell && originCR) {
3948
+ var remainingSpan = (originCR.row - 0 + originCell.rowspan) - oldrownum;
3949
+ if (remainingSpan > maxrowspan) maxrowspan = remainingSpan;
3950
+ }
3951
+ continue;
3952
+ }
3953
+ cell = sheetobj.cells[coord];
3954
+ if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan;
3955
+ }
3956
+ }
3957
+
3958
+ if (maxrowspan > 1) {
3959
+ for (rownum = 1; rownum < maxrowspan; rownum++) {
3960
+ var targetRow = rownum + oldrownum;
3961
+ if (targetRow > context.rowpanes[rowpane].last) break;
3962
+ newrow = context.RenderRow(targetRow, rowpane);
3963
+ oldchild = tbodyobj.childNodes[toprow + rownum - 1];
3964
+ if (oldchild && newrow) {
3965
+ tbodyobj.replaceChild(newrow, oldchild);
3966
+ }
3967
+ }
3968
+ }
3969
+
3970
+ // If added bottom row includes rowspans from above, update the size of those to include new row
3971
+ bottomrownum = context.rowpanes[rowpane].last;
3972
+
3973
+ for (colpane = 0; colpane < context.colpanes.length; colpane++) {
3974
+ for (
3975
+ colnum = context.colpanes[colpane].first;
3976
+ colnum <= context.colpanes[colpane].last;
3977
+ colnum++
3978
+ ) {
3979
+ if (!context.cellskip) continue;
3980
+ coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)];
3981
+ if (!coord) continue;
3982
+ rownum = context.coordToCR ? (context.coordToCR[coord].row - 0) : null;
3983
+ if (
3984
+ rownum === null ||
3985
+ rownum == context.rowpanes[rowpane].last ||
3986
+ rownum < context.rowpanes[rowpane].first
3987
+ ) {
3988
+ continue;
3989
+ }
3990
+ cell = sheetobj.cells[coord];
3991
+ if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true;
3992
+ }
3993
+ }
3994
+
3995
+ for (rownum in rowneedsrefresh) {
3996
+ newrow = context.RenderRow(rownum - 0, rowpane);
3997
+ oldchild =
3998
+ tbodyobj.childNodes[
3999
+ toprow + (rownum - context.rowpanes[rowpane].first)
4000
+ ];
4001
+ if (oldchild && newrow) {
4002
+ tbodyobj.replaceChild(newrow, oldchild);
4003
+ }
4004
+ }
4005
+
4006
+ return tableobj;
3878
4007
  };
3879
4008
 
3880
4009
  SocialCalc.ScrollTableDownOneRow = function (editor) {
3881
- SocialCalc.ScrollRelativeBoth(editor, -1, 0);
3882
- return editor.fullgrid;
4010
+ var toprow,
4011
+ rowpane,
4012
+ rownum,
4013
+ colnum,
4014
+ colpane,
4015
+ cell,
4016
+ newrownum,
4017
+ maxspan,
4018
+ newbottomrow,
4019
+ newrow,
4020
+ oldchild,
4021
+ bottomrownum,
4022
+ maxrowspan,
4023
+ coord;
4024
+ var rowneedsrefresh = {};
4025
+
4026
+ var context = editor.context;
4027
+ var sheetobj = context.sheetobj;
4028
+ var tableobj = editor.fullgrid;
4029
+
4030
+ var tbodyobj = tableobj.lastChild;
4031
+ if (!tbodyobj || !tbodyobj.childNodes) return editor.fullgrid;
4032
+
4033
+ toprow = context.showRCHeaders ? 2 : 1;
4034
+ for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) {
4035
+ toprow +=
4036
+ context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row
4037
+ }
4038
+
4039
+ var removeIdx = toprow + (context.rowpanes[rowpane].last - context.rowpanes[rowpane].first);
4040
+ if (removeIdx < tbodyobj.childNodes.length) {
4041
+ tbodyobj.removeChild(tbodyobj.childNodes[removeIdx]);
4042
+ }
4043
+
4044
+ context.rowpanes[rowpane].first--;
4045
+ context.rowpanes[rowpane].last--;
4046
+ editor.FitToEditTable();
4047
+ context.CalculateColWidthData();
4048
+
4049
+ newrow = context.RenderRow(context.rowpanes[rowpane].first, rowpane);
4050
+ if (toprow < tbodyobj.childNodes.length) {
4051
+ tbodyobj.insertBefore(newrow, tbodyobj.childNodes[toprow]);
4052
+ } else {
4053
+ tbodyobj.appendChild(newrow);
4054
+ }
4055
+
4056
+ // If inserted a row with starting rowspans or continuation spans, refresh rows for largest span
4057
+ maxrowspan = 1;
4058
+ newrownum = context.rowpanes[rowpane].first;
4059
+
4060
+ for (colpane = 0; colpane < context.colpanes.length; colpane++) {
4061
+ for (
4062
+ colnum = context.colpanes[colpane].first;
4063
+ colnum <= context.colpanes[colpane].last;
4064
+ colnum++
4065
+ ) {
4066
+ coord = SocialCalc.crToCoord(colnum, newrownum);
4067
+ if (context.cellskip && context.cellskip[coord]) {
4068
+ var originCoord = context.cellskip[coord];
4069
+ var originCell = sheetobj.cells[originCoord];
4070
+ var originCR = context.coordToCR ? context.coordToCR[originCoord] : null;
4071
+ if (originCell && originCR) {
4072
+ var remaining = (originCR.row - 0 + originCell.rowspan) - newrownum;
4073
+ if (remaining > maxrowspan) maxrowspan = remaining;
4074
+ }
4075
+ continue;
4076
+ }
4077
+ cell = sheetobj.cells[coord];
4078
+ if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan;
4079
+ }
4080
+ }
4081
+
4082
+ if (maxrowspan > 1) {
4083
+ for (rownum = 1; rownum < maxrowspan; rownum++) {
4084
+ var targetRow = rownum + newrownum;
4085
+ if (targetRow > context.rowpanes[rowpane].last) break;
4086
+ newrow = context.RenderRow(targetRow, rowpane);
4087
+ oldchild = tbodyobj.childNodes[toprow + rownum];
4088
+ if (oldchild && newrow) {
4089
+ tbodyobj.replaceChild(newrow, oldchild);
4090
+ }
4091
+ }
4092
+ }
4093
+
4094
+ // If last row now includes rowspans or rowspans from above, update the size of those to remove deleted row
4095
+ bottomrownum = context.rowpanes[rowpane].last;
4096
+
4097
+ for (colpane = 0; colpane < context.colpanes.length; colpane++) {
4098
+ for (
4099
+ colnum = context.colpanes[colpane].first;
4100
+ colnum <= context.colpanes[colpane].last;
4101
+ colnum++
4102
+ ) {
4103
+ coord = SocialCalc.crToCoord(colnum, bottomrownum);
4104
+ cell = sheetobj.cells[coord];
4105
+ if (cell && cell.rowspan > 1) {
4106
+ rowneedsrefresh[bottomrownum] = true;
4107
+ continue;
4108
+ }
4109
+ if (!context.cellskip) continue;
4110
+ coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)];
4111
+ if (!coord) continue;
4112
+ rownum = context.coordToCR ? (context.coordToCR[coord].row - 0) : null;
4113
+ if (
4114
+ rownum === null ||
4115
+ rownum == bottomrownum ||
4116
+ rownum < context.rowpanes[rowpane].first
4117
+ ) {
4118
+ continue;
4119
+ }
4120
+ cell = sheetobj.cells[coord];
4121
+ if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true;
4122
+ }
4123
+ }
4124
+
4125
+ for (rownum in rowneedsrefresh) {
4126
+ newrow = context.RenderRow(rownum - 0, rowpane);
4127
+ oldchild =
4128
+ tbodyobj.childNodes[
4129
+ toprow + (rownum - context.rowpanes[rowpane].first)
4130
+ ];
4131
+ if (oldchild && newrow) {
4132
+ tbodyobj.replaceChild(newrow, oldchild);
4133
+ }
4134
+ }
4135
+
4136
+ return tableobj;
3883
4137
  };
3884
4138
 
3885
4139
  // *************************************
package/modules/init.js CHANGED
@@ -40,6 +40,7 @@ export function initializeApp(data) {
40
40
  }
41
41
 
42
42
  spreadsheet.InitializeSpreadsheetControl(tableeditor, 0, 0, 0);
43
+ SocialCalc._activeEditor = spreadsheet.editor;
43
44
  spreadsheet.ExecuteCommand("redisplay", "");
44
45
 
45
46
  // Determine which workbook control id exists (string passed to constructor)
@@ -244,8 +244,15 @@ export function setupMouseListener() {
244
244
  while (headerTd && headerTd.tagName !== "TD" && headerTd.tagName !== "TH" && headerTd !== document.body) {
245
245
  headerTd = headerTd.parentNode;
246
246
  }
247
- if (!isColHeader && headerTd && headerTd.className && typeof headerTd.className === "string" && headerTd.className.indexOf("colname") !== -1) {
248
- isColHeader = true;
247
+ if (!isColHeader && headerTd) {
248
+ if (headerTd.className && typeof headerTd.className === "string" && headerTd.className.indexOf("colname") !== -1) {
249
+ isColHeader = true;
250
+ } else if (headerTd.tagName === "TD" || headerTd.tagName === "TH") {
251
+ var headerTxt = (headerTd.textContent || "").trim().toUpperCase();
252
+ if (/^[A-Z]{1,2}$/.test(headerTxt)) {
253
+ isColHeader = true;
254
+ }
255
+ }
249
256
  }
250
257
 
251
258
  if (isRowColHeadersEnabled() && isColHeader) {
@@ -31,6 +31,9 @@ export function getActiveEditor() {
31
31
  if (typeof window !== "undefined" && window.SocialCalc) {
32
32
  SocialCalc = window.SocialCalc;
33
33
  }
34
+ if (SocialCalc && SocialCalc._activeEditor) {
35
+ return SocialCalc._activeEditor;
36
+ }
34
37
  if (SocialCalc && SocialCalc.GetCurrentWorkBookControl) {
35
38
  const ctrl = SocialCalc.GetCurrentWorkBookControl();
36
39
  if (ctrl && ctrl.workbook && ctrl.workbook.spreadsheet && ctrl.workbook.spreadsheet.editor) {
@@ -114,14 +114,17 @@ export function updateColumnResizeHandlePosition() {
114
114
 
115
115
  // 1. Try finding exact header cell from DOM for pixel-perfect coordinates
116
116
  if (editor.fullgrid) {
117
- const cells = editor.fullgrid.querySelectorAll("td");
117
+ const cells = editor.fullgrid.querySelectorAll("td, th");
118
118
  for (let i = 0; i < cells.length; i++) {
119
119
  const c = cells[i];
120
- if (c.textContent && c.textContent.trim() === colLetter && c.className && typeof c.className === "string" && c.className.indexOf("colname") !== -1) {
120
+ const txt = c.textContent ? c.textContent.trim() : "";
121
+ if (txt === colLetter) {
121
122
  const rect = c.getBoundingClientRect();
122
- colRight = Math.round(rect.right);
123
- headerBottom = Math.round(rect.bottom);
124
- break;
123
+ if (rect.width > 0 && rect.height > 0) {
124
+ colRight = Math.round(rect.right);
125
+ headerBottom = Math.round(rect.bottom);
126
+ break;
127
+ }
125
128
  }
126
129
  }
127
130
  }
@@ -151,8 +154,24 @@ export function updateColumnResizeHandlePosition() {
151
154
  handle = document.createElement("div");
152
155
  handle.id = "sc-col-resize-corner-handle";
153
156
  handle.className = "sc-col-resize-corner-handle";
157
+ handle.style.position = "fixed";
158
+ handle.style.zIndex = "99999";
159
+ handle.style.width = "24px";
160
+ handle.style.height = "24px";
161
+ handle.style.backgroundColor = "#2563eb";
162
+ handle.style.color = "#ffffff";
163
+ handle.style.borderRadius = "50%";
164
+ handle.style.border = "2px solid #ffffff";
165
+ handle.style.boxShadow = "0 2px 8px rgba(0,0,0,0.35)";
166
+ handle.style.display = "flex";
167
+ handle.style.alignItems = "center";
168
+ handle.style.justifyContent = "center";
169
+ handle.style.cursor = "col-resize";
170
+ handle.style.touchAction = "none";
171
+ handle.style.transform = "translate(-50%, -50%)";
172
+ handle.style.pointerEvents = "auto";
154
173
  handle.innerHTML = `
155
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
174
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
156
175
  <polyline points="8 4 4 8 8 12"></polyline>
157
176
  <polyline points="16 4 20 8 16 12"></polyline>
158
177
  <line x1="4" y1="8" x2="20" y2="8"></line>
@@ -497,8 +516,16 @@ export function isRowColHeadersEnabled() {
497
516
 
498
517
  // Window scroll and resize listeners to reposition the handle
499
518
  if (typeof window !== "undefined") {
519
+ SocialCalc = window.SocialCalc = window.SocialCalc || {};
520
+ SocialCalc.selectColumn = selectColumn;
521
+ SocialCalc.selectRow = selectRow;
522
+ SocialCalc.showColumnResizeHandle = showColumnResizeHandle;
523
+ SocialCalc.hideColumnResizeHandle = hideColumnResizeHandle;
524
+ SocialCalc.updateColumnResizeHandlePosition = updateColumnResizeHandlePosition;
525
+
500
526
  window.addEventListener("resize", updateColumnResizeHandlePosition);
501
527
  window.addEventListener("socialcalc:horizontal-scroll", updateColumnResizeHandlePosition);
528
+ window.addEventListener("socialcalc:scroll", updateColumnResizeHandlePosition);
502
529
  }
503
530
 
504
531
  // Register as a plugin in the plugin manager
@@ -108,8 +108,8 @@ function getEditor() {
108
108
 
109
109
  function editorCanScroll(editor) {
110
110
  if (!editor) return false;
111
- if (editor.busy) return false;
112
- if (editor.state !== "start") return false;
111
+ // Do not block touch scrolling during micro-renders or position steps
112
+ if (editor.busy && editor.busy !== true && editor.busy !== "render") return false;
113
113
  return true;
114
114
  }
115
115
 
@@ -175,14 +175,16 @@ function doScroll(editor, rowDelta, colDelta) {
175
175
  // ─── Replacement Touch Handlers ──────────────────────────────────────────────
176
176
 
177
177
  function handleTouchStart(event) {
178
- // Only handle single-finger touches
179
- if (event.touches.length !== 1) return;
180
-
181
- const touch = event.touches[0];
182
-
183
178
  // Cancel any running momentum
184
179
  cancelMomentum();
185
180
 
181
+ // Prevent default browser multi-touch behaviors (pinch-to-zoom, 2-finger page drag)
182
+ if (event.touches.length > 1) {
183
+ if (event.cancelable) event.preventDefault();
184
+ }
185
+
186
+ const touch = event.touches[0];
187
+
186
188
  _gesture.active = true;
187
189
  _gesture.isScrolling = false;
188
190
  _gesture.startX = touch.pageX;
@@ -193,41 +195,36 @@ function handleTouchStart(event) {
193
195
  _gesture.colDebt = 0;
194
196
  _gesture.velocitySamples = [];
195
197
  _gesture.startTime = performance.now();
196
-
197
- // Do NOT call preventDefault here — we decide in touchmove whether this
198
- // is a scroll or a tap. If it's a tap we want the event to flow normally.
198
+ _gesture.initialTouchCount = event.touches.length;
199
199
  }
200
200
 
201
201
  function handleTouchMove(event) {
202
- if (!_gesture.active) return;
203
- if (event.touches.length !== 1) return;
202
+ // CRITICAL: Always prevent default native browser scroll / container pan on the sheet
203
+ // so the entire outer sheet box is NEVER dragged or moved in any direction
204
+ if (event.cancelable) event.preventDefault();
205
+ event.stopPropagation();
206
+
207
+ if (!_gesture.active || !event.touches.length) return;
204
208
 
205
209
  const touch = event.touches[0];
206
- const dx = _gesture.lastX - touch.pageX; // positive = finger moved left (scroll right)
207
210
  const dy = _gesture.lastY - touch.pageY; // positive = finger moved up (scroll down)
208
211
 
209
212
  // Check deadzone (only once per gesture)
210
213
  if (!_gesture.isScrolling) {
211
- const totalDX = Math.abs(touch.pageX - _gesture.startX);
212
214
  const totalDY = Math.abs(touch.pageY - _gesture.startY);
213
- if (totalDX < TOUCH_SCROLL_CONFIG.scrollDeadzone &&
214
- totalDY < TOUCH_SCROLL_CONFIG.scrollDeadzone) {
215
+ if (totalDY < TOUCH_SCROLL_CONFIG.scrollDeadzone) {
215
216
  return; // still within deadzone — not scrolling yet
216
217
  }
217
218
  _gesture.isScrolling = true;
218
219
  }
219
220
 
220
- // Prevent native scroll / browser actions once we own the gesture
221
- if (event.cancelable) event.preventDefault();
222
- event.stopPropagation();
223
-
224
- // Track velocity (raw pixels)
221
+ // Track velocity (raw pixels, vertical only)
225
222
  addVelocitySample(0, dy);
226
223
 
227
- // Accumulate fractional row debt (vertical only)
224
+ // Accumulate fractional row debt (strictly vertical)
228
225
  _gesture.rowDebt += dy / TOUCH_SCROLL_CONFIG.pixelsPerRow;
229
226
 
230
- // Scroll integer rows (strictly vertical)
227
+ // Scroll integer rows (strictly vertical - colStep is always 0)
231
228
  const rowStep = Math.trunc(_gesture.rowDebt);
232
229
 
233
230
  if (rowStep !== 0) {
@@ -244,12 +241,21 @@ function handleTouchMove(event) {
244
241
 
245
242
  function handleTouchEnd(event) {
246
243
  if (!_gesture.active) return;
244
+
245
+ // If fingers still remain on screen, don't end gesture yet
246
+ if (event.touches && event.touches.length > 0) {
247
+ const touch = event.touches[0];
248
+ _gesture.lastX = touch.pageX;
249
+ _gesture.lastY = touch.pageY;
250
+ return;
251
+ }
252
+
247
253
  _gesture.active = false;
248
254
 
249
255
  const elapsed = performance.now() - _gesture.startTime;
250
256
 
251
- // If we never crossed the deadzone, treat as a tap
252
- if (!_gesture.isScrolling) {
257
+ // If we never crossed the deadzone and started with exactly 1 finger, treat as a tap
258
+ if (!_gesture.isScrolling && _gesture.initialTouchCount === 1) {
253
259
  handleTap(event, elapsed);
254
260
  return;
255
261
  }
@@ -308,6 +314,51 @@ function handleTouchCancel() {
308
314
  function handleTap(event, elapsed) {
309
315
  const now = performance.now();
310
316
  const touchinfo = SocialCalc.TouchInfo;
317
+ const touch = (event.changedTouches && event.changedTouches[0]) || (event.touches && event.touches[0]);
318
+
319
+ // Check if user tapped a column header, row header, or resize handle
320
+ if (touch && typeof document !== "undefined") {
321
+ const tappedEl = document.elementFromPoint(touch.clientX, touch.clientY);
322
+ if (tappedEl) {
323
+ // If tapped the resize handle itself, let drag handler take care of it
324
+ if (tappedEl.id === "sc-col-resize-corner-handle" || (tappedEl.closest && tappedEl.closest("#sc-col-resize-corner-handle"))) {
325
+ return;
326
+ }
327
+
328
+ // Check for Column or Row Header Cell
329
+ let headerTd = tappedEl;
330
+ while (headerTd && headerTd.tagName !== "TD" && headerTd.tagName !== "TH" && headerTd !== document.body) {
331
+ headerTd = headerTd.parentNode;
332
+ }
333
+ if (headerTd && (headerTd.tagName === "TD" || headerTd.tagName === "TH")) {
334
+ const txt = headerTd.textContent ? headerTd.textContent.trim().toUpperCase() : "";
335
+ if (/^[A-Z]{1,2}$/.test(txt)) {
336
+ let colNum = 0;
337
+ for (let i = 0; i < txt.length; i++) {
338
+ colNum = colNum * 26 + (txt.charCodeAt(i) - 64);
339
+ }
340
+ if (colNum > 0 && SocialCalc.selectColumn) {
341
+ SocialCalc.selectColumn(colNum);
342
+ return;
343
+ }
344
+ } else if (/^[0-9]+$/.test(txt)) {
345
+ const rowNum = parseInt(txt, 10);
346
+ if (rowNum > 0 && SocialCalc.selectRow) {
347
+ SocialCalc.selectRow(rowNum);
348
+ window.dispatchEvent(new CustomEvent("socialcalc:row-header-click", {
349
+ detail: {
350
+ rowNum: rowNum,
351
+ clientX: touch.clientX,
352
+ clientY: touch.clientY
353
+ }
354
+ }));
355
+ return;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+
311
362
  const wobj = SocialCalc.FindTouchElement
312
363
  ? SocialCalc.FindTouchElement(event)
313
364
  : null;
@@ -387,14 +438,26 @@ export function enableTouchScroll() {
387
438
  el.removeEventListener("touchstart", _origProcessTouchStart, true);
388
439
  el.removeEventListener("touchmove", _origProcessTouchMove, true);
389
440
 
390
- // Add new listeners — touchmove must be non-passive so we can preventDefault
391
- el.addEventListener("touchstart", handleTouchStart, { passive: true });
441
+ // Add new listeners — both touchstart and touchmove non-passive so we can preventDefault
442
+ el.addEventListener("touchstart", handleTouchStart, { passive: false });
392
443
  el.addEventListener("touchmove", handleTouchMove, { passive: false });
393
444
  el.addEventListener("touchend", handleTouchEnd, { passive: true });
394
445
  el.addEventListener("touchcancel", handleTouchCancel, { passive: true });
395
446
  }
396
447
  }
397
448
 
449
+ // Lock outer containers against native touch dragging / pan
450
+ const outerCandidates = ["container", "tableeditor", "te_griddiv"];
451
+ for (const cid of outerCandidates) {
452
+ const cel = document.getElementById(cid);
453
+ if (cel && !cel.__touchLocked) {
454
+ cel.__touchLocked = true;
455
+ cel.addEventListener("touchmove", (e) => {
456
+ if (e.cancelable) e.preventDefault();
457
+ }, { passive: false });
458
+ }
459
+ }
460
+
398
461
  _enabled = true;
399
462
  console.log("[TouchScroll] Smooth touch scrolling enabled");
400
463
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialcalc-ai",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Modernized, modular spreadsheet calculation engine and React/Ionic UI suite with AI formula and template capabilities.",
5
5
  "type": "module",
6
6
  "main": "index.js",