@treenity/react 3.0.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 (297) hide show
  1. package/dist/AclEditor.d.ts +11 -0
  2. package/dist/AclEditor.d.ts.map +1 -0
  3. package/dist/AclEditor.js +152 -0
  4. package/dist/AclEditor.js.map +1 -0
  5. package/dist/App.d.ts +2 -0
  6. package/dist/App.d.ts.map +1 -0
  7. package/dist/App.js +521 -0
  8. package/dist/App.js.map +1 -0
  9. package/dist/Inspector.d.ts +12 -0
  10. package/dist/Inspector.d.ts.map +1 -0
  11. package/dist/Inspector.js +360 -0
  12. package/dist/Inspector.js.map +1 -0
  13. package/dist/Tree.d.ts +16 -0
  14. package/dist/Tree.d.ts.map +1 -0
  15. package/dist/Tree.js +100 -0
  16. package/dist/Tree.js.map +1 -0
  17. package/dist/ViewPage.d.ts +5 -0
  18. package/dist/ViewPage.d.ts.map +1 -0
  19. package/dist/ViewPage.js +13 -0
  20. package/dist/ViewPage.js.map +1 -0
  21. package/dist/bind/computed.d.ts +9 -0
  22. package/dist/bind/computed.d.ts.map +1 -0
  23. package/dist/bind/computed.js +61 -0
  24. package/dist/bind/computed.js.map +1 -0
  25. package/dist/bind/engine.d.ts +3 -0
  26. package/dist/bind/engine.d.ts.map +1 -0
  27. package/dist/bind/engine.js +184 -0
  28. package/dist/bind/engine.js.map +1 -0
  29. package/dist/bind/eval.d.ts +13 -0
  30. package/dist/bind/eval.d.ts.map +1 -0
  31. package/dist/bind/eval.js +97 -0
  32. package/dist/bind/eval.js.map +1 -0
  33. package/dist/bind/hook.d.ts +8 -0
  34. package/dist/bind/hook.d.ts.map +1 -0
  35. package/dist/bind/hook.js +99 -0
  36. package/dist/bind/hook.js.map +1 -0
  37. package/dist/bind/parse.d.ts +19 -0
  38. package/dist/bind/parse.d.ts.map +1 -0
  39. package/dist/bind/parse.js +86 -0
  40. package/dist/bind/parse.js.map +1 -0
  41. package/dist/bind/pipes.d.ts +4 -0
  42. package/dist/bind/pipes.d.ts.map +1 -0
  43. package/dist/bind/pipes.js +43 -0
  44. package/dist/bind/pipes.js.map +1 -0
  45. package/dist/cache.d.ts +27 -0
  46. package/dist/cache.d.ts.map +1 -0
  47. package/dist/cache.js +236 -0
  48. package/dist/cache.js.map +1 -0
  49. package/dist/client-tree.d.ts +9 -0
  50. package/dist/client-tree.d.ts.map +1 -0
  51. package/dist/client-tree.js +14 -0
  52. package/dist/client-tree.js.map +1 -0
  53. package/dist/client.d.ts +2 -0
  54. package/dist/client.d.ts.map +1 -0
  55. package/dist/client.js +10 -0
  56. package/dist/client.js.map +1 -0
  57. package/dist/components/ui/accordion.d.ts +8 -0
  58. package/dist/components/ui/accordion.d.ts.map +1 -0
  59. package/dist/components/ui/accordion.js +18 -0
  60. package/dist/components/ui/accordion.js.map +1 -0
  61. package/dist/components/ui/badge.d.ts +10 -0
  62. package/dist/components/ui/badge.d.ts.map +1 -0
  63. package/dist/components/ui/badge.js +19 -0
  64. package/dist/components/ui/badge.js.map +1 -0
  65. package/dist/components/ui/button.d.ts +11 -0
  66. package/dist/components/ui/button.d.ts.map +1 -0
  67. package/dist/components/ui/button.js +31 -0
  68. package/dist/components/ui/button.js.map +1 -0
  69. package/dist/components/ui/checkbox.d.ts +4 -0
  70. package/dist/components/ui/checkbox.d.ts.map +1 -0
  71. package/dist/components/ui/checkbox.js +7 -0
  72. package/dist/components/ui/checkbox.js.map +1 -0
  73. package/dist/components/ui/dialog.d.ts +18 -0
  74. package/dist/components/ui/dialog.d.ts.map +1 -0
  75. package/dist/components/ui/dialog.js +37 -0
  76. package/dist/components/ui/dialog.js.map +1 -0
  77. package/dist/components/ui/drawer.d.ts +14 -0
  78. package/dist/components/ui/drawer.d.ts.map +1 -0
  79. package/dist/components/ui/drawer.js +35 -0
  80. package/dist/components/ui/drawer.js.map +1 -0
  81. package/dist/components/ui/input.d.ts +4 -0
  82. package/dist/components/ui/input.d.ts.map +1 -0
  83. package/dist/components/ui/input.js +7 -0
  84. package/dist/components/ui/input.js.map +1 -0
  85. package/dist/components/ui/label.d.ts +5 -0
  86. package/dist/components/ui/label.d.ts.map +1 -0
  87. package/dist/components/ui/label.js +8 -0
  88. package/dist/components/ui/label.js.map +1 -0
  89. package/dist/components/ui/popover.d.ts +11 -0
  90. package/dist/components/ui/popover.d.ts.map +1 -0
  91. package/dist/components/ui/popover.js +26 -0
  92. package/dist/components/ui/popover.js.map +1 -0
  93. package/dist/components/ui/progress.d.ts +5 -0
  94. package/dist/components/ui/progress.d.ts.map +1 -0
  95. package/dist/components/ui/progress.js +9 -0
  96. package/dist/components/ui/progress.js.map +1 -0
  97. package/dist/components/ui/select.d.ts +16 -0
  98. package/dist/components/ui/select.d.ts.map +1 -0
  99. package/dist/components/ui/select.js +39 -0
  100. package/dist/components/ui/select.js.map +1 -0
  101. package/dist/components/ui/slider.d.ts +5 -0
  102. package/dist/components/ui/slider.d.ts.map +1 -0
  103. package/dist/components/ui/slider.js +15 -0
  104. package/dist/components/ui/slider.js.map +1 -0
  105. package/dist/components/ui/sonner.d.ts +4 -0
  106. package/dist/components/ui/sonner.d.ts.map +1 -0
  107. package/dist/components/ui/sonner.js +21 -0
  108. package/dist/components/ui/sonner.js.map +1 -0
  109. package/dist/components/ui/switch.d.ts +7 -0
  110. package/dist/components/ui/switch.d.ts.map +1 -0
  111. package/dist/components/ui/switch.js +9 -0
  112. package/dist/components/ui/switch.js.map +1 -0
  113. package/dist/components/ui/textarea.d.ts +4 -0
  114. package/dist/components/ui/textarea.d.ts.map +1 -0
  115. package/dist/components/ui/textarea.js +7 -0
  116. package/dist/components/ui/textarea.js.map +1 -0
  117. package/dist/components/ui/tooltip.d.ts +8 -0
  118. package/dist/components/ui/tooltip.d.ts.map +1 -0
  119. package/dist/components/ui/tooltip.js +18 -0
  120. package/dist/components/ui/tooltip.js.map +1 -0
  121. package/dist/context/index.d.ts +31 -0
  122. package/dist/context/index.d.ts.map +1 -0
  123. package/dist/context/index.js +98 -0
  124. package/dist/context/index.js.map +1 -0
  125. package/dist/context.d.ts +2 -0
  126. package/dist/context.d.ts.map +1 -0
  127. package/dist/context.js +2 -0
  128. package/dist/context.js.map +1 -0
  129. package/dist/hooks.d.ts +21 -0
  130. package/dist/hooks.d.ts.map +1 -0
  131. package/dist/hooks.js +156 -0
  132. package/dist/hooks.js.map +1 -0
  133. package/dist/idb.d.ts +13 -0
  134. package/dist/idb.d.ts.map +1 -0
  135. package/dist/idb.js +67 -0
  136. package/dist/idb.js.map +1 -0
  137. package/dist/lib/minimd.d.ts +3 -0
  138. package/dist/lib/minimd.d.ts.map +1 -0
  139. package/dist/lib/minimd.js +97 -0
  140. package/dist/lib/minimd.js.map +1 -0
  141. package/dist/lib/utils.d.ts +3 -0
  142. package/dist/lib/utils.d.ts.map +1 -0
  143. package/dist/lib/utils.js +6 -0
  144. package/dist/lib/utils.js.map +1 -0
  145. package/dist/load-client.d.ts +2 -0
  146. package/dist/load-client.d.ts.map +1 -0
  147. package/dist/load-client.js +6 -0
  148. package/dist/load-client.js.map +1 -0
  149. package/dist/main.d.ts +4 -0
  150. package/dist/main.d.ts.map +1 -0
  151. package/dist/main.js +16 -0
  152. package/dist/main.js.map +1 -0
  153. package/dist/mods/editor-ui/client.d.ts +6 -0
  154. package/dist/mods/editor-ui/client.d.ts.map +1 -0
  155. package/dist/mods/editor-ui/client.js +8 -0
  156. package/dist/mods/editor-ui/client.js.map +1 -0
  157. package/dist/mods/editor-ui/default-view.d.ts +2 -0
  158. package/dist/mods/editor-ui/default-view.d.ts.map +1 -0
  159. package/dist/mods/editor-ui/default-view.js +71 -0
  160. package/dist/mods/editor-ui/default-view.js.map +1 -0
  161. package/dist/mods/editor-ui/dir-view.d.ts +2 -0
  162. package/dist/mods/editor-ui/dir-view.d.ts.map +1 -0
  163. package/dist/mods/editor-ui/dir-view.js +42 -0
  164. package/dist/mods/editor-ui/dir-view.js.map +1 -0
  165. package/dist/mods/editor-ui/form-fields.d.ts +6 -0
  166. package/dist/mods/editor-ui/form-fields.d.ts.map +1 -0
  167. package/dist/mods/editor-ui/form-fields.js +401 -0
  168. package/dist/mods/editor-ui/form-fields.js.map +1 -0
  169. package/dist/mods/editor-ui/layout-view.d.ts +2 -0
  170. package/dist/mods/editor-ui/layout-view.d.ts.map +1 -0
  171. package/dist/mods/editor-ui/layout-view.js +22 -0
  172. package/dist/mods/editor-ui/layout-view.js.map +1 -0
  173. package/dist/mods/editor-ui/list-items.d.ts +2 -0
  174. package/dist/mods/editor-ui/list-items.d.ts.map +1 -0
  175. package/dist/mods/editor-ui/list-items.js +38 -0
  176. package/dist/mods/editor-ui/list-items.js.map +1 -0
  177. package/dist/mods/editor-ui/node-utils.d.ts +10 -0
  178. package/dist/mods/editor-ui/node-utils.d.ts.map +1 -0
  179. package/dist/mods/editor-ui/node-utils.js +76 -0
  180. package/dist/mods/editor-ui/node-utils.js.map +1 -0
  181. package/dist/mods/editor-ui/user-view.d.ts +2 -0
  182. package/dist/mods/editor-ui/user-view.d.ts.map +1 -0
  183. package/dist/mods/editor-ui/user-view.js +47 -0
  184. package/dist/mods/editor-ui/user-view.js.map +1 -0
  185. package/dist/mods/treenity/client.d.ts +4 -0
  186. package/dist/mods/treenity/client.d.ts.map +1 -0
  187. package/dist/mods/treenity/client.js +6 -0
  188. package/dist/mods/treenity/client.js.map +1 -0
  189. package/dist/mods/treenity/groups/index.d.ts +2 -0
  190. package/dist/mods/treenity/groups/index.d.ts.map +1 -0
  191. package/dist/mods/treenity/groups/index.js +27 -0
  192. package/dist/mods/treenity/groups/index.js.map +1 -0
  193. package/dist/mods/treenity/preview.d.ts +6 -0
  194. package/dist/mods/treenity/preview.d.ts.map +1 -0
  195. package/dist/mods/treenity/preview.js +95 -0
  196. package/dist/mods/treenity/preview.js.map +1 -0
  197. package/dist/mods/treenity/ref-view.d.ts +2 -0
  198. package/dist/mods/treenity/ref-view.d.ts.map +1 -0
  199. package/dist/mods/treenity/ref-view.js +29 -0
  200. package/dist/mods/treenity/ref-view.js.map +1 -0
  201. package/dist/mods/treenity/schema-form.d.ts +2 -0
  202. package/dist/mods/treenity/schema-form.d.ts.map +1 -0
  203. package/dist/mods/treenity/schema-form.js +38 -0
  204. package/dist/mods/treenity/schema-form.js.map +1 -0
  205. package/dist/mods/treenity/seed.d.ts +2 -0
  206. package/dist/mods/treenity/seed.d.ts.map +1 -0
  207. package/dist/mods/treenity/seed.js +53 -0
  208. package/dist/mods/treenity/seed.js.map +1 -0
  209. package/dist/mods/treenity/server.d.ts +2 -0
  210. package/dist/mods/treenity/server.d.ts.map +1 -0
  211. package/dist/mods/treenity/server.js +2 -0
  212. package/dist/mods/treenity/server.js.map +1 -0
  213. package/dist/mods/treenity/type-view.d.ts +2 -0
  214. package/dist/mods/treenity/type-view.d.ts.map +1 -0
  215. package/dist/mods/treenity/type-view.js +36 -0
  216. package/dist/mods/treenity/type-view.js.map +1 -0
  217. package/dist/remote-tree.d.ts +6 -0
  218. package/dist/remote-tree.d.ts.map +1 -0
  219. package/dist/remote-tree.js +18 -0
  220. package/dist/remote-tree.js.map +1 -0
  221. package/dist/schema-loader.d.ts +19 -0
  222. package/dist/schema-loader.d.ts.map +1 -0
  223. package/dist/schema-loader.js +63 -0
  224. package/dist/schema-loader.js.map +1 -0
  225. package/dist/trpc.d.ts +187 -0
  226. package/dist/trpc.d.ts.map +1 -0
  227. package/dist/trpc.js +21 -0
  228. package/dist/trpc.js.map +1 -0
  229. package/package.json +88 -0
  230. package/src/AclEditor.tsx +330 -0
  231. package/src/App.tsx +775 -0
  232. package/src/CLAUDE.md +16 -0
  233. package/src/Inspector.tsx +857 -0
  234. package/src/Tree.tsx +237 -0
  235. package/src/ViewPage.tsx +45 -0
  236. package/src/bind/bind.test.ts +316 -0
  237. package/src/bind/computed.ts +64 -0
  238. package/src/bind/engine.ts +198 -0
  239. package/src/bind/eval.ts +108 -0
  240. package/src/bind/hook.ts +112 -0
  241. package/src/bind/parse.ts +104 -0
  242. package/src/bind/pipes.ts +71 -0
  243. package/src/cache.test.ts +139 -0
  244. package/src/cache.ts +244 -0
  245. package/src/client-tree.test.ts +116 -0
  246. package/src/client-tree.ts +24 -0
  247. package/src/client.ts +11 -0
  248. package/src/components/ui/accordion.tsx +63 -0
  249. package/src/components/ui/badge.tsx +27 -0
  250. package/src/components/ui/button.tsx +44 -0
  251. package/src/components/ui/checkbox.tsx +19 -0
  252. package/src/components/ui/dialog.tsx +156 -0
  253. package/src/components/ui/drawer.tsx +132 -0
  254. package/src/components/ui/input.tsx +19 -0
  255. package/src/components/ui/label.tsx +21 -0
  256. package/src/components/ui/popover.tsx +86 -0
  257. package/src/components/ui/progress.tsx +30 -0
  258. package/src/components/ui/select.tsx +189 -0
  259. package/src/components/ui/slider.tsx +62 -0
  260. package/src/components/ui/sonner.tsx +32 -0
  261. package/src/components/ui/switch.tsx +34 -0
  262. package/src/components/ui/textarea.tsx +17 -0
  263. package/src/components/ui/tooltip.tsx +56 -0
  264. package/src/context/index.tsx +131 -0
  265. package/src/context.ts +1 -0
  266. package/src/hooks.ts +208 -0
  267. package/src/idb.ts +80 -0
  268. package/src/index.html +14 -0
  269. package/src/lib/minimd.css +28 -0
  270. package/src/lib/minimd.ts +95 -0
  271. package/src/lib/utils.ts +6 -0
  272. package/src/load-client.ts +5 -0
  273. package/src/main.tsx +22 -0
  274. package/src/mods/editor-ui/CLAUDE.md +3 -0
  275. package/src/mods/editor-ui/client.ts +8 -0
  276. package/src/mods/editor-ui/default-view.tsx +148 -0
  277. package/src/mods/editor-ui/dir-view.tsx +91 -0
  278. package/src/mods/editor-ui/form-fields.tsx +861 -0
  279. package/src/mods/editor-ui/layout-view.tsx +62 -0
  280. package/src/mods/editor-ui/list-items.tsx +63 -0
  281. package/src/mods/editor-ui/node-utils.ts +84 -0
  282. package/src/mods/editor-ui/user-view.tsx +101 -0
  283. package/src/mods/treenity/CLAUDE.md +7 -0
  284. package/src/mods/treenity/client.ts +6 -0
  285. package/src/mods/treenity/groups/index.tsx +65 -0
  286. package/src/mods/treenity/preview.tsx +133 -0
  287. package/src/mods/treenity/ref-view.tsx +87 -0
  288. package/src/mods/treenity/schema-form.tsx +65 -0
  289. package/src/mods/treenity/seed.ts +56 -0
  290. package/src/mods/treenity/server.ts +1 -0
  291. package/src/mods/treenity/type-view.tsx +116 -0
  292. package/src/remote-tree.test.ts +142 -0
  293. package/src/remote-tree.ts +25 -0
  294. package/src/schema-loader.ts +84 -0
  295. package/src/style.css +1269 -0
  296. package/src/trpc.ts +27 -0
  297. package/src/vite-env.d.ts +3 -0
@@ -0,0 +1,32 @@
1
+ import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon } from 'lucide-react';
2
+ import { useTheme } from 'next-themes';
3
+ import { Toaster as Sonner, type ToasterProps } from 'sonner';
4
+
5
+ const Toaster = ({ ...props }: ToasterProps) => {
6
+ const { theme = "system" } = useTheme()
7
+
8
+ return (
9
+ <Sonner
10
+ theme={theme as ToasterProps["theme"]}
11
+ className="toaster group"
12
+ icons={{
13
+ success: <CircleCheckIcon className="size-4" />,
14
+ info: <InfoIcon className="size-4" />,
15
+ warning: <TriangleAlertIcon className="size-4" />,
16
+ error: <OctagonXIcon className="size-4" />,
17
+ loading: <Loader2Icon className="size-4 animate-spin" />,
18
+ }}
19
+ style={
20
+ {
21
+ "--normal-bg": "var(--popover)",
22
+ "--normal-text": "var(--popover-foreground)",
23
+ "--normal-border": "var(--border)",
24
+ "--border-radius": "var(--radius)",
25
+ } as React.CSSProperties
26
+ }
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ export { Toaster }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { cn } from '#lib/utils';
4
+ import { Switch as SwitchPrimitive } from 'radix-ui';
5
+ import * as React from 'react';
6
+
7
+ function Switch({
8
+ className,
9
+ size = "default",
10
+ ...props
11
+ }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
12
+ size?: "sm" | "default"
13
+ }) {
14
+ return (
15
+ <SwitchPrimitive.Root
16
+ data-slot="switch"
17
+ data-size={size}
18
+ className={cn(
19
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
20
+ className
21
+ )}
22
+ {...props}
23
+ >
24
+ <SwitchPrimitive.Thumb
25
+ data-slot="switch-thumb"
26
+ className={cn(
27
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
28
+ )}
29
+ />
30
+ </SwitchPrimitive.Root>
31
+ )
32
+ }
33
+
34
+ export { Switch }
@@ -0,0 +1,17 @@
1
+ import { cn } from '#lib/utils';
2
+ import * as React from 'react';
3
+
4
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
5
+ return (
6
+ <textarea
7
+ data-slot="textarea"
8
+ className={cn(
9
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ export { Textarea }
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+
3
+ import { cn } from '#lib/utils';
4
+ import { Tooltip as TooltipPrimitive } from 'radix-ui';
5
+ import * as React from 'react';
6
+
7
+ function TooltipProvider({
8
+ delayDuration = 0,
9
+ ...props
10
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
11
+ return (
12
+ <TooltipPrimitive.Provider
13
+ data-slot="tooltip-provider"
14
+ delayDuration={delayDuration}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+
20
+ function Tooltip({
21
+ ...props
22
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
23
+ return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
24
+ }
25
+
26
+ function TooltipTrigger({
27
+ ...props
28
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
29
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
30
+ }
31
+
32
+ function TooltipContent({
33
+ className,
34
+ sideOffset = 0,
35
+ children,
36
+ ...props
37
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
38
+ return (
39
+ <TooltipPrimitive.Portal>
40
+ <TooltipPrimitive.Content
41
+ data-slot="tooltip-content"
42
+ sideOffset={sideOffset}
43
+ className={cn(
44
+ "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ {children}
50
+ <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
51
+ </TooltipPrimitive.Content>
52
+ </TooltipPrimitive.Portal>
53
+ )
54
+ }
55
+
56
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,131 @@
1
+ // Treenity React Binding — Layer 2
2
+ // <Render> + <RenderContext> + <NodeProvider>
3
+ // Depends on: core (resolve), React
4
+
5
+ import {
6
+ type ComponentData,
7
+ hasMissResolver,
8
+ type NodeData,
9
+ resolve,
10
+ resolveExact,
11
+ subscribeRegistry,
12
+ } from '@treenity/core/core';
13
+ import { createContext, createElement, type FC, type ReactNode, useContext, useEffect, useState } from 'react';
14
+
15
+ // ── Tree context (rendering context string) ──
16
+
17
+ const TreeContext = createContext<string>('react');
18
+
19
+ export function useTreeContext(): string {
20
+ return useContext(TreeContext);
21
+ }
22
+
23
+ export function RenderContext({ name, children }: { name: string; children: ReactNode }) {
24
+ return <TreeContext.Provider value={name}>{children}</TreeContext.Provider>;
25
+ }
26
+
27
+ // ── Node context — gives any renderer access to the current node ──
28
+
29
+ const NodeCtx = createContext<NodeData | null>(null);
30
+
31
+ export function NodeProvider({ value, children }: { value: NodeData | null; children: ReactNode }) {
32
+ if (!value?.$path) return null;
33
+ return <NodeCtx.Provider value={value}>{children}</NodeCtx.Provider>;
34
+ }
35
+
36
+ export function useCurrentNode(): NodeData {
37
+ const n = useContext(NodeCtx);
38
+ if (!n) throw new Error('useCurrentNode: no node in context');
39
+ return n;
40
+ }
41
+
42
+ // ── Handler type for React context ──
43
+ // value is ComponentData (base type). NodeData IS ComponentData.
44
+ // Renderers that need $path use usePath().
45
+
46
+ export type RenderProps = {
47
+ value: ComponentData;
48
+ onChange?: (next: ComponentData) => void;
49
+ };
50
+
51
+ export type ReactHandler = FC<RenderProps>;
52
+
53
+ declare module '@treenity/core/core/context' {
54
+ interface ContextHandlers {
55
+ react: ReactHandler;
56
+ }
57
+ }
58
+
59
+ // ── SystemFallbackView — registered by UIX when a type has no custom view ──
60
+ // Renders default@context without going through type-specific resolve (avoids infinite loop).
61
+ export const SystemFallbackView: FC<RenderProps> = ({ value, onChange }) => {
62
+ const context = useTreeContext();
63
+ const def = resolve('default', context, false) as FC<RenderProps> | null;
64
+ if (!def) return null;
65
+ const el = createElement(def, { value, onChange });
66
+ if ('$path' in value) return <NodeProvider value={value as NodeData}>{el}</NodeProvider>;
67
+ return el;
68
+ };
69
+
70
+ // ── <Render> — component/node-level rendering ──
71
+
72
+ export function Render({ value, onChange }: RenderProps) {
73
+ const context = useTreeContext();
74
+ const type = value.$type;
75
+
76
+ // Tree actual handler in state so React Compiler can't optimize away the update
77
+ // (a dummy tick counter gets eliminated because its value is never read in render output)
78
+ const [Handler, setHandler] = useState<ReactHandler | null>(
79
+ () => resolveExact(type, context) as ReactHandler | null,
80
+ );
81
+
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
+ useEffect(() => {
85
+ const found = resolveExact(type, context) as ReactHandler | null;
86
+ if (found) { setHandler(() => found); return; }
87
+ setHandler(null); // Clear stale handler when type/context changes
88
+ if (hasMissResolver(context)) resolve(type, context);
89
+ const unsub = subscribeRegistry(() => {
90
+ const h = resolveExact(type, context) as ReactHandler | null;
91
+ if (h) setHandler(() => h);
92
+ });
93
+ return unsub;
94
+ }, [type, context]);
95
+
96
+ // Fallback: if no exact handler, try default/parent context resolution
97
+ let Final = Handler;
98
+ if (!Final) {
99
+ if (hasMissResolver(context)) {
100
+ resolve(type, context);
101
+ return null;
102
+ }
103
+ Final = resolve(type, context, false) as ReactHandler | null;
104
+ }
105
+
106
+ if (!Final) return null;
107
+ const el = createElement(Final, { value, onChange });
108
+ if ('$path' in value) return <NodeProvider value={value as NodeData}>{el}</NodeProvider>;
109
+ return el;
110
+ }
111
+
112
+ // ── <RenderField> — field-level rendering by type name ──
113
+ // Bridge between block renderers (primitive values) and form-field handlers ({ $type, value }).
114
+ // Wraps primitive → component shape on the way in, extracts .value on the way out.
115
+
116
+ export function RenderField({ type = 'string', value, onChange, ...rest }: { type?: string; value?: unknown; onChange?: (v: unknown) => void; [k: string]: unknown }) {
117
+ const ctx = useTreeContext();
118
+ const handler = resolve(type, ctx);
119
+ if (!handler) return value != null ? <span>{String(value)}</span> : null;
120
+
121
+ // Already component-shaped? pass through. Otherwise wrap primitive.
122
+ const wrapped = value && typeof value === 'object' && '$type' in (value as object)
123
+ ? value
124
+ : { $type: type, value, ...rest };
125
+
126
+ const handleChange = onChange
127
+ ? (next: unknown) => onChange(next && typeof next === 'object' && 'value' in (next as object) ? (next as any).value : next)
128
+ : undefined;
129
+
130
+ return createElement(handler as FC<Record<string, unknown>>, { value: wrapped, onChange: handleChange });
131
+ }
package/src/context.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './context/index.tsx';
package/src/hooks.ts ADDED
@@ -0,0 +1,208 @@
1
+ // Treenity Hooks — reactive node access
2
+ // usePath: universal reactive read (URI or typed proxy)
3
+ // useChildren: reactive children list
4
+ // set: persist node (optimistic + server)
5
+ // execute: action caller
6
+ // watch: universal async generator
7
+
8
+ import { type Class, getComp, type TypeProxy } from '@treenity/core/comp';
9
+ import { getComponent, type NodeData, normalizeType } from '@treenity/core/core';
10
+ import { deriveURI, parseURI } from '@treenity/core/uri';
11
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
12
+ import * as cache from './cache';
13
+ import { tree } from './client';
14
+ import { trpc } from './trpc';
15
+
16
+ // ── Navigation context — shell provides, views consume ──
17
+
18
+ export type NavigateFn = (path: string) => void;
19
+ const NavigateCtx = createContext<NavigateFn | null>(null);
20
+ export const NavigateProvider = NavigateCtx.Provider;
21
+
22
+ export function useNavigate(): NavigateFn {
23
+ const nav = useContext(NavigateCtx);
24
+ if (!nav) throw new Error('useNavigate: no NavigateProvider');
25
+ return nav;
26
+ }
27
+
28
+ // ── usePath: universal reactive hook ──
29
+ // URI mode: usePath('/path#comp.field') → derived value
30
+ // Typed mode: usePath('/path', MyClass) → TypeProxy<T>
31
+ // Options: usePath('/path', { once: true }) → no server watch
32
+
33
+ type PathOpts = { once?: boolean };
34
+
35
+ export function usePath<T = NodeData>(uri: string | null, opts?: PathOpts): T | undefined;
36
+ export function usePath<T extends object>(path: string, cls: Class<T>, key?: string): TypeProxy<T>;
37
+ export function usePath<T extends object>(
38
+ pathOrUri: string | null,
39
+ clsOrOpts?: Class<T> | PathOpts,
40
+ key?: string,
41
+ ) {
42
+ const isTyped = typeof clsOrOpts === 'function';
43
+ const cls = isTyped ? clsOrOpts as Class<T> : undefined;
44
+ const opts = isTyped ? undefined : clsOrOpts as PathOpts | undefined;
45
+
46
+ const parsed = useMemo(
47
+ () => pathOrUri && !isTyped ? parseURI(pathOrUri) : null,
48
+ [pathOrUri, isTyped],
49
+ );
50
+ const path = isTyped ? pathOrUri : (parsed?.path ?? null);
51
+
52
+ const node = useSyncExternalStore(
53
+ useCallback((cb: () => void) => (path ? cache.subscribePath(path, cb) : () => {}), [path]),
54
+ useCallback(() => (path ? cache.get(path) : undefined), [path]),
55
+ );
56
+
57
+ useEffect(() => {
58
+ if (!path) return;
59
+ trpc.get.query({ path, watch: !opts?.once }).then((n: unknown) => {
60
+ if (n) cache.put(n as NodeData);
61
+ });
62
+ }, [path, opts?.once]);
63
+
64
+ return useMemo(() => {
65
+ if (cls && path) return makeProxy(path, cls, node, key);
66
+ return parsed ? deriveURI<T>(node, parsed) : node;
67
+ }, [node, cls, key, path, parsed?.key, parsed?.field]);
68
+ }
69
+
70
+ // ── useChildren: reactive children list ──
71
+
72
+ type WatchOpts = { watch?: boolean; watchNew?: boolean; limit?: number };
73
+
74
+ export function useChildren(parentPath: string, opts?: WatchOpts) {
75
+ const loaded = useRef<string | null>(null);
76
+ const gen = useSyncExternalStore(cache.subscribeSSEGen, cache.getSSEGen);
77
+ const prevGen = useRef(gen);
78
+
79
+ useEffect(() => {
80
+ const reconnected = prevGen.current !== gen;
81
+ if (loaded.current === parentPath && !reconnected) return;
82
+ loaded.current = parentPath;
83
+ prevGen.current = gen;
84
+
85
+ if (!cache.has(parentPath) && parentPath !== '/') {
86
+ trpc.get.query({ path: parentPath }).then(n => {
87
+ if (n) cache.put(n as NodeData);
88
+ });
89
+ }
90
+
91
+ trpc.getChildren
92
+ .query({ path: parentPath, limit: opts?.limit, watch: opts?.watch, watchNew: opts?.watchNew })
93
+ .then((result: any) => cache.putMany(result.items as NodeData[], parentPath));
94
+ }, [parentPath, gen, opts?.limit, opts?.watch, opts?.watchNew]);
95
+
96
+ return useSyncExternalStore(
97
+ useCallback((cb: () => void) => cache.subscribeChildren(parentPath, cb), [parentPath]),
98
+ useCallback(() => cache.getChildren(parentPath), [parentPath]),
99
+ );
100
+ }
101
+
102
+ // ── set: optimistic update + server persist ──
103
+
104
+ export async function set(next: NodeData) {
105
+ cache.put(next);
106
+ await tree.set(next);
107
+ const fresh = await tree.get(next.$path);
108
+ if (fresh) cache.put(fresh);
109
+ }
110
+
111
+ // ── execute: action caller ──
112
+
113
+ export const execute = (
114
+ path: string, action: string, data?: unknown, type?: string, key?: string,
115
+ ) => trpc.execute.mutate({ path, type, key, action, data });
116
+
117
+ // ── Internals ──
118
+
119
+ const AsyncGenFn = Object.getPrototypeOf(async function* () {}).constructor;
120
+
121
+ function streamToAsyncIterable<T>(
122
+ input: { path: string; type?: string; key?: string; action: string; data?: unknown },
123
+ ): AsyncIterable<T> {
124
+ return {
125
+ [Symbol.asyncIterator](): AsyncIterator<T> {
126
+ const queue: T[] = [];
127
+ let notify: (() => void) | null = null;
128
+ let done = false;
129
+ let error: unknown = null;
130
+
131
+ const sub = trpc.streamAction.subscribe(input, {
132
+ onData(item) { queue.push(item as T); notify?.(); notify = null; },
133
+ onComplete() { done = true; notify?.(); notify = null; },
134
+ onError(err) { error = err; done = true; notify?.(); notify = null; },
135
+ });
136
+
137
+ return {
138
+ async next(): Promise<IteratorResult<T>> {
139
+ while (!queue.length && !done)
140
+ await new Promise<void>(r => { notify = r; });
141
+ if (error) throw error;
142
+ if (queue.length) return { value: queue.shift()!, done: false };
143
+ return { value: undefined as any, done: true };
144
+ },
145
+ async return(): Promise<IteratorResult<T>> {
146
+ sub.unsubscribe();
147
+ done = true; notify?.(); notify = null;
148
+ return { value: undefined as any, done: true };
149
+ },
150
+ };
151
+ },
152
+ };
153
+ }
154
+
155
+ function makeProxy<T extends object>(
156
+ path: string, cls: Class<T>, node: NodeData | undefined, key?: string,
157
+ ): TypeProxy<T> {
158
+ const type = normalizeType(cls);
159
+ const comp = node
160
+ ? (key ? getComponent(node, key) : getComp(node, cls))
161
+ : undefined;
162
+
163
+ return new Proxy(comp ?? {}, {
164
+ get: (_target, prop: string) => {
165
+ const fn = (cls.prototype as any)[prop];
166
+ if (typeof fn === 'function') {
167
+ if (fn instanceof AsyncGenFn)
168
+ return (data?: unknown) => streamToAsyncIterable({ path, type, key, action: prop, data });
169
+ return (data?: unknown) => trpc.execute.mutate({ path, type, key, action: prop, data });
170
+ }
171
+ return (comp as any)?.[prop];
172
+ },
173
+ }) as TypeProxy<T>;
174
+ }
175
+
176
+ // ── watch: universal async generator ──
177
+
178
+ export async function* watch<T = unknown>(uri: string): AsyncGenerator<T> {
179
+ const parsed = parseURI(uri);
180
+
181
+ if (parsed.action) {
182
+ yield* streamToAsyncIterable<T>({
183
+ path: parsed.path,
184
+ key: parsed.key,
185
+ action: parsed.action,
186
+ data: parsed.data,
187
+ });
188
+ return;
189
+ }
190
+
191
+ const { path } = parsed;
192
+ const initial = await trpc.get.query({ path, watch: true });
193
+ if (initial) cache.put(initial as NodeData);
194
+
195
+ let resolve: (() => void) | null = null;
196
+ const unsub = cache.subscribePath(path, () => { resolve?.(); resolve = null; });
197
+
198
+ try {
199
+ yield deriveURI<T>(cache.get(path), parsed) as T;
200
+
201
+ while (true) {
202
+ await new Promise<void>(r => { resolve = r; });
203
+ yield deriveURI<T>(cache.get(path), parsed) as T;
204
+ }
205
+ } finally {
206
+ unsub();
207
+ }
208
+ }
package/src/idb.ts ADDED
@@ -0,0 +1,80 @@
1
+ // Treenity IDB — thin IndexedDB wrapper for client node cache
2
+ // Raw IDB API, no dependencies. Fire-and-forget friendly.
3
+ // Degrades silently if IDB unavailable (private browsing, SSR).
4
+
5
+ import type { NodeData } from '@treenity/core/core';
6
+
7
+ const DB_NAME = 'treenity';
8
+ const DB_VERSION = 1;
9
+ const STORE = 'nodes';
10
+
11
+ export type IDBEntry = {
12
+ path: string;
13
+ data: NodeData;
14
+ lastUpdated: number;
15
+ virtualParent?: string; // only when differs from parentOf(path)
16
+ };
17
+
18
+ let dbPromise: Promise<IDBDatabase> | null = null;
19
+
20
+ function getDb(): Promise<IDBDatabase> {
21
+ if (!dbPromise) dbPromise = new Promise((resolve, reject) => {
22
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
23
+ req.onupgradeneeded = () => req.result.createObjectStore(STORE, { keyPath: 'path' });
24
+ req.onsuccess = () => resolve(req.result);
25
+ req.onerror = () => reject(req.error);
26
+ });
27
+ return dbPromise;
28
+ }
29
+
30
+ // Load all entries sorted by lastUpdated desc — most recently viewed first
31
+ export async function loadAll(): Promise<IDBEntry[]> {
32
+ const d = await getDb();
33
+ return new Promise((resolve, reject) => {
34
+ const req = d.transaction(STORE, 'readonly').objectStore(STORE).getAll();
35
+ req.onsuccess = () => {
36
+ (req.result as IDBEntry[]).sort((a, b) => b.lastUpdated - a.lastUpdated);
37
+ resolve(req.result as IDBEntry[]);
38
+ };
39
+ req.onerror = () => reject(req.error);
40
+ });
41
+ }
42
+
43
+ export async function save(entry: IDBEntry): Promise<void> {
44
+ const d = await getDb();
45
+ return new Promise((resolve, reject) => {
46
+ const req = d.transaction(STORE, 'readwrite').objectStore(STORE).put(entry);
47
+ req.onsuccess = () => resolve();
48
+ req.onerror = () => reject(req.error);
49
+ });
50
+ }
51
+
52
+ export async function saveMany(entries: IDBEntry[]): Promise<void> {
53
+ if (!entries.length) return;
54
+ const d = await getDb();
55
+ return new Promise((resolve, reject) => {
56
+ const tx = d.transaction(STORE, 'readwrite');
57
+ const store = tx.objectStore(STORE);
58
+ for (const e of entries) store.put(e);
59
+ tx.oncomplete = () => resolve();
60
+ tx.onerror = () => reject(tx.error);
61
+ });
62
+ }
63
+
64
+ export async function del(path: string): Promise<void> {
65
+ const d = await getDb();
66
+ return new Promise((resolve, reject) => {
67
+ const req = d.transaction(STORE, 'readwrite').objectStore(STORE).delete(path);
68
+ req.onsuccess = () => resolve();
69
+ req.onerror = () => reject(req.error);
70
+ });
71
+ }
72
+
73
+ export async function clearAll(): Promise<void> {
74
+ const d = await getDb();
75
+ return new Promise((resolve, reject) => {
76
+ const req = d.transaction(STORE, 'readwrite').objectStore(STORE).clear();
77
+ req.onsuccess = () => resolve();
78
+ req.onerror = () => reject(req.error);
79
+ });
80
+ }
package/src/index.html ADDED
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Treenity</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ <script type="module" src="main.tsx"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,28 @@
1
+ /* minimd — lightweight markdown rendering */
2
+ .minimd *:first-child { margin-top: 0; }
3
+ .minimd h1, .minimd h2, .minimd h3 { font-weight: 600; margin: 14px 0 6px; color: var(--text); }
4
+ .minimd h1 { font-size: 1.3em; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
5
+ .minimd h2 { font-size: 1.15em; }
6
+ .minimd h3 { font-size: 1.05em; color: var(--text-2); }
7
+ .minimd p { margin: 4px 0; }
8
+ .minimd ul, .minimd ol { padding-left: 20px; margin: 4px 0; }
9
+ .minimd li { margin: 2px 0; }
10
+ .minimd li::marker { color: var(--text-3); }
11
+ .minimd code {
12
+ font-family: var(--mono); font-size: 0.88em;
13
+ background: var(--surface-3); padding: 1px 5px; border-radius: 4px; color: #f0abfc;
14
+ }
15
+ .minimd pre {
16
+ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
17
+ padding: 10px 12px; margin: 8px 0; overflow-x: auto; font-size: 12px; line-height: 1.5;
18
+ }
19
+ .minimd pre code { background: none; padding: 0; color: var(--text); font-size: inherit; }
20
+ .minimd strong { font-weight: 600; }
21
+ .minimd em { color: var(--text-2); }
22
+ .minimd a { color: #58a6ff; text-decoration: none; }
23
+ .minimd a:hover { text-decoration: underline; }
24
+ .minimd blockquote { border-left: 3px solid var(--border); padding-left: 12px; color: var(--text-2); margin: 6px 0; }
25
+ .minimd hr { border: none; border-top: 1px solid var(--border); margin: 10px 0; }
26
+ .minimd table { border-collapse: collapse; margin: 8px 0; font-size: 12px; width: 100%; }
27
+ .minimd th, .minimd td { border: 1px solid var(--border); padding: 6px 12px; }
28
+ .minimd th { background: var(--surface-3); font-weight: 600; color: var(--text); text-align: left; }