@monimesl/dot-service 0.1.0-alpha.9 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { a as Page, c as PageHost, d as PageOptions, f as State, h as definePage, i as Mutation, l as PageId, m as StateUpdate, n as ActionContext, o as PageContext, p as StateChanges, r as DeepReadonly, s as PageDefinition, t as Action, u as PageNavigation } from "./page-Xn5DL6zY.js";
2
- import { AppState, ColorScheme, DotEvent, DotEvent as DotEvent$1, DotHooks, DotHooks as DotHooks$1, EventEmitter, HostCaller, HostLayerMessages, LogicLayerMessages, MessageChannel, Navigation, NavigationBarOptions, PageConfigs, PageConfigs as PageConfigs$1, PageHooks, PageLaunchOptions, PageLaunchOptions as PageLaunchOptions$1, PageState, PageState as PageState$1, SystemContext, SystemContext as SystemContext$1, TabBarOptions, Theme, Theme as Theme$1, Transport, Transport as Transport$1, UILayerMessages, createEvent, hostTransport } from "@monimesl/dot-core";
1
+ import { a as Page, c as PageHost, d as PageOptions, f as State, h as definePage, i as Mutation, l as PageId, m as StateUpdate, n as ActionContext, o as PageContext, p as StateChanges, r as DeepReadonly, s as PageDefinition, t as Action, u as PageNavigation } from "./page-DBnKjef6.js";
2
+ import { AppState, CheckedEvent, ColorScheme, DataEvent, DotEvent, DotEvent as DotEvent$1, DotHooks, DotHooks as DotHooks$1, EventEmitter, HexColor, HostCaller, HostLayerMessages, IndexEvent, LogicLayerMessages, MessageChannel, Navigation, NavigationBarOptions, PageColors, PageConfigs, PageConfigs as PageConfigs$1, PageHooks, PageLaunchOptions, PageLaunchOptions as PageLaunchOptions$1, PageShare, PageState, PageState as PageState$1, SchemeColor, SystemContext, SystemContext as SystemContext$1, TabBarOptions, Theme, Theme as Theme$1, ThemeColors, Transport, Transport as Transport$1, UILayerMessages, ValueEvent, createEvent, hostTransport } from "@monimesl/dot-core";
3
3
  //#region src/service.d.ts
4
4
  type ServiceChannel = MessageChannel<LogicLayerMessages, UILayerMessages & HostLayerMessages>;
5
5
  interface ServiceHooks {
@@ -35,12 +35,13 @@ interface ServiceOptions {
35
35
  dot?: Partial<DotDefinition>;
36
36
  pages: Record<string, PageDefinition>;
37
37
  strict?: boolean;
38
+ dashMinApiVersion?: string | undefined;
38
39
  }
39
40
  export declare class Service implements DotContext {
40
41
  #private;
41
42
  readonly channel: ServiceChannel;
42
43
  readonly hooks: EventEmitter<ServiceHooks>;
43
- constructor({ transport, pages, dot, strict }: ServiceOptions);
44
+ constructor({ transport, pages, dot, strict, dashMinApiVersion }: ServiceOptions);
44
45
  use(plugin: ServicePlugin): this;
45
46
  start(): () => void;
46
47
  get theme(): Theme$1;
@@ -64,5 +65,5 @@ export declare function logger(options?: {
64
65
  export declare function createService(options: ServiceOptions): Service;
65
66
  export declare function runService(options: Omit<ServiceOptions, 'transport'>): Service;
66
67
  //#endregion
67
- export { type Action, type ActionContext, type AppState, type ColorScheme, type DeepReadonly, type DotContext, type DotDefinition, type DotEvent, type DotHooks, type Mutation, type NavigationBarOptions, Page, type PageConfigs, type PageContext, type PageDefinition, type PageHooks, type PageHost, type PageId, type PageLaunchOptions, type PageNavigation, type PageOptions, type PageState, type ServiceChannel, type ServiceHooks, type ServiceOptions, type ServicePlugin, type State, type StateChanges, type StateUpdate, type SystemContext, type TabBarOptions, type Theme, type Transport, createEvent, definePage, hostTransport };
68
+ export { type Action, type ActionContext, type AppState, type CheckedEvent, type ColorScheme, type DataEvent, type DeepReadonly, type DotContext, type DotDefinition, type DotEvent, type DotHooks, type HexColor, type IndexEvent, type Mutation, type NavigationBarOptions, Page, type PageColors, type PageConfigs, type PageContext, type PageDefinition, type PageHooks, type PageHost, type PageId, type PageLaunchOptions, type PageNavigation, type PageOptions, type PageShare, type PageState, type SchemeColor, type ServiceChannel, type ServiceHooks, type ServiceOptions, type ServicePlugin, type State, type StateChanges, type StateUpdate, type SystemContext, type TabBarOptions, type Theme, type ThemeColors, type Transport, type ValueEvent, createEvent, definePage, hostTransport };
68
69
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/service.ts","../src/plugins/logger.ts","../src/index.ts"],"mappings":";;;KA6BY,iBAAiB,eAC3B,oBACA,kBAAkB;UAGH;EACf;EAEA,SAAS,MAAM;EAEf,WAAW,MAAM;EAEjB,SAAS,MAAM;EAEf,SAAS,MAAM;EAGf,SAAS,MAAM,MAAM,YAAY;EAGjC,MAAM,MAAM,MAAM,iBAAiB,OAAO;EAG1C,SAAS,MAAM,MAAM,OAAO;EAG5B,MAAM,gBAAgB;;UAGP;WACN;EAET,MAAM,SAAS;;UAGA;WACN,OAAO;WACP,eAAe;EAExB,SAAS,OAAO;;UAGD;WACN,QAAQ;WACR,mBAAmB;WACnB;WAEA,UAAU;WACV,QAAQ,QAAQ,cAAY,SAAS;;wBAGhC,UAAU,KAAK,gBAAgB;UAI9B;EACf,WAAW;EACX,MAAM,QAAQ;EACd,OAAO,eAAe;EACtB;;qBAIW,mBAAmB;;WACrB,SAAS;WACT,OAAK,aAAA;EAUF,cAAE,WAAW,OAAO,KAAU,UAAkB;EAuC5D,IAAI,QAAQ;EAKZ;MAaI,SAAS;MAIT,iBAAiB;EAIrB,SAAS,OAAO;EAKhB,QAAQ,qBAAqB;WASpB,UAAU;EAGnB,SAAS,oBAAoB,YAAY,UAAS,sBAAyB;EAU3E,aAAa,aAAa,eAAe;EAgEzC,WAAW;EAmBX,YAAY,MAAM,eAAe;;;;wBCpRnB,OACd;EACE,SAAS;EACT,cAAc;IAKf;;;wBCqCa,cAAc,SAAS,iBAAiB;wBAUxC,WAAW,SAAS,KAAK,+BAA+B"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/service.ts","../src/plugins/logger.ts","../src/index.ts"],"mappings":";;;KAmCY,iBAAiB,eAC3B,oBACA,kBAAkB;UAGH;EACf;EAEA,SAAS,MAAM;EAEf,WAAW,MAAM;EAEjB,SAAS,MAAM;EAEf,SAAS,MAAM;EAGf,SAAS,MAAM,MAAM,YAAY;EAGjC,MAAM,MAAM,MAAM,iBAAiB,OAAO;EAG1C,SAAS,MAAM,MAAM,OAAO;EAG5B,MAAM,gBAAgB;;UAGP;WACN;EAET,MAAM,SAAS;;UAGA;WACN,OAAO;WACP,eAAe;EAExB,SAAS,OAAO;;UAGD;WACN,QAAQ;WACR,mBAAmB;WACnB;WAEA,UAAU;WACV,QAAQ,QAAQ,cAAY,SAAS;;wBAGhC,UAAU,KAAK,gBAAgB;UAI9B;EACf,WAAW;EACX,MAAM,QAAQ;EACd,OAAO,eAAe;EACtB;EAEA;;qBAKW,mBAAmB;;WACrB,SAAS;WACT,OAAK,aAAA;EAeF,cAAE,WAAW,OAAO,KAAU,QAAgB,qBAAqB;EA0D/E,IAAI,QAAQ;EAKZ;MAeI,SAAS;MAIT,iBAAiB;EAIrB,SAAS,OAAO;EAMhB,QAAQ,qBAAqB;WASpB,UAAU;EAGnB,SAAS,oBAAoB,YAAY,UAAS,sBAAyB;EAW3E,aAAa,aAAa,eAAe;EA0IzC,WAAW;EAoBX,YAAY,MAAM,eAAe;;;;wBCpYnB,OACd;EACE,SAAS;EACT,cAAc;IAKf;;;wBC+Ca,cAAc,SAAS,iBAAiB;wBAUxC,WAAW,SAAS,KAAK,+BAA+B"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { i as definePage, n as defineDot, r as Page, t as Service } from "./service-CHUmeUjF.js";
1
+ import { i as definePage, n as defineDot, r as Page, t as Service } from "./service-f7lmyOkU.js";
2
2
  import { connectHost, createEvent, hostTransport, hostTransport as hostTransport$1 } from "@monimesl/dot-core";
3
+ import { useHostDashApiVersions, warnAboveDashApiVersion } from "@monimesl/dot-dash";
3
4
  //#region src/plugins/logger.ts
4
5
  function logger(options = {
5
6
  log: console.debug,
@@ -27,10 +28,12 @@ function createService(options) {
27
28
  }
28
29
  const RUNNING_SERVICE = Symbol.for("dot.service");
29
30
  function runService(options) {
30
- const scope = globalThis;
31
- const running = scope[RUNNING_SERVICE];
31
+ const globals = globalThis;
32
+ const running = globals[RUNNING_SERVICE];
32
33
  if (running) {
33
34
  connectHost(running.callHost);
35
+ useHostDashApiVersions(running.systemContext.dashApiVersions);
36
+ if (options.dashMinApiVersion !== void 0) warnAboveDashApiVersion(options.dashMinApiVersion);
34
37
  running.replacePages(options.pages);
35
38
  return running;
36
39
  }
@@ -38,7 +41,7 @@ function runService(options) {
38
41
  ...options,
39
42
  transport: hostTransport$1()
40
43
  });
41
- scope[RUNNING_SERVICE] = service;
44
+ globals[RUNNING_SERVICE] = service;
42
45
  return service;
43
46
  }
44
47
  //#endregion
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["hostTransport"],"sources":["../src/plugins/logger.ts","../src/index.ts"],"sourcesContent":["import type { ServicePlugin } from '../service';\n\n// Logs page lifecycle, events, state changes, and errors.\nexport function logger(\n options: {\n log: (...args: unknown[]) => void;\n logError: (...args: unknown[]) => void;\n } = {\n log: console.debug,\n logError: console.error,\n },\n): ServicePlugin {\n const { log, logError } = options;\n return {\n name: 'logger',\n setup({ hooks }) {\n hooks.on('pageLoad', (page) => log(`[dot] load ${page.id}#${page.instanceId}`, page.state));\n hooks.on('pageUnload', (page) => log(`[dot] unload ${page.id}#${page.instanceId}`));\n hooks.on('event', (page, handler, event) => log(`[dot] ${page.id} ${handler}`, event));\n hooks.on('navigate', (page, navigation) => log(`[dot] ${page.id} navigate`, navigation));\n hooks.on('setState', (page, patch) => log(`[dot] ${page.id} setState`, patch));\n hooks.on('error', (error) => logError('[dot] error', error));\n },\n };\n}\n","import { connectHost, hostTransport } from '@monimesl/dot-core';\n\nimport type { ServiceOptions } from './service';\nimport { Service } from './service';\n\nexport { Page, definePage } from './page';\nexport type {\n Action,\n ActionContext,\n DeepReadonly,\n Mutation,\n PageContext,\n PageDefinition,\n PageHost,\n PageId,\n PageNavigation,\n PageOptions,\n State,\n StateChanges,\n StateUpdate,\n} from './page';\nexport { Service, defineDot } from './service';\nexport type {\n DotContext,\n DotDefinition,\n ServiceHooks,\n ServiceOptions,\n ServicePlugin,\n ServiceChannel,\n} from './service';\nexport { logger } from './plugins/logger';\nexport { createEvent, hostTransport } from '@monimesl/dot-core';\nexport type {\n ColorScheme,\n DotEvent,\n DotHooks,\n NavigationBarOptions,\n PageConfigs,\n PageState,\n PageHooks,\n PageLaunchOptions,\n AppState,\n SystemContext,\n TabBarOptions,\n Theme,\n Transport,\n} from '@monimesl/dot-core';\n\nexport function createService(options: ServiceOptions): Service {\n const service = new Service(options);\n service.start();\n return service;\n}\n\n// Where the running service is found by the next build of the dot, run in the same engine.\nconst RUNNING_SERVICE = Symbol.for('dot.service');\n\n// Starts the service, or hands a newer build's pages to the one already running, as a hot reload does.\nexport function runService(options: Omit<ServiceOptions, 'transport'>): Service {\n const scope = globalThis as unknown as Record<symbol, Service | undefined>;\n const running = scope[RUNNING_SERVICE];\n if (running) {\n // The new build has its own copy of the host connection, which its code's `dash` calls use.\n connectHost(running.callHost);\n running.replacePages(options.pages);\n return running;\n }\n const service = createService({ ...options, transport: hostTransport() });\n scope[RUNNING_SERVICE] = service;\n return service;\n}\n"],"mappings":";;;AAGA,SAAgB,OACd,UAGI;CACF,KAAK,QAAQ;CACb,UAAU,QAAQ;AACpB,GACe;CACf,MAAM,EAAE,KAAK,aAAa;CAC1B,OAAO;EACL,MAAM;EACN,MAAM,EAAE,SAAS;GACf,MAAM,GAAG,aAAa,SAAS,IAAI,cAAc,KAAK,GAAG,GAAG,KAAK,cAAc,KAAK,KAAK,CAAC;GAC1F,MAAM,GAAG,eAAe,SAAS,IAAI,gBAAgB,KAAK,GAAG,GAAG,KAAK,YAAY,CAAC;GAClF,MAAM,GAAG,UAAU,MAAM,SAAS,UAAU,IAAI,SAAS,KAAK,GAAG,GAAG,WAAW,KAAK,CAAC;GACrF,MAAM,GAAG,aAAa,MAAM,eAAe,IAAI,SAAS,KAAK,GAAG,YAAY,UAAU,CAAC;GACvF,MAAM,GAAG,aAAa,MAAM,UAAU,IAAI,SAAS,KAAK,GAAG,YAAY,KAAK,CAAC;GAC7E,MAAM,GAAG,UAAU,UAAU,SAAS,eAAe,KAAK,CAAC;EAC7D;CACF;AACF;;;ACwBA,SAAgB,cAAc,SAAkC;CAC9D,MAAM,UAAU,IAAI,QAAQ,OAAO;CACnC,QAAQ,MAAM;CACd,OAAO;AACT;AAGA,MAAM,kBAAkB,OAAO,IAAI,aAAa;AAGhD,SAAgB,WAAW,SAAqD;CAC9E,MAAM,QAAQ;CACd,MAAM,UAAU,MAAM;CACtB,IAAI,SAAS;EAEX,YAAY,QAAQ,QAAQ;EAC5B,QAAQ,aAAa,QAAQ,KAAK;EAClC,OAAO;CACT;CACA,MAAM,UAAU,cAAc;EAAE,GAAG;EAAS,WAAWA,gBAAc;CAAE,CAAC;CACxE,MAAM,mBAAmB;CACzB,OAAO;AACT"}
1
+ {"version":3,"file":"index.js","names":["hostTransport"],"sources":["../src/plugins/logger.ts","../src/index.ts"],"sourcesContent":["import type { ServicePlugin } from '../service';\n\n// Logs page lifecycle, events, state changes, and errors.\nexport function logger(\n options: {\n log: (...args: unknown[]) => void;\n logError: (...args: unknown[]) => void;\n } = {\n log: console.debug,\n logError: console.error,\n },\n): ServicePlugin {\n const { log, logError } = options;\n return {\n name: 'logger',\n setup({ hooks }) {\n hooks.on('pageLoad', (page) => log(`[dot] load ${page.id}#${page.instanceId}`, page.state));\n hooks.on('pageUnload', (page) => log(`[dot] unload ${page.id}#${page.instanceId}`));\n hooks.on('event', (page, handler, event) => log(`[dot] ${page.id} ${handler}`, event));\n hooks.on('navigate', (page, navigation) => log(`[dot] ${page.id} navigate`, navigation));\n hooks.on('setState', (page, patch) => log(`[dot] ${page.id} setState`, patch));\n hooks.on('error', (error) => logError('[dot] error', error));\n },\n };\n}\n","import { connectHost, hostTransport } from '@monimesl/dot-core';\nimport { useHostDashApiVersions, warnAboveDashApiVersion } from '@monimesl/dot-dash';\n\nimport type { ServiceOptions } from './service';\nimport { Service } from './service';\n\nexport { Page, definePage } from './page';\nexport type {\n Action,\n ActionContext,\n DeepReadonly,\n Mutation,\n PageContext,\n PageDefinition,\n PageHost,\n PageId,\n PageNavigation,\n PageOptions,\n State,\n StateChanges,\n StateUpdate,\n} from './page';\nexport { Service, defineDot } from './service';\nexport type {\n DotContext,\n DotDefinition,\n ServiceHooks,\n ServiceOptions,\n ServicePlugin,\n ServiceChannel,\n} from './service';\nexport { logger } from './plugins/logger';\nexport { createEvent, hostTransport } from '@monimesl/dot-core';\nexport type {\n CheckedEvent,\n ColorScheme,\n DataEvent,\n DotEvent,\n IndexEvent,\n ValueEvent,\n DotHooks,\n HexColor,\n NavigationBarOptions,\n PageColors,\n PageConfigs,\n PageState,\n PageHooks,\n PageLaunchOptions,\n PageShare,\n AppState,\n SchemeColor,\n SystemContext,\n TabBarOptions,\n Theme,\n ThemeColors,\n Transport,\n} from '@monimesl/dot-core';\n\nexport function createService(options: ServiceOptions): Service {\n const service = new Service(options);\n service.start();\n return service;\n}\n\n// Lets the dot's next build, loaded into the same engine, find the running service.\nconst RUNNING_SERVICE = Symbol.for('dot.service');\n\n// Starts the service. On a hot reload, hands the new build's pages to the one already running.\nexport function runService(options: Omit<ServiceOptions, 'transport'>): Service {\n const globals = globalThis as unknown as Record<symbol, Service | undefined>;\n const running = globals[RUNNING_SERVICE];\n if (running) {\n // The new build has its own copy of the host connection and of `dash`, so both hear again what\n // the running service knows.\n connectHost(running.callHost);\n useHostDashApiVersions(running.systemContext.dashApiVersions);\n if (options.dashMinApiVersion !== undefined) {\n warnAboveDashApiVersion(options.dashMinApiVersion);\n }\n running.replacePages(options.pages);\n return running;\n }\n const service = createService({ ...options, transport: hostTransport() });\n globals[RUNNING_SERVICE] = service;\n return service;\n}\n"],"mappings":";;;;AAGA,SAAgB,OACd,UAGI;CACF,KAAK,QAAQ;CACb,UAAU,QAAQ;AACpB,GACe;CACf,MAAM,EAAE,KAAK,aAAa;CAC1B,OAAO;EACL,MAAM;EACN,MAAM,EAAE,SAAS;GACf,MAAM,GAAG,aAAa,SAAS,IAAI,cAAc,KAAK,GAAG,GAAG,KAAK,cAAc,KAAK,KAAK,CAAC;GAC1F,MAAM,GAAG,eAAe,SAAS,IAAI,gBAAgB,KAAK,GAAG,GAAG,KAAK,YAAY,CAAC;GAClF,MAAM,GAAG,UAAU,MAAM,SAAS,UAAU,IAAI,SAAS,KAAK,GAAG,GAAG,WAAW,KAAK,CAAC;GACrF,MAAM,GAAG,aAAa,MAAM,eAAe,IAAI,SAAS,KAAK,GAAG,YAAY,UAAU,CAAC;GACvF,MAAM,GAAG,aAAa,MAAM,UAAU,IAAI,SAAS,KAAK,GAAG,YAAY,KAAK,CAAC;GAC7E,MAAM,GAAG,UAAU,UAAU,SAAS,eAAe,KAAK,CAAC;EAC7D;CACF;AACF;;;ACkCA,SAAgB,cAAc,SAAkC;CAC9D,MAAM,UAAU,IAAI,QAAQ,OAAO;CACnC,QAAQ,MAAM;CACd,OAAO;AACT;AAGA,MAAM,kBAAkB,OAAO,IAAI,aAAa;AAGhD,SAAgB,WAAW,SAAqD;CAC9E,MAAM,UAAU;CAChB,MAAM,UAAU,QAAQ;CACxB,IAAI,SAAS;EAGX,YAAY,QAAQ,QAAQ;EAC5B,uBAAuB,QAAQ,cAAc,eAAe;EAC5D,IAAI,QAAQ,sBAAsB,KAAA,GAChC,wBAAwB,QAAQ,iBAAiB;EAEnD,QAAQ,aAAa,QAAQ,KAAK;EAClC,OAAO;CACT;CACA,MAAM,UAAU,cAAc;EAAE,GAAG;EAAS,WAAWA,gBAAc;CAAE,CAAC;CACxE,QAAQ,mBAAmB;CAC3B,OAAO;AACT"}
@@ -1,15 +1,22 @@
1
- import { DotEvent, Navigation, NavigationBarOptions, PageBars, PageConfigs, PageHooks, PageLaunchOptions, PageState, TabBarOptions } from "@monimesl/dot-core";
1
+ import { DotEvent, Navigation, NavigationBarOptions, PageBars, PageColors, PageConfigs, PageHooks, PageLaunchOptions, PageShare, PageState, TabBarOptions } from "@monimesl/dot-core";
2
2
  import { Dash } from "@monimesl/dot-dash";
3
3
  //#region src/page.d.ts
4
4
  declare global {
5
5
  interface DotPageIds {}
6
6
  }
7
7
  type PageId = [keyof DotPageIds] extends [never] ? string : Extract<keyof DotPageIds, string>;
8
+ interface PushNavigation {
9
+ readonly action: 'push';
10
+ readonly page: string;
11
+ readonly options: PageLaunchOptions;
12
+ }
8
13
  interface PageNavigation {
9
14
  push(page: PageId, options?: PageLaunchOptions): void;
10
15
  replace(page: PageId, options?: PageLaunchOptions): void;
11
16
  reset(page: PageId, options?: PageLaunchOptions): void;
12
17
  back(delta?: number): void;
18
+ pushForResult<Result = unknown>(page: PageId, options?: PageLaunchOptions): Promise<Result | undefined>;
19
+ backWithResult(result: unknown): void;
13
20
  }
14
21
  type DeepReadonly<T> = T extends ((...args: never[]) => unknown) ? T : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;
15
22
  type State<S> = DeepReadonly<S>;
@@ -30,6 +37,7 @@ interface PageContext<S extends PageState, Mu, Ac> {
30
37
  setState(update: StateUpdate<S>): void;
31
38
  setNavigationBar(options: NavigationBarOptions): void;
32
39
  setTabBar(options: TabBarOptions): void;
40
+ setColors(colors: PageColors): void;
33
41
  readonly mutations: { readonly [K in keyof Mu]: (...event: EventArgs<Mu[K]>) => void; };
34
42
  readonly actions: { readonly [K in keyof Ac]: (...event: EventArgs<Ac[K]>) => Promise<void>; };
35
43
  }
@@ -40,6 +48,7 @@ interface PageOptions<S extends PageState, Mu, Ac> {
40
48
  id?: string;
41
49
  initialState: S;
42
50
  configs?: PageConfigs;
51
+ share?: PageShare;
43
52
  hooks?: Partial<PageHooks> & ThisType<PageContext<S, Mu, Ac>>;
44
53
  mutations?: Mu & ThisType<void>;
45
54
  actions?: Ac & ThisType<ActionContext<S, Mu, Ac>>;
@@ -48,6 +57,7 @@ interface PageDefinition<S extends PageState = PageState> {
48
57
  readonly id?: string;
49
58
  readonly initialState: S;
50
59
  readonly configs?: PageConfigs;
60
+ readonly share?: PageShare;
51
61
  readonly hooks?: Partial<PageHooks>;
52
62
  readonly mutations?: Readonly<Record<string, Mutation<S>>>;
53
63
  readonly actions?: Readonly<Record<string, Action<S>>>;
@@ -58,6 +68,8 @@ interface PageHost {
58
68
  showBars(page: Page): void;
59
69
  readonly dotConfigs?: PageConfigs | undefined;
60
70
  navigate(page: Page, navigation: Navigation): void;
71
+ pushForResult(page: Page, navigation: PushNavigation): Promise<unknown>;
72
+ backWithResult(page: Page, result: unknown): void;
61
73
  readonly strict?: boolean;
62
74
  }
63
75
  interface RestoredPage<S> {
@@ -75,10 +87,12 @@ declare class Page<S extends PageState = PageState> {
75
87
  readonly dash: Dash;
76
88
  constructor(instanceId: number, id: string, definition: PageDefinition<S>, host: PageHost, restored?: RestoredPage<S>);
77
89
  get state(): State<S>;
90
+ get refreshable(): boolean;
78
91
  get bars(): PageBars;
79
92
  get barChanges(): PageBars;
80
93
  setNavigationBar(options: NavigationBarOptions): void;
81
94
  setTabBar(options: TabBarOptions): void;
95
+ setColors(colors: PageColors): void;
82
96
  setState(update: StateUpdate<S>): void;
83
97
  mutate(name: string, event?: DotEvent): void;
84
98
  act(name: string, event?: DotEvent): Promise<void>;
@@ -88,4 +102,4 @@ declare class Page<S extends PageState = PageState> {
88
102
  }
89
103
  //#endregion
90
104
  export { Page as a, PageHost as c, PageOptions as d, State as f, definePage as h, Mutation as i, PageId as l, StateUpdate as m, ActionContext as n, PageContext as o, StateChanges as p, DeepReadonly as r, PageDefinition as s, Action as t, PageNavigation as u };
91
- //# sourceMappingURL=page-Xn5DL6zY.d.ts.map
105
+ //# sourceMappingURL=page-DBnKjef6.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-DBnKjef6.d.ts","names":[],"sources":["../src/page.ts"],"mappings":";;;QAyBQ;YAEI;;KAKA,gBAAgB,uCAExB,cAAc;UAGD;WACN;WACA;WACA,SAAS;;UAGH;EACf,KAAK,MAAM,QAAQ,UAAU;EAE7B,QAAQ,MAAM,QAAQ,UAAU;EAEhC,MAAM,MAAM,QAAQ,UAAU;EAE9B,KAAK;EAIL,cAAc,kBACZ,MAAM,QACN,UAAU,oBACT,QAAQ;EAGX,eAAe;;KAGL,aAAa,KAAK,eAAc,6BACxC,IACA,+BACc,WAAW,IAAI,aAAa,EAAE,SAC1C;KAEM,MAAM,KAAK,aAAa;KAExB,aAAa,KAAK,QAAQ,MAAM;KAEhC,YAAY,KAAK,aAAa,OAAO,OAAO,MAAM,OAAO,aAAa;KAItE,SAAS;EACnB,SAAS,OAAO,MAAM,IAAI,OAAO,WAAW,aAAa;;KAG/C,OAAO;EACjB,OAAO,OAAO,MAAM,IAAI,OAAO,kBAAkB;;KAI9C,UAAU,KAAK,YAAW,iBAAiB,YAAY,iBAAgB;UAE3D,YAAY,UAAU,WAAW,IAAI;WAC3C;WACA;WACA,OAAO,MAAM;WAEb,MAAM;EAEf,SAAS,QAAQ,YAAY;EAG7B,iBAAiB,SAAS;EAG1B,UAAU,SAAS;EAInB,UAAU,QAAQ;WAET,uBAAuB,WAAW,SAAS,OAAO,UAAU,GAAG;WAC/D,qBAAqB,WAAW,SAAS,OAAO,UAAU,GAAG,QAAQ;;UAG/D,cAAc,UAAU,WAAW,IAAI,YAAY,YAAY,GAAG,IAAI;WAC5E,YAAY;;UAGN,YAAY,UAAU,WAAW,IAAI;EACpD;EACA,cAAc;EAGd,UAAU;EAGV,QAAQ;EACR,QAAQ,QAAQ,aAAa,SAAS,YAAY,GAAG,IAAI;EACzD,YAAY,KAAK;EACjB,UAAU,KAAK,SAAS,cAAc,GAAG,IAAI;;UAG9B,eAAe,UAAU,YAAY;WAC3C;WACA,cAAc;WACd,UAAU;WACV,QAAQ;WACR,QAAQ,QAAQ;WAChB,YAAY,SAAS,eAAe,SAAS;WAC7C,UAAU,SAAS,eAAe,OAAO;;iBAGpC,WACd,UAAU,WACV,WAAW,eAAe,SAAS,KACnC,WAAW,eAAe,OAAO,KACjC,SAAS,YAAY,GAAG,IAAI,MAAM,eAAe;UAIlC;EACf,OAAO,MAAM,MAAM,OAAO;EAG1B,SAAS,MAAM;WAGN,aAAa;EAEtB,SAAS,MAAM,MAAM,YAAY;EAGjC,cAAc,MAAM,MAAM,YAAY,iBAAiB;EAEvD,eAAe,MAAM,MAAM;WAElB;;UAIM,aAAa;WACnB,OAAO,MAAM;WACb,YAAY;;KAGlB,UAAU,KAAK,eAAe,SAAS;KACvC,QAAQ,KAAK,eAAe,OAAO;cAI3B,KAAK,UAAU,YAAY;;WAC7B;WACA;WACA,SAAS,YAAY,GAAG,UAAU,IAAI,QAAQ;WAC9C,eAAe,cAAc,GAAG,UAAU,IAAI,QAAQ;WACtD,MAAM;EAWb,YAAA,oBACA,YACA,YAAY,eAAe,IAC3B,MAAM,UACN,WAAW,aAAa;MAsCtB,SAAS,MAAM;MAKf;MAIA,QAAQ;MAIR,cAAc;EAIlB,iBAAiB,SAAS;EAM1B,UAAU,SAAS;EAMnB,UAAU,QAAQ;EAclB,SAAS,QAAQ,YAAY;EA+B7B,OAAO,cAAc,QAAQ;EAQvB,IAAI,cAAc,QAAQ,WAAW;EAS3C,SAAS,cAAc,OAAO,WAAW;EAanC,UAAU,gBAAgB,WAC9B,UAAU,MACP,MAAM,WAAW,UAAU,MAC7B;EASH"}
@@ -1,5 +1,36 @@
1
- import { DEFAULT_SYSTEM_CONTEXT, EventEmitter, HostError, MAX_STACK_DEPTH, MessageChannel, applyChanges, checkConfigs, checkNavigationBar, checkTabBar, connectHost, createHostRequest, mergeConfigs } from "@monimesl/dot-core";
2
- import { dash, tracked } from "@monimesl/dot-dash";
1
+ import { DEFAULT_SYSTEM_CONTEXT, EventEmitter, HostError, MAX_BAR_TITLE_LENGTH, MAX_STACK_DEPTH, MessageChannel, READABLE_CONTRAST, applyChanges, checkColors, checkConfigs, checkNavigationBar, checkTabBar, connectHost, contrast, createHostRequest, isRefusedBarTitle, mergeColors, mergeConfigs, pageBackground, themeScheme, withSystemDefaults } from "@monimesl/dot-core";
2
+ import { dash, tracked, useHostDashApiVersions, warnAboveDashApiVersion } from "@monimesl/dot-dash";
3
+ //#region src/bar-title.ts
4
+ const warnedTitles = /* @__PURE__ */ new Set();
5
+ function warnOnBarTitle(pageId, title) {
6
+ if (title === void 0 || warnedTitles.has(title)) return;
7
+ let problem;
8
+ if (isRefusedBarTitle(title)) problem = "names Dash or Monime, so Dash shows the dot's name instead. Pick a title of your own";
9
+ else if (title.length > MAX_BAR_TITLE_LENGTH) problem = `is longer than ${MAX_BAR_TITLE_LENGTH} characters, so Dash cuts it. Pick a shorter one`;
10
+ if (problem === void 0) return;
11
+ warnedTitles.add(title);
12
+ console.warn(`[dot] Page "${pageId}": the navigation bar's title "${title}" ${problem}.`);
13
+ }
14
+ //#endregion
15
+ //#region src/contrast.ts
16
+ const shownWarnings = /* @__PURE__ */ new Set();
17
+ function warnOnPoorContrast(pageId, navigationBar, scheme, textColor, background) {
18
+ const { backgroundColor: barBackground, textColor: barText } = navigationBar;
19
+ if (barBackground && barText) warnIfHardToRead(`Page "${pageId}": the navigation bar's textColor ${barText} on its backgroundColor ${barBackground}`, barText, barBackground, "leave textColor out so Dash picks one");
20
+ if (textColor) warnIfHardToRead(`Page "${pageId}": the ${scheme} text ${textColor} on the page's background ${background}`, textColor, background, "leave the text color out so Dash picks one");
21
+ }
22
+ function warnIfHardToRead(subject, textColor, background, advice) {
23
+ const ratio = contrast(textColor, background);
24
+ if (ratio >= READABLE_CONTRAST || shownWarnings.has(subject)) return;
25
+ shownWarnings.add(subject);
26
+ console.warn(`[dot] ${subject} is hard to read: its contrast is ${ratio.toFixed(1)}, and text needs at least ${READABLE_CONTRAST}. Pick colours further apart, or ${advice}.`);
27
+ }
28
+ //#endregion
29
+ //#region src/host-errors.ts
30
+ function cancelledError(method) {
31
+ return new HostError("cancelled", `"${method}" was cancelled`);
32
+ }
33
+ //#endregion
3
34
  //#region src/host-calls.ts
4
35
  var HostCalls = class {
5
36
  #channel;
@@ -32,7 +63,7 @@ var HostCalls = class {
32
63
  if (!call) return;
33
64
  this.#waiting.delete(id);
34
65
  this.#channel.send("hostCancel", { id });
35
- call.reject(new HostError("cancelled", `"${call.method}" was cancelled`));
66
+ call.reject(cancelledError(call.method));
36
67
  }
37
68
  #settle(result) {
38
69
  const call = this.#waiting.get(result.id);
@@ -52,12 +83,12 @@ var Page = class {
52
83
  instanceId;
53
84
  context;
54
85
  actionContext;
86
+ dash = tracked(dash, (request) => this.#track(request));
55
87
  #definition;
56
88
  #host;
57
89
  #state;
58
90
  #closed = false;
59
- #calls = /* @__PURE__ */ new Set();
60
- dash = tracked(dash, (request) => this.#track(request));
91
+ #dashRequests = /* @__PURE__ */ new Set();
61
92
  #previousHook = Promise.resolve();
62
93
  #barChanges;
63
94
  constructor(instanceId, id, definition, host, restored) {
@@ -65,16 +96,22 @@ var Page = class {
65
96
  this.instanceId = instanceId;
66
97
  this.#definition = definition;
67
98
  this.#host = host;
68
- const state = restored ? restored.state : JSON.parse(JSON.stringify(definition.initialState));
69
- this.#state = this.#freeze(state);
70
- this.#barChanges = restored?.barChanges ?? {
71
- navigationBar: {},
72
- tabBar: {}
73
- };
99
+ if (restored) {
100
+ this.#state = this.#freeze(restored.state);
101
+ this.#barChanges = restored.barChanges;
102
+ } else {
103
+ const copy = JSON.parse(JSON.stringify(definition.initialState));
104
+ this.#state = this.#freeze(copy);
105
+ this.#barChanges = {
106
+ navigationBar: {},
107
+ tabBar: {},
108
+ colors: {}
109
+ };
110
+ }
74
111
  checkConfigs(definition.configs ?? {});
75
112
  const { mutations = {}, actions = {} } = definition;
76
- const clash = Object.keys(mutations).find((name) => name in actions);
77
- if (clash) throw new Error(`Page "${id}": "${clash}" is both a mutation and an action`);
113
+ const sharedName = Object.keys(mutations).find((name) => name in actions);
114
+ if (sharedName) throw new Error(`Page "${id}" uses "${sharedName}" as both a mutation and an action. Rename one of them`);
78
115
  const mutationCalls = mapNames(mutations, (name) => (event) => this.mutate(name, event));
79
116
  const actionCalls = mapNames(actions, (name) => (event) => this.act(name, event));
80
117
  this.context = new HookContext(this, mutationCalls, actionCalls);
@@ -83,6 +120,9 @@ var Page = class {
83
120
  get state() {
84
121
  return this.#state;
85
122
  }
123
+ get refreshable() {
124
+ return this.#definition.hooks?.onRefresh !== void 0;
125
+ }
86
126
  get bars() {
87
127
  return mergeConfigs(this.#host.dotConfigs, this.#definition.configs, this.#barChanges);
88
128
  }
@@ -91,40 +131,55 @@ var Page = class {
91
131
  }
92
132
  setNavigationBar(options) {
93
133
  checkNavigationBar(options);
94
- if (this.#closed) return;
95
134
  const navigationBar = {
96
135
  ...this.#barChanges.navigationBar,
97
136
  ...options
98
137
  };
99
- this.#barChanges = {
138
+ this.#changeBars({
100
139
  ...this.#barChanges,
101
140
  navigationBar
102
- };
103
- this.#host.showBars(this);
141
+ });
104
142
  }
105
143
  setTabBar(options) {
106
144
  checkTabBar(options);
107
- if (this.#closed) return;
108
145
  const tabBar = {
109
146
  ...this.#barChanges.tabBar,
110
147
  ...options
111
148
  };
112
- this.#barChanges = {
149
+ this.#changeBars({
113
150
  ...this.#barChanges,
114
151
  tabBar
115
- };
152
+ });
153
+ }
154
+ setColors(colors) {
155
+ checkColors(colors);
156
+ const mergedColors = mergeColors(this.#barChanges.colors, colors);
157
+ this.#changeBars({
158
+ ...this.#barChanges,
159
+ colors: mergedColors
160
+ });
161
+ }
162
+ #changeBars(barChanges) {
163
+ if (this.#closed) return;
164
+ this.#barChanges = barChanges;
116
165
  this.#host.showBars(this);
117
166
  }
118
167
  setState(update) {
119
168
  if (this.#closed) return;
120
- const changes = typeof update === "function" ? update(this.#state) : update;
169
+ let changes;
170
+ if (typeof update === "function") changes = update(this.#state);
171
+ else changes = update;
121
172
  if (changes === void 0) return;
122
173
  const next = applyChanges(this.#state, changes);
123
- const patch = {};
124
- for (const key of Object.keys(next)) if (next[key] !== this.#state[key]) patch[key] = next[key];
174
+ const patch = this.#changedKeys(next);
125
175
  this.#state = this.#freeze(next);
126
176
  if (Object.keys(patch).length > 0) this.#host.commit(this, patch);
127
177
  }
178
+ #changedKeys(next) {
179
+ const patch = {};
180
+ for (const key of Object.keys(next)) if (next[key] !== this.#state[key]) patch[key] = next[key];
181
+ return patch;
182
+ }
128
183
  mutate(name, event) {
129
184
  const mutation = this.#definition.mutations?.[name];
130
185
  if (!mutation) throw new Error(`Page "${this.id}" has no mutation "${name}"`);
@@ -151,13 +206,16 @@ var Page = class {
151
206
  }
152
207
  close() {
153
208
  this.#closed = true;
154
- for (const request of this.#calls) request.cancel();
155
- this.#calls.clear();
209
+ for (const request of this.#dashRequests) request.cancel();
210
+ this.#dashRequests.clear();
156
211
  }
157
212
  #track(request) {
158
- if (this.#closed) return;
159
- this.#calls.add(request);
160
- const forget = () => this.#calls.delete(request);
213
+ if (this.#closed) {
214
+ request.cancel();
215
+ return;
216
+ }
217
+ this.#dashRequests.add(request);
218
+ const forget = () => this.#dashRequests.delete(request);
161
219
  request.then(forget, forget);
162
220
  }
163
221
  #runMutation(mutation, event) {
@@ -186,14 +244,26 @@ var Page = class {
186
244
  back: (delta = 1) => this.#navigate({
187
245
  action: "back",
188
246
  delta
189
- })
247
+ }),
248
+ pushForResult: (page, options = {}) => {
249
+ if (this.#closed) return Promise.resolve(void 0);
250
+ return this.#host.pushForResult(this, {
251
+ action: "push",
252
+ page,
253
+ options
254
+ });
255
+ },
256
+ backWithResult: (result) => {
257
+ if (!this.#closed) this.#host.backWithResult(this, result);
258
+ }
190
259
  };
191
260
  }
192
261
  #navigate(navigation) {
193
262
  if (!this.#closed) this.#host.navigate(this, navigation);
194
263
  }
195
264
  #freeze(state) {
196
- return this.#host.strict ? deepFreeze(state) : state;
265
+ if (this.#host.strict) return deepFreeze(state);
266
+ return state;
197
267
  }
198
268
  };
199
269
  var HookContext = class {
@@ -223,6 +293,9 @@ var HookContext = class {
223
293
  setTabBar = (options) => {
224
294
  this.#page.setTabBar(options);
225
295
  };
296
+ setColors = (colors) => {
297
+ this.#page.setColors(colors);
298
+ };
226
299
  };
227
300
  var HandlerContext = class extends HookContext {
228
301
  navigation;
@@ -253,19 +326,24 @@ var Service = class {
253
326
  #pages = /* @__PURE__ */ new Map();
254
327
  #dot;
255
328
  #strict;
329
+ #development;
256
330
  #theme;
257
331
  #systemContext = DEFAULT_SYSTEM_CONTEXT;
258
332
  #stack = {
259
333
  pages: [],
260
334
  limit: MAX_STACK_DEPTH
261
335
  };
336
+ #awaitingResults = /* @__PURE__ */ new Map();
337
+ #results = /* @__PURE__ */ new Map();
262
338
  #hostCalls;
263
- constructor({ transport, pages, dot = {}, strict = false }) {
339
+ constructor({ transport, pages, dot = {}, strict = false, dashMinApiVersion }) {
264
340
  this.channel = new MessageChannel(transport);
265
341
  this.#hostCalls = new HostCalls(this.channel);
266
342
  this.#definitions = pages;
267
343
  this.#dot = dot;
268
344
  this.#strict = strict;
345
+ this.#development = dashMinApiVersion !== void 0;
346
+ if (dashMinApiVersion !== void 0) warnAboveDashApiVersion(dashMinApiVersion);
269
347
  this.#theme = dot.theme ?? {};
270
348
  checkConfigs(dot.configs ?? {});
271
349
  this.channel.handle("connect", () => this.channel.send("setTheme", { theme: this.#theme }));
@@ -273,12 +351,16 @@ var Service = class {
273
351
  this.#stack = stack;
274
352
  });
275
353
  this.channel.handle("syncSystemContext", (context) => {
276
- this.#systemContext = context;
354
+ const previousScheme = this.#systemContext.scheme;
355
+ this.#systemContext = withSystemDefaults(context);
356
+ useHostDashApiVersions(this.#systemContext.dashApiVersions);
357
+ if (this.#systemContext.scheme !== previousScheme) this.#resendBars();
277
358
  });
278
359
  this.channel.handle("load", ({ instanceId, page, options }) => this.loadPage(instanceId, page, options));
279
360
  this.channel.handle("unload", ({ instanceId }) => this.unloadPage(instanceId));
280
361
  this.channel.handle("show", ({ instanceId }) => {
281
362
  const page = this.getPage(instanceId);
363
+ this.#settleResult(instanceId);
282
364
  this.hooks.emit("pageShow", page);
283
365
  this.reportError(page.lifecycle("onShow"), instanceId);
284
366
  });
@@ -287,6 +369,12 @@ var Service = class {
287
369
  this.hooks.emit("pageHide", page);
288
370
  this.reportError(page.lifecycle("onHide"), instanceId);
289
371
  });
372
+ this.channel.handle("refresh", ({ instanceId }) => {
373
+ const refresh = this.getPage(instanceId).lifecycle("onRefresh").finally(() => {
374
+ this.channel.send("refreshed", { instanceId });
375
+ });
376
+ this.reportError(refresh, instanceId);
377
+ });
290
378
  this.channel.handle("event", ({ instanceId, handler, event }) => {
291
379
  const page = this.getPage(instanceId);
292
380
  this.hooks.emit("event", page, handler, event);
@@ -302,11 +390,13 @@ var Service = class {
302
390
  const stopListening = this.channel.listen((error, message) => {
303
391
  this.#reportError(error, instanceIdOf(message));
304
392
  });
393
+ const stopWatchingUncaught = this.#watchUncaughtErrors();
305
394
  connectHost(this.callHost);
306
395
  this.hooks.emit("launch");
307
396
  this.reportError(this.#runLaunchHook());
308
397
  return () => {
309
398
  stopListening();
399
+ stopWatchingUncaught();
310
400
  connectHost(null);
311
401
  };
312
402
  }
@@ -319,6 +409,7 @@ var Service = class {
319
409
  setTheme(theme) {
320
410
  this.#theme = theme;
321
411
  this.channel.send("setTheme", { theme });
412
+ this.#resendBars();
322
413
  }
323
414
  getPage(instanceId) {
324
415
  const page = this.#pages.get(instanceId);
@@ -328,34 +419,42 @@ var Service = class {
328
419
  callHost = (method, params, options) => this.#hostCalls.call(method, params, options);
329
420
  loadPage(instanceId, id, options = {}) {
330
421
  const page = this.#createPage(instanceId, id);
331
- this.channel.send("initPageState", {
332
- instanceId,
333
- state: page.state
334
- });
422
+ this.#sendInitialState(page);
335
423
  this.#sendBars(page);
336
424
  this.hooks.emit("pageLoad", page);
337
425
  this.reportError(page.lifecycle("onLoad", options), instanceId);
338
426
  return page;
339
427
  }
340
428
  replacePages(definitions) {
341
- const previous = this.#definitions;
429
+ const previousDefinitions = this.#definitions;
342
430
  this.#definitions = definitions;
343
431
  for (const [instanceId, page] of this.#pages) {
344
432
  const definition = definitions[page.id];
345
433
  if (!definition) continue;
346
434
  page.close();
347
- if (JSON.stringify(previous[page.id]?.initialState) !== JSON.stringify(definition.initialState)) {
348
- const options = this.#stack.pages.find((entry) => entry.instanceId === instanceId)?.options;
349
- this.loadPage(instanceId, page.id, options);
350
- } else {
351
- const kept = this.#createPage(instanceId, page.id, {
352
- state: page.state,
353
- barChanges: page.barChanges
354
- });
355
- this.#sendBars(kept);
356
- }
435
+ const previousInitialState = previousDefinitions[page.id]?.initialState;
436
+ if (JSON.stringify(previousInitialState) !== JSON.stringify(definition.initialState)) this.loadPage(instanceId, page.id, this.#launchOptions(instanceId));
437
+ else this.#keepPage(page);
357
438
  }
358
439
  }
440
+ #keepPage(page) {
441
+ const kept = this.#createPage(page.instanceId, page.id, {
442
+ state: page.state,
443
+ barChanges: page.barChanges
444
+ });
445
+ this.#sendBars(kept);
446
+ if (kept.refreshable !== page.refreshable) this.#sendInitialState(kept);
447
+ }
448
+ #sendInitialState(page) {
449
+ this.channel.send("initPageState", {
450
+ instanceId: page.instanceId,
451
+ state: page.state,
452
+ refreshable: page.refreshable
453
+ });
454
+ }
455
+ #launchOptions(instanceId) {
456
+ return this.#stack.pages.find((stackEntry) => stackEntry.instanceId === instanceId)?.options;
457
+ }
359
458
  #createPage(instanceId, id, restored) {
360
459
  const page = new Page(instanceId, id, this.#definition(id), {
361
460
  commit: (page, patch) => {
@@ -367,32 +466,80 @@ var Service = class {
367
466
  },
368
467
  showBars: (page) => this.#sendBars(page),
369
468
  dotConfigs: this.#dot.configs,
370
- navigate: (page, navigation) => {
371
- if (navigation.action !== "back") this.#definition(navigation.page);
372
- const { pages, limit } = this.#stack;
373
- if (navigation.action === "push" && pages.length >= limit) throw new Error(`Can't open "${navigation.page}": ${limit} pages are already open. Use replace or back.`);
374
- this.hooks.emit("navigate", page, navigation);
375
- this.channel.send("navigate", {
376
- ...navigation,
377
- from: page.instanceId
378
- });
379
- },
469
+ navigate: (page, navigation) => this.#navigate(page, navigation),
470
+ pushForResult: (page, navigation) => this.#pushForResult(page, navigation),
471
+ backWithResult: (page, result) => this.#backWithResult(page, result),
380
472
  strict: this.#strict
381
473
  }, restored);
382
474
  this.#pages.set(instanceId, page);
383
475
  return page;
384
476
  }
477
+ #navigate(page, navigation) {
478
+ if (navigation.action !== "back") this.#definition(navigation.page);
479
+ const { pages, limit } = this.#stack;
480
+ if (navigation.action === "push" && pages.length >= limit) throw new Error(`Can't open "${navigation.page}": ${limit} pages are already open. Use replace or back.`);
481
+ this.hooks.emit("navigate", page, navigation);
482
+ this.channel.send("navigate", {
483
+ ...navigation,
484
+ from: page.instanceId
485
+ });
486
+ }
487
+ #pushForResult(page, navigation) {
488
+ this.#settleResult(page.instanceId);
489
+ return new Promise((resolve) => {
490
+ this.#navigate(page, navigation);
491
+ this.#awaitingResults.set(page.instanceId, resolve);
492
+ });
493
+ }
494
+ #backWithResult(page, result) {
495
+ const { pages } = this.#stack;
496
+ const index = pages.findIndex((entry) => entry.instanceId === page.instanceId);
497
+ if (index > 0) {
498
+ const below = pages[index - 1];
499
+ this.#results.set(below.instanceId, result);
500
+ }
501
+ this.#navigate(page, {
502
+ action: "back",
503
+ delta: 1
504
+ });
505
+ }
506
+ #settleResult(instanceId) {
507
+ const result = this.#results.get(instanceId);
508
+ this.#results.delete(instanceId);
509
+ const resolve = this.#awaitingResults.get(instanceId);
510
+ this.#awaitingResults.delete(instanceId);
511
+ resolve?.(result);
512
+ }
385
513
  #sendBars(page) {
514
+ const { navigationBar, tabBar, colors } = page.bars;
515
+ const deviceScheme = this.#systemContext.scheme;
516
+ const background = pageBackground(colors.background, this.#theme, deviceScheme);
386
517
  this.channel.send("pageBars", {
387
518
  instanceId: page.instanceId,
388
- ...page.bars
519
+ navigationBar,
520
+ tabBar,
521
+ background
389
522
  });
523
+ this.channel.send("pageColors", {
524
+ instanceId: page.instanceId,
525
+ colors
526
+ });
527
+ if (this.#development) {
528
+ const scheme = themeScheme(this.#theme, deviceScheme);
529
+ const textColor = colors.text?.[scheme] ?? this.#theme.colors?.text?.[scheme];
530
+ warnOnPoorContrast(page.id, navigationBar, scheme, textColor, background);
531
+ warnOnBarTitle(page.id, navigationBar.title);
532
+ }
533
+ }
534
+ #resendBars() {
535
+ for (const page of this.#pages.values()) this.#sendBars(page);
390
536
  }
391
537
  unloadPage(instanceId) {
392
538
  const page = this.#pages.get(instanceId);
393
539
  if (!page) return;
394
540
  this.#pages.delete(instanceId);
395
541
  page.close();
542
+ this.#settleResult(instanceId);
396
543
  this.reportError(page.lifecycle("onUnload"), instanceId);
397
544
  this.hooks.emit("pageUnload", page);
398
545
  }
@@ -409,20 +556,40 @@ var Service = class {
409
556
  async #runLaunchHook() {
410
557
  await this.#dot.hooks?.onLaunch?.call(this);
411
558
  }
412
- #reportError(error, instanceId) {
559
+ #reportError(error, instanceId, uncaught = false) {
413
560
  this.hooks.emit("error", error, instanceId);
414
561
  const reason = error instanceof Error ? error : new Error(String(error));
415
562
  this.#dot.hooks?.onError?.call(this, reason);
416
563
  const report = { message: reason.message };
417
564
  if (instanceId !== void 0) report.instanceId = instanceId;
565
+ if (reason.stack !== void 0) report.stack = reason.stack;
566
+ if (uncaught) report.uncaught = true;
418
567
  this.channel.send("error", report);
419
568
  }
569
+ #watchUncaughtErrors() {
570
+ const scope = globalThis;
571
+ const addListener = scope.addEventListener;
572
+ const removeListener = scope.removeEventListener;
573
+ if (addListener === void 0 || removeListener === void 0) return () => {};
574
+ const onError = (event) => {
575
+ this.#reportError(event.error ?? event.message, void 0, true);
576
+ };
577
+ const onRejection = (event) => {
578
+ this.#reportError(event.reason, void 0, true);
579
+ };
580
+ addListener.call(scope, "error", onError);
581
+ addListener.call(scope, "unhandledrejection", onRejection);
582
+ return () => {
583
+ removeListener.call(scope, "error", onError);
584
+ removeListener.call(scope, "unhandledrejection", onRejection);
585
+ };
586
+ }
420
587
  };
421
588
  function instanceIdOf(message) {
422
589
  const payload = message.payload;
423
590
  if (typeof payload?.instanceId === "number") return payload.instanceId;
424
591
  }
425
592
  //#endregion
426
- export { definePage as i, defineDot as n, Page as r, Service as t };
593
+ export { cancelledError as a, definePage as i, defineDot as n, Page as r, Service as t };
427
594
 
428
- //# sourceMappingURL=service-CHUmeUjF.js.map
595
+ //# sourceMappingURL=service-f7lmyOkU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-f7lmyOkU.js","names":[],"sources":["../src/bar-title.ts","../src/contrast.ts","../src/host-errors.ts","../src/host-calls.ts","../src/page.ts","../src/service.ts"],"sourcesContent":["import { isRefusedBarTitle, MAX_BAR_TITLE_LENGTH } from '@monimesl/dot-core';\n\nconst warnedTitles = new Set<string>();\n\n// In development: warns once when Dash won't show a page's title as written.\nexport function warnOnBarTitle(pageId: string, title: string | undefined): void {\n if (title === undefined || warnedTitles.has(title)) {\n return;\n }\n let problem: string | undefined;\n if (isRefusedBarTitle(title)) {\n problem =\n \"names Dash or Monime, so Dash shows the dot's name instead. Pick a title of your own\";\n } else if (title.length > MAX_BAR_TITLE_LENGTH) {\n problem = `is longer than ${MAX_BAR_TITLE_LENGTH} characters, so Dash cuts it. Pick a shorter one`;\n }\n if (problem === undefined) {\n return;\n }\n warnedTitles.add(title);\n console.warn(`[dot] Page \"${pageId}\": the navigation bar's title \"${title}\" ${problem}.`);\n}\n","import type { ColorScheme, NavigationBarOptions } from '@monimesl/dot-core';\nimport { contrast, READABLE_CONTRAST } from '@monimesl/dot-core';\n\n// Each warning shows once, so a page that changes its bars often doesn't fill the console.\nconst shownWarnings = new Set<string>();\n\n// In development: warns when text colours the dot picks are hard to read on their backgrounds.\n// Colours Dash picks itself always read well. `textColor` is the page's or the theme's, if set.\nexport function warnOnPoorContrast(\n pageId: string,\n navigationBar: NavigationBarOptions,\n scheme: ColorScheme,\n textColor: string | undefined,\n background: string,\n): void {\n const { backgroundColor: barBackground, textColor: barText } = navigationBar;\n if (barBackground && barText) {\n warnIfHardToRead(\n `Page \"${pageId}\": the navigation bar's textColor ${barText} on its backgroundColor ${barBackground}`,\n barText,\n barBackground,\n 'leave textColor out so Dash picks one',\n );\n }\n if (textColor) {\n warnIfHardToRead(\n `Page \"${pageId}\": the ${scheme} text ${textColor} on the page's background ${background}`,\n textColor,\n background,\n 'leave the text color out so Dash picks one',\n );\n }\n}\n\nfunction warnIfHardToRead(\n subject: string,\n textColor: string,\n background: string,\n advice: string,\n): void {\n const ratio = contrast(textColor, background);\n if (ratio >= READABLE_CONTRAST || shownWarnings.has(subject)) {\n return;\n }\n shownWarnings.add(subject);\n console.warn(\n `[dot] ${subject} is hard to read: its contrast is ${ratio.toFixed(1)}, and text needs at least ${READABLE_CONTRAST}. Pick colours further apart, or ${advice}.`,\n );\n}\n","import { HostError } from '@monimesl/dot-core';\n\nexport function cancelledError(method: string): HostError {\n return new HostError('cancelled', `\"${method}\" was cancelled`);\n}\n","import type {\n HostCallOptions,\n HostRequest,\n HostResult,\n LogicLayerMessages,\n} from '@monimesl/dot-core';\nimport { HostError, createHostRequest } from '@monimesl/dot-core';\n\nimport type { ServiceChannel } from './service';\nimport { cancelledError } from './host-errors';\n\ninterface WaitingCall {\n readonly method: string;\n resolve(value: unknown): void;\n reject(error: HostError): void;\n}\n\n// Calls to the host that are waiting for their `hostResult`.\nexport class HostCalls {\n readonly #channel: ServiceChannel;\n readonly #waiting = new Map<number, WaitingCall>();\n #nextId = 1;\n\n constructor(channel: ServiceChannel) {\n this.#channel = channel;\n channel.handle('hostResult', (result) => this.#settle(result));\n }\n\n call(method: string, params: unknown, options: HostCallOptions): HostRequest<unknown> {\n const id = this.#nextId++;\n const promise = new Promise<unknown>((resolve, reject) => {\n this.#waiting.set(id, { method, resolve, reject });\n });\n const message: LogicLayerMessages['hostCall'] = { id, method, params };\n if (options.timeout !== undefined) {\n message.timeout = options.timeout;\n }\n this.#channel.send('hostCall', message);\n return createHostRequest(promise, () => this.#cancel(id));\n }\n\n #cancel(id: number): void {\n const call = this.#waiting.get(id);\n if (!call) {\n return;\n }\n this.#waiting.delete(id);\n this.#channel.send('hostCancel', { id });\n call.reject(cancelledError(call.method));\n }\n\n #settle(result: HostResult): void {\n const call = this.#waiting.get(result.id);\n // A cancelled call may still get a late answer from the host. Ignore it.\n if (!call) {\n return;\n }\n this.#waiting.delete(result.id);\n if ('error' in result) {\n call.reject(new HostError(result.error.code, result.error.message));\n } else {\n call.resolve(result.value);\n }\n }\n}\n","import type {\n DotEvent,\n Navigation,\n NavigationBarOptions,\n PageBars,\n PageConfigs,\n PageShare,\n PageHooks,\n PageLaunchOptions,\n PageState,\n PageColors,\n TabBarOptions,\n} from '@monimesl/dot-core';\nimport {\n applyChanges,\n checkColors,\n checkConfigs,\n checkNavigationBar,\n checkTabBar,\n mergeColors,\n mergeConfigs,\n} from '@monimesl/dot-core';\nimport type { Dash, DashRequest } from '@monimesl/dot-dash';\nimport { dash, tracked } from '@monimesl/dot-dash';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface DotPageIds {}\n}\n\n// Any string until `.dot/pages.d.ts` lists the dot's pages. The brackets make TypeScript test the\n// ids as a whole, not one by one.\nexport type PageId = [keyof DotPageIds] extends [never]\n ? string\n : Extract<keyof DotPageIds, string>;\n\n// The navigation pushForResult asks for: a page on top.\nexport interface PushNavigation {\n readonly action: 'push';\n readonly page: string;\n readonly options: PageLaunchOptions;\n}\n\nexport interface PageNavigation {\n push(page: PageId, options?: PageLaunchOptions): void;\n\n replace(page: PageId, options?: PageLaunchOptions): void;\n\n reset(page: PageId, options?: PageLaunchOptions): void;\n\n back(delta?: number): void;\n\n // Opens `page` on top, and resolves with what it gives back with backWithResult, or undefined if\n // it closes without one, such as when the person goes back.\n pushForResult<Result = unknown>(\n page: PageId,\n options?: PageLaunchOptions,\n ): Promise<Result | undefined>;\n\n // Closes this page, and gives `result` to the page below if it opened this one with pushForResult.\n backWithResult(result: unknown): void;\n}\n\nexport type DeepReadonly<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\nexport type State<S> = DeepReadonly<S>;\n\nexport type StateChanges<S> = Partial<State<S>>;\n\nexport type StateUpdate<S> = StateChanges<S> | ((state: State<S>) => StateChanges<S> | undefined);\n\n// Declared as methods because TypeScript checks method parameters loosely. That lets a handler\n// narrow its event, such as `(state, event: DotEvent<{ size: number }>) => ...`.\nexport type Mutation<S> = {\n mutation(state: State<S>, event: DotEvent): StateChanges<S> | undefined;\n}['mutation'];\n\nexport type Action<S> = {\n action(state: State<S>, event: DotEvent): void | Promise<void>;\n}['action'];\n\n// A handler's parameters after `state`, which another handler passes when it calls it.\ntype EventArgs<F> = F extends (state: never, ...rest: infer R) => unknown ? R : never;\n\nexport interface PageContext<S extends PageState, Mu, Ac> {\n readonly id: string;\n readonly instanceId: number;\n readonly state: State<S>;\n // Dash's features. Calls still running when the page closes are cancelled.\n readonly dash: Dash;\n\n setState(update: StateUpdate<S>): void;\n\n // Changes Dash's navigation bar on this page, on top of what definePage set.\n setNavigationBar(options: NavigationBarOptions): void;\n\n // Hides or shows Dash's tab bar while this page shows.\n setTabBar(options: TabBarOptions): void;\n\n // Changes this page's colours, on top of what definePage set, one colour at a time, such as\n // { background: { light: \"#FFF3E0\" } }. Undefined puts the theme's colour back.\n setColors(colors: PageColors): void;\n\n readonly mutations: { readonly [K in keyof Mu]: (...event: EventArgs<Mu[K]>) => void };\n readonly actions: { readonly [K in keyof Ac]: (...event: EventArgs<Ac[K]>) => Promise<void> };\n}\n\nexport interface ActionContext<S extends PageState, Mu, Ac> extends PageContext<S, Mu, Ac> {\n readonly navigation: PageNavigation;\n}\n\nexport interface PageOptions<S extends PageState, Mu, Ac> {\n id?: string;\n initialState: S;\n // What Dash's bars show on this page, on top of defineDot's configs. `setNavigationBar` and\n // `setTabBar` change them while the page runs.\n configs?: PageConfigs;\n // Lets Dash's Share open this page, with these of its options. Without it, Share opens the dot's\n // first page. Write it out in place, as the build reads it without running the code.\n share?: PageShare;\n hooks?: Partial<PageHooks> & ThisType<PageContext<S, Mu, Ac>>;\n mutations?: Mu & ThisType<void>;\n actions?: Ac & ThisType<ActionContext<S, Mu, Ac>>;\n}\n\nexport interface PageDefinition<S extends PageState = PageState> {\n readonly id?: string;\n readonly initialState: S;\n readonly configs?: PageConfigs;\n readonly share?: PageShare;\n readonly hooks?: Partial<PageHooks>;\n readonly mutations?: Readonly<Record<string, Mutation<S>>>;\n readonly actions?: Readonly<Record<string, Action<S>>>;\n}\n\nexport function definePage<\n S extends PageState,\n Mu extends Record<string, Mutation<S>>,\n Ac extends Record<string, Action<S>>,\n>(options: PageOptions<S, Mu, Ac>): PageDefinition<S> {\n return options;\n}\n\nexport interface PageHost {\n commit(page: Page, patch: PageState): void;\n\n // The page's bars changed.\n showBars(page: Page): void;\n\n // defineDot's configs. The page's own configs override them.\n readonly dotConfigs?: PageConfigs | undefined;\n\n navigate(page: Page, navigation: Navigation): void;\n\n // Opens a page on top of `page`, which waits for what that page gives back.\n pushForResult(page: Page, navigation: PushNavigation): Promise<unknown>;\n\n backWithResult(page: Page, result: unknown): void;\n\n readonly strict?: boolean;\n}\n\n// What a page keeps from the copy it replaces, such as after a hot reload.\nexport interface RestoredPage<S> {\n readonly state: State<S>;\n readonly barChanges: PageBars;\n}\n\ntype Mutations<S> = Record<string, Mutation<S>>;\ntype Actions<S> = Record<string, Action<S>>;\ntype MutationCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['mutations'];\ntype ActionCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['actions'];\n\nexport class Page<S extends PageState = PageState> {\n readonly id: string;\n readonly instanceId: number;\n readonly context: PageContext<S, Mutations<S>, Actions<S>>;\n readonly actionContext: ActionContext<S, Mutations<S>, Actions<S>>;\n readonly dash: Dash = tracked(dash, (request) => this.#track(request));\n readonly #definition: PageDefinition<S>;\n readonly #host: PageHost;\n #state: State<S>;\n #closed = false;\n readonly #dashRequests = new Set<DashRequest<unknown>>();\n #previousHook: Promise<unknown> = Promise.resolve();\n // Bar changes the page made while running, on top of its configs.\n #barChanges: PageBars;\n\n constructor(\n instanceId: number,\n id: string,\n definition: PageDefinition<S>,\n host: PageHost,\n restored?: RestoredPage<S>,\n ) {\n this.id = id;\n this.instanceId = instanceId;\n this.#definition = definition;\n this.#host = host;\n if (restored) {\n this.#state = this.#freeze(restored.state);\n this.#barChanges = restored.barChanges;\n } else {\n const copy = JSON.parse(JSON.stringify(definition.initialState)) as State<S>;\n this.#state = this.#freeze(copy);\n this.#barChanges = { navigationBar: {}, tabBar: {}, colors: {} };\n }\n checkConfigs(definition.configs ?? {});\n\n const { mutations = {}, actions = {} } = definition;\n const sharedName = Object.keys(mutations).find((name) => name in actions);\n if (sharedName) {\n throw new Error(\n `Page \"${id}\" uses \"${sharedName}\" as both a mutation and an action. Rename one of them`,\n );\n }\n\n const mutationCalls = mapNames(\n mutations,\n (name) => (event?: DotEvent) => this.mutate(name, event),\n );\n const actionCalls = mapNames(actions, (name) => (event?: DotEvent) => this.act(name, event));\n this.context = new HookContext(this, mutationCalls, actionCalls);\n this.actionContext = new HandlerContext(\n this,\n mutationCalls,\n actionCalls,\n this.#createNavigation(),\n );\n }\n\n get state(): State<S> {\n return this.#state;\n }\n\n // Whether the person can pull the page down to refresh it.\n get refreshable(): boolean {\n return this.#definition.hooks?.onRefresh !== undefined;\n }\n\n get bars(): PageBars {\n return mergeConfigs(this.#host.dotConfigs, this.#definition.configs, this.#barChanges);\n }\n\n get barChanges(): PageBars {\n return this.#barChanges;\n }\n\n setNavigationBar(options: NavigationBarOptions): void {\n checkNavigationBar(options);\n const navigationBar = { ...this.#barChanges.navigationBar, ...options };\n this.#changeBars({ ...this.#barChanges, navigationBar });\n }\n\n setTabBar(options: TabBarOptions): void {\n checkTabBar(options);\n const tabBar = { ...this.#barChanges.tabBar, ...options };\n this.#changeBars({ ...this.#barChanges, tabBar });\n }\n\n setColors(colors: PageColors): void {\n checkColors(colors);\n const mergedColors = mergeColors(this.#barChanges.colors, colors);\n this.#changeBars({ ...this.#barChanges, colors: mergedColors });\n }\n\n #changeBars(barChanges: PageBars): void {\n if (this.#closed) {\n return;\n }\n this.#barChanges = barChanges;\n this.#host.showBars(this as Page);\n }\n\n setState(update: StateUpdate<S>): void {\n if (this.#closed) {\n return;\n }\n let changes: StateChanges<S> | undefined;\n if (typeof update === 'function') {\n changes = update(this.#state);\n } else {\n changes = update;\n }\n if (changes === undefined) {\n return;\n }\n const next = applyChanges(this.#state, changes);\n const patch = this.#changedKeys(next);\n this.#state = this.#freeze(next);\n if (Object.keys(patch).length > 0) {\n this.#host.commit(this as Page, patch);\n }\n }\n\n #changedKeys(next: State<S>): PageState {\n const patch: PageState = {};\n for (const key of Object.keys(next) as (keyof State<S> & string)[]) {\n if (next[key] !== this.#state[key]) {\n patch[key] = next[key];\n }\n }\n return patch;\n }\n\n mutate(name: string, event?: DotEvent): void {\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n }\n\n async act(name: string, event?: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (!action) {\n throw new Error(`Page \"${this.id}\" has no action \"${name}\"`);\n }\n await this.#runAction(action, event);\n }\n\n // Runs the mutation or action named by a view event. A mutation is done when this returns.\n dispatch(name: string, event: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (action) {\n return this.#runAction(action, event);\n }\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation or action \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n return Promise.resolve();\n }\n\n async lifecycle<K extends keyof PageHooks>(\n hookName: K,\n ...args: Parameters<PageHooks[K]>\n ): Promise<void> {\n const hook = this.#definition.hooks?.[hookName] as\n ((...hookArgs: Parameters<PageHooks[K]>) => unknown) | undefined;\n // Hooks run one at a time, in call order. A failed hook doesn't block the next one.\n const run = this.#previousHook.then(() => hook?.call(this.context, ...args));\n this.#previousHook = run.catch(() => {});\n await run;\n }\n\n close(): void {\n this.#closed = true;\n for (const request of this.#dashRequests) {\n request.cancel();\n }\n this.#dashRequests.clear();\n }\n\n #track(request: DashRequest<unknown>): void {\n // An action still running after the page closed mustn't reach the person, so its calls stop.\n if (this.#closed) {\n request.cancel();\n return;\n }\n this.#dashRequests.add(request);\n const forget = () => this.#dashRequests.delete(request);\n void request.then(forget, forget);\n }\n\n // Handlers called from other handlers may leave out the event, so it can be undefined here.\n #runMutation(mutation: Mutation<S>, event: DotEvent | undefined): void {\n this.setState((state) => mutation(state, event as DotEvent));\n }\n\n async #runAction(action: Action<S>, event: DotEvent | undefined): Promise<void> {\n await action.call(this.actionContext, this.#state, event as DotEvent);\n }\n\n #createNavigation(): PageNavigation {\n return {\n push: (page, options = {}) => this.#navigate({ action: 'push', page, options }),\n replace: (page, options = {}) => this.#navigate({ action: 'replace', page, options }),\n reset: (page, options = {}) => this.#navigate({ action: 'reset', page, options }),\n back: (delta = 1) => this.#navigate({ action: 'back', delta }),\n pushForResult: <Result>(page: PageId, options: PageLaunchOptions = {}) => {\n if (this.#closed) {\n return Promise.resolve(undefined);\n }\n const result = this.#host.pushForResult(this as Page, { action: 'push', page, options });\n return result as Promise<Result | undefined>;\n },\n backWithResult: (result) => {\n if (!this.#closed) {\n this.#host.backWithResult(this as Page, result);\n }\n },\n };\n }\n\n #navigate(navigation: Navigation): void {\n if (!this.#closed) {\n this.#host.navigate(this as Page, navigation);\n }\n }\n\n #freeze(state: State<S>): State<S> {\n if (this.#host.strict) {\n return deepFreeze(state);\n }\n return state;\n }\n}\n\n// What hooks get as `this`. `setState` is bound, so a hook may destructure it.\nclass HookContext<S extends PageState> implements PageContext<S, Mutations<S>, Actions<S>> {\n readonly id: string;\n readonly instanceId: number;\n readonly mutations: MutationCalls<S>;\n readonly actions: ActionCalls<S>;\n readonly dash: Dash;\n readonly #page: Page<S>;\n\n constructor(page: Page<S>, mutations: MutationCalls<S>, actions: ActionCalls<S>) {\n this.id = page.id;\n this.instanceId = page.instanceId;\n this.dash = page.dash;\n this.mutations = mutations;\n this.actions = actions;\n this.#page = page;\n }\n\n get state(): State<S> {\n return this.#page.state;\n }\n\n readonly setState = (update: StateUpdate<S>): void => {\n this.#page.setState(update);\n };\n\n readonly setNavigationBar = (options: NavigationBarOptions): void => {\n this.#page.setNavigationBar(options);\n };\n\n readonly setTabBar = (options: TabBarOptions): void => {\n this.#page.setTabBar(options);\n };\n\n readonly setColors = (colors: PageColors): void => {\n this.#page.setColors(colors);\n };\n}\n\n// What actions get as `this`: the hook context plus navigation.\nclass HandlerContext<S extends PageState>\n extends HookContext<S>\n implements ActionContext<S, Mutations<S>, Actions<S>>\n{\n readonly navigation: PageNavigation;\n\n constructor(\n page: Page<S>,\n mutations: MutationCalls<S>,\n actions: ActionCalls<S>,\n navigation: PageNavigation,\n ) {\n super(page, mutations, actions);\n this.navigation = navigation;\n }\n}\n\nfunction mapNames<T>(\n record: Readonly<Record<string, unknown>>,\n create: (name: string) => T,\n): Record<string, T> {\n return Object.fromEntries(Object.keys(record).map((name) => [name, create(name)]));\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value);\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n }\n return value;\n}\n","import type {\n DotHooks,\n DotEvent,\n Envelope,\n HostCaller,\n HostLayerMessages,\n LogicLayerMessages,\n Navigation,\n PageConfigs,\n PageLaunchOptions,\n PageState,\n SystemContext,\n Theme,\n Transport,\n UILayerMessages,\n} from '@monimesl/dot-core';\nimport {\n DEFAULT_SYSTEM_CONTEXT,\n checkConfigs,\n connectHost,\n EventEmitter,\n MAX_STACK_DEPTH,\n MessageChannel,\n pageBackground,\n themeScheme,\n withSystemDefaults,\n} from '@monimesl/dot-core';\nimport { useHostDashApiVersions, warnAboveDashApiVersion } from '@monimesl/dot-dash';\n\nimport { warnOnBarTitle } from './bar-title';\nimport { warnOnPoorContrast } from './contrast';\nimport { HostCalls } from './host-calls';\nimport type { PageDefinition, PushNavigation, RestoredPage } from './page';\nimport { Page } from './page';\n\nexport type ServiceChannel = MessageChannel<\n LogicLayerMessages,\n UILayerMessages & HostLayerMessages\n>;\n\nexport interface ServiceHooks {\n launch(): void;\n\n pageLoad(page: Page): void;\n\n pageUnload(page: Page): void;\n\n pageShow(page: Page): void;\n\n pageHide(page: Page): void;\n\n // A page asked to navigate. The request is on its way to the view layer.\n navigate(page: Page, navigation: Navigation): void;\n\n // Before a handler runs\n event(page: Page, handler: string, event: DotEvent): void;\n\n // After the state changes, with the changed keys, before they go to the view layer\n setState(page: Page, patch: PageState): void;\n\n // `instanceId` is set when the error came from a page.\n error(error: unknown, instanceId: number | undefined): void;\n}\n\nexport interface ServicePlugin {\n readonly name: string;\n\n setup(service: Service): void;\n}\n\nexport interface DotContext {\n readonly theme: Theme;\n readonly systemContext: SystemContext;\n\n setTheme(theme: Theme): void;\n}\n\nexport interface DotDefinition {\n readonly theme?: Theme;\n readonly plugins?: readonly ServicePlugin[];\n readonly pages: readonly [string, ...string[]];\n // What Dash's bars show on every page. A page's own configs override them.\n readonly configs?: PageConfigs;\n readonly hooks?: Partial<DotHooks> & ThisType<DotContext>;\n}\n\nexport function defineDot(dot: DotDefinition): DotDefinition {\n return dot;\n}\n\nexport interface ServiceOptions {\n transport: Transport;\n dot?: Partial<DotDefinition>;\n pages: Record<string, PageDefinition>;\n strict?: boolean;\n // While developing, the dot's minApiVersion from dot.json, so Dash calls newer than it are pointed out.\n dashMinApiVersion?: string | undefined;\n}\n\n// The logic layer. It owns every page's state, runs handlers, and sends state changes to the\n// view layer.\nexport class Service implements DotContext {\n readonly channel: ServiceChannel;\n readonly hooks = new EventEmitter<ServiceHooks>();\n #definitions: Record<string, PageDefinition>;\n readonly #pages = new Map<number, Page>();\n readonly #dot: Partial<DotDefinition>;\n readonly #strict: boolean;\n readonly #development: boolean;\n #theme: Theme;\n #systemContext: SystemContext = DEFAULT_SYSTEM_CONTEXT;\n #stack: HostLayerMessages['syncStack'] = { pages: [], limit: MAX_STACK_DEPTH };\n // How each page waiting for a result from a page it opened with pushForResult gets it.\n readonly #awaitingResults = new Map<number, (result: unknown) => void>();\n // Results given with backWithResult, kept for the page below until it shows again.\n readonly #results = new Map<number, unknown>();\n readonly #hostCalls: HostCalls;\n\n constructor({ transport, pages, dot = {}, strict = false, dashMinApiVersion }: ServiceOptions) {\n this.channel = new MessageChannel(transport);\n this.#hostCalls = new HostCalls(this.channel);\n this.#definitions = pages;\n this.#dot = dot;\n this.#strict = strict;\n // Only development builds pass the Dash API version, so only they warn about it.\n this.#development = dashMinApiVersion !== undefined;\n if (dashMinApiVersion !== undefined) {\n warnAboveDashApiVersion(dashMinApiVersion);\n }\n this.#theme = dot.theme ?? {};\n checkConfigs(dot.configs ?? {});\n\n this.channel.handle('connect', () => this.channel.send('setTheme', { theme: this.#theme }));\n this.channel.handle('syncStack', (stack) => {\n this.#stack = stack;\n });\n this.channel.handle('syncSystemContext', (context) => {\n const previousScheme = this.#systemContext.scheme;\n this.#systemContext = withSystemDefaults(context);\n useHostDashApiVersions(this.#systemContext.dashApiVersions);\n if (this.#systemContext.scheme !== previousScheme) {\n this.#resendBars();\n }\n });\n this.channel.handle('load', ({ instanceId, page, options }) =>\n this.loadPage(instanceId, page, options),\n );\n this.channel.handle('unload', ({ instanceId }) => this.unloadPage(instanceId));\n this.channel.handle('show', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n // The page it opened has closed, with or without a result.\n this.#settleResult(instanceId);\n this.hooks.emit('pageShow', page);\n this.reportError(page.lifecycle('onShow'), instanceId);\n });\n this.channel.handle('hide', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('pageHide', page);\n this.reportError(page.lifecycle('onHide'), instanceId);\n });\n this.channel.handle('refresh', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n const refresh = page.lifecycle('onRefresh').finally(() => {\n this.channel.send('refreshed', { instanceId });\n });\n this.reportError(refresh, instanceId);\n });\n this.channel.handle('event', ({ instanceId, handler, event }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('event', page, handler, event);\n this.reportError(page.dispatch(handler, event), instanceId);\n });\n\n dot.plugins?.forEach((plugin) => this.use(plugin));\n }\n\n use(plugin: ServicePlugin): this {\n plugin.setup(this);\n return this;\n }\n\n start(): () => void {\n const stopListening = this.channel.listen((error, message) => {\n this.#reportError(error, instanceIdOf(message));\n });\n const stopWatchingUncaught = this.#watchUncaughtErrors();\n connectHost(this.callHost);\n this.hooks.emit('launch');\n this.reportError(this.#runLaunchHook());\n return () => {\n stopListening();\n stopWatchingUncaught();\n connectHost(null);\n };\n }\n\n get theme(): Theme {\n return this.#theme;\n }\n\n get systemContext(): SystemContext {\n return this.#systemContext;\n }\n\n setTheme(theme: Theme): void {\n this.#theme = theme;\n this.channel.send('setTheme', { theme });\n this.#resendBars();\n }\n\n getPage(instanceId: number): Page {\n const page = this.#pages.get(instanceId);\n if (!page) {\n throw new Error(`Page instance ${instanceId} is not loaded`);\n }\n return page;\n }\n\n // Runs a host call, such as `dash.ui.alert`, through this service's channel.\n readonly callHost: HostCaller = (method, params, options) =>\n this.#hostCalls.call(method, params, options);\n\n loadPage(instanceId: number, id: string, options: PageLaunchOptions = {}): Page {\n const page = this.#createPage(instanceId, id);\n this.#sendInitialState(page);\n this.#sendBars(page);\n this.hooks.emit('pageLoad', page);\n this.reportError(page.lifecycle('onLoad', options), instanceId);\n return page;\n }\n\n // Swaps in a newer build's pages on a hot reload. A page restarts only if its initialState\n // changed.\n replacePages(definitions: Record<string, PageDefinition>): void {\n const previousDefinitions = this.#definitions;\n this.#definitions = definitions;\n for (const [instanceId, page] of this.#pages) {\n const definition = definitions[page.id];\n // A page the new build no longer has keeps its old code until it closes.\n if (!definition) {\n continue;\n }\n page.close();\n const previousInitialState = previousDefinitions[page.id]?.initialState;\n if (JSON.stringify(previousInitialState) !== JSON.stringify(definition.initialState)) {\n this.loadPage(instanceId, page.id, this.#launchOptions(instanceId));\n } else {\n this.#keepPage(page);\n }\n }\n }\n\n #keepPage(page: Page): void {\n const kept = this.#createPage(page.instanceId, page.id, {\n state: page.state,\n barChanges: page.barChanges,\n });\n // The new definePage may set other configs, or add or drop onRefresh.\n this.#sendBars(kept);\n if (kept.refreshable !== page.refreshable) {\n this.#sendInitialState(kept);\n }\n }\n\n #sendInitialState(page: Page): void {\n this.channel.send('initPageState', {\n instanceId: page.instanceId,\n state: page.state,\n refreshable: page.refreshable,\n });\n }\n\n // The options the page was opened with, so its onLoad can run again with them.\n #launchOptions(instanceId: number): PageLaunchOptions | undefined {\n const entry = this.#stack.pages.find((stackEntry) => stackEntry.instanceId === instanceId);\n return entry?.options;\n }\n\n #createPage(instanceId: number, id: string, restored?: RestoredPage<PageState>): Page {\n const page = new Page(\n instanceId,\n id,\n this.#definition(id),\n {\n commit: (page, patch) => {\n this.hooks.emit('setState', page, patch);\n this.channel.send('setPageState', { instanceId: page.instanceId, patchState: patch });\n },\n showBars: (page) => this.#sendBars(page),\n dotConfigs: this.#dot.configs,\n navigate: (page, navigation) => this.#navigate(page, navigation),\n pushForResult: (page, navigation) => this.#pushForResult(page, navigation),\n backWithResult: (page, result) => this.#backWithResult(page, result),\n strict: this.#strict,\n },\n restored,\n );\n this.#pages.set(instanceId, page);\n return page;\n }\n\n #navigate(page: Page, navigation: Navigation): void {\n if (navigation.action !== 'back') {\n this.#definition(navigation.page);\n }\n const { pages, limit } = this.#stack;\n if (navigation.action === 'push' && pages.length >= limit) {\n throw new Error(\n `Can't open \"${navigation.page}\": ${limit} pages are already open. Use replace or back.`,\n );\n }\n this.hooks.emit('navigate', page, navigation);\n this.channel.send('navigate', { ...navigation, from: page.instanceId });\n }\n\n #pushForResult(page: Page, navigation: PushNavigation): Promise<unknown> {\n // A result it was still waiting for can't come now.\n this.#settleResult(page.instanceId);\n // When the page can't open, the promise rejects with the reason.\n return new Promise((resolve) => {\n this.#navigate(page, navigation);\n this.#awaitingResults.set(page.instanceId, resolve);\n });\n }\n\n #backWithResult(page: Page, result: unknown): void {\n const { pages } = this.#stack;\n const index = pages.findIndex((entry) => entry.instanceId === page.instanceId);\n if (index > 0) {\n const below = pages[index - 1]!;\n this.#results.set(below.instanceId, result);\n }\n this.#navigate(page, { action: 'back', delta: 1 });\n }\n\n // Ends the page's wait for a result: with the one kept for it, or undefined without one.\n #settleResult(instanceId: number): void {\n const result = this.#results.get(instanceId);\n this.#results.delete(instanceId);\n const resolve = this.#awaitingResults.get(instanceId);\n this.#awaitingResults.delete(instanceId);\n resolve?.(result);\n }\n\n // The host gets the background as the dot shows it now; the webview gets the page's colours.\n #sendBars(page: Page): void {\n const { navigationBar, tabBar, colors } = page.bars;\n const deviceScheme = this.#systemContext.scheme;\n const background = pageBackground(colors.background, this.#theme, deviceScheme);\n this.channel.send('pageBars', {\n instanceId: page.instanceId,\n navigationBar,\n tabBar,\n background,\n });\n this.channel.send('pageColors', { instanceId: page.instanceId, colors });\n if (this.#development) {\n const scheme = themeScheme(this.#theme, deviceScheme);\n const textColor = colors.text?.[scheme] ?? this.#theme.colors?.text?.[scheme];\n warnOnPoorContrast(page.id, navigationBar, scheme, textColor, background);\n warnOnBarTitle(page.id, navigationBar.title);\n }\n }\n\n // A new theme or scheme can change the background every open page shows.\n #resendBars(): void {\n for (const page of this.#pages.values()) {\n this.#sendBars(page);\n }\n }\n\n unloadPage(instanceId: number): void {\n const page = this.#pages.get(instanceId);\n if (!page) {\n return;\n }\n this.#pages.delete(instanceId);\n page.close();\n this.#settleResult(instanceId);\n this.reportError(page.lifecycle('onUnload'), instanceId);\n this.hooks.emit('pageUnload', page);\n }\n\n #definition(id: string): PageDefinition {\n const definition = this.#definitions[id];\n if (!definition) {\n throw new Error(`Unknown page \"${id}\"`);\n }\n return definition;\n }\n\n reportError(work: Promise<void>, instanceId?: number): void {\n work.catch((error: unknown) => {\n this.#reportError(error, instanceId);\n });\n }\n\n async #runLaunchHook(): Promise<void> {\n await this.#dot.hooks?.onLaunch?.call(this);\n }\n\n #reportError(error: unknown, instanceId: number | undefined, uncaught = false): void {\n this.hooks.emit('error', error, instanceId);\n const reason = error instanceof Error ? error : new Error(String(error));\n this.#dot.hooks?.onError?.call(this, reason);\n const report: LogicLayerMessages['error'] = { message: reason.message };\n if (instanceId !== undefined) {\n report.instanceId = instanceId;\n }\n if (reason.stack !== undefined) {\n report.stack = reason.stack;\n }\n if (uncaught) {\n report.uncaught = true;\n }\n this.channel.send('error', report);\n }\n\n // Only some engines have global error events, such as a browser's Web Worker. Elsewhere the native\n // host catches what it can itself.\n #watchUncaughtErrors(): () => void {\n const scope = globalThis as ErrorEventScope;\n const addListener = scope.addEventListener;\n const removeListener = scope.removeEventListener;\n if (addListener === undefined || removeListener === undefined) {\n return () => {};\n }\n const onError = (event: UncaughtErrorEvent) => {\n this.#reportError(event.error ?? event.message, undefined, true);\n };\n const onRejection = (event: UncaughtErrorEvent) => {\n this.#reportError(event.reason, undefined, true);\n };\n addListener.call(scope, 'error', onError);\n addListener.call(scope, 'unhandledrejection', onRejection);\n return () => {\n removeListener.call(scope, 'error', onError);\n removeListener.call(scope, 'unhandledrejection', onRejection);\n };\n }\n}\n\n// What the service reads of a global scope's error events, as dots type-check without the DOM.\ninterface ErrorEventScope {\n addEventListener?: (type: string, listener: (event: UncaughtErrorEvent) => void) => void;\n removeEventListener?: (type: string, listener: (event: UncaughtErrorEvent) => void) => void;\n}\n\n// An `error` event has `error` and `message`; an `unhandledrejection` event has `reason`.\ninterface UncaughtErrorEvent {\n error?: unknown;\n message?: string;\n reason?: unknown;\n}\n\nfunction instanceIdOf(message: Envelope): number | undefined {\n const payload = message.payload as { instanceId?: unknown } | undefined;\n if (typeof payload?.instanceId === 'number') {\n return payload.instanceId;\n }\n return undefined;\n}\n"],"mappings":"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"}
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { f as State, s as PageDefinition } from "./page-Xn5DL6zY.js";
1
+ import { f as State, s as PageDefinition } from "./page-DBnKjef6.js";
2
2
  import { DotEvent, Navigation, PageBars, PageLaunchOptions, PageState } from "@monimesl/dot-core";
3
3
  //#region src/testing.d.ts
4
4
  export type TestHost = Readonly<Record<string, (params: never) => unknown>>;
@@ -6,6 +6,7 @@ export interface TestPageOptions {
6
6
  id?: string;
7
7
  options?: PageLaunchOptions;
8
8
  host?: TestHost;
9
+ results?: Readonly<Record<string, unknown>>;
9
10
  }
10
11
  export interface HostCallRecord {
11
12
  readonly method: string;
@@ -17,12 +18,14 @@ export interface PageTest<S extends PageState> {
17
18
  readonly navigations: readonly Navigation[];
18
19
  readonly hostCalls: readonly HostCallRecord[];
19
20
  readonly bars: PageBars;
21
+ readonly result: unknown;
20
22
  tap(name: string, detail?: unknown): Promise<void>;
21
23
  dispatch(name: string, event?: DotEvent): Promise<void>;
22
24
  show(): Promise<void>;
23
25
  hide(): Promise<void>;
24
26
  unload(): Promise<void>;
27
+ refresh(): Promise<void>;
25
28
  }
26
- export declare function testPage<S extends PageState>(definition: PageDefinition<S>, { id, options, host }?: TestPageOptions): Promise<PageTest<S>>;
29
+ export declare function testPage<S extends PageState>(definition: PageDefinition<S>, { id, options, host, results }?: TestPageOptions): Promise<PageTest<S>>;
27
30
  //#endregion
28
31
  //# sourceMappingURL=testing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing.ts"],"mappings":";;;YAqBY,WAAW,SAAS,gBAAgB;iBAE/B;EACf;EACA,UAAU;EAEV,OAAO;;iBAGQ;WACN;WACA;;iBAGM,SAAS,UAAU;WACzB,OAAO,MAAM;WACb,kBAAkB;WAClB,sBAAsB;WACtB,oBAAoB;WAEpB,MAAM;EAEf,IAAI,cAAc,mBAAmB;EAErC,SAAS,cAAc,QAAQ,WAAW;EAE1C,QAAQ;EAER,QAAQ;EAER,UAAU;;wBAGU,SAAS,UAAU,WACvC,YAAY,eAAe,MACzB,IAAa,SAAc,SAAa,kBACzC,QAAQ,SAAS"}
1
+ {"version":3,"file":"testing.d.ts","names":[],"sources":["../src/testing.ts"],"mappings":";;;YAuBY,WAAW,SAAS,gBAAgB;iBAE/B;EACf;EACA,UAAU;EAEV,OAAO;EAGP,UAAU,SAAS;;iBAGJ;WACN;WACA;;iBAGM,SAAS,UAAU;WACzB,OAAO,MAAM;WACb,kBAAkB;WAClB,sBAAsB;WACtB,oBAAoB;WAEpB,MAAM;WAEN;EAET,IAAI,cAAc,mBAAmB;EAErC,SAAS,cAAc,QAAQ,WAAW;EAE1C,QAAQ;EAER,QAAQ;EAER,UAAU;EAGV,WAAW;;wBAGS,SAAS,UAAU,WACvC,YAAY,eAAe,MACzB,IAAa,SAAc,MAAW,YAAgB,kBACvD,QAAQ,SAAS"}
package/dist/testing.js CHANGED
@@ -1,35 +1,31 @@
1
- import { i as definePage, n as defineDot, r as Page } from "./service-CHUmeUjF.js";
1
+ import { a as cancelledError, i as definePage, n as defineDot, r as Page } from "./service-f7lmyOkU.js";
2
2
  import { HostError, connectHost, createEvent, createHostRequest } from "@monimesl/dot-core";
3
3
  //#region src/testing.ts
4
- const scope = globalThis;
5
- scope.definePage = definePage;
6
- scope.defineDot = defineDot;
7
- scope.createEvent = createEvent;
8
- async function testPage(definition, { id = "test", options = {}, host = {} } = {}) {
4
+ const globals = globalThis;
5
+ globals.definePage = definePage;
6
+ globals.defineDot = defineDot;
7
+ globals.createEvent = createEvent;
8
+ async function testPage(definition, { id = "test", options = {}, host = {}, results = {} } = {}) {
9
9
  const patches = [];
10
10
  const navigations = [];
11
11
  const hostCalls = [];
12
- connectHost((method, params) => {
13
- hostCalls.push({
14
- method,
15
- params
16
- });
17
- const handler = host[method];
18
- if (!handler) {
19
- const error = new HostError("unsupported", `The test gives no host method "${method}"`);
20
- return createHostRequest(Promise.reject(error));
21
- }
22
- let cancel = () => {};
23
- const answer = new Promise((resolve, reject) => {
24
- cancel = () => reject(new HostError("cancelled", `"${method}" was cancelled`));
25
- Promise.resolve().then(() => handler(params)).then(resolve, reject);
26
- });
27
- return createHostRequest(answer, () => cancel());
28
- });
12
+ let result;
13
+ connectHost(fakeHostCaller(host, hostCalls));
29
14
  const page = new Page(1, id, definition, {
30
15
  commit: (_page, patch) => patches.push(patch),
31
16
  showBars: () => {},
32
17
  navigate: (_page, navigation) => navigations.push(navigation),
18
+ pushForResult: (_page, navigation) => {
19
+ navigations.push(navigation);
20
+ return Promise.resolve(results[navigation.page]);
21
+ },
22
+ backWithResult: (_page, given) => {
23
+ result = given;
24
+ navigations.push({
25
+ action: "back",
26
+ delta: 1
27
+ });
28
+ },
33
29
  strict: true
34
30
  });
35
31
  const dispatch = async (name, event = createEvent("tap")) => {
@@ -44,6 +40,9 @@ async function testPage(definition, { id = "test", options = {}, host = {} } = {
44
40
  get bars() {
45
41
  return page.bars;
46
42
  },
43
+ get result() {
44
+ return result;
45
+ },
47
46
  patches,
48
47
  navigations,
49
48
  hostCalls,
@@ -54,7 +53,27 @@ async function testPage(definition, { id = "test", options = {}, host = {} } = {
54
53
  unload: async () => {
55
54
  page.close();
56
55
  await page.lifecycle("onUnload");
56
+ },
57
+ refresh: () => page.lifecycle("onRefresh")
58
+ };
59
+ }
60
+ function fakeHostCaller(host, hostCalls) {
61
+ return (method, params) => {
62
+ hostCalls.push({
63
+ method,
64
+ params
65
+ });
66
+ const handler = host[method];
67
+ if (!handler) {
68
+ const error = new HostError("unsupported", `The test has no host method "${method}". Add it to testPage's \`host\` option`);
69
+ return createHostRequest(Promise.reject(error));
57
70
  }
71
+ let cancel = () => {};
72
+ const answer = new Promise((resolve, reject) => {
73
+ cancel = () => reject(cancelledError(method));
74
+ Promise.resolve().then(() => handler(params)).then(resolve, reject);
75
+ });
76
+ return createHostRequest(answer, () => cancel());
58
77
  };
59
78
  }
60
79
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type {\n DotEvent,\n Navigation,\n PageBars,\n PageLaunchOptions,\n PageState,\n} from '@monimesl/dot-core';\nimport { HostError, connectHost, createEvent, createHostRequest } from '@monimesl/dot-core';\n\nimport type { PageDefinition, State } from './page';\nimport { definePage, Page } from './page';\nimport { defineDot } from './service';\n\n// A dot's code calls these without importing them; the compiler adds the imports when it bundles.\n// Tests import that code directly, so this module provides them, and must be imported first.\nconst scope = globalThis as unknown as Record<string, unknown>;\nscope.definePage = definePage;\nscope.defineDot = defineDot;\nscope.createEvent = createEvent;\n\n// Fake host methods by name, such as `'dash.ui.alert': () => ({ action: 'ok' })`.\nexport type TestHost = Readonly<Record<string, (params: never) => unknown>>;\n\nexport interface TestPageOptions {\n id?: string;\n options?: PageLaunchOptions;\n // Methods the page may call on the host; any other rejects with `unsupported`.\n host?: TestHost;\n}\n\nexport interface HostCallRecord {\n readonly method: string;\n readonly params: unknown;\n}\n\nexport interface PageTest<S extends PageState> {\n readonly state: State<S>;\n readonly patches: readonly PageState[];\n readonly navigations: readonly Navigation[];\n readonly hostCalls: readonly HostCallRecord[];\n // What Dash's bars show over the page now.\n readonly bars: PageBars;\n\n tap(name: string, detail?: unknown): Promise<void>;\n\n dispatch(name: string, event?: DotEvent): Promise<void>;\n\n show(): Promise<void>;\n\n hide(): Promise<void>;\n\n unload(): Promise<void>;\n}\n\nexport async function testPage<S extends PageState>(\n definition: PageDefinition<S>,\n { id = 'test', options = {}, host = {} }: TestPageOptions = {},\n): Promise<PageTest<S>> {\n const patches: PageState[] = [];\n const navigations: Navigation[] = [];\n const hostCalls: HostCallRecord[] = [];\n connectHost((method, params) => {\n hostCalls.push({ method, params });\n const handler = host[method];\n if (!handler) {\n const error = new HostError('unsupported', `The test gives no host method \"${method}\"`);\n return createHostRequest(Promise.reject(error));\n }\n // Cancelling rejects the call, as it does in a running dot.\n let cancel = () => {};\n const answer = new Promise<unknown>((resolve, reject) => {\n cancel = () => reject(new HostError('cancelled', `\"${method}\" was cancelled`));\n Promise.resolve()\n .then(() => handler(params as never))\n .then(resolve, reject);\n });\n return createHostRequest(answer, () => cancel());\n });\n const page = new Page<S>(1, id, definition, {\n commit: (_page, patch) => patches.push(patch),\n showBars: () => {},\n navigate: (_page, navigation) => navigations.push(navigation),\n strict: true,\n });\n const dispatch = async (name: string, event: DotEvent = createEvent('tap')) => {\n await page.dispatch(name, event);\n };\n\n await page.lifecycle('onLoad', options);\n await page.lifecycle('onShow');\n\n return {\n get state() {\n return page.state;\n },\n get bars() {\n return page.bars;\n },\n patches,\n navigations,\n hostCalls,\n tap: (name, detail = {}) => dispatch(name, createEvent('tap', detail)),\n dispatch,\n show: () => page.lifecycle('onShow'),\n hide: () => page.lifecycle('onHide'),\n unload: async () => {\n page.close();\n await page.lifecycle('onUnload');\n },\n };\n}\n"],"mappings":";;;AAeA,MAAM,QAAQ;AACd,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,cAAc;AAoCpB,eAAsB,SACpB,YACA,EAAE,KAAK,QAAQ,UAAU,CAAC,GAAG,OAAO,CAAC,MAAuB,CAAC,GACvC;CACtB,MAAM,UAAuB,CAAC;CAC9B,MAAM,cAA4B,CAAC;CACnC,MAAM,YAA8B,CAAC;CACrC,aAAa,QAAQ,WAAW;EAC9B,UAAU,KAAK;GAAE;GAAQ;EAAO,CAAC;EACjC,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,SAAS;GACZ,MAAM,QAAQ,IAAI,UAAU,eAAe,kCAAkC,OAAO,EAAE;GACtF,OAAO,kBAAkB,QAAQ,OAAO,KAAK,CAAC;EAChD;EAEA,IAAI,eAAe,CAAC;EACpB,MAAM,SAAS,IAAI,SAAkB,SAAS,WAAW;GACvD,eAAe,OAAO,IAAI,UAAU,aAAa,IAAI,OAAO,gBAAgB,CAAC;GAC7E,QAAQ,QAAQ,CAAC,CACd,WAAW,QAAQ,MAAe,CAAC,CAAC,CACpC,KAAK,SAAS,MAAM;EACzB,CAAC;EACD,OAAO,kBAAkB,cAAc,OAAO,CAAC;CACjD,CAAC;CACD,MAAM,OAAO,IAAI,KAAQ,GAAG,IAAI,YAAY;EAC1C,SAAS,OAAO,UAAU,QAAQ,KAAK,KAAK;EAC5C,gBAAgB,CAAC;EACjB,WAAW,OAAO,eAAe,YAAY,KAAK,UAAU;EAC5D,QAAQ;CACV,CAAC;CACD,MAAM,WAAW,OAAO,MAAc,QAAkB,YAAY,KAAK,MAAM;EAC7E,MAAM,KAAK,SAAS,MAAM,KAAK;CACjC;CAEA,MAAM,KAAK,UAAU,UAAU,OAAO;CACtC,MAAM,KAAK,UAAU,QAAQ;CAE7B,OAAO;EACL,IAAI,QAAQ;GACV,OAAO,KAAK;EACd;EACA,IAAI,OAAO;GACT,OAAO,KAAK;EACd;EACA;EACA;EACA;EACA,MAAM,MAAM,SAAS,CAAC,MAAM,SAAS,MAAM,YAAY,OAAO,MAAM,CAAC;EACrE;EACA,YAAY,KAAK,UAAU,QAAQ;EACnC,YAAY,KAAK,UAAU,QAAQ;EACnC,QAAQ,YAAY;GAClB,KAAK,MAAM;GACX,MAAM,KAAK,UAAU,UAAU;EACjC;CACF;AACF"}
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/testing.ts"],"sourcesContent":["import type {\n DotEvent,\n HostCaller,\n Navigation,\n PageBars,\n PageLaunchOptions,\n PageState,\n} from '@monimesl/dot-core';\nimport { HostError, connectHost, createEvent, createHostRequest } from '@monimesl/dot-core';\n\nimport type { PageDefinition, State } from './page';\nimport { definePage, Page } from './page';\nimport { defineDot } from './service';\nimport { cancelledError } from './host-errors';\n\n// A dot's code uses these without imports, and the compiler adds the imports when it bundles.\n// Tests load that code directly, so this module sets them up. Import it first.\nconst globals = globalThis as unknown as Record<string, unknown>;\nglobals.definePage = definePage;\nglobals.defineDot = defineDot;\nglobals.createEvent = createEvent;\n\n// Fake host methods by name, such as `'dash.ui.alert': () => ({ action: 'ok' })`.\nexport type TestHost = Readonly<Record<string, (params: never) => unknown>>;\n\nexport interface TestPageOptions {\n id?: string;\n options?: PageLaunchOptions;\n // Host methods the page may call. Any other call rejects with `unsupported`.\n host?: TestHost;\n // What each page the test page opens with pushForResult gives back, by its id. A page not listed\n // gives undefined, as if the person went back.\n results?: Readonly<Record<string, unknown>>;\n}\n\nexport interface HostCallRecord {\n readonly method: string;\n readonly params: unknown;\n}\n\nexport interface PageTest<S extends PageState> {\n readonly state: State<S>;\n readonly patches: readonly PageState[];\n readonly navigations: readonly Navigation[];\n readonly hostCalls: readonly HostCallRecord[];\n // What Dash's bars show over the page now.\n readonly bars: PageBars;\n // What the page gave back with backWithResult, or undefined.\n readonly result: unknown;\n\n tap(name: string, detail?: unknown): Promise<void>;\n\n dispatch(name: string, event?: DotEvent): Promise<void>;\n\n show(): Promise<void>;\n\n hide(): Promise<void>;\n\n unload(): Promise<void>;\n\n // Runs the page's onRefresh, as when the person pulls it down.\n refresh(): Promise<void>;\n}\n\nexport async function testPage<S extends PageState>(\n definition: PageDefinition<S>,\n { id = 'test', options = {}, host = {}, results = {} }: TestPageOptions = {},\n): Promise<PageTest<S>> {\n const patches: PageState[] = [];\n const navigations: Navigation[] = [];\n const hostCalls: HostCallRecord[] = [];\n let result: unknown;\n connectHost(fakeHostCaller(host, hostCalls));\n const page = new Page<S>(1, id, definition, {\n commit: (_page, patch) => patches.push(patch),\n showBars: () => {},\n navigate: (_page, navigation) => navigations.push(navigation),\n pushForResult: (_page, navigation) => {\n navigations.push(navigation);\n return Promise.resolve(results[navigation.page]);\n },\n backWithResult: (_page, given) => {\n result = given;\n navigations.push({ action: 'back', delta: 1 });\n },\n strict: true,\n });\n const dispatch = async (name: string, event: DotEvent = createEvent('tap')) => {\n await page.dispatch(name, event);\n };\n\n await page.lifecycle('onLoad', options);\n await page.lifecycle('onShow');\n\n return {\n get state() {\n return page.state;\n },\n get bars() {\n return page.bars;\n },\n get result() {\n return result;\n },\n patches,\n navigations,\n hostCalls,\n tap: (name, detail = {}) => dispatch(name, createEvent('tap', detail)),\n dispatch,\n show: () => page.lifecycle('onShow'),\n hide: () => page.lifecycle('onHide'),\n unload: async () => {\n page.close();\n await page.lifecycle('onUnload');\n },\n refresh: () => page.lifecycle('onRefresh'),\n };\n}\n\n// Answers each call with the test's fake for that method.\nfunction fakeHostCaller(host: TestHost, hostCalls: HostCallRecord[]): HostCaller {\n return (method, params) => {\n hostCalls.push({ method, params });\n const handler = host[method];\n if (!handler) {\n const error = new HostError(\n 'unsupported',\n `The test has no host method \"${method}\". Add it to testPage's \\`host\\` option`,\n );\n return createHostRequest(Promise.reject(error));\n }\n // Cancelling rejects the call, as it does in a running dot.\n let cancel = () => {};\n const answer = new Promise<unknown>((resolve, reject) => {\n cancel = () => reject(cancelledError(method));\n Promise.resolve()\n .then(() => handler(params as never))\n .then(resolve, reject);\n });\n return createHostRequest(answer, () => cancel());\n };\n}\n"],"mappings":";;;AAiBA,MAAM,UAAU;AAChB,QAAQ,aAAa;AACrB,QAAQ,YAAY;AACpB,QAAQ,cAAc;AA4CtB,eAAsB,SACpB,YACA,EAAE,KAAK,QAAQ,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,MAAuB,CAAC,GACrD;CACtB,MAAM,UAAuB,CAAC;CAC9B,MAAM,cAA4B,CAAC;CACnC,MAAM,YAA8B,CAAC;CACrC,IAAI;CACJ,YAAY,eAAe,MAAM,SAAS,CAAC;CAC3C,MAAM,OAAO,IAAI,KAAQ,GAAG,IAAI,YAAY;EAC1C,SAAS,OAAO,UAAU,QAAQ,KAAK,KAAK;EAC5C,gBAAgB,CAAC;EACjB,WAAW,OAAO,eAAe,YAAY,KAAK,UAAU;EAC5D,gBAAgB,OAAO,eAAe;GACpC,YAAY,KAAK,UAAU;GAC3B,OAAO,QAAQ,QAAQ,QAAQ,WAAW,KAAK;EACjD;EACA,iBAAiB,OAAO,UAAU;GAChC,SAAS;GACT,YAAY,KAAK;IAAE,QAAQ;IAAQ,OAAO;GAAE,CAAC;EAC/C;EACA,QAAQ;CACV,CAAC;CACD,MAAM,WAAW,OAAO,MAAc,QAAkB,YAAY,KAAK,MAAM;EAC7E,MAAM,KAAK,SAAS,MAAM,KAAK;CACjC;CAEA,MAAM,KAAK,UAAU,UAAU,OAAO;CACtC,MAAM,KAAK,UAAU,QAAQ;CAE7B,OAAO;EACL,IAAI,QAAQ;GACV,OAAO,KAAK;EACd;EACA,IAAI,OAAO;GACT,OAAO,KAAK;EACd;EACA,IAAI,SAAS;GACX,OAAO;EACT;EACA;EACA;EACA;EACA,MAAM,MAAM,SAAS,CAAC,MAAM,SAAS,MAAM,YAAY,OAAO,MAAM,CAAC;EACrE;EACA,YAAY,KAAK,UAAU,QAAQ;EACnC,YAAY,KAAK,UAAU,QAAQ;EACnC,QAAQ,YAAY;GAClB,KAAK,MAAM;GACX,MAAM,KAAK,UAAU,UAAU;EACjC;EACA,eAAe,KAAK,UAAU,WAAW;CAC3C;AACF;AAGA,SAAS,eAAe,MAAgB,WAAyC;CAC/E,QAAQ,QAAQ,WAAW;EACzB,UAAU,KAAK;GAAE;GAAQ;EAAO,CAAC;EACjC,MAAM,UAAU,KAAK;EACrB,IAAI,CAAC,SAAS;GACZ,MAAM,QAAQ,IAAI,UAChB,eACA,gCAAgC,OAAO,wCACzC;GACA,OAAO,kBAAkB,QAAQ,OAAO,KAAK,CAAC;EAChD;EAEA,IAAI,eAAe,CAAC;EACpB,MAAM,SAAS,IAAI,SAAkB,SAAS,WAAW;GACvD,eAAe,OAAO,eAAe,MAAM,CAAC;GAC5C,QAAQ,QAAQ,CAAC,CACd,WAAW,QAAQ,MAAe,CAAC,CAAC,CACpC,KAAK,SAAS,MAAM;EACzB,CAAC;EACD,OAAO,kBAAkB,cAAc,OAAO,CAAC;CACjD;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monimesl/dot-service",
3
- "version": "0.1.0-alpha.9",
3
+ "version": "1.0.0-beta.0",
4
4
  "description": "Dot logic layer: pages, state and plugins",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,8 +30,8 @@
30
30
  "main": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",
32
32
  "dependencies": {
33
- "@monimesl/dot-core": "0.1.0-alpha.9",
34
- "@monimesl/dot-dash": "0.1.0-alpha.9"
33
+ "@monimesl/dot-core": "1.0.0-beta.0",
34
+ "@monimesl/dot-dash": "1.0.0-beta.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "publint": "^0.3.24",
@@ -1 +0,0 @@
1
- {"version":3,"file":"page-Xn5DL6zY.d.ts","names":[],"sources":["../src/page.ts"],"mappings":";;;QAqBQ;YAEI;;KAKA,gBAAgB,uCAExB,cAAc;UAED;EACf,KAAK,MAAM,QAAQ,UAAU;EAE7B,QAAQ,MAAM,QAAQ,UAAU;EAEhC,MAAM,MAAM,QAAQ,UAAU;EAE9B,KAAK;;KAGK,aAAa,KAAK,eAAc,6BACxC,IACA,+BACc,WAAW,IAAI,aAAa,EAAE,SAC1C;KAEM,MAAM,KAAK,aAAa;KAExB,aAAa,KAAK,QAAQ,MAAM;KAEhC,YAAY,KAAK,aAAa,OAAO,OAAO,MAAM,OAAO,aAAa;KAItE,SAAS;EACnB,SAAS,OAAO,MAAM,IAAI,OAAO,WAAW,aAAa;;KAG/C,OAAO;EACjB,OAAO,OAAO,MAAM,IAAI,OAAO,kBAAkB;;KAI9C,UAAU,KAAK,YAAW,iBAAiB,YAAY,iBAAgB;UAE3D,YAAY,UAAU,WAAW,IAAI;WAC3C;WACA;WACA,OAAO,MAAM;WAEb,MAAM;EAEf,SAAS,QAAQ,YAAY;EAG7B,iBAAiB,SAAS;EAG1B,UAAU,SAAS;WAEV,uBAAuB,WAAW,SAAS,OAAO,UAAU,GAAG;WAC/D,qBAAqB,WAAW,SAAS,OAAO,UAAU,GAAG,QAAQ;;UAG/D,cAAc,UAAU,WAAW,IAAI,YAAY,YAAY,GAAG,IAAI;WAC5E,YAAY;;UAGN,YAAY,UAAU,WAAW,IAAI;EACpD;EACA,cAAc;EAGd,UAAU;EACV,QAAQ,QAAQ,aAAa,SAAS,YAAY,GAAG,IAAI;EACzD,YAAY,KAAK;EACjB,UAAU,KAAK,SAAS,cAAc,GAAG,IAAI;;UAG9B,eAAe,UAAU,YAAY;WAC3C;WACA,cAAc;WACd,UAAU;WACV,QAAQ,QAAQ;WAChB,YAAY,SAAS,eAAe,SAAS;WAC7C,UAAU,SAAS,eAAe,OAAO;;iBAGpC,WACd,UAAU,WACV,WAAW,eAAe,SAAS,KACnC,WAAW,eAAe,OAAO,KACjC,SAAS,YAAY,GAAG,IAAI,MAAM,eAAe;UAIlC;EACf,OAAO,MAAM,MAAM,OAAO;EAG1B,SAAS,MAAM;WAGN,aAAa;EAEtB,SAAS,MAAM,MAAM,YAAY;WAExB;;UAIM,aAAa;WACnB,OAAO,MAAM;WACb,YAAY;;KAGlB,UAAU,KAAK,eAAe,SAAS;KACvC,QAAQ,KAAK,eAAe,OAAO;cAI3B,KAAK,UAAU,YAAY;;WAC7B;WACA;WACA,SAAS,YAAY,GAAG,UAAU,IAAI,QAAQ;WAC9C,eAAe,cAAc,GAAG,UAAU,IAAI,QAAQ;WAMtD,MAAM;EAMb,YAAA,oBACA,YACA,YAAY,eAAe,IAC3B,MAAM,UACN,WAAW,aAAa;MAiCtB,SAAS,MAAM;MAIf,QAAQ;MAIR,cAAc;EAIlB,iBAAiB,SAAS;EAU1B,UAAU,SAAS;EAUnB,SAAS,QAAQ,YAAY;EAqB7B,OAAO,cAAc,QAAQ;EAQvB,IAAI,cAAc,QAAQ,WAAW;EAS3C,SAAS,cAAc,OAAO,WAAW;EAanC,UAAU,gBAAgB,WAC9B,UAAU,MACP,MAAM,WAAW,UAAU,MAC7B;EASH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"service-CHUmeUjF.js","names":[],"sources":["../src/host-calls.ts","../src/page.ts","../src/service.ts"],"sourcesContent":["import type {\n HostCallOptions,\n HostRequest,\n HostResult,\n LogicLayerMessages,\n} from '@monimesl/dot-core';\nimport { HostError, createHostRequest } from '@monimesl/dot-core';\n\nimport type { ServiceChannel } from './service';\n\ninterface WaitingCall {\n readonly method: string;\n resolve(value: unknown): void;\n reject(error: HostError): void;\n}\n\n// Calls to the host that are waiting for their `hostResult`.\nexport class HostCalls {\n readonly #channel: ServiceChannel;\n readonly #waiting = new Map<number, WaitingCall>();\n #nextId = 1;\n\n constructor(channel: ServiceChannel) {\n this.#channel = channel;\n channel.handle('hostResult', (result) => this.#settle(result));\n }\n\n call(method: string, params: unknown, options: HostCallOptions): HostRequest<unknown> {\n const id = this.#nextId++;\n const promise = new Promise<unknown>((resolve, reject) => {\n this.#waiting.set(id, { method, resolve, reject });\n });\n const message: LogicLayerMessages['hostCall'] = { id, method, params };\n if (options.timeout !== undefined) {\n message.timeout = options.timeout;\n }\n this.#channel.send('hostCall', message);\n return createHostRequest(promise, () => this.#cancel(id));\n }\n\n #cancel(id: number): void {\n const call = this.#waiting.get(id);\n if (!call) {\n return;\n }\n this.#waiting.delete(id);\n this.#channel.send('hostCancel', { id });\n call.reject(new HostError('cancelled', `\"${call.method}\" was cancelled`));\n }\n\n #settle(result: HostResult): void {\n const call = this.#waiting.get(result.id);\n // A call cancelled here may still get the host's late answer, which no one wants any more.\n if (!call) {\n return;\n }\n this.#waiting.delete(result.id);\n if ('error' in result) {\n call.reject(new HostError(result.error.code, result.error.message));\n } else {\n call.resolve(result.value);\n }\n }\n}\n","import type {\n DotEvent,\n Navigation,\n NavigationBarOptions,\n PageBars,\n PageConfigs,\n PageHooks,\n PageLaunchOptions,\n PageState,\n TabBarOptions,\n} from '@monimesl/dot-core';\nimport {\n applyChanges,\n checkConfigs,\n checkNavigationBar,\n checkTabBar,\n mergeConfigs,\n} from '@monimesl/dot-core';\nimport type { Dash, DashRequest } from '@monimesl/dot-dash';\nimport { dash, tracked } from '@monimesl/dot-dash';\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface DotPageIds {}\n}\n\n// Any string until `.dot/pages.d.ts` lists the dot's pages; the brackets stop TypeScript from\n// applying the condition to each id separately.\nexport type PageId = [keyof DotPageIds] extends [never]\n ? string\n : Extract<keyof DotPageIds, string>;\n\nexport interface PageNavigation {\n push(page: PageId, options?: PageLaunchOptions): void;\n\n replace(page: PageId, options?: PageLaunchOptions): void;\n\n reset(page: PageId, options?: PageLaunchOptions): void;\n\n back(delta?: number): void;\n}\n\nexport type DeepReadonly<T> = T extends (...args: never[]) => unknown\n ? T\n : T extends object\n ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n : T;\n\nexport type State<S> = DeepReadonly<S>;\n\nexport type StateChanges<S> = Partial<State<S>>;\n\nexport type StateUpdate<S> = StateChanges<S> | ((state: State<S>) => StateChanges<S> | undefined);\n\n// Declared as methods, whose parameters TypeScript checks loosely, so a handler may narrow its\n// event, e.g. `(state, event: DotEvent<{ size: number }>) => ...`.\nexport type Mutation<S> = {\n mutation(state: State<S>, event: DotEvent): StateChanges<S> | undefined;\n}['mutation'];\n\nexport type Action<S> = {\n action(state: State<S>, event: DotEvent): void | Promise<void>;\n}['action'];\n\n// A handler's parameters after `state`: what calling it from another handler takes.\ntype EventArgs<F> = F extends (state: never, ...rest: infer R) => unknown ? R : never;\n\nexport interface PageContext<S extends PageState, Mu, Ac> {\n readonly id: string;\n readonly instanceId: number;\n readonly state: State<S>;\n // Dash, whose calls still running when the page closes are cancelled.\n readonly dash: Dash;\n\n setState(update: StateUpdate<S>): void;\n\n // Changes what Dash's navigation bar shows over this page, from what definePage set.\n setNavigationBar(options: NavigationBarOptions): void;\n\n // Hides or shows Dash's tab bar while this page shows.\n setTabBar(options: TabBarOptions): void;\n\n readonly mutations: { readonly [K in keyof Mu]: (...event: EventArgs<Mu[K]>) => void };\n readonly actions: { readonly [K in keyof Ac]: (...event: EventArgs<Ac[K]>) => Promise<void> };\n}\n\nexport interface ActionContext<S extends PageState, Mu, Ac> extends PageContext<S, Mu, Ac> {\n readonly navigation: PageNavigation;\n}\n\nexport interface PageOptions<S extends PageState, Mu, Ac> {\n id?: string;\n initialState: S;\n // What Dash's bars show while this page shows, over defineDot's configs; `setNavigationBar` and\n // `setTabBar` change them.\n configs?: PageConfigs;\n hooks?: Partial<PageHooks> & ThisType<PageContext<S, Mu, Ac>>;\n mutations?: Mu & ThisType<void>;\n actions?: Ac & ThisType<ActionContext<S, Mu, Ac>>;\n}\n\nexport interface PageDefinition<S extends PageState = PageState> {\n readonly id?: string;\n readonly initialState: S;\n readonly configs?: PageConfigs;\n readonly hooks?: Partial<PageHooks>;\n readonly mutations?: Readonly<Record<string, Mutation<S>>>;\n readonly actions?: Readonly<Record<string, Action<S>>>;\n}\n\nexport function definePage<\n S extends PageState,\n Mu extends Record<string, Mutation<S>>,\n Ac extends Record<string, Action<S>>,\n>(options: PageOptions<S, Mu, Ac>): PageDefinition<S> {\n return options;\n}\n\nexport interface PageHost {\n commit(page: Page, patch: PageState): void;\n\n // The page's bars changed.\n showBars(page: Page): void;\n\n // defineDot's configs, which the page's own override.\n readonly dotConfigs?: PageConfigs | undefined;\n\n navigate(page: Page, navigation: Navigation): void;\n\n readonly strict?: boolean;\n}\n\n// What a page that goes on from an earlier one, as after a hot reload, starts with.\nexport interface RestoredPage<S> {\n readonly state: State<S>;\n readonly barChanges: PageBars;\n}\n\ntype Mutations<S> = Record<string, Mutation<S>>;\ntype Actions<S> = Record<string, Action<S>>;\ntype MutationCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['mutations'];\ntype ActionCalls<S extends PageState> = PageContext<S, Mutations<S>, Actions<S>>['actions'];\n\nexport class Page<S extends PageState = PageState> {\n readonly id: string;\n readonly instanceId: number;\n readonly context: PageContext<S, Mutations<S>, Actions<S>>;\n readonly actionContext: ActionContext<S, Mutations<S>, Actions<S>>;\n readonly #definition: PageDefinition<S>;\n readonly #host: PageHost;\n #state: State<S>;\n #closed = false;\n readonly #calls = new Set<DashRequest<unknown>>();\n readonly dash: Dash = tracked(dash, (request) => this.#track(request));\n #previousHook: Promise<unknown> = Promise.resolve();\n // What the page changed in its bars while running, over its configs.\n #barChanges: PageBars;\n\n constructor(\n instanceId: number,\n id: string,\n definition: PageDefinition<S>,\n host: PageHost,\n restored?: RestoredPage<S>,\n ) {\n this.id = id;\n this.instanceId = instanceId;\n this.#definition = definition;\n this.#host = host;\n const state = restored\n ? restored.state\n : (JSON.parse(JSON.stringify(definition.initialState)) as State<S>);\n this.#state = this.#freeze(state);\n this.#barChanges = restored?.barChanges ?? { navigationBar: {}, tabBar: {} };\n checkConfigs(definition.configs ?? {});\n\n const { mutations = {}, actions = {} } = definition;\n const clash = Object.keys(mutations).find((name) => name in actions);\n if (clash) {\n throw new Error(`Page \"${id}\": \"${clash}\" is both a mutation and an action`);\n }\n\n const mutationCalls = mapNames(\n mutations,\n (name) => (event?: DotEvent) => this.mutate(name, event),\n );\n const actionCalls = mapNames(actions, (name) => (event?: DotEvent) => this.act(name, event));\n this.context = new HookContext(this, mutationCalls, actionCalls);\n this.actionContext = new HandlerContext(\n this,\n mutationCalls,\n actionCalls,\n this.#createNavigation(),\n );\n }\n\n get state(): State<S> {\n return this.#state;\n }\n\n get bars(): PageBars {\n return mergeConfigs(this.#host.dotConfigs, this.#definition.configs, this.#barChanges);\n }\n\n get barChanges(): PageBars {\n return this.#barChanges;\n }\n\n setNavigationBar(options: NavigationBarOptions): void {\n checkNavigationBar(options);\n if (this.#closed) {\n return;\n }\n const navigationBar = { ...this.#barChanges.navigationBar, ...options };\n this.#barChanges = { ...this.#barChanges, navigationBar };\n this.#host.showBars(this as Page);\n }\n\n setTabBar(options: TabBarOptions): void {\n checkTabBar(options);\n if (this.#closed) {\n return;\n }\n const tabBar = { ...this.#barChanges.tabBar, ...options };\n this.#barChanges = { ...this.#barChanges, tabBar };\n this.#host.showBars(this as Page);\n }\n\n setState(update: StateUpdate<S>): void {\n if (this.#closed) {\n return;\n }\n const changes = typeof update === 'function' ? update(this.#state) : update;\n if (changes === undefined) {\n return;\n }\n const next = applyChanges(this.#state, changes);\n const patch: PageState = {};\n for (const key of Object.keys(next) as (keyof State<S> & string)[]) {\n if (next[key] !== this.#state[key]) {\n patch[key] = next[key];\n }\n }\n this.#state = this.#freeze(next);\n if (Object.keys(patch).length > 0) {\n this.#host.commit(this as Page, patch);\n }\n }\n\n mutate(name: string, event?: DotEvent): void {\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n }\n\n async act(name: string, event?: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (!action) {\n throw new Error(`Page \"${this.id}\" has no action \"${name}\"`);\n }\n await this.#runAction(action, event);\n }\n\n // Runs the mutation or action named by a view event. A mutation is done when this returns.\n dispatch(name: string, event: DotEvent): Promise<void> {\n const action = this.#definition.actions?.[name];\n if (action) {\n return this.#runAction(action, event);\n }\n const mutation = this.#definition.mutations?.[name];\n if (!mutation) {\n throw new Error(`Page \"${this.id}\" has no mutation or action \"${name}\"`);\n }\n this.#runMutation(mutation, event);\n return Promise.resolve();\n }\n\n async lifecycle<K extends keyof PageHooks>(\n hookName: K,\n ...args: Parameters<PageHooks[K]>\n ): Promise<void> {\n const hook = this.#definition.hooks?.[hookName] as\n ((...hookArgs: Parameters<PageHooks[K]>) => unknown) | undefined;\n // Hooks run one at a time, in call order; a failed hook does not block the next one.\n const run = this.#previousHook.then(() => hook?.call(this.context, ...args));\n this.#previousHook = run.catch(() => {});\n await run;\n }\n\n close(): void {\n this.#closed = true;\n for (const request of this.#calls) {\n request.cancel();\n }\n this.#calls.clear();\n }\n\n #track(request: DashRequest<unknown>): void {\n if (this.#closed) {\n return;\n }\n this.#calls.add(request);\n const forget = () => this.#calls.delete(request);\n void request.then(forget, forget);\n }\n\n // Handlers called from other handlers may leave out the event, so it can be undefined here.\n #runMutation(mutation: Mutation<S>, event: DotEvent | undefined): void {\n this.setState((state) => mutation(state, event as DotEvent));\n }\n\n async #runAction(action: Action<S>, event: DotEvent | undefined): Promise<void> {\n await action.call(this.actionContext, this.#state, event as DotEvent);\n }\n\n #createNavigation(): PageNavigation {\n return {\n push: (page, options = {}) => this.#navigate({ action: 'push', page, options }),\n replace: (page, options = {}) => this.#navigate({ action: 'replace', page, options }),\n reset: (page, options = {}) => this.#navigate({ action: 'reset', page, options }),\n back: (delta = 1) => this.#navigate({ action: 'back', delta }),\n };\n }\n\n #navigate(navigation: Navigation): void {\n if (!this.#closed) {\n this.#host.navigate(this as Page, navigation);\n }\n }\n\n #freeze(state: State<S>): State<S> {\n return this.#host.strict ? deepFreeze(state) : state;\n }\n}\n\n// What hooks get as `this`. `setState` is bound, so a hook may destructure it.\nclass HookContext<S extends PageState> implements PageContext<S, Mutations<S>, Actions<S>> {\n readonly id: string;\n readonly instanceId: number;\n readonly mutations: MutationCalls<S>;\n readonly actions: ActionCalls<S>;\n readonly dash: Dash;\n readonly #page: Page<S>;\n\n constructor(page: Page<S>, mutations: MutationCalls<S>, actions: ActionCalls<S>) {\n this.id = page.id;\n this.instanceId = page.instanceId;\n this.dash = page.dash;\n this.mutations = mutations;\n this.actions = actions;\n this.#page = page;\n }\n\n get state(): State<S> {\n return this.#page.state;\n }\n\n readonly setState = (update: StateUpdate<S>): void => {\n this.#page.setState(update);\n };\n\n readonly setNavigationBar = (options: NavigationBarOptions): void => {\n this.#page.setNavigationBar(options);\n };\n\n readonly setTabBar = (options: TabBarOptions): void => {\n this.#page.setTabBar(options);\n };\n}\n\n// What actions get as `this`: the hook context plus navigation.\nclass HandlerContext<S extends PageState>\n extends HookContext<S>\n implements ActionContext<S, Mutations<S>, Actions<S>>\n{\n readonly navigation: PageNavigation;\n\n constructor(\n page: Page<S>,\n mutations: MutationCalls<S>,\n actions: ActionCalls<S>,\n navigation: PageNavigation,\n ) {\n super(page, mutations, actions);\n this.navigation = navigation;\n }\n}\n\nfunction mapNames<T>(\n record: Readonly<Record<string, unknown>>,\n create: (name: string) => T,\n): Record<string, T> {\n return Object.fromEntries(Object.keys(record).map((name) => [name, create(name)]));\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {\n Object.freeze(value);\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n }\n return value;\n}\n","import type {\n DotHooks,\n DotEvent,\n Envelope,\n HostCaller,\n HostLayerMessages,\n LogicLayerMessages,\n Navigation,\n PageConfigs,\n PageLaunchOptions,\n PageState,\n SystemContext,\n Theme,\n Transport,\n UILayerMessages,\n} from '@monimesl/dot-core';\nimport {\n DEFAULT_SYSTEM_CONTEXT,\n checkConfigs,\n connectHost,\n EventEmitter,\n MAX_STACK_DEPTH,\n MessageChannel,\n} from '@monimesl/dot-core';\n\nimport { HostCalls } from './host-calls';\nimport type { PageDefinition, RestoredPage } from './page';\nimport { Page } from './page';\n\nexport type ServiceChannel = MessageChannel<\n LogicLayerMessages,\n UILayerMessages & HostLayerMessages\n>;\n\nexport interface ServiceHooks {\n launch(): void;\n\n pageLoad(page: Page): void;\n\n pageUnload(page: Page): void;\n\n pageShow(page: Page): void;\n\n pageHide(page: Page): void;\n\n // A page asked to navigate; the request is on its way to the view layer.\n navigate(page: Page, navigation: Navigation): void;\n\n // Before a handler runs\n event(page: Page, handler: string, event: DotEvent): void;\n\n // After the state changes, with the changed keys, before they are sent to the view layer\n setState(page: Page, patch: PageState): void;\n\n // The instance is known when the error came from one page.\n error(error: unknown, instanceId: number | undefined): void;\n}\n\nexport interface ServicePlugin {\n readonly name: string;\n\n setup(service: Service): void;\n}\n\nexport interface DotContext {\n readonly theme: Theme;\n readonly systemContext: SystemContext;\n\n setTheme(theme: Theme): void;\n}\n\nexport interface DotDefinition {\n readonly theme?: Theme;\n readonly plugins?: readonly ServicePlugin[];\n readonly pages: readonly [string, ...string[]];\n // What Dash's bars show over every page; each page's configs override them.\n readonly configs?: PageConfigs;\n readonly hooks?: Partial<DotHooks> & ThisType<DotContext>;\n}\n\nexport function defineDot(dot: DotDefinition): DotDefinition {\n return dot;\n}\n\nexport interface ServiceOptions {\n transport: Transport;\n dot?: Partial<DotDefinition>;\n pages: Record<string, PageDefinition>;\n strict?: boolean;\n}\n\n// The logic layer: owns every page's state, runs handlers, and sends state changes to the view layer.\nexport class Service implements DotContext {\n readonly channel: ServiceChannel;\n readonly hooks = new EventEmitter<ServiceHooks>();\n #definitions: Record<string, PageDefinition>;\n readonly #pages = new Map<number, Page>();\n readonly #dot: Partial<DotDefinition>;\n readonly #strict: boolean;\n #theme: Theme;\n #systemContext: SystemContext = DEFAULT_SYSTEM_CONTEXT;\n #stack: HostLayerMessages['syncStack'] = { pages: [], limit: MAX_STACK_DEPTH };\n readonly #hostCalls: HostCalls;\n\n constructor({ transport, pages, dot = {}, strict = false }: ServiceOptions) {\n this.channel = new MessageChannel(transport);\n this.#hostCalls = new HostCalls(this.channel);\n this.#definitions = pages;\n this.#dot = dot;\n this.#strict = strict;\n this.#theme = dot.theme ?? {};\n checkConfigs(dot.configs ?? {});\n\n this.channel.handle('connect', () => this.channel.send('setTheme', { theme: this.#theme }));\n this.channel.handle('syncStack', (stack) => {\n this.#stack = stack;\n });\n this.channel.handle('syncSystemContext', (context) => {\n this.#systemContext = context;\n });\n this.channel.handle('load', ({ instanceId, page, options }) =>\n this.loadPage(instanceId, page, options),\n );\n this.channel.handle('unload', ({ instanceId }) => this.unloadPage(instanceId));\n this.channel.handle('show', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('pageShow', page);\n this.reportError(page.lifecycle('onShow'), instanceId);\n });\n this.channel.handle('hide', ({ instanceId }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('pageHide', page);\n this.reportError(page.lifecycle('onHide'), instanceId);\n });\n this.channel.handle('event', ({ instanceId, handler, event }) => {\n const page = this.getPage(instanceId);\n this.hooks.emit('event', page, handler, event);\n this.reportError(page.dispatch(handler, event), instanceId);\n });\n\n dot.plugins?.forEach((plugin) => this.use(plugin));\n }\n\n use(plugin: ServicePlugin): this {\n plugin.setup(this);\n return this;\n }\n\n start(): () => void {\n const stopListening = this.channel.listen((error, message) => {\n this.#reportError(error, instanceIdOf(message));\n });\n connectHost(this.callHost);\n this.hooks.emit('launch');\n this.reportError(this.#runLaunchHook());\n return () => {\n stopListening();\n connectHost(null);\n };\n }\n\n get theme(): Theme {\n return this.#theme;\n }\n\n get systemContext(): SystemContext {\n return this.#systemContext;\n }\n\n setTheme(theme: Theme): void {\n this.#theme = theme;\n this.channel.send('setTheme', { theme });\n }\n\n getPage(instanceId: number): Page {\n const page = this.#pages.get(instanceId);\n if (!page) {\n throw new Error(`Page instance ${instanceId} is not loaded`);\n }\n return page;\n }\n\n // Runs a host call, such as `dash.ui.alert`, through this service's channel.\n readonly callHost: HostCaller = (method, params, options) =>\n this.#hostCalls.call(method, params, options);\n\n loadPage(instanceId: number, id: string, options: PageLaunchOptions = {}): Page {\n const page = this.#createPage(instanceId, id);\n this.channel.send('initPageState', { instanceId, state: page.state });\n this.#sendBars(page);\n this.hooks.emit('pageLoad', page);\n this.reportError(page.lifecycle('onLoad', options), instanceId);\n return page;\n }\n\n // Swaps in a newer build's pages, as a hot reload does; a page restarts only if its initialState changed.\n replacePages(definitions: Record<string, PageDefinition>): void {\n const previous = this.#definitions;\n this.#definitions = definitions;\n for (const [instanceId, page] of this.#pages) {\n const definition = definitions[page.id];\n // A page the new build no longer has keeps its old code until it closes.\n if (!definition) {\n continue;\n }\n page.close();\n const initialStateChanged =\n JSON.stringify(previous[page.id]?.initialState) !== JSON.stringify(definition.initialState);\n if (initialStateChanged) {\n // What the page was opened with, for its onLoad to run again.\n const options = this.#stack.pages.find((entry) => entry.instanceId === instanceId)?.options;\n this.loadPage(instanceId, page.id, options);\n } else {\n const kept = this.#createPage(instanceId, page.id, {\n state: page.state,\n barChanges: page.barChanges,\n });\n // The new definePage may set other configs.\n this.#sendBars(kept);\n }\n }\n }\n\n #createPage(instanceId: number, id: string, restored?: RestoredPage<PageState>): Page {\n const page = new Page(\n instanceId,\n id,\n this.#definition(id),\n {\n commit: (page, patch) => {\n this.hooks.emit('setState', page, patch);\n this.channel.send('setPageState', { instanceId: page.instanceId, patchState: patch });\n },\n showBars: (page) => this.#sendBars(page),\n dotConfigs: this.#dot.configs,\n navigate: (page, navigation) => {\n if (navigation.action !== 'back') {\n this.#definition(navigation.page);\n }\n const { pages, limit } = this.#stack;\n if (navigation.action === 'push' && pages.length >= limit) {\n throw new Error(\n `Can't open \"${navigation.page}\": ${limit} pages are already open. Use replace or back.`,\n );\n }\n this.hooks.emit('navigate', page, navigation);\n this.channel.send('navigate', { ...navigation, from: page.instanceId });\n },\n strict: this.#strict,\n },\n restored,\n );\n this.#pages.set(instanceId, page);\n return page;\n }\n\n #sendBars(page: Page): void {\n this.channel.send('pageBars', { instanceId: page.instanceId, ...page.bars });\n }\n\n unloadPage(instanceId: number): void {\n const page = this.#pages.get(instanceId);\n if (!page) {\n return;\n }\n this.#pages.delete(instanceId);\n page.close();\n this.reportError(page.lifecycle('onUnload'), instanceId);\n this.hooks.emit('pageUnload', page);\n }\n\n #definition(id: string): PageDefinition {\n const definition = this.#definitions[id];\n if (!definition) {\n throw new Error(`Unknown page \"${id}\"`);\n }\n return definition;\n }\n\n reportError(work: Promise<void>, instanceId?: number): void {\n work.catch((error: unknown) => {\n this.#reportError(error, instanceId);\n });\n }\n\n async #runLaunchHook(): Promise<void> {\n await this.#dot.hooks?.onLaunch?.call(this);\n }\n\n #reportError(error: unknown, instanceId: number | undefined): void {\n this.hooks.emit('error', error, instanceId);\n const reason = error instanceof Error ? error : new Error(String(error));\n this.#dot.hooks?.onError?.call(this, reason);\n const report: LogicLayerMessages['error'] = { message: reason.message };\n if (instanceId !== undefined) {\n report.instanceId = instanceId;\n }\n this.channel.send('error', report);\n }\n}\n\nfunction instanceIdOf(message: Envelope): number | undefined {\n const payload = message.payload as { instanceId?: unknown } | undefined;\n if (typeof payload?.instanceId === 'number') {\n return payload.instanceId;\n }\n return undefined;\n}\n"],"mappings":"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"}