@api-client/ui 0.5.31 → 0.5.32

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.
@@ -1,10 +1,11 @@
1
1
  import type { Application } from '../Application.js';
2
2
  import type { Activity } from '../Activity.js';
3
- import { Renderer, renderFn } from './Renderer.js';
3
+ import { Renderer } from './Renderer.js';
4
4
  export declare class ApplicationRenderer extends Renderer {
5
5
  protected app: Application;
6
6
  renderedFirst: WeakMap<Application | Activity, boolean>;
7
7
  constructor(app: Application);
8
- [renderFn](): void;
8
+ private _handleFirstRender;
9
+ protected renderer(): void;
9
10
  }
10
11
  //# sourceMappingURL=ApplicationRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationRenderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/ApplicationRenderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGlD,qBAAa,mBAAoB,SAAQ,QAAQ;IAGnC,SAAS,CAAC,GAAG,EAAE,WAAW;IAFtC,aAAa,EAAE,OAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAA;gBAEjC,GAAG,EAAE,WAAW;IAKtC,CAAC,QAAQ,CAAC,IAAI,IAAI;CA0CnB"}
1
+ {"version":3,"file":"ApplicationRenderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/ApplicationRenderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,qBAAa,mBAAoB,SAAQ,QAAQ;IAGnC,SAAS,CAAC,GAAG,EAAE,WAAW;IAFtC,aAAa,EAAE,OAAO,CAAC,WAAW,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAA;gBAEjC,GAAG,EAAE,WAAW;IAKtC,OAAO,CAAC,kBAAkB;IAU1B,SAAS,CAAC,QAAQ,IAAI,IAAI;CA4B3B"}
@@ -1,5 +1,4 @@
1
- import { render } from 'lit';
2
- import { Renderer, renderFn } from './Renderer.js';
1
+ import { Renderer } from './Renderer.js';
3
2
  import { ModalActivity } from '../ModalActivity.js';
4
3
  export class ApplicationRenderer extends Renderer {
5
4
  app;
@@ -9,48 +8,40 @@ export class ApplicationRenderer extends Renderer {
9
8
  this.app = app;
10
9
  this.renderedFirst = new WeakMap();
11
10
  }
12
- [renderFn]() {
13
- const { app, renderRoot } = this;
14
- let host;
15
- let root = renderRoot;
16
- let content;
17
- const activity = app.manager.getTopActivity();
18
- if (activity) {
19
- host = activity;
20
- if (activity.renderRoot) {
21
- root = activity.renderRoot;
22
- }
23
- if (activity instanceof ModalActivity) {
24
- content = activity.renderDialog();
25
- }
26
- else {
27
- content = activity.render();
11
+ _handleFirstRender(target, clearRoot, root) {
12
+ if (!this.renderedFirst.has(target)) {
13
+ this.renderedFirst.set(target, true);
14
+ if (clearRoot) {
15
+ this._clearRoot(root);
28
16
  }
17
+ queueMicrotask(() => target.onFirstRender());
18
+ }
19
+ }
20
+ renderer() {
21
+ const { app, renderRoot: defaultRenderRoot } = this;
22
+ const activity = app.manager.getTopActivity();
23
+ const host = activity || app;
24
+ const root = activity?.renderRoot || defaultRenderRoot;
25
+ let content;
26
+ if (activity instanceof ModalActivity) {
27
+ content = activity.renderDialog();
29
28
  }
30
29
  else {
31
- host = app;
32
- content = app.render();
30
+ content = host.render();
33
31
  }
34
32
  if (!root) {
35
33
  // eslint-disable-next-line no-console
36
34
  console.warn(`The "renderRoot" is not set up.`);
37
35
  return;
38
36
  }
39
- if (!this.renderedFirst.has(app)) {
40
- this.renderedFirst.set(app, true);
41
- Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node));
42
- setTimeout(() => app.onFirstRender());
43
- }
44
- // host might !== app
45
- if (!this.renderedFirst.has(host)) {
46
- setTimeout(() => host.onFirstRender());
37
+ this._handleFirstRender(app, true, root);
38
+ if (!this.firstRendered) {
39
+ this.firstRenderedFlag = true;
47
40
  }
48
- const litPart = root;
49
- if (litPart._$litPart$ && litPart._$litPart$.options) {
50
- litPart._$litPart$.options.host = host;
41
+ if (host !== app) {
42
+ this._handleFirstRender(host, false, root);
51
43
  }
52
- render(content, root, { host });
53
- this.resolveUpdatePromise();
44
+ this._render(content, root, host);
54
45
  this.app.onRendered();
55
46
  }
56
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ApplicationRenderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/ApplicationRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAsC,MAAM,KAAK,CAAA;AAGzE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IAGzB;IAFtB,aAAa,CAA0C;IAEvD,YAAsB,GAAgB;QACpC,KAAK,EAAE,CAAA;QADa,QAAG,GAAH,GAAG,CAAa;QAEpC,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,EAAE,CAAA;IACpC,CAAC;IAED,CAAC,QAAQ,CAAC;QACR,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,CAAA;QAChC,IAAI,IAA4B,CAAA;QAChC,IAAI,IAAI,GAAuB,UAAU,CAAA;QACzC,IAAI,OAAwC,CAAA;QAC5C,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,CAAA;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,GAAG,QAAQ,CAAA;YACf,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;gBACxB,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAA;YAC5B,CAAC;YACD,IAAI,QAAQ,YAAY,aAAa,EAAE,CAAC;gBACtC,OAAO,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAA;YACnC,CAAC;iBAAM,CAAC;gBACN,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAA;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,GAAG,CAAA;YACV,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;QACxB,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAA;YAC/C,OAAM;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACjC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;YACjF,UAAU,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAA;QACvC,CAAC;QACD,qBAAqB;QACrB,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAA;QACxC,CAAC;QACD,MAAM,OAAO,GAAG,IAA+C,CAAA;QAC/D,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACrD,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;QACxC,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,oBAAoB,EAAE,CAAA;QAC3B,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAA;IACvB,CAAC;CACF","sourcesContent":["import { nothing, render, type RootPart, type TemplateResult } from 'lit'\nimport type { Application } from '../Application.js'\nimport type { Activity } from '../Activity.js'\nimport { Renderer, renderFn } from './Renderer.js'\nimport { ModalActivity } from '../ModalActivity.js'\n\nexport class ApplicationRenderer extends Renderer {\n renderedFirst: WeakMap<Application | Activity, boolean>\n\n constructor(protected app: Application) {\n super()\n this.renderedFirst = new WeakMap()\n }\n\n [renderFn](): void {\n const { app, renderRoot } = this\n let host: Application | Activity\n let root: HTMLElement | null = renderRoot\n let content: TemplateResult | typeof nothing\n const activity = app.manager.getTopActivity()\n if (activity) {\n host = activity\n if (activity.renderRoot) {\n root = activity.renderRoot\n }\n if (activity instanceof ModalActivity) {\n content = activity.renderDialog()\n } else {\n content = activity.render()\n }\n } else {\n host = app\n content = app.render()\n }\n if (!root) {\n // eslint-disable-next-line no-console\n console.warn(`The \"renderRoot\" is not set up.`)\n return\n }\n if (!this.renderedFirst.has(app)) {\n this.renderedFirst.set(app, true)\n Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node))\n setTimeout(() => app.onFirstRender())\n }\n // host might !== app\n if (!this.renderedFirst.has(host)) {\n setTimeout(() => host.onFirstRender())\n }\n const litPart = root as HTMLElement & { _$litPart$?: RootPart }\n if (litPart._$litPart$ && litPart._$litPart$.options) {\n litPart._$litPart$.options.host = host\n }\n render(content, root, { host })\n this.resolveUpdatePromise()\n this.app.onRendered()\n }\n}\n"]}
1
+ {"version":3,"file":"ApplicationRenderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/ApplicationRenderer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,OAAO,mBAAoB,SAAQ,QAAQ;IAGzB;IAFtB,aAAa,CAA0C;IAEvD,YAAsB,GAAgB;QACpC,KAAK,EAAE,CAAA;QADa,QAAG,GAAH,GAAG,CAAa;QAEpC,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,EAAE,CAAA;IACpC,CAAC;IAEO,kBAAkB,CAAC,MAA8B,EAAE,SAAkB,EAAE,IAAiB;QAC9F,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACpC,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACvB,CAAC;YACD,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC;IAES,QAAQ;QAChB,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,IAAI,CAAA;QACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,CAAA;QAE7C,MAAM,IAAI,GAA2B,QAAQ,IAAI,GAAG,CAAA;QACpD,MAAM,IAAI,GAAuB,QAAQ,EAAE,UAAU,IAAI,iBAAiB,CAAA;QAE1E,IAAI,OAAwC,CAAA;QAC5C,IAAI,QAAQ,YAAY,aAAa,EAAE,CAAC;YACtC,OAAO,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAA;QACnC,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;QACzB,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAA;YAC/C,OAAM;QACR,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACxC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAA;QAC/B,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAA;IACvB,CAAC;CACF","sourcesContent":["import { nothing, type TemplateResult } from 'lit'\nimport type { Application } from '../Application.js'\nimport type { Activity } from '../Activity.js'\nimport { Renderer } from './Renderer.js'\nimport { ModalActivity } from '../ModalActivity.js'\n\nexport class ApplicationRenderer extends Renderer {\n renderedFirst: WeakMap<Application | Activity, boolean>\n\n constructor(protected app: Application) {\n super()\n this.renderedFirst = new WeakMap()\n }\n\n private _handleFirstRender(target: Application | Activity, clearRoot: boolean, root: HTMLElement): void {\n if (!this.renderedFirst.has(target)) {\n this.renderedFirst.set(target, true)\n if (clearRoot) {\n this._clearRoot(root)\n }\n queueMicrotask(() => target.onFirstRender())\n }\n }\n\n protected renderer(): void {\n const { app, renderRoot: defaultRenderRoot } = this\n const activity = app.manager.getTopActivity()\n\n const host: Application | Activity = activity || app\n const root: HTMLElement | null = activity?.renderRoot || defaultRenderRoot\n\n let content: TemplateResult | typeof nothing\n if (activity instanceof ModalActivity) {\n content = activity.renderDialog()\n } else {\n content = host.render()\n }\n if (!root) {\n // eslint-disable-next-line no-console\n console.warn(`The \"renderRoot\" is not set up.`)\n return\n }\n this._handleFirstRender(app, true, root)\n if (!this.firstRendered) {\n this.firstRenderedFlag = true\n }\n if (host !== app) {\n this._handleFirstRender(host, false, root)\n }\n this._render(content, root, host)\n this.app.onRendered()\n }\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import type { Fragment } from '../Fragment.js';
2
- import { Renderer, renderFn } from './Renderer.js';
2
+ import { Renderer } from './Renderer.js';
3
3
  export declare class FragmentRenderer extends Renderer {
4
4
  protected fragment: Fragment;
5
5
  constructor(fragment: Fragment);
6
- [renderFn](): void;
6
+ protected renderer(): void;
7
7
  }
8
8
  //# sourceMappingURL=FragmentRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FragmentRenderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/FragmentRenderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAElD,qBAAa,gBAAiB,SAAQ,QAAQ;IAChC,SAAS,CAAC,QAAQ,EAAE,QAAQ;gBAAlB,QAAQ,EAAE,QAAQ;IAIxC,CAAC,QAAQ,CAAC,IAAI,IAAI;CAuBnB"}
1
+ {"version":3,"file":"FragmentRenderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/FragmentRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,qBAAa,gBAAiB,SAAQ,QAAQ;IAChC,SAAS,CAAC,QAAQ,EAAE,QAAQ;gBAAlB,QAAQ,EAAE,QAAQ;IAIxC,SAAS,CAAC,QAAQ,IAAI,IAAI;CAe3B"}
@@ -1,34 +1,24 @@
1
- import { render } from 'lit';
2
- import { Renderer, renderFn } from './Renderer.js';
1
+ import { Renderer } from './Renderer.js';
3
2
  export class FragmentRenderer extends Renderer {
4
3
  fragment;
5
4
  constructor(fragment) {
6
5
  super();
7
6
  this.fragment = fragment;
8
7
  }
9
- [renderFn]() {
8
+ renderer() {
10
9
  const root = this.renderRoot;
11
10
  if (!root) {
12
11
  // return quietly. Fragments can be rendered directly.
13
12
  return;
14
13
  }
15
- const fragment = this.fragment;
16
- const host = fragment.getSingleVisibleFragment() || fragment;
14
+ const host = this.fragment.getSingleVisibleFragment() ?? this.fragment;
17
15
  if (!this.firstRendered) {
18
16
  this.firstRenderedFlag = true;
19
17
  // cleanup any pre-existing content.
20
- Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node));
21
- setTimeout(() => host.onFirstRender());
18
+ this._clearRoot(root);
19
+ queueMicrotask(() => host.onFirstRender());
22
20
  }
23
- const litPart = root;
24
- if (litPart._$litPart$) {
25
- ;
26
- litPart._$litPart$.options.host = host;
27
- }
28
- // console.log(host, root)
29
- render(host.render(), root, { host });
30
- this.resolveUpdatePromise();
31
- // this.app.onRendered()
21
+ this._render(host.render(), root, host);
32
22
  }
33
23
  }
34
24
  //# sourceMappingURL=FragmentRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FragmentRenderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/FragmentRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA2B,MAAM,KAAK,CAAA;AAErD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAElD,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;IACtB;IAAtB,YAAsB,QAAkB;QACtC,KAAK,EAAE,CAAA;QADa,aAAQ,GAAR,QAAQ,CAAU;IAExC,CAAC;IAED,CAAC,QAAQ,CAAC;QACR,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAA;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,sDAAsD;YACtD,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,wBAAwB,EAAE,IAAI,QAAQ,CAAA;QAC5D,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAA;YAC7B,oCAAoC;YACpC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;YACjF,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAA;QACxC,CAAC;QACD,MAAM,OAAO,GAAG,IAA0D,CAAA;QAC1E,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,CAAC;YAAC,OAAO,CAAC,UAAU,CAAC,OAAyB,CAAC,IAAI,GAAG,IAAI,CAAA;QAC5D,CAAC;QACD,0BAA0B;QAC1B,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,oBAAoB,EAAE,CAAA;QAC3B,wBAAwB;IAC1B,CAAC;CACF","sourcesContent":["import { render, RenderOptions, RootPart } from 'lit'\nimport type { Fragment } from '../Fragment.js'\nimport { Renderer, renderFn } from './Renderer.js'\n\nexport class FragmentRenderer extends Renderer {\n constructor(protected fragment: Fragment) {\n super()\n }\n\n [renderFn](): void {\n const root = this.renderRoot\n if (!root) {\n // return quietly. Fragments can be rendered directly.\n return\n }\n const fragment = this.fragment\n const host = fragment.getSingleVisibleFragment() || fragment\n if (!this.firstRendered) {\n this.firstRenderedFlag = true\n // cleanup any pre-existing content.\n Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node))\n setTimeout(() => host.onFirstRender())\n }\n const litPart = root as unknown as HTMLElement & { _$litPart$?: RootPart }\n if (litPart._$litPart$) {\n ;(litPart._$litPart$.options as RenderOptions).host = host\n }\n // console.log(host, root)\n render(host.render(), root, { host })\n this.resolveUpdatePromise()\n // this.app.onRendered()\n }\n}\n"]}
1
+ {"version":3,"file":"FragmentRenderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/FragmentRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,MAAM,OAAO,gBAAiB,SAAQ,QAAQ;IACtB;IAAtB,YAAsB,QAAkB;QACtC,KAAK,EAAE,CAAA;QADa,aAAQ,GAAR,QAAQ,CAAU;IAExC,CAAC;IAES,QAAQ;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAA;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,sDAAsD;YACtD,OAAM;QACR,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,wBAAwB,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAA;QACtE,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAA;YAC7B,oCAAoC;YACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YACrB,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAA;QAC5C,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACzC,CAAC;CACF","sourcesContent":["import type { Fragment } from '../Fragment.js'\nimport { Renderer } from './Renderer.js'\n\nexport class FragmentRenderer extends Renderer {\n constructor(protected fragment: Fragment) {\n super()\n }\n\n protected renderer(): void {\n const root = this.renderRoot\n if (!root) {\n // return quietly. Fragments can be rendered directly.\n return\n }\n const host = this.fragment.getSingleVisibleFragment() ?? this.fragment\n if (!this.firstRendered) {\n this.firstRenderedFlag = true\n // cleanup any pre-existing content.\n this._clearRoot(root)\n queueMicrotask(() => host.onFirstRender())\n }\n this._render(host.render(), root, host)\n }\n}\n"]}
@@ -1,8 +1,8 @@
1
+ import { type TemplateResult, type nothing } from 'lit';
1
2
  export declare enum RenderingState {
2
3
  Idle = 0,
3
4
  Rendering = 1
4
5
  }
5
- export declare const renderFn: unique symbol;
6
6
  /**
7
7
  * A class that manages rendering of the application screen.
8
8
  */
@@ -17,25 +17,54 @@ export declare abstract class Renderer {
17
17
  */
18
18
  get updateComplete(): Promise<void> | undefined;
19
19
  /**
20
- * Sets the root element where the application will be rendered.
21
- * @param renderRoot The root element or its selector.
20
+ * Sets the root element where the application or activity will be rendered.
21
+ *
22
+ * @param renderRoot - The root element or a CSS selector string for the element.
23
+ * If a string is provided, it will be queried from the document.
22
24
  */
23
25
  setRenderRoot(renderRoot: HTMLElement | string): void;
24
26
  /**
25
- * Requests an update of the application UI. This will call the `render()` method
26
- * and update the DOM.
27
+ * Requests an update of the UI. Schedules a render if one is not already pending.
28
+ *
29
+ * This will call the `renderer()` method and update the DOM in the next animation frame.
30
+ * Ensures only one render is scheduled at a time.
27
31
  */
28
32
  requestUpdate(): void;
29
33
  protected firstRenderedFlag: boolean;
30
34
  /**
31
- * Determines whether the initial render had run and the `onFirstRender()`
32
- * function was called.
35
+ * Indicates whether the initial render has occurred and `onFirstRender()` was called.
33
36
  */
34
37
  get firstRendered(): boolean;
35
38
  /**
36
- * Manages the rendering of the UI.
39
+ * Returns the current rendering state.
40
+ *
41
+ * @returns The current rendering state.
42
+ */
43
+ get state(): RenderingState;
44
+ /**
45
+ * Abstract method that must be implemented by subclasses to perform the actual rendering logic.
46
+ *
47
+ * Called by `requestUpdate()` when a render is scheduled.
48
+ */
49
+ protected abstract renderer(): void;
50
+ /**
51
+ * Resolves the update promise if one is pending, indicating that rendering has finished.
37
52
  */
38
- abstract [renderFn](): void;
39
53
  protected resolveUpdatePromise(): void;
54
+ /**
55
+ * Renders the given Lit template into the specified root element, setting the host context.
56
+ *
57
+ * @param content - The Lit template or `nothing` to render.
58
+ * @param root - The root HTMLElement to render into.
59
+ * @param host - The host object for Lit's context (usually the Application or Activity).
60
+ */
61
+ protected _render(content: TemplateResult | typeof nothing, root: HTMLElement, host: object): void;
62
+ /**
63
+ * Removes all child nodes from the given root element.
64
+ *
65
+ * Uses `replaceChildren()` for efficient DOM cleanup.
66
+ * @param root - The root HTMLElement to clear.
67
+ */
68
+ protected _clearRoot(root: HTMLElement): void;
40
69
  }
41
70
  //# sourceMappingURL=Renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/Renderer.ts"],"names":[],"mappings":"AAAA,oBAAY,cAAc;IACxB,IAAI,IAAA;IACJ,SAAS,IAAA;CACV;AAED,eAAO,MAAM,QAAQ,eAAqB,CAAA;AAE1C;;GAEG;AACH,8BAAsB,QAAQ;;IAC5B;;OAEG;IACH,UAAU,EAAE,WAAW,GAAG,IAAI,CAAO;IAErC;;OAEG;IACH,IAAI,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAE9C;IAED;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAQrD;;;OAGG;IACH,aAAa,IAAI,IAAI;IAerB,SAAS,CAAC,iBAAiB,UAAQ;IAEnC;;;OAGG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAsBD;;OAEG;IACH,QAAQ,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI;IAS3B,SAAS,CAAC,oBAAoB,IAAI,IAAI;CAUvC"}
1
+ {"version":3,"file":"Renderer.d.ts","sourceRoot":"","sources":["../../../../src/core/renderer/Renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,KAAK,CAAA;AAE9E,oBAAY,cAAc;IACxB,IAAI,IAAA;IACJ,SAAS,IAAA;CACV;AAED;;GAEG;AACH,8BAAsB,QAAQ;;IAC5B;;OAEG;IACH,UAAU,EAAE,WAAW,GAAG,IAAI,CAAO;IAErC;;OAEG;IACH,IAAI,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,CAE9C;IAED;;;;;OAKG;IACH,aAAa,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI;IAQrD;;;;;OAKG;IACH,aAAa,IAAI,IAAI;IAerB,SAAS,CAAC,iBAAiB,UAAQ;IAEnC;;OAEG;IACH,IAAI,aAAa,IAAI,OAAO,CAE3B;IAOD;;;;OAIG;IACH,IAAI,KAAK,IAAI,cAAc,CAE1B;IAiBD;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,QAAQ,IAAI,IAAI;IAanC;;OAEG;IACH,SAAS,CAAC,oBAAoB,IAAI,IAAI;IAWtC;;;;;;OAMG;IACH,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IASlG;;;;;OAKG;IACH,SAAS,CAAC,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;CAI9C"}
@@ -1,9 +1,9 @@
1
+ import { render } from 'lit';
1
2
  export var RenderingState;
2
3
  (function (RenderingState) {
3
4
  RenderingState[RenderingState["Idle"] = 0] = "Idle";
4
5
  RenderingState[RenderingState["Rendering"] = 1] = "Rendering";
5
6
  })(RenderingState || (RenderingState = {}));
6
- export const renderFn = Symbol('renderFn');
7
7
  /**
8
8
  * A class that manages rendering of the application screen.
9
9
  */
@@ -19,8 +19,10 @@ export class Renderer {
19
19
  return this.#updateComplete;
20
20
  }
21
21
  /**
22
- * Sets the root element where the application will be rendered.
23
- * @param renderRoot The root element or its selector.
22
+ * Sets the root element where the application or activity will be rendered.
23
+ *
24
+ * @param renderRoot - The root element or a CSS selector string for the element.
25
+ * If a string is provided, it will be queried from the document.
24
26
  */
25
27
  setRenderRoot(renderRoot) {
26
28
  if (typeof renderRoot === 'string') {
@@ -31,8 +33,10 @@ export class Renderer {
31
33
  }
32
34
  }
33
35
  /**
34
- * Requests an update of the application UI. This will call the `render()` method
35
- * and update the DOM.
36
+ * Requests an update of the UI. Schedules a render if one is not already pending.
37
+ *
38
+ * This will call the `renderer()` method and update the DOM in the next animation frame.
39
+ * Ensures only one render is scheduled at a time.
36
40
  */
37
41
  requestUpdate() {
38
42
  if (this.#renderingState !== RenderingState.Idle) {
@@ -44,14 +48,13 @@ export class Renderer {
44
48
  this.#setUpdatePromise();
45
49
  }
46
50
  requestAnimationFrame(() => {
47
- this[renderFn]();
51
+ this.renderer();
48
52
  this.#renderingState = RenderingState.Idle;
49
53
  });
50
54
  }
51
55
  firstRenderedFlag = false;
52
56
  /**
53
- * Determines whether the initial render had run and the `onFirstRender()`
54
- * function was called.
57
+ * Indicates whether the initial render has occurred and `onFirstRender()` was called.
55
58
  */
56
59
  get firstRendered() {
57
60
  return this.firstRenderedFlag;
@@ -60,6 +63,14 @@ export class Renderer {
60
63
  * A flag used to determine in which rendering state the UI is.
61
64
  */
62
65
  #renderingState = RenderingState.Idle;
66
+ /**
67
+ * Returns the current rendering state.
68
+ *
69
+ * @returns The current rendering state.
70
+ */
71
+ get state() {
72
+ return this.#renderingState;
73
+ }
63
74
  /**
64
75
  * A flag that helps to determine whether the `updateComplete` is setup.
65
76
  */
@@ -72,12 +83,19 @@ export class Renderer {
72
83
  * The resolver to call when the update completes.
73
84
  */
74
85
  #updateResolver;
86
+ /**
87
+ * Sets up the promise that will be resolved when the next render completes.
88
+ * Used for tracking asynchronous rendering.
89
+ */
75
90
  #setUpdatePromise() {
76
91
  this.#updateComplete = new Promise((resolve) => {
77
92
  this.#updateResolver = resolve;
78
93
  this.#hasPendingUpdatePromise = true;
79
94
  });
80
95
  }
96
+ /**
97
+ * Resolves the update promise if one is pending, indicating that rendering has finished.
98
+ */
81
99
  resolveUpdatePromise() {
82
100
  if (!this.#hasPendingUpdatePromise) {
83
101
  return;
@@ -88,5 +106,30 @@ export class Renderer {
88
106
  resolver();
89
107
  }
90
108
  }
109
+ /**
110
+ * Renders the given Lit template into the specified root element, setting the host context.
111
+ *
112
+ * @param content - The Lit template or `nothing` to render.
113
+ * @param root - The root HTMLElement to render into.
114
+ * @param host - The host object for Lit's context (usually the Application or Activity).
115
+ */
116
+ _render(content, root, host) {
117
+ const litPart = root;
118
+ if (litPart._$litPart$ && litPart._$litPart$.options) {
119
+ litPart._$litPart$.options.host = host;
120
+ }
121
+ render(content, root, { host });
122
+ this.resolveUpdatePromise();
123
+ }
124
+ /**
125
+ * Removes all child nodes from the given root element.
126
+ *
127
+ * Uses `replaceChildren()` for efficient DOM cleanup.
128
+ * @param root - The root HTMLElement to clear.
129
+ */
130
+ _clearRoot(root) {
131
+ // Using replaceChildren() is a modern and clean way to clear a node's children.
132
+ root.replaceChildren();
133
+ }
91
134
  }
92
135
  //# sourceMappingURL=Renderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Renderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/Renderer.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,mDAAI,CAAA;IACJ,6DAAS,CAAA;AACX,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;AAE1C;;GAEG;AACH,MAAM,OAAgB,QAAQ;IAC5B;;OAEG;IACH,UAAU,GAAuB,IAAI,CAAA;IAErC;;OAEG;IACH,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,eAAe,CAAA;IAC7B,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,UAAgC;QAC5C,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAc,UAAU,CAAC,CAAA;QACnE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC9B,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,aAAa;QACX,IAAI,IAAI,CAAC,eAAe,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;YACjD,kCAAkC;YAClC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC,SAAS,CAAA;QAC/C,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACnC,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC1B,CAAC;QACD,qBAAqB,CAAC,GAAG,EAAE;YACzB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAA;YAChB,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC,IAAI,CAAA;QAC5C,CAAC,CAAC,CAAA;IACJ,CAAC;IAES,iBAAiB,GAAG,KAAK,CAAA;IAEnC;;;OAGG;IACH,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,iBAAiB,CAAA;IAC/B,CAAC;IAED;;OAEG;IACH,eAAe,GAAG,cAAc,CAAC,IAAI,CAAA;IAErC;;OAEG;IACH,wBAAwB,GAAG,KAAK,CAAA;IAEhC;;OAEG;IACH,eAAe,CAAgB;IAE/B;;OAEG;IACH,eAAe,CAAc;IAO7B,iBAAiB;QACf,IAAI,CAAC,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnD,IAAI,CAAC,eAAe,GAAG,OAAO,CAAA;YAC9B,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAA;QACtC,CAAC,CAAC,CAAA;IACJ,CAAC;IAES,oBAAoB;QAC5B,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACnC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,wBAAwB,GAAG,KAAK,CAAA;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAA;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;CACF","sourcesContent":["export enum RenderingState {\n Idle,\n Rendering,\n}\n\nexport const renderFn = Symbol('renderFn')\n\n/**\n * A class that manages rendering of the application screen.\n */\nexport abstract class Renderer {\n /**\n * The root element where the activity or fragment will be rendered.\n */\n renderRoot: HTMLElement | null = null\n\n /**\n * @type A promise resolved when the render finished.\n */\n get updateComplete(): Promise<void> | undefined {\n return this.#updateComplete\n }\n\n /**\n * Sets the root element where the application will be rendered.\n * @param renderRoot The root element or its selector.\n */\n setRenderRoot(renderRoot: HTMLElement | string): void {\n if (typeof renderRoot === 'string') {\n this.renderRoot = document.querySelector<HTMLElement>(renderRoot)\n } else {\n this.renderRoot = renderRoot\n }\n }\n\n /**\n * Requests an update of the application UI. This will call the `render()` method\n * and update the DOM.\n */\n requestUpdate(): void {\n if (this.#renderingState !== RenderingState.Idle) {\n // An update is already scheduled.\n return\n }\n this.#renderingState = RenderingState.Rendering\n if (!this.#hasPendingUpdatePromise) {\n this.#setUpdatePromise()\n }\n requestAnimationFrame(() => {\n this[renderFn]()\n this.#renderingState = RenderingState.Idle\n })\n }\n\n protected firstRenderedFlag = false\n\n /**\n * Determines whether the initial render had run and the `onFirstRender()`\n * function was called.\n */\n get firstRendered(): boolean {\n return this.firstRenderedFlag\n }\n\n /**\n * A flag used to determine in which rendering state the UI is.\n */\n #renderingState = RenderingState.Idle\n\n /**\n * A flag that helps to determine whether the `updateComplete` is setup.\n */\n #hasPendingUpdatePromise = false\n\n /**\n * A hidden value for the `updateComplete` getter.\n */\n #updateComplete?: Promise<void>\n\n /**\n * The resolver to call when the update completes.\n */\n #updateResolver?: () => void;\n\n /**\n * Manages the rendering of the UI.\n */\n abstract [renderFn](): void\n\n #setUpdatePromise(): void {\n this.#updateComplete = new Promise<void>((resolve) => {\n this.#updateResolver = resolve\n this.#hasPendingUpdatePromise = true\n })\n }\n\n protected resolveUpdatePromise(): void {\n if (!this.#hasPendingUpdatePromise) {\n return\n }\n this.#hasPendingUpdatePromise = false\n const resolver = this.#updateResolver\n if (resolver) {\n resolver()\n }\n }\n}\n"]}
1
+ {"version":3,"file":"Renderer.js","sourceRoot":"","sources":["../../../../src/core/renderer/Renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAoD,MAAM,KAAK,CAAA;AAE9E,MAAM,CAAN,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,mDAAI,CAAA;IACJ,6DAAS,CAAA;AACX,CAAC,EAHW,cAAc,KAAd,cAAc,QAGzB;AAED;;GAEG;AACH,MAAM,OAAgB,QAAQ;IAC5B;;OAEG;IACH,UAAU,GAAuB,IAAI,CAAA;IAErC;;OAEG;IACH,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,eAAe,CAAA;IAC7B,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,UAAgC;QAC5C,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAc,UAAU,CAAC,CAAA;QACnE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC9B,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,aAAa;QACX,IAAI,IAAI,CAAC,eAAe,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;YACjD,kCAAkC;YAClC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC,SAAS,CAAA;QAC/C,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACnC,IAAI,CAAC,iBAAiB,EAAE,CAAA;QAC1B,CAAC;QACD,qBAAqB,CAAC,GAAG,EAAE;YACzB,IAAI,CAAC,QAAQ,EAAE,CAAA;YACf,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC,IAAI,CAAA;QAC5C,CAAC,CAAC,CAAA;IACJ,CAAC;IAES,iBAAiB,GAAG,KAAK,CAAA;IAEnC;;OAEG;IACH,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,iBAAiB,CAAA;IAC/B,CAAC;IAED;;OAEG;IACH,eAAe,GAAG,cAAc,CAAC,IAAI,CAAA;IAErC;;;;OAIG;IACH,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,eAAe,CAAA;IAC7B,CAAC;IAED;;OAEG;IACH,wBAAwB,GAAG,KAAK,CAAA;IAEhC;;OAEG;IACH,eAAe,CAAgB;IAE/B;;OAEG;IACH,eAAe,CAAa;IAS5B;;;OAGG;IACH,iBAAiB;QACf,IAAI,CAAC,eAAe,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnD,IAAI,CAAC,eAAe,GAAG,OAAO,CAAA;YAC9B,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAA;QACtC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACO,oBAAoB;QAC5B,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACnC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,wBAAwB,GAAG,KAAK,CAAA;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAA;QACrC,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACO,OAAO,CAAC,OAAwC,EAAE,IAAiB,EAAE,IAAY;QACzF,MAAM,OAAO,GAAG,IAA+C,CAAA;QAC/D,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YACrD,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;QACxC,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/B,IAAI,CAAC,oBAAoB,EAAE,CAAA;IAC7B,CAAC;IAED;;;;;OAKG;IACO,UAAU,CAAC,IAAiB;QACpC,gFAAgF;QAChF,IAAI,CAAC,eAAe,EAAE,CAAA;IACxB,CAAC;CACF","sourcesContent":["import { render, type RootPart, type TemplateResult, type nothing } from 'lit'\n\nexport enum RenderingState {\n Idle,\n Rendering,\n}\n\n/**\n * A class that manages rendering of the application screen.\n */\nexport abstract class Renderer {\n /**\n * The root element where the activity or fragment will be rendered.\n */\n renderRoot: HTMLElement | null = null\n\n /**\n * @type A promise resolved when the render finished.\n */\n get updateComplete(): Promise<void> | undefined {\n return this.#updateComplete\n }\n\n /**\n * Sets the root element where the application or activity will be rendered.\n *\n * @param renderRoot - The root element or a CSS selector string for the element.\n * If a string is provided, it will be queried from the document.\n */\n setRenderRoot(renderRoot: HTMLElement | string): void {\n if (typeof renderRoot === 'string') {\n this.renderRoot = document.querySelector<HTMLElement>(renderRoot)\n } else {\n this.renderRoot = renderRoot\n }\n }\n\n /**\n * Requests an update of the UI. Schedules a render if one is not already pending.\n *\n * This will call the `renderer()` method and update the DOM in the next animation frame.\n * Ensures only one render is scheduled at a time.\n */\n requestUpdate(): void {\n if (this.#renderingState !== RenderingState.Idle) {\n // An update is already scheduled.\n return\n }\n this.#renderingState = RenderingState.Rendering\n if (!this.#hasPendingUpdatePromise) {\n this.#setUpdatePromise()\n }\n requestAnimationFrame(() => {\n this.renderer()\n this.#renderingState = RenderingState.Idle\n })\n }\n\n protected firstRenderedFlag = false\n\n /**\n * Indicates whether the initial render has occurred and `onFirstRender()` was called.\n */\n get firstRendered(): boolean {\n return this.firstRenderedFlag\n }\n\n /**\n * A flag used to determine in which rendering state the UI is.\n */\n #renderingState = RenderingState.Idle\n\n /**\n * Returns the current rendering state.\n *\n * @returns The current rendering state.\n */\n get state(): RenderingState {\n return this.#renderingState\n }\n\n /**\n * A flag that helps to determine whether the `updateComplete` is setup.\n */\n #hasPendingUpdatePromise = false\n\n /**\n * A hidden value for the `updateComplete` getter.\n */\n #updateComplete?: Promise<void>\n\n /**\n * The resolver to call when the update completes.\n */\n #updateResolver?: () => void\n\n /**\n * Abstract method that must be implemented by subclasses to perform the actual rendering logic.\n *\n * Called by `requestUpdate()` when a render is scheduled.\n */\n protected abstract renderer(): void\n\n /**\n * Sets up the promise that will be resolved when the next render completes.\n * Used for tracking asynchronous rendering.\n */\n #setUpdatePromise(): void {\n this.#updateComplete = new Promise<void>((resolve) => {\n this.#updateResolver = resolve\n this.#hasPendingUpdatePromise = true\n })\n }\n\n /**\n * Resolves the update promise if one is pending, indicating that rendering has finished.\n */\n protected resolveUpdatePromise(): void {\n if (!this.#hasPendingUpdatePromise) {\n return\n }\n this.#hasPendingUpdatePromise = false\n const resolver = this.#updateResolver\n if (resolver) {\n resolver()\n }\n }\n\n /**\n * Renders the given Lit template into the specified root element, setting the host context.\n *\n * @param content - The Lit template or `nothing` to render.\n * @param root - The root HTMLElement to render into.\n * @param host - The host object for Lit's context (usually the Application or Activity).\n */\n protected _render(content: TemplateResult | typeof nothing, root: HTMLElement, host: object): void {\n const litPart = root as HTMLElement & { _$litPart$?: RootPart }\n if (litPart._$litPart$ && litPart._$litPart$.options) {\n litPart._$litPart$.options.host = host\n }\n render(content, root, { host })\n this.resolveUpdatePromise()\n }\n\n /**\n * Removes all child nodes from the given root element.\n *\n * Uses `replaceChildren()` for efficient DOM cleanup.\n * @param root - The root HTMLElement to clear.\n */\n protected _clearRoot(root: HTMLElement): void {\n // Using replaceChildren() is a modern and clean way to clear a node's children.\n root.replaceChildren()\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@api-client/ui",
3
- "version": "0.5.31",
3
+ "version": "0.5.32",
4
4
  "description": "Internal UI component library for the API Client ecosystem.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "build/src/index.js",
@@ -1,7 +1,7 @@
1
- import { nothing, render, type RootPart, type TemplateResult } from 'lit'
1
+ import { nothing, type TemplateResult } from 'lit'
2
2
  import type { Application } from '../Application.js'
3
3
  import type { Activity } from '../Activity.js'
4
- import { Renderer, renderFn } from './Renderer.js'
4
+ import { Renderer } from './Renderer.js'
5
5
  import { ModalActivity } from '../ModalActivity.js'
6
6
 
7
7
  export class ApplicationRenderer extends Renderer {
@@ -12,46 +12,42 @@ export class ApplicationRenderer extends Renderer {
12
12
  this.renderedFirst = new WeakMap()
13
13
  }
14
14
 
15
- [renderFn](): void {
16
- const { app, renderRoot } = this
17
- let host: Application | Activity
18
- let root: HTMLElement | null = renderRoot
19
- let content: TemplateResult | typeof nothing
20
- const activity = app.manager.getTopActivity()
21
- if (activity) {
22
- host = activity
23
- if (activity.renderRoot) {
24
- root = activity.renderRoot
25
- }
26
- if (activity instanceof ModalActivity) {
27
- content = activity.renderDialog()
28
- } else {
29
- content = activity.render()
15
+ private _handleFirstRender(target: Application | Activity, clearRoot: boolean, root: HTMLElement): void {
16
+ if (!this.renderedFirst.has(target)) {
17
+ this.renderedFirst.set(target, true)
18
+ if (clearRoot) {
19
+ this._clearRoot(root)
30
20
  }
21
+ queueMicrotask(() => target.onFirstRender())
22
+ }
23
+ }
24
+
25
+ protected renderer(): void {
26
+ const { app, renderRoot: defaultRenderRoot } = this
27
+ const activity = app.manager.getTopActivity()
28
+
29
+ const host: Application | Activity = activity || app
30
+ const root: HTMLElement | null = activity?.renderRoot || defaultRenderRoot
31
+
32
+ let content: TemplateResult | typeof nothing
33
+ if (activity instanceof ModalActivity) {
34
+ content = activity.renderDialog()
31
35
  } else {
32
- host = app
33
- content = app.render()
36
+ content = host.render()
34
37
  }
35
38
  if (!root) {
36
39
  // eslint-disable-next-line no-console
37
40
  console.warn(`The "renderRoot" is not set up.`)
38
41
  return
39
42
  }
40
- if (!this.renderedFirst.has(app)) {
41
- this.renderedFirst.set(app, true)
42
- Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node))
43
- setTimeout(() => app.onFirstRender())
44
- }
45
- // host might !== app
46
- if (!this.renderedFirst.has(host)) {
47
- setTimeout(() => host.onFirstRender())
43
+ this._handleFirstRender(app, true, root)
44
+ if (!this.firstRendered) {
45
+ this.firstRenderedFlag = true
48
46
  }
49
- const litPart = root as HTMLElement & { _$litPart$?: RootPart }
50
- if (litPart._$litPart$ && litPart._$litPart$.options) {
51
- litPart._$litPart$.options.host = host
47
+ if (host !== app) {
48
+ this._handleFirstRender(host, false, root)
52
49
  }
53
- render(content, root, { host })
54
- this.resolveUpdatePromise()
50
+ this._render(content, root, host)
55
51
  this.app.onRendered()
56
52
  }
57
53
  }
@@ -1,33 +1,24 @@
1
- import { render, RenderOptions, RootPart } from 'lit'
2
1
  import type { Fragment } from '../Fragment.js'
3
- import { Renderer, renderFn } from './Renderer.js'
2
+ import { Renderer } from './Renderer.js'
4
3
 
5
4
  export class FragmentRenderer extends Renderer {
6
5
  constructor(protected fragment: Fragment) {
7
6
  super()
8
7
  }
9
8
 
10
- [renderFn](): void {
9
+ protected renderer(): void {
11
10
  const root = this.renderRoot
12
11
  if (!root) {
13
12
  // return quietly. Fragments can be rendered directly.
14
13
  return
15
14
  }
16
- const fragment = this.fragment
17
- const host = fragment.getSingleVisibleFragment() || fragment
15
+ const host = this.fragment.getSingleVisibleFragment() ?? this.fragment
18
16
  if (!this.firstRendered) {
19
17
  this.firstRenderedFlag = true
20
18
  // cleanup any pre-existing content.
21
- Array.from(root.childNodes).forEach((node) => root.parentNode?.removeChild(node))
22
- setTimeout(() => host.onFirstRender())
19
+ this._clearRoot(root)
20
+ queueMicrotask(() => host.onFirstRender())
23
21
  }
24
- const litPart = root as unknown as HTMLElement & { _$litPart$?: RootPart }
25
- if (litPart._$litPart$) {
26
- ;(litPart._$litPart$.options as RenderOptions).host = host
27
- }
28
- // console.log(host, root)
29
- render(host.render(), root, { host })
30
- this.resolveUpdatePromise()
31
- // this.app.onRendered()
22
+ this._render(host.render(), root, host)
32
23
  }
33
24
  }
@@ -1,10 +1,10 @@
1
+ import { render, type RootPart, type TemplateResult, type nothing } from 'lit'
2
+
1
3
  export enum RenderingState {
2
4
  Idle,
3
5
  Rendering,
4
6
  }
5
7
 
6
- export const renderFn = Symbol('renderFn')
7
-
8
8
  /**
9
9
  * A class that manages rendering of the application screen.
10
10
  */
@@ -22,8 +22,10 @@ export abstract class Renderer {
22
22
  }
23
23
 
24
24
  /**
25
- * Sets the root element where the application will be rendered.
26
- * @param renderRoot The root element or its selector.
25
+ * Sets the root element where the application or activity will be rendered.
26
+ *
27
+ * @param renderRoot - The root element or a CSS selector string for the element.
28
+ * If a string is provided, it will be queried from the document.
27
29
  */
28
30
  setRenderRoot(renderRoot: HTMLElement | string): void {
29
31
  if (typeof renderRoot === 'string') {
@@ -34,8 +36,10 @@ export abstract class Renderer {
34
36
  }
35
37
 
36
38
  /**
37
- * Requests an update of the application UI. This will call the `render()` method
38
- * and update the DOM.
39
+ * Requests an update of the UI. Schedules a render if one is not already pending.
40
+ *
41
+ * This will call the `renderer()` method and update the DOM in the next animation frame.
42
+ * Ensures only one render is scheduled at a time.
39
43
  */
40
44
  requestUpdate(): void {
41
45
  if (this.#renderingState !== RenderingState.Idle) {
@@ -47,7 +51,7 @@ export abstract class Renderer {
47
51
  this.#setUpdatePromise()
48
52
  }
49
53
  requestAnimationFrame(() => {
50
- this[renderFn]()
54
+ this.renderer()
51
55
  this.#renderingState = RenderingState.Idle
52
56
  })
53
57
  }
@@ -55,8 +59,7 @@ export abstract class Renderer {
55
59
  protected firstRenderedFlag = false
56
60
 
57
61
  /**
58
- * Determines whether the initial render had run and the `onFirstRender()`
59
- * function was called.
62
+ * Indicates whether the initial render has occurred and `onFirstRender()` was called.
60
63
  */
61
64
  get firstRendered(): boolean {
62
65
  return this.firstRenderedFlag
@@ -67,6 +70,15 @@ export abstract class Renderer {
67
70
  */
68
71
  #renderingState = RenderingState.Idle
69
72
 
73
+ /**
74
+ * Returns the current rendering state.
75
+ *
76
+ * @returns The current rendering state.
77
+ */
78
+ get state(): RenderingState {
79
+ return this.#renderingState
80
+ }
81
+
70
82
  /**
71
83
  * A flag that helps to determine whether the `updateComplete` is setup.
72
84
  */
@@ -80,13 +92,19 @@ export abstract class Renderer {
80
92
  /**
81
93
  * The resolver to call when the update completes.
82
94
  */
83
- #updateResolver?: () => void;
95
+ #updateResolver?: () => void
84
96
 
85
97
  /**
86
- * Manages the rendering of the UI.
98
+ * Abstract method that must be implemented by subclasses to perform the actual rendering logic.
99
+ *
100
+ * Called by `requestUpdate()` when a render is scheduled.
87
101
  */
88
- abstract [renderFn](): void
102
+ protected abstract renderer(): void
89
103
 
104
+ /**
105
+ * Sets up the promise that will be resolved when the next render completes.
106
+ * Used for tracking asynchronous rendering.
107
+ */
90
108
  #setUpdatePromise(): void {
91
109
  this.#updateComplete = new Promise<void>((resolve) => {
92
110
  this.#updateResolver = resolve
@@ -94,6 +112,9 @@ export abstract class Renderer {
94
112
  })
95
113
  }
96
114
 
115
+ /**
116
+ * Resolves the update promise if one is pending, indicating that rendering has finished.
117
+ */
97
118
  protected resolveUpdatePromise(): void {
98
119
  if (!this.#hasPendingUpdatePromise) {
99
120
  return
@@ -104,4 +125,31 @@ export abstract class Renderer {
104
125
  resolver()
105
126
  }
106
127
  }
128
+
129
+ /**
130
+ * Renders the given Lit template into the specified root element, setting the host context.
131
+ *
132
+ * @param content - The Lit template or `nothing` to render.
133
+ * @param root - The root HTMLElement to render into.
134
+ * @param host - The host object for Lit's context (usually the Application or Activity).
135
+ */
136
+ protected _render(content: TemplateResult | typeof nothing, root: HTMLElement, host: object): void {
137
+ const litPart = root as HTMLElement & { _$litPart$?: RootPart }
138
+ if (litPart._$litPart$ && litPart._$litPart$.options) {
139
+ litPart._$litPart$.options.host = host
140
+ }
141
+ render(content, root, { host })
142
+ this.resolveUpdatePromise()
143
+ }
144
+
145
+ /**
146
+ * Removes all child nodes from the given root element.
147
+ *
148
+ * Uses `replaceChildren()` for efficient DOM cleanup.
149
+ * @param root - The root HTMLElement to clear.
150
+ */
151
+ protected _clearRoot(root: HTMLElement): void {
152
+ // Using replaceChildren() is a modern and clean way to clear a node's children.
153
+ root.replaceChildren()
154
+ }
107
155
  }
@@ -0,0 +1,113 @@
1
+ import { html } from 'lit'
2
+ import { assert, nextFrame } from '@open-wc/testing'
3
+ import sinon from 'sinon'
4
+ import { ApplicationRenderer } from '../../src/core/renderer/ApplicationRenderer.js'
5
+ import { type Application } from '../../src/core/Application.js'
6
+ import { FragmentRenderer } from '../../src/core/renderer/FragmentRenderer.js'
7
+ import { Renderer } from '../../src/core/renderer/Renderer.js'
8
+ import { type Fragment } from '../../src/core/Fragment.js'
9
+
10
+ describe('Renderer', () => {
11
+ class TestRenderer extends Renderer {
12
+ public renderCalled = false
13
+ public renderArgs: unknown[] = []
14
+ protected renderer(): void {
15
+ this.renderCalled = true
16
+ // eslint-disable-next-line prefer-rest-params
17
+ this.renderArgs = [...arguments]
18
+ this.resolveUpdatePromise()
19
+ }
20
+ }
21
+
22
+ it('should set renderRoot from HTMLElement', () => {
23
+ const el = document.createElement('div')
24
+ const renderer = new TestRenderer()
25
+ renderer.setRenderRoot(el)
26
+ assert.strictEqual(renderer.renderRoot, el)
27
+ })
28
+
29
+ it('should set renderRoot from selector', () => {
30
+ const el = document.createElement('div')
31
+ el.id = 'test-root'
32
+ document.body.appendChild(el)
33
+ const renderer = new TestRenderer()
34
+ renderer.setRenderRoot('#test-root')
35
+ assert.strictEqual(renderer.renderRoot, el)
36
+ document.body.removeChild(el)
37
+ })
38
+
39
+ it('should call renderer() on requestUpdate()', () => {
40
+ const renderer = new TestRenderer()
41
+ const spy = sinon.spy(renderer as unknown as { renderer: () => void }, 'renderer')
42
+ renderer.requestUpdate()
43
+ // Wait for animation frame
44
+ return new Promise<void>((resolve) => {
45
+ requestAnimationFrame(() => {
46
+ assert.isTrue(spy.calledOnce)
47
+ resolve()
48
+ })
49
+ })
50
+ })
51
+
52
+ it('should not schedule multiple renders if already rendering', async () => {
53
+ const renderer = new TestRenderer()
54
+ renderer.requestUpdate()
55
+ const spy = sinon.spy(renderer as unknown as { renderer: () => void }, 'renderer')
56
+ renderer.requestUpdate()
57
+ assert.isTrue(spy.notCalled)
58
+ })
59
+
60
+ it('should clear root with _clearRoot()', () => {
61
+ const el = document.createElement('div')
62
+ el.appendChild(document.createElement('span'))
63
+ const renderer = new TestRenderer()
64
+ renderer['_clearRoot'](el)
65
+ assert.strictEqual(el.childNodes.length, 0)
66
+ })
67
+
68
+ it('should resolve updateComplete after _render()', async () => {
69
+ const el = document.createElement('div')
70
+ const renderer = new TestRenderer()
71
+ renderer.setRenderRoot(el)
72
+ renderer.requestUpdate()
73
+ await renderer.updateComplete
74
+ assert.isFalse(renderer.firstRendered) // firstRenderedFlag is not set in TestRenderer
75
+ })
76
+ })
77
+
78
+ describe('ApplicationRenderer', () => {
79
+ it('should call _handleFirstRender and _render', async () => {
80
+ const app = {
81
+ onFirstRender: sinon.spy(),
82
+ onRendered: sinon.spy(),
83
+ manager: { getTopActivity: () => null },
84
+ render: () => html`<div>app</div>`,
85
+ }
86
+ const renderer = new ApplicationRenderer(app as unknown as Application)
87
+ const root = document.createElement('div')
88
+ renderer.setRenderRoot(root)
89
+ const spyRender = sinon.spy(renderer as unknown as { _render: () => void }, '_render')
90
+ renderer['renderer']()
91
+ await nextFrame()
92
+ assert.isTrue(app.onFirstRender.called, 'onFirstRender should be called')
93
+ assert.isTrue(app.onRendered.called, 'onRendered should be called')
94
+ assert.isTrue(spyRender.called, '_render should be called')
95
+ })
96
+ })
97
+
98
+ describe('FragmentRenderer', () => {
99
+ it('should call _clearRoot and _render on first render', async () => {
100
+ const fragment = {
101
+ getSingleVisibleFragment: () => null,
102
+ render: () => html`<div>fragment</div>`,
103
+ onFirstRender: sinon.spy(),
104
+ }
105
+ const renderer = new FragmentRenderer(fragment as unknown as Fragment)
106
+ const root = document.createElement('div')
107
+ renderer.setRenderRoot(root)
108
+ renderer['renderer']()
109
+ await nextFrame()
110
+ assert.isTrue(fragment.onFirstRender.called, 'onFirstRender should be called')
111
+ assert.strictEqual(root.childElementCount, 1, 'Root should have one child after rendering')
112
+ })
113
+ })