@asteby/metacore-runtime-react 12.0.0 → 13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @asteby/metacore-runtime-react
2
2
 
3
+ ## 13.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7ea7caa: Acciones con `placement` (`row` | `table` | `create`) y nuevo primitivo `<ModelActionToolbar>`.
8
+
9
+ `ActionMetadata`/`ActionDefinition` ganan `placement`, espejando `manifest/v3` Action.placement del kernel (v0.30.0):
10
+ - `row` (default) — acción por fila dentro de `<DynamicTable>` (comportamiento actual).
11
+ - `table` — botón en la toolbar de la página, sin contexto de record.
12
+ - `create` — botón en la toolbar que **reemplaza** el botón "crear" genérico, para addons que traen una experiencia de creación custom (p.ej. un asiento contable con líneas débito/crédito).
13
+
14
+ `<ModelActionToolbar>` (+ hook `useModelActions`) es el primitivo genérico que renderiza esos triggers de nivel página y monta el `ActionModalDispatcher` (record vacío para `create`). Resuelve tanto modales federados custom (vía el action registry) como el form declarativo genérico. `DynamicCRUDPage` lo consume internamente y suprime su botón crear cuando existe una acción `create`; `DynamicTable` excluye los placements `table`/`create` de la columna de acciones por fila. Los hosts ya no reimplementan el plumbing de botones de acción — montan `<ModelActionToolbar>` y listo.
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies [7ea7caa]
19
+ - Updated dependencies [3b40ed5]
20
+ - @asteby/metacore-sdk@3.1.0
21
+ - @asteby/metacore-ui@2.1.0
22
+
3
23
  ## 12.0.0
4
24
 
5
25
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAWd,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAsL1D"}
1
+ {"version":3,"file":"dynamic-crud-page.d.ts","sourceRoot":"","sources":["../src/dynamic-crud-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAKN,MAAM,OAAO,CAAA;AAYd,MAAM,WAAW,sBAAsB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;mDAC+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AASD,MAAM,WAAW,sBAAsB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAA;IACb,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,sBAAsB,CAAA;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,8DAA8D;IAC9D,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,sDAAsD;IACtD,OAAO,CAAC,EAAE,sBAAsB,CAAA;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CA+L1D"}
@@ -32,6 +32,7 @@ import { DynamicRecordDialog } from './dialogs/dynamic-record';
32
32
  import { ExportDialog } from './dialogs/export';
33
33
  import { ImportDialog } from './dialogs/import';
34
34
  import { getModelExtension } from './model-extension-registry';
35
+ import { ModelActionToolbar } from './model-action-toolbar';
35
36
  const defaultStrings = {
36
37
  refresh: 'Refresh',
37
38
  export: 'Export',
@@ -84,7 +85,10 @@ export function DynamicCRUDPage(props) {
84
85
  return t.charAt(0).toUpperCase() + t.slice(1);
85
86
  }, [title]);
86
87
  const enableCRUD = metadata?.enableCRUDActions ?? false;
87
- const effectiveHideCreate = hideCreate || ext?.hideCreate;
88
+ // A "create"-placement action ships a custom create experience — it
89
+ // replaces the generic create button (the ModelActionToolbar renders it).
90
+ const hasCreateAction = metadata?.actions?.some((a) => a.placement === 'create') ?? false;
91
+ const effectiveHideCreate = hideCreate || ext?.hideCreate || hasCreateAction;
88
92
  const effectiveHideExport = hideExport || ext?.hideExport;
89
93
  const effectiveHideImport = hideImport || ext?.hideImport;
90
94
  // Refresh defaults to hidden in the page header — <DynamicTable> ships
@@ -106,5 +110,5 @@ export function DynamicCRUDPage(props) {
106
110
  const titleCls = classes?.title ?? 'text-2xl font-bold tracking-tight';
107
111
  const toolbarCls = classes?.toolbar ?? 'flex items-center gap-2';
108
112
  const tableWrapperCls = classes?.tableWrapper ?? 'flex-1 min-h-0';
109
- return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, showCreate && (_jsxs("button", { type: 'button', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey }, model) })] }), showCreate && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
113
+ return (_jsxs("div", { className: rootCls, children: [_jsxs("div", { className: containerCls, children: [ext?.headerExtras && _jsx(ext.headerExtras, { model: model, onRefresh: handleRefresh }), headerExtras, _jsxs("div", { className: headerCls, children: [metadata ? (_jsx("h1", { className: titleCls, children: title })) : (_jsx("div", { className: 'h-8 w-48 bg-muted rounded animate-pulse' })), _jsxs("div", { className: toolbarCls, children: [showRefresh && (_jsx("button", { type: 'button', onClick: handleRefresh, "aria-label": strings.refresh, className: 'inline-flex items-center justify-center size-9 rounded-md border border-border bg-background hover:bg-accent text-foreground', children: _jsx(RefreshCw, { className: 'size-4' }) })), metadata && showExport && (_jsxs("button", { type: 'button', onClick: () => setOpenExport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Download, { className: 'size-4' }), strings.export] })), metadata && showImport && (_jsxs("button", { type: 'button', onClick: () => setOpenImport(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md border border-border bg-background hover:bg-accent text-sm font-medium text-foreground', children: [_jsx(Upload, { className: 'size-4' }), strings.import] })), ext?.toolbarExtras && _jsx(ext.toolbarExtras, { model: model, onRefresh: handleRefresh }), toolbarExtras, _jsx(ModelActionToolbar, { model: model, endpoint: dataEndpoint, actions: metadata?.actions, onChange: handleRefresh }), showCreate && (_jsxs("button", { type: 'button', onClick: () => setOpenCreate(true), className: 'inline-flex items-center gap-2 h-9 px-3 rounded-md bg-primary text-primary-foreground hover:opacity-90 text-sm font-medium', children: [_jsx(Plus, { className: 'size-4' }), resolvedNewLabel ?? `${strings.newPrefix} ${singular}`] }))] })] }), _jsx("div", { className: tableWrapperCls, children: _jsx(DynamicTable, { model: model, endpoint: dataEndpoint, refreshTrigger: refreshKey }, model) })] }), showCreate && (_jsx(DynamicRecordDialog, { open: openCreate, onOpenChange: setOpenCreate, mode: 'create', model: model, endpoint: dataEndpoint, onSaved: handleRefresh })), metadata && showExport && (_jsx(ExportDialog, { open: openExport, onOpenChange: setOpenExport, model: model, metadata: metadata })), metadata && showImport && (_jsx(ImportDialog, { open: openImport, onOpenChange: setOpenImport, model: model, metadata: metadata, onImported: handleRefresh }))] }));
110
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CA+sBnB"}
1
+ {"version":3,"file":"dynamic-table.d.ts","sourceRoot":"","sources":["../src/dynamic-table.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAKH,KAAK,SAAS,EAajB,MAAM,uBAAuB,CAAA;AA+B9B,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAUnF,UAAU,iBAAiB;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC7C,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,YAAY,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAA;IAC/B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,aAAoB,EACpB,aAAkB,EAClB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,YAAiB,EACjB,iBAA4C,GAC/C,EAAE,iBAAiB,2CAqtBnB"}
@@ -532,7 +532,13 @@ export function DynamicTable({ model, endpoint, enableUrlSync = true, hiddenColu
532
532
  const columns = useMemo(() => {
533
533
  if (!metadata)
534
534
  return [];
535
- const baseColumns = getDynamicColumns(metadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
535
+ // Row-action column only renders per-row actions. Table-level placements
536
+ // ("table"/"create") are surfaced by <ModelActionToolbar> at the page
537
+ // level, so strip them here to avoid a meaningless per-row button.
538
+ const rowMetadata = metadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
539
+ ? { ...metadata, actions: metadata.actions.filter((a) => !a.placement || a.placement === 'row') }
540
+ : metadata;
541
+ const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs);
536
542
  const filteredBase = baseColumns.filter((col) => !hiddenColumns.includes(col.id));
537
543
  const actionsCol = filteredBase.find((c) => c.id === 'actions');
538
544
  const otherCols = filteredBase.filter((c) => c.id !== 'actions');
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './options-context';
3
3
  export * from './dynamic-table';
4
4
  export * from './dynamic-form';
5
5
  export { ActionModalDispatcher, type ActionModalProps, } from './action-modal-dispatcher';
6
+ export { ModelActionToolbar, useModelActions, type ModelActionToolbarProps, type ActionPlacement, } from './model-action-toolbar';
6
7
  export * from './addon-loader';
7
8
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, type AddonLayout, type AddonLayoutProviderProps, } from './addon-layout-context';
8
9
  export * from './slot';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,GACxB,MAAM,2BAA2B,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EACH,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,WAAW,EAChB,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,cAAc,QAAQ,CAAA;AACtB,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,KAAK,2BAA2B,EAChC,KAAK,qBAAqB,EAC1B,KAAK,8BAA8B,GACtC,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACH,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC9B,MAAM,yBAAyB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,YAAY,EACR,kBAAkB,EAClB,YAAY,IAAI,yBAAyB,EACzC,iBAAiB,EACjB,oBAAoB,GACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,wBAAwB,EACxB,4BAA4B,EAC5B,KAAK,qBAAqB,GAC7B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AAC/D,YAAY,EACR,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC9B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACH,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GAC3B,MAAM,4BAA4B,CAAA;AACnC,OAAO,EACH,sBAAsB,EACtB,uBAAuB,GAC1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACH,kBAAkB,EAClB,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,GAChC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EACH,kBAAkB,EAClB,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,eAAe,GACvB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ export * from './options-context';
8
8
  export * from './dynamic-table';
9
9
  export * from './dynamic-form';
10
10
  export { ActionModalDispatcher, } from './action-modal-dispatcher';
11
+ export { ModelActionToolbar, useModelActions, } from './model-action-toolbar';
11
12
  export * from './addon-loader';
12
13
  export { AddonLayoutProvider, useAddonLayout, useAddonLayoutControl, useDeclareAddonLayout, } from './addon-layout-context';
13
14
  export * from './slot';
@@ -0,0 +1,27 @@
1
+ import type { ActionDefinition } from './types';
2
+ export type ActionPlacement = 'row' | 'table' | 'create';
3
+ export interface ModelActionToolbarProps {
4
+ /** Model key as registered on the backend (e.g. "JournalEntry"). */
5
+ model: string;
6
+ /** Data endpoint passed to the dispatcher. Defaults to `/data/<model>/me`. */
7
+ endpoint?: string;
8
+ /**
9
+ * Pre-fetched action definitions. When omitted the toolbar reads them from
10
+ * the metadata cache, falling back to `/metadata/table/<model>`. Pass this
11
+ * when the host page already holds the metadata to avoid a second fetch.
12
+ */
13
+ actions?: ActionDefinition[];
14
+ /** Which placements to render. Defaults to `['table', 'create']`. */
15
+ placements?: ActionPlacement[];
16
+ /** Fired after an action's modal reports success. */
17
+ onChange?: () => void;
18
+ /** Extra classes on the button row container. */
19
+ className?: string;
20
+ }
21
+ /**
22
+ * Returns the model's actions matching the requested placements. Reads from the
23
+ * `actions` prop when provided, else the metadata cache, else fetches once.
24
+ */
25
+ export declare function useModelActions(model: string, placements?: ActionPlacement[], provided?: ActionDefinition[]): ActionDefinition[];
26
+ export declare function ModelActionToolbar({ model, endpoint, actions, placements, onChange, className, }: ModelActionToolbarProps): import("react/jsx-runtime").JSX.Element | null;
27
+ //# sourceMappingURL=model-action-toolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-action-toolbar.d.ts","sourceRoot":"","sources":["../src/model-action-toolbar.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,gBAAgB,EAAiC,MAAM,SAAS,CAAA;AAE9E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACpC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,EAAE,CAAA;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAC3B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,eAAe,EAAuB,EAClD,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAC9B,gBAAgB,EAAE,CA2BpB;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,UAA+B,EAC/B,QAAQ,EACR,SAAS,GACZ,EAAE,uBAAuB,kDA4CzB"}
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // ModelActionToolbar — renders page-level (toolbar) triggers for a model's
3
+ // declarative actions and owns the modal dispatch for them.
4
+ //
5
+ // A model's actions carry a `placement` hint (see manifest/v3 Action.placement):
6
+ // "row" (default) — rendered per-row inside <DynamicTable>'s action column.
7
+ // "table" — a plain toolbar button (no record context).
8
+ // "create" — a primary toolbar button that replaces the generic
9
+ // "create" button, for addons shipping a custom create
10
+ // experience (e.g. a journal entry with debit/credit lines).
11
+ //
12
+ // This component renders the buttons for the placements it's asked to surface
13
+ // (default: table + create) and mounts the <ActionModalDispatcher>, which
14
+ // resolves either a custom federated modal (registered via the action registry)
15
+ // or the generic declarative form. For create-style actions there is no record
16
+ // yet, so the dispatcher receives an empty record `{}`.
17
+ //
18
+ // It is the single generic primitive every host consumes — DynamicCRUDPage uses
19
+ // it internally, and bespoke host pages (e.g. ops `/m/$model`) mount it directly
20
+ // next to their own toolbar. Hosts never reimplement action-button plumbing.
21
+ import { useEffect, useMemo, useState } from 'react';
22
+ import { Button } from '@asteby/metacore-ui/primitives';
23
+ import { useApi } from './api-context';
24
+ import { useMetadataCache } from './metadata-cache';
25
+ import { DynamicIcon } from './dynamic-icon';
26
+ import { ActionModalDispatcher } from './action-modal-dispatcher';
27
+ const DEFAULT_PLACEMENTS = ['table', 'create'];
28
+ function toActionMetadata(a) {
29
+ return {
30
+ key: a.key,
31
+ label: a.label,
32
+ icon: a.icon || 'Zap',
33
+ color: a.color,
34
+ confirm: a.confirm,
35
+ confirmMessage: a.confirmMessage,
36
+ fields: a.fields,
37
+ requiresState: a.requiresState,
38
+ executable: a.executable,
39
+ placement: a.placement,
40
+ };
41
+ }
42
+ /**
43
+ * Returns the model's actions matching the requested placements. Reads from the
44
+ * `actions` prop when provided, else the metadata cache, else fetches once.
45
+ */
46
+ export function useModelActions(model, placements = DEFAULT_PLACEMENTS, provided) {
47
+ const api = useApi();
48
+ const cached = useMetadataCache((s) => s.getMetadata(model));
49
+ const [fetched, setFetched] = useState(null);
50
+ const haveSource = provided != null || cached != null;
51
+ useEffect(() => {
52
+ if (haveSource)
53
+ return;
54
+ let cancelled = false;
55
+ api
56
+ .get(`/metadata/table/${model}`)
57
+ .then((res) => {
58
+ if (!cancelled)
59
+ setFetched((res.data?.data ?? res.data));
60
+ })
61
+ .catch(() => {
62
+ if (!cancelled)
63
+ setFetched(null);
64
+ });
65
+ return () => {
66
+ cancelled = true;
67
+ };
68
+ }, [model, haveSource, api]);
69
+ const all = provided ?? cached?.actions ?? fetched?.actions ?? [];
70
+ return useMemo(() => all.filter((a) => placements.includes((a.placement ?? 'row'))), [all, placements]);
71
+ }
72
+ export function ModelActionToolbar({ model, endpoint, actions, placements = DEFAULT_PLACEMENTS, onChange, className, }) {
73
+ const surfaced = useModelActions(model, placements, actions);
74
+ const [active, setActive] = useState(null);
75
+ const dataEndpoint = endpoint ?? `/data/${model}/me`;
76
+ if (surfaced.length === 0)
77
+ return null;
78
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: className ?? 'flex items-center gap-2', children: surfaced.map((a) => {
79
+ const isCreate = (a.placement ?? 'row') === 'create';
80
+ return (_jsxs(Button, { variant: isCreate ? 'default' : 'outline', onClick: () => setActive(toActionMetadata(a)), style: a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined, children: [_jsx(DynamicIcon, { name: a.icon || (isCreate ? 'Plus' : 'Zap'), className: "mr-2 h-4 w-4" }), a.label] }, a.key));
81
+ }) }), active && (_jsx(ActionModalDispatcher, { open: !!active, onOpenChange: (open) => {
82
+ if (!open)
83
+ setActive(null);
84
+ }, action: active, model: model, record: {}, endpoint: dataEndpoint, onSuccess: () => {
85
+ setActive(null);
86
+ onChange?.();
87
+ } }))] }));
88
+ }
package/dist/types.d.ts CHANGED
@@ -147,6 +147,13 @@ export interface ActionDefinition {
147
147
  fields?: ActionFieldDef[];
148
148
  requiresState?: string[];
149
149
  executable?: boolean;
150
+ /**
151
+ * Where the host surfaces the trigger. Mirrors manifest/v3 Action.placement.
152
+ * "row" (default) — per-row table action.
153
+ * "table" — page toolbar button (no record context).
154
+ * "create" — toolbar button that replaces the generic create button.
155
+ */
156
+ placement?: 'row' | 'table' | 'create';
150
157
  }
151
158
  export interface ApiResponse<T> {
152
159
  success: boolean;
@@ -173,5 +180,6 @@ export interface ActionMetadata {
173
180
  fields?: ActionFieldDef[];
174
181
  requiresState?: string[];
175
182
  executable?: boolean;
183
+ placement?: 'row' | 'table' | 'create';
176
184
  }
177
185
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteby/metacore-runtime-react",
3
- "version": "12.0.0",
3
+ "version": "13.0.0",
4
4
  "description": "React runtime for metacore hosts — renders addon contributions dynamically",
5
5
  "repository": {
6
6
  "type": "git",
@@ -32,8 +32,8 @@
32
32
  "lucide-react": ">=0.460",
33
33
  "date-fns": ">=3",
34
34
  "react-day-picker": ">=8",
35
- "@asteby/metacore-sdk": "^3.0.0",
36
- "@asteby/metacore-ui": "^2.0.1"
35
+ "@asteby/metacore-sdk": "^3.1.0",
36
+ "@asteby/metacore-ui": "^2.1.0"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@tanstack/react-router": {
@@ -60,8 +60,8 @@
60
60
  "typescript": "^6.0.0",
61
61
  "vitest": "^4.0.0",
62
62
  "zustand": "^5.0.0",
63
- "@asteby/metacore-sdk": "3.0.0",
64
- "@asteby/metacore-ui": "2.0.1"
63
+ "@asteby/metacore-sdk": "3.1.0",
64
+ "@asteby/metacore-ui": "2.1.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsc -p tsconfig.json",
@@ -36,6 +36,7 @@ import { DynamicRecordDialog } from './dialogs/dynamic-record'
36
36
  import { ExportDialog } from './dialogs/export'
37
37
  import { ImportDialog } from './dialogs/import'
38
38
  import { getModelExtension } from './model-extension-registry'
39
+ import { ModelActionToolbar } from './model-action-toolbar'
39
40
  import type { TableMetadata } from './types'
40
41
 
41
42
  export interface DynamicCRUDPageStrings {
@@ -153,7 +154,10 @@ export function DynamicCRUDPage(props: DynamicCRUDPageProps) {
153
154
  }, [title])
154
155
 
155
156
  const enableCRUD = metadata?.enableCRUDActions ?? false
156
- const effectiveHideCreate = hideCreate || ext?.hideCreate
157
+ // A "create"-placement action ships a custom create experience — it
158
+ // replaces the generic create button (the ModelActionToolbar renders it).
159
+ const hasCreateAction = metadata?.actions?.some((a) => a.placement === 'create') ?? false
160
+ const effectiveHideCreate = hideCreate || ext?.hideCreate || hasCreateAction
157
161
  const effectiveHideExport = hideExport || ext?.hideExport
158
162
  const effectiveHideImport = hideImport || ext?.hideImport
159
163
  // Refresh defaults to hidden in the page header — <DynamicTable> ships
@@ -222,6 +226,12 @@ export function DynamicCRUDPage(props: DynamicCRUDPageProps) {
222
226
  )}
223
227
  {ext?.toolbarExtras && <ext.toolbarExtras model={model} onRefresh={handleRefresh} />}
224
228
  {toolbarExtras}
229
+ <ModelActionToolbar
230
+ model={model}
231
+ endpoint={dataEndpoint}
232
+ actions={metadata?.actions}
233
+ onChange={handleRefresh}
234
+ />
225
235
  {showCreate && (
226
236
  <button
227
237
  type='button'
@@ -569,7 +569,13 @@ export function DynamicTable({
569
569
 
570
570
  const columns = useMemo(() => {
571
571
  if (!metadata) return []
572
- const baseColumns = getDynamicColumns(metadata, handleInternalAction, t, i18n.language, columnFilterConfigs)
572
+ // Row-action column only renders per-row actions. Table-level placements
573
+ // ("table"/"create") are surfaced by <ModelActionToolbar> at the page
574
+ // level, so strip them here to avoid a meaningless per-row button.
575
+ const rowMetadata = metadata.actions?.some((a) => a.placement === 'table' || a.placement === 'create')
576
+ ? { ...metadata, actions: metadata.actions.filter((a) => !a.placement || a.placement === 'row') }
577
+ : metadata
578
+ const baseColumns = getDynamicColumns(rowMetadata, handleInternalAction, t, i18n.language, columnFilterConfigs)
573
579
  const filteredBase = baseColumns.filter((col: ColumnDef<any>) => !hiddenColumns.includes(col.id as string))
574
580
  const actionsCol = filteredBase.find((c: ColumnDef<any>) => c.id === 'actions')
575
581
  const otherCols = filteredBase.filter((c: ColumnDef<any>) => c.id !== 'actions')
package/src/index.ts CHANGED
@@ -11,6 +11,12 @@ export {
11
11
  ActionModalDispatcher,
12
12
  type ActionModalProps,
13
13
  } from './action-modal-dispatcher'
14
+ export {
15
+ ModelActionToolbar,
16
+ useModelActions,
17
+ type ModelActionToolbarProps,
18
+ type ActionPlacement,
19
+ } from './model-action-toolbar'
14
20
  export * from './addon-loader'
15
21
  export {
16
22
  AddonLayoutProvider,
@@ -0,0 +1,154 @@
1
+ // ModelActionToolbar — renders page-level (toolbar) triggers for a model's
2
+ // declarative actions and owns the modal dispatch for them.
3
+ //
4
+ // A model's actions carry a `placement` hint (see manifest/v3 Action.placement):
5
+ // "row" (default) — rendered per-row inside <DynamicTable>'s action column.
6
+ // "table" — a plain toolbar button (no record context).
7
+ // "create" — a primary toolbar button that replaces the generic
8
+ // "create" button, for addons shipping a custom create
9
+ // experience (e.g. a journal entry with debit/credit lines).
10
+ //
11
+ // This component renders the buttons for the placements it's asked to surface
12
+ // (default: table + create) and mounts the <ActionModalDispatcher>, which
13
+ // resolves either a custom federated modal (registered via the action registry)
14
+ // or the generic declarative form. For create-style actions there is no record
15
+ // yet, so the dispatcher receives an empty record `{}`.
16
+ //
17
+ // It is the single generic primitive every host consumes — DynamicCRUDPage uses
18
+ // it internally, and bespoke host pages (e.g. ops `/m/$model`) mount it directly
19
+ // next to their own toolbar. Hosts never reimplement action-button plumbing.
20
+ import { useEffect, useMemo, useState } from 'react'
21
+ import { Button } from '@asteby/metacore-ui/primitives'
22
+ import { useApi } from './api-context'
23
+ import { useMetadataCache } from './metadata-cache'
24
+ import { DynamicIcon } from './dynamic-icon'
25
+ import { ActionModalDispatcher } from './action-modal-dispatcher'
26
+ import type { ActionDefinition, ActionMetadata, TableMetadata } from './types'
27
+
28
+ export type ActionPlacement = 'row' | 'table' | 'create'
29
+
30
+ export interface ModelActionToolbarProps {
31
+ /** Model key as registered on the backend (e.g. "JournalEntry"). */
32
+ model: string
33
+ /** Data endpoint passed to the dispatcher. Defaults to `/data/<model>/me`. */
34
+ endpoint?: string
35
+ /**
36
+ * Pre-fetched action definitions. When omitted the toolbar reads them from
37
+ * the metadata cache, falling back to `/metadata/table/<model>`. Pass this
38
+ * when the host page already holds the metadata to avoid a second fetch.
39
+ */
40
+ actions?: ActionDefinition[]
41
+ /** Which placements to render. Defaults to `['table', 'create']`. */
42
+ placements?: ActionPlacement[]
43
+ /** Fired after an action's modal reports success. */
44
+ onChange?: () => void
45
+ /** Extra classes on the button row container. */
46
+ className?: string
47
+ }
48
+
49
+ const DEFAULT_PLACEMENTS: ActionPlacement[] = ['table', 'create']
50
+
51
+ function toActionMetadata(a: ActionDefinition): ActionMetadata {
52
+ return {
53
+ key: a.key,
54
+ label: a.label,
55
+ icon: a.icon || 'Zap',
56
+ color: a.color,
57
+ confirm: a.confirm,
58
+ confirmMessage: a.confirmMessage,
59
+ fields: a.fields,
60
+ requiresState: a.requiresState,
61
+ executable: a.executable,
62
+ placement: a.placement,
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Returns the model's actions matching the requested placements. Reads from the
68
+ * `actions` prop when provided, else the metadata cache, else fetches once.
69
+ */
70
+ export function useModelActions(
71
+ model: string,
72
+ placements: ActionPlacement[] = DEFAULT_PLACEMENTS,
73
+ provided?: ActionDefinition[],
74
+ ): ActionDefinition[] {
75
+ const api = useApi()
76
+ const cached = useMetadataCache((s) => s.getMetadata(model))
77
+ const [fetched, setFetched] = useState<TableMetadata | null>(null)
78
+
79
+ const haveSource = provided != null || cached != null
80
+ useEffect(() => {
81
+ if (haveSource) return
82
+ let cancelled = false
83
+ api
84
+ .get(`/metadata/table/${model}`)
85
+ .then((res) => {
86
+ if (!cancelled) setFetched((res.data?.data ?? res.data) as TableMetadata)
87
+ })
88
+ .catch(() => {
89
+ if (!cancelled) setFetched(null)
90
+ })
91
+ return () => {
92
+ cancelled = true
93
+ }
94
+ }, [model, haveSource, api])
95
+
96
+ const all = provided ?? cached?.actions ?? fetched?.actions ?? []
97
+ return useMemo(
98
+ () => all.filter((a) => placements.includes((a.placement ?? 'row') as ActionPlacement)),
99
+ [all, placements],
100
+ )
101
+ }
102
+
103
+ export function ModelActionToolbar({
104
+ model,
105
+ endpoint,
106
+ actions,
107
+ placements = DEFAULT_PLACEMENTS,
108
+ onChange,
109
+ className,
110
+ }: ModelActionToolbarProps) {
111
+ const surfaced = useModelActions(model, placements, actions)
112
+ const [active, setActive] = useState<ActionMetadata | null>(null)
113
+ const dataEndpoint = endpoint ?? `/data/${model}/me`
114
+
115
+ if (surfaced.length === 0) return null
116
+
117
+ return (
118
+ <>
119
+ <div className={className ?? 'flex items-center gap-2'}>
120
+ {surfaced.map((a) => {
121
+ const isCreate = (a.placement ?? 'row') === 'create'
122
+ return (
123
+ <Button
124
+ key={a.key}
125
+ variant={isCreate ? 'default' : 'outline'}
126
+ onClick={() => setActive(toActionMetadata(a))}
127
+ style={a.color && !isCreate ? { borderColor: a.color, color: a.color } : undefined}
128
+ >
129
+ <DynamicIcon name={a.icon || (isCreate ? 'Plus' : 'Zap')} className="mr-2 h-4 w-4" />
130
+ {a.label}
131
+ </Button>
132
+ )
133
+ })}
134
+ </div>
135
+
136
+ {active && (
137
+ <ActionModalDispatcher
138
+ open={!!active}
139
+ onOpenChange={(open) => {
140
+ if (!open) setActive(null)
141
+ }}
142
+ action={active}
143
+ model={model}
144
+ record={{}}
145
+ endpoint={dataEndpoint}
146
+ onSuccess={() => {
147
+ setActive(null)
148
+ onChange?.()
149
+ }}
150
+ />
151
+ )}
152
+ </>
153
+ )
154
+ }
package/src/types.ts CHANGED
@@ -162,6 +162,13 @@ export interface ActionDefinition {
162
162
  fields?: ActionFieldDef[]
163
163
  requiresState?: string[]
164
164
  executable?: boolean
165
+ /**
166
+ * Where the host surfaces the trigger. Mirrors manifest/v3 Action.placement.
167
+ * "row" (default) — per-row table action.
168
+ * "table" — page toolbar button (no record context).
169
+ * "create" — toolbar button that replaces the generic create button.
170
+ */
171
+ placement?: 'row' | 'table' | 'create'
165
172
  }
166
173
 
167
174
  export interface ApiResponse<T> {
@@ -194,4 +201,5 @@ export interface ActionMetadata {
194
201
  fields?: ActionFieldDef[]
195
202
  requiresState?: string[]
196
203
  executable?: boolean
204
+ placement?: 'row' | 'table' | 'create'
197
205
  }