wx-svelte-grid 2.6.1 → 2.7.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/package.json +11 -11
- package/src/components/Cell.svelte +1 -8
- package/src/components/Grid.svelte +0 -1
- package/src/components/HeaderCell.svelte +1 -1
- package/src/components/Layout.svelte +60 -27
- package/src/components/MultiSelect.svelte +197 -0
- package/src/components/Tooltip.svelte +27 -103
- package/src/components/inlineEditors/MultiSelect.svelte +32 -94
- package/src/components/inlineEditors/Text.svelte +2 -1
- package/src/components/inlineFilters/MultiSelect.svelte +55 -0
- package/src/components/inlineFilters/filters.js +2 -0
- package/types/index.d.ts +27 -11
- package/whatsnew.md +29 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wx-svelte-grid",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "A fast, feature-rich Svelte DataGrid component",
|
|
5
5
|
"productTag": "grid",
|
|
6
6
|
"productTrial": false,
|
|
@@ -33,19 +33,19 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://svar.dev/svelte/datagrid/",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@svar-ui/lib-dom": "0.
|
|
37
|
-
"@svar-ui/lib-state": "1.9.
|
|
36
|
+
"@svar-ui/lib-dom": "0.13.1",
|
|
37
|
+
"@svar-ui/lib-state": "1.9.7",
|
|
38
38
|
"@svar-ui/lib-svelte": "0.5.2",
|
|
39
|
-
"@svar-ui/svelte-menu": "2.
|
|
40
|
-
"@svar-ui/svelte-core": "2.
|
|
41
|
-
"@svar-ui/svelte-toolbar": "2.
|
|
42
|
-
"@svar-ui/grid-data-provider": "2.
|
|
43
|
-
"@svar-ui/grid-locales": "2.
|
|
44
|
-
"@svar-ui/grid-store": "2.
|
|
39
|
+
"@svar-ui/svelte-menu": "2.6.0",
|
|
40
|
+
"@svar-ui/svelte-core": "2.6.0",
|
|
41
|
+
"@svar-ui/svelte-toolbar": "2.6.0",
|
|
42
|
+
"@svar-ui/grid-data-provider": "2.7.0",
|
|
43
|
+
"@svar-ui/grid-locales": "2.7.0",
|
|
44
|
+
"@svar-ui/grid-store": "2.7.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@svar-ui/svelte-editor": "2.
|
|
48
|
-
"@svar-ui/svelte-filter": "2.
|
|
47
|
+
"@svar-ui/svelte-editor": "2.6.0",
|
|
48
|
+
"@svar-ui/svelte-filter": "2.6.0"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"src",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import {
|
|
2
|
+
import { getContext, untrack } from "svelte";
|
|
3
3
|
import { getStyle } from "../helpers/columnWidth";
|
|
4
4
|
import { getRenderValue } from "@svar-ui/grid-store";
|
|
5
5
|
import { setID } from "@svar-ui/lib-dom";
|
|
@@ -72,13 +72,6 @@
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
onDestroy(() => {
|
|
76
|
-
if (focusable && $focusCell) {
|
|
77
|
-
api.exec("focus-cell", { eventSource: "destroy" });
|
|
78
|
-
focusable = false;
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
75
|
function highlightText(text) {
|
|
83
76
|
const regex = new RegExp(`(${$search.value.trim()})`, "gi");
|
|
84
77
|
const parts = String(text).split(regex);
|
|
@@ -97,7 +97,6 @@
|
|
|
97
97
|
getReactiveState: dataStore.getReactive.bind(dataStore),
|
|
98
98
|
exec: firstInRoute.exec.bind(firstInRoute),
|
|
99
99
|
getRow: dataStore.getRow.bind(dataStore),
|
|
100
|
-
getRowIndex: dataStore.getRowIndex.bind(dataStore),
|
|
101
100
|
});
|
|
102
101
|
// auto config columns
|
|
103
102
|
const finalColumns = $derived.by(() => {
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
? "0"
|
|
128
128
|
: undefined}
|
|
129
129
|
role="columnheader"
|
|
130
|
-
aria-colindex={
|
|
130
|
+
aria-colindex={column._colindex}
|
|
131
131
|
aria-colspan={cell.colspan > 1 ? cell.colspan : undefined}
|
|
132
132
|
aria-rowspan={cell.rowspan > 1 ? cell.rowspan : undefined}
|
|
133
133
|
aria-sort={!sortMark?.order || cell.filter
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { getContext, tick, onMount } from "svelte";
|
|
2
|
+
import { getContext, tick, onMount, untrack } from "svelte";
|
|
3
3
|
import { onresize } from "../helpers/actions/onresize";
|
|
4
4
|
import { reorder as drag, getOffset } from "../helpers/actions/reorder";
|
|
5
5
|
import {
|
|
@@ -53,6 +53,8 @@
|
|
|
53
53
|
select,
|
|
54
54
|
editor,
|
|
55
55
|
scroll,
|
|
56
|
+
scrollLeft,
|
|
57
|
+
scrollTop,
|
|
56
58
|
tree,
|
|
57
59
|
focusCell,
|
|
58
60
|
_print,
|
|
@@ -65,8 +67,7 @@
|
|
|
65
67
|
let SCROLLSIZE = $state(0);
|
|
66
68
|
onMount(() => (SCROLLSIZE = getScrollSize()));
|
|
67
69
|
|
|
68
|
-
let
|
|
69
|
-
scrollTop = $state(0);
|
|
70
|
+
let bodyClientHeight = $state(0);
|
|
70
71
|
const hasAny = $derived.by(() => {
|
|
71
72
|
return $_columns.some(col => !col.hidden && col.flexgrow);
|
|
72
73
|
});
|
|
@@ -168,8 +169,8 @@
|
|
|
168
169
|
let data, header, footer;
|
|
169
170
|
|
|
170
171
|
// get visible columns
|
|
171
|
-
const left = scrollLeft;
|
|
172
|
-
const right = scrollLeft + clientWidth;
|
|
172
|
+
const left = $scrollLeft;
|
|
173
|
+
const right = $scrollLeft + clientWidth;
|
|
173
174
|
|
|
174
175
|
let start = 0;
|
|
175
176
|
let end = 0;
|
|
@@ -247,25 +248,36 @@
|
|
|
247
248
|
});
|
|
248
249
|
// $inspect(renderColumns, "renderColumns");
|
|
249
250
|
|
|
250
|
-
const contentWidth = $derived(
|
|
251
|
-
hasAny && fullWidth <= clientWidth
|
|
252
|
-
? clientWidth - (hasVScroll ? SCROLLSIZE : 0)
|
|
253
|
-
: fullWidth
|
|
254
|
-
);
|
|
255
|
-
// $inspect(contentWidth, "contentWidth");
|
|
256
|
-
|
|
257
251
|
const headerHeight = $derived(header ? $_sizes.headerHeight : 0);
|
|
258
252
|
const footerHeight = $derived(footer ? $_sizes.footerHeight : 0);
|
|
259
253
|
|
|
260
|
-
const hasVScroll = $derived(
|
|
261
|
-
clientWidth && clientHeight
|
|
262
|
-
? fullHeight + headerHeight + footerHeight >=
|
|
263
|
-
clientHeight - (fullWidth >= clientWidth ? SCROLLSIZE : 0)
|
|
264
|
-
: false
|
|
265
|
-
);
|
|
266
254
|
const hasHScroll = $derived(
|
|
267
255
|
clientWidth && clientHeight ? fullWidth >= clientWidth : false
|
|
268
256
|
);
|
|
257
|
+
let hasVScroll = $state(false);
|
|
258
|
+
|
|
259
|
+
function setVScroll() {
|
|
260
|
+
hasVScroll =
|
|
261
|
+
clientWidth && clientHeight
|
|
262
|
+
? fullHeight + headerHeight + footerHeight >=
|
|
263
|
+
clientHeight - (fullWidth >= clientWidth ? SCROLLSIZE : 0)
|
|
264
|
+
: false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
$effect(() => {
|
|
268
|
+
bodyClientHeight;
|
|
269
|
+
untrack(() => requestAnimationFrame(setVScroll));
|
|
270
|
+
});
|
|
271
|
+
$effect(() => {
|
|
272
|
+
clientHeight;
|
|
273
|
+
untrack(setVScroll);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const contentWidth = $derived(
|
|
277
|
+
hasAny && fullWidth <= clientWidth
|
|
278
|
+
? clientWidth - (hasVScroll ? SCROLLSIZE : 0)
|
|
279
|
+
: fullWidth
|
|
280
|
+
);
|
|
269
281
|
|
|
270
282
|
// set global width
|
|
271
283
|
// if we have flexible columns
|
|
@@ -297,14 +309,14 @@
|
|
|
297
309
|
let start = 0,
|
|
298
310
|
deltaTop = 0;
|
|
299
311
|
if (autoRowHeight) {
|
|
300
|
-
let st = scrollTop;
|
|
312
|
+
let st = $scrollTop;
|
|
301
313
|
while (st > 0) {
|
|
302
314
|
st -= rowHeights[start] || defaultRowHeight;
|
|
303
315
|
start++;
|
|
304
316
|
}
|
|
305
317
|
|
|
306
318
|
// space to first rendered row
|
|
307
|
-
deltaTop = scrollTop - st;
|
|
319
|
+
deltaTop = $scrollTop - st;
|
|
308
320
|
for (let i = Math.max(0, start - EXTRAROWS - 1); i < start; i++)
|
|
309
321
|
deltaTop -= rowHeights[start - i] || defaultRowHeight;
|
|
310
322
|
|
|
@@ -315,7 +327,7 @@
|
|
|
315
327
|
let topHeight = 0;
|
|
316
328
|
for (let i = 0; i < $data.length; i++) {
|
|
317
329
|
const height = $data[i].rowHeight || defaultRowHeight;
|
|
318
|
-
if (topHeight + height > scrollTop) {
|
|
330
|
+
if (topHeight + height > $scrollTop) {
|
|
319
331
|
startInd = i;
|
|
320
332
|
break;
|
|
321
333
|
}
|
|
@@ -346,7 +358,7 @@
|
|
|
346
358
|
return { d: deltaTop, start, end };
|
|
347
359
|
}
|
|
348
360
|
|
|
349
|
-
start = Math.floor(scrollTop / defaultRowHeight);
|
|
361
|
+
start = Math.floor($scrollTop / defaultRowHeight);
|
|
350
362
|
start = Math.max(0, start - EXTRAROWS);
|
|
351
363
|
deltaTop = start * defaultRowHeight;
|
|
352
364
|
}
|
|
@@ -388,8 +400,10 @@
|
|
|
388
400
|
let renderEnd = $state();
|
|
389
401
|
|
|
390
402
|
function onScroll(ev) {
|
|
391
|
-
|
|
392
|
-
|
|
403
|
+
const top = ev.target.scrollTop;
|
|
404
|
+
const left = ev.target.scrollLeft;
|
|
405
|
+
if (top !== $scrollTop || left !== $scrollLeft)
|
|
406
|
+
api.exec("scroll-to", { top, left });
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
function lockSelection(ev) {
|
|
@@ -479,7 +493,7 @@
|
|
|
479
493
|
.forEach(element => element.setAttribute("tabindex", "-1"));
|
|
480
494
|
container.appendChild(dragNode);
|
|
481
495
|
|
|
482
|
-
const offsetX = scrollLeft - renderColumns.d;
|
|
496
|
+
const offsetX = $scrollLeft - renderColumns.d;
|
|
483
497
|
const vScrollSize = hasVScroll ? SCROLLSIZE : 0;
|
|
484
498
|
|
|
485
499
|
container.style.width =
|
|
@@ -531,7 +545,7 @@
|
|
|
531
545
|
? dragNode?.offsetHeight
|
|
532
546
|
: $_sizes.rowHeight;
|
|
533
547
|
|
|
534
|
-
if (scrollTop === 0 || pos.y > min + rowHeight - 1) {
|
|
548
|
+
if ($scrollTop === 0 || pos.y > min + rowHeight - 1) {
|
|
535
549
|
const targetRect = targetRow.getBoundingClientRect();
|
|
536
550
|
const dragNodeOffset = getOffset(dragNode);
|
|
537
551
|
|
|
@@ -540,8 +554,10 @@
|
|
|
540
554
|
|
|
541
555
|
const dir = dragNodePos > targetNodePos ? -1 : 1;
|
|
542
556
|
const initialMode = dir === 1 ? "after" : "before";
|
|
557
|
+
const flat = api.getState().flatData;
|
|
543
558
|
const diff = Math.abs(
|
|
544
|
-
|
|
559
|
+
flat.findIndex(r => r.id === from) -
|
|
560
|
+
flat.findIndex(r => r.id === to)
|
|
545
561
|
);
|
|
546
562
|
|
|
547
563
|
const mode =
|
|
@@ -657,6 +673,20 @@
|
|
|
657
673
|
|
|
658
674
|
$effect(() => dataRows && autoRowHeight && adjustHeight());
|
|
659
675
|
|
|
676
|
+
$effect(() => {
|
|
677
|
+
if ($focusCell) {
|
|
678
|
+
const rowExists = dataRows.some(row => row.id === $focusCell.row);
|
|
679
|
+
const cellExists =
|
|
680
|
+
rowExists &&
|
|
681
|
+
renderColumns.data.some(
|
|
682
|
+
col => col.id === $focusCell.column && !col.collapsed
|
|
683
|
+
);
|
|
684
|
+
if (!cellExists) {
|
|
685
|
+
api.exec("focus-cell", { eventSource: "destroy" });
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
|
|
660
690
|
/* focus is a focusable cell which either belongs to visible selection
|
|
661
691
|
or is the first visible cell in grid, which maybe scrolled up due to EXTRAROWS
|
|
662
692
|
If select is false, focusCell can be outside selection*/
|
|
@@ -731,6 +761,8 @@
|
|
|
731
761
|
onscroll={onScroll}
|
|
732
762
|
use:scrollTo={{
|
|
733
763
|
scroll,
|
|
764
|
+
scrollLeft,
|
|
765
|
+
scrollTop,
|
|
734
766
|
getWidth: () => clientWidth - (hasVScroll ? SCROLLSIZE : 0),
|
|
735
767
|
getHeight: () => visibleRowsHeight,
|
|
736
768
|
getScrollMargin: () => leftColumns.width + rightColumns.width,
|
|
@@ -756,6 +788,7 @@
|
|
|
756
788
|
$focusCell &&
|
|
757
789
|
api.exec("focus-cell", { eventSource: "click" })}
|
|
758
790
|
use:delegateClick={bodyClickHandlers}
|
|
791
|
+
bind:clientHeight={bodyClientHeight}
|
|
759
792
|
>
|
|
760
793
|
{#if overlay}
|
|
761
794
|
<Overlay {overlay} />
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { SuggestDropdown } from "@svar-ui/svelte-core";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
value = $bindable([]),
|
|
7
|
+
options = [],
|
|
8
|
+
placeholder = "",
|
|
9
|
+
clear = false,
|
|
10
|
+
text = null,
|
|
11
|
+
template = null,
|
|
12
|
+
cell = null,
|
|
13
|
+
dropdown = {},
|
|
14
|
+
autoOpen = false,
|
|
15
|
+
onchange,
|
|
16
|
+
onaction,
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
const selected = $derived(
|
|
20
|
+
(value || []).map(id => options.find(o => o.id === id)).filter(Boolean)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
let node = $state();
|
|
24
|
+
let navigate;
|
|
25
|
+
let keydown;
|
|
26
|
+
function ready(ev) {
|
|
27
|
+
navigate = ev.navigate;
|
|
28
|
+
keydown = ev.keydown;
|
|
29
|
+
if (autoOpen) navigate(index());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onMount(() => {
|
|
33
|
+
if (autoOpen) {
|
|
34
|
+
node?.focus();
|
|
35
|
+
if (window?.getSelection) window.getSelection().removeAllRanges();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const index = () => {
|
|
40
|
+
const v = value || [];
|
|
41
|
+
if (!v.length) return 0;
|
|
42
|
+
const firstSelected = options.find(o => v.includes(o.id));
|
|
43
|
+
return firstSelected ? options.indexOf(firstSelected) : 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function select({ id }) {
|
|
47
|
+
value = id;
|
|
48
|
+
onchange && onchange({ value });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function unselect(ev) {
|
|
52
|
+
ev.stopPropagation();
|
|
53
|
+
value = [];
|
|
54
|
+
onchange && onchange({ value });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function onclick() {
|
|
58
|
+
navigate?.(index());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function oncancel() {
|
|
62
|
+
navigate?.(null);
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
67
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
68
|
+
<div
|
|
69
|
+
bind:this={node}
|
|
70
|
+
class="wx-multiselect"
|
|
71
|
+
{onclick}
|
|
72
|
+
onkeydown={ev => keydown?.(ev, index())}
|
|
73
|
+
tabindex="0"
|
|
74
|
+
>
|
|
75
|
+
<div class="wx-label">
|
|
76
|
+
{#if template}
|
|
77
|
+
{template(selected)}
|
|
78
|
+
{:else if cell}
|
|
79
|
+
{@const CellComponent = cell}
|
|
80
|
+
<CellComponent data={selected} {onaction} />
|
|
81
|
+
{:else if text}
|
|
82
|
+
<span class="wx-text">{text}</span>
|
|
83
|
+
{:else if selected.length}
|
|
84
|
+
<span class="wx-text">{selected.map(s => s.label).join(", ")}</span>
|
|
85
|
+
{:else if placeholder}
|
|
86
|
+
<span class="wx-placeholder">{placeholder}</span>
|
|
87
|
+
{:else} {/if}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{#if clear && value?.length}
|
|
91
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
92
|
+
<i class="wx-icon wxi-close" onclick={unselect}></i>
|
|
93
|
+
{:else}<i class="wx-icon wxi-angle-down"></i>{/if}
|
|
94
|
+
|
|
95
|
+
<SuggestDropdown
|
|
96
|
+
items={options}
|
|
97
|
+
onready={ready}
|
|
98
|
+
onselect={select}
|
|
99
|
+
multiselect={true}
|
|
100
|
+
checkboxes={true}
|
|
101
|
+
value={value || []}
|
|
102
|
+
{oncancel}
|
|
103
|
+
{...dropdown}
|
|
104
|
+
>
|
|
105
|
+
{#snippet children({ option })}
|
|
106
|
+
<div class="wx-option">
|
|
107
|
+
{#if template}
|
|
108
|
+
{template(option)}
|
|
109
|
+
{:else if cell}
|
|
110
|
+
{@const CellComponent = cell}
|
|
111
|
+
<CellComponent data={option} {onaction} />
|
|
112
|
+
{:else}{option.label}{/if}
|
|
113
|
+
</div>
|
|
114
|
+
{/snippet}
|
|
115
|
+
</SuggestDropdown>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<style>
|
|
119
|
+
.wx-multiselect {
|
|
120
|
+
position: relative;
|
|
121
|
+
outline: none;
|
|
122
|
+
width: var(--wx-input-width);
|
|
123
|
+
min-height: var(--wx-input-height);
|
|
124
|
+
border: var(--wx-input-border);
|
|
125
|
+
border-radius: var(--wx-input-border-radius);
|
|
126
|
+
background: var(--wx-input-background);
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
}
|
|
129
|
+
.wx-multiselect:focus {
|
|
130
|
+
border: var(--wx-input-border-focus);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.wx-label {
|
|
134
|
+
position: relative;
|
|
135
|
+
top: 50%;
|
|
136
|
+
transform: translateY(-50%);
|
|
137
|
+
display: block;
|
|
138
|
+
width: 100%;
|
|
139
|
+
font-family: var(--wx-input-font-family);
|
|
140
|
+
font-size: var(--wx-input-font-size);
|
|
141
|
+
line-height: var(--wx-input-line-height);
|
|
142
|
+
font-weight: var(--wx-input-font-weight);
|
|
143
|
+
text-align: var(--wx-input-text-align);
|
|
144
|
+
color: var(--wx-input-font-color);
|
|
145
|
+
padding: var(--wx-input-padding);
|
|
146
|
+
padding-right: calc(
|
|
147
|
+
var(--wx-input-icon-size) + var(--wx-input-icon-indent) * 2
|
|
148
|
+
);
|
|
149
|
+
overflow: hidden;
|
|
150
|
+
white-space: nowrap;
|
|
151
|
+
text-overflow: ellipsis;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.wx-text {
|
|
155
|
+
display: block;
|
|
156
|
+
overflow: hidden;
|
|
157
|
+
text-overflow: ellipsis;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.wx-placeholder {
|
|
161
|
+
color: var(--wx-input-placeholder-color);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.wx-icon {
|
|
165
|
+
position: absolute;
|
|
166
|
+
right: var(--wx-input-icon-indent);
|
|
167
|
+
top: 50%;
|
|
168
|
+
transform: translateY(-50%);
|
|
169
|
+
font-size: var(--wx-input-icon-size);
|
|
170
|
+
line-height: 1;
|
|
171
|
+
width: var(--wx-input-icon-size);
|
|
172
|
+
height: var(--wx-input-icon-size);
|
|
173
|
+
display: flex;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
align-items: center;
|
|
176
|
+
pointer-events: none;
|
|
177
|
+
user-select: none;
|
|
178
|
+
color: var(--wx-input-icon-color);
|
|
179
|
+
}
|
|
180
|
+
.wx-icon:before {
|
|
181
|
+
display: block;
|
|
182
|
+
}
|
|
183
|
+
.wx-icon.wxi-close {
|
|
184
|
+
pointer-events: all;
|
|
185
|
+
}
|
|
186
|
+
.wx-icon.wxi-close:hover {
|
|
187
|
+
background: var(--wx-background-hover);
|
|
188
|
+
border-radius: var(--wx-icon-border-radius);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.wx-option {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: flex-start;
|
|
195
|
+
gap: 8px;
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
@@ -1,116 +1,40 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { getRenderValue } from "@svar-ui/grid-store";
|
|
3
|
+
import { Tooltip } from "@svar-ui/svelte-core";
|
|
3
4
|
import { getID } from "@svar-ui/lib-dom";
|
|
4
5
|
|
|
5
|
-
let {
|
|
6
|
+
let {
|
|
7
|
+
api,
|
|
8
|
+
at = "point",
|
|
9
|
+
overflow = false,
|
|
10
|
+
content: Content = null,
|
|
11
|
+
resolver = defaultResolver,
|
|
12
|
+
...restProps
|
|
13
|
+
} = $props();
|
|
6
14
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let areaCoords = $state();
|
|
10
|
-
let tooltipData = $state();
|
|
11
|
-
let pos = $state();
|
|
15
|
+
function defaultResolver(element) {
|
|
16
|
+
if (!api) return null;
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (node.getAttribute) {
|
|
16
|
-
const id = getID(node, "data-row-id");
|
|
17
|
-
const colId = getID(node, "data-col-id");
|
|
18
|
-
if (id && api && colId) {
|
|
19
|
-
const col = api.getColumn(colId);
|
|
20
|
-
return { id, col, target: node };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
node = node.parentNode;
|
|
24
|
-
}
|
|
25
|
-
return { id: null, col: null, target: null };
|
|
26
|
-
}
|
|
18
|
+
const rowId = getID(element, "data-row-id");
|
|
19
|
+
const columnId = getID(element, "data-col-id");
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
if (tooltipNode) {
|
|
30
|
-
let tooltipCoords = tooltipNode.getBoundingClientRect();
|
|
31
|
-
if (tooltipCoords.right >= areaCoords.right) {
|
|
32
|
-
pos.left = areaCoords.width - tooltipCoords.width - 5;
|
|
33
|
-
}
|
|
34
|
-
if (tooltipCoords.bottom >= areaCoords.bottom) {
|
|
35
|
-
pos.top -= tooltipCoords.bottom - areaCoords.bottom + 2;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
});
|
|
21
|
+
if (!rowId || !columnId) return null;
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const debounce = code => {
|
|
43
|
-
clearTimeout(timer);
|
|
44
|
-
timer = setTimeout(() => {
|
|
45
|
-
code();
|
|
46
|
-
}, TIMEOUT);
|
|
47
|
-
};
|
|
48
|
-
function move(e) {
|
|
49
|
-
let { id, target, col } = findAttribute(e.target);
|
|
50
|
-
pos = null;
|
|
51
|
-
if (!id) {
|
|
52
|
-
clearTimeout(timer);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
debounce(() => {
|
|
56
|
-
let text = "";
|
|
57
|
-
if (id) {
|
|
58
|
-
tooltipData = getTooltipData(id);
|
|
59
|
-
text = getTooltipText(col);
|
|
60
|
-
}
|
|
61
|
-
let targetCoords = target.getBoundingClientRect();
|
|
62
|
-
areaCoords = area.getBoundingClientRect();
|
|
63
|
-
const top = targetCoords.top + targetCoords.height - areaCoords.top;
|
|
64
|
-
const left = e.clientX - areaCoords.left;
|
|
65
|
-
pos = { top, left, col, text };
|
|
66
|
-
});
|
|
67
|
-
}
|
|
23
|
+
const row = api.getRow(rowId);
|
|
24
|
+
const column = api.getColumn(columnId);
|
|
68
25
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
26
|
+
if (column.tooltip === false) return null;
|
|
27
|
+
if (overflow && element.scrollWidth <= element.clientWidth) return null;
|
|
72
28
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
29
|
+
if (Content) {
|
|
30
|
+
return { data: { row, column } };
|
|
31
|
+
} else {
|
|
32
|
+
if (typeof column.tooltip === "function") {
|
|
33
|
+
return column.tooltip(row);
|
|
34
|
+
}
|
|
35
|
+
return getRenderValue(row, column);
|
|
36
|
+
}
|
|
76
37
|
}
|
|
77
38
|
</script>
|
|
78
39
|
|
|
79
|
-
|
|
80
|
-
<div class="wx-area" bind:this={area} onmousemove={move}>
|
|
81
|
-
{#if pos && pos.col.tooltip !== false && (Content || pos.text)}
|
|
82
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
83
|
-
<div
|
|
84
|
-
class="tooltip"
|
|
85
|
-
role="alert"
|
|
86
|
-
tabindex="0"
|
|
87
|
-
bind:this={tooltipNode}
|
|
88
|
-
style="top:{pos.top}px;left:{pos.left}px"
|
|
89
|
-
>
|
|
90
|
-
{#if Content}
|
|
91
|
-
<Content data={tooltipData} />
|
|
92
|
-
{:else}{pos.text}{/if}
|
|
93
|
-
</div>
|
|
94
|
-
{/if}
|
|
95
|
-
{@render children()}
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<style>
|
|
99
|
-
.wx-area {
|
|
100
|
-
position: relative;
|
|
101
|
-
height: 100%;
|
|
102
|
-
width: 100%;
|
|
103
|
-
}
|
|
104
|
-
:global(.tooltip) {
|
|
105
|
-
padding: 2px 10px;
|
|
106
|
-
border-radius: 2px;
|
|
107
|
-
box-shadow: var(--wx-box-shadow);
|
|
108
|
-
pointer-events: none;
|
|
109
|
-
position: absolute;
|
|
110
|
-
z-index: 10;
|
|
111
|
-
font-size: var(--wx-font-size-sm);
|
|
112
|
-
font-family: var(--wx-font-family);
|
|
113
|
-
color: var(--wx-color-primary-font);
|
|
114
|
-
background-color: #1a1e21;
|
|
115
|
-
}
|
|
116
|
-
</style>
|
|
40
|
+
<Tooltip {at} content={Content} {resolver} {...restProps} />
|
|
@@ -1,121 +1,59 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { SuggestDropdown } from "@svar-ui/svelte-core";
|
|
3
2
|
import { clickOutside } from "@svar-ui/lib-dom";
|
|
4
|
-
import
|
|
3
|
+
import MultiSelect from "../MultiSelect.svelte";
|
|
5
4
|
|
|
6
|
-
let { editor, onaction, onsave, onapply
|
|
7
|
-
let { config } = $state(editor);
|
|
5
|
+
let { editor, onaction, onsave, onapply } = $props();
|
|
8
6
|
|
|
7
|
+
const config = $state(editor?.config || {});
|
|
9
8
|
const options = $derived(editor?.options ?? []);
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const value = $derived(editor?.value || []);
|
|
10
|
+
const text = $derived(editor?.renderedValue);
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const dropdownOptions = $derived({
|
|
13
|
+
trackScroll: true,
|
|
14
|
+
...(config.dropdown || {}),
|
|
16
15
|
});
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return { trackScroll: true, ...dropdown };
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function updateValue({ id }) {
|
|
24
|
-
onapply(id);
|
|
25
|
-
node.focus();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let navigate;
|
|
29
|
-
let keydown = $state();
|
|
30
|
-
|
|
31
|
-
function ready(ev) {
|
|
32
|
-
navigate = ev.navigate;
|
|
33
|
-
keydown = ev.keydown;
|
|
34
|
-
navigate(index);
|
|
17
|
+
function updateValue({ value }) {
|
|
18
|
+
onapply(value);
|
|
35
19
|
}
|
|
36
|
-
|
|
37
|
-
let node = $state();
|
|
38
|
-
onMount(() => {
|
|
39
|
-
node.focus();
|
|
40
|
-
if (window && window.getSelection) {
|
|
41
|
-
window.getSelection().removeAllRanges();
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
20
|
</script>
|
|
45
21
|
|
|
46
22
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
47
23
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
48
|
-
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
49
24
|
<div
|
|
50
|
-
bind:this={node}
|
|
51
25
|
class="wx-value"
|
|
52
|
-
|
|
53
|
-
onclick={oncancel}
|
|
54
|
-
onkeydown={ev => {
|
|
55
|
-
keydown(ev, index);
|
|
56
|
-
ev.preventDefault();
|
|
57
|
-
}}
|
|
26
|
+
onclick={() => onsave(true)}
|
|
58
27
|
use:clickOutside={() => onsave(true)}
|
|
59
28
|
>
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
29
|
+
<MultiSelect
|
|
30
|
+
{value}
|
|
31
|
+
{options}
|
|
32
|
+
{text}
|
|
33
|
+
template={config.template}
|
|
34
|
+
cell={config.cell}
|
|
35
|
+
clear={config.clear}
|
|
36
|
+
dropdown={dropdownOptions}
|
|
37
|
+
autoOpen
|
|
38
|
+
onchange={updateValue}
|
|
39
|
+
{onaction}
|
|
40
|
+
/>
|
|
70
41
|
</div>
|
|
71
42
|
|
|
72
|
-
<SuggestDropdown
|
|
73
|
-
items={options}
|
|
74
|
-
onready={ready}
|
|
75
|
-
onselect={updateValue}
|
|
76
|
-
checkboxes={true}
|
|
77
|
-
multiselect={true}
|
|
78
|
-
{...dropdownOptions}
|
|
79
|
-
{oncancel}
|
|
80
|
-
{value}
|
|
81
|
-
>
|
|
82
|
-
{#snippet children({ option })}
|
|
83
|
-
<div class="wx-option">
|
|
84
|
-
{#if config?.template}
|
|
85
|
-
{config.template(option)}
|
|
86
|
-
{:else if config?.cell}
|
|
87
|
-
{@const SvelteComponent = config.cell}
|
|
88
|
-
<SvelteComponent data={option} {onaction} />
|
|
89
|
-
{:else}
|
|
90
|
-
{option.label}
|
|
91
|
-
{/if}
|
|
92
|
-
</div>
|
|
93
|
-
{/snippet}
|
|
94
|
-
</SuggestDropdown>
|
|
95
|
-
|
|
96
43
|
<style>
|
|
97
|
-
.wx-
|
|
98
|
-
display: flex;
|
|
99
|
-
direction: row;
|
|
100
|
-
align-items: center;
|
|
101
|
-
justify-content: flex-start;
|
|
102
|
-
gap: 8px;
|
|
103
|
-
}
|
|
104
|
-
.wx-text {
|
|
44
|
+
.wx-value {
|
|
105
45
|
width: 100%;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
text-overflow: ellipsis;
|
|
46
|
+
height: 100%;
|
|
47
|
+
background: var(--wx-background);
|
|
109
48
|
}
|
|
110
|
-
|
|
111
|
-
.wx-value {
|
|
49
|
+
.wx-value :global(.wx-multiselect) {
|
|
112
50
|
width: 100%;
|
|
113
51
|
height: 100%;
|
|
114
|
-
padding: 8px;
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
outline: none;
|
|
117
52
|
border: 1px solid var(--wx-color-primary);
|
|
118
|
-
|
|
119
|
-
|
|
53
|
+
border-radius: 0;
|
|
54
|
+
background: var(--wx-background);
|
|
55
|
+
}
|
|
56
|
+
.wx-value :global(.wx-multiselect:focus) {
|
|
57
|
+
border: 1px solid var(--wx-color-primary);
|
|
120
58
|
}
|
|
121
59
|
</style>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
let { editor, onsave, onapply } = $props();
|
|
6
6
|
|
|
7
7
|
let value = $state(editor.value || "");
|
|
8
|
+
let { type = "text" } = $derived(editor?.config || {});
|
|
8
9
|
|
|
9
10
|
let node = $state();
|
|
10
11
|
onMount(() => node.focus());
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
oninput={updateValue}
|
|
26
27
|
onkeydown={closeAndSave}
|
|
27
28
|
bind:this={node}
|
|
28
|
-
type
|
|
29
|
+
{type}
|
|
29
30
|
{value}
|
|
30
31
|
/>
|
|
31
32
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { getContext } from "svelte";
|
|
3
|
+
import { locale } from "@svar-ui/lib-dom";
|
|
4
|
+
import { en } from "@svar-ui/grid-locales";
|
|
5
|
+
import MultiSelect from "../MultiSelect.svelte";
|
|
6
|
+
|
|
7
|
+
let { filter, column, action, filterValue } = $props();
|
|
8
|
+
|
|
9
|
+
const _ =
|
|
10
|
+
getContext("wx-i18n")?.getGroup("grid") || locale(en).getGroup("grid");
|
|
11
|
+
|
|
12
|
+
const config = $derived.by(() => {
|
|
13
|
+
const obj = filter?.config || {};
|
|
14
|
+
return { clear: true, ...obj };
|
|
15
|
+
});
|
|
16
|
+
let options = $derived(config.options || column.options);
|
|
17
|
+
|
|
18
|
+
const text = $derived.by(() => {
|
|
19
|
+
const len = filterValue?.length;
|
|
20
|
+
if (!len) return "";
|
|
21
|
+
if (len < 3)
|
|
22
|
+
return filterValue.map(v => column.optionsMap.get(v)).join(", ");
|
|
23
|
+
return len + " " + _("selected");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function filterRows({ value }) {
|
|
27
|
+
action({ value, key: column.id });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleKeyDown(ev) {
|
|
31
|
+
if (ev.key !== "Tab") ev.preventDefault();
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
36
|
+
<div style="width:100%;" onkeydown={handleKeyDown}>
|
|
37
|
+
<MultiSelect
|
|
38
|
+
placeholder={""}
|
|
39
|
+
{...config}
|
|
40
|
+
{options}
|
|
41
|
+
value={filterValue || []}
|
|
42
|
+
{text}
|
|
43
|
+
onchange={filterRows}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
:global(.wx-cell.wx-filter div.wx-multiselect) {
|
|
49
|
+
min-height: 28px;
|
|
50
|
+
height: 28px;
|
|
51
|
+
}
|
|
52
|
+
:global(.wx-cell.wx-filter div.wx-multiselect .wx-label) {
|
|
53
|
+
padding: 4px 8px;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import Text from "./Text.svelte";
|
|
2
2
|
import Richselect from "./Richselect.svelte";
|
|
3
3
|
import DatePicker from "./DatePicker.svelte";
|
|
4
|
+
import MultiSelect from "./MultiSelect.svelte";
|
|
4
5
|
|
|
5
6
|
export const filters = {
|
|
6
7
|
text: Text,
|
|
7
8
|
richselect: Richselect,
|
|
8
9
|
datepicker: DatePicker,
|
|
10
|
+
multiselect: MultiSelect,
|
|
9
11
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Component, ComponentProps } from "svelte";
|
|
2
2
|
import { ContextMenu as BaseContextMenu } from "@svar-ui/svelte-menu";
|
|
3
3
|
import { Toolbar as BaseToolbar } from "@svar-ui/svelte-toolbar";
|
|
4
|
+
import { Tooltip as BaseTooltip } from "@svar-ui/svelte-core";
|
|
4
5
|
|
|
5
6
|
import type {
|
|
6
7
|
IColumn,
|
|
@@ -34,19 +35,28 @@ export type TEditorHandlerConfig = (
|
|
|
34
35
|
column?: IColumn
|
|
35
36
|
) => TEditorType | IColumnEditorConfig | null;
|
|
36
37
|
|
|
38
|
+
export type IInnerApi = Pick<
|
|
39
|
+
IApi,
|
|
40
|
+
"exec" | "getState" | "getReactiveState" | "getRow"
|
|
41
|
+
>;
|
|
42
|
+
|
|
37
43
|
export interface ICellProps {
|
|
38
|
-
api:
|
|
44
|
+
api: IInnerApi;
|
|
39
45
|
row: IRow;
|
|
40
46
|
column: IColumn;
|
|
41
47
|
onaction: (ev: { action?: any; data?: { [key: string]: any } }) => void;
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
export interface IHeaderCellProps {
|
|
51
|
+
api: IInnerApi;
|
|
52
|
+
row: number;
|
|
53
|
+
column: IColumn;
|
|
54
|
+
cell: Omit<IHeaderCell, "cell">;
|
|
55
|
+
onaction: (ev: { action?: any; data?: { [key: string]: any } }) => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
export interface IHeaderCellConfig extends IHeaderCell {
|
|
45
|
-
cell?: Component<
|
|
46
|
-
ICellProps & {
|
|
47
|
-
cell: Omit<IHeaderCell, "cell">;
|
|
48
|
-
}
|
|
49
|
-
>;
|
|
59
|
+
cell?: Component<IHeaderCellProps>;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
export type TColumnHeaderConfig =
|
|
@@ -111,11 +121,17 @@ export declare const Toolbar: Component<
|
|
|
111
121
|
}
|
|
112
122
|
>;
|
|
113
123
|
|
|
114
|
-
export declare const Tooltip: Component<
|
|
115
|
-
content
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
export declare const Tooltip: Component<
|
|
125
|
+
Omit<ComponentProps<typeof BaseTooltip>, "content"> & {
|
|
126
|
+
content?: Component<{
|
|
127
|
+
data: {
|
|
128
|
+
row: IRow;
|
|
129
|
+
column: IColumn;
|
|
130
|
+
};
|
|
131
|
+
}>;
|
|
132
|
+
api?: IApi;
|
|
133
|
+
}
|
|
134
|
+
>;
|
|
119
135
|
|
|
120
136
|
export declare const Material: Component<{
|
|
121
137
|
fonts?: boolean;
|
package/whatsnew.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## 2.7.0
|
|
2
|
+
|
|
3
|
+
### New features
|
|
4
|
+
|
|
5
|
+
- Multi-select filter for grid columns
|
|
6
|
+
- Scroll-to action to force and listen to scroll movements
|
|
7
|
+
|
|
8
|
+
### Updates
|
|
9
|
+
|
|
10
|
+
- Extra Tooltip settings: arrow, delay, at, overlow, etc
|
|
11
|
+
- Ability to set input type for "text" editor
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- Closing an inline editor by clicking on another cell doesn't focus the cell clicked
|
|
16
|
+
- Incorrect type for header cells
|
|
17
|
+
- Columns flicker when a new row is added in a Grid with flexgrow
|
|
18
|
+
|
|
19
|
+
### Breaking changes
|
|
20
|
+
|
|
21
|
+
- Parameters of Tooltip content component changed from `{ row, column }` to `{ data: row, column } `
|
|
22
|
+
|
|
23
|
+
## 2.6.2
|
|
24
|
+
|
|
25
|
+
### Fixes
|
|
26
|
+
|
|
27
|
+
- Incorrect type for `filterValues`
|
|
28
|
+
- Multiselect editor value is not applied
|
|
29
|
+
|
|
1
30
|
## 2.6.1
|
|
2
31
|
|
|
3
32
|
### Fixes
|