runeforge 0.0.1

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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +551 -0
  3. package/dist/components/Avatar.svelte +31 -0
  4. package/dist/components/Avatar.svelte.d.ts +10 -0
  5. package/dist/components/IconRenderer.svelte +22 -0
  6. package/dist/components/IconRenderer.svelte.d.ts +8 -0
  7. package/dist/components/Modal.svelte +47 -0
  8. package/dist/components/Modal.svelte.d.ts +9 -0
  9. package/dist/components/common/Header.svelte +30 -0
  10. package/dist/components/common/Header.svelte.d.ts +11 -0
  11. package/dist/components/crud/Field.svelte +159 -0
  12. package/dist/components/crud/Field.svelte.d.ts +30 -0
  13. package/dist/components/crud/GenericCRUD.svelte +236 -0
  14. package/dist/components/crud/GenericCRUD.svelte.d.ts +44 -0
  15. package/dist/components/crud/columns/Avatar.svelte +15 -0
  16. package/dist/components/crud/columns/Avatar.svelte.d.ts +8 -0
  17. package/dist/components/crud/columns/Icon.svelte +8 -0
  18. package/dist/components/crud/columns/Icon.svelte.d.ts +4 -0
  19. package/dist/components/crud/utils/constants.d.ts +1 -0
  20. package/dist/components/crud/utils/constants.js +6 -0
  21. package/dist/components/crud/utils/formatters.d.ts +6 -0
  22. package/dist/components/crud/utils/formatters.js +42 -0
  23. package/dist/components/crud/utils/misc.d.ts +5 -0
  24. package/dist/components/crud/utils/misc.js +8 -0
  25. package/dist/components/crud/utils/resolution.d.ts +4 -0
  26. package/dist/components/crud/utils/resolution.js +22 -0
  27. package/dist/components/crud/views/Create.svelte +147 -0
  28. package/dist/components/crud/views/Create.svelte.d.ts +34 -0
  29. package/dist/components/crud/views/List.svelte +170 -0
  30. package/dist/components/crud/views/List.svelte.d.ts +41 -0
  31. package/dist/components/crud/views/Read.svelte +85 -0
  32. package/dist/components/crud/views/Read.svelte.d.ts +33 -0
  33. package/dist/components/crud/views/Update.svelte +148 -0
  34. package/dist/components/crud/views/Update.svelte.d.ts +35 -0
  35. package/dist/components/form/Button.svelte +27 -0
  36. package/dist/components/form/Button.svelte.d.ts +10 -0
  37. package/dist/components/form/Label.svelte +37 -0
  38. package/dist/components/form/Label.svelte.d.ts +14 -0
  39. package/dist/components/form/PasswordInput.svelte +61 -0
  40. package/dist/components/form/PasswordInput.svelte.d.ts +13 -0
  41. package/dist/components/form/Required.svelte +1 -0
  42. package/dist/components/form/Required.svelte.d.ts +26 -0
  43. package/dist/components/form/Select.svelte +114 -0
  44. package/dist/components/form/Select.svelte.d.ts +14 -0
  45. package/dist/components/navigation/Breadcrumbs.svelte +51 -0
  46. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +9 -0
  47. package/dist/components/table/ColumnFilter.svelte +140 -0
  48. package/dist/components/table/ColumnFilter.svelte.d.ts +33 -0
  49. package/dist/components/table/PaginatedTable.svelte +106 -0
  50. package/dist/components/table/PaginatedTable.svelte.d.ts +35 -0
  51. package/dist/components/table/Paginator.svelte +62 -0
  52. package/dist/components/table/Paginator.svelte.d.ts +10 -0
  53. package/dist/components/table/SortHeader.svelte +43 -0
  54. package/dist/components/table/SortHeader.svelte.d.ts +10 -0
  55. package/dist/components/table/TableBody.svelte +70 -0
  56. package/dist/components/table/TableBody.svelte.d.ts +36 -0
  57. package/dist/components/table/TableHeader.svelte +83 -0
  58. package/dist/components/table/TableHeader.svelte.d.ts +39 -0
  59. package/dist/components/table/state.svelte.d.ts +30 -0
  60. package/dist/components/table/state.svelte.js +109 -0
  61. package/dist/components/table/utils.d.ts +7 -0
  62. package/dist/components/table/utils.js +44 -0
  63. package/dist/i18n/context.d.ts +3 -0
  64. package/dist/i18n/context.js +10 -0
  65. package/dist/i18n/en.d.ts +2 -0
  66. package/dist/i18n/en.js +24 -0
  67. package/dist/i18n/es.d.ts +2 -0
  68. package/dist/i18n/es.js +24 -0
  69. package/dist/i18n/types.d.ts +24 -0
  70. package/dist/i18n/types.js +1 -0
  71. package/dist/icons/bootstrapIconSet.d.ts +1 -0
  72. package/dist/icons/bootstrapIconSet.js +1 -0
  73. package/dist/icons/context.d.ts +3 -0
  74. package/dist/icons/context.js +9 -0
  75. package/dist/icons/defaultIconSet.d.ts +1 -0
  76. package/dist/icons/defaultIconSet.js +1 -0
  77. package/dist/icons/defaults/Create.svelte +6 -0
  78. package/dist/icons/defaults/Create.svelte.d.ts +7 -0
  79. package/dist/icons/defaults/Delete.svelte +6 -0
  80. package/dist/icons/defaults/Delete.svelte.d.ts +7 -0
  81. package/dist/icons/defaults/Edit.svelte +7 -0
  82. package/dist/icons/defaults/Edit.svelte.d.ts +7 -0
  83. package/dist/icons/defaults/Filter.svelte +6 -0
  84. package/dist/icons/defaults/Filter.svelte.d.ts +7 -0
  85. package/dist/icons/defaults/FilterActive.svelte +6 -0
  86. package/dist/icons/defaults/FilterActive.svelte.d.ts +7 -0
  87. package/dist/icons/defaults/Folder.svelte +6 -0
  88. package/dist/icons/defaults/Folder.svelte.d.ts +7 -0
  89. package/dist/icons/defaults/Home.svelte +6 -0
  90. package/dist/icons/defaults/Home.svelte.d.ts +7 -0
  91. package/dist/icons/defaults/PasswordHide.svelte +9 -0
  92. package/dist/icons/defaults/PasswordHide.svelte.d.ts +7 -0
  93. package/dist/icons/defaults/PasswordShow.svelte +7 -0
  94. package/dist/icons/defaults/PasswordShow.svelte.d.ts +7 -0
  95. package/dist/icons/defaults/SortAsc.svelte +6 -0
  96. package/dist/icons/defaults/SortAsc.svelte.d.ts +7 -0
  97. package/dist/icons/defaults/SortDesc.svelte +6 -0
  98. package/dist/icons/defaults/SortDesc.svelte.d.ts +7 -0
  99. package/dist/icons/defaults/SortNone.svelte +6 -0
  100. package/dist/icons/defaults/SortNone.svelte.d.ts +7 -0
  101. package/dist/icons/defaults/View.svelte +7 -0
  102. package/dist/icons/defaults/View.svelte.d.ts +7 -0
  103. package/dist/icons/sets/bootstrap.d.ts +2 -0
  104. package/dist/icons/sets/bootstrap.js +29 -0
  105. package/dist/icons/sets/default.d.ts +2 -0
  106. package/dist/icons/sets/default.js +28 -0
  107. package/dist/icons/types.d.ts +26 -0
  108. package/dist/icons/types.js +1 -0
  109. package/dist/index.d.ts +42 -0
  110. package/dist/index.js +41 -0
  111. package/dist/types/attribute.d.ts +38 -0
  112. package/dist/types/attribute.js +11 -0
  113. package/dist/types/breadcrumb.d.ts +7 -0
  114. package/dist/types/breadcrumb.js +1 -0
  115. package/dist/types/crud.d.ts +48 -0
  116. package/dist/types/crud.js +1 -0
  117. package/dist/types/table.d.ts +16 -0
  118. package/dist/types/table.js +1 -0
  119. package/package.json +82 -0
@@ -0,0 +1,106 @@
1
+ <script lang="ts" generics="T extends object = Record<string, unknown>">
2
+ import { SvelteSet } from 'svelte/reactivity';
3
+ import TableBody from './TableBody.svelte';
4
+ import Paginator from './Paginator.svelte';
5
+ import TableHeader from './TableHeader.svelte';
6
+ import { SortState, FilterState } from './state.svelte.js';
7
+ import { distinctEntries } from './utils.js';
8
+ import type { IndexedRow } from '../../types/table.js';
9
+ import type { Snippet } from 'svelte';
10
+ import type { ColumnDefinition } from '../../types/crud.js';
11
+ import { getStrings } from '../../i18n/context.js';
12
+
13
+ const strings = getStrings();
14
+
15
+ let {
16
+ data = [] as T[],
17
+ columns = [] as ColumnDefinition<T>[],
18
+ pageSize = 10,
19
+ selectable = true,
20
+ selected = $bindable(new SvelteSet<number>()),
21
+ rowActions = undefined as Snippet<[T]> | undefined,
22
+ actionsLabel = strings.actions,
23
+ }: {
24
+ data?: T[];
25
+ columns?: ColumnDefinition<T>[];
26
+ pageSize?: number;
27
+ selectable?: boolean;
28
+ selected?: SvelteSet<number>;
29
+ rowActions?: Snippet<[T]>;
30
+ actionsLabel?: string;
31
+ } = $props();
32
+
33
+ const sort = new SortState();
34
+ const filter = new FilterState();
35
+
36
+ let currentPage = $state(1);
37
+
38
+ const distinctValues = $derived(distinctEntries(data, columns));
39
+
40
+ const indexed = $derived(data.map((row, index): IndexedRow<T> => ({ row, index })));
41
+ const filtered = $derived(indexed.filter(({ row }) => filter.matches(row, columns)));
42
+ const sorted = $derived(sort.apply(filtered, columns));
43
+
44
+ const totalPages = $derived(Math.ceil(sorted.length / pageSize));
45
+ const pageStart = $derived((currentPage - 1) * pageSize);
46
+ const pageData = $derived(sorted.slice(pageStart, pageStart + pageSize));
47
+ const allChecked = $derived(pageData.length > 0 && pageData.every((e) => selected.has(e.index)));
48
+ const someChecked = $derived(pageData.some((e) => selected.has(e.index)));
49
+
50
+ $effect(() => {
51
+ if (currentPage > totalPages && totalPages > 0) currentPage = totalPages;
52
+ });
53
+
54
+ function resetPage() {
55
+ currentPage = 1;
56
+ }
57
+
58
+ function toggleAll() {
59
+ if (allChecked) pageData.forEach((e) => selected.delete(e.index));
60
+ else pageData.forEach((e) => selected.add(e.index));
61
+ }
62
+
63
+ function toggleItem(index: number) {
64
+ if (selected.has(index)) selected.delete(index);
65
+ else selected.add(index);
66
+ }
67
+
68
+ const colCount = $derived(columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0));
69
+ </script>
70
+
71
+ <div class="flex flex-col gap-6">
72
+ <div class="min-w-0 overflow-x-auto rounded-box border border-base-content/10">
73
+ <table class="table table-zebra table-xs w-full text-xs sm:table-md sm:text-base">
74
+ <TableHeader
75
+ {columns}
76
+ {selectable}
77
+ {allChecked}
78
+ {someChecked}
79
+ onToggleAll={toggleAll}
80
+ {sort}
81
+ {filter}
82
+ {distinctValues}
83
+ hasRowActions={!!rowActions}
84
+ {actionsLabel}
85
+ onchange={resetPage}
86
+ />
87
+ <TableBody
88
+ {columns}
89
+ rows={pageData}
90
+ {selectable}
91
+ {selected}
92
+ onToggle={toggleItem}
93
+ {colCount}
94
+ {rowActions}
95
+ />
96
+ </table>
97
+ </div>
98
+
99
+ <Paginator
100
+ bind:page={currentPage}
101
+ {totalPages}
102
+ {pageStart}
103
+ {pageSize}
104
+ total={sorted.length}
105
+ />
106
+ </div>
@@ -0,0 +1,35 @@
1
+ import { SvelteSet } from 'svelte/reactivity';
2
+ import type { Snippet } from 'svelte';
3
+ import type { ColumnDefinition } from '../../types/crud.js';
4
+ declare function $$render<T extends object = Record<string, unknown>>(): {
5
+ props: {
6
+ data?: T[];
7
+ columns?: ColumnDefinition<T>[];
8
+ pageSize?: number;
9
+ selectable?: boolean;
10
+ selected?: SvelteSet<number>;
11
+ rowActions?: Snippet<[T]>;
12
+ actionsLabel?: string;
13
+ };
14
+ exports: {};
15
+ bindings: "selected";
16
+ slots: {};
17
+ events: {};
18
+ };
19
+ declare class __sveltets_Render<T extends object = Record<string, unknown>> {
20
+ props(): ReturnType<typeof $$render<T>>['props'];
21
+ events(): ReturnType<typeof $$render<T>>['events'];
22
+ slots(): ReturnType<typeof $$render<T>>['slots'];
23
+ bindings(): "selected";
24
+ exports(): {};
25
+ }
26
+ interface $$IsomorphicComponent {
27
+ new <T extends object = Record<string, unknown>>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
28
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
29
+ } & ReturnType<__sveltets_Render<T>['exports']>;
30
+ <T extends object = Record<string, unknown>>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
31
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
32
+ }
33
+ declare const PaginatedTable: $$IsomorphicComponent;
34
+ type PaginatedTable<T extends object = Record<string, unknown>> = InstanceType<typeof PaginatedTable<T>>;
35
+ export default PaginatedTable;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import Button from '../form/Button.svelte';
3
+ import { getStrings } from '../../i18n/context.js';
4
+
5
+ const strings = getStrings();
6
+
7
+ let {
8
+ page = $bindable(1),
9
+ totalPages,
10
+ pageStart,
11
+ pageSize,
12
+ total,
13
+ }: {
14
+ page?: number;
15
+ totalPages: number;
16
+ pageStart: number;
17
+ pageSize: number;
18
+ total: number;
19
+ } = $props();
20
+
21
+ function prev() {
22
+ if (page > 1) page--;
23
+ }
24
+ function next() {
25
+ if (page < totalPages) page++;
26
+ }
27
+ </script>
28
+
29
+ {#if totalPages > 1}
30
+ <div class="flex items-center justify-between">
31
+ <span class="text-sm text-base-content/60">
32
+ {strings.showing(pageStart + 1, Math.min(pageStart + pageSize, total), total)}
33
+ </span>
34
+
35
+ <div class="join">
36
+ <Button
37
+ class="join-item btn-sm"
38
+ disabled={page === 1}
39
+ onclick={prev}
40
+ >
41
+ «
42
+ </Button>
43
+
44
+ {#each Array.from({ length: totalPages }, (_, i) => i + 1) as n (n)}
45
+ <Button
46
+ class={['join-item btn-sm', n === page && 'btn-active']}
47
+ onclick={() => (page = n)}
48
+ >
49
+ {n}
50
+ </Button>
51
+ {/each}
52
+
53
+ <Button
54
+ class="join-item btn-sm"
55
+ disabled={page === totalPages}
56
+ onclick={next}
57
+ >
58
+ »
59
+ </Button>
60
+ </div>
61
+ </div>
62
+ {/if}
@@ -0,0 +1,10 @@
1
+ type $$ComponentProps = {
2
+ page?: number;
3
+ totalPages: number;
4
+ pageStart: number;
5
+ pageSize: number;
6
+ total: number;
7
+ };
8
+ declare const Paginator: import("svelte").Component<$$ComponentProps, {}, "page">;
9
+ type Paginator = ReturnType<typeof Paginator>;
10
+ export default Paginator;
@@ -0,0 +1,43 @@
1
+ <script lang="ts">
2
+ import Button from '../form/Button.svelte';
3
+ import { getIconSet } from '../../icons/context.js';
4
+ import { defaultIconSet } from '../../icons/sets/default.js';
5
+ import type { SortDirection } from '../../types/table.js';
6
+
7
+ let {
8
+ title,
9
+ sortable = true,
10
+ direction = null,
11
+ onsort,
12
+ }: {
13
+ title: string;
14
+ sortable?: boolean;
15
+ direction?: SortDirection | null;
16
+ onsort?: () => void;
17
+ } = $props();
18
+
19
+ const icons = $derived(getIconSet() ?? defaultIconSet);
20
+ </script>
21
+
22
+ {#if sortable}
23
+ <Button
24
+ btn={false}
25
+ class="flex cursor-pointer items-center gap-1 capitalize hover:text-primary"
26
+ onclick={onsort}
27
+ title="Ordenar"
28
+ >
29
+ <span>{title}</span>
30
+ {#if direction === 'desc'}
31
+ {@const Icon = icons.sortDesc}
32
+ <Icon class="size-3" />
33
+ {:else if direction === 'asc'}
34
+ {@const Icon = icons.sortAsc}
35
+ <Icon class="size-3" />
36
+ {:else}
37
+ {@const Icon = icons.sortNone}
38
+ <Icon class="size-3 opacity-30" />
39
+ {/if}
40
+ </Button>
41
+ {:else}
42
+ <span class="capitalize">{title}</span>
43
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { SortDirection } from '../../types/table.js';
2
+ type $$ComponentProps = {
3
+ title: string;
4
+ sortable?: boolean;
5
+ direction?: SortDirection | null;
6
+ onsort?: () => void;
7
+ };
8
+ declare const SortHeader: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type SortHeader = ReturnType<typeof SortHeader>;
10
+ export default SortHeader;
@@ -0,0 +1,70 @@
1
+ <script lang="ts" generics="T extends object">
2
+ import type { Snippet } from 'svelte';
3
+ import type { SvelteSet } from 'svelte/reactivity';
4
+ import type { ColumnDefinition } from '../../types/crud.js';
5
+ import type { IndexedRow } from '../../types/table.js';
6
+
7
+ let {
8
+ columns,
9
+ rows,
10
+ selectable,
11
+ selected,
12
+ onToggle,
13
+ colCount,
14
+ rowActions,
15
+ }: {
16
+ columns: ColumnDefinition<T>[];
17
+ rows: IndexedRow<T>[];
18
+ selectable: boolean;
19
+ selected: SvelteSet<number>;
20
+ onToggle: (index: number) => void;
21
+ colCount: number;
22
+ rowActions?: Snippet<[T]>;
23
+ } = $props();
24
+ </script>
25
+
26
+ <tbody>
27
+ {#if rows.length === 0}
28
+ <tr>
29
+ <td colspan={colCount} class="py-10 text-center text-base-content/50">
30
+ Sin registros
31
+ </td>
32
+ </tr>
33
+ {:else}
34
+ {#each rows as { row, index } (index)}
35
+ <tr
36
+ class="hover"
37
+ class:cursor-pointer={selectable}
38
+ onclick={selectable ? () => onToggle(index) : undefined}
39
+ >
40
+ {#if selectable}
41
+ <td>
42
+ <input
43
+ type="checkbox"
44
+ class="checkbox checkbox-sm"
45
+ checked={selected.has(index)}
46
+ onchange={() => onToggle(index)}
47
+ onclick={(e) => e.stopPropagation()}
48
+ />
49
+ </td>
50
+ {/if}
51
+ {#each columns as col (col.attribute)}
52
+ <td>
53
+ {#if col.component}
54
+ {@const Cell = col.component}
55
+ <Cell value={row[col.attribute]} row={row} />
56
+ {:else if col.formatter}
57
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
58
+ {@html col.formatter(row[col.attribute], row)}
59
+ {:else}
60
+ {String(row[col.attribute] ?? '')}
61
+ {/if}
62
+ </td>
63
+ {/each}
64
+ {#if rowActions}
65
+ <td class="text-right">{@render rowActions(row)}</td>
66
+ {/if}
67
+ </tr>
68
+ {/each}
69
+ {/if}
70
+ </tbody>
@@ -0,0 +1,36 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { SvelteSet } from 'svelte/reactivity';
3
+ import type { ColumnDefinition } from '../../types/crud.js';
4
+ import type { IndexedRow } from '../../types/table.js';
5
+ declare function $$render<T extends object>(): {
6
+ props: {
7
+ columns: ColumnDefinition<T>[];
8
+ rows: IndexedRow<T>[];
9
+ selectable: boolean;
10
+ selected: SvelteSet<number>;
11
+ onToggle: (index: number) => void;
12
+ colCount: number;
13
+ rowActions?: Snippet<[T]>;
14
+ };
15
+ exports: {};
16
+ bindings: "";
17
+ slots: {};
18
+ events: {};
19
+ };
20
+ declare class __sveltets_Render<T extends object> {
21
+ props(): ReturnType<typeof $$render<T>>['props'];
22
+ events(): ReturnType<typeof $$render<T>>['events'];
23
+ slots(): ReturnType<typeof $$render<T>>['slots'];
24
+ bindings(): "";
25
+ exports(): {};
26
+ }
27
+ interface $$IsomorphicComponent {
28
+ new <T extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
29
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
30
+ } & ReturnType<__sveltets_Render<T>['exports']>;
31
+ <T extends object>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
32
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
33
+ }
34
+ declare const TableBody: $$IsomorphicComponent;
35
+ type TableBody<T extends object> = InstanceType<typeof TableBody<T>>;
36
+ export default TableBody;
@@ -0,0 +1,83 @@
1
+ <script lang="ts" generics="T extends object">
2
+ import SortHeader from './SortHeader.svelte';
3
+ import ColumnFilter from './ColumnFilter.svelte';
4
+ import { isSortable, isFilterable } from './utils.js';
5
+ import type { DistinctEntry } from '../../types/table.js';
6
+ import type { ColumnDefinition } from '../../types/crud.js';
7
+ import type { SortState, FilterState } from './state.svelte.js';
8
+ import { getStrings } from '../../i18n/context.js';
9
+
10
+ const strings = getStrings();
11
+
12
+ let {
13
+ columns,
14
+ selectable,
15
+ allChecked,
16
+ someChecked,
17
+ onToggleAll,
18
+ sort,
19
+ filter,
20
+ distinctValues,
21
+ hasRowActions = false,
22
+ actionsLabel = strings.actions,
23
+ onchange,
24
+ }: {
25
+ columns: ColumnDefinition<T>[];
26
+ selectable: boolean;
27
+ allChecked: boolean;
28
+ someChecked: boolean;
29
+ onToggleAll: () => void;
30
+ sort: SortState;
31
+ filter: FilterState;
32
+ distinctValues: Record<string, DistinctEntry<T>[]>;
33
+ hasRowActions?: boolean;
34
+ actionsLabel?: string;
35
+ onchange?: () => void;
36
+ } = $props();
37
+
38
+ function sortBy(col: ColumnDefinition<T>) {
39
+ if (!isSortable(col)) return;
40
+ sort.cycle(col.attribute);
41
+ onchange?.();
42
+ }
43
+ </script>
44
+
45
+ <thead class="bg-base-200">
46
+ <tr>
47
+ {#if selectable}
48
+ <th>
49
+ <input
50
+ type="checkbox"
51
+ class="checkbox checkbox-sm"
52
+ checked={allChecked}
53
+ indeterminate={someChecked && !allChecked}
54
+ onchange={onToggleAll}
55
+ />
56
+ </th>
57
+ {/if}
58
+ {#each columns as col (col.attribute)}
59
+ {@const title = (col.title ?? col.attribute).replace(/_/g, ' ')}
60
+ <th>
61
+ <div class="flex items-center gap-1">
62
+ <SortHeader
63
+ {title}
64
+ sortable={isSortable(col)}
65
+ direction={sort.directionFor(col.attribute)}
66
+ onsort={() => sortBy(col)}
67
+ />
68
+ {#if isFilterable(col)}
69
+ <ColumnFilter
70
+ column={col}
71
+ entries={distinctValues[col.attribute] ?? []}
72
+ {filter}
73
+ {onchange}
74
+ />
75
+ {/if}
76
+ </div>
77
+ </th>
78
+ {/each}
79
+ {#if hasRowActions}
80
+ <th class="text-right">{actionsLabel}</th>
81
+ {/if}
82
+ </tr>
83
+ </thead>
@@ -0,0 +1,39 @@
1
+ import type { DistinctEntry } from '../../types/table.js';
2
+ import type { ColumnDefinition } from '../../types/crud.js';
3
+ import type { SortState, FilterState } from './state.svelte.js';
4
+ declare function $$render<T extends object>(): {
5
+ props: {
6
+ columns: ColumnDefinition<T>[];
7
+ selectable: boolean;
8
+ allChecked: boolean;
9
+ someChecked: boolean;
10
+ onToggleAll: () => void;
11
+ sort: SortState;
12
+ filter: FilterState;
13
+ distinctValues: Record<string, DistinctEntry<T>[]>;
14
+ hasRowActions?: boolean;
15
+ actionsLabel?: string;
16
+ onchange?: () => void;
17
+ };
18
+ exports: {};
19
+ bindings: "";
20
+ slots: {};
21
+ events: {};
22
+ };
23
+ declare class __sveltets_Render<T extends object> {
24
+ props(): ReturnType<typeof $$render<T>>['props'];
25
+ events(): ReturnType<typeof $$render<T>>['events'];
26
+ slots(): ReturnType<typeof $$render<T>>['slots'];
27
+ bindings(): "";
28
+ exports(): {};
29
+ }
30
+ interface $$IsomorphicComponent {
31
+ new <T extends object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
32
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
33
+ } & ReturnType<__sveltets_Render<T>['exports']>;
34
+ <T extends object>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
35
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
36
+ }
37
+ declare const TableHeader: $$IsomorphicComponent;
38
+ type TableHeader<T extends object> = InstanceType<typeof TableHeader<T>>;
39
+ export default TableHeader;
@@ -0,0 +1,30 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
2
+ import type { ColumnDefinition } from '../../types/crud.js';
3
+ import type { IndexedRow, SortDirection } from '../../types/table.js';
4
+ export declare class SortState {
5
+ column: string | null;
6
+ direction: SortDirection | null;
7
+ directionFor(attribute: string): SortDirection | null;
8
+ cycle(attribute: string): void;
9
+ apply<T extends object>(rows: IndexedRow<T>[], columns: ColumnDefinition<T>[]): IndexedRow<T>[];
10
+ }
11
+ export declare class FilterState {
12
+ text: SvelteMap<string, string>;
13
+ values: SvelteMap<string, SvelteSet<string>>;
14
+ dateRanges: SvelteMap<string, {
15
+ from: string;
16
+ to: string;
17
+ }>;
18
+ textFor(attribute: string): string;
19
+ dateRangeFor(attribute: string): {
20
+ from: string;
21
+ to: string;
22
+ };
23
+ hasActive(attribute: string): boolean;
24
+ isChecked(attribute: string, value: string): boolean;
25
+ setText(attribute: string, value: string): void;
26
+ toggleValue(attribute: string, value: string): void;
27
+ setDateRange(attribute: string, from: string, to: string): void;
28
+ clear(attribute: string): void;
29
+ matches<T extends object>(row: T, columns: ColumnDefinition<T>[]): boolean;
30
+ }
@@ -0,0 +1,109 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
2
+ import { cellRenderedText, compare, isFilterable } from './utils.js';
3
+ export class SortState {
4
+ column = $state(null);
5
+ direction = $state(null);
6
+ directionFor(attribute) {
7
+ return this.column === attribute ? this.direction : null;
8
+ }
9
+ cycle(attribute) {
10
+ if (this.column !== attribute) {
11
+ this.column = attribute;
12
+ this.direction = 'desc';
13
+ }
14
+ else if (this.direction === 'desc') {
15
+ this.direction = 'asc';
16
+ }
17
+ else {
18
+ this.column = null;
19
+ this.direction = null;
20
+ }
21
+ }
22
+ apply(rows, columns) {
23
+ const attribute = this.column;
24
+ const direction = this.direction;
25
+ if (!attribute || !direction)
26
+ return rows;
27
+ const col = columns.find((c) => c.attribute === attribute);
28
+ const factor = direction === 'asc' ? 1 : -1;
29
+ if (col?.formatter) {
30
+ return [...rows].sort((a, b) => factor * cellRenderedText(a.row, col).localeCompare(cellRenderedText(b.row, col), undefined, { numeric: true }));
31
+ }
32
+ return [...rows].sort((a, b) => factor * compare(a.row[attribute], b.row[attribute]));
33
+ }
34
+ }
35
+ export class FilterState {
36
+ text = new SvelteMap();
37
+ values = new SvelteMap();
38
+ dateRanges = new SvelteMap();
39
+ textFor(attribute) {
40
+ return this.text.get(attribute) ?? '';
41
+ }
42
+ dateRangeFor(attribute) {
43
+ return this.dateRanges.get(attribute) ?? { from: '', to: '' };
44
+ }
45
+ hasActive(attribute) {
46
+ return (this.text.get(attribute)?.length ?? 0) > 0
47
+ || (this.values.get(attribute)?.size ?? 0) > 0
48
+ || !!(this.dateRanges.get(attribute)?.from || this.dateRanges.get(attribute)?.to);
49
+ }
50
+ isChecked(attribute, value) {
51
+ return this.values.get(attribute)?.has(value) ?? false;
52
+ }
53
+ setText(attribute, value) {
54
+ if (value)
55
+ this.text.set(attribute, value);
56
+ else
57
+ this.text.delete(attribute);
58
+ }
59
+ toggleValue(attribute, value) {
60
+ let set = this.values.get(attribute);
61
+ if (!set) {
62
+ set = new SvelteSet();
63
+ this.values.set(attribute, set);
64
+ }
65
+ if (set.has(value))
66
+ set.delete(value);
67
+ else
68
+ set.add(value);
69
+ }
70
+ setDateRange(attribute, from, to) {
71
+ if (from || to)
72
+ this.dateRanges.set(attribute, { from, to });
73
+ else
74
+ this.dateRanges.delete(attribute);
75
+ }
76
+ clear(attribute) {
77
+ this.text.delete(attribute);
78
+ this.values.get(attribute)?.clear();
79
+ this.dateRanges.delete(attribute);
80
+ }
81
+ matches(row, columns) {
82
+ return columns.every((col) => {
83
+ if (!isFilterable(col))
84
+ return true;
85
+ if (col.type === 'datetime') {
86
+ const range = this.dateRanges.get(col.attribute);
87
+ if (range?.from || range?.to) {
88
+ const raw = row[col.attribute];
89
+ const ts = raw ? new Date(raw).getTime() : NaN;
90
+ if (isNaN(ts))
91
+ return false;
92
+ if (range.from && ts < new Date(range.from).getTime())
93
+ return false;
94
+ if (range.to && ts > new Date(range.to + 'T23:59:59.999').getTime())
95
+ return false;
96
+ }
97
+ return true;
98
+ }
99
+ const cell = cellRenderedText(row, col);
100
+ const text = this.text.get(col.attribute);
101
+ if (text && !cell.toLowerCase().includes(text.toLowerCase()))
102
+ return false;
103
+ const selected = this.values.get(col.attribute);
104
+ if (selected && selected.size > 0 && !selected.has(cell))
105
+ return false;
106
+ return true;
107
+ });
108
+ }
109
+ }
@@ -0,0 +1,7 @@
1
+ import type { ColumnDefinition } from '../../types/crud.js';
2
+ import type { DistinctEntry } from '../../types/table.js';
3
+ export declare function cellRenderedText<T extends object>(row: T, col: ColumnDefinition<T>): string;
4
+ export declare function isSortable<T extends object>(col: ColumnDefinition<T>): boolean;
5
+ export declare function isFilterable<T extends object>(col: ColumnDefinition<T>): boolean;
6
+ export declare function compare(a: unknown, b: unknown): number;
7
+ export declare function distinctEntries<T extends object>(data: T[], columns: ColumnDefinition<T>[]): Record<string, DistinctEntry<T>[]>;