@warkypublic/svelix 0.1.27 → 0.1.28
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.
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
value = $bindable<any>(undefined),
|
|
38
38
|
}: BoxerProps = $props();
|
|
39
39
|
|
|
40
|
+
const instanceId = `boxer-${Math.random().toString(36).slice(2, 10)}`;
|
|
41
|
+
const inputId = `${instanceId}-input`;
|
|
42
|
+
const listboxId = `${instanceId}-listbox`;
|
|
43
|
+
|
|
40
44
|
// Derive effective dataSource and onAPICall from adapter when not explicitly provided
|
|
41
45
|
const dataSource = dataSourceProp ?? (adapter ? "server" : "local");
|
|
42
46
|
const onAPICall =
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
let popupTop = $state(0);
|
|
89
93
|
let popupLeft = $state(0);
|
|
90
94
|
let popupWidth = $state(0);
|
|
95
|
+
let activeOptionIndex = $state<number | null>(null);
|
|
91
96
|
let backdropZ = $state(1090);
|
|
92
97
|
let dropdownZ = $state(1100);
|
|
93
98
|
let pointerInteractingWithDropdown = $state(false);
|
|
@@ -209,6 +214,8 @@
|
|
|
209
214
|
const option = $store.boxerData?.[index];
|
|
210
215
|
if (!option) return;
|
|
211
216
|
|
|
217
|
+
activeOptionIndex = index;
|
|
218
|
+
|
|
212
219
|
if (multiSelect) {
|
|
213
220
|
const currentValues = Array.isArray(value) ? value : [];
|
|
214
221
|
const isSelected = currentValues.includes(option.value);
|
|
@@ -227,6 +234,7 @@
|
|
|
227
234
|
store.setSearch("");
|
|
228
235
|
store.setInput(option.label);
|
|
229
236
|
store.setOpened(false);
|
|
237
|
+
activeOptionIndex = null;
|
|
230
238
|
}
|
|
231
239
|
}
|
|
232
240
|
|
|
@@ -238,6 +246,7 @@
|
|
|
238
246
|
onChange?.(value);
|
|
239
247
|
store.setSearch("");
|
|
240
248
|
store.setInput("");
|
|
249
|
+
activeOptionIndex = null;
|
|
241
250
|
targetRef?.focus();
|
|
242
251
|
}
|
|
243
252
|
if (openOnClear) {
|
|
@@ -247,6 +256,7 @@
|
|
|
247
256
|
|
|
248
257
|
function focusDropdownItem(index: number) {
|
|
249
258
|
const clamped = Math.max(0, Math.min(index, $store.boxerData.length - 1));
|
|
259
|
+
activeOptionIndex = clamped;
|
|
250
260
|
requestAnimationFrame(() => {
|
|
251
261
|
const el = parentEl?.querySelector<HTMLDivElement>(
|
|
252
262
|
`[data-index="${clamped}"]`,
|
|
@@ -277,17 +287,20 @@
|
|
|
277
287
|
);
|
|
278
288
|
if (selectedIndex >= 0) {
|
|
279
289
|
store.setOpened(false);
|
|
290
|
+
activeOptionIndex = null;
|
|
280
291
|
} else {
|
|
281
292
|
onOptionSubmit(0);
|
|
282
293
|
}
|
|
283
294
|
} else {
|
|
284
295
|
store.setOpened(false);
|
|
296
|
+
activeOptionIndex = null;
|
|
285
297
|
}
|
|
286
298
|
break;
|
|
287
299
|
case "Escape":
|
|
288
300
|
if ($store.opened) {
|
|
289
301
|
e.preventDefault();
|
|
290
302
|
store.setOpened(false);
|
|
303
|
+
activeOptionIndex = null;
|
|
291
304
|
}
|
|
292
305
|
break;
|
|
293
306
|
}
|
|
@@ -302,6 +315,7 @@
|
|
|
302
315
|
case "ArrowUp":
|
|
303
316
|
e.preventDefault();
|
|
304
317
|
if (index === 0) {
|
|
318
|
+
activeOptionIndex = null;
|
|
305
319
|
targetRef?.focus();
|
|
306
320
|
} else {
|
|
307
321
|
focusDropdownItem(index - 1);
|
|
@@ -316,6 +330,7 @@
|
|
|
316
330
|
case "Escape":
|
|
317
331
|
e.preventDefault();
|
|
318
332
|
store.setOpened(false);
|
|
333
|
+
activeOptionIndex = null;
|
|
319
334
|
targetRef?.focus();
|
|
320
335
|
break;
|
|
321
336
|
}
|
|
@@ -326,7 +341,8 @@
|
|
|
326
341
|
const rect = anchorEl.getBoundingClientRect();
|
|
327
342
|
const viewportWidth = typeof window === "undefined" ? rect.width : window.innerWidth;
|
|
328
343
|
const horizontalPadding = 8;
|
|
329
|
-
const
|
|
344
|
+
const maxWidth = Math.max(160, viewportWidth - horizontalPadding * 2);
|
|
345
|
+
const nextWidth = Math.min(Math.max(220, rect.width), maxWidth);
|
|
330
346
|
const maxLeft = viewportWidth - nextWidth - horizontalPadding;
|
|
331
347
|
|
|
332
348
|
popupTop = rect.bottom + 4;
|
|
@@ -368,6 +384,7 @@
|
|
|
368
384
|
}
|
|
369
385
|
export function close() {
|
|
370
386
|
store.setOpened(false);
|
|
387
|
+
activeOptionIndex = null;
|
|
371
388
|
}
|
|
372
389
|
export function focus() {
|
|
373
390
|
targetRef?.focus();
|
|
@@ -377,6 +394,7 @@
|
|
|
377
394
|
}
|
|
378
395
|
export function open() {
|
|
379
396
|
store.setOpened(true);
|
|
397
|
+
activeOptionIndex = null;
|
|
380
398
|
}
|
|
381
399
|
export function setValue(val: unknown) {
|
|
382
400
|
value = val;
|
|
@@ -462,6 +480,7 @@
|
|
|
462
480
|
const relatedTarget = e.relatedTarget as Node | null;
|
|
463
481
|
const insideDropdown = !!(relatedTarget && dropdownEl?.contains(relatedTarget));
|
|
464
482
|
if (!currentTarget.contains(relatedTarget) && !insideDropdown) {
|
|
483
|
+
activeOptionIndex = null;
|
|
465
484
|
if (!value && !multiSelect) {
|
|
466
485
|
store.setSearch("");
|
|
467
486
|
store.setInput("");
|
|
@@ -472,9 +491,13 @@
|
|
|
472
491
|
>
|
|
473
492
|
<BoxerTarget
|
|
474
493
|
bind:this={targetRef}
|
|
494
|
+
activeDescendant={activeOptionIndex !== null ? `${instanceId}-option-${activeOptionIndex}` : undefined}
|
|
475
495
|
{clearable}
|
|
496
|
+
controlsId={listboxId}
|
|
476
497
|
{disabled}
|
|
477
498
|
{error}
|
|
499
|
+
expanded={$store.opened}
|
|
500
|
+
{inputId}
|
|
478
501
|
{label}
|
|
479
502
|
{leftSection}
|
|
480
503
|
{placeholder}
|
|
@@ -486,6 +509,7 @@
|
|
|
486
509
|
onsearch={(v) => {
|
|
487
510
|
store.setSearch(v);
|
|
488
511
|
store.setInput(v);
|
|
512
|
+
activeOptionIndex = null;
|
|
489
513
|
store.setOpened(true);
|
|
490
514
|
}}
|
|
491
515
|
/>
|
|
@@ -495,6 +519,7 @@
|
|
|
495
519
|
{#if !effectiveDisablePortal}
|
|
496
520
|
<div
|
|
497
521
|
class="fixed inset-0"
|
|
522
|
+
data-testid="boxer-backdrop"
|
|
498
523
|
onpointerdown={onBackdropPointerDown}
|
|
499
524
|
style:z-index={backdropZ}
|
|
500
525
|
></div>
|
|
@@ -505,6 +530,8 @@
|
|
|
505
530
|
class={`${effectiveDisablePortal
|
|
506
531
|
? "absolute left-0 right-0 top-full mt-1"
|
|
507
532
|
: "fixed"} card bg-surface-50-950 shadow-lg border border-surface-300-700 overflow-hidden`}
|
|
533
|
+
data-testid="boxer-dropdown"
|
|
534
|
+
id={listboxId}
|
|
508
535
|
role="listbox"
|
|
509
536
|
aria-label={label ?? "Options"}
|
|
510
537
|
onpointerdown={markDropdownPointerInteraction}
|
|
@@ -539,6 +566,7 @@
|
|
|
539
566
|
class:bg-primary-100={isSelected}
|
|
540
567
|
class:dark:bg-primary-900={isSelected}
|
|
541
568
|
data-index={vRow.index}
|
|
569
|
+
id={`${instanceId}-option-${vRow.index}`}
|
|
542
570
|
role="option"
|
|
543
571
|
aria-selected={isSelected}
|
|
544
572
|
tabindex="-1"
|
|
@@ -561,7 +589,14 @@
|
|
|
561
589
|
</div>
|
|
562
590
|
</div>
|
|
563
591
|
{:else}
|
|
564
|
-
<div
|
|
592
|
+
<div
|
|
593
|
+
aria-live="polite"
|
|
594
|
+
class="px-3 py-2 text-sm text-surface-500"
|
|
595
|
+
data-testid="boxer-empty"
|
|
596
|
+
role="status"
|
|
597
|
+
>
|
|
598
|
+
no data
|
|
599
|
+
</div>
|
|
565
600
|
{/if}
|
|
566
601
|
</div>
|
|
567
602
|
{/if}
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
|
|
4
4
|
export interface Props {
|
|
5
|
+
activeDescendant?: string;
|
|
6
|
+
controlsId?: string;
|
|
5
7
|
clearable?: boolean;
|
|
6
8
|
disabled?: boolean;
|
|
7
9
|
error?: string;
|
|
10
|
+
expanded?: boolean;
|
|
11
|
+
inputId?: string;
|
|
8
12
|
isFetching?: boolean;
|
|
9
13
|
label?: string;
|
|
10
14
|
leftSection?: Snippet;
|
|
@@ -17,9 +21,13 @@
|
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
let {
|
|
24
|
+
activeDescendant,
|
|
25
|
+
controlsId,
|
|
20
26
|
clearable = true,
|
|
21
27
|
disabled,
|
|
22
28
|
error,
|
|
29
|
+
expanded = false,
|
|
30
|
+
inputId = 'boxer-input',
|
|
23
31
|
isFetching,
|
|
24
32
|
label,
|
|
25
33
|
leftSection,
|
|
@@ -40,7 +48,7 @@
|
|
|
40
48
|
|
|
41
49
|
<div class="flex flex-col gap-1">
|
|
42
50
|
{#if label}
|
|
43
|
-
<label class="label text-sm font-medium" for=
|
|
51
|
+
<label class="label text-sm font-medium" for={inputId}>{label}</label>
|
|
44
52
|
{/if}
|
|
45
53
|
<div class="flex items-center relative">
|
|
46
54
|
{#if leftSection}
|
|
@@ -49,9 +57,17 @@
|
|
|
49
57
|
</div>
|
|
50
58
|
{/if}
|
|
51
59
|
<input
|
|
52
|
-
id=
|
|
60
|
+
id={inputId}
|
|
53
61
|
bind:this={inputEl}
|
|
54
62
|
bind:value={search}
|
|
63
|
+
aria-activedescendant={activeDescendant}
|
|
64
|
+
aria-autocomplete="list"
|
|
65
|
+
aria-controls={controlsId}
|
|
66
|
+
aria-describedby={error ? `${inputId}-error` : undefined}
|
|
67
|
+
aria-expanded={expanded}
|
|
68
|
+
aria-haspopup="listbox"
|
|
69
|
+
aria-invalid={!!error}
|
|
70
|
+
role="combobox"
|
|
55
71
|
class="input pr-8"
|
|
56
72
|
class:input-error={!!error}
|
|
57
73
|
class:pl-8={!!leftSection}
|
|
@@ -82,6 +98,6 @@
|
|
|
82
98
|
</div>
|
|
83
99
|
</div>
|
|
84
100
|
{#if error}
|
|
85
|
-
<p class="text-error-500 text-xs">{error}</p>
|
|
101
|
+
<p class="text-error-500 text-xs" id={`${inputId}-error`}>{error}</p>
|
|
86
102
|
{/if}
|
|
87
103
|
</div>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
export interface Props {
|
|
3
|
+
activeDescendant?: string;
|
|
4
|
+
controlsId?: string;
|
|
3
5
|
clearable?: boolean;
|
|
4
6
|
disabled?: boolean;
|
|
5
7
|
error?: string;
|
|
8
|
+
expanded?: boolean;
|
|
9
|
+
inputId?: string;
|
|
6
10
|
isFetching?: boolean;
|
|
7
11
|
label?: string;
|
|
8
12
|
leftSection?: Snippet;
|
|
@@ -131,6 +131,7 @@
|
|
|
131
131
|
let dragHeaderCurrentX = $state(0);
|
|
132
132
|
let announcement = $state("");
|
|
133
133
|
let hasFocus = $state(false);
|
|
134
|
+
const gridId = `gridler-${Math.random().toString(36).slice(2, 10)}`;
|
|
134
135
|
|
|
135
136
|
let isDraggingVScrollbar = $state(false);
|
|
136
137
|
let isDraggingHScrollbar = $state(false);
|
|
@@ -141,7 +142,13 @@
|
|
|
141
142
|
let isHoveringVScrollbar = $state(false);
|
|
142
143
|
let isHoveringHScrollbar = $state(false);
|
|
143
144
|
let hoverMenuButton = $state(false);
|
|
145
|
+
let pendingGridMenuOpen = $state(false);
|
|
144
146
|
let hoverSortCol = $state<number | null>(null);
|
|
147
|
+
let touchStartClientX = $state(0);
|
|
148
|
+
let touchStartClientY = $state(0);
|
|
149
|
+
let touchStartScrollX = $state(0);
|
|
150
|
+
let touchStartScrollY = $state(0);
|
|
151
|
+
let isTouchScrolling = $state(false);
|
|
145
152
|
|
|
146
153
|
let containerRef: HTMLDivElement | undefined = $state();
|
|
147
154
|
let canvasRef: HTMLCanvasElement | undefined = $state();
|
|
@@ -201,6 +208,35 @@
|
|
|
201
208
|
const containerHeightStyle = $derived(
|
|
202
209
|
typeof height === "number" ? `${height}px` : height,
|
|
203
210
|
);
|
|
211
|
+
const activeCellDescription = $derived.by(() => {
|
|
212
|
+
if (!focusedCell) {
|
|
213
|
+
return rows > 0 && columns.length > 0
|
|
214
|
+
? "Use arrow keys to move between cells. On touch devices, swipe to scroll and tap to select."
|
|
215
|
+
: "No rows available.";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const [col, row] = focusedCell;
|
|
219
|
+
const columnTitle = columns[col]?.title ?? `Column ${col + 1}`;
|
|
220
|
+
const cell = getCellContent(focusedCell);
|
|
221
|
+
const value = `${cell.displayData ?? ""}`.trim();
|
|
222
|
+
return value
|
|
223
|
+
? `Row ${row + 1}, ${columnTitle}, value ${value}`
|
|
224
|
+
: `Row ${row + 1}, ${columnTitle}, empty cell`;
|
|
225
|
+
});
|
|
226
|
+
const selectionDescription = $derived.by(() => {
|
|
227
|
+
switch (currentSelection.type) {
|
|
228
|
+
case "cell":
|
|
229
|
+
return "One cell selected.";
|
|
230
|
+
case "range":
|
|
231
|
+
return `Range selected from row ${currentSelection.start[1] + 1} to row ${currentSelection.end[1] + 1}.`;
|
|
232
|
+
case "row":
|
|
233
|
+
return `${currentSelection.rows.length} row${currentSelection.rows.length === 1 ? "" : "s"} selected.`;
|
|
234
|
+
case "column":
|
|
235
|
+
return `${currentSelection.columns.length} column${currentSelection.columns.length === 1 ? "" : "s"} selected.`;
|
|
236
|
+
default:
|
|
237
|
+
return "No selection.";
|
|
238
|
+
}
|
|
239
|
+
});
|
|
204
240
|
|
|
205
241
|
// ── Dark mode detection ───────────────────────────────────────────────────────
|
|
206
242
|
|
|
@@ -467,6 +503,7 @@
|
|
|
467
503
|
|
|
468
504
|
function handleMouseDown(e: MouseEvent) {
|
|
469
505
|
if (e.button !== 0) return;
|
|
506
|
+
pendingGridMenuOpen = false;
|
|
470
507
|
containerRef?.focus();
|
|
471
508
|
|
|
472
509
|
const sbGeo = getScrollbarGeometry(buildRenderContext());
|
|
@@ -504,7 +541,7 @@
|
|
|
504
541
|
e.offsetX < rowMarkerWidth &&
|
|
505
542
|
e.offsetY < headerHeight
|
|
506
543
|
) {
|
|
507
|
-
|
|
544
|
+
pendingGridMenuOpen = true;
|
|
508
545
|
return;
|
|
509
546
|
}
|
|
510
547
|
|
|
@@ -792,6 +829,17 @@
|
|
|
792
829
|
}
|
|
793
830
|
|
|
794
831
|
function handleMouseUp(e: MouseEvent) {
|
|
832
|
+
if (
|
|
833
|
+
pendingGridMenuOpen &&
|
|
834
|
+
onGridMenuOpen &&
|
|
835
|
+
rowMarkerWidth > 0 &&
|
|
836
|
+
e.offsetX < rowMarkerWidth &&
|
|
837
|
+
e.offsetY < headerHeight
|
|
838
|
+
) {
|
|
839
|
+
onGridMenuOpen({ x: e.clientX, y: e.clientY });
|
|
840
|
+
}
|
|
841
|
+
pendingGridMenuOpen = false;
|
|
842
|
+
|
|
795
843
|
if (draggingHeaderCol !== null && onColumnMoved) {
|
|
796
844
|
const fixedBoundaryX =
|
|
797
845
|
fixedColumns > 0
|
|
@@ -883,6 +931,122 @@
|
|
|
883
931
|
export function setAnnouncement(msg: string): void {
|
|
884
932
|
announcement = msg;
|
|
885
933
|
}
|
|
934
|
+
|
|
935
|
+
function getLocalPointFromClient(clientX: number, clientY: number) {
|
|
936
|
+
const rect = canvasRef?.getBoundingClientRect();
|
|
937
|
+
if (!rect) return null;
|
|
938
|
+
|
|
939
|
+
return {
|
|
940
|
+
offsetX: clientX - rect.left,
|
|
941
|
+
offsetY: clientY - rect.top,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function handleTouchTap(offsetX: number, offsetY: number) {
|
|
946
|
+
containerRef?.focus();
|
|
947
|
+
|
|
948
|
+
if (
|
|
949
|
+
onGridMenuOpen &&
|
|
950
|
+
rowMarkerWidth > 0 &&
|
|
951
|
+
offsetX < rowMarkerWidth &&
|
|
952
|
+
offsetY < headerHeight
|
|
953
|
+
) {
|
|
954
|
+
const rect = canvasRef?.getBoundingClientRect();
|
|
955
|
+
onGridMenuOpen({
|
|
956
|
+
x: (rect?.left ?? 0) + offsetX,
|
|
957
|
+
y: (rect?.top ?? 0) + offsetY,
|
|
958
|
+
});
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const fixedBoundaryX =
|
|
963
|
+
fixedColumns > 0
|
|
964
|
+
? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
|
|
965
|
+
: 0;
|
|
966
|
+
const localX = offsetX - rowMarkerWidth;
|
|
967
|
+
const effectiveScrollX =
|
|
968
|
+
fixedColumns > 0 && localX >= 0 && localX < fixedBoundaryX ? 0 : scrollX;
|
|
969
|
+
|
|
970
|
+
if (offsetY < headerHeight) {
|
|
971
|
+
const col = getColumnFromX(localX + effectiveScrollX, effectiveColumns);
|
|
972
|
+
if (col >= 0 && col < columns.length) {
|
|
973
|
+
toggleSort(col, false);
|
|
974
|
+
if (!readonly) {
|
|
975
|
+
currentSelection = { type: "column", columns: [col] };
|
|
976
|
+
onSelectionChange?.(currentSelection);
|
|
977
|
+
announcement = `Column ${columns[col].title} selected`;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (rowMarkers !== "none" && offsetX < rowMarkerWidth) {
|
|
984
|
+
const row = Math.floor((offsetY - headerHeight + scrollY) / rowHeight);
|
|
985
|
+
if (row >= 0 && row < rows) {
|
|
986
|
+
onRowToggle?.(row);
|
|
987
|
+
}
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const cell = getCellFromPoint(
|
|
992
|
+
localX,
|
|
993
|
+
offsetY,
|
|
994
|
+
effectiveScrollX,
|
|
995
|
+
scrollY,
|
|
996
|
+
effectiveColumns,
|
|
997
|
+
rowHeight,
|
|
998
|
+
headerHeight,
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
if (cell) {
|
|
1002
|
+
focusedCell = cell;
|
|
1003
|
+
currentSelection = { type: "cell", item: cell };
|
|
1004
|
+
onSelectionChange?.(currentSelection);
|
|
1005
|
+
announcement = `Row ${cell[1] + 1}, column ${columns[cell[0]]?.title ?? cell[0] + 1}`;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function handleTouchStart(e: TouchEvent) {
|
|
1010
|
+
if (e.touches.length !== 1) return;
|
|
1011
|
+
|
|
1012
|
+
const touch = e.touches[0];
|
|
1013
|
+
touchStartClientX = touch.clientX;
|
|
1014
|
+
touchStartClientY = touch.clientY;
|
|
1015
|
+
touchStartScrollX = scrollRef?.scrollLeft ?? 0;
|
|
1016
|
+
touchStartScrollY = scrollRef?.scrollTop ?? 0;
|
|
1017
|
+
isTouchScrolling = false;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function handleTouchMove(e: TouchEvent) {
|
|
1021
|
+
if (e.touches.length !== 1 || !scrollRef) return;
|
|
1022
|
+
|
|
1023
|
+
const touch = e.touches[0];
|
|
1024
|
+
const deltaX = touch.clientX - touchStartClientX;
|
|
1025
|
+
const deltaY = touch.clientY - touchStartClientY;
|
|
1026
|
+
|
|
1027
|
+
if (!isTouchScrolling && (Math.abs(deltaX) > 8 || Math.abs(deltaY) > 8)) {
|
|
1028
|
+
isTouchScrolling = true;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (!isTouchScrolling) return;
|
|
1032
|
+
|
|
1033
|
+
e.preventDefault();
|
|
1034
|
+
scrollRef.scrollLeft = touchStartScrollX - deltaX;
|
|
1035
|
+
scrollRef.scrollTop = touchStartScrollY - deltaY;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function handleTouchEnd(e: TouchEvent) {
|
|
1039
|
+
const touch = e.changedTouches[0];
|
|
1040
|
+
if (!touch) return;
|
|
1041
|
+
|
|
1042
|
+
const point = getLocalPointFromClient(touch.clientX, touch.clientY);
|
|
1043
|
+
const wasScrolling = isTouchScrolling;
|
|
1044
|
+
isTouchScrolling = false;
|
|
1045
|
+
|
|
1046
|
+
if (!point || wasScrolling) return;
|
|
1047
|
+
|
|
1048
|
+
handleTouchTap(point.offsetX, point.offsetY);
|
|
1049
|
+
}
|
|
886
1050
|
</script>
|
|
887
1051
|
|
|
888
1052
|
<div
|
|
@@ -893,6 +1057,10 @@
|
|
|
893
1057
|
aria-rowcount={rows}
|
|
894
1058
|
aria-colcount={columns.length}
|
|
895
1059
|
aria-label="Data grid"
|
|
1060
|
+
aria-describedby={`${gridId}-instructions ${gridId}-selection ${gridId}-active`}
|
|
1061
|
+
aria-activedescendant={focusedCell ? `${gridId}-active` : undefined}
|
|
1062
|
+
aria-multiselectable="true"
|
|
1063
|
+
aria-readonly={readonly}
|
|
896
1064
|
style:width={containerWidthStyle}
|
|
897
1065
|
style:height={containerHeightStyle}
|
|
898
1066
|
style:--gridler-border={mergedTheme.borderColor}
|
|
@@ -916,6 +1084,9 @@
|
|
|
916
1084
|
onmousemove={handleMouseMove}
|
|
917
1085
|
onmouseup={handleMouseUp}
|
|
918
1086
|
onmouseleave={handleMouseLeave}
|
|
1087
|
+
ontouchstart={handleTouchStart}
|
|
1088
|
+
ontouchmove={handleTouchMove}
|
|
1089
|
+
ontouchend={handleTouchEnd}
|
|
919
1090
|
ondblclick={handleDblClick}
|
|
920
1091
|
onwheel={handleWheel}
|
|
921
1092
|
oncontextmenu={handleContextMenu}
|
|
@@ -956,6 +1127,15 @@
|
|
|
956
1127
|
/>
|
|
957
1128
|
{/if}
|
|
958
1129
|
|
|
1130
|
+
<div class="sr-only" id={`${gridId}-instructions`}>
|
|
1131
|
+
Swipe with one finger to scroll the grid on touch devices. Tap a cell to select it. Use arrow keys to move between cells when the grid is focused.
|
|
1132
|
+
</div>
|
|
1133
|
+
<div class="sr-only" id={`${gridId}-selection`}>
|
|
1134
|
+
{selectionDescription}
|
|
1135
|
+
</div>
|
|
1136
|
+
<div class="sr-only" id={`${gridId}-active`} role="gridcell">
|
|
1137
|
+
{activeCellDescription}
|
|
1138
|
+
</div>
|
|
959
1139
|
<div role="status" aria-live="polite" class="sr-only">
|
|
960
1140
|
{announcement}
|
|
961
1141
|
</div>
|
|
@@ -986,6 +1166,8 @@
|
|
|
986
1166
|
position: relative;
|
|
987
1167
|
z-index: 1;
|
|
988
1168
|
scrollbar-width: none;
|
|
1169
|
+
-webkit-overflow-scrolling: touch;
|
|
1170
|
+
overscroll-behavior: contain;
|
|
989
1171
|
}
|
|
990
1172
|
|
|
991
1173
|
.gridler-scroll::-webkit-scrollbar {
|
|
@@ -1003,6 +1185,7 @@
|
|
|
1003
1185
|
left: 0;
|
|
1004
1186
|
z-index: 2;
|
|
1005
1187
|
cursor: default;
|
|
1188
|
+
touch-action: none;
|
|
1006
1189
|
}
|
|
1007
1190
|
|
|
1008
1191
|
.sr-only {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warkypublic/svelix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "Svelte 5 component library with Skeleton UI and Tailwind CSS",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
@@ -26,43 +26,43 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@changesets/cli": "^2.30.0",
|
|
29
|
-
"@chromatic-com/storybook": "^5.0.
|
|
29
|
+
"@chromatic-com/storybook": "^5.0.2",
|
|
30
30
|
"@eslint/compat": "^2.0.3",
|
|
31
31
|
"@eslint/js": "^10.0.1",
|
|
32
32
|
"@playwright/test": "^1.58.2",
|
|
33
|
-
"@sentry/svelte": "^10.
|
|
34
|
-
"@skeletonlabs/skeleton": "^4.
|
|
35
|
-
"@skeletonlabs/skeleton-svelte": "^4.
|
|
36
|
-
"@storybook/addon-a11y": "^10.
|
|
37
|
-
"@storybook/addon-docs": "^10.
|
|
38
|
-
"@storybook/addon-svelte-csf": "^5.0.
|
|
39
|
-
"@storybook/addon-vitest": "^10.
|
|
40
|
-
"@storybook/sveltekit": "^10.
|
|
33
|
+
"@sentry/svelte": "^10.45.0",
|
|
34
|
+
"@skeletonlabs/skeleton": "^4.13.0",
|
|
35
|
+
"@skeletonlabs/skeleton-svelte": "^4.13.0",
|
|
36
|
+
"@storybook/addon-a11y": "^10.3.1",
|
|
37
|
+
"@storybook/addon-docs": "^10.3.1",
|
|
38
|
+
"@storybook/addon-svelte-csf": "^5.0.12",
|
|
39
|
+
"@storybook/addon-vitest": "^10.3.1",
|
|
40
|
+
"@storybook/sveltekit": "^10.3.1",
|
|
41
41
|
"@storybook/test": "^8.6.15",
|
|
42
42
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
43
|
-
"@sveltejs/kit": "^2.
|
|
43
|
+
"@sveltejs/kit": "^2.55.0",
|
|
44
44
|
"@sveltejs/package": "^2.5.7",
|
|
45
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
46
|
-
"@tailwindcss/vite": "^4.2.
|
|
47
|
-
"@tanstack/svelte-virtual": "^3.13.
|
|
48
|
-
"@types/node": "^25.
|
|
49
|
-
"@vitest/browser-playwright": "^4.0
|
|
50
|
-
"@vitest/coverage-v8": "^4.0
|
|
45
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
46
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
47
|
+
"@tanstack/svelte-virtual": "^3.13.23",
|
|
48
|
+
"@types/node": "^25.5.0",
|
|
49
|
+
"@vitest/browser-playwright": "^4.1.0",
|
|
50
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
51
51
|
"eslint": "^10.0.3",
|
|
52
52
|
"eslint-plugin-svelte": "^3.15.2",
|
|
53
53
|
"globals": "^17.4.0",
|
|
54
54
|
"playwright": "^1.58.2",
|
|
55
55
|
"publint": "^0.3.18",
|
|
56
|
-
"storybook": "^10.
|
|
57
|
-
"svelte": "^5.
|
|
56
|
+
"storybook": "^10.3.1",
|
|
57
|
+
"svelte": "^5.54.0",
|
|
58
58
|
"svelte-check": "^4.4.5",
|
|
59
|
-
"tailwindcss": "^4.2.
|
|
59
|
+
"tailwindcss": "^4.2.2",
|
|
60
60
|
"tslib": "^2.8.1",
|
|
61
61
|
"typescript": "^5.9.3",
|
|
62
|
-
"typescript-eslint": "^8.57.
|
|
63
|
-
"vite": "^
|
|
62
|
+
"typescript-eslint": "^8.57.1",
|
|
63
|
+
"vite": "^8.0.1",
|
|
64
64
|
"vite-plugin-monaco-editor": "^1.1.0",
|
|
65
|
-
"vitest": "^4.0
|
|
65
|
+
"vitest": "^4.1.0"
|
|
66
66
|
},
|
|
67
67
|
"svelte": "./dist/index.js",
|
|
68
68
|
"types": "./dist/index.d.ts",
|
|
@@ -76,13 +76,13 @@
|
|
|
76
76
|
"@cartamd/plugin-math": "^4.3.1",
|
|
77
77
|
"@friendofsvelte/tipex": "^0.1.1",
|
|
78
78
|
"@js-temporal/polyfill": "^0.5.1",
|
|
79
|
-
"@svar-ui/svelte-grid": "^2.
|
|
79
|
+
"@svar-ui/svelte-grid": "^2.6.0",
|
|
80
80
|
"@warkypublic/resolvespec-js": "^1.0.1",
|
|
81
81
|
"@warkypublic/artemis-kit": "^1.0.10",
|
|
82
82
|
"carta-md": "^4.11.1",
|
|
83
83
|
"github-markdown-css": "^5.9.0",
|
|
84
|
-
"isomorphic-dompurify": "^3.1
|
|
85
|
-
"katex": "^0.16.
|
|
84
|
+
"isomorphic-dompurify": "^3.5.1",
|
|
85
|
+
"katex": "^0.16.39",
|
|
86
86
|
"monaco-editor": "^0.55.1"
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|