@lobb-js/studio 0.30.0 → 0.32.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.
Files changed (71) hide show
  1. package/dist/actions.d.ts +2 -0
  2. package/dist/components/Studio.svelte +1 -10
  3. package/dist/components/dataTable/dataTable.svelte +104 -39
  4. package/dist/components/dataTable/dataTable.svelte.d.ts +4 -1
  5. package/dist/components/dataTable/fieldCell.svelte +7 -4
  6. package/dist/components/dataTable/fieldCell.svelte.d.ts +2 -2
  7. package/dist/components/dataTable/filter.svelte +0 -15
  8. package/dist/components/dataTable/header.svelte +13 -14
  9. package/dist/components/dataTable/header.svelte.d.ts +3 -2
  10. package/dist/components/dataTable/numberCell.svelte +28 -0
  11. package/dist/components/dataTable/numberCell.svelte.d.ts +7 -0
  12. package/dist/components/dataTable/polymorphicFieldCell.svelte +3 -3
  13. package/dist/components/dataTable/polymorphicFieldCell.svelte.d.ts +2 -2
  14. package/dist/components/dataTablePopup/dataTablePopup.svelte +17 -0
  15. package/dist/components/dataTablePopup/dataTablePopup.svelte.d.ts +2 -0
  16. package/dist/components/detailView/create/createDetailView.svelte +28 -54
  17. package/dist/components/detailView/create/createDetailView.svelte.d.ts +4 -3
  18. package/dist/components/detailView/create/createDetailViewChildren.svelte +113 -0
  19. package/dist/components/detailView/create/createDetailViewChildren.svelte.d.ts +9 -0
  20. package/dist/components/detailView/create/createManyView.svelte +2 -2
  21. package/dist/components/detailView/detailView.svelte +6 -1
  22. package/dist/components/detailView/fieldInput.svelte +7 -5
  23. package/dist/components/detailView/update/updateDetailView.svelte +46 -40
  24. package/dist/components/detailView/update/updateDetailView.svelte.d.ts +5 -3
  25. package/dist/components/detailView/update/updateDetailViewButton.svelte +0 -1
  26. package/dist/components/detailView/update/updateDetailViewChildren.svelte +122 -0
  27. package/dist/components/detailView/update/updateDetailViewChildren.svelte.d.ts +10 -0
  28. package/dist/components/detailView/utils.d.ts +1 -2
  29. package/dist/components/importButton.svelte +1 -1
  30. package/dist/components/miniSidebar.svelte +6 -3
  31. package/dist/components/richTextEditor.svelte +2 -0
  32. package/dist/components/routes/extensions/extension.svelte +1 -1
  33. package/dist/components/routes/home.svelte +35 -21
  34. package/dist/components/ui/input/numberInput.svelte +104 -0
  35. package/dist/components/ui/input/numberInput.svelte.d.ts +9 -0
  36. package/dist/components/workflowEditor.svelte +6 -4
  37. package/package.json +4 -3
  38. package/src/lib/actions.ts +2 -0
  39. package/src/lib/components/Studio.svelte +1 -10
  40. package/src/lib/components/dataTable/dataTable.svelte +104 -39
  41. package/src/lib/components/dataTable/fieldCell.svelte +7 -4
  42. package/src/lib/components/dataTable/filter.svelte +0 -15
  43. package/src/lib/components/dataTable/header.svelte +13 -14
  44. package/src/lib/components/dataTable/numberCell.svelte +28 -0
  45. package/src/lib/components/dataTable/polymorphicFieldCell.svelte +3 -3
  46. package/src/lib/components/dataTablePopup/dataTablePopup.svelte +17 -0
  47. package/src/lib/components/detailView/create/createDetailView.svelte +28 -54
  48. package/src/lib/components/detailView/create/createDetailViewChildren.svelte +113 -0
  49. package/src/lib/components/detailView/create/createManyView.svelte +2 -2
  50. package/src/lib/components/detailView/detailView.svelte +6 -1
  51. package/src/lib/components/detailView/fieldInput.svelte +7 -5
  52. package/src/lib/components/detailView/update/updateDetailView.svelte +46 -40
  53. package/src/lib/components/detailView/update/updateDetailViewButton.svelte +0 -1
  54. package/src/lib/components/detailView/update/updateDetailViewChildren.svelte +122 -0
  55. package/src/lib/components/detailView/utils.ts +1 -1
  56. package/src/lib/components/importButton.svelte +1 -1
  57. package/src/lib/components/miniSidebar.svelte +6 -3
  58. package/src/lib/components/richTextEditor.svelte +2 -0
  59. package/src/lib/components/routes/extensions/extension.svelte +1 -1
  60. package/src/lib/components/routes/home.svelte +35 -21
  61. package/src/lib/components/ui/input/numberInput.svelte +104 -0
  62. package/src/lib/components/workflowEditor.svelte +6 -4
  63. package/dist/components/breadCrumbs.svelte +0 -61
  64. package/dist/components/breadCrumbs.svelte.d.ts +0 -3
  65. package/dist/components/detailView/update/detailViewChildren.svelte +0 -61
  66. package/dist/components/detailView/update/detailViewChildren.svelte.d.ts +0 -9
  67. package/dist/components/header.svelte +0 -45
  68. package/dist/components/header.svelte.d.ts +0 -6
  69. package/src/lib/components/breadCrumbs.svelte +0 -61
  70. package/src/lib/components/detailView/update/detailViewChildren.svelte +0 -61
  71. package/src/lib/components/header.svelte +0 -45
@@ -0,0 +1,104 @@
1
+ <script lang="ts">
2
+ import IMask from "imask";
3
+ import type { HTMLInputAttributes } from "svelte/elements";
4
+ import { cn } from "../../../utils.js";
5
+
6
+ // Locale-neutral grouping: space for thousands, dot for decimals
7
+ // (ISO 31-0). Norwegian, French, scientific contexts — same shape, and
8
+ // space can't be confused with comma-as-decimal vs comma-as-thousands.
9
+ const THOUSANDS = " ";
10
+ const RADIX = ".";
11
+
12
+ type Props = Omit<HTMLInputAttributes, "type" | "value" | "step"> & {
13
+ value?: number | string | null;
14
+ // 0 = integer-only, > 0 = allow that many decimal places.
15
+ scale?: number;
16
+ // When false, render a plain browser number input — no masking, no
17
+ // formatting. Default false so the component is safe to drop in
18
+ // anywhere; opt into grouping only where it makes sense.
19
+ groupDigits?: boolean;
20
+ };
21
+
22
+ let {
23
+ value = $bindable<number | string | null | undefined>(),
24
+ scale = 0,
25
+ groupDigits = false,
26
+ class: className,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let inputEl: HTMLInputElement | undefined = $state();
31
+ let mask: ReturnType<typeof IMask> | null = null;
32
+ // Re-entrance guard: imask's `accept` event fires when we programmatically
33
+ // set unmaskedValue, which would create a sync→reflect loop with the
34
+ // "external value changed" effect below.
35
+ let applyingExternal = false;
36
+
37
+ $effect(() => {
38
+ if (!groupDigits) return;
39
+ if (!inputEl) return;
40
+ mask = IMask(inputEl, {
41
+ mask: Number,
42
+ thousandsSeparator: THOUSANDS,
43
+ radix: RADIX,
44
+ scale,
45
+ signed: true,
46
+ padFractionalZeros: false,
47
+ normalizeZeros: true,
48
+ // Allow the user to type either radix; imask remaps to the chosen
49
+ // one and ignores stray locale separators on input.
50
+ mapToRadix: [",", "."],
51
+ });
52
+
53
+ mask.on("accept", () => {
54
+ if (applyingExternal) return;
55
+ const unmasked = mask!.unmaskedValue;
56
+ value = unmasked === "" ? null : Number(unmasked);
57
+ });
58
+
59
+ applyingExternal = true;
60
+ mask.unmaskedValue = value == null ? "" : String(value);
61
+ applyingExternal = false;
62
+
63
+ return () => {
64
+ mask?.destroy();
65
+ mask = null;
66
+ };
67
+ });
68
+
69
+ // Sync external value changes back into the mask (e.g. form reset, parent
70
+ // clearing the value). No-op when grouping is off.
71
+ $effect(() => {
72
+ const v = value;
73
+ if (!mask) return;
74
+ const next = v == null ? "" : String(v);
75
+ if (mask.unmaskedValue === next) return;
76
+ applyingExternal = true;
77
+ mask.unmaskedValue = next;
78
+ applyingExternal = false;
79
+ });
80
+ </script>
81
+
82
+ {#if groupDigits}
83
+ <input
84
+ bind:this={inputEl}
85
+ type="text"
86
+ inputmode={scale > 0 ? "decimal" : "numeric"}
87
+ class={cn(
88
+ "border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
89
+ className,
90
+ )}
91
+ {...rest}
92
+ />
93
+ {:else}
94
+ <input
95
+ type="number"
96
+ step={scale > 0 ? "any" : "1"}
97
+ class={cn(
98
+ "border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
99
+ className,
100
+ )}
101
+ bind:value
102
+ {...rest}
103
+ />
104
+ {/if}
@@ -93,7 +93,7 @@
93
93
  return;
94
94
  }
95
95
 
96
- const reponse = await lobb.createOne("core_workflows", workflow);
96
+ const reponse = await lobb.createOne("core_workflows", { data: workflow });
97
97
  const result = await reponse.json();
98
98
  const workflowEntry = result.data;
99
99
  goto(`/studio/workflows/${workflowEntry.name}`);
@@ -107,9 +107,11 @@
107
107
  );
108
108
  }
109
109
  const reponse = await lobb.updateOne("core_workflows", workflow.id, {
110
- name: workflow.name,
111
- event_name: workflow.event_name,
112
- handler: workflow.handler,
110
+ data: {
111
+ name: workflow.name,
112
+ event_name: workflow.event_name,
113
+ handler: workflow.handler,
114
+ },
113
115
  });
114
116
  const result = await reponse.json();
115
117
 
@@ -1,61 +0,0 @@
1
- <script lang="ts">
2
- import * as Breadcrumb from "./ui/breadcrumb";
3
- import { mediaQueries } from "../utils";
4
- import { page } from "$app/state";
5
- import { goto } from "$app/navigation";
6
-
7
- const isSmall = $derived(!mediaQueries.sm.current);
8
- const pathNames = $derived(
9
- page.url.pathname
10
- .replace("/studio", "")
11
- .split("/")
12
- .filter((el: any) => el !== "")
13
- .slice(0, 3),
14
- );
15
- </script>
16
-
17
- {#if isSmall}
18
- <div class="text-muted-foreground text-sm">
19
- {pathNames[pathNames.length - 1] || "Home"}
20
- </div>
21
- {:else}
22
- <Breadcrumb.Root>
23
- <Breadcrumb.List class="flex-nowrap">
24
- <Breadcrumb.Item>
25
- {#if pathNames.length === 0}
26
- <Breadcrumb.Page>Home</Breadcrumb.Page>
27
- {:else}
28
- <Breadcrumb.Link
29
- class="cursor-pointer"
30
- onclick={() => goto("/studio")}
31
- >
32
- Home
33
- </Breadcrumb.Link>
34
- <Breadcrumb.Separator />
35
- {/if}
36
- </Breadcrumb.Item>
37
- {#each pathNames as path, index}
38
- {@const isLastElement = pathNames.length - 1 === index}
39
- {@const currentFullPaths = pathNames
40
- .slice(0, index + 1)
41
- .join("/")}
42
- <Breadcrumb.Item>
43
- {#if isLastElement}
44
- <Breadcrumb.Page>{path}</Breadcrumb.Page>
45
- {:else}
46
- <Breadcrumb.Link
47
- class="cursor-pointer"
48
- onclick={() =>
49
- goto(`/studio/${currentFullPaths}`)}
50
- >
51
- {path}
52
- </Breadcrumb.Link>
53
- {/if}
54
- </Breadcrumb.Item>
55
- {#if !isLastElement}
56
- <Breadcrumb.Separator />
57
- {/if}
58
- {/each}
59
- </Breadcrumb.List>
60
- </Breadcrumb.Root>
61
- {/if}
@@ -1,3 +0,0 @@
1
- declare const BreadCrumbs: import("svelte").Component<Record<string, never>, {}, "">;
2
- type BreadCrumbs = ReturnType<typeof BreadCrumbs>;
3
- export default BreadCrumbs;
@@ -1,61 +0,0 @@
1
- <script lang="ts">
2
- import DataTable from "../../dataTable/dataTable.svelte";
3
- import { getStudioContext } from "../../../context";
4
- import { Table, Link } from "lucide-svelte";
5
-
6
- const { ctx } = getStudioContext();
7
-
8
- import type { Changes, ChildrenChanges } from "../utils";
9
-
10
- interface LocalProp {
11
- collectionName: string;
12
- entry: any;
13
- activeChanges?: Changes;
14
- }
15
-
16
- let { collectionName, entry, activeChanges }: LocalProp = $props();
17
-
18
- const children = (ctx.meta.collections[collectionName]?.children ?? [])
19
- .filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
20
-
21
- // Ensure all child collection slots exist before the template renders so bind:changes works
22
- $effect.pre(() => {
23
- if (!activeChanges) return;
24
- for (const child of children) {
25
- if (!activeChanges.children[child.collection]) {
26
- activeChanges.children[child.collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
27
- }
28
- }
29
- });
30
- </script>
31
-
32
- {#if children.length}
33
- <div class="flex flex-col gap-3 border-t p-4">
34
- <div class="flex items-center gap-2">
35
- <Link size="14" class="text-muted-foreground" />
36
- <span class="text-sm font-medium">Sub Records</span>
37
- </div>
38
- {#each children as child}
39
- <div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
40
- <DataTable
41
- collectionName={child.collection}
42
- searchParams={{ children_of: collectionName, parent_id: entry.id }}
43
- parentContext={{ collectionName, recordId: entry.id }}
44
- bind:changes={activeChanges!.children[child.collection]}
45
- showImport={false}
46
- showHeader={true}
47
- showFooter={true}
48
- showDelete={child.type === "fk" || child.type === "m2m"}
49
- tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
50
- >
51
- {#snippet headerLeft()}
52
- <div class="flex items-center gap-2 px-1">
53
- <Table size="14" class="text-muted-foreground" />
54
- <span class="text-sm font-medium">{child.collection}</span>
55
- </div>
56
- {/snippet}
57
- </DataTable>
58
- </div>
59
- {/each}
60
- </div>
61
- {/if}
@@ -1,9 +0,0 @@
1
- import type { Changes } from "../utils";
2
- interface LocalProp {
3
- collectionName: string;
4
- entry: any;
5
- activeChanges?: Changes;
6
- }
7
- declare const DetailViewChildren: import("svelte").Component<LocalProp, {}, "">;
8
- type DetailViewChildren = ReturnType<typeof DetailViewChildren>;
9
- export default DetailViewChildren;
@@ -1,45 +0,0 @@
1
- <script>
2
- import * as Tooltip from "./ui/tooltip";
3
- import Button from "./ui/button/button.svelte";
4
- import { Menu, Moon, Sun } from "lucide-svelte";
5
- import BreadCrumbs from "./breadCrumbs.svelte";
6
- import { toggleMode, mode } from "mode-watcher";
7
- import { mediaQueries } from "../utils";
8
- import { expandMiniSideBar } from "./miniSidebar.svelte";
9
-
10
- let isSmallScreen = $derived(!mediaQueries.sm.current);
11
- </script>
12
-
13
- <div
14
- class="flex items-center justify-between border-b border-input bg-background px-3"
15
- >
16
- <div class="flex items-center gap-4">
17
- {#if isSmallScreen}
18
- <Menu
19
- class="h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
20
- style="left: 0.6rem; top: 0rem;"
21
- size="18"
22
- onclick={() => expandMiniSideBar()}
23
- />
24
- {/if}
25
- <BreadCrumbs />
26
- </div>
27
- <div class="flex h-full items-center gap-3">
28
- <Tooltip.Root>
29
- <Tooltip.Trigger>
30
- <Button
31
- class="h-6 w-6 text-muted-foreground hover:bg-transparent"
32
- variant="ghost"
33
- size="icon"
34
- onclick={toggleMode}
35
- Icon={$mode === "light" ? Moon : Sun}
36
- ></Button>
37
- </Tooltip.Trigger>
38
- <Tooltip.Content side="bottom" sideOffset={7.5}
39
- >{$mode === "light"
40
- ? "Night Mode"
41
- : "Light Mode"}</Tooltip.Content
42
- >
43
- </Tooltip.Root>
44
- </div>
45
- </div>
@@ -1,6 +0,0 @@
1
- export default Header;
2
- type Header = {
3
- $on?(type: string, callback: (e: any) => void): () => void;
4
- $set?(props: Partial<Record<string, never>>): void;
5
- };
6
- declare const Header: import("svelte").Component<Record<string, never>, {}, "">;
@@ -1,61 +0,0 @@
1
- <script lang="ts">
2
- import * as Breadcrumb from "./ui/breadcrumb";
3
- import { mediaQueries } from "../utils";
4
- import { page } from "$app/state";
5
- import { goto } from "$app/navigation";
6
-
7
- const isSmall = $derived(!mediaQueries.sm.current);
8
- const pathNames = $derived(
9
- page.url.pathname
10
- .replace("/studio", "")
11
- .split("/")
12
- .filter((el: any) => el !== "")
13
- .slice(0, 3),
14
- );
15
- </script>
16
-
17
- {#if isSmall}
18
- <div class="text-muted-foreground text-sm">
19
- {pathNames[pathNames.length - 1] || "Home"}
20
- </div>
21
- {:else}
22
- <Breadcrumb.Root>
23
- <Breadcrumb.List class="flex-nowrap">
24
- <Breadcrumb.Item>
25
- {#if pathNames.length === 0}
26
- <Breadcrumb.Page>Home</Breadcrumb.Page>
27
- {:else}
28
- <Breadcrumb.Link
29
- class="cursor-pointer"
30
- onclick={() => goto("/studio")}
31
- >
32
- Home
33
- </Breadcrumb.Link>
34
- <Breadcrumb.Separator />
35
- {/if}
36
- </Breadcrumb.Item>
37
- {#each pathNames as path, index}
38
- {@const isLastElement = pathNames.length - 1 === index}
39
- {@const currentFullPaths = pathNames
40
- .slice(0, index + 1)
41
- .join("/")}
42
- <Breadcrumb.Item>
43
- {#if isLastElement}
44
- <Breadcrumb.Page>{path}</Breadcrumb.Page>
45
- {:else}
46
- <Breadcrumb.Link
47
- class="cursor-pointer"
48
- onclick={() =>
49
- goto(`/studio/${currentFullPaths}`)}
50
- >
51
- {path}
52
- </Breadcrumb.Link>
53
- {/if}
54
- </Breadcrumb.Item>
55
- {#if !isLastElement}
56
- <Breadcrumb.Separator />
57
- {/if}
58
- {/each}
59
- </Breadcrumb.List>
60
- </Breadcrumb.Root>
61
- {/if}
@@ -1,61 +0,0 @@
1
- <script lang="ts">
2
- import DataTable from "../../dataTable/dataTable.svelte";
3
- import { getStudioContext } from "../../../context";
4
- import { Table, Link } from "lucide-svelte";
5
-
6
- const { ctx } = getStudioContext();
7
-
8
- import type { Changes, ChildrenChanges } from "../utils";
9
-
10
- interface LocalProp {
11
- collectionName: string;
12
- entry: any;
13
- activeChanges?: Changes;
14
- }
15
-
16
- let { collectionName, entry, activeChanges }: LocalProp = $props();
17
-
18
- const children = (ctx.meta.collections[collectionName]?.children ?? [])
19
- .filter((c: any) => c.type === "fk" || c.type === "m2m" || c.type === "polymorphic");
20
-
21
- // Ensure all child collection slots exist before the template renders so bind:changes works
22
- $effect.pre(() => {
23
- if (!activeChanges) return;
24
- for (const child of children) {
25
- if (!activeChanges.children[child.collection]) {
26
- activeChanges.children[child.collection] = { created: [], updated: [], deleted: [], linked: [], unlinked: [] };
27
- }
28
- }
29
- });
30
- </script>
31
-
32
- {#if children.length}
33
- <div class="flex flex-col gap-3 border-t p-4">
34
- <div class="flex items-center gap-2">
35
- <Link size="14" class="text-muted-foreground" />
36
- <span class="text-sm font-medium">Sub Records</span>
37
- </div>
38
- {#each children as child}
39
- <div class="rounded-lg border bg-background overflow-hidden flex flex-col max-h-96">
40
- <DataTable
41
- collectionName={child.collection}
42
- searchParams={{ children_of: collectionName, parent_id: entry.id }}
43
- parentContext={{ collectionName, recordId: entry.id }}
44
- bind:changes={activeChanges!.children[child.collection]}
45
- showImport={false}
46
- showHeader={true}
47
- showFooter={true}
48
- showDelete={child.type === "fk" || child.type === "m2m"}
49
- tableProps={{ showLastColumnBorder: false, showLastRowBorder: true }}
50
- >
51
- {#snippet headerLeft()}
52
- <div class="flex items-center gap-2 px-1">
53
- <Table size="14" class="text-muted-foreground" />
54
- <span class="text-sm font-medium">{child.collection}</span>
55
- </div>
56
- {/snippet}
57
- </DataTable>
58
- </div>
59
- {/each}
60
- </div>
61
- {/if}
@@ -1,45 +0,0 @@
1
- <script>
2
- import * as Tooltip from "./ui/tooltip";
3
- import Button from "./ui/button/button.svelte";
4
- import { Menu, Moon, Sun } from "lucide-svelte";
5
- import BreadCrumbs from "./breadCrumbs.svelte";
6
- import { toggleMode, mode } from "mode-watcher";
7
- import { mediaQueries } from "../utils";
8
- import { expandMiniSideBar } from "./miniSidebar.svelte";
9
-
10
- let isSmallScreen = $derived(!mediaQueries.sm.current);
11
- </script>
12
-
13
- <div
14
- class="flex items-center justify-between border-b border-input bg-background px-3"
15
- >
16
- <div class="flex items-center gap-4">
17
- {#if isSmallScreen}
18
- <Menu
19
- class="h-10 text-muted-foreground hover:bg-transparent cursor-pointer hover:text-foreground"
20
- style="left: 0.6rem; top: 0rem;"
21
- size="18"
22
- onclick={() => expandMiniSideBar()}
23
- />
24
- {/if}
25
- <BreadCrumbs />
26
- </div>
27
- <div class="flex h-full items-center gap-3">
28
- <Tooltip.Root>
29
- <Tooltip.Trigger>
30
- <Button
31
- class="h-6 w-6 text-muted-foreground hover:bg-transparent"
32
- variant="ghost"
33
- size="icon"
34
- onclick={toggleMode}
35
- Icon={$mode === "light" ? Moon : Sun}
36
- ></Button>
37
- </Tooltip.Trigger>
38
- <Tooltip.Content side="bottom" sideOffset={7.5}
39
- >{$mode === "light"
40
- ? "Night Mode"
41
- : "Light Mode"}</Tooltip.Content
42
- >
43
- </Tooltip.Root>
44
- </div>
45
- </div>