vgapp 1.4.8 → 1.5.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 (86) 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 +29 -15
  11. package/app/modules/vgfiles/js/droppable.js +37 -31
  12. package/app/modules/vgfiles/js/vgfiles.js +10 -4
  13. package/app/modules/vgfiles/scss/_mixins.scss +12 -2
  14. package/app/modules/vgfiles/scss/_variables.scss +3 -1
  15. package/app/modules/vgfiles/scss/vgfiles.scss +55 -10
  16. package/app/modules/vgformsender/js/vgformsender.js +5 -2
  17. package/app/modules/vgloadmore/js/vgloadmore.js +35 -7
  18. package/app/modules/vgnestable/js/vgnestable.js +140 -0
  19. package/app/modules/vgnestable/scss/_variables.scss +16 -9
  20. package/app/modules/vgnestable/scss/vgnestable.scss +35 -2
  21. package/app/modules/vgtable/index.js +3 -0
  22. package/app/modules/vgtable/js/_columns.js +567 -0
  23. package/app/modules/vgtable/js/_expandable.js +242 -0
  24. package/app/modules/vgtable/js/_filters.js +264 -0
  25. package/app/modules/vgtable/js/_fixed-columns.js +249 -0
  26. package/app/modules/vgtable/js/_i18n.js +75 -0
  27. package/app/modules/vgtable/js/_options.js +428 -0
  28. package/app/modules/vgtable/js/_pagination.js +491 -0
  29. package/app/modules/vgtable/js/_panning.js +124 -0
  30. package/app/modules/vgtable/js/_params-groups.js +21 -0
  31. package/app/modules/vgtable/js/_remote.js +395 -0
  32. package/app/modules/vgtable/js/_row-reorder.js +268 -0
  33. package/app/modules/vgtable/js/_search.js +134 -0
  34. package/app/modules/vgtable/js/_selection.js +113 -0
  35. package/app/modules/vgtable/js/_skeleton.js +123 -0
  36. package/app/modules/vgtable/js/_sorting.js +340 -0
  37. package/app/modules/vgtable/js/_states.js +143 -0
  38. package/app/modules/vgtable/js/_sticky-header.js +218 -0
  39. package/app/modules/vgtable/js/_url-state.js +178 -0
  40. package/app/modules/vgtable/js/vgtable.js +1444 -0
  41. package/app/modules/vgtable/readme.md +922 -0
  42. package/app/modules/vgtable/scss/_columns.scss +52 -0
  43. package/app/modules/vgtable/scss/_expandable.scss +54 -0
  44. package/app/modules/vgtable/scss/_fixed-columns.scss +117 -0
  45. package/app/modules/vgtable/scss/_pagination.scss +191 -0
  46. package/app/modules/vgtable/scss/_row-reorder.scss +45 -0
  47. package/app/modules/vgtable/scss/_skeleton.scss +65 -0
  48. package/app/modules/vgtable/scss/_sorting.scss +89 -0
  49. package/app/modules/vgtable/scss/_states.scss +70 -0
  50. package/app/modules/vgtable/scss/_sticky-header.scss +127 -0
  51. package/app/modules/vgtable/scss/_variables.scss +296 -0
  52. package/app/modules/vgtable/scss/vgtable.scss +206 -0
  53. package/app/utils/js/components/placement.js +64 -10
  54. package/app/vgapp.js +3 -3
  55. package/build/vgapp.css +1 -1
  56. package/build/vgapp.css.map +1 -1
  57. package/build/vgapp.js +1 -1
  58. package/build/vgapp.js.map +1 -1
  59. package/index.js +1 -1
  60. package/index.scss +3 -3
  61. package/package.json +4 -4
  62. package/app/modules/vgdynamictable/index.js +0 -4
  63. package/app/modules/vgdynamictable/js/editable.js +0 -438
  64. package/app/modules/vgdynamictable/js/expandable.js +0 -248
  65. package/app/modules/vgdynamictable/js/filters.js +0 -450
  66. package/app/modules/vgdynamictable/js/fixed.js +0 -566
  67. package/app/modules/vgdynamictable/js/options.js +0 -650
  68. package/app/modules/vgdynamictable/js/pagination.js +0 -623
  69. package/app/modules/vgdynamictable/js/search.js +0 -82
  70. package/app/modules/vgdynamictable/js/skeleton.js +0 -144
  71. package/app/modules/vgdynamictable/js/sortable.js +0 -442
  72. package/app/modules/vgdynamictable/js/summary-footer.js +0 -284
  73. package/app/modules/vgdynamictable/js/table-remote.js +0 -821
  74. package/app/modules/vgdynamictable/js/table-state.js +0 -243
  75. package/app/modules/vgdynamictable/js/table-url-state.js +0 -444
  76. package/app/modules/vgdynamictable/js/utils/common.js +0 -48
  77. package/app/modules/vgdynamictable/js/vgdynamictable.js +0 -3870
  78. package/app/modules/vgdynamictable/js/viewport.js +0 -322
  79. package/app/modules/vgdynamictable/readme.md +0 -251
  80. package/app/modules/vgdynamictable/scss/_actions.scss +0 -196
  81. package/app/modules/vgdynamictable/scss/_pagination.scss +0 -186
  82. package/app/modules/vgdynamictable/scss/_skeleton.scss +0 -63
  83. package/app/modules/vgdynamictable/scss/_sortable.scss +0 -66
  84. package/app/modules/vgdynamictable/scss/_table.scss +0 -137
  85. package/app/modules/vgdynamictable/scss/_variables.scss +0 -138
  86. 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;