@ims360/svelte-ivory 0.0.46 → 0.0.48
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/dist/components/table/Column.svelte +29 -26
- package/dist/components/table/Column.svelte.d.ts.map +1 -1
- package/dist/components/table/ColumnHead.svelte +3 -5
- package/dist/components/table/ColumnHead.svelte.d.ts.map +1 -1
- package/dist/components/table/Row.svelte +25 -52
- package/dist/components/table/Row.svelte.d.ts +6 -4
- package/dist/components/table/Row.svelte.d.ts.map +1 -1
- package/dist/components/table/Table.svelte +74 -32
- package/dist/components/table/Table.svelte.d.ts +18 -8
- package/dist/components/table/Table.svelte.d.ts.map +1 -1
- package/dist/components/table/VirtualList.svelte +8 -5
- package/dist/components/table/VirtualList.svelte.d.ts +13 -3
- package/dist/components/table/VirtualList.svelte.d.ts.map +1 -1
- package/dist/components/table/controller.d.ts +3 -2
- package/dist/components/table/controller.d.ts.map +1 -1
- package/dist/components/table/index.d.ts +1 -2
- package/dist/components/table/index.d.ts.map +1 -1
- package/dist/components/table/index.js +0 -1
- package/dist/components/table/search.svelte.d.ts +8 -0
- package/dist/components/table/search.svelte.d.ts.map +1 -0
- package/dist/components/table/search.svelte.js +28 -0
- package/package.json +33 -32
- package/src/lib/components/table/Column.svelte +29 -26
- package/src/lib/components/table/ColumnHead.svelte +3 -5
- package/src/lib/components/table/Row.svelte +25 -52
- package/src/lib/components/table/Table.svelte +74 -32
- package/src/lib/components/table/VirtualList.svelte +8 -5
- package/src/lib/components/table/controller.ts +4 -2
- package/src/lib/components/table/index.ts +1 -2
- package/src/lib/components/table/search.svelte.ts +34 -0
- package/dist/components/table/plugins/search.svelte.d.ts +0 -14
- package/dist/components/table/plugins/search.svelte.d.ts.map +0 -1
- package/dist/components/table/plugins/search.svelte.js +0 -55
- package/src/lib/components/table/plugins/search.svelte.ts +0 -66
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
/** collapses everything that doesnt match the searchString, expands direct search hit */
|
|
3
|
+
export const searchData = (nodes, searchString, stringsMatch) => {
|
|
4
|
+
const search = searchString.trim().toLowerCase();
|
|
5
|
+
const hidden = new SvelteSet();
|
|
6
|
+
const expanded = new SvelteSet();
|
|
7
|
+
function nodeMatches(node, childOfMatch = false) {
|
|
8
|
+
const matches = stringsMatch(node, search);
|
|
9
|
+
let intermediate = false;
|
|
10
|
+
for (const child of node.children || []) {
|
|
11
|
+
const childMatches = nodeMatches(child, matches || childOfMatch);
|
|
12
|
+
if (childMatches)
|
|
13
|
+
intermediate = true;
|
|
14
|
+
}
|
|
15
|
+
if (intermediate) {
|
|
16
|
+
expanded.add(node.id);
|
|
17
|
+
}
|
|
18
|
+
else if (!childOfMatch && !matches) {
|
|
19
|
+
hidden.add(node.id);
|
|
20
|
+
}
|
|
21
|
+
return matches || intermediate;
|
|
22
|
+
}
|
|
23
|
+
nodes.forEach((n) => nodeMatches(n));
|
|
24
|
+
return {
|
|
25
|
+
hidden,
|
|
26
|
+
expanded
|
|
27
|
+
};
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ims360/svelte-ivory",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"svelte"
|
|
6
6
|
],
|
|
@@ -67,43 +67,44 @@
|
|
|
67
67
|
"test:unit": "vitest"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@floating-ui/dom": "^1.
|
|
70
|
+
"@floating-ui/dom": "^1.7.4",
|
|
71
71
|
"@lucide/svelte": "^0.503.0",
|
|
72
72
|
"@tailwindcss/forms": "^0.5.10",
|
|
73
|
-
"@tailwindcss/typography": "^0.5.
|
|
74
|
-
"@tailwindcss/vite": "^4.1.
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
73
|
+
"@tailwindcss/typography": "^0.5.18",
|
|
74
|
+
"@tailwindcss/vite": "^4.1.13",
|
|
75
|
+
"clsx": "^2.1.1",
|
|
76
|
+
"dompurify": "^3.2.7",
|
|
77
|
+
"jsdom": "^26.1.0",
|
|
78
|
+
"marked": "^15.0.12",
|
|
79
|
+
"tailwind-merge": "^3.3.1",
|
|
80
|
+
"tailwindcss": "^4.1.13"
|
|
80
81
|
},
|
|
81
82
|
"devDependencies": {
|
|
82
|
-
"@eslint/compat": "^1.2
|
|
83
|
-
"@eslint/js": "^9.
|
|
83
|
+
"@eslint/compat": "^1.3.2",
|
|
84
|
+
"@eslint/js": "^9.36.0",
|
|
84
85
|
"@sveltejs/adapter-auto": "^4.0.0",
|
|
85
|
-
"@sveltejs/package": "^2.
|
|
86
|
-
"@sveltejs/vite-plugin-svelte": "^5.
|
|
87
|
-
"@testing-library/jest-dom": "^6.
|
|
88
|
-
"@testing-library/svelte": "^5.2.
|
|
86
|
+
"@sveltejs/package": "^2.5.3",
|
|
87
|
+
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
|
88
|
+
"@testing-library/jest-dom": "^6.8.0",
|
|
89
|
+
"@testing-library/svelte": "^5.2.8",
|
|
89
90
|
"@testing-library/user-event": "^14.6.1",
|
|
90
|
-
"@vitest/browser": "^3.
|
|
91
|
-
"@vitest/coverage-v8": "^3.
|
|
92
|
-
"@vitest/spy": "^3.
|
|
93
|
-
"eslint": "^9.
|
|
94
|
-
"eslint-config-prettier": "^10.
|
|
95
|
-
"eslint-plugin-svelte": "^3.
|
|
96
|
-
"globals": "^16.
|
|
97
|
-
"playwright": "^1.
|
|
98
|
-
"prettier": "^3.
|
|
99
|
-
"prettier-plugin-svelte": "^3.
|
|
100
|
-
"prettier-plugin-tailwindcss": "^0.6.
|
|
101
|
-
"publint": "^0.3.
|
|
102
|
-
"svelte-check": "^4.
|
|
103
|
-
"typescript": "^5.
|
|
104
|
-
"typescript-eslint": "^8.
|
|
105
|
-
"vite": "^6.
|
|
106
|
-
"vitest": "^3.
|
|
91
|
+
"@vitest/browser": "^3.2.4",
|
|
92
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
93
|
+
"@vitest/spy": "^3.2.4",
|
|
94
|
+
"eslint": "^9.36.0",
|
|
95
|
+
"eslint-config-prettier": "^10.1.8",
|
|
96
|
+
"eslint-plugin-svelte": "^3.12.4",
|
|
97
|
+
"globals": "^16.4.0",
|
|
98
|
+
"playwright": "^1.55.0",
|
|
99
|
+
"prettier": "^3.6.2",
|
|
100
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
101
|
+
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
102
|
+
"publint": "^0.3.13",
|
|
103
|
+
"svelte-check": "^4.3.1",
|
|
104
|
+
"typescript": "^5.9.2",
|
|
105
|
+
"typescript-eslint": "^8.44.0",
|
|
106
|
+
"vite": "^6.3.6",
|
|
107
|
+
"vitest": "^3.2.4"
|
|
107
108
|
},
|
|
108
109
|
"peerDependencies": {
|
|
109
110
|
"@skeletonlabs/skeleton": "^3.1.1",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { ClassValue } from 'svelte/elements';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
import type { ColumnConfig } from './columnController.svelte';
|
|
7
|
+
import { getRowContext } from './Row.svelte';
|
|
7
8
|
import { getTableContext } from './Table.svelte';
|
|
8
9
|
|
|
9
10
|
let defaultClasses = $state<ClassValue>();
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
|
|
26
27
|
<script lang="ts">
|
|
27
28
|
let {
|
|
28
|
-
class: clazz = 'py-2 flex flex-row items-center',
|
|
29
|
+
class: clazz = 'py-2 flex flex-row items-center truncate',
|
|
29
30
|
children,
|
|
30
31
|
onclick,
|
|
31
32
|
ignoreWidth = false,
|
|
@@ -36,10 +37,19 @@
|
|
|
36
37
|
}: ColumnProps = $props();
|
|
37
38
|
|
|
38
39
|
// Register the new column if this is the first table row that was rendered
|
|
39
|
-
const
|
|
40
|
-
const
|
|
40
|
+
const tableContext = getTableContext();
|
|
41
|
+
const rowContext = getRowContext();
|
|
42
|
+
const column = tableContext.registerColumn({ resizable, ...props });
|
|
43
|
+
|
|
44
|
+
const finalOnClick = $derived(onclick || rowContext.onclick);
|
|
41
45
|
const allowClicking = $derived(!!onclick);
|
|
42
46
|
|
|
47
|
+
const element = $derived.by(() => {
|
|
48
|
+
if (finalOnClick) return 'button';
|
|
49
|
+
if (rowContext.href) return 'a';
|
|
50
|
+
return 'div';
|
|
51
|
+
});
|
|
52
|
+
|
|
43
53
|
// passes updated props to the column
|
|
44
54
|
$effect(() => {
|
|
45
55
|
column.updateConfig({ resizable, ...props });
|
|
@@ -49,34 +59,27 @@
|
|
|
49
59
|
$effect(() => {
|
|
50
60
|
if (!column.resizable && props.width !== undefined) column.resize(props.width);
|
|
51
61
|
});
|
|
52
|
-
|
|
53
|
-
function onClick(e: MouseEvent) {
|
|
54
|
-
e.stopPropagation();
|
|
55
|
-
e.preventDefault();
|
|
56
|
-
onclick?.(e);
|
|
57
|
-
}
|
|
58
62
|
</script>
|
|
59
63
|
|
|
60
64
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
61
65
|
<svelte:element
|
|
62
|
-
this={
|
|
63
|
-
onclick={allowClicking ?
|
|
64
|
-
|
|
66
|
+
this={element}
|
|
67
|
+
onclick={allowClicking ? finalOnClick : undefined}
|
|
68
|
+
href={rowContext.href}
|
|
69
|
+
type={allowClicking ? 'button' : undefined}
|
|
65
70
|
style={ignoreWidth
|
|
66
71
|
? ''
|
|
67
|
-
: `width: calc(${column.width ?? 0}px - var(--spacing) * ${offsetNestingLevel * nestingInset}) !important;`}
|
|
68
|
-
class={
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
defaultClasses,
|
|
77
|
-
clazz
|
|
78
|
-
)
|
|
79
|
-
)}
|
|
72
|
+
: `width: calc(${column.width ?? 0}px - var(--spacing) * ${offsetNestingLevel * tableContext.nestingInset}) !important;`}
|
|
73
|
+
class={[
|
|
74
|
+
'flex h-full shrink-0 flex-row items-stretch justify-start truncate',
|
|
75
|
+
!ignoreWidth && [
|
|
76
|
+
'after:mr-2 after:ml-auto after:h-full after:w-px',
|
|
77
|
+
column.dragging && 'after:bg-primary-400-600',
|
|
78
|
+
!column.dragging && column.hovering && 'after:bg-surface-300-700'
|
|
79
|
+
]
|
|
80
|
+
]}
|
|
80
81
|
>
|
|
81
|
-
{
|
|
82
|
+
<div class={twMerge(clsx(['flex flex-row items-center gap-1', defaultClasses, clazz]))}>
|
|
83
|
+
{@render children()}
|
|
84
|
+
</div>
|
|
82
85
|
</svelte:element>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
const onResize = (mouseX: number) => {
|
|
37
37
|
if (!target) return;
|
|
38
38
|
let newWidth = mouseX - target.getBoundingClientRect().left;
|
|
39
|
-
column.resize(newWidth +
|
|
39
|
+
column.resize(newWidth + 8);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
const onDragging = (d: boolean) => {
|
|
@@ -55,10 +55,8 @@
|
|
|
55
55
|
<button
|
|
56
56
|
type="button"
|
|
57
57
|
class={[
|
|
58
|
-
'ml-auto
|
|
59
|
-
dragging
|
|
60
|
-
? '!border-primary-400-600'
|
|
61
|
-
: 'group-hover:!border-surface-300-700 border-transparent'
|
|
58
|
+
'relative ml-auto h-full w-4 shrink-0 cursor-col-resize bg-inherit after:absolute after:top-0 after:right-2 after:h-full after:w-px',
|
|
59
|
+
dragging ? 'after:bg-primary-400-600' : 'group-hover:after:bg-surface-300-700'
|
|
62
60
|
]}
|
|
63
61
|
use:resize={{ resized: onResize, dragging: onDragging }}
|
|
64
62
|
onmouseenter={onHoverStart}
|
|
@@ -1,66 +1,39 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
import
|
|
3
|
-
import { type Snippet } from 'svelte';
|
|
4
|
-
import type { ClassValue } from 'svelte/elements';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
import { getContext, setContext, type Snippet } from 'svelte';
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export function setClasses(c: ClassValue) {
|
|
10
|
-
defaultClasses = c;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface Props {
|
|
14
|
-
class?: ClassValue;
|
|
4
|
+
interface Props {
|
|
15
5
|
onclick?: () => void;
|
|
16
6
|
href?: string;
|
|
17
7
|
children: Snippet;
|
|
18
8
|
}
|
|
9
|
+
|
|
10
|
+
interface RowContext {
|
|
11
|
+
readonly onclick?: () => void;
|
|
12
|
+
readonly href?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CONTEXT = {};
|
|
16
|
+
|
|
17
|
+
function setRowContext(context: RowContext) {
|
|
18
|
+
setContext(CONTEXT, context);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getRowContext(): RowContext {
|
|
22
|
+
return getContext(CONTEXT);
|
|
23
|
+
}
|
|
19
24
|
</script>
|
|
20
25
|
|
|
21
26
|
<script lang="ts">
|
|
22
|
-
let {
|
|
23
|
-
class: clazz = 'hover:bg-surface-950-50/10 transition-colors',
|
|
24
|
-
onclick,
|
|
25
|
-
href,
|
|
26
|
-
children
|
|
27
|
-
}: Props = $props();
|
|
27
|
+
let { onclick, href, children }: Props = $props();
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
href
|
|
34
|
-
|
|
35
|
-
if (onclick) {
|
|
36
|
-
return {
|
|
37
|
-
this: 'button',
|
|
38
|
-
type: 'button',
|
|
39
|
-
onclick
|
|
40
|
-
};
|
|
41
|
-
} else if (href) {
|
|
42
|
-
return {
|
|
43
|
-
this: 'a',
|
|
44
|
-
href: href
|
|
45
|
-
};
|
|
46
|
-
} else {
|
|
47
|
-
return {
|
|
48
|
-
this: 'div'
|
|
49
|
-
};
|
|
29
|
+
setRowContext({
|
|
30
|
+
get onclick() {
|
|
31
|
+
return onclick;
|
|
32
|
+
},
|
|
33
|
+
get href() {
|
|
34
|
+
return href;
|
|
50
35
|
}
|
|
51
36
|
});
|
|
52
37
|
</script>
|
|
53
38
|
|
|
54
|
-
|
|
55
|
-
this={elementProps.this}
|
|
56
|
-
{...elementProps}
|
|
57
|
-
class={twMerge(
|
|
58
|
-
clsx(
|
|
59
|
-
'flex h-full min-w-full grow flex-row items-stretch gap-2 overflow-hidden pr-4 pl-2',
|
|
60
|
-
defaultClasses,
|
|
61
|
-
clazz
|
|
62
|
-
)
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
{@render children()}
|
|
66
|
-
</svelte:element>
|
|
39
|
+
{@render children()}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import { ChevronRight } from '@lucide/svelte';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { getContext, setContext, type Snippet } from 'svelte';
|
|
4
|
+
import { getContext, setContext, untrack, type Snippet } from 'svelte';
|
|
5
5
|
import type { ClassValue } from 'svelte/elements';
|
|
6
6
|
import { SvelteSet } from 'svelte/reactivity';
|
|
7
7
|
import { twMerge } from 'tailwind-merge';
|
|
8
|
-
import { Column as ColumnComponent, type
|
|
8
|
+
import { Column as ColumnComponent, type TableRow } from '.';
|
|
9
|
+
import { ColumnController, type ColumnConfig } from './columnController.svelte';
|
|
9
10
|
import ColumnHead from './ColumnHead.svelte';
|
|
11
|
+
import { treeWalker } from './controller';
|
|
10
12
|
import Row from './Row.svelte';
|
|
13
|
+
import { searchData } from './search.svelte';
|
|
11
14
|
import VirtualList from './VirtualList.svelte';
|
|
12
|
-
import { ColumnController, type ColumnConfig } from './columnController.svelte';
|
|
13
|
-
import { treeWalker, type TableState } from './controller';
|
|
14
15
|
|
|
15
16
|
export interface TableProps<T extends TableRow<T>> {
|
|
16
17
|
class?: ClassValue;
|
|
@@ -24,7 +25,10 @@
|
|
|
24
25
|
firstColumn?: Snippet<[{ row: T }]>;
|
|
25
26
|
rowClass?: ClassValue;
|
|
26
27
|
headerClass?: ClassValue;
|
|
27
|
-
|
|
28
|
+
search?: {
|
|
29
|
+
term: string;
|
|
30
|
+
matches: (row: T) => boolean;
|
|
31
|
+
};
|
|
28
32
|
/**
|
|
29
33
|
* **Bindable**
|
|
30
34
|
*/
|
|
@@ -40,9 +44,9 @@
|
|
|
40
44
|
|
|
41
45
|
const TABLE_CONTEXT = {};
|
|
42
46
|
export type TableContext<T extends TableRow<T>> = {
|
|
43
|
-
registerColumn: (config: ColumnConfig) => ColumnController;
|
|
44
|
-
toggleExpansion: (id: string) => void;
|
|
45
|
-
nestingInset: number;
|
|
47
|
+
readonly registerColumn: (config: ColumnConfig) => ColumnController;
|
|
48
|
+
readonly toggleExpansion: (id: string) => void;
|
|
49
|
+
readonly nestingInset: number;
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
function setTableContext<T extends TableRow<T>>(context: TableContext<T>) {
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
const treeIndicatorId = 'tree-chevron';
|
|
61
|
+
const treeIndicatorInset = 32;
|
|
57
62
|
</script>
|
|
58
63
|
|
|
59
64
|
<script lang="ts" generics="T extends TableRow<T>">
|
|
@@ -62,20 +67,19 @@
|
|
|
62
67
|
data,
|
|
63
68
|
children: passedChildren,
|
|
64
69
|
firstColumn,
|
|
65
|
-
rowClass,
|
|
70
|
+
rowClass = 'hover:bg-surface-950-50/10 transition-colors',
|
|
66
71
|
headerClass,
|
|
67
72
|
rowHeight = 64,
|
|
68
73
|
onclick,
|
|
69
74
|
href,
|
|
70
|
-
plugins = [],
|
|
71
75
|
expanded: expanded = new SvelteSet<string>(),
|
|
72
76
|
nestingInset = 4,
|
|
73
77
|
b_columns: externalColumns = $bindable(),
|
|
74
|
-
b_scrollTop = $bindable()
|
|
78
|
+
b_scrollTop = $bindable(),
|
|
79
|
+
search
|
|
75
80
|
}: TableProps<T> = $props();
|
|
76
81
|
|
|
77
82
|
let columns = $state<ColumnController[]>(externalColumns ?? []);
|
|
78
|
-
const results = $derived(computeResults(data, expanded, plugins));
|
|
79
83
|
let treeIndicatorColumn = $state<ColumnController>();
|
|
80
84
|
|
|
81
85
|
function toggleExpansion(id: string) {
|
|
@@ -83,6 +87,57 @@
|
|
|
83
87
|
else expanded.add(id);
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
let expandedBeforeSearch = $state<SvelteSet<string> | null>(null);
|
|
91
|
+
let prevSearch = $state('');
|
|
92
|
+
|
|
93
|
+
const searchResult = $derived.by(() => {
|
|
94
|
+
if (!search)
|
|
95
|
+
return {
|
|
96
|
+
filteredData: data,
|
|
97
|
+
isSearching: false
|
|
98
|
+
};
|
|
99
|
+
const query = search.term.trim();
|
|
100
|
+
// Note: We only use the 'filteredData' part of the search result here.
|
|
101
|
+
const { hidden } = searchData(data, query, search.matches);
|
|
102
|
+
return {
|
|
103
|
+
filteredData: data.filter((d) => !hidden.has(d.id)),
|
|
104
|
+
isSearching: true
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
const results = $derived(treeWalker({ data: searchResult.filteredData, expanded }));
|
|
108
|
+
|
|
109
|
+
$effect(() => {
|
|
110
|
+
if (!search) return;
|
|
111
|
+
const currentSearch = search.term.trim();
|
|
112
|
+
const wasSearching = prevSearch !== '';
|
|
113
|
+
const isSearching = currentSearch !== '';
|
|
114
|
+
|
|
115
|
+
// Transition: Not Searching -> Searching
|
|
116
|
+
if (!wasSearching && isSearching) {
|
|
117
|
+
// Save the current expansion state before overwriting it.
|
|
118
|
+
expandedBeforeSearch = untrack(() => new SvelteSet(expanded));
|
|
119
|
+
const { expanded: searchExpanded } = searchData(data, currentSearch, search.matches);
|
|
120
|
+
expanded = searchExpanded; // Set the initial expansion for the search.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Transition: Searching -> Not Searching
|
|
124
|
+
if (wasSearching && !isSearching) {
|
|
125
|
+
// Restore the saved expansion state.
|
|
126
|
+
if (expandedBeforeSearch) {
|
|
127
|
+
expanded = expandedBeforeSearch;
|
|
128
|
+
expandedBeforeSearch = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Transition: Searching -> Searching (different query)
|
|
133
|
+
if (wasSearching && isSearching && currentSearch !== prevSearch) {
|
|
134
|
+
const { expanded: searchExpanded } = searchData(data, currentSearch, search.matches);
|
|
135
|
+
expanded = searchExpanded; // Update the expansion for the new search.
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
prevSearch = currentSearch;
|
|
139
|
+
});
|
|
140
|
+
|
|
86
141
|
setTableContext({
|
|
87
142
|
toggleExpansion,
|
|
88
143
|
registerColumn(config: ColumnConfig) {
|
|
@@ -107,17 +162,6 @@
|
|
|
107
162
|
return nestingInset;
|
|
108
163
|
}
|
|
109
164
|
});
|
|
110
|
-
|
|
111
|
-
function computeResults(data: T[], expanded: Set<string>, plugins: TablePlugin<T>[]) {
|
|
112
|
-
let state: TableState<T> = {
|
|
113
|
-
data,
|
|
114
|
-
expanded
|
|
115
|
-
};
|
|
116
|
-
for (const plugin of plugins) {
|
|
117
|
-
state = plugin(state);
|
|
118
|
-
}
|
|
119
|
-
return treeWalker(state);
|
|
120
|
-
}
|
|
121
165
|
</script>
|
|
122
166
|
|
|
123
167
|
<VirtualList
|
|
@@ -125,12 +169,13 @@
|
|
|
125
169
|
class={twMerge(clsx(['flex flex-col overflow-hidden border-transparent', clazz]))}
|
|
126
170
|
bind:b_scrollTop
|
|
127
171
|
{rowHeight}
|
|
172
|
+
rowClass={['pl-2 pr-4', rowClass]}
|
|
128
173
|
>
|
|
129
174
|
{#snippet header()}
|
|
130
175
|
<div
|
|
131
176
|
class={twMerge(
|
|
132
177
|
clsx(
|
|
133
|
-
'flex w-fit min-w-full flex-row
|
|
178
|
+
'flex w-fit min-w-full flex-row border-b border-inherit pr-4 pl-2',
|
|
134
179
|
headerClass
|
|
135
180
|
)
|
|
136
181
|
)}
|
|
@@ -154,11 +199,7 @@
|
|
|
154
199
|
</div>
|
|
155
200
|
{/snippet}
|
|
156
201
|
{#snippet children({ row: { node, id, nestingLevel }, index })}
|
|
157
|
-
<Row
|
|
158
|
-
onclick={onclick ? () => onclick(node) : undefined}
|
|
159
|
-
href={href?.(node)}
|
|
160
|
-
class={rowClass}
|
|
161
|
-
>
|
|
202
|
+
<Row href={href?.(node)} onclick={onclick ? () => onclick(node) : undefined}>
|
|
162
203
|
{@render firstColumn?.({ row: node })}
|
|
163
204
|
<ColumnComponent
|
|
164
205
|
id={treeIndicatorId}
|
|
@@ -168,12 +209,13 @@
|
|
|
168
209
|
toggleExpansion(node.id);
|
|
169
210
|
}}
|
|
170
211
|
ignoreWidth={results.someHaveChildren}
|
|
171
|
-
width={results.someHaveChildren ?
|
|
212
|
+
width={results.someHaveChildren ? treeIndicatorInset : 0}
|
|
172
213
|
minWidth={0}
|
|
173
214
|
>
|
|
174
215
|
<div
|
|
175
|
-
class="flex h-full items-center justify-end"
|
|
176
|
-
style="width: calc(var(--spacing) * {nestingLevel *
|
|
216
|
+
class="flex h-full items-center justify-end pr-2"
|
|
217
|
+
style="width: calc(var(--spacing) * {nestingLevel *
|
|
218
|
+
nestingInset} + {treeIndicatorInset}px);"
|
|
177
219
|
>
|
|
178
220
|
{#if node.children}
|
|
179
221
|
<ChevronRight
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
b_scrollTop?: number;
|
|
13
13
|
rowHeight: number;
|
|
14
14
|
overscan?: number;
|
|
15
|
+
rowClass?: ClassValue;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
let {
|
|
@@ -21,9 +22,14 @@
|
|
|
21
22
|
header,
|
|
22
23
|
b_scrollTop = $bindable(),
|
|
23
24
|
rowHeight,
|
|
24
|
-
overscan = 2
|
|
25
|
+
overscan = 2,
|
|
26
|
+
rowClass
|
|
25
27
|
}: Props<T> = $props();
|
|
26
28
|
|
|
29
|
+
const finalRowClass = $derived(
|
|
30
|
+
twMerge(clsx(['flex w-full shrink-0 grow flex-row items-center overflow-hidden', rowClass]))
|
|
31
|
+
);
|
|
32
|
+
|
|
27
33
|
let scroll_top = $state(b_scrollTop ?? 0);
|
|
28
34
|
let scroll_left = $state(0);
|
|
29
35
|
let header_width = $state(0);
|
|
@@ -85,10 +91,7 @@
|
|
|
85
91
|
style="padding-top: {top}px; padding-bottom: {bottom}px; min-width: max(100%, {header_width}px) !important;"
|
|
86
92
|
>
|
|
87
93
|
{#each visible as row, i (row.data.id)}
|
|
88
|
-
<virtual-list-row
|
|
89
|
-
class="flex w-full shrink-0 grow flex-row items-center overflow-hidden"
|
|
90
|
-
style="height: {rowHeight}px !important;"
|
|
91
|
-
>
|
|
94
|
+
<virtual-list-row class={finalRowClass} style="height: {rowHeight}px !important;">
|
|
92
95
|
{@render children({
|
|
93
96
|
row: row.data,
|
|
94
97
|
domIndex: i,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
|
|
1
3
|
export type TableRow<T> = { id: string; children?: T[] };
|
|
2
|
-
export type TablePlugin<T extends TableRow<T>> = (state: TableState<T>) =>
|
|
4
|
+
export type TablePlugin<T extends TableRow<T>> = (state: TableState<T>) => void;
|
|
3
5
|
|
|
4
6
|
export interface TableState<T extends TableRow<T>> {
|
|
5
7
|
data: T[];
|
|
6
|
-
expanded:
|
|
8
|
+
expanded: SvelteSet<string>;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
interface TreeRow<T> {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export { default as Column, type ColumnProps } from './Column.svelte';
|
|
2
2
|
export { ColumnController, type ColumnConfig } from './columnController.svelte';
|
|
3
3
|
export { getColumnHeadContext } from './ColumnHead.svelte';
|
|
4
|
-
export { getAllIds, type
|
|
5
|
-
export { searchPlugin } from './plugins/search.svelte';
|
|
4
|
+
export { getAllIds, type TableRow, type TableState } from './controller';
|
|
6
5
|
export {
|
|
7
6
|
getTableContext,
|
|
8
7
|
default as Table,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import type { TableRow } from '.';
|
|
3
|
+
|
|
4
|
+
/** collapses everything that doesnt match the searchString, expands direct search hit */
|
|
5
|
+
export const searchData = <T extends TableRow<T>>(
|
|
6
|
+
nodes: T[],
|
|
7
|
+
searchString: string,
|
|
8
|
+
stringsMatch: (a: T, b: string) => boolean
|
|
9
|
+
) => {
|
|
10
|
+
const search = searchString.trim().toLowerCase();
|
|
11
|
+
const hidden = new SvelteSet<string>();
|
|
12
|
+
const expanded = new SvelteSet<string>();
|
|
13
|
+
|
|
14
|
+
function nodeMatches(node: T, childOfMatch = false): boolean {
|
|
15
|
+
const matches = stringsMatch(node, search);
|
|
16
|
+
let intermediate = false;
|
|
17
|
+
for (const child of node.children || []) {
|
|
18
|
+
const childMatches = nodeMatches(child, matches || childOfMatch);
|
|
19
|
+
if (childMatches) intermediate = true;
|
|
20
|
+
}
|
|
21
|
+
if (intermediate) {
|
|
22
|
+
expanded.add(node.id);
|
|
23
|
+
} else if (!childOfMatch && !matches) {
|
|
24
|
+
hidden.add(node.id);
|
|
25
|
+
}
|
|
26
|
+
return matches || intermediate;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
nodes.forEach((n) => nodeMatches(n));
|
|
30
|
+
return {
|
|
31
|
+
hidden,
|
|
32
|
+
expanded
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SvelteSet } from 'svelte/reactivity';
|
|
2
|
-
import type { TablePlugin, TableRow } from '../';
|
|
3
|
-
interface SearchConfig<T extends TableRow<T>> {
|
|
4
|
-
search: string;
|
|
5
|
-
matches: (row: T) => boolean;
|
|
6
|
-
}
|
|
7
|
-
export declare function searchPlugin<T extends TableRow<T>>(conf: SearchConfig<T>): TablePlugin<T>;
|
|
8
|
-
/** collapses everything that doesnt match the searchString, expands direct search hit */
|
|
9
|
-
export declare const search: <T extends TableRow<T>>(nodes: T[], searchString: string, stringsMatch: (a: T, b: string) => boolean) => {
|
|
10
|
-
hidden: SvelteSet<string>;
|
|
11
|
-
expanded: SvelteSet<string>;
|
|
12
|
-
};
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=search.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/table/plugins/search.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAEjD,UAAU,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,OAAO,CAAC;CAChC;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAyBzF;AAED,yFAAyF;AACzF,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,QAAQ,CAAC,CAAC,CAAC,EACxC,OAAO,CAAC,EAAE,EACV,cAAc,MAAM,EACpB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO;;;CA0B7C,CAAC"}
|