@zerotal/admin 1.8.1 → 1.10.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 +22 -0
- package/api-surface.md +42 -97
- package/package.json +9 -9
- package/src/Cluster.ts +5 -1
- package/src/PanelInstance.ts +2 -0
- package/src/Resource.ts +5 -4
- package/src/actions/Action.ts +5 -1
- package/src/actions/render.tsx +9 -1
- package/src/auth/AuthLayout.tsx +1 -0
- package/src/authRoles.ts +2 -2
- package/src/config.ts +35 -26
- package/src/databaseMedia.ts +2 -2
- package/src/databaseNotifications.ts +5 -4
- package/src/form/ResourceForm.ts +1 -0
- package/src/history.ts +2 -2
- package/src/media.ts +4 -4
- package/src/pages/AdminPage.ts +2 -0
- package/src/pages/ConsolePage.tsx +3 -0
- package/src/pages/DashboardPage.tsx +5 -1
- package/src/pages/MediaPage.tsx +6 -1
- package/src/pages/NotificationsPage.tsx +6 -1
- package/src/pages/RecordViewPage.tsx +3 -0
- package/src/pages/ResourceFormPage.tsx +6 -1
- package/src/pages/ResourceListPage.tsx +3 -0
- package/src/pages/RolesPage.tsx +6 -1
- package/src/pages/SearchPage.tsx +5 -1
- package/src/plugin.ts +4 -0
- package/src/table/Column.ts +1 -0
- package/src/theme.ts +4 -4
- package/src/ui/AdminLayout.tsx +3 -0
- package/src/widgets/render.tsx +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,28 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.9.0] — 2026-08-29
|
|
12
|
+
|
|
13
|
+
### Documented
|
|
14
|
+
|
|
15
|
+
- **Every promised export is documented.** The `docs-coverage` gate reads `maturity: stable` as a
|
|
16
|
+
promise about a package's exports, and measures how much of that promise is written down. It
|
|
17
|
+
was 798 gaps across the suite; it is now zero. This package's share is covered on its own
|
|
18
|
+
pages — types named, options shapes described, and the decisions behind them recorded where
|
|
19
|
+
somebody looking for them will find them.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **INTERNAL: the panel's own page machinery is marked `@internal`.** The `make*Page` factories,
|
|
24
|
+
the page classes they produce (`ResourceListPage`, `RecordViewPage`, `ResourceFormPage`,
|
|
25
|
+
`RolesPage`, `MediaPage`, `NotificationsPage`, `ConsolePage`, …), the action and widget
|
|
26
|
+
renderers, and `AdminPanelHost`.
|
|
27
|
+
|
|
28
|
+
**Nothing is removed and nothing breaks** — they are still exported and still work. An app
|
|
29
|
+
declares a `Resource` and the panel builds these pages from it; none of them has a caller
|
|
30
|
+
outside `@zerotal/admin` or a line in the guide, because writing one was never the way to use
|
|
31
|
+
this package.
|
|
32
|
+
|
|
11
33
|
## [1.6.0] — 2026-08-15
|
|
12
34
|
|
|
13
35
|
### Fixed
|
package/api-surface.md
CHANGED
|
@@ -1114,38 +1114,32 @@ interface ActionContext = {
|
|
|
1114
1114
|
slug: string
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
|
-
interface ActionPage = {
|
|
1118
|
-
download?: (filename: string, content: string | Uint8Array, mime?: string) => unknown
|
|
1119
|
-
flash: (message: string, level?: string) => unknown
|
|
1120
|
-
redirect: (url: string) => { withSuccess(message: string): unknown;}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
1117
|
interface AdminAuthConfig = {
|
|
1124
|
-
authMiddleware?: MiddlewareClass[]
|
|
1125
|
-
authenticateWhen?: (user: unknown) => boolean | Promise<boolean>
|
|
1126
|
-
emailVerification?: { isVerified(user: unknown): boolean; resend(user: unknown): Promise<void> | void;}
|
|
1127
|
-
enabled?: boolean
|
|
1128
|
-
guestMiddleware?: MiddlewareClass[]
|
|
1129
|
-
heading?: string
|
|
1130
|
-
identifier?: string
|
|
1131
|
-
loginPath?: string
|
|
1132
|
-
passwordReset?: { sendResetLink(email: string): Promise<boolean> | boolean; reset(input: { email: string; token: string; password: string; }): Promise<boolean> | boolean;}
|
|
1133
|
-
profilePath?: string
|
|
1134
|
-
redirectTo?: string
|
|
1135
|
-
remember?: boolean
|
|
1136
|
-
updateProfile?: (user: unknown, data: Record<string, unknown>) => Promise<void> | void
|
|
1118
|
+
authMiddleware?: MiddlewareClass[] | undefined
|
|
1119
|
+
authenticateWhen?: ((user: unknown) => boolean | Promise<boolean>) | undefined
|
|
1120
|
+
emailVerification?: { isVerified(user: unknown): boolean; resend(user: unknown): Promise<void> | void;} | undefined
|
|
1121
|
+
enabled?: boolean | undefined
|
|
1122
|
+
guestMiddleware?: MiddlewareClass[] | undefined
|
|
1123
|
+
heading?: string | undefined
|
|
1124
|
+
identifier?: string | undefined
|
|
1125
|
+
loginPath?: string | undefined
|
|
1126
|
+
passwordReset?: { sendResetLink(email: string): Promise<boolean> | boolean; reset(input: { email: string; token: string; password: string; }): Promise<boolean> | boolean;} | undefined
|
|
1127
|
+
profilePath?: string | undefined
|
|
1128
|
+
redirectTo?: string | undefined
|
|
1129
|
+
remember?: boolean | undefined
|
|
1130
|
+
updateProfile?: ((user: unknown, data: Record<string, unknown>) => Promise<void> | void) | undefined
|
|
1137
1131
|
}
|
|
1138
1132
|
|
|
1139
1133
|
interface AdminConfigShape = {
|
|
1140
|
-
auth?: AdminAuthConfig
|
|
1141
|
-
authorize?: AdminAuthorizer
|
|
1134
|
+
auth?: AdminAuthConfig | undefined
|
|
1135
|
+
authorize?: AdminAuthorizer | undefined
|
|
1142
1136
|
brand: string
|
|
1143
|
-
middleware?: MiddlewareClass[]
|
|
1137
|
+
middleware?: MiddlewareClass[] | undefined
|
|
1144
1138
|
path: string
|
|
1145
|
-
plugins?: Record<string, boolean>
|
|
1146
|
-
tagline?: string
|
|
1147
|
-
theme?: AdminThemeConfig
|
|
1148
|
-
userMenu?: UserMenu
|
|
1139
|
+
plugins?: Record<string, boolean> | undefined
|
|
1140
|
+
tagline?: string | undefined
|
|
1141
|
+
theme?: AdminThemeConfig | undefined
|
|
1142
|
+
userMenu?: UserMenu | undefined
|
|
1149
1143
|
}
|
|
1150
1144
|
|
|
1151
1145
|
interface AdminModel = {
|
|
@@ -1170,18 +1164,6 @@ interface AdminNotification = {
|
|
|
1170
1164
|
title: string
|
|
1171
1165
|
}
|
|
1172
1166
|
|
|
1173
|
-
interface AdminPanelHost = {
|
|
1174
|
-
console: (contribution: ConsoleContribution) => void
|
|
1175
|
-
enabled: (id: string) => boolean
|
|
1176
|
-
navItem: (contribution: NavContribution) => void
|
|
1177
|
-
page: (contribution: PageContribution) => void
|
|
1178
|
-
renderHook: (name: string, hook: (context: RenderHookContext) => HtmlNode | string | null) => void
|
|
1179
|
-
searchProvider: (provider: PanelSearchProvider) => void
|
|
1180
|
-
topbarSlot: (slot: TopbarSlot) => void
|
|
1181
|
-
userMenuItem: (item: UserMenuContribution) => void
|
|
1182
|
-
widget: (contribution: WidgetContribution) => void
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
1167
|
interface AdminPlugin = {
|
|
1186
1168
|
id: string
|
|
1187
1169
|
install: (panel: AdminPanelHost) => void | Promise<void>
|
|
@@ -1220,15 +1202,15 @@ interface AdminRecord = {
|
|
|
1220
1202
|
}
|
|
1221
1203
|
|
|
1222
1204
|
interface AdminThemeConfig = {
|
|
1223
|
-
cdn?: boolean
|
|
1224
|
-
noFonts?: boolean
|
|
1225
|
-
stylesheet?: string
|
|
1226
|
-
tokensCss?: string
|
|
1205
|
+
cdn?: boolean | undefined
|
|
1206
|
+
noFonts?: boolean | undefined
|
|
1207
|
+
stylesheet?: string | undefined
|
|
1208
|
+
tokensCss?: string | undefined
|
|
1227
1209
|
}
|
|
1228
1210
|
|
|
1229
1211
|
interface AuthRolesOptions = {
|
|
1230
|
-
guard?: string
|
|
1231
|
-
superusers?: string[]
|
|
1212
|
+
guard?: string | undefined
|
|
1213
|
+
superusers?: string[] | undefined
|
|
1232
1214
|
}
|
|
1233
1215
|
|
|
1234
1216
|
interface ChartData = {
|
|
@@ -1330,15 +1312,15 @@ interface DashboardLayoutStore = {
|
|
|
1330
1312
|
}
|
|
1331
1313
|
|
|
1332
1314
|
interface DatabaseMediaOptions = {
|
|
1333
|
-
columns?: Partial<Record<keyof MediaItem, string>>
|
|
1334
|
-
table?: string
|
|
1315
|
+
columns?: Partial<Record<keyof MediaItem, string>> | undefined
|
|
1316
|
+
table?: string | undefined
|
|
1335
1317
|
}
|
|
1336
1318
|
|
|
1337
1319
|
interface DatabaseNotificationOptions = {
|
|
1338
|
-
limit?: number
|
|
1339
|
-
notifiable?: () => Promise<unknown> | unknown
|
|
1340
|
-
present?: (row: StoredNotification, data: Record<string, unknown>) => AdminNotification
|
|
1341
|
-
table?: string
|
|
1320
|
+
limit?: number | undefined
|
|
1321
|
+
notifiable?: (() => Promise<unknown> | unknown) | undefined
|
|
1322
|
+
present?: ((row: StoredNotification, data: Record<string, unknown>) => AdminNotification) | undefined
|
|
1323
|
+
table?: string | undefined
|
|
1342
1324
|
}
|
|
1343
1325
|
|
|
1344
1326
|
interface EmptyState = {
|
|
@@ -1396,8 +1378,8 @@ interface HistoryEntry = {
|
|
|
1396
1378
|
|
|
1397
1379
|
interface HistoryOptions = {
|
|
1398
1380
|
id: unknown
|
|
1399
|
-
limit?: number
|
|
1400
|
-
resolveActor?: (id: number) => Promise<string | null> | string | null
|
|
1381
|
+
limit?: number | undefined
|
|
1382
|
+
resolveActor?: ((id: number) => Promise<string | null> | string | null) | undefined
|
|
1401
1383
|
type: string
|
|
1402
1384
|
}
|
|
1403
1385
|
|
|
@@ -1414,12 +1396,12 @@ interface ImportResult = {
|
|
|
1414
1396
|
}
|
|
1415
1397
|
|
|
1416
1398
|
interface ListOptions = {
|
|
1417
|
-
modifyQuery?: QueryModifier
|
|
1418
|
-
page?: number
|
|
1419
|
-
perPage?: number
|
|
1399
|
+
modifyQuery?: QueryModifier | undefined
|
|
1400
|
+
page?: number | undefined
|
|
1401
|
+
perPage?: number | undefined
|
|
1420
1402
|
search?: string | undefined
|
|
1421
1403
|
sortBy?: string | undefined
|
|
1422
|
-
sortDir?: 'asc' | 'desc'
|
|
1404
|
+
sortDir?: 'asc' | 'desc' | undefined
|
|
1423
1405
|
thenSort?: readonly { column: string; direction: 'asc' | 'desc';}[] | undefined
|
|
1424
1406
|
trashed?: 'with' | 'only' | undefined
|
|
1425
1407
|
}
|
|
@@ -1492,22 +1474,6 @@ interface PageContribution = {
|
|
|
1492
1474
|
title: string
|
|
1493
1475
|
}
|
|
1494
1476
|
|
|
1495
|
-
interface PanelPage = {
|
|
1496
|
-
ability: string | undefined
|
|
1497
|
-
cluster?: typeof Cluster | undefined
|
|
1498
|
-
navigationBadge?: (() => Promise<string | number | null> | string | number | null) | undefined
|
|
1499
|
-
navigationBadgeColor?: BadgeTone | undefined
|
|
1500
|
-
navigationGroup: string | undefined
|
|
1501
|
-
navigationIcon: string
|
|
1502
|
-
navigationLabel: string
|
|
1503
|
-
navigationSort: number
|
|
1504
|
-
page: PanelPageClass
|
|
1505
|
-
routeParams: string[]
|
|
1506
|
-
showInNavigation: boolean
|
|
1507
|
-
slug: string
|
|
1508
|
-
title: string
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
1477
|
interface PanelSearchProvider = {
|
|
1512
1478
|
ability: string
|
|
1513
1479
|
icon?: string
|
|
@@ -1527,19 +1493,6 @@ interface PivotColumn = {
|
|
|
1527
1493
|
label?: string
|
|
1528
1494
|
}
|
|
1529
1495
|
|
|
1530
|
-
interface RecordPage = {
|
|
1531
|
-
lastPage: number
|
|
1532
|
-
page: number
|
|
1533
|
-
perPage: number
|
|
1534
|
-
rows: Record<string, unknown>[]
|
|
1535
|
-
total: number
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
interface RenderableCell = {
|
|
1539
|
-
badge?: BadgeTone | undefined
|
|
1540
|
-
text: string
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
1496
|
interface RenderHookContext = {
|
|
1544
1497
|
page?: 'list' | 'record' | 'form' | 'dashboard' | undefined
|
|
1545
1498
|
recordId?: string | undefined
|
|
@@ -1597,10 +1550,10 @@ interface StoredNotification = {
|
|
|
1597
1550
|
}
|
|
1598
1551
|
|
|
1599
1552
|
interface StoreMediaOptions = {
|
|
1600
|
-
accept?: string[]
|
|
1601
|
-
disk?: string
|
|
1602
|
-
folder?: string
|
|
1603
|
-
maxBytes?: number
|
|
1553
|
+
accept?: string[] | undefined
|
|
1554
|
+
disk?: string | undefined
|
|
1555
|
+
folder?: string | undefined
|
|
1556
|
+
maxBytes?: number | undefined
|
|
1604
1557
|
provider: MediaProvider
|
|
1605
1558
|
}
|
|
1606
1559
|
|
|
@@ -1663,8 +1616,6 @@ type ActionVisible = (record: AdminRecord | undefined, ctx: ActionContext) => bo
|
|
|
1663
1616
|
|
|
1664
1617
|
type AdminAuthorizer = (ability: string) => boolean | Promise<boolean>
|
|
1665
1618
|
|
|
1666
|
-
type AdminPageClass = typeof AdminPage & (new () => AdminPage)
|
|
1667
|
-
|
|
1668
1619
|
type BadgeTone = 'default' | 'primary' | 'success' | 'muted' | 'destructive'
|
|
1669
1620
|
|
|
1670
1621
|
type CalloutTone = 'default' | 'primary' | 'success' | 'warning' | 'destructive'
|
|
@@ -1675,8 +1626,6 @@ type ChartResolver = () => Promise<ChartData> | ChartData
|
|
|
1675
1626
|
|
|
1676
1627
|
type ChartType = 'line' | 'bar' | 'doughnut' | 'pie'
|
|
1677
1628
|
|
|
1678
|
-
type ClusterClass = typeof Cluster
|
|
1679
|
-
|
|
1680
1629
|
type ColumnKind = 'text' | 'toggle' | 'select' | 'input' | 'image' | 'color' | 'icon'
|
|
1681
1630
|
|
|
1682
1631
|
type Conjunction = 'and' | 'or'
|
|
@@ -1711,8 +1660,6 @@ type FormComponent = FormSection | FormTabs | Wizard | FormSplit | Callout | Pri
|
|
|
1711
1660
|
|
|
1712
1661
|
type InfolistComponent = Section | Entry
|
|
1713
1662
|
|
|
1714
|
-
type PanelPageClass = new () => object
|
|
1715
|
-
|
|
1716
1663
|
type PrimeKind = 'text' | 'html' | 'image'
|
|
1717
1664
|
|
|
1718
1665
|
type QueryModifier = (query: AdminQuery) => AdminQuery
|
|
@@ -1727,8 +1674,6 @@ type RenderHookName = 'body.start' | 'body.end' | 'sidebar.start' | 'sidebar.end
|
|
|
1727
1674
|
|
|
1728
1675
|
type ResourceClass = typeof Resource
|
|
1729
1676
|
|
|
1730
|
-
type ResourceFormClass = new () => Form & Record<string, unknown>
|
|
1731
|
-
|
|
1732
1677
|
type StatsResolver = () => Promise<Stat[]> | Stat[]
|
|
1733
1678
|
|
|
1734
1679
|
type SummaryKind = 'sum' | 'avg' | 'count' | 'min' | 'max' | 'range'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/admin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -32,16 +32,16 @@
|
|
|
32
32
|
"typecheck": "tsc --noEmit"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zerotal/cache": "1.
|
|
36
|
-
"@zerotal/core": "1.
|
|
37
|
-
"@zerotal/flow": "1.
|
|
38
|
-
"@zerotal/flow-ui": "1.
|
|
39
|
-
"@zerotal/validator": "1.
|
|
35
|
+
"@zerotal/cache": "1.10.0",
|
|
36
|
+
"@zerotal/core": "1.10.0",
|
|
37
|
+
"@zerotal/flow": "1.10.0",
|
|
38
|
+
"@zerotal/flow-ui": "1.10.0",
|
|
39
|
+
"@zerotal/validator": "1.10.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@zerotal/orm": "1.
|
|
43
|
-
"@zerotal/auth": "1.
|
|
44
|
-
"@zerotal/queue": "1.
|
|
42
|
+
"@zerotal/orm": "1.10.0",
|
|
43
|
+
"@zerotal/auth": "1.10.0",
|
|
44
|
+
"@zerotal/queue": "1.10.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@zerotal/orm": "^1.0.0",
|
package/src/Cluster.ts
CHANGED
|
@@ -46,5 +46,9 @@ export abstract class Cluster {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* A Cluster subclass (used by its static surface — never instantiated).
|
|
51
|
+
*
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
50
54
|
export type ClusterClass = typeof Cluster;
|
package/src/PanelInstance.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface NavGroup {
|
|
|
72
72
|
* A registered custom page, normalized from either door — an {@link AdminPage}
|
|
73
73
|
* subclass registered by the app, or a {@link PageContribution} pushed in by a
|
|
74
74
|
* package.
|
|
75
|
+
*
|
|
76
|
+
* @internal
|
|
75
77
|
*/
|
|
76
78
|
export interface PanelPage {
|
|
77
79
|
slug: string;
|
package/src/Resource.ts
CHANGED
|
@@ -114,6 +114,7 @@ export interface EmptyState {
|
|
|
114
114
|
actions?: ActionItem[];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/** @internal */
|
|
117
118
|
export interface RecordPage {
|
|
118
119
|
rows: Record<string, unknown>[];
|
|
119
120
|
total: number;
|
|
@@ -123,13 +124,13 @@ export interface RecordPage {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
export interface ListOptions {
|
|
126
|
-
page?: number;
|
|
127
|
-
perPage?: number;
|
|
127
|
+
page?: number | undefined;
|
|
128
|
+
perPage?: number | undefined;
|
|
128
129
|
search?: string | undefined;
|
|
129
130
|
sortBy?: string | undefined;
|
|
130
|
-
sortDir?: "asc" | "desc";
|
|
131
|
+
sortDir?: "asc" | "desc" | undefined;
|
|
131
132
|
/** Scope the base query before search/sort/pagination (e.g. the active tab). */
|
|
132
|
-
modifyQuery?: QueryModifier;
|
|
133
|
+
modifyQuery?: QueryModifier | undefined;
|
|
133
134
|
/** Soft-delete scope: include trashed (`with`) or only trashed (`only`). */
|
|
134
135
|
trashed?: "with" | "only" | undefined;
|
|
135
136
|
/**
|
package/src/actions/Action.ts
CHANGED
|
@@ -25,7 +25,11 @@ import { flattenFields } from "../form/index.ts";
|
|
|
25
25
|
|
|
26
26
|
export type ActionColor = "default" | "primary" | "success" | "muted" | "destructive";
|
|
27
27
|
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Minimal view of the host page an action handler can drive (flash/redirect).
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
29
33
|
export interface ActionPage {
|
|
30
34
|
flash(message: string, level?: string): unknown;
|
|
31
35
|
redirect(url: string): { withSuccess(message: string): unknown };
|
package/src/actions/render.tsx
CHANGED
|
@@ -27,7 +27,11 @@ export interface RenderActionOptions {
|
|
|
27
27
|
args?: unknown[];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Render a single action. Link actions become `<a navigate>`, callbacks `<button>`.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
31
35
|
export function renderAction(
|
|
32
36
|
a: Action,
|
|
33
37
|
ctx: ActionContext,
|
|
@@ -92,6 +96,8 @@ export function renderAction(
|
|
|
92
96
|
* Returns `null` when nothing inside is visible, so an empty menu never appears.
|
|
93
97
|
* `argsFor` builds each member's handler arguments, since those carry the
|
|
94
98
|
* action's own key.
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
95
101
|
*/
|
|
96
102
|
export function renderActionGroup(
|
|
97
103
|
group: ActionGroup,
|
|
@@ -128,6 +134,8 @@ export function renderActionGroup(
|
|
|
128
134
|
/**
|
|
129
135
|
* Render an action as a full-width dropdown menu row (icon + label). Used by the
|
|
130
136
|
* row-action overflow menu when a row has more actions than fit inline.
|
|
137
|
+
*
|
|
138
|
+
* @internal
|
|
131
139
|
*/
|
|
132
140
|
export function renderActionMenuItem(
|
|
133
141
|
a: Action,
|
package/src/auth/AuthLayout.tsx
CHANGED
package/src/authRoles.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface AuthRolesOptions {
|
|
|
24
24
|
* Guard the roles belong to. Defaults to `"web"`, matching the framework's
|
|
25
25
|
* own default, so most apps need not think about it.
|
|
26
26
|
*/
|
|
27
|
-
guard?: string;
|
|
27
|
+
guard?: string | undefined;
|
|
28
28
|
/**
|
|
29
29
|
* Role names that hold every permission.
|
|
30
30
|
*
|
|
@@ -32,7 +32,7 @@ export interface AuthRolesOptions {
|
|
|
32
32
|
* something the data can tell you. A superuser role is shown as holding
|
|
33
33
|
* everything and cannot be edited or deleted from the panel.
|
|
34
34
|
*/
|
|
35
|
-
superusers?: string[];
|
|
35
|
+
superusers?: string[] | undefined;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** The role surface this needs, kept structural so `auth` stays optional. */
|
package/src/config.ts
CHANGED
|
@@ -26,37 +26,46 @@ export interface UserMenu {
|
|
|
26
26
|
*/
|
|
27
27
|
export interface AdminAuthConfig {
|
|
28
28
|
/** Mount the auth pages. */
|
|
29
|
-
enabled?: boolean;
|
|
29
|
+
enabled?: boolean | undefined;
|
|
30
30
|
/** Login route (relative to the panel path). Default `/login`. */
|
|
31
|
-
loginPath?: string;
|
|
31
|
+
loginPath?: string | undefined;
|
|
32
32
|
/** Profile route (relative to the panel path). Default `/profile`. */
|
|
33
|
-
profilePath?: string;
|
|
33
|
+
profilePath?: string | undefined;
|
|
34
34
|
/** Where to send the user after a successful login. Default the panel root. */
|
|
35
|
-
redirectTo?: string;
|
|
35
|
+
redirectTo?: string | undefined;
|
|
36
36
|
/** The credential column users log in with. Default `"email"`. */
|
|
37
|
-
identifier?: string;
|
|
37
|
+
identifier?: string | undefined;
|
|
38
38
|
/** Show a "remember me" checkbox. Default `true`. */
|
|
39
|
-
remember?: boolean;
|
|
39
|
+
remember?: boolean | undefined;
|
|
40
40
|
/** Heading on the auth screens. Defaults to the panel brand. */
|
|
41
|
-
heading?: string;
|
|
41
|
+
heading?: string | undefined;
|
|
42
42
|
/** Extra gate after the password check (e.g. "is the account active?"). */
|
|
43
|
-
authenticateWhen?: (user: unknown) => boolean | Promise<boolean
|
|
43
|
+
authenticateWhen?: ((user: unknown) => boolean | Promise<boolean>) | undefined;
|
|
44
44
|
/** Persist profile edits (name/email). Defaults to `user.fill(data); user.save()`. */
|
|
45
|
-
updateProfile?:
|
|
45
|
+
updateProfile?:
|
|
46
|
+
((user: unknown, data: Record<string, unknown>) => Promise<void> | void) | undefined;
|
|
46
47
|
/** Middleware guarding the guest screens (login/forgot/reset). Default none. */
|
|
47
|
-
guestMiddleware?: MiddlewareClass[];
|
|
48
|
+
guestMiddleware?: MiddlewareClass[] | undefined;
|
|
48
49
|
/** Middleware guarding the profile/verify screens. Defaults to the panel guard. */
|
|
49
|
-
authMiddleware?: MiddlewareClass[];
|
|
50
|
+
authMiddleware?: MiddlewareClass[] | undefined;
|
|
50
51
|
/** Password-reset broker — mounting the forgot/reset pages when provided. */
|
|
51
|
-
passwordReset?:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
passwordReset?:
|
|
53
|
+
| {
|
|
54
|
+
sendResetLink(email: string): Promise<boolean> | boolean;
|
|
55
|
+
reset(input: {
|
|
56
|
+
email: string;
|
|
57
|
+
token: string;
|
|
58
|
+
password: string;
|
|
59
|
+
}): Promise<boolean> | boolean;
|
|
60
|
+
}
|
|
61
|
+
| undefined;
|
|
55
62
|
/** Email-verification hooks — mounting the verify page when provided. */
|
|
56
|
-
emailVerification?:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
emailVerification?:
|
|
64
|
+
| {
|
|
65
|
+
isVerified(user: unknown): boolean;
|
|
66
|
+
resend(user: unknown): Promise<void> | void;
|
|
67
|
+
}
|
|
68
|
+
| undefined;
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
/** Admin panel configuration (read from `config/admin.ts`, with defaults). */
|
|
@@ -66,7 +75,7 @@ export interface AdminConfigShape {
|
|
|
66
75
|
/** Brand name shown in the sidebar. */
|
|
67
76
|
brand: string;
|
|
68
77
|
/** Optional short tagline under the brand. */
|
|
69
|
-
tagline?: string;
|
|
78
|
+
tagline?: string | undefined;
|
|
70
79
|
/**
|
|
71
80
|
* Middleware guarding every panel route. When left empty, the panel is
|
|
72
81
|
* default-denied in production-like environments (fail closed) and open only
|
|
@@ -75,13 +84,13 @@ export interface AdminConfigShape {
|
|
|
75
84
|
* `[AuthMiddleware.with({ ... })]`. To deliberately expose it without auth,
|
|
76
85
|
* pass an explicit pass-through middleware.
|
|
77
86
|
*/
|
|
78
|
-
middleware?: MiddlewareClass[];
|
|
87
|
+
middleware?: MiddlewareClass[] | undefined;
|
|
79
88
|
/** Optional top-bar user menu (Profile / Logout / …). */
|
|
80
|
-
userMenu?: UserMenu;
|
|
89
|
+
userMenu?: UserMenu | undefined;
|
|
81
90
|
/** Styling source — Tailwind Play CDN (default) or a prebuilt stylesheet. */
|
|
82
|
-
theme?: AdminThemeConfig;
|
|
91
|
+
theme?: AdminThemeConfig | undefined;
|
|
83
92
|
/** Auth pages (login / profile / password-reset / email-verification). */
|
|
84
|
-
auth?: AdminAuthConfig;
|
|
93
|
+
auth?: AdminAuthConfig | undefined;
|
|
85
94
|
/**
|
|
86
95
|
* Decide the abilities named by pages, widgets, nav entries and search
|
|
87
96
|
* providers. Set this when the app models permissions itself; leave it unset to
|
|
@@ -90,13 +99,13 @@ export interface AdminConfigShape {
|
|
|
90
99
|
* With neither configured, every ability is denied outside a development
|
|
91
100
|
* environment — a panel with no authorization wired stays closed in production.
|
|
92
101
|
*/
|
|
93
|
-
authorize?: AdminAuthorizer;
|
|
102
|
+
authorize?: AdminAuthorizer | undefined;
|
|
94
103
|
/**
|
|
95
104
|
* Switch contributing packages off by id — `{ monitor: false }` keeps the
|
|
96
105
|
* monitor provider installed but drops its pages, widgets and nav entries from
|
|
97
106
|
* the panel. Anything absent here is on.
|
|
98
107
|
*/
|
|
99
|
-
plugins?: Record<string, boolean
|
|
108
|
+
plugins?: Record<string, boolean> | undefined;
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
/**
|
package/src/databaseMedia.ts
CHANGED
|
@@ -21,9 +21,9 @@ import type { MediaItem, MediaProvider } from "./media.ts";
|
|
|
21
21
|
|
|
22
22
|
export interface DatabaseMediaOptions {
|
|
23
23
|
/** Table holding the catalogue. Defaults to `"media"`. */
|
|
24
|
-
table?: string;
|
|
24
|
+
table?: string | undefined;
|
|
25
25
|
/** Map the panel's fields onto your column names. */
|
|
26
|
-
columns?: Partial<Record<keyof MediaItem, string
|
|
26
|
+
columns?: Partial<Record<keyof MediaItem, string>> | undefined;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const DEFAULT_COLUMNS: Record<keyof MediaItem, string> = {
|
|
@@ -32,21 +32,22 @@ export interface StoredNotification {
|
|
|
32
32
|
|
|
33
33
|
export interface DatabaseNotificationOptions {
|
|
34
34
|
/** Table holding the notifications. Defaults to `"notifications"`. */
|
|
35
|
-
table?: string;
|
|
35
|
+
table?: string | undefined;
|
|
36
36
|
/**
|
|
37
37
|
* Who the bell is showing. Defaults to the signed-in user from
|
|
38
38
|
* `@zerotal/auth`; returning `null` shows nothing, which is the right answer
|
|
39
39
|
* for a guest.
|
|
40
40
|
*/
|
|
41
|
-
notifiable?: () => Promise<unknown> | unknown;
|
|
41
|
+
notifiable?: (() => Promise<unknown> | unknown) | undefined;
|
|
42
42
|
/** How many to show. Defaults to 20 — a bell is not an archive. */
|
|
43
|
-
limit?: number;
|
|
43
|
+
limit?: number | undefined;
|
|
44
44
|
/**
|
|
45
45
|
* Turn a stored row into what the panel renders. The default reads `title`,
|
|
46
46
|
* `body`/`message`, `url`/`href` and `icon` out of the payload, which is what
|
|
47
47
|
* most notifications carry.
|
|
48
48
|
*/
|
|
49
|
-
present?:
|
|
49
|
+
present?:
|
|
50
|
+
((row: StoredNotification, data: Record<string, unknown>) => AdminNotification) | undefined;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/** Parse a row's JSON payload, tolerating a payload that was never JSON. */
|
package/src/form/ResourceForm.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { RuleBuilder, FieldRule } from "@zerotal/validator";
|
|
|
16
16
|
import type { Field, FieldMode } from "./Field.ts";
|
|
17
17
|
import { type FormComponent, flattenFields } from "./Section.ts";
|
|
18
18
|
|
|
19
|
+
/** @internal */
|
|
19
20
|
export type ResourceFormClass = new () => Form & Record<string, unknown>;
|
|
20
21
|
|
|
21
22
|
/** @internal */
|
package/src/history.ts
CHANGED
|
@@ -50,9 +50,9 @@ export interface HistoryOptions {
|
|
|
50
50
|
type: string;
|
|
51
51
|
id: unknown;
|
|
52
52
|
/** How many entries to show. Defaults to 25 — a record page is not an archive. */
|
|
53
|
-
limit?: number;
|
|
53
|
+
limit?: number | undefined;
|
|
54
54
|
/** Turn an actor id into a name. Defaults to `#<id>`. */
|
|
55
|
-
resolveActor?: (id: number) => Promise<string | null> | string | null;
|
|
55
|
+
resolveActor?: ((id: number) => Promise<string | null> | string | null) | undefined;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/** Fields that change on every write and say nothing about intent. */
|
package/src/media.ts
CHANGED
|
@@ -220,12 +220,12 @@ export function isUpload(value: unknown): value is UploadedFileLike {
|
|
|
220
220
|
export interface StoreMediaOptions {
|
|
221
221
|
provider: MediaProvider;
|
|
222
222
|
/** Disk to write to; the default disk when omitted. */
|
|
223
|
-
disk?: string;
|
|
224
|
-
folder?: string;
|
|
223
|
+
disk?: string | undefined;
|
|
224
|
+
folder?: string | undefined;
|
|
225
225
|
/** Refuse anything larger, in bytes. */
|
|
226
|
-
maxBytes?: number;
|
|
226
|
+
maxBytes?: number | undefined;
|
|
227
227
|
/** Accepted MIME types; anything goes when omitted. */
|
|
228
|
-
accept?: string[];
|
|
228
|
+
accept?: string[] | undefined;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/**
|
package/src/pages/AdminPage.ts
CHANGED
|
@@ -96,5 +96,7 @@ export abstract class AdminPage extends Component {
|
|
|
96
96
|
/**
|
|
97
97
|
* A concrete {@link AdminPage} subclass — the static metadata above, plus the
|
|
98
98
|
* zero-argument constructor Flow builds the page with.
|
|
99
|
+
*
|
|
100
|
+
* @internal
|
|
99
101
|
*/
|
|
100
102
|
export type AdminPageClass = typeof AdminPage & (new () => AdminPage);
|
|
@@ -36,6 +36,7 @@ const ALIGN: Record<string, string> = {
|
|
|
36
36
|
end: "text-right tabular-nums",
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
/** @internal */
|
|
39
40
|
export class ConsolePage extends Component {
|
|
40
41
|
static layout = AdminLayout;
|
|
41
42
|
|
|
@@ -304,6 +305,8 @@ function cell(c: ConsoleColumn, row: ConsoleRow): HtmlNode | string {
|
|
|
304
305
|
* shared class resolving itself from the route — keeps the console on a static
|
|
305
306
|
* and matches how resource list pages are generated. The explicit name matters:
|
|
306
307
|
* Flow's component registry is keyed by constructor name.
|
|
308
|
+
*
|
|
309
|
+
* @internal
|
|
307
310
|
*/
|
|
308
311
|
export function makeConsolePage(
|
|
309
312
|
console: ConsoleContribution,
|
|
@@ -253,7 +253,11 @@ export class DashboardPage extends Component {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* The dashboard for one panel, showing only that panel's resources and widgets.
|
|
258
|
+
*
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
257
261
|
export function makeDashboardPage(panel: PanelInstance = Panel.default()): typeof DashboardPage {
|
|
258
262
|
const Page = class extends DashboardPage {
|
|
259
263
|
static override panel = panel;
|
package/src/pages/MediaPage.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { deleteMedia, formatSize, isImage, isUpload, mediaUrl, storeMedia } from
|
|
|
15
15
|
/** How many files the grid shows before the "load more" step. */
|
|
16
16
|
const PAGE_SIZE = 60;
|
|
17
17
|
|
|
18
|
+
/** @internal */
|
|
18
19
|
export class MediaPage extends Component {
|
|
19
20
|
static layout = AdminLayout;
|
|
20
21
|
/** The panel this page belongs to — set by each generated subclass. */
|
|
@@ -338,7 +339,11 @@ export class MediaPage extends Component {
|
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Build a MediaPage bound to one panel.
|
|
344
|
+
*
|
|
345
|
+
* @internal
|
|
346
|
+
*/
|
|
342
347
|
export function makeMediaPage(panel: PanelInstance = Panel.default()): typeof MediaPage {
|
|
343
348
|
return class BoundMediaPage extends MediaPage {
|
|
344
349
|
static override panel = panel;
|
|
@@ -10,6 +10,7 @@ import { Icon } from "../ui/icons.tsx";
|
|
|
10
10
|
import { Panel } from "../Panel.ts";
|
|
11
11
|
import type { PanelInstance } from "../PanelInstance.ts";
|
|
12
12
|
|
|
13
|
+
/** @internal */
|
|
13
14
|
export class NotificationsPage extends Component {
|
|
14
15
|
static layout = AdminLayout;
|
|
15
16
|
/** The panel this page belongs to — set by each generated subclass. */
|
|
@@ -142,7 +143,11 @@ export class NotificationsPage extends Component {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* The notification centre for one panel, reading that panel's provider.
|
|
148
|
+
*
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
146
151
|
export function makeNotificationsPage(
|
|
147
152
|
panel: PanelInstance = Panel.default(),
|
|
148
153
|
): typeof NotificationsPage {
|
|
@@ -95,6 +95,7 @@ interface PivotProxy {
|
|
|
95
95
|
all?: () => unknown;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** @internal */
|
|
98
99
|
export class RecordViewPage extends Component {
|
|
99
100
|
static layout = AdminLayout;
|
|
100
101
|
/** Set by each generated subclass. */
|
|
@@ -925,6 +926,8 @@ export class RecordViewPage extends Component {
|
|
|
925
926
|
/**
|
|
926
927
|
* Build a uniquely-named View page subclass bound to a resource — mirrors
|
|
927
928
|
* {@link makeResourceListPage} so Flow keeps component identity stable.
|
|
929
|
+
*
|
|
930
|
+
* @internal
|
|
928
931
|
*/
|
|
929
932
|
export function makeRecordViewPage(
|
|
930
933
|
resource: ResourceClass,
|
|
@@ -63,7 +63,11 @@ const _formRegistry = new Map<string, FormMeta>();
|
|
|
63
63
|
|
|
64
64
|
const formKey = (panelId: string, slug: string): string => `${panelId}:${slug}`;
|
|
65
65
|
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Register a resource's form config so the shared page can resolve it.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
67
71
|
export function registerResourceForm(panelId: string, slug: string, meta: FormMeta): void {
|
|
68
72
|
_formRegistry.set(formKey(panelId, slug), meta);
|
|
69
73
|
}
|
|
@@ -72,6 +76,7 @@ const SPAN_CLASS = ["", "", "sm:col-span-2", "sm:col-span-3", "sm:col-span-4"];
|
|
|
72
76
|
const INPUT_CLASS =
|
|
73
77
|
"mt-1.5 block w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-sm outline-none transition placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:ring-offset-background disabled:cursor-not-allowed disabled:opacity-60";
|
|
74
78
|
|
|
79
|
+
/** @internal */
|
|
75
80
|
export class ResourceFormPage extends Component {
|
|
76
81
|
@locked slug = "";
|
|
77
82
|
@locked mode: FieldMode = "create";
|
|
@@ -104,6 +104,7 @@ function parseFilters(s: string): Record<string, string> {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/** @internal */
|
|
107
108
|
export class ResourceListPage extends Component {
|
|
108
109
|
static layout = AdminLayout;
|
|
109
110
|
/** Set by each generated subclass. */
|
|
@@ -2535,6 +2536,8 @@ export class ResourceListPage extends Component {
|
|
|
2535
2536
|
/**
|
|
2536
2537
|
* Build a uniquely-named List page subclass bound to a resource. The distinct
|
|
2537
2538
|
* class name keeps Flow's snapshot/component identity stable per resource.
|
|
2539
|
+
*
|
|
2540
|
+
* @internal
|
|
2538
2541
|
*/
|
|
2539
2542
|
export function makeResourceListPage(
|
|
2540
2543
|
resource: ResourceClass,
|
package/src/pages/RolesPage.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import type { PanelInstance } from "../PanelInstance.ts";
|
|
|
12
12
|
import type { Permission, Role } from "../roles.ts";
|
|
13
13
|
import { groupPermissions, panelPermissions, roleHas } from "../roles.ts";
|
|
14
14
|
|
|
15
|
+
/** @internal */
|
|
15
16
|
export class RolesPage extends Component {
|
|
16
17
|
static layout = AdminLayout;
|
|
17
18
|
/** The panel this page belongs to — set by each generated subclass. */
|
|
@@ -317,7 +318,11 @@ export class RolesPage extends Component {
|
|
|
317
318
|
}
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
/**
|
|
321
|
+
/**
|
|
322
|
+
* Build a RolesPage bound to one panel.
|
|
323
|
+
*
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
321
326
|
export function makeRolesPage(panel: PanelInstance = Panel.default()): typeof RolesPage {
|
|
322
327
|
return class BoundRolesPage extends RolesPage {
|
|
323
328
|
static override panel = panel;
|
package/src/pages/SearchPage.tsx
CHANGED
|
@@ -158,7 +158,11 @@ export class SearchPage extends Component {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* The search page for one panel, searching only that panel's resources.
|
|
163
|
+
*
|
|
164
|
+
* @internal
|
|
165
|
+
*/
|
|
162
166
|
export function makeSearchPage(panel: PanelInstance = Panel.default()): typeof SearchPage {
|
|
163
167
|
const Page = class extends SearchPage {
|
|
164
168
|
static override panel = panel;
|
package/src/plugin.ts
CHANGED
|
@@ -40,6 +40,8 @@ import type { RenderHookContext } from "./renderHooks.ts";
|
|
|
40
40
|
* over without importing anything from `@zerotal/admin`. The panel gives the page
|
|
41
41
|
* its own layout when it mounts the route, so a contributed page renders only its
|
|
42
42
|
* content and inherits the panel's chrome.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
43
45
|
*/
|
|
44
46
|
export type PanelPageClass = new () => object;
|
|
45
47
|
|
|
@@ -245,6 +247,8 @@ export interface UserMenuContribution {
|
|
|
245
247
|
* Contributors should declare their own minimal copy of the members they use
|
|
246
248
|
* rather than importing this type, so they depend on the admin package at build
|
|
247
249
|
* time not at all.
|
|
250
|
+
*
|
|
251
|
+
* @internal
|
|
248
252
|
*/
|
|
249
253
|
export interface AdminPanelHost {
|
|
250
254
|
/**
|
package/src/table/Column.ts
CHANGED
package/src/theme.ts
CHANGED
|
@@ -38,13 +38,13 @@ export const THEME_TOGGLE_SCRIPT = FLOW_THEME_TOGGLE_SCRIPT;
|
|
|
38
38
|
*/
|
|
39
39
|
export interface AdminThemeConfig {
|
|
40
40
|
/** A prebuilt stylesheet URL/path to link instead of (or alongside) the CDN. */
|
|
41
|
-
stylesheet?: string;
|
|
41
|
+
stylesheet?: string | undefined;
|
|
42
42
|
/** Keep loading the Tailwind Play CDN. Defaults to `true` unless `stylesheet` is set. */
|
|
43
|
-
cdn?: boolean;
|
|
43
|
+
cdn?: boolean | undefined;
|
|
44
44
|
/** Extra design-token CSS appended after the defaults (override `:root` / `.dark`). */
|
|
45
|
-
tokensCss?: string;
|
|
45
|
+
tokensCss?: string | undefined;
|
|
46
46
|
/** Skip the bundled Google Fonts links (e.g. you self-host Inter). */
|
|
47
|
-
noFonts?: boolean;
|
|
47
|
+
noFonts?: boolean | undefined;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** The design-token CSS (`:root` + `.dark` custom properties + base styles). */
|
package/src/ui/AdminLayout.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import { Icon } from "./icons.tsx";
|
|
|
15
15
|
import { Command, Toaster, Sidebar } from "@zerotal/flow-ui";
|
|
16
16
|
import type { CommandItem, SidebarItem, SidebarGroup } from "@zerotal/flow-ui";
|
|
17
17
|
|
|
18
|
+
/** @internal */
|
|
18
19
|
export class AdminLayout extends Layout {
|
|
19
20
|
/**
|
|
20
21
|
* The panel this shell belongs to. The base class serves whichever panel owns
|
|
@@ -335,6 +336,8 @@ export class AdminLayout extends Layout {
|
|
|
335
336
|
* The router keys shell persistence on the layout class name, so each panel needs
|
|
336
337
|
* a class of its own: without that, navigating from one panel to another would
|
|
337
338
|
* swap the content while leaving the previous panel's sidebar in place.
|
|
339
|
+
*
|
|
340
|
+
* @internal
|
|
338
341
|
*/
|
|
339
342
|
export function makeAdminLayout(panel: PanelInstance): typeof AdminLayout {
|
|
340
343
|
const cached = LAYOUTS.get(panel);
|
package/src/widgets/render.tsx
CHANGED
|
@@ -45,6 +45,8 @@ export function chartInitScript(configs: unknown[]): string {
|
|
|
45
45
|
*
|
|
46
46
|
* Returns `null` when there is nothing to draw, so a caller can drop the whole
|
|
47
47
|
* block rather than leaving an empty gap above its table.
|
|
48
|
+
*
|
|
49
|
+
* @internal
|
|
48
50
|
*/
|
|
49
51
|
export async function renderWidgets(
|
|
50
52
|
widgets: DashboardWidget[],
|