@graview/embed 0.1.12 → 0.1.14
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 +73 -1
- package/dist/address.d.ts +34 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +109 -0
- package/dist/address.js.map +1 -0
- package/dist/embed.d.ts +32 -3
- package/dist/embed.d.ts.map +1 -1
- package/dist/embed.js +112 -21
- package/dist/embed.js.map +1 -1
- package/dist/frame.d.ts +30 -2
- package/dist/frame.d.ts.map +1 -1
- package/dist/frame.js +6 -2
- package/dist/frame.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pages-content.d.ts.map +1 -1
- package/dist/pages-content.js +4 -1
- package/dist/pages-content.js.map +1 -1
- package/dist/pages.d.ts +2 -0
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +4 -2
- package/dist/pages.js.map +1 -1
- package/dist/scene-face.d.ts +3 -1
- package/dist/scene-face.d.ts.map +1 -1
- package/dist/scene-face.js +3 -3
- package/dist/scene-face.js.map +1 -1
- package/dist/where.d.ts +35 -0
- package/dist/where.d.ts.map +1 -0
- package/dist/where.js +94 -0
- package/dist/where.js.map +1 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -54,10 +54,82 @@ element (`themeBaseCss(scheme, brand, { scope })`, and the scene's own rules,
|
|
|
54
54
|
rule of it held inside that element, so nothing of the host's is restyled; the
|
|
55
55
|
panes size against the picture's own box (`cqh`) rather than the viewport;
|
|
56
56
|
the routed face runs on a memory router, so the host page's address is never
|
|
57
|
-
touched; the brand's fonts are fetched by the embed rather than assumed. The
|
|
57
|
+
touched (unless the host's page is the app: see below); the brand's fonts are fetched by the embed rather than assumed. The
|
|
58
58
|
store is in memory and starts from the seed on every mount, unless the host
|
|
59
59
|
hands it one.
|
|
60
60
|
|
|
61
|
+
## When the page is the app: the address bar
|
|
62
|
+
|
|
63
|
+
A host whose whole page is the app — Graview Cloud's hosted app is one —
|
|
64
|
+
hands the routed face the address bar, so a place, a record and the home can
|
|
65
|
+
be linked, reloaded and shared:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const handle = mount(root, {
|
|
69
|
+
app, store,
|
|
70
|
+
face: innerWidth < 768 ? "pages" : "graview", // where a bare address opens
|
|
71
|
+
routing: "address", // the default is "memory"
|
|
72
|
+
basePath: "/apps/a1/", // where the app is served; "/" by default
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The host answers every address under the base with the same page. The
|
|
77
|
+
address then says which face is drawn and where on it, as the whole-page
|
|
78
|
+
Shell spells it:
|
|
79
|
+
|
|
80
|
+
| Address | Drawn |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `/apps/a1/places/the-board`, `/apps/a1/tasks/t1` | the routed face, at that page |
|
|
83
|
+
| `/apps/a1#overview=1`, `/apps/a1#focus=t1` | the scene, at that stop (the Graview at altitude) |
|
|
84
|
+
| `/apps/a1` | the routed face's home, or on arrival the host's `face` |
|
|
85
|
+
|
|
86
|
+
Each page the routed face opens is pushed, and Back returns. The scene
|
|
87
|
+
keeps its stop in the fragment the way the Shell does: a step is pushed,
|
|
88
|
+
moving the furniture replaces. The face toggle pushes the address of the
|
|
89
|
+
face it goes to, so Back undoes it, and the page you left is where the
|
|
90
|
+
toggle returns you. A reload stays where it was. `faceAtAddress(options)`
|
|
91
|
+
is the face an address opens on, for a host that renders `<Embed>` itself.
|
|
92
|
+
`placesOf(app)` gives each place's `address` within the app, and
|
|
93
|
+
`addressOf(place, { basePath })` from `@graview/core` gives it under the
|
|
94
|
+
base, spelled as the face's own links are (`pathWithin` reads one back).
|
|
95
|
+
|
|
96
|
+
`routing: "memory"`, the default, is for somebody else's page: it never
|
|
97
|
+
writes `history` and leaves `location` as it was. A host that keeps its
|
|
98
|
+
own history stays on memory routing: `onNavigate(path, how)` is told each
|
|
99
|
+
page the routed face opens (the path within the app, and `"push"`,
|
|
100
|
+
`"replace"` or `"pop"`), and `handle.setPath(path)` sends it back to one
|
|
101
|
+
when the host's own Back arrives.
|
|
102
|
+
|
|
103
|
+
## When the declaration changes
|
|
104
|
+
|
|
105
|
+
A chat that changes the app hands the host a new compiled app and a new
|
|
106
|
+
store. `handle.setApp(app, store)` swaps them under the reader (or
|
|
107
|
+
`setApp(app, remote)`, whose presence comes with it), and the reader stays
|
|
108
|
+
where they were:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
remote.onDeclaration((next) => handle.setApp(latestApp, next));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The face, the place or record open on Pages, and the scene's stop and
|
|
115
|
+
focus are kept. What the change took away falls back to its nearest
|
|
116
|
+
parent: a removed record to its kind's list (or, in the scene, its kind's
|
|
117
|
+
group); a removed kind or place to the home; a lens gone from the scene to
|
|
118
|
+
its kind's group, or the home when the kind went too. Under address
|
|
119
|
+
routing the address stays the source of truth, and one that names
|
|
120
|
+
something gone is replaced, not pushed. The seat, the seats, the people,
|
|
121
|
+
the scheme, the brand and the notices stay; the faces are drawn again, so
|
|
122
|
+
an open menu, a scroll position and a half-typed field do not. `drawn()`
|
|
123
|
+
resolves once the new app is on the page.
|
|
124
|
+
|
|
125
|
+
A host that must remount reads the place first and hands it back:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const at = handle.where(); // { face, path, stop, kind? }
|
|
129
|
+
handle.unmount();
|
|
130
|
+
handle = mount(root, { app, store, at });
|
|
131
|
+
```
|
|
132
|
+
|
|
61
133
|
## What stands over what
|
|
62
134
|
|
|
63
135
|
Every popover, menu and list of suggestions the embed draws — the profile,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Place } from "@graview/core";
|
|
2
|
+
import { type MutableRefObject } from "react";
|
|
3
|
+
import type { EmbedFace, FrameOptions } from "./frame.js";
|
|
4
|
+
/**
|
|
5
|
+
* THE FACE AN EMBED OPENS ON (FR-106): under memory routing, the host's
|
|
6
|
+
* (`face`, else the one its `stop` implies); under address routing, the one
|
|
7
|
+
* the address names — a page past the home is the routed face, a fragment
|
|
8
|
+
* at the home is the scene's stop — and the host's when it names none.
|
|
9
|
+
* `mount` asks this itself; a host that renders `<Embed>` passes it as `face`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function faceAtAddress(options: Pick<FrameOptions, "routing" | "basePath"> & {
|
|
12
|
+
readonly face?: EmbedFace;
|
|
13
|
+
readonly stop?: string;
|
|
14
|
+
}): EmbedFace;
|
|
15
|
+
/** The stop the address holds for the scene on arrival, if it holds one. */
|
|
16
|
+
export declare function stopAtAddress(basePath: string | undefined): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Keeps the face in step with the address: Back and Forward move between
|
|
19
|
+
* the routed face and the scene, and the face toggle pushes the address of
|
|
20
|
+
* the face it goes to. Inside the provider; the scene's own fragment sync
|
|
21
|
+
* (`UrlSync`) is drawn by the scene face, so it is fetched with the scene.
|
|
22
|
+
*/
|
|
23
|
+
export declare function AddressBar({ basePath, pagesWere, shown, onFace, toggle, kinds, places, }: {
|
|
24
|
+
readonly basePath: string | undefined;
|
|
25
|
+
/** The page the routed face had before a new declaration (FR-116), for the toggle to go back to. */
|
|
26
|
+
readonly pagesWere?: string | undefined;
|
|
27
|
+
readonly shown: EmbedFace;
|
|
28
|
+
readonly onFace: ((face: EmbedFace) => void) | undefined;
|
|
29
|
+
/** Where the strip's toggle is handed this bar's way of changing face. */
|
|
30
|
+
readonly toggle: MutableRefObject<((face: EmbedFace) => void) | undefined>;
|
|
31
|
+
readonly kinds: readonly string[];
|
|
32
|
+
readonly places: readonly Place[];
|
|
33
|
+
}): null;
|
|
34
|
+
//# sourceMappingURL=address.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../src/address.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAIlE,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA+B1D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIpJ;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG9E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,SAAS,EACT,KAAK,EACL,MAAM,EACN,MAAM,EACN,KAAK,EACL,MAAM,GACP,EAAE;IACD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oGAAoG;IACpG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACzD,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;CACnC,QA8CA"}
|
package/dist/address.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { addressOf, pathWithin } from "@graview/core";
|
|
2
|
+
import { aggregateId, fromUrl, toUrl, withFocus, withOverview } from "@graview/layout/view";
|
|
3
|
+
import { descentTarget } from "@graview/primitives/frame";
|
|
4
|
+
import { useNavigation } from "@graview/react/provider";
|
|
5
|
+
import { useEffect, useRef } from "react";
|
|
6
|
+
/*
|
|
7
|
+
* THE ADDRESS BAR, FOR A HOST WHOSE PAGE IS THE APP (FR-106).
|
|
8
|
+
*
|
|
9
|
+
* Under `routing: "address"` the address says which face is drawn and
|
|
10
|
+
* where on it, the way the whole-page Shell spells it:
|
|
11
|
+
*
|
|
12
|
+
* <base>/places/the-board, <base>/tasks/t1 a page: the routed face, on its router
|
|
13
|
+
* <base>#overview=1, <base>#focus=t1 a stop: the scene (the Graview at altitude),
|
|
14
|
+
* its view state in the fragment
|
|
15
|
+
* <base> the routed face's home — or, on arrival
|
|
16
|
+
* with no entry the router wrote, the host's face
|
|
17
|
+
*
|
|
18
|
+
* The scene always writes a fragment, even an empty `#`, so an address
|
|
19
|
+
* without one past arrival is a page. The face toggle is a step like any
|
|
20
|
+
* other: it pushes the address of the face it goes to, and Back undoes it.
|
|
21
|
+
*/
|
|
22
|
+
/** The router's own mark on an entry it wrote: `{ idx }` (react-router), kept across a reload. */
|
|
23
|
+
const routerWrote = (state) => typeof state === "object" && state !== null && "idx" in state;
|
|
24
|
+
/** The face an address names, or null when it names none: a bare home the router did not write, or outside the base. */
|
|
25
|
+
function faceNamed(basePath) {
|
|
26
|
+
const path = pathWithin(window.location.pathname, basePath);
|
|
27
|
+
if (path === null)
|
|
28
|
+
return null;
|
|
29
|
+
if (path !== "/")
|
|
30
|
+
return "pages";
|
|
31
|
+
if (window.location.href.includes("#"))
|
|
32
|
+
return fromUrl(window.location.hash).overview ? "graview" : "scene";
|
|
33
|
+
return routerWrote(window.history.state) ? "pages" : null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* THE FACE AN EMBED OPENS ON (FR-106): under memory routing, the host's
|
|
37
|
+
* (`face`, else the one its `stop` implies); under address routing, the one
|
|
38
|
+
* the address names — a page past the home is the routed face, a fragment
|
|
39
|
+
* at the home is the scene's stop — and the host's when it names none.
|
|
40
|
+
* `mount` asks this itself; a host that renders `<Embed>` passes it as `face`.
|
|
41
|
+
*/
|
|
42
|
+
export function faceAtAddress(options) {
|
|
43
|
+
const asked = options.face ?? (options.stop && fromUrl(options.stop).overview ? "graview" : "scene");
|
|
44
|
+
if (options.routing !== "address" || typeof window === "undefined")
|
|
45
|
+
return asked;
|
|
46
|
+
return faceNamed(options.basePath) ?? asked;
|
|
47
|
+
}
|
|
48
|
+
/** The stop the address holds for the scene on arrival, if it holds one. */
|
|
49
|
+
export function stopAtAddress(basePath) {
|
|
50
|
+
if (typeof window === "undefined")
|
|
51
|
+
return undefined;
|
|
52
|
+
return pathWithin(window.location.pathname, basePath) === "/" && window.location.href.includes("#") ? window.location.hash || "#" : undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keeps the face in step with the address: Back and Forward move between
|
|
56
|
+
* the routed face and the scene, and the face toggle pushes the address of
|
|
57
|
+
* the face it goes to. Inside the provider; the scene's own fragment sync
|
|
58
|
+
* (`UrlSync`) is drawn by the scene face, so it is fetched with the scene.
|
|
59
|
+
*/
|
|
60
|
+
export function AddressBar({ basePath, pagesWere, shown, onFace, toggle, kinds, places, }) {
|
|
61
|
+
const { view, go } = useNavigation();
|
|
62
|
+
const latest = useRef({ shown, view, onFace });
|
|
63
|
+
latest.current = { shown, view, onFace };
|
|
64
|
+
// The page the routed face was on when the scene was asked for, to go back to by the toggle.
|
|
65
|
+
const pagesAt = useRef(pagesWere ?? "/");
|
|
66
|
+
const leaving = () => {
|
|
67
|
+
pagesAt.current = `${pathWithin(window.location.pathname, basePath) ?? "/"}${window.location.search}`;
|
|
68
|
+
};
|
|
69
|
+
toggle.current = (next) => {
|
|
70
|
+
const { shown: from, view: here, onFace: tell } = latest.current;
|
|
71
|
+
if (next === "pages" && from !== "pages") {
|
|
72
|
+
window.history.pushState(null, "", addressOf(pagesAt.current, { basePath }));
|
|
73
|
+
}
|
|
74
|
+
else if (next !== "pages" && from === "pages") {
|
|
75
|
+
leaving();
|
|
76
|
+
// The view the scene will draw, decided here so the fragment pushed is the one it lands on.
|
|
77
|
+
const stop = next === "graview" ? withOverview(here, true) : here.overview ? withFocus(withOverview(here, false), descentTarget(here, kinds)) : here;
|
|
78
|
+
go(stop);
|
|
79
|
+
window.history.pushState(null, "", `${addressOf("/", { basePath })}${toUrl(stop)}`);
|
|
80
|
+
}
|
|
81
|
+
tell?.(next);
|
|
82
|
+
};
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const onPop = () => {
|
|
85
|
+
if (pathWithin(window.location.pathname, basePath) === null)
|
|
86
|
+
return;
|
|
87
|
+
// Past arrival, a bare home is the routed face's.
|
|
88
|
+
const named = faceNamed(basePath) ?? "pages";
|
|
89
|
+
const { shown: from, onFace: tell } = latest.current;
|
|
90
|
+
if (named === "pages") {
|
|
91
|
+
if (from !== "pages")
|
|
92
|
+
tell?.("pages");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Between the scene and the Graview the fragment sync follows by itself; from the pages, the scene comes back where the fragment says.
|
|
96
|
+
if (from !== "pages")
|
|
97
|
+
return;
|
|
98
|
+
const parsed = fromUrl(window.location.hash);
|
|
99
|
+
const name = parsed.within?.["view"];
|
|
100
|
+
const place = name !== undefined && !parsed.focusId ? places.find((candidate) => candidate.as === name) : undefined;
|
|
101
|
+
go(place ? { ...parsed, focusId: aggregateId(place.kind) } : parsed);
|
|
102
|
+
tell?.(named);
|
|
103
|
+
};
|
|
104
|
+
window.addEventListener("popstate", onPop);
|
|
105
|
+
return () => window.removeEventListener("popstate", onPop);
|
|
106
|
+
}, [basePath, go, places]);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"address.js","sourceRoot":"","sources":["../src/address.tsx"],"names":[],"mappings":"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"}
|
package/dist/embed.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnySchema, type Brand, type GraviewApp, type Person, type Principal, type Store } from "@graview/core";
|
|
2
2
|
import type { StudioOffered, StudioOnApply, StudioPlace as StudioPlaceType } from "@graview/studio";
|
|
3
3
|
import type { CompanionMode } from "@graview/primitives";
|
|
4
4
|
import { type Notice, type NoticeHandle } from "@graview/primitives/frame";
|
|
5
5
|
import { type Scheme } from "@graview/react/provider";
|
|
6
|
-
import
|
|
6
|
+
import type { EmbedWhere } from "./where.js";
|
|
7
|
+
import { AUTO_SCENE_HEIGHT, type EmbedFace, type EmbedHostContext, type EmbedRemote, type FrameOptions } from "./frame.js";
|
|
7
8
|
/**
|
|
8
9
|
* FETCH A FACE BEFORE IT IS DRAWN (FR-57). A host that knows which face it
|
|
9
10
|
* will open on — Graview Cloud's shell knows from the window's width before
|
|
@@ -20,7 +21,8 @@ export declare function preload(...faces: readonly EmbedFace[]): Promise<void>;
|
|
|
20
21
|
* one element. So the theme is scoped to that element, the panes size
|
|
21
22
|
* against the picture's own box, the routed face runs on a memory router,
|
|
22
23
|
* the store lives in memory and starts from the seed, and the view state is
|
|
23
|
-
* a prop rather than a URL
|
|
24
|
+
* a prop rather than a URL — unless the host's page IS the app and it hands
|
|
25
|
+
* over the address bar (`routing: "address"`, FR-106). Everything else — the scene, the rails, the
|
|
24
26
|
* inspector, the pages — is the framework's own, unchanged.
|
|
25
27
|
*/
|
|
26
28
|
export { AUTO_SCENE_HEIGHT };
|
|
@@ -65,6 +67,13 @@ export interface EmbedOptions<S extends AnySchema = AnySchema> extends FrameOpti
|
|
|
65
67
|
* `"hidden"`, which is the host's to say.
|
|
66
68
|
*/
|
|
67
69
|
readonly companion?: CompanionMode;
|
|
70
|
+
/**
|
|
71
|
+
* WHERE TO OPEN, AS `handle.where()` SAID IT (FR-116): the face, the page
|
|
72
|
+
* on Pages and the scene's stop, over `face`, `path` and `stop`, settled
|
|
73
|
+
* in this app — what it no longer has falls back to its nearest parent. A
|
|
74
|
+
* host that must remount on a new declaration hands the old place back here.
|
|
75
|
+
*/
|
|
76
|
+
readonly at?: EmbedWhere;
|
|
68
77
|
}
|
|
69
78
|
/** The studio an embed offers, for a host that keeps the declaration itself. */
|
|
70
79
|
export interface EmbedStudio {
|
|
@@ -110,8 +119,28 @@ export interface EmbedProps<S extends AnySchema = AnySchema> extends EmbedOption
|
|
|
110
119
|
export declare function faceOf(stop: string | undefined): EmbedFace;
|
|
111
120
|
export declare function Embed<S extends AnySchema>(props: EmbedProps<S>): import("react").JSX.Element;
|
|
112
121
|
export interface EmbedHandle {
|
|
122
|
+
/**
|
|
123
|
+
* WHERE THE READER IS (FR-116): the face, the page on Pages (or the one it
|
|
124
|
+
* last had) and the scene's stop — for a host that must remount to hand
|
|
125
|
+
* back as `mount(…, { at })`.
|
|
126
|
+
*/
|
|
127
|
+
where(): EmbedWhere;
|
|
128
|
+
/**
|
|
129
|
+
* A NEW DECLARATION UNDER THE READER (FR-116): the app and its store — or a
|
|
130
|
+
* remote, whose presence comes with it — swapped in place. The face, the
|
|
131
|
+
* page on Pages and the scene's stop are kept; what the app no longer has
|
|
132
|
+
* falls back to its nearest parent. The seat, the seats, the people, the
|
|
133
|
+
* scheme, the brand asked for and the notices stay; what is drawn is drawn
|
|
134
|
+
* again, so an open menu, a scroll and a half-typed field do not.
|
|
135
|
+
*/
|
|
136
|
+
setApp(app: GraviewApp<AnySchema>, store: Store<AnySchema> | EmbedRemote<AnySchema>): void;
|
|
113
137
|
setFace(face: EmbedFace): void;
|
|
114
138
|
setStop(stop: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* Sends the routed face to a path within the app's own routes (FR-106): a
|
|
141
|
+
* host that keeps its own history hands back the path its Back arrived at.
|
|
142
|
+
*/
|
|
143
|
+
setPath(path: string): void;
|
|
115
144
|
setScheme(scheme: Scheme): void;
|
|
116
145
|
/** Put another principal at the keyboard; the store and its history stay. */
|
|
117
146
|
setSeat(principal: Principal): void;
|
package/dist/embed.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../src/embed.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../src/embed.tsx"],"names":[],"mappings":"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"}
|
package/dist/embed.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { pathWithin } from "@graview/core";
|
|
3
|
+
import { EMPTY_VIEW, aggregateId, fromUrl, toUrl, withFocus, withOverview } from "@graview/layout/view";
|
|
3
4
|
import { VISUALLY_HIDDEN, descentTarget, fetchFrameworkViews, frameworkViewDoors, useWidth } from "@graview/primitives/frame";
|
|
4
5
|
import { createNoticeBoard } from "@graview/primitives/frame";
|
|
5
6
|
import { ErrorReportContext, GraviewProvider, openingView, useNavigation } from "@graview/react/provider";
|
|
6
|
-
import {
|
|
7
|
+
import { AddressBar, faceAtAddress, stopAtAddress } from "./address.js";
|
|
8
|
+
import { createContext, createElement, lazy, Suspense, useCallback, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
9
|
import { createRoot } from "react-dom/client";
|
|
8
10
|
import { flushSync } from "react-dom";
|
|
9
11
|
import { AUTO_SCENE_HEIGHT, FaceBoundary, FrameNotices, providerProps, storeOf, Strip, useErrorReport, useFrame, useIntrinsicHeight, useReady, useViews, } from "./frame.js";
|
|
@@ -82,7 +84,8 @@ const StudioPlace = lazy(() => import("@graview/studio").then((studio) => ({ def
|
|
|
82
84
|
* one element. So the theme is scoped to that element, the panes size
|
|
83
85
|
* against the picture's own box, the routed face runs on a memory router,
|
|
84
86
|
* the store lives in memory and starts from the seed, and the view state is
|
|
85
|
-
* a prop rather than a URL
|
|
87
|
+
* a prop rather than a URL — unless the host's page IS the app and it hands
|
|
88
|
+
* over the address bar (`routing: "address"`, FR-106). Everything else — the scene, the rails, the
|
|
86
89
|
* inspector, the pages — is the framework's own, unchanged.
|
|
87
90
|
*/
|
|
88
91
|
export { AUTO_SCENE_HEIGHT };
|
|
@@ -113,12 +116,40 @@ export function faceOf(stop) {
|
|
|
113
116
|
return stop && fromUrl(stop).overview ? "graview" : "scene";
|
|
114
117
|
}
|
|
115
118
|
export function Embed(props) {
|
|
116
|
-
const
|
|
119
|
+
const [, ready] = useState(false);
|
|
120
|
+
const arrived = useCallback(() => ready(true), []);
|
|
121
|
+
// A place handed back is settled before the embed draws: until the rules for it are here, nothing stands in the element.
|
|
122
|
+
return props.at && !settling ? _jsx(Settling, { onReady: arrived }) : _jsx(Drawing, { ...props });
|
|
123
|
+
}
|
|
124
|
+
function Drawing(props) {
|
|
125
|
+
const { app, face = props.at?.face ?? faceOf(props.stop), stop, toggle = true, standing = "Everything is in order", principal, heading = 2 } = props;
|
|
117
126
|
const { rootRef, scope, css, scheme, store, presence, brand, auto, height } = useFrame(props);
|
|
118
127
|
const views = useViews(props, frameworkViewDoors);
|
|
119
128
|
const kinds = app.schema.kinds;
|
|
120
|
-
|
|
121
|
-
const
|
|
129
|
+
const address = props.routing === "address";
|
|
130
|
+
const at = useMemo(() => (props.at ? settling.settleAt(props, store.seenBy(principal ?? { kind: "human" }), views.places()) : undefined), []);
|
|
131
|
+
// The first view only: after it, where the reader goes is theirs. Under address routing, a stop in the fragment is where it starts.
|
|
132
|
+
const initialView = useMemo(() => viewFor(face, at?.stop ?? (address ? stopAtAddress(props.basePath) : undefined) ?? stop, kinds, views.places(), openingView(views, app.schema)), []);
|
|
133
|
+
/*
|
|
134
|
+
* THE PAGE ON PAGES, KEPT (FR-116): where the routed face is, or last was,
|
|
135
|
+
* so the scene and back is the same page, and `where()` can say it. Under
|
|
136
|
+
* memory routing the routed face opens on it; under address routing the
|
|
137
|
+
* address is where it is.
|
|
138
|
+
*/
|
|
139
|
+
const pagesAt = useRef(undefined);
|
|
140
|
+
pagesAt.current ??= { path: at?.path ?? (address ? (face === "pages" ? (pathWithin(window.location.pathname, props.basePath) ?? "/") + window.location.search : "/") : (props.path ?? "/")), asked: props.path };
|
|
141
|
+
if (props.path !== pagesAt.current.asked)
|
|
142
|
+
pagesAt.current = { path: props.path ?? "/", asked: props.path };
|
|
143
|
+
const told = useRef(props.onNavigate);
|
|
144
|
+
told.current = props.onNavigate;
|
|
145
|
+
const onNavigate = useCallback((path, how) => {
|
|
146
|
+
pagesAt.current.path = path;
|
|
147
|
+
told.current?.(path, how);
|
|
148
|
+
}, []);
|
|
149
|
+
const whereabouts = useContext(Whereabouts);
|
|
150
|
+
if (whereabouts)
|
|
151
|
+
whereabouts.pages = () => pagesAt.current.path;
|
|
152
|
+
const toggled = useRef(undefined);
|
|
122
153
|
/*
|
|
123
154
|
* NARROW, THE PAGES (FR-13). Below `pagesBelow` the scene and the Graview
|
|
124
155
|
* give way to the routed face, and come back when there is room: the
|
|
@@ -130,11 +161,11 @@ export function Embed(props) {
|
|
|
130
161
|
useIntrinsicHeight(rootRef, shown, props.onIntrinsicHeight);
|
|
131
162
|
const report = useErrorReport(props.onError, shown);
|
|
132
163
|
const ready = useReady(props.onReady, shown);
|
|
133
|
-
const
|
|
134
|
-
|
|
164
|
+
const drawnTold = useRef(props.onDrawn);
|
|
165
|
+
drawnTold.current = props.onDrawn;
|
|
135
166
|
const drawn = useCallback((asked, drew) => {
|
|
136
167
|
ready(drew);
|
|
137
|
-
|
|
168
|
+
drawnTold.current?.(asked);
|
|
138
169
|
}, [ready]);
|
|
139
170
|
/*
|
|
140
171
|
* THE EMBED IS ITSELF A LANDMARK.
|
|
@@ -150,7 +181,25 @@ export function Embed(props) {
|
|
|
150
181
|
* the docs warn about, said out loud by `landmark-unique` instead of
|
|
151
182
|
* silently.
|
|
152
183
|
*/
|
|
153
|
-
return (_jsxs("section", { ref: rootRef, className: scope, "aria-label": props.label ?? app.name, "data-graview-embed": shown, "data-graview-scheme": scheme, ...(narrow ? { "data-graview-embed-narrow": "" } : {}), style: { position: "relative", height, minHeight: auto ? 0 : 320, display: "flex", flexDirection: "column", overflow: "hidden", borderRadius: "var(--graview-radius, 12px)" }, children: [_jsx("style", { children: css }), heading !== false && shown !== "pages" ? _jsx(HeadingAt, { level: heading, children: props.label ?? app.name }) : null, _jsxs(ErrorReportContext.Provider, { value: report, children: [_jsx(FrameNotices, { rootRef: rootRef, board: props.notices }), _jsx(FaceBoundary, { module: "@graview/react", report: report, content: true, children: _jsxs(GraviewProvider, { store: store, views: views, initialView: initialView, scheme: scheme, ...providerProps(props, presence, brand), children: [_jsx(Faces, { face: shown, stop: stop, kinds: kinds, places: views.places() }), toggle && shown !== "picture" ? (_jsx(FaceBoundary, { module: "@graview/embed", report: report, children: _jsx(EmbedStrip, { app: app, studio: props.studio, face: shown, narrow: narrow, onFace: props.onFace, standing: standing, seats: props.seats, principal: principal, onSeat: props.onSeat, hostActions: props.hostActions, report: report }) })) : null, _jsx(FaceBoundary, { module: shown === "pages" || shown === "picture" ? "@graview/pages" : "@graview/react", report: report, content: true, children: _jsxs(Suspense, { fallback: _jsx(Arriving, { auto: auto, scene: shown !== "pages" && shown !== "picture" }), children: [shown === "picture" ? (_jsx(PictureFace, { store: store, views: views, as: (stop ? fromUrl(stop).within?.["view"] : undefined) ?? "" })) : shown === "pages" ? (_jsx(PagesContent, { store: store, views: views, presence: presence, auto: auto, brand: brand, props: props })) : (_jsx(SceneFace, { auto: auto, rememberAs: app.name, scheme: scheme, scope: scope, ...(props.companion ? { companion: props.companion } : {}) })), _jsx(Drawn, { asked: face, shown: shown, onDrawn: drawn })] }) }, shown)] }) })] })] }));
|
|
184
|
+
return (_jsxs("section", { ref: rootRef, className: scope, "aria-label": props.label ?? app.name, "data-graview-embed": shown, "data-graview-scheme": scheme, ...(narrow ? { "data-graview-embed-narrow": "" } : {}), style: { position: "relative", height, minHeight: auto ? 0 : 320, display: "flex", flexDirection: "column", overflow: "hidden", borderRadius: "var(--graview-radius, 12px)" }, children: [_jsx("style", { children: css }), heading !== false && shown !== "pages" ? _jsx(HeadingAt, { level: heading, children: props.label ?? app.name }) : null, _jsxs(ErrorReportContext.Provider, { value: report, children: [_jsx(FrameNotices, { rootRef: rootRef, board: props.notices }), _jsx(FaceBoundary, { module: "@graview/react", report: report, content: true, children: _jsxs(GraviewProvider, { store: store, views: views, initialView: initialView, scheme: scheme, ...providerProps(props, presence, brand), children: [_jsx(Faces, { face: shown, stop: stop, kinds: kinds, places: views.places() }), whereabouts ? _jsx(Watch, { into: whereabouts }) : null, address ? _jsx(AddressBar, { pagesWere: at?.path, basePath: props.basePath, shown: shown, onFace: props.onFace, toggle: toggled, kinds: kinds, places: views.places() }) : null, toggle && shown !== "picture" ? (_jsx(FaceBoundary, { module: "@graview/embed", report: report, children: _jsx(EmbedStrip, { app: app, studio: props.studio, face: shown, narrow: narrow, onFace: address ? (next) => toggled.current?.(next) : props.onFace, standing: standing, seats: props.seats, principal: principal, onSeat: props.onSeat, hostActions: props.hostActions, report: report }) })) : null, _jsx(FaceBoundary, { module: shown === "pages" || shown === "picture" ? "@graview/pages" : "@graview/react", report: report, content: true, children: _jsxs(Suspense, { fallback: _jsx(Arriving, { auto: auto, scene: shown !== "pages" && shown !== "picture" }), children: [shown === "picture" ? (_jsx(PictureFace, { store: store, views: views, as: (stop ? fromUrl(stop).within?.["view"] : undefined) ?? "" })) : shown === "pages" ? (_jsx(PagesContent, { store: store, views: views, presence: presence, auto: auto, brand: brand, props: { ...props, onNavigate, ...(address ? {} : { path: pagesAt.current.path }) } })) : (_jsx(SceneFace, { address: address, auto: auto, rememberAs: app.name, scheme: scheme, scope: scope, ...(props.companion ? { companion: props.companion } : {}) })), _jsx(Drawn, { asked: face, shown: shown, onDrawn: drawn })] }) }, shown)] }) })] })] }));
|
|
185
|
+
}
|
|
186
|
+
const Whereabouts = createContext(null);
|
|
187
|
+
/** Keeps the scene's view where `where()` can read it. */
|
|
188
|
+
function Watch({ into }) {
|
|
189
|
+
into.view = useNavigation().view;
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/*
|
|
193
|
+
* SETTLING A PLACE IN A NEW APP, fetched when one is handed over (FR-116):
|
|
194
|
+
* a declaration changes rarely, and a page that never swaps one never loads
|
|
195
|
+
* the rules for what falls back to what.
|
|
196
|
+
*/
|
|
197
|
+
let settling;
|
|
198
|
+
const fetchSettling = () => import("./where.js").then((module) => void (settling = module));
|
|
199
|
+
/** Stands in for the embed while the rules for a handed-back place arrive, then draws it. */
|
|
200
|
+
function Settling({ onReady }) {
|
|
201
|
+
useLayoutEffect(() => void fetchSettling().then(onReady), [onReady]);
|
|
202
|
+
return null;
|
|
154
203
|
}
|
|
155
204
|
/** A heading at a level the host chose, heard and not seen. */
|
|
156
205
|
function HeadingAt({ level, children }) {
|
|
@@ -202,7 +251,8 @@ function EmbedStrip({ app, studio, face, narrow = false, onFace, standing, seats
|
|
|
202
251
|
fontSize: "0.875rem",
|
|
203
252
|
borderWidth: 1,
|
|
204
253
|
borderStyle: "solid",
|
|
205
|
-
|
|
254
|
+
// The face you are on wears the capsule; the other is a word to press (FR-117).
|
|
255
|
+
borderColor: candidate.pressed ? "var(--graview-accent)" : "transparent",
|
|
206
256
|
color: candidate.pressed ? "var(--graview-accent)" : "var(--graview-ink-muted)",
|
|
207
257
|
background: candidate.pressed ? "var(--graview-panel)" : "transparent",
|
|
208
258
|
}, children: candidate.label }, candidate.id))), scene: face === "pages"
|
|
@@ -226,16 +276,26 @@ function StudioOnTheStrip({ app, studio }) {
|
|
|
226
276
|
return _jsx(Handed, { ...props });
|
|
227
277
|
return (_jsx(Suspense, { fallback: null, children: _jsx(StudioPlace, { ...props }) }));
|
|
228
278
|
}
|
|
279
|
+
/** The options with a new declaration in them: where the reader was stands for the path and the stop the host first gave. */
|
|
280
|
+
function swapped({ path: _path, stop: _stop, ...options }, swap) {
|
|
281
|
+
return { ...options, app: swap.app, at: swap.at, ...(swap.remote.presence ? { presence: swap.remote.presence } : {}) };
|
|
282
|
+
}
|
|
229
283
|
/**
|
|
230
284
|
* Mounts an app into an element and hands back the controls. The first
|
|
231
285
|
* render is synchronous, so what comes back is already on the page.
|
|
232
286
|
*/
|
|
233
287
|
export function mount(element, options) {
|
|
234
|
-
|
|
288
|
+
let store = (options.store ?? options.remote?.store ?? storeOf(options.app, options.seed));
|
|
235
289
|
const board = options.notices ?? createNoticeBoard();
|
|
236
290
|
let setters = null;
|
|
291
|
+
const here = {};
|
|
292
|
+
let swaps = 0;
|
|
293
|
+
// A new declaration on its way (FR-116): `drawn()` waits for it.
|
|
294
|
+
let swapping = Promise.resolve();
|
|
237
295
|
// Which face is asked for, which is drawn, and who is waiting for the one asked for.
|
|
238
|
-
|
|
296
|
+
// Under address routing, the face the address names (FR-106).
|
|
297
|
+
const opening = faceAtAddress({ ...options, ...(options.at ? { face: options.at.face } : {}) });
|
|
298
|
+
let asked = opening;
|
|
239
299
|
let drawnFace = null;
|
|
240
300
|
let waiting = [];
|
|
241
301
|
const onDrawn = (face) => {
|
|
@@ -249,29 +309,60 @@ export function mount(element, options) {
|
|
|
249
309
|
resolve();
|
|
250
310
|
};
|
|
251
311
|
function Host() {
|
|
252
|
-
const [face, setFace] = useState(
|
|
312
|
+
const [face, setFace] = useState(opening);
|
|
253
313
|
const [stop, setStop] = useState(options.stop);
|
|
314
|
+
const [path, setPath] = useState(options.path);
|
|
254
315
|
const [scheme, setScheme] = useState(options.scheme ?? "auto");
|
|
255
316
|
const [principal, setSeat] = useState(options.principal);
|
|
256
317
|
const [seats, setSeats] = useState(options.seats);
|
|
257
318
|
const [people, setPeople] = useState(options.people);
|
|
258
319
|
const [hostContext, setHostContext] = useState(options.hostContext);
|
|
259
320
|
const [brand, setBrand] = useState(options.brand);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
321
|
+
const [swap, setSwap] = useState(undefined);
|
|
322
|
+
setters = {
|
|
323
|
+
app: (next) => {
|
|
324
|
+
setSwap(next);
|
|
325
|
+
setPath(undefined);
|
|
326
|
+
setStop(undefined);
|
|
327
|
+
},
|
|
328
|
+
face: setFace, stop: setStop, path: setPath, scheme: setScheme, seat: setSeat, seats: setSeats, people: setPeople, hostContext: setHostContext, brand: setBrand
|
|
329
|
+
};
|
|
330
|
+
return (_jsx(Whereabouts.Provider, { value: here, children: _jsx(Embed, { ...(swap ? swapped(options, swap) : options), store: store, notices: board, face: face, ...(stop !== undefined ? { stop } : {}), ...(path !== undefined ? { path } : {}), ...(principal ? { principal } : {}), ...(seats ? { seats } : {}), ...(people ? { people } : {}), ...(hostContext ? { hostContext } : {}), ...(brand ? { brand } : {}), scheme: scheme, onFace: (next) => {
|
|
331
|
+
asked = next;
|
|
332
|
+
setFace(next);
|
|
333
|
+
}, onSeat: setSeat, onDrawn: onDrawn }, swap?.n ?? 0) }));
|
|
265
334
|
}
|
|
335
|
+
const where = () => {
|
|
336
|
+
const focus = here.view?.focusId;
|
|
337
|
+
const kind = focus ? store.graph.getNode(focus)?.kind : undefined;
|
|
338
|
+
return { face: asked, path: here.pages?.() ?? options.path ?? "/", stop: here.view ? toUrl(here.view) : (options.stop ?? "#"), ...(kind ? { kind } : {}) };
|
|
339
|
+
};
|
|
266
340
|
const root = createRoot(element);
|
|
267
341
|
flushSync(() => root.render(_jsx(Host, {})));
|
|
268
342
|
return {
|
|
269
|
-
store
|
|
343
|
+
get store() {
|
|
344
|
+
return store;
|
|
345
|
+
},
|
|
346
|
+
where,
|
|
347
|
+
setApp: (app, given) => {
|
|
348
|
+
const remote = "graph" in given ? { store: given } : given;
|
|
349
|
+
// The app on the page stays drawn until the rules for settling the place in the new one are here.
|
|
350
|
+
const swap = () => {
|
|
351
|
+
const at = where();
|
|
352
|
+
store = remote.store;
|
|
353
|
+
flushSync(() => setters?.app({ app, remote, at, n: ++swaps }));
|
|
354
|
+
};
|
|
355
|
+
if (settling)
|
|
356
|
+
swap();
|
|
357
|
+
else
|
|
358
|
+
swapping = swapping.then(fetchSettling).then(swap);
|
|
359
|
+
},
|
|
270
360
|
setFace: (face) => {
|
|
271
361
|
asked = face;
|
|
272
362
|
flushSync(() => setters?.face(face));
|
|
273
363
|
},
|
|
274
364
|
setStop: (stop) => flushSync(() => setters?.stop(stop)),
|
|
365
|
+
setPath: (path) => flushSync(() => setters?.path(path)),
|
|
275
366
|
setScheme: (scheme) => flushSync(() => setters?.scheme(scheme)),
|
|
276
367
|
setSeat: (principal) => flushSync(() => setters?.seat(principal)),
|
|
277
368
|
setSeats: (seats) => flushSync(() => setters?.seats(seats)),
|
|
@@ -279,7 +370,7 @@ export function mount(element, options) {
|
|
|
279
370
|
setHostContext: (context) => flushSync(() => setters?.hostContext(context)),
|
|
280
371
|
setBrand: (brand) => flushSync(() => setters?.brand(brand)),
|
|
281
372
|
/* Drawn is the face asked for — below `pagesBelow`, drawn as the pages that stand in for it. */
|
|
282
|
-
drawn: () => (drawnFace === asked ?
|
|
373
|
+
drawn: () => swapping.then(() => (drawnFace === asked ? undefined : new Promise((resolve) => waiting.push(resolve)))),
|
|
283
374
|
notify: (notice) => {
|
|
284
375
|
let said;
|
|
285
376
|
flushSync(() => {
|