@asteby/metacore-runtime-react 18.17.2 → 18.17.3
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-grid.d.ts","sourceRoot":"","sources":["../src/dashboard-grid.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EACR,kBAAkB,EAElB,oBAAoB,EACpB,mBAAmB,EACtB,MAAM,mBAAmB,CAAA;AAW1B,uEAAuE;AACvE,wBAAgB,eAAe,CAC3B,MAAM,CAAC,EAAE,oBAAoB,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,GAChC,oBAAoB,EAAE,CAgBxB;AASD,wBAAgB,aAAa,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,GACV,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"dashboard-grid.d.ts","sourceRoot":"","sources":["../src/dashboard-grid.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EACR,kBAAkB,EAElB,oBAAoB,EACpB,mBAAmB,EACtB,MAAM,mBAAmB,CAAA;AAW1B,uEAAuE;AACvE,wBAAgB,eAAe,CAC3B,MAAM,CAAC,EAAE,oBAAoB,EAAE,EAC/B,OAAO,CAAC,EAAE,mBAAmB,EAAE,GAChC,oBAAoB,EAAE,CAgBxB;AASD,wBAAgB,aAAa,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,GACV,EAAE,kBAAkB,qBAiJpB"}
|
package/dist/dashboard-grid.js
CHANGED
|
@@ -106,15 +106,10 @@ export function DashboardGrid({ groups, widgets, loadData, isAdmin, locale, curr
|
|
|
106
106
|
};
|
|
107
107
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
108
108
|
}, [keySig, loadData]);
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
// ONE unified dense grid across every group. Per-group sections used to
|
|
114
|
-
// break the layout into rows, so a lone-widget group (e.g. a single KPI)
|
|
115
|
-
// left the rest of its row blank. Flattening + `grid-flow-row-dense`
|
|
116
|
-
// backfills those holes; ordering compact KPIs before charts makes the top
|
|
117
|
-
// read as a metric band and the charts mosaic below it. No blank space.
|
|
109
|
+
// Flatten every group into ONE ordered list (compact KPIs before charts) for
|
|
110
|
+
// the masonry grid. MUST run before any early return — it is a hook, and a
|
|
111
|
+
// conditional hook (placed after the empty-state return) trips React #310
|
|
112
|
+
// when the dashboard transitions empty → populated.
|
|
118
113
|
const ordered = React.useMemo(() => {
|
|
119
114
|
const flat = visibleGroups.flatMap((g) => g.widgets);
|
|
120
115
|
return flat
|
|
@@ -123,6 +118,10 @@ export function DashboardGrid({ groups, widgets, loadData, isAdmin, locale, curr
|
|
|
123
118
|
a.i - b.i)
|
|
124
119
|
.map((x) => x.w);
|
|
125
120
|
}, [visibleGroups]);
|
|
121
|
+
// Global empty state (no widgets at all / none visible after gating).
|
|
122
|
+
if (visibleGroups.length === 0) {
|
|
123
|
+
return (_jsxs("div", { "data-testid": "dashboard-empty", className: cn('flex min-h-[40vh] flex-col items-center justify-center rounded-xl border border-dashed border-border/60 p-10 text-center', className), children: [_jsx("div", { className: "mb-4 flex size-14 items-center justify-center rounded-2xl bg-muted text-muted-foreground", children: _jsx(DynamicIcon, { name: "LayoutDashboard", className: "size-7" }) }), _jsx("h3", { className: "text-base font-semibold text-foreground", children: tr(undefined, s.emptyTitle) || s.emptyTitle }), _jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: s.emptyDescription })] }));
|
|
124
|
+
}
|
|
126
125
|
return (_jsx("div", { "data-testid": "dashboard-grid", className: cn(
|
|
127
126
|
// Masonry: balanced CSS columns. Cards take their natural height
|
|
128
127
|
// (compact stats, taller charts) and flow to equalize column
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asteby/metacore-runtime-react",
|
|
3
|
-
"version": "18.17.
|
|
3
|
+
"version": "18.17.3",
|
|
4
4
|
"description": "React runtime for metacore hosts — renders addon contributions dynamically",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"typescript": "^6.0.0",
|
|
65
65
|
"vitest": "^4.0.0",
|
|
66
66
|
"zustand": "^5.0.0",
|
|
67
|
-
"@asteby/metacore-
|
|
68
|
-
"@asteby/metacore-
|
|
67
|
+
"@asteby/metacore-sdk": "3.2.0",
|
|
68
|
+
"@asteby/metacore-ui": "2.5.2"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsc -p tsconfig.json",
|
|
@@ -155,6 +155,24 @@ describe('DashboardGrid render', () => {
|
|
|
155
155
|
render(<DashboardGrid widgets={[]} loadData={loaderOf({})} />)
|
|
156
156
|
expect(screen.getByTestId('dashboard-empty')).toBeTruthy()
|
|
157
157
|
})
|
|
158
|
+
|
|
159
|
+
it('survives an empty → populated transition (React #310 regression)', async () => {
|
|
160
|
+
// The flatten/order useMemo must run BEFORE the empty-state early return.
|
|
161
|
+
// When it sat after the return, an empty render called one fewer hook
|
|
162
|
+
// than the populated render → "Rendered more hooks" (React #310) crash.
|
|
163
|
+
const { rerender } = render(
|
|
164
|
+
<DashboardGrid widgets={[]} loadData={loaderOf({})} />,
|
|
165
|
+
)
|
|
166
|
+
expect(screen.getByTestId('dashboard-empty')).toBeTruthy()
|
|
167
|
+
rerender(
|
|
168
|
+
<DashboardGrid
|
|
169
|
+
widgets={[spec({ key: 'rev', kind: 'stat' })]}
|
|
170
|
+
loadData={loaderOf({ rev: { value: 5 } })}
|
|
171
|
+
/>,
|
|
172
|
+
)
|
|
173
|
+
await waitFor(() => expect(screen.getByTestId('widget-rev')).toBeTruthy())
|
|
174
|
+
expect(screen.getByText('5')).toBeTruthy()
|
|
175
|
+
})
|
|
158
176
|
})
|
|
159
177
|
|
|
160
178
|
describe('DashboardGrid permission gating', () => {
|
package/src/dashboard-grid.tsx
CHANGED
|
@@ -132,6 +132,22 @@ export function DashboardGrid({
|
|
|
132
132
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
133
|
}, [keySig, loadData])
|
|
134
134
|
|
|
135
|
+
// Flatten every group into ONE ordered list (compact KPIs before charts) for
|
|
136
|
+
// the masonry grid. MUST run before any early return — it is a hook, and a
|
|
137
|
+
// conditional hook (placed after the empty-state return) trips React #310
|
|
138
|
+
// when the dashboard transitions empty → populated.
|
|
139
|
+
const ordered = React.useMemo(() => {
|
|
140
|
+
const flat = visibleGroups.flatMap((g) => g.widgets)
|
|
141
|
+
return flat
|
|
142
|
+
.map((w, i) => ({ w, i }))
|
|
143
|
+
.sort(
|
|
144
|
+
(a, b) =>
|
|
145
|
+
(isTallWidget(a.w) ? 1 : 0) - (isTallWidget(b.w) ? 1 : 0) ||
|
|
146
|
+
a.i - b.i,
|
|
147
|
+
)
|
|
148
|
+
.map((x) => x.w)
|
|
149
|
+
}, [visibleGroups])
|
|
150
|
+
|
|
135
151
|
// Global empty state (no widgets at all / none visible after gating).
|
|
136
152
|
if (visibleGroups.length === 0) {
|
|
137
153
|
return (
|
|
@@ -155,23 +171,6 @@ export function DashboardGrid({
|
|
|
155
171
|
)
|
|
156
172
|
}
|
|
157
173
|
|
|
158
|
-
// ONE unified dense grid across every group. Per-group sections used to
|
|
159
|
-
// break the layout into rows, so a lone-widget group (e.g. a single KPI)
|
|
160
|
-
// left the rest of its row blank. Flattening + `grid-flow-row-dense`
|
|
161
|
-
// backfills those holes; ordering compact KPIs before charts makes the top
|
|
162
|
-
// read as a metric band and the charts mosaic below it. No blank space.
|
|
163
|
-
const ordered = React.useMemo(() => {
|
|
164
|
-
const flat = visibleGroups.flatMap((g) => g.widgets)
|
|
165
|
-
return flat
|
|
166
|
-
.map((w, i) => ({ w, i }))
|
|
167
|
-
.sort(
|
|
168
|
-
(a, b) =>
|
|
169
|
-
(isTallWidget(a.w) ? 1 : 0) - (isTallWidget(b.w) ? 1 : 0) ||
|
|
170
|
-
a.i - b.i,
|
|
171
|
-
)
|
|
172
|
-
.map((x) => x.w)
|
|
173
|
-
}, [visibleGroups])
|
|
174
|
-
|
|
175
174
|
return (
|
|
176
175
|
<div
|
|
177
176
|
data-testid="dashboard-grid"
|