@asteby/metacore-runtime-react 13.7.0 → 13.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 13.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - efac939: fix(dynamic-select): the inline-create "+" no longer overlaps the combobox. The trigger used `w-full` which, in the flex row beside the "+", forced 100% width and overlapped the button in narrow (2-column) modal grids. Use `flex-1 min-w-0` so it grows to fill the space left for the "+".
8
+
9
+ ## 13.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - ab80937: feat(dynamic-select): inline "+" to create the referenced record. A dynamic_select with a `ref` now shows a "+" button that opens the referenced model's OWN create modal (via a decoupled `metacore:create-record` window event the host handles) and auto-selects the newly created record. Lets users add a missing Category/Brand/etc. without leaving the form.
14
+
3
15
  ## 13.7.0
4
16
 
5
17
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAW7C,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CA0GrF;AAED,eAAe,kBAAkB,CAAA"}
1
+ {"version":3,"file":"dynamic-select-field.d.ts","sourceRoot":"","sources":["../src/dynamic-select-field.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAW7C,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,cAAc,CAAA;IACrB,KAAK,EAAE,GAAG,CAAA;IACV,QAAQ,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;CAC7B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAgJrF;AAED,eAAe,kBAAkB,CAAA"}
@@ -24,7 +24,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
24
24
  // case — start empty and never hit this.
25
25
  import { useEffect, useState } from 'react';
26
26
  import { Button, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, Popover, PopoverContent, PopoverTrigger, } from '@asteby/metacore-ui/primitives';
27
- import { Check, ChevronsUpDown, Loader2 } from 'lucide-react';
27
+ import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react';
28
28
  import { useOptionsResolver } from './use-options-resolver';
29
29
  function useDebounced(value, ms) {
30
30
  const [debounced, setDebounced] = useState(value);
@@ -63,12 +63,33 @@ export function DynamicSelectField({ field, value, onChange }) {
63
63
  setOpen(false);
64
64
  setSearch('');
65
65
  };
66
- return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, className: "w-full justify-between font-normal", "data-empty": !value, children: [_jsx("span", { className: 'truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: selectedLabel || field.placeholder || 'Buscar…' }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
67
- // Match the trigger width without an arbitrary Tailwind class
68
- // (those don't always survive a consuming app's Tailwind scan).
69
- style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: debounced ? 'Sin resultados' : 'Escribí para buscar…' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
70
- const isSel = String(opt.id) === String(value);
71
- return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 ' + (isSel ? 'opacity-100' : 'opacity-0') }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
72
- }) }))] })] }) })] }));
66
+ // Inline-create: the "+" opens the REFERENCED model's own create modal (the
67
+ // real one the host renders for that model full fields, not a duplicate),
68
+ // via a decoupled window event the host listens for. On success the host
69
+ // hands back the new record and we select it immediately. No host import
70
+ // no circular dependency; works for ANY dynamic_select with a `ref`.
71
+ const openCreate = () => {
72
+ if (!field.ref || typeof window === 'undefined')
73
+ return;
74
+ window.dispatchEvent(new CustomEvent('metacore:create-record', {
75
+ detail: {
76
+ model: field.ref,
77
+ onCreated: (rec) => {
78
+ if (rec && rec.id != null) {
79
+ const id = String(rec.id);
80
+ const label = String(rec.name ?? rec.label ?? rec.title ?? rec.id);
81
+ handlePick({ id, value: id, label, name: label });
82
+ }
83
+ },
84
+ },
85
+ }));
86
+ };
87
+ return (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", role: "combobox", "aria-expanded": open, id: field.key, className: "min-w-0 flex-1 justify-between font-normal", "data-empty": !value, children: [_jsx("span", { className: 'truncate ' + (selectedLabel ? '' : 'text-muted-foreground'), children: selectedLabel || field.placeholder || 'Buscar…' }), _jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })] }) }), _jsx(PopoverContent, { className: "p-0", align: "start",
88
+ // Match the trigger width without an arbitrary Tailwind class
89
+ // (those don't always survive a consuming app's Tailwind scan).
90
+ style: { width: 'var(--radix-popover-trigger-width)' }, children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { placeholder: field.placeholder || 'Buscar…', value: search, onValueChange: setSearch }), _jsxs(CommandList, { children: [loading && (_jsxs("div", { className: "text-muted-foreground flex items-center justify-center gap-2 py-6 text-sm", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), "Buscando\u2026"] })), !loading && options.length === 0 && (_jsx(CommandEmpty, { children: debounced ? 'Sin resultados' : 'Escribí para buscar…' })), !loading && options.length > 0 && (_jsx(CommandGroup, { className: "max-h-64 overflow-auto", children: options.map((opt) => {
91
+ const isSel = String(opt.id) === String(value);
92
+ return (_jsxs(CommandItem, { value: String(opt.id), onSelect: () => handlePick(opt), children: [_jsx(Check, { className: 'mr-2 size-4 ' + (isSel ? 'opacity-100' : 'opacity-0') }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate", children: opt.label }), opt.description && (_jsx("span", { className: "text-muted-foreground truncate text-xs", children: opt.description }))] })] }, String(opt.id)));
93
+ }) }))] })] }) })] }), field.ref && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "size-9 shrink-0", onClick: openCreate, title: `Crear ${field.label ?? field.ref}`, "aria-label": `Crear ${field.label ?? field.ref}`, children: _jsx(Plus, { className: "size-4" }) }))] }));
73
94
  }
74
95
  export default DynamicSelectField;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "13.7.0",
3
+ "version": "13.8.1",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -61,8 +61,8 @@
61
61
  "typescript": "^6.0.0",
62
62
  "vitest": "^4.0.0",
63
63
  "zustand": "^5.0.0",
64
- "@asteby/metacore-sdk": "3.1.0",
65
- "@asteby/metacore-ui": "2.1.1"
64
+ "@asteby/metacore-ui": "2.1.1",
65
+ "@asteby/metacore-sdk": "3.1.0"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsc -p tsconfig.json",
@@ -34,7 +34,7 @@ import {
34
34
  PopoverContent,
35
35
  PopoverTrigger,
36
36
  } from '@asteby/metacore-ui/primitives'
37
- import { Check, ChevronsUpDown, Loader2 } from 'lucide-react'
37
+ import { Check, ChevronsUpDown, Loader2, Plus } from 'lucide-react'
38
38
  import { useOptionsResolver, type ResolvedOption } from './use-options-resolver'
39
39
  import type { ActionFieldDef } from './types'
40
40
 
@@ -87,8 +87,32 @@ export function DynamicSelectField({ field, value, onChange }: DynamicSelectFiel
87
87
  setSearch('')
88
88
  }
89
89
 
90
+ // Inline-create: the "+" opens the REFERENCED model's own create modal (the
91
+ // real one the host renders for that model — full fields, not a duplicate),
92
+ // via a decoupled window event the host listens for. On success the host
93
+ // hands back the new record and we select it immediately. No host import →
94
+ // no circular dependency; works for ANY dynamic_select with a `ref`.
95
+ const openCreate = () => {
96
+ if (!field.ref || typeof window === 'undefined') return
97
+ window.dispatchEvent(
98
+ new CustomEvent('metacore:create-record', {
99
+ detail: {
100
+ model: field.ref,
101
+ onCreated: (rec: any) => {
102
+ if (rec && rec.id != null) {
103
+ const id = String(rec.id)
104
+ const label = String(rec.name ?? rec.label ?? rec.title ?? rec.id)
105
+ handlePick({ id, value: id, label, name: label })
106
+ }
107
+ },
108
+ },
109
+ }),
110
+ )
111
+ }
112
+
90
113
  return (
91
- <Popover open={open} onOpenChange={setOpen}>
114
+ <div className="flex items-center gap-1.5">
115
+ <Popover open={open} onOpenChange={setOpen}>
92
116
  <PopoverTrigger asChild>
93
117
  <Button
94
118
  type="button"
@@ -96,7 +120,7 @@ export function DynamicSelectField({ field, value, onChange }: DynamicSelectFiel
96
120
  role="combobox"
97
121
  aria-expanded={open}
98
122
  id={field.key}
99
- className="w-full justify-between font-normal"
123
+ className="min-w-0 flex-1 justify-between font-normal"
100
124
  data-empty={!value}
101
125
  >
102
126
  <span className={'truncate ' + (selectedLabel ? '' : 'text-muted-foreground')}>
@@ -157,7 +181,21 @@ export function DynamicSelectField({ field, value, onChange }: DynamicSelectFiel
157
181
  </CommandList>
158
182
  </Command>
159
183
  </PopoverContent>
160
- </Popover>
184
+ </Popover>
185
+ {field.ref && (
186
+ <Button
187
+ type="button"
188
+ variant="outline"
189
+ size="icon"
190
+ className="size-9 shrink-0"
191
+ onClick={openCreate}
192
+ title={`Crear ${field.label ?? field.ref}`}
193
+ aria-label={`Crear ${field.label ?? field.ref}`}
194
+ >
195
+ <Plus className="size-4" />
196
+ </Button>
197
+ )}
198
+ </div>
161
199
  )
162
200
  }
163
201