@bexis2/bexis2-core-ui 0.3.2 → 0.3.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.
package/README.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # bexis-core-ui
2
2
 
3
+ ## 0.3.3
4
+
5
+ - Table
6
+ - `rowHeight (number)`: fixed height for rows in pixels. (if combined with resizability, acts as minimum height)
7
+ - `exportability (boolean)`: if toggled true, lets user to export the current state of the table as CSV.
8
+ - Optimized styles for dark mode.
9
+ - Bug fix related to empty tables.
10
+ - Migrated to new TableOfContents (Beta) on documentation page.
11
+ - LightSwitch for toggling dark mode on documentation pages.
12
+ - Optimized surface colors in dark mode.
13
+
3
14
  ## 0.3.1
4
15
 
5
16
  - add bexis2theme
@@ -5,7 +5,8 @@ import {
5
5
  addPagination,
6
6
  addExpandedRows,
7
7
  addColumnFilters,
8
- addTableFilter
8
+ addTableFilter,
9
+ addDataExport
9
10
  } from "svelte-headless-table/plugins";
10
11
  import { computePosition, autoUpdate, offset, shift, flip, arrow } from "@floating-ui/dom";
11
12
  import { SlideToggle, storePopup } from "@skeletonlabs/skeleton";
@@ -16,15 +17,29 @@ import { columnFilter, searchFilter } from "./filter";
16
17
  export let config;
17
18
  let {
18
19
  id: tableId,
20
+ // Unique table ID
19
21
  data,
22
+ // Data store
20
23
  columns,
24
+ // Column configuration
21
25
  resizable = "none",
26
+ // Resizability config
22
27
  height = null,
28
+ // Table height
29
+ rowHeight = null,
30
+ // Row height
23
31
  optionsComponent,
32
+ // Custom component to render in the last column
24
33
  defaultPageSize = 10,
34
+ // Default page size - number of rows to display per page
25
35
  toggle = false,
36
+ // Whether to display the fitToScreen toggle
26
37
  pageSizes = [5, 10, 15, 20],
27
- fitToScreen = true
38
+ // Page sizes to display in the pagination component
39
+ fitToScreen = true,
40
+ // Whether to fit the table to the screen,
41
+ exportable = false
42
+ // Whether to display the export button and enable export functionality
28
43
  } = config;
29
44
  const dispatch = createEventDispatcher();
30
45
  const actionDispatcher = (obj) => dispatch("action", obj);
@@ -35,7 +50,8 @@ const table = createTable(data, {
35
50
  }),
36
51
  sort: addSortBy({ disableMultiSort: true }),
37
52
  page: addPagination({ initialPageSize: defaultPageSize }),
38
- expand: addExpandedRows()
53
+ expand: addExpandedRows(),
54
+ export: addDataExport({ format: "csv" })
39
55
  });
40
56
  const allCols = {};
41
57
  $data.forEach((item) => {
@@ -58,20 +74,29 @@ const tableColumns = [
58
74
  if (columns !== void 0 && key in columns) {
59
75
  const {
60
76
  header,
77
+ // Custom header to display
61
78
  colFilterFn,
79
+ // Custom column filter function
62
80
  colFilterComponent,
81
+ // Custom column filter component
63
82
  instructions,
83
+ // Custom instructions for the column cells (sorting, filtering, searching, rendering)
64
84
  disableFiltering = false,
85
+ // Whether to disable filtering for the column
65
86
  disableSorting = false
87
+ // Whether to disable sorting for the column
66
88
  } = columns[key];
67
89
  const { toSortableValueFn, toFilterableValueFn, toStringFn, renderComponent } = instructions ?? {};
68
90
  return table.column({
91
+ // If header is not provided, use the key as the header
69
92
  header: header ?? key,
70
93
  accessor,
94
+ // Render the cell with the provided component, or use the toStringFn if provided, or just use the value
71
95
  cell: ({ value, row }) => {
72
96
  return renderComponent ? createRender(renderComponent, { value, row }) : toStringFn ? toStringFn(value) : value;
73
97
  },
74
98
  plugins: {
99
+ // Sorting config
75
100
  sort: {
76
101
  disable: disableSorting,
77
102
  invert: true,
@@ -95,6 +120,7 @@ const tableColumns = [
95
120
  }
96
121
  } : void 0,
97
122
  tableFilter: {
123
+ // Search filter config
98
124
  getFilterValue: (row) => {
99
125
  return toStringFn ? toStringFn(row) : row;
100
126
  }
@@ -109,9 +135,11 @@ const tableColumns = [
109
135
  return value ? value : "";
110
136
  },
111
137
  plugins: {
138
+ // Sorting enabled by default
112
139
  sort: {
113
140
  invert: true
114
141
  },
142
+ // Filtering enabled by default
115
143
  colFilter: {
116
144
  fn: columnFilter,
117
145
  render: ({ filterValue: filterValue2, values, id }) => createRender(TableFilter, {
@@ -131,6 +159,11 @@ if (optionsComponent !== void 0) {
131
159
  table.display({
132
160
  id: "optionsColumn",
133
161
  header: "",
162
+ plugins: {
163
+ export: {
164
+ exclude: true
165
+ }
166
+ },
134
167
  cell: ({ row }, _) => {
135
168
  return createRender(optionsComponent, {
136
169
  row: row.isData() ? row.original : null,
@@ -143,6 +176,7 @@ if (optionsComponent !== void 0) {
143
176
  const createdTableColumns = table.createColumns(tableColumns);
144
177
  const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } = table.createViewModel(createdTableColumns);
145
178
  const { filterValue } = pluginStates.tableFilter;
179
+ const { exportedData } = pluginStates.export;
146
180
  const minWidth = (id) => {
147
181
  if (columns && id in columns) {
148
182
  return columns[id].minWidth ?? 0;
@@ -183,10 +217,20 @@ const resetResize = () => {
183
217
  });
184
218
  }
185
219
  };
220
+ const exportAsCsv = () => {
221
+ const anchor = document.createElement("a");
222
+ anchor.style.display = "none";
223
+ anchor.href = `data:text/csv;charset=utf-8,${encodeURIComponent($exportedData)}`;
224
+ anchor.download = `${tableId}.csv`;
225
+ document.body.appendChild(anchor);
226
+ anchor.click();
227
+ document.body.removeChild(anchor);
228
+ };
186
229
  </script>
187
230
 
188
231
  <div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
189
232
  <div class="table-container">
233
+ <!-- Enable the search filter if table is not empty -->
190
234
  {#if $data.length > 0}
191
235
  <input
192
236
  class="input p-2 border border-primary-500"
@@ -195,123 +239,144 @@ const resetResize = () => {
195
239
  placeholder="Search rows..."
196
240
  id="{tableId}-search"
197
241
  />
198
- {/if}
199
-
200
- <div class="flex justify-between items-center py-2 w-full">
201
- <div>
202
- {#if toggle}
203
- <SlideToggle
204
- name="slider-label"
205
- active="bg-primary-500"
206
- size="sm"
207
- checked={fitToScreen}
208
- id="{tableId}-toggle"
209
- on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
210
- >
211
- {/if}
212
- </div>
213
- <div>
214
- {#if resizable !== 'none'}
215
- <button
216
- type="button"
217
- class="btn btn-sm variant-filled-primary rounded-full order-last"
218
- on:click|preventDefault={resetResize}>Reset sizing</button
219
- >
220
- {/if}
242
+ <div class="flex justify-between items-center py-2 w-full">
243
+ <div>
244
+ <!-- Enable the fitToScreen toggle if toggle === true -->
245
+ {#if toggle}
246
+ <SlideToggle
247
+ name="slider-label"
248
+ active="bg-primary-500"
249
+ size="sm"
250
+ checked={fitToScreen}
251
+ id="{tableId}-toggle"
252
+ on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
253
+ >
254
+ {/if}
255
+ </div>
256
+ <div class="flex gap-2">
257
+ <!-- Enable the resetResize button if resizable !== 'none' -->
258
+ {#if resizable !== 'none'}
259
+ <button
260
+ type="button"
261
+ class="btn btn-sm variant-filled-primary rounded-full order-last"
262
+ on:click|preventDefault={resetResize}>Reset sizing</button
263
+ >
264
+ {/if}
265
+ {#if exportable}
266
+ <button
267
+ type="button"
268
+ class="btn btn-sm variant-filled-primary rounded-full order-last"
269
+ on:click|preventDefault={exportAsCsv}>Export as CSV</button
270
+ >
271
+ {/if}
272
+ </div>
221
273
  </div>
222
- </div>
274
+ {/if}
223
275
 
224
276
  <div class="overflow-auto" style="height: {height}px">
225
277
  <table
226
278
  {...$tableAttrs}
227
- class="table table-auto table-compact bg-tertiary-500/30 overflow-clip"
279
+ class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip"
228
280
  id="{tableId}-table"
229
281
  >
282
+ <!-- If table height is provided, making the top row sticky -->
230
283
  <thead class=" {height != null ? `sticky top-0` : ''}">
231
- {#each $headerRows as headerRow (headerRow.id)}
232
- <Subscribe
233
- rowAttrs={headerRow.attrs()}
234
- let:rowAttrs
235
- rowProps={headerRow.props()}
236
- let:rowProps
237
- >
238
- <tr {...rowAttrs} class="bg-primary-300 items-stretch">
239
- {#each headerRow.cells as cell (cell.id)}
240
- <Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
241
- <th
242
- scope="col"
243
- class="!p-2 overflow-auto"
244
- class:resize-x={(resizable === 'columns' || resizable === 'both') &&
245
- !fixedWidth(cell.id)}
246
- {...attrs}
247
- id="th-{tableId}-{cell.id}"
248
- style={cellStyle(cell.id)}
249
- >
250
- <div class="flex justify-between items-center">
251
- <div class="flex gap-1 whitespace-pre-wrap">
252
- <span
253
- class:underline={props.sort.order}
254
- class:normal-case={cell.id !== cell.label}
255
- class:cursor-pointer={!props.sort.disabled}
256
- on:click={props.sort.toggle}
257
- on:keydown={props.sort.toggle}
258
- >
259
- {cell.render()}
260
- </span>
261
- <div class="w-2">
262
- {#if props.sort.order === 'asc'}
263
-
264
- {:else if props.sort.order === 'desc'}
265
-
266
- {/if}
267
- </div>
268
- </div>
269
- {#if cell.isData()}
270
- {#if props.colFilter?.render}
271
- <div class="">
272
- <Render of={props.colFilter.render} />
284
+ {#if $data.length > 0}
285
+ {#each $headerRows as headerRow (headerRow.id)}
286
+ <Subscribe
287
+ rowAttrs={headerRow.attrs()}
288
+ let:rowAttrs
289
+ rowProps={headerRow.props()}
290
+ let:rowProps
291
+ >
292
+ <tr {...rowAttrs} class="bg-primary-300 dark:bg-primary-500 items-stretch">
293
+ {#each headerRow.cells as cell (cell.id)}
294
+ <Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
295
+ <th
296
+ scope="col"
297
+ class="!p-2 overflow-auto"
298
+ class:resize-x={(resizable === 'columns' || resizable === 'both') &&
299
+ !fixedWidth(cell.id)}
300
+ {...attrs}
301
+ id="th-{tableId}-{cell.id}"
302
+ style={cellStyle(cell.id)}
303
+ >
304
+ <div class="flex justify-between items-center">
305
+ <div class="flex gap-1 whitespace-pre-wrap">
306
+ <!-- Adding sorting config and styling -->
307
+ <span
308
+ class:underline={props.sort.order}
309
+ class:normal-case={cell.id !== cell.label}
310
+ class:cursor-pointer={!props.sort.disabled}
311
+ on:click={props.sort.toggle}
312
+ on:keydown={props.sort.toggle}
313
+ >
314
+ {cell.render()}
315
+ </span>
316
+ <div class="w-2">
317
+ {#if props.sort.order === 'asc'}
318
+
319
+ {:else if props.sort.order === 'desc'}
320
+
321
+ {/if}
273
322
  </div>
323
+ </div>
324
+ <!-- Adding column filter config -->
325
+ {#if cell.isData()}
326
+ {#if props.colFilter?.render}
327
+ <div class="">
328
+ <Render of={props.colFilter.render} />
329
+ </div>
330
+ {/if}
274
331
  {/if}
275
- {/if}
276
- </div>
277
- </th>
278
- </Subscribe>
279
- {/each}
280
- </tr>
281
- </Subscribe>
332
+ </div>
333
+ </th>
334
+ </Subscribe>
335
+ {/each}
336
+ </tr>
337
+ </Subscribe>
338
+ {/each}
282
339
  {:else}
340
+ <!-- Table is empty -->
283
341
  <p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>
284
- {/each}
342
+ {/if}
285
343
  </thead>
286
344
 
287
345
  <tbody class="overflow-auto" {...$tableBodyAttrs}>
288
- {#each $pageRows as row (row.id)}
289
- <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
290
- <tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
291
- {#each row.cells as cell, index (cell?.id)}
292
- <Subscribe attrs={cell.attrs()} let:attrs>
293
- <td
294
- {...attrs}
295
- class="!p-2 overflow-auto {index === 0 &&
296
- (resizable === 'rows' || resizable === 'both')
297
- ? 'resize-y'
298
- : ''}"
299
- id="{tableId}-{cell.id}-{row.id}"
300
- >
301
- <div class="flex items-center h-max overflow-x-auto">
302
- <Render of={cell.render()} />
303
- </div>
304
- </td>
305
- </Subscribe>
306
- {/each}
307
- </tr>
308
- </Subscribe>
309
- {/each}
346
+ {#if $data.length > 0}
347
+ {#each $pageRows as row (row.id)}
348
+ <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
349
+ <tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
350
+ {#each row.cells as cell, index (cell?.id)}
351
+ <Subscribe attrs={cell.attrs()} let:attrs>
352
+ <td
353
+ {...attrs}
354
+ class="!p-2 overflow-auto {index === 0 &&
355
+ (resizable === 'rows' || resizable === 'both')
356
+ ? 'resize-y'
357
+ : ''}"
358
+ id="{tableId}-{cell.id}-{row.id}"
359
+ >
360
+ <!-- Adding config for initial rowHeight, if provided -->
361
+ <div
362
+ class="flex items-center"
363
+ style="height: {rowHeight ? `${rowHeight}px` : 'auto'};"
364
+ >
365
+ <div class="grow h-full"><Render of={cell.render()} /></div>
366
+ </div>
367
+ </td>
368
+ </Subscribe>
369
+ {/each}
370
+ </tr>
371
+ </Subscribe>
372
+ {/each}
373
+ {/if}
310
374
  </tbody>
311
375
  </table>
312
376
  </div>
313
377
  </div>
314
378
  {#if $data.length > 0}
379
+ <!-- Adding pagination, if table is not empty -->
315
380
  <TablePagination pageConfig={pluginStates.page} {pageSizes} id={tableId} />
316
381
  {/if}
317
382
  </div>
@@ -129,6 +129,7 @@ type = isDate ? "date" : type;
129
129
  class="btn variant-filled-primary btn-sm"
130
130
  type="button"
131
131
  on:click|preventDefault={() => {
132
+ // Set the defaults when cleared
132
133
  firstOption = 'isequal';
133
134
  firstValue = undefined;
134
135
  secondOption = 'isequal';
package/dist/index.d.ts CHANGED
@@ -22,12 +22,12 @@ import type { TableConfig, Columns, Column } from './models/Models';
22
22
  import CodeEditor from './components/CodeEditor/CodeEditor.svelte';
23
23
  import Notification from './components/page/Notification.svelte';
24
24
  import TablePlaceholder from './components/page/TablePlaceholder.svelte';
25
+ import { bexis2theme } from './themes/theme-bexis2';
25
26
  export { Checkbox, CheckboxKVPList, CheckboxList, DateInput, DropdownKVP, MultiSelect, NumberInput, TextArea, TextInput };
26
27
  export { FileInfo, FileIcon, FileUploader };
27
28
  export { Spinner, Page, Alert, Menu, ErrorMessage };
28
29
  export { Api } from './services/Api.js';
29
30
  export { host, username, password, setApiConfig } from './stores/apiStores.js';
30
- export { bexis2theme } from './themes/theme-bexis2';
31
31
  export type { userType, inputType, fileUploaderType, linkType, listItemType, keyValuePairType, fileInfoType, fileReaderInfoType, asciiFileReaderInfoType } from './models/Models.js';
32
32
  export { helpStore } from './stores/pageStores';
33
33
  export type { helpStoreType, helpItemType } from './models/Models';
@@ -40,3 +40,4 @@ export { positionType, pageContentLayoutType, decimalCharacterType, orientationT
40
40
  export { Table, TableFilter, columnFilter, searchFilter };
41
41
  export { CodeEditor };
42
42
  export type { TableConfig, Columns, Column };
43
+ export { bexis2theme };
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ import CodeEditor from './components/CodeEditor/CodeEditor.svelte';
30
30
  import Notification from './components/page/Notification.svelte';
31
31
  //table placeholder
32
32
  import TablePlaceholder from './components/page/TablePlaceholder.svelte';
33
+ // theme
34
+ import { bexis2theme } from './themes/theme-bexis2';
33
35
  //Form
34
36
  export { Checkbox, CheckboxKVPList, CheckboxList, DateInput, DropdownKVP, MultiSelect, NumberInput, TextArea, TextInput };
35
37
  //File
@@ -39,8 +41,6 @@ export { Spinner, Page, Alert, Menu, ErrorMessage };
39
41
  //Api
40
42
  export { Api } from './services/Api.js';
41
43
  export { host, username, password, setApiConfig } from './stores/apiStores.js';
42
- // theme
43
- export { bexis2theme } from './themes/theme-bexis2';
44
44
  //help
45
45
  export { helpStore } from './stores/pageStores';
46
46
  //notification
@@ -55,3 +55,5 @@ export { positionType, pageContentLayoutType, decimalCharacterType, orientationT
55
55
  export { Table, TableFilter, columnFilter, searchFilter };
56
56
  // CodeEditor
57
57
  export { CodeEditor };
58
+ // theme
59
+ export { bexis2theme };
@@ -87,7 +87,9 @@ export interface TableConfig<T> {
87
87
  toggle?: boolean;
88
88
  fitToScreen?: boolean;
89
89
  height?: null | number;
90
+ rowHeight?: number;
90
91
  columns?: Columns;
92
+ exportable?: boolean;
91
93
  pageSizes?: number[];
92
94
  defaultPageSize?: number;
93
95
  optionsComponent?: typeof SvelteComponent;
@@ -95,5 +95,18 @@ export const bexis2theme = {
95
95
  '--color-surface-700': '149 149 149',
96
96
  '--color-surface-800': '119 119 119',
97
97
  '--color-surface-900': '98 98 98' // #626262
98
+ },
99
+ properties_dark: {
100
+ // surface | #2e2e2e
101
+ '--color-surface-50': '224 224 224',
102
+ '--color-surface-100': '213 213 213',
103
+ '--color-surface-200': '203 203 203',
104
+ '--color-surface-300': '171 171 171',
105
+ '--color-surface-400': '109 109 109',
106
+ '--color-surface-500': '46 46 46',
107
+ '--color-surface-600': '41 41 41',
108
+ '--color-surface-700': '35 35 35',
109
+ '--color-surface-800': '28 28 28',
110
+ '--color-surface-900': '23 23 23' // #171717
98
111
  }
99
112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bexis2/bexis2-core-ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -74,7 +74,7 @@
74
74
  "delay": "^6.0.0",
75
75
  "dotenv": "^16.3.1",
76
76
  "eslint4b-prebuilt": "^6.7.2",
77
- "highlight.js": "^11.8.0",
77
+ "highlight.js": "^11.9.0",
78
78
  "highlightjs-svelte": "^1.0.6",
79
79
  "svelte": "^3.54.0",
80
80
  "svelte-codemirror-editor": "^1.1.0",
@@ -6,7 +6,8 @@
6
6
  addPagination,
7
7
  addExpandedRows,
8
8
  addColumnFilters,
9
- addTableFilter
9
+ addTableFilter,
10
+ addDataExport
10
11
  } from 'svelte-headless-table/plugins';
11
12
  import { computePosition, autoUpdate, offset, shift, flip, arrow } from '@floating-ui/dom';
12
13
  import { SlideToggle, storePopup } from '@skeletonlabs/skeleton';
@@ -20,24 +21,30 @@
20
21
 
21
22
  export let config: TableConfig<any>;
22
23
 
24
+ // Destructuring the config object and setting default values
23
25
  let {
24
- id: tableId,
25
- data,
26
- columns,
27
- resizable = 'none',
28
- height = null,
29
- optionsComponent,
30
- defaultPageSize = 10,
31
- toggle = false,
32
- pageSizes = [5, 10, 15, 20],
33
- fitToScreen = true
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
34
38
  } = config;
35
39
 
40
+ // Creatign a type to access keys of the objects in the data store
36
41
  type AccessorType = keyof (typeof $data)[number];
37
42
 
43
+ // Creating a dispatcher to dispatch actions to the parent component
38
44
  const dispatch = createEventDispatcher();
39
45
  const actionDispatcher = (obj) => dispatch('action', obj);
40
46
 
47
+ // Initializing the table
41
48
  const table = createTable(data, {
42
49
  colFilter: addColumnFilters(),
43
50
  tableFilter: addTableFilter({
@@ -45,11 +52,13 @@
45
52
  }),
46
53
  sort: addSortBy({ disableMultiSort: true }),
47
54
  page: addPagination({ initialPageSize: defaultPageSize }),
48
- expand: addExpandedRows()
55
+ expand: addExpandedRows(),
56
+ export: addDataExport({ format: 'csv' })
49
57
  });
50
58
 
59
+ // A variable to hold all the keys
51
60
  const allCols: { [key: string]: any } = {};
52
-
61
+ // Iterating over each item to get all the possible keys
53
62
  $data.forEach((item) => {
54
63
  Object.keys(item).forEach((key) => {
55
64
  if (!allCols[key]) {
@@ -57,12 +66,13 @@
57
66
  }
58
67
  });
59
68
  });
60
-
69
+ // Creating an array of all the keys
61
70
  const accessors: AccessorType[] = Object.keys(allCols) as AccessorType[];
62
-
71
+ // Configuring every table column with the provided options
63
72
  const tableColumns = [
64
73
  ...accessors
65
74
  .filter((accessor) => {
75
+ // Filtering only unexcluded columns
66
76
  const key = accessor as string;
67
77
  if (columns !== undefined && key in columns && columns[key].exclude === true) {
68
78
  return false;
@@ -71,22 +81,25 @@
71
81
  })
72
82
  .map((accessor) => {
73
83
  const key = accessor as string;
84
+ // Applying configuration options for configured columns
74
85
  if (columns !== undefined && key in columns) {
75
86
  const {
76
- header,
77
- colFilterFn,
78
- colFilterComponent,
79
- instructions,
80
- disableFiltering = false,
81
- disableSorting = false
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
82
93
  } = columns[key];
83
94
 
84
95
  const { toSortableValueFn, toFilterableValueFn, toStringFn, renderComponent } =
85
96
  instructions ?? {};
86
97
 
87
98
  return table.column({
99
+ // If header is not provided, use the key as the header
88
100
  header: header ?? key,
89
101
  accessor: accessor,
102
+ // Render the cell with the provided component, or use the toStringFn if provided, or just use the value
90
103
  cell: ({ value, row }) => {
91
104
  return renderComponent
92
105
  ? createRender(renderComponent, { value, row })
@@ -95,23 +108,27 @@
95
108
  : value;
96
109
  },
97
110
  plugins: {
111
+ // Sorting config
98
112
  sort: {
99
113
  disable: disableSorting,
100
114
  invert: true,
101
115
  getSortValue: (row) => {
116
+ // If provided, use the custom sorting function toSortableValueFn(), or just use the value
102
117
  return toSortableValueFn ? toSortableValueFn(row) : row;
103
118
  }
104
119
  },
105
120
  colFilter: !disableFiltering
106
121
  ? {
107
122
  fn: ({ filterValue, value }) => {
123
+ // If provided, use the custom filtering function toFilterableValueFn(), or just use the value
108
124
  const val = toFilterableValueFn ? toFilterableValueFn(value) : value;
109
-
125
+ // If provided, use the custom filtering function colFilterFn(), or just use the default columnFilter()
110
126
  return colFilterFn
111
127
  ? colFilterFn({ filterValue, value: val })
112
128
  : columnFilter({ filterValue, value: val });
113
129
  },
114
130
  render: ({ filterValue, values, id }) => {
131
+ // If provided, use the custom filter component, or use the default TableFilter component
115
132
  return createRender(colFilterComponent ?? TableFilter, {
116
133
  filterValue,
117
134
  id,
@@ -123,23 +140,29 @@
123
140
  }
124
141
  : undefined,
125
142
  tableFilter: {
143
+ // Search filter config
126
144
  getFilterValue: (row) => {
145
+ // If provided, use the custom toString function toStringFn(), or just use the value
127
146
  return toStringFn ? toStringFn(row) : row;
128
147
  }
129
148
  }
130
149
  }
131
150
  });
132
151
  } else {
152
+ // Default configuration for unconfigured columns
133
153
  return table.column({
134
154
  header: key,
135
155
  accessor: accessor,
136
156
  cell: ({ value }) => {
157
+ // If null or undefined, return an empty string
137
158
  return value ? value : '';
138
159
  },
139
160
  plugins: {
161
+ // Sorting enabled by default
140
162
  sort: {
141
163
  invert: true
142
164
  },
165
+ // Filtering enabled by default
143
166
  colFilter: {
144
167
  fn: columnFilter,
145
168
  render: ({ filterValue, values, id }) =>
@@ -156,11 +179,17 @@
156
179
  })
157
180
  ];
158
181
 
182
+ // If optionsComponent is provided, add a column for it at the end
159
183
  if (optionsComponent !== undefined) {
160
184
  tableColumns.push(
161
185
  table.display({
162
186
  id: 'optionsColumn',
163
187
  header: '',
188
+ plugins: {
189
+ export: {
190
+ exclude: true
191
+ }
192
+ },
164
193
  cell: ({ row }, _) => {
165
194
  return createRender(optionsComponent!, {
166
195
  row: row.isData() ? row.original : null,
@@ -171,53 +200,63 @@
171
200
  );
172
201
  }
173
202
 
203
+ // Creating the table columns
174
204
  const createdTableColumns = table.createColumns(tableColumns);
175
-
205
+ // Creating the table view model
176
206
  const { headerRows, pageRows, tableAttrs, tableBodyAttrs, pluginStates } =
177
207
  table.createViewModel(createdTableColumns);
208
+ // Extracting filterValue to bind it for the search input and search immediately on input
178
209
  const { filterValue } = pluginStates.tableFilter;
210
+ // CSV content to be exported. If unexportable, then null
211
+ const { exportedData } = pluginStates.export;
179
212
 
213
+ // Function to determine minWidth for a column to simplify the logic in the HTML
180
214
  const minWidth = (id: string) => {
181
215
  if (columns && id in columns) {
182
216
  return columns[id].minWidth ?? 0;
183
217
  }
184
218
  return 0;
185
219
  };
186
-
220
+ // Function to determine fixedWidth for a column to simplify the logic in the HTML
187
221
  const fixedWidth = (id: string) => {
188
222
  if (columns && id in columns) {
189
223
  return columns[id].fixedWidth ?? 0;
190
224
  }
191
225
  return 0;
192
226
  };
193
-
227
+ // Function to create custom styles for the columns to simplify the logic in the HTML
194
228
  const cellStyle = (id: string) => {
195
229
  const minW = minWidth(id);
196
230
  const fixedW = fixedWidth(id);
197
231
  const styles: string[] = [];
198
232
 
233
+ // If minWidth is provided, add to styles
199
234
  minW && styles.push(`min-width: ${minW}px`);
235
+ // If fixedWidth is provided, add to styles
200
236
  fixedW && styles.push(`width: ${fixedW}px`);
201
-
237
+ // Create and return styles separated by ';'
202
238
  return styles.join(';');
203
239
  };
204
240
 
241
+ // Resetting the resized columns and/or rows
205
242
  const resetResize = () => {
243
+ // Run only if resizable is not none
206
244
  if (resizable === 'columns' || resizable === 'both') {
207
245
  $headerRows.forEach((row) => {
208
246
  row.cells.forEach((cell) => {
209
247
  const minW = minWidth(cell.id);
210
248
  const fixedW = fixedWidth(cell.id);
211
-
249
+ // If a fixedWidth is provided for a column, then reset the width to that value
212
250
  fixedW &&
213
251
  document
214
252
  .getElementById(`th-${tableId}-${cell.id}`)
215
253
  ?.style.setProperty('width', `${fixedW}px`);
254
+ // If a minWidth is provided for a column, then reset the width to that value
216
255
  minW &&
217
256
  document
218
257
  .getElementById(`th-${tableId}-${cell.id}`)
219
258
  ?.style.setProperty('min-width', `${minW}px`);
220
-
259
+ // If neither minWidth nor fixedWidth provided for a column, then reset the width to auto
221
260
  !minW &&
222
261
  !fixedW &&
223
262
  document.getElementById(`th-${tableId}-${cell.id}`)?.style.setProperty('width', 'auto');
@@ -228,6 +267,7 @@
228
267
  if (resizable === 'rows' || resizable === 'both') {
229
268
  $pageRows.forEach((row) => {
230
269
  row.cells.forEach((cell) => {
270
+ // Reset all row heights to auto
231
271
  document
232
272
  .getElementById(`${tableId}-${cell.id}-${row.id}`)
233
273
  ?.style.setProperty('height', 'auto');
@@ -235,10 +275,22 @@
235
275
  });
236
276
  }
237
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
+ };
238
289
  </script>
239
290
 
240
291
  <div class="grid gap-2 overflow-auto" class:w-fit={!fitToScreen} class:w-full={fitToScreen}>
241
292
  <div class="table-container">
293
+ <!-- Enable the search filter if table is not empty -->
242
294
  {#if $data.length > 0}
243
295
  <input
244
296
  class="input p-2 border border-primary-500"
@@ -247,123 +299,144 @@
247
299
  placeholder="Search rows..."
248
300
  id="{tableId}-search"
249
301
  />
250
- {/if}
251
-
252
- <div class="flex justify-between items-center py-2 w-full">
253
- <div>
254
- {#if toggle}
255
- <SlideToggle
256
- name="slider-label"
257
- active="bg-primary-500"
258
- size="sm"
259
- checked={fitToScreen}
260
- id="{tableId}-toggle"
261
- on:change={() => (fitToScreen = !fitToScreen)}>Fit to screen</SlideToggle
262
- >
263
- {/if}
264
- </div>
265
- <div>
266
- {#if resizable !== 'none'}
267
- <button
268
- type="button"
269
- class="btn btn-sm variant-filled-primary rounded-full order-last"
270
- on:click|preventDefault={resetResize}>Reset sizing</button
271
- >
272
- {/if}
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>
273
333
  </div>
274
- </div>
334
+ {/if}
275
335
 
276
336
  <div class="overflow-auto" style="height: {height}px">
277
337
  <table
278
338
  {...$tableAttrs}
279
- class="table table-auto table-compact bg-tertiary-500/30 overflow-clip"
339
+ class="table table-auto table-compact bg-tertiary-500/30 dark:bg-tertiary-900/10 overflow-clip"
280
340
  id="{tableId}-table"
281
341
  >
342
+ <!-- If table height is provided, making the top row sticky -->
282
343
  <thead class=" {height != null ? `sticky top-0` : ''}">
283
- {#each $headerRows as headerRow (headerRow.id)}
284
- <Subscribe
285
- rowAttrs={headerRow.attrs()}
286
- let:rowAttrs
287
- rowProps={headerRow.props()}
288
- let:rowProps
289
- >
290
- <tr {...rowAttrs} class="bg-primary-300 items-stretch">
291
- {#each headerRow.cells as cell (cell.id)}
292
- <Subscribe attrs={cell.attrs()} props={cell.props()} let:props let:attrs>
293
- <th
294
- scope="col"
295
- class="!p-2 overflow-auto"
296
- class:resize-x={(resizable === 'columns' || resizable === 'both') &&
297
- !fixedWidth(cell.id)}
298
- {...attrs}
299
- id="th-{tableId}-{cell.id}"
300
- style={cellStyle(cell.id)}
301
- >
302
- <div class="flex justify-between items-center">
303
- <div class="flex gap-1 whitespace-pre-wrap">
304
- <span
305
- class:underline={props.sort.order}
306
- class:normal-case={cell.id !== cell.label}
307
- class:cursor-pointer={!props.sort.disabled}
308
- on:click={props.sort.toggle}
309
- on:keydown={props.sort.toggle}
310
- >
311
- {cell.render()}
312
- </span>
313
- <div class="w-2">
314
- {#if props.sort.order === 'asc'}
315
-
316
- {:else if props.sort.order === 'desc'}
317
-
318
- {/if}
319
- </div>
320
- </div>
321
- {#if cell.isData()}
322
- {#if props.colFilter?.render}
323
- <div class="">
324
- <Render of={props.colFilter.render} />
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}
325
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}
326
391
  {/if}
327
- {/if}
328
- </div>
329
- </th>
330
- </Subscribe>
331
- {/each}
332
- </tr>
333
- </Subscribe>
392
+ </div>
393
+ </th>
394
+ </Subscribe>
395
+ {/each}
396
+ </tr>
397
+ </Subscribe>
398
+ {/each}
334
399
  {:else}
400
+ <!-- Table is empty -->
335
401
  <p class="items-center justify-center flex w-full p-10 italic">Nothing to show here.</p>
336
- {/each}
402
+ {/if}
337
403
  </thead>
338
404
 
339
405
  <tbody class="overflow-auto" {...$tableBodyAttrs}>
340
- {#each $pageRows as row (row.id)}
341
- <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
342
- <tr {...rowAttrs} id="{tableId}-row-{row.id}" class="">
343
- {#each row.cells as cell, index (cell?.id)}
344
- <Subscribe attrs={cell.attrs()} let:attrs>
345
- <td
346
- {...attrs}
347
- class="!p-2 overflow-auto {index === 0 &&
348
- (resizable === 'rows' || resizable === 'both')
349
- ? 'resize-y'
350
- : ''}"
351
- id="{tableId}-{cell.id}-{row.id}"
352
- >
353
- <div class="flex items-center h-max overflow-x-auto">
354
- <Render of={cell.render()} />
355
- </div>
356
- </td>
357
- </Subscribe>
358
- {/each}
359
- </tr>
360
- </Subscribe>
361
- {/each}
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}
362
434
  </tbody>
363
435
  </table>
364
436
  </div>
365
437
  </div>
366
438
  {#if $data.length > 0}
439
+ <!-- Adding pagination, if table is not empty -->
367
440
  <TablePagination pageConfig={pluginStates.page} {pageSizes} id={tableId} />
368
441
  {/if}
369
442
  </div>
@@ -14,8 +14,10 @@
14
14
  let firstValue;
15
15
  let secondOption;
16
16
  let secondValue;
17
+ // If the filter is applied and the displayed values are filtered
17
18
  let active = false;
18
19
 
20
+ // Options for different types of values
19
21
  const options = {
20
22
  number: [
21
23
  {
@@ -97,8 +99,9 @@
97
99
  ]
98
100
  };
99
101
 
102
+ // Unique ID for the column filter popup
100
103
  const popupId = `${tableId}-${id}`;
101
-
104
+ // Popup config
102
105
  const popupFeatured: PopupSettings = {
103
106
  event: 'click',
104
107
  target: popupId,
@@ -107,7 +110,7 @@
107
110
 
108
111
  let type: string = 'string';
109
112
  let isDate = false;
110
-
113
+ // Check the type of the column
111
114
  $values.forEach((item) => {
112
115
  if (item) {
113
116
  type = typeof (toFilterableValueFn ? toFilterableValueFn(item) : item);
@@ -119,7 +122,7 @@
119
122
  }
120
123
  }
121
124
  });
122
-
125
+ // Determine if the type is date
123
126
  type = isDate ? 'date' : type;
124
127
  </script>
125
128
 
@@ -140,6 +143,7 @@
140
143
  class="btn variant-filled-primary btn-sm"
141
144
  type="button"
142
145
  on:click|preventDefault={() => {
146
+ // Set the defaults when cleared
143
147
  firstOption = 'isequal';
144
148
  firstValue = undefined;
145
149
  secondOption = 'isequal';
package/src/lib/index.ts CHANGED
@@ -39,6 +39,9 @@ import Notification from './components/page/Notification.svelte';
39
39
  //table placeholder
40
40
  import TablePlaceholder from './components/page/TablePlaceholder.svelte';
41
41
 
42
+ // theme
43
+ import { bexis2theme } from './themes/theme-bexis2';
44
+
42
45
  //Form
43
46
  export {
44
47
  Checkbox,
@@ -62,11 +65,6 @@ export { Spinner, Page, Alert, Menu, ErrorMessage };
62
65
  export { Api } from './services/Api.js';
63
66
  export { host, username, password, setApiConfig } from './stores/apiStores.js';
64
67
 
65
- // theme
66
- export { bexis2theme } from './themes/theme-bexis2';
67
-
68
-
69
-
70
68
  //Type
71
69
  export type {
72
70
  userType,
@@ -109,3 +107,6 @@ export { Table, TableFilter, columnFilter, searchFilter };
109
107
  export { CodeEditor };
110
108
 
111
109
  export type { TableConfig, Columns, Column };
110
+
111
+ // theme
112
+ export { bexis2theme };
@@ -80,10 +80,10 @@ export interface fileObjType {
80
80
  }
81
81
 
82
82
  export interface ColumnInstructions {
83
- toStringFn?: (any) => string;
84
- toSortableValueFn?: (any) => string | number;
85
- toFilterableValueFn?: (any) => string | number | Date;
86
- renderComponent?: typeof SvelteComponent;
83
+ toStringFn?: (any) => string; // value by default
84
+ toSortableValueFn?: (any) => string | number; // value by default
85
+ toFilterableValueFn?: (any) => string | number | Date; // value by default
86
+ renderComponent?: typeof SvelteComponent; // null by default
87
87
  }
88
88
 
89
89
  // Table column type
@@ -111,7 +111,9 @@ export interface TableConfig<T> {
111
111
  toggle?: boolean; // false by default
112
112
  fitToScreen?: boolean; // true by default
113
113
  height?: null | number; // null by default
114
+ rowHeight?: number; // auto by default
114
115
  columns?: Columns;
116
+ exportable?: boolean; // false by default
115
117
  pageSizes?: number[]; // [5, 10, 15, 20] by default
116
118
  defaultPageSize?: number; // 10 by default
117
119
  optionsComponent?: typeof SvelteComponent;
@@ -214,4 +214,3 @@ function createNotificationStore() {
214
214
 
215
215
  //crate and export the instance of the store
216
216
  export const notificationStore = createNotificationStore();
217
-
@@ -97,5 +97,18 @@ export const bexis2theme: CustomThemeConfig = {
97
97
  '--color-surface-700': '149 149 149', // #959595
98
98
  '--color-surface-800': '119 119 119', // #777777
99
99
  '--color-surface-900': '98 98 98' // #626262
100
+ },
101
+ properties_dark: {
102
+ // surface | #2e2e2e
103
+ '--color-surface-50': '224 224 224', // #e0e0e0
104
+ '--color-surface-100': '213 213 213', // #d5d5d5
105
+ '--color-surface-200': '203 203 203', // #cbcbcb
106
+ '--color-surface-300': '171 171 171', // #ababab
107
+ '--color-surface-400': '109 109 109', // #6d6d6d
108
+ '--color-surface-500': '46 46 46', // #2e2e2e
109
+ '--color-surface-600': '41 41 41', // #292929
110
+ '--color-surface-700': '35 35 35', // #232323
111
+ '--color-surface-800': '28 28 28', // #1c1c1c
112
+ '--color-surface-900': '23 23 23' // #171717
100
113
  }
101
114
  };