@monimesl/dot-webview 0.1.0-alpha.2

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 ADDED
@@ -0,0 +1,3 @@
1
+ # @monimesl/dot-webview
2
+
3
+ The view layer, independent of any UI library.
@@ -0,0 +1,85 @@
1
+ import { ColorScheme, DotEvent, DotEvent as DotEvent$1, Envelope, EventEmitter, HostLayerMessages, LogicLayerMessages, MessageChannel, PageLaunchOptions, PageState, PageState as PageState$1, RenderContext, SystemContext, SystemContext as SystemContext$1, Theme, Theme as Theme$1, Transport, Transport as Transport$1, UILayerMessages, ViewRender, ViewRender as ViewRender$1, hostTransport } from "@monimesl/dot-core";
2
+ //#region src/store.d.ts
3
+ interface ReadonlyStore<T> {
4
+ readonly get: () => T;
5
+ readonly subscribe: (listener: () => void) => () => void;
6
+ }
7
+ export declare class Store<T> implements ReadonlyStore<T> {
8
+ #private;
9
+ constructor(value: T);
10
+ readonly get: () => T;
11
+ readonly subscribe: (listener: () => void) => (() => void);
12
+ set(value: T): void;
13
+ }
14
+ //#endregion
15
+ //#region src/page.d.ts
16
+ type Dispatch = (instanceId: number, handler: string, event: DotEvent$1) => void;
17
+ export declare class PageController {
18
+ #private;
19
+ readonly pageId: string;
20
+ readonly pageInstanceId: number;
21
+ readonly viewRenderer: ViewRender$1;
22
+ readonly pageOptions: PageLaunchOptions;
23
+ constructor(instanceId: number, id: string, options: PageLaunchOptions, viewRenderer: ViewRender$1, dispatch: Dispatch);
24
+ readonly getState: () => PageState$1 | null;
25
+ readonly subscribe: (listener: () => void) => (() => void);
26
+ on(handler: string): (event: DotEvent$1) => void;
27
+ initializeState(state: PageState$1): void;
28
+ updateState(changes: PageState$1): void;
29
+ }
30
+ //#endregion
31
+ //#region src/renderer.d.ts
32
+ interface DotContext {
33
+ readonly theme: ReadonlyStore<Theme$1>;
34
+ readonly systemContext: ReadonlyStore<SystemContext$1>;
35
+ }
36
+ interface Renderer {
37
+ readonly name: string;
38
+ mount(container: Element, page: PageController, dotContext: DotContext): () => void;
39
+ }
40
+ //#endregion
41
+ //#region src/webview.d.ts
42
+ type WebviewChannel = MessageChannel<UILayerMessages, LogicLayerMessages & HostLayerMessages>;
43
+ interface WebviewHooks {
44
+ pageOpen(page: PageController): void;
45
+ pageClose(page: PageController): void;
46
+ error(error: unknown, message: Envelope): void;
47
+ }
48
+ interface WebviewPlugin {
49
+ readonly name: string;
50
+ setup(webview: Webview): void;
51
+ }
52
+ interface WebviewOptions {
53
+ renderer: Renderer;
54
+ transport: Transport$1;
55
+ plugins?: readonly WebviewPlugin[];
56
+ views: Record<string, ViewRender$1>;
57
+ }
58
+ /**
59
+ * The view layer: shows the navigation stack the host announces, keeps each open page in sync with
60
+ * the service, and hands pages to the renderer. Every open page gets its own element in the
61
+ * container; only the top one is visible, so going back is instant and keeps its UI.
62
+ */
63
+ declare class Webview {
64
+ #private;
65
+ readonly pageRenderer: Renderer;
66
+ readonly channel: WebviewChannel;
67
+ readonly hooks: EventEmitter<WebviewHooks>;
68
+ readonly theme: Store<Theme$1>;
69
+ readonly systemContext: Store<SystemContext$1>;
70
+ constructor({ transport, renderer, views, plugins }: WebviewOptions);
71
+ preparePlugin(plugin: WebviewPlugin): this;
72
+ start(): () => void;
73
+ mount(container: Element): () => void;
74
+ get stack(): readonly PageController[];
75
+ locatePage(instanceId: number): PageController | undefined;
76
+ }
77
+ //#endregion
78
+ //#region src/index.d.ts
79
+ export interface StartOptions extends WebviewOptions {
80
+ container: Element;
81
+ }
82
+ export declare function startWebview({ container, ...options }: StartOptions): Webview;
83
+ //#endregion
84
+ export { type ColorScheme, type DotContext, type DotEvent, type PageState, type ReadonlyStore, type RenderContext, type Renderer, type SystemContext, type Theme, type Transport, type ViewRender, type WebviewChannel, type WebviewHooks, type WebviewOptions, type WebviewPlugin, hostTransport };
85
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/store.ts","../src/page.ts","../src/renderer.ts","../src/webview.ts","../src/index.ts"],"mappings":";;UAAiB,cAAc;WACpB,WAAW;WACX,YAAY;;qBAGV,MAAM,cAAc,cAAc;;EAIjC,YAAA,OAAO;WAIV,WAAU;WAEV,YAAa;EAKtB,IAAI,OAAO;;;;KChBR,YAAY,oBAAoB,iBAAiB,OAAO;qBAEhD;;WACF;WAEA;WACA,cAAc;WACd,aAAa;EAKpB,YAAA,oBACA,YACA,SAAS,mBACT,cAAc,cACd,UAAU;WASH,gBAAQ;WAER,YAAS;EAElB,GAAG,mBAAmB,OAAO;EAS7B,gBAAgB,OAAO;EAIvB,YAAY,SAAS;;;;UC1CN;WACN,OAAO,cAAc;WACrB,eAAe,cAAc;;UAGvB;WACN;EACT,MAAM,WAAW,SAAS,MAAM,gBAAgB,YAAY;;;;KCMlD,iBAAiB,eAC3B,iBACA,qBAAqB;UAGN;EACf,SAAS,MAAM;EAEf,UAAU,MAAM;EAEhB,MAAM,gBAAgB,SAAS;;UAGhB;WACN;EAET,MAAM,SAAS;;UAGA;EACf,UAAU;EACV,WAAW;EACX,mBAAmB;EACnB,OAAO,eAAe;;;;;;;cAcX;;WACF,cAAc;WACd,SAAS;WACT,OAAK,aAAA;WACL,OAAK,MAAA;WACL,eAAa,MAAA;EASV,cAAE,WAAW,UAAU,OAAO,WAAgB;EAgC1D,cAAc,QAAQ;EAKtB;EASA,MAAM,WAAW;MAMb,kBAAkB;EAkHtB,WAAW,qBAAqB;;;;iBCrNjB,qBAAqB;EACpC,WAAW;;wBAGG,eAAe,cAAc,WAAW,eAAe"}
package/dist/index.js ADDED
@@ -0,0 +1,215 @@
1
+ import { DEFAULT_SYSTEM_CONTEXT, EventEmitter, MessageChannel, applyChanges, hostTransport } from "@monimesl/dot-core";
2
+ //#region src/store.ts
3
+ var Store = class {
4
+ #value;
5
+ #listeners = /* @__PURE__ */ new Set();
6
+ constructor(value) {
7
+ this.#value = value;
8
+ }
9
+ get = () => this.#value;
10
+ subscribe = (listener) => {
11
+ this.#listeners.add(listener);
12
+ return () => this.#listeners.delete(listener);
13
+ };
14
+ set(value) {
15
+ this.#value = value;
16
+ this.#listeners.forEach((listener) => listener());
17
+ }
18
+ };
19
+ //#endregion
20
+ //#region src/page.ts
21
+ var PageController = class {
22
+ pageId;
23
+ #dispatch;
24
+ pageInstanceId;
25
+ viewRenderer;
26
+ pageOptions;
27
+ #state = new Store(null);
28
+ #callbacks = /* @__PURE__ */ new Map();
29
+ constructor(instanceId, id, options, viewRenderer, dispatch) {
30
+ this.pageId = id;
31
+ this.pageOptions = options;
32
+ this.#dispatch = dispatch;
33
+ this.pageInstanceId = instanceId;
34
+ this.viewRenderer = viewRenderer;
35
+ }
36
+ getState = this.#state.get;
37
+ subscribe = this.#state.subscribe;
38
+ on(handler) {
39
+ let callback = this.#callbacks.get(handler);
40
+ if (!callback) {
41
+ callback = (event) => this.#dispatch(this.pageInstanceId, handler, event);
42
+ this.#callbacks.set(handler, callback);
43
+ }
44
+ return callback;
45
+ }
46
+ initializeState(state) {
47
+ this.#state.set(state);
48
+ }
49
+ updateState(changes) {
50
+ const state = this.#state.get();
51
+ if (state) this.#state.set(applyChanges(state, changes));
52
+ }
53
+ };
54
+ //#endregion
55
+ //#region src/webview.ts
56
+ /**
57
+ * The view layer: shows the navigation stack the host announces, keeps each open page in sync with
58
+ * the service, and hands pages to the renderer. Every open page gets its own element in the
59
+ * container; only the top one is visible, so going back is instant and keeps its UI.
60
+ */
61
+ var Webview = class {
62
+ pageRenderer;
63
+ channel;
64
+ hooks = new EventEmitter();
65
+ theme = new Store({});
66
+ systemContext = new Store(DEFAULT_SYSTEM_CONTEXT);
67
+ #dotContext = {
68
+ theme: this.theme,
69
+ systemContext: this.systemContext
70
+ };
71
+ #viewByPageId;
72
+ #openPages = [];
73
+ #shownPage;
74
+ #pending = [];
75
+ #container = null;
76
+ constructor({ transport, renderer, views, plugins = [] }) {
77
+ this.pageRenderer = renderer;
78
+ this.#viewByPageId = views;
79
+ this.channel = new MessageChannel(transport);
80
+ this.channel.handle("setTheme", ({ theme }) => {
81
+ this.theme.set(theme);
82
+ });
83
+ this.channel.handle("initPageState", ({ instanceId, state }) => {
84
+ this.locatePage(instanceId)?.initializeState(state);
85
+ });
86
+ this.channel.handle("setPageState", ({ instanceId, patchState }) => {
87
+ this.locatePage(instanceId)?.updateState(patchState);
88
+ });
89
+ this.channel.handle("syncStack", ({ pages }) => {
90
+ this.#reconcile(pages);
91
+ });
92
+ this.channel.handle("syncSystemContext", (context) => {
93
+ this.systemContext.set(context);
94
+ this.#showTopPage();
95
+ });
96
+ this.channel.handle("error", ({ instanceId, message }) => {
97
+ if (instanceId === void 0) {
98
+ console.error(`[dot] service error: ${message}`);
99
+ return;
100
+ }
101
+ const pageName = this.locatePage(instanceId)?.pageId ?? `#${instanceId}`;
102
+ console.error(`[dot] service error in page "${pageName}": ${message}`);
103
+ });
104
+ plugins.forEach((plugin) => this.preparePlugin(plugin));
105
+ }
106
+ preparePlugin(plugin) {
107
+ plugin.setup(this);
108
+ return this;
109
+ }
110
+ start() {
111
+ const stopListening = this.channel.listen((error, message) => {
112
+ this.hooks.emit("error", error, message);
113
+ console.error("[dot] webview error", error);
114
+ });
115
+ this.channel.send("connect", {});
116
+ return stopListening;
117
+ }
118
+ mount(container) {
119
+ this.#container = container;
120
+ this.#reconcile(this.#pending);
121
+ return () => this.#reconcile([]);
122
+ }
123
+ get stack() {
124
+ return this.#openPages.map(({ page }) => page);
125
+ }
126
+ #reconcile(pages) {
127
+ if (!this.#container) {
128
+ this.#pending = pages;
129
+ return;
130
+ }
131
+ this.#closePagesNotIn(pages);
132
+ if (this.#shownPage?.page.pageInstanceId !== pages.at(-1)?.instanceId) this.#showOnly(void 0);
133
+ this.#openPagesIn(pages);
134
+ const top = this.#top();
135
+ for (const open of this.#openPages) open.element.hidden = open !== top;
136
+ this.#showTopPage();
137
+ }
138
+ #showTopPage() {
139
+ if (this.systemContext.get().appState === "active") this.#showOnly(this.#top());
140
+ else this.#showOnly(void 0);
141
+ }
142
+ #showOnly(open) {
143
+ if (open === this.#shownPage) return;
144
+ if (this.#shownPage) this.channel.send("hide", { instanceId: this.#shownPage.page.pageInstanceId });
145
+ this.#shownPage = open;
146
+ if (open) this.channel.send("show", { instanceId: open.page.pageInstanceId });
147
+ }
148
+ #closePagesNotIn(pages) {
149
+ const instanceIdsToKeep = new Set(pages.map((entry) => entry.instanceId));
150
+ for (const open of [...this.#openPages].reverse()) if (!instanceIdsToKeep.has(open.page.pageInstanceId)) this.#close(open);
151
+ }
152
+ #openPagesIn(pages) {
153
+ const kept = new Map(this.#openPages.map((open) => [open.page.pageInstanceId, open]));
154
+ const opened = [];
155
+ this.#openPages = pages.map((entry) => {
156
+ let open = kept.get(entry.instanceId);
157
+ if (!open) {
158
+ open = this.#open(entry);
159
+ opened.push(open);
160
+ }
161
+ return open;
162
+ });
163
+ for (const { page } of opened) this.channel.send("load", {
164
+ instanceId: page.pageInstanceId,
165
+ page: page.pageId,
166
+ options: page.pageOptions
167
+ });
168
+ }
169
+ #open({ instanceId, page: id, options }) {
170
+ const view = this.#viewByPageId[id];
171
+ if (!view) throw new Error(`Unknown page "${id}"`);
172
+ const page = new PageController(instanceId, id, options, view, (instanceId, handler, event) => this.channel.send("event", {
173
+ instanceId,
174
+ handler,
175
+ event
176
+ }));
177
+ const element = this.#container.ownerDocument.createElement("div");
178
+ element.dataset.page = id;
179
+ element.hidden = true;
180
+ this.#container.append(element);
181
+ const open = {
182
+ page,
183
+ element,
184
+ unmount: this.pageRenderer.mount(element, page, this.#dotContext)
185
+ };
186
+ this.hooks.emit("pageOpen", page);
187
+ return open;
188
+ }
189
+ #close(open) {
190
+ if (this.#shownPage === open) this.#shownPage = void 0;
191
+ open.unmount();
192
+ open.element.remove();
193
+ this.#openPages = this.#openPages.filter((other) => other !== open);
194
+ this.channel.send("unload", { instanceId: open.page.pageInstanceId });
195
+ this.hooks.emit("pageClose", open.page);
196
+ }
197
+ #top() {
198
+ return this.#openPages.at(-1);
199
+ }
200
+ locatePage(instanceId) {
201
+ return this.#openPages.find(({ page }) => page.pageInstanceId === instanceId)?.page;
202
+ }
203
+ };
204
+ //#endregion
205
+ //#region src/index.ts
206
+ function startWebview({ container, ...options }) {
207
+ const webview = new Webview(options);
208
+ webview.start();
209
+ webview.mount(container);
210
+ return webview;
211
+ }
212
+ //#endregion
213
+ export { PageController, Store, hostTransport, startWebview };
214
+
215
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/store.ts","../src/page.ts","../src/webview.ts","../src/index.ts"],"sourcesContent":["export interface ReadonlyStore<T> {\n readonly get: () => T;\n readonly subscribe: (listener: () => void) => () => void;\n}\n\nexport class Store<T> implements ReadonlyStore<T> {\n #value: T;\n readonly #listeners = new Set<() => void>();\n\n constructor(value: T) {\n this.#value = value;\n }\n\n readonly get = (): T => this.#value;\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener);\n return () => this.#listeners.delete(listener);\n };\n\n set(value: T): void {\n this.#value = value;\n this.#listeners.forEach((listener) => listener());\n }\n}\n","import { Store } from './store';\nimport type { DotEvent, PageLaunchOptions, PageState, ViewRender } from '@monimesl/dot-core';\nimport { applyChanges } from '@monimesl/dot-core';\n\ntype Dispatch = (instanceId: number, handler: string, event: DotEvent) => void;\n\nexport class PageController {\n readonly pageId: string;\n readonly #dispatch: Dispatch;\n readonly pageInstanceId: number;\n readonly viewRenderer: ViewRender;\n readonly pageOptions: PageLaunchOptions;\n readonly #state = new Store<PageState | null>(null);\n readonly #callbacks = new Map<string, (event: DotEvent) => void>();\n\n constructor(\n instanceId: number,\n id: string,\n options: PageLaunchOptions,\n viewRenderer: ViewRender,\n dispatch: Dispatch,\n ) {\n this.pageId = id;\n this.pageOptions = options;\n this.#dispatch = dispatch;\n this.pageInstanceId = instanceId;\n this.viewRenderer = viewRenderer;\n }\n\n readonly getState = this.#state.get;\n\n readonly subscribe = this.#state.subscribe;\n\n on(handler: string): (event: DotEvent) => void {\n let callback = this.#callbacks.get(handler);\n if (!callback) {\n callback = (event) => this.#dispatch(this.pageInstanceId, handler, event);\n this.#callbacks.set(handler, callback);\n }\n return callback;\n }\n\n initializeState(state: PageState): void {\n this.#state.set(state);\n }\n\n updateState(changes: PageState): void {\n const state = this.#state.get();\n if (state) {\n this.#state.set(applyChanges(state, changes));\n }\n }\n}\n","import type {\n Envelope,\n HostLayerMessages,\n LogicLayerMessages,\n StackEntry,\n SystemContext,\n Theme,\n Transport,\n UILayerMessages,\n ViewRender,\n} from '@monimesl/dot-core';\nimport { DEFAULT_SYSTEM_CONTEXT, EventEmitter, MessageChannel } from '@monimesl/dot-core';\n\nimport { Store } from './store';\nimport { PageController } from './page';\nimport type { DotContext, Renderer } from './renderer';\n\nexport type WebviewChannel = MessageChannel<\n UILayerMessages,\n LogicLayerMessages & HostLayerMessages\n>;\n\nexport interface WebviewHooks {\n pageOpen(page: PageController): void;\n\n pageClose(page: PageController): void;\n\n error(error: unknown, message: Envelope): void;\n}\n\nexport interface WebviewPlugin {\n readonly name: string;\n\n setup(webview: Webview): void;\n}\n\nexport interface WebviewOptions {\n renderer: Renderer;\n transport: Transport;\n plugins?: readonly WebviewPlugin[];\n views: Record<string, ViewRender>;\n}\n\ninterface OpenPage {\n readonly page: PageController;\n readonly element: HTMLElement;\n readonly unmount: () => void;\n}\n\n/**\n * The view layer: shows the navigation stack the host announces, keeps each open page in sync with\n * the service, and hands pages to the renderer. Every open page gets its own element in the\n * container; only the top one is visible, so going back is instant and keeps its UI.\n */\nexport class Webview {\n readonly pageRenderer: Renderer;\n readonly channel: WebviewChannel;\n readonly hooks = new EventEmitter<WebviewHooks>();\n readonly theme = new Store<Theme>({});\n readonly systemContext = new Store<SystemContext>(DEFAULT_SYSTEM_CONTEXT);\n readonly #dotContext: DotContext = { theme: this.theme, systemContext: this.systemContext };\n readonly #viewByPageId: Record<string, ViewRender>;\n #openPages: OpenPage[] = [];\n // The page the service was last told is showing: the top one, while the app is active.\n #shownPage: OpenPage | undefined;\n #pending: readonly StackEntry[] = [];\n #container: Element | null = null;\n\n constructor({ transport, renderer, views, plugins = [] }: WebviewOptions) {\n this.pageRenderer = renderer;\n this.#viewByPageId = views;\n this.channel = new MessageChannel(transport);\n this.channel.handle('setTheme', ({ theme }) => {\n this.theme.set(theme);\n });\n this.channel.handle('initPageState', ({ instanceId, state }) => {\n this.locatePage(instanceId)?.initializeState(state);\n });\n this.channel.handle('setPageState', ({ instanceId, patchState }) => {\n this.locatePage(instanceId)?.updateState(patchState);\n });\n this.channel.handle('syncStack', ({ pages }) => {\n this.#reconcile(pages);\n });\n this.channel.handle('syncSystemContext', (context) => {\n this.systemContext.set(context);\n this.#showTopPage();\n });\n this.channel.handle('error', ({ instanceId, message }) => {\n if (instanceId === undefined) {\n console.error(`[dot] service error: ${message}`);\n return;\n }\n // The page may have closed before its error arrived; its instance id is all that is left.\n const pageName = this.locatePage(instanceId)?.pageId ?? `#${instanceId}`;\n console.error(`[dot] service error in page \"${pageName}\": ${message}`);\n });\n plugins.forEach((plugin) => this.preparePlugin(plugin));\n }\n\n preparePlugin(plugin: WebviewPlugin): this {\n plugin.setup(this);\n return this;\n }\n\n start(): () => void {\n const stopListening = this.channel.listen((error, message) => {\n this.hooks.emit('error', error, message);\n console.error('[dot] webview error', error);\n });\n this.channel.send('connect', {});\n return stopListening;\n }\n\n mount(container: Element): () => void {\n this.#container = container;\n this.#reconcile(this.#pending);\n return () => this.#reconcile([]);\n }\n\n get stack(): readonly PageController[] {\n return this.#openPages.map(({ page }) => page);\n }\n\n // Makes the open pages match the stack the host announced.\n #reconcile(pages: readonly StackEntry[]): void {\n if (!this.#container) {\n this.#pending = pages;\n return;\n }\n this.#closePagesNotIn(pages);\n // The page being covered hides before the new one loads, as on a device.\n if (this.#shownPage?.page.pageInstanceId !== pages.at(-1)?.instanceId) {\n this.#showOnly(undefined);\n }\n this.#openPagesIn(pages);\n const top = this.#top();\n for (const open of this.#openPages) {\n open.element.hidden = open !== top;\n }\n this.#showTopPage();\n }\n\n // The top page shows while the app is active, and hides while the person is away from Dash.\n // Its element stays visible either way, so the app switcher's snapshot shows it.\n #showTopPage(): void {\n if (this.systemContext.get().appState === 'active') {\n this.#showOnly(this.#top());\n } else {\n this.#showOnly(undefined);\n }\n }\n\n #showOnly(open: OpenPage | undefined): void {\n if (open === this.#shownPage) {\n return;\n }\n if (this.#shownPage) {\n this.channel.send('hide', { instanceId: this.#shownPage.page.pageInstanceId });\n }\n this.#shownPage = open;\n if (open) {\n this.channel.send('show', { instanceId: open.page.pageInstanceId });\n }\n }\n\n #closePagesNotIn(pages: readonly StackEntry[]): void {\n const instanceIdsToKeep = new Set(pages.map((entry) => entry.instanceId));\n // Close from the top of the stack down, the order a device closes pages in.\n for (const open of [...this.#openPages].reverse()) {\n if (!instanceIdsToKeep.has(open.page.pageInstanceId)) {\n this.#close(open);\n }\n }\n }\n\n #openPagesIn(pages: readonly StackEntry[]): void {\n const kept = new Map(this.#openPages.map((open) => [open.page.pageInstanceId, open]));\n const opened: OpenPage[] = [];\n this.#openPages = pages.map((entry) => {\n let open = kept.get(entry.instanceId);\n if (!open) {\n open = this.#open(entry);\n opened.push(open);\n }\n return open;\n });\n // Only now, with every page in the stack, can the service's replies find their page.\n for (const { page } of opened) {\n this.channel.send('load', {\n instanceId: page.pageInstanceId,\n page: page.pageId,\n options: page.pageOptions,\n });\n }\n }\n\n #open({ instanceId, page: id, options }: StackEntry): OpenPage {\n const view = this.#viewByPageId[id];\n if (!view) {\n throw new Error(`Unknown page \"${id}\"`);\n }\n const page = new PageController(instanceId, id, options, view, (instanceId, handler, event) =>\n this.channel.send('event', { instanceId, handler, event }),\n );\n const element = this.#container!.ownerDocument.createElement('div');\n element.dataset.page = id;\n element.hidden = true;\n this.#container!.append(element);\n const open: OpenPage = {\n page,\n element,\n unmount: this.pageRenderer.mount(element, page, this.#dotContext),\n };\n this.hooks.emit('pageOpen', page);\n return open;\n }\n\n #close(open: OpenPage): void {\n // A closing page unloads rather than hides.\n if (this.#shownPage === open) {\n this.#shownPage = undefined;\n }\n open.unmount();\n open.element.remove();\n this.#openPages = this.#openPages.filter((other) => other !== open);\n this.channel.send('unload', { instanceId: open.page.pageInstanceId });\n this.hooks.emit('pageClose', open.page);\n }\n\n #top(): OpenPage | undefined {\n return this.#openPages.at(-1);\n }\n\n locatePage(instanceId: number): PageController | undefined {\n return this.#openPages.find(({ page }) => page.pageInstanceId === instanceId)?.page;\n }\n}\n","export { Store } from './store';\nimport type { WebviewOptions } from './webview';\nimport { Webview } from './webview';\n\nexport { PageController } from './page';\nexport type { ReadonlyStore } from './store';\n\nexport { hostTransport } from '@monimesl/dot-core';\nexport type { DotContext, Renderer } from './renderer';\nexport type { WebviewHooks, WebviewOptions, WebviewPlugin, WebviewChannel } from './webview';\nexport type {\n ColorScheme,\n DotEvent,\n PageState,\n RenderContext,\n SystemContext,\n Theme,\n Transport,\n ViewRender,\n} from '@monimesl/dot-core';\n\nexport interface StartOptions extends WebviewOptions {\n container: Element;\n}\n\nexport function startWebview({ container, ...options }: StartOptions): Webview {\n const webview = new Webview(options);\n webview.start();\n webview.mount(container);\n return webview;\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@monimesl/dot-webview",
3
+ "version": "0.1.0-alpha.2",
4
+ "description": "Dot view layer: keeps pages in sync with the logic and hands them to a renderer",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/monime-lab/dot.git",
9
+ "directory": "packages/webview"
10
+ },
11
+ "sideEffects": false,
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "@monimesl/dot-source": "./src/index.ts",
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "dependencies": {
27
+ "@monimesl/dot-core": "0.1.0-alpha.2"
28
+ },
29
+ "devDependencies": {
30
+ "jsdom": "^30.1.1",
31
+ "publint": "^0.3.24",
32
+ "rimraf": "^6.1.3",
33
+ "tsdown": "^0.23.0",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^5.0.1"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown",
42
+ "dev": "tsdown --watch",
43
+ "typecheck": "tsc --noEmit -p tsconfig.json",
44
+ "test": "vitest run",
45
+ "clean": "rimraf dist *.tsbuildinfo"
46
+ }
47
+ }