vgapp 1.4.7 → 1.4.9

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 (85) hide show
  1. package/README.md +24 -2
  2. package/app/modules/base-module.js +14 -2
  3. package/app/modules/module-fn.js +5 -4
  4. package/app/modules/vgalert/js/vgalert.js +21 -1
  5. package/app/modules/vgalert/scss/vgalert.scss +48 -0
  6. package/app/modules/vgdropdown/js/vgdropdown.js +58 -27
  7. package/app/modules/vgfilepreview/js/renderers/video-modal.js +5 -1
  8. package/app/modules/vgfilepreview/js/renderers/video.js +6 -1
  9. package/app/modules/vgfilepreview/js/vgfilepreview.js +5 -1
  10. package/app/modules/vgfiles/js/base.js +14 -5
  11. package/app/modules/vgfiles/js/droppable.js +37 -31
  12. package/app/modules/vgfiles/js/vgfiles.js +5 -2
  13. package/app/modules/vgfiles/scss/_variables.scss +3 -1
  14. package/app/modules/vgfiles/scss/vgfiles.scss +55 -10
  15. package/app/modules/vgformsender/js/vgformsender.js +5 -2
  16. package/app/modules/vgloadmore/js/vgloadmore.js +35 -7
  17. package/app/modules/vgnestable/js/vgnestable.js +140 -0
  18. package/app/modules/vgnestable/scss/_variables.scss +16 -9
  19. package/app/modules/vgnestable/scss/vgnestable.scss +35 -2
  20. package/app/modules/vgtable/index.js +3 -0
  21. package/app/modules/vgtable/js/_columns.js +567 -0
  22. package/app/modules/vgtable/js/_expandable.js +242 -0
  23. package/app/modules/vgtable/js/_filters.js +264 -0
  24. package/app/modules/vgtable/js/_fixed-columns.js +249 -0
  25. package/app/modules/vgtable/js/_i18n.js +75 -0
  26. package/app/modules/vgtable/js/_options.js +428 -0
  27. package/app/modules/vgtable/js/_pagination.js +491 -0
  28. package/app/modules/vgtable/js/_panning.js +124 -0
  29. package/app/modules/vgtable/js/_params-groups.js +21 -0
  30. package/app/modules/vgtable/js/_remote.js +395 -0
  31. package/app/modules/vgtable/js/_row-reorder.js +268 -0
  32. package/app/modules/vgtable/js/_search.js +134 -0
  33. package/app/modules/vgtable/js/_selection.js +113 -0
  34. package/app/modules/vgtable/js/_skeleton.js +123 -0
  35. package/app/modules/vgtable/js/_sorting.js +340 -0
  36. package/app/modules/vgtable/js/_states.js +143 -0
  37. package/app/modules/vgtable/js/_sticky-header.js +193 -0
  38. package/app/modules/vgtable/js/_url-state.js +178 -0
  39. package/app/modules/vgtable/js/vgtable.js +1444 -0
  40. package/app/modules/vgtable/readme.md +922 -0
  41. package/app/modules/vgtable/scss/_columns.scss +52 -0
  42. package/app/modules/vgtable/scss/_expandable.scss +54 -0
  43. package/app/modules/vgtable/scss/_fixed-columns.scss +117 -0
  44. package/app/modules/vgtable/scss/_pagination.scss +191 -0
  45. package/app/modules/vgtable/scss/_row-reorder.scss +45 -0
  46. package/app/modules/vgtable/scss/_skeleton.scss +65 -0
  47. package/app/modules/vgtable/scss/_sorting.scss +89 -0
  48. package/app/modules/vgtable/scss/_states.scss +70 -0
  49. package/app/modules/vgtable/scss/_sticky-header.scss +121 -0
  50. package/app/modules/vgtable/scss/_variables.scss +296 -0
  51. package/app/modules/vgtable/scss/vgtable.scss +206 -0
  52. package/app/utils/js/components/placement.js +64 -10
  53. package/app/vgapp.js +3 -3
  54. package/build/vgapp.css +1 -1
  55. package/build/vgapp.css.map +1 -1
  56. package/build/vgapp.js +1 -1
  57. package/build/vgapp.js.map +1 -1
  58. package/index.js +1 -1
  59. package/index.scss +3 -3
  60. package/package.json +5 -5
  61. package/app/modules/vgdynamictable/index.js +0 -4
  62. package/app/modules/vgdynamictable/js/editable.js +0 -438
  63. package/app/modules/vgdynamictable/js/expandable.js +0 -248
  64. package/app/modules/vgdynamictable/js/filters.js +0 -450
  65. package/app/modules/vgdynamictable/js/fixed.js +0 -566
  66. package/app/modules/vgdynamictable/js/options.js +0 -650
  67. package/app/modules/vgdynamictable/js/pagination.js +0 -623
  68. package/app/modules/vgdynamictable/js/search.js +0 -82
  69. package/app/modules/vgdynamictable/js/skeleton.js +0 -144
  70. package/app/modules/vgdynamictable/js/sortable.js +0 -442
  71. package/app/modules/vgdynamictable/js/summary-footer.js +0 -284
  72. package/app/modules/vgdynamictable/js/table-remote.js +0 -821
  73. package/app/modules/vgdynamictable/js/table-state.js +0 -243
  74. package/app/modules/vgdynamictable/js/table-url-state.js +0 -444
  75. package/app/modules/vgdynamictable/js/utils/common.js +0 -48
  76. package/app/modules/vgdynamictable/js/vgdynamictable.js +0 -3870
  77. package/app/modules/vgdynamictable/js/viewport.js +0 -322
  78. package/app/modules/vgdynamictable/readme.md +0 -251
  79. package/app/modules/vgdynamictable/scss/_actions.scss +0 -196
  80. package/app/modules/vgdynamictable/scss/_pagination.scss +0 -186
  81. package/app/modules/vgdynamictable/scss/_skeleton.scss +0 -63
  82. package/app/modules/vgdynamictable/scss/_sortable.scss +0 -66
  83. package/app/modules/vgdynamictable/scss/_table.scss +0 -137
  84. package/app/modules/vgdynamictable/scss/_variables.scss +0 -138
  85. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +0 -264
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Описание: многоуровневые сворачиваемые строки базовой таблицы VGTable.
3
+ * Возможности: дерево любой глубины, автоматические toggle-кнопки, Data API, публичное управление и события состояния.
4
+ */
5
+ import EventHandler from "../../../utils/js/dom/event";
6
+
7
+
8
+ const EVENT_TOGGLE = 'rowtoggle.vg.table';
9
+ const EVENT_EXPAND = 'rowexpand.vg.table';
10
+ const EVENT_COLLAPSE = 'rowcollapse.vg.table';
11
+ const GENERATED_TOGGLE_ATTRIBUTE = 'data-vg-table-expandable-generated';
12
+ const HIDDEN_ATTRIBUTE = 'data-vg-table-expand-hidden';
13
+ const DEPTH_ATTRIBUTE = 'data-expand-depth';
14
+ const PAGE_ATTRIBUTE = 'data-vg-table-page-row';
15
+
16
+ class _expandable {
17
+ constructor(table, options = {}) {
18
+ this._table = table;
19
+ this._options = options;
20
+ this._collapsed = new Set();
21
+ this._seeded = new Set();
22
+ this._rowById = new Map();
23
+ this._childrenByParent = new Map();
24
+ this._boundClick = this._handleClick.bind(this);
25
+ }
26
+
27
+ init() {
28
+ this._table.classList.add('vg-table-expandable');
29
+ this._table.addEventListener('click', this._boundClick);
30
+ return this.refresh();
31
+ }
32
+
33
+ dispose() {
34
+ this._table.removeEventListener('click', this._boundClick);
35
+ this._table.classList.remove('vg-table-expandable');
36
+ this._rows().forEach((row) => {
37
+ row.removeAttribute(DEPTH_ATTRIBUTE);
38
+ row.removeAttribute(HIDDEN_ATTRIBUTE);
39
+ row.style.removeProperty('--vg-table-expandable-depth');
40
+ row.hidden = row.getAttribute(PAGE_ATTRIBUTE) === 'hidden'
41
+ || row.hasAttribute('data-vg-table-filter-hidden');
42
+ row.querySelectorAll(`[${GENERATED_TOGGLE_ATTRIBUTE}]`).forEach((toggle) => toggle.remove());
43
+ const toggle = this._findToggle(row);
44
+ if (toggle) {
45
+ toggle.classList.remove('vg-table-expandable__toggle');
46
+ toggle.removeAttribute('aria-expanded');
47
+ toggle.removeAttribute('aria-label');
48
+ toggle.hidden = false;
49
+ }
50
+ });
51
+ this._collapsed.clear();
52
+ this._seeded.clear();
53
+ this._rowById.clear();
54
+ this._childrenByParent.clear();
55
+ }
56
+
57
+ refresh() {
58
+ const rows = this._rows();
59
+ const idAttr = this._idAttr();
60
+ const parentAttr = this._parentAttr();
61
+ this._rowById = new Map();
62
+ this._childrenByParent = new Map();
63
+ const parentById = new Map();
64
+
65
+ rows.forEach((row, index) => {
66
+ const fallback = `row-${index + 1}`;
67
+ const id = String(row.getAttribute(idAttr) || fallback).trim() || fallback;
68
+ row.setAttribute(idAttr, id);
69
+ this._rowById.set(id, row);
70
+ const parentId = String(row.getAttribute(parentAttr) || '').trim();
71
+ parentById.set(id, parentId);
72
+ if (parentId) {
73
+ if (!this._childrenByParent.has(parentId)) this._childrenByParent.set(parentId, []);
74
+ this._childrenByParent.get(parentId).push(id);
75
+ }
76
+ });
77
+
78
+ const depthCache = new Map();
79
+ this._rowById.forEach((row, id) => {
80
+ const depth = this._depth(id, parentById, depthCache, new Set());
81
+ const hasChildren = (this._childrenByParent.get(id) || []).length > 0;
82
+ row.setAttribute(DEPTH_ATTRIBUTE, String(depth));
83
+ row.style.setProperty('--vg-table-expandable-depth', String(depth));
84
+ if (hasChildren && !this._seeded.has(id)) {
85
+ if (this._initiallyCollapsed(row)) this._collapsed.add(id);
86
+ this._seeded.add(id);
87
+ }
88
+ this._syncToggle(row, id, hasChildren);
89
+ });
90
+
91
+ this._rowById.forEach((row, id) => {
92
+ const hidden = this._hasCollapsedAncestor(id, parentById);
93
+ if (hidden) row.setAttribute(HIDDEN_ATTRIBUTE, 'true');
94
+ else row.removeAttribute(HIDDEN_ATTRIBUTE);
95
+ row.hidden = hidden
96
+ || row.getAttribute(PAGE_ATTRIBUTE) === 'hidden'
97
+ || row.hasAttribute('data-vg-table-filter-hidden');
98
+ });
99
+
100
+ return this.getState();
101
+ }
102
+
103
+ toggle(row, expanded = null, emit = true) {
104
+ const target = this._resolveRow(row);
105
+ if (!target) return false;
106
+ const id = String(target.getAttribute(this._idAttr()) || '').trim();
107
+ if (!id || !(this._childrenByParent.get(id) || []).length) return false;
108
+ const nextExpanded = expanded === null ? this._collapsed.has(id) : expanded === true;
109
+ if (nextExpanded) this._collapsed.delete(id);
110
+ else this._collapsed.add(id);
111
+ this.refresh();
112
+ if (emit) this._emit(target, id, nextExpanded);
113
+ return true;
114
+ }
115
+
116
+ expand(row, emit = true) {
117
+ return this.toggle(row, true, emit);
118
+ }
119
+
120
+ collapse(row, emit = true) {
121
+ return this.toggle(row, false, emit);
122
+ }
123
+
124
+ getState() {
125
+ const parents = Array.from(this._childrenByParent.keys()).filter((id) => this._rowById.has(id));
126
+ return {
127
+ expanded: parents.filter((id) => !this._collapsed.has(id)),
128
+ collapsed: parents.filter((id) => this._collapsed.has(id)),
129
+ };
130
+ }
131
+
132
+ _handleClick(event) {
133
+ const target = event.target instanceof Element ? event.target : event.target?.parentElement;
134
+ let toggle = null;
135
+ try {
136
+ toggle = target?.closest(this._combinedToggleSelector());
137
+ } catch (_) {
138
+ toggle = target?.closest('[data-expand-toggle]');
139
+ }
140
+ if (!toggle || !this._table.contains(toggle)) return;
141
+ event.preventDefault();
142
+ const row = toggle.closest('tr');
143
+ if (row) this.toggle(row, null, true);
144
+ }
145
+
146
+ _syncToggle(row, id, hasChildren) {
147
+ let toggle = this._findToggle(row);
148
+ if (!toggle && hasChildren) {
149
+ const cell = row.cells?.[0];
150
+ if (cell) {
151
+ toggle = document.createElement('button');
152
+ toggle.type = 'button';
153
+ toggle.setAttribute('data-expand-toggle', id);
154
+ toggle.setAttribute(GENERATED_TOGGLE_ATTRIBUTE, '');
155
+ cell.prepend(toggle);
156
+ }
157
+ }
158
+ if (!toggle) return;
159
+ toggle.classList.add('vg-table-expandable__toggle');
160
+ toggle.hidden = !hasChildren;
161
+ if (!hasChildren) return;
162
+ const expanded = !this._collapsed.has(id);
163
+ const labels = this._options.labels || {};
164
+ toggle.setAttribute('data-expand-toggle', id);
165
+ toggle.setAttribute('aria-expanded', String(expanded));
166
+ toggle.setAttribute('aria-label', expanded ? labels.collapse || 'Свернуть дочерние строки' : labels.expand || 'Развернуть дочерние строки');
167
+ }
168
+
169
+ _findToggle(row) {
170
+ try {
171
+ return row.querySelector(this._combinedToggleSelector());
172
+ } catch (_) {
173
+ return row.querySelector('[data-expand-toggle]');
174
+ }
175
+ }
176
+
177
+ _combinedToggleSelector() {
178
+ const selector = String(this._options.toggleSelector || '[data-expand-toggle]').trim();
179
+ return selector === '[data-expand-toggle]' ? selector : `${selector}, [data-expand-toggle]`;
180
+ }
181
+
182
+ _depth(id, parentById, cache, trail) {
183
+ if (cache.has(id)) return cache.get(id);
184
+ if (trail.has(id)) return 0;
185
+ trail.add(id);
186
+ const parentId = String(parentById.get(id) || '').trim();
187
+ const depth = !parentId || parentId === id || !this._rowById.has(parentId)
188
+ ? 0
189
+ : this._depth(parentId, parentById, cache, trail) + 1;
190
+ trail.delete(id);
191
+ cache.set(id, depth);
192
+ return depth;
193
+ }
194
+
195
+ _hasCollapsedAncestor(id, parentById) {
196
+ const visited = new Set([id]);
197
+ let parentId = String(parentById.get(id) || '').trim();
198
+ while (parentId && !visited.has(parentId)) {
199
+ if (this._collapsed.has(parentId)) return true;
200
+ visited.add(parentId);
201
+ parentId = String(parentById.get(parentId) || '').trim();
202
+ }
203
+ return false;
204
+ }
205
+
206
+ _initiallyCollapsed(row) {
207
+ const value = row.getAttribute('data-expand-collapsed');
208
+ return value === null ? this._options.collapsed === true : this._boolean(value);
209
+ }
210
+
211
+ _resolveRow(row) {
212
+ if (row instanceof Element) {
213
+ const target = row.matches('tbody tr') ? row : row.closest('tbody tr');
214
+ return target && this._table.contains(target) ? target : null;
215
+ }
216
+ return this._rowById.get(String(row)) || null;
217
+ }
218
+
219
+ _rows() {
220
+ return Array.from(this._table.tBodies || []).flatMap((body) => Array.from(body.rows || []));
221
+ }
222
+
223
+ _idAttr() {
224
+ return String(this._options.idAttr || 'data-expand-id').trim() || 'data-expand-id';
225
+ }
226
+
227
+ _parentAttr() {
228
+ return String(this._options.parentAttr || 'data-expand-parent-id').trim() || 'data-expand-parent-id';
229
+ }
230
+
231
+ _boolean(value) {
232
+ return !['false', '0', 'off', 'no'].includes(String(value).toLowerCase());
233
+ }
234
+
235
+ _emit(row, id, expanded) {
236
+ const detail = {id, row, expanded, collapsed: !expanded};
237
+ EventHandler.trigger(this._table, EVENT_TOGGLE, detail);
238
+ EventHandler.trigger(this._table, expanded ? EVENT_EXPAND : EVENT_COLLAPSE, detail);
239
+ }
240
+ }
241
+
242
+ export default _expandable;
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Описание: форма локальных и remote-фильтров VGTable.
3
+ * Возможности: auto/manual применение, debounce, операторы, reset и публичное состояние.
4
+ */
5
+ import EventHandler from "../../../utils/js/dom/event";
6
+
7
+ const EVENT_CHANGE = 'filterschange.vg.table';
8
+
9
+ class _filters {
10
+ constructor(table, options = {}, onChange = null) {
11
+ this._table = table;
12
+ this._options = options;
13
+ this._onChange = typeof onChange === 'function' ? onChange : null;
14
+ this._form = null;
15
+ this._controls = [];
16
+ this._timer = null;
17
+ this._boundInput = this._handleInput.bind(this);
18
+ this._boundChange = this._handleChange.bind(this);
19
+ this._boundClick = this._handleClick.bind(this);
20
+ this._boundSubmit = this._handleSubmit.bind(this);
21
+ }
22
+
23
+ init() {
24
+ this._form = this._resolveForm();
25
+ if (!this._form) return this;
26
+ this.refresh();
27
+ this._form.addEventListener('input', this._boundInput);
28
+ this._form.addEventListener('change', this._boundChange);
29
+ this._form.addEventListener('click', this._boundClick);
30
+ this._form.addEventListener('submit', this._boundSubmit);
31
+ return this;
32
+ }
33
+
34
+ dispose() {
35
+ if (this._form) {
36
+ this._form.removeEventListener('input', this._boundInput);
37
+ this._form.removeEventListener('change', this._boundChange);
38
+ this._form.removeEventListener('click', this._boundClick);
39
+ this._form.removeEventListener('submit', this._boundSubmit);
40
+ }
41
+ if (this._timer) window.clearTimeout(this._timer);
42
+ this._timer = null;
43
+ this._controls = [];
44
+ this._form = null;
45
+ }
46
+
47
+ refresh() {
48
+ if (!this._form) return this.getState();
49
+ this._controls = Array.from(this._form.querySelectorAll(`[${this._fieldAttr()}]`))
50
+ .filter((control) => ['INPUT', 'SELECT', 'TEXTAREA'].includes(control.tagName));
51
+ return this.getState();
52
+ }
53
+
54
+ getState() {
55
+ const groups = new Map();
56
+ this._controls.forEach((control) => {
57
+ const field = String(control.getAttribute(this._fieldAttr()) || '').trim();
58
+ if (!field) return;
59
+ if (!groups.has(field)) groups.set(field, {
60
+ field,
61
+ type: String(control.getAttribute(this._typeAttr()) || 'text').trim() || 'text',
62
+ operator: String(control.getAttribute(this._operatorAttr()) || this._options.defaultOperator || 'eq').trim() || 'eq',
63
+ value: '',
64
+ values: [],
65
+ });
66
+ const item = groups.get(field);
67
+ const part = String(control.getAttribute(this._partAttr()) || 'value').toLowerCase();
68
+ const value = this._readValue(control);
69
+ if (part === 'operator') {
70
+ item.operator = String(value || this._options.defaultOperator || 'eq').trim() || 'eq';
71
+ return;
72
+ }
73
+ if (part === 'type') {
74
+ item.type = String(value || 'text').trim() || 'text';
75
+ return;
76
+ }
77
+ if (Array.isArray(value)) {
78
+ item.values = value;
79
+ item.value = value.join(',');
80
+ return;
81
+ }
82
+ if ((control.type === 'checkbox' || control.type === 'radio') && value === '') return;
83
+ item.value = value;
84
+ });
85
+
86
+ const filters = {};
87
+ const params = {};
88
+ groups.forEach((item, field) => {
89
+ const normalized = this._normalizeItem(item);
90
+ if (!normalized) return;
91
+ filters[field] = normalized;
92
+ params[field] = normalized.values.length ? normalized.values.slice() : normalized.value;
93
+ if (normalized.operator) params[`${field}_op`] = normalized.operator;
94
+ });
95
+ return {filters, params, fields: Object.keys(filters), meta: {count: Object.keys(filters).length}};
96
+ }
97
+
98
+ getFields() {
99
+ return this._fieldNames();
100
+ }
101
+
102
+ setValues(values = {}, options = {}) {
103
+ const source = values && typeof values === 'object' ? values : {};
104
+ this._controls.forEach((control) => {
105
+ const field = String(control.getAttribute(this._fieldAttr()) || '').trim();
106
+ if (!field) return;
107
+ const part = String(control.getAttribute(this._partAttr()) || 'value').toLowerCase();
108
+ const key = part === 'operator' ? `${field}_op` : field;
109
+ this._writeValue(control, Object.prototype.hasOwnProperty.call(source, key) ? source[key] : '');
110
+ });
111
+ if (options.emit === true) this._emit(String(options.source || 'api'));
112
+ return this.getState();
113
+ }
114
+
115
+ reset(options = {}) {
116
+ this._controls.forEach((control) => this._resetControl(control));
117
+ if (options.emit !== false) this._emit(String(options.source || 'reset'));
118
+ return this.getState();
119
+ }
120
+
121
+ _handleInput(event) {
122
+ if (!this._isControl(event.target) || this._isManual()) return;
123
+ if (['checkbox', 'radio'].includes(String(event.target.type).toLowerCase()) || event.target.tagName === 'SELECT') {
124
+ this._emit('input');
125
+ return;
126
+ }
127
+ this._schedule('input');
128
+ }
129
+
130
+ _handleChange(event) {
131
+ if (!this._isControl(event.target) || this._isManual()) return;
132
+ this._clearTimer();
133
+ this._emit('change');
134
+ }
135
+
136
+ _handleClick(event) {
137
+ const buttons = this._options.buttons || {};
138
+ const apply = this._closest(event.target, buttons.apply || '[data-filter-apply]');
139
+ if (apply && this._form.contains(apply)) {
140
+ event.preventDefault();
141
+ this._clearTimer();
142
+ this._emit('apply');
143
+ return;
144
+ }
145
+ const reset = this._closest(event.target, buttons.reset || '[data-filter-reset]');
146
+ if (!reset || !this._form.contains(reset)) return;
147
+ event.preventDefault();
148
+ this._clearTimer();
149
+ this.reset({source: 'reset'});
150
+ }
151
+
152
+ _handleSubmit(event) {
153
+ if (!this._isManual()) return;
154
+ event.preventDefault();
155
+ this._clearTimer();
156
+ this._emit('submit');
157
+ }
158
+
159
+ _emit(source) {
160
+ const state = Object.assign(this.getState(), {source});
161
+ EventHandler.trigger(this._table, EVENT_CHANGE, state);
162
+ this._onChange?.(state);
163
+ }
164
+
165
+ _schedule(source) {
166
+ this._clearTimer();
167
+ this._timer = window.setTimeout(() => {
168
+ this._timer = null;
169
+ this._emit(source);
170
+ }, this._nonNegativeInt(this._options.debounce, 300));
171
+ }
172
+
173
+ _clearTimer() {
174
+ if (this._timer) window.clearTimeout(this._timer);
175
+ this._timer = null;
176
+ }
177
+
178
+ _resolveForm() {
179
+ if (typeof Element !== 'undefined' && this._options.form instanceof Element) return this._options.form;
180
+ const selector = String(this._options.form || '').trim();
181
+ if (!selector) return null;
182
+ try { return document.querySelector(selector); } catch (_) { return null; }
183
+ }
184
+
185
+ _normalizeItem(item) {
186
+ let value = item.value;
187
+ let values = Array.isArray(item.values) ? item.values.slice() : [];
188
+ if (this._options.trim !== false) {
189
+ if (typeof value === 'string') value = value.trim();
190
+ values = values.map((entry) => String(entry ?? '').trim()).filter(Boolean);
191
+ }
192
+ if (this._options.skipEmpty !== false && !values.length && (value === null || value === undefined || String(value) === '')) return null;
193
+ return {
194
+ field: item.field,
195
+ type: String(item.type || 'text').toLowerCase(),
196
+ operator: String(item.operator || this._options.defaultOperator || 'eq').toLowerCase(),
197
+ value: value ?? '',
198
+ values,
199
+ };
200
+ }
201
+
202
+ _readValue(control) {
203
+ if (control.tagName === 'SELECT' && control.multiple) {
204
+ return Array.from(control.selectedOptions).map((option) => option.value).filter(Boolean);
205
+ }
206
+ if (control.type === 'checkbox' || control.type === 'radio') {
207
+ if (!control.checked) return '';
208
+ return control.getAttribute(this._valueAttr()) ?? control.value ?? '1';
209
+ }
210
+ return control.value ?? '';
211
+ }
212
+
213
+ _writeValue(control, value) {
214
+ if (control.type === 'checkbox' || control.type === 'radio') {
215
+ const values = Array.isArray(value) ? value.map(String) : [String(value ?? '')];
216
+ const own = String(control.getAttribute(this._valueAttr()) ?? control.value ?? '1');
217
+ control.checked = values.includes(own) || (control.type === 'checkbox' && ['1', 'true', 'on'].includes(values[0]?.toLowerCase()));
218
+ return;
219
+ }
220
+ if (control.tagName === 'SELECT' && control.multiple) {
221
+ const values = new Set((Array.isArray(value) ? value : String(value ?? '').split(',')).map(String));
222
+ Array.from(control.options).forEach((option) => { option.selected = values.has(option.value); });
223
+ return;
224
+ }
225
+ control.value = String(value ?? '');
226
+ }
227
+
228
+ _resetControl(control) {
229
+ if (control.type === 'checkbox' || control.type === 'radio') control.checked = control.defaultChecked;
230
+ else if (control.tagName === 'SELECT') Array.from(control.options).forEach((option) => { option.selected = option.defaultSelected; });
231
+ else control.value = control.defaultValue || '';
232
+ }
233
+
234
+ _fieldNames() {
235
+ return Array.from(new Set(this._controls.map((control) => String(control.getAttribute(this._fieldAttr()) || '').trim()).filter(Boolean)));
236
+ }
237
+
238
+ _isControl(node) {
239
+ return this._controls.includes(node);
240
+ }
241
+
242
+ _closest(node, selector) {
243
+ if (!node?.closest) return null;
244
+ const normalized = String(selector || '').trim();
245
+ if (!normalized) return null;
246
+ try { return node.closest(normalized); } catch (_) { return null; }
247
+ }
248
+
249
+ _isManual() {
250
+ return String(this._options.apply || 'auto').toLowerCase() === 'manual';
251
+ }
252
+
253
+ _fieldAttr() { return String(this._options.fieldAttr || 'data-filter-field'); }
254
+ _partAttr() { return String(this._options.partAttr || 'data-filter-part'); }
255
+ _typeAttr() { return String(this._options.typeAttr || 'data-filter-type'); }
256
+ _valueAttr() { return String(this._options.valueAttr || 'data-filter-value'); }
257
+ _operatorAttr() { return String(this._options.operatorAttr || 'data-filter-operator'); }
258
+ _nonNegativeInt(value, fallback) {
259
+ const parsed = Number.parseInt(value, 10);
260
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
261
+ }
262
+ }
263
+
264
+ export default _filters;