@almadar/ui 5.27.0 → 5.28.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.
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import type { TraitConfigValue } from '@almadar/core';
3
+ export interface JsonTreeEditorProps {
4
+ /** Current value (object / array / scalar). */
5
+ value: TraitConfigValue;
6
+ /** Fired with the next value on any edit. */
7
+ onChange: (next: TraitConfigValue) => void;
8
+ /** Additional CSS classes. */
9
+ className?: string;
10
+ }
11
+ /**
12
+ * JsonTreeEditor — a visual, collapsible tree editor for any `TraitConfigValue`
13
+ * (object / array / nested / scalar). Replaces raw `JSON.stringify` text editing:
14
+ * each node renders a typed inline control, containers collapse, and rows can be
15
+ * added, removed, retyped, and (for objects) re-keyed. Self-contained — styled
16
+ * purely with `@almadar/ui` atoms + design tokens.
17
+ */
18
+ export declare const JsonTreeEditor: React.FC<JsonTreeEditorProps>;
@@ -2,6 +2,7 @@ export { ErrorBoundary, type ErrorBoundaryProps } from './ErrorBoundary';
2
2
  export { ArrayEditor, type ArrayEditorProps } from './ArrayEditor';
3
3
  export { ObjectEditor, type ObjectEditorProps } from './ObjectEditor';
4
4
  export { MapEditor, type MapEditorProps } from './MapEditor';
5
+ export { JsonTreeEditor, type JsonTreeEditorProps } from './JsonTreeEditor';
5
6
  export { FileTree, type FileTreeProps, type FileTreeNode } from './FileTree';
6
7
  export { FormField, type FormFieldProps } from './FormField';
7
8
  export { EmptyState, type EmptyStateProps } from './EmptyState';
@@ -22640,6 +22640,274 @@ var init_MapEditor = __esm({
22640
22640
  };
22641
22641
  }
22642
22642
  });
22643
+ function kindOf(v) {
22644
+ if (v === null) return "null";
22645
+ if (isArr(v)) return "array";
22646
+ if (isObj(v)) return "object";
22647
+ if (typeof v === "number") return "number";
22648
+ if (typeof v === "boolean") return "boolean";
22649
+ return "string";
22650
+ }
22651
+ function emptyOf(kind) {
22652
+ switch (kind) {
22653
+ case "object":
22654
+ return {};
22655
+ case "array":
22656
+ return [];
22657
+ case "number":
22658
+ return 0;
22659
+ case "boolean":
22660
+ return false;
22661
+ case "null":
22662
+ return null;
22663
+ default:
22664
+ return "";
22665
+ }
22666
+ }
22667
+ function templateFrom(arr) {
22668
+ if (arr.length === 0) return "";
22669
+ const first = arr[0];
22670
+ if (isObj(first)) {
22671
+ const blank = {};
22672
+ for (const k of Object.keys(first)) blank[k] = isObj(first[k]) ? {} : isArr(first[k]) ? [] : first[k];
22673
+ return blank;
22674
+ }
22675
+ if (isArr(first)) return [];
22676
+ return emptyOf(kindOf(first));
22677
+ }
22678
+ function ScalarControl2({
22679
+ value,
22680
+ onChange
22681
+ }) {
22682
+ if (typeof value === "boolean") {
22683
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Switch, { checked: value, onChange: (c) => onChange(c) });
22684
+ }
22685
+ const numeric = typeof value === "number";
22686
+ const initial = value === null ? "" : String(value);
22687
+ const [draft, setDraft] = React74__namespace.default.useState(initial);
22688
+ React74__namespace.default.useEffect(() => setDraft(initial), [initial]);
22689
+ const commit = () => {
22690
+ if (numeric) {
22691
+ const n = draft.trim() === "" ? 0 : Number(draft);
22692
+ onChange(Number.isNaN(n) ? 0 : n);
22693
+ } else {
22694
+ onChange(draft);
22695
+ }
22696
+ };
22697
+ return /* @__PURE__ */ jsxRuntime.jsx(
22698
+ exports.Input,
22699
+ {
22700
+ inputType: numeric ? "number" : "text",
22701
+ value: draft,
22702
+ placeholder: value === null ? "null" : void 0,
22703
+ onChange: (e) => setDraft(e.target.value),
22704
+ onBlur: commit,
22705
+ onKeyDown: (e) => {
22706
+ if (e.key === "Enter") commit();
22707
+ }
22708
+ }
22709
+ );
22710
+ }
22711
+ function KindSelect({
22712
+ kind,
22713
+ onChange
22714
+ }) {
22715
+ return /* @__PURE__ */ jsxRuntime.jsx(
22716
+ "select",
22717
+ {
22718
+ value: kind === "null" ? "string" : kind,
22719
+ onChange: (e) => onChange(e.target.value),
22720
+ "aria-label": "Value type",
22721
+ className: cn(
22722
+ "h-6 rounded-sm bg-muted text-muted-foreground text-[10px] font-mono px-1",
22723
+ "border-[length:var(--border-width-thin)] border-border",
22724
+ "hover:bg-card focus:outline-none focus:ring-1 focus:ring-ring"
22725
+ ),
22726
+ children: KIND_OPTIONS.map((k) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: k, children: TYPE_LABEL[k] }, k))
22727
+ }
22728
+ );
22729
+ }
22730
+ function ValueNode({
22731
+ value,
22732
+ onChange,
22733
+ depth
22734
+ }) {
22735
+ const kind = kindOf(value);
22736
+ if (kind === "object" || kind === "array") {
22737
+ return /* @__PURE__ */ jsxRuntime.jsx(ContainerNode, { value, onChange, depth });
22738
+ }
22739
+ return /* @__PURE__ */ jsxRuntime.jsx(ScalarControl2, { value, onChange });
22740
+ }
22741
+ function Row({
22742
+ rowKey,
22743
+ isArrayItem,
22744
+ value,
22745
+ depth,
22746
+ onValue,
22747
+ onRenameKey,
22748
+ onChangeKind,
22749
+ onRemove
22750
+ }) {
22751
+ const [keyDraft, setKeyDraft] = React74__namespace.default.useState(rowKey);
22752
+ React74__namespace.default.useEffect(() => setKeyDraft(rowKey), [rowKey]);
22753
+ const container = isObj(value) || isArr(value);
22754
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "none", className: "group", children: [
22755
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", align: "center", className: "py-0.5", children: [
22756
+ /* @__PURE__ */ jsxRuntime.jsx(KindSelect, { kind: kindOf(value), onChange: onChangeKind }),
22757
+ isArrayItem ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "w-7 shrink-0 font-mono", children: rowKey }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-28 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
22758
+ exports.Input,
22759
+ {
22760
+ inputType: "text",
22761
+ value: keyDraft,
22762
+ onChange: (e) => setKeyDraft(e.target.value),
22763
+ onBlur: () => keyDraft !== rowKey && onRenameKey(keyDraft),
22764
+ onKeyDown: (e) => {
22765
+ if (e.key === "Enter" && keyDraft !== rowKey) onRenameKey(keyDraft);
22766
+ },
22767
+ className: "font-mono"
22768
+ }
22769
+ ) }),
22770
+ !container && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(ValueNode, { value, onChange: onValue, depth: depth + 1 }) }),
22771
+ container && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
22772
+ /* @__PURE__ */ jsxRuntime.jsx(
22773
+ exports.Button,
22774
+ {
22775
+ variant: "ghost",
22776
+ size: "sm",
22777
+ icon: "x",
22778
+ onClick: onRemove,
22779
+ "aria-label": "Remove",
22780
+ className: "opacity-0 group-hover:opacity-100 transition-opacity"
22781
+ }
22782
+ )
22783
+ ] }),
22784
+ container && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-3", children: /* @__PURE__ */ jsxRuntime.jsx(ValueNode, { value, onChange: onValue, depth: depth + 1 }) })
22785
+ ] });
22786
+ }
22787
+ function ContainerNode({
22788
+ value,
22789
+ onChange,
22790
+ depth
22791
+ }) {
22792
+ const [open, setOpen] = React74__namespace.default.useState(depth < 2);
22793
+ const array = isArr(value);
22794
+ const entries = array ? value.map((v, i) => [String(i), v]) : Object.entries(value);
22795
+ const setObjValue = (key, next) => {
22796
+ onChange({ ...value, [key]: next });
22797
+ };
22798
+ const renameKey = (oldKey, newKey) => {
22799
+ if (!newKey || newKey === oldKey) return;
22800
+ const out = {};
22801
+ for (const [k, v] of Object.entries(value)) out[k === oldKey ? newKey : k] = v;
22802
+ onChange(out);
22803
+ };
22804
+ const removeObjKey = (key) => {
22805
+ const out = {};
22806
+ for (const [k, v] of Object.entries(value)) if (k !== key) out[k] = v;
22807
+ onChange(out);
22808
+ };
22809
+ const changeObjKind = (key, k) => setObjValue(key, emptyOf(k));
22810
+ const setArrValue = (idx, next) => {
22811
+ const copy = [...value];
22812
+ copy[idx] = next;
22813
+ onChange(copy);
22814
+ };
22815
+ const removeArrIdx = (idx) => onChange(value.filter((_, i) => i !== idx));
22816
+ const changeArrKind = (idx, k) => setArrValue(idx, emptyOf(k));
22817
+ const add = () => {
22818
+ if (array) {
22819
+ onChange([...value, templateFrom(value)]);
22820
+ } else {
22821
+ const obj = value;
22822
+ let key = "key";
22823
+ let n = 1;
22824
+ while (key in obj) {
22825
+ key = `key${n}`;
22826
+ n += 1;
22827
+ }
22828
+ onChange({ ...obj, [key]: "" });
22829
+ }
22830
+ };
22831
+ const summary = array ? `${entries.length} item${entries.length === 1 ? "" : "s"}` : `${entries.length} field${entries.length === 1 ? "" : "s"}`;
22832
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "none", className: "w-full", children: [
22833
+ /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", align: "center", children: /* @__PURE__ */ jsxRuntime.jsxs(
22834
+ "button",
22835
+ {
22836
+ type: "button",
22837
+ onClick: () => setOpen((o) => !o),
22838
+ className: "flex items-center gap-1 text-muted-foreground hover:text-foreground",
22839
+ "aria-label": open ? "Collapse" : "Expand",
22840
+ children: [
22841
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: open ? "chevron-down" : "chevron-right", size: "sm" }),
22842
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-xs", children: array ? "[ ]" : "{ }" }),
22843
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: summary })
22844
+ ]
22845
+ }
22846
+ ) }),
22847
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
22848
+ exports.VStack,
22849
+ {
22850
+ gap: "none",
22851
+ className: cn("ml-2 pl-2 border-l-[length:var(--border-width-thin)] border-border"),
22852
+ children: [
22853
+ entries.map(([k, v], idx) => /* @__PURE__ */ jsxRuntime.jsx(
22854
+ Row,
22855
+ {
22856
+ rowKey: k,
22857
+ isArrayItem: array,
22858
+ value: v,
22859
+ depth,
22860
+ onValue: (next) => array ? setArrValue(idx, next) : setObjValue(k, next),
22861
+ onRenameKey: (nk) => renameKey(k, nk),
22862
+ onChangeKind: (kind) => array ? changeArrKind(idx, kind) : changeObjKind(k, kind),
22863
+ onRemove: () => array ? removeArrIdx(idx) : removeObjKey(k)
22864
+ },
22865
+ array ? idx : k
22866
+ )),
22867
+ /* @__PURE__ */ jsxRuntime.jsx(
22868
+ exports.Button,
22869
+ {
22870
+ variant: "ghost",
22871
+ size: "sm",
22872
+ icon: "plus",
22873
+ label: array ? "Add item" : "Add field",
22874
+ onClick: add,
22875
+ className: "self-start text-muted-foreground"
22876
+ }
22877
+ )
22878
+ ]
22879
+ }
22880
+ )
22881
+ ] });
22882
+ }
22883
+ var isObj, isArr, TYPE_LABEL, KIND_OPTIONS; exports.JsonTreeEditor = void 0;
22884
+ var init_JsonTreeEditor = __esm({
22885
+ "components/core/molecules/JsonTreeEditor.tsx"() {
22886
+ "use client";
22887
+ init_Stack();
22888
+ init_Button();
22889
+ init_Input();
22890
+ init_Switch();
22891
+ init_Typography();
22892
+ init_Icon();
22893
+ init_cn();
22894
+ isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
22895
+ isArr = (v) => Array.isArray(v);
22896
+ TYPE_LABEL = {
22897
+ object: "{}",
22898
+ array: "[]",
22899
+ string: "abc",
22900
+ number: "123",
22901
+ boolean: "on/off",
22902
+ null: "\u2014"
22903
+ };
22904
+ KIND_OPTIONS = ["string", "number", "boolean", "object", "array"];
22905
+ exports.JsonTreeEditor = ({ value, onChange, className }) => {
22906
+ const root = value ?? "";
22907
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("w-full rounded-sm bg-card/40 p-2 border-[length:var(--border-width-thin)] border-border", className), children: /* @__PURE__ */ jsxRuntime.jsx(ValueNode, { value: root, onChange, depth: 0 }) });
22908
+ };
22909
+ }
22910
+ });
22643
22911
  function fileIcon(name) {
22644
22912
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
22645
22913
  switch (ext) {
@@ -35402,30 +35670,10 @@ function FieldControl({
35402
35670
  control = /* @__PURE__ */ jsxRuntime.jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
35403
35671
  } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
35404
35672
  const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
35405
- control = /* @__PURE__ */ jsxRuntime.jsx(
35406
- exports.ArrayEditor,
35407
- {
35408
- value: arr,
35409
- onChange: (next) => onChange(name, next)
35410
- }
35411
- );
35412
- } else if (decl.type === "object" && isTraitConfigObject4(effectiveValue)) {
35413
- control = /* @__PURE__ */ jsxRuntime.jsx(
35414
- exports.ObjectEditor,
35415
- {
35416
- value: effectiveValue,
35417
- onChange: (next) => onChange(name, next)
35418
- }
35419
- );
35420
- } else if (decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject4(effectiveValue)) {
35673
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
35674
+ } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject4(effectiveValue)) {
35421
35675
  const obj = isTraitConfigObject4(effectiveValue) ? effectiveValue : {};
35422
- control = /* @__PURE__ */ jsxRuntime.jsx(
35423
- exports.MapEditor,
35424
- {
35425
- value: obj,
35426
- onChange: (next) => onChange(name, next)
35427
- }
35428
- );
35676
+ control = /* @__PURE__ */ jsxRuntime.jsx(exports.JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
35429
35677
  } else {
35430
35678
  control = /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { variant: "caption", color: "muted", children: [
35431
35679
  decl.type,
@@ -35452,9 +35700,7 @@ var init_PropertyInspector = __esm({
35452
35700
  init_FormSection();
35453
35701
  init_IconPicker();
35454
35702
  init_AssetPicker();
35455
- init_ArrayEditor();
35456
- init_ObjectEditor();
35457
- init_MapEditor();
35703
+ init_JsonTreeEditor();
35458
35704
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
35459
35705
  SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
35460
35706
  exports.PropertyInspector = ({
@@ -37353,6 +37599,7 @@ var init_molecules2 = __esm({
37353
37599
  init_ArrayEditor();
37354
37600
  init_ObjectEditor();
37355
37601
  init_MapEditor();
37602
+ init_JsonTreeEditor();
37356
37603
  init_FileTree();
37357
37604
  init_FormField();
37358
37605
  init_EmptyState();
@@ -22592,6 +22592,274 @@ var init_MapEditor = __esm({
22592
22592
  };
22593
22593
  }
22594
22594
  });
22595
+ function kindOf(v) {
22596
+ if (v === null) return "null";
22597
+ if (isArr(v)) return "array";
22598
+ if (isObj(v)) return "object";
22599
+ if (typeof v === "number") return "number";
22600
+ if (typeof v === "boolean") return "boolean";
22601
+ return "string";
22602
+ }
22603
+ function emptyOf(kind) {
22604
+ switch (kind) {
22605
+ case "object":
22606
+ return {};
22607
+ case "array":
22608
+ return [];
22609
+ case "number":
22610
+ return 0;
22611
+ case "boolean":
22612
+ return false;
22613
+ case "null":
22614
+ return null;
22615
+ default:
22616
+ return "";
22617
+ }
22618
+ }
22619
+ function templateFrom(arr) {
22620
+ if (arr.length === 0) return "";
22621
+ const first = arr[0];
22622
+ if (isObj(first)) {
22623
+ const blank = {};
22624
+ for (const k of Object.keys(first)) blank[k] = isObj(first[k]) ? {} : isArr(first[k]) ? [] : first[k];
22625
+ return blank;
22626
+ }
22627
+ if (isArr(first)) return [];
22628
+ return emptyOf(kindOf(first));
22629
+ }
22630
+ function ScalarControl2({
22631
+ value,
22632
+ onChange
22633
+ }) {
22634
+ if (typeof value === "boolean") {
22635
+ return /* @__PURE__ */ jsx(Switch, { checked: value, onChange: (c) => onChange(c) });
22636
+ }
22637
+ const numeric = typeof value === "number";
22638
+ const initial = value === null ? "" : String(value);
22639
+ const [draft, setDraft] = React74__default.useState(initial);
22640
+ React74__default.useEffect(() => setDraft(initial), [initial]);
22641
+ const commit = () => {
22642
+ if (numeric) {
22643
+ const n = draft.trim() === "" ? 0 : Number(draft);
22644
+ onChange(Number.isNaN(n) ? 0 : n);
22645
+ } else {
22646
+ onChange(draft);
22647
+ }
22648
+ };
22649
+ return /* @__PURE__ */ jsx(
22650
+ Input,
22651
+ {
22652
+ inputType: numeric ? "number" : "text",
22653
+ value: draft,
22654
+ placeholder: value === null ? "null" : void 0,
22655
+ onChange: (e) => setDraft(e.target.value),
22656
+ onBlur: commit,
22657
+ onKeyDown: (e) => {
22658
+ if (e.key === "Enter") commit();
22659
+ }
22660
+ }
22661
+ );
22662
+ }
22663
+ function KindSelect({
22664
+ kind,
22665
+ onChange
22666
+ }) {
22667
+ return /* @__PURE__ */ jsx(
22668
+ "select",
22669
+ {
22670
+ value: kind === "null" ? "string" : kind,
22671
+ onChange: (e) => onChange(e.target.value),
22672
+ "aria-label": "Value type",
22673
+ className: cn(
22674
+ "h-6 rounded-sm bg-muted text-muted-foreground text-[10px] font-mono px-1",
22675
+ "border-[length:var(--border-width-thin)] border-border",
22676
+ "hover:bg-card focus:outline-none focus:ring-1 focus:ring-ring"
22677
+ ),
22678
+ children: KIND_OPTIONS.map((k) => /* @__PURE__ */ jsx("option", { value: k, children: TYPE_LABEL[k] }, k))
22679
+ }
22680
+ );
22681
+ }
22682
+ function ValueNode({
22683
+ value,
22684
+ onChange,
22685
+ depth
22686
+ }) {
22687
+ const kind = kindOf(value);
22688
+ if (kind === "object" || kind === "array") {
22689
+ return /* @__PURE__ */ jsx(ContainerNode, { value, onChange, depth });
22690
+ }
22691
+ return /* @__PURE__ */ jsx(ScalarControl2, { value, onChange });
22692
+ }
22693
+ function Row({
22694
+ rowKey,
22695
+ isArrayItem,
22696
+ value,
22697
+ depth,
22698
+ onValue,
22699
+ onRenameKey,
22700
+ onChangeKind,
22701
+ onRemove
22702
+ }) {
22703
+ const [keyDraft, setKeyDraft] = React74__default.useState(rowKey);
22704
+ React74__default.useEffect(() => setKeyDraft(rowKey), [rowKey]);
22705
+ const container = isObj(value) || isArr(value);
22706
+ return /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "group", children: [
22707
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", className: "py-0.5", children: [
22708
+ /* @__PURE__ */ jsx(KindSelect, { kind: kindOf(value), onChange: onChangeKind }),
22709
+ isArrayItem ? /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "w-7 shrink-0 font-mono", children: rowKey }) : /* @__PURE__ */ jsx("div", { className: "w-28 shrink-0", children: /* @__PURE__ */ jsx(
22710
+ Input,
22711
+ {
22712
+ inputType: "text",
22713
+ value: keyDraft,
22714
+ onChange: (e) => setKeyDraft(e.target.value),
22715
+ onBlur: () => keyDraft !== rowKey && onRenameKey(keyDraft),
22716
+ onKeyDown: (e) => {
22717
+ if (e.key === "Enter" && keyDraft !== rowKey) onRenameKey(keyDraft);
22718
+ },
22719
+ className: "font-mono"
22720
+ }
22721
+ ) }),
22722
+ !container && /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(ValueNode, { value, onChange: onValue, depth: depth + 1 }) }),
22723
+ container && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
22724
+ /* @__PURE__ */ jsx(
22725
+ Button,
22726
+ {
22727
+ variant: "ghost",
22728
+ size: "sm",
22729
+ icon: "x",
22730
+ onClick: onRemove,
22731
+ "aria-label": "Remove",
22732
+ className: "opacity-0 group-hover:opacity-100 transition-opacity"
22733
+ }
22734
+ )
22735
+ ] }),
22736
+ container && /* @__PURE__ */ jsx("div", { className: "pl-3", children: /* @__PURE__ */ jsx(ValueNode, { value, onChange: onValue, depth: depth + 1 }) })
22737
+ ] });
22738
+ }
22739
+ function ContainerNode({
22740
+ value,
22741
+ onChange,
22742
+ depth
22743
+ }) {
22744
+ const [open, setOpen] = React74__default.useState(depth < 2);
22745
+ const array = isArr(value);
22746
+ const entries = array ? value.map((v, i) => [String(i), v]) : Object.entries(value);
22747
+ const setObjValue = (key, next) => {
22748
+ onChange({ ...value, [key]: next });
22749
+ };
22750
+ const renameKey = (oldKey, newKey) => {
22751
+ if (!newKey || newKey === oldKey) return;
22752
+ const out = {};
22753
+ for (const [k, v] of Object.entries(value)) out[k === oldKey ? newKey : k] = v;
22754
+ onChange(out);
22755
+ };
22756
+ const removeObjKey = (key) => {
22757
+ const out = {};
22758
+ for (const [k, v] of Object.entries(value)) if (k !== key) out[k] = v;
22759
+ onChange(out);
22760
+ };
22761
+ const changeObjKind = (key, k) => setObjValue(key, emptyOf(k));
22762
+ const setArrValue = (idx, next) => {
22763
+ const copy = [...value];
22764
+ copy[idx] = next;
22765
+ onChange(copy);
22766
+ };
22767
+ const removeArrIdx = (idx) => onChange(value.filter((_, i) => i !== idx));
22768
+ const changeArrKind = (idx, k) => setArrValue(idx, emptyOf(k));
22769
+ const add = () => {
22770
+ if (array) {
22771
+ onChange([...value, templateFrom(value)]);
22772
+ } else {
22773
+ const obj = value;
22774
+ let key = "key";
22775
+ let n = 1;
22776
+ while (key in obj) {
22777
+ key = `key${n}`;
22778
+ n += 1;
22779
+ }
22780
+ onChange({ ...obj, [key]: "" });
22781
+ }
22782
+ };
22783
+ const summary = array ? `${entries.length} item${entries.length === 1 ? "" : "s"}` : `${entries.length} field${entries.length === 1 ? "" : "s"}`;
22784
+ return /* @__PURE__ */ jsxs(VStack, { gap: "none", className: "w-full", children: [
22785
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", align: "center", children: /* @__PURE__ */ jsxs(
22786
+ "button",
22787
+ {
22788
+ type: "button",
22789
+ onClick: () => setOpen((o) => !o),
22790
+ className: "flex items-center gap-1 text-muted-foreground hover:text-foreground",
22791
+ "aria-label": open ? "Collapse" : "Expand",
22792
+ children: [
22793
+ /* @__PURE__ */ jsx(Icon, { name: open ? "chevron-down" : "chevron-right", size: "sm" }),
22794
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: array ? "[ ]" : "{ }" }),
22795
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: summary })
22796
+ ]
22797
+ }
22798
+ ) }),
22799
+ open && /* @__PURE__ */ jsxs(
22800
+ VStack,
22801
+ {
22802
+ gap: "none",
22803
+ className: cn("ml-2 pl-2 border-l-[length:var(--border-width-thin)] border-border"),
22804
+ children: [
22805
+ entries.map(([k, v], idx) => /* @__PURE__ */ jsx(
22806
+ Row,
22807
+ {
22808
+ rowKey: k,
22809
+ isArrayItem: array,
22810
+ value: v,
22811
+ depth,
22812
+ onValue: (next) => array ? setArrValue(idx, next) : setObjValue(k, next),
22813
+ onRenameKey: (nk) => renameKey(k, nk),
22814
+ onChangeKind: (kind) => array ? changeArrKind(idx, kind) : changeObjKind(k, kind),
22815
+ onRemove: () => array ? removeArrIdx(idx) : removeObjKey(k)
22816
+ },
22817
+ array ? idx : k
22818
+ )),
22819
+ /* @__PURE__ */ jsx(
22820
+ Button,
22821
+ {
22822
+ variant: "ghost",
22823
+ size: "sm",
22824
+ icon: "plus",
22825
+ label: array ? "Add item" : "Add field",
22826
+ onClick: add,
22827
+ className: "self-start text-muted-foreground"
22828
+ }
22829
+ )
22830
+ ]
22831
+ }
22832
+ )
22833
+ ] });
22834
+ }
22835
+ var isObj, isArr, TYPE_LABEL, KIND_OPTIONS, JsonTreeEditor;
22836
+ var init_JsonTreeEditor = __esm({
22837
+ "components/core/molecules/JsonTreeEditor.tsx"() {
22838
+ "use client";
22839
+ init_Stack();
22840
+ init_Button();
22841
+ init_Input();
22842
+ init_Switch();
22843
+ init_Typography();
22844
+ init_Icon();
22845
+ init_cn();
22846
+ isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
22847
+ isArr = (v) => Array.isArray(v);
22848
+ TYPE_LABEL = {
22849
+ object: "{}",
22850
+ array: "[]",
22851
+ string: "abc",
22852
+ number: "123",
22853
+ boolean: "on/off",
22854
+ null: "\u2014"
22855
+ };
22856
+ KIND_OPTIONS = ["string", "number", "boolean", "object", "array"];
22857
+ JsonTreeEditor = ({ value, onChange, className }) => {
22858
+ const root = value ?? "";
22859
+ return /* @__PURE__ */ jsx("div", { className: cn("w-full rounded-sm bg-card/40 p-2 border-[length:var(--border-width-thin)] border-border", className), children: /* @__PURE__ */ jsx(ValueNode, { value: root, onChange, depth: 0 }) });
22860
+ };
22861
+ }
22862
+ });
22595
22863
  function fileIcon(name) {
22596
22864
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
22597
22865
  switch (ext) {
@@ -35354,30 +35622,10 @@ function FieldControl({
35354
35622
  control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
35355
35623
  } else if (decl.type.startsWith("[") || Array.isArray(effectiveValue)) {
35356
35624
  const arr = Array.isArray(effectiveValue) ? effectiveValue : [];
35357
- control = /* @__PURE__ */ jsx(
35358
- ArrayEditor,
35359
- {
35360
- value: arr,
35361
- onChange: (next) => onChange(name, next)
35362
- }
35363
- );
35364
- } else if (decl.type === "object" && isTraitConfigObject4(effectiveValue)) {
35365
- control = /* @__PURE__ */ jsx(
35366
- ObjectEditor,
35367
- {
35368
- value: effectiveValue,
35369
- onChange: (next) => onChange(name, next)
35370
- }
35371
- );
35372
- } else if (decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject4(effectiveValue)) {
35625
+ control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: arr, onChange: (next) => onChange(name, next) });
35626
+ } else if (decl.type === "object" || decl.type.startsWith("Map ") || !SCALAR_TYPES.has(decl.type) && isTraitConfigObject4(effectiveValue)) {
35373
35627
  const obj = isTraitConfigObject4(effectiveValue) ? effectiveValue : {};
35374
- control = /* @__PURE__ */ jsx(
35375
- MapEditor,
35376
- {
35377
- value: obj,
35378
- onChange: (next) => onChange(name, next)
35379
- }
35380
- );
35628
+ control = /* @__PURE__ */ jsx(JsonTreeEditor, { value: obj, onChange: (next) => onChange(name, next) });
35381
35629
  } else {
35382
35630
  control = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "muted", children: [
35383
35631
  decl.type,
@@ -35404,9 +35652,7 @@ var init_PropertyInspector = __esm({
35404
35652
  init_FormSection();
35405
35653
  init_IconPicker();
35406
35654
  init_AssetPicker();
35407
- init_ArrayEditor();
35408
- init_ObjectEditor();
35409
- init_MapEditor();
35655
+ init_JsonTreeEditor();
35410
35656
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
35411
35657
  SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset"]);
35412
35658
  PropertyInspector = ({
@@ -37305,6 +37551,7 @@ var init_molecules2 = __esm({
37305
37551
  init_ArrayEditor();
37306
37552
  init_ObjectEditor();
37307
37553
  init_MapEditor();
37554
+ init_JsonTreeEditor();
37308
37555
  init_FileTree();
37309
37556
  init_FormField();
37310
37557
  init_EmptyState();
@@ -49933,4 +50180,4 @@ init_AboutPageTemplate();
49933
50180
  // components/index.ts
49934
50181
  init_cn();
49935
50182
 
49936
- export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArrayEditor, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FeatureRenderer2 as FeatureRenderer, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapEditor, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NotifyListener, NumberStepper, ObjectEditor, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerCanvas, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };
50183
+ export { ALL_PRESETS, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, ActionButton, ActionButtons, Card2 as ActionCard, ActionPalette, ActionTile, Alert, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArrayEditor, ArticleSection, Aside, AssetPicker, AuthLayout, Avatar, Badge, BattleBoard, BattleTemplate, BehaviorView, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, BuilderBoard, Button, ButtonGroup, CTABanner, CalendarGrid, CanvasEffect, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, CastleBoard, CastleTemplate, Center, Chart, ChartLegend, Checkbox, ChoiceButton, ClassifierBoard, Coachmark, CodeBlock, CollapsibleSection, CombatLog, ComboCounter, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, Container, ContentRenderer, ContentSection, ControlButton, CounterTemplate, CraftingRecipe, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DIAMOND_TOP_Y, DPad, DamageNumber, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DebuggerBoard, DetailPanel, Dialog, DialogueBox, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmptyState, EnemyPlate, EntityDisplayEvents, ErrorBoundary, ErrorState, EventHandlerBoard, EventLog, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FeatureRenderer2 as FeatureRenderer, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, GameAudioContext, GameAudioProvider, GameAudioToggle, GameCanvas2D, GameHud, GameMenu, GameOverScreen, GameShell, GameTemplate, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HealthPanel, HeroOrganism, HeroSection, IDENTITY_BOOK_FIELDS, Icon, IconPicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, InventoryGrid, InventoryPanel, IsometricCanvas, ItemSlot, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapEditor, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MatrixQuestion, MediaGallery, Menu, Meter, MiniMap, Modal, ModalSlot, ModuleCard, Navigation, NegotiatorBoard, NotifyListener, NumberStepper, ObjectEditor, ObjectRulePanel, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, Pagination, PatternTile, PhysicsManager, PlatformerCanvas, Popover, PositionedCanvas, PowerupSlots, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuestTracker, QuizBlock, Radio, RangeSlider, RelationSelect, RepeatableFormSection, ReplyTree, ResourceBar, ResourceCounter, RichBlockEditor, RuleEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreBoard, ScoreDisplay, SearchInput, Section, SectionHeader, Select, SequenceBar, SequencerBoard, ServiceCatalog, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, SimulationCanvas, SimulationControls, SimulationGraph, SimulatorBoard, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Sprite, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateArchitectBoard, StateIndicator, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StatusEffect, StepFlow, StepFlowOrganism, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeSelector, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TurnIndicator, TurnPanel, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UncontrolledBattleBoard, UnitCommandBar, UploadDropZone, VStack, VariablePanel, VersionDiff, ViolationAlert, VoteStack, WaypointMarker, WizardContainer, WizardNavigation, WizardProgress, WorldMapBoard, WorldMapTemplate, XPBar, applyTemporaryEffect, calculateAttackTargets, calculateDamage, calculateValidMoves, cn, combatAnimations, combatClasses, combatEffects, createInitialGameState, createUnitAnimationState, drawSprite, generateCombatMessage, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, mapBookData, pendulum, projectileMotion, resolveFieldMap, resolveFrame, resolveSheetDirection, screenToIso, springOscillator, tickAnimationState, toCodeLanguage, transitionAnimation, useAnchorRect, useBattleState, useCamera, useGameAudio, useGameAudioContext, useImageCache, usePhysics2D, useSpriteAnimations };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.27.0",
3
+ "version": "5.28.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [