@thomas-siegfried/tapout 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1205 -1205
  3. package/package.json +55 -55
  4. package/src/applyBindings.ts +372 -372
  5. package/src/arrayToDomMapping.ts +300 -300
  6. package/src/attributeInterpolationMarkup.ts +91 -91
  7. package/src/bindingContext.ts +207 -207
  8. package/src/bindingEvent.ts +198 -198
  9. package/src/bindingProvider.ts +260 -260
  10. package/src/bindings.ts +963 -963
  11. package/src/compareArrays.ts +122 -122
  12. package/src/componentBinding.ts +318 -318
  13. package/src/componentDecorator.ts +62 -62
  14. package/src/components.ts +367 -367
  15. package/src/computed.ts +439 -439
  16. package/src/configure.ts +52 -52
  17. package/src/controlFlowBindings.ts +206 -206
  18. package/src/core.ts +60 -60
  19. package/src/decorators.ts +407 -407
  20. package/src/dependencyDetection.ts +76 -76
  21. package/src/disposable.ts +36 -36
  22. package/src/domData.ts +42 -42
  23. package/src/domNodeDisposal.ts +94 -94
  24. package/src/effects.ts +29 -29
  25. package/src/event.ts +173 -173
  26. package/src/expressionRewriting.ts +219 -219
  27. package/src/extenders.ts +102 -102
  28. package/src/filters.ts +91 -91
  29. package/src/index.ts +150 -150
  30. package/src/interpolationMarkup.ts +130 -130
  31. package/src/memoization.ts +71 -71
  32. package/src/namespacedBindings.ts +132 -132
  33. package/src/observable.ts +48 -48
  34. package/src/observableArray.ts +397 -397
  35. package/src/options.ts +25 -25
  36. package/src/selectExtensions.ts +68 -68
  37. package/src/slotBinding.ts +84 -84
  38. package/src/subscribable.ts +266 -266
  39. package/src/tasks.ts +79 -79
  40. package/src/templateEngine.ts +108 -108
  41. package/src/templateRendering.ts +399 -399
  42. package/src/templateRewriting.ts +94 -94
  43. package/src/templateSources.ts +134 -134
  44. package/src/utils.ts +123 -123
  45. package/src/utilsDom.ts +87 -87
  46. package/src/virtualElements.ts +153 -153
  47. package/src/wireParams.ts +49 -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];