@lobb-js/studio 0.38.0 → 0.40.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.
@@ -1,10 +1,8 @@
1
- import Filter from "./filter.svelte";
2
1
  interface Props {
3
- filter: any;
2
+ filter: Record<string, any>;
4
3
  collectionName: string;
5
- isFirst?: boolean;
6
- deleteFilter?: () => void;
4
+ isEmpty?: boolean;
7
5
  }
8
- declare const Filter: import("svelte").Component<Props, {}, "filter">;
6
+ declare const Filter: import("svelte").Component<Props, {}, "filter" | "isEmpty">;
9
7
  type Filter = ReturnType<typeof Filter>;
10
8
  export default Filter;
@@ -15,6 +15,34 @@
15
15
  showText = true,
16
16
  collectionName,
17
17
  }: Props = $props();
18
+
19
+ // Tracks whether the filter has any rules applied. Filter pushes
20
+ // this back up so we can shrink the popover when empty — the
21
+ // loaded width feels hollow against the empty-state icon.
22
+ let filterIsEmpty = $state(true);
23
+
24
+ // Count rules across the whole filter tree. A single field with two
25
+ // operators ({age: {$gte, $lte}}) is two rules. Nested $and / $or
26
+ // arrays (groups) recurse into their children so a group with three
27
+ // rules contributes three, not one.
28
+ function countRules(f: any): number {
29
+ if (f == null || typeof f !== "object") return 0;
30
+ let n = 0;
31
+ for (const [key, value] of Object.entries(f)) {
32
+ if (key === "$and" || key === "$or") {
33
+ if (Array.isArray(value)) {
34
+ for (const child of value) n += countRules(child);
35
+ }
36
+ continue;
37
+ }
38
+ if (value && typeof value === "object" && !Array.isArray(value)) {
39
+ n += Object.keys(value).length;
40
+ } else {
41
+ n += 1;
42
+ }
43
+ }
44
+ return n;
45
+ }
18
46
  </script>
19
47
 
20
48
  <Popover.Root>
@@ -27,14 +55,21 @@
27
55
  >
28
56
  <Settings2 />
29
57
  {#if showText}
30
- {#if Object.keys(filter).length}
31
- Filtered by {Object.keys(filter).length} rules
58
+ {@const n = countRules(filter)}
59
+ {#if n}
60
+ Filtered by {n} {n === 1 ? "rule" : "rules"}
32
61
  {:else}
33
62
  Filter
34
63
  {/if}
35
64
  {/if}
36
65
  </Popover.Trigger>
37
- <Popover.Content class="w-screen max-w-100 p-0">
38
- <Filter bind:filter {collectionName} isFirst={true} />
66
+ <Popover.Content
67
+ class="
68
+ w-screen p-0
69
+ transition-[max-width] duration-150
70
+ {filterIsEmpty ? 'max-w-xs' : 'max-w-[36rem]'}
71
+ "
72
+ >
73
+ <Filter bind:filter {collectionName} bind:isEmpty={filterIsEmpty} />
39
74
  </Popover.Content>
40
75
  </Popover.Root>
@@ -4,7 +4,6 @@
4
4
  import type { ParentContext } from "./dataTable.svelte";
5
5
  import CanAccess from "../canAccess.svelte";
6
6
  import { Download, ListRestart, LoaderCircle, Plus, Trash, Link } from "lucide-svelte";
7
- import LlmButton from "../LlmButton.svelte";
8
7
  import FilterButton from "./filterButton.svelte";
9
8
  import SortButton from "./sortButton.svelte";
10
9
  import Button from "../ui/button/button.svelte";
@@ -26,6 +25,7 @@
26
25
  onLink?: (record: any) => void;
27
26
  onCreate?: (changes: Changes) => void;
28
27
  showImport?: boolean;
28
+ showFilter?: boolean;
29
29
  loading?: boolean;
30
30
  left?: Snippet<[]>;
31
31
  }
@@ -38,6 +38,7 @@
38
38
  onLink,
39
39
  onCreate,
40
40
  showImport = true,
41
+ showFilter = true,
41
42
  loading = false,
42
43
  left
43
44
  }: Props = $props();
@@ -122,41 +123,18 @@
122
123
  {selectedRecords.length > 1 ? "records" : "record"}
123
124
  </Button>
124
125
  {:else}
125
- <FilterButton
126
- bind:filter={params.filter}
127
- showText={!headerIsSmall}
128
- {collectionName}
129
- />
126
+ {#if showFilter}
127
+ <FilterButton
128
+ bind:filter={params.filter}
129
+ showText={!headerIsSmall}
130
+ {collectionName}
131
+ />
132
+ {/if}
130
133
  <SortButton
131
134
  {collectionName}
132
135
  bind:sort={params.sort}
133
136
  showText={!headerIsSmall}
134
137
  />
135
- <LlmButton
136
- variant="outline"
137
- title="Filter table with AI"
138
- description="Tell the AI how do you want to filter the table"
139
- size="sm"
140
- format={{
141
- type: "json_object",
142
- }}
143
- messages={[
144
- {
145
- role: "system",
146
- content: [
147
- "You will receive natural language queries from a user describing how they want to filter a table list view.",
148
- "Your task is to generate a filter object based on the user's prompt.",
149
- `This is the schema of the filter json: ${JSON.stringify(ctx.meta.filter.filter_schema)}`,
150
- `This is the schema of the current collection: ${JSON.stringify(ctx.meta.collections[collectionName])}`,
151
- ].join(" "),
152
- },
153
- ]}
154
- onApiResponseComplete={async (res) => {
155
- params.filter = JSON.parse(res);
156
- }}
157
- >
158
- {headerIsSmall ? "" : "Filter with AI"}
159
- </LlmButton>
160
138
  {/if}
161
139
  </div>
162
140
  <div>
@@ -9,6 +9,7 @@ interface Props {
9
9
  onLink?: (record: any) => void;
10
10
  onCreate?: (changes: Changes) => void;
11
11
  showImport?: boolean;
12
+ showFilter?: boolean;
12
13
  loading?: boolean;
13
14
  left?: Snippet<[]>;
14
15
  }
@@ -17,6 +17,11 @@
17
17
  title?: string;
18
18
  showHeader?: boolean;
19
19
  showFooter?: boolean;
20
+ // Popups are usually opened from chart drill-downs with a
21
+ // preset filter — re-filtering inside the popup almost never
22
+ // makes sense, so the filter button is hidden by default here.
23
+ // Pass showFilter={true} to surface it.
24
+ showFilter?: boolean;
20
25
  tableProps?: Partial<TableProps>;
21
26
  tabs?: CollectionTab[];
22
27
  view?: { id: string; [key: string]: any };
@@ -31,6 +36,7 @@
31
36
  title,
32
37
  showHeader = true,
33
38
  showFooter = true,
39
+ showFilter = false,
34
40
  tableProps,
35
41
  tabs,
36
42
  view,
@@ -78,6 +84,7 @@
78
84
  {searchParams}
79
85
  {showHeader}
80
86
  {showFooter}
87
+ {showFilter}
81
88
  {tableProps}
82
89
  {tabs}
83
90
  {view}
@@ -8,6 +8,7 @@ interface Props {
8
8
  title?: string;
9
9
  showHeader?: boolean;
10
10
  showFooter?: boolean;
11
+ showFilter?: boolean;
11
12
  tableProps?: Partial<TableProps>;
12
13
  tabs?: CollectionTab[];
13
14
  view?: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/studio",
3
3
  "license": "UNLICENSED",
4
- "version": "0.38.0",
4
+ "version": "0.40.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -29,6 +29,7 @@ export interface OpenDataTablePopupProps {
29
29
  title?: string;
30
30
  showHeader?: boolean;
31
31
  showFooter?: boolean;
32
+ showFilter?: boolean;
32
33
  tabs?: CollectionTab[];
33
34
  view?: { id: string; [key: string]: any };
34
35
  }
@@ -44,6 +44,7 @@
44
44
  showImport?: boolean;
45
45
  showDelete?: boolean;
46
46
  showEdit?: boolean;
47
+ showFilter?: boolean;
47
48
  onDataLoad?: (total: number) => void;
48
49
  tableProps?: Partial<TableProps>;
49
50
  tabs?: CollectionTab[];
@@ -63,6 +64,7 @@
63
64
  showImport = true,
64
65
  showDelete = false,
65
66
  showEdit = true,
67
+ showFilter = true,
66
68
  onDataLoad,
67
69
  tableProps,
68
70
  tabs,
@@ -299,6 +301,7 @@
299
301
  {collectionName}
300
302
  bind:selectedRecords
301
303
  {showImport}
304
+ {showFilter}
302
305
  {loading}
303
306
  {parentContext}
304
307
  onLink={isRecordingMode ? handleLink : undefined}