@chalabi/svelte-sheets 2.0.2 → 2.0.4
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 +1 -1
- package/dist/Sheet.svelte +112 -77
- package/package.json +1 -1
package/Readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Ultra fast excel sheets in the browser. Hugely inspired by JExcel, built on XLSX shoulders.
|
|
4
4
|
|
|
5
|
-
=> Find a live example [Here](https://
|
|
5
|
+
=> Find a live example [Here](https://chalabi2.github.io/svelte-sheets/)
|
|
6
6
|
|
|
7
7
|
### Motivation
|
|
8
8
|
|
package/dist/Sheet.svelte
CHANGED
|
@@ -84,8 +84,8 @@ let extendRaf = null;
|
|
|
84
84
|
// implement virtual list
|
|
85
85
|
export let startY = 0;
|
|
86
86
|
export let startX = 0;
|
|
87
|
-
export let endY =
|
|
88
|
-
export let endX =
|
|
87
|
+
export let endY = 50; // Initial render batch
|
|
88
|
+
export let endX = 20; // Initial render batch
|
|
89
89
|
// virtual list state
|
|
90
90
|
let height_map = [];
|
|
91
91
|
let width_map = [];
|
|
@@ -180,45 +180,58 @@ export function onInputChange(value, row, column) {
|
|
|
180
180
|
}
|
|
181
181
|
function refresh(data, viewport_height, viewport_width) {
|
|
182
182
|
return __awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
if (!viewport)
|
|
184
|
+
return;
|
|
183
185
|
const { scrollTop, scrollLeft } = viewport;
|
|
184
186
|
yield tick(); // wait until the DOM is up to date
|
|
187
|
+
const defaultHeight = 24;
|
|
188
|
+
const defaultWidth = config.defaultColWidth || 50;
|
|
189
|
+
// Safety limits to prevent infinite loops
|
|
190
|
+
const maxRowsToRender = Math.max(data.length, Math.ceil((viewport_height + bottom_buffer * 2) / defaultHeight) + 10, 100);
|
|
191
|
+
const maxColsToRender = Math.max(columns.length, Math.ceil((viewport_width + right_buffer * 2) / defaultWidth) + 10, 50);
|
|
185
192
|
let content_height = top - scrollTop - bottom_buffer;
|
|
186
193
|
let content_width = left - scrollLeft - left_buffer;
|
|
187
194
|
// vertical
|
|
188
195
|
let y = startY;
|
|
189
|
-
while (content_height < viewport_height) {
|
|
196
|
+
while (content_height < viewport_height && y < maxRowsToRender) {
|
|
190
197
|
let row = rowElements[y - startY];
|
|
191
198
|
if (!row) {
|
|
192
199
|
endY = y + 1;
|
|
193
200
|
yield tick(); // render the newly visible row
|
|
194
201
|
row = rowElements[y - startY];
|
|
202
|
+
// If still no row after tick, break to prevent infinite loop
|
|
203
|
+
if (!row)
|
|
204
|
+
break;
|
|
195
205
|
}
|
|
196
206
|
const row_height = (height_map[y] = getRowHeight(y));
|
|
197
207
|
content_height += row_height;
|
|
198
208
|
y += 1;
|
|
199
209
|
}
|
|
200
|
-
endY = y;
|
|
210
|
+
endY = Math.max(y, 1);
|
|
201
211
|
let remaining = data.length - endY;
|
|
202
|
-
average_height = (top + content_height) / endY;
|
|
203
|
-
bottom = remaining * average_height;
|
|
212
|
+
average_height = endY > 0 ? (top + content_height) / endY : defaultHeight;
|
|
213
|
+
bottom = remaining * (average_height || defaultHeight);
|
|
204
214
|
height_map.length = data.length;
|
|
205
215
|
// horizontal
|
|
206
216
|
let x = startX;
|
|
207
|
-
while (content_width < viewport_width) {
|
|
217
|
+
while (content_width < viewport_width && x < maxColsToRender) {
|
|
208
218
|
let col = colElements[x - startX];
|
|
209
219
|
if (!col) {
|
|
210
220
|
endX = x + 1;
|
|
211
221
|
yield tick(); // render the newly visible col
|
|
212
222
|
col = colElements[x - startX];
|
|
223
|
+
// If still no col after tick, break to prevent infinite loop
|
|
224
|
+
if (!col)
|
|
225
|
+
break;
|
|
213
226
|
}
|
|
214
227
|
const col_width = (width_map[x] = getColumnsWidth(x));
|
|
215
228
|
content_width += col_width;
|
|
216
229
|
x += 1;
|
|
217
230
|
}
|
|
218
|
-
endX = x;
|
|
231
|
+
endX = Math.max(x, 1);
|
|
219
232
|
let remains = columns.length - endX;
|
|
220
|
-
average_width = (left + content_width) / endX;
|
|
221
|
-
right = remains * average_width;
|
|
233
|
+
average_width = endX > 0 ? (left + content_width) / endX : defaultWidth;
|
|
234
|
+
right = remains * (average_width || defaultWidth);
|
|
222
235
|
width_map.length = columns.length;
|
|
223
236
|
});
|
|
224
237
|
}
|
|
@@ -227,8 +240,11 @@ function refresh(data, viewport_height, viewport_width) {
|
|
|
227
240
|
let scrollLeft;
|
|
228
241
|
let scrollTop;
|
|
229
242
|
$: (function scrollX() {
|
|
230
|
-
if (
|
|
243
|
+
if (scrollLeft === undefined || !colElements)
|
|
231
244
|
return;
|
|
245
|
+
// Ensure we have valid dimensions to work with
|
|
246
|
+
const totalCols = Math.max(columns.length, endX, 1);
|
|
247
|
+
const defaultWidth = config.defaultColWidth || 50;
|
|
232
248
|
// if (!scrollLeft) ;
|
|
233
249
|
// horizontal scrolling
|
|
234
250
|
for (let v = 0; v < colElements.length; v += 1) {
|
|
@@ -236,8 +252,8 @@ $: (function scrollX() {
|
|
|
236
252
|
}
|
|
237
253
|
let c = 0;
|
|
238
254
|
let x = 0;
|
|
239
|
-
while (
|
|
240
|
-
const col_width = width_map[c] || average_width;
|
|
255
|
+
while (c < totalCols) {
|
|
256
|
+
const col_width = width_map[c] || average_width || defaultWidth;
|
|
241
257
|
if (x + col_width > scrollLeft - left_buffer) {
|
|
242
258
|
startX = c;
|
|
243
259
|
left = x;
|
|
@@ -246,31 +262,40 @@ $: (function scrollX() {
|
|
|
246
262
|
x += col_width;
|
|
247
263
|
c += 1;
|
|
248
264
|
}
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
// Safety: ensure we found a valid startX
|
|
266
|
+
if (c >= totalCols) {
|
|
267
|
+
startX = Math.max(0, totalCols - 1);
|
|
268
|
+
left = x;
|
|
269
|
+
}
|
|
270
|
+
while (c < totalCols + Math.ceil((viewport_width + right_buffer) / defaultWidth)) {
|
|
271
|
+
const w = width_map[c] || average_width || defaultWidth;
|
|
272
|
+
x += w;
|
|
251
273
|
c += 1;
|
|
252
274
|
if (x > scrollLeft + viewport_width + right_buffer)
|
|
253
275
|
break;
|
|
254
276
|
}
|
|
255
|
-
endX = c;
|
|
277
|
+
endX = Math.max(c, 1);
|
|
256
278
|
const remaining = endX > columns.length
|
|
257
|
-
? (viewport_width + right_buffer) /
|
|
279
|
+
? (viewport_width + right_buffer) / defaultWidth
|
|
258
280
|
: columns.length - endX;
|
|
259
|
-
average_width = x / endX;
|
|
281
|
+
average_width = endX > 0 ? x / endX : defaultWidth;
|
|
260
282
|
// while (c < columns.length) width_map[c++] = average_width;
|
|
261
|
-
right = remaining * average_width;
|
|
283
|
+
right = remaining * (average_width || defaultWidth);
|
|
262
284
|
})();
|
|
263
285
|
$: (function scrollY() {
|
|
264
|
-
if (
|
|
286
|
+
if (scrollTop === undefined || !rowElements)
|
|
265
287
|
return;
|
|
288
|
+
// Ensure we have valid dimensions to work with
|
|
289
|
+
const totalRows = Math.max(data.length, endY, 1);
|
|
290
|
+
const defaultHeight = 24;
|
|
266
291
|
// vertical scrolling
|
|
267
292
|
for (let v = 0; v < rowElements.length; v += 1) {
|
|
268
293
|
height_map[startY + v] = getRowHeight(startY + v);
|
|
269
294
|
}
|
|
270
295
|
let r = 0;
|
|
271
296
|
let y = 0;
|
|
272
|
-
while (
|
|
273
|
-
const row_height = height_map[r] || average_height;
|
|
297
|
+
while (r < totalRows) {
|
|
298
|
+
const row_height = height_map[r] || average_height || defaultHeight;
|
|
274
299
|
if (y + row_height > scrollTop - top_buffer) {
|
|
275
300
|
startY = r;
|
|
276
301
|
top = y;
|
|
@@ -279,19 +304,25 @@ $: (function scrollY() {
|
|
|
279
304
|
y += row_height;
|
|
280
305
|
r += 1;
|
|
281
306
|
}
|
|
282
|
-
|
|
283
|
-
|
|
307
|
+
// Safety: ensure we found a valid startY
|
|
308
|
+
if (r >= totalRows) {
|
|
309
|
+
startY = Math.max(0, totalRows - 1);
|
|
310
|
+
top = y;
|
|
311
|
+
}
|
|
312
|
+
while (r < totalRows + Math.ceil((viewport_height + bottom_buffer) / defaultHeight)) {
|
|
313
|
+
const h = height_map[r] || average_height || defaultHeight;
|
|
314
|
+
y += h;
|
|
284
315
|
r += 1;
|
|
285
316
|
if (y > scrollTop + viewport_height + bottom_buffer)
|
|
286
317
|
break;
|
|
287
318
|
}
|
|
288
|
-
endY = r;
|
|
319
|
+
endY = Math.max(r, 1);
|
|
289
320
|
const remaining = endY > data.length
|
|
290
|
-
? (viewport_height + bottom_buffer) /
|
|
321
|
+
? (viewport_height + bottom_buffer) / defaultHeight
|
|
291
322
|
: data.length - endY;
|
|
292
|
-
average_height = y / endY;
|
|
323
|
+
average_height = endY > 0 ? y / endY : defaultHeight;
|
|
293
324
|
// while (r < data.length) height_map[r++] = average_height;
|
|
294
|
-
bottom = remaining * average_height;
|
|
325
|
+
bottom = remaining * (average_height || defaultHeight);
|
|
295
326
|
})();
|
|
296
327
|
function handle_scroll(e) {
|
|
297
328
|
scrollTop = viewport.scrollTop;
|
|
@@ -315,6 +346,53 @@ onMount(() => {
|
|
|
315
346
|
// document.addEventListener("touchmove", jexcel.touchEndControls);
|
|
316
347
|
document === null || document === void 0 ? void 0 : document.addEventListener("keydown", onKeyDown);
|
|
317
348
|
document === null || document === void 0 ? void 0 : document.addEventListener("keyup", onKeyUp);
|
|
349
|
+
// Initialize hotkeys in browser only
|
|
350
|
+
hotkeys("ctrl+z, command+z", function (e) {
|
|
351
|
+
if (isReadOnly)
|
|
352
|
+
return;
|
|
353
|
+
e.preventDefault();
|
|
354
|
+
cmdz = true;
|
|
355
|
+
if (historyIndex == 0)
|
|
356
|
+
return;
|
|
357
|
+
historyIndex -= 1;
|
|
358
|
+
const res = JSON.parse(history[historyIndex]);
|
|
359
|
+
data = res.data;
|
|
360
|
+
columns = res.columns;
|
|
361
|
+
rows = res.rows;
|
|
362
|
+
style = res.style;
|
|
363
|
+
setTimeout((_) => (cmdz = false), 10);
|
|
364
|
+
});
|
|
365
|
+
hotkeys("ctrl+shift+z, command+shift+z", function (e) {
|
|
366
|
+
if (isReadOnly)
|
|
367
|
+
return;
|
|
368
|
+
console.log("redo");
|
|
369
|
+
e.preventDefault();
|
|
370
|
+
cmdz = true;
|
|
371
|
+
if (history.length - 1 == historyIndex)
|
|
372
|
+
return;
|
|
373
|
+
historyIndex = historyIndex + 1;
|
|
374
|
+
const res = JSON.parse(history[historyIndex]);
|
|
375
|
+
data = res.data;
|
|
376
|
+
columns = res.columns;
|
|
377
|
+
rows = res.rows;
|
|
378
|
+
style = res.style;
|
|
379
|
+
setTimeout((_) => (cmdz = false), 10);
|
|
380
|
+
});
|
|
381
|
+
hotkeys("ctrl+c, command+c, ctrl+x, command+x", function (e, handler) {
|
|
382
|
+
var _a;
|
|
383
|
+
if (isReadOnly && ((_a = handler === null || handler === void 0 ? void 0 : handler.key) === null || _a === void 0 ? void 0 : _a.includes("x")))
|
|
384
|
+
return;
|
|
385
|
+
e.preventDefault();
|
|
386
|
+
clipboard = JSON.stringify(selected);
|
|
387
|
+
});
|
|
388
|
+
hotkeys("ctrl+v, command+v", function (e) {
|
|
389
|
+
if (isReadOnly)
|
|
390
|
+
return;
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
if (!clipboard)
|
|
393
|
+
return;
|
|
394
|
+
data = pasteSelection(data, JSON.parse(clipboard), selected);
|
|
395
|
+
});
|
|
318
396
|
}
|
|
319
397
|
});
|
|
320
398
|
function onMouseDown(e) {
|
|
@@ -521,52 +599,6 @@ function onKeyUp(e) {
|
|
|
521
599
|
// on keyup just reinitialize everything
|
|
522
600
|
keypressed[e.keyCode] = false;
|
|
523
601
|
}
|
|
524
|
-
hotkeys("ctrl+z, command+z", function (e) {
|
|
525
|
-
if (isReadOnly)
|
|
526
|
-
return;
|
|
527
|
-
e.preventDefault();
|
|
528
|
-
cmdz = true;
|
|
529
|
-
if (historyIndex == 0)
|
|
530
|
-
return;
|
|
531
|
-
historyIndex -= 1;
|
|
532
|
-
const res = JSON.parse(history[historyIndex]);
|
|
533
|
-
data = res.data;
|
|
534
|
-
columns = res.columns;
|
|
535
|
-
rows = res.rows;
|
|
536
|
-
style = res.style;
|
|
537
|
-
setTimeout((_) => (cmdz = false), 10);
|
|
538
|
-
});
|
|
539
|
-
hotkeys("ctrl+shift+z, command+shift+z", function (e) {
|
|
540
|
-
if (isReadOnly)
|
|
541
|
-
return;
|
|
542
|
-
console.log("redo");
|
|
543
|
-
e.preventDefault();
|
|
544
|
-
cmdz = true;
|
|
545
|
-
if (history.length - 1 == historyIndex)
|
|
546
|
-
return;
|
|
547
|
-
historyIndex = historyIndex + 1;
|
|
548
|
-
const res = JSON.parse(history[historyIndex]);
|
|
549
|
-
data = res.data;
|
|
550
|
-
columns = res.columns;
|
|
551
|
-
rows = res.rows;
|
|
552
|
-
style = res.style;
|
|
553
|
-
setTimeout((_) => (cmdz = false), 10);
|
|
554
|
-
});
|
|
555
|
-
hotkeys("ctrl+c, command+c, ctrl+x, command+x", function (e, handler) {
|
|
556
|
-
var _a;
|
|
557
|
-
if (isReadOnly && ((_a = handler === null || handler === void 0 ? void 0 : handler.key) === null || _a === void 0 ? void 0 : _a.includes("x")))
|
|
558
|
-
return;
|
|
559
|
-
e.preventDefault();
|
|
560
|
-
clipboard = JSON.stringify(selected);
|
|
561
|
-
});
|
|
562
|
-
hotkeys("ctrl+v, command+v", function (e) {
|
|
563
|
-
if (isReadOnly)
|
|
564
|
-
return;
|
|
565
|
-
e.preventDefault();
|
|
566
|
-
if (!clipboard)
|
|
567
|
-
return;
|
|
568
|
-
data = pasteSelection(data, JSON.parse(clipboard), selected);
|
|
569
|
-
});
|
|
570
602
|
function onKeyDown(e) {
|
|
571
603
|
keypressed[e.keyCode] = true;
|
|
572
604
|
if (!!edition) {
|
|
@@ -634,6 +666,9 @@ $: (() => {
|
|
|
634
666
|
}
|
|
635
667
|
data = d;
|
|
636
668
|
}
|
|
669
|
+
else if (!columns.length) {
|
|
670
|
+
columns = Array.from({ length: data[0].length }, () => ({}));
|
|
671
|
+
}
|
|
637
672
|
}
|
|
638
673
|
// Adjust minimal dimensions
|
|
639
674
|
var j = 0;
|
|
@@ -669,8 +704,8 @@ let rowLine;
|
|
|
669
704
|
let square;
|
|
670
705
|
let squareX;
|
|
671
706
|
let squareY;
|
|
672
|
-
let topLeft;
|
|
673
|
-
let bottomRight;
|
|
707
|
+
let topLeft = { c: 0, r: 0 };
|
|
708
|
+
let bottomRight = { c: 0, r: 0 };
|
|
674
709
|
$: if (mounted) {
|
|
675
710
|
if (extendRaf)
|
|
676
711
|
cancelAnimationFrame(extendRaf);
|
|
@@ -864,7 +899,7 @@ function historyPush(data, rows, columns, style) {
|
|
|
864
899
|
{/each}
|
|
865
900
|
</tr>
|
|
866
901
|
</thead>
|
|
867
|
-
<tbody class="draggable"
|
|
902
|
+
<tbody class="draggable" on:scroll={handle_scroll}>
|
|
868
903
|
{#each visibleY as r}
|
|
869
904
|
<tr
|
|
870
905
|
class="virtual-row"
|