@ethisyscore/components-react 1.7.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/README.md +53 -0
- package/dist/index.cjs +26 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# `@ethisyscore/components-react`
|
|
2
|
+
|
|
3
|
+
Host-bound React contract for EthisysCore PlatformReact pages (RFC 0003).
|
|
4
|
+
|
|
5
|
+
Provides the typed page-props contract every PlatformReact page receives from
|
|
6
|
+
the host at surface mount time, plus a `definePlatformReactPage` helper so
|
|
7
|
+
plugin authors get type-safe access to the host realm without depending on
|
|
8
|
+
the host app's internal modules.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
|
|
12
|
+
|
|
13
|
+
const Dashboard = ({ extensionId, organisationId, pageId }: PlatformReactPageProps) => (
|
|
14
|
+
<section>
|
|
15
|
+
<h1>Dashboard</h1>
|
|
16
|
+
<p>Extension {extensionId} for org {organisationId} (page: {pageId})</p>
|
|
17
|
+
</section>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default definePlatformReactPage(Dashboard);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The plugin's `feature.manifest.json` references this module via
|
|
24
|
+
`ui.platformReactPages[].moduleSpecifier`; the host loads the module at surface
|
|
25
|
+
mount time, reads the declared `exportName`, and renders the component with the
|
|
26
|
+
typed props.
|
|
27
|
+
|
|
28
|
+
## Why this package exists
|
|
29
|
+
|
|
30
|
+
PlatformReact is the high-trust platform-extension tier — the plugin's bundle
|
|
31
|
+
runs in the host realm, so the plugin can use any npm package the author
|
|
32
|
+
bundles. This package is the **one** dependency that has to come from the host
|
|
33
|
+
side: the contract for what the host passes to the mounted page. Keeping it as
|
|
34
|
+
a focused npm package means:
|
|
35
|
+
|
|
36
|
+
- The host's internal modules (`coreconnect-web`'s `PlatformReactSurfaceMount`)
|
|
37
|
+
do not leak as plugin dependencies.
|
|
38
|
+
- The contract grows with explicit versioning — additive changes are
|
|
39
|
+
backward-compatible, breaking changes bump the major.
|
|
40
|
+
- Plugin authors get strict TypeScript at the host-↔-plugin seam — no `any`,
|
|
41
|
+
no shape drift.
|
|
42
|
+
|
|
43
|
+
## What's in the package
|
|
44
|
+
|
|
45
|
+
- `PlatformReactPageProps` — the props every page receives from the host
|
|
46
|
+
(`extensionId`, `organisationId`, optional `tenantId`, `pageId`).
|
|
47
|
+
- `definePlatformReactPage(Component)` — pass-through helper today; the
|
|
48
|
+
indirection point for future host integrations (boundary injection,
|
|
49
|
+
suspense fallbacks, error contracts) to land without every plugin author
|
|
50
|
+
updating their export.
|
|
51
|
+
|
|
52
|
+
See `docs/authoring/platform-react.md` for the full PlatformReact authoring
|
|
53
|
+
guide.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/navigation.ts
|
|
4
|
+
function emitNavigation(to) {
|
|
5
|
+
if (typeof window === "undefined") {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const event = new CustomEvent("ethisys:navigate", {
|
|
9
|
+
detail: { to },
|
|
10
|
+
cancelable: true
|
|
11
|
+
});
|
|
12
|
+
window.dispatchEvent(event);
|
|
13
|
+
if (!event.defaultPrevented) {
|
|
14
|
+
window.location.assign(to);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/index.ts
|
|
19
|
+
function definePlatformReactPage(component) {
|
|
20
|
+
return component;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.definePlatformReactPage = definePlatformReactPage;
|
|
24
|
+
exports.emitNavigation = emitNavigation;
|
|
25
|
+
//# sourceMappingURL=index.cjs.map
|
|
26
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/navigation.ts","../src/index.ts"],"names":[],"mappings":";;;AAmBO,SAAS,eAAe,EAAA,EAAkB;AAC/C,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,kBAAA,EAAoB;AAAA,IAChD,MAAA,EAAQ,EAAE,EAAA,EAAG;AAAA,IACb,UAAA,EAAY;AAAA,GACb,CAAA;AACD,EAAA,MAAA,CAAO,cAAc,KAAK,CAAA;AAG1B,EAAA,IAAI,CAAC,MAAM,gBAAA,EAAkB;AAC3B,IAAA,MAAA,CAAO,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,EAC3B;AACF;;;ACmCO,SAAS,wBACd,SAAA,EACkB;AAClB,EAAA,OAAO,SAAA;AACT","file":"index.cjs","sourcesContent":["/**\n * Host navigation bridge for PlatformReact pages (RFC 0003).\n *\n * PlatformReact pages run inside the host's React tree but cannot import the\n * host's `useNavigate` directly — that would couple the plugin's bundle to\n * the host's router internals and break the realm boundary the contract\n * preserves. Instead, plugins dispatch a `CustomEvent(\"ethisys:navigate\")`\n * on `window`; the host's `useExtensionNavigation` listener intercepts the\n * event and translates it into a React Router push.\n *\n * `cancelable: true` is load-bearing — without it the host's\n * `event.preventDefault()` is a no-op (CustomEvent defaults to\n * non-cancelable), so `defaultPrevented` stays `false`, the\n * `window.location.assign` fallback fires AFTER the SPA navigation has\n * already run, and the user sees an unwanted full-page reload. The flag is\n * the entire contract between plugin and host on this seam, which is why\n * the helper exists — getting it right per-plugin is exactly the kind of\n * detail that should be baked into the SDK rather than the README.\n */\nexport function emitNavigation(to: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const event = new CustomEvent(\"ethisys:navigate\", {\n detail: { to },\n cancelable: true,\n });\n window.dispatchEvent(event);\n // Fallback for hosts that haven't wired the listener yet — full-document\n // navigation isn't ideal but keeps the page functional.\n if (!event.defaultPrevented) {\n window.location.assign(to);\n }\n}\n","import type { ComponentType } from \"react\";\n\n// ── Public contract ────────────────────────────────────────────────\n\n/**\n * The injected props every PlatformReact page component receives from the\n * host at surface mount time (RFC 0003).\n *\n * The host's surface mount (`PlatformReactSurfaceMount.tsx` in\n * `coreconnect-web`) reads each declared page's `exportName` from the loaded\n * module and renders it with these props. Plugin authors typing their page\n * components against this interface get strict type-checking for everything\n * the host actually supplies — no `any` at the seam.\n *\n * The shape is intentionally minimal. Additional context the host wires up\n * (theme, navigation, telemetry) reaches plugins via cross-cutting providers\n * the host wraps around the mounted component, not through this props\n * object — so growing the host's contextual surface does not change this\n * interface.\n */\nexport interface PlatformReactPageProps {\n /**\n * The extension's GUID as declared on the manifest's `id` field. Stable\n * across enable/disable lifecycle transitions within a tenant.\n */\n extensionId: string;\n /**\n * The active organisation. Plugins MUST scope every data read and write to\n * this id — the host enforces tenant isolation downstream, but propagating\n * the id keeps the dependency direction explicit and testable.\n */\n organisationId: string;\n /**\n * Optional tenant id. Present for tenant-bound deployments; absent for\n * platform-wide pages. Pages that branch on this MUST handle `undefined`.\n */\n tenantId?: string;\n /**\n * The host-side surface key for this page. Matches the `id` declared on\n * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags\n * and for pages that share a component implementation across multiple\n * surfaces.\n */\n pageId: string;\n}\n\n/**\n * Type-safe declaration helper for a PlatformReact page component.\n *\n * The plugin author exports the result of this call from the module path\n * referenced by the manifest's `moduleSpecifier`:\n *\n * @example\n * ```tsx\n * import { definePlatformReactPage, type PlatformReactPageProps } from \"@ethisyscore/components-react\";\n *\n * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (\n * <div>Dashboard for {extensionId} / {organisationId}</div>\n * );\n *\n * export default definePlatformReactPage(Dashboard);\n * ```\n *\n * The helper currently returns the input component unchanged — it exists as\n * a stable indirection point so future host integrations (boundary-injection,\n * suspense fallbacks, error contracts) can land transparently without every\n * plugin author updating their export.\n */\nexport function definePlatformReactPage<P extends PlatformReactPageProps>(\n component: ComponentType<P>,\n): ComponentType<P> {\n return component;\n}\n\nexport { emitNavigation } from \"./navigation\";\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host navigation bridge for PlatformReact pages (RFC 0003).
|
|
5
|
+
*
|
|
6
|
+
* PlatformReact pages run inside the host's React tree but cannot import the
|
|
7
|
+
* host's `useNavigate` directly — that would couple the plugin's bundle to
|
|
8
|
+
* the host's router internals and break the realm boundary the contract
|
|
9
|
+
* preserves. Instead, plugins dispatch a `CustomEvent("ethisys:navigate")`
|
|
10
|
+
* on `window`; the host's `useExtensionNavigation` listener intercepts the
|
|
11
|
+
* event and translates it into a React Router push.
|
|
12
|
+
*
|
|
13
|
+
* `cancelable: true` is load-bearing — without it the host's
|
|
14
|
+
* `event.preventDefault()` is a no-op (CustomEvent defaults to
|
|
15
|
+
* non-cancelable), so `defaultPrevented` stays `false`, the
|
|
16
|
+
* `window.location.assign` fallback fires AFTER the SPA navigation has
|
|
17
|
+
* already run, and the user sees an unwanted full-page reload. The flag is
|
|
18
|
+
* the entire contract between plugin and host on this seam, which is why
|
|
19
|
+
* the helper exists — getting it right per-plugin is exactly the kind of
|
|
20
|
+
* detail that should be baked into the SDK rather than the README.
|
|
21
|
+
*/
|
|
22
|
+
declare function emitNavigation(to: string): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The injected props every PlatformReact page component receives from the
|
|
26
|
+
* host at surface mount time (RFC 0003).
|
|
27
|
+
*
|
|
28
|
+
* The host's surface mount (`PlatformReactSurfaceMount.tsx` in
|
|
29
|
+
* `coreconnect-web`) reads each declared page's `exportName` from the loaded
|
|
30
|
+
* module and renders it with these props. Plugin authors typing their page
|
|
31
|
+
* components against this interface get strict type-checking for everything
|
|
32
|
+
* the host actually supplies — no `any` at the seam.
|
|
33
|
+
*
|
|
34
|
+
* The shape is intentionally minimal. Additional context the host wires up
|
|
35
|
+
* (theme, navigation, telemetry) reaches plugins via cross-cutting providers
|
|
36
|
+
* the host wraps around the mounted component, not through this props
|
|
37
|
+
* object — so growing the host's contextual surface does not change this
|
|
38
|
+
* interface.
|
|
39
|
+
*/
|
|
40
|
+
interface PlatformReactPageProps {
|
|
41
|
+
/**
|
|
42
|
+
* The extension's GUID as declared on the manifest's `id` field. Stable
|
|
43
|
+
* across enable/disable lifecycle transitions within a tenant.
|
|
44
|
+
*/
|
|
45
|
+
extensionId: string;
|
|
46
|
+
/**
|
|
47
|
+
* The active organisation. Plugins MUST scope every data read and write to
|
|
48
|
+
* this id — the host enforces tenant isolation downstream, but propagating
|
|
49
|
+
* the id keeps the dependency direction explicit and testable.
|
|
50
|
+
*/
|
|
51
|
+
organisationId: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional tenant id. Present for tenant-bound deployments; absent for
|
|
54
|
+
* platform-wide pages. Pages that branch on this MUST handle `undefined`.
|
|
55
|
+
*/
|
|
56
|
+
tenantId?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The host-side surface key for this page. Matches the `id` declared on
|
|
59
|
+
* the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags
|
|
60
|
+
* and for pages that share a component implementation across multiple
|
|
61
|
+
* surfaces.
|
|
62
|
+
*/
|
|
63
|
+
pageId: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type-safe declaration helper for a PlatformReact page component.
|
|
67
|
+
*
|
|
68
|
+
* The plugin author exports the result of this call from the module path
|
|
69
|
+
* referenced by the manifest's `moduleSpecifier`:
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
|
|
74
|
+
*
|
|
75
|
+
* const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (
|
|
76
|
+
* <div>Dashboard for {extensionId} / {organisationId}</div>
|
|
77
|
+
* );
|
|
78
|
+
*
|
|
79
|
+
* export default definePlatformReactPage(Dashboard);
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The helper currently returns the input component unchanged — it exists as
|
|
83
|
+
* a stable indirection point so future host integrations (boundary-injection,
|
|
84
|
+
* suspense fallbacks, error contracts) can land transparently without every
|
|
85
|
+
* plugin author updating their export.
|
|
86
|
+
*/
|
|
87
|
+
declare function definePlatformReactPage<P extends PlatformReactPageProps>(component: ComponentType<P>): ComponentType<P>;
|
|
88
|
+
|
|
89
|
+
export { type PlatformReactPageProps, definePlatformReactPage, emitNavigation };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Host navigation bridge for PlatformReact pages (RFC 0003).
|
|
5
|
+
*
|
|
6
|
+
* PlatformReact pages run inside the host's React tree but cannot import the
|
|
7
|
+
* host's `useNavigate` directly — that would couple the plugin's bundle to
|
|
8
|
+
* the host's router internals and break the realm boundary the contract
|
|
9
|
+
* preserves. Instead, plugins dispatch a `CustomEvent("ethisys:navigate")`
|
|
10
|
+
* on `window`; the host's `useExtensionNavigation` listener intercepts the
|
|
11
|
+
* event and translates it into a React Router push.
|
|
12
|
+
*
|
|
13
|
+
* `cancelable: true` is load-bearing — without it the host's
|
|
14
|
+
* `event.preventDefault()` is a no-op (CustomEvent defaults to
|
|
15
|
+
* non-cancelable), so `defaultPrevented` stays `false`, the
|
|
16
|
+
* `window.location.assign` fallback fires AFTER the SPA navigation has
|
|
17
|
+
* already run, and the user sees an unwanted full-page reload. The flag is
|
|
18
|
+
* the entire contract between plugin and host on this seam, which is why
|
|
19
|
+
* the helper exists — getting it right per-plugin is exactly the kind of
|
|
20
|
+
* detail that should be baked into the SDK rather than the README.
|
|
21
|
+
*/
|
|
22
|
+
declare function emitNavigation(to: string): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The injected props every PlatformReact page component receives from the
|
|
26
|
+
* host at surface mount time (RFC 0003).
|
|
27
|
+
*
|
|
28
|
+
* The host's surface mount (`PlatformReactSurfaceMount.tsx` in
|
|
29
|
+
* `coreconnect-web`) reads each declared page's `exportName` from the loaded
|
|
30
|
+
* module and renders it with these props. Plugin authors typing their page
|
|
31
|
+
* components against this interface get strict type-checking for everything
|
|
32
|
+
* the host actually supplies — no `any` at the seam.
|
|
33
|
+
*
|
|
34
|
+
* The shape is intentionally minimal. Additional context the host wires up
|
|
35
|
+
* (theme, navigation, telemetry) reaches plugins via cross-cutting providers
|
|
36
|
+
* the host wraps around the mounted component, not through this props
|
|
37
|
+
* object — so growing the host's contextual surface does not change this
|
|
38
|
+
* interface.
|
|
39
|
+
*/
|
|
40
|
+
interface PlatformReactPageProps {
|
|
41
|
+
/**
|
|
42
|
+
* The extension's GUID as declared on the manifest's `id` field. Stable
|
|
43
|
+
* across enable/disable lifecycle transitions within a tenant.
|
|
44
|
+
*/
|
|
45
|
+
extensionId: string;
|
|
46
|
+
/**
|
|
47
|
+
* The active organisation. Plugins MUST scope every data read and write to
|
|
48
|
+
* this id — the host enforces tenant isolation downstream, but propagating
|
|
49
|
+
* the id keeps the dependency direction explicit and testable.
|
|
50
|
+
*/
|
|
51
|
+
organisationId: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional tenant id. Present for tenant-bound deployments; absent for
|
|
54
|
+
* platform-wide pages. Pages that branch on this MUST handle `undefined`.
|
|
55
|
+
*/
|
|
56
|
+
tenantId?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The host-side surface key for this page. Matches the `id` declared on
|
|
59
|
+
* the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags
|
|
60
|
+
* and for pages that share a component implementation across multiple
|
|
61
|
+
* surfaces.
|
|
62
|
+
*/
|
|
63
|
+
pageId: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Type-safe declaration helper for a PlatformReact page component.
|
|
67
|
+
*
|
|
68
|
+
* The plugin author exports the result of this call from the module path
|
|
69
|
+
* referenced by the manifest's `moduleSpecifier`:
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* import { definePlatformReactPage, type PlatformReactPageProps } from "@ethisyscore/components-react";
|
|
74
|
+
*
|
|
75
|
+
* const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (
|
|
76
|
+
* <div>Dashboard for {extensionId} / {organisationId}</div>
|
|
77
|
+
* );
|
|
78
|
+
*
|
|
79
|
+
* export default definePlatformReactPage(Dashboard);
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* The helper currently returns the input component unchanged — it exists as
|
|
83
|
+
* a stable indirection point so future host integrations (boundary-injection,
|
|
84
|
+
* suspense fallbacks, error contracts) can land transparently without every
|
|
85
|
+
* plugin author updating their export.
|
|
86
|
+
*/
|
|
87
|
+
declare function definePlatformReactPage<P extends PlatformReactPageProps>(component: ComponentType<P>): ComponentType<P>;
|
|
88
|
+
|
|
89
|
+
export { type PlatformReactPageProps, definePlatformReactPage, emitNavigation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/navigation.ts
|
|
2
|
+
function emitNavigation(to) {
|
|
3
|
+
if (typeof window === "undefined") {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
const event = new CustomEvent("ethisys:navigate", {
|
|
7
|
+
detail: { to },
|
|
8
|
+
cancelable: true
|
|
9
|
+
});
|
|
10
|
+
window.dispatchEvent(event);
|
|
11
|
+
if (!event.defaultPrevented) {
|
|
12
|
+
window.location.assign(to);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
function definePlatformReactPage(component) {
|
|
18
|
+
return component;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { definePlatformReactPage, emitNavigation };
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/navigation.ts","../src/index.ts"],"names":[],"mappings":";AAmBO,SAAS,eAAe,EAAA,EAAkB;AAC/C,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACjC,IAAA;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,kBAAA,EAAoB;AAAA,IAChD,MAAA,EAAQ,EAAE,EAAA,EAAG;AAAA,IACb,UAAA,EAAY;AAAA,GACb,CAAA;AACD,EAAA,MAAA,CAAO,cAAc,KAAK,CAAA;AAG1B,EAAA,IAAI,CAAC,MAAM,gBAAA,EAAkB;AAC3B,IAAA,MAAA,CAAO,QAAA,CAAS,OAAO,EAAE,CAAA;AAAA,EAC3B;AACF;;;ACmCO,SAAS,wBACd,SAAA,EACkB;AAClB,EAAA,OAAO,SAAA;AACT","file":"index.js","sourcesContent":["/**\n * Host navigation bridge for PlatformReact pages (RFC 0003).\n *\n * PlatformReact pages run inside the host's React tree but cannot import the\n * host's `useNavigate` directly — that would couple the plugin's bundle to\n * the host's router internals and break the realm boundary the contract\n * preserves. Instead, plugins dispatch a `CustomEvent(\"ethisys:navigate\")`\n * on `window`; the host's `useExtensionNavigation` listener intercepts the\n * event and translates it into a React Router push.\n *\n * `cancelable: true` is load-bearing — without it the host's\n * `event.preventDefault()` is a no-op (CustomEvent defaults to\n * non-cancelable), so `defaultPrevented` stays `false`, the\n * `window.location.assign` fallback fires AFTER the SPA navigation has\n * already run, and the user sees an unwanted full-page reload. The flag is\n * the entire contract between plugin and host on this seam, which is why\n * the helper exists — getting it right per-plugin is exactly the kind of\n * detail that should be baked into the SDK rather than the README.\n */\nexport function emitNavigation(to: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const event = new CustomEvent(\"ethisys:navigate\", {\n detail: { to },\n cancelable: true,\n });\n window.dispatchEvent(event);\n // Fallback for hosts that haven't wired the listener yet — full-document\n // navigation isn't ideal but keeps the page functional.\n if (!event.defaultPrevented) {\n window.location.assign(to);\n }\n}\n","import type { ComponentType } from \"react\";\n\n// ── Public contract ────────────────────────────────────────────────\n\n/**\n * The injected props every PlatformReact page component receives from the\n * host at surface mount time (RFC 0003).\n *\n * The host's surface mount (`PlatformReactSurfaceMount.tsx` in\n * `coreconnect-web`) reads each declared page's `exportName` from the loaded\n * module and renders it with these props. Plugin authors typing their page\n * components against this interface get strict type-checking for everything\n * the host actually supplies — no `any` at the seam.\n *\n * The shape is intentionally minimal. Additional context the host wires up\n * (theme, navigation, telemetry) reaches plugins via cross-cutting providers\n * the host wraps around the mounted component, not through this props\n * object — so growing the host's contextual surface does not change this\n * interface.\n */\nexport interface PlatformReactPageProps {\n /**\n * The extension's GUID as declared on the manifest's `id` field. Stable\n * across enable/disable lifecycle transitions within a tenant.\n */\n extensionId: string;\n /**\n * The active organisation. Plugins MUST scope every data read and write to\n * this id — the host enforces tenant isolation downstream, but propagating\n * the id keeps the dependency direction explicit and testable.\n */\n organisationId: string;\n /**\n * Optional tenant id. Present for tenant-bound deployments; absent for\n * platform-wide pages. Pages that branch on this MUST handle `undefined`.\n */\n tenantId?: string;\n /**\n * The host-side surface key for this page. Matches the `id` declared on\n * the manifest's `ui.platformReactPages[]` entry. Useful for telemetry tags\n * and for pages that share a component implementation across multiple\n * surfaces.\n */\n pageId: string;\n}\n\n/**\n * Type-safe declaration helper for a PlatformReact page component.\n *\n * The plugin author exports the result of this call from the module path\n * referenced by the manifest's `moduleSpecifier`:\n *\n * @example\n * ```tsx\n * import { definePlatformReactPage, type PlatformReactPageProps } from \"@ethisyscore/components-react\";\n *\n * const Dashboard = ({ extensionId, organisationId }: PlatformReactPageProps) => (\n * <div>Dashboard for {extensionId} / {organisationId}</div>\n * );\n *\n * export default definePlatformReactPage(Dashboard);\n * ```\n *\n * The helper currently returns the input component unchanged — it exists as\n * a stable indirection point so future host integrations (boundary-injection,\n * suspense fallbacks, error contracts) can land transparently without every\n * plugin author updating their export.\n */\nexport function definePlatformReactPage<P extends PlatformReactPageProps>(\n component: ComponentType<P>,\n): ComponentType<P> {\n return component;\n}\n\nexport { emitNavigation } from \"./navigation\";\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ethisyscore/components-react",
|
|
3
|
+
"version": "1.7.1",
|
|
4
|
+
"description": "Host-bound React contract for EthisysCore PlatformReact pages (RFC 0003). Provides the typed page-props contract and `definePlatformReactPage` helper so plugin authors get type-safe access to the host realm — extensionId, organisationId, mcp transport — without depending on the host app's internal modules.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"lint": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.5.0",
|
|
30
|
+
"@types/react": "^19.0.0",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"tsup": "^8.5.1",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"vitest": "^4.1.0"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"ethisyscore",
|
|
38
|
+
"platform-react",
|
|
39
|
+
"rfc-0003",
|
|
40
|
+
"plugin"
|
|
41
|
+
],
|
|
42
|
+
"license": "Apache-2.0",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/ethisysltd/ethisyscore-plugin-sdk"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
}
|
|
50
|
+
}
|