@thomas-siegfried/tapout 0.0.1 → 0.0.3

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 (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1207 -1205
  3. package/decorator-config.md +220 -0
  4. package/dist/wireParams.d.ts.map +1 -1
  5. package/dist/wireParams.js +27 -4
  6. package/dist/wireParams.js.map +1 -1
  7. package/llms.txt +360 -0
  8. package/package.json +57 -55
  9. package/src/applyBindings.ts +372 -372
  10. package/src/arrayToDomMapping.ts +300 -300
  11. package/src/attributeInterpolationMarkup.ts +91 -91
  12. package/src/bindingContext.ts +207 -207
  13. package/src/bindingEvent.ts +198 -198
  14. package/src/bindingProvider.ts +260 -260
  15. package/src/bindings.ts +963 -963
  16. package/src/compareArrays.ts +122 -122
  17. package/src/componentBinding.ts +318 -318
  18. package/src/componentDecorator.ts +62 -62
  19. package/src/components.ts +367 -367
  20. package/src/computed.ts +439 -439
  21. package/src/configure.ts +52 -52
  22. package/src/controlFlowBindings.ts +206 -206
  23. package/src/core.ts +60 -60
  24. package/src/decorators.ts +407 -407
  25. package/src/dependencyDetection.ts +76 -76
  26. package/src/disposable.ts +36 -36
  27. package/src/domData.ts +42 -42
  28. package/src/domNodeDisposal.ts +94 -94
  29. package/src/effects.ts +29 -29
  30. package/src/event.ts +173 -173
  31. package/src/expressionRewriting.ts +219 -219
  32. package/src/extenders.ts +102 -102
  33. package/src/filters.ts +91 -91
  34. package/src/index.ts +150 -150
  35. package/src/interpolationMarkup.ts +130 -130
  36. package/src/memoization.ts +71 -71
  37. package/src/namespacedBindings.ts +132 -132
  38. package/src/observable.ts +48 -48
  39. package/src/observableArray.ts +397 -397
  40. package/src/options.ts +25 -25
  41. package/src/selectExtensions.ts +68 -68
  42. package/src/slotBinding.ts +84 -84
  43. package/src/subscribable.ts +266 -266
  44. package/src/tasks.ts +79 -79
  45. package/src/templateEngine.ts +108 -108
  46. package/src/templateRendering.ts +399 -399
  47. package/src/templateRewriting.ts +94 -94
  48. package/src/templateSources.ts +134 -134
  49. package/src/utils.ts +123 -123
  50. package/src/utilsDom.ts +87 -87
  51. package/src/virtualElements.ts +153 -153
  52. package/src/wireParams.ts +69 -49
package/src/components.ts CHANGED
@@ -1,367 +1,367 @@
1
- import { Subscribable } from './subscribable.js';
2
- import { schedule } from './tasks.js';
3
- import { ignore } from './dependencyDetection.js';
4
- import { parseHtmlFragment, cloneNodes } from './utilsDom.js';
5
-
6
- // ---- Types ----
7
-
8
- export interface ComponentInfo {
9
- element: Node;
10
- templateNodes: Node[];
11
- }
12
-
13
- export interface ComponentDefinition {
14
- template?: Node[];
15
- createViewModel?: (params: unknown, componentInfo: ComponentInfo) => unknown;
16
- }
17
-
18
- export interface ComponentConfig {
19
- template?: unknown;
20
- viewModel?: unknown;
21
- synchronous?: boolean;
22
- [key: string]: unknown;
23
- }
24
-
25
- export interface ComponentLoader {
26
- getConfig?(
27
- name: string,
28
- callback: (config: ComponentConfig | null) => void,
29
- ): void;
30
-
31
- loadComponent?(
32
- name: string,
33
- config: ComponentConfig,
34
- callback: (definition: ComponentDefinition | null) => void,
35
- ): void;
36
-
37
- loadTemplate?(
38
- name: string,
39
- templateConfig: unknown,
40
- callback: (nodes: Node[] | null) => void,
41
- ): void;
42
-
43
- loadViewModel?(
44
- name: string,
45
- viewModelConfig: unknown,
46
- callback: (factory: ((params: unknown, info: ComponentInfo) => unknown) | null) => void,
47
- ): void;
48
-
49
- suppressLoaderExceptions?: boolean;
50
- }
51
-
52
- interface CachedDefinition {
53
- definition: ComponentDefinition | null;
54
- isSynchronousComponent: boolean;
55
- }
56
-
57
- // ---- Caches ----
58
-
59
- const loadingSubscribablesCache: Record<string, Subscribable<ComponentDefinition | null>> = {};
60
- const loadedDefinitionsCache: Record<string, CachedDefinition> = {};
61
-
62
- // ---- Default Config Registry ----
63
-
64
- const defaultConfigRegistry: Record<string, ComponentConfig> = {};
65
-
66
- // ---- Registration API ----
67
-
68
- export function register(name: string, config: ComponentConfig): void {
69
- if (!config) {
70
- throw new Error('Invalid configuration for ' + name);
71
- }
72
- if (isRegistered(name)) {
73
- throw new Error('Component ' + name + ' is already registered');
74
- }
75
- defaultConfigRegistry[name] = config;
76
- }
77
-
78
- export function isRegistered(name: string): boolean {
79
- return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, name);
80
- }
81
-
82
- export function unregister(name: string): void {
83
- delete defaultConfigRegistry[name];
84
- clearCachedDefinition(name);
85
- }
86
-
87
- export function clearCachedDefinition(name: string): void {
88
- delete loadedDefinitionsCache[name];
89
- }
90
-
91
- /** Reset all component caches. For testing only. */
92
- export function _resetForTesting(): void {
93
- for (const key of Object.keys(defaultConfigRegistry)) delete defaultConfigRegistry[key];
94
- for (const key of Object.keys(loadedDefinitionsCache)) delete loadedDefinitionsCache[key];
95
- for (const key of Object.keys(loadingSubscribablesCache)) delete loadingSubscribablesCache[key];
96
- }
97
-
98
- // ---- Loader Pipeline ----
99
-
100
- export function get(
101
- name: string,
102
- callback: (definition: ComponentDefinition | null) => void,
103
- ): void {
104
- const cached = Object.prototype.hasOwnProperty.call(loadedDefinitionsCache, name)
105
- ? loadedDefinitionsCache[name]
106
- : undefined;
107
-
108
- if (cached) {
109
- if (cached.isSynchronousComponent) {
110
- ignore(() => callback(cached.definition));
111
- } else {
112
- schedule(() => callback(cached.definition));
113
- }
114
- } else {
115
- loadComponentAndNotify(name, callback);
116
- }
117
- }
118
-
119
- function loadComponentAndNotify(
120
- name: string,
121
- callback: (definition: ComponentDefinition | null) => void,
122
- ): void {
123
- let subscribable = Object.prototype.hasOwnProperty.call(loadingSubscribablesCache, name)
124
- ? loadingSubscribablesCache[name]
125
- : undefined;
126
-
127
- let completedAsync: boolean | undefined;
128
-
129
- if (!subscribable) {
130
- subscribable = loadingSubscribablesCache[name] = new Subscribable<ComponentDefinition | null>();
131
- subscribable.subscribe(callback);
132
-
133
- beginLoadingComponent(name, (definition, config) => {
134
- const isSynchronousComponent = !!(config && config.synchronous);
135
- loadedDefinitionsCache[name] = { definition, isSynchronousComponent };
136
- delete loadingSubscribablesCache[name];
137
-
138
- if (completedAsync || isSynchronousComponent) {
139
- subscribable!.notifySubscribers(definition);
140
- } else {
141
- schedule(() => subscribable!.notifySubscribers(definition));
142
- }
143
- });
144
- completedAsync = true;
145
- } else {
146
- subscribable.subscribe(callback);
147
- }
148
- }
149
-
150
- function beginLoadingComponent(
151
- name: string,
152
- callback: (definition: ComponentDefinition | null, config: ComponentConfig | null) => void,
153
- ): void {
154
- getFirstResultFromLoaders('getConfig', [name], (config) => {
155
- if (config) {
156
- getFirstResultFromLoaders('loadComponent', [name, config as ComponentConfig], (definition) => {
157
- callback(definition as ComponentDefinition | null, config as ComponentConfig);
158
- });
159
- } else {
160
- callback(null, null);
161
- }
162
- });
163
- }
164
-
165
- type LoaderMethod = keyof ComponentLoader & ('getConfig' | 'loadComponent' | 'loadTemplate' | 'loadViewModel');
166
-
167
- export function getFirstResultFromLoaders(
168
- methodName: LoaderMethod,
169
- argsExceptCallback: unknown[],
170
- callback: (result: unknown) => void,
171
- candidateLoaders?: ComponentLoader[],
172
- ): void {
173
- if (!candidateLoaders) {
174
- candidateLoaders = loaders.slice(0);
175
- }
176
-
177
- const currentCandidateLoader = candidateLoaders.shift();
178
- if (currentCandidateLoader) {
179
- const methodInstance = currentCandidateLoader[methodName] as ((...args: unknown[]) => unknown) | undefined;
180
- if (methodInstance) {
181
- let wasAborted = false;
182
- const synchronousReturnValue = methodInstance.apply(
183
- currentCandidateLoader,
184
- [...argsExceptCallback, (result: unknown) => {
185
- if (wasAborted) {
186
- callback(null);
187
- } else if (result !== null && result !== undefined) {
188
- callback(result);
189
- } else {
190
- getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
191
- }
192
- }],
193
- );
194
-
195
- if (synchronousReturnValue !== undefined) {
196
- wasAborted = true;
197
- if (!currentCandidateLoader.suppressLoaderExceptions) {
198
- throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
199
- }
200
- }
201
- } else {
202
- getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
203
- }
204
- } else {
205
- callback(null);
206
- }
207
- }
208
-
209
- // ---- Default Loader ----
210
-
211
- function makeErrorCallback(componentName: string): (message: string) => never {
212
- return function (message: string): never {
213
- throw new Error("Component '" + componentName + "': " + message);
214
- };
215
- }
216
-
217
- function resolveConfig(
218
- componentName: string,
219
- errorCallback: (msg: string) => never,
220
- config: ComponentConfig,
221
- callback: (definition: ComponentDefinition) => void,
222
- ): void {
223
- const result: ComponentDefinition = {};
224
- let remaining = 2;
225
-
226
- const tryIssueCallback = () => {
227
- if (--remaining === 0) {
228
- callback(result);
229
- }
230
- };
231
-
232
- const templateConfig = config.template;
233
- const viewModelConfig = config.viewModel;
234
-
235
- if (templateConfig) {
236
- getFirstResultFromLoaders('loadTemplate', [componentName, templateConfig], (resolvedTemplate) => {
237
- result.template = resolvedTemplate as Node[];
238
- tryIssueCallback();
239
- });
240
- } else {
241
- tryIssueCallback();
242
- }
243
-
244
- if (viewModelConfig) {
245
- getFirstResultFromLoaders('loadViewModel', [componentName, viewModelConfig], (resolvedViewModel) => {
246
- result.createViewModel = resolvedViewModel as (params: unknown, info: ComponentInfo) => unknown;
247
- tryIssueCallback();
248
- });
249
- } else {
250
- tryIssueCallback();
251
- }
252
- }
253
-
254
- function cloneNodesFromTemplateSourceElement(elemInstance: Element): Node[] {
255
- const tag = elemInstance.tagName?.toLowerCase();
256
- switch (tag) {
257
- case 'script':
258
- return parseHtmlFragment((elemInstance as HTMLScriptElement).text);
259
- case 'textarea':
260
- return parseHtmlFragment((elemInstance as HTMLTextAreaElement).value);
261
- case 'template': {
262
- const content = (elemInstance as HTMLTemplateElement).content;
263
- if (isDocumentFragment(content)) {
264
- return cloneNodes(Array.from(content.childNodes));
265
- }
266
- break;
267
- }
268
- }
269
- return cloneNodes(Array.from(elemInstance.childNodes));
270
- }
271
-
272
- function isDocumentFragment(obj: unknown): obj is DocumentFragment {
273
- return !!obj && (obj as Node).nodeType === 11;
274
- }
275
-
276
- function isDomElement(obj: unknown): obj is Element {
277
- return !!obj && (obj as Node).nodeType === 1;
278
- }
279
-
280
- function resolveTemplate(
281
- errorCallback: (msg: string) => never,
282
- templateConfig: unknown,
283
- callback: (nodes: Node[]) => void,
284
- ): void {
285
- if (typeof templateConfig === 'string') {
286
- callback(parseHtmlFragment(templateConfig));
287
- } else if (Array.isArray(templateConfig)) {
288
- callback(templateConfig as Node[]);
289
- } else if (isDocumentFragment(templateConfig)) {
290
- callback(Array.from(templateConfig.childNodes));
291
- } else if (templateConfig && typeof templateConfig === 'object' && 'element' in templateConfig) {
292
- const element = (templateConfig as { element: unknown }).element;
293
- if (isDomElement(element)) {
294
- callback(cloneNodesFromTemplateSourceElement(element));
295
- } else if (typeof element === 'string') {
296
- const elemInstance = (typeof document !== 'undefined' ? document : null)?.getElementById(element) ?? null;
297
- if (elemInstance) {
298
- callback(cloneNodesFromTemplateSourceElement(elemInstance));
299
- } else {
300
- errorCallback('Cannot find element with ID ' + element);
301
- }
302
- } else {
303
- errorCallback('Unknown element type: ' + element);
304
- }
305
- } else {
306
- errorCallback('Unknown template value: ' + templateConfig);
307
- }
308
- }
309
-
310
- function resolveViewModel(
311
- errorCallback: (msg: string) => never,
312
- viewModelConfig: unknown,
313
- callback: (factory: (params: unknown, info: ComponentInfo) => unknown) => void,
314
- ): void {
315
- if (typeof viewModelConfig === 'function') {
316
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
- callback((params: unknown) => new (viewModelConfig as any)(params));
318
- } else if (
319
- viewModelConfig &&
320
- typeof viewModelConfig === 'object' &&
321
- typeof (viewModelConfig as Record<string, unknown>).createViewModel === 'function'
322
- ) {
323
- callback((viewModelConfig as { createViewModel: (params: unknown, info: ComponentInfo) => unknown }).createViewModel);
324
- } else if (viewModelConfig && typeof viewModelConfig === 'object' && 'instance' in viewModelConfig) {
325
- const fixedInstance = (viewModelConfig as { instance: unknown }).instance;
326
- callback(() => fixedInstance);
327
- } else if (viewModelConfig && typeof viewModelConfig === 'object' && 'viewModel' in viewModelConfig) {
328
- resolveViewModel(errorCallback, (viewModelConfig as { viewModel: unknown }).viewModel, callback);
329
- } else {
330
- errorCallback('Unknown viewModel value: ' + viewModelConfig);
331
- }
332
- }
333
-
334
- export const defaultLoader: ComponentLoader = {
335
- getConfig(name: string, callback: (config: ComponentConfig | null) => void): void {
336
- callback(isRegistered(name) ? defaultConfigRegistry[name] : null);
337
- },
338
-
339
- loadComponent(
340
- name: string,
341
- config: ComponentConfig,
342
- callback: (definition: ComponentDefinition | null) => void,
343
- ): void {
344
- const errorCallback = makeErrorCallback(name);
345
- resolveConfig(name, errorCallback, config, callback);
346
- },
347
-
348
- loadTemplate(
349
- name: string,
350
- templateConfig: unknown,
351
- callback: (nodes: Node[] | null) => void,
352
- ): void {
353
- resolveTemplate(makeErrorCallback(name), templateConfig, callback);
354
- },
355
-
356
- loadViewModel(
357
- name: string,
358
- viewModelConfig: unknown,
359
- callback: (factory: ((params: unknown, info: ComponentInfo) => unknown) | null) => void,
360
- ): void {
361
- resolveViewModel(makeErrorCallback(name), viewModelConfig, callback);
362
- },
363
- };
364
-
365
- // ---- Loader Array ----
366
-
367
- export const loaders: ComponentLoader[] = [defaultLoader];
1
+ import { Subscribable } from './subscribable.js';
2
+ import { schedule } from './tasks.js';
3
+ import { ignore } from './dependencyDetection.js';
4
+ import { parseHtmlFragment, cloneNodes } from './utilsDom.js';
5
+
6
+ // ---- Types ----
7
+
8
+ export interface ComponentInfo {
9
+ element: Node;
10
+ templateNodes: Node[];
11
+ }
12
+
13
+ export interface ComponentDefinition {
14
+ template?: Node[];
15
+ createViewModel?: (params: unknown, componentInfo: ComponentInfo) => unknown;
16
+ }
17
+
18
+ export interface ComponentConfig {
19
+ template?: unknown;
20
+ viewModel?: unknown;
21
+ synchronous?: boolean;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export interface ComponentLoader {
26
+ getConfig?(
27
+ name: string,
28
+ callback: (config: ComponentConfig | null) => void,
29
+ ): void;
30
+
31
+ loadComponent?(
32
+ name: string,
33
+ config: ComponentConfig,
34
+ callback: (definition: ComponentDefinition | null) => void,
35
+ ): void;
36
+
37
+ loadTemplate?(
38
+ name: string,
39
+ templateConfig: unknown,
40
+ callback: (nodes: Node[] | null) => void,
41
+ ): void;
42
+
43
+ loadViewModel?(
44
+ name: string,
45
+ viewModelConfig: unknown,
46
+ callback: (factory: ((params: unknown, info: ComponentInfo) => unknown) | null) => void,
47
+ ): void;
48
+
49
+ suppressLoaderExceptions?: boolean;
50
+ }
51
+
52
+ interface CachedDefinition {
53
+ definition: ComponentDefinition | null;
54
+ isSynchronousComponent: boolean;
55
+ }
56
+
57
+ // ---- Caches ----
58
+
59
+ const loadingSubscribablesCache: Record<string, Subscribable<ComponentDefinition | null>> = {};
60
+ const loadedDefinitionsCache: Record<string, CachedDefinition> = {};
61
+
62
+ // ---- Default Config Registry ----
63
+
64
+ const defaultConfigRegistry: Record<string, ComponentConfig> = {};
65
+
66
+ // ---- Registration API ----
67
+
68
+ export function register(name: string, config: ComponentConfig): void {
69
+ if (!config) {
70
+ throw new Error('Invalid configuration for ' + name);
71
+ }
72
+ if (isRegistered(name)) {
73
+ throw new Error('Component ' + name + ' is already registered');
74
+ }
75
+ defaultConfigRegistry[name] = config;
76
+ }
77
+
78
+ export function isRegistered(name: string): boolean {
79
+ return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, name);
80
+ }
81
+
82
+ export function unregister(name: string): void {
83
+ delete defaultConfigRegistry[name];
84
+ clearCachedDefinition(name);
85
+ }
86
+
87
+ export function clearCachedDefinition(name: string): void {
88
+ delete loadedDefinitionsCache[name];
89
+ }
90
+
91
+ /** Reset all component caches. For testing only. */
92
+ export function _resetForTesting(): void {
93
+ for (const key of Object.keys(defaultConfigRegistry)) delete defaultConfigRegistry[key];
94
+ for (const key of Object.keys(loadedDefinitionsCache)) delete loadedDefinitionsCache[key];
95
+ for (const key of Object.keys(loadingSubscribablesCache)) delete loadingSubscribablesCache[key];
96
+ }
97
+
98
+ // ---- Loader Pipeline ----
99
+
100
+ export function get(
101
+ name: string,
102
+ callback: (definition: ComponentDefinition | null) => void,
103
+ ): void {
104
+ const cached = Object.prototype.hasOwnProperty.call(loadedDefinitionsCache, name)
105
+ ? loadedDefinitionsCache[name]
106
+ : undefined;
107
+
108
+ if (cached) {
109
+ if (cached.isSynchronousComponent) {
110
+ ignore(() => callback(cached.definition));
111
+ } else {
112
+ schedule(() => callback(cached.definition));
113
+ }
114
+ } else {
115
+ loadComponentAndNotify(name, callback);
116
+ }
117
+ }
118
+
119
+ function loadComponentAndNotify(
120
+ name: string,
121
+ callback: (definition: ComponentDefinition | null) => void,
122
+ ): void {
123
+ let subscribable = Object.prototype.hasOwnProperty.call(loadingSubscribablesCache, name)
124
+ ? loadingSubscribablesCache[name]
125
+ : undefined;
126
+
127
+ let completedAsync: boolean | undefined;
128
+
129
+ if (!subscribable) {
130
+ subscribable = loadingSubscribablesCache[name] = new Subscribable<ComponentDefinition | null>();
131
+ subscribable.subscribe(callback);
132
+
133
+ beginLoadingComponent(name, (definition, config) => {
134
+ const isSynchronousComponent = !!(config && config.synchronous);
135
+ loadedDefinitionsCache[name] = { definition, isSynchronousComponent };
136
+ delete loadingSubscribablesCache[name];
137
+
138
+ if (completedAsync || isSynchronousComponent) {
139
+ subscribable!.notifySubscribers(definition);
140
+ } else {
141
+ schedule(() => subscribable!.notifySubscribers(definition));
142
+ }
143
+ });
144
+ completedAsync = true;
145
+ } else {
146
+ subscribable.subscribe(callback);
147
+ }
148
+ }
149
+
150
+ function beginLoadingComponent(
151
+ name: string,
152
+ callback: (definition: ComponentDefinition | null, config: ComponentConfig | null) => void,
153
+ ): void {
154
+ getFirstResultFromLoaders('getConfig', [name], (config) => {
155
+ if (config) {
156
+ getFirstResultFromLoaders('loadComponent', [name, config as ComponentConfig], (definition) => {
157
+ callback(definition as ComponentDefinition | null, config as ComponentConfig);
158
+ });
159
+ } else {
160
+ callback(null, null);
161
+ }
162
+ });
163
+ }
164
+
165
+ type LoaderMethod = keyof ComponentLoader & ('getConfig' | 'loadComponent' | 'loadTemplate' | 'loadViewModel');
166
+
167
+ export function getFirstResultFromLoaders(
168
+ methodName: LoaderMethod,
169
+ argsExceptCallback: unknown[],
170
+ callback: (result: unknown) => void,
171
+ candidateLoaders?: ComponentLoader[],
172
+ ): void {
173
+ if (!candidateLoaders) {
174
+ candidateLoaders = loaders.slice(0);
175
+ }
176
+
177
+ const currentCandidateLoader = candidateLoaders.shift();
178
+ if (currentCandidateLoader) {
179
+ const methodInstance = currentCandidateLoader[methodName] as ((...args: unknown[]) => unknown) | undefined;
180
+ if (methodInstance) {
181
+ let wasAborted = false;
182
+ const synchronousReturnValue = methodInstance.apply(
183
+ currentCandidateLoader,
184
+ [...argsExceptCallback, (result: unknown) => {
185
+ if (wasAborted) {
186
+ callback(null);
187
+ } else if (result !== null && result !== undefined) {
188
+ callback(result);
189
+ } else {
190
+ getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
191
+ }
192
+ }],
193
+ );
194
+
195
+ if (synchronousReturnValue !== undefined) {
196
+ wasAborted = true;
197
+ if (!currentCandidateLoader.suppressLoaderExceptions) {
198
+ throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
199
+ }
200
+ }
201
+ } else {
202
+ getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
203
+ }
204
+ } else {
205
+ callback(null);
206
+ }
207
+ }
208
+
209
+ // ---- Default Loader ----
210
+
211
+ function makeErrorCallback(componentName: string): (message: string) => never {
212
+ return function (message: string): never {
213
+ throw new Error("Component '" + componentName + "': " + message);
214
+ };
215
+ }
216
+
217
+ function resolveConfig(
218
+ componentName: string,
219
+ errorCallback: (msg: string) => never,
220
+ config: ComponentConfig,
221
+ callback: (definition: ComponentDefinition) => void,
222
+ ): void {
223
+ const result: ComponentDefinition = {};
224
+ let remaining = 2;
225
+
226
+ const tryIssueCallback = () => {
227
+ if (--remaining === 0) {
228
+ callback(result);
229
+ }
230
+ };
231
+
232
+ const templateConfig = config.template;
233
+ const viewModelConfig = config.viewModel;
234
+
235
+ if (templateConfig) {
236
+ getFirstResultFromLoaders('loadTemplate', [componentName, templateConfig], (resolvedTemplate) => {
237
+ result.template = resolvedTemplate as Node[];
238
+ tryIssueCallback();
239
+ });
240
+ } else {
241
+ tryIssueCallback();
242
+ }
243
+
244
+ if (viewModelConfig) {
245
+ getFirstResultFromLoaders('loadViewModel', [componentName, viewModelConfig], (resolvedViewModel) => {
246
+ result.createViewModel = resolvedViewModel as (params: unknown, info: ComponentInfo) => unknown;
247
+ tryIssueCallback();
248
+ });
249
+ } else {
250
+ tryIssueCallback();
251
+ }
252
+ }
253
+
254
+ function cloneNodesFromTemplateSourceElement(elemInstance: Element): Node[] {
255
+ const tag = elemInstance.tagName?.toLowerCase();
256
+ switch (tag) {
257
+ case 'script':
258
+ return parseHtmlFragment((elemInstance as HTMLScriptElement).text);
259
+ case 'textarea':
260
+ return parseHtmlFragment((elemInstance as HTMLTextAreaElement).value);
261
+ case 'template': {
262
+ const content = (elemInstance as HTMLTemplateElement).content;
263
+ if (isDocumentFragment(content)) {
264
+ return cloneNodes(Array.from(content.childNodes));
265
+ }
266
+ break;
267
+ }
268
+ }
269
+ return cloneNodes(Array.from(elemInstance.childNodes));
270
+ }
271
+
272
+ function isDocumentFragment(obj: unknown): obj is DocumentFragment {
273
+ return !!obj && (obj as Node).nodeType === 11;
274
+ }
275
+
276
+ function isDomElement(obj: unknown): obj is Element {
277
+ return !!obj && (obj as Node).nodeType === 1;
278
+ }
279
+
280
+ function resolveTemplate(
281
+ errorCallback: (msg: string) => never,
282
+ templateConfig: unknown,
283
+ callback: (nodes: Node[]) => void,
284
+ ): void {
285
+ if (typeof templateConfig === 'string') {
286
+ callback(parseHtmlFragment(templateConfig));
287
+ } else if (Array.isArray(templateConfig)) {
288
+ callback(templateConfig as Node[]);
289
+ } else if (isDocumentFragment(templateConfig)) {
290
+ callback(Array.from(templateConfig.childNodes));
291
+ } else if (templateConfig && typeof templateConfig === 'object' && 'element' in templateConfig) {
292
+ const element = (templateConfig as { element: unknown }).element;
293
+ if (isDomElement(element)) {
294
+ callback(cloneNodesFromTemplateSourceElement(element));
295
+ } else if (typeof element === 'string') {
296
+ const elemInstance = (typeof document !== 'undefined' ? document : null)?.getElementById(element) ?? null;
297
+ if (elemInstance) {
298
+ callback(cloneNodesFromTemplateSourceElement(elemInstance));
299
+ } else {
300
+ errorCallback('Cannot find element with ID ' + element);
301
+ }
302
+ } else {
303
+ errorCallback('Unknown element type: ' + element);
304
+ }
305
+ } else {
306
+ errorCallback('Unknown template value: ' + templateConfig);
307
+ }
308
+ }
309
+
310
+ function resolveViewModel(
311
+ errorCallback: (msg: string) => never,
312
+ viewModelConfig: unknown,
313
+ callback: (factory: (params: unknown, info: ComponentInfo) => unknown) => void,
314
+ ): void {
315
+ if (typeof viewModelConfig === 'function') {
316
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
+ callback((params: unknown) => new (viewModelConfig as any)(params));
318
+ } else if (
319
+ viewModelConfig &&
320
+ typeof viewModelConfig === 'object' &&
321
+ typeof (viewModelConfig as Record<string, unknown>).createViewModel === 'function'
322
+ ) {
323
+ callback((viewModelConfig as { createViewModel: (params: unknown, info: ComponentInfo) => unknown }).createViewModel);
324
+ } else if (viewModelConfig && typeof viewModelConfig === 'object' && 'instance' in viewModelConfig) {
325
+ const fixedInstance = (viewModelConfig as { instance: unknown }).instance;
326
+ callback(() => fixedInstance);
327
+ } else if (viewModelConfig && typeof viewModelConfig === 'object' && 'viewModel' in viewModelConfig) {
328
+ resolveViewModel(errorCallback, (viewModelConfig as { viewModel: unknown }).viewModel, callback);
329
+ } else {
330
+ errorCallback('Unknown viewModel value: ' + viewModelConfig);
331
+ }
332
+ }
333
+
334
+ export const defaultLoader: ComponentLoader = {
335
+ getConfig(name: string, callback: (config: ComponentConfig | null) => void): void {
336
+ callback(isRegistered(name) ? defaultConfigRegistry[name] : null);
337
+ },
338
+
339
+ loadComponent(
340
+ name: string,
341
+ config: ComponentConfig,
342
+ callback: (definition: ComponentDefinition | null) => void,
343
+ ): void {
344
+ const errorCallback = makeErrorCallback(name);
345
+ resolveConfig(name, errorCallback, config, callback);
346
+ },
347
+
348
+ loadTemplate(
349
+ name: string,
350
+ templateConfig: unknown,
351
+ callback: (nodes: Node[] | null) => void,
352
+ ): void {
353
+ resolveTemplate(makeErrorCallback(name), templateConfig, callback);
354
+ },
355
+
356
+ loadViewModel(
357
+ name: string,
358
+ viewModelConfig: unknown,
359
+ callback: (factory: ((params: unknown, info: ComponentInfo) => unknown) | null) => void,
360
+ ): void {
361
+ resolveViewModel(makeErrorCallback(name), viewModelConfig, callback);
362
+ },
363
+ };
364
+
365
+ // ---- Loader Array ----
366
+
367
+ export const loaders: ComponentLoader[] = [defaultLoader];