@doyosi/laraisy 1.0.2 → 1.0.3

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/LICENSE +1 -1
  2. package/package.json +1 -1
  3. package/src/CodeInput.js +48 -48
  4. package/src/DSAlert.js +352 -352
  5. package/src/DSAvatar.js +207 -207
  6. package/src/DSDelete.js +274 -274
  7. package/src/DSForm.js +568 -568
  8. package/src/DSGridOrTable.js +453 -453
  9. package/src/DSLocaleSwitcher.js +239 -239
  10. package/src/DSLogout.js +293 -293
  11. package/src/DSNotifications.js +365 -365
  12. package/src/DSRestore.js +181 -181
  13. package/src/DSSelect.js +1071 -1071
  14. package/src/DSSelectBox.js +563 -563
  15. package/src/DSSimpleSlider.js +517 -517
  16. package/src/DSSvgFetch.js +69 -69
  17. package/src/DSTable/DSTableExport.js +68 -68
  18. package/src/DSTable/DSTableFilter.js +224 -224
  19. package/src/DSTable/DSTablePagination.js +136 -136
  20. package/src/DSTable/DSTableSearch.js +40 -40
  21. package/src/DSTable/DSTableSelection.js +192 -192
  22. package/src/DSTable/DSTableSort.js +58 -58
  23. package/src/DSTable.js +353 -353
  24. package/src/DSTabs.js +488 -488
  25. package/src/DSUpload.js +887 -887
  26. package/dist/CodeInput.d.ts +0 -10
  27. package/dist/DSAlert.d.ts +0 -112
  28. package/dist/DSAvatar.d.ts +0 -45
  29. package/dist/DSDelete.d.ts +0 -61
  30. package/dist/DSForm.d.ts +0 -151
  31. package/dist/DSGridOrTable/DSGOTRenderer.d.ts +0 -60
  32. package/dist/DSGridOrTable/DSGOTViewToggle.d.ts +0 -26
  33. package/dist/DSGridOrTable.d.ts +0 -296
  34. package/dist/DSLocaleSwitcher.d.ts +0 -71
  35. package/dist/DSLogout.d.ts +0 -76
  36. package/dist/DSNotifications.d.ts +0 -54
  37. package/dist/DSRestore.d.ts +0 -56
  38. package/dist/DSSelect.d.ts +0 -221
  39. package/dist/DSSelectBox.d.ts +0 -123
  40. package/dist/DSSimpleSlider.d.ts +0 -136
  41. package/dist/DSSvgFetch.d.ts +0 -17
  42. package/dist/DSTable/DSTableExport.d.ts +0 -11
  43. package/dist/DSTable/DSTableFilter.d.ts +0 -40
  44. package/dist/DSTable/DSTablePagination.d.ts +0 -12
  45. package/dist/DSTable/DSTableSearch.d.ts +0 -8
  46. package/dist/DSTable/DSTableSelection.d.ts +0 -46
  47. package/dist/DSTable/DSTableSort.d.ts +0 -8
  48. package/dist/DSTable.d.ts +0 -116
  49. package/dist/DSTabs.d.ts +0 -156
  50. package/dist/DSUpload.d.ts +0 -220
  51. package/dist/index.d.ts +0 -17
package/src/DSTable.js CHANGED
@@ -1,353 +1,353 @@
1
- import DSTablePagination from './DSTable/DSTablePagination.js';
2
- import DSTableSearch from './DSTable/DSTableSearch.js';
3
- import DSTableSort from './DSTable/DSTableSort.js';
4
- import DSTableFilter from './DSTable/DSTableFilter.js';
5
- import DSTableExport from './DSTable/DSTableExport.js';
6
- import DSTableSelection from './DSTable/DSTableSelection.js';
7
-
8
- /**
9
- * DSTable
10
- *
11
- * A comprehensive table plugin for data management.
12
- * Supports: Pagination, Search, Sort, Filter, Export, Selection.
13
- */
14
- export class DSTable {
15
- static defaults = {
16
- pagination: true,
17
- pagination_translations: {
18
- prev: 'Previous',
19
- next: 'Next',
20
- goto: 'Go to',
21
- stats: 'Showing {from} to {to} of {total} entries'
22
- },
23
- search: true,
24
- sort: true,
25
- filter: true,
26
- export: true,
27
- selection: true,
28
-
29
- table_source: 'ajax', // ajax | html | json
30
- ajax_url: null,
31
- ajax_data: {},
32
- ajax_method: 'GET',
33
- ajax_function: 'axios', // xhr | axios | fetch
34
-
35
- success: null,
36
- error: null,
37
- beforeSend: null,
38
- afterSend: null,
39
-
40
- template_source: 'html', // function | html | response
41
- template_function: null,
42
- template_html: null,
43
- template_response: 'data.*.html_response',
44
-
45
-
46
- emptyIcon: 'search_off',
47
-
48
- // Selectors
49
- tableSelector: 'table',
50
- bodySelector: 'tbody',
51
- messageSelector: '.ds-table-message',
52
- table_translations: {
53
- no_data: 'No data available',
54
- loading: 'Loading...',
55
- error: 'Error loading data',
56
- empty: 'No data available',
57
-
58
- }
59
- };
60
-
61
- constructor(wrapper, options = {}) {
62
- this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
63
- if (!this.wrapper) throw new Error('DSTable: Wrapper element not found');
64
-
65
- this.config = { ...DSTable.defaults, ...options };
66
-
67
- // State
68
- this.data = [];
69
- this.meta = {}; // Pagination meta
70
- this.params = {
71
- page: 1,
72
- per_page: 15,
73
- sort_by: null,
74
- sort_by: null,
75
- sort_order: 'asc',
76
- search: null,
77
- filters: {}
78
- };
79
-
80
- this.modules = {};
81
- this.isLoading = false;
82
-
83
- this._init();
84
- }
85
-
86
- _init() {
87
- this.table = this.wrapper.querySelector(this.config.tableSelector);
88
- this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
89
-
90
- if (!this.table) throw new Error('DSTable: Table element not found inside wrapper');
91
-
92
- // Initialize Modules
93
- if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
94
- if (this.config.search) this.modules.search = new DSTableSearch(this);
95
- if (this.config.sort) this.modules.sort = new DSTableSort(this);
96
- if (this.config.filter) this.modules.filter = new DSTableFilter(this);
97
- if (this.config.export) this.modules.export = new DSTableExport(this);
98
- if (this.config.selection) this.modules.selection = new DSTableSelection(this);
99
-
100
- // Initial Load
101
- this.loadData();
102
- }
103
-
104
- // ================= DATA LOADING =================
105
-
106
- async loadData() {
107
- if (this.isLoading) return;
108
- this.isLoading = true;
109
- this._toggleLoading(true);
110
-
111
- if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
112
-
113
- try {
114
- if (this.config.table_source === 'ajax') {
115
- await this._loadFromAjax();
116
- } else if (this.config.table_source === 'json') {
117
- // Load from local JSON data provided in config
118
- this._handleDataSuccess(this.config.data || []);
119
- } else if (this.config.table_source === 'html') {
120
- // Parse existing HTML? (Not fully implemented in this plan, mainly for Ajax)
121
- this.isLoading = false;
122
- this._toggleLoading(false);
123
- }
124
- } catch (error) {
125
- console.error('DSTable: Error loading data', error);
126
- if (this.config.error) this.config.error(error);
127
- this._showError('Error loading data');
128
- this.isLoading = false;
129
- this._toggleLoading(false);
130
- }
131
- }
132
-
133
- async _loadFromAjax() {
134
- const url = this.config.ajax_url;
135
- const method = this.config.ajax_method;
136
- const data = { ...this.config.ajax_data, ...this.params };
137
-
138
- let response;
139
-
140
- if (this.config.ajax_function === 'axios' && window.axios) {
141
- response = await window.axios({ method, url, params: method === 'GET' ? data : undefined, data: method !== 'GET' ? data : undefined });
142
- this._handleDataSuccess(response.data);
143
- } else if (this.config.ajax_function === 'fetch' || window.fetch) {
144
- // Basic fetch impl
145
- const queryString = new URLSearchParams(data).toString();
146
- const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
147
- const options = {
148
- method,
149
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
150
- body: method !== 'GET' ? JSON.stringify(data) : undefined
151
- };
152
- const res = await fetch(fetchUrl, options);
153
- const json = await res.json();
154
- this._handleDataSuccess(json);
155
- } else {
156
- throw new Error('DSTable: No valid ajax function found');
157
- }
158
- }
159
-
160
- _handleDataSuccess(response) {
161
- // Standardize response
162
- // Expected: { success: true, data: [...], meta: {...} }
163
-
164
- if (response.data) {
165
- this.data = response.data;
166
- this.meta = response.meta || {};
167
- } else if (Array.isArray(response)) {
168
- this.data = response;
169
- }
170
-
171
- this.render();
172
-
173
- if (this.config.success) this.config.success(response);
174
- if (this.config.afterSend) this.config.afterSend(response);
175
-
176
- // Notify modules
177
- Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
178
-
179
- this.isLoading = false;
180
- this._toggleLoading(false);
181
- this._emit('dataLoaded', response);
182
- }
183
-
184
- // ================= RENDERING =================
185
-
186
- render() {
187
- if (!this.tbody) return;
188
- this.tbody.innerHTML = '';
189
-
190
- if (this.data.length === 0) {
191
- this._showEmpty();
192
- return;
193
- }
194
-
195
- const fragment = document.createDocumentFragment();
196
-
197
- this.data.forEach((row, index) => {
198
- const tr = document.createElement('tr');
199
- let html = '';
200
-
201
- if (this.config.template_source === 'function' && typeof this.config.template_function === 'function') {
202
- html = this.config.template_function(row, index);
203
- } else if (this.config.template_source === 'html' && this.config.template_html) {
204
- html = this._renderTemplate(this.config.template_html, row);
205
- } else if (this.config.template_source === 'response') {
206
- // deeply get html_response from row using dot notation if needed
207
- html = this._getNestedValue(row, this.config.template_response) || '';
208
- }
209
-
210
- // If the template returns a full TR, we might need to parse it.
211
- // Expectation: The template returns innerHTML for the TR, or the configuration handles the TR wrapper.
212
- // Adjusting based on standard table needs: assumes content is <td>...</td>
213
- // BUT if template_response is used, the example shows "<tr><td>...</td></tr>"
214
-
215
- if (html.trim().startsWith('<tr')) {
216
- const tempTable = document.createElement('table');
217
- const tempTbody = document.createElement('tbody');
218
- tempTable.appendChild(tempTbody);
219
- tempTbody.innerHTML = html;
220
- const newRow = tempTbody.firstElementChild;
221
- if (newRow) {
222
- fragment.appendChild(newRow);
223
- }
224
- } else {
225
- tr.innerHTML = html;
226
- fragment.appendChild(tr);
227
- }
228
- });
229
-
230
- this.tbody.appendChild(fragment);
231
- this._emit('render');
232
-
233
- // Re-initialize selection module for new rows if needed
234
- if (this.modules.selection) this.modules.selection.update();
235
- }
236
-
237
- _renderTemplate(template, data) {
238
- return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
239
- return data[key] !== undefined ? data[key] : '';
240
- });
241
- }
242
-
243
- _showEmpty() {
244
- this.tbody.innerHTML = `
245
- <tr>
246
- <td colspan="100%" class="text-center py-12">
247
- <div class="flex flex-col items-center gap-2 text-base-content/50">
248
- <span class="material-symbols-outlined text-5xl">${this.config.emptyIcon}</span>
249
- <p class="text-base">${this.config.table_translations.empty}</p>
250
- </div>
251
- </td>
252
- </tr>`;
253
- }
254
-
255
- _showError(msg) {
256
- this.tbody.innerHTML =
257
- `
258
- <tr>
259
- <td colspan="100%" class="text-center py-12 text-error">
260
- <div class="flex flex-col items-center gap-2">
261
- <span class="material-symbols-outlined text-5xl">error</span>
262
- <p class="text-base">${msg || this.config.table_translations.error}</p>
263
- </div>
264
- </td>
265
- </tr>
266
- `;
267
- }
268
-
269
- _toggleLoading(loading) {
270
- if (loading) {
271
- this.wrapper.classList.add('loading-state');
272
- this._showSkeleton();
273
- } else {
274
- this.wrapper.classList.remove('loading-state');
275
- // Skeleton will be cleared by render() normally,
276
- // but if we just want to hide it before render?
277
- // render() clears tbody, so we are good.
278
- }
279
- }
280
-
281
- _showSkeleton() {
282
- if (!this.tbody) return;
283
-
284
- const perPage = this.params.per_page || 10;
285
- const columns = this.table.querySelectorAll('thead th').length || 5;
286
- let skeletonHtml = '';
287
-
288
- for (let i = 0; i < perPage; i++) {
289
- skeletonHtml += '<tr>';
290
-
291
- // First column: Avatar + Text skeleton (matching user's "Skeleton - circle with content")
292
- skeletonHtml += `
293
- <td>
294
- <div class="flex items-center gap-4">
295
- <div class="skeleton h-12 w-12 shrink-0 rounded-full"></div>
296
- <div class="flex flex-col gap-2">
297
- <div class="skeleton h-3 w-20"></div>
298
- <div class="skeleton h-3 w-28"></div>
299
- </div>
300
- </div>
301
- </td>
302
- `;
303
-
304
- // Other columns: Simple text lines
305
- for (let j = 1; j < columns; j++) {
306
- skeletonHtml += `
307
- <td>
308
- <div class="skeleton h-4 w-full opacity-50"></div>
309
- </td>
310
- `;
311
- }
312
-
313
- skeletonHtml += '</tr>';
314
- }
315
-
316
- this.tbody.innerHTML = skeletonHtml;
317
- }
318
-
319
- // ================= PUBLIC API =================
320
-
321
- refresh() {
322
- this.params.page = 1;
323
- this.loadData();
324
- }
325
-
326
- setParam(key, value) {
327
- this.params[key] = value;
328
- }
329
-
330
- getParam(key) {
331
- return this.params[key];
332
- }
333
-
334
- registerModule(name, instance) {
335
- this.modules[name] = instance;
336
- }
337
-
338
- // ================= EVENTS =================
339
-
340
- on(event, handler) {
341
- this.wrapper.addEventListener(`dstable:${event}`, handler);
342
- }
343
-
344
- _emit(event, detail = {}) {
345
- this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
346
- }
347
- _getNestedValue(obj, path) {
348
- if (!path) return undefined;
349
- return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
350
- }
351
- }
352
-
353
- export default DSTable;
1
+ import DSTablePagination from './DSTable/DSTablePagination.js';
2
+ import DSTableSearch from './DSTable/DSTableSearch.js';
3
+ import DSTableSort from './DSTable/DSTableSort.js';
4
+ import DSTableFilter from './DSTable/DSTableFilter.js';
5
+ import DSTableExport from './DSTable/DSTableExport.js';
6
+ import DSTableSelection from './DSTable/DSTableSelection.js';
7
+
8
+ /**
9
+ * DSTable
10
+ *
11
+ * A comprehensive table plugin for data management.
12
+ * Supports: Pagination, Search, Sort, Filter, Export, Selection.
13
+ */
14
+ export class DSTable {
15
+ static defaults = {
16
+ pagination: true,
17
+ pagination_translations: {
18
+ prev: 'Previous',
19
+ next: 'Next',
20
+ goto: 'Go to',
21
+ stats: 'Showing {from} to {to} of {total} entries'
22
+ },
23
+ search: true,
24
+ sort: true,
25
+ filter: true,
26
+ export: true,
27
+ selection: true,
28
+
29
+ table_source: 'ajax', // ajax | html | json
30
+ ajax_url: null,
31
+ ajax_data: {},
32
+ ajax_method: 'GET',
33
+ ajax_function: 'axios', // xhr | axios | fetch
34
+
35
+ success: null,
36
+ error: null,
37
+ beforeSend: null,
38
+ afterSend: null,
39
+
40
+ template_source: 'html', // function | html | response
41
+ template_function: null,
42
+ template_html: null,
43
+ template_response: 'data.*.html_response',
44
+
45
+
46
+ emptyIcon: 'search_off',
47
+
48
+ // Selectors
49
+ tableSelector: 'table',
50
+ bodySelector: 'tbody',
51
+ messageSelector: '.ds-table-message',
52
+ table_translations: {
53
+ no_data: 'No data available',
54
+ loading: 'Loading...',
55
+ error: 'Error loading data',
56
+ empty: 'No data available',
57
+
58
+ }
59
+ };
60
+
61
+ constructor(wrapper, options = {}) {
62
+ this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
63
+ if (!this.wrapper) throw new Error('DSTable: Wrapper element not found');
64
+
65
+ this.config = { ...DSTable.defaults, ...options };
66
+
67
+ // State
68
+ this.data = [];
69
+ this.meta = {}; // Pagination meta
70
+ this.params = {
71
+ page: 1,
72
+ per_page: 15,
73
+ sort_by: null,
74
+ sort_by: null,
75
+ sort_order: 'asc',
76
+ search: null,
77
+ filters: {}
78
+ };
79
+
80
+ this.modules = {};
81
+ this.isLoading = false;
82
+
83
+ this._init();
84
+ }
85
+
86
+ _init() {
87
+ this.table = this.wrapper.querySelector(this.config.tableSelector);
88
+ this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
89
+
90
+ if (!this.table) throw new Error('DSTable: Table element not found inside wrapper');
91
+
92
+ // Initialize Modules
93
+ if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
94
+ if (this.config.search) this.modules.search = new DSTableSearch(this);
95
+ if (this.config.sort) this.modules.sort = new DSTableSort(this);
96
+ if (this.config.filter) this.modules.filter = new DSTableFilter(this);
97
+ if (this.config.export) this.modules.export = new DSTableExport(this);
98
+ if (this.config.selection) this.modules.selection = new DSTableSelection(this);
99
+
100
+ // Initial Load
101
+ this.loadData();
102
+ }
103
+
104
+ // ================= DATA LOADING =================
105
+
106
+ async loadData() {
107
+ if (this.isLoading) return;
108
+ this.isLoading = true;
109
+ this._toggleLoading(true);
110
+
111
+ if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
112
+
113
+ try {
114
+ if (this.config.table_source === 'ajax') {
115
+ await this._loadFromAjax();
116
+ } else if (this.config.table_source === 'json') {
117
+ // Load from local JSON data provided in config
118
+ this._handleDataSuccess(this.config.data || []);
119
+ } else if (this.config.table_source === 'html') {
120
+ // Parse existing HTML? (Not fully implemented in this plan, mainly for Ajax)
121
+ this.isLoading = false;
122
+ this._toggleLoading(false);
123
+ }
124
+ } catch (error) {
125
+ console.error('DSTable: Error loading data', error);
126
+ if (this.config.error) this.config.error(error);
127
+ this._showError('Error loading data');
128
+ this.isLoading = false;
129
+ this._toggleLoading(false);
130
+ }
131
+ }
132
+
133
+ async _loadFromAjax() {
134
+ const url = this.config.ajax_url;
135
+ const method = this.config.ajax_method;
136
+ const data = { ...this.config.ajax_data, ...this.params };
137
+
138
+ let response;
139
+
140
+ if (this.config.ajax_function === 'axios' && window.axios) {
141
+ response = await window.axios({ method, url, params: method === 'GET' ? data : undefined, data: method !== 'GET' ? data : undefined });
142
+ this._handleDataSuccess(response.data);
143
+ } else if (this.config.ajax_function === 'fetch' || window.fetch) {
144
+ // Basic fetch impl
145
+ const queryString = new URLSearchParams(data).toString();
146
+ const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
147
+ const options = {
148
+ method,
149
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
150
+ body: method !== 'GET' ? JSON.stringify(data) : undefined
151
+ };
152
+ const res = await fetch(fetchUrl, options);
153
+ const json = await res.json();
154
+ this._handleDataSuccess(json);
155
+ } else {
156
+ throw new Error('DSTable: No valid ajax function found');
157
+ }
158
+ }
159
+
160
+ _handleDataSuccess(response) {
161
+ // Standardize response
162
+ // Expected: { success: true, data: [...], meta: {...} }
163
+
164
+ if (response.data) {
165
+ this.data = response.data;
166
+ this.meta = response.meta || {};
167
+ } else if (Array.isArray(response)) {
168
+ this.data = response;
169
+ }
170
+
171
+ this.render();
172
+
173
+ if (this.config.success) this.config.success(response);
174
+ if (this.config.afterSend) this.config.afterSend(response);
175
+
176
+ // Notify modules
177
+ Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
178
+
179
+ this.isLoading = false;
180
+ this._toggleLoading(false);
181
+ this._emit('dataLoaded', response);
182
+ }
183
+
184
+ // ================= RENDERING =================
185
+
186
+ render() {
187
+ if (!this.tbody) return;
188
+ this.tbody.innerHTML = '';
189
+
190
+ if (this.data.length === 0) {
191
+ this._showEmpty();
192
+ return;
193
+ }
194
+
195
+ const fragment = document.createDocumentFragment();
196
+
197
+ this.data.forEach((row, index) => {
198
+ const tr = document.createElement('tr');
199
+ let html = '';
200
+
201
+ if (this.config.template_source === 'function' && typeof this.config.template_function === 'function') {
202
+ html = this.config.template_function(row, index);
203
+ } else if (this.config.template_source === 'html' && this.config.template_html) {
204
+ html = this._renderTemplate(this.config.template_html, row);
205
+ } else if (this.config.template_source === 'response') {
206
+ // deeply get html_response from row using dot notation if needed
207
+ html = this._getNestedValue(row, this.config.template_response) || '';
208
+ }
209
+
210
+ // If the template returns a full TR, we might need to parse it.
211
+ // Expectation: The template returns innerHTML for the TR, or the configuration handles the TR wrapper.
212
+ // Adjusting based on standard table needs: assumes content is <td>...</td>
213
+ // BUT if template_response is used, the example shows "<tr><td>...</td></tr>"
214
+
215
+ if (html.trim().startsWith('<tr')) {
216
+ const tempTable = document.createElement('table');
217
+ const tempTbody = document.createElement('tbody');
218
+ tempTable.appendChild(tempTbody);
219
+ tempTbody.innerHTML = html;
220
+ const newRow = tempTbody.firstElementChild;
221
+ if (newRow) {
222
+ fragment.appendChild(newRow);
223
+ }
224
+ } else {
225
+ tr.innerHTML = html;
226
+ fragment.appendChild(tr);
227
+ }
228
+ });
229
+
230
+ this.tbody.appendChild(fragment);
231
+ this._emit('render');
232
+
233
+ // Re-initialize selection module for new rows if needed
234
+ if (this.modules.selection) this.modules.selection.update();
235
+ }
236
+
237
+ _renderTemplate(template, data) {
238
+ return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
239
+ return data[key] !== undefined ? data[key] : '';
240
+ });
241
+ }
242
+
243
+ _showEmpty() {
244
+ this.tbody.innerHTML = `
245
+ <tr>
246
+ <td colspan="100%" class="text-center py-12">
247
+ <div class="flex flex-col items-center gap-2 text-base-content/50">
248
+ <span class="material-symbols-outlined text-5xl">${this.config.emptyIcon}</span>
249
+ <p class="text-base">${this.config.table_translations.empty}</p>
250
+ </div>
251
+ </td>
252
+ </tr>`;
253
+ }
254
+
255
+ _showError(msg) {
256
+ this.tbody.innerHTML =
257
+ `
258
+ <tr>
259
+ <td colspan="100%" class="text-center py-12 text-error">
260
+ <div class="flex flex-col items-center gap-2">
261
+ <span class="material-symbols-outlined text-5xl">error</span>
262
+ <p class="text-base">${msg || this.config.table_translations.error}</p>
263
+ </div>
264
+ </td>
265
+ </tr>
266
+ `;
267
+ }
268
+
269
+ _toggleLoading(loading) {
270
+ if (loading) {
271
+ this.wrapper.classList.add('loading-state');
272
+ this._showSkeleton();
273
+ } else {
274
+ this.wrapper.classList.remove('loading-state');
275
+ // Skeleton will be cleared by render() normally,
276
+ // but if we just want to hide it before render?
277
+ // render() clears tbody, so we are good.
278
+ }
279
+ }
280
+
281
+ _showSkeleton() {
282
+ if (!this.tbody) return;
283
+
284
+ const perPage = this.params.per_page || 10;
285
+ const columns = this.table.querySelectorAll('thead th').length || 5;
286
+ let skeletonHtml = '';
287
+
288
+ for (let i = 0; i < perPage; i++) {
289
+ skeletonHtml += '<tr>';
290
+
291
+ // First column: Avatar + Text skeleton (matching user's "Skeleton - circle with content")
292
+ skeletonHtml += `
293
+ <td>
294
+ <div class="flex items-center gap-4">
295
+ <div class="skeleton h-12 w-12 shrink-0 rounded-full"></div>
296
+ <div class="flex flex-col gap-2">
297
+ <div class="skeleton h-3 w-20"></div>
298
+ <div class="skeleton h-3 w-28"></div>
299
+ </div>
300
+ </div>
301
+ </td>
302
+ `;
303
+
304
+ // Other columns: Simple text lines
305
+ for (let j = 1; j < columns; j++) {
306
+ skeletonHtml += `
307
+ <td>
308
+ <div class="skeleton h-4 w-full opacity-50"></div>
309
+ </td>
310
+ `;
311
+ }
312
+
313
+ skeletonHtml += '</tr>';
314
+ }
315
+
316
+ this.tbody.innerHTML = skeletonHtml;
317
+ }
318
+
319
+ // ================= PUBLIC API =================
320
+
321
+ refresh() {
322
+ this.params.page = 1;
323
+ this.loadData();
324
+ }
325
+
326
+ setParam(key, value) {
327
+ this.params[key] = value;
328
+ }
329
+
330
+ getParam(key) {
331
+ return this.params[key];
332
+ }
333
+
334
+ registerModule(name, instance) {
335
+ this.modules[name] = instance;
336
+ }
337
+
338
+ // ================= EVENTS =================
339
+
340
+ on(event, handler) {
341
+ this.wrapper.addEventListener(`dstable:${event}`, handler);
342
+ }
343
+
344
+ _emit(event, detail = {}) {
345
+ this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
346
+ }
347
+ _getNestedValue(obj, path) {
348
+ if (!path) return undefined;
349
+ return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
350
+ }
351
+ }
352
+
353
+ export default DSTable;