@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/form.js CHANGED
@@ -1,291 +1,317 @@
1
- (function(window) {
2
- class KupolaForm {
3
- constructor(formOrSelector, options = {}) {
4
- this.form = typeof formOrSelector === 'string'
5
- ? document.querySelector(formOrSelector)
6
- : formOrSelector;
7
-
8
- this.options = {
9
- validateOnSubmit: true,
10
- validateOnChange: false,
11
- validateOnBlur: false,
12
- preventDefault: true,
13
- ...options
14
- };
15
-
16
- this.submitHandlers = [];
17
- this.isSubmitting = false;
18
- this.errors = {};
19
- this.rules = {};
20
- this.validator = window.kupolaValidator || new window.KupolaValidator();
21
-
22
- if (!this.form || !(this.form instanceof HTMLFormElement)) {
23
- console.warn('[KupolaForm] Invalid form element');
24
- return;
25
- }
26
-
27
- this._init();
28
- }
29
-
30
- _init() {
31
- if (this.options.validateOnChange) {
32
- this.form.addEventListener('input', (e) => {
33
- this._validateField(e.target);
34
- });
35
- }
1
+ class KupolaForm {
2
+ constructor(formElement) {
3
+ this.form = formElement;
4
+ this.fields = [];
5
+ this.validators = {};
6
+ this.errorMessages = {};
7
+ this._init();
8
+ }
36
9
 
37
- if (this.options.validateOnBlur) {
38
- this.form.addEventListener('blur', (e) => {
39
- if (e.target.form === this.form) {
40
- this._validateField(e.target);
41
- }
42
- }, true);
43
- }
44
- }
10
+ _init() {
11
+ this._setupValidators();
12
+ this._collectFields();
13
+ this._bindEvents();
14
+ }
45
15
 
46
- submit(handler) {
47
- const wrappedHandler = async (e) => {
48
- if (this.options.preventDefault) {
49
- e.preventDefault();
16
+ _setupValidators() {
17
+ this.validators = {
18
+ required: (value) => {
19
+ if (typeof value === 'string') {
20
+ return value.trim() !== '';
50
21
  }
51
- if (this.isSubmitting) return;
52
- this.isSubmitting = true;
53
-
54
- try {
55
- if (this.options.validateOnSubmit) {
56
- const validation = await this.validate();
57
- if (!validation.valid) {
58
- this._showErrors(validation.errors);
59
- return;
60
- }
61
- }
62
-
63
- const result = handler(e);
64
- if (result instanceof Promise) {
65
- await result;
66
- }
67
- } finally {
68
- this.isSubmitting = false;
22
+ if (Array.isArray(value)) {
23
+ return value.length > 0;
69
24
  }
70
- };
71
-
72
- this.submitHandlers.push(wrappedHandler);
73
- this.form.addEventListener('submit', wrappedHandler);
74
-
75
- return () => {
76
- const index = this.submitHandlers.indexOf(wrappedHandler);
77
- if (index > -1) {
78
- this.submitHandlers.splice(index, 1);
79
- this.form.removeEventListener('submit', wrappedHandler);
25
+ return value !== null && value !== undefined;
26
+ },
27
+ email: (value) => {
28
+ if (!value) return true;
29
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
30
+ return regex.test(value);
31
+ },
32
+ phone: (value) => {
33
+ if (!value) return true;
34
+ const regex = /^1[3-9]\d{9}$/;
35
+ return regex.test(value);
36
+ },
37
+ url: (value) => {
38
+ if (!value) return true;
39
+ try {
40
+ new URL(value);
41
+ return true;
42
+ } catch {
43
+ return false;
80
44
  }
81
- };
82
- }
83
-
84
- setRules(rules) {
85
- this.rules = { ...this.rules, ...rules };
86
- this._applyRulesToDOM();
87
- return this;
88
- }
89
-
90
- _applyRulesToDOM() {
91
- Object.keys(this.rules).forEach(name => {
92
- const input = this.form.querySelector(`[name="${name}"]`);
93
- if (!input) return;
94
-
95
- const fieldRules = this.rules[name];
96
- const ruleString = this._rulesToString(fieldRules);
97
- input.setAttribute('data-validate', ruleString);
98
- });
99
- }
100
-
101
- _rulesToString(rules) {
102
- if (typeof rules === 'function') {
103
- return 'custom';
45
+ },
46
+ number: (value) => {
47
+ if (!value) return true;
48
+ return !isNaN(parseFloat(value)) && isFinite(value);
49
+ },
50
+ minlength: (value, min) => {
51
+ if (!value) return true;
52
+ return value.length >= parseInt(min);
53
+ },
54
+ maxlength: (value, max) => {
55
+ if (!value) return true;
56
+ return value.length <= parseInt(max);
57
+ },
58
+ min: (value, min) => {
59
+ if (!value) return true;
60
+ return parseFloat(value) >= parseFloat(min);
61
+ },
62
+ max: (value, max) => {
63
+ if (!value) return true;
64
+ return parseFloat(value) <= parseFloat(max);
65
+ },
66
+ pattern: (value, pattern) => {
67
+ if (!value) return true;
68
+ const regex = new RegExp(pattern);
69
+ return regex.test(value);
70
+ },
71
+ equalTo: (value, targetId) => {
72
+ const target = document.getElementById(targetId);
73
+ if (!target) return true;
74
+ return value === target.value;
104
75
  }
76
+ };
77
+
78
+ this.errorMessages = {
79
+ required: '该字段为必填项',
80
+ email: '请输入有效的邮箱地址',
81
+ phone: '请输入有效的手机号码',
82
+ url: '请输入有效的URL地址',
83
+ number: '请输入有效的数字',
84
+ minlength: (param) => `至少需要${param}个字符`,
85
+ maxlength: (param) => `最多允许${param}个字符`,
86
+ min: (param) => `最小值为${param}`,
87
+ max: (param) => `最大值为${param}`,
88
+ pattern: '格式不正确',
89
+ equalTo: '两次输入不一致'
90
+ };
91
+ }
105
92
 
106
- const parts = [];
107
- Object.keys(rules).forEach(rule => {
108
- const config = rules[rule];
109
- if (typeof config === 'object' && config !== null) {
110
- if (rule === 'pattern') {
111
- parts.push(`pattern:${config.source?.replace(/\//g, '')}`);
112
- } else if (rule === 'equalTo') {
113
- parts.push(`equalTo:${config}`);
114
- } else {
115
- parts.push(rule);
116
- }
117
- } else if (typeof config === 'number' || typeof config === 'string') {
118
- parts.push(`${rule}:${config}`);
119
- } else if (config === true) {
120
- parts.push(rule);
121
- }
122
- });
123
- return parts.join('|');
124
- }
125
-
126
- async validate(fields = null) {
127
- this.errors = {};
128
- const allErrors = [];
93
+ _collectFields() {
94
+ const inputs = this.form.querySelectorAll('input, select, textarea');
95
+ inputs.forEach(input => {
96
+ if (!input.hasAttribute('data-kupola-ignore')) {
97
+ this.fields.push(input);
98
+ }
99
+ });
100
+ }
129
101
 
130
- const inputs = fields
131
- ? Array.isArray(fields) ? fields : [fields]
132
- : this.form.querySelectorAll('input, select, textarea');
102
+ _bindEvents() {
103
+ this.form.addEventListener('submit', (e) => {
104
+ if (!this.validate()) {
105
+ e.preventDefault();
106
+ }
107
+ });
133
108
 
134
- for (const input of inputs) {
135
- const name = input.name;
136
- if (!name) continue;
109
+ this.fields.forEach(field => {
110
+ field.addEventListener('blur', () => this.validateField(field));
111
+ field.addEventListener('input', () => this.clearError(field));
112
+ });
113
+ }
137
114
 
138
- const errors = await this._validateField(input);
139
- if (errors.length > 0) {
140
- this.errors[name] = errors;
141
- allErrors.push(...errors);
142
- }
115
+ validate() {
116
+ let isValid = true;
117
+ this.fields.forEach(field => {
118
+ if (!this.validateField(field)) {
119
+ isValid = false;
143
120
  }
121
+ });
122
+ return isValid;
123
+ }
144
124
 
145
- return {
146
- valid: allErrors.length === 0,
147
- errors: allErrors,
148
- fieldErrors: this.errors
149
- };
125
+ validateField(field) {
126
+ const errors = this._getFieldErrors(field);
127
+
128
+ if (errors.length > 0) {
129
+ this.showError(field, errors[0]);
130
+ return false;
150
131
  }
132
+
133
+ this.clearError(field);
134
+ return true;
135
+ }
151
136
 
152
- async _validateField(input) {
153
- const name = input.name;
154
- const rules = this.rules[name];
155
-
156
- if (typeof rules === 'function') {
157
- const result = rules(input.value.trim(), input);
158
- if (result instanceof Promise) {
159
- const asyncResult = await result;
160
- if (asyncResult !== true) {
161
- const displayName = input.getAttribute('data-label') || input.getAttribute('placeholder') || name;
162
- const message = typeof asyncResult === 'string' ? asyncResult : `${displayName} 验证失败`;
163
- this.validator.showError(input, message);
164
- return [message];
137
+ _getFieldErrors(field) {
138
+ const errors = [];
139
+ const value = this._getFieldValue(field);
140
+
141
+ for (const [validatorName, validator] of Object.entries(this.validators)) {
142
+ const attrValue = field.getAttribute(`data-${validatorName}`);
143
+ if (attrValue !== null) {
144
+ const isValid = validator(value, attrValue);
145
+ if (!isValid) {
146
+ let message = this.errorMessages[validatorName];
147
+ if (typeof message === 'function') {
148
+ message = message(attrValue);
165
149
  }
166
- } else if (result !== true) {
167
- const displayName = input.getAttribute('data-label') || input.getAttribute('placeholder') || name;
168
- const message = typeof result === 'string' ? result : `${displayName} 验证失败`;
169
- this.validator.showError(input, message);
170
- return [message];
150
+ errors.push(message);
171
151
  }
172
- this.validator.clearError(input);
173
- return [];
174
- }
175
-
176
- const isValid = await this.validator.validateInputAsync(input);
177
- if (!isValid) {
178
- const errorElement = input.parentElement?.querySelector('.ds-input__error');
179
- return errorElement ? [errorElement.textContent] : ['Validation failed'];
180
152
  }
181
- return [];
182
153
  }
154
+
155
+ return errors;
156
+ }
183
157
 
184
- _showErrors(errors) {
185
- if (typeof Message !== 'undefined') {
186
- Message.error(errors.join('\n'));
187
- } else if (typeof console !== 'undefined') {
188
- console.error('Form validation errors:', errors);
189
- }
158
+ _getFieldValue(field) {
159
+ const type = field.type;
160
+
161
+ if (type === 'checkbox') {
162
+ return field.checked;
190
163
  }
191
-
192
- getValues() {
193
- const values = {};
194
- const inputs = this.form.querySelectorAll('input, select, textarea');
195
-
196
- inputs.forEach(input => {
197
- const name = input.name;
198
- if (!name) return;
199
-
200
- if (input.type === 'checkbox') {
201
- if (!values[name]) {
202
- values[name] = [];
203
- }
204
- if (input.checked) {
205
- values[name].push(input.value);
206
- }
207
- } else if (input.type === 'radio') {
208
- if (input.checked) {
209
- values[name] = input.value;
210
- }
211
- } else if (input.type === 'file') {
212
- values[name] = input.files;
213
- } else {
214
- values[name] = input.value;
215
- }
216
- });
217
-
218
- return values;
164
+
165
+ if (type === 'radio') {
166
+ const name = field.name;
167
+ const checked = this.form.querySelector(`input[name="${name}"]:checked`);
168
+ return checked ? checked.value : null;
219
169
  }
220
-
221
- setValues(values) {
222
- Object.keys(values).forEach(key => {
223
- const input = this.form.querySelector(`[name="${key}"]`);
224
- if (!input) return;
225
-
226
- if (input.type === 'checkbox') {
227
- const valuesArray = Array.isArray(values[key]) ? values[key] : [values[key]];
228
- input.checked = valuesArray.includes(input.value);
229
- } else if (input.type === 'radio') {
230
- const radio = this.form.querySelector(`[name="${key}"][value="${values[key]}"]`);
231
- if (radio) radio.checked = true;
232
- } else if (input.type === 'file') {
233
- const dataTransfer = new DataTransfer();
234
- if (values[key] instanceof FileList) {
235
- Array.from(values[key]).forEach(file => {
236
- dataTransfer.items.add(file);
237
- });
238
- }
239
- input.files = dataTransfer.files;
240
- } else {
241
- input.value = values[key];
242
- }
243
- });
244
- return this;
170
+
171
+ if (type === 'select-multiple') {
172
+ return Array.from(field.selectedOptions).map(option => option.value);
245
173
  }
174
+
175
+ return field.value;
176
+ }
246
177
 
247
- reset() {
248
- this.form.reset();
249
- this.errors = {};
250
- this.validator.resetForm(this.form);
251
- return this;
178
+ showError(field, message) {
179
+ this.clearError(field);
180
+
181
+ const errorElement = document.createElement('span');
182
+ errorElement.className = 'ds-form-error';
183
+ errorElement.textContent = message;
184
+
185
+ field.classList.add('ds-form-field--error');
186
+
187
+ const container = field.parentElement;
188
+ if (container.classList.contains('ds-form-field')) {
189
+ container.appendChild(errorElement);
190
+ } else {
191
+ field.parentNode.insertBefore(errorElement, field.nextSibling);
252
192
  }
193
+ }
253
194
 
254
- clearErrors() {
255
- this.errors = {};
256
- this.form.querySelectorAll('.ds-input--error').forEach(input => {
257
- this.validator.clearError(input);
258
- });
259
- return this;
195
+ clearError(field) {
196
+ field.classList.remove('ds-form-field--error');
197
+
198
+ const errorElement = field.parentElement.querySelector('.ds-form-error');
199
+ if (errorElement) {
200
+ errorElement.remove();
260
201
  }
202
+ }
261
203
 
262
- getError(fieldName) {
263
- return this.errors[fieldName] || [];
264
- }
204
+ addValidator(name, validator, message) {
205
+ this.validators[name] = validator;
206
+ this.errorMessages[name] = message;
207
+ }
265
208
 
266
- hasError(fieldName) {
267
- return this.errors[fieldName] && this.errors[fieldName].length > 0;
268
- }
209
+ getData() {
210
+ const data = {};
211
+
212
+ this.fields.forEach(field => {
213
+ const name = field.name;
214
+ if (!name) return;
215
+
216
+ const value = this._getFieldValue(field);
217
+
218
+ if (field.type === 'checkbox') {
219
+ if (!data[name]) {
220
+ data[name] = [];
221
+ }
222
+ if (field.checked) {
223
+ data[name].push(field.value);
224
+ }
225
+ } else if (field.type === 'radio') {
226
+ if (!data[name] && field.checked) {
227
+ data[name] = field.value;
228
+ }
229
+ } else {
230
+ data[name] = value;
231
+ }
232
+ });
233
+
234
+ return data;
235
+ }
269
236
 
270
- destroy() {
271
- this.submitHandlers.forEach(handler => {
272
- this.form.removeEventListener('submit', handler);
237
+ setData(data) {
238
+ Object.keys(data).forEach(name => {
239
+ const fields = this.form.querySelectorAll(`[name="${name}"]`);
240
+
241
+ fields.forEach(field => {
242
+ const type = field.type;
243
+
244
+ if (type === 'checkbox') {
245
+ const values = Array.isArray(data[name]) ? data[name] : [data[name]];
246
+ field.checked = values.includes(field.value);
247
+ } else if (type === 'radio') {
248
+ field.checked = field.value === data[name];
249
+ } else if (type === 'select-multiple') {
250
+ const values = Array.isArray(data[name]) ? data[name] : [data[name]];
251
+ Array.from(field.options).forEach(option => {
252
+ option.selected = values.includes(option.value);
253
+ });
254
+ } else {
255
+ field.value = data[name] || '';
256
+ }
273
257
  });
274
- this.submitHandlers = [];
275
- this.errors = {};
276
- this.rules = {};
277
- }
258
+ });
278
259
  }
279
260
 
280
- function useForm(formOrSelector, options = {}) {
281
- return new KupolaForm(formOrSelector, options);
261
+ reset() {
262
+ this.form.reset();
263
+ this.fields.forEach(field => this.clearError(field));
282
264
  }
283
265
 
284
- function createForm(formOrSelector, options = {}) {
285
- return new KupolaForm(formOrSelector, options);
266
+ destroy() {
267
+ this.fields.forEach(field => {
268
+ field.removeEventListener('blur', () => this.validateField(field));
269
+ field.removeEventListener('input', () => this.clearError(field));
270
+ });
271
+
272
+ this.form.removeEventListener('submit', () => {});
273
+
274
+ this.fields = null;
275
+ this.validators = null;
276
+ this.errorMessages = null;
277
+ this.form = null;
278
+ }
279
+ }
280
+
281
+ function initFormValidation(formSelector) {
282
+ const forms = document.querySelectorAll(formSelector || '.ds-form');
283
+
284
+ forms.forEach(form => {
285
+ if (form._kupolaForm) return;
286
+
287
+ const instance = new KupolaForm(form);
288
+ form._kupolaForm = instance;
289
+ });
290
+
291
+ return forms.length;
292
+ }
293
+
294
+ function getFormInstance(formElement) {
295
+ return formElement._kupolaForm;
296
+ }
297
+
298
+ function validateForm(formElement) {
299
+ const instance = formElement._kupolaForm;
300
+ if (instance) {
301
+ return instance.validate();
286
302
  }
303
+ return false;
304
+ }
287
305
 
306
+ export { KupolaForm, initFormValidation, getFormInstance, validateForm };
307
+
308
+ if (typeof window !== 'undefined') {
288
309
  window.KupolaForm = KupolaForm;
289
- window.useForm = useForm;
290
- window.createForm = createForm;
291
- })(window);
310
+ window.initFormValidation = initFormValidation;
311
+ window.getFormInstance = getFormInstance;
312
+ window.validateForm = validateForm;
313
+
314
+ if (window.kupolaInitializer) {
315
+ window.kupolaInitializer.register('form-validation', initFormValidation);
316
+ }
317
+ }