@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
@@ -7,6 +7,12 @@ class ImagePreview {
7
7
  this.keyHandler = this.handleKeydown.bind(this);
8
8
  this.clickHandler = this.handleOverlayClick.bind(this);
9
9
 
10
+ this.zoom = 1;
11
+ this.rotation = 0;
12
+ this.zoomStep = options.zoomStep || 0.2;
13
+ this.minZoom = options.minZoom || 0.5;
14
+ this.maxZoom = options.maxZoom || 3;
15
+
10
16
  this.init();
11
17
  }
12
18
 
@@ -36,6 +42,38 @@ class ImagePreview {
36
42
  </svg>
37
43
  </button>
38
44
  </div>
45
+ <div class="ds-image-preview__toolbar">
46
+ <button class="ds-image-preview__toolbar-btn" type="button" aria-label="Zoom in" data-action="zoom-in">
47
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
48
+ <line x1="12" y1="5" x2="12" y2="19"/>
49
+ <line x1="5" y1="12" x2="19" y2="12"/>
50
+ </svg>
51
+ </button>
52
+ <button class="ds-image-preview__toolbar-btn" type="button" aria-label="Zoom out" data-action="zoom-out">
53
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
54
+ <line x1="5" y1="12" x2="19" y2="12"/>
55
+ </svg>
56
+ </button>
57
+ <button class="ds-image-preview__toolbar-btn" type="button" aria-label="Reset zoom" data-action="zoom-reset">
58
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
59
+ <path d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/>
60
+ <line x1="12" y1="5" x2="12" y2="19"/>
61
+ <line x1="5" y1="12" x2="19" y2="12"/>
62
+ </svg>
63
+ </button>
64
+ <button class="ds-image-preview__toolbar-btn" type="button" aria-label="Rotate left" data-action="rotate-left">
65
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
66
+ <polyline points="1 4 1 10 7 10"/>
67
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
68
+ </svg>
69
+ </button>
70
+ <button class="ds-image-preview__toolbar-btn" type="button" aria-label="Rotate right" data-action="rotate-right">
71
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
72
+ <polyline points="23 4 23 10 17 10"/>
73
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
74
+ </svg>
75
+ </button>
76
+ </div>
39
77
  <div class="ds-image-preview__content">
40
78
  <img src="" alt="" />
41
79
  </div>
@@ -62,6 +100,82 @@ class ImagePreview {
62
100
  closeBtn.addEventListener('click', this.closeHandler);
63
101
  prevBtn.addEventListener('click', this._prevHandler);
64
102
  nextBtn.addEventListener('click', this._nextHandler);
103
+
104
+ const toolbarBtns = this.overlay.querySelectorAll('.ds-image-preview__toolbar-btn');
105
+ toolbarBtns.forEach(btn => {
106
+ btn.addEventListener('click', (e) => {
107
+ const action = btn.getAttribute('data-action');
108
+ this.handleToolbarAction(action);
109
+ });
110
+ });
111
+
112
+ const content = this.overlay.querySelector('.ds-image-preview__content');
113
+ content.addEventListener('wheel', (e) => {
114
+ e.preventDefault();
115
+ if (e.deltaY < 0) {
116
+ this.zoomIn();
117
+ } else {
118
+ this.zoomOut();
119
+ }
120
+ }, { passive: false });
121
+ }
122
+
123
+ handleToolbarAction(action) {
124
+ switch (action) {
125
+ case 'zoom-in':
126
+ this.zoomIn();
127
+ break;
128
+ case 'zoom-out':
129
+ this.zoomOut();
130
+ break;
131
+ case 'zoom-reset':
132
+ this.resetZoom();
133
+ break;
134
+ case 'rotate-left':
135
+ this.rotate(-90);
136
+ break;
137
+ case 'rotate-right':
138
+ this.rotate(90);
139
+ break;
140
+ }
141
+ }
142
+
143
+ zoomIn() {
144
+ this.zoom = Math.min(this.maxZoom, this.zoom + this.zoomStep);
145
+ this.updateTransform();
146
+ }
147
+
148
+ zoomOut() {
149
+ this.zoom = Math.max(this.minZoom, this.zoom - this.zoomStep);
150
+ this.updateTransform();
151
+ }
152
+
153
+ resetZoom() {
154
+ this.zoom = 1;
155
+ this.rotation = 0;
156
+ this.updateTransform();
157
+ }
158
+
159
+ rotate(degrees) {
160
+ this.rotation += degrees;
161
+ this.updateTransform();
162
+ }
163
+
164
+ setRotation(degrees) {
165
+ this.rotation = degrees;
166
+ this.updateTransform();
167
+ }
168
+
169
+ setZoom(zoom) {
170
+ this.zoom = Math.max(this.minZoom, Math.min(this.maxZoom, zoom));
171
+ this.updateTransform();
172
+ }
173
+
174
+ updateTransform() {
175
+ const img = this.overlay.querySelector('.ds-image-preview__content img');
176
+ if (img) {
177
+ img.style.transform = `scale(${this.zoom}) rotate(${this.rotation}deg)`;
178
+ }
65
179
  }
66
180
 
67
181
  handleKeydown(e) {
@@ -77,6 +191,28 @@ class ImagePreview {
77
191
  case 'ArrowRight':
78
192
  this.next();
79
193
  break;
194
+ case '+':
195
+ case '=':
196
+ e.preventDefault();
197
+ this.zoomIn();
198
+ break;
199
+ case '-':
200
+ case '_':
201
+ e.preventDefault();
202
+ this.zoomOut();
203
+ break;
204
+ case '0':
205
+ e.preventDefault();
206
+ this.resetZoom();
207
+ break;
208
+ case '[':
209
+ e.preventDefault();
210
+ this.rotate(-90);
211
+ break;
212
+ case ']':
213
+ e.preventDefault();
214
+ this.rotate(90);
215
+ break;
80
216
  }
81
217
  }
82
218
 
@@ -90,6 +226,7 @@ class ImagePreview {
90
226
  this.images = images;
91
227
  this.currentIndex = Math.min(Math.max(index, 0), images.length - 1);
92
228
 
229
+ this.resetZoom();
93
230
  this.render();
94
231
  this.overlay.classList.add('is-visible');
95
232
 
@@ -109,6 +246,7 @@ class ImagePreview {
109
246
  prev() {
110
247
  if (this.currentIndex > 0) {
111
248
  this.currentIndex--;
249
+ this.resetZoom();
112
250
  this.render();
113
251
  }
114
252
  }
@@ -116,6 +254,7 @@ class ImagePreview {
116
254
  next() {
117
255
  if (this.currentIndex < this.images.length - 1) {
118
256
  this.currentIndex++;
257
+ this.resetZoom();
119
258
  this.render();
120
259
  }
121
260
  }
@@ -123,6 +262,7 @@ class ImagePreview {
123
262
  goTo(index) {
124
263
  if (index >= 0 && index < this.images.length) {
125
264
  this.currentIndex = index;
265
+ this.resetZoom();
126
266
  this.render();
127
267
  }
128
268
  }
@@ -213,6 +353,10 @@ function showImagePreview(images, index = 0) {
213
353
  imagePreviewInstance.show(images, index);
214
354
  }
215
355
 
216
- if (typeof module !== 'undefined' && module.exports) {
217
- module.exports = { ImagePreview, initImagePreview, showImagePreview };
356
+ export { ImagePreview, initImagePreview, showImagePreview };
357
+
358
+ if (typeof window !== 'undefined') {
359
+ window.ImagePreview = ImagePreview;
360
+ window.initImagePreview = initImagePreview;
361
+ window.showImagePreview = showImagePreview;
218
362
  }
package/js/initializer.js CHANGED
@@ -1,15 +1,40 @@
1
+ /**
2
+ * Component Initializer Registry — auto-discovery and lifecycle management for declarative components.
3
+ * Scans the DOM for `data-*` attributes or CSS classes, calls init functions,
4
+ * and uses MutationObserver for dynamically added elements.
5
+ */
1
6
  class ComponentInitializerRegistry {
2
7
  constructor() {
3
8
  this.initializers = new Map();
4
9
  this.cleanupFunctions = new Map();
5
10
  this.processedElements = new WeakSet();
11
+ // Single source of truth for selectors
12
+ this._dataAttrs = ['data-component'];
13
+ this._cssClasses = [];
14
+ this._cachedSelector = null;
6
15
  }
7
16
 
8
- register(name, initFn, cleanupFn = null) {
17
+ /**
18
+ * Register a component initializer.
19
+ * @param {string} name - Component name
20
+ * @param {Function} initFn - Init function called with the DOM element
21
+ * @param {Function} [cleanupFn] - Optional cleanup/destroy function
22
+ * @param {Object} [options] - { dataAttribute, cssClass } for auto-discovery
23
+ */
24
+ register(name, initFn, cleanupFn = null, options = {}) {
9
25
  this.initializers.set(name, initFn);
10
26
  if (cleanupFn) {
11
27
  this.cleanupFunctions.set(name, cleanupFn);
12
28
  }
29
+ // Auto-register selectors
30
+ if (options.dataAttribute && !this._dataAttrs.includes(options.dataAttribute)) {
31
+ this._dataAttrs.push(options.dataAttribute);
32
+ this._cachedSelector = null;
33
+ }
34
+ if (options.cssClass && !this._cssClasses.includes(options.cssClass)) {
35
+ this._cssClasses.push(options.cssClass);
36
+ this._cachedSelector = null;
37
+ }
13
38
  }
14
39
 
15
40
  unregister(name) {
@@ -25,20 +50,26 @@ class ComponentInitializerRegistry {
25
50
  return this.initializers.get(name);
26
51
  }
27
52
 
53
+ _buildSelector() {
54
+ if (this._cachedSelector !== null) return this._cachedSelector;
55
+ const parts = this._dataAttrs.map(a => `[${a}]`);
56
+ for (const cls of this._cssClasses) {
57
+ parts.push(`.${cls}`);
58
+ }
59
+ this._cachedSelector = parts.join(', ');
60
+ return this._cachedSelector;
61
+ }
62
+
63
+ /** Initialize a single DOM element if it matches a registered component. */
28
64
  async initialize(element) {
29
65
  if (this.processedElements.has(element)) return;
30
66
 
31
- const dataAttrs = ['data-component', 'data-dropdown', 'data-select', 'data-datepicker',
32
- 'data-timepicker', 'data-slider', 'data-carousel', 'data-drawer',
33
- 'data-modal', 'data-dialog', 'data-color-picker', 'data-calendar',
34
- 'data-slide-captcha', 'data-heatmap'];
35
-
36
- for (const attr of dataAttrs) {
67
+ // Check data attributes
68
+ for (const attr of this._dataAttrs) {
37
69
  const value = element.getAttribute(attr);
38
- if (value) {
70
+ if (value !== null) {
39
71
  const name = value || attr.replace('data-', '');
40
72
  const initFn = this.initializers.get(name) || this.initializers.get(attr.replace('data-', ''));
41
-
42
73
  if (initFn) {
43
74
  try {
44
75
  await initFn(element);
@@ -51,46 +82,35 @@ class ComponentInitializerRegistry {
51
82
  }
52
83
  }
53
84
 
85
+ // Check CSS classes
54
86
  const className = element.className;
55
- const classSelectors = [
56
- '.ds-dropdown', '.ds-select', '.ds-datepicker', '.ds-timepicker',
57
- '.ds-slider', '.ds-carousel', '.ds-drawer', '.ds-modal', '.ds-dialog',
58
- '.ds-color-picker', '.ds-calendar', '.ds-slider-captcha', '.ds-heatmap',
59
- '.ds-tooltip', '.ds-tag', '.ds-statcard', '.ds-collapse', '.ds-fileupload',
60
- '.ds-notification', '.ds-message'
61
- ];
62
-
63
- for (const selector of classSelectors) {
64
- const classNamePart = selector.replace('.', '');
65
- if (className.includes(classNamePart)) {
66
- const name = classNamePart.replace('ds-', '');
67
- const initFn = this.initializers.get(name) || this.initializers.get(classNamePart);
68
-
69
- if (initFn) {
70
- try {
71
- await initFn(element);
72
- this.processedElements.add(element);
73
- } catch (error) {
74
- console.error(`[ComponentInitializerRegistry] Error initializing "${name}":`, error);
87
+ if (typeof className === 'string') {
88
+ for (const cls of this._cssClasses) {
89
+ if (className.includes(cls)) {
90
+ const name = cls.replace('ds-', '');
91
+ const initFn = this.initializers.get(name) || this.initializers.get(cls);
92
+ if (initFn) {
93
+ try {
94
+ await initFn(element);
95
+ this.processedElements.add(element);
96
+ } catch (error) {
97
+ console.error(`[ComponentInitializerRegistry] Error initializing "${name}":`, error);
98
+ }
99
+ return;
75
100
  }
76
- return;
77
101
  }
78
102
  }
79
103
  }
80
104
  }
81
105
 
106
+ /** Run cleanup/destroy for a single DOM element. */
82
107
  cleanup(element) {
83
- const dataAttrs = ['data-component', 'data-dropdown', 'data-select', 'data-datepicker',
84
- 'data-timepicker', 'data-slider', 'data-carousel', 'data-drawer',
85
- 'data-modal', 'data-dialog', 'data-color-picker', 'data-calendar',
86
- 'data-slide-captcha', 'data-heatmap'];
87
-
88
- for (const attr of dataAttrs) {
108
+ // Check data attributes
109
+ for (const attr of this._dataAttrs) {
89
110
  const value = element.getAttribute(attr);
90
- if (value) {
111
+ if (value !== null) {
91
112
  const name = value || attr.replace('data-', '');
92
113
  const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(attr.replace('data-', ''));
93
-
94
114
  if (cleanupFn) {
95
115
  try {
96
116
  cleanupFn(element);
@@ -103,45 +123,33 @@ class ComponentInitializerRegistry {
103
123
  }
104
124
  }
105
125
 
126
+ // Check CSS classes
106
127
  const className = element.className;
107
- const classSelectors = [
108
- '.ds-dropdown', '.ds-select', '.ds-datepicker', '.ds-timepicker',
109
- '.ds-slider', '.ds-carousel', '.ds-drawer', '.ds-modal', '.ds-dialog',
110
- '.ds-color-picker', '.ds-calendar', '.ds-slider-captcha', '.ds-heatmap',
111
- '.ds-tooltip', '.ds-tag', '.ds-statcard', '.ds-collapse', '.ds-fileupload',
112
- '.ds-notification', '.ds-message'
113
- ];
114
-
115
- for (const selector of classSelectors) {
116
- const classNamePart = selector.replace('.', '');
117
- if (className.includes(classNamePart)) {
118
- const name = classNamePart.replace('ds-', '');
119
- const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(classNamePart);
120
-
121
- if (cleanupFn) {
122
- try {
123
- cleanupFn(element);
124
- } catch (error) {
125
- console.error(`[ComponentInitializerRegistry] Error cleaning up "${name}":`, error);
128
+ if (typeof className === 'string') {
129
+ for (const cls of this._cssClasses) {
130
+ if (className.includes(cls)) {
131
+ const name = cls.replace('ds-', '');
132
+ const cleanupFn = this.cleanupFunctions.get(name) || this.cleanupFunctions.get(cls);
133
+ if (cleanupFn) {
134
+ try {
135
+ cleanupFn(element);
136
+ } catch (error) {
137
+ console.error(`[ComponentInitializerRegistry] Error cleaning up "${name}":`, error);
138
+ }
139
+ this.processedElements.delete(element);
140
+ return;
126
141
  }
127
- this.processedElements.delete(element);
128
- return;
129
142
  }
130
143
  }
131
144
  }
132
145
  }
133
146
 
147
+ /** Scan and initialize all matching components. Uses MutationObserver for dynamic content. */
134
148
  async initializeAll(root = document) {
135
- const elements = root.querySelectorAll('[data-component], [data-dropdown], [data-select], ' +
136
- '[data-datepicker], [data-timepicker], [data-slider], [data-carousel], ' +
137
- '[data-drawer], [data-modal], [data-dialog], [data-color-picker], ' +
138
- '[data-calendar], [data-slide-captcha], [data-heatmap], ' +
139
- '.ds-dropdown, .ds-select, .ds-datepicker, .ds-timepicker, ' +
140
- '.ds-slider, .ds-carousel, .ds-drawer, .ds-modal, .ds-dialog, ' +
141
- '.ds-color-picker, .ds-calendar, .ds-slider-captcha, .ds-heatmap, ' +
142
- '.ds-tooltip, .ds-tag, .ds-statcard, .ds-collapse, .ds-fileupload, ' +
143
- '.ds-notification, .ds-message');
149
+ const selector = this._buildSelector();
150
+ if (!selector) return;
144
151
 
152
+ const elements = root.querySelectorAll(selector);
145
153
  const promises = [];
146
154
  elements.forEach(element => {
147
155
  if (!this.processedElements.has(element)) {
@@ -152,11 +160,83 @@ class ComponentInitializerRegistry {
152
160
  }
153
161
  }
154
162
 
163
+ /** @type {ComponentInitializerRegistry} Global initializer instance */
155
164
  const kupolaInitializer = new ComponentInitializerRegistry();
156
165
 
157
- if (typeof module !== 'undefined' && module.exports) {
158
- module.exports = { ComponentInitializerRegistry, kupolaInitializer };
159
- } else {
166
+ // Pre-register known component selectors
167
+ const knownComponents = [
168
+ { attr: 'data-dropdown', cls: 'ds-dropdown' },
169
+ { attr: 'data-select', cls: 'ds-select' },
170
+ { attr: 'data-datepicker', cls: 'ds-datepicker' },
171
+ { attr: 'data-timepicker', cls: 'ds-timepicker' },
172
+ { attr: 'data-slider', cls: 'ds-slider' },
173
+ { attr: 'data-carousel', cls: 'ds-carousel' },
174
+ { attr: 'data-drawer', cls: 'ds-drawer' },
175
+ { attr: 'data-modal', cls: 'ds-modal' },
176
+ { attr: 'data-dialog', cls: 'ds-dialog' },
177
+ { attr: 'data-color-picker', cls: 'ds-color-picker' },
178
+ { attr: 'data-calendar', cls: 'ds-calendar' },
179
+ { attr: 'data-slide-captcha', cls: 'ds-slide-captcha' },
180
+ { attr: 'data-heatmap', cls: 'ds-heatmap' },
181
+ { cls: 'ds-tooltip' },
182
+ { cls: 'ds-tag' },
183
+ { cls: 'ds-statcard' },
184
+ { cls: 'ds-collapse' },
185
+ { cls: 'ds-fileupload' },
186
+ { cls: 'ds-notification' },
187
+ { cls: 'ds-message' }
188
+ ];
189
+
190
+ for (const comp of knownComponents) {
191
+ if (comp.attr && !kupolaInitializer._dataAttrs.includes(comp.attr)) {
192
+ kupolaInitializer._dataAttrs.push(comp.attr);
193
+ }
194
+ if (comp.cls && !kupolaInitializer._cssClasses.includes(comp.cls)) {
195
+ kupolaInitializer._cssClasses.push(comp.cls);
196
+ }
197
+ }
198
+
199
+ export { ComponentInitializerRegistry, kupolaInitializer };
200
+
201
+ /**
202
+ * Kupola global namespace — convenience API for external consumers.
203
+ * Provides Kupola.refresh(root) to re-initialize dynamically inserted DOM.
204
+ */
205
+ if (typeof window !== 'undefined') {
206
+ window.Kupola = window.Kupola || {};
207
+
208
+ /**
209
+ * Re-scan and initialize components within a given scope.
210
+ * @param {Element|Document} [root=document] - Scope to scan
211
+ * @returns {Promise<void>}
212
+ */
213
+ window.Kupola.refresh = async function refresh(root) {
214
+ return kupolaInitializer.initializeAll(root || document);
215
+ };
216
+
217
+ /**
218
+ * Initialize a single element or container.
219
+ * @param {Element} element
220
+ * @returns {Promise<void>}
221
+ */
222
+ window.Kupola.init = async function init(element) {
223
+ return kupolaInitializer.initialize(element);
224
+ };
225
+
226
+ /**
227
+ * Cleanup a single element.
228
+ * @param {Element} element
229
+ */
230
+ window.Kupola.cleanup = function cleanup(element) {
231
+ return kupolaInitializer.cleanup(element);
232
+ };
233
+
234
+ /** Expose the initializer registry directly */
235
+ window.Kupola.initializer = kupolaInitializer;
236
+ window.Kupola.ComponentInitializerRegistry = ComponentInitializerRegistry;
237
+ }
238
+
239
+ if (typeof window !== 'undefined') {
160
240
  window.ComponentInitializerRegistry = ComponentInitializerRegistry;
161
241
  window.kupolaInitializer = kupolaInitializer;
162
242
  }