@asteby/metacore-runtime-react 12.0.0 → 13.1.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 +28 -0
- package/dist/addon-loader.d.ts.map +1 -1
- package/dist/addon-loader.js +49 -10
- package/dist/dynamic-crud-page.d.ts.map +1 -1
- package/dist/dynamic-crud-page.js +6 -2
- package/dist/dynamic-table.d.ts.map +1 -1
- package/dist/dynamic-table.js +7 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/model-action-toolbar.d.ts +27 -0
- package/dist/model-action-toolbar.d.ts.map +1 -0
- package/dist/model-action-toolbar.js +88 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/addon-loader.tsx +55 -11
- package/src/dynamic-crud-page.tsx +11 -1
- package/src/dynamic-table.tsx +7 -1
- package/src/index.ts +6 -0
- package/src/model-action-toolbar.tsx +154 -0
- package/src/types.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @asteby/metacore-runtime-react
|
|
2
2
|
|
|
3
|
+
## 13.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8d9c602: AddonLoader carga remotes de federación ESM vía `import()` dinámico (fix "Cannot use import statement outside a module").
|
|
8
|
+
|
|
9
|
+
Los remotes built con Vite/@originjs `format:"esm"` (el estándar de `metacoreFederationShared`) son módulos ES que hacen `import` top-level y exportan `{ init, get }` — DEBEN cargarse como módulo. El `AddonLoader` los inyectaba como `<script>` clásico → el browser tiraba `Cannot use import statement outside a module` y la UI federada nunca cargaba. Ahora hace `import()` dinámico (vía `new Function` para que ningún bundler reescriba el import del URL externo) y usa el namespace del módulo como container; los remotes legacy "var"/window siguen soportados con fallback a `<script>` + `window[scope]`.
|
|
10
|
+
|
|
11
|
+
## 13.0.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 7ea7caa: Acciones con `placement` (`row` | `table` | `create`) y nuevo primitivo `<ModelActionToolbar>`.
|
|
16
|
+
|
|
17
|
+
`ActionMetadata`/`ActionDefinition` ganan `placement`, espejando `manifest/v3` Action.placement del kernel (v0.30.0):
|
|
18
|
+
- `row` (default) — acción por fila dentro de `<DynamicTable>` (comportamiento actual).
|
|
19
|
+
- `table` — botón en la toolbar de la página, sin contexto de record.
|
|
20
|
+
- `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).
|
|
21
|
+
|
|
22
|
+
`<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.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [7ea7caa]
|
|
27
|
+
- Updated dependencies [3b40ed5]
|
|
28
|
+
- @asteby/metacore-sdk@3.1.0
|
|
29
|
+
- @asteby/metacore-ui@2.1.0
|
|
30
|
+
|
|
3
31
|
## 12.0.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"addon-loader.d.ts","sourceRoot":"","sources":["../src/addon-loader.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;QAClB,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3D,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACrD;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,GAAG,EAAE,QAAQ,CAAA;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;IAC9B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC7B;AAqFD,wBAAgB,WAAW,CAAC,EACxB,KAAK,EACL,GAAG,EACH,MAAqB,EACrB,GAAG,EACH,QAAe,EACf,OAAO,EACP,OAAO,EACP,MAAM,EACN,QAAQ,GACX,EAAE,gBAAgB,2CAoClB"}
|
package/dist/addon-loader.js
CHANGED
|
@@ -4,6 +4,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
4
4
|
// addon's `register(api)` export with the AddonAPI injected by the host.
|
|
5
5
|
import { useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { useDeclareAddonLayout } from './addon-layout-context';
|
|
7
|
+
// Runtime dynamic import of an external URL. Wrapped in `new Function` so no
|
|
8
|
+
// build tool (tsc here, Vite in the consuming host) tries to statically
|
|
9
|
+
// analyse or rewrite the import — it stays a genuine runtime ESM fetch.
|
|
10
|
+
const importModule = new Function('u', 'return import(u)');
|
|
7
11
|
const loadedScripts = new Map();
|
|
8
12
|
function loadScript(url, scope) {
|
|
9
13
|
const key = `${scope}::${url}`;
|
|
@@ -22,15 +26,53 @@ function loadScript(url, scope) {
|
|
|
22
26
|
loadedScripts.set(key, promise);
|
|
23
27
|
return promise;
|
|
24
28
|
}
|
|
25
|
-
|
|
29
|
+
const esmContainers = new Map();
|
|
30
|
+
// Resolve a federation container for the remote. Vite/@originjs remotes built
|
|
31
|
+
// with `format:"esm"` (our standard) are ES modules that top-level `import`
|
|
32
|
+
// their preload helper and export `{ init, get }` — they MUST be loaded as a
|
|
33
|
+
// module (a classic <script> throws "Cannot use import statement outside a
|
|
34
|
+
// module"), so we dynamic-import them and use the module namespace as the
|
|
35
|
+
// container. Legacy "var"/window remotes (which assign `window[scope]`) are
|
|
36
|
+
// still supported via the classic <script> fallback.
|
|
37
|
+
async function resolveContainer(scope, url) {
|
|
38
|
+
const key = `${scope}::${url}`;
|
|
39
|
+
const cached = esmContainers.get(key);
|
|
40
|
+
if (cached)
|
|
41
|
+
return cached;
|
|
42
|
+
const p = (async () => {
|
|
43
|
+
try {
|
|
44
|
+
const mod = await importModule(url);
|
|
45
|
+
if (mod && typeof mod.init === 'function' && typeof mod.get === 'function') {
|
|
46
|
+
return mod;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Not an importable module (legacy var-format remote) — fall back.
|
|
51
|
+
}
|
|
52
|
+
await loadScript(url, scope);
|
|
53
|
+
return window[scope];
|
|
54
|
+
})();
|
|
55
|
+
esmContainers.set(key, p);
|
|
56
|
+
p.catch(() => esmContainers.delete(key));
|
|
57
|
+
return p;
|
|
58
|
+
}
|
|
59
|
+
async function loadRemote(scope, url, module) {
|
|
26
60
|
if (typeof window.__webpack_init_sharing__ === 'function') {
|
|
27
61
|
await window.__webpack_init_sharing__('default');
|
|
28
62
|
}
|
|
29
|
-
const container =
|
|
30
|
-
if (!container)
|
|
31
|
-
throw new Error(`Addon container "${scope}" not found
|
|
32
|
-
|
|
33
|
-
|
|
63
|
+
const container = await resolveContainer(scope, url);
|
|
64
|
+
if (!container) {
|
|
65
|
+
throw new Error(`Addon container "${scope}" not found (neither ESM export nor window[scope])`);
|
|
66
|
+
}
|
|
67
|
+
if (typeof container.init === 'function') {
|
|
68
|
+
const shareScope = window.__webpack_share_scopes__?.default ??
|
|
69
|
+
(window.__METACORE_SHARE_SCOPE__ ??= {});
|
|
70
|
+
try {
|
|
71
|
+
await container.init(shareScope);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Container already initialized (re-entrant load) — safe to ignore.
|
|
75
|
+
}
|
|
34
76
|
}
|
|
35
77
|
const factory = await container.get(module);
|
|
36
78
|
return factory();
|
|
@@ -48,10 +90,7 @@ export function AddonLoader({ scope, url, module = './register', api, fallback =
|
|
|
48
90
|
let cancelled = false;
|
|
49
91
|
(async () => {
|
|
50
92
|
try {
|
|
51
|
-
await
|
|
52
|
-
if (cancelled)
|
|
53
|
-
return;
|
|
54
|
-
const mod = await loadRemote(scope, module);
|
|
93
|
+
const mod = await loadRemote(scope, url, module);
|
|
55
94
|
if (cancelled)
|
|
56
95
|
return;
|
|
57
96
|
if (!didRegister.current && typeof mod?.register === 'function') {
|
|
@@ -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;
|
|
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
|
-
|
|
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,
|
|
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"}
|
package/dist/dynamic-table.js
CHANGED
|
@@ -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
|
-
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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": "
|
|
3
|
+
"version": "13.1.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.
|
|
36
|
-
"@asteby/metacore-ui": "^2.0
|
|
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.
|
|
64
|
-
"@asteby/metacore-ui": "2.0
|
|
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",
|
package/src/addon-loader.tsx
CHANGED
|
@@ -43,6 +43,18 @@ export interface AddonLoaderProps {
|
|
|
43
43
|
children?: React.ReactNode
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
interface FederationContainer {
|
|
47
|
+
init: (shareScope: unknown) => Promise<void>
|
|
48
|
+
get: (module: string) => Promise<() => any>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Runtime dynamic import of an external URL. Wrapped in `new Function` so no
|
|
52
|
+
// build tool (tsc here, Vite in the consuming host) tries to statically
|
|
53
|
+
// analyse or rewrite the import — it stays a genuine runtime ESM fetch.
|
|
54
|
+
const importModule = new Function('u', 'return import(u)') as (
|
|
55
|
+
u: string,
|
|
56
|
+
) => Promise<Record<string, unknown>>
|
|
57
|
+
|
|
46
58
|
const loadedScripts = new Map<string, Promise<void>>()
|
|
47
59
|
|
|
48
60
|
function loadScript(url: string, scope: string): Promise<void> {
|
|
@@ -62,19 +74,53 @@ function loadScript(url: string, scope: string): Promise<void> {
|
|
|
62
74
|
return promise
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
const esmContainers = new Map<string, Promise<FederationContainer | undefined>>()
|
|
78
|
+
|
|
79
|
+
// Resolve a federation container for the remote. Vite/@originjs remotes built
|
|
80
|
+
// with `format:"esm"` (our standard) are ES modules that top-level `import`
|
|
81
|
+
// their preload helper and export `{ init, get }` — they MUST be loaded as a
|
|
82
|
+
// module (a classic <script> throws "Cannot use import statement outside a
|
|
83
|
+
// module"), so we dynamic-import them and use the module namespace as the
|
|
84
|
+
// container. Legacy "var"/window remotes (which assign `window[scope]`) are
|
|
85
|
+
// still supported via the classic <script> fallback.
|
|
86
|
+
async function resolveContainer(scope: string, url: string): Promise<FederationContainer | undefined> {
|
|
87
|
+
const key = `${scope}::${url}`
|
|
88
|
+
const cached = esmContainers.get(key)
|
|
89
|
+
if (cached) return cached
|
|
90
|
+
const p = (async () => {
|
|
91
|
+
try {
|
|
92
|
+
const mod = await importModule(url)
|
|
93
|
+
if (mod && typeof mod.init === 'function' && typeof mod.get === 'function') {
|
|
94
|
+
return mod as unknown as FederationContainer
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// Not an importable module (legacy var-format remote) — fall back.
|
|
98
|
+
}
|
|
99
|
+
await loadScript(url, scope)
|
|
100
|
+
return (window as any)[scope] as FederationContainer | undefined
|
|
101
|
+
})()
|
|
102
|
+
esmContainers.set(key, p)
|
|
103
|
+
p.catch(() => esmContainers.delete(key))
|
|
104
|
+
return p
|
|
68
105
|
}
|
|
69
106
|
|
|
70
|
-
async function loadRemote(scope: string, module: string) {
|
|
107
|
+
async function loadRemote(scope: string, url: string, module: string) {
|
|
71
108
|
if (typeof window.__webpack_init_sharing__ === 'function') {
|
|
72
109
|
await window.__webpack_init_sharing__('default')
|
|
73
110
|
}
|
|
74
|
-
const container =
|
|
75
|
-
if (!container)
|
|
76
|
-
|
|
77
|
-
|
|
111
|
+
const container = await resolveContainer(scope, url)
|
|
112
|
+
if (!container) {
|
|
113
|
+
throw new Error(`Addon container "${scope}" not found (neither ESM export nor window[scope])`)
|
|
114
|
+
}
|
|
115
|
+
if (typeof container.init === 'function') {
|
|
116
|
+
const shareScope =
|
|
117
|
+
window.__webpack_share_scopes__?.default ??
|
|
118
|
+
((window as any).__METACORE_SHARE_SCOPE__ ??= {})
|
|
119
|
+
try {
|
|
120
|
+
await container.init(shareScope)
|
|
121
|
+
} catch {
|
|
122
|
+
// Container already initialized (re-entrant load) — safe to ignore.
|
|
123
|
+
}
|
|
78
124
|
}
|
|
79
125
|
const factory = await container.get(module)
|
|
80
126
|
return factory()
|
|
@@ -105,9 +151,7 @@ export function AddonLoader({
|
|
|
105
151
|
let cancelled = false
|
|
106
152
|
;(async () => {
|
|
107
153
|
try {
|
|
108
|
-
await
|
|
109
|
-
if (cancelled) return
|
|
110
|
-
const mod = await loadRemote(scope, module)
|
|
154
|
+
const mod = await loadRemote(scope, url, module)
|
|
111
155
|
if (cancelled) return
|
|
112
156
|
if (!didRegister.current && typeof mod?.register === 'function') {
|
|
113
157
|
didRegister.current = true
|
|
@@ -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
|
-
|
|
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'
|
package/src/dynamic-table.tsx
CHANGED
|
@@ -569,7 +569,13 @@ export function DynamicTable({
|
|
|
569
569
|
|
|
570
570
|
const columns = useMemo(() => {
|
|
571
571
|
if (!metadata) return []
|
|
572
|
-
|
|
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
|
}
|