@kupola/kupola 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +330 -286
  3. package/adapters/axios.d.ts +34 -0
  4. package/adapters/axios.js +122 -0
  5. package/adapters/navios-http.d.ts +110 -0
  6. package/adapters/navios-http.js +151 -0
  7. package/css/components-ext.css +157 -0
  8. package/css/components.css +69 -0
  9. package/css/theme-dark.css +5 -5
  10. package/css/utilities.css +119 -1
  11. package/dist/css/accessibility.css +119 -0
  12. package/dist/css/animations.css +224 -0
  13. package/dist/css/brand-themes.css +300 -0
  14. package/dist/css/colors_and_type.css +441 -0
  15. package/dist/css/components-ext.css +4165 -0
  16. package/dist/css/components.css +1483 -0
  17. package/dist/css/kupola.css +1 -12
  18. package/dist/css/kupola.min.css +1 -0
  19. package/dist/css/responsive.css +697 -0
  20. package/dist/css/scaffold.css +145 -0
  21. package/dist/css/states.css +316 -0
  22. package/dist/css/theme-dark.css +296 -0
  23. package/dist/css/theme-light.css +296 -0
  24. package/dist/css/utilities.css +171 -0
  25. package/dist/icons.svg +284 -0
  26. package/dist/kupola.cjs.js +17409 -257
  27. package/dist/kupola.cjs.js.map +1 -1
  28. package/dist/kupola.css +9000 -0
  29. package/dist/kupola.esm.js +17183 -7597
  30. package/dist/kupola.esm.js.map +1 -1
  31. package/dist/kupola.min.css +1 -0
  32. package/dist/kupola.min.js +2 -0
  33. package/dist/kupola.min.js.map +1 -0
  34. package/dist/kupola.umd.js +17415 -257
  35. package/dist/kupola.umd.js.map +1 -1
  36. package/dist/plugins/vite-plugin-kupola.js +120 -0
  37. package/dist/types/kupola.d.ts +421 -323
  38. package/js/calendar.js +334 -25
  39. package/js/carousel.js +182 -48
  40. package/js/collapse.js +148 -34
  41. package/js/color-picker.js +416 -108
  42. package/js/component.js +8 -19
  43. package/js/countdown.js +9 -8
  44. package/js/data-bind.js +73 -16
  45. package/js/datepicker.js +488 -110
  46. package/js/depends.js +710 -0
  47. package/js/dialog.js +4 -2
  48. package/js/drawer.js +172 -8
  49. package/js/dropdown.js +272 -17
  50. package/js/dynamic-tags.js +156 -40
  51. package/js/fileupload.js +9 -8
  52. package/js/form.js +280 -254
  53. package/js/global-events.js +281 -188
  54. package/js/heatmap.js +10 -7
  55. package/js/i18n.js +18 -10
  56. package/js/icons.js +141 -161
  57. package/js/image-preview.js +146 -2
  58. package/js/initializer.js +151 -71
  59. package/js/kupola-core.js +123 -45
  60. package/js/kupola-lifecycle.js +13 -11
  61. package/js/message.js +8 -1
  62. package/js/modal.js +207 -59
  63. package/js/notification.js +8 -1
  64. package/js/numberinput.js +9 -8
  65. package/js/pagination.js +263 -0
  66. package/js/registry.js +29 -12
  67. package/js/select.js +482 -27
  68. package/js/slide-captcha.js +11 -2
  69. package/js/slider.js +442 -25
  70. package/js/statcard.js +9 -7
  71. package/js/table.js +1210 -0
  72. package/js/tag.js +268 -14
  73. package/js/theme-standalone.js +88 -0
  74. package/js/theme.js +14 -43
  75. package/js/timepicker.js +335 -66
  76. package/js/tooltip.js +317 -86
  77. package/js/utils.js +6 -2
  78. package/js/validation.js +6 -2
  79. package/js/virtual-list.js +11 -7
  80. package/js/web-components.js +288 -0
  81. package/package.json +81 -67
  82. package/plugins/vite-plugin-kupola.js +120 -0
  83. package/scripts/build-css.cjs +113 -0
  84. package/scripts/build-svg-sprite.cjs +107 -0
  85. package/types/kupola.d.ts +421 -323
  86. package/CHANGELOG.md +0 -130
  87. package/INTEGRATION.md +0 -440
  88. package/PROJECT_SUMMARY.md +0 -312
  89. package/SKILL.md +0 -572
  90. package/dist/utils/utils/Kupola.cs +0 -77
  91. package/dist/utils/utils/Kupola.java +0 -104
  92. package/dist/utils/utils/kupola.go +0 -120
  93. package/dist/utils/utils/kupola.js +0 -63
  94. package/dist/utils/utils/kupola.py +0 -1392
  95. package/dist/utils/utils/kupola.rb +0 -69
  96. package/js/composition-api.js +0 -458
  97. package/js/error-handler.js +0 -181
  98. package/js/http.js +0 -419
  99. package/js/kupola-devtools.js +0 -598
  100. package/js/performance.js +0 -250
  101. package/js/router.js +0 -396
  102. package/js/security.js +0 -189
  103. package/js/test-utils.js +0 -251
  104. package/templates/base.html +0 -30
  105. package/templates/base_dashboard.html +0 -99
  106. package/utils/Kupola.cs +0 -77
  107. package/utils/Kupola.java +0 -104
  108. package/utils/kupola.go +0 -120
  109. package/utils/kupola.js +0 -63
  110. package/utils/kupola.py +0 -1392
  111. package/utils/kupola.rb +0 -69
package/js/performance.js DELETED
@@ -1,250 +0,0 @@
1
- class KupolaPerformance {
2
- constructor() {
3
- this.metrics = new Map();
4
- this.timers = new Map();
5
- this.observers = [];
6
- this.enabled = false;
7
- this.thresholds = {
8
- render: 16,
9
- mount: 100,
10
- update: 50,
11
- http: 3000
12
- };
13
- }
14
-
15
- init() {
16
- this.enabled = true;
17
- this._setupObservers();
18
- this._measureNavigation();
19
- }
20
-
21
- _setupObservers() {
22
- if ('PerformanceObserver' in window) {
23
- const paintObserver = new PerformanceObserver((entryList) => {
24
- entryList.getEntries().forEach(entry => {
25
- this._recordMetric('paint', {
26
- name: entry.name,
27
- startTime: entry.startTime,
28
- duration: entry.duration
29
- });
30
- });
31
- });
32
- paintObserver.observe({ type: 'paint', buffered: true });
33
- this.observers.push(paintObserver);
34
-
35
- const resourceObserver = new PerformanceObserver((entryList) => {
36
- entryList.getEntries().forEach(entry => {
37
- this._recordMetric('resource', {
38
- name: entry.name,
39
- type: entry.initiatorType,
40
- duration: entry.duration,
41
- size: entry.transferSize,
42
- startTime: entry.startTime
43
- });
44
- });
45
- });
46
- resourceObserver.observe({ type: 'resource', buffered: true });
47
- this.observers.push(resourceObserver);
48
-
49
- const longTaskObserver = new PerformanceObserver((entryList) => {
50
- entryList.getEntries().forEach(entry => {
51
- this._recordMetric('longtask', {
52
- duration: entry.duration,
53
- startTime: entry.startTime
54
- });
55
- });
56
- });
57
- longTaskObserver.observe({ type: 'longtask', buffered: true });
58
- this.observers.push(longTaskObserver);
59
- }
60
- }
61
-
62
- _measureNavigation() {
63
- if ('performance' in window) {
64
- const nav = performance.getEntriesByType('navigation')[0];
65
- if (nav) {
66
- this._recordMetric('navigation', {
67
- domContentLoaded: nav.domContentLoadedEventEnd - nav.fetchStart,
68
- load: nav.loadEventEnd - nav.fetchStart,
69
- firstByte: nav.responseStart - nav.requestStart,
70
- redirectCount: nav.redirectCount
71
- });
72
- }
73
- }
74
- }
75
-
76
- startTimer(name, context = {}) {
77
- if (!this.enabled) return;
78
-
79
- this.timers.set(name, {
80
- startTime: performance.now(),
81
- context
82
- });
83
- }
84
-
85
- stopTimer(name) {
86
- if (!this.enabled) return null;
87
-
88
- const timer = this.timers.get(name);
89
- if (!timer) return null;
90
-
91
- const duration = performance.now() - timer.startTime;
92
- this.timers.delete(name);
93
-
94
- this._recordMetric('timer', {
95
- name,
96
- duration,
97
- ...timer.context
98
- });
99
-
100
- return duration;
101
- }
102
-
103
- _recordMetric(type, data) {
104
- if (!this.enabled) return;
105
-
106
- const metric = {
107
- type,
108
- timestamp: Date.now(),
109
- ...data
110
- };
111
-
112
- if (!this.metrics.has(type)) {
113
- this.metrics.set(type, []);
114
- }
115
- this.metrics.get(type).push(metric);
116
- }
117
-
118
- measure(fn, name, context = {}) {
119
- if (!this.enabled) return fn();
120
-
121
- this.startTimer(name, context);
122
-
123
- let result;
124
- let error;
125
-
126
- try {
127
- result = fn();
128
- } catch (e) {
129
- error = e;
130
- }
131
-
132
- const duration = this.stopTimer(name);
133
-
134
- if (error) {
135
- throw error;
136
- }
137
-
138
- return result;
139
- }
140
-
141
- measureAsync(fn, name, context = {}) {
142
- if (!this.enabled) return fn();
143
-
144
- this.startTimer(name, context);
145
-
146
- return fn().then(result => {
147
- this.stopTimer(name);
148
- return result;
149
- }).catch(error => {
150
- this.stopTimer(name);
151
- throw error;
152
- });
153
- }
154
-
155
- getMetrics(type) {
156
- if (type) {
157
- return this.metrics.get(type) || [];
158
- }
159
- return Array.from(this.metrics.entries());
160
- }
161
-
162
- getSummary() {
163
- const summary = {
164
- navigation: null,
165
- paint: null,
166
- timers: [],
167
- resources: [],
168
- longTasks: []
169
- };
170
-
171
- if (this.metrics.has('navigation')) {
172
- summary.navigation = this.metrics.get('navigation')[0];
173
- }
174
-
175
- if (this.metrics.has('paint')) {
176
- summary.paint = this.metrics.get('paint');
177
- }
178
-
179
- if (this.metrics.has('timer')) {
180
- summary.timers = this.metrics.get('timer');
181
- }
182
-
183
- if (this.metrics.has('resource')) {
184
- summary.resources = this.metrics.get('resource');
185
- }
186
-
187
- if (this.metrics.has('longtask')) {
188
- summary.longTasks = this.metrics.get('longtask');
189
- }
190
-
191
- return summary;
192
- }
193
-
194
- getPerformanceScore() {
195
- const summary = this.getSummary();
196
- let score = 100;
197
-
198
- if (summary.navigation) {
199
- if (summary.navigation.load > 3000) score -= 20;
200
- else if (summary.navigation.load > 2000) score -= 10;
201
-
202
- if (summary.navigation.domContentLoaded > 1500) score -= 15;
203
- else if (summary.navigation.domContentLoaded > 1000) score -= 5;
204
- }
205
-
206
- if (summary.paint) {
207
- const fcp = summary.paint.find(p => p.name === 'first-contentful-paint');
208
- if (fcp && fcp.startTime > 2000) score -= 20;
209
- else if (fcp && fcp.startTime > 1000) score -= 10;
210
- }
211
-
212
- if (summary.longTasks.length > 0) {
213
- score -= summary.longTasks.length * 10;
214
- }
215
-
216
- return Math.max(0, score);
217
- }
218
-
219
- logSummary() {
220
- const summary = this.getSummary();
221
- const score = this.getPerformanceScore();
222
-
223
- console.group('[KupolaPerformance] Summary');
224
- console.log('Score:', score);
225
- console.log('Navigation:', summary.navigation);
226
- console.log('Paint:', summary.paint);
227
- console.log('Long Tasks:', summary.longTasks.length);
228
- console.groupEnd();
229
- }
230
-
231
- clear() {
232
- this.metrics.clear();
233
- this.timers.clear();
234
- }
235
-
236
- destroy() {
237
- this.observers.forEach(observer => {
238
- observer.disconnect();
239
- });
240
- this.observers = [];
241
- this.clear();
242
- this.enabled = false;
243
- }
244
- }
245
-
246
- const kupolaPerformance = new KupolaPerformance();
247
- window.kupolaPerformance = kupolaPerformance;
248
- window.KupolaPerformance = KupolaPerformance;
249
-
250
- export default KupolaPerformance;
package/js/router.js DELETED
@@ -1,396 +0,0 @@
1
- class KupolaRouter {
2
- constructor(options = {}) {
3
- this.mode = options.mode || 'hash';
4
- this.routes = options.routes || [];
5
- this.base = options.base || '';
6
- this.currentRoute = null;
7
- this.beforeEachHandlers = [];
8
- this.afterEachHandlers = [];
9
- this.isStarted = false;
10
- this.animationClass = 'fade';
11
- this.componentCache = new Map();
12
- this._routeHandler = () => this._handleRoute();
13
-
14
- this._setupRoutes();
15
- this._bindEvents();
16
- }
17
-
18
- _setupRoutes() {
19
- this.routeMap = new Map();
20
- this._registerRoutes(this.routes, '');
21
- }
22
-
23
- _registerRoutes(routes, parentPath) {
24
- routes.forEach(route => {
25
- const fullPath = route.path.startsWith('/')
26
- ? route.path
27
- : parentPath + (parentPath && !route.path.startsWith('/') ? '/' : '') + route.path;
28
- const pathRegex = this._pathToRegex(fullPath);
29
- this.routeMap.set(fullPath, { ...route, regex: pathRegex, parentPath });
30
-
31
- if (route.children && route.children.length > 0) {
32
- this._registerRoutes(route.children, fullPath);
33
- }
34
- });
35
- }
36
-
37
- _pathToRegex(path) {
38
- const regexStr = path
39
- .replace(/:(\w+)/g, '([^/]+)')
40
- .replace(/\*/g, '.*');
41
- return new RegExp(`^${regexStr}$`);
42
- }
43
-
44
- _bindEvents() {
45
- if (this.mode === 'hash') {
46
- window.addEventListener('hashchange', this._routeHandler);
47
- } else {
48
- window.addEventListener('popstate', this._routeHandler);
49
- }
50
- }
51
-
52
- _handleRoute() {
53
- const path = this._getCurrentPath();
54
- this.navigate(path);
55
- }
56
-
57
- _getCurrentPath() {
58
- if (this.mode === 'hash') {
59
- return location.hash.slice(1) || '/';
60
- }
61
- return location.pathname.replace(this.base, '') || '/';
62
- }
63
-
64
- async navigate(path, replace = false) {
65
- const route = this._matchRoute(path);
66
- if (!route) return;
67
-
68
- const from = this.currentRoute;
69
-
70
- for (const handler of this.beforeEachHandlers) {
71
- const result = await handler(route, from);
72
- if (result === false) return;
73
- }
74
-
75
- this.currentRoute = route;
76
- this._updateURL(path, replace);
77
- await this._renderRoute(route);
78
-
79
- for (const handler of this.afterEachHandlers) {
80
- handler(route);
81
- }
82
- }
83
-
84
- _matchRoute(path) {
85
- for (const [, route] of this.routeMap) {
86
- const match = path.match(route.regex);
87
- if (match) {
88
- const params = {};
89
- const paramNames = route.path.match(/:(\w+)/g) || [];
90
- paramNames.forEach((name, index) => {
91
- params[name.slice(1)] = match[index + 1];
92
- });
93
-
94
- const query = this._parseQuery();
95
-
96
- return {
97
- ...route,
98
- params,
99
- query,
100
- fullPath: path
101
- };
102
- }
103
- }
104
- return null;
105
- }
106
-
107
- _parseQuery() {
108
- const search = location.search.slice(1);
109
- const params = {};
110
- if (search) {
111
- search.split('&').forEach(pair => {
112
- const [key, value] = pair.split('=');
113
- params[key] = decodeURIComponent(value || '');
114
- });
115
- }
116
- return params;
117
- }
118
-
119
- _updateURL(path, replace) {
120
- if (this.mode === 'hash') {
121
- const url = `#${path}`;
122
- if (replace) {
123
- location.replace(url);
124
- } else {
125
- location.hash = path;
126
- }
127
- } else {
128
- const url = this.base + path + location.search;
129
- if (replace) {
130
- history.replaceState({}, '', url);
131
- } else {
132
- history.pushState({}, '', url);
133
- }
134
- }
135
- }
136
-
137
- async _renderRoute(route) {
138
- if (route.parentPath && route.parentPath !== '') {
139
- await this._renderNestedRoute(route);
140
- } else {
141
- await this._renderRootRoute(route);
142
- }
143
- }
144
-
145
- async _renderRootRoute(route) {
146
- const container = document.querySelector('[data-router-view]');
147
- if (!container || !route.component) return;
148
-
149
- this._cleanupContainerComponents(container);
150
-
151
- container.classList.add('router-view-leave');
152
- container.classList.remove('router-view-enter');
153
-
154
- await this._waitForAnimation(container);
155
-
156
- const componentName = await this._resolveComponent(route.component);
157
- if (!componentName) return;
158
-
159
- const componentElement = document.createElement('div');
160
- componentElement.setAttribute('data-component', componentName);
161
- componentElement.classList.add('router-view-enter');
162
-
163
- Object.keys(route.params).forEach(key => {
164
- componentElement.setAttribute(`data-prop-${key}`, route.params[key]);
165
- });
166
-
167
- container.innerHTML = '';
168
- container.appendChild(componentElement);
169
-
170
- if (window.kupolaRegistry) {
171
- await window.kupolaRegistry.bootstrap(container);
172
- }
173
-
174
- setTimeout(() => {
175
- container.classList.remove('router-view-leave');
176
- componentElement.classList.remove('router-view-enter');
177
- }, 10);
178
- }
179
-
180
- async _renderNestedRoute(route) {
181
- const parentRoute = this.routeMap.get(route.parentPath);
182
- if (!parentRoute) return;
183
-
184
- const rootContainer = document.querySelector('[data-router-view]');
185
- if (!rootContainer) return;
186
-
187
- const existingParent = document.querySelector(`[data-component="${parentRoute.component}"]`);
188
-
189
- if (!existingParent) {
190
- this._cleanupContainerComponents(rootContainer);
191
-
192
- rootContainer.classList.add('router-view-leave');
193
- rootContainer.classList.remove('router-view-enter');
194
-
195
- await this._waitForAnimation(rootContainer);
196
-
197
- const parentName = await this._resolveComponent(parentRoute.component);
198
- if (!parentName) return;
199
-
200
- const parentElement = document.createElement('div');
201
- parentElement.setAttribute('data-component', parentName);
202
-
203
- rootContainer.innerHTML = '';
204
- rootContainer.appendChild(parentElement);
205
-
206
- if (window.kupolaRegistry) {
207
- await window.kupolaRegistry.bootstrap(rootContainer);
208
- }
209
-
210
- setTimeout(() => {
211
- rootContainer.classList.remove('router-view-leave');
212
- }, 50);
213
-
214
- await this._renderChildComponent(route);
215
- } else {
216
- await this._renderChildComponent(route);
217
- }
218
- }
219
-
220
- async _renderChildComponent(route) {
221
- const parentElement = document.querySelector(`[data-component="${this._getParentComponent(route.parentPath)}"]`);
222
- if (!parentElement) return;
223
-
224
- const container = parentElement.querySelector('[data-router-view]');
225
- if (!container || !route.component) return;
226
-
227
- const componentName = await this._resolveComponent(route.component);
228
- if (!componentName) return;
229
-
230
- const componentElement = document.createElement('div');
231
- componentElement.setAttribute('data-component', componentName);
232
- componentElement.classList.add('router-view-enter');
233
-
234
- Object.keys(route.params).forEach(key => {
235
- componentElement.setAttribute(`data-prop-${key}`, route.params[key]);
236
- });
237
-
238
- container.innerHTML = '';
239
- container.appendChild(componentElement);
240
-
241
- if (window.kupolaRegistry) {
242
- await window.kupolaRegistry.bootstrap(container);
243
- }
244
-
245
- setTimeout(() => {
246
- componentElement.classList.remove('router-view-enter');
247
- }, 10);
248
- }
249
-
250
- async _resolveComponent(component) {
251
- if (typeof component === 'function') {
252
- const cacheKey = component.toString();
253
- if (this.componentCache.has(cacheKey)) {
254
- return this.componentCache.get(cacheKey);
255
- }
256
-
257
- try {
258
- const result = await component();
259
- const componentClass = result.default || result;
260
-
261
- const componentName = `lazy-${Math.random().toString(36).substr(2, 9)}`;
262
-
263
- if (window.kupolaRegistry) {
264
- window.kupolaRegistry.register(componentName, componentClass);
265
- }
266
-
267
- this.componentCache.set(cacheKey, componentName);
268
- return componentName;
269
- } catch (e) {
270
- console.error('Failed to load lazy component:', e);
271
- return null;
272
- }
273
- }
274
-
275
- if (typeof component === 'string' && component.startsWith('lazy:')) {
276
- const componentName = component.slice(5);
277
- if (window.kupolaRegistry) {
278
- try {
279
- await window.kupolaRegistry.getAsync(componentName);
280
- } catch (e) {
281
- console.error(`Failed to load lazy component ${componentName}:`, e);
282
- return null;
283
- }
284
- }
285
- return componentName;
286
- }
287
-
288
- return component;
289
- }
290
-
291
- _getParentComponent(parentPath) {
292
- for (const [path, route] of this.routeMap) {
293
- if (path === parentPath) {
294
- return route.component;
295
- }
296
- }
297
- return null;
298
- }
299
-
300
- setAnimation(name) {
301
- this.animationClass = name;
302
- }
303
-
304
- push(path) {
305
- this.navigate(path, false);
306
- }
307
-
308
- replace(path) {
309
- this.navigate(path, true);
310
- }
311
-
312
- go(delta) {
313
- history.go(delta);
314
- }
315
-
316
- back() {
317
- history.back();
318
- }
319
-
320
- forward() {
321
- history.forward();
322
- }
323
-
324
- beforeEach(handler) {
325
- this.beforeEachHandlers.push(handler);
326
- }
327
-
328
- afterEach(handler) {
329
- this.afterEachHandlers.push(handler);
330
- }
331
-
332
- _waitForAnimation(element) {
333
- return new Promise(resolve => {
334
- const transitionDuration = this._getTransitionDuration(element);
335
- if (transitionDuration > 0) {
336
- const cleanup = () => {
337
- element.removeEventListener('transitionend', cleanup);
338
- element.removeEventListener('animationend', cleanup);
339
- resolve();
340
- };
341
- element.addEventListener('transitionend', cleanup);
342
- element.addEventListener('animationend', cleanup);
343
- setTimeout(cleanup, transitionDuration + 100);
344
- } else {
345
- resolve();
346
- }
347
- });
348
- }
349
-
350
- _getTransitionDuration(element) {
351
- const style = window.getComputedStyle(element);
352
- const transitionDuration = parseFloat(style.transitionDuration) || 0;
353
- const animationDuration = parseFloat(style.animationDuration) || 0;
354
- return Math.max(transitionDuration, animationDuration) * 1000;
355
- }
356
-
357
- _cleanupContainerComponents(container) {
358
- const components = container.querySelectorAll('[data-component]');
359
- components.forEach(element => {
360
- const instance = element.__kupolaInstance;
361
- if (instance && typeof instance.unmount === 'function') {
362
- instance.unmount().catch(e => console.error(`Error unmounting component:`, e));
363
- }
364
- });
365
-
366
- if (window.kupolaInitializer) {
367
- const allElements = container.querySelectorAll('[data-component], [data-dropdown], [data-select], [data-datepicker], [data-timepicker], [data-slider], [data-carousel], [data-drawer], [data-modal], [data-dialog], [data-color-picker], [data-calendar], [data-slide-captcha], [data-heatmap]');
368
- allElements.forEach(element => {
369
- window.kupolaInitializer.cleanup(element);
370
- });
371
- }
372
- }
373
-
374
- destroy() {
375
- if (this.mode === 'hash') {
376
- window.removeEventListener('hashchange', this._routeHandler);
377
- } else {
378
- window.removeEventListener('popstate', this._routeHandler);
379
- }
380
- this.routes = [];
381
- this.routeMap.clear();
382
- this.beforeEachHandlers = [];
383
- this.afterEachHandlers = [];
384
- }
385
- }
386
-
387
- function createRouter(options) {
388
- return new KupolaRouter(options);
389
- }
390
-
391
- if (typeof module !== 'undefined' && module.exports) {
392
- module.exports = { KupolaRouter, createRouter };
393
- } else {
394
- window.KupolaRouter = KupolaRouter;
395
- window.createRouter = createRouter;
396
- }