@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@instantdb/components",
3
3
  "private": false,
4
- "version": "1.0.4",
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/admin": "1.0.4",
97
- "@instantdb/platform": "1.0.4",
98
- "@instantdb/react": "1.0.4",
99
- "@instantdb/version": "1.0.4",
100
- "@instantdb/core": "1.0.4"
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 { SearchFilter, useNamespacesQuery } from '@lib/hooks/explorer';
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 || searchFilters,
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) => setSearchFilters(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
+ });
@@ -12,7 +12,19 @@ export type SearchFilterOp =
12
12
  | '$isNull';
13
13
  export type SearchFilter = [string, SearchFilterOp, any];
14
14
 
15
- function makeWhere(
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
  ) {