@pagent-libs/core 0.1.0
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 +32 -0
- package/dist/canvas/cell-renderer.d.ts +45 -0
- package/dist/canvas/cell-renderer.d.ts.map +1 -0
- package/dist/canvas/grid-renderer.d.ts +29 -0
- package/dist/canvas/grid-renderer.d.ts.map +1 -0
- package/dist/canvas/header-renderer.d.ts +58 -0
- package/dist/canvas/header-renderer.d.ts.map +1 -0
- package/dist/canvas/hit-testing.d.ts +81 -0
- package/dist/canvas/hit-testing.d.ts.map +1 -0
- package/dist/canvas/index.d.ts +9 -0
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/renderer.d.ts +140 -0
- package/dist/canvas/renderer.d.ts.map +1 -0
- package/dist/canvas/selection-renderer.d.ts +55 -0
- package/dist/canvas/selection-renderer.d.ts.map +1 -0
- package/dist/canvas/text-renderer.d.ts +49 -0
- package/dist/canvas/text-renderer.d.ts.map +1 -0
- package/dist/canvas/types.d.ts +200 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/collaboration/firebase-provider.d.ts +13 -0
- package/dist/collaboration/firebase-provider.d.ts.map +1 -0
- package/dist/collaboration/index.d.ts +3 -0
- package/dist/collaboration/index.d.ts.map +1 -0
- package/dist/collaboration/types.d.ts +34 -0
- package/dist/collaboration/types.d.ts.map +1 -0
- package/dist/event-emitter.d.ts +13 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/export/csv.d.ts +5 -0
- package/dist/export/csv.d.ts.map +1 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/features/filter.d.ts +58 -0
- package/dist/features/filter.d.ts.map +1 -0
- package/dist/features/freeze.d.ts +86 -0
- package/dist/features/freeze.d.ts.map +1 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/sort.d.ts +15 -0
- package/dist/features/sort.d.ts.map +1 -0
- package/dist/format-pool.d.ts +17 -0
- package/dist/format-pool.d.ts.map +1 -0
- package/dist/formula-graph.d.ts +12 -0
- package/dist/formula-graph.d.ts.map +1 -0
- package/dist/formula-parser/cell-reference.d.ts +7 -0
- package/dist/formula-parser/cell-reference.d.ts.map +1 -0
- package/dist/formula-parser/formula-adjust.d.ts +13 -0
- package/dist/formula-parser/formula-adjust.d.ts.map +1 -0
- package/dist/formula-parser/formula-ranges.d.ts +22 -0
- package/dist/formula-parser/formula-ranges.d.ts.map +1 -0
- package/dist/formula-parser/index.d.ts +6 -0
- package/dist/formula-parser/index.d.ts.map +1 -0
- package/dist/formula-parser/parser.d.ts +18 -0
- package/dist/formula-parser/parser.d.ts.map +1 -0
- package/dist/formula-parser/types.d.ts +33 -0
- package/dist/formula-parser/types.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +5823 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +5885 -0
- package/dist/index.js.map +1 -0
- package/dist/sheet.d.ts +119 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/style-pool.d.ts +17 -0
- package/dist/style-pool.d.ts.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/cell-key.d.ts +7 -0
- package/dist/utils/cell-key.d.ts.map +1 -0
- package/dist/utils/format-utils.d.ts +75 -0
- package/dist/utils/format-utils.d.ts.map +1 -0
- package/dist/utils/range.d.ts +13 -0
- package/dist/utils/range.d.ts.map +1 -0
- package/dist/workbook.d.ts +155 -0
- package/dist/workbook.d.ts.map +1 -0
- package/package.json +46 -0
- package/src/canvas/cell-renderer.ts +181 -0
- package/src/canvas/grid-renderer.ts +238 -0
- package/src/canvas/header-renderer.ts +402 -0
- package/src/canvas/hit-testing.ts +537 -0
- package/src/canvas/index.ts +16 -0
- package/src/canvas/renderer.ts +1056 -0
- package/src/canvas/selection-renderer.ts +604 -0
- package/src/canvas/text-renderer.ts +321 -0
- package/src/canvas/types.ts +289 -0
- package/src/collaboration/firebase-provider.ts +48 -0
- package/src/collaboration/index.ts +5 -0
- package/src/collaboration/types.ts +38 -0
- package/src/event-emitter.ts +73 -0
- package/src/export/csv.ts +101 -0
- package/src/export/index.ts +4 -0
- package/src/features/filter.ts +231 -0
- package/src/features/freeze.ts +271 -0
- package/src/features/index.ts +5 -0
- package/src/features/sort.ts +282 -0
- package/src/format-pool.ts +61 -0
- package/src/formula-graph.ts +84 -0
- package/src/formula-parser/cell-reference.ts +99 -0
- package/src/formula-parser/formula-adjust.ts +129 -0
- package/src/formula-parser/formula-ranges.ts +159 -0
- package/src/formula-parser/index.ts +8 -0
- package/src/formula-parser/parser.ts +438 -0
- package/src/formula-parser/types.ts +39 -0
- package/src/index.ts +25 -0
- package/src/sheet.ts +502 -0
- package/src/style-pool.ts +62 -0
- package/src/types.ts +291 -0
- package/src/utils/cell-key.ts +19 -0
- package/src/utils/format-utils.ts +515 -0
- package/src/utils/range.ts +53 -0
- package/src/workbook.ts +1031 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
// Hit Testing - Maps mouse coordinates to grid positions
|
|
2
|
+
|
|
3
|
+
import type { CellPosition, HeaderHit, ResizeHandle } from './types';
|
|
4
|
+
import type { Range } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resize handle hit area size (pixels from edge)
|
|
8
|
+
*/
|
|
9
|
+
const RESIZE_HANDLE_SIZE = 5;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fill handle hit area size
|
|
13
|
+
*/
|
|
14
|
+
const FILL_HANDLE_HIT_SIZE = 8;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles hit testing for mouse interactions
|
|
18
|
+
*/
|
|
19
|
+
export class HitTester {
|
|
20
|
+
private headerWidth: number;
|
|
21
|
+
private headerHeight: number;
|
|
22
|
+
private defaultRowHeight: number;
|
|
23
|
+
private defaultColWidth: number;
|
|
24
|
+
|
|
25
|
+
// Current scroll position
|
|
26
|
+
private scrollTop: number = 0;
|
|
27
|
+
private scrollLeft: number = 0;
|
|
28
|
+
|
|
29
|
+
// Dimension maps (updated from render state)
|
|
30
|
+
private rowHeights: Map<number, number> = new Map();
|
|
31
|
+
private colWidths: Map<number, number> = new Map();
|
|
32
|
+
private rowCount: number = 0;
|
|
33
|
+
private colCount: number = 0;
|
|
34
|
+
|
|
35
|
+
// Hidden rows/cols
|
|
36
|
+
private hiddenRows: Set<number> = new Set();
|
|
37
|
+
private hiddenCols: Set<number> = new Set();
|
|
38
|
+
|
|
39
|
+
// Freeze panes configuration
|
|
40
|
+
private frozenRows: number = 0;
|
|
41
|
+
private frozenCols: number = 0;
|
|
42
|
+
private frozenWidth: number = 0;
|
|
43
|
+
private frozenHeight: number = 0;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
headerWidth: number,
|
|
47
|
+
headerHeight: number,
|
|
48
|
+
defaultRowHeight: number,
|
|
49
|
+
defaultColWidth: number
|
|
50
|
+
) {
|
|
51
|
+
this.headerWidth = headerWidth;
|
|
52
|
+
this.headerHeight = headerHeight;
|
|
53
|
+
this.defaultRowHeight = defaultRowHeight;
|
|
54
|
+
this.defaultColWidth = defaultColWidth;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Update scroll position
|
|
59
|
+
*/
|
|
60
|
+
setScroll(scrollTop: number, scrollLeft: number): void {
|
|
61
|
+
this.scrollTop = scrollTop;
|
|
62
|
+
this.scrollLeft = scrollLeft;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Update dimension information
|
|
67
|
+
*/
|
|
68
|
+
setDimensions(
|
|
69
|
+
rowHeights: Map<number, number>,
|
|
70
|
+
colWidths: Map<number, number>,
|
|
71
|
+
defaultRowHeight: number,
|
|
72
|
+
defaultColWidth: number,
|
|
73
|
+
rowCount: number,
|
|
74
|
+
colCount: number,
|
|
75
|
+
hiddenRows?: Set<number>,
|
|
76
|
+
hiddenCols?: Set<number>
|
|
77
|
+
): void {
|
|
78
|
+
this.rowHeights = rowHeights;
|
|
79
|
+
this.colWidths = colWidths;
|
|
80
|
+
this.defaultRowHeight = defaultRowHeight;
|
|
81
|
+
this.defaultColWidth = defaultColWidth;
|
|
82
|
+
this.rowCount = rowCount;
|
|
83
|
+
this.colCount = colCount;
|
|
84
|
+
this.hiddenRows = hiddenRows ?? new Set();
|
|
85
|
+
this.hiddenCols = hiddenCols ?? new Set();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Update freeze panes configuration
|
|
90
|
+
*/
|
|
91
|
+
setFreezeConfig(
|
|
92
|
+
frozenRows: number,
|
|
93
|
+
frozenCols: number,
|
|
94
|
+
frozenWidth: number,
|
|
95
|
+
frozenHeight: number
|
|
96
|
+
): void {
|
|
97
|
+
this.frozenRows = frozenRows;
|
|
98
|
+
this.frozenCols = frozenCols;
|
|
99
|
+
this.frozenWidth = frozenWidth;
|
|
100
|
+
this.frozenHeight = frozenHeight;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get the cell at a canvas point (accounting for freeze panes)
|
|
105
|
+
*/
|
|
106
|
+
getCellAt(x: number, y: number): CellPosition | null {
|
|
107
|
+
// Check if point is in the cell area (not headers)
|
|
108
|
+
if (x <= this.headerWidth || y <= this.headerHeight) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Determine which freeze region the point is in
|
|
113
|
+
const inFrozenCols = x < this.headerWidth + this.frozenWidth && this.frozenCols > 0;
|
|
114
|
+
const inFrozenRows = y < this.headerHeight + this.frozenHeight && this.frozenRows > 0;
|
|
115
|
+
|
|
116
|
+
// Calculate effective scroll based on region
|
|
117
|
+
const effectiveScrollLeft = inFrozenCols ? 0 : this.scrollLeft;
|
|
118
|
+
const effectiveScrollTop = inFrozenRows ? 0 : this.scrollTop;
|
|
119
|
+
|
|
120
|
+
// Determine starting column and position for search
|
|
121
|
+
let col: number;
|
|
122
|
+
let accX: number;
|
|
123
|
+
|
|
124
|
+
if (inFrozenCols) {
|
|
125
|
+
// In frozen columns - start from column 0
|
|
126
|
+
col = 0;
|
|
127
|
+
accX = 0;
|
|
128
|
+
const localX = x - this.headerWidth;
|
|
129
|
+
|
|
130
|
+
while (col < this.frozenCols && col < this.colCount) {
|
|
131
|
+
if (this.hiddenCols.has(col)) {
|
|
132
|
+
col++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const width = this.colWidths.get(col) ?? this.defaultColWidth;
|
|
136
|
+
if (accX + width > localX) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
accX += width;
|
|
140
|
+
col++;
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
// In scrollable columns - start from first visible scrollable column
|
|
144
|
+
col = this.frozenCols;
|
|
145
|
+
accX = 0;
|
|
146
|
+
const localX = x - this.headerWidth - this.frozenWidth + effectiveScrollLeft;
|
|
147
|
+
|
|
148
|
+
while (col < this.colCount) {
|
|
149
|
+
if (this.hiddenCols.has(col)) {
|
|
150
|
+
col++;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const width = this.colWidths.get(col) ?? this.defaultColWidth;
|
|
154
|
+
if (accX + width > localX) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
accX += width;
|
|
158
|
+
col++;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Determine starting row and position for search
|
|
163
|
+
let row: number;
|
|
164
|
+
let accY: number;
|
|
165
|
+
|
|
166
|
+
if (inFrozenRows) {
|
|
167
|
+
// In frozen rows - start from row 0
|
|
168
|
+
row = 0;
|
|
169
|
+
accY = 0;
|
|
170
|
+
const localY = y - this.headerHeight;
|
|
171
|
+
|
|
172
|
+
while (row < this.frozenRows && row < this.rowCount) {
|
|
173
|
+
if (this.hiddenRows.has(row)) {
|
|
174
|
+
row++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const height = this.rowHeights.get(row) ?? this.defaultRowHeight;
|
|
178
|
+
if (accY + height > localY) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
accY += height;
|
|
182
|
+
row++;
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// In scrollable rows - start from first visible scrollable row
|
|
186
|
+
row = this.frozenRows;
|
|
187
|
+
accY = 0;
|
|
188
|
+
const localY = y - this.headerHeight - this.frozenHeight + effectiveScrollTop;
|
|
189
|
+
|
|
190
|
+
while (row < this.rowCount) {
|
|
191
|
+
if (this.hiddenRows.has(row)) {
|
|
192
|
+
row++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const height = this.rowHeights.get(row) ?? this.defaultRowHeight;
|
|
196
|
+
if (accY + height > localY) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
accY += height;
|
|
200
|
+
row++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check bounds and if cell is hidden
|
|
205
|
+
if (row >= this.rowCount || col >= this.colCount) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (this.hiddenRows.has(row) || this.hiddenCols.has(col)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { row, col };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the header at a canvas point (accounting for freeze panes)
|
|
217
|
+
*/
|
|
218
|
+
getHeaderAt(x: number, y: number): HeaderHit | null {
|
|
219
|
+
// Column header
|
|
220
|
+
if (y <= this.headerHeight && x > this.headerWidth) {
|
|
221
|
+
// Check if in frozen columns area
|
|
222
|
+
const inFrozenCols = x < this.headerWidth + this.frozenWidth && this.frozenCols > 0;
|
|
223
|
+
|
|
224
|
+
if (inFrozenCols) {
|
|
225
|
+
// Frozen column headers - no scroll
|
|
226
|
+
const localX = x - this.headerWidth;
|
|
227
|
+
|
|
228
|
+
let col = 0;
|
|
229
|
+
let accX = 0;
|
|
230
|
+
while (col < this.frozenCols && col < this.colCount) {
|
|
231
|
+
if (this.hiddenCols.has(col)) {
|
|
232
|
+
col++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const width = this.colWidths.get(col) ?? this.defaultColWidth;
|
|
237
|
+
const nextAccX = accX + width;
|
|
238
|
+
|
|
239
|
+
// Check for resize handle
|
|
240
|
+
if (localX >= nextAccX - RESIZE_HANDLE_SIZE && localX <= nextAccX + RESIZE_HANDLE_SIZE) {
|
|
241
|
+
return { type: 'column', index: col, isResize: true };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (nextAccX > localX) {
|
|
245
|
+
return { type: 'column', index: col, isResize: false };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
accX = nextAccX;
|
|
249
|
+
col++;
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
// Scrollable column headers
|
|
253
|
+
const localX = x - this.headerWidth - this.frozenWidth + this.scrollLeft;
|
|
254
|
+
|
|
255
|
+
let col = this.frozenCols;
|
|
256
|
+
let accX = 0;
|
|
257
|
+
while (col < this.colCount) {
|
|
258
|
+
if (this.hiddenCols.has(col)) {
|
|
259
|
+
col++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const width = this.colWidths.get(col) ?? this.defaultColWidth;
|
|
264
|
+
const nextAccX = accX + width;
|
|
265
|
+
|
|
266
|
+
// Check for resize handle
|
|
267
|
+
if (localX >= nextAccX - RESIZE_HANDLE_SIZE && localX <= nextAccX + RESIZE_HANDLE_SIZE) {
|
|
268
|
+
return { type: 'column', index: col, isResize: true };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (nextAccX > localX) {
|
|
272
|
+
return { type: 'column', index: col, isResize: false };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
accX = nextAccX;
|
|
276
|
+
col++;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Row header
|
|
284
|
+
if (x <= this.headerWidth && y > this.headerHeight) {
|
|
285
|
+
// Check if in frozen rows area
|
|
286
|
+
const inFrozenRows = y < this.headerHeight + this.frozenHeight && this.frozenRows > 0;
|
|
287
|
+
|
|
288
|
+
if (inFrozenRows) {
|
|
289
|
+
// Frozen row headers - no scroll
|
|
290
|
+
const localY = y - this.headerHeight;
|
|
291
|
+
|
|
292
|
+
let row = 0;
|
|
293
|
+
let accY = 0;
|
|
294
|
+
while (row < this.frozenRows && row < this.rowCount) {
|
|
295
|
+
if (this.hiddenRows.has(row)) {
|
|
296
|
+
row++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const height = this.rowHeights.get(row) ?? this.defaultRowHeight;
|
|
301
|
+
const nextAccY = accY + height;
|
|
302
|
+
|
|
303
|
+
// Check for resize handle
|
|
304
|
+
if (localY >= nextAccY - RESIZE_HANDLE_SIZE && localY <= nextAccY + RESIZE_HANDLE_SIZE) {
|
|
305
|
+
return { type: 'row', index: row, isResize: true };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (nextAccY > localY) {
|
|
309
|
+
return { type: 'row', index: row, isResize: false };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
accY = nextAccY;
|
|
313
|
+
row++;
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// Scrollable row headers
|
|
317
|
+
const localY = y - this.headerHeight - this.frozenHeight + this.scrollTop;
|
|
318
|
+
|
|
319
|
+
let row = this.frozenRows;
|
|
320
|
+
let accY = 0;
|
|
321
|
+
while (row < this.rowCount) {
|
|
322
|
+
if (this.hiddenRows.has(row)) {
|
|
323
|
+
row++;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const height = this.rowHeights.get(row) ?? this.defaultRowHeight;
|
|
328
|
+
const nextAccY = accY + height;
|
|
329
|
+
|
|
330
|
+
// Check for resize handle
|
|
331
|
+
if (localY >= nextAccY - RESIZE_HANDLE_SIZE && localY <= nextAccY + RESIZE_HANDLE_SIZE) {
|
|
332
|
+
return { type: 'row', index: row, isResize: true };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (nextAccY > localY) {
|
|
336
|
+
return { type: 'row', index: row, isResize: false };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
accY = nextAccY;
|
|
340
|
+
row++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Corner cell
|
|
348
|
+
if (x <= this.headerWidth && y <= this.headerHeight) {
|
|
349
|
+
// Corner cell doesn't have a specific header hit type
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get resize handle at a point
|
|
358
|
+
*/
|
|
359
|
+
getResizeHandleAt(x: number, y: number): ResizeHandle | null {
|
|
360
|
+
const header = this.getHeaderAt(x, y);
|
|
361
|
+
if (header && header.isResize) {
|
|
362
|
+
return { type: header.type, index: header.index };
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if point is on the fill handle (accounting for freeze panes)
|
|
369
|
+
*/
|
|
370
|
+
getFillHandleAt(
|
|
371
|
+
x: number,
|
|
372
|
+
y: number,
|
|
373
|
+
selectionRange: Range | undefined
|
|
374
|
+
): boolean {
|
|
375
|
+
if (!selectionRange) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Calculate fill handle position (bottom-right of selection)
|
|
380
|
+
const range = selectionRange;
|
|
381
|
+
|
|
382
|
+
// Determine if the end of the range is in frozen area
|
|
383
|
+
const endColFrozen = range.endCol < this.frozenCols;
|
|
384
|
+
const endRowFrozen = range.endRow < this.frozenRows;
|
|
385
|
+
|
|
386
|
+
// Calculate handleX
|
|
387
|
+
let handleX: number;
|
|
388
|
+
if (endColFrozen) {
|
|
389
|
+
// End column is frozen - no scroll offset
|
|
390
|
+
handleX = this.headerWidth;
|
|
391
|
+
for (let c = 0; c <= range.endCol; c++) {
|
|
392
|
+
if (!this.hiddenCols.has(c)) {
|
|
393
|
+
handleX += this.colWidths.get(c) ?? this.defaultColWidth;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
// End column is scrollable
|
|
398
|
+
handleX = this.headerWidth + this.frozenWidth;
|
|
399
|
+
for (let c = this.frozenCols; c <= range.endCol; c++) {
|
|
400
|
+
if (!this.hiddenCols.has(c)) {
|
|
401
|
+
handleX += this.colWidths.get(c) ?? this.defaultColWidth;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
handleX -= this.scrollLeft;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Calculate handleY
|
|
408
|
+
let handleY: number;
|
|
409
|
+
if (endRowFrozen) {
|
|
410
|
+
// End row is frozen - no scroll offset
|
|
411
|
+
handleY = this.headerHeight;
|
|
412
|
+
for (let r = 0; r <= range.endRow; r++) {
|
|
413
|
+
if (!this.hiddenRows.has(r)) {
|
|
414
|
+
handleY += this.rowHeights.get(r) ?? this.defaultRowHeight;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} else {
|
|
418
|
+
// End row is scrollable
|
|
419
|
+
handleY = this.headerHeight + this.frozenHeight;
|
|
420
|
+
for (let r = this.frozenRows; r <= range.endRow; r++) {
|
|
421
|
+
if (!this.hiddenRows.has(r)) {
|
|
422
|
+
handleY += this.rowHeights.get(r) ?? this.defaultRowHeight;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
handleY -= this.scrollTop;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check if point is within fill handle hit area
|
|
429
|
+
const dx = Math.abs(x - handleX);
|
|
430
|
+
const dy = Math.abs(y - handleY);
|
|
431
|
+
|
|
432
|
+
return dx <= FILL_HANDLE_HIT_SIZE && dy <= FILL_HANDLE_HIT_SIZE;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Convert grid position to canvas coordinates (accounting for freeze panes)
|
|
437
|
+
*/
|
|
438
|
+
gridToCanvas(row: number, col: number): { x: number; y: number } {
|
|
439
|
+
// Determine if cell is in frozen area
|
|
440
|
+
const colFrozen = col < this.frozenCols;
|
|
441
|
+
const rowFrozen = row < this.frozenRows;
|
|
442
|
+
|
|
443
|
+
// Calculate X position
|
|
444
|
+
let x: number;
|
|
445
|
+
if (colFrozen) {
|
|
446
|
+
// Column is frozen - no scroll offset
|
|
447
|
+
x = this.headerWidth;
|
|
448
|
+
for (let c = 0; c < col; c++) {
|
|
449
|
+
if (!this.hiddenCols.has(c)) {
|
|
450
|
+
x += this.colWidths.get(c) ?? this.defaultColWidth;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Column is scrollable
|
|
455
|
+
x = this.headerWidth + this.frozenWidth;
|
|
456
|
+
for (let c = this.frozenCols; c < col; c++) {
|
|
457
|
+
if (!this.hiddenCols.has(c)) {
|
|
458
|
+
x += this.colWidths.get(c) ?? this.defaultColWidth;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
x -= this.scrollLeft;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Calculate Y position
|
|
465
|
+
let y: number;
|
|
466
|
+
if (rowFrozen) {
|
|
467
|
+
// Row is frozen - no scroll offset
|
|
468
|
+
y = this.headerHeight;
|
|
469
|
+
for (let r = 0; r < row; r++) {
|
|
470
|
+
if (!this.hiddenRows.has(r)) {
|
|
471
|
+
y += this.rowHeights.get(r) ?? this.defaultRowHeight;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
// Row is scrollable
|
|
476
|
+
y = this.headerHeight + this.frozenHeight;
|
|
477
|
+
for (let r = this.frozenRows; r < row; r++) {
|
|
478
|
+
if (!this.hiddenRows.has(r)) {
|
|
479
|
+
y += this.rowHeights.get(r) ?? this.defaultRowHeight;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
y -= this.scrollTop;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return { x, y };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Get the bounds of a cell in canvas coordinates (accounting for freeze panes)
|
|
490
|
+
*/
|
|
491
|
+
getCellBounds(row: number, col: number): { x: number; y: number; width: number; height: number } | null {
|
|
492
|
+
// Return null for hidden cells
|
|
493
|
+
if (this.hiddenRows.has(row) || this.hiddenCols.has(col)) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const { x, y } = this.gridToCanvas(row, col);
|
|
498
|
+
const width = this.colWidths.get(col) ?? this.defaultColWidth;
|
|
499
|
+
const height = this.rowHeights.get(row) ?? this.defaultRowHeight;
|
|
500
|
+
|
|
501
|
+
return { x, y, width, height };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check if a point is in the corner cell
|
|
506
|
+
*/
|
|
507
|
+
isInCornerCell(x: number, y: number): boolean {
|
|
508
|
+
return x <= this.headerWidth && y <= this.headerHeight;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Get the total width of the grid (excluding hidden columns)
|
|
513
|
+
*/
|
|
514
|
+
getTotalWidth(): number {
|
|
515
|
+
let total = 0;
|
|
516
|
+
for (let c = 0; c < this.colCount; c++) {
|
|
517
|
+
if (!this.hiddenCols.has(c)) {
|
|
518
|
+
total += this.colWidths.get(c) ?? this.defaultColWidth;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return total;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Get the total height of the grid (excluding hidden rows)
|
|
526
|
+
*/
|
|
527
|
+
getTotalHeight(): number {
|
|
528
|
+
let total = 0;
|
|
529
|
+
for (let r = 0; r < this.rowCount; r++) {
|
|
530
|
+
if (!this.hiddenRows.has(r)) {
|
|
531
|
+
total += this.rowHeights.get(r) ?? this.defaultRowHeight;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return total;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Canvas rendering module for pagent-sheets
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
export * from './types';
|
|
5
|
+
|
|
6
|
+
// Renderers
|
|
7
|
+
export { CanvasRenderer } from './renderer';
|
|
8
|
+
export { TextRenderer } from './text-renderer';
|
|
9
|
+
export { GridRenderer } from './grid-renderer';
|
|
10
|
+
export { CellRenderer } from './cell-renderer';
|
|
11
|
+
export { HeaderRenderer } from './header-renderer';
|
|
12
|
+
export { SelectionRenderer } from './selection-renderer';
|
|
13
|
+
|
|
14
|
+
// Hit testing
|
|
15
|
+
export { HitTester } from './hit-testing';
|
|
16
|
+
|