vgapp 1.2.4 → 1.2.6

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 (51) hide show
  1. package/CHANGELOG.md +66 -7
  2. package/README.md +2 -1
  3. package/agents.md +2 -2
  4. package/app/modules/base-module.js +87 -10
  5. package/app/modules/vgalert/js/vgalert.js +133 -202
  6. package/app/modules/vgalert/readme.md +105 -46
  7. package/app/modules/vgalert/scss/vgalert.scss +18 -0
  8. package/app/modules/vgdynamictable/index.js +5 -0
  9. package/app/modules/vgdynamictable/js/editable.js +438 -0
  10. package/app/modules/vgdynamictable/js/expandable.js +248 -0
  11. package/app/modules/vgdynamictable/js/filters.js +450 -0
  12. package/app/modules/vgdynamictable/js/fixed.js +566 -0
  13. package/app/modules/vgdynamictable/js/options.js +646 -0
  14. package/app/modules/vgdynamictable/js/pagination.js +623 -0
  15. package/app/modules/vgdynamictable/js/search.js +82 -0
  16. package/app/modules/vgdynamictable/js/skeleton.js +136 -0
  17. package/app/modules/vgdynamictable/js/sortable.js +442 -0
  18. package/app/modules/vgdynamictable/js/summary-footer.js +284 -0
  19. package/app/modules/vgdynamictable/js/table-remote.js +821 -0
  20. package/app/modules/vgdynamictable/js/table-state.js +243 -0
  21. package/app/modules/vgdynamictable/js/table-url-state.js +444 -0
  22. package/app/modules/vgdynamictable/js/utils/common.js +48 -0
  23. package/app/modules/vgdynamictable/js/vgdynamictable.js +3829 -0
  24. package/app/modules/vgdynamictable/js/viewport.js +322 -0
  25. package/app/modules/vgdynamictable/readme.md +251 -0
  26. package/app/modules/vgdynamictable/scss/_actions.scss +193 -0
  27. package/app/modules/vgdynamictable/scss/_pagination.scss +183 -0
  28. package/app/modules/vgdynamictable/scss/_skeleton.scss +60 -0
  29. package/app/modules/vgdynamictable/scss/_sortable.scss +63 -0
  30. package/app/modules/vgdynamictable/scss/_table.scss +157 -0
  31. package/app/modules/vgdynamictable/scss/_variables.scss +130 -0
  32. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +267 -0
  33. package/app/modules/vgrangeslider/index.js +3 -0
  34. package/app/modules/vgrangeslider/js/skins.js +222 -0
  35. package/app/modules/vgrangeslider/js/vgrangeslider.js +704 -0
  36. package/app/modules/vgrangeslider/readme.md +523 -0
  37. package/app/modules/vgrangeslider/scss/_variables.scss +53 -0
  38. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +240 -0
  39. package/app/modules/vgtoast/js/vgtoast.js +111 -111
  40. package/app/modules/vgtooltip/index.js +3 -0
  41. package/app/modules/vgtooltip/js/vgtooltip.js +493 -0
  42. package/app/modules/vgtooltip/readme.md +181 -0
  43. package/app/modules/vgtooltip/scss/_variables.scss +15 -0
  44. package/app/modules/vgtooltip/scss/vgtooltip.scss +64 -0
  45. package/app/utils/js/components/ajax.js +116 -7
  46. package/app/utils/js/components/placement.js +112 -41
  47. package/build/vgapp.css +1 -1
  48. package/build/vgapp.css.map +1 -1
  49. package/index.js +20 -17
  50. package/index.scss +9 -0
  51. package/package.json +1 -1
@@ -0,0 +1,450 @@
1
+ import {normalizeNonNegativeInt, safeQuerySelector} from "./utils/common";
2
+
3
+ class Filters {
4
+ constructor(options = {}) {
5
+ this._options = Object.assign({
6
+ enabled: true,
7
+ formSelector: '',
8
+ formNode: null,
9
+ debounceMs: 300,
10
+ applyMode: 'auto', // auto | manual
11
+ button: {
12
+ apply: '[data-filter-apply]',
13
+ reset: '[data-filter-reset]',
14
+ },
15
+ fieldAttr: 'data-filter-field',
16
+ partAttr: 'data-filter-part',
17
+ typeAttr: 'data-filter-type',
18
+ valueAttr: 'data-filter-value',
19
+ operatorAttr: 'data-filter-operator',
20
+ defaultOperator: 'eq',
21
+ skipEmpty: true,
22
+ trimValues: true,
23
+ emitOnInit: false,
24
+ initialValues: {},
25
+ onChange: null,
26
+ onReset: null,
27
+ }, options);
28
+
29
+ this._form = null;
30
+ this._timer = null;
31
+ this._controls = [];
32
+ this._boundInput = this._handleInput.bind(this);
33
+ this._boundChange = this._handleChange.bind(this);
34
+ this._boundClick = this._handleClick.bind(this);
35
+ this._boundSubmit = this._handleSubmit.bind(this);
36
+ }
37
+
38
+ init() {
39
+ if (!this._options.enabled) {
40
+ return;
41
+ }
42
+
43
+ const form = this._resolveFormNode();
44
+ if (!form) {
45
+ return;
46
+ }
47
+
48
+ this._form = form;
49
+ this._syncCachedNodes();
50
+ this._form.addEventListener('input', this._boundInput);
51
+ this._form.addEventListener('change', this._boundChange);
52
+ this._form.addEventListener('click', this._boundClick);
53
+ this._form.addEventListener('submit', this._boundSubmit);
54
+
55
+ this.setValues(this._options.initialValues || {}, { emit: false });
56
+ if (this._options.emitOnInit) {
57
+ this._emit();
58
+ }
59
+ }
60
+
61
+ destroy() {
62
+ if (!this._form) {
63
+ return;
64
+ }
65
+ this._form.removeEventListener('input', this._boundInput);
66
+ this._form.removeEventListener('change', this._boundChange);
67
+ this._form.removeEventListener('click', this._boundClick);
68
+ this._form.removeEventListener('submit', this._boundSubmit);
69
+ this._form = null;
70
+ clearTimeout(this._timer);
71
+ this._timer = null;
72
+ this._controls = [];
73
+ }
74
+
75
+ getState() {
76
+ return this._collectState();
77
+ }
78
+
79
+ getValues() {
80
+ return this._collectState().params;
81
+ }
82
+
83
+ getFields() {
84
+ const unique = new Set();
85
+ this._getControls().forEach((control) => {
86
+ const field = this._getField(control);
87
+ if (field) {
88
+ unique.add(field);
89
+ }
90
+ });
91
+ return Array.from(unique);
92
+ }
93
+
94
+ setValues(nextValues = {}, options = {}) {
95
+ if (!this._form) {
96
+ return;
97
+ }
98
+ this._getControls().forEach((control) => {
99
+ const field = this._getField(control);
100
+ if (!field) {
101
+ return;
102
+ }
103
+ const part = this._getPart(control);
104
+ const key = part === 'operator' ? `${field}_op` : field;
105
+ const hasOwn = Object.prototype.hasOwnProperty.call(nextValues, key);
106
+ const nextValue = hasOwn ? nextValues[key] : '';
107
+ this._setControlValue(control, nextValue);
108
+ });
109
+ if (options.emit) {
110
+ this._emit();
111
+ }
112
+ }
113
+
114
+ _resolveFormNode() {
115
+ if (this._options.formNode && this._options.formNode.nodeType === 1) {
116
+ return this._options.formNode;
117
+ }
118
+ const selector = String(this._options.formSelector || '').trim();
119
+ if (!selector) {
120
+ return null;
121
+ }
122
+ return safeQuerySelector(selector);
123
+ }
124
+
125
+ _handleInput(event) {
126
+ if (!this._isFilterControl(event.target)) {
127
+ return;
128
+ }
129
+ if (this._isManualMode()) {
130
+ return;
131
+ }
132
+ const type = String(event.target.type || '').toLowerCase();
133
+ if (type === 'checkbox' || type === 'radio' || event.target.tagName === 'SELECT') {
134
+ this._emit();
135
+ return;
136
+ }
137
+ clearTimeout(this._timer);
138
+ this._timer = setTimeout(() => this._emit(), normalizeNonNegativeInt(this._options.debounceMs, 300));
139
+ }
140
+
141
+ _handleChange(event) {
142
+ if (!this._isFilterControl(event.target)) {
143
+ return;
144
+ }
145
+ if (this._isManualMode()) {
146
+ return;
147
+ }
148
+ clearTimeout(this._timer);
149
+ this._emit();
150
+ }
151
+
152
+ _handleClick(event) {
153
+ if (!this._form) {
154
+ return;
155
+ }
156
+ const button = this._options.button || {};
157
+ const applySelector = String(button.apply || '[data-filter-apply]').trim();
158
+ const resetSelector = String(button.reset || '[data-filter-reset]').trim();
159
+
160
+ const applyButton = applySelector ? event.target.closest(applySelector) : null;
161
+ if (applyButton) {
162
+ event.preventDefault();
163
+ clearTimeout(this._timer);
164
+ this._emit();
165
+ return;
166
+ }
167
+
168
+ const resetButton = resetSelector ? event.target.closest(resetSelector) : null;
169
+ if (!resetButton) {
170
+ return;
171
+ }
172
+ event.preventDefault();
173
+ this._reset();
174
+ if (typeof this._options.onReset === 'function') {
175
+ this._options.onReset();
176
+ }
177
+ clearTimeout(this._timer);
178
+ this._emit();
179
+ }
180
+
181
+ _handleSubmit(event) {
182
+ if (!this._isManualMode()) {
183
+ return;
184
+ }
185
+ event.preventDefault();
186
+ clearTimeout(this._timer);
187
+ this._emit();
188
+ }
189
+
190
+ _emit() {
191
+ if (!this._form || typeof this._options.onChange !== 'function') {
192
+ return;
193
+ }
194
+ this._options.onChange(this._collectState());
195
+ }
196
+
197
+ _collectState() {
198
+ const groups = new Map();
199
+ this._getControls().forEach((control) => {
200
+ const field = this._getField(control);
201
+ if (!field) {
202
+ return;
203
+ }
204
+ if (!groups.has(field)) {
205
+ groups.set(field, {
206
+ field,
207
+ type: this._getType(control),
208
+ operator: this._getStaticOperator(control),
209
+ value: '',
210
+ values: [],
211
+ });
212
+ }
213
+ const item = groups.get(field);
214
+ const part = this._getPart(control);
215
+ const value = this._readControlValue(control);
216
+ const controlType = String(control.type || '').toLowerCase();
217
+ if (part === 'operator') {
218
+ item.operator = String(value || '').trim() || item.operator || this._getDefaultOperator();
219
+ return;
220
+ }
221
+ if (part === 'type') {
222
+ item.type = String(value || '').trim() || item.type || 'text';
223
+ return;
224
+ }
225
+ // Ignore unchecked radio/checkbox values so they do not wipe selected values in a group.
226
+ if ((controlType === 'checkbox' || controlType === 'radio') && value === '') {
227
+ return;
228
+ }
229
+ if (Array.isArray(value)) {
230
+ item.values = value.slice();
231
+ item.value = item.values.join(',');
232
+ return;
233
+ }
234
+ item.value = value;
235
+ });
236
+
237
+ const filters = {};
238
+ const params = {};
239
+ groups.forEach((item, field) => {
240
+ const normalized = this._normalizeFilterItem(item);
241
+ if (!normalized) {
242
+ return;
243
+ }
244
+ filters[field] = normalized;
245
+
246
+ if (normalized.values.length > 0) {
247
+ params[field] = normalized.values.slice();
248
+ } else {
249
+ params[field] = normalized.value;
250
+ }
251
+ if (normalized.operator) {
252
+ params[`${field}_op`] = normalized.operator;
253
+ }
254
+ });
255
+
256
+ return {
257
+ filters,
258
+ params,
259
+ fields: Object.keys(filters),
260
+ meta: {
261
+ count: Object.keys(filters).length,
262
+ },
263
+ };
264
+ }
265
+
266
+ _normalizeFilterItem(item) {
267
+ const type = String(item.type || 'text').trim() || 'text';
268
+ const operator = String(item.operator || this._getDefaultOperator()).trim();
269
+ let value = item.value;
270
+ let values = Array.isArray(item.values) ? item.values.slice() : [];
271
+
272
+ if (this._options.trimValues) {
273
+ if (typeof value === 'string') {
274
+ value = value.trim();
275
+ }
276
+ values = values.map((entry) => String(entry || '').trim()).filter((entry) => entry !== '');
277
+ }
278
+
279
+ if (this._options.skipEmpty) {
280
+ const isEmptyArray = values.length === 0;
281
+ const isEmptyValue = value === undefined || value === null || String(value) === '';
282
+ if (isEmptyArray && isEmptyValue) {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ return {
288
+ field: String(item.field || '').trim(),
289
+ type,
290
+ operator,
291
+ value: value === undefined || value === null ? '' : value,
292
+ values,
293
+ };
294
+ }
295
+
296
+ _setControlValue(control, nextValue) {
297
+ const type = String(control.type || '').toLowerCase();
298
+ if (type === 'checkbox') {
299
+ const normalized = String(nextValue || '').toLowerCase();
300
+ control.checked = normalized === String(control.value || '').toLowerCase()
301
+ || normalized === '1'
302
+ || normalized === 'true'
303
+ || normalized === 'on';
304
+ return;
305
+ }
306
+ if (type === 'radio') {
307
+ control.checked = String(control.value || '') === String(nextValue || '');
308
+ return;
309
+ }
310
+ if (control.tagName === 'SELECT' && control.multiple) {
311
+ const rawValues = Array.isArray(nextValue)
312
+ ? nextValue
313
+ : String(nextValue || '').split(',');
314
+ const selected = new Set(rawValues
315
+ .map((value) => String(value || '').trim())
316
+ .filter((value) => value !== ''));
317
+ Array.from(control.options || []).forEach((option) => {
318
+ option.selected = selected.has(String(option.value || '').trim());
319
+ });
320
+ return;
321
+ }
322
+ control.value = String(nextValue || '');
323
+ }
324
+
325
+ _readControlValue(control) {
326
+ if (control.tagName === 'SELECT' && control.multiple) {
327
+ return Array.from(control.selectedOptions || [])
328
+ .map((option) => String(option.value || '').trim())
329
+ .filter((value) => value !== '');
330
+ }
331
+ const type = String(control.type || '').toLowerCase();
332
+ if (type === 'checkbox') {
333
+ return control.checked ? (control.value || '1') : '';
334
+ }
335
+ if (type === 'radio') {
336
+ return control.checked ? (control.value || '') : '';
337
+ }
338
+ return String(control.value || '');
339
+ }
340
+
341
+ _syncCachedNodes() {
342
+ if (!this._form) {
343
+ this._controls = [];
344
+ return;
345
+ }
346
+ const attr = this._getFieldAttr();
347
+ this._controls = Array.from(this._form.querySelectorAll(`[${attr}]`))
348
+ .filter((control) => this._isSupportedControl(control));
349
+ }
350
+
351
+ _getControls() {
352
+ if (!this._form) {
353
+ return [];
354
+ }
355
+ if (!Array.isArray(this._controls) || this._controls.length === 0) {
356
+ this._syncCachedNodes();
357
+ }
358
+ return this._controls;
359
+ }
360
+
361
+ _reset() {
362
+ this._getControls().forEach((control) => {
363
+ const type = String(control.type || '').toLowerCase();
364
+ if (type === 'checkbox' || type === 'radio') {
365
+ control.checked = false;
366
+ return;
367
+ }
368
+ if (control.tagName === 'SELECT') {
369
+ if (control.multiple) {
370
+ Array.from(control.options || []).forEach((option) => {
371
+ option.selected = false;
372
+ });
373
+ return;
374
+ }
375
+ control.selectedIndex = 0;
376
+ return;
377
+ }
378
+ control.value = '';
379
+ });
380
+ }
381
+
382
+ _getFieldAttr() {
383
+ return String(this._options.fieldAttr || 'data-filter-field').trim() || 'data-filter-field';
384
+ }
385
+
386
+ _getPartAttr() {
387
+ return String(this._options.partAttr || 'data-filter-part').trim() || 'data-filter-part';
388
+ }
389
+
390
+ _getTypeAttr() {
391
+ return String(this._options.typeAttr || 'data-filter-type').trim() || 'data-filter-type';
392
+ }
393
+
394
+ _getValueAttr() {
395
+ return String(this._options.valueAttr || 'data-filter-value').trim() || 'data-filter-value';
396
+ }
397
+
398
+ _getOperatorAttr() {
399
+ return String(this._options.operatorAttr || 'data-filter-operator').trim() || 'data-filter-operator';
400
+ }
401
+
402
+ _getDefaultOperator() {
403
+ return String(this._options.defaultOperator || 'eq').trim() || 'eq';
404
+ }
405
+
406
+ _getField(control) {
407
+ return String(control.getAttribute(this._getFieldAttr()) || '').trim();
408
+ }
409
+
410
+ _getPart(control) {
411
+ const part = String(control.getAttribute(this._getPartAttr()) || 'value').toLowerCase().trim();
412
+ if (part === 'operator' || part === 'type') {
413
+ return part;
414
+ }
415
+ return 'value';
416
+ }
417
+
418
+ _getType(control) {
419
+ const type = String(control.getAttribute(this._getTypeAttr()) || '').toLowerCase().trim();
420
+ return type || 'text';
421
+ }
422
+
423
+ _getStaticOperator(control) {
424
+ const operator = String(control.getAttribute(this._getOperatorAttr()) || '').trim();
425
+ return operator || '';
426
+ }
427
+
428
+ _isFilterControl(node) {
429
+ return Boolean(
430
+ node
431
+ && this._isSupportedControl(node)
432
+ && typeof node.getAttribute === 'function'
433
+ && String(node.getAttribute(this._getFieldAttr()) || '').trim() !== ''
434
+ );
435
+ }
436
+
437
+ _isSupportedControl(node) {
438
+ if (!node || typeof node.tagName !== 'string') {
439
+ return false;
440
+ }
441
+ const tag = String(node.tagName || '').toUpperCase();
442
+ return tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA';
443
+ }
444
+ _isManualMode() {
445
+ const mode = String(this._options.applyMode || 'auto').toLowerCase().trim();
446
+ return mode === 'manual';
447
+ }
448
+ }
449
+
450
+ export default Filters;