@reidelsaltres/pureper 0.1.49

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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/out/foundation/Fetcher.d.ts +6 -0
  4. package/out/foundation/Fetcher.d.ts.map +1 -0
  5. package/out/foundation/Fetcher.js +18 -0
  6. package/out/foundation/Fetcher.js.map +1 -0
  7. package/out/foundation/Theme.d.ts +9 -0
  8. package/out/foundation/Theme.d.ts.map +1 -0
  9. package/out/foundation/Theme.js +28 -0
  10. package/out/foundation/Theme.js.map +1 -0
  11. package/out/foundation/Triplet.d.ts +44 -0
  12. package/out/foundation/Triplet.d.ts.map +1 -0
  13. package/out/foundation/Triplet.js +174 -0
  14. package/out/foundation/Triplet.js.map +1 -0
  15. package/out/foundation/api/ElementHolder.d.ts +4 -0
  16. package/out/foundation/api/ElementHolder.d.ts.map +1 -0
  17. package/out/foundation/api/ElementHolder.js +2 -0
  18. package/out/foundation/api/ElementHolder.js.map +1 -0
  19. package/out/foundation/api/EmptyConstructor.d.ts +7 -0
  20. package/out/foundation/api/EmptyConstructor.d.ts.map +1 -0
  21. package/out/foundation/api/EmptyConstructor.js +2 -0
  22. package/out/foundation/api/EmptyConstructor.js.map +1 -0
  23. package/out/foundation/api/Lazy.d.ts +7 -0
  24. package/out/foundation/api/Lazy.d.ts.map +1 -0
  25. package/out/foundation/api/Lazy.js +12 -0
  26. package/out/foundation/api/Lazy.js.map +1 -0
  27. package/out/foundation/component_api/Component.d.ts +58 -0
  28. package/out/foundation/component_api/Component.d.ts.map +1 -0
  29. package/out/foundation/component_api/Component.js +61 -0
  30. package/out/foundation/component_api/Component.js.map +1 -0
  31. package/out/foundation/component_api/Page.d.ts +8 -0
  32. package/out/foundation/component_api/Page.d.ts.map +1 -0
  33. package/out/foundation/component_api/Page.js +8 -0
  34. package/out/foundation/component_api/Page.js.map +1 -0
  35. package/out/foundation/component_api/UniHtml.d.ts +43 -0
  36. package/out/foundation/component_api/UniHtml.d.ts.map +1 -0
  37. package/out/foundation/component_api/UniHtml.js +67 -0
  38. package/out/foundation/component_api/UniHtml.js.map +1 -0
  39. package/out/foundation/component_api/mixin/Proto.d.ts +33 -0
  40. package/out/foundation/component_api/mixin/Proto.d.ts.map +1 -0
  41. package/out/foundation/component_api/mixin/Proto.js +43 -0
  42. package/out/foundation/component_api/mixin/Proto.js.map +1 -0
  43. package/out/foundation/worker/Router.d.ts +31 -0
  44. package/out/foundation/worker/Router.d.ts.map +1 -0
  45. package/out/foundation/worker/Router.js +95 -0
  46. package/out/foundation/worker/Router.js.map +1 -0
  47. package/out/foundation/worker/ServiceWorker.d.ts +25 -0
  48. package/out/foundation/worker/ServiceWorker.d.ts.map +1 -0
  49. package/out/foundation/worker/ServiceWorker.js +141 -0
  50. package/out/foundation/worker/ServiceWorker.js.map +1 -0
  51. package/out/foundation/worker/api/Clients.d.ts +4 -0
  52. package/out/foundation/worker/api/Clients.d.ts.map +1 -0
  53. package/out/foundation/worker/api/Clients.js +2 -0
  54. package/out/foundation/worker/api/Clients.js.map +1 -0
  55. package/out/foundation/worker/api/ExtendableEvent.d.ts +4 -0
  56. package/out/foundation/worker/api/ExtendableEvent.d.ts.map +1 -0
  57. package/out/foundation/worker/api/ExtendableEvent.js +2 -0
  58. package/out/foundation/worker/api/ExtendableEvent.js.map +1 -0
  59. package/out/foundation/worker/api/ExtendableMessageEvent.d.ts +6 -0
  60. package/out/foundation/worker/api/ExtendableMessageEvent.d.ts.map +1 -0
  61. package/out/foundation/worker/api/ExtendableMessageEvent.js +2 -0
  62. package/out/foundation/worker/api/ExtendableMessageEvent.js.map +1 -0
  63. package/out/foundation/worker/api/FetchEvent.d.ts +6 -0
  64. package/out/foundation/worker/api/FetchEvent.d.ts.map +1 -0
  65. package/out/foundation/worker/api/FetchEvent.js +2 -0
  66. package/out/foundation/worker/api/FetchEvent.js.map +1 -0
  67. package/out/foundation/worker/api/ServiceWorkerGlobalScope.d.ts +14 -0
  68. package/out/foundation/worker/api/ServiceWorkerGlobalScope.d.ts.map +1 -0
  69. package/out/foundation/worker/api/ServiceWorkerGlobalScope.js +2 -0
  70. package/out/foundation/worker/api/ServiceWorkerGlobalScope.js.map +1 -0
  71. package/out/index.d.ts +13 -0
  72. package/out/index.d.ts.map +1 -0
  73. package/out/index.js +11 -0
  74. package/out/index.js.map +1 -0
  75. package/package.json +39 -0
  76. package/src/foundation/Fetcher.ts +20 -0
  77. package/src/foundation/Theme.ts +37 -0
  78. package/src/foundation/Triplet.ts +208 -0
  79. package/src/foundation/api/ElementHolder.ts +3 -0
  80. package/src/foundation/api/EmptyConstructor.ts +6 -0
  81. package/src/foundation/api/Lazy.ts +12 -0
  82. package/src/foundation/component_api/Component.ts +92 -0
  83. package/src/foundation/component_api/Page.ts +9 -0
  84. package/src/foundation/component_api/UniHtml.ts +82 -0
  85. package/src/foundation/component_api/mixin/Proto.ts +67 -0
  86. package/src/foundation/worker/Router.ts +124 -0
  87. package/src/foundation/worker/ServiceWorker.ts +160 -0
  88. package/src/foundation/worker/api/Clients.ts +3 -0
  89. package/src/foundation/worker/api/ExtendableEvent.ts +3 -0
  90. package/src/foundation/worker/api/ExtendableMessageEvent.ts +6 -0
  91. package/src/foundation/worker/api/FetchEvent.ts +6 -0
  92. package/src/foundation/worker/api/ServiceWorkerGlobalScope.ts +14 -0
  93. package/src/index.ts +22 -0
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@reidelsaltres/pureper",
3
+ "version": "0.1.49",
4
+ "description": "Minimal library extracted from the Pureper SPA foundation — utilities and base classes for components/pages.",
5
+ "type": "module",
6
+ "main": "out/src/index.js",
7
+ "module": "out/src/index.js",
8
+ "types": "out/src/index.d.ts",
9
+ "files": [
10
+ "out",
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "publish:npm": "npm publish --registry=https://registry.npmjs.org/",
17
+ "publish:gh": "npm publish",
18
+ "prepack": "tsc",
19
+ "types:check": "tsc --noEmit",
20
+ "build": "tsc -p tsconfig.json",
21
+ "prepare": "npm run build",
22
+ "test": "npx tsc -p tests/tsconfig.json"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/ReiDelsAltres/Pureper.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/ReiDelsAltres/Pureper/issues"
30
+ },
31
+ "homepage": "https://github.com/ReiDelsAltres/Pureper#readme",
32
+ "keywords": [
33
+ "spa",
34
+ "components",
35
+ "typescript"
36
+ ],
37
+ "author": "ReiDelsAltres",
38
+ "license": "MIT"
39
+ }
@@ -0,0 +1,20 @@
1
+ export default class Fetcher {
2
+ static async fetchText(url: string): Promise<string> {
3
+ const response = await this.internalFetch(url);
4
+
5
+ return await response.text();
6
+ }
7
+ static async fetchJSON(url: string): Promise<any> {
8
+ const response = await this.internalFetch(url);
9
+
10
+ return await response.json();
11
+ }
12
+
13
+ private static async internalFetch(url: string): Promise<Response> {
14
+ const response = await fetch(url, { cache: 'default' });
15
+ if (!response.ok) {
16
+ throw new Error(`HTTP error! status: ${response.status}`);
17
+ }
18
+ return response;
19
+ }
20
+ }
@@ -0,0 +1,37 @@
1
+ import Fetcher from "./Fetcher.js";
2
+
3
+ export type IconName = 'home' | 'user' | 'settings' | 'copy' | 'menu' | 'close' | 'arrow-left' |
4
+ 'arrow-right' | 'search' | 'heart' | 'star' | 'palette' | string;
5
+
6
+ export type SizeClass = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
7
+
8
+ export type ThemeColor = 'primary' | 'secondary' | 'tertiary' | 'additional' |
9
+ 'success' | 'warning' | 'error' | 'info' | 'text';
10
+
11
+ export let ACTIVE_THEME_KEY = "Empty";
12
+
13
+ export async function loadTheme(name: string) : Promise<string> {
14
+ return Fetcher.fetchText(`../../../resources/${name}.theme.css`);
15
+ }
16
+ export async function loadThemeAsInstant(name: string) : Promise<CSSStyleSheet> {
17
+ let theme: string = await loadTheme(name);
18
+ const sheet = new CSSStyleSheet();
19
+ sheet.replaceSync(theme);
20
+ return sheet;
21
+ }
22
+ export async function init() {
23
+ ACTIVE_THEME_KEY = localStorage.getItem("theme");
24
+ if (ACTIVE_THEME_KEY) {
25
+ await setTheme(ACTIVE_THEME_KEY);
26
+ } else {
27
+ await setTheme("Blazor");
28
+ }
29
+ }
30
+ export async function setTheme(name: string) {
31
+ let theme: string = await loadTheme(name);
32
+ theme = theme.replace(/\.[\w-]+-theme/g, ":root");
33
+ const sheet = new CSSStyleSheet();
34
+ sheet.replaceSync(theme);
35
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
36
+ }
37
+
@@ -0,0 +1,208 @@
1
+ import Fetcher from "./Fetcher.js";
2
+ import UniHtml from "./component_api/UniHtml.js";
3
+ import { Router } from "./worker/Router.js";
4
+ import ServiceWorker from "./worker/ServiceWorker.js";
5
+ import Page from "./component_api/Page.js";
6
+ import Component from "./component_api/Component.js";
7
+ import { AnyConstructor, Constructor } from "./component_api/mixin/Proto.js";
8
+ import "./UrlExtensions.js";
9
+
10
+ export default class Triplet<T extends UniHtml> implements ITriplet {
11
+ private uni?: AnyConstructor<UniHtml>;
12
+ private readonly access: AccessType;
13
+
14
+ public readonly html?: string;
15
+ public readonly css?: string;
16
+ public readonly js?: string;
17
+
18
+ public readonly additionalFiles: Map<string, string> = new Map();
19
+
20
+ public constructor(builder: TripletBuilder<T>) {
21
+ this.html = builder.html;
22
+ this.css = builder.css;
23
+ this.js = builder.js;
24
+
25
+ this.additionalFiles = builder.additionalFiles;
26
+
27
+ this.uni = builder.uni;;
28
+ this.access = builder.access;
29
+ }
30
+
31
+ public async init(): Promise<boolean> {
32
+ const isOnline: boolean = await ServiceWorker.isOnline();
33
+
34
+ if (this.access === AccessType.NONE) return false;
35
+ if (this.access === AccessType.BOTH) {
36
+ await this.cache();
37
+ return true
38
+ };
39
+ if (this.access === AccessType.OFFLINE && isOnline) return false;
40
+ if (this.access === AccessType.ONLINE && !isOnline) return false;
41
+
42
+ return true;
43
+ }
44
+
45
+ public async cache(): Promise<void> {
46
+ if (this.html)
47
+ await ServiceWorker.addToCache(this.html);
48
+ if (this.css)
49
+ await ServiceWorker.addToCache(this.css);
50
+ if (this.js)
51
+ await ServiceWorker.addToCache(this.js);
52
+ if (this.additionalFiles.size > 0) {
53
+ for (const [type, filePath] of this.additionalFiles) {
54
+ await ServiceWorker.addToCache(filePath);
55
+ }
56
+ }
57
+ }
58
+
59
+ private createLink(cssPath: string): HTMLLinkElement {
60
+ const link = document.createElement('link');
61
+ link.rel = 'stylesheet';
62
+ ///
63
+ link.href = cssPath;
64
+ ///
65
+ return link;
66
+ }
67
+
68
+
69
+ public async register(type: "router" | "markup", name: string): Promise<boolean> {
70
+ if (!this.uni) {
71
+ switch (type) {
72
+ case "router":
73
+ this.uni = Page;
74
+ break;
75
+ case "markup":
76
+ this.uni = Component;
77
+ break;
78
+ }
79
+ }
80
+
81
+ for (const [type, filePath] of this.additionalFiles) {
82
+ if (type !== 'light-dom') continue;
83
+ if (!filePath.endsWith(".css")) continue;
84
+
85
+ const link = this.createLink(filePath);
86
+ document.head.appendChild(link);
87
+
88
+ console.info(`[Triplet]: Additional light-dom CSS file '${filePath}' added to document head.`);
89
+ }
90
+
91
+ let ori = this.createInjectedClass(this.uni);
92
+
93
+ if (type === "router") {
94
+ var reg = Router.registerRoute(this.html!, name, (search) => {
95
+ const paramNames = (() => {
96
+ const ctor = this.uni.prototype.constructor;
97
+ const fnStr = ctor.toString();
98
+ const argsMatch = fnStr.match(/constructor\s*\(([^)]*)\)/);
99
+ if (!argsMatch) return [];
100
+ return argsMatch[1].split(',').map(s => s.trim()).filter(Boolean);
101
+ })();
102
+
103
+
104
+ const args = paramNames.map(name => {
105
+ const string = search?.get(name);
106
+
107
+ return search?.get(name)
108
+ });
109
+ const unn : UniHtml = new ori(...args);
110
+
111
+ return unn;
112
+ });
113
+
114
+ console.info(`[Triplet]` + `: Router route '${name}' registered for path '${this.html}' by class ${ori}.`);
115
+ return reg.then(() => true).catch(() => false);
116
+ } else if (type === "markup") {
117
+ if (customElements.get(name)) throw new Error(`Custom element '${name}' is already defined.`);
118
+ customElements.define(name, ori.prototype.constructor as CustomElementConstructor);
119
+
120
+ console.info(`[Triplet]: Custom element '${name}' defined.`);
121
+ return Promise.resolve(true);
122
+ }
123
+ return Promise.resolve(false);
124
+ }
125
+ private createInjectedClass(c: AnyConstructor<UniHtml>): any {
126
+ let that = this;
127
+ let ori = class extends c {
128
+ constructor(...args: any[]) {
129
+ super(...args);
130
+ }
131
+ };
132
+ let proto = ori.prototype as any;
133
+ proto._init = async function () {
134
+ ///
135
+ const fullPath = that.html!;
136
+ ///
137
+ return Fetcher.fetchText(fullPath);
138
+ }
139
+ proto._postInit = async function (preHtml: string): Promise<string> {
140
+ if (that.css) {
141
+ const link = that.createLink(that.css);
142
+ preHtml = link.outerHTML + "\n" + preHtml;
143
+ }
144
+ for (const [type, filePath] of that.additionalFiles) {
145
+ if (!filePath.endsWith(".css")) continue;
146
+ if (type !== 'light-dom') {
147
+ const link = that.createLink(filePath);
148
+ preHtml = link.outerHTML + "\n" + preHtml;
149
+ }
150
+ }
151
+ return preHtml;
152
+ }
153
+ return ori;
154
+ }
155
+
156
+ }
157
+
158
+ interface ITriplet {
159
+ readonly html?: string;
160
+ readonly css?: string;
161
+ readonly js?: string;
162
+ }
163
+
164
+ export enum AccessType {
165
+ NONE = 0,
166
+ OFFLINE = 1 << 0,
167
+ ONLINE = 1 << 1,
168
+ BOTH = OFFLINE | ONLINE
169
+ }
170
+
171
+ export class TripletBuilder<T extends UniHtml> implements ITriplet {
172
+ public uni?: AnyConstructor<UniHtml>;
173
+ public access: AccessType = AccessType.BOTH;
174
+
175
+ public readonly additionalFiles: Map<string, string> = new Map();
176
+
177
+ private constructor(
178
+ public readonly html?: string,
179
+ public readonly css?: string,
180
+ public readonly js?: string
181
+ ) { }
182
+
183
+ public static create<T extends UniHtml>(html?: string, css?: string, js?: string): TripletBuilder<T> {
184
+ let urlHtml: URL = html ? new URL(html, window.location.origin) : null;
185
+ let urlCss: URL = css ? new URL(css, window.location.origin) : null;
186
+ let urlJs: URL = js ? new URL(js, window.location.origin) : null;
187
+
188
+ return new TripletBuilder(urlHtml?.href, urlCss?.href, urlJs?.href);
189
+ }
190
+
191
+ public withUni(cls: AnyConstructor<UniHtml>): TripletBuilder<T> {
192
+ this.uni = cls;
193
+ return this;
194
+ }
195
+ public withAccess(access: AccessType): TripletBuilder<T> {
196
+ this.access = access;
197
+ return this;
198
+ }
199
+ public withLightDOMCss(css: string): TripletBuilder<T> {
200
+ let urlCss: URL = css ? new URL(css, window.location.origin) : null;
201
+ this.additionalFiles.set('light-dom', urlCss?.href);
202
+ return this;
203
+ }
204
+
205
+ public build(): Triplet<T> {
206
+ return new Triplet<T>(this);
207
+ }
208
+ }
@@ -0,0 +1,3 @@
1
+ export default interface IElementHolder {
2
+ readonly element: HTMLElement;
3
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Helper interface for factory methods (empty constructor signature).
3
+ */
4
+ export default interface EmptyConstructor<T> {
5
+ new(): T;
6
+ }
@@ -0,0 +1,12 @@
1
+ export default class Lazy<T, Args extends any[] = []> {
2
+ private _value?: T;
3
+
4
+ constructor(private readonly factory: (...args: Args) => T) { }
5
+
6
+ get(...args: Args): T {
7
+ if (this._value === undefined) {
8
+ this._value = this.factory(...args);
9
+ }
10
+ return this._value;
11
+ }
12
+ }
@@ -0,0 +1,92 @@
1
+ import IElementHolder from "../api/ElementHolder.js";
2
+ import UniHtml from "../component_api/UniHtml.js";
3
+ import { Class, Mixined } from "./mixin/Proto.js";
4
+
5
+ export default interface Component extends Mixined,HTMLElement, UniHtml {}
6
+ export default class Component extends Class(HTMLElement).extend(UniHtml).build() implements IUniHtmlComponent {
7
+ private _attributeChangedCallbacks?: ((name: string, oldValue: any, newValue: any) => void)[];
8
+ constructor() {
9
+ super();
10
+ }
11
+
12
+ onConnected(): void {
13
+ }
14
+
15
+ onDisconnected(): void {
16
+ }
17
+
18
+ onMoved(): void {
19
+ }
20
+
21
+ onAdopted(): void {
22
+ }
23
+
24
+
25
+ onAttributeChanged(name: string, oldValue: any, newValue: any): void {
26
+ }
27
+
28
+ onAttributeChangedCallback(callback: (name: string, oldValue: any, newValue: any) => void): void {
29
+ this._attributeChangedCallbacks = this._attributeChangedCallbacks ?? [];
30
+ this._attributeChangedCallbacks.push(callback);
31
+ }
32
+
33
+ /**
34
+ * @deprecated Use onConnected instead.
35
+ */
36
+ private connectedCallback(): void {
37
+ this.attachShadow({ mode: 'open' });
38
+
39
+ this.onConnected();
40
+
41
+ this.load(this.shadowRoot);
42
+ }
43
+ protected render(element: IElementHolder, renderTarget: HTMLElement | ShadowRoot): Promise<void> {
44
+ (this.getMixin(UniHtml)?.instance.get() as any).render(element, renderTarget);
45
+ //super.render(element, renderTarget);
46
+ //this.shadowRoot!.appendChild(renderTarget);
47
+ return Promise.resolve();
48
+ }
49
+ /**
50
+ * @deprecated Use onDisconnected instead.
51
+ */
52
+ private disconnectedCallback(): void {
53
+ this.onDisconnected();
54
+ }
55
+ /**
56
+ * @deprecated Use onMoved instead.
57
+ */
58
+ private connectedMoveCallback(): void {
59
+ this.onMoved();
60
+ }
61
+ /**
62
+ * @deprecated Use onAdopted instead.
63
+ */
64
+ private adoptedCallback(): void {
65
+ this.onAdopted();
66
+ }
67
+ /**
68
+ * @deprecated Use onAttributeChanged instead.
69
+ */
70
+ private attributeChangedCallback(name: string, oldValue: any, newValue: any): void {
71
+ this.onAttributeChanged(name, oldValue, newValue);
72
+ this._attributeChangedCallbacks?.forEach(cb => cb(name, oldValue, newValue));
73
+ }
74
+ }
75
+
76
+ export interface IUniHtmlComponent {
77
+ onConnected(): void;
78
+ onDisconnected(): void;
79
+ onMoved(): void;
80
+ onAdopted(): void;
81
+
82
+ onAttributeChanged(name: string, oldValue: any, newValue: any): void;
83
+ }
84
+
85
+ export interface IUniHtmlVanillaComponent {
86
+ connectedCallback(): void;
87
+ disconnectedCallback(): void;
88
+ connectedMoveCallback(): void;
89
+ adoptedCallback(): void;
90
+
91
+ attributeChangedCallback(name: string, oldValue: any, newValue: any): void;
92
+ }
@@ -0,0 +1,9 @@
1
+ import UniHtml from "./UniHtml.js";
2
+ import Fetcher from "../Fetcher.js";
3
+
4
+ /**
5
+ * Base class for SPA pages in Pureper application
6
+ * Provides lifecycle hooks and template rendering functionality
7
+ */
8
+ export default class Page extends UniHtml {
9
+ }
@@ -0,0 +1,82 @@
1
+
2
+ /**
3
+ * Abstract base class for HTML components in Pureper SPA.
4
+ * Provides a unified lifecycle: init → preLoadJS → render → postLoadJS.
5
+ * Use static factory methods to create instances from an HTML file or string.
6
+ * Designed to replace legacy Page and Component base classes.
7
+ */
8
+ import IElementHolder from "../api/ElementHolder.js";
9
+
10
+
11
+ /**
12
+ * Universal SPA component base for pages and elements.
13
+ * Use static factory methods for instantiation.
14
+ */
15
+ export default class UniHtml {
16
+ /**
17
+ * Unified component lifecycle entrypoint.
18
+ * Loads HTML, then calls preLoadJS, render, and postLoadJS hooks in order.
19
+ * @param element Target container (usually shadowRoot.host)
20
+ */
21
+ public async load(element: HTMLElement | ShadowRoot): Promise<void> {
22
+ const preHtml: string = await this._init();
23
+ const html: string = await this._postInit(preHtml);
24
+
25
+ const localRoot = document.createElement('div');
26
+ localRoot.innerHTML = html;
27
+
28
+ const holder : IElementHolder = { element: localRoot };
29
+
30
+ // ВАЖНО: preLoad() вызывается ДО монтирования в DOM/Shadow DOM.
31
+ // Для компонентов (UniHtmlComponent) на этом этапе ещё нельзя полагаться на this.shadowRoot —
32
+ // используйте переданный localRoot для подготовки DOM, данных и навешивания обработчиков.
33
+ // Это предпочтительный этап инициализации для компонентов.
34
+ await this.preLoad(holder);
35
+ // render() отвечает за помещение содержимого из localRoot в конечную цель (renderTarget).
36
+ // В UniHtmlComponent.render() после вызова базового render() происходит добавление wrapper в shadowRoot.
37
+ await this.render(holder, element);
38
+ // postLoad() вызывается ПОСЛЕ render(). Для компонентов к этому моменту содержимое уже добавлено
39
+ // внутрь shadowRoot, и можно безопасно работать с this.shadowRoot, измерениями layout и т.п.
40
+ await this.postLoad(holder);
41
+ }
42
+
43
+ private async _postInit(html: string): Promise<string> {
44
+ throw new Error("Method not implemented.");
45
+ }
46
+ private async _init(): Promise<string> {
47
+ throw new Error("Method not implemented.");
48
+ }
49
+ /**
50
+ * Hook before rendering (e.g., data preparation).
51
+ * Для компонентов вызывается до появления содержимого в Shadow DOM, this.shadowRoot может быть недоступен.
52
+ * РЕКОМЕНДАЦИЯ: предпочитайте выполнять основную подготовку, поиск элементов, навешивание обработчиков
53
+ * на узлы из localRoot именно здесь; затем render() вставит их в целевой контейнер/теневой DOM.
54
+ */
55
+ protected async preLoad(holder : IElementHolder) { }
56
+ /**
57
+ * Hook after rendering (e.g., event binding).
58
+ * Для компонентов вызывается после того, как содержимое вставлено в shadowRoot (см. UniHtmlComponent.render()).
59
+ * Используйте этот этап только когда необходим доступ к реально смонтированному DOM (layout/measurements,
60
+ * интеграции, требующие присутствия в документе). В остальных случаях предпочитайте preLoad().
61
+ */
62
+ protected async postLoad(holder: IElementHolder) { }
63
+ /**
64
+ * Main rendering step. By default, simply inserts HTML into the container.
65
+ * Override in subclasses for custom rendering logic.
66
+ * @param element Target container
67
+ * @param html HTML content
68
+ */
69
+ protected async render(holder: IElementHolder, renderTarget: HTMLElement | ShadowRoot): Promise<void> {
70
+ while (renderTarget.firstChild) {
71
+ renderTarget.removeChild(renderTarget.firstChild);
72
+ }
73
+
74
+ const children = Array.from(holder.element.childNodes);
75
+ for (const child of children) {
76
+ renderTarget.appendChild(child);
77
+ }
78
+
79
+ (holder as any).element = this;
80
+ return Promise.resolve();
81
+ }
82
+ }
@@ -0,0 +1,67 @@
1
+ import Lazy from "../../../foundation/api/Lazy.js";
2
+ class ClassBuilder<T extends Constructor> {
3
+ constructor(private base: T) { }
4
+
5
+ public extend<U extends Constructor>(extend: U): ClassBuilder<T> {
6
+ const proto = this.base.prototype;
7
+
8
+ Object.getOwnPropertyNames(extend.prototype).forEach(name => {
9
+ if (name !== 'constructor') {
10
+ Object.defineProperty(proto, name, Object.getOwnPropertyDescriptor(extend.prototype, name) as PropertyDescriptor);
11
+ }
12
+ });
13
+
14
+
15
+ this.base = class extends this.base {
16
+ constructor(...args: any[]) {
17
+ super(...args);
18
+
19
+ var map : Super = {};
20
+ var classHolder : ClassHolder<U> =
21
+ { class: extend, instance: new Lazy<any>(() => new extend(...args)) };
22
+
23
+ if (!this.hasOwnProperty('super'))
24
+ (this as any).super = map;
25
+ (this as any).super[extend.name] = classHolder;
26
+ if (!this.hasOwnProperty('getMixin')) {
27
+ (this as any).getMixin = function <T extends Constructor>(ctor : T): ClassHolder<T> | undefined {
28
+ return (this as any).super?.[ctor.name];
29
+ }
30
+ }
31
+
32
+ Object.assign(this, classHolder.instance.get());
33
+ }
34
+ };
35
+ return this;
36
+ }
37
+ public extendAbstract<U extends AbstractConstructor>(extend: U): ClassBuilder<T> {
38
+ let concrete = extend as unknown as ConcreteConstructor;
39
+ return this.extend(concrete);
40
+ }
41
+
42
+ public build() {
43
+ return class extends this.base {
44
+ };
45
+ }
46
+ }
47
+ export type Mixined = {
48
+ super: Super;
49
+ getMixin<T extends Constructor>(ctor : T): ClassHolder<T> | undefined;
50
+ };
51
+ interface Super {
52
+ [key: string]: ClassHolder;
53
+ }
54
+ interface ClassHolder<T extends Constructor = any> {
55
+ class: T;
56
+ instance: Lazy<InstanceType<T>>;
57
+ }
58
+ export type AnyConstructor<T = {}> = Constructor<T> | LazyConstructor<T>;
59
+
60
+ export type Constructor<T = {}> = (new (...args: any[]) => T);
61
+ export type LazyConstructor<T = {}> = (new () => T);
62
+ export type ConcreteConstructor<T = any> = { new(...args: any[]): T } & AbstractConstructor<T>;
63
+ export type AbstractConstructor<T = any> = Function & { prototype: T }
64
+
65
+ export function Class<T extends Constructor>(Base: T) {
66
+ return new ClassBuilder(Base);
67
+ }