@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/tag.js CHANGED
@@ -1,17 +1,30 @@
1
1
  class Tag {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.closeBtn = element.querySelector('.ds-tag__close');
5
+ this.checkbox = element.querySelector('.ds-tag__checkbox');
6
+ this.editInput = element.querySelector('.ds-tag__input');
7
+
5
8
  this._listeners = [];
9
+
10
+ this.color = options.color || element.getAttribute('data-tag-color') || 'default';
11
+ this.size = options.size || element.getAttribute('data-tag-size') || 'default';
12
+ this.checkable = options.checkable || element.hasAttribute('data-tag-checkable');
13
+ this.checked = options.checked || element.hasAttribute('data-tag-checked');
14
+ this.editable = options.editable || element.hasAttribute('data-tag-editable');
15
+ this.maxLength = options.maxLength || parseInt(element.getAttribute('data-tag-maxlength')) || 50;
16
+
6
17
  this.init();
7
18
  }
8
19
 
9
20
  init() {
21
+ this._applyStyles();
22
+
10
23
  if (this.closeBtn) {
11
24
  const closeHandler = (e) => {
12
25
  e.stopPropagation();
13
- this.element.dispatchEvent(new CustomEvent('ds-tag-remove', {
14
- detail: { tag: this.element },
26
+ this.element.dispatchEvent(new CustomEvent('kupola:tag-remove', {
27
+ detail: { tag: this.element, content: this.getContent() },
15
28
  bubbles: true
16
29
  }));
17
30
  this.element.remove();
@@ -19,9 +32,81 @@ class Tag {
19
32
  this.closeBtn.addEventListener('click', closeHandler);
20
33
  this._listeners.push({ el: this.closeBtn, event: 'click', handler: closeHandler });
21
34
  }
35
+
36
+ if (this.checkable) {
37
+ const clickHandler = (e) => {
38
+ if (e.target !== this.checkbox && e.target !== this.closeBtn) {
39
+ this.toggleChecked();
40
+ }
41
+ };
42
+ this.element.addEventListener('click', clickHandler);
43
+ this._listeners.push({ el: this.element, event: 'click', handler: clickHandler });
44
+
45
+ if (this.checkbox) {
46
+ const checkboxHandler = () => {
47
+ this.toggleChecked();
48
+ };
49
+ this.checkbox.addEventListener('change', checkboxHandler);
50
+ this._listeners.push({ el: this.checkbox, event: 'change', handler: checkboxHandler });
51
+ }
52
+ }
53
+
54
+ if (this.editable) {
55
+ const dblclickHandler = () => {
56
+ this.startEdit();
57
+ };
58
+ this.element.addEventListener('dblclick', dblclickHandler);
59
+ this._listeners.push({ el: this.element, event: 'dblclick', handler: dblclickHandler });
60
+
61
+ if (this.editInput) {
62
+ const blurHandler = () => {
63
+ this.endEdit();
64
+ };
65
+ const keydownHandler = (e) => {
66
+ if (e.key === 'Enter') {
67
+ this.endEdit();
68
+ } else if (e.key === 'Escape') {
69
+ this.cancelEdit();
70
+ }
71
+ };
72
+ this.editInput.addEventListener('blur', blurHandler);
73
+ this.editInput.addEventListener('keydown', keydownHandler);
74
+ this._listeners.push({ el: this.editInput, event: 'blur', handler: blurHandler });
75
+ this._listeners.push({ el: this.editInput, event: 'keydown', handler: keydownHandler });
76
+ }
77
+ }
78
+ }
79
+
80
+ _applyStyles() {
81
+ const colorClasses = ['ds-tag--default', 'ds-tag--primary', 'ds-tag--success', 'ds-tag--warning', 'ds-tag--danger', 'ds-tag--info'];
82
+ const sizeClasses = ['ds-tag--default', 'ds-tag--small', 'ds-tag--large'];
83
+
84
+ colorClasses.forEach(cls => this.element.classList.remove(cls));
85
+ sizeClasses.forEach(cls => this.element.classList.remove(cls));
86
+
87
+ if (colorClasses.includes(`ds-tag--${this.color}`)) {
88
+ this.element.classList.add(`ds-tag--${this.color}`);
89
+ }
90
+ if (sizeClasses.includes(`ds-tag--${this.size}`)) {
91
+ this.element.classList.add(`ds-tag--${this.size}`);
92
+ }
93
+
94
+ if (this.checkable) {
95
+ this.element.classList.add('ds-tag--checkable');
96
+ }
97
+ if (this.checked) {
98
+ this.element.classList.add('is-checked');
99
+ }
100
+ if (this.editable) {
101
+ this.element.classList.add('ds-tag--editable');
102
+ }
22
103
  }
23
104
 
24
105
  setContent(content) {
106
+ if (this.editable && this.editInput) {
107
+ this.editInput.value = content;
108
+ }
109
+
25
110
  const textNodes = [];
26
111
  this.element.childNodes.forEach(node => {
27
112
  if (node.nodeType === Node.TEXT_NODE) {
@@ -29,27 +114,195 @@ class Tag {
29
114
  }
30
115
  });
31
116
  textNodes.forEach(node => node.remove());
32
- this.element.insertBefore(document.createTextNode(content), this.closeBtn || null);
117
+
118
+ const closeBtn = this.element.querySelector('.ds-tag__close');
119
+ const checkbox = this.element.querySelector('.ds-tag__checkbox');
120
+ const input = this.element.querySelector('.ds-tag__input');
121
+
122
+ const insertBeforeEl = closeBtn || checkbox || input || null;
123
+ this.element.insertBefore(document.createTextNode(content), insertBeforeEl);
124
+
125
+ this.element.dispatchEvent(new CustomEvent('kupola:tag-change', {
126
+ detail: { tag: this.element, content },
127
+ bubbles: true
128
+ }));
33
129
  }
34
130
 
35
131
  getContent() {
132
+ if (this.editable && this.editInput && this.element.classList.contains('is-editing')) {
133
+ return this.editInput.value;
134
+ }
36
135
  return this.element.textContent.trim();
37
136
  }
38
137
 
138
+ setColor(color) {
139
+ const validColors = ['default', 'primary', 'success', 'warning', 'danger', 'info'];
140
+ if (validColors.includes(color)) {
141
+ this.color = color;
142
+ this.element.setAttribute('data-tag-color', color);
143
+ this._applyStyles();
144
+ }
145
+ }
146
+
147
+ getColor() {
148
+ return this.color;
149
+ }
150
+
151
+ setSize(size) {
152
+ const validSizes = ['default', 'small', 'large'];
153
+ if (validSizes.includes(size)) {
154
+ this.size = size;
155
+ this.element.setAttribute('data-tag-size', size);
156
+ this._applyStyles();
157
+ }
158
+ }
159
+
160
+ getSize() {
161
+ return this.size;
162
+ }
163
+
164
+ toggleChecked() {
165
+ this.checked = !this.checked;
166
+ this.element.setAttribute('data-tag-checked', this.checked ? 'true' : 'false');
167
+ this._applyStyles();
168
+
169
+ if (this.checkbox) {
170
+ this.checkbox.checked = this.checked;
171
+ }
172
+
173
+ this.element.dispatchEvent(new CustomEvent('kupola:tag-check', {
174
+ detail: { tag: this.element, checked: this.checked, content: this.getContent() },
175
+ bubbles: true
176
+ }));
177
+ }
178
+
179
+ setChecked(checked) {
180
+ this.checked = checked;
181
+ this.element.setAttribute('data-tag-checked', checked ? 'true' : 'false');
182
+ this._applyStyles();
183
+
184
+ if (this.checkbox) {
185
+ this.checkbox.checked = checked;
186
+ }
187
+ }
188
+
189
+ isChecked() {
190
+ return this.checked;
191
+ }
192
+
193
+ startEdit() {
194
+ if (!this.editable) return;
195
+
196
+ const content = this.getContent();
197
+
198
+ if (!this.editInput) {
199
+ const input = document.createElement('input');
200
+ input.type = 'text';
201
+ input.className = 'ds-tag__input';
202
+ input.value = content;
203
+ input.maxLength = this.maxLength;
204
+ this.editInput = input;
205
+
206
+ const closeBtn = this.element.querySelector('.ds-tag__close');
207
+ this.element.insertBefore(input, closeBtn);
208
+
209
+ const blurHandler = () => this.endEdit();
210
+ const keydownHandler = (e) => {
211
+ if (e.key === 'Enter') {
212
+ this.endEdit();
213
+ } else if (e.key === 'Escape') {
214
+ this.cancelEdit();
215
+ }
216
+ };
217
+ input.addEventListener('blur', blurHandler);
218
+ input.addEventListener('keydown', keydownHandler);
219
+ this._listeners.push({ el: input, event: 'blur', handler: blurHandler });
220
+ this._listeners.push({ el: input, event: 'keydown', handler: keydownHandler });
221
+ } else {
222
+ this.editInput.value = content;
223
+ }
224
+
225
+ this.element.classList.add('is-editing');
226
+
227
+ setTimeout(() => {
228
+ if (this.editInput) {
229
+ this.editInput.focus();
230
+ this.editInput.select();
231
+ }
232
+ }, 0);
233
+ }
234
+
235
+ endEdit() {
236
+ if (!this.editable || !this.element.classList.contains('is-editing')) return;
237
+
238
+ const newContent = this.editInput.value.trim();
239
+ this.element.classList.remove('is-editing');
240
+
241
+ if (newContent && newContent !== this.getContent()) {
242
+ this.setContent(newContent);
243
+ this.element.dispatchEvent(new CustomEvent('kupola:tag-edit', {
244
+ detail: { tag: this.element, content: newContent },
245
+ bubbles: true
246
+ }));
247
+ }
248
+ }
249
+
250
+ cancelEdit() {
251
+ if (!this.editable || !this.element.classList.contains('is-editing')) return;
252
+
253
+ this.element.classList.remove('is-editing');
254
+ if (this.editInput) {
255
+ this.editInput.value = this.getContent();
256
+ }
257
+ }
258
+
259
+ setEditable(editable) {
260
+ this.editable = editable;
261
+ if (editable) {
262
+ this.element.setAttribute('data-tag-editable', '');
263
+ } else {
264
+ this.element.removeAttribute('data-tag-editable');
265
+ }
266
+ this._applyStyles();
267
+ }
268
+
269
+ isEditable() {
270
+ return this.editable;
271
+ }
272
+
273
+ setCheckable(checkable) {
274
+ if (this.checkable !== checkable) {
275
+ this.destroy();
276
+ this.checkable = checkable;
277
+ if (checkable) {
278
+ this.element.setAttribute('data-tag-checkable', '');
279
+ } else {
280
+ this.element.removeAttribute('data-tag-checkable');
281
+ }
282
+ this.init();
283
+ }
284
+ }
285
+
286
+ isCheckable() {
287
+ return this.checkable;
288
+ }
289
+
39
290
  destroy() {
40
291
  this._listeners.forEach(({ el, event, handler }) => {
41
292
  el.removeEventListener(event, handler);
42
293
  });
43
- this._listeners = null;
294
+ this._listeners = [];
44
295
  this.closeBtn = null;
296
+ this.checkbox = null;
297
+ this.editInput = null;
45
298
  this.element = null;
46
299
  }
47
300
  }
48
301
 
49
- function initTag(element) {
302
+ function initTag(element, options) {
50
303
  if (element.__kupolaInitialized) return;
51
304
 
52
- const instance = new Tag(element);
305
+ const instance = new Tag(element, options);
53
306
  element.__kupolaInstance = instance;
54
307
  element.__kupolaInitialized = true;
55
308
  }
@@ -70,14 +323,15 @@ function initTags() {
70
323
  });
71
324
  }
72
325
 
73
- if (typeof module !== 'undefined' && module.exports) {
74
- module.exports = { Tag, initTags, initTag, cleanupTag };
75
- } else {
326
+ export { Tag, initTags, initTag, cleanupTag };
327
+
328
+ if (typeof window !== 'undefined') {
76
329
  window.Tag = Tag;
77
330
  window.initTag = initTag;
78
331
  window.cleanupTag = cleanupTag;
79
- }
80
-
81
- if (window.kupolaInitializer) {
82
- window.kupolaInitializer.register('tag', initTag, cleanupTag);
332
+ window.initTags = initTags;
333
+
334
+ if (window.kupolaInitializer) {
335
+ window.kupolaInitializer.register('tag', initTag, cleanupTag);
336
+ }
83
337
  }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Kupola Theme Standalone — lightweight theme toggle (~1KB).
3
+ *
4
+ * Usage: Include this single script in your page. No other Kupola JS required.
5
+ * <script src="theme-standalone.js"></script>
6
+ *
7
+ * Features:
8
+ * - Reads/writes theme preference from localStorage
9
+ * - Sets data-theme attribute on <html>
10
+ * - Auto-binds [data-theme-toggle] buttons
11
+ * - Supports prefers-color-scheme as default
12
+ * - Optional: auto-detects brand preference
13
+ */
14
+ (function () {
15
+ 'use strict';
16
+
17
+ var THEME_KEY = 'kupola-theme';
18
+ var BRAND_KEY = 'kupola-brand';
19
+
20
+ function getPreferred() {
21
+ var saved = localStorage.getItem(THEME_KEY);
22
+ if (saved === 'dark' || saved === 'light') return saved;
23
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
24
+ return 'light';
25
+ }
26
+ return 'dark';
27
+ }
28
+
29
+ function applyTheme(theme) {
30
+ document.documentElement.setAttribute('data-theme', theme);
31
+ localStorage.setItem(THEME_KEY, theme);
32
+ }
33
+
34
+ function applyBrand(brand) {
35
+ if (brand) {
36
+ document.documentElement.setAttribute('data-brand', brand);
37
+ localStorage.setItem(BRAND_KEY, brand);
38
+ }
39
+ }
40
+
41
+ function toggleTheme() {
42
+ var current = document.documentElement.getAttribute('data-theme') || getPreferred();
43
+ var next = current === 'dark' ? 'light' : 'dark';
44
+ applyTheme(next);
45
+ }
46
+
47
+ function bindToggleButtons() {
48
+ var buttons = document.querySelectorAll('[data-theme-toggle]');
49
+ buttons.forEach(function (btn) {
50
+ btn.addEventListener('click', function (e) {
51
+ e.preventDefault();
52
+ toggleTheme();
53
+ });
54
+ });
55
+ }
56
+
57
+ // Initialize immediately (works even before DOMContentLoaded)
58
+ applyTheme(getPreferred());
59
+ applyBrand(localStorage.getItem(BRAND_KEY));
60
+
61
+ // Bind toggle buttons when DOM is ready
62
+ if (document.readyState === 'loading') {
63
+ document.addEventListener('DOMContentLoaded', bindToggleButtons);
64
+ } else {
65
+ bindToggleButtons();
66
+ }
67
+
68
+ // Listen for system theme changes
69
+ if (window.matchMedia) {
70
+ var mq = window.matchMedia('(prefers-color-scheme: dark)');
71
+ if (mq.addEventListener) {
72
+ mq.addEventListener('change', function (e) {
73
+ if (!localStorage.getItem(THEME_KEY)) {
74
+ applyTheme(e.matches ? 'dark' : 'light');
75
+ }
76
+ });
77
+ }
78
+ }
79
+
80
+ // Expose minimal API
81
+ window.KupolaTheme = {
82
+ toggle: toggleTheme,
83
+ set: applyTheme,
84
+ get: function () {
85
+ return document.documentElement.getAttribute('data-theme') || 'dark';
86
+ }
87
+ };
88
+ })();
package/js/theme.js CHANGED
@@ -316,48 +316,19 @@ function createBrandPicker() {
316
316
  return { toggleBtn, container };
317
317
  }
318
318
 
319
- function loadDevTools() {
320
- const urlParams = new URLSearchParams(window.location.search);
321
- const enabled = urlParams.has('dev') || urlParams.has('debug');
322
-
323
- if (!enabled) return;
324
-
325
- const themeScript = document.querySelector('script[src*="theme.js"]');
326
- if (!themeScript || !themeScript.src) return;
327
-
328
- const scriptPath = themeScript.src.substring(0, themeScript.src.lastIndexOf('/') + 1);
329
-
330
- const devToolsScript = document.createElement('script');
331
- devToolsScript.src = scriptPath + 'kupola-devtools.js?v=15';
332
- devToolsScript.async = true;
333
-
334
- document.head.appendChild(devToolsScript);
335
- }
319
+ export {
320
+ getTheme, setTheme, initTheme, createThemeToggle,
321
+ getBrand, setBrand, BRAND_OPTIONS, createBrandPicker,
322
+ THEME_KEY, BRAND_KEY
323
+ };
336
324
 
337
- if (typeof module !== 'undefined' && module.exports) {
338
- module.exports = {
339
- getTheme, setTheme, initTheme, createThemeToggle,
340
- getBrand, setBrand, BRAND_OPTIONS, createBrandPicker,
341
- loadDevTools
342
- };
343
- } else {
344
- if (!window.__kupolaThemeInitialized) {
345
- window.__kupolaThemeInitialized = true;
346
-
347
- function runInit() {
348
- initTheme();
349
- loadDevTools();
350
- }
351
-
352
- if (document.readyState === 'loading') {
353
- document.addEventListener('readystatechange', function readyStateHandler() {
354
- if (document.readyState !== 'loading') {
355
- document.removeEventListener('readystatechange', readyStateHandler);
356
- runInit();
357
- }
358
- });
359
- } else {
360
- setTimeout(runInit, 0);
361
- }
362
- }
325
+ if (typeof window !== 'undefined') {
326
+ window.getTheme = getTheme;
327
+ window.setTheme = setTheme;
328
+ window.initTheme = initTheme;
329
+ window.createThemeToggle = createThemeToggle;
330
+ window.getBrand = getBrand;
331
+ window.setBrand = setBrand;
332
+ window.BRAND_OPTIONS = BRAND_OPTIONS;
333
+ window.createBrandPicker = createBrandPicker;
363
334
  }