@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/kupola-core.js CHANGED
@@ -1,66 +1,144 @@
1
- if (typeof window === 'undefined') {
2
- const { KupolaComponent, applyMixin } = require('./component.js');
3
- const { KupolaComponentRegistry } = require('./registry.js');
4
- const { KupolaRouter, createRouter } = require('./router.js');
5
- const { KupolaHttp, createHttp } = require('./http.js');
6
-
7
- module.exports = {
8
- KupolaComponent,
9
- applyMixin,
10
- KupolaComponentRegistry,
11
- KupolaRouter,
12
- KupolaHttp,
13
- createRouter,
14
- createHttp
15
- };
16
- }
1
+ /**
2
+ * Kupola Core — Component system bootstrap, registry, and lifecycle management.
3
+ * @module kupola-core
4
+ */
5
+
6
+ import { KupolaComponent, applyMixin } from './component.js';
7
+ import { KupolaComponentRegistry } from './registry.js';
8
+ import { KupolaLifecycle } from './kupola-lifecycle.js';
9
+ import { kupolaInitializer } from './initializer.js';
10
+ import { kupolaData } from './data-bind.js';
11
+ import { initTheme } from './theme.js';
12
+
13
+ let kupolaRegistry = null;
17
14
 
18
- if (window.KupolaComponentRegistry) {
19
- window.kupolaRegistry = new window.KupolaComponentRegistry();
15
+ if (typeof window !== 'undefined') {
16
+ kupolaRegistry = new KupolaComponentRegistry();
20
17
  }
21
18
 
19
+ /** Bootstrap the Kupola component system (data binding, theme, component discovery). */
22
20
  async function kupolaBootstrap() {
23
- if (window.kupolaData) {
24
- window.kupolaData.loadPersisted();
25
- }
26
- if (window.kupolaRegistry) {
27
- await window.kupolaRegistry.bootstrap();
21
+ if (typeof window !== 'undefined') {
22
+ // Load persisted data and bind data-bind elements
23
+ kupolaData.loadPersisted();
24
+ kupolaData.bind();
25
+ // Initialize theme
26
+ initTheme();
27
+ // Initialize function-based components first
28
+ await kupolaInitializer.initializeAll();
29
+ // Then bootstrap class-based components
30
+ if (kupolaRegistry) {
31
+ await kupolaRegistry.bootstrap();
32
+ }
28
33
  }
29
34
  }
30
35
 
31
- if (document.readyState === 'loading') {
36
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
32
37
  document.addEventListener('DOMContentLoaded', kupolaBootstrap);
33
- } else {
38
+ } else if (typeof window !== 'undefined') {
34
39
  setTimeout(kupolaBootstrap, 0);
35
40
  }
36
41
 
37
- window.registerComponent = function(name, componentClass) {
38
- if (window.kupolaRegistry) {
39
- window.kupolaRegistry.register(name, componentClass);
42
+ /** Register a class-based component. */
43
+ function registerComponent(name, componentClass) {
44
+ if (kupolaRegistry) {
45
+ kupolaRegistry.register(name, componentClass);
40
46
  }
41
- };
47
+ }
42
48
 
43
- window.registerLazyComponent = function(name, loader) {
44
- if (window.kupolaRegistry) {
45
- window.kupolaRegistry.registerLazy(name, loader);
49
+ /** Register a lazy-loaded component (loaded on first use). */
50
+ function registerLazyComponent(name, loader) {
51
+ if (kupolaRegistry) {
52
+ kupolaRegistry.registerLazy(name, loader);
46
53
  }
47
- };
54
+ }
48
55
 
49
- window.bootstrapComponents = function(root) {
50
- if (window.kupolaRegistry) {
51
- return window.kupolaRegistry.bootstrap(root);
56
+ /** Manually bootstrap components within a specific root element. */
57
+ function bootstrapComponents(root) {
58
+ if (kupolaRegistry) {
59
+ return kupolaRegistry.bootstrap(root);
52
60
  }
53
61
  return Promise.resolve();
54
- };
62
+ }
55
63
 
56
- window.defineMixin = function(name, mixin) {
57
- if (window.kupolaRegistry) {
58
- window.kupolaRegistry.defineMixin(name, mixin);
64
+ /** Define a reusable mixin for component classes. */
65
+ function defineMixin(name, mixin) {
66
+ if (kupolaRegistry) {
67
+ kupolaRegistry.defineMixin(name, mixin);
59
68
  }
60
- };
69
+ }
61
70
 
62
- window.useMixin = function(componentClass, ...mixinNames) {
63
- if (window.kupolaRegistry) {
64
- window.kupolaRegistry.useMixin(componentClass, ...mixinNames);
71
+ /** Apply named mixins to a component class. */
72
+ function useMixin(componentClass, ...mixinNames) {
73
+ if (kupolaRegistry) {
74
+ kupolaRegistry.useMixin(componentClass, ...mixinNames);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Unified component registration API.
80
+ * @param {string} name - Component name
81
+ * @param {object} options
82
+ * @param {Function} [options.init] - Init function (for function-based components)
83
+ * @param {Function} [options.cleanup] - Cleanup function
84
+ * @param {Function} [options.componentClass] - Component class (for class-based components)
85
+ * @param {Function} [options.lazy] - Lazy loader function
86
+ * @param {string} [options.dataAttribute] - Custom data attribute (e.g. 'data-my-component')
87
+ * @param {string} [options.cssClass] - CSS class selector (e.g. 'ds-my-component')
88
+ */
89
+ function defineComponent(name, options) {
90
+ if (!options || typeof options !== 'object') {
91
+ throw new Error(`defineComponent("${name}"): options must be an object`);
92
+ }
93
+ // Register with registry (class or lazy)
94
+ if (options.componentClass) {
95
+ if (kupolaRegistry) {
96
+ kupolaRegistry.register(name, options.componentClass);
97
+ }
98
+ } else if (options.lazy) {
99
+ if (kupolaRegistry) {
100
+ kupolaRegistry.registerLazy(name, options.lazy);
101
+ }
65
102
  }
66
- };
103
+ // Register with initializer (function-based)
104
+ if (options.init) {
105
+ kupolaInitializer.register(name, options.init, options.cleanup || null, {
106
+ dataAttribute: options.dataAttribute,
107
+ cssClass: options.cssClass
108
+ });
109
+ } else if (options.dataAttribute || options.cssClass) {
110
+ // Even without init, register selectors so MutationObserver can find them
111
+ kupolaInitializer.register(name, () => {}, null, {
112
+ dataAttribute: options.dataAttribute,
113
+ cssClass: options.cssClass
114
+ });
115
+ }
116
+ }
117
+
118
+ export {
119
+ KupolaComponent,
120
+ applyMixin,
121
+ KupolaComponentRegistry,
122
+ KupolaLifecycle,
123
+ kupolaRegistry,
124
+ kupolaInitializer,
125
+ kupolaBootstrap,
126
+ registerComponent,
127
+ registerLazyComponent,
128
+ bootstrapComponents,
129
+ defineMixin,
130
+ useMixin,
131
+ defineComponent
132
+ };
133
+
134
+ if (typeof window !== 'undefined') {
135
+ window.kupolaRegistry = kupolaRegistry;
136
+ window.kupolaInitializer = kupolaInitializer;
137
+ window.kupolaBootstrap = kupolaBootstrap;
138
+ window.registerComponent = registerComponent;
139
+ window.registerLazyComponent = registerLazyComponent;
140
+ window.bootstrapComponents = bootstrapComponents;
141
+ window.defineMixin = defineMixin;
142
+ window.useMixin = useMixin;
143
+ window.defineComponent = defineComponent;
144
+ }
@@ -1,4 +1,4 @@
1
- class KupolaLifecycle {
1
+ export class KupolaLifecycle {
2
2
  constructor(scope = 'app') {
3
3
  this.scope = scope;
4
4
  this.hooks = new Map();
@@ -345,7 +345,7 @@ class KupolaLifecycle {
345
345
  }
346
346
  }
347
347
 
348
- class KupolaPluginManager {
348
+ export class KupolaPluginManager {
349
349
  constructor() {
350
350
  this.plugins = new Map();
351
351
  this.enabledPlugins = new Set();
@@ -477,7 +477,7 @@ class KupolaPluginManager {
477
477
  const pluginHooks = this.pluginHooks.get(pluginName);
478
478
 
479
479
  Object.keys(hooks).forEach(phase => {
480
- const unsubscribe = window.kupolaLifecycle?.on(phase, hooks[phase], { name: `${pluginName}:${phase}` });
480
+ const unsubscribe = typeof window !== 'undefined' && window.kupolaLifecycle?.on(phase, hooks[phase], { name: `${pluginName}:${phase}` });
481
481
  if (unsubscribe) {
482
482
  pluginHooks.push({ phase, unsubscribe });
483
483
  }
@@ -496,15 +496,17 @@ class KupolaPluginManager {
496
496
  }
497
497
  }
498
498
 
499
- const kupolaLifecycle = new KupolaLifecycle('app');
500
- const kupolaPluginManager = new KupolaPluginManager();
499
+ export const kupolaLifecycle = new KupolaLifecycle('app');
500
+ export const kupolaPluginManager = new KupolaPluginManager();
501
501
 
502
- function createLifecycle(scope = 'app') {
502
+ export function createLifecycle(scope = 'app') {
503
503
  return new KupolaLifecycle(scope);
504
504
  }
505
505
 
506
- window.KupolaLifecycle = KupolaLifecycle;
507
- window.KupolaPluginManager = KupolaPluginManager;
508
- window.kupolaLifecycle = kupolaLifecycle;
509
- window.kupolaPluginManager = kupolaPluginManager;
510
- window.createLifecycle = createLifecycle;
506
+ if (typeof window !== 'undefined') {
507
+ window.KupolaLifecycle = KupolaLifecycle;
508
+ window.KupolaPluginManager = KupolaPluginManager;
509
+ window.kupolaLifecycle = kupolaLifecycle;
510
+ window.kupolaPluginManager = kupolaPluginManager;
511
+ window.createLifecycle = createLifecycle;
512
+ }
package/js/message.js CHANGED
@@ -57,4 +57,11 @@ const Message = {
57
57
  }
58
58
  };
59
59
 
60
- function initMessages() {}
60
+ function initMessages() {}
61
+
62
+ export { Message, initMessages };
63
+
64
+ if (typeof window !== 'undefined') {
65
+ window.Message = Message;
66
+ window.initMessages = initMessages;
67
+ }
package/js/modal.js CHANGED
@@ -1,12 +1,23 @@
1
1
  class Modal {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.mask = element.querySelector('.ds-modal-mask');
5
5
  this.modal = element.querySelector('.ds-modal');
6
6
  this.closeBtn = element.querySelector('.ds-modal__close');
7
7
 
8
+ // Options
9
+ this.fullscreen = options.fullscreen || element.hasAttribute('data-modal-fullscreen');
10
+ this.closableOnMask = options.closableOnMask !== false; // default true
11
+ this.escClose = options.escClose !== false;
12
+ this.width = options.width || element.getAttribute('data-modal-width') || '';
13
+ this.center = options.center !== false;
14
+ this.onBeforeOpen = options.onBeforeOpen || null;
15
+ this.onBeforeClose = options.onBeforeClose || null;
16
+ this.onOpened = options.onOpened || null;
17
+ this.onClosed = options.onClosed || null;
18
+
8
19
  this._keydownHandler = (e) => {
9
- if (e.key === 'Escape' && this.isVisible()) {
20
+ if (this.escClose && e.key === 'Escape' && this.isVisible()) {
10
21
  this.close();
11
22
  }
12
23
  };
@@ -14,7 +25,7 @@ class Modal {
14
25
  this._closeBtnClickHandler = () => this.close();
15
26
 
16
27
  this._maskClickHandler = (e) => {
17
- if (e.target === this.mask) {
28
+ if (this.closableOnMask && e.target === this.mask) {
18
29
  this.close();
19
30
  }
20
31
  };
@@ -32,24 +43,93 @@ class Modal {
32
43
  }
33
44
 
34
45
  document.addEventListener('keydown', this._keydownHandler);
46
+
47
+ // Apply fullscreen
48
+ if (this.fullscreen && this.modal) {
49
+ this.modal.classList.add('ds-modal--fullscreen');
50
+ }
51
+
52
+ // Apply custom width
53
+ if (this.width && this.modal) {
54
+ this.modal.style.maxWidth = this.width;
55
+ }
35
56
  }
36
57
 
37
58
  open() {
38
- this.mask.classList.add('is-visible');
59
+ if (this.onBeforeOpen) {
60
+ const result = this.onBeforeOpen();
61
+ if (result === false) return;
62
+ }
63
+
64
+ if (this.mask) {
65
+ this.mask.classList.add('is-visible');
66
+ this.mask.classList.add('ds-modal-fade-enter');
67
+ requestAnimationFrame(() => {
68
+ this.mask.classList.add('ds-modal-fade-enter-active');
69
+ });
70
+ }
71
+ if (this.modal) {
72
+ this.modal.classList.add('ds-modal-zoom-enter');
73
+ requestAnimationFrame(() => {
74
+ this.modal.classList.add('ds-modal-zoom-enter-active');
75
+ });
76
+ }
77
+
39
78
  Modal._openCount = (Modal._openCount || 0) + 1;
40
79
  document.body.style.overflow = 'hidden';
80
+
81
+ if (this.onOpened) {
82
+ setTimeout(() => this.onOpened(), 300);
83
+ }
84
+
85
+ this.element.dispatchEvent(new CustomEvent('kupola:modal-open', { bubbles: true }));
41
86
  }
42
87
 
43
88
  close() {
44
- this.mask.classList.remove('is-visible');
89
+ if (this.onBeforeClose) {
90
+ const result = this.onBeforeClose();
91
+ if (result === false) return;
92
+ }
93
+
94
+ if (this.mask) {
95
+ this.mask.classList.remove('ds-modal-fade-enter-active');
96
+ this.mask.classList.add('ds-modal-fade-leave-active');
97
+ }
98
+ if (this.modal) {
99
+ this.modal.classList.remove('ds-modal-zoom-enter-active');
100
+ this.modal.classList.add('ds-modal-zoom-leave-active');
101
+ }
102
+
103
+ setTimeout(() => {
104
+ if (this.mask) {
105
+ this.mask.classList.remove('is-visible', 'ds-modal-fade-enter', 'ds-modal-fade-leave-active');
106
+ }
107
+ if (this.modal) {
108
+ this.modal.classList.remove('ds-modal-zoom-enter', 'ds-modal-zoom-leave-active');
109
+ }
110
+ }, 300);
111
+
45
112
  Modal._openCount = Math.max(0, (Modal._openCount || 0) - 1);
46
113
  if (Modal._openCount === 0) {
47
114
  document.body.style.overflow = '';
48
115
  }
116
+
117
+ if (this.onClosed) {
118
+ setTimeout(() => this.onClosed(), 300);
119
+ }
120
+
121
+ this.element.dispatchEvent(new CustomEvent('kupola:modal-close', { bubbles: true }));
122
+ }
123
+
124
+ toggleFullscreen() {
125
+ this.fullscreen = !this.fullscreen;
126
+ if (this.modal) {
127
+ this.modal.classList.toggle('ds-modal--fullscreen', this.fullscreen);
128
+ }
49
129
  }
50
130
 
51
131
  isVisible() {
52
- return this.mask.classList.contains('is-visible');
132
+ return this.mask && this.mask.classList.contains('is-visible');
53
133
  }
54
134
 
55
135
  destroy() {
@@ -72,89 +152,151 @@ function createModal(options = {}) {
72
152
  const {
73
153
  title = '',
74
154
  content = '',
155
+ html = false,
75
156
  width = '480px',
157
+ fullscreen = false,
158
+ showCancel = true,
159
+ showConfirm = true,
160
+ confirmText = 'OK',
161
+ cancelText = 'Cancel',
162
+ confirmClass = 'ds-btn--brand',
163
+ cancelClass = 'ds-btn--ghost',
164
+ closable = true,
165
+ maskClosable = true,
76
166
  onConfirm,
77
- onCancel
167
+ onCancel,
168
+ onOpen,
169
+ onClose,
170
+ footer = null
78
171
  } = options;
79
172
 
80
173
  const container = document.createElement('div');
81
174
  container.className = 'ds-modal-container';
175
+
176
+ let footerHTML = '';
177
+ if (footer !== null) {
178
+ if (typeof footer === 'string') {
179
+ footerHTML = `<div class="ds-modal__footer">${footer}</div>`;
180
+ } else if (showConfirm || showCancel) {
181
+ footerHTML = `<div class="ds-modal__footer">
182
+ ${showCancel ? `<button class="ds-btn ${cancelClass}" data-modal-cancel>${cancelText}</button>` : ''}
183
+ ${showConfirm ? `<button class="ds-btn ${confirmClass}" data-modal-confirm>${confirmText}</button>` : ''}
184
+ </div>`;
185
+ }
186
+ }
187
+
82
188
  container.innerHTML = `
83
189
  <div class="ds-modal-mask">
84
- <div class="ds-modal" style="max-width: ${width}">
190
+ <div class="ds-modal${fullscreen ? ' ds-modal--fullscreen' : ''}" style="${!fullscreen ? 'max-width: ' + width : ''}">
85
191
  <div class="ds-modal__header">
86
192
  <span class="ds-modal__title"></span>
87
- <button class="ds-modal__close" aria-label="Close">
193
+ ${closable ? `<button class="ds-modal__close" aria-label="Close">
88
194
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
89
195
  <path d="M18 6L6 18M6 6l12 12"/>
90
196
  </svg>
91
- </button>
197
+ </button>` : ''}
92
198
  </div>
93
199
  <div class="ds-modal__body"></div>
94
- ${onConfirm || onCancel ? `
95
- <div class="ds-modal__footer">
96
- ${onCancel ? `<button class="ds-btn ds-btn--ghost" data-modal-cancel>Cancel</button>` : ''}
97
- ${onConfirm ? `<button class="ds-btn ds-btn--brand" data-modal-confirm>OK</button>` : ''}
98
- </div>
99
- ` : ''}
200
+ ${footerHTML}
100
201
  </div>
101
202
  </div>
102
203
  `;
103
204
 
104
205
  document.body.appendChild(container);
105
206
 
106
- const modal = new Modal(container);
207
+ const modal = new Modal(container, { fullscreen, closableOnMask: maskClosable });
107
208
 
108
209
  const titleEl = container.querySelector('.ds-modal__title');
109
210
  if (titleEl) titleEl.textContent = title;
110
211
  const bodyEl = container.querySelector('.ds-modal__body');
111
- if (bodyEl) bodyEl.textContent = content;
212
+ if (bodyEl) {
213
+ if (html) {
214
+ bodyEl.innerHTML = content;
215
+ } else {
216
+ bodyEl.textContent = content;
217
+ }
218
+ }
112
219
 
113
220
  const confirmBtn = container.querySelector('[data-modal-confirm]');
114
221
  const cancelBtn = container.querySelector('[data-modal-cancel]');
115
222
 
116
- const confirmHandler = () => {
117
- if (onConfirm) onConfirm();
118
- modal.close();
119
- };
120
-
121
- const cancelHandler = () => {
122
- if (onCancel) onCancel();
123
- modal.close();
124
- };
125
-
126
- if (confirmBtn) {
127
- confirmBtn.addEventListener('click', confirmHandler);
128
- }
129
-
130
- if (cancelBtn) {
131
- cancelBtn.addEventListener('click', cancelHandler);
132
- }
133
-
134
- const closeAndDestroy = () => {
135
- setTimeout(() => {
136
- if (confirmBtn) {
137
- confirmBtn.removeEventListener('click', confirmHandler);
138
- }
139
- if (cancelBtn) {
140
- cancelBtn.removeEventListener('click', cancelHandler);
223
+ let isConfirming = false;
224
+
225
+ const confirmHandler = async () => {
226
+ if (onConfirm) {
227
+ // Support async onConfirm
228
+ confirmBtn.disabled = true;
229
+ confirmBtn.classList.add('is-loading');
230
+ try {
231
+ const result = await onConfirm();
232
+ if (result === false) {
233
+ confirmBtn.disabled = false;
234
+ confirmBtn.classList.remove('is-loading');
235
+ return;
141
236
  }
142
- modal.destroy();
143
- container.remove();
144
- }, 300);
145
- };
146
-
147
- const originalClose = modal.close.bind(modal);
148
- modal.close = () => {
149
- originalClose();
150
- closeAndDestroy();
151
- };
237
+ } catch(e) {
238
+ confirmBtn.disabled = false;
239
+ confirmBtn.classList.remove('is-loading');
240
+ return;
241
+ }
242
+ }
243
+ isConfirming = true;
244
+ modal.close();
245
+ };
246
+
247
+ const cancelHandler = () => {
248
+ if (onCancel) onCancel();
249
+ modal.close();
250
+ };
251
+
252
+ if (confirmBtn) confirmBtn.addEventListener('click', confirmHandler);
253
+ if (cancelBtn) cancelBtn.addEventListener('click', cancelHandler);
254
+
255
+ const closeAndDestroy = () => {
256
+ setTimeout(() => {
257
+ if (confirmBtn) confirmBtn.removeEventListener('click', confirmHandler);
258
+ if (cancelBtn) cancelBtn.removeEventListener('click', cancelHandler);
259
+ modal.destroy();
260
+ container.remove();
261
+ if (onClose) onClose(isConfirming);
262
+ }, 300);
263
+ };
264
+
265
+ const originalClose = modal.close.bind(modal);
266
+ modal.close = () => {
267
+ originalClose();
268
+ closeAndDestroy();
269
+ };
152
270
 
153
271
  modal.open();
272
+ if (onOpen) setTimeout(() => onOpen(), 50);
154
273
 
155
274
  return modal;
156
275
  }
157
276
 
277
+ // Convenience methods
278
+ function confirmModal(options) {
279
+ if (typeof options === 'string') {
280
+ options = { content: options };
281
+ }
282
+ return createModal({
283
+ ...options,
284
+ showCancel: true,
285
+ showConfirm: true
286
+ });
287
+ }
288
+
289
+ function alertModal(options) {
290
+ if (typeof options === 'string') {
291
+ options = { content: options };
292
+ }
293
+ return createModal({
294
+ ...options,
295
+ showCancel: false,
296
+ showConfirm: true
297
+ });
298
+ }
299
+
158
300
  function initModal(element) {
159
301
  if (element.__kupolaInitialized) return;
160
302
 
@@ -179,13 +321,19 @@ function initModals() {
179
321
  });
180
322
  }
181
323
 
182
- if (typeof module !== 'undefined' && module.exports) {
183
- module.exports = { Modal, initModals, initModal, cleanupModal, createModal };
184
- } else {
324
+ export { Modal, initModals, initModal, cleanupModal, createModal, confirmModal, alertModal };
325
+
326
+ if (typeof window !== 'undefined') {
327
+ window.Modal = Modal;
185
328
  window.initModal = initModal;
186
329
  window.cleanupModal = cleanupModal;
330
+ window.initModals = initModals;
331
+ window.createModal = createModal;
332
+ window.confirmModal = confirmModal;
333
+ window.alertModal = alertModal;
334
+
335
+ if (window.kupolaInitializer) {
336
+ window.kupolaInitializer.register('modal', initModal, cleanupModal);
337
+ }
187
338
  }
188
339
 
189
- if (window.kupolaInitializer) {
190
- window.kupolaInitializer.register('modal', initModal, cleanupModal);
191
- }
@@ -70,4 +70,11 @@ const Notification = {
70
70
  }
71
71
  };
72
72
 
73
- function initNotifications() {}
73
+ function initNotifications() {}
74
+
75
+ export { Notification, initNotifications };
76
+
77
+ if (typeof window !== 'undefined') {
78
+ window.Notification = Notification;
79
+ window.initNotifications = initNotifications;
80
+ }
package/js/numberinput.js CHANGED
@@ -86,7 +86,7 @@ class NumberInput {
86
86
  }
87
87
 
88
88
  dispatchChange() {
89
- this.element.dispatchEvent(new CustomEvent('ds-number-input-change', {
89
+ this.element.dispatchEvent(new CustomEvent('kupola:number-input-change', {
90
90
  detail: {
91
91
  value: this.getValue()
92
92
  }
@@ -133,14 +133,15 @@ function initNumberInputs() {
133
133
  });
134
134
  }
135
135
 
136
- if (typeof module !== 'undefined' && module.exports) {
137
- module.exports = { NumberInput, initNumberInputs, initNumberInput, cleanupNumberInput };
138
- } else {
136
+ export { NumberInput, initNumberInputs, initNumberInput, cleanupNumberInput };
137
+
138
+ if (typeof window !== 'undefined') {
139
139
  window.NumberInput = NumberInput;
140
140
  window.initNumberInput = initNumberInput;
141
141
  window.cleanupNumberInput = cleanupNumberInput;
142
- }
143
-
144
- if (window.kupolaInitializer) {
145
- window.kupolaInitializer.register('number-input', initNumberInput, cleanupNumberInput);
142
+ window.initNumberInputs = initNumberInputs;
143
+
144
+ if (window.kupolaInitializer) {
145
+ window.kupolaInitializer.register('number-input', initNumberInput, cleanupNumberInput);
146
+ }
146
147
  }