@kupola/kupola 1.2.0 → 1.3.1

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 (97) 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/dist/css/accessibility.css +119 -0
  8. package/dist/css/animations.css +224 -0
  9. package/dist/css/brand-themes.css +300 -0
  10. package/dist/css/colors_and_type.css +441 -0
  11. package/dist/css/components-ext.css +4165 -0
  12. package/dist/css/components.css +1483 -0
  13. package/dist/css/responsive.css +697 -0
  14. package/dist/css/scaffold.css +145 -0
  15. package/dist/css/states.css +316 -0
  16. package/dist/css/theme-dark.css +296 -0
  17. package/dist/css/theme-light.css +296 -0
  18. package/dist/css/utilities.css +171 -0
  19. package/dist/kupola.cjs.js +219 -161
  20. package/dist/kupola.cjs.js.map +1 -1
  21. package/dist/kupola.esm.js +6643 -5709
  22. package/dist/kupola.esm.js.map +1 -1
  23. package/dist/kupola.umd.js +219 -161
  24. package/dist/kupola.umd.js.map +1 -1
  25. package/dist/plugins/vite-plugin-kupola.js +120 -0
  26. package/dist/types/kupola.d.ts +421 -323
  27. package/js/calendar.js +334 -25
  28. package/js/carousel.js +182 -48
  29. package/js/collapse.js +148 -34
  30. package/js/color-picker.js +416 -108
  31. package/js/component.js +8 -19
  32. package/js/countdown.js +9 -8
  33. package/js/data-bind.js +73 -16
  34. package/js/datepicker.js +488 -110
  35. package/js/depends.js +710 -0
  36. package/js/dialog.js +4 -2
  37. package/js/drawer.js +172 -8
  38. package/js/dropdown.js +272 -17
  39. package/js/dynamic-tags.js +156 -40
  40. package/js/fileupload.js +9 -8
  41. package/js/form.js +280 -254
  42. package/js/global-events.js +281 -188
  43. package/js/heatmap.js +10 -7
  44. package/js/i18n.js +18 -10
  45. package/js/icons.js +141 -161
  46. package/js/image-preview.js +146 -2
  47. package/js/initializer.js +113 -71
  48. package/js/kupola-core.js +123 -45
  49. package/js/kupola-lifecycle.js +13 -11
  50. package/js/message.js +8 -1
  51. package/js/modal.js +207 -59
  52. package/js/notification.js +8 -1
  53. package/js/numberinput.js +9 -8
  54. package/js/pagination.js +263 -0
  55. package/js/registry.js +29 -12
  56. package/js/select.js +482 -27
  57. package/js/slide-captcha.js +11 -2
  58. package/js/slider.js +442 -25
  59. package/js/statcard.js +9 -7
  60. package/js/table.js +1210 -0
  61. package/js/tag.js +268 -14
  62. package/js/theme.js +14 -43
  63. package/js/timepicker.js +335 -66
  64. package/js/tooltip.js +317 -86
  65. package/js/utils.js +6 -2
  66. package/js/validation.js +6 -2
  67. package/js/virtual-list.js +11 -7
  68. package/js/web-components.js +288 -0
  69. package/package.json +77 -67
  70. package/plugins/vite-plugin-kupola.js +120 -0
  71. package/types/kupola.d.ts +421 -323
  72. package/CHANGELOG.md +0 -130
  73. package/INTEGRATION.md +0 -440
  74. package/PROJECT_SUMMARY.md +0 -312
  75. package/SKILL.md +0 -572
  76. package/dist/utils/utils/Kupola.cs +0 -77
  77. package/dist/utils/utils/Kupola.java +0 -104
  78. package/dist/utils/utils/kupola.go +0 -120
  79. package/dist/utils/utils/kupola.js +0 -63
  80. package/dist/utils/utils/kupola.py +0 -1392
  81. package/dist/utils/utils/kupola.rb +0 -69
  82. package/js/composition-api.js +0 -458
  83. package/js/error-handler.js +0 -181
  84. package/js/http.js +0 -419
  85. package/js/kupola-devtools.js +0 -598
  86. package/js/performance.js +0 -250
  87. package/js/router.js +0 -396
  88. package/js/security.js +0 -189
  89. package/js/test-utils.js +0 -251
  90. package/templates/base.html +0 -30
  91. package/templates/base_dashboard.html +0 -99
  92. package/utils/Kupola.cs +0 -77
  93. package/utils/Kupola.java +0 -104
  94. package/utils/kupola.go +0 -120
  95. package/utils/kupola.js +0 -63
  96. package/utils/kupola.py +0 -1392
  97. 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
+ }