wx-svelte-grid 2.6.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wx-svelte-grid",
3
- "version": "2.6.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.12.1",
37
- "@svar-ui/lib-state": "1.9.6",
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.5.1",
40
- "@svar-ui/svelte-core": "2.5.1",
41
- "@svar-ui/svelte-toolbar": "2.5.1",
42
- "@svar-ui/grid-data-provider": "2.6.2",
43
- "@svar-ui/grid-locales": "2.6.2",
44
- "@svar-ui/grid-store": "2.6.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.5.1",
48
- "@svar-ui/svelte-filter": "2.5.0"
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 { onDestroy, getContext, untrack } from "svelte";
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={cell._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 scrollLeft = $state(0),
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
- scrollTop = ev.target.scrollTop;
392
- scrollLeft = ev.target.scrollLeft;
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
- api.getRowIndex(from) - api.getRowIndex(to)
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}&nbsp;{/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 { content: Content = null, api, children } = $props();
6
+ let {
7
+ api,
8
+ at = "point",
9
+ overflow = false,
10
+ content: Content = null,
11
+ resolver = defaultResolver,
12
+ ...restProps
13
+ } = $props();
6
14
 
7
- let area;
8
- let tooltipNode = $state();
9
- let areaCoords = $state();
10
- let tooltipData = $state();
11
- let pos = $state();
15
+ function defaultResolver(element) {
16
+ if (!api) return null;
12
17
 
13
- function findAttribute(node) {
14
- while (node) {
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
- $effect(() => {
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
- let timer;
41
- const TIMEOUT = 300;
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
- function getTooltipData(id) {
70
- return api.getRow(id);
71
- }
26
+ if (column.tooltip === false) return null;
27
+ if (overflow && element.scrollWidth <= element.clientWidth) return null;
72
28
 
73
- function getTooltipText(col) {
74
- if (typeof col.tooltip === "function") return col.tooltip(tooltipData);
75
- return getRenderValue(tooltipData, col) || "";
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
- <!-- svelte-ignore a11y_no_static_element_interactions -->
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 { onMount } from "svelte";
3
+ import MultiSelect from "../MultiSelect.svelte";
5
4
 
6
5
  let { editor, onaction, onsave, onapply } = $props();
7
- let { config } = $state(editor);
8
6
 
7
+ const config = $state(editor?.config || {});
9
8
  const options = $derived(editor?.options ?? []);
10
- let value = $derived(editor?.value || []);
11
- let renderedValue = $derived(editor?.renderedValue);
9
+ const value = $derived(editor?.value || []);
10
+ const text = $derived(editor?.renderedValue);
12
11
 
13
- let index = $derived.by(() => {
14
- const firstSelected = options.find(opt => value.includes(opt.id));
15
- return firstSelected ? options.indexOf(firstSelected) : -1;
12
+ const dropdownOptions = $derived({
13
+ trackScroll: true,
14
+ ...(config.dropdown || {}),
16
15
  });
17
16
 
18
- const dropdownOptions = $derived.by(() => {
19
- const dropdown = config?.dropdown || {};
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
- tabindex="0"
53
- onclick={() => onsave()}
54
- onkeydown={ev => {
55
- keydown(ev, index);
56
- ev.preventDefault();
57
- }}
26
+ onclick={() => onsave(true)}
58
27
  use:clickOutside={() => onsave(true)}
59
28
  >
60
- {#if config?.template}
61
- {config.template(value?.map(id => options.find(opt => opt.id === id)))}
62
- {:else if config?.cell}
63
- {@const SvelteComponent = config.cell}
64
- <SvelteComponent
65
- data={value.map(id => options.find(opt => opt.id === id))}
66
- />
67
- {:else}
68
- <span class="wx-text">{renderedValue}</span>
69
- {/if}
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={() => onsave()}
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-option {
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
- white-space: nowrap;
107
- overflow: hidden;
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
- text-overflow: ellipsis;
119
- white-space: nowrap;
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="text"
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: IApi;
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?: Component;
116
- api?: IApi;
117
- children?: () => any;
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,25 @@
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
+
1
23
  ## 2.6.2
2
24
 
3
25
  ### Fixes