runeforge 0.0.53 → 0.0.55

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 (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1342 -1222
  3. package/dist/components/Avatar.svelte +31 -31
  4. package/dist/components/IconRenderer.svelte +22 -22
  5. package/dist/components/Modal.svelte +75 -75
  6. package/dist/components/common/Header.svelte +40 -40
  7. package/dist/components/crud/EmbeddedField.svelte +175 -175
  8. package/dist/components/crud/Field.svelte +376 -376
  9. package/dist/components/crud/GenericCRUD.svelte +426 -435
  10. package/dist/components/crud/GenericCRUD.svelte.d.ts +6 -9
  11. package/dist/components/crud/SearchInput.svelte +53 -53
  12. package/dist/components/crud/columns/Avatar.svelte +15 -15
  13. package/dist/components/crud/columns/Icon.svelte +8 -8
  14. package/dist/components/crud/utils/reorder.d.ts +4 -0
  15. package/dist/components/crud/utils/reorder.js +13 -0
  16. package/dist/components/crud/views/Create.svelte +232 -232
  17. package/dist/components/crud/views/Read.svelte +124 -124
  18. package/dist/components/crud/views/Update.svelte +208 -208
  19. package/dist/components/crud/views/list/List.svelte +291 -0
  20. package/dist/components/crud/views/{List.svelte.d.ts → list/List.svelte.d.ts} +4 -14
  21. package/dist/components/crud/views/list/Modals.svelte +56 -0
  22. package/dist/components/crud/views/list/Modals.svelte.d.ts +36 -0
  23. package/dist/components/crud/views/list/Table.svelte +176 -0
  24. package/dist/components/crud/views/list/Table.svelte.d.ts +59 -0
  25. package/dist/components/crud/views/list/Toolbar.svelte +341 -0
  26. package/dist/components/crud/views/list/Toolbar.svelte.d.ts +46 -0
  27. package/dist/components/form/Button.svelte +27 -27
  28. package/dist/components/form/Label.svelte +37 -37
  29. package/dist/components/form/MultiSelect.svelte +248 -248
  30. package/dist/components/form/PasswordInput.svelte +68 -68
  31. package/dist/components/form/Required.svelte +1 -1
  32. package/dist/components/form/Select.svelte +209 -209
  33. package/dist/components/form/Tree.svelte +62 -62
  34. package/dist/components/form/TreeNode.svelte +66 -66
  35. package/dist/components/navigation/Breadcrumbs.svelte +111 -111
  36. package/dist/components/table/ColumnFilter.svelte +168 -168
  37. package/dist/components/table/PaginatedTable.svelte +536 -215
  38. package/dist/components/table/PaginatedTable.svelte.d.ts +11 -2
  39. package/dist/components/table/Paginator.svelte +113 -113
  40. package/dist/components/table/SortHeader.svelte +43 -43
  41. package/dist/components/table/TableBody.svelte +150 -70
  42. package/dist/components/table/TableBody.svelte.d.ts +14 -1
  43. package/dist/components/table/TableHeader.svelte +88 -83
  44. package/dist/components/table/TableHeader.svelte.d.ts +1 -0
  45. package/dist/components/table/sortable.d.ts +27 -0
  46. package/dist/components/table/sortable.js +55 -0
  47. package/dist/components/table/utils.d.ts +28 -1
  48. package/dist/components/table/utils.js +75 -0
  49. package/dist/i18n/en.js +2 -0
  50. package/dist/i18n/es.js +2 -0
  51. package/dist/i18n/types.d.ts +2 -0
  52. package/dist/icons/defaults/Clear.svelte +6 -6
  53. package/dist/icons/defaults/Create.svelte +6 -6
  54. package/dist/icons/defaults/Delete.svelte +6 -6
  55. package/dist/icons/defaults/Download.svelte +7 -7
  56. package/dist/icons/defaults/Edit.svelte +7 -7
  57. package/dist/icons/defaults/Filter.svelte +6 -6
  58. package/dist/icons/defaults/FilterActive.svelte +6 -6
  59. package/dist/icons/defaults/Folder.svelte +6 -6
  60. package/dist/icons/defaults/Grip.svelte +7 -0
  61. package/dist/icons/defaults/Grip.svelte.d.ts +7 -0
  62. package/dist/icons/defaults/Home.svelte +6 -6
  63. package/dist/icons/defaults/PasswordHide.svelte +9 -9
  64. package/dist/icons/defaults/PasswordShow.svelte +7 -7
  65. package/dist/icons/defaults/SortAsc.svelte +6 -6
  66. package/dist/icons/defaults/SortDesc.svelte +6 -6
  67. package/dist/icons/defaults/SortNone.svelte +6 -6
  68. package/dist/icons/defaults/View.svelte +7 -7
  69. package/dist/icons/sets/bootstrap.js +2 -1
  70. package/dist/icons/sets/default.js +2 -0
  71. package/dist/icons/types.d.ts +3 -0
  72. package/dist/index.d.ts +5 -4
  73. package/dist/index.js +3 -2
  74. package/dist/types/attribute.d.ts +9 -0
  75. package/dist/types/crud.d.ts +72 -1
  76. package/dist/types/table.d.ts +56 -0
  77. package/package.json +3 -1
  78. package/dist/components/crud/views/List.svelte +0 -584
@@ -1,111 +1,111 @@
1
- <script lang="ts">
2
- import { getIconSet } from '../../icons/context.js';
3
- import { defaultIconSet } from '../../icons/sets/default.js';
4
- import { getConfig } from '../../config/context.js';
5
- import type { BreadcrumbItem } from '../../types/breadcrumb.js';
6
-
7
- let {
8
- items = [],
9
- homeHref,
10
- }: {
11
- items?: BreadcrumbItem[];
12
- homeHref?: string;
13
- } = $props();
14
-
15
- const icons = $derived(getIconSet() ?? defaultIconSet);
16
-
17
- const home: BreadcrumbItem = $derived({
18
- label: 'Inicio',
19
- icon: icons.home,
20
- link: { href: homeHref ?? getConfig().homeHref ?? '/' },
21
- });
22
-
23
- const allItems = $derived([home, ...items]);
24
- </script>
25
-
26
- <div class="breadcrumbs">
27
- <ul>
28
- {#each allItems as item, i (i)}
29
- <li>
30
- {#if item.link !== undefined}
31
- <a class="inline-flex items-center gap-2" {...item.link}>
32
- {#if item.icon}
33
- {@const ItemIcon = item.icon}
34
- <ItemIcon />
35
- {/if}
36
- <span>{item.label}</span>
37
- </a>
38
- {:else}
39
- <span class="inline-flex items-center gap-2">
40
- {#if item.icon}
41
- {@const ItemIcon = item.icon}
42
- <ItemIcon />
43
- {/if}
44
- <span>{item.label}</span>
45
- </span>
46
- {/if}
47
- </li>
48
- {/each}
49
- </ul>
50
- </div>
51
-
52
- <style>
53
- div {
54
- font-size: var(--runeforge-breadcrumb-font-size, 0.875rem);
55
- }
56
-
57
- :global(.breadcrumbs svg) {
58
- width: var(--runeforge-breadcrumb-icon-size, 1rem);
59
- height: var(--runeforge-breadcrumb-icon-size, 1rem);
60
- flex-shrink: 0;
61
- }
62
-
63
- /*
64
- * DaisyUI breadcrumbs use @layer rules that Tailwind v4 doesn't propagate
65
- * from node_modules. These unlayered rules override that gap.
66
- */
67
- :global(.breadcrumbs > ul),
68
- :global(.breadcrumbs > ol),
69
- :global(.breadcrumbs > menu) {
70
- display: flex;
71
- align-items: center;
72
- white-space: nowrap;
73
- overflow-x: auto;
74
- }
75
-
76
- :global(.breadcrumbs > ul > li),
77
- :global(.breadcrumbs > ol > li),
78
- :global(.breadcrumbs > menu > li) {
79
- display: flex;
80
- align-items: center;
81
- }
82
-
83
- :global(.breadcrumbs > ul > li + li::before),
84
- :global(.breadcrumbs > ol > li + li::before),
85
- :global(.breadcrumbs > menu > li + li::before) {
86
- content: '';
87
- display: block;
88
- opacity: 0.4;
89
- border-top: 1px solid;
90
- border-right: 1px solid;
91
- width: 0.375rem;
92
- height: 0.375rem;
93
- margin-inline: 0.5rem 0.75rem;
94
- rotate: 45deg;
95
- }
96
-
97
- /*
98
- * Tailwind preflight sets svg { display: block }, which stacks icons above
99
- * labels when the <a>/<span> inline-flex class isn't generated from node_modules.
100
- */
101
- :global(.breadcrumbs > ul > li > a),
102
- :global(.breadcrumbs > ul > li > span),
103
- :global(.breadcrumbs > ol > li > a),
104
- :global(.breadcrumbs > ol > li > span),
105
- :global(.breadcrumbs > menu > li > a),
106
- :global(.breadcrumbs > menu > li > span) {
107
- display: inline-flex;
108
- align-items: center;
109
- gap: 0.5rem;
110
- }
111
- </style>
1
+ <script lang="ts">
2
+ import { getIconSet } from '../../icons/context.js';
3
+ import { defaultIconSet } from '../../icons/sets/default.js';
4
+ import { getConfig } from '../../config/context.js';
5
+ import type { BreadcrumbItem } from '../../types/breadcrumb.js';
6
+
7
+ let {
8
+ items = [],
9
+ homeHref,
10
+ }: {
11
+ items?: BreadcrumbItem[];
12
+ homeHref?: string;
13
+ } = $props();
14
+
15
+ const icons = $derived(getIconSet() ?? defaultIconSet);
16
+
17
+ const home: BreadcrumbItem = $derived({
18
+ label: 'Inicio',
19
+ icon: icons.home,
20
+ link: { href: homeHref ?? getConfig().homeHref ?? '/' },
21
+ });
22
+
23
+ const allItems = $derived([home, ...items]);
24
+ </script>
25
+
26
+ <div class="breadcrumbs">
27
+ <ul>
28
+ {#each allItems as item, i (i)}
29
+ <li>
30
+ {#if item.link !== undefined}
31
+ <a class="inline-flex items-center gap-2" {...item.link}>
32
+ {#if item.icon}
33
+ {@const ItemIcon = item.icon}
34
+ <ItemIcon />
35
+ {/if}
36
+ <span>{item.label}</span>
37
+ </a>
38
+ {:else}
39
+ <span class="inline-flex items-center gap-2">
40
+ {#if item.icon}
41
+ {@const ItemIcon = item.icon}
42
+ <ItemIcon />
43
+ {/if}
44
+ <span>{item.label}</span>
45
+ </span>
46
+ {/if}
47
+ </li>
48
+ {/each}
49
+ </ul>
50
+ </div>
51
+
52
+ <style>
53
+ div {
54
+ font-size: var(--runeforge-breadcrumb-font-size, 0.875rem);
55
+ }
56
+
57
+ :global(.breadcrumbs svg) {
58
+ width: var(--runeforge-breadcrumb-icon-size, 1rem);
59
+ height: var(--runeforge-breadcrumb-icon-size, 1rem);
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ /*
64
+ * DaisyUI breadcrumbs use @layer rules that Tailwind v4 doesn't propagate
65
+ * from node_modules. These unlayered rules override that gap.
66
+ */
67
+ :global(.breadcrumbs > ul),
68
+ :global(.breadcrumbs > ol),
69
+ :global(.breadcrumbs > menu) {
70
+ display: flex;
71
+ align-items: center;
72
+ white-space: nowrap;
73
+ overflow-x: auto;
74
+ }
75
+
76
+ :global(.breadcrumbs > ul > li),
77
+ :global(.breadcrumbs > ol > li),
78
+ :global(.breadcrumbs > menu > li) {
79
+ display: flex;
80
+ align-items: center;
81
+ }
82
+
83
+ :global(.breadcrumbs > ul > li + li::before),
84
+ :global(.breadcrumbs > ol > li + li::before),
85
+ :global(.breadcrumbs > menu > li + li::before) {
86
+ content: '';
87
+ display: block;
88
+ opacity: 0.4;
89
+ border-top: 1px solid;
90
+ border-right: 1px solid;
91
+ width: 0.375rem;
92
+ height: 0.375rem;
93
+ margin-inline: 0.5rem 0.75rem;
94
+ rotate: 45deg;
95
+ }
96
+
97
+ /*
98
+ * Tailwind preflight sets svg { display: block }, which stacks icons above
99
+ * labels when the <a>/<span> inline-flex class isn't generated from node_modules.
100
+ */
101
+ :global(.breadcrumbs > ul > li > a),
102
+ :global(.breadcrumbs > ul > li > span),
103
+ :global(.breadcrumbs > ol > li > a),
104
+ :global(.breadcrumbs > ol > li > span),
105
+ :global(.breadcrumbs > menu > li > a),
106
+ :global(.breadcrumbs > menu > li > span) {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ gap: 0.5rem;
110
+ }
111
+ </style>
@@ -1,168 +1,168 @@
1
- <script lang="ts" generics="T extends object">
2
- import { onMount } from 'svelte';
3
- import Label from '../form/Label.svelte';
4
- import Button from '../form/Button.svelte';
5
- import { getIconSet } from '../../icons/context.js';
6
- import { defaultIconSet } from '../../icons/sets/default.js';
7
- import { getStrings } from '../../i18n/context.js';
8
-
9
- const strings = getStrings();
10
-
11
- onMount(() => {
12
- import('cally');
13
- });
14
- import type { DistinctEntry } from '../../types/table.js';
15
- import type { FilterState } from './state.svelte.js';
16
- import type { ColumnDefinition } from '../../types/crud.js';
17
-
18
- let {
19
- column,
20
- entries = [],
21
- filter,
22
- maxCheckboxValues = 20,
23
- onchange
24
- }: {
25
- column: ColumnDefinition<T>;
26
- entries?: DistinctEntry<T>[];
27
- filter: FilterState;
28
- maxCheckboxValues?: number;
29
- onchange?: () => void;
30
- } = $props();
31
-
32
- const icons = $derived(getIconSet() ?? defaultIconSet);
33
- const popId = $derived(`filter-pop-${column.attribute}`);
34
- const anchor = $derived(`--filter-anchor-${column.attribute}`);
35
- const isDatetime = $derived(column.type === 'datetime');
36
-
37
- const dateRange = $derived(filter.dateRangeFor(column.attribute));
38
- const calendarValue = $derived(
39
- dateRange.from || dateRange.to ? `${dateRange.from}/${dateRange.to}` : ''
40
- );
41
-
42
- let calendarEl: HTMLElement | undefined = $state();
43
-
44
- $effect(() => {
45
- if (!calendarEl) return;
46
- function handler() {
47
- const value = (calendarEl as HTMLElement & { value: string }).value ?? '';
48
- const [from = '', to = ''] = value.split('/');
49
- filter.setDateRange(column.attribute, from, to);
50
- onchange?.();
51
- }
52
- calendarEl.addEventListener('change', handler);
53
- return () => calendarEl?.removeEventListener('change', handler);
54
- });
55
-
56
- let debounceTimer: ReturnType<typeof setTimeout>;
57
- function setText(value: string) {
58
- filter.setText(column.attribute, value);
59
- clearTimeout(debounceTimer);
60
- debounceTimer = setTimeout(() => onchange?.(), 300);
61
- }
62
- function toggle(value: string) {
63
- filter.toggleValue(column.attribute, value);
64
- onchange?.();
65
- }
66
- function clear() {
67
- filter.clear(column.attribute);
68
- onchange?.();
69
- }
70
- </script>
71
-
72
- <Button
73
- variant="ghost"
74
- class={['btn-xs btn-square', filter.hasActive(column.attribute) && 'text-primary']}
75
- popovertarget={popId}
76
- style="anchor-name:{anchor}"
77
- aria-label={strings.filterColumn(column.title ?? column.attribute)}
78
- title={strings.filter}
79
- >
80
- {#if filter.hasActive(column.attribute)}
81
- {@const Icon = icons.filterActive}
82
- <Icon class="size-3.5" />
83
- {:else}
84
- {@const Icon = icons.filter}
85
- <Icon class="size-3.5" />
86
- {/if}
87
- </Button>
88
-
89
- <div
90
- popover="auto"
91
- id={popId}
92
- style="position-anchor:{anchor}; position-try-fallbacks:flip-block;"
93
- class="dropdown dropdown-end rounded-box border border-base-content/10 bg-base-100 p-3 shadow-lg"
94
- class:w-56={!isDatetime}
95
- >
96
- {#if isDatetime}
97
- <calendar-range class="cally bg-base-100" value={calendarValue} bind:this={calendarEl}>
98
- <svg
99
- aria-label={strings.previous}
100
- class="fill-current size-4"
101
- {...{ slot: 'previous' }}
102
- xmlns="http://www.w3.org/2000/svg"
103
- viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
104
- >
105
- <svg
106
- aria-label={strings.next}
107
- class="fill-current size-4"
108
- {...{ slot: 'next' }}
109
- xmlns="http://www.w3.org/2000/svg"
110
- viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
111
- >
112
- <calendar-month></calendar-month>
113
- </calendar-range>
114
- {#if dateRange.from || dateRange.to}
115
- <p class="mt-2 text-center text-xs text-base-content/60">
116
- {dateRange.from || '…'} → {dateRange.to || '…'}
117
- </p>
118
- {/if}
119
- {#if filter.hasActive(column.attribute)}
120
- <Button variant="ghost" class="btn-sm mt-2 w-full" onclick={clear}>
121
- {strings.clearFilter}
122
- </Button>
123
- {/if}
124
- {:else}
125
- <div class="relative">
126
- <input
127
- type="text"
128
- class={[
129
- 'input input-bordered input-sm w-full',
130
- filter.hasActive(column.attribute) && 'pr-7'
131
- ]}
132
- placeholder={strings.filterPlaceholder}
133
- value={filter.textFor(column.attribute)}
134
- oninput={(e) => setText(e.currentTarget.value)}
135
- />
136
- {#if filter.hasActive(column.attribute)}
137
- {@const Icon = icons.clear}
138
- <Button
139
- variant="ghost"
140
- class="btn-xs btn-square btn-circle absolute top-1/2 right-1 -translate-y-1/2"
141
- aria-label={strings.clearFilter}
142
- title={strings.clearFilter}
143
- onclick={clear}
144
- >
145
- <Icon class="size-3" />
146
- </Button>
147
- {/if}
148
- </div>
149
-
150
- {#if entries.length > 0 && entries.length < maxCheckboxValues}
151
- <div class="mt-2 flex max-h-60 flex-col gap-1 overflow-y-auto">
152
- {#each entries as entry (entry.key)}
153
- <Label class="flex cursor-pointer items-center gap-2 text-sm font-normal normal-case">
154
- <input
155
- type="checkbox"
156
- class="checkbox checkbox-xs shrink-0"
157
- checked={filter.isChecked(column.attribute, entry.key)}
158
- onchange={() => toggle(entry.key)}
159
- />
160
- <span class="truncate">
161
- {entry.key === '' ? strings.emptyValue : (entry.label ?? entry.key)}
162
- </span>
163
- </Label>
164
- {/each}
165
- </div>
166
- {/if}
167
- {/if}
168
- </div>
1
+ <script lang="ts" generics="T extends object">
2
+ import { onMount } from 'svelte';
3
+ import Label from '../form/Label.svelte';
4
+ import Button from '../form/Button.svelte';
5
+ import { getIconSet } from '../../icons/context.js';
6
+ import { defaultIconSet } from '../../icons/sets/default.js';
7
+ import { getStrings } from '../../i18n/context.js';
8
+
9
+ const strings = getStrings();
10
+
11
+ onMount(() => {
12
+ import('cally');
13
+ });
14
+ import type { DistinctEntry } from '../../types/table.js';
15
+ import type { FilterState } from './state.svelte.js';
16
+ import type { ColumnDefinition } from '../../types/crud.js';
17
+
18
+ let {
19
+ column,
20
+ entries = [],
21
+ filter,
22
+ maxCheckboxValues = 20,
23
+ onchange
24
+ }: {
25
+ column: ColumnDefinition<T>;
26
+ entries?: DistinctEntry<T>[];
27
+ filter: FilterState;
28
+ maxCheckboxValues?: number;
29
+ onchange?: () => void;
30
+ } = $props();
31
+
32
+ const icons = $derived(getIconSet() ?? defaultIconSet);
33
+ const popId = $derived(`filter-pop-${column.attribute}`);
34
+ const anchor = $derived(`--filter-anchor-${column.attribute}`);
35
+ const isDatetime = $derived(column.type === 'datetime');
36
+
37
+ const dateRange = $derived(filter.dateRangeFor(column.attribute));
38
+ const calendarValue = $derived(
39
+ dateRange.from || dateRange.to ? `${dateRange.from}/${dateRange.to}` : ''
40
+ );
41
+
42
+ let calendarEl: HTMLElement | undefined = $state();
43
+
44
+ $effect(() => {
45
+ if (!calendarEl) return;
46
+ function handler() {
47
+ const value = (calendarEl as HTMLElement & { value: string }).value ?? '';
48
+ const [from = '', to = ''] = value.split('/');
49
+ filter.setDateRange(column.attribute, from, to);
50
+ onchange?.();
51
+ }
52
+ calendarEl.addEventListener('change', handler);
53
+ return () => calendarEl?.removeEventListener('change', handler);
54
+ });
55
+
56
+ let debounceTimer: ReturnType<typeof setTimeout>;
57
+ function setText(value: string) {
58
+ filter.setText(column.attribute, value);
59
+ clearTimeout(debounceTimer);
60
+ debounceTimer = setTimeout(() => onchange?.(), 300);
61
+ }
62
+ function toggle(value: string) {
63
+ filter.toggleValue(column.attribute, value);
64
+ onchange?.();
65
+ }
66
+ function clear() {
67
+ filter.clear(column.attribute);
68
+ onchange?.();
69
+ }
70
+ </script>
71
+
72
+ <Button
73
+ variant="ghost"
74
+ class={['btn-xs btn-square', filter.hasActive(column.attribute) && 'text-primary']}
75
+ popovertarget={popId}
76
+ style="anchor-name:{anchor}"
77
+ aria-label={strings.filterColumn(column.title ?? column.attribute)}
78
+ title={strings.filter}
79
+ >
80
+ {#if filter.hasActive(column.attribute)}
81
+ {@const Icon = icons.filterActive}
82
+ <Icon class="size-3.5" />
83
+ {:else}
84
+ {@const Icon = icons.filter}
85
+ <Icon class="size-3.5" />
86
+ {/if}
87
+ </Button>
88
+
89
+ <div
90
+ popover="auto"
91
+ id={popId}
92
+ style="position-anchor:{anchor}; position-try-fallbacks:flip-block;"
93
+ class="dropdown dropdown-end rounded-box border border-base-content/10 bg-base-100 p-3 shadow-lg"
94
+ class:w-56={!isDatetime}
95
+ >
96
+ {#if isDatetime}
97
+ <calendar-range class="cally bg-base-100" value={calendarValue} bind:this={calendarEl}>
98
+ <svg
99
+ aria-label={strings.previous}
100
+ class="fill-current size-4"
101
+ {...{ slot: 'previous' }}
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ viewBox="0 0 24 24"><path fill="currentColor" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg
104
+ >
105
+ <svg
106
+ aria-label={strings.next}
107
+ class="fill-current size-4"
108
+ {...{ slot: 'next' }}
109
+ xmlns="http://www.w3.org/2000/svg"
110
+ viewBox="0 0 24 24"><path fill="currentColor" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg
111
+ >
112
+ <calendar-month></calendar-month>
113
+ </calendar-range>
114
+ {#if dateRange.from || dateRange.to}
115
+ <p class="mt-2 text-center text-xs text-base-content/60">
116
+ {dateRange.from || '…'} → {dateRange.to || '…'}
117
+ </p>
118
+ {/if}
119
+ {#if filter.hasActive(column.attribute)}
120
+ <Button variant="ghost" class="btn-sm mt-2 w-full" onclick={clear}>
121
+ {strings.clearFilter}
122
+ </Button>
123
+ {/if}
124
+ {:else}
125
+ <div class="relative">
126
+ <input
127
+ type="text"
128
+ class={[
129
+ 'input input-bordered input-sm w-full',
130
+ filter.hasActive(column.attribute) && 'pr-7'
131
+ ]}
132
+ placeholder={strings.filterPlaceholder}
133
+ value={filter.textFor(column.attribute)}
134
+ oninput={(e) => setText(e.currentTarget.value)}
135
+ />
136
+ {#if filter.hasActive(column.attribute)}
137
+ {@const Icon = icons.clear}
138
+ <Button
139
+ variant="ghost"
140
+ class="btn-xs btn-square btn-circle absolute top-1/2 right-1 -translate-y-1/2"
141
+ aria-label={strings.clearFilter}
142
+ title={strings.clearFilter}
143
+ onclick={clear}
144
+ >
145
+ <Icon class="size-3" />
146
+ </Button>
147
+ {/if}
148
+ </div>
149
+
150
+ {#if entries.length > 0 && entries.length < maxCheckboxValues}
151
+ <div class="mt-2 flex max-h-60 flex-col gap-1 overflow-y-auto">
152
+ {#each entries as entry (entry.key)}
153
+ <Label class="flex cursor-pointer items-center gap-2 text-sm font-normal normal-case">
154
+ <input
155
+ type="checkbox"
156
+ class="checkbox checkbox-xs shrink-0"
157
+ checked={filter.isChecked(column.attribute, entry.key)}
158
+ onchange={() => toggle(entry.key)}
159
+ />
160
+ <span class="truncate">
161
+ {entry.key === '' ? strings.emptyValue : (entry.label ?? entry.key)}
162
+ </span>
163
+ </Label>
164
+ {/each}
165
+ </div>
166
+ {/if}
167
+ {/if}
168
+ </div>