vgapp 1.2.5 → 1.3.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 (48) hide show
  1. package/README.md +131 -20
  2. package/app/modules/base-module.js +111 -17
  3. package/app/modules/vgalert/js/vgalert.js +3 -3
  4. package/app/modules/vgdynamictable/index.js +5 -0
  5. package/app/modules/vgdynamictable/js/editable.js +438 -0
  6. package/app/modules/vgdynamictable/js/expandable.js +248 -0
  7. package/app/modules/vgdynamictable/js/filters.js +450 -0
  8. package/app/modules/vgdynamictable/js/fixed.js +566 -0
  9. package/app/modules/vgdynamictable/js/options.js +646 -0
  10. package/app/modules/vgdynamictable/js/pagination.js +623 -0
  11. package/app/modules/vgdynamictable/js/search.js +82 -0
  12. package/app/modules/vgdynamictable/js/skeleton.js +136 -0
  13. package/app/modules/vgdynamictable/js/sortable.js +442 -0
  14. package/app/modules/vgdynamictable/js/summary-footer.js +284 -0
  15. package/app/modules/vgdynamictable/js/table-remote.js +821 -0
  16. package/app/modules/vgdynamictable/js/table-state.js +243 -0
  17. package/app/modules/vgdynamictable/js/table-url-state.js +444 -0
  18. package/app/modules/vgdynamictable/js/utils/common.js +48 -0
  19. package/app/modules/vgdynamictable/js/vgdynamictable.js +3829 -0
  20. package/app/modules/vgdynamictable/js/viewport.js +322 -0
  21. package/app/modules/vgdynamictable/readme.md +251 -0
  22. package/app/modules/vgdynamictable/scss/_actions.scss +193 -0
  23. package/app/modules/vgdynamictable/scss/_pagination.scss +183 -0
  24. package/app/modules/vgdynamictable/scss/_skeleton.scss +60 -0
  25. package/app/modules/vgdynamictable/scss/_sortable.scss +63 -0
  26. package/app/modules/vgdynamictable/scss/_table.scss +157 -0
  27. package/app/modules/vgdynamictable/scss/_variables.scss +130 -0
  28. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +267 -0
  29. package/app/modules/vgrangeslider/index.js +3 -0
  30. package/app/modules/vgrangeslider/js/skins.js +222 -0
  31. package/app/modules/vgrangeslider/js/vgrangeslider.js +704 -0
  32. package/app/modules/vgrangeslider/readme.md +523 -0
  33. package/app/modules/vgrangeslider/scss/_variables.scss +53 -0
  34. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +240 -0
  35. package/app/utils/js/components/ajax.js +116 -7
  36. package/app/vgapp.js +107 -0
  37. package/build/vgapp.css +2 -3246
  38. package/build/vgapp.css.map +1 -1
  39. package/build/vgapp.js +3 -30
  40. package/build/vgapp.js.LICENSE.txt +1 -0
  41. package/build/vgapp.js.map +1 -1
  42. package/index.js +15 -8
  43. package/index.scss +6 -0
  44. package/package.json +21 -1
  45. package/.gitattributes +0 -1
  46. package/CHANGELOG.md +0 -369
  47. package/agents.md +0 -14
  48. package/webpack.config.js +0 -63
@@ -0,0 +1,284 @@
1
+ import {normalizeBooleanOption, normalizeEnabledFlag, safeQuerySelector} from "./utils/common";
2
+
3
+ const summaryFooterMethods = {
4
+ _renderSummary(meta = {}) {
5
+ if (!this._isSummaryEnabled() || !this._summaryNode || !this._isRemote) {
6
+ return;
7
+ }
8
+
9
+ const totalRaw = Number.parseInt(meta.total, 10);
10
+ const total = Number.isFinite(totalRaw) && totalRaw >= 0 ? totalRaw : 0;
11
+ const summaryI18n = this._getI18nSection('summary');
12
+ const foundLabel = (summaryI18n.foundLabel && String(summaryI18n.foundLabel).trim()) || 'Found';
13
+ const totalLabel = `${foundLabel}: ${total}`;
14
+ const active = this._getActiveCriteria();
15
+
16
+ const chips = active.map((item) => {
17
+ const label = this._escapeHtml(item.label);
18
+ const value = this._escapeHtml(item.value);
19
+ return `<span class="vgdt-summary__chip"><b>${label}:</b> ${value}</span>`;
20
+ }).join('');
21
+
22
+ this._summaryNode.innerHTML = `
23
+ <div class="vgdt-summary__row">
24
+ <span class="vgdt-summary__count">${this._escapeHtml(totalLabel)}</span>
25
+ ${chips ? `<div class="vgdt-summary__chips">${chips}</div>` : ''}
26
+ </div>
27
+ `;
28
+ this._summaryNode.hidden = false;
29
+ },
30
+
31
+ _isSummaryEnabled() {
32
+ const options = this._params.summary || {};
33
+ if (options.enable !== undefined && options.enable !== null) {
34
+ return normalizeBooleanOption(options.enable, false);
35
+ }
36
+
37
+ const attr = this._element.getAttribute('data-summary-enable');
38
+ return normalizeEnabledFlag(attr, false);
39
+ },
40
+
41
+ _getActiveCriteria() {
42
+ const list = [];
43
+ const searchParam = this._getSearchParamName();
44
+ const searchValue = String(this._remoteParams[searchParam] || '').trim();
45
+ if (searchValue !== '') {
46
+ const summaryI18n = this._getI18nSection('summary');
47
+ const searchLabel = (summaryI18n.searchLabel && String(summaryI18n.searchLabel).trim()) || 'Search';
48
+ list.push({ label: searchLabel, value: searchValue });
49
+ }
50
+
51
+ Object.keys(this._remoteParams || {}).forEach((key) => {
52
+ if (key === searchParam) {
53
+ return;
54
+ }
55
+ const value = String(this._remoteParams[key] || '').trim();
56
+ if (value === '') {
57
+ return;
58
+ }
59
+ const label = this._getFilterLabelByParam(key) || key;
60
+ list.push({ label, value });
61
+ });
62
+
63
+ return list;
64
+ },
65
+
66
+ _getFilterLabelByParam(param) {
67
+ const selector = this._getFiltersFormSelector();
68
+ if (!selector) {
69
+ return '';
70
+ }
71
+
72
+ const shouldRefreshFormCache = this._filtersFormCacheSelector !== selector
73
+ || !this._filtersFormCacheNode
74
+ || !this._filtersFormCacheNode.isConnected;
75
+ if (shouldRefreshFormCache) {
76
+ this._filtersFormCacheSelector = selector;
77
+ this._filtersFormCacheNode = safeQuerySelector(selector);
78
+ this._filtersLabelCache = {};
79
+ }
80
+
81
+ const form = this._filtersFormCacheNode;
82
+ if (!form) {
83
+ return '';
84
+ }
85
+
86
+ const cache = this._filtersLabelCache && typeof this._filtersLabelCache === 'object'
87
+ ? this._filtersLabelCache
88
+ : {};
89
+ this._filtersLabelCache = cache;
90
+ if (Object.prototype.hasOwnProperty.call(cache, param)) {
91
+ return cache[param];
92
+ }
93
+
94
+ const options = this._params.filters || {};
95
+ const fieldAttr = String(options.fieldAttr || 'data-filter-field').trim() || 'data-filter-field';
96
+ const partAttr = String(options.partAttr || 'data-filter-part').trim() || 'data-filter-part';
97
+ const normalizedParam = String(param || '').trim();
98
+ const isOperator = /_op$/i.test(normalizedParam);
99
+ const fieldKey = normalizedParam.replace(/_op$/i, '');
100
+ const controlSelector = isOperator
101
+ ? `[${fieldAttr}="${fieldKey}"][${partAttr}="operator"]`
102
+ : `[${fieldAttr}="${fieldKey}"]`;
103
+ const control = form.querySelector(controlSelector);
104
+ if (!control) {
105
+ cache[param] = '';
106
+ return '';
107
+ }
108
+ const field = control.closest('.field');
109
+ const labelNode = field ? field.querySelector('.field__label') : null;
110
+ if (labelNode) {
111
+ const label = String(labelNode.textContent || '').trim();
112
+ cache[param] = label;
113
+ return label;
114
+ }
115
+ cache[param] = '';
116
+ return '';
117
+ },
118
+
119
+ _getFooterCell() {
120
+ if (!this._isFooterEnabled()) {
121
+ return null;
122
+ }
123
+ const foot = this._element.tFoot || this._element.querySelector('tfoot');
124
+ if (!foot || !foot.rows || !foot.rows.length) {
125
+ return null;
126
+ }
127
+ return foot.querySelector('[data-vgdt-footer]') || foot.rows[0].cells[0] || null;
128
+ },
129
+
130
+ _clearFooter() {
131
+ const cell = this._getFooterCell();
132
+ if (!cell) {
133
+ return;
134
+ }
135
+ cell.innerHTML = '';
136
+ },
137
+
138
+ _renderFooterFromCurrentState() {
139
+ if (!this._isFooterEnabled()) {
140
+ return;
141
+ }
142
+ if (this._isRemote) {
143
+ return;
144
+ }
145
+ const visibleRows = this._getVisibleRows();
146
+ const records = visibleRows.map((row) => this._mapDomRowToRecord(row));
147
+ this._renderFooterStats(records, null);
148
+ },
149
+
150
+ _renderFooterFromRows(rows, meta) {
151
+ if (!this._isFooterEnabled()) {
152
+ return;
153
+ }
154
+ if (!this._isRemote) {
155
+ return;
156
+ }
157
+ this._renderFooterStats(Array.isArray(rows) ? rows : [], meta || null);
158
+ },
159
+
160
+ _getVisibleRows() {
161
+ const body = this._getBody();
162
+ return Array.from(body.rows || []).filter((row) => !row.hidden);
163
+ },
164
+
165
+ _mapDomRowToRecord(row) {
166
+ const valueByField = {};
167
+ this._fields.forEach((field, index) => {
168
+ const cell = row && row.cells && row.cells[index] ? row.cells[index] : null;
169
+ valueByField[field] = cell ? String(cell.textContent || '').trim() : '';
170
+ });
171
+ return valueByField;
172
+ },
173
+
174
+ _renderFooterStats(records, meta) {
175
+ if (!this._isFooterEnabled()) {
176
+ return;
177
+ }
178
+ const cell = this._getFooterCell();
179
+ if (!cell) {
180
+ return;
181
+ }
182
+
183
+ const rows = Array.isArray(records) ? records : [];
184
+ const visibleCount = rows.length;
185
+ let sumPrice = 0;
186
+ let sumStock = 0;
187
+ let ratingSum = 0;
188
+ let ratingCount = 0;
189
+
190
+ rows.forEach((row) => {
191
+ const price = this._extractNumber(row.price);
192
+ if (Number.isFinite(price)) {
193
+ sumPrice += price;
194
+ }
195
+
196
+ const stock = this._extractNumber(row.stock);
197
+ if (Number.isFinite(stock)) {
198
+ sumStock += stock;
199
+ }
200
+
201
+ const rating = this._extractNumber(row.rating);
202
+ if (Number.isFinite(rating)) {
203
+ ratingSum += rating;
204
+ ratingCount += 1;
205
+ }
206
+ });
207
+
208
+ const avgRating = ratingCount > 0 ? (ratingSum / ratingCount) : 0;
209
+
210
+ const summaryI18n = this._getI18nSection('summary');
211
+ const visibleRowsLabel = (summaryI18n.visibleRowsLabel && String(summaryI18n.visibleRowsLabel).trim()) || 'Rows on page';
212
+ const pagePriceSumLabel = (summaryI18n.pagePriceSumLabel && String(summaryI18n.pagePriceSumLabel).trim()) || 'Price sum';
213
+ const pageStockSumLabel = (summaryI18n.pageStockSumLabel && String(summaryI18n.pageStockSumLabel).trim()) || 'Total stock';
214
+ const pageAvgRatingLabel = (summaryI18n.pageAvgRatingLabel && String(summaryI18n.pageAvgRatingLabel).trim()) || 'Average rating';
215
+ const foundLabel = (summaryI18n.foundLabel && String(summaryI18n.foundLabel).trim()) || 'Found';
216
+
217
+ const metaTotalRaw = meta && Object.prototype.hasOwnProperty.call(meta, 'total')
218
+ ? Number.parseInt(meta.total, 10)
219
+ : NaN;
220
+ const hasTotal = Number.isFinite(metaTotalRaw) && metaTotalRaw >= 0;
221
+ const totalHtml = hasTotal
222
+ ? `<span class="vgdt-foot__item"><b>${this._escapeHtml(foundLabel)}:</b> ${metaTotalRaw}</span>`
223
+ : '';
224
+
225
+ const finiteSumPrice = Number.isFinite(sumPrice) ? sumPrice : 0;
226
+ const finiteSumStock = Number.isFinite(sumStock) ? sumStock : 0;
227
+ const finiteAvgRating = Number.isFinite(avgRating) ? avgRating : 0;
228
+
229
+ cell.innerHTML = `
230
+ <div class="vgdt-foot">
231
+ <span class="vgdt-foot__item"><b>${this._escapeHtml(visibleRowsLabel)}:</b> ${visibleCount}</span>
232
+ ${totalHtml}
233
+ <span class="vgdt-foot__item"><b>${this._escapeHtml(pagePriceSumLabel)}:</b> ${finiteSumPrice.toFixed(2)} RUB</span>
234
+ <span class="vgdt-foot__item"><b>${this._escapeHtml(pageStockSumLabel)}:</b> ${Math.round(finiteSumStock)}</span>
235
+ <span class="vgdt-foot__item"><b>${this._escapeHtml(pageAvgRatingLabel)}:</b> ${finiteAvgRating.toFixed(2)}</span>
236
+ </div>
237
+ `;
238
+ },
239
+
240
+ _isFooterEnabled() {
241
+ const options = this._params.footer || {};
242
+ if (options.enable !== undefined && options.enable !== null) {
243
+ return normalizeBooleanOption(options.enable, false);
244
+ }
245
+
246
+ const attr = this._element.getAttribute('data-footer-enable');
247
+ return normalizeEnabledFlag(attr, false);
248
+ },
249
+
250
+ _applyFooterVisibility() {
251
+ const foot = this._element.tFoot || this._element.querySelector('tfoot');
252
+ if (!foot) {
253
+ return;
254
+ }
255
+ foot.hidden = !this._isFooterEnabled();
256
+ },
257
+
258
+ _formatFieldValue(field, value) {
259
+ if (field === 'price') {
260
+ const amount = Number.parseFloat(value);
261
+ if (Number.isFinite(amount)) {
262
+ return `${amount.toFixed(2)} RUB`;
263
+ }
264
+ return '';
265
+ }
266
+
267
+ if (field === 'category' && (value === null || value === undefined || value === '')) {
268
+ return '-';
269
+ }
270
+
271
+ return value === null || value === undefined ? '' : String(value);
272
+ },
273
+
274
+ _escapeHtml(value) {
275
+ return String(value)
276
+ .replace(/&/g, '&amp;')
277
+ .replace(/</g, '&lt;')
278
+ .replace(/>/g, '&gt;')
279
+ .replace(/"/g, '&quot;')
280
+ .replace(/'/g, '&#39;');
281
+ },
282
+ };
283
+
284
+ export default summaryFooterMethods;