@instantdb/components 1.0.4 → 1.0.5
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/.turbo/turbo-build.log +5 -5
- package/dist/components/explorer/inner-explorer.d.ts.map +1 -1
- package/dist/hooks/__tests__/explorer.test.d.ts +2 -0
- package/dist/hooks/__tests__/explorer.test.d.ts.map +1 -0
- package/dist/hooks/explorer.d.ts +4 -0
- package/dist/hooks/explorer.d.ts.map +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.js +203 -192
- package/package.json +6 -6
- package/src/components/explorer/inner-explorer.tsx +16 -3
- package/src/hooks/__tests__/explorer.test.ts +109 -0
- package/src/hooks/explorer.tsx +13 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instantdb/components",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Instant's UI components",
|
|
7
7
|
"homepage": "https://github.com/instantdb/instant/tree/main/client/packages/components",
|
|
@@ -93,11 +93,11 @@
|
|
|
93
93
|
"swr": "^2.2.4",
|
|
94
94
|
"tailwind-merge": "^2.2.1",
|
|
95
95
|
"uuid": "^11.1.0",
|
|
96
|
-
"@instantdb/
|
|
97
|
-
"@instantdb/
|
|
98
|
-
"@instantdb/
|
|
99
|
-
"@instantdb/
|
|
100
|
-
"@instantdb/
|
|
96
|
+
"@instantdb/core": "1.0.5",
|
|
97
|
+
"@instantdb/react": "1.0.5",
|
|
98
|
+
"@instantdb/platform": "1.0.5",
|
|
99
|
+
"@instantdb/admin": "1.0.5",
|
|
100
|
+
"@instantdb/version": "1.0.5"
|
|
101
101
|
},
|
|
102
102
|
"scripts": {
|
|
103
103
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -68,7 +68,11 @@ import {
|
|
|
68
68
|
IconButton,
|
|
69
69
|
Select,
|
|
70
70
|
} from '@lib/components/ui';
|
|
71
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
SearchFilter,
|
|
73
|
+
resolveFilters,
|
|
74
|
+
useNamespacesQuery,
|
|
75
|
+
} from '@lib/hooks/explorer';
|
|
72
76
|
import { useColumnVisibility } from '@lib/hooks/useColumnVisibility';
|
|
73
77
|
import { useLocalStorage } from '@lib/hooks/useLocalStorage';
|
|
74
78
|
import { SchemaAttr, SchemaNamespace } from '@lib/types';
|
|
@@ -298,7 +302,7 @@ export const InnerExplorer: React.FC<{
|
|
|
298
302
|
db,
|
|
299
303
|
selectedNamespace,
|
|
300
304
|
currentNav?.where,
|
|
301
|
-
currentNav?.filters
|
|
305
|
+
resolveFilters(currentNav?.where, currentNav?.filters, searchFilters),
|
|
302
306
|
limit,
|
|
303
307
|
offset,
|
|
304
308
|
sortAttr,
|
|
@@ -1008,7 +1012,16 @@ export const InnerExplorer: React.FC<{
|
|
|
1008
1012
|
</Button>
|
|
1009
1013
|
<SearchInput
|
|
1010
1014
|
key={selectedNamespaceId}
|
|
1011
|
-
onSearchChange={(filters) =>
|
|
1015
|
+
onSearchChange={(filters) => {
|
|
1016
|
+
setSearchFilters(filters);
|
|
1017
|
+
history.push(
|
|
1018
|
+
(prev) => ({
|
|
1019
|
+
...prev!,
|
|
1020
|
+
filters: filters.length ? filters : undefined,
|
|
1021
|
+
}),
|
|
1022
|
+
true,
|
|
1023
|
+
);
|
|
1024
|
+
}}
|
|
1012
1025
|
attrs={selectedNamespace?.attrs}
|
|
1013
1026
|
initialFilters={currentNav?.filters || []}
|
|
1014
1027
|
/>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { test, expect, describe } from 'vitest';
|
|
2
|
+
import { makeWhere, resolveFilters, SearchFilter } from '../explorer';
|
|
3
|
+
import { ExplorerNav } from '../../components/explorer';
|
|
4
|
+
|
|
5
|
+
describe('makeWhere', () => {
|
|
6
|
+
test('returns empty object when no navWhere and no filters', () => {
|
|
7
|
+
expect(makeWhere(null, null)).toEqual({});
|
|
8
|
+
expect(makeWhere(undefined, undefined)).toEqual({});
|
|
9
|
+
expect(makeWhere(null, [])).toEqual({});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('builds where clause from navWhere only', () => {
|
|
13
|
+
const result = makeWhere(['user.id', 'abc-123'], null);
|
|
14
|
+
expect(result).toEqual({ 'user.id': 'abc-123' });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('builds where clause from search filters only', () => {
|
|
18
|
+
const filters: SearchFilter[] = [['email', '$ilike', '%test%']];
|
|
19
|
+
const result = makeWhere(null, filters);
|
|
20
|
+
expect(result).toEqual({
|
|
21
|
+
or: [{ email: { $ilike: '%test%' } }],
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('handles = filter op', () => {
|
|
26
|
+
const filters: SearchFilter[] = [['handle', '=', 'joe']];
|
|
27
|
+
const result = makeWhere(null, filters);
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
or: [{ handle: 'joe' }],
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('handles $isNull filter op', () => {
|
|
34
|
+
const filters: SearchFilter[] = [['name', '$isNull', null]];
|
|
35
|
+
const result = makeWhere(null, filters);
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
or: [{ name: { $isNull: true } }],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('resolveFilters', () => {
|
|
43
|
+
test('uses navFilters when present', () => {
|
|
44
|
+
const navFilters: SearchFilter[] = [['name', '=', 'test']];
|
|
45
|
+
const localFilters: SearchFilter[] = [['email', '$ilike', '%foo%']];
|
|
46
|
+
expect(resolveFilters(undefined, navFilters, localFilters)).toEqual(
|
|
47
|
+
navFilters,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('uses local search filters when no navWhere and no navFilters', () => {
|
|
52
|
+
const localFilters: SearchFilter[] = [['email', '$ilike', '%foo%']];
|
|
53
|
+
expect(resolveFilters(undefined, undefined, localFilters)).toEqual(
|
|
54
|
+
localFilters,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('does not apply stale local filters when navigating via relationship link', () => {
|
|
59
|
+
const navWhere: [string, any] = ['user.id', 'abc-123'];
|
|
60
|
+
const staleFilters: SearchFilter[] = [['email', '$ilike', '%test%']];
|
|
61
|
+
|
|
62
|
+
const result = resolveFilters(navWhere, undefined, staleFilters);
|
|
63
|
+
expect(result).toEqual([]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('navFilters take precedence even when navWhere is set', () => {
|
|
67
|
+
const navWhere: [string, any] = ['user.id', 'abc-123'];
|
|
68
|
+
const navFilters: SearchFilter[] = [['status', '=', 'active']];
|
|
69
|
+
const localFilters: SearchFilter[] = [['email', '$ilike', '%foo%']];
|
|
70
|
+
|
|
71
|
+
expect(resolveFilters(navWhere, navFilters, localFilters)).toEqual(
|
|
72
|
+
navFilters,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('explorer history preserves filters', () => {
|
|
78
|
+
test('filters survive link navigation and back', () => {
|
|
79
|
+
// Simulates the explorer state history stack (mirrors pushExplorerState/popExplorerState)
|
|
80
|
+
let explorerState: ExplorerNav = { namespace: 'users' };
|
|
81
|
+
const history: ExplorerNav[] = [];
|
|
82
|
+
|
|
83
|
+
// Step 1: User types a search, onSearchChange syncs filters to explorer state
|
|
84
|
+
const searchFilters: SearchFilter[] = [['email', '$ilike', '%test%']];
|
|
85
|
+
explorerState = { ...explorerState, filters: searchFilters };
|
|
86
|
+
|
|
87
|
+
expect(
|
|
88
|
+
resolveFilters(explorerState.where, explorerState.filters, searchFilters),
|
|
89
|
+
).toEqual(searchFilters);
|
|
90
|
+
|
|
91
|
+
// Step 2: User clicks relationship link — current state saved to history
|
|
92
|
+
history.push(explorerState);
|
|
93
|
+
explorerState = { namespace: 'user_info', where: ['user.id', 'abc-123'] };
|
|
94
|
+
|
|
95
|
+
// Stale filters should not leak to the new table
|
|
96
|
+
expect(
|
|
97
|
+
resolveFilters(explorerState.where, explorerState.filters, searchFilters),
|
|
98
|
+
).toEqual([]);
|
|
99
|
+
|
|
100
|
+
// Step 3: User hits back — old state restored from history
|
|
101
|
+
explorerState = history.pop()!;
|
|
102
|
+
|
|
103
|
+
// Filters are restored from the saved state
|
|
104
|
+
expect(explorerState.filters).toEqual(searchFilters);
|
|
105
|
+
expect(
|
|
106
|
+
resolveFilters(explorerState.where, explorerState.filters, []),
|
|
107
|
+
).toEqual(searchFilters);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/src/hooks/explorer.tsx
CHANGED
|
@@ -12,7 +12,19 @@ export type SearchFilterOp =
|
|
|
12
12
|
| '$isNull';
|
|
13
13
|
export type SearchFilter = [string, SearchFilterOp, any];
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// When navWhere is set (relationship link navigation), ignore stale local
|
|
16
|
+
// search filters from the previous table.
|
|
17
|
+
export function resolveFilters(
|
|
18
|
+
navWhere: [string, any] | undefined,
|
|
19
|
+
navFilters: SearchFilter[] | undefined,
|
|
20
|
+
localSearchFilters: SearchFilter[],
|
|
21
|
+
): SearchFilter[] {
|
|
22
|
+
if (navFilters) return navFilters;
|
|
23
|
+
if (navWhere) return [];
|
|
24
|
+
return localSearchFilters;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function makeWhere(
|
|
16
28
|
navWhere: null | undefined | [string, any],
|
|
17
29
|
searchFilters: null | undefined | SearchFilter[],
|
|
18
30
|
) {
|