@treenity/react 3.0.0 → 3.0.1
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/README.md +91 -0
- package/dist/AclEditor.d.ts +1 -1
- package/dist/AclEditor.d.ts.map +1 -1
- package/dist/AclEditor.js +5 -5
- package/dist/AclEditor.js.map +1 -1
- package/dist/ActionCards.d.ts +9 -0
- package/dist/ActionCards.d.ts.map +1 -0
- package/dist/ActionCards.js +96 -0
- package/dist/ActionCards.js.map +1 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +71 -185
- package/dist/App.js.map +1 -1
- package/dist/ComponentSection.d.ts +15 -0
- package/dist/ComponentSection.d.ts.map +1 -0
- package/dist/ComponentSection.js +25 -0
- package/dist/ComponentSection.js.map +1 -0
- package/dist/ErrorBoundary.d.ts +18 -0
- package/dist/ErrorBoundary.d.ts.map +1 -0
- package/dist/ErrorBoundary.js +17 -0
- package/dist/ErrorBoundary.js.map +1 -0
- package/dist/Inspector.d.ts +1 -0
- package/dist/Inspector.d.ts.map +1 -1
- package/dist/Inspector.js +22 -347
- package/dist/Inspector.js.map +1 -1
- package/dist/Login.d.ts +8 -0
- package/dist/Login.d.ts.map +1 -0
- package/dist/Login.js +45 -0
- package/dist/Login.js.map +1 -0
- package/dist/NodeEditor.d.ts +11 -0
- package/dist/NodeEditor.d.ts.map +1 -0
- package/dist/NodeEditor.js +157 -0
- package/dist/NodeEditor.js.map +1 -0
- package/dist/Tree.d.ts +1 -0
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Tree.js +8 -27
- package/dist/Tree.js.map +1 -1
- package/dist/bind/engine.js +1 -1
- package/dist/bind/engine.js.map +1 -1
- package/dist/bind/eval.d.ts +1 -1
- package/dist/bind/eval.d.ts.map +1 -1
- package/dist/bind/hook.d.ts +1 -1
- package/dist/bind/hook.d.ts.map +1 -1
- package/dist/bind/hook.js +1 -1
- package/dist/bind/hook.js.map +1 -1
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +4 -0
- package/dist/cache.js.map +1 -1
- package/dist/client-tree.d.ts +1 -2
- package/dist/client-tree.d.ts.map +1 -1
- package/dist/client-tree.js +12 -5
- package/dist/client-tree.js.map +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -4
- package/dist/client.js.map +1 -1
- package/dist/components/ConfirmDialog.d.ts +9 -0
- package/dist/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/components/ConfirmDialog.js +6 -0
- package/dist/components/ConfirmDialog.js.map +1 -0
- package/dist/components/ConfirmPopover.d.ts +8 -0
- package/dist/components/ConfirmPopover.d.ts.map +1 -0
- package/dist/components/ConfirmPopover.js +9 -0
- package/dist/components/ConfirmPopover.js.map +1 -0
- package/dist/components/PathBreadcrumb.d.ts +5 -0
- package/dist/components/PathBreadcrumb.d.ts.map +1 -0
- package/dist/components/PathBreadcrumb.js +16 -0
- package/dist/components/PathBreadcrumb.js.map +1 -0
- package/dist/components/lib/utils.d.ts +3 -0
- package/dist/components/lib/utils.d.ts.map +1 -0
- package/dist/components/lib/utils.js +6 -0
- package/dist/components/lib/utils.js.map +1 -0
- package/dist/components/ui/accordion.js +1 -1
- package/dist/components/ui/accordion.js.map +1 -1
- package/dist/components/ui/alert-dialog.d.ts +19 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +42 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts +12 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +28 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +9 -8
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.js +26 -21
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/card.d.ts +10 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +25 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/collapsible.d.ts +6 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +13 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/command.d.ts +19 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +35 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +1 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/drawer.js +1 -1
- package/dist/components/ui/drawer.js.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts +26 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +52 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/form-field.d.ts +7 -0
- package/dist/components/ui/form-field.d.ts.map +1 -0
- package/dist/components/ui/form-field.js +17 -0
- package/dist/components/ui/form-field.js.map +1 -0
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/label.js +1 -1
- package/dist/components/ui/label.js.map +1 -1
- package/dist/components/ui/pagination.d.ts +14 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +30 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.js +2 -2
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/progress.js +1 -1
- package/dist/components/ui/progress.js.map +1 -1
- package/dist/components/ui/resizable.d.ts +8 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +14 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +6 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +13 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/separator.d.ts +5 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +9 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +15 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +40 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +3 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +7 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.js +1 -1
- package/dist/components/ui/slider.js.map +1 -1
- package/dist/components/ui/switch.js +1 -1
- package/dist/components/ui/switch.js.map +1 -1
- package/dist/components/ui/table.d.ts +11 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +29 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +12 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +29 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.js +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/components/ui/toggle-group.d.ts +10 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +23 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +10 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +27 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/context/index.d.ts +27 -10
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +43 -36
- package/dist/context/index.js.map +1 -1
- package/dist/events.d.ts +10 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +78 -0
- package/dist/events.js.map +1 -0
- package/dist/fiber-tree.d.ts +3 -0
- package/dist/fiber-tree.d.ts.map +1 -0
- package/dist/fiber-tree.js +93 -0
- package/dist/fiber-tree.js.map +1 -0
- package/dist/hooks.d.ts +5 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +66 -6
- package/dist/hooks.js.map +1 -1
- package/dist/idb.d.ts +1 -1
- package/dist/idb.d.ts.map +1 -1
- package/dist/lib/to-plain.d.ts +2 -0
- package/dist/lib/to-plain.d.ts.map +1 -0
- package/dist/lib/to-plain.js +21 -0
- package/dist/lib/to-plain.js.map +1 -0
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -3
- package/dist/main.js.map +1 -1
- package/dist/mods/clients.d.ts +3 -0
- package/dist/mods/clients.d.ts.map +1 -0
- package/dist/mods/clients.js +4 -0
- package/dist/mods/clients.js.map +1 -0
- package/dist/mods/editor-ui/FieldLabel.d.ts +15 -0
- package/dist/mods/editor-ui/FieldLabel.d.ts.map +1 -0
- package/dist/mods/editor-ui/FieldLabel.js +55 -0
- package/dist/mods/editor-ui/FieldLabel.js.map +1 -0
- package/dist/mods/editor-ui/client.d.ts +1 -1
- package/dist/mods/editor-ui/client.d.ts.map +1 -1
- package/dist/mods/editor-ui/client.js +1 -1
- package/dist/mods/editor-ui/client.js.map +1 -1
- package/dist/mods/editor-ui/default-edit.d.ts +2 -0
- package/dist/mods/editor-ui/default-edit.d.ts.map +1 -0
- package/dist/mods/editor-ui/default-edit.js +54 -0
- package/dist/mods/editor-ui/default-edit.js.map +1 -0
- package/dist/mods/editor-ui/default-view.d.ts +8 -1
- package/dist/mods/editor-ui/default-view.d.ts.map +1 -1
- package/dist/mods/editor-ui/default-view.js +8 -5
- package/dist/mods/editor-ui/default-view.js.map +1 -1
- package/dist/mods/editor-ui/dir-view.js +0 -2
- package/dist/mods/editor-ui/dir-view.js.map +1 -1
- package/dist/mods/editor-ui/empty-placeholder.d.ts +5 -0
- package/dist/mods/editor-ui/empty-placeholder.d.ts.map +1 -0
- package/dist/mods/editor-ui/empty-placeholder.js +14 -0
- package/dist/mods/editor-ui/empty-placeholder.js.map +1 -0
- package/dist/mods/editor-ui/form-field.d.ts +17 -0
- package/dist/mods/editor-ui/form-field.d.ts.map +1 -0
- package/dist/mods/editor-ui/form-field.js +68 -0
- package/dist/mods/editor-ui/form-field.js.map +1 -0
- package/dist/mods/editor-ui/form-fields.d.ts +1 -2
- package/dist/mods/editor-ui/form-fields.d.ts.map +1 -1
- package/dist/mods/editor-ui/form-fields.js +56 -60
- package/dist/mods/editor-ui/form-fields.js.map +1 -1
- package/dist/mods/editor-ui/layout-view.js +3 -2
- package/dist/mods/editor-ui/layout-view.js.map +1 -1
- package/dist/mods/editor-ui/list-items.js +1 -1
- package/dist/mods/editor-ui/list-items.js.map +1 -1
- package/dist/mods/editor-ui/node-utils.d.ts +2 -2
- package/dist/mods/editor-ui/node-utils.d.ts.map +1 -1
- package/dist/mods/editor-ui/node-utils.js +4 -5
- package/dist/mods/editor-ui/node-utils.js.map +1 -1
- package/dist/mods/editor-ui/type-picker.d.ts +15 -0
- package/dist/mods/editor-ui/type-picker.d.ts.map +1 -0
- package/dist/mods/editor-ui/type-picker.js +69 -0
- package/dist/mods/editor-ui/type-picker.js.map +1 -0
- package/dist/mods/editor-ui/user-view.js +1 -1
- package/dist/mods/editor-ui/user-view.js.map +1 -1
- package/dist/mods/servers.d.ts +1 -0
- package/dist/mods/servers.d.ts.map +1 -0
- package/dist/mods/servers.js +4 -0
- package/dist/mods/servers.js.map +1 -0
- package/dist/mods/treenity/groups/index.js +1 -1
- package/dist/mods/treenity/groups/index.js.map +1 -1
- package/dist/mods/treenity/preview.js +1 -1
- package/dist/mods/treenity/preview.js.map +1 -1
- package/dist/mods/treenity/ref-view.js +1 -1
- package/dist/mods/treenity/ref-view.js.map +1 -1
- package/dist/mods/treenity/schema-form.js +1 -1
- package/dist/mods/treenity/schema-form.js.map +1 -1
- package/dist/mods/treenity/seed.js +1 -1
- package/dist/mods/treenity/seed.js.map +1 -1
- package/dist/mods/treenity/type-view.js +1 -1
- package/dist/mods/treenity/type-view.js.map +1 -1
- package/dist/schema-loader.d.ts +1 -1
- package/dist/schema-loader.d.ts.map +1 -1
- package/dist/schema-loader.js +1 -1
- package/dist/schema-loader.js.map +1 -1
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +16 -0
- package/dist/symbols.js.map +1 -0
- package/dist/trpc.d.ts +10 -3
- package/dist/trpc.d.ts.map +1 -1
- package/package.json +74 -8
- package/src/AclEditor.tsx +11 -18
- package/src/ActionCards.tsx +224 -0
- package/src/App.tsx +204 -385
- package/src/ComponentSection.tsx +113 -0
- package/src/ErrorBoundary.tsx +37 -0
- package/src/Inspector.css +54 -0
- package/src/Inspector.tsx +73 -793
- package/src/Login.tsx +97 -0
- package/src/NodeEditor.tsx +300 -0
- package/src/Tree.css +91 -0
- package/src/Tree.tsx +40 -43
- package/src/bind/bind.test.ts +1 -1
- package/src/bind/engine.ts +1 -1
- package/src/bind/eval.ts +1 -1
- package/src/bind/hook.ts +1 -1
- package/src/bind/pipes.ts +1 -1
- package/src/cache.ts +5 -1
- package/src/client-tree.test.ts +1 -1
- package/src/client-tree.ts +22 -16
- package/src/client.ts +2 -4
- package/src/components/ConfirmDialog.tsx +34 -0
- package/src/components/ConfirmPopover.tsx +41 -0
- package/src/components/PathBreadcrumb.tsx +36 -0
- package/src/components/lib/utils.ts +6 -0
- package/src/components/lib/utils.ts.bak +6 -0
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/alert-dialog.tsx +189 -0
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +108 -0
- package/src/components/ui/button.tsx +53 -31
- package/src/components/ui/card.tsx +91 -0
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/command.tsx +177 -0
- package/src/components/ui/dialog.tsx +1 -2
- package/src/components/ui/drawer.tsx +1 -1
- package/src/components/ui/dropdown-menu.tsx +256 -0
- package/src/components/ui/form-field.tsx +37 -0
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +2 -2
- package/src/components/ui/progress.tsx +1 -1
- package/src/components/ui/resizable.tsx +47 -0
- package/src/components/ui/scroll-area.tsx +55 -0
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/separator.tsx +27 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +1 -1
- package/src/components/ui/switch.tsx +1 -1
- package/src/components/ui/table.tsx +115 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/textarea.tsx +1 -1
- package/src/components/ui/toggle-group.tsx +82 -0
- package/src/components/ui/toggle.tsx +46 -0
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/context/index.tsx +75 -42
- package/src/events.ts +81 -0
- package/src/fiber-tree.ts +112 -0
- package/src/hooks.ts +88 -9
- package/src/idb.ts +1 -1
- package/src/lib/to-plain.ts +21 -0
- package/src/main.tsx +3 -1
- package/src/mods/clients.ts +3 -0
- package/src/mods/editor-ui/FieldLabel.tsx +124 -0
- package/src/mods/editor-ui/client.ts +1 -1
- package/src/mods/editor-ui/default-edit.tsx +99 -0
- package/src/mods/editor-ui/default-view.tsx +13 -8
- package/src/mods/editor-ui/dir-view.tsx +2 -2
- package/src/mods/editor-ui/editor-ui.css +174 -0
- package/src/mods/editor-ui/empty-placeholder.tsx +39 -0
- package/src/mods/editor-ui/form-field.tsx +144 -0
- package/src/mods/editor-ui/form-fields.tsx +132 -113
- package/src/mods/editor-ui/layout-view.tsx +4 -2
- package/src/mods/editor-ui/list-items.tsx +2 -2
- package/src/mods/editor-ui/node-utils.ts +4 -5
- package/src/mods/editor-ui/type-picker.tsx +147 -0
- package/src/mods/editor-ui/user-view.tsx +1 -1
- package/src/mods/servers.ts +2 -0
- package/src/mods/treenity/groups/index.tsx +1 -1
- package/src/mods/treenity/preview.tsx +1 -1
- package/src/mods/treenity/ref-view.tsx +1 -1
- package/src/mods/treenity/schema-form.tsx +1 -1
- package/src/mods/treenity/seed.ts +1 -1
- package/src/mods/treenity/type-view.tsx +1 -1
- package/src/optimistic.test.ts +111 -0
- package/src/remote-tree.test.ts +1 -1
- package/src/remote-tree.ts +1 -1
- package/src/root.css +117 -0
- package/src/schema-loader.ts +1 -1
- package/src/symbols.ts +18 -0
- package/src/index.html +0 -14
- package/src/style.css +0 -1269
- package/src/vite-env.d.ts +0 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '#components/lib/utils';
|
|
4
|
+
import { toggleVariants } from '#components/ui/toggle';
|
|
5
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
6
|
+
import { ToggleGroup as ToggleGroupPrimitive } from 'radix-ui';
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
|
|
9
|
+
const ToggleGroupContext = React.createContext<
|
|
10
|
+
VariantProps<typeof toggleVariants> & {
|
|
11
|
+
spacing?: number
|
|
12
|
+
}
|
|
13
|
+
>({
|
|
14
|
+
size: "default",
|
|
15
|
+
variant: "default",
|
|
16
|
+
spacing: 0,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
function ToggleGroup({
|
|
20
|
+
className,
|
|
21
|
+
variant,
|
|
22
|
+
size,
|
|
23
|
+
spacing = 0,
|
|
24
|
+
children,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
|
27
|
+
VariantProps<typeof toggleVariants> & {
|
|
28
|
+
spacing?: number
|
|
29
|
+
}) {
|
|
30
|
+
return (
|
|
31
|
+
<ToggleGroupPrimitive.Root
|
|
32
|
+
data-slot="toggle-group"
|
|
33
|
+
data-variant={variant}
|
|
34
|
+
data-size={size}
|
|
35
|
+
data-spacing={spacing}
|
|
36
|
+
style={{ "--gap": spacing } as React.CSSProperties}
|
|
37
|
+
className={cn(
|
|
38
|
+
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
|
|
44
|
+
{children}
|
|
45
|
+
</ToggleGroupContext.Provider>
|
|
46
|
+
</ToggleGroupPrimitive.Root>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ToggleGroupItem({
|
|
51
|
+
className,
|
|
52
|
+
children,
|
|
53
|
+
variant,
|
|
54
|
+
size,
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
|
57
|
+
VariantProps<typeof toggleVariants>) {
|
|
58
|
+
const context = React.useContext(ToggleGroupContext)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ToggleGroupPrimitive.Item
|
|
62
|
+
data-slot="toggle-group-item"
|
|
63
|
+
data-variant={context.variant || variant}
|
|
64
|
+
data-size={context.size || size}
|
|
65
|
+
data-spacing={context.spacing}
|
|
66
|
+
className={cn(
|
|
67
|
+
toggleVariants({
|
|
68
|
+
variant: context.variant || variant,
|
|
69
|
+
size: context.size || size,
|
|
70
|
+
}),
|
|
71
|
+
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
|
72
|
+
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</ToggleGroupPrimitive.Item>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { ToggleGroup, ToggleGroupItem }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '#components/lib/utils';
|
|
4
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
5
|
+
import { Toggle as TogglePrimitive } from 'radix-ui';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
|
|
8
|
+
const toggleVariants = cva(
|
|
9
|
+
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default: "bg-transparent",
|
|
14
|
+
outline:
|
|
15
|
+
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: "h-9 min-w-9 px-2",
|
|
19
|
+
sm: "h-8 min-w-8 px-1.5",
|
|
20
|
+
lg: "h-10 min-w-10 px-2.5",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
size: "default",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function Toggle({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
|
36
|
+
VariantProps<typeof toggleVariants>) {
|
|
37
|
+
return (
|
|
38
|
+
<TogglePrimitive.Root
|
|
39
|
+
data-slot="toggle"
|
|
40
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Toggle, toggleVariants }
|
package/src/context/index.tsx
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// <Render> + <RenderContext> + <NodeProvider>
|
|
3
3
|
// Depends on: core (resolve), React
|
|
4
4
|
|
|
5
|
+
import { execute } from '#hooks';
|
|
6
|
+
import { $key, $node } from '#symbols';
|
|
5
7
|
import {
|
|
6
8
|
type ComponentData,
|
|
7
9
|
hasMissResolver,
|
|
@@ -9,8 +11,8 @@ import {
|
|
|
9
11
|
resolve,
|
|
10
12
|
resolveExact,
|
|
11
13
|
subscribeRegistry,
|
|
12
|
-
} from '@treenity/core
|
|
13
|
-
import { createContext, createElement, type FC, type ReactNode, useContext, useEffect, useState } from 'react';
|
|
14
|
+
} from '@treenity/core';
|
|
15
|
+
import { createContext, createElement, type FC, type ReactNode, useContext, useEffect, useMemo, useState } from 'react';
|
|
14
16
|
|
|
15
17
|
// ── Tree context (rendering context string) ──
|
|
16
18
|
|
|
@@ -28,7 +30,7 @@ export function RenderContext({ name, children }: { name: string; children: Reac
|
|
|
28
30
|
|
|
29
31
|
const NodeCtx = createContext<NodeData | null>(null);
|
|
30
32
|
|
|
31
|
-
export function NodeProvider({ value, children }: { value: NodeData | null; children
|
|
33
|
+
export function NodeProvider({ value, children }: { value: NodeData | null; children?: ReactNode }) {
|
|
32
34
|
if (!value?.$path) return null;
|
|
33
35
|
return <NodeCtx.Provider value={value}>{children}</NodeCtx.Provider>;
|
|
34
36
|
}
|
|
@@ -39,32 +41,67 @@ export function useCurrentNode(): NodeData {
|
|
|
39
41
|
return n;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
// ── viewCtx — derive location context from value's symbol metadata ──
|
|
45
|
+
|
|
46
|
+
export type ViewCtx = {
|
|
47
|
+
node: NodeData;
|
|
48
|
+
path: string;
|
|
49
|
+
execute(action: string, data?: unknown): Promise<unknown>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function viewCtx(value: ComponentData): ViewCtx | null {
|
|
53
|
+
const node: NodeData | undefined = (value as any)[$node];
|
|
54
|
+
if (!node) return null;
|
|
55
|
+
const key: string = (value as any)[$key] ?? '';
|
|
56
|
+
const path = key ? `${node.$path}#${key}` : node.$path;
|
|
57
|
+
return { node, path, execute: (action, data?) => execute(path, action, data) };
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
// ── Handler type for React context ──
|
|
43
61
|
// value is ComponentData (base type). NodeData IS ComponentData.
|
|
44
|
-
// Renderers that need $path use usePath().
|
|
45
62
|
|
|
46
|
-
export type RenderProps = {
|
|
47
|
-
value:
|
|
48
|
-
onChange?: (next:
|
|
63
|
+
export type RenderProps<T = ComponentData> = {
|
|
64
|
+
value: T;
|
|
65
|
+
onChange?: (next: T) => void;
|
|
66
|
+
ctx?: ViewCtx | null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type ReactHandler = FC<RenderProps<any>>;
|
|
70
|
+
|
|
71
|
+
/** Typed view component. Use: `const MyView: View<MyType> = ({ value, ctx }) => ...` */
|
|
72
|
+
export type View<T> = FC<RenderProps<T>>;
|
|
73
|
+
|
|
74
|
+
// ── useActions — proxy that turns value's symbol metadata into action calls ──
|
|
75
|
+
|
|
76
|
+
type Actions<T> = {
|
|
77
|
+
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]:
|
|
78
|
+
T[K] extends (data: infer D) => any
|
|
79
|
+
? (data: D) => Promise<unknown>
|
|
80
|
+
: () => Promise<unknown>;
|
|
49
81
|
};
|
|
50
82
|
|
|
51
|
-
export
|
|
83
|
+
export function useActions<T extends ComponentData>(value: T): Actions<T> {
|
|
84
|
+
const ctx = viewCtx(value);
|
|
85
|
+
if (!ctx) throw new Error('useActions: value has no node context (missing $node symbol)');
|
|
86
|
+
|
|
87
|
+
return useMemo(() => new Proxy({} as Actions<T>, {
|
|
88
|
+
get: (_target, prop: string) => (data?: unknown) => ctx.execute(prop, data),
|
|
89
|
+
}), [ctx.path]);
|
|
90
|
+
}
|
|
52
91
|
|
|
53
92
|
declare module '@treenity/core/core/context' {
|
|
54
|
-
interface ContextHandlers {
|
|
55
|
-
react:
|
|
93
|
+
interface ContextHandlers<T> {
|
|
94
|
+
react: FC<RenderProps<T>>;
|
|
56
95
|
}
|
|
57
96
|
}
|
|
58
97
|
|
|
59
|
-
// ──
|
|
98
|
+
// ── UixNoView — registered by UIX when a type has no custom view yet ──
|
|
60
99
|
// Renders default@context without going through type-specific resolve (avoids infinite loop).
|
|
61
|
-
export const
|
|
100
|
+
export const UixNoView: FC<RenderProps> = ({ value, onChange }) => {
|
|
62
101
|
const context = useTreeContext();
|
|
63
102
|
const def = resolve('default', context, false) as FC<RenderProps> | null;
|
|
64
103
|
if (!def) return null;
|
|
65
|
-
|
|
66
|
-
if ('$path' in value) return <NodeProvider value={value as NodeData}>{el}</NodeProvider>;
|
|
67
|
-
return el;
|
|
104
|
+
return createElement(def, { value, onChange });
|
|
68
105
|
};
|
|
69
106
|
|
|
70
107
|
// ── <Render> — component/node-level rendering ──
|
|
@@ -73,40 +110,36 @@ export function Render({ value, onChange }: RenderProps) {
|
|
|
73
110
|
const context = useTreeContext();
|
|
74
111
|
const type = value.$type;
|
|
75
112
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const [
|
|
79
|
-
() => resolveExact(type, context) as ReactHandler | null,
|
|
80
|
-
);
|
|
113
|
+
const ctx_ = context as 'react';
|
|
114
|
+
const sync = useMemo(() => resolveExact(type, ctx_), [type, ctx_]);
|
|
115
|
+
const [async_, setAsync] = useState<ReactHandler | null>(null);
|
|
81
116
|
|
|
82
|
-
// Subscribe to registry bumps. When handler is registered async (UIX lazy load),
|
|
83
|
-
// the callback fires and stores the resolved handler → triggers re-render.
|
|
84
117
|
useEffect(() => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const h = resolveExact(type,
|
|
91
|
-
if (h)
|
|
118
|
+
if (sync) return;
|
|
119
|
+
setAsync(null);
|
|
120
|
+
if (hasMissResolver(ctx_)) resolve(type, ctx_);
|
|
121
|
+
|
|
122
|
+
return subscribeRegistry(() => {
|
|
123
|
+
const h = resolveExact(type, ctx_);
|
|
124
|
+
if (h) setAsync(() => h);
|
|
92
125
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
resolve(type, context);
|
|
126
|
+
}, [type, ctx_, sync]);
|
|
127
|
+
|
|
128
|
+
let Handler = sync ?? async_;
|
|
129
|
+
|
|
130
|
+
if (!Handler) {
|
|
131
|
+
if (hasMissResolver(ctx_)) {
|
|
132
|
+
resolve(type, ctx_);
|
|
101
133
|
return null;
|
|
102
134
|
}
|
|
103
|
-
|
|
135
|
+
Handler = resolve(type, ctx_, false);
|
|
104
136
|
}
|
|
105
137
|
|
|
106
|
-
if (!
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
if (!Handler) return null;
|
|
139
|
+
|
|
140
|
+
const ctx = viewCtx(value);
|
|
141
|
+
const el = createElement(Handler, { value, onChange, ctx });
|
|
142
|
+
return ctx?.node ? createElement(NodeProvider, { value: ctx.node }, el) : el;
|
|
110
143
|
}
|
|
111
144
|
|
|
112
145
|
// ── <RenderField> — field-level rendering by type name ──
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Server event subscription — module-level, not tied to any React component.
|
|
2
|
+
// Listens to trpc.events SSE and updates the cache.
|
|
3
|
+
|
|
4
|
+
import type { NodeData } from '@treenity/core';
|
|
5
|
+
import { applyPatch, type Operation } from 'fast-json-patch';
|
|
6
|
+
import * as cache from './cache';
|
|
7
|
+
import { trpc } from './trpc';
|
|
8
|
+
|
|
9
|
+
type LoadChildren = (path: string) => Promise<void>;
|
|
10
|
+
|
|
11
|
+
interface EventsConfig {
|
|
12
|
+
loadChildren: LoadChildren;
|
|
13
|
+
getExpanded: () => Set<string>;
|
|
14
|
+
getSelected: () => string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let unsub: (() => void) | null = null;
|
|
18
|
+
|
|
19
|
+
export function startEvents(config: EventsConfig) {
|
|
20
|
+
stopEvents();
|
|
21
|
+
|
|
22
|
+
const { loadChildren, getExpanded, getSelected } = config;
|
|
23
|
+
|
|
24
|
+
const sub = trpc.events.subscribe(undefined as void, {
|
|
25
|
+
onData(event) {
|
|
26
|
+
if (event.type === 'reconnect') {
|
|
27
|
+
if (!event.preserved) {
|
|
28
|
+
cache.signalReconnect();
|
|
29
|
+
for (const path of getExpanded()) loadChildren(path);
|
|
30
|
+
const sel = getSelected();
|
|
31
|
+
if (sel) {
|
|
32
|
+
trpc.get.query({ path: sel, watch: true }).then(n => {
|
|
33
|
+
if (n) cache.put(n as NodeData);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (event.type === 'set') {
|
|
41
|
+
cache.put({ $path: event.path, ...event.node } as NodeData);
|
|
42
|
+
if (event.addVps) event.addVps.forEach((vp: string) => cache.addToParent(event.path, vp));
|
|
43
|
+
if (event.rmVps) event.rmVps.forEach((vp: string) => cache.removeFromParent(event.path, vp));
|
|
44
|
+
} else if (event.type === 'patch') {
|
|
45
|
+
const existing = cache.get(event.path);
|
|
46
|
+
if (existing && event.patches) {
|
|
47
|
+
try {
|
|
48
|
+
applyPatch(existing, event.patches as Operation[]);
|
|
49
|
+
cache.put(existing);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error('Failed to apply patches, fetching full node:', e);
|
|
52
|
+
trpc.get.query({ path: event.path }).then((n) => {
|
|
53
|
+
if (n) cache.put(n as NodeData);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
trpc.get.query({ path: event.path }).then((n) => {
|
|
58
|
+
if (n) cache.put(n as NodeData);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (event.addVps) event.addVps.forEach((vp: string) => cache.addToParent(event.path, vp));
|
|
62
|
+
if (event.rmVps) event.rmVps.forEach((vp: string) => cache.removeFromParent(event.path, vp));
|
|
63
|
+
} else if (event.type === 'remove') {
|
|
64
|
+
if (event.rmVps && event.rmVps.length > 0) {
|
|
65
|
+
event.rmVps.forEach((vp: string) => cache.removeFromParent(event.path, vp));
|
|
66
|
+
} else {
|
|
67
|
+
cache.remove(event.path);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
unsub = () => sub.unsubscribe();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function stopEvents() {
|
|
77
|
+
if (unsub) {
|
|
78
|
+
unsub();
|
|
79
|
+
unsub = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Virtual read-only Tree over React Fiber.
|
|
2
|
+
// Walks the live Fiber tree on each query — no state, no tracking.
|
|
3
|
+
// Registered as t.mount.react — isomorphic mount, same as server-side mounts.
|
|
4
|
+
|
|
5
|
+
import type { NodeData } from '@treenity/core'
|
|
6
|
+
import { register } from '@treenity/core'
|
|
7
|
+
import type { Tree } from '@treenity/core/tree'
|
|
8
|
+
|
|
9
|
+
const PREFIX = '/local/react'
|
|
10
|
+
|
|
11
|
+
interface FiberNode {
|
|
12
|
+
$path: string
|
|
13
|
+
$type: string
|
|
14
|
+
sourcePath?: string
|
|
15
|
+
children?: FiberNode[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Detect <Render> by name + props shape (resilient to HMR wrappers)
|
|
19
|
+
function isRenderFiber(f: any): boolean {
|
|
20
|
+
if (!f.memoizedProps?.value?.$type) return false
|
|
21
|
+
const name = f.type?.name || f.type?.displayName || ''
|
|
22
|
+
return name === 'Render'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getRootFiber() {
|
|
26
|
+
const el = document.getElementById('root')
|
|
27
|
+
if (!el) return null
|
|
28
|
+
|
|
29
|
+
// React 19 createRoot: __reactContainer$ on the container element
|
|
30
|
+
const containerKey = Object.keys(el).find(k => k.startsWith('__reactContainer$'))
|
|
31
|
+
if (containerKey) return (el as any)[containerKey]
|
|
32
|
+
|
|
33
|
+
// React 18 fallback: __reactFiber$ on first child
|
|
34
|
+
const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber$'))
|
|
35
|
+
return fiberKey ? (el as any)[fiberKey] : null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function walk(fiber: any, parentPath: string, maxDepth: number, depth = 0): FiberNode[] {
|
|
39
|
+
if (depth >= maxDepth) return []
|
|
40
|
+
|
|
41
|
+
const result: FiberNode[] = []
|
|
42
|
+
let f = fiber?.child
|
|
43
|
+
let i = 0
|
|
44
|
+
|
|
45
|
+
while (f) {
|
|
46
|
+
if (isRenderFiber(f)) {
|
|
47
|
+
const v = f.memoizedProps.value
|
|
48
|
+
const path = `${parentPath}/${i}`
|
|
49
|
+
result.push({
|
|
50
|
+
$path: path,
|
|
51
|
+
$type: v.$type,
|
|
52
|
+
sourcePath: v.$path,
|
|
53
|
+
children: depth + 1 < maxDepth ? walk(f, path, maxDepth, depth + 1) : undefined,
|
|
54
|
+
})
|
|
55
|
+
i++
|
|
56
|
+
} else {
|
|
57
|
+
result.push(...walk(f, parentPath, maxDepth, depth))
|
|
58
|
+
}
|
|
59
|
+
f = f.sibling
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scan(depth = 1): FiberNode[] {
|
|
66
|
+
const root = getRootFiber()
|
|
67
|
+
return root ? walk(root, PREFIX, depth) : []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findByPath(nodes: FiberNode[], path: string): FiberNode | undefined {
|
|
71
|
+
for (const n of nodes) {
|
|
72
|
+
if (n.$path === path) return n
|
|
73
|
+
if (n.children) {
|
|
74
|
+
const found = findByPath(n.children, path)
|
|
75
|
+
if (found) return found
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toNode(n: FiberNode): NodeData {
|
|
81
|
+
return { $path: n.$path, $type: n.$type, sourcePath: n.sourcePath } as NodeData
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createFiberTree(): Tree {
|
|
85
|
+
return {
|
|
86
|
+
async get(path) {
|
|
87
|
+
if (path === PREFIX) return { $path: PREFIX, $type: 'dir' } as NodeData
|
|
88
|
+
const node = findByPath(scan(100), path)
|
|
89
|
+
return node ? toNode(node) : undefined
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
async getChildren(path, opts?) {
|
|
93
|
+
const depth = opts?.depth ?? 1
|
|
94
|
+
|
|
95
|
+
if (path === PREFIX) {
|
|
96
|
+
const nodes = scan(depth)
|
|
97
|
+
return { items: nodes.map(toNode), total: nodes.length }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parent = findByPath(scan(100), path)
|
|
101
|
+
const children = parent?.children ?? []
|
|
102
|
+
return { items: children.map(toNode), total: children.length }
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async set() { throw new Error('fiber tree is read-only') },
|
|
106
|
+
async remove() { throw new Error('fiber tree is read-only') },
|
|
107
|
+
async patch() { throw new Error('fiber tree is read-only') },
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Mount adapter — same pattern as t.mount.fs, t.mount.mongo
|
|
112
|
+
register('t.mount.react', 'mount', () => createFiberTree())
|
package/src/hooks.ts
CHANGED
|
@@ -5,10 +5,19 @@
|
|
|
5
5
|
// execute: action caller
|
|
6
6
|
// watch: universal async generator
|
|
7
7
|
|
|
8
|
-
import { type
|
|
9
|
-
import {
|
|
8
|
+
import { getComponent, type NodeData, normalizeType, resolve } from '@treenity/core';
|
|
9
|
+
import { type Class, type TypeProxy } from '@treenity/core/comp';
|
|
10
10
|
import { deriveURI, parseURI } from '@treenity/core/uri';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
createContext,
|
|
13
|
+
useCallback,
|
|
14
|
+
useContext,
|
|
15
|
+
useEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
useSyncExternalStore,
|
|
20
|
+
} from 'react';
|
|
12
21
|
import * as cache from './cache';
|
|
13
22
|
import { tree } from './client';
|
|
14
23
|
import { trpc } from './trpc';
|
|
@@ -50,7 +59,7 @@ export function usePath<T extends object>(
|
|
|
50
59
|
const path = isTyped ? pathOrUri : (parsed?.path ?? null);
|
|
51
60
|
|
|
52
61
|
const node = useSyncExternalStore(
|
|
53
|
-
useCallback((cb: () => void) => (path ? cache.subscribePath(path, cb) : () => {}), [path]),
|
|
62
|
+
useCallback((cb: () => void) => (path ? cache.subscribePath(path, cb) : () => { }), [path]),
|
|
54
63
|
useCallback(() => (path ? cache.get(path) : undefined), [path]),
|
|
55
64
|
);
|
|
56
65
|
|
|
@@ -111,12 +120,78 @@ export async function set(next: NodeData) {
|
|
|
111
120
|
// ── execute: action caller ──
|
|
112
121
|
|
|
113
122
|
export const execute = (
|
|
114
|
-
|
|
115
|
-
) =>
|
|
123
|
+
pathOrUri: string, action: string, data?: unknown, type?: string, key?: string,
|
|
124
|
+
) => {
|
|
125
|
+
let path = pathOrUri;
|
|
126
|
+
if (!key && pathOrUri.includes('#')) {
|
|
127
|
+
const parsed = parseURI(pathOrUri);
|
|
128
|
+
path = parsed.path;
|
|
129
|
+
key = parsed.key;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Optimistic: resolve class from cache + registry, predict locally
|
|
133
|
+
const cached = cache.get(path);
|
|
134
|
+
if (cached) {
|
|
135
|
+
const compType = type ?? cached.$type;
|
|
136
|
+
const cls = resolve(compType, 'class');
|
|
137
|
+
if (cls) {
|
|
138
|
+
const fn = cls.prototype?.[action];
|
|
139
|
+
if (fn) predictOptimistic(path, cls, key, fn, data);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return trpc.execute.mutate({ path, type, key, action, data });
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// ── useCanWrite: ACL-based write permission check ──
|
|
147
|
+
|
|
148
|
+
const W = 2;
|
|
149
|
+
const permCache = new Map<string, { perm: number; ts: number }>();
|
|
150
|
+
const PERM_TTL = 30_000; // 30s cache
|
|
151
|
+
|
|
152
|
+
export function useCanWrite(path: string | null): boolean {
|
|
153
|
+
const [perm, setPerm] = useState<number>(0);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!path) return;
|
|
157
|
+
const cached = permCache.get(path);
|
|
158
|
+
if (cached && Date.now() - cached.ts < PERM_TTL) {
|
|
159
|
+
setPerm(cached.perm);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
trpc.getPerm.query({ path }).then((p) => {
|
|
163
|
+
permCache.set(path, { perm: p, ts: Date.now() });
|
|
164
|
+
setPerm(p);
|
|
165
|
+
}).catch(() => setPerm(0));
|
|
166
|
+
}, [path]);
|
|
167
|
+
|
|
168
|
+
return (perm & W) !== 0;
|
|
169
|
+
}
|
|
116
170
|
|
|
117
171
|
// ── Internals ──
|
|
118
172
|
|
|
119
|
-
const AsyncGenFn = Object.getPrototypeOf(async function* () {}).constructor;
|
|
173
|
+
const AsyncGenFn = Object.getPrototypeOf(async function* () { }).constructor;
|
|
174
|
+
const AsyncFn = Object.getPrototypeOf(async function () { }).constructor;
|
|
175
|
+
|
|
176
|
+
/** Optimistic prediction: run a sync method locally on a cloned cached node */
|
|
177
|
+
export function predictOptimistic<T extends object>(
|
|
178
|
+
path: string, cls: Class<T>, key: string | undefined,
|
|
179
|
+
fn: Function, data: unknown,
|
|
180
|
+
): void {
|
|
181
|
+
if (fn instanceof AsyncFn) return;
|
|
182
|
+
|
|
183
|
+
const cached = cache.get(path);
|
|
184
|
+
if (!cached) return;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const draft = structuredClone(cached);
|
|
188
|
+
const target = getComponent(draft, cls, key);
|
|
189
|
+
if (!target) return;
|
|
190
|
+
|
|
191
|
+
fn.call(target, data);
|
|
192
|
+
cache.put(draft);
|
|
193
|
+
} catch { /* prediction failed — server-only */ }
|
|
194
|
+
}
|
|
120
195
|
|
|
121
196
|
function streamToAsyncIterable<T>(
|
|
122
197
|
input: { path: string; type?: string; key?: string; action: string; data?: unknown },
|
|
@@ -157,7 +232,7 @@ function makeProxy<T extends object>(
|
|
|
157
232
|
): TypeProxy<T> {
|
|
158
233
|
const type = normalizeType(cls);
|
|
159
234
|
const comp = node
|
|
160
|
-
? (key ? getComponent(node, key) :
|
|
235
|
+
? (key ? getComponent(node, key) : getComponent(node, cls))
|
|
161
236
|
: undefined;
|
|
162
237
|
|
|
163
238
|
return new Proxy(comp ?? {}, {
|
|
@@ -166,7 +241,11 @@ function makeProxy<T extends object>(
|
|
|
166
241
|
if (typeof fn === 'function') {
|
|
167
242
|
if (fn instanceof AsyncGenFn)
|
|
168
243
|
return (data?: unknown) => streamToAsyncIterable({ path, type, key, action: prop, data });
|
|
169
|
-
|
|
244
|
+
|
|
245
|
+
return (data?: unknown) => {
|
|
246
|
+
predictOptimistic(path, cls, key, fn, data);
|
|
247
|
+
return trpc.execute.mutate({ path, type, key, action: prop, data });
|
|
248
|
+
};
|
|
170
249
|
}
|
|
171
250
|
return (comp as any)?.[prop];
|
|
172
251
|
},
|
package/src/idb.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Raw IDB API, no dependencies. Fire-and-forget friendly.
|
|
3
3
|
// Degrades silently if IDB unavailable (private browsing, SSR).
|
|
4
4
|
|
|
5
|
-
import type { NodeData } from '@treenity/core
|
|
5
|
+
import type { NodeData } from '@treenity/core';
|
|
6
6
|
|
|
7
7
|
const DB_NAME = 'treenity';
|
|
8
8
|
const DB_VERSION = 1;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Deep-clone any value into a plain JSON-safe object.
|
|
2
|
+
// Strips valtio proxies, preserves Dates, drops functions/symbols.
|
|
3
|
+
|
|
4
|
+
export function toPlain<T>(value: T): T {
|
|
5
|
+
if (value === null || value === undefined) return value;
|
|
6
|
+
|
|
7
|
+
if (value instanceof Date) return new Date(value.getTime()) as T;
|
|
8
|
+
|
|
9
|
+
if (Array.isArray(value)) return value.map(toPlain) as T;
|
|
10
|
+
|
|
11
|
+
if (typeof value === 'object') {
|
|
12
|
+
const out: Record<string, unknown> = {};
|
|
13
|
+
for (const [k, v] of Object.entries(value)) {
|
|
14
|
+
if (typeof v === 'function' || typeof v === 'symbol') continue;
|
|
15
|
+
out[k] = toPlain(v);
|
|
16
|
+
}
|
|
17
|
+
return out as T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return value;
|
|
21
|
+
}
|
package/src/main.tsx
CHANGED
|
@@ -5,7 +5,8 @@ import { StrictMode } from 'react';
|
|
|
5
5
|
import { createRoot } from 'react-dom/client';
|
|
6
6
|
import { App } from './App';
|
|
7
7
|
import './load-client';
|
|
8
|
-
import './
|
|
8
|
+
import { Toaster } from './components/ui/sonner';
|
|
9
|
+
import './root.css';
|
|
9
10
|
|
|
10
11
|
enablePatches();
|
|
11
12
|
|
|
@@ -17,6 +18,7 @@ createRoot(root).render(
|
|
|
17
18
|
<StrictMode>
|
|
18
19
|
<QueryClientProvider client={queryClient}>
|
|
19
20
|
<App />
|
|
21
|
+
<Toaster />
|
|
20
22
|
</QueryClientProvider>
|
|
21
23
|
</StrictMode>,
|
|
22
24
|
);
|