@bexis2/bexis2-core-ui 0.3.4 → 0.3.6

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.
@@ -0,0 +1,442 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import { createTable, Subscribe, Render, createRender } from 'svelte-headless-table';
4
+ import {
5
+ addSortBy,
6
+ addPagination,
7
+ addExpandedRows,
8
+ addColumnFilters,
9
+ addTableFilter,
10
+ addDataExport
11
+ } from 'svelte-headless-table/plugins';
12
+ import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
13
+ import { SlideToggle, storePopup } from '@skeletonlabs/skeleton';
14
+
15
+ storePopup.set({ computePosition, autoUpdate, offset, shift, flip, arrow });
16
+
17
+ import TableFilter from './TableFilter.svelte';
18
+ import TablePagination from './TablePagination.svelte';
19
+ import { columnFilter, searchFilter } from './filter';
20
+ import type { TableConfig } from '$lib/models/Models';
21
+
22
+ export let config: TableConfig<any>;
23
+
24
+ // Destructuring the config object and setting default values
25
+ let {
26
+ id: tableId, // Unique table ID
27
+ data, // Data store
28
+ columns, // Column configuration
29
+ resizable = 'none', // Resizability config
30
+ height = null, // Table height
31
+ rowHeight = null, // Row height
32
+ optionsComponent, // Custom component to render in the last column
33
+ defaultPageSize = 10, // Default page size - number of rows to display per page
34
+ toggle = false, // Whether to display the fitToScreen toggle
35
+ pageSizes = [5, 10, 15, 20], // Page sizes to display in the pagination component
36
+ fitToScreen = true, // Whether to fit the table to the screen,
37
+ exportable = false // Whether to display the export button and enable export functionality
38
+ } = config;
39
+
40
+ // Creatign a type to access keys of the objects in the data store
41
+ type AccessorType = keyof (typeof $data)[number];
42
+
43
+ // Creating a dispatcher to dispatch actions to the parent component
44
+ const dispatch = createEventDispatcher();
45
+ const actionDispatcher = (obj) => dispatch('action', obj);
46
+
47
+ // Initializing the table
48
+ const table = createTable(data, {
49
+ colFilter: addColumnFilters(),
50
+ tableFilter: addTableFilter({
51
+ fn: searchFilter
52
+ }),
53
+ sort: addSortBy({ disableMultiSort: true }),
54
+ page: addPagination({ initialPageSize: defaultPageSize }),
55
+ expand: addExpandedRows(),
56
+ export: addDataExport({ format: 'csv' })
57
+ });
58
+
59
+ // A variable to hold all the keys
60
+ const allCols: { [key: string]: any } = {};
61
+ // Iterating over each item to get all the possible keys
62
+ $data.forEach((item) => {
63
+ Object.keys(item).forEach((key) => {
64
+ if (!allCols[key]) {
65
+ allCols[key] = {};
66
+ }
67
+ });
68
+ });
69
+ // Creating an array of all the keys
70
+ const accessors: AccessorType[] = Object.keys(allCols) as AccessorType[];
71
+ // Configuring every table column with the provided options
72
+ const tableColumns = [
73
+ ...accessors
74
+ .filter((accessor) => {
75
+ // Filtering only unexcluded columns
76
+ const key = accessor as string;
77
+ if (columns !== undefined && key in columns && columns[key].exclude === true) {
78
+ return false;
79
+ }
80
+ return true;
81
+ })
82
+ .map((accessor) => {
83
+ const key = accessor as string;
84
+ // Applying configuration options for configured columns
85
+ if (columns !== undefined && key in columns) {
86
+ const {
87
+ header, // Custom header to display
88
+ colFilterFn, // Custom column filter function
89
+ colFilterComponent, // Custom column filter component
90
+ instructions, // Custom instructions for the column cells (sorting, filtering, searching, rendering)
91
+ disableFiltering = false, // Whether to disable filtering for the column
92
+ disableSorting = false // Whether to disable sorting for the column
93
+ } = columns[key];
94
+
95
+ const { toSortableValueFn, toFilterableValueFn, toStringFn, renderComponent } =
96
+ instructions ?? {};
97
+
98
+ return table.column({
99
+ // If header is not provided, use the key as the header
100
+ header: header ?? key,
101
+ accessor: accessor,
102
+ // Render the cell with the provided component, or use the toStringFn if provided, or just use the value
103
+ cell: ({ value, row }) => {
104
+ return renderComponent
105
+ ? createRender(renderComponent, { value, row })
106
+ : toStringFn
107
+ ? toStringFn(value)
108
+ : value;
109
+ },
110
+ plugins: {
111
+ // Sorting config
112
+ sort: {
113
+ disable: disableSorting,
114
+ invert: true,
115
+ getSortValue: (row) => {
116
+ // If provided, use the custom sorting function toSortableValueFn(), or just use the value
117
+ return toSortableValueFn ? toSortableValueFn(row) : row;
118
+ }
119
+ },
120
+ colFilter: !disableFiltering
121
+ ? {
122
+ fn: ({ filterValue, value }) => {
123
+ // If provided, use the custom filtering function toFilterableValueFn(), or just use the value
124
+ const val = toFilterableValueFn ? toFilterableValueFn(value) : value;
125
+ // If provided, use the custom filtering function colFilterFn(), or just use the default columnFilter()
126
+ return colFilterFn
127
+ ? colFilterFn({ filterValue, value: val })
128
+ : columnFilter({ filterValue, value: val });
129
+ },
130
+ render: ({ filterValue, values, id }) => {
131
+ // If provided, use the custom filter component, or use the default TableFilter component
132
+ return createRender(colFilterComponent ?? TableFilter, {
133
+ filterValue,
134
+ id,
135
+ tableId,
136
+ values,
137
+ toFilterableValueFn
138
+ });
139
+ }
140
+ }
141
+ : undefined,
142
+ tableFilter: {
143
+ // Search filter config
144
+ getFilterValue: (row) => {
145
+ // If provided, use the custom toString function toStringFn(), or just use the value
146
+ return toStringFn ? toStringFn(row) : row;
147
+ }
148
+ }
149
+ }
150
+ });
151
+ } else {
152
+ // Default configuration for unconfigured columns
153
+ return table.column({
154
+ header: key,
155
+ accessor: accessor,
156
+ cell: ({ value }) => {
157
+ // If null or undefined, return an empty string
158
+ return value ? value : '';
159
+ },
160
+ plugins: {
161
+ // Sorting enabled by default
162
+ sort: {
163
+ invert: true
164
+ },
165
+ // Filtering enabled by default
166
+ colFilter: {
167
+ fn: columnFilter,
168
+ render: ({ filterValue, values, id }) =>
169
+ createRender(TableFilter, {
170
+ filterValue,
171
+ id,
172
+ tableId,
173
+ values
174
+ })
175
+ }
176
+ }
177
+ });
178
+ }
179
+ })
180
+ ];
181
+
182
+ // If optionsComponent is provided, add a column for it at the end
183
+ if (optionsComponent !== undefined) {
184
+ tableColumns.push(
185
+ table.display({
186
+ id: 'optionsColumn',
187
+ header: '',
188
+ plugins: {
189
+ export: {
190
+ exclude: true
191
+ }
192
+ },
193
+ cell: ({ row }, _) => {
194
+ return createRender(optionsComponent!, {
195
+ row: row.isData() ? row.original : null,
196
+ dispatchFn: actionDispatcher
197
+ });
198
+ }
199
+ }) as any
200
+ );
201
+ }
202
+
203
+ // Creating the table columns
204
+ const createdTableColumns = table.createColumns(tableColumns);
205
+ // Creating the table view model
206
+ const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } =
207
+ table.createViewModel(createdTableColumns);
208
+ // Extracting filterValue to bind it for the search input and search immediately on input
209
+ const { filterValue } = pluginStates.tableFilter;
210
+ // CSV content to be exported. If unexportable, then null
211
+ const { exportedData } = pluginStates.export;
212
+
213
+ // Function to determine minWidth for a column to simplify the logic in the HTML
214
+ const minWidth = (id: string) => {
215
+ if (columns && id in columns) {
216
+ return columns[id].minWidth ?? 0;
217
+ }
218
+ return 0;
219
+ };
220
+ // Function to determine fixedWidth for a column to simplify the logic in the HTML
221
+ const fixedWidth = (id: string) => {
222
+ if (columns && id in columns) {
223
+ return columns[id].fixedWidth ?? 0;
224
+ }
225
+ return 0;
226
+ };
227
+ // Function to create custom styles for the columns to simplify the logic in the HTML
228
+ const cellStyle = (id: string) => {
229
+ const minW = minWidth(id);
230
+ const fixedW = fixedWidth(id);
231
+ const styles: string[] = [];
232
+
233
+ // If minWidth is provided, add to styles
234
+ minW && styles.push(`min-width: ${minW}px`);
235
+ // If fixedWidth is provided, add to styles
236
+ fixedW && styles.push(`width: ${fixedW}px`);
237
+ // Create and return styles separated by ';'
238
+ return styles.join(';');
239
+ };
240
+
241
+ // Resetting the resized columns and/or rows
242
+ const resetResize = () => {
243
+ // Run only if resizable is not none
244
+ if (resizable === 'columns' || resizable === 'both') {
245
+ $headerRows.forEach((row) => {
246
+ row.cells.forEach((cell) => {
247
+ const minW = minWidth(cell.id);
248
+ const fixedW = fixedWidth(cell.id);
249
+ // If a fixedWidth is provided for a column, then reset the width to that value
250
+ fixedW &&
251
+ document
252
+ .getElementById(`th-${tableId}-${cell.id}`)
253
+ ?.style.setProperty('width', `${fixedW}px`);
254
+ // If a minWidth is provided for a column, then reset the width to that value
255
+ minW &&
256
+ document
257
+ .getElementById(`th-${tableId}-${cell.id}`)
258
+ ?.style.setProperty('min-width', `${minW}px`);
259
+ // If neither minWidth nor fixedWidth provided for a column, then reset the width to auto
260
+ !minW &&
261
+ !fixedW &&
262
+ document.getElementById(`th-${tableId}-${cell.id}`)?.style.setProperty('width', 'auto');
263
+ });
264
+ });
265
+ }
266
+
267
+ if (resizable === 'rows' || resizable === 'both') {
268
+ $pageRows.forEach((row) => {
269
+ row.cells.forEach((cell) => {
270
+ // Reset all row heights to auto
271
+ document
272
+ .getElementById(`${tableId}-${cell.id}-${row.id}`)
273
+ ?.style.setProperty('height', 'auto');
274
+ });
275
+ });
276
+ }
277
+ };
278
+
279
+ const exportAsCsv = () => {
280
+ // Creating a hidden anchor element to download the CSV file
281
+ const anchor = document.createElement('a');
282
+ anchor.style.display = 'none';
283
+ anchor.href = `data:text/csv;charset=utf-8,${encodeURIComponent($exportedData)}`;
284
+ anchor.download = `${tableId}.csv`;
285
+ document.body.appendChild(anchor);
286
+ anchor.click();
287
+ document.body.removeChild(anchor);
288
+ };
289
+ </script>
290
+
291
+ <div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
292
+ <div class="table-container">
293
+ <!-- Enable the search filter if table is not empty -->
294
+ {#if $data.length > 0}
295
+ <input
296
+ class="input p-2 border border-primary-500"
297
+ type="text"
298
+ bind:value={$filterValue}
299
+ placeholder="Search rows..."
300
+ id="{tableId}-search"
301
+ />
302
+ <div class="flex justify-between items-center py-2 w-full">
303
+ <div>
304
+ <!-- Enable the fitToScreen toggle if toggle === true -->
305
+ {#if toggle}
306
+ <SlideToggle
307
+ name="slider-label"
308
+ active="bg-primary-500"
309
+ size="sm"
310
+ checked={fitToScreen}
311
+ id="{tableId}-toggle"
312
+ on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
313
+ >
314
+ {/if}
315
+ </div>
316
+ <div class="flex gap-2">
317
+ <!-- Enable the resetResize button if resizable !== 'none' -->
318
+ {#if resizable !== 'none'}
319
+ <button
320
+ type="button"
321
+ class="btn btn-sm variant-filled-primary rounded-full order-last"
322
+ on:click|preventDefault={resetResize}>Reset sizing</button
323
+ >
324
+ {/if}
325
+ {#if exportable}
326
+ <button
327
+ type="button"
328
+ class="btn btn-sm variant-filled-primary rounded-full order-last"
329
+ on:click|preventDefault={exportAsCsv}>Export as CSV</button
330
+ >
331
+ {/if}
332
+ </div>
333
+ </div>
334
+ {/if}
335
+
336
+ <div class="overflow-auto" style="height: {height}px">
337
+ <table
338
+ {...$tableAttrs}
339
+ class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip"
340
+ id="{tableId}-table"
341
+ >
342
+ <!-- If table height is provided, making the top row sticky -->
343
+ <thead class=" {height != null ? `sticky top-0` : ''}">
344
+ {#if $data.length > 0}
345
+ {#each $headerRows as headerRow (headerRow.id)}
346
+ <Subscribe
347
+ rowAttrs={headerRow.attrs()}
348
+ let:rowAttrs
349
+ rowProps={headerRow.props()}
350
+ let:rowProps
351
+ >
352
+ <tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-500 items-stretch">
353
+ {#each headerRow.cells as cell (cell.id)}
354
+ <Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
355
+ <th
356
+ scope="col"
357
+ class="!p-2 overflow-auto"
358
+ class:resize-x={(resizable === 'columns' || resizable === 'both') &&
359
+ !fixedWidth(cell.id)}
360
+ {...attrs}
361
+ id="th-{tableId}-{cell.id}"
362
+ style={cellStyle(cell.id)}
363
+ >
364
+ <div class="flex justify-between items-center">
365
+ <div class="flex gap-1 whitespace-pre-wrap">
366
+ <!-- Adding sorting config and styling -->
367
+ <span
368
+ class:underline={props.sort.order}
369
+ class:normal-case={cell.id !== cell.label}
370
+ class:cursor-pointer={!props.sort.disabled}
371
+ on:click={props.sort.toggle}
372
+ on:keydown={props.sort.toggle}
373
+ >
374
+ {cell.render()}
375
+ </span>
376
+ <div class="w-2">
377
+ {#if props.sort.order === 'asc'}
378
+
379
+ {:else if props.sort.order === 'desc'}
380
+
381
+ {/if}
382
+ </div>
383
+ </div>
384
+ <!-- Adding column filter config -->
385
+ {#if cell.isData()}
386
+ {#if props.colFilter?.render}
387
+ <div class="">
388
+ <Render of={props.colFilter.render} />
389
+ </div>
390
+ {/if}
391
+ {/if}
392
+ </div>
393
+ </th>
394
+ </Subscribe>
395
+ {/each}
396
+ </tr>
397
+ </Subscribe>
398
+ {/each}
399
+ {:else}
400
+ <!-- Table is empty -->
401
+ <p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>
402
+ {/if}
403
+ </thead>
404
+
405
+ <tbody class="overflow-auto" {...$tableBodyAttrs}>
406
+ {#if $data.length > 0}
407
+ {#each $pageRows as row (row.id)}
408
+ <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
409
+ <tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
410
+ {#each row.cells as cell, index (cell?.id)}
411
+ <Subscribe attrs={cell.attrs()} let:attrs>
412
+ <td
413
+ {...attrs}
414
+ class="!p-2 overflow-auto {index === 0 &&
415
+ (resizable === 'rows' || resizable === 'both')
416
+ ? 'resize-y'
417
+ : ''}"
418
+ id="{tableId}-{cell.id}-{row.id}"
419
+ >
420
+ <!-- Adding config for initial rowHeight, if provided -->
421
+ <div
422
+ class="flex items-center"
423
+ style="height: {rowHeight ? `${rowHeight}px` : 'auto'};"
424
+ >
425
+ <div class="grow h-full"><Render of={cell.render()} /></div>
426
+ </div>
427
+ </td>
428
+ </Subscribe>
429
+ {/each}
430
+ </tr>
431
+ </Subscribe>
432
+ {/each}
433
+ {/if}
434
+ </tbody>
435
+ </table>
436
+ </div>
437
+ </div>
438
+ {#if $data.length > 0}
439
+ <!-- Adding pagination, if table is not empty -->
440
+ <TablePagination pageConfig={pluginStates.page} {pageSizes} id={tableId} />
441
+ {/if}
442
+ </div>
@@ -21,6 +21,7 @@
21
21
  export let version = 1;
22
22
 
23
23
  import { onMount } from 'svelte';
24
+ import { ProgressBar } from '@skeletonlabs/skeleton';
24
25
 
25
26
  // export let description="";
26
27
  // export let status=0;
@@ -34,6 +35,8 @@
34
35
 
35
36
  export let data: fileUploaderType | undefined;
36
37
 
38
+ let isUploading:boolean = false;
39
+
37
40
  $: model = data;
38
41
  $: submitBt = 'submit';
39
42
 
@@ -124,6 +127,7 @@
124
127
  //console.log('SUBMIT');
125
128
 
126
129
  dispatch('submit');
130
+ isUploading = true;
127
131
 
128
132
  let url = submit + '?id=' + id;
129
133
 
@@ -156,6 +160,8 @@
156
160
  files.accepted = [];
157
161
  }
158
162
  }
163
+
164
+ isUploading = false;
159
165
  }
160
166
  </script>
161
167
 
@@ -182,6 +188,9 @@
182
188
  {/if}
183
189
  </p>
184
190
  </DropZone>
191
+ {#if isUploading}
192
+ <ProgressBar value={undefined}/>
193
+ {/if}
185
194
  </div>
186
195
 
187
196
  <button id={submitBt} color="primary" style="display:none"><Fa icon={faSave} /></button>
@@ -16,7 +16,7 @@
16
16
  function onMouseOut() {}
17
17
  </script>
18
18
 
19
- <div id="{id}-container" on:mouseover={onMouseOver} on:mouseout={onMouseOut}>
19
+ <div id="{id}-container" on:mouseover={onMouseOver} on:focus={onMouseOver} on:mouseout={onMouseOut} on:blur={onMouseOut}>
20
20
  <label class="label">
21
21
  <span
22
22
  >{label}
@@ -39,7 +39,7 @@
39
39
  {/if}
40
40
 
41
41
  {#each links as link}
42
- <span class="chip variant-soft hover:variant-filled" on:click={() => goTo(link.url, false)}>
42
+ <span class="chip variant-soft hover:variant-filled" on:click={() => goTo(link.url, false)} on:keypress={() => goTo(link.url, false)}>
43
43
  <span>{link.label}</span>
44
44
  </span>
45
45
  {/each}