@wordpress/boot 0.3.1-next.8b30e05b0.0 → 0.4.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/build-module/components/app/router.js +25 -21
- package/build-module/components/app/router.js.map +2 -2
- package/build-module/components/app/use-route-title.js +50 -0
- package/build-module/components/app/use-route-title.js.map +7 -0
- package/build-module/components/canvas/back-button.js +3 -3
- package/build-module/components/canvas/back-button.js.map +1 -1
- package/build-module/components/navigation/navigation-item/index.js +2 -2
- package/build-module/components/navigation/navigation-item/index.js.map +1 -1
- package/build-module/components/root/index.js +291 -37
- package/build-module/components/root/index.js.map +2 -2
- package/build-module/components/root/single-page.js +164 -7
- package/build-module/components/root/single-page.js.map +2 -2
- package/build-module/components/site-hub/index.js +3 -3
- package/build-module/components/site-hub/index.js.map +1 -1
- package/build-module/components/site-icon/index.js +2 -2
- package/build-module/components/site-icon/index.js.map +1 -1
- package/build-module/components/site-icon-link/index.js +3 -3
- package/build-module/components/site-icon-link/index.js.map +1 -1
- package/build-module/index.js +108 -46
- package/build-module/index.js.map +2 -2
- package/build-style/style-rtl.css +107 -45
- package/build-style/style.css +107 -45
- package/build-types/components/app/router.d.ts.map +1 -1
- package/build-types/components/app/use-route-title.d.ts +8 -0
- package/build-types/components/app/use-route-title.d.ts.map +1 -0
- package/build-types/components/root/index.d.ts.map +1 -1
- package/build-types/components/root/single-page.d.ts.map +1 -1
- package/build-types/store/types.d.ts +64 -7
- package/build-types/store/types.d.ts.map +1 -1
- package/package.json +22 -21
- package/src/components/app/router.tsx +39 -38
- package/src/components/app/use-route-title.ts +80 -0
- package/src/components/canvas/back-button.scss +2 -2
- package/src/components/navigation/navigation-item/style.scss +1 -1
- package/src/components/root/index.tsx +148 -31
- package/src/components/root/single-page.tsx +3 -0
- package/src/components/root/style.scss +122 -4
- package/src/components/site-hub/style.scss +2 -2
- package/src/components/site-icon/style.scss +1 -1
- package/src/components/site-icon-link/style.scss +2 -2
- package/src/store/types.ts +71 -7
- package/src/style.scss +1 -0
- package/tsconfig.json +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -55,6 +55,68 @@ export interface CanvasData {
|
|
|
55
55
|
*/
|
|
56
56
|
editLink?: string;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Route lifecycle configuration exported from route_module.
|
|
60
|
+
* The module should export a named `route` object with these optional functions.
|
|
61
|
+
*/
|
|
62
|
+
export interface RouteConfig {
|
|
63
|
+
/**
|
|
64
|
+
* Pre-navigation hook for authentication, validation, or redirects.
|
|
65
|
+
* Called before the route is loaded.
|
|
66
|
+
*/
|
|
67
|
+
beforeLoad?: (context: RouteLoaderContext) => void | Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Data preloading function.
|
|
70
|
+
* Called when the route is being loaded.
|
|
71
|
+
*/
|
|
72
|
+
loader?: (context: RouteLoaderContext) => Promise<unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Function that returns canvas data for rendering.
|
|
75
|
+
* - Returns CanvasData to use default editor canvas
|
|
76
|
+
* - Returns null to use custom canvas component from content_module
|
|
77
|
+
* - Returns undefined to show no canvas
|
|
78
|
+
*/
|
|
79
|
+
canvas?: (context: RouteLoaderContext) => Promise<CanvasData | null | undefined>;
|
|
80
|
+
/**
|
|
81
|
+
* Function that determines whether to show the inspector panel.
|
|
82
|
+
* When not defined, defaults to true (always show inspector if component exists).
|
|
83
|
+
* When it returns false, the inspector is hidden even if an inspector component is exported.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* export const route = {
|
|
88
|
+
* inspector: ({ search }) => {
|
|
89
|
+
* // Only show inspector when items are selected
|
|
90
|
+
* return search.selectedIds?.length > 0;
|
|
91
|
+
* },
|
|
92
|
+
* };
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
inspector?: (context: RouteLoaderContext) => boolean | Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Function that returns the document title for the route.
|
|
98
|
+
* The returned title will be formatted as: "{title} ‹ {siteTitle} — WordPress"
|
|
99
|
+
* and announced to screen readers for accessibility.
|
|
100
|
+
*
|
|
101
|
+
* @param context Route context with params and search
|
|
102
|
+
* @return The document title string or a Promise resolving to a string
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* export const route = {
|
|
107
|
+
* title: async ({ params }) => {
|
|
108
|
+
* const post = await resolveSelect(coreStore).getEntityRecord(
|
|
109
|
+
* 'postType',
|
|
110
|
+
* params.type,
|
|
111
|
+
* params.id
|
|
112
|
+
* );
|
|
113
|
+
* return post?.title?.rendered || 'Edit Post';
|
|
114
|
+
* },
|
|
115
|
+
* };
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
title?: (context: RouteLoaderContext) => string | Promise<string>;
|
|
119
|
+
}
|
|
58
120
|
/**
|
|
59
121
|
* Route configuration interface.
|
|
60
122
|
* Routes specify content_module for surfaces and optionally route_module for lifecycle functions.
|
|
@@ -75,13 +137,8 @@ export interface Route {
|
|
|
75
137
|
content_module?: string;
|
|
76
138
|
/**
|
|
77
139
|
* Module path for route lifecycle functions.
|
|
78
|
-
* The module should export a named
|
|
79
|
-
*
|
|
80
|
-
* - loader?: Data preloading function
|
|
81
|
-
* - canvas?: Function that returns canvas data for rendering
|
|
82
|
-
* - Returns CanvasData to use default editor canvas
|
|
83
|
-
* - Returns null to use custom canvas component from content_module
|
|
84
|
-
* - Returns undefined to show no canvas
|
|
140
|
+
* The module should export a named `route` object implementing RouteConfig.
|
|
141
|
+
* @see RouteConfig for available lifecycle functions.
|
|
85
142
|
*/
|
|
86
143
|
route_module?: string;
|
|
87
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAE,MAAM,EAAE,MAAM,CAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,KAAK;IACrB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtD;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC;AAExD,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;CACvC;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC7B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,aAAa,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,MAAM,EAAE,MAAM,CAAE,MAAM,EAAE,MAAM,CAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC3B;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAE,OAAO,EAAE,kBAAkB,KAAM,IAAI,GAAG,OAAO,CAAE,IAAI,CAAE,CAAC;IAEvE;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAE,OAAO,EAAE,kBAAkB,KAAM,OAAO,CAAE,OAAO,CAAE,CAAC;IAE/D;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CACR,OAAO,EAAE,kBAAkB,KACvB,OAAO,CAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAE,CAAC;IAE9C;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,EAAE,CAAE,OAAO,EAAE,kBAAkB,KAAM,OAAO,GAAG,OAAO,CAAE,OAAO,CAAE,CAAC;IAE5E;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,EAAE,CAAE,OAAO,EAAE,kBAAkB,KAAM,MAAM,GAAG,OAAO,CAAE,MAAM,CAAE,CAAC;CACtE;AAED;;;GAGG;AACH,MAAM,WAAW,KAAK;IACrB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,KAAK;IACrB,SAAS,EAAE,MAAM,CAAE,MAAM,EAAE,QAAQ,CAAE,CAAC;IACtC,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/boot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Minimal boot package for WordPress admin pages.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -34,25 +34,26 @@
|
|
|
34
34
|
"wpScriptModuleExports": "./build-module/index.js",
|
|
35
35
|
"types": "build-types",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@wordpress/
|
|
38
|
-
"@wordpress/
|
|
39
|
-
"@wordpress/
|
|
40
|
-
"@wordpress/
|
|
41
|
-
"@wordpress/
|
|
42
|
-
"@wordpress/data": "^
|
|
43
|
-
"@wordpress/
|
|
44
|
-
"@wordpress/
|
|
45
|
-
"@wordpress/
|
|
46
|
-
"@wordpress/
|
|
47
|
-
"@wordpress/
|
|
48
|
-
"@wordpress/
|
|
49
|
-
"@wordpress/
|
|
50
|
-
"@wordpress/
|
|
51
|
-
"@wordpress/
|
|
52
|
-
"@wordpress/
|
|
53
|
-
"@wordpress/
|
|
54
|
-
"@wordpress/
|
|
55
|
-
"@wordpress/
|
|
37
|
+
"@wordpress/a11y": "^4.37.0",
|
|
38
|
+
"@wordpress/admin-ui": "^1.5.0",
|
|
39
|
+
"@wordpress/commands": "^1.37.0",
|
|
40
|
+
"@wordpress/components": "^31.0.0",
|
|
41
|
+
"@wordpress/compose": "^7.37.0",
|
|
42
|
+
"@wordpress/core-data": "^7.37.0",
|
|
43
|
+
"@wordpress/data": "^10.37.0",
|
|
44
|
+
"@wordpress/editor": "^14.37.0",
|
|
45
|
+
"@wordpress/element": "^6.37.0",
|
|
46
|
+
"@wordpress/html-entities": "^4.37.0",
|
|
47
|
+
"@wordpress/i18n": "^6.10.0",
|
|
48
|
+
"@wordpress/icons": "^11.4.0",
|
|
49
|
+
"@wordpress/keyboard-shortcuts": "^5.37.0",
|
|
50
|
+
"@wordpress/keycodes": "^4.37.0",
|
|
51
|
+
"@wordpress/lazy-editor": "^1.3.0",
|
|
52
|
+
"@wordpress/primitives": "^4.37.0",
|
|
53
|
+
"@wordpress/private-apis": "^1.37.0",
|
|
54
|
+
"@wordpress/route": "^0.3.0",
|
|
55
|
+
"@wordpress/theme": "^0.4.0",
|
|
56
|
+
"@wordpress/url": "^4.37.0",
|
|
56
57
|
"clsx": "^2.1.1"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
@@ -62,5 +63,5 @@
|
|
|
62
63
|
"publishConfig": {
|
|
63
64
|
"access": "public"
|
|
64
65
|
},
|
|
65
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "2cf13ec6cf86153c9b3cf369bf5c59046f5cd950"
|
|
66
67
|
}
|
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
privateApis as routePrivateApis,
|
|
14
14
|
type AnyRoute,
|
|
15
15
|
} from '@wordpress/route';
|
|
16
|
+
import { resolveSelect } from '@wordpress/data';
|
|
17
|
+
import { store as coreStore } from '@wordpress/core-data';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Internal dependencies
|
|
19
21
|
*/
|
|
20
22
|
import Root from '../root';
|
|
21
|
-
import type { Route, RouteLoaderContext } from '../../store/types';
|
|
23
|
+
import type { Route, RouteConfig, RouteLoaderContext } from '../../store/types';
|
|
22
24
|
import { unlock } from '../../lock-unlock';
|
|
23
25
|
|
|
24
26
|
const {
|
|
@@ -29,6 +31,7 @@ const {
|
|
|
29
31
|
RouterProvider,
|
|
30
32
|
createBrowserHistory,
|
|
31
33
|
parseHref,
|
|
34
|
+
useLoaderData,
|
|
32
35
|
} = unlock( routePrivateApis );
|
|
33
36
|
|
|
34
37
|
// Not found component
|
|
@@ -42,29 +45,6 @@ function NotFoundComponent() {
|
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
function RouteComponent( {
|
|
46
|
-
stage: Stage,
|
|
47
|
-
inspector: Inspector,
|
|
48
|
-
}: {
|
|
49
|
-
stage?: ComponentType;
|
|
50
|
-
inspector?: ComponentType;
|
|
51
|
-
} ) {
|
|
52
|
-
return (
|
|
53
|
-
<>
|
|
54
|
-
{ Stage && (
|
|
55
|
-
<div className="boot-layout__stage">
|
|
56
|
-
<Stage />
|
|
57
|
-
</div>
|
|
58
|
-
) }
|
|
59
|
-
{ Inspector && (
|
|
60
|
-
<div className="boot-layout__inspector">
|
|
61
|
-
<Inspector />
|
|
62
|
-
</div>
|
|
63
|
-
) }
|
|
64
|
-
</>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
48
|
/**
|
|
69
49
|
* Creates a TanStack route from a Route definition.
|
|
70
50
|
*
|
|
@@ -76,12 +56,7 @@ async function createRouteFromDefinition(
|
|
|
76
56
|
route: Route,
|
|
77
57
|
parentRoute: AnyRoute
|
|
78
58
|
) {
|
|
79
|
-
|
|
80
|
-
let routeConfig: {
|
|
81
|
-
beforeLoad?: ( context: RouteLoaderContext ) => void | Promise< void >;
|
|
82
|
-
loader?: ( context: RouteLoaderContext ) => Promise< unknown >;
|
|
83
|
-
canvas?: ( context: RouteLoaderContext ) => Promise< any >;
|
|
84
|
-
} = {};
|
|
59
|
+
let routeConfig: RouteConfig = {};
|
|
85
60
|
|
|
86
61
|
if ( route.route_module ) {
|
|
87
62
|
const module = await import( route.route_module );
|
|
@@ -105,20 +80,32 @@ async function createRouteFromDefinition(
|
|
|
105
80
|
search: opts.deps || {},
|
|
106
81
|
};
|
|
107
82
|
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
const [ , loaderData, canvasData, titleData ] = await Promise.all( [
|
|
84
|
+
resolveSelect( coreStore ).getEntityRecord(
|
|
85
|
+
'root',
|
|
86
|
+
'__unstableBase'
|
|
87
|
+
),
|
|
110
88
|
routeConfig.loader
|
|
111
89
|
? routeConfig.loader( context )
|
|
112
90
|
: Promise.resolve( undefined ),
|
|
113
91
|
routeConfig.canvas
|
|
114
92
|
? routeConfig.canvas( context )
|
|
115
93
|
: Promise.resolve( undefined ),
|
|
94
|
+
routeConfig.title
|
|
95
|
+
? routeConfig.title( context )
|
|
96
|
+
: Promise.resolve( undefined ),
|
|
116
97
|
] );
|
|
117
98
|
|
|
99
|
+
let inspector = true;
|
|
100
|
+
if ( routeConfig.inspector ) {
|
|
101
|
+
inspector = await routeConfig.inspector( context );
|
|
102
|
+
}
|
|
103
|
+
|
|
118
104
|
return {
|
|
119
105
|
...( loaderData as any ),
|
|
120
106
|
canvas: canvasData,
|
|
121
|
-
|
|
107
|
+
inspector,
|
|
108
|
+
title: titleData,
|
|
122
109
|
routeContentModule: route.content_module,
|
|
123
110
|
};
|
|
124
111
|
},
|
|
@@ -131,13 +118,27 @@ async function createRouteFromDefinition(
|
|
|
131
118
|
? await import( route.content_module )
|
|
132
119
|
: {};
|
|
133
120
|
|
|
121
|
+
const Stage = module.stage;
|
|
122
|
+
const Inspector = module.inspector;
|
|
123
|
+
|
|
134
124
|
return createLazyRoute( route.path )( {
|
|
135
|
-
component: function
|
|
125
|
+
component: function RouteComponent() {
|
|
126
|
+
const { inspector: showInspector } =
|
|
127
|
+
useLoaderData( { from: route.path } ) ?? {};
|
|
128
|
+
|
|
136
129
|
return (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
<>
|
|
131
|
+
{ Stage && (
|
|
132
|
+
<div className="boot-layout__stage">
|
|
133
|
+
<Stage />
|
|
134
|
+
</div>
|
|
135
|
+
) }
|
|
136
|
+
{ Inspector && showInspector && (
|
|
137
|
+
<div className="boot-layout__inspector">
|
|
138
|
+
<Inspector />
|
|
139
|
+
</div>
|
|
140
|
+
) }
|
|
141
|
+
</>
|
|
141
142
|
);
|
|
142
143
|
},
|
|
143
144
|
} );
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { useEffect, useRef } from '@wordpress/element';
|
|
5
|
+
import { useSelect } from '@wordpress/data';
|
|
6
|
+
import { store as coreStore, type UnstableBase } from '@wordpress/core-data';
|
|
7
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
8
|
+
import { speak } from '@wordpress/a11y';
|
|
9
|
+
import { decodeEntities } from '@wordpress/html-entities';
|
|
10
|
+
import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import { unlock } from '../../lock-unlock';
|
|
16
|
+
|
|
17
|
+
const { useLocation, useMatches } = unlock( routePrivateApis );
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook that manages document title updates based on route changes.
|
|
21
|
+
* Formats titles with WordPress conventions and announces them to screen readers.
|
|
22
|
+
*
|
|
23
|
+
* This hook should be called from the Root component to ensure it runs on every route.
|
|
24
|
+
*/
|
|
25
|
+
export default function useRouteTitle() {
|
|
26
|
+
const location = useLocation();
|
|
27
|
+
const matches = useMatches();
|
|
28
|
+
const currentMatch = matches[ matches.length - 1 ];
|
|
29
|
+
const routeTitle = ( currentMatch?.loaderData as any )?.title as
|
|
30
|
+
| string
|
|
31
|
+
| undefined;
|
|
32
|
+
|
|
33
|
+
const siteTitle = useSelect(
|
|
34
|
+
( select ) =>
|
|
35
|
+
select( coreStore ).getEntityRecord< UnstableBase >(
|
|
36
|
+
'root',
|
|
37
|
+
'__unstableBase'
|
|
38
|
+
)?.name,
|
|
39
|
+
[]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const isInitialLocationRef = useRef( true );
|
|
43
|
+
|
|
44
|
+
useEffect( () => {
|
|
45
|
+
isInitialLocationRef.current = false;
|
|
46
|
+
}, [ location ] );
|
|
47
|
+
|
|
48
|
+
useEffect( () => {
|
|
49
|
+
// Don't update or announce the title for initial page load.
|
|
50
|
+
if ( isInitialLocationRef.current ) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
routeTitle &&
|
|
56
|
+
typeof routeTitle === 'string' &&
|
|
57
|
+
siteTitle &&
|
|
58
|
+
typeof siteTitle === 'string'
|
|
59
|
+
) {
|
|
60
|
+
// Decode entities for display
|
|
61
|
+
const decodedRouteTitle = decodeEntities( routeTitle );
|
|
62
|
+
const decodedSiteTitle = decodeEntities( siteTitle );
|
|
63
|
+
|
|
64
|
+
// Format title following WordPress admin conventions
|
|
65
|
+
const formattedTitle = sprintf(
|
|
66
|
+
/* translators: Admin document title. 1: Admin screen name, 2: Site name. */
|
|
67
|
+
__( '%1$s ‹ %2$s — WordPress' ),
|
|
68
|
+
decodedRouteTitle,
|
|
69
|
+
decodedSiteTitle
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
document.title = formattedTitle;
|
|
73
|
+
|
|
74
|
+
// Announce title on route change for screen readers.
|
|
75
|
+
if ( decodedRouteTitle ) {
|
|
76
|
+
speak( decodedRouteTitle, 'assertive' );
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, [ routeTitle, siteTitle, location ] );
|
|
80
|
+
}
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
|
|
33
33
|
&:focus:not(:active) {
|
|
34
34
|
outline:
|
|
35
|
-
var(--wpds-border-width-focus) solid
|
|
35
|
+
var(--wpds-border-width-interactive-focus) solid
|
|
36
36
|
var(--wpds-color-stroke-focus-brand);
|
|
37
|
-
outline-offset: calc(-1 * var(--wpds-border-width-focus));
|
|
37
|
+
outline-offset: calc(-1 * var(--wpds-border-width-interactive-focus));
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -11,6 +11,17 @@ import { privateApis as routePrivateApis } from '@wordpress/route';
|
|
|
11
11
|
import { CommandMenu } from '@wordpress/commands';
|
|
12
12
|
import { privateApis as themePrivateApis } from '@wordpress/theme';
|
|
13
13
|
import { EditorSnackbars } from '@wordpress/editor';
|
|
14
|
+
import { useViewportMatch, useReducedMotion } from '@wordpress/compose';
|
|
15
|
+
import {
|
|
16
|
+
__unstableMotion as motion,
|
|
17
|
+
__unstableAnimatePresence as AnimatePresence,
|
|
18
|
+
Button,
|
|
19
|
+
SlotFillProvider,
|
|
20
|
+
} from '@wordpress/components';
|
|
21
|
+
import { menu } from '@wordpress/icons';
|
|
22
|
+
import { useState, useEffect } from '@wordpress/element';
|
|
23
|
+
import { __ } from '@wordpress/i18n';
|
|
24
|
+
import { Page } from '@wordpress/admin-ui';
|
|
14
25
|
|
|
15
26
|
/**
|
|
16
27
|
* Internal dependencies
|
|
@@ -18,15 +29,17 @@ import { EditorSnackbars } from '@wordpress/editor';
|
|
|
18
29
|
import Sidebar from '../sidebar';
|
|
19
30
|
import SavePanel from '../save-panel';
|
|
20
31
|
import CanvasRenderer from '../canvas-renderer';
|
|
32
|
+
import useRouteTitle from '../app/use-route-title';
|
|
21
33
|
import { unlock } from '../../lock-unlock';
|
|
22
34
|
import type { CanvasData } from '../../store/types';
|
|
23
35
|
import './style.scss';
|
|
24
36
|
|
|
25
37
|
const { ThemeProvider } = unlock( themePrivateApis );
|
|
26
|
-
const { useMatches, Outlet } = unlock( routePrivateApis );
|
|
38
|
+
const { useLocation, useMatches, Outlet } = unlock( routePrivateApis );
|
|
27
39
|
|
|
28
40
|
export default function Root() {
|
|
29
41
|
const matches = useMatches();
|
|
42
|
+
const location = useLocation();
|
|
30
43
|
const currentMatch = matches[ matches.length - 1 ];
|
|
31
44
|
const canvas = ( currentMatch?.loaderData as any )?.canvas as
|
|
32
45
|
| CanvasData
|
|
@@ -36,41 +49,145 @@ export default function Root() {
|
|
|
36
49
|
?.routeContentModule as string | undefined;
|
|
37
50
|
const isFullScreen = canvas && ! canvas.isPreview;
|
|
38
51
|
|
|
52
|
+
useRouteTitle();
|
|
53
|
+
|
|
54
|
+
// Mobile sidebar state
|
|
55
|
+
const isMobileViewport = useViewportMatch( 'medium', '<' );
|
|
56
|
+
const [ isMobileSidebarOpen, setIsMobileSidebarOpen ] = useState( false );
|
|
57
|
+
const disableMotion = useReducedMotion();
|
|
58
|
+
// Close mobile sidebar on viewport resize and path change
|
|
59
|
+
useEffect( () => {
|
|
60
|
+
setIsMobileSidebarOpen( false );
|
|
61
|
+
}, [ location.pathname, isMobileViewport ] );
|
|
62
|
+
|
|
39
63
|
return (
|
|
40
|
-
<
|
|
41
|
-
<ThemeProvider
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<div className="boot-layout__canvas">
|
|
65
|
-
<CanvasRenderer
|
|
66
|
-
canvas={ canvas }
|
|
67
|
-
routeContentModule={ routeContentModule }
|
|
64
|
+
<SlotFillProvider>
|
|
65
|
+
<ThemeProvider
|
|
66
|
+
isRoot
|
|
67
|
+
color={ { bg: '#f8f8f8', primary: '#3858e9' } }
|
|
68
|
+
>
|
|
69
|
+
<ThemeProvider color={ { bg: '#1d2327', primary: '#3858e9' } }>
|
|
70
|
+
<div
|
|
71
|
+
className={ clsx( 'boot-layout', {
|
|
72
|
+
'has-canvas': !! canvas || canvas === null,
|
|
73
|
+
'has-full-canvas': isFullScreen,
|
|
74
|
+
} ) }
|
|
75
|
+
>
|
|
76
|
+
<CommandMenu />
|
|
77
|
+
<SavePanel />
|
|
78
|
+
<EditorSnackbars />
|
|
79
|
+
{ isMobileViewport && (
|
|
80
|
+
<Page.SidebarToggleFill>
|
|
81
|
+
<Button
|
|
82
|
+
icon={ menu }
|
|
83
|
+
onClick={ () =>
|
|
84
|
+
setIsMobileSidebarOpen( true )
|
|
85
|
+
}
|
|
86
|
+
label={ __( 'Open navigation panel' ) }
|
|
87
|
+
size="compact"
|
|
68
88
|
/>
|
|
89
|
+
</Page.SidebarToggleFill>
|
|
90
|
+
) }
|
|
91
|
+
{ /* Mobile Sidebar Backdrop */ }
|
|
92
|
+
<AnimatePresence>
|
|
93
|
+
{ isMobileViewport &&
|
|
94
|
+
isMobileSidebarOpen &&
|
|
95
|
+
! isFullScreen && (
|
|
96
|
+
<motion.div
|
|
97
|
+
initial={ { opacity: 0 } }
|
|
98
|
+
animate={ { opacity: 1 } }
|
|
99
|
+
exit={ { opacity: 0 } }
|
|
100
|
+
transition={ {
|
|
101
|
+
type: 'tween',
|
|
102
|
+
duration: disableMotion ? 0 : 0.2,
|
|
103
|
+
ease: 'easeOut',
|
|
104
|
+
} }
|
|
105
|
+
className="boot-layout__sidebar-backdrop"
|
|
106
|
+
onClick={ () =>
|
|
107
|
+
setIsMobileSidebarOpen( false )
|
|
108
|
+
}
|
|
109
|
+
onKeyDown={ ( event ) => {
|
|
110
|
+
if ( event.key === 'Escape' ) {
|
|
111
|
+
setIsMobileSidebarOpen( false );
|
|
112
|
+
}
|
|
113
|
+
} }
|
|
114
|
+
role="button"
|
|
115
|
+
tabIndex={ -1 }
|
|
116
|
+
aria-label={ __(
|
|
117
|
+
'Close navigation panel'
|
|
118
|
+
) }
|
|
119
|
+
/>
|
|
120
|
+
) }
|
|
121
|
+
</AnimatePresence>
|
|
122
|
+
{ /* Mobile Sidebar */ }
|
|
123
|
+
<AnimatePresence>
|
|
124
|
+
{ isMobileViewport &&
|
|
125
|
+
isMobileSidebarOpen &&
|
|
126
|
+
! isFullScreen && (
|
|
127
|
+
<motion.div
|
|
128
|
+
initial={ { x: '-100%' } }
|
|
129
|
+
animate={ { x: 0 } }
|
|
130
|
+
exit={ { x: '-100%' } }
|
|
131
|
+
transition={ {
|
|
132
|
+
type: 'tween',
|
|
133
|
+
duration: disableMotion ? 0 : 0.2,
|
|
134
|
+
ease: 'easeOut',
|
|
135
|
+
} }
|
|
136
|
+
className="boot-layout__sidebar is-mobile"
|
|
137
|
+
>
|
|
138
|
+
<Sidebar />
|
|
139
|
+
</motion.div>
|
|
140
|
+
) }
|
|
141
|
+
</AnimatePresence>
|
|
142
|
+
{ /* Desktop Sidebar */ }
|
|
143
|
+
{ ! isMobileViewport && ! isFullScreen && (
|
|
144
|
+
<div className="boot-layout__sidebar">
|
|
145
|
+
<Sidebar />
|
|
69
146
|
</div>
|
|
70
147
|
) }
|
|
148
|
+
<div className="boot-layout__surfaces">
|
|
149
|
+
<ThemeProvider
|
|
150
|
+
color={ { bg: '#ffffff', primary: '#3858e9' } }
|
|
151
|
+
>
|
|
152
|
+
<Outlet />
|
|
153
|
+
</ThemeProvider>
|
|
154
|
+
{ /* Render Canvas in Root to prevent remounting on route changes */ }
|
|
155
|
+
{ ( canvas || canvas === null ) && (
|
|
156
|
+
<div
|
|
157
|
+
className={ clsx( 'boot-layout__canvas', {
|
|
158
|
+
'has-mobile-drawer':
|
|
159
|
+
canvas?.isPreview &&
|
|
160
|
+
isMobileViewport,
|
|
161
|
+
} ) }
|
|
162
|
+
>
|
|
163
|
+
{ canvas?.isPreview && isMobileViewport && (
|
|
164
|
+
<div className="boot-layout__mobile-sidebar-drawer">
|
|
165
|
+
<Button
|
|
166
|
+
icon={ menu }
|
|
167
|
+
onClick={ () =>
|
|
168
|
+
setIsMobileSidebarOpen(
|
|
169
|
+
true
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
label={ __(
|
|
173
|
+
'Open navigation panel'
|
|
174
|
+
) }
|
|
175
|
+
size="compact"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
) }
|
|
179
|
+
<CanvasRenderer
|
|
180
|
+
canvas={ canvas }
|
|
181
|
+
routeContentModule={
|
|
182
|
+
routeContentModule
|
|
183
|
+
}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
) }
|
|
187
|
+
</div>
|
|
71
188
|
</div>
|
|
72
|
-
</
|
|
189
|
+
</ThemeProvider>
|
|
73
190
|
</ThemeProvider>
|
|
74
|
-
</
|
|
191
|
+
</SlotFillProvider>
|
|
75
192
|
);
|
|
76
193
|
}
|
|
@@ -20,6 +20,7 @@ import CanvasRenderer from '../canvas-renderer';
|
|
|
20
20
|
import { unlock } from '../../lock-unlock';
|
|
21
21
|
import type { CanvasData } from '../../store/types';
|
|
22
22
|
import './style.scss';
|
|
23
|
+
import useRouteTitle from '../app/use-route-title';
|
|
23
24
|
|
|
24
25
|
const { useMatches, Outlet } = unlock( routePrivateApis );
|
|
25
26
|
const { ThemeProvider } = unlock( themePrivateApis );
|
|
@@ -39,6 +40,8 @@ export default function RootSinglePage() {
|
|
|
39
40
|
?.routeContentModule as string | undefined;
|
|
40
41
|
const isFullScreen = canvas && ! canvas.isPreview;
|
|
41
42
|
|
|
43
|
+
useRouteTitle();
|
|
44
|
+
|
|
42
45
|
return (
|
|
43
46
|
<ThemeProvider isRoot color={ { bg: '#f8f8f8', primary: '#3858e9' } }>
|
|
44
47
|
<ThemeProvider color={ { bg: '#1d2327', primary: '#3858e9' } }>
|