@modcore/plugin-ui-sdk 0.1.0 → 0.1.1
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 +6 -0
- package/README.md +12 -0
- package/dist/testing.d.ts +19 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +34 -0
- package/dist/testing.js.map +1 -1
- package/package.json +3 -2
- package/templates/development-sandbox/README.md +7 -0
- package/templates/development-sandbox/index.html +12 -0
- package/templates/development-sandbox/package.json +13 -0
- package/templates/development-sandbox/scripts/build.mjs +16 -0
- package/templates/development-sandbox/scripts/serve.mjs +27 -0
- package/templates/development-sandbox/src/main.ts +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Added the packaged loopback-only development sandbox with one declared mock request and locale/theme switching.
|
|
6
|
+
- Added `createDevelopmentSandboxHost()` for credential-free browser development.
|
|
7
|
+
- Kept development-server assets outside the publishable UI archive boundary.
|
|
8
|
+
|
|
3
9
|
## 0.1.0
|
|
4
10
|
|
|
5
11
|
- Initial public iframe-bridge types, constrained client, UI binding declaration, accessibility contract, and deterministic mock host.
|
package/README.md
CHANGED
|
@@ -22,6 +22,18 @@ import { createMockUiHost } from "@modcore/plugin-ui-sdk/testing";
|
|
|
22
22
|
|
|
23
23
|
Use `defineUiBinding()` for a `UI_ONLY` theme’s explicit target contract. `createPluginUiClient()` accepts only the authenticated host bridge transport; it has no API for URLs, authorization headers, cookies, or raw network clients. Use `createMockUiHost()` for local tests without a ModCore checkout, management token, or production host.
|
|
24
24
|
|
|
25
|
+
## Local browser sandbox
|
|
26
|
+
|
|
27
|
+
The release tarball includes `templates/development-sandbox`. It is a loopback-only browser
|
|
28
|
+
preview with one declared mock request and visible `en`/`zh-CN` and light/dark switches. It uses
|
|
29
|
+
`createDevelopmentSandboxHost()`; it never contacts a ModCore host and cannot be packaged as a
|
|
30
|
+
production UI asset. See the reproducible commands and production boundary in
|
|
31
|
+
[`docs/guide/插件开发.md`](../docs/guide/插件开发.md).
|
|
32
|
+
|
|
33
|
+
The sandbox is development evidence only. A release candidate must instead contain its built,
|
|
34
|
+
inventory-checked UI files under `META-INF/modcore/ui`, pass artifact preflight, and be signed by
|
|
35
|
+
a production-trusted publisher.
|
|
36
|
+
|
|
25
37
|
## Release evidence
|
|
26
38
|
|
|
27
39
|
Before a public release, run `pnpm test`, `pnpm test:types`, `pnpm build`, and `pnpm pack:check`; publish the generated package metadata, registry integrity value, source revision, changelog, and this support contract together. This source checkout does not publish to a registry automatically.
|
package/dist/testing.d.ts
CHANGED
|
@@ -9,6 +9,25 @@ export interface MockUiHost<TContract extends UiContract> {
|
|
|
9
9
|
setLocale(locale: string): void;
|
|
10
10
|
setTheme(tokens: Readonly<Record<string, string>>): void;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Fixture data used by the public development sandbox. It deliberately remains a local mock:
|
|
14
|
+
* it has no credentials, host-private modules, or network transport.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DEVELOPMENT_SANDBOX_CONTRACT: {
|
|
17
|
+
readonly id: "com.example.profile.settings-ui";
|
|
18
|
+
readonly version: "v1";
|
|
19
|
+
readonly schemaSha256: string;
|
|
20
|
+
readonly operations: {
|
|
21
|
+
readonly "com.example.profile.read": {
|
|
22
|
+
readonly id: "com.example.profile.read";
|
|
23
|
+
readonly request: {};
|
|
24
|
+
readonly response: {};
|
|
25
|
+
readonly access: "read";
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/** Creates the locale/theme-switchable, credential-free host used by the packaged browser sandbox. */
|
|
30
|
+
export declare function createDevelopmentSandboxHost(): MockUiHost<typeof DEVELOPMENT_SANDBOX_CONTRACT>;
|
|
12
31
|
/** A deterministic local host for SDK tests; it exposes only operations declared by the contract. */
|
|
13
32
|
export declare function createMockUiHost<TContract extends UiContract>(contract: TContract, responses: Partial<{
|
|
14
33
|
[K in keyof TContract["operations"] & string]: TContract["operations"][K]["response"];
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAyB,MAAM,YAAY,CAAC;AAEtI,MAAM,WAAW,UAAU,CAAC,SAAS,SAAS,UAAU;IACtD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC9E,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED,qGAAqG;AACrG,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,UAAU,EAC3D,QAAQ,EAAE,SAAS,EACnB,SAAS,EAAE,OAAO,CAAC;KAAG,CAAC,IAAI,MAAM,SAAS,CAAC,YAAY,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;CAAE,CAAC,EAC7G,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GAC5C,UAAU,CAAC,SAAS,CAAC,CAiDvB"}
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAyB,MAAM,YAAY,CAAC;AAEtI,MAAM,WAAW,UAAU,CAAC,SAAS,SAAS,UAAU;IACtD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IAC9E,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;CAYV,CAAC;AAEhC,sGAAsG;AACtG,wBAAgB,4BAA4B,IAAI,UAAU,CAAC,OAAO,4BAA4B,CAAC,CAmB9F;AAED,qGAAqG;AACrG,wBAAgB,gBAAgB,CAAC,SAAS,SAAS,UAAU,EAC3D,QAAQ,EAAE,SAAS,EACnB,SAAS,EAAE,OAAO,CAAC;KAAG,CAAC,IAAI,MAAM,SAAS,CAAC,YAAY,CAAC,GAAG,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;CAAE,CAAC,EAC7G,OAAO,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,CAAC,GAC5C,UAAU,CAAC,SAAS,CAAC,CAiDvB"}
|
package/dist/testing.js
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
import { BRIDGE_PROTOCOL } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fixture data used by the public development sandbox. It deliberately remains a local mock:
|
|
4
|
+
* it has no credentials, host-private modules, or network transport.
|
|
5
|
+
*/
|
|
6
|
+
export const DEVELOPMENT_SANDBOX_CONTRACT = {
|
|
7
|
+
id: "com.example.profile.settings-ui",
|
|
8
|
+
version: "v1",
|
|
9
|
+
schemaSha256: "1".repeat(64),
|
|
10
|
+
operations: {
|
|
11
|
+
"com.example.profile.read": {
|
|
12
|
+
id: "com.example.profile.read",
|
|
13
|
+
request: {},
|
|
14
|
+
response: {},
|
|
15
|
+
access: "read",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
/** Creates the locale/theme-switchable, credential-free host used by the packaged browser sandbox. */
|
|
20
|
+
export function createDevelopmentSandboxHost() {
|
|
21
|
+
return createMockUiHost(DEVELOPMENT_SANDBOX_CONTRACT, { "com.example.profile.read": { displayName: "Ada Lovelace" } }, {
|
|
22
|
+
candidate: {
|
|
23
|
+
providerPluginInstanceId: "com.example.theme:development",
|
|
24
|
+
providerPluginId: "com.example.theme",
|
|
25
|
+
artifactSha256: "2".repeat(64),
|
|
26
|
+
uiId: "com.example.theme.development",
|
|
27
|
+
bindingId: "com.example.theme.profile",
|
|
28
|
+
},
|
|
29
|
+
surfaceId: "com.example.profile.settings",
|
|
30
|
+
policyGeneration: 1,
|
|
31
|
+
locale: "en",
|
|
32
|
+
themeTokens: { "color.background": "#ffffff", "color.text": "#172033", "color.accent": "#155eef" },
|
|
33
|
+
allowedNavigationIds: ["com.example.profile.settings"],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
2
36
|
/** A deterministic local host for SDK tests; it exposes only operations declared by the contract. */
|
|
3
37
|
export function createMockUiHost(contract, responses, context) {
|
|
4
38
|
const calls = [];
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA2F,MAAM,YAAY,CAAC;AAUtI,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAC9B,QAAmB,EACnB,SAA6G,EAC7G,OAA6C;IAE7C,MAAM,KAAK,GAAuC,EAAE,CAAC;IACrD,MAAM,eAAe,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC5D,MAAM,cAAc,GAAG,IAAI,GAAG,EAAsD,CAAC;IACrF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,aAAa,GAAG,GAAS,EAAE;QAC/B,IAAI,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC7D,CAAC,CAAC;IACF,MAAM,SAAS,GAAiC;QAC9C,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO;YAChC,aAAa,EAAE,CAAC;YAChB,IAAI,CAAC,CAAC,WAAW,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,WAAW,IAAI,SAAS,CAAC,EAAE,CAAC;gBACzE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;YACjF,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YACjE,OAAO,SAAS,CAAC,WAAW,CAA4D,CAAC;QAC3F,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,YAAY;YACzB,aAAa,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,KAAuB;YACrC,aAAa,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;QACD,KAAK,CAAC,OAAO;YACX,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,cAAc,CAAC,QAAQ;YACrB,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9B,OAAO,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;QACD,aAAa,CAAC,QAAQ;YACpB,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC/C,CAAC;KACF,CAAC;IACF,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;QACjE,SAAS;QACT,KAAK;QACL,SAAS,CAAC,MAAM;YACd,eAAe,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,QAAQ,CAAC,MAAM;YACb,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACzD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modcore/plugin-ui-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Public, framework-independent UI bridge SDK for ModCore plugins.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"dist",
|
|
28
28
|
"README.md",
|
|
29
29
|
"CHANGELOG.md",
|
|
30
|
-
"SECURITY.md"
|
|
30
|
+
"SECURITY.md",
|
|
31
|
+
"templates"
|
|
31
32
|
],
|
|
32
33
|
"scripts": {
|
|
33
34
|
"clean": "node ./scripts/clean.mjs",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ModCore UI development sandbox
|
|
2
|
+
|
|
3
|
+
This template is for local development only. It uses `createDevelopmentSandboxHost()` and has no ModCore server, management token, browser cookie, or production identity.
|
|
4
|
+
|
|
5
|
+
Run `pnpm install`, `pnpm build`, then `pnpm dev`. Open the printed loopback URL and use the three keyboard-accessible buttons to exercise one declared mock request, the `en`/`zh-CN` locale switch, and the light/dark theme switch.
|
|
6
|
+
|
|
7
|
+
Never copy `index.html`, `scripts/serve.mjs`, a loopback URL, or development output into `META-INF/modcore/ui`. A publishable plugin contains the built, inventory-checked assets generated by the plugin template, is signed, and is admitted by production preflight.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ModCore UI development sandbox</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<main id="sandbox-root"></main>
|
|
10
|
+
<script type="module" src="./main.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "modcore-plugin-ui-development-sandbox",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "node ./scripts/build.mjs",
|
|
7
|
+
"dev": "node ./scripts/serve.mjs"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@modcore/plugin-ui-sdk": "^0.1.1",
|
|
11
|
+
"esbuild": "0.25.10"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { build } from "esbuild";
|
|
2
|
+
import { cp, mkdir, rm } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
await rm("dist", { recursive: true, force: true });
|
|
5
|
+
await mkdir("dist", { recursive: true });
|
|
6
|
+
await cp("index.html", "dist/index.html");
|
|
7
|
+
await build({
|
|
8
|
+
entryPoints: ["src/main.ts"],
|
|
9
|
+
outfile: "dist/main.js",
|
|
10
|
+
bundle: true,
|
|
11
|
+
format: "esm",
|
|
12
|
+
platform: "browser",
|
|
13
|
+
target: "es2022",
|
|
14
|
+
legalComments: "none",
|
|
15
|
+
});
|
|
16
|
+
console.log("MODCORE_UI_DEVELOPMENT_SANDBOX_BUILD_SUCCESS");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { extname, join, normalize } from "node:path";
|
|
4
|
+
|
|
5
|
+
const port = Number.parseInt(process.env.PORT ?? "4173", 10);
|
|
6
|
+
const root = new URL("../dist/", import.meta.url).pathname;
|
|
7
|
+
const mime = { ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8" };
|
|
8
|
+
const server = createServer(async (request, response) => {
|
|
9
|
+
const pathname = new URL(request.url ?? "/", "http://127.0.0.1").pathname;
|
|
10
|
+
if (pathname === "/favicon.ico") {
|
|
11
|
+
response.writeHead(204, { "Cache-Control": "no-store" }).end();
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const relative = pathname === "/" ? "index.html" : pathname.replace(/^\//, "");
|
|
15
|
+
const file = normalize(join(root, relative));
|
|
16
|
+
if (!file.startsWith(root) || !mime[extname(file)]) {
|
|
17
|
+
response.writeHead(404, { "Cache-Control": "no-store" }).end();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
response.writeHead(200, { "Content-Type": mime[extname(file)], "Cache-Control": "no-store" });
|
|
22
|
+
response.end(await readFile(file));
|
|
23
|
+
} catch {
|
|
24
|
+
response.writeHead(404, { "Cache-Control": "no-store" }).end();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.listen(port, "127.0.0.1", () => console.log(`MODCORE_UI_DEVELOPMENT_SANDBOX_READY url=http://127.0.0.1:${port}`));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createPluginUiClient, createUiLocalizer, defineUiAccessibility } from "@modcore/plugin-ui-sdk";
|
|
2
|
+
import { createDevelopmentSandboxHost } from "@modcore/plugin-ui-sdk/testing";
|
|
3
|
+
|
|
4
|
+
const host = createDevelopmentSandboxHost();
|
|
5
|
+
const client = createPluginUiClient(host.context, host.transport);
|
|
6
|
+
const root = document.querySelector<HTMLElement>("#sandbox-root");
|
|
7
|
+
if (!root) throw new Error("Development sandbox root is missing.");
|
|
8
|
+
|
|
9
|
+
const accessibility = defineUiAccessibility({
|
|
10
|
+
localizedIframeTitle: "Profile settings development sandbox",
|
|
11
|
+
statusRole: "status",
|
|
12
|
+
supportsKeyboardReturn: true,
|
|
13
|
+
supportsReducedMotion: true,
|
|
14
|
+
supportsUserColors: true,
|
|
15
|
+
supportsTextZoom: true,
|
|
16
|
+
});
|
|
17
|
+
const messages = {
|
|
18
|
+
en: { title: "Profile UI sandbox", load: "Load profile", locale: "中文", theme: "Dark theme", ready: "Mock host ready", loaded: "Loaded profile for {name}" },
|
|
19
|
+
"zh-CN": { title: "个人中心 UI 沙箱", load: "读取个人资料", locale: "English", theme: "浅色主题", ready: "Mock host 已就绪", loaded: "已读取 {name} 的个人资料" },
|
|
20
|
+
} as const;
|
|
21
|
+
let locale: keyof typeof messages = "en";
|
|
22
|
+
let dark = false;
|
|
23
|
+
|
|
24
|
+
function render(status = createUiLocalizer(locale, messages[locale]).t("ready")): void {
|
|
25
|
+
const t = createUiLocalizer(locale, messages[locale]).t.bind(createUiLocalizer(locale, messages[locale]));
|
|
26
|
+
root.lang = locale;
|
|
27
|
+
root.style.cssText = `font: 16px/1.5 system-ui; max-width: 42rem; margin: 3rem auto; padding: 1.5rem; color: ${dark ? "#f2f4f7" : "#172033"}; background: ${dark ? "#101828" : "#ffffff"};`;
|
|
28
|
+
root.innerHTML = `<section aria-label="${accessibility.localizedIframeTitle}"><h1>${t("title")}</h1><p id="sandbox-status" role="status" aria-live="polite">${status}</p><button id="load" type="button">${t("load")}</button> <button id="locale" type="button">${t("locale")}</button> <button id="theme" type="button">${t("theme")}</button></section>`;
|
|
29
|
+
root.querySelector<HTMLButtonElement>("#load")?.addEventListener("click", async () => {
|
|
30
|
+
const profile = await client.request("com.example.profile.read", {});
|
|
31
|
+
render(t("loaded", { name: profile.displayName }));
|
|
32
|
+
});
|
|
33
|
+
root.querySelector<HTMLButtonElement>("#locale")?.addEventListener("click", () => host.setLocale(locale === "en" ? "zh-CN" : "en"));
|
|
34
|
+
root.querySelector<HTMLButtonElement>("#theme")?.addEventListener("click", () => host.setTheme(dark ? { "color.background": "#ffffff", "color.text": "#172033" } : { "color.background": "#101828", "color.text": "#f2f4f7" }));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
client.onLocaleChange((nextLocale) => {
|
|
38
|
+
locale = nextLocale === "zh-CN" ? "zh-CN" : "en";
|
|
39
|
+
render();
|
|
40
|
+
});
|
|
41
|
+
client.onThemeChange((tokens) => {
|
|
42
|
+
dark = tokens["color.background"] === "#101828";
|
|
43
|
+
render();
|
|
44
|
+
});
|
|
45
|
+
render();
|