socialcalc-ai 1.0.0 → 1.0.2
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 +459 -178
- package/core/core.js +28 -8
- package/core/spreadsheet-control.js +5 -0
- package/core/table-editor.js +23 -226
- package/modules/init.js +3 -1
- package/modules/listeners.js +49 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,55 +1,243 @@
|
|
|
1
|
-
#
|
|
1
|
+
# socialcalc-ai
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/socialcalc-ai)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://nodejs.org/api/esm.html)
|
|
2
7
|
|
|
3
8
|
> **A modernized, modularized, high-performance spreadsheet engine with ES6 module exports, React/Ionic UI components, AI template capabilities, and an extensible plugin system.**
|
|
4
9
|
|
|
10
|
+
Derived from Dan Bricklin's proven, industrial-strength **SocialCalc** engine, `socialcalc-ai` has been re-architected for modern web, mobile, and AI-driven applications. It replaces legacy global variables with clean ES modules, provides full TypeScript definitions, adds smooth mobile touch physics, and includes a full suite of modern React/Ionic UI controls.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 📑 Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Features](#-features)
|
|
17
|
+
- [Installation](#-installation)
|
|
18
|
+
- [Quick Start in React / Ionic](#-quick-start-in-react--ionic)
|
|
19
|
+
- [Quick Start in Vanilla JavaScript](#-quick-start-in-vanilla-javascript)
|
|
20
|
+
- [Architecture Overview](#-architecture-overview)
|
|
21
|
+
- [UI Components](#-ui-components)
|
|
22
|
+
- [CellEditModal](#1-celleditmodal)
|
|
23
|
+
- [HorizontalScrollBar](#2-horizontalscrollbar)
|
|
24
|
+
- [RowActionPopover](#3-rowactionpopover)
|
|
25
|
+
- [EditableCellsModal](#4-editablecellsmodal)
|
|
26
|
+
- [DemoVideosModal](#5-demovideosmodal)
|
|
27
|
+
- [Plugins & Modules API](#-plugins--modules-api)
|
|
28
|
+
- [Row & Column Headers](#1-row--column-headers-modulesrow-col-headersjs)
|
|
29
|
+
- [Grid Lines](#2-grid-lines-modulesgrid-linesjs)
|
|
30
|
+
- [Smooth Touch Scroll](#3-smooth-touch-scroll-modulestouch-scrolljs)
|
|
31
|
+
- [Horizontal Scroll](#4-horizontal-scroll-moduleshorizontal-scrolljs)
|
|
32
|
+
- [Editable Cells & Template Locks](#5-editable-cells--template-locks-moduleseditable-cellsjs)
|
|
33
|
+
- [Plugin Manager](#6-plugin-manager-modulesplugin-managerjs)
|
|
34
|
+
- [History & Undo/Redo](#7-history-undo--redo-moduleshistoryjs)
|
|
35
|
+
- [Exporters & Sheets](#8-exporters--sheets-modulesexportersjs-modulessheetsjs)
|
|
36
|
+
- [Invoice Utilities](#9-invoice-utilities-modulesinvoicejs)
|
|
37
|
+
- [Event-Driven Architecture](#-event-driven-architecture)
|
|
38
|
+
- [AI & MultiSheet Calc (MSC) Integration](#-ai--multisheet-calc-msc-integration)
|
|
39
|
+
- [TypeScript Support](#-typescript-support)
|
|
40
|
+
- [Browser & Framework Compatibility](#-browser--framework-compatibility)
|
|
41
|
+
- [License & Acknowledgments](#-license--acknowledgments)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## ✨ Features
|
|
46
|
+
|
|
47
|
+
- **⚡ Modern ES6 Architecture**: Pure ES module exports replacing legacy global namespace pollution. Works with Vite, Next.js, Webpack, Rollup, and ES2020+ runtimes.
|
|
48
|
+
- **📱 Touch & Mobile Optimized**: Smooth momentum physics and inertia swipe scrolling without screen jumping or layout stutter.
|
|
49
|
+
- **🎨 Modern React & Ionic UI Suite**:
|
|
50
|
+
- Full-featured **CellEditModal** (Formulas, rich formatting, custom color palettes, borders, and compressed image inserts).
|
|
51
|
+
- Smooth **HorizontalScrollBar** with live column indicators and drag navigation.
|
|
52
|
+
- Interactive **RowActionPopover** for dynamic row insertion and deletion.
|
|
53
|
+
- **EditableCellsModal** to inspect and manage cell locks.
|
|
54
|
+
- **DemoVideosModal** with an interactive formula cheat sheet.
|
|
55
|
+
- **🔌 Pluggable Architecture**: Dynamically enable, disable, or configure spreadsheet subsystems (headers, gridlines, momentum scroll, locks).
|
|
56
|
+
- **📐 Universal Calculation Engine**: Industrial-grade formula AST evaluator supporting standard math, statistics, string, logic, and financial functions.
|
|
57
|
+
- **🤖 AI-Ready MultiSheet Calc (MSC)**: Serialize and deserialize full spreadsheet workbooks as structured JSON or MSC strings, ideal for LLM spreadsheet generation pipelines.
|
|
58
|
+
- **🛡 Full TypeScript Definitions**: First-class `index.d.ts` definitions included out of the box.
|
|
59
|
+
|
|
5
60
|
---
|
|
6
61
|
|
|
7
62
|
## 📦 Installation
|
|
8
63
|
|
|
64
|
+
Install `socialcalc-ai` via npm:
|
|
65
|
+
|
|
9
66
|
```bash
|
|
10
67
|
npm install socialcalc-ai
|
|
11
68
|
```
|
|
12
69
|
|
|
13
|
-
Or
|
|
70
|
+
Or using Yarn / pnpm:
|
|
71
|
+
|
|
14
72
|
```bash
|
|
73
|
+
# Using Yarn
|
|
15
74
|
yarn add socialcalc-ai
|
|
75
|
+
|
|
76
|
+
# Using pnpm
|
|
16
77
|
pnpm add socialcalc-ai
|
|
17
78
|
```
|
|
18
79
|
|
|
80
|
+
### Peer Dependencies
|
|
81
|
+
|
|
82
|
+
If you plan to use the pre-built React / Ionic components (`CellEditModal`, `HorizontalScrollBar`, etc.), ensure the peer dependencies are installed:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm install react react-dom @ionic/react ionicons
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
*(Note: Peer dependencies are optional if you only consume the core calculation engine in headless Node.js or Vanilla JS environments).*
|
|
89
|
+
|
|
19
90
|
---
|
|
20
91
|
|
|
21
|
-
##
|
|
92
|
+
## 🚀 Quick Start in React / Ionic
|
|
93
|
+
|
|
94
|
+
Below is a complete, minimal working example showing how to mount the spreadsheet engine and hook up the interactive React UI suite:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import React, { useEffect, useState } from "react";
|
|
98
|
+
import * as AppGeneral from "socialcalc-ai";
|
|
99
|
+
import {
|
|
100
|
+
CellEditModal,
|
|
101
|
+
HorizontalScrollBar,
|
|
102
|
+
RowActionPopover,
|
|
103
|
+
EditableCellsModal,
|
|
104
|
+
} from "socialcalc-ai";
|
|
105
|
+
|
|
106
|
+
// Optional: Ionic base styles if using Ionic framework
|
|
107
|
+
import "@ionic/react/css/core.css";
|
|
22
108
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
109
|
+
export const SpreadsheetEditor: React.FC = () => {
|
|
110
|
+
const [cellEditData, setCellEditData] = useState<any>(null);
|
|
111
|
+
const [showEditModal, setShowEditModal] = useState(false);
|
|
112
|
+
const [rowPopoverEvent, setRowPopoverEvent] = useState<{ event: any; rowNum: number } | null>(null);
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
// 1. Initialize SocialCalc DOM container with initial data (or empty string)
|
|
116
|
+
const initialMSC = "";
|
|
117
|
+
AppGeneral.initializeApp(initialMSC);
|
|
29
118
|
|
|
30
|
-
|
|
31
|
-
|
|
119
|
+
// 2. Activate desired plugins
|
|
120
|
+
AppGeneral.enableRowColHeaders(); // 1, 2, 3... & A, B, C... headers with resize handles
|
|
121
|
+
AppGeneral.enableGridLines(); // Modern cell border grid
|
|
122
|
+
AppGeneral.enableTouchScroll(); // Smooth mobile inertia scroll
|
|
123
|
+
|
|
124
|
+
// 3. Listen for Cell Edit Modal requests
|
|
125
|
+
const handleCellEdit = (e: any) => {
|
|
126
|
+
setCellEditData(e.detail);
|
|
127
|
+
setShowEditModal(true);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 4. Listen for Row Header clicks (context actions)
|
|
131
|
+
const handleRowClick = (e: any) => {
|
|
132
|
+
setRowPopoverEvent({
|
|
133
|
+
event: e.detail.originalEvent || e.detail,
|
|
134
|
+
rowNum: e.detail.rowNum,
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
window.addEventListener("socialcalc:cell-edit-request", handleCellEdit);
|
|
139
|
+
window.addEventListener("socialcalc:row-header-click", handleRowClick);
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
window.removeEventListener("socialcalc:cell-edit-request", handleCellEdit);
|
|
143
|
+
window.removeEventListener("socialcalc:row-header-click", handleRowClick);
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div style={{ position: "relative", width: "100%", height: "100vh", overflow: "hidden" }}>
|
|
149
|
+
{/* SocialCalc DOM Mounting Target Elements */}
|
|
150
|
+
<div id="container" style={{ width: "100%", height: "calc(100% - 44px)" }}>
|
|
151
|
+
<div id="workbookControl" style={{ display: "none" }}></div>
|
|
152
|
+
<div id="tableeditor" style={{ width: "100%", height: "100%" }}></div>
|
|
153
|
+
<div id="msg"></div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Horizontal Viewport Scrollbar */}
|
|
157
|
+
<HorizontalScrollBar />
|
|
158
|
+
|
|
159
|
+
{/* Rich Cell Editor Bottom Sheet */}
|
|
160
|
+
<CellEditModal
|
|
161
|
+
isOpen={showEditModal}
|
|
162
|
+
cellData={cellEditData}
|
|
163
|
+
onClose={() => setShowEditModal(false)}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
{/* Row Insert / Delete Popover */}
|
|
167
|
+
<RowActionPopover
|
|
168
|
+
isOpen={!!rowPopoverEvent}
|
|
169
|
+
event={rowPopoverEvent?.event}
|
|
170
|
+
selectedRow={rowPopoverEvent?.rowNum}
|
|
171
|
+
onDismiss={() => setRowPopoverEvent(null)}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default SpreadsheetEditor;
|
|
178
|
+
```
|
|
32
179
|
|
|
33
180
|
---
|
|
34
181
|
|
|
35
|
-
##
|
|
182
|
+
## 🌐 Quick Start in Vanilla JavaScript
|
|
183
|
+
|
|
184
|
+
`socialcalc-ai` can be used in plain HTML/JavaScript or non-React web apps without any framework overhead:
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<!DOCTYPE html>
|
|
188
|
+
<html lang="en">
|
|
189
|
+
<head>
|
|
190
|
+
<meta charset="UTF-8" />
|
|
191
|
+
<title>SocialCalc AI Standalone</title>
|
|
192
|
+
</head>
|
|
193
|
+
<body>
|
|
194
|
+
<div id="container">
|
|
195
|
+
<div id="workbookControl" style="display: none;"></div>
|
|
196
|
+
<div id="tableeditor"></div>
|
|
197
|
+
<div id="msg"></div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<script type="module">
|
|
201
|
+
import * as SocialCalcApp from "socialcalc-ai";
|
|
202
|
+
|
|
203
|
+
// Initialize spreadsheet
|
|
204
|
+
SocialCalcApp.initializeApp("");
|
|
205
|
+
|
|
206
|
+
// Enable modern features
|
|
207
|
+
SocialCalcApp.enableGridLines();
|
|
208
|
+
SocialCalcApp.enableRowColHeaders();
|
|
209
|
+
SocialCalcApp.enableTouchScroll();
|
|
210
|
+
|
|
211
|
+
// Listen for cell change events
|
|
212
|
+
SocialCalcApp.setupCellChangeListener((coord, value) => {
|
|
213
|
+
console.log(`Cell ${coord} updated:`, value);
|
|
214
|
+
});
|
|
215
|
+
</script>
|
|
216
|
+
</body>
|
|
217
|
+
</html>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 📁 Architecture Overview
|
|
36
223
|
|
|
37
224
|
```text
|
|
38
|
-
socialcalc/
|
|
39
|
-
├── index.js # Main entry point:
|
|
225
|
+
socialcalc-ai/
|
|
226
|
+
├── index.js # Main entry point: aggregates core, modules & UI
|
|
227
|
+
├── index.d.ts # Complete TypeScript typings
|
|
40
228
|
├── package.json # Standalone package definition
|
|
41
|
-
├── core/ # Core calculation engine & table rendering
|
|
42
|
-
│ ├── constants.js #
|
|
229
|
+
├── core/ # Core calculation engine & DOM table rendering
|
|
230
|
+
│ ├── constants.js # Formatting codes, default styles & limits
|
|
43
231
|
│ ├── core.js # Spreadsheet sheet model, cell evaluation, formula AST
|
|
44
|
-
│ ├── formula.js # Built-in math, logical, string, financial
|
|
232
|
+
│ ├── formula.js # Built-in math, logical, string, financial functions
|
|
45
233
|
│ ├── format-number.js # Numeric and date formatting routines
|
|
46
234
|
│ ├── spreadsheet-control.js # Spreadsheet control lifecycle & pane management
|
|
47
235
|
│ ├── table-editor.js # DOM table editor layout & cursor rendering
|
|
48
236
|
│ └── index.js # Assembled core SocialCalc singleton export
|
|
49
|
-
├── modules/ #
|
|
237
|
+
├── modules/ # Modular functional features & plugins
|
|
50
238
|
│ ├── plugin-manager.js # Central registry for dynamic SocialCalc plugins
|
|
51
|
-
│ ├── grid-lines.js #
|
|
52
|
-
│ ├── row-col-headers.js # 123
|
|
239
|
+
│ ├── grid-lines.js # Cell border grid-lines plugin
|
|
240
|
+
│ ├── row-col-headers.js # 123 / ABCD headers with resize handles & row selection
|
|
53
241
|
│ ├── touch-scroll.js # Momentum/inertia mobile touch scroll
|
|
54
242
|
│ ├── horizontal-scroll.js # Viewport tracking & horizontal scrolling subscriptions
|
|
55
243
|
│ ├── editable-cells.js # Cell lock & template permission plugin
|
|
@@ -60,12 +248,13 @@ socialcalc/
|
|
|
60
248
|
│ ├── init.js # Workbook & table editor DOM initialization
|
|
61
249
|
│ ├── prompts.js # Enhanced input dialogs & modal dispatchers
|
|
62
250
|
│ ├── exporters.js # HTML, CSV, MSC export helpers
|
|
251
|
+
│ ├── invoice.js # Coordinate mapping & invoice utilities
|
|
63
252
|
│ ├── device.js # Device detection and responsive helpers
|
|
64
253
|
│ ├── logos.js # Brand/logo helper utilities
|
|
65
254
|
│ ├── utils.js # Coordinate conversions, DOM utilities
|
|
66
255
|
│ └── weight.js # Cell weight/scoring utilities
|
|
67
256
|
├── components/ # React & Ionic UI Components
|
|
68
|
-
│ ├── CellEditModal/ # Rich
|
|
257
|
+
│ ├── CellEditModal/ # Rich editor sheet (Formulas, Text, Colors, Borders, Images)
|
|
69
258
|
│ ├── HorizontalScrollBar.tsx # Smooth horizontal navigation bar & track slider
|
|
70
259
|
│ ├── RowActionPopover/ # Context menu for row insertion & deletion
|
|
71
260
|
│ ├── EditableCellsModal/ # Template mapping manager & cell lock editor
|
|
@@ -77,192 +266,284 @@ socialcalc/
|
|
|
77
266
|
|
|
78
267
|
---
|
|
79
268
|
|
|
80
|
-
##
|
|
269
|
+
## 🎨 UI Components
|
|
81
270
|
|
|
82
|
-
### 1.
|
|
271
|
+
### 1. `CellEditModal`
|
|
272
|
+
A modern, responsive bottom-sheet modal that replaces legacy browser `prompt()` dialogs with a rich spreadsheet formatting bar:
|
|
273
|
+
- **Formula & Text Editor**: Live input bar with syntax validation (e.g. `=SUM(A1:B10)`).
|
|
274
|
+
- **Styling**: Bold, Italic, Alignment (Left, Center, Right).
|
|
275
|
+
- **Curated Palette**: 8 font colors (`FONT_COLORS`) and 7 background colors (`BG_COLORS`).
|
|
276
|
+
- **Cell Borders**: Top, Bottom, Left, Right, and All borders with Solid, Dashed, or Dotted styles.
|
|
277
|
+
- **Image Insertion**: Embed images into cells with automatic client-side compression via `imageCompressor.ts`.
|
|
278
|
+
- **Fallback**: Automatically falls back to native inline input if modal mode is disabled.
|
|
83
279
|
|
|
84
|
-
```
|
|
85
|
-
|
|
280
|
+
```tsx
|
|
281
|
+
<CellEditModal
|
|
282
|
+
isOpen={isOpen}
|
|
283
|
+
cellData={cellData}
|
|
284
|
+
onClose={() => setIsOpen(false)}
|
|
285
|
+
/>
|
|
86
286
|
```
|
|
87
287
|
|
|
88
|
-
### 2.
|
|
288
|
+
### 2. `HorizontalScrollBar`
|
|
289
|
+
A smooth horizontal track controller designed specifically for wide spreadsheets on mobile and desktop:
|
|
290
|
+
- Step-by-step column advancement buttons (◀ / ▶).
|
|
291
|
+
- Responsive drag track with active column indicator (e.g., `Cols: A - J`).
|
|
292
|
+
- Synchronizes bi-directionally with keyboard arrow navigation and touch scrolling.
|
|
89
293
|
|
|
90
294
|
```tsx
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
import {
|
|
94
|
-
CellEditModal,
|
|
95
|
-
HorizontalScrollBar,
|
|
96
|
-
RowActionPopover,
|
|
97
|
-
} from "socialcalc-ai";
|
|
98
|
-
|
|
99
|
-
const MySpreadsheet: React.FC = () => {
|
|
100
|
-
const [cellEditData, setCellEditData] = useState<any>(null);
|
|
101
|
-
const [showModal, setShowModal] = useState(false);
|
|
102
|
-
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
// 1. Initialize DOM workbook container
|
|
105
|
-
const initialMSC = { /* SocialCalc MSC json or save string */ };
|
|
106
|
-
AppGeneral.initializeApp(JSON.stringify(initialMSC));
|
|
295
|
+
<HorizontalScrollBar />
|
|
296
|
+
```
|
|
107
297
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
298
|
+
### 3. `RowActionPopover`
|
|
299
|
+
A context popup that appears when clicking any row header index:
|
|
300
|
+
- **Insert Row Above**: Inserts an empty row above the selected index.
|
|
301
|
+
- **Insert Row Below**: Inserts an empty row below the selected index.
|
|
302
|
+
- **Delete Row**: Removes the selected row and cascades formula references.
|
|
112
303
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
304
|
+
```tsx
|
|
305
|
+
<RowActionPopover
|
|
306
|
+
isOpen={showPopover}
|
|
307
|
+
event={clickEvent}
|
|
308
|
+
selectedRow={rowNumber}
|
|
309
|
+
onDismiss={() => setShowPopover(false)}
|
|
310
|
+
/>
|
|
311
|
+
```
|
|
119
312
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
313
|
+
### 4. `EditableCellsModal`
|
|
314
|
+
An administrative inspector for template creators:
|
|
315
|
+
- View all locked vs editable cells in the active sheet.
|
|
316
|
+
- Bind application field mappings (e.g., `vendor_name -> B4`) to spreadsheet coordinates.
|
|
317
|
+
- Test template locking permissions.
|
|
124
318
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<div id="msg"></div>
|
|
132
|
-
</div>
|
|
319
|
+
```tsx
|
|
320
|
+
<EditableCellsModal
|
|
321
|
+
isOpen={showModal}
|
|
322
|
+
onClose={() => setShowModal(false)}
|
|
323
|
+
/>
|
|
324
|
+
```
|
|
133
325
|
|
|
134
|
-
|
|
135
|
-
|
|
326
|
+
### 5. `DemoVideosModal`
|
|
327
|
+
An interactive help and reference modal containing:
|
|
328
|
+
- Built-in formula cheat sheets (`SUM`, `AVERAGE`, `IF`, `VLOOKUP`, `COUNT`, `CONCATENATE`, etc.).
|
|
329
|
+
- Embedded tutorial guides for spreadsheet operators.
|
|
136
330
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
/>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
331
|
+
```tsx
|
|
332
|
+
<DemoVideosModal
|
|
333
|
+
isOpen={showHelp}
|
|
334
|
+
onClose={() => setShowHelp(false)}
|
|
335
|
+
/>
|
|
146
336
|
```
|
|
147
337
|
|
|
148
338
|
---
|
|
149
339
|
|
|
150
|
-
##
|
|
151
|
-
|
|
152
|
-
### 1. Row
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
340
|
+
## 🔌 Plugins & Modules API
|
|
341
|
+
|
|
342
|
+
### 1. Row & Column Headers (`modules/row-col-headers.js`)
|
|
343
|
+
Enables 123 row numbers and ABCD column headers with interactive column resizing:
|
|
344
|
+
```ts
|
|
345
|
+
// Enable row/col headers
|
|
346
|
+
AppGeneral.enableRowColHeaders();
|
|
347
|
+
|
|
348
|
+
// Disable row/col headers
|
|
349
|
+
AppGeneral.disableRowColHeaders();
|
|
350
|
+
|
|
351
|
+
// Toggle headers dynamically (returns boolean)
|
|
352
|
+
const isEnabled = AppGeneral.toggleRowColHeaders();
|
|
353
|
+
|
|
354
|
+
// Execute raw SocialCalc commands
|
|
355
|
+
AppGeneral.executeSheetCommand("set A1 text hello");
|
|
356
|
+
```
|
|
165
357
|
|
|
166
358
|
### 2. Grid Lines (`modules/grid-lines.js`)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
AppGeneral.isGridLinesEnabled(); // returns boolean
|
|
174
|
-
```
|
|
359
|
+
Adds subtle, high-contrast borders between cells:
|
|
360
|
+
```ts
|
|
361
|
+
AppGeneral.enableGridLines();
|
|
362
|
+
AppGeneral.disableGridLines();
|
|
363
|
+
AppGeneral.toggleGridLines();
|
|
364
|
+
```
|
|
175
365
|
|
|
176
366
|
### 3. Smooth Touch Scroll (`modules/touch-scroll.js`)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
### 4.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
367
|
+
Adds mobile touch inertia scrolling:
|
|
368
|
+
```ts
|
|
369
|
+
AppGeneral.enableTouchScroll();
|
|
370
|
+
AppGeneral.configureTouchScroll({
|
|
371
|
+
momentumFriction: 0.94, // adjust friction
|
|
372
|
+
velocityMultiplier: 1.2
|
|
373
|
+
});
|
|
374
|
+
AppGeneral.disableTouchScroll();
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### 4. Horizontal Scroll (`modules/horizontal-scroll.js`)
|
|
378
|
+
Programmatic viewport scrolling:
|
|
379
|
+
```ts
|
|
380
|
+
// Scroll horizontally by a relative number of columns
|
|
381
|
+
AppGeneral.scrollHorizontalBy(3);
|
|
382
|
+
|
|
383
|
+
// Scroll to a specific target column index (1-based)
|
|
384
|
+
AppGeneral.scrollToColumn(5); // Scrolls to column E
|
|
385
|
+
|
|
386
|
+
// Subscribe to horizontal scroll events
|
|
387
|
+
const unsubscribe = AppGeneral.subscribeHorizontalScroll((info) => {
|
|
388
|
+
console.log("Current first col:", info.currentFirstCol);
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 5. Editable Cells & Template Locks (`modules/editable-cells.js`)
|
|
393
|
+
Restricts user editing to designated cells while protecting formula and header cells:
|
|
394
|
+
```ts
|
|
395
|
+
// Enable template protection mode
|
|
396
|
+
AppGeneral.enableEditableCellsOnly();
|
|
397
|
+
|
|
398
|
+
// Define allowed mapping
|
|
399
|
+
AppGeneral.setAppMapping({
|
|
400
|
+
sheet1: {
|
|
401
|
+
customerName: "B3",
|
|
402
|
+
itemsTotal: "D15"
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Check if a specific cell coordinate is editable
|
|
407
|
+
const canEdit = AppGeneral.isCellEditable(editor, "B3"); // true
|
|
408
|
+
const isLocked = AppGeneral.isCellEditable(editor, "A1"); // false
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 6. Plugin Manager (`modules/plugin-manager.js`)
|
|
412
|
+
Allows registration of custom lifecycle plugins:
|
|
413
|
+
```ts
|
|
414
|
+
AppGeneral.registerPlugin("myCustomPlugin", {
|
|
415
|
+
metadata: {
|
|
416
|
+
displayName: "Audit Logger",
|
|
417
|
+
description: "Logs cell updates to an external service"
|
|
418
|
+
},
|
|
419
|
+
enable: () => console.log("Plugin activated"),
|
|
420
|
+
disable: () => console.log("Plugin deactivated"),
|
|
421
|
+
isEnabled: () => true
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 7. History (Undo / Redo) (`modules/history.js`)
|
|
426
|
+
```ts
|
|
427
|
+
AppGeneral.undo();
|
|
428
|
+
AppGeneral.redo();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### 8. Exporters & Sheets (`modules/exporters.js`, `modules/sheets.js`)
|
|
432
|
+
```ts
|
|
433
|
+
// Export sheet as CSV string
|
|
434
|
+
const csvData = AppGeneral.getCSVContent();
|
|
435
|
+
|
|
436
|
+
// Export sheet as HTML string
|
|
437
|
+
const htmlContent = AppGeneral.getCurrentHTMLContent();
|
|
438
|
+
|
|
439
|
+
// Get full spreadsheet data (save format / MSC)
|
|
440
|
+
const mscData = AppGeneral.getSpreadsheetContent();
|
|
441
|
+
|
|
442
|
+
// Multi-sheet operations
|
|
443
|
+
AppGeneral.switchSheet("Sheet2");
|
|
444
|
+
AppGeneral.addNewSheet("Expenses");
|
|
445
|
+
AppGeneral.deleteSheet("Sheet3");
|
|
446
|
+
AppGeneral.renameSheet("Sheet1", "Overview");
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### 9. Invoice Utilities (`modules/invoice.js`)
|
|
450
|
+
```ts
|
|
451
|
+
import { getInvoiceCoordinates } from "socialcalc-ai";
|
|
452
|
+
|
|
453
|
+
const coords = getInvoiceCoordinates();
|
|
454
|
+
// Returns standard coordinates for billTo, from, invoiceDetails, items, totals
|
|
455
|
+
```
|
|
241
456
|
|
|
242
457
|
---
|
|
243
458
|
|
|
244
|
-
## 📡
|
|
459
|
+
## 📡 Event-Driven Architecture
|
|
460
|
+
|
|
461
|
+
`socialcalc-ai` broadcasts standard DOM CustomEvents on the `window` object. This allows any framework (React, Vue, Svelte, Angular, Vanilla JS) to react to spreadsheet changes:
|
|
245
462
|
|
|
246
|
-
| Event Name | `detail`
|
|
463
|
+
| Event Name | `event.detail` Structure | Purpose |
|
|
247
464
|
| :--- | :--- | :--- |
|
|
248
|
-
| `socialcalc:cell-edit-request` | `{ coord, text, okfn, cleanup }` |
|
|
249
|
-
| `socialcalc:row-header-click` | `{ rowNum
|
|
250
|
-
| `socialcalc:cell-change` | `{ coord, value }` |
|
|
251
|
-
| `socialcalc:horizontal-scroll`| `{ currentFirstCol, currentColName, totalCols }` |
|
|
252
|
-
| `socialcalc:plugin-change` | `{ pluginName, state, allStates }` |
|
|
465
|
+
| `socialcalc:cell-edit-request` | `{ coord: string, text: string, okfn: Function, cleanup: Function }` | Fired when a cell is clicked/tapped. Consumed by `CellEditModal`. |
|
|
466
|
+
| `socialcalc:row-header-click` | `{ rowNum: number, originalEvent: MouseEvent }` | Fired when a row number index is clicked. Consumed by `RowActionPopover`. |
|
|
467
|
+
| `socialcalc:cell-change` | `{ coord: string, value: string }` | Fired after a cell's value or formula is committed and recalculated. |
|
|
468
|
+
| `socialcalc:horizontal-scroll`| `{ currentFirstCol: number, currentColName: string, totalCols: number }` | Fired whenever the horizontal viewport column changes. |
|
|
469
|
+
| `socialcalc:plugin-change` | `{ pluginName: string, state: boolean, allStates: Record<string, boolean> }` | Fired whenever a plugin is registered, enabled, or disabled. |
|
|
470
|
+
|
|
471
|
+
### Event Listener Example:
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
window.addEventListener("socialcalc:cell-change", (e: CustomEvent) => {
|
|
475
|
+
const { coord, value } = e.detail;
|
|
476
|
+
console.log(`Cell ${coord} changed to: ${value}`);
|
|
477
|
+
});
|
|
478
|
+
```
|
|
253
479
|
|
|
254
480
|
---
|
|
255
481
|
|
|
256
|
-
##
|
|
482
|
+
## 🤖 AI & MultiSheet Calc (MSC) Integration
|
|
257
483
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
484
|
+
`socialcalc-ai` is built for automated and LLM-driven spreadsheet workflows:
|
|
485
|
+
|
|
486
|
+
### Generating Spreadsheets via LLMs
|
|
487
|
+
AI agents can generate spreadsheet structures in SocialCalc MSC format (MultiSheet Calc). An MSC document defines multiple named sheets with cell formulas, formats, and values:
|
|
488
|
+
|
|
489
|
+
```json
|
|
490
|
+
{
|
|
491
|
+
"sheets": {
|
|
492
|
+
"Summary": {
|
|
493
|
+
"content": "cell:A1:t:Quarterly Revenue\ncell:A3:t:Q1\ncell:B3:v:12500\ncell:A4:t:Q2\ncell:B4:v:14200\ncell:A5:t:Total\ncell:B5:v#:=SUM(B3:B4)"
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
262
498
|
|
|
263
|
-
|
|
264
|
-
npm run build
|
|
499
|
+
Load this directly into `socialcalc-ai`:
|
|
265
500
|
|
|
266
|
-
|
|
267
|
-
|
|
501
|
+
```ts
|
|
502
|
+
import * as AppGeneral from "socialcalc-ai";
|
|
503
|
+
|
|
504
|
+
AppGeneral.loadWorkbookData(aiGeneratedMscJson);
|
|
268
505
|
```
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 🛡 TypeScript Support
|
|
510
|
+
|
|
511
|
+
`socialcalc-ai` ships with full type definitions in `index.d.ts`:
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
import * as AppGeneral from "socialcalc-ai";
|
|
515
|
+
import {
|
|
516
|
+
CellEditModal,
|
|
517
|
+
CellEditModalProps,
|
|
518
|
+
HorizontalScrollBarProps,
|
|
519
|
+
RowActionPopoverProps
|
|
520
|
+
} from "socialcalc-ai";
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 💻 Browser & Framework Compatibility
|
|
526
|
+
|
|
527
|
+
- **Browsers**: Chrome, Edge, Safari, Firefox (Desktop & Mobile).
|
|
528
|
+
- **Frameworks**: React 18+, Ionic React 7+, Next.js (Client Component), Vite, Plain JavaScript.
|
|
529
|
+
- **Runtimes**: Node.js 18+ (for headless calculation & CSV/MSC transformation).
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## 📦 Release Notes
|
|
534
|
+
|
|
535
|
+
### v1.0.2
|
|
536
|
+
- **Cell Edit Modal Fix**: Fixed cell click interceptor and mouse delegates so clicking any cell reliably triggers the `CellEditModal` bottom sheet.
|
|
537
|
+
- **Scroll Distortion Fix**: Resolved table border collapse and column misalignment when scrolling past multi-row (`rowspan`) and multi-column (`colspan`) blocks.
|
|
538
|
+
- **Formula Support**: Enhanced modal editing to support formula values (`=SUM(...)`) seamlessly alongside raw numbers, text, and HTML formatting.
|
|
539
|
+
- **Strict Column Sizing**: Enforced CSS pixel widths and `box-sizing: border-box` containment across `<colgroup>` and sizing rows to preserve layout integrity.
|
|
540
|
+
- **Safety Guards**: Added null guards and try/catch protection across SocialCalc workbook control callbacks.
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
## 📜 License & Acknowledgments
|
|
545
|
+
|
|
546
|
+
- **Original SocialCalc Engine**: Created by Dan Bricklin (co-creator of VisiCalc) and Socialtext.
|
|
547
|
+
- **Modernized `socialcalc-ai` Package**: Maintained and modernized for ES6 modules, React/Ionic UI components, touch physics, and AI workflows by **Anirudh Sharma** under the **MIT License**.
|
|
548
|
+
|
|
549
|
+
Feel free to open issues and pull requests to help enhance the modern spreadsheet engine!
|
package/core/core.js
CHANGED
|
@@ -5186,6 +5186,7 @@
|
|
|
5186
5186
|
if (context.showRCHeaders) {
|
|
5187
5187
|
newcol = document.createElement("col");
|
|
5188
5188
|
newcol.width = context.rownamewidth;
|
|
5189
|
+
newcol.style.width = context.rownamewidth + "px";
|
|
5189
5190
|
result.appendChild(newcol);
|
|
5190
5191
|
}
|
|
5191
5192
|
|
|
@@ -5197,12 +5198,16 @@
|
|
|
5197
5198
|
) {
|
|
5198
5199
|
newcol = document.createElement("col");
|
|
5199
5200
|
t = context.colwidth[colnum];
|
|
5200
|
-
if (t)
|
|
5201
|
+
if (t) {
|
|
5202
|
+
newcol.width = t;
|
|
5203
|
+
newcol.style.width = t + "px";
|
|
5204
|
+
}
|
|
5201
5205
|
result.appendChild(newcol);
|
|
5202
5206
|
}
|
|
5203
5207
|
if (colpane < context.colpanes.length - 1) {
|
|
5204
5208
|
newcol = document.createElement("col");
|
|
5205
5209
|
newcol.width = context.defaultpanedividerwidth;
|
|
5210
|
+
newcol.style.width = context.defaultpanedividerwidth + "px";
|
|
5206
5211
|
result.appendChild(newcol);
|
|
5207
5212
|
}
|
|
5208
5213
|
}
|
|
@@ -5218,6 +5223,8 @@
|
|
|
5218
5223
|
if (context.showRCHeaders) {
|
|
5219
5224
|
newcell = document.createElement("td");
|
|
5220
5225
|
newcell.style.width = context.rownamewidth + "px";
|
|
5226
|
+
newcell.style.minWidth = context.rownamewidth + "px";
|
|
5227
|
+
newcell.style.maxWidth = context.rownamewidth + "px";
|
|
5221
5228
|
newcell.height = "1";
|
|
5222
5229
|
result.appendChild(newcell);
|
|
5223
5230
|
}
|
|
@@ -5230,13 +5237,21 @@
|
|
|
5230
5237
|
) {
|
|
5231
5238
|
newcell = document.createElement("td");
|
|
5232
5239
|
t = context.colwidth[colnum];
|
|
5233
|
-
if (t)
|
|
5240
|
+
if (t) {
|
|
5241
|
+
newcell.width = t;
|
|
5242
|
+
newcell.style.width = t + "px";
|
|
5243
|
+
newcell.style.minWidth = t + "px";
|
|
5244
|
+
newcell.style.maxWidth = t + "px";
|
|
5245
|
+
}
|
|
5234
5246
|
newcell.height = "1";
|
|
5235
5247
|
result.appendChild(newcell);
|
|
5236
5248
|
}
|
|
5237
5249
|
if (colpane < context.colpanes.length - 1) {
|
|
5238
5250
|
newcell = document.createElement("td");
|
|
5239
5251
|
newcell.width = context.defaultpanedividerwidth;
|
|
5252
|
+
newcell.style.width = context.defaultpanedividerwidth + "px";
|
|
5253
|
+
newcell.style.minWidth = context.defaultpanedividerwidth + "px";
|
|
5254
|
+
newcell.style.maxWidth = context.defaultpanedividerwidth + "px";
|
|
5240
5255
|
newcell.height = "1";
|
|
5241
5256
|
result.appendChild(newcell);
|
|
5242
5257
|
}
|
|
@@ -5289,8 +5304,8 @@
|
|
|
5289
5304
|
if (rownum === firstVisibleRow && colnum === firstVisibleCol) {
|
|
5290
5305
|
// This is the visible start of the scrolled spanned cell in this pane!
|
|
5291
5306
|
isSpanContinuation = true;
|
|
5292
|
-
spanColSpan = Math.min(spanEndCol, panecollimits ? panecollimits.last : spanEndCol) - firstVisibleCol + 1;
|
|
5293
|
-
spanRowSpan = Math.min(spanEndRow, panerowlimits ? panerowlimits.last : spanEndRow) - firstVisibleRow + 1;
|
|
5307
|
+
spanColSpan = Math.max(1, Math.min(spanEndCol, panecollimits ? panecollimits.last : spanEndCol) - firstVisibleCol + 1);
|
|
5308
|
+
spanRowSpan = Math.max(1, Math.min(spanEndRow, panerowlimits ? panerowlimits.last : spanEndRow) - firstVisibleRow + 1);
|
|
5294
5309
|
cell = originCell;
|
|
5295
5310
|
} else {
|
|
5296
5311
|
// Covered by the visible span in this pane -- skip
|
|
@@ -5416,6 +5431,9 @@
|
|
|
5416
5431
|
} else stylestr += "text-align:left;";
|
|
5417
5432
|
}
|
|
5418
5433
|
|
|
5434
|
+
var activeColSpan = isSpanContinuation ? spanColSpan : (result.colSpan || cell.colspan || 1);
|
|
5435
|
+
var activeRowSpan = isSpanContinuation ? spanRowSpan : (result.rowSpan || cell.rowspan || 1);
|
|
5436
|
+
|
|
5419
5437
|
num = cell.bt;
|
|
5420
5438
|
if (num && (!isSpanContinuation || (originCR && rownum === originCR.row))) {
|
|
5421
5439
|
stylestr += "border-top:" + sheetobj.borderstyles[num] + ";";
|
|
@@ -5428,12 +5446,12 @@
|
|
|
5428
5446
|
if (
|
|
5429
5447
|
context.CellInPane(
|
|
5430
5448
|
rownum,
|
|
5431
|
-
colnum +
|
|
5449
|
+
colnum + activeColSpan,
|
|
5432
5450
|
rowpane,
|
|
5433
5451
|
colpane
|
|
5434
5452
|
)
|
|
5435
5453
|
)
|
|
5436
|
-
t = SocialCalc.crToCoord(colnum +
|
|
5454
|
+
t = SocialCalc.crToCoord(colnum + activeColSpan, rownum);
|
|
5437
5455
|
else t = "nomatch";
|
|
5438
5456
|
if (context.cellskip[t]) t = context.cellskip[t];
|
|
5439
5457
|
if (!sheetobj.cells[t] || !sheetobj.cells[t].bl)
|
|
@@ -5446,13 +5464,13 @@
|
|
|
5446
5464
|
} else if (context.showGrid) {
|
|
5447
5465
|
if (
|
|
5448
5466
|
context.CellInPane(
|
|
5449
|
-
rownum +
|
|
5467
|
+
rownum + activeRowSpan,
|
|
5450
5468
|
colnum,
|
|
5451
5469
|
rowpane,
|
|
5452
5470
|
colpane
|
|
5453
5471
|
)
|
|
5454
5472
|
)
|
|
5455
|
-
t = SocialCalc.crToCoord(colnum, rownum +
|
|
5473
|
+
t = SocialCalc.crToCoord(colnum, rownum + activeRowSpan);
|
|
5456
5474
|
else t = "nomatch";
|
|
5457
5475
|
if (context.cellskip[t]) t = context.cellskip[t];
|
|
5458
5476
|
if (!sheetobj.cells[t] || !sheetobj.cells[t].bt)
|
|
@@ -5483,6 +5501,8 @@
|
|
|
5483
5501
|
}
|
|
5484
5502
|
|
|
5485
5503
|
result.style.cssText = stylestr;
|
|
5504
|
+
result.style.boxSizing = "border-box";
|
|
5505
|
+
result.style.overflow = "hidden";
|
|
5486
5506
|
|
|
5487
5507
|
//!!!!!!!!!
|
|
5488
5508
|
// NOTE: csss and cssc are not supported yet.
|
|
@@ -7014,8 +7014,13 @@ SocialCalc.SpreadsheetCmdTable = {
|
|
|
7014
7014
|
// this is for checkmark toggling
|
|
7015
7015
|
SocialCalc.Callbacks.ToggleCell = function (cellname) {
|
|
7016
7016
|
var control = SocialCalc.GetCurrentWorkBookControl();
|
|
7017
|
+
if (!control || !control.currentSheetButton || !control.workbook || !control.workbook.sheetArr) {
|
|
7018
|
+
return;
|
|
7019
|
+
}
|
|
7017
7020
|
var sheetid = control.currentSheetButton.id;
|
|
7021
|
+
if (!control.workbook.sheetArr[sheetid]) return;
|
|
7018
7022
|
var sheetobj = control.workbook.sheetArr[sheetid].sheet;
|
|
7023
|
+
if (!sheetobj || !sheetobj.cells) return;
|
|
7019
7024
|
var cell = sheetobj.cells[cellname];
|
|
7020
7025
|
var sheetname = sheetobj.sheetname;
|
|
7021
7026
|
|
package/core/table-editor.js
CHANGED
|
@@ -1582,22 +1582,33 @@
|
|
|
1582
1582
|
mouseinfo.registeredElements.push({ element: element, editor: editor });
|
|
1583
1583
|
}
|
|
1584
1584
|
|
|
1585
|
+
if (!SocialCalc.ProcessEditorMouseDownHandler) {
|
|
1586
|
+
SocialCalc.ProcessEditorMouseDownHandler = function (e) {
|
|
1587
|
+
return SocialCalc.ProcessEditorMouseDown(e);
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
if (!SocialCalc.ProcessEditorDblClickHandler) {
|
|
1591
|
+
SocialCalc.ProcessEditorDblClickHandler = function (e) {
|
|
1592
|
+
return SocialCalc.ProcessEditorDblClick(e);
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1585
1596
|
if (element.addEventListener) {
|
|
1586
1597
|
// DOM Level 2 -- Firefox, et al
|
|
1587
1598
|
element.addEventListener(
|
|
1588
1599
|
"mousedown",
|
|
1589
|
-
SocialCalc.
|
|
1600
|
+
SocialCalc.ProcessEditorMouseDownHandler,
|
|
1590
1601
|
false
|
|
1591
1602
|
);
|
|
1592
1603
|
element.addEventListener(
|
|
1593
1604
|
"dblclick",
|
|
1594
|
-
SocialCalc.
|
|
1605
|
+
SocialCalc.ProcessEditorDblClickHandler,
|
|
1595
1606
|
false
|
|
1596
1607
|
);
|
|
1597
1608
|
} else if (element.attachEvent) {
|
|
1598
1609
|
// IE 5+
|
|
1599
|
-
element.attachEvent("onmousedown", SocialCalc.
|
|
1600
|
-
element.attachEvent("ondblclick", SocialCalc.
|
|
1610
|
+
element.attachEvent("onmousedown", SocialCalc.ProcessEditorMouseDownHandler);
|
|
1611
|
+
element.attachEvent("ondblclick", SocialCalc.ProcessEditorDblClickHandler);
|
|
1601
1612
|
} else {
|
|
1602
1613
|
// don't handle this
|
|
1603
1614
|
throw "Browser not supported";
|
|
@@ -1629,21 +1640,21 @@
|
|
|
1629
1640
|
// DOM Level 2
|
|
1630
1641
|
oldelement.removeEventListener(
|
|
1631
1642
|
"mousedown",
|
|
1632
|
-
SocialCalc.ProcessEditorMouseDown,
|
|
1643
|
+
SocialCalc.ProcessEditorMouseDownHandler || SocialCalc.ProcessEditorMouseDown,
|
|
1633
1644
|
false
|
|
1634
1645
|
);
|
|
1635
1646
|
oldelement.removeEventListener(
|
|
1636
1647
|
"dblclick",
|
|
1637
|
-
SocialCalc.ProcessEditorDblClick,
|
|
1648
|
+
SocialCalc.ProcessEditorDblClickHandler || SocialCalc.ProcessEditorDblClick,
|
|
1638
1649
|
false
|
|
1639
1650
|
);
|
|
1640
1651
|
} else if (oldelement.detachEvent) {
|
|
1641
1652
|
// IE
|
|
1642
1653
|
oldelement.detachEvent(
|
|
1643
1654
|
"onmousedown",
|
|
1644
|
-
SocialCalc.ProcessEditorMouseDown
|
|
1655
|
+
SocialCalc.ProcessEditorMouseDownHandler || SocialCalc.ProcessEditorMouseDown
|
|
1645
1656
|
);
|
|
1646
|
-
oldelement.detachEvent("ondblclick", SocialCalc.ProcessEditorDblClick);
|
|
1657
|
+
oldelement.detachEvent("ondblclick", SocialCalc.ProcessEditorDblClickHandler || SocialCalc.ProcessEditorDblClick);
|
|
1647
1658
|
}
|
|
1648
1659
|
mouseinfo.registeredElements.splice(i, 1);
|
|
1649
1660
|
}
|
|
@@ -3772,18 +3783,6 @@
|
|
|
3772
3783
|
return;
|
|
3773
3784
|
}
|
|
3774
3785
|
|
|
3775
|
-
if ((vamount == 1 || vamount == -1) && hamount == 0) {
|
|
3776
|
-
// special case quick scrolls
|
|
3777
|
-
if (vamount == 1) {
|
|
3778
|
-
editor.ScrollTableUpOneRow();
|
|
3779
|
-
} else {
|
|
3780
|
-
editor.ScrollTableDownOneRow();
|
|
3781
|
-
}
|
|
3782
|
-
if (editor.ecell) editor.SetECellHeaders("selected");
|
|
3783
|
-
editor.SchedulePositionCalculations();
|
|
3784
|
-
return;
|
|
3785
|
-
}
|
|
3786
|
-
|
|
3787
3786
|
// Do a gross move and render
|
|
3788
3787
|
|
|
3789
3788
|
if (vamount != 0 || hamount != 0) {
|
|
@@ -3874,215 +3873,13 @@
|
|
|
3874
3873
|
};
|
|
3875
3874
|
|
|
3876
3875
|
SocialCalc.ScrollTableUpOneRow = function (editor) {
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
rownum,
|
|
3880
|
-
colnum,
|
|
3881
|
-
colpane,
|
|
3882
|
-
cell,
|
|
3883
|
-
oldrownum,
|
|
3884
|
-
maxspan,
|
|
3885
|
-
newbottomrow,
|
|
3886
|
-
newrow,
|
|
3887
|
-
oldchild,
|
|
3888
|
-
bottomrownum;
|
|
3889
|
-
var rowneedsrefresh = {};
|
|
3890
|
-
|
|
3891
|
-
var context = editor.context;
|
|
3892
|
-
var sheetobj = context.sheetobj;
|
|
3893
|
-
var tableobj = editor.fullgrid;
|
|
3894
|
-
|
|
3895
|
-
var tbodyobj;
|
|
3896
|
-
|
|
3897
|
-
tbodyobj = tableobj.lastChild;
|
|
3898
|
-
|
|
3899
|
-
toprow = context.showRCHeaders ? 2 : 1;
|
|
3900
|
-
for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) {
|
|
3901
|
-
toprow +=
|
|
3902
|
-
context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row
|
|
3903
|
-
}
|
|
3904
|
-
|
|
3905
|
-
tbodyobj.removeChild(tbodyobj.childNodes[toprow]);
|
|
3906
|
-
|
|
3907
|
-
context.rowpanes[rowpane].first++;
|
|
3908
|
-
context.rowpanes[rowpane].last++;
|
|
3909
|
-
editor.FitToEditTable();
|
|
3910
|
-
context.CalculateColWidthData(); // Just in case, since normally done in RenderSheet
|
|
3911
|
-
|
|
3912
|
-
newbottomrow = context.RenderRow(context.rowpanes[rowpane].last, rowpane);
|
|
3913
|
-
tbodyobj.appendChild(newbottomrow);
|
|
3914
|
-
|
|
3915
|
-
// if scrolled off a row with starting rowspans, replace rows for the largest rowspan
|
|
3916
|
-
|
|
3917
|
-
var maxrowspan = 1;
|
|
3918
|
-
oldrownum = context.rowpanes[rowpane].first - 1;
|
|
3919
|
-
|
|
3920
|
-
for (colpane = 0; colpane < context.colpanes.length; colpane++) {
|
|
3921
|
-
for (
|
|
3922
|
-
colnum = context.colpanes[colpane].first;
|
|
3923
|
-
colnum <= context.colpanes[colpane].last;
|
|
3924
|
-
colnum++
|
|
3925
|
-
) {
|
|
3926
|
-
var coord = SocialCalc.crToCoord(colnum, oldrownum);
|
|
3927
|
-
if (context.cellskip[coord]) continue;
|
|
3928
|
-
cell = sheetobj.cells[coord];
|
|
3929
|
-
if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan;
|
|
3930
|
-
}
|
|
3931
|
-
}
|
|
3932
|
-
|
|
3933
|
-
if (maxrowspan > 1) {
|
|
3934
|
-
for (rownum = 1; rownum < maxrowspan; rownum++) {
|
|
3935
|
-
if (rownum + oldrownum >= context.rowpanes[rowpane].last) break;
|
|
3936
|
-
newrow = context.RenderRow(rownum + oldrownum, rowpane);
|
|
3937
|
-
oldchild = tbodyobj.childNodes[toprow + rownum - 1];
|
|
3938
|
-
tbodyobj.replaceChild(newrow, oldchild);
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
|
|
3942
|
-
// if added a row that includes rowspans from above, update the size of those to include new row
|
|
3943
|
-
|
|
3944
|
-
bottomrownum = context.rowpanes[rowpane].last;
|
|
3945
|
-
|
|
3946
|
-
for (colpane = 0; colpane < context.colpanes.length; colpane++) {
|
|
3947
|
-
for (
|
|
3948
|
-
colnum = context.colpanes[colpane].first;
|
|
3949
|
-
colnum <= context.colpanes[colpane].last;
|
|
3950
|
-
colnum++
|
|
3951
|
-
) {
|
|
3952
|
-
coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)];
|
|
3953
|
-
if (!coord) continue; // only look at spanned cells
|
|
3954
|
-
rownum = context.coordToCR[coord].row - 0;
|
|
3955
|
-
if (
|
|
3956
|
-
rownum == context.rowpanes[rowpane].last ||
|
|
3957
|
-
rownum < context.rowpanes[rowpane].first
|
|
3958
|
-
)
|
|
3959
|
-
continue; // this row (colspan) or starts above pane
|
|
3960
|
-
cell = sheetobj.cells[coord];
|
|
3961
|
-
if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true; // remember row num to update
|
|
3962
|
-
}
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
for (rownum in rowneedsrefresh) {
|
|
3966
|
-
newrow = context.RenderRow(rownum, rowpane);
|
|
3967
|
-
oldchild =
|
|
3968
|
-
tbodyobj.childNodes[
|
|
3969
|
-
toprow + (rownum - context.rowpanes[rowpane].first)
|
|
3970
|
-
];
|
|
3971
|
-
tbodyobj.replaceChild(newrow, oldchild);
|
|
3972
|
-
}
|
|
3973
|
-
|
|
3974
|
-
return tableobj;
|
|
3876
|
+
SocialCalc.ScrollRelativeBoth(editor, 1, 0);
|
|
3877
|
+
return editor.fullgrid;
|
|
3975
3878
|
};
|
|
3976
3879
|
|
|
3977
3880
|
SocialCalc.ScrollTableDownOneRow = function (editor) {
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
rownum,
|
|
3981
|
-
colnum,
|
|
3982
|
-
colpane,
|
|
3983
|
-
cell,
|
|
3984
|
-
newrownum,
|
|
3985
|
-
maxspan,
|
|
3986
|
-
newbottomrow,
|
|
3987
|
-
newrow,
|
|
3988
|
-
oldchild,
|
|
3989
|
-
bottomrownum,
|
|
3990
|
-
maxrowspan,
|
|
3991
|
-
coord;
|
|
3992
|
-
var rowneedsrefresh = {};
|
|
3993
|
-
|
|
3994
|
-
var context = editor.context;
|
|
3995
|
-
var sheetobj = context.sheetobj;
|
|
3996
|
-
var tableobj = editor.fullgrid;
|
|
3997
|
-
|
|
3998
|
-
var tbodyobj;
|
|
3999
|
-
|
|
4000
|
-
tbodyobj = tableobj.lastChild;
|
|
4001
|
-
|
|
4002
|
-
toprow = context.showRCHeaders ? 2 : 1;
|
|
4003
|
-
for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) {
|
|
4004
|
-
toprow +=
|
|
4005
|
-
context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row
|
|
4006
|
-
}
|
|
4007
|
-
|
|
4008
|
-
tbodyobj.removeChild(
|
|
4009
|
-
tbodyobj.childNodes[
|
|
4010
|
-
toprow +
|
|
4011
|
-
(context.rowpanes[rowpane].last - context.rowpanes[rowpane].first)
|
|
4012
|
-
]
|
|
4013
|
-
);
|
|
4014
|
-
|
|
4015
|
-
context.rowpanes[rowpane].first--;
|
|
4016
|
-
context.rowpanes[rowpane].last--;
|
|
4017
|
-
editor.FitToEditTable();
|
|
4018
|
-
context.CalculateColWidthData(); // Just in case, since normally done in RenderSheet
|
|
4019
|
-
|
|
4020
|
-
newrow = context.RenderRow(context.rowpanes[rowpane].first, rowpane);
|
|
4021
|
-
tbodyobj.insertBefore(newrow, tbodyobj.childNodes[toprow]);
|
|
4022
|
-
|
|
4023
|
-
// if inserted a row with starting rowspans, replace rows for the largest rowspan
|
|
4024
|
-
|
|
4025
|
-
maxrowspan = 1;
|
|
4026
|
-
newrownum = context.rowpanes[rowpane].first;
|
|
4027
|
-
|
|
4028
|
-
for (colpane = 0; colpane < context.colpanes.length; colpane++) {
|
|
4029
|
-
for (
|
|
4030
|
-
colnum = context.colpanes[colpane].first;
|
|
4031
|
-
colnum <= context.colpanes[colpane].last;
|
|
4032
|
-
colnum++
|
|
4033
|
-
) {
|
|
4034
|
-
coord = SocialCalc.crToCoord(colnum, newrownum);
|
|
4035
|
-
if (context.cellskip[coord]) continue;
|
|
4036
|
-
cell = sheetobj.cells[coord];
|
|
4037
|
-
if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan;
|
|
4038
|
-
}
|
|
4039
|
-
}
|
|
4040
|
-
|
|
4041
|
-
if (maxrowspan > 1) {
|
|
4042
|
-
for (rownum = 1; rownum < maxrowspan; rownum++) {
|
|
4043
|
-
if (rownum + newrownum > context.rowpanes[rowpane].last) break;
|
|
4044
|
-
newrow = context.RenderRow(rownum + newrownum, rowpane);
|
|
4045
|
-
oldchild = tbodyobj.childNodes[toprow + rownum];
|
|
4046
|
-
tbodyobj.replaceChild(newrow, oldchild);
|
|
4047
|
-
}
|
|
4048
|
-
}
|
|
4049
|
-
|
|
4050
|
-
// if last row now includes rowspans or rowspans from above, update the size of those to remove deleted row
|
|
4051
|
-
|
|
4052
|
-
bottomrownum = context.rowpanes[rowpane].last;
|
|
4053
|
-
|
|
4054
|
-
for (colpane = 0; colpane < context.colpanes.length; colpane++) {
|
|
4055
|
-
for (
|
|
4056
|
-
colnum = context.colpanes[colpane].first;
|
|
4057
|
-
colnum <= context.colpanes[colpane].last;
|
|
4058
|
-
colnum++
|
|
4059
|
-
) {
|
|
4060
|
-
coord = SocialCalc.crToCoord(colnum, bottomrownum);
|
|
4061
|
-
cell = sheetobj.cells[coord];
|
|
4062
|
-
if (cell && cell.rowspan > 1) {
|
|
4063
|
-
rowneedsrefresh[bottomrownum] = true; // need to update this row
|
|
4064
|
-
continue;
|
|
4065
|
-
}
|
|
4066
|
-
coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)];
|
|
4067
|
-
if (!coord) continue; // only look at spanned cells
|
|
4068
|
-
rownum = context.coordToCR[coord].row - 0;
|
|
4069
|
-
if (rownum == bottomrownum || rownum < context.rowpanes[rowpane].first)
|
|
4070
|
-
continue; // this row (colspan) or starts above pane
|
|
4071
|
-
cell = sheetobj.cells[coord];
|
|
4072
|
-
if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true; // remember row num to update
|
|
4073
|
-
}
|
|
4074
|
-
}
|
|
4075
|
-
|
|
4076
|
-
for (rownum in rowneedsrefresh) {
|
|
4077
|
-
newrow = context.RenderRow(rownum, rowpane);
|
|
4078
|
-
oldchild =
|
|
4079
|
-
tbodyobj.childNodes[
|
|
4080
|
-
toprow + (rownum - context.rowpanes[rowpane].first)
|
|
4081
|
-
];
|
|
4082
|
-
tbodyobj.replaceChild(newrow, oldchild);
|
|
4083
|
-
}
|
|
4084
|
-
|
|
4085
|
-
return tableobj;
|
|
3881
|
+
SocialCalc.ScrollRelativeBoth(editor, -1, 0);
|
|
3882
|
+
return editor.fullgrid;
|
|
4086
3883
|
};
|
|
4087
3884
|
|
|
4088
3885
|
// *************************************
|
package/modules/init.js
CHANGED
|
@@ -8,9 +8,10 @@ if (typeof window !== "undefined" && window.SocialCalc) {
|
|
|
8
8
|
SocialCalc = global.SocialCalc;
|
|
9
9
|
} else {
|
|
10
10
|
console.error("SocialCalc not found in global scope");
|
|
11
|
-
SocialCalc = {}; // Fallback to prevent errors
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
import { setupMouseListener } from "./listeners.js";
|
|
14
|
+
|
|
14
15
|
export function initializeApp(data) {
|
|
15
16
|
/* Initializes the spreadsheet */
|
|
16
17
|
// Try multiple candidate IDs so the renderer can namespace its elements
|
|
@@ -91,4 +92,5 @@ export function initializeApp(data) {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
spreadsheet.DoOnResize();
|
|
95
|
+
setupMouseListener();
|
|
94
96
|
}
|
package/modules/listeners.js
CHANGED
|
@@ -30,6 +30,7 @@ export function enableCellEditModal() {
|
|
|
30
30
|
if (typeof window !== "undefined" && window.SocialCalc) {
|
|
31
31
|
window.SocialCalc.isCellEditModalEnabled = () => true;
|
|
32
32
|
}
|
|
33
|
+
setupMouseListener();
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -57,6 +58,9 @@ export function toggleCellEditModal(show) {
|
|
|
57
58
|
if (typeof window !== "undefined" && window.SocialCalc) {
|
|
58
59
|
window.SocialCalc.isCellEditModalEnabled = () => _cellEditModalEnabled;
|
|
59
60
|
}
|
|
61
|
+
if (_cellEditModalEnabled) {
|
|
62
|
+
setupMouseListener();
|
|
63
|
+
}
|
|
60
64
|
return _cellEditModalEnabled;
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -167,7 +171,15 @@ export function setupMouseListener() {
|
|
|
167
171
|
if (typeof window !== "undefined" && window.SocialCalc) {
|
|
168
172
|
SocialCalc = window.SocialCalc;
|
|
169
173
|
}
|
|
170
|
-
if (!SocialCalc || !SocialCalc.EditorMouseInfo)
|
|
174
|
+
if (!SocialCalc || !SocialCalc.EditorMouseInfo) {
|
|
175
|
+
if (typeof window !== "undefined") {
|
|
176
|
+
setTimeout(setupMouseListener, 50);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (SocialCalc._customMouseListenerInstalled) return;
|
|
182
|
+
SocialCalc._customMouseListenerInstalled = true;
|
|
171
183
|
|
|
172
184
|
const originalDblClick = SocialCalc.ProcessEditorDblClick;
|
|
173
185
|
|
|
@@ -292,7 +304,11 @@ export function setupMouseListener() {
|
|
|
292
304
|
|
|
293
305
|
// Toggle Cell Callback
|
|
294
306
|
if (SocialCalc.Callbacks && SocialCalc.Callbacks.ToggleCell) {
|
|
295
|
-
|
|
307
|
+
try {
|
|
308
|
+
SocialCalc.Callbacks.ToggleCell(result.coord);
|
|
309
|
+
} catch (err) {
|
|
310
|
+
// Ignore toggle cell errors in custom environments
|
|
311
|
+
}
|
|
296
312
|
}
|
|
297
313
|
|
|
298
314
|
// Move ECell (Updates Selection)
|
|
@@ -367,7 +383,7 @@ export function setupMouseListener() {
|
|
|
367
383
|
const originalKeyPress = SocialCalc.ProcessKeyPress;
|
|
368
384
|
|
|
369
385
|
SocialCalc.ProcessKeyDown = function (e) {
|
|
370
|
-
const isModalOpen = !!document.querySelector(".sc-cell-edit-backdrop");
|
|
386
|
+
const isModalOpen = !!document.querySelector(".sc-cell-modal-backdrop, .sc-cell-edit-backdrop, .sc-modal-container");
|
|
371
387
|
const activeElement = document.activeElement;
|
|
372
388
|
const isTypingInInput =
|
|
373
389
|
activeElement &&
|
|
@@ -386,7 +402,7 @@ export function setupMouseListener() {
|
|
|
386
402
|
};
|
|
387
403
|
|
|
388
404
|
SocialCalc.ProcessKeyPress = function (e) {
|
|
389
|
-
const isModalOpen = !!document.querySelector(".sc-cell-edit-backdrop");
|
|
405
|
+
const isModalOpen = !!document.querySelector(".sc-cell-modal-backdrop, .sc-cell-edit-backdrop, .sc-modal-container");
|
|
390
406
|
const activeElement = document.activeElement;
|
|
391
407
|
const isTypingInInput =
|
|
392
408
|
activeElement &&
|
|
@@ -416,8 +432,8 @@ function triggerCustomModal(editor) {
|
|
|
416
432
|
if (!editor || !editor.ecell) return;
|
|
417
433
|
|
|
418
434
|
const coord = editor.ecell.coord;
|
|
419
|
-
const sheet = editor.context.sheetobj;
|
|
420
|
-
const cell = sheet.GetAssuredCell(coord);
|
|
435
|
+
const sheet = editor.context ? editor.context.sheetobj : null;
|
|
436
|
+
const cell = sheet && sheet.GetAssuredCell ? sheet.GetAssuredCell(coord) : null;
|
|
421
437
|
|
|
422
438
|
// Hide the native input box and cancel its timer
|
|
423
439
|
if (editor.inputBox) {
|
|
@@ -442,25 +458,35 @@ function triggerCustomModal(editor) {
|
|
|
442
458
|
|
|
443
459
|
// Determine initial text
|
|
444
460
|
let initialText = "";
|
|
445
|
-
if (cell
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
461
|
+
if (cell) {
|
|
462
|
+
if (cell.formula) {
|
|
463
|
+
initialText = "=" + cell.formula;
|
|
464
|
+
} else if (cell.datavalue !== undefined && cell.datavalue !== null) {
|
|
465
|
+
initialText = String(cell.datavalue);
|
|
466
|
+
} else if (cell.displaystring !== undefined && cell.displaystring !== null) {
|
|
467
|
+
initialText = String(cell.displaystring);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (!initialText && SocialCalc && SocialCalc.GetCellContents && sheet) {
|
|
471
|
+
initialText = SocialCalc.GetCellContents(sheet, coord) || "";
|
|
449
472
|
}
|
|
450
473
|
|
|
451
474
|
// Callback when user confirms edit in Modal
|
|
452
475
|
const handleOk = (newValue) => {
|
|
453
476
|
let cmd = "";
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (isNum) {
|
|
457
|
-
cmd = `set ${coord} value n ${newValue}`;
|
|
477
|
+
if (typeof newValue === "string" && newValue.startsWith("=")) {
|
|
478
|
+
cmd = `set ${coord} formula ${newValue.slice(1)}`;
|
|
458
479
|
} else {
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
cmd = `set ${coord}
|
|
480
|
+
const isNum = !isNaN(Number(newValue)) && newValue.trim() !== "";
|
|
481
|
+
if (isNum) {
|
|
482
|
+
cmd = `set ${coord} value n ${newValue}`;
|
|
462
483
|
} else {
|
|
463
|
-
|
|
484
|
+
const isHtml = /<[a-z][\s\S]*>/i.test(newValue);
|
|
485
|
+
if (isHtml) {
|
|
486
|
+
cmd = `set ${coord} text th ${newValue}\nset ${coord} textvalueformat text-html`;
|
|
487
|
+
} else {
|
|
488
|
+
cmd = `set ${coord} text t ${newValue}`;
|
|
489
|
+
}
|
|
464
490
|
}
|
|
465
491
|
}
|
|
466
492
|
|
|
@@ -529,3 +555,8 @@ export function setupCellChangeListener(callback) {
|
|
|
529
555
|
};
|
|
530
556
|
}
|
|
531
557
|
|
|
558
|
+
// Auto-initialize mouse listener if in browser environment
|
|
559
|
+
if (typeof window !== "undefined") {
|
|
560
|
+
setupMouseListener();
|
|
561
|
+
}
|
|
562
|
+
|
package/package.json
CHANGED