@doyosi/laraisy 1.0.1 → 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
@@ -1,453 +1,453 @@
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
- import DSGOTRenderer from './DSGridOrTable/DSGOTRenderer.js';
8
- import DSGOTViewToggle from './DSGridOrTable/DSGOTViewToggle.js';
9
-
10
- /**
11
- * @typedef {Object} DSGridOrTableOptions
12
- * @property {'table'|'grid'|'gridable'} [type] - Display type
13
- * @property {'grid'|'table'} [defaultView] - For 'gridable' mode: initial view
14
- * @property {boolean} [showToggle] - Show view toggle buttons
15
- * @property {boolean} [pagination] - Enable pagination
16
- * @property {boolean} [search] - Enable search
17
- * @property {boolean} [sort] - Enable sorting
18
- * @property {boolean} [filter] - Enable filtering
19
- * @property {boolean} [export] - Enable export
20
- * @property {boolean} [selection] - Enable selection
21
- * @property {'ajax'|'html'|'json'} [table_source] - Data source type
22
- * @property {string} [ajax_url] - URL for AJAX requests
23
- * @property {Object} [ajax_data] - Additional data for AJAX requests
24
- * @property {'GET'|'POST'|'PUT'} [ajax_method] - HTTP method
25
- * @property {'xhr'|'axios'|'fetch'} [ajax_function] - Fetcher implementation
26
- * @property {Object} [rowTemplate] - Template config for table rows
27
- * @property {Object} [gridTemplate] - Template config for grid items
28
- * @property {Object|number} [gridColumns] - Grid column configuration
29
- * @property {number} [gridGap] - Tailwind gap value
30
- * @property {string} [gridContainerClass] - Grid container class
31
- * @property {string} [tableSelector] - Selector for table element
32
- * @property {string} [bodySelector] - Selector for tbody element
33
- * @property {string} [gridSelector] - Selector for grid container
34
- * @property {string} [toggleSelector] - Selector for view toggle
35
- * @property {string} [emptyMessage] - Message when no data found
36
- * @property {string} [errorMessage] - Message on error
37
- * @property {Function} [success] - Success callback
38
- * @property {Function} [error] - Error callback
39
- */
40
-
41
- /**
42
- * DSGridOrTable
43
- *
44
- * A flexible data display plugin supporting Table, Grid, or switchable (Gridable) layouts.
45
- * Built on top of DSTable architecture with extended rendering capabilities.
46
- *
47
- * @example
48
- * // Table mode (like standard DSTable)
49
- * const table = new DSGridOrTable('#container', {
50
- * type: 'table',
51
- * ajax_url: '/api/data',
52
- * rowTemplate: { source: 'response', response: 'html' }
53
- * });
54
- *
55
- * @example
56
- * // Grid mode (card layout)
57
- * const grid = new DSGridOrTable('#container', {
58
- * type: 'grid',
59
- * ajax_url: '/api/data',
60
- * gridTemplate: { source: 'response', response: 'grid_html' }
61
- * });
62
- *
63
- * @example
64
- * // Gridable mode (toggle between table and grid)
65
- * const gridable = new DSGridOrTable('#container', {
66
- * type: 'gridable',
67
- * ajax_url: '/api/data',
68
- * rowTemplate: { source: 'response', response: 'html' },
69
- * gridTemplate: { source: 'response', response: 'grid_html' },
70
- * defaultView: 'grid',
71
- * showToggle: true
72
- * });
73
- */
74
- export class DSGridOrTable {
75
- static defaults = {
76
- // Display type
77
- type: 'table', // 'table' | 'grid' | 'gridable'
78
- defaultView: 'grid', // For 'gridable' mode: 'grid' | 'table'
79
- showToggle: true, // For 'gridable' mode: show view toggle buttons
80
-
81
- // Standard features (same as DSTable)
82
- pagination: true,
83
- pagination_translations: {
84
- prev: 'Previous',
85
- next: 'Next',
86
- goto: 'Go to',
87
- stats: 'Showing {from} to {to} of {total} entries'
88
- },
89
- search: true,
90
- sort: true,
91
- filter: true,
92
- export: true,
93
- selection: true,
94
-
95
- // Data source
96
- table_source: 'ajax', // 'ajax' | 'html' | 'json'
97
- ajax_url: null,
98
- ajax_data: {},
99
- ajax_method: 'GET',
100
- ajax_function: 'axios', // 'xhr' | 'axios' | 'fetch'
101
-
102
- // Callbacks
103
- success: null,
104
- error: null,
105
- beforeSend: null,
106
- afterSend: null,
107
-
108
- // Template configurations
109
- rowTemplate: {
110
- source: 'response', // 'function' | 'html' | 'response'
111
- response: 'html', // Property path in response data
112
- function: null, // Function that returns HTML: (row, index) => '<tr>...</tr>'
113
- html: null // Template string with {{placeholders}}
114
- },
115
- gridTemplate: {
116
- source: 'response',
117
- response: 'grid_html',
118
- function: null,
119
- html: null
120
- },
121
-
122
- // Grid layout options
123
- gridColumns: {
124
- default: 2,
125
- sm: 1,
126
- md: 2,
127
- lg: 3,
128
- xl: 4
129
- },
130
- gridGap: 4, // Tailwind gap value (gap-4)
131
- gridContainerClass: 'ds-grid-container',
132
-
133
- // Selectors
134
- tableSelector: 'table',
135
- bodySelector: 'tbody',
136
- gridSelector: '.ds-grid-container',
137
- toggleSelector: '.ds-view-toggle',
138
- messageSelector: '.ds-table-message',
139
- search_selector: null,
140
-
141
- // Empty/Error states
142
- emptyMessage: 'No data found',
143
- emptyIcon: 'search_off',
144
- errorMessage: 'Error loading data',
145
-
146
- // Filter selectors (passed to DSTableFilter)
147
- filter_selectors: {}
148
- };
149
-
150
- /**
151
- * @param {string|HTMLElement} wrapper
152
- * @param {DSGridOrTableOptions} options
153
- */
154
- constructor(wrapper, options = {}) {
155
- this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
156
- if (!this.wrapper) throw new Error('DSGridOrTable: Wrapper element not found');
157
-
158
- // Merge configs with special handling for nested objects
159
- /** @type {DSGridOrTableOptions} */
160
- this.config = this._mergeDeep({}, DSGridOrTable.defaults, options);
161
-
162
- // State
163
- this.data = [];
164
- this.meta = {};
165
- this.params = {
166
- page: 1,
167
- per_page: 15,
168
- sort_by: null,
169
- sort_order: 'asc',
170
- search: null,
171
- filters: {}
172
- };
173
-
174
- this.modules = {};
175
- this.isLoading = false;
176
- this.currentView = this.config.defaultView;
177
-
178
- this._init();
179
- }
180
-
181
- _mergeDeep(target, ...sources) {
182
- if (!sources.length) return target;
183
- const source = sources.shift();
184
-
185
- if (this._isObject(target) && this._isObject(source)) {
186
- for (const key in source) {
187
- if (this._isObject(source[key])) {
188
- if (!target[key]) Object.assign(target, { [key]: {} });
189
- this._mergeDeep(target[key], source[key]);
190
- } else {
191
- Object.assign(target, { [key]: source[key] });
192
- }
193
- }
194
- }
195
- return this._mergeDeep(target, ...sources);
196
- }
197
-
198
- _isObject(item) {
199
- return item && typeof item === 'object' && !Array.isArray(item);
200
- }
201
-
202
- _init() {
203
- // Find elements based on type
204
- this.table = this.wrapper.querySelector(this.config.tableSelector);
205
- this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
206
- this.gridContainer = this.wrapper.querySelector(this.config.gridSelector);
207
-
208
- // Create grid container if needed and not exists
209
- if ((this.config.type === 'grid' || this.config.type === 'gridable') && !this.gridContainer) {
210
- this.gridContainer = document.createElement('div');
211
- this.gridContainer.className = this._buildGridClasses();
212
- if (this.table) {
213
- this.table.parentNode.insertBefore(this.gridContainer, this.table.nextSibling);
214
- } else {
215
- this.wrapper.insertBefore(this.gridContainer, this.wrapper.firstChild);
216
- }
217
- }
218
-
219
- // Initialize the renderer module
220
- this.renderer = new DSGOTRenderer(this);
221
-
222
- // Initialize view toggle for gridable mode
223
- if (this.config.type === 'gridable' && this.config.showToggle) {
224
- this.viewToggle = new DSGOTViewToggle(this);
225
- }
226
-
227
- // Initialize standard modules (reuse DSTable modules)
228
- if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
229
- if (this.config.search) this.modules.search = new DSTableSearch(this);
230
- if (this.config.sort && this.table) this.modules.sort = new DSTableSort(this);
231
- if (this.config.filter) this.modules.filter = new DSTableFilter(this);
232
- if (this.config.export) this.modules.export = new DSTableExport(this);
233
- if (this.config.selection && this.table) this.modules.selection = new DSTableSelection(this);
234
-
235
- // Set initial visibility based on type
236
- this._setInitialVisibility();
237
-
238
- // Initial data load
239
- this.loadData();
240
- }
241
-
242
- _buildGridClasses() {
243
- const { gridColumns, gridGap, gridContainerClass } = this.config;
244
- let classes = `${gridContainerClass} grid gap-${gridGap}`;
245
-
246
- // Build responsive column classes
247
- if (typeof gridColumns === 'number') {
248
- classes += ` grid-cols-${gridColumns}`;
249
- } else if (typeof gridColumns === 'object') {
250
- classes += ` grid-cols-${gridColumns.default || 1}`;
251
- if (gridColumns.sm) classes += ` sm:grid-cols-${gridColumns.sm}`;
252
- if (gridColumns.md) classes += ` md:grid-cols-${gridColumns.md}`;
253
- if (gridColumns.lg) classes += ` lg:grid-cols-${gridColumns.lg}`;
254
- if (gridColumns.xl) classes += ` xl:grid-cols-${gridColumns.xl}`;
255
- }
256
-
257
- return classes;
258
- }
259
-
260
- _setInitialVisibility() {
261
- if (this.config.type === 'table') {
262
- if (this.gridContainer) this.gridContainer.classList.add('hidden');
263
- if (this.table) this.table.classList.remove('hidden');
264
- } else if (this.config.type === 'grid') {
265
- if (this.table) this.table.classList.add('hidden');
266
- if (this.gridContainer) this.gridContainer.classList.remove('hidden');
267
- } else { // gridable
268
- if (this.currentView === 'grid') {
269
- if (this.table) this.table.classList.add('hidden');
270
- if (this.gridContainer) this.gridContainer.classList.remove('hidden');
271
- } else {
272
- if (this.gridContainer) this.gridContainer.classList.add('hidden');
273
- if (this.table) this.table.classList.remove('hidden');
274
- }
275
- }
276
- }
277
-
278
- // ================= DATA LOADING =================
279
-
280
- async loadData() {
281
- if (this.isLoading) return;
282
- this.isLoading = true;
283
- this._toggleLoading(true);
284
-
285
- if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
286
-
287
- try {
288
- if (this.config.table_source === 'ajax') {
289
- await this._loadFromAjax();
290
- } else if (this.config.table_source === 'json') {
291
- this._handleDataSuccess(this.config.data || []);
292
- } else if (this.config.table_source === 'html') {
293
- this.isLoading = false;
294
- this._toggleLoading(false);
295
- }
296
- } catch (error) {
297
- console.error('DSGridOrTable: Error loading data', error);
298
- if (this.config.error) this.config.error(error);
299
- this.renderer.showError(this.config.errorMessage);
300
- this.isLoading = false;
301
- this._toggleLoading(false);
302
- }
303
- }
304
-
305
- async _loadFromAjax() {
306
- const url = this.config.ajax_url;
307
- const method = this.config.ajax_method;
308
- const data = { ...this.config.ajax_data, ...this.params };
309
-
310
- let response;
311
-
312
- if (this.config.ajax_function === 'axios' && window.axios) {
313
- response = await window.axios({
314
- method,
315
- url,
316
- params: method === 'GET' ? data : undefined,
317
- data: method !== 'GET' ? data : undefined
318
- });
319
- this._handleDataSuccess(response.data);
320
- } else if (this.config.ajax_function === 'fetch' || window.fetch) {
321
- const queryString = new URLSearchParams(data).toString();
322
- const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
323
- const options = {
324
- method,
325
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
326
- body: method !== 'GET' ? JSON.stringify(data) : undefined
327
- };
328
- const res = await fetch(fetchUrl, options);
329
- const json = await res.json();
330
- this._handleDataSuccess(json);
331
- } else {
332
- throw new Error('DSGridOrTable: No valid ajax function found');
333
- }
334
- }
335
-
336
- _handleDataSuccess(response) {
337
- if (response.data) {
338
- this.data = response.data;
339
- this.meta = response.meta || {};
340
- } else if (Array.isArray(response)) {
341
- this.data = response;
342
- }
343
-
344
- this.renderer.render();
345
-
346
- if (this.config.success) this.config.success(response);
347
- if (this.config.afterSend) this.config.afterSend(response);
348
-
349
- // Notify modules
350
- Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
351
-
352
- this.isLoading = false;
353
- this._toggleLoading(false);
354
- this._emit('dataLoaded', response);
355
- }
356
-
357
- // ================= LOADING STATE =================
358
-
359
- _toggleLoading(loading) {
360
- if (loading) {
361
- this.wrapper.classList.add('loading-state');
362
- this.renderer.showSkeleton();
363
- } else {
364
- this.wrapper.classList.remove('loading-state');
365
- }
366
- }
367
-
368
- // ================= VIEW SWITCHING =================
369
-
370
- /**
371
- * Switch between grid and table views (for gridable mode)
372
- * @param {string} view - 'grid' or 'table'
373
- */
374
- setView(view) {
375
- if (this.config.type !== 'gridable') return;
376
- if (view !== 'grid' && view !== 'table') return;
377
- if (view === this.currentView) return;
378
-
379
- this.currentView = view;
380
- this._setInitialVisibility();
381
- this.renderer.render();
382
-
383
- if (this.viewToggle) this.viewToggle.update();
384
-
385
- this._emit('viewChange', { view });
386
- }
387
-
388
- /**
389
- * Toggle between grid and table views
390
- */
391
- toggleView() {
392
- const newView = this.currentView === 'grid' ? 'table' : 'grid';
393
- this.setView(newView);
394
- }
395
-
396
- /**
397
- * Get current view mode
398
- * @returns {string} 'grid' or 'table'
399
- */
400
- getView() {
401
- return this.currentView;
402
- }
403
-
404
- // ================= PUBLIC API =================
405
-
406
- refresh() {
407
- this.params.page = 1;
408
- this.loadData();
409
- }
410
-
411
- setParam(key, value) {
412
- this.params[key] = value;
413
- }
414
-
415
- getParam(key) {
416
- return this.params[key];
417
- }
418
-
419
- registerModule(name, instance) {
420
- this.modules[name] = instance;
421
- }
422
-
423
- /**
424
- * Get the current render target element
425
- * @returns {HTMLElement}
426
- */
427
- getRenderTarget() {
428
- if (this.config.type === 'grid') return this.gridContainer;
429
- if (this.config.type === 'table') return this.tbody;
430
- return this.currentView === 'grid' ? this.gridContainer : this.tbody;
431
- }
432
-
433
- // ================= EVENTS =================
434
-
435
- on(event, handler) {
436
- this.wrapper.addEventListener(`dsgot:${event}`, handler);
437
- }
438
-
439
- _emit(event, detail = {}) {
440
- this.wrapper.dispatchEvent(new CustomEvent(`dsgot:${event}`, { bubbles: true, detail }));
441
- // Also emit dstable events for compatibility with DSTable modules
442
- this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
443
- }
444
-
445
- // ================= UTILITIES =================
446
-
447
- _getNestedValue(obj, path) {
448
- if (!path) return undefined;
449
- return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
450
- }
451
- }
452
-
453
- export default DSGridOrTable;
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
+ import DSGOTRenderer from './DSGridOrTable/DSGOTRenderer.js';
8
+ import DSGOTViewToggle from './DSGridOrTable/DSGOTViewToggle.js';
9
+
10
+ /**
11
+ * @typedef {Object} DSGridOrTableOptions
12
+ * @property {'table'|'grid'|'gridable'} [type] - Display type
13
+ * @property {'grid'|'table'} [defaultView] - For 'gridable' mode: initial view
14
+ * @property {boolean} [showToggle] - Show view toggle buttons
15
+ * @property {boolean} [pagination] - Enable pagination
16
+ * @property {boolean} [search] - Enable search
17
+ * @property {boolean} [sort] - Enable sorting
18
+ * @property {boolean} [filter] - Enable filtering
19
+ * @property {boolean} [export] - Enable export
20
+ * @property {boolean} [selection] - Enable selection
21
+ * @property {'ajax'|'html'|'json'} [table_source] - Data source type
22
+ * @property {string} [ajax_url] - URL for AJAX requests
23
+ * @property {Object} [ajax_data] - Additional data for AJAX requests
24
+ * @property {'GET'|'POST'|'PUT'} [ajax_method] - HTTP method
25
+ * @property {'xhr'|'axios'|'fetch'} [ajax_function] - Fetcher implementation
26
+ * @property {Object} [rowTemplate] - Template config for table rows
27
+ * @property {Object} [gridTemplate] - Template config for grid items
28
+ * @property {Object|number} [gridColumns] - Grid column configuration
29
+ * @property {number} [gridGap] - Tailwind gap value
30
+ * @property {string} [gridContainerClass] - Grid container class
31
+ * @property {string} [tableSelector] - Selector for table element
32
+ * @property {string} [bodySelector] - Selector for tbody element
33
+ * @property {string} [gridSelector] - Selector for grid container
34
+ * @property {string} [toggleSelector] - Selector for view toggle
35
+ * @property {string} [emptyMessage] - Message when no data found
36
+ * @property {string} [errorMessage] - Message on error
37
+ * @property {Function} [success] - Success callback
38
+ * @property {Function} [error] - Error callback
39
+ */
40
+
41
+ /**
42
+ * DSGridOrTable
43
+ *
44
+ * A flexible data display plugin supporting Table, Grid, or switchable (Gridable) layouts.
45
+ * Built on top of DSTable architecture with extended rendering capabilities.
46
+ *
47
+ * @example
48
+ * // Table mode (like standard DSTable)
49
+ * const table = new DSGridOrTable('#container', {
50
+ * type: 'table',
51
+ * ajax_url: '/api/data',
52
+ * rowTemplate: { source: 'response', response: 'html' }
53
+ * });
54
+ *
55
+ * @example
56
+ * // Grid mode (card layout)
57
+ * const grid = new DSGridOrTable('#container', {
58
+ * type: 'grid',
59
+ * ajax_url: '/api/data',
60
+ * gridTemplate: { source: 'response', response: 'grid_html' }
61
+ * });
62
+ *
63
+ * @example
64
+ * // Gridable mode (toggle between table and grid)
65
+ * const gridable = new DSGridOrTable('#container', {
66
+ * type: 'gridable',
67
+ * ajax_url: '/api/data',
68
+ * rowTemplate: { source: 'response', response: 'html' },
69
+ * gridTemplate: { source: 'response', response: 'grid_html' },
70
+ * defaultView: 'grid',
71
+ * showToggle: true
72
+ * });
73
+ */
74
+ export class DSGridOrTable {
75
+ static defaults = {
76
+ // Display type
77
+ type: 'table', // 'table' | 'grid' | 'gridable'
78
+ defaultView: 'grid', // For 'gridable' mode: 'grid' | 'table'
79
+ showToggle: true, // For 'gridable' mode: show view toggle buttons
80
+
81
+ // Standard features (same as DSTable)
82
+ pagination: true,
83
+ pagination_translations: {
84
+ prev: 'Previous',
85
+ next: 'Next',
86
+ goto: 'Go to',
87
+ stats: 'Showing {from} to {to} of {total} entries'
88
+ },
89
+ search: true,
90
+ sort: true,
91
+ filter: true,
92
+ export: true,
93
+ selection: true,
94
+
95
+ // Data source
96
+ table_source: 'ajax', // 'ajax' | 'html' | 'json'
97
+ ajax_url: null,
98
+ ajax_data: {},
99
+ ajax_method: 'GET',
100
+ ajax_function: 'axios', // 'xhr' | 'axios' | 'fetch'
101
+
102
+ // Callbacks
103
+ success: null,
104
+ error: null,
105
+ beforeSend: null,
106
+ afterSend: null,
107
+
108
+ // Template configurations
109
+ rowTemplate: {
110
+ source: 'response', // 'function' | 'html' | 'response'
111
+ response: 'html', // Property path in response data
112
+ function: null, // Function that returns HTML: (row, index) => '<tr>...</tr>'
113
+ html: null // Template string with {{placeholders}}
114
+ },
115
+ gridTemplate: {
116
+ source: 'response',
117
+ response: 'grid_html',
118
+ function: null,
119
+ html: null
120
+ },
121
+
122
+ // Grid layout options
123
+ gridColumns: {
124
+ default: 2,
125
+ sm: 1,
126
+ md: 2,
127
+ lg: 3,
128
+ xl: 4
129
+ },
130
+ gridGap: 4, // Tailwind gap value (gap-4)
131
+ gridContainerClass: 'ds-grid-container',
132
+
133
+ // Selectors
134
+ tableSelector: 'table',
135
+ bodySelector: 'tbody',
136
+ gridSelector: '.ds-grid-container',
137
+ toggleSelector: '.ds-view-toggle',
138
+ messageSelector: '.ds-table-message',
139
+ search_selector: null,
140
+
141
+ // Empty/Error states
142
+ emptyMessage: 'No data found',
143
+ emptyIcon: 'search_off',
144
+ errorMessage: 'Error loading data',
145
+
146
+ // Filter selectors (passed to DSTableFilter)
147
+ filter_selectors: {}
148
+ };
149
+
150
+ /**
151
+ * @param {string|HTMLElement} wrapper
152
+ * @param {DSGridOrTableOptions} options
153
+ */
154
+ constructor(wrapper, options = {}) {
155
+ this.wrapper = typeof wrapper === 'string' ? document.querySelector(wrapper) : wrapper;
156
+ if (!this.wrapper) throw new Error('DSGridOrTable: Wrapper element not found');
157
+
158
+ // Merge configs with special handling for nested objects
159
+ /** @type {DSGridOrTableOptions} */
160
+ this.config = this._mergeDeep({}, DSGridOrTable.defaults, options);
161
+
162
+ // State
163
+ this.data = [];
164
+ this.meta = {};
165
+ this.params = {
166
+ page: 1,
167
+ per_page: 15,
168
+ sort_by: null,
169
+ sort_order: 'asc',
170
+ search: null,
171
+ filters: {}
172
+ };
173
+
174
+ this.modules = {};
175
+ this.isLoading = false;
176
+ this.currentView = this.config.defaultView;
177
+
178
+ this._init();
179
+ }
180
+
181
+ _mergeDeep(target, ...sources) {
182
+ if (!sources.length) return target;
183
+ const source = sources.shift();
184
+
185
+ if (this._isObject(target) && this._isObject(source)) {
186
+ for (const key in source) {
187
+ if (this._isObject(source[key])) {
188
+ if (!target[key]) Object.assign(target, { [key]: {} });
189
+ this._mergeDeep(target[key], source[key]);
190
+ } else {
191
+ Object.assign(target, { [key]: source[key] });
192
+ }
193
+ }
194
+ }
195
+ return this._mergeDeep(target, ...sources);
196
+ }
197
+
198
+ _isObject(item) {
199
+ return item && typeof item === 'object' && !Array.isArray(item);
200
+ }
201
+
202
+ _init() {
203
+ // Find elements based on type
204
+ this.table = this.wrapper.querySelector(this.config.tableSelector);
205
+ this.tbody = this.wrapper.querySelector(this.config.bodySelector) || this.table?.querySelector('tbody');
206
+ this.gridContainer = this.wrapper.querySelector(this.config.gridSelector);
207
+
208
+ // Create grid container if needed and not exists
209
+ if ((this.config.type === 'grid' || this.config.type === 'gridable') && !this.gridContainer) {
210
+ this.gridContainer = document.createElement('div');
211
+ this.gridContainer.className = this._buildGridClasses();
212
+ if (this.table) {
213
+ this.table.parentNode.insertBefore(this.gridContainer, this.table.nextSibling);
214
+ } else {
215
+ this.wrapper.insertBefore(this.gridContainer, this.wrapper.firstChild);
216
+ }
217
+ }
218
+
219
+ // Initialize the renderer module
220
+ this.renderer = new DSGOTRenderer(this);
221
+
222
+ // Initialize view toggle for gridable mode
223
+ if (this.config.type === 'gridable' && this.config.showToggle) {
224
+ this.viewToggle = new DSGOTViewToggle(this);
225
+ }
226
+
227
+ // Initialize standard modules (reuse DSTable modules)
228
+ if (this.config.pagination) this.modules.pagination = new DSTablePagination(this);
229
+ if (this.config.search) this.modules.search = new DSTableSearch(this);
230
+ if (this.config.sort && this.table) this.modules.sort = new DSTableSort(this);
231
+ if (this.config.filter) this.modules.filter = new DSTableFilter(this);
232
+ if (this.config.export) this.modules.export = new DSTableExport(this);
233
+ if (this.config.selection && this.table) this.modules.selection = new DSTableSelection(this);
234
+
235
+ // Set initial visibility based on type
236
+ this._setInitialVisibility();
237
+
238
+ // Initial data load
239
+ this.loadData();
240
+ }
241
+
242
+ _buildGridClasses() {
243
+ const { gridColumns, gridGap, gridContainerClass } = this.config;
244
+ let classes = `${gridContainerClass} grid gap-${gridGap}`;
245
+
246
+ // Build responsive column classes
247
+ if (typeof gridColumns === 'number') {
248
+ classes += ` grid-cols-${gridColumns}`;
249
+ } else if (typeof gridColumns === 'object') {
250
+ classes += ` grid-cols-${gridColumns.default || 1}`;
251
+ if (gridColumns.sm) classes += ` sm:grid-cols-${gridColumns.sm}`;
252
+ if (gridColumns.md) classes += ` md:grid-cols-${gridColumns.md}`;
253
+ if (gridColumns.lg) classes += ` lg:grid-cols-${gridColumns.lg}`;
254
+ if (gridColumns.xl) classes += ` xl:grid-cols-${gridColumns.xl}`;
255
+ }
256
+
257
+ return classes;
258
+ }
259
+
260
+ _setInitialVisibility() {
261
+ if (this.config.type === 'table') {
262
+ if (this.gridContainer) this.gridContainer.classList.add('hidden');
263
+ if (this.table) this.table.classList.remove('hidden');
264
+ } else if (this.config.type === 'grid') {
265
+ if (this.table) this.table.classList.add('hidden');
266
+ if (this.gridContainer) this.gridContainer.classList.remove('hidden');
267
+ } else { // gridable
268
+ if (this.currentView === 'grid') {
269
+ if (this.table) this.table.classList.add('hidden');
270
+ if (this.gridContainer) this.gridContainer.classList.remove('hidden');
271
+ } else {
272
+ if (this.gridContainer) this.gridContainer.classList.add('hidden');
273
+ if (this.table) this.table.classList.remove('hidden');
274
+ }
275
+ }
276
+ }
277
+
278
+ // ================= DATA LOADING =================
279
+
280
+ async loadData() {
281
+ if (this.isLoading) return;
282
+ this.isLoading = true;
283
+ this._toggleLoading(true);
284
+
285
+ if (this.config.beforeSend) this.config.beforeSend({ params: this.params });
286
+
287
+ try {
288
+ if (this.config.table_source === 'ajax') {
289
+ await this._loadFromAjax();
290
+ } else if (this.config.table_source === 'json') {
291
+ this._handleDataSuccess(this.config.data || []);
292
+ } else if (this.config.table_source === 'html') {
293
+ this.isLoading = false;
294
+ this._toggleLoading(false);
295
+ }
296
+ } catch (error) {
297
+ console.error('DSGridOrTable: Error loading data', error);
298
+ if (this.config.error) this.config.error(error);
299
+ this.renderer.showError(this.config.errorMessage);
300
+ this.isLoading = false;
301
+ this._toggleLoading(false);
302
+ }
303
+ }
304
+
305
+ async _loadFromAjax() {
306
+ const url = this.config.ajax_url;
307
+ const method = this.config.ajax_method;
308
+ const data = { ...this.config.ajax_data, ...this.params };
309
+
310
+ let response;
311
+
312
+ if (this.config.ajax_function === 'axios' && window.axios) {
313
+ response = await window.axios({
314
+ method,
315
+ url,
316
+ params: method === 'GET' ? data : undefined,
317
+ data: method !== 'GET' ? data : undefined
318
+ });
319
+ this._handleDataSuccess(response.data);
320
+ } else if (this.config.ajax_function === 'fetch' || window.fetch) {
321
+ const queryString = new URLSearchParams(data).toString();
322
+ const fetchUrl = method === 'GET' ? `${url}?${queryString}` : url;
323
+ const options = {
324
+ method,
325
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
326
+ body: method !== 'GET' ? JSON.stringify(data) : undefined
327
+ };
328
+ const res = await fetch(fetchUrl, options);
329
+ const json = await res.json();
330
+ this._handleDataSuccess(json);
331
+ } else {
332
+ throw new Error('DSGridOrTable: No valid ajax function found');
333
+ }
334
+ }
335
+
336
+ _handleDataSuccess(response) {
337
+ if (response.data) {
338
+ this.data = response.data;
339
+ this.meta = response.meta || {};
340
+ } else if (Array.isArray(response)) {
341
+ this.data = response;
342
+ }
343
+
344
+ this.renderer.render();
345
+
346
+ if (this.config.success) this.config.success(response);
347
+ if (this.config.afterSend) this.config.afterSend(response);
348
+
349
+ // Notify modules
350
+ Object.values(this.modules).forEach(m => m.onDataLoaded && m.onDataLoaded(response));
351
+
352
+ this.isLoading = false;
353
+ this._toggleLoading(false);
354
+ this._emit('dataLoaded', response);
355
+ }
356
+
357
+ // ================= LOADING STATE =================
358
+
359
+ _toggleLoading(loading) {
360
+ if (loading) {
361
+ this.wrapper.classList.add('loading-state');
362
+ this.renderer.showSkeleton();
363
+ } else {
364
+ this.wrapper.classList.remove('loading-state');
365
+ }
366
+ }
367
+
368
+ // ================= VIEW SWITCHING =================
369
+
370
+ /**
371
+ * Switch between grid and table views (for gridable mode)
372
+ * @param {string} view - 'grid' or 'table'
373
+ */
374
+ setView(view) {
375
+ if (this.config.type !== 'gridable') return;
376
+ if (view !== 'grid' && view !== 'table') return;
377
+ if (view === this.currentView) return;
378
+
379
+ this.currentView = view;
380
+ this._setInitialVisibility();
381
+ this.renderer.render();
382
+
383
+ if (this.viewToggle) this.viewToggle.update();
384
+
385
+ this._emit('viewChange', { view });
386
+ }
387
+
388
+ /**
389
+ * Toggle between grid and table views
390
+ */
391
+ toggleView() {
392
+ const newView = this.currentView === 'grid' ? 'table' : 'grid';
393
+ this.setView(newView);
394
+ }
395
+
396
+ /**
397
+ * Get current view mode
398
+ * @returns {string} 'grid' or 'table'
399
+ */
400
+ getView() {
401
+ return this.currentView;
402
+ }
403
+
404
+ // ================= PUBLIC API =================
405
+
406
+ refresh() {
407
+ this.params.page = 1;
408
+ this.loadData();
409
+ }
410
+
411
+ setParam(key, value) {
412
+ this.params[key] = value;
413
+ }
414
+
415
+ getParam(key) {
416
+ return this.params[key];
417
+ }
418
+
419
+ registerModule(name, instance) {
420
+ this.modules[name] = instance;
421
+ }
422
+
423
+ /**
424
+ * Get the current render target element
425
+ * @returns {HTMLElement}
426
+ */
427
+ getRenderTarget() {
428
+ if (this.config.type === 'grid') return this.gridContainer;
429
+ if (this.config.type === 'table') return this.tbody;
430
+ return this.currentView === 'grid' ? this.gridContainer : this.tbody;
431
+ }
432
+
433
+ // ================= EVENTS =================
434
+
435
+ on(event, handler) {
436
+ this.wrapper.addEventListener(`dsgot:${event}`, handler);
437
+ }
438
+
439
+ _emit(event, detail = {}) {
440
+ this.wrapper.dispatchEvent(new CustomEvent(`dsgot:${event}`, { bubbles: true, detail }));
441
+ // Also emit dstable events for compatibility with DSTable modules
442
+ this.wrapper.dispatchEvent(new CustomEvent(`dstable:${event}`, { bubbles: true, detail }));
443
+ }
444
+
445
+ // ================= UTILITIES =================
446
+
447
+ _getNestedValue(obj, path) {
448
+ if (!path) return undefined;
449
+ return path.split('.').reduce((o, i) => (o ? o[i] : undefined), obj);
450
+ }
451
+ }
452
+
453
+ export default DSGridOrTable;