@treenity/react 3.0.6 → 3.0.7

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 (86) hide show
  1. package/dist/AclEditor.js +3 -3
  2. package/dist/ActionCards.js +5 -5
  3. package/dist/App.js +6 -6
  4. package/dist/CLAUDE.md +16 -0
  5. package/dist/ComponentSection.js +4 -4
  6. package/dist/ComponentSection.js.map +1 -1
  7. package/dist/ErrorBoundary.js +1 -1
  8. package/dist/Inspector.js +9 -9
  9. package/dist/Inspector.js.map +1 -1
  10. package/dist/Login.js +4 -4
  11. package/dist/NodeEditor.d.ts.map +1 -1
  12. package/dist/NodeEditor.js +10 -9
  13. package/dist/NodeEditor.js.map +1 -1
  14. package/dist/Tree.js +3 -3
  15. package/dist/ViewPage.js +1 -1
  16. package/dist/bind/engine.js +2 -2
  17. package/dist/bind/hook.js +2 -2
  18. package/dist/components/ConfirmDialog.js +1 -1
  19. package/dist/components/ConfirmPopover.js +2 -2
  20. package/dist/components/PathBreadcrumb.js +1 -1
  21. package/dist/components/lib/utils.ts.bak +6 -0
  22. package/dist/components/ui/accordion.js +1 -1
  23. package/dist/components/ui/alert-dialog.js +2 -2
  24. package/dist/components/ui/badge.js +1 -1
  25. package/dist/components/ui/breadcrumb.js +1 -1
  26. package/dist/components/ui/button.js +1 -1
  27. package/dist/components/ui/card.js +1 -1
  28. package/dist/components/ui/checkbox.js +1 -1
  29. package/dist/components/ui/command.js +2 -2
  30. package/dist/components/ui/dialog.js +2 -2
  31. package/dist/components/ui/drawer.js +1 -1
  32. package/dist/components/ui/dropdown-menu.js +1 -1
  33. package/dist/components/ui/form-field.js +1 -1
  34. package/dist/components/ui/input.js +1 -1
  35. package/dist/components/ui/label.js +1 -1
  36. package/dist/components/ui/pagination.js +2 -2
  37. package/dist/components/ui/popover.js +1 -1
  38. package/dist/components/ui/progress.js +1 -1
  39. package/dist/components/ui/resizable.js +1 -1
  40. package/dist/components/ui/scroll-area.js +1 -1
  41. package/dist/components/ui/select.js +1 -1
  42. package/dist/components/ui/separator.js +1 -1
  43. package/dist/components/ui/sheet.js +1 -1
  44. package/dist/components/ui/skeleton.js +1 -1
  45. package/dist/components/ui/slider.js +1 -1
  46. package/dist/components/ui/switch.js +1 -1
  47. package/dist/components/ui/table.js +1 -1
  48. package/dist/components/ui/tabs.js +1 -1
  49. package/dist/components/ui/textarea.js +1 -1
  50. package/dist/components/ui/toggle-group.js +2 -2
  51. package/dist/components/ui/toggle.js +1 -1
  52. package/dist/components/ui/tooltip.js +1 -1
  53. package/dist/context/index.js +2 -2
  54. package/dist/mods/editor-ui/CLAUDE.md +3 -0
  55. package/dist/mods/editor-ui/DraftTextarea.d.ts +8 -0
  56. package/dist/mods/editor-ui/DraftTextarea.d.ts.map +1 -0
  57. package/dist/mods/editor-ui/DraftTextarea.js +23 -0
  58. package/dist/mods/editor-ui/DraftTextarea.js.map +1 -0
  59. package/dist/mods/editor-ui/FieldLabel.js +2 -2
  60. package/dist/mods/editor-ui/default-edit.js +3 -3
  61. package/dist/mods/editor-ui/default-view.js +4 -4
  62. package/dist/mods/editor-ui/dir-view.js +2 -2
  63. package/dist/mods/editor-ui/empty-placeholder.js +2 -2
  64. package/dist/mods/editor-ui/form-field.js +5 -5
  65. package/dist/mods/editor-ui/form-field.js.map +1 -1
  66. package/dist/mods/editor-ui/form-fields.d.ts.map +1 -1
  67. package/dist/mods/editor-ui/form-fields.js +17 -16
  68. package/dist/mods/editor-ui/form-fields.js.map +1 -1
  69. package/dist/mods/editor-ui/layout-view.js +2 -2
  70. package/dist/mods/editor-ui/list-items.js +1 -1
  71. package/dist/mods/editor-ui/node-utils.js +1 -1
  72. package/dist/mods/editor-ui/node-utils.js.map +1 -1
  73. package/dist/mods/editor-ui/type-picker.js +5 -5
  74. package/dist/mods/treenity/CLAUDE.md +7 -0
  75. package/dist/mods/treenity/groups/index.js +3 -3
  76. package/dist/mods/treenity/preview.js +2 -2
  77. package/dist/mods/treenity/ref-view.js +3 -3
  78. package/package.json +2 -2
  79. package/src/ComponentSection.tsx +1 -1
  80. package/src/Inspector.tsx +2 -2
  81. package/src/NodeEditor.tsx +3 -2
  82. package/src/mods/editor-ui/DraftTextarea.tsx +42 -0
  83. package/src/mods/editor-ui/form-field.tsx +4 -4
  84. package/src/mods/editor-ui/form-fields.tsx +11 -10
  85. package/src/mods/editor-ui/node-utils.ts +1 -1
  86. package/vite-plugin-treenity.ts +7 -1
package/src/Inspector.tsx CHANGED
@@ -28,7 +28,7 @@ export function Inspector({ path, currentUserId, onDelete, onAddComponent, onSel
28
28
  const node = usePath(path);
29
29
  const [confirmDelete, setConfirmDelete] = useState(false);
30
30
  const [editing, setEditing] = useState(false);
31
- const [context, setContext] = useState('react');
31
+ const [context, setContext] = useState('react:layout');
32
32
 
33
33
  // Reset context when path changes
34
34
  const [prevPath, setPrevPath] = useState(path);
@@ -113,7 +113,7 @@ export function Inspector({ path, currentUserId, onDelete, onAddComponent, onSel
113
113
  {/* Rendered view */}
114
114
  <ScrollArea className="flex-1">
115
115
  <div className="p-4">
116
- <ErrorBoundary>
116
+ <ErrorBoundary key={node.$path}>
117
117
  <RenderContext name={context}>
118
118
  <div className="node-view">
119
119
  <Render value={node} />
@@ -7,6 +7,7 @@ import { Input } from '#components/ui/input';
7
7
  import { ScrollArea } from '#components/ui/scroll-area';
8
8
  import { Tabs, TabsList, TabsTrigger } from '#components/ui/tabs';
9
9
  import { toPlain } from '#lib/to-plain';
10
+ import { DraftTextarea } from '#mods/editor-ui/DraftTextarea';
10
11
  import { FieldLabel, RefEditor } from '#mods/editor-ui/FieldLabel';
11
12
  import { getComponents, getPlainFields, getSchema } from '#mods/editor-ui/node-utils';
12
13
  import { type ComponentData, type GroupPerm, isRef, type NodeData, resolve } from '@treenity/core';
@@ -272,9 +273,9 @@ export function NodeEditor({ node, open, onClose, currentUserId, toast, onAddCom
272
273
  )}
273
274
  </>
274
275
  ) : (
275
- <textarea
276
+ <DraftTextarea
276
277
  value={snap.jsonText}
277
- onChange={(e) => { st.jsonText = e.target.value; st.dirty = true; }}
278
+ onChange={(text) => { st.jsonText = text; st.dirty = true; }}
278
279
  spellCheck={false}
279
280
  />
280
281
  )}
@@ -0,0 +1,42 @@
1
+ // Uncontrolled textarea that preserves cursor position during external re-renders.
2
+ // Uses ref + defaultValue so React never touches the DOM value while user is editing.
3
+
4
+ import { Textarea } from '#components/ui/textarea';
5
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
6
+
7
+ type Props = Omit<React.ComponentProps<typeof Textarea>, 'value' | 'defaultValue' | 'onChange'> & {
8
+ value: string;
9
+ onChange: (text: string) => void;
10
+ };
11
+
12
+ export const DraftTextarea = forwardRef<HTMLTextAreaElement, Props>(
13
+ ({ value, onChange, ...props }, fwd) => {
14
+ const ref = useRef<HTMLTextAreaElement>(null);
15
+ const editing = useRef(false);
16
+
17
+ useImperativeHandle(fwd, () => ref.current!);
18
+
19
+ // Sync external value into DOM only when not focused
20
+ useEffect(() => {
21
+ if (!editing.current && ref.current && ref.current.value !== value) {
22
+ ref.current.value = value;
23
+ }
24
+ }, [value]);
25
+
26
+ return (
27
+ <Textarea
28
+ ref={ref}
29
+ defaultValue={value}
30
+ onFocus={() => { editing.current = true; }}
31
+ onBlur={() => {
32
+ editing.current = false;
33
+ if (ref.current && ref.current.value !== value) {
34
+ ref.current.value = value;
35
+ }
36
+ }}
37
+ onChange={(e) => onChange(e.target.value)}
38
+ {...props}
39
+ />
40
+ );
41
+ },
42
+ );
@@ -1,6 +1,6 @@
1
1
  import { Button } from '#components/ui/button';
2
2
  import { Input } from '#components/ui/input';
3
- import { Textarea } from '#components/ui/textarea';
3
+ import { DraftTextarea } from '#mods/editor-ui/DraftTextarea';
4
4
  import { isRef, resolveExact } from '@treenity/core';
5
5
  import { createElement, useState } from 'react';
6
6
  import { FieldLabel, RefEditor } from './FieldLabel';
@@ -92,12 +92,12 @@ export function StringArrayField({
92
92
 
93
93
  if (!isStrings) {
94
94
  return (
95
- <Textarea
95
+ <DraftTextarea
96
96
  className="min-h-16 text-xs font-mono"
97
97
  value={JSON.stringify(value, null, 2)}
98
- onChange={(e) => {
98
+ onChange={(text) => {
99
99
  try {
100
- onChange(JSON.parse(e.target.value));
100
+ onChange(JSON.parse(text));
101
101
  } catch {
102
102
  /* typing */
103
103
  }
@@ -5,6 +5,7 @@ import { tree as clientStore } from '#client';
5
5
  // Covers: string, text, textarea, number, integer, boolean, array, object, image, uri, url, select, timestamp, path
6
6
  import { Button } from '#components/ui/button';
7
7
  import { Input } from '#components/ui/input';
8
+ import { DraftTextarea } from '#mods/editor-ui/DraftTextarea';
8
9
  import { Popover, PopoverContent, PopoverTrigger } from '#components/ui/popover';
9
10
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '#components/ui/select';
10
11
  import { Switch } from '#components/ui/switch';
@@ -281,13 +282,13 @@ function ObjectForm({ value, onChange }: FP) {
281
282
  return (
282
283
  <div className="rounded border border-border/50 bg-muted/30 p-2">
283
284
  {modeToggle}
284
- <Textarea
285
+ <DraftTextarea
285
286
  className={`text-[11px] min-h-[60px] ${jsonError ? 'border-destructive' : ''}`}
286
287
  value={jsonDraft}
287
- onChange={(e) => {
288
- setJsonDraft(e.target.value);
288
+ onChange={(text) => {
289
+ setJsonDraft(text);
289
290
  try {
290
- const parsed = JSON.parse(e.target.value);
291
+ const parsed = JSON.parse(text);
291
292
  if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
292
293
  emit(parsed);
293
294
  setJsonError(false);
@@ -334,12 +335,12 @@ function ObjectForm({ value, onChange }: FP) {
334
335
  onChange={(e) => emit({ ...obj, [k]: e.target.value })}
335
336
  />
336
337
  ) : (
337
- <Textarea
338
+ <DraftTextarea
338
339
  className="flex-1 min-w-0 text-[11px] font-mono min-h-[40px]"
339
340
  value={JSON.stringify(v, null, 2)}
340
- onChange={(e) => {
341
+ onChange={(text) => {
341
342
  try {
342
- emit({ ...obj, [k]: JSON.parse(e.target.value) });
343
+ emit({ ...obj, [k]: JSON.parse(text) });
343
344
  } catch {
344
345
  /* typing */
345
346
  }
@@ -484,11 +485,11 @@ function ArrayForm({ value, onChange }: FP) {
484
485
 
485
486
  // object/other — textarea fallback
486
487
  return (
487
- <Textarea
488
+ <DraftTextarea
488
489
  value={JSON.stringify(arr, null, 2)}
489
- onChange={(e) => {
490
+ onChange={(text) => {
490
491
  try {
491
- emit(JSON.parse(e.target.value));
492
+ emit(JSON.parse(text));
492
493
  } catch {
493
494
  /* let user keep typing */
494
495
  }
@@ -79,5 +79,5 @@ export function getActionSchema(type: string, action: string): TypeSchema | null
79
79
  }
80
80
 
81
81
  export function pickDefaultContext(_type: string): string {
82
- return 'react';
82
+ return 'react:layout';
83
83
  }
@@ -189,7 +189,13 @@ export default function treenityPlugin(opts?: { modsDirs?: string[] }): Plugin {
189
189
  // Resolve # imports via nearest package.json imports field
190
190
  if (id.startsWith('#')) {
191
191
  const pkg = readPkg(dirname(importer));
192
- if (pkg?.imports) return matchPattern(id, pkg.imports, pkg.dir, conditions);
192
+ if (pkg?.imports) {
193
+ // Inside node_modules: skip 'development' condition to stay in dist/
194
+ // (dist files use relative ./hooks, # must resolve to same dist files)
195
+ const isNm = importer.includes('/node_modules/');
196
+ const conds = isNm ? conditions.filter(c => c !== 'development') : conditions;
197
+ return matchPattern(id, pkg.imports, pkg.dir, conds);
198
+ }
193
199
  }
194
200
 
195
201
  // Resolve @treenity/* exports (Vite doesn't handle array conditions)