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,248 +1,248 @@
1
- <script lang="ts">
2
- import { SvelteMap } from 'svelte/reactivity';
3
- import { getStrings } from '../../i18n/context.js';
4
- import { getIconSet } from '../../icons/context.js';
5
- import { defaultIconSet } from '../../icons/sets/default.js';
6
- import Button from './Button.svelte';
7
- import type { SearchResolver, SelectOption } from '../../types/attribute.js';
8
-
9
- const strings = getStrings();
10
- const icons = $derived(getIconSet() ?? defaultIconSet);
11
-
12
- let {
13
- name,
14
- id,
15
- value = $bindable([]),
16
- options = [],
17
- search: searchFn,
18
- searchDebounceMs = 300,
19
- placeholder = strings.selectPlaceholder,
20
- error = '',
21
- disabled = false,
22
- // When passed, the dropdown's top row selects every option instead of
23
- // clearing the selection (labelled `strings.selectAll` instead of
24
- // `placeholder`) — clearing moves to the trigger's own × button instead.
25
- // Left unset, existing callers keep today's behavior unchanged: top row
26
- // clears, no × button.
27
- onSelectAll
28
- }: {
29
- name?: string;
30
- id?: string;
31
- value?: string[];
32
- options?: SelectOption[];
33
- search?: SearchResolver;
34
- searchDebounceMs?: number;
35
- placeholder?: string;
36
- error?: string;
37
- disabled?: boolean;
38
- onSelectAll?: () => void;
39
- } = $props();
40
-
41
- const popId = $props.id();
42
- const anchorName = `--multiselect-anchor-${popId}`;
43
-
44
- let query = $state('');
45
- let open = $state(false);
46
- let containerEl: HTMLElement | undefined = $state();
47
- let popoverEl: HTMLElement | undefined = $state();
48
- let inputEl: HTMLInputElement | undefined = $state();
49
-
50
- let remoteResults = $state<SelectOption[] | null>(null);
51
- let searching = $state(false);
52
- const pickedLabels = new SvelteMap<string, string>();
53
-
54
- let searchToken = 0;
55
- $effect(() => {
56
- if (!searchFn) return;
57
- const q = query.trim();
58
- if (!q) {
59
- remoteResults = null;
60
- searching = false;
61
- return;
62
- }
63
- const token = ++searchToken;
64
- searching = true;
65
- const timer = setTimeout(() => {
66
- searchFn(q).then((results) => {
67
- if (token !== searchToken) return; // stale response, a newer query took over
68
- remoteResults = results;
69
- searching = false;
70
- });
71
- }, searchDebounceMs);
72
- return () => clearTimeout(timer);
73
- });
74
-
75
- const filtered = $derived(
76
- searchFn
77
- ? (remoteResults ?? options)
78
- : query.trim()
79
- ? options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase()))
80
- : options
81
- );
82
-
83
- const selectedLabels = $derived(
84
- value.map((v) => options.find((o) => o.value === v)?.label ?? pickedLabels.get(v) ?? v)
85
- );
86
- const buttonLabel = $derived(
87
- value.length === 0 ? placeholder : strings.selectedCount(value.length)
88
- );
89
-
90
- function toggle(option: SelectOption) {
91
- if (value.includes(option.value)) {
92
- value = value.filter((v) => v !== option.value);
93
- } else {
94
- value = [...value, option.value];
95
- pickedLabels.set(option.value, option.label);
96
- }
97
- }
98
-
99
- function openPopover() {
100
- open = true;
101
- query = '';
102
- popoverEl?.showPopover();
103
- }
104
-
105
- function clear() {
106
- value = [];
107
- pickedLabels.clear();
108
- query = '';
109
- }
110
-
111
- function onToggle(e: Event) {
112
- if ((e as ToggleEvent).newState === 'closed') {
113
- open = false;
114
- query = '';
115
- }
116
- }
117
-
118
- // Escape can be pressed while an option button has focus (not just the
119
- // text input), so this is wired at the container level rather than on
120
- // the input alone — otherwise picking an option and then hitting Escape
121
- // would leave the popover open with nothing listening for the key.
122
- function onKeydown(e: KeyboardEvent) {
123
- if (e.key === 'Enter' && e.target === inputEl) {
124
- // The trigger used to be a plain button, so Enter never submitted the
125
- // surrounding form. Now that it's a text input, guard against that.
126
- e.preventDefault();
127
- } else if (e.key === 'Escape') {
128
- // Also suppress the browser's native "revert to last value" behavior
129
- // for text inputs on Escape, which would otherwise race our own
130
- // close-and-restore-label update and blank the field.
131
- e.preventDefault();
132
- popoverEl?.hidePopover();
133
- }
134
- }
135
-
136
- const hasSelection = $derived(value.length > 0);
137
-
138
- function clearFromTrigger(e: MouseEvent) {
139
- // Stop the click from also reaching the input underneath (they're
140
- // overlapping siblings, not nested) so clearing doesn't focus/open the
141
- // dropdown at the same time.
142
- e.preventDefault();
143
- e.stopPropagation();
144
- clear();
145
- }
146
-
147
- // The trigger is a text input now, not a button, so it can't rely on the
148
- // native `popovertarget` invoker exclusion to survive its own opening
149
- // click (that relationship only applies to button-like elements — for a
150
- // text input, the click that focuses it and opens the popover would
151
- // immediately light-dismiss it again). `popover="manual"` opts out of
152
- // that dismiss behavior entirely; this handler reimplements "close when
153
- // focus leaves the widget" instead.
154
- function onFocusOut(e: FocusEvent) {
155
- const next = e.relatedTarget as Node | null;
156
- if (!next || !containerEl?.contains(next)) {
157
- popoverEl?.hidePopover();
158
- }
159
- }
160
- </script>
161
-
162
- <!--
163
- The keydown/focusout handlers here are event delegation for the input and
164
- popover option buttons below, not interaction with this div itself — every
165
- actually-interactive element inside already has correct native semantics.
166
- -->
167
- <!-- svelte-ignore a11y_no_static_element_interactions -->
168
- <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
169
- <div class="relative w-full" bind:this={containerEl} onfocusout={onFocusOut} onkeydown={onKeydown}>
170
- {#if name}
171
- <input type="hidden" {name} value={JSON.stringify(value)} />
172
- {/if}
173
-
174
- <div class="relative">
175
- <input
176
- bind:this={inputEl}
177
- {id}
178
- type="text"
179
- class="input input-bordered w-full font-normal"
180
- class:input-error={!!error}
181
- class:opacity-40={value.length === 0 && !open}
182
- class:pr-8={hasSelection}
183
- {disabled}
184
- {placeholder}
185
- value={open ? query : buttonLabel}
186
- oninput={(e) => (query = e.currentTarget.value)}
187
- onfocus={openPopover}
188
- style="anchor-name:{anchorName}"
189
- title={selectedLabels.join(', ')}
190
- autocomplete="off"
191
- />
192
-
193
- {#if hasSelection && !disabled}
194
- {@const Icon = icons.clear}
195
- <Button
196
- variant="ghost"
197
- class="btn-xs btn-square btn-circle absolute top-1/2 right-6 -translate-y-1/2"
198
- aria-label={strings.selectClear}
199
- title={strings.selectClear}
200
- onclick={clearFromTrigger}
201
- >
202
- <Icon class="size-3" />
203
- </Button>
204
- {/if}
205
- </div>
206
-
207
- <div
208
- popover="manual"
209
- id={popId}
210
- bind:this={popoverEl}
211
- style="position-anchor:{anchorName}; width:anchor-size(width); position-try-fallbacks:flip-block;"
212
- class="dropdown mt-1 rounded-box border border-base-content/10 bg-base-100 shadow-lg"
213
- ontoggle={onToggle}
214
- >
215
- <ul class="max-h-48 overflow-y-auto p-1">
216
- <li>
217
- <button
218
- type="button"
219
- class="w-full rounded-btn px-3 py-2 text-left text-sm text-base-content/40 hover:bg-base-200"
220
- onclick={onSelectAll ?? clear}
221
- >
222
- {onSelectAll ? strings.selectAll : placeholder}
223
- </button>
224
- </li>
225
- {#if searching}
226
- <li class="px-3 py-2 text-sm text-base-content/40">{strings.selectSearching}</li>
227
- {/if}
228
- {#each filtered as option (option.value)}
229
- {@const checked = value.includes(option.value)}
230
- <li>
231
- <button
232
- type="button"
233
- class="flex w-full items-center gap-2 rounded-btn px-3 py-2 text-left text-sm hover:bg-base-200"
234
- class:bg-primary={checked}
235
- class:text-primary-content={checked}
236
- onclick={() => toggle(option)}
237
- >
238
- <input type="checkbox" class="checkbox checkbox-sm" {checked} tabindex="-1" readonly />
239
- {option.label}
240
- </button>
241
- </li>
242
- {/each}
243
- {#if !searching && filtered.length === 0}
244
- <li class="px-3 py-2 text-sm text-base-content/40">{strings.selectNoResults}</li>
245
- {/if}
246
- </ul>
247
- </div>
248
- </div>
1
+ <script lang="ts">
2
+ import { SvelteMap } from 'svelte/reactivity';
3
+ import { getStrings } from '../../i18n/context.js';
4
+ import { getIconSet } from '../../icons/context.js';
5
+ import { defaultIconSet } from '../../icons/sets/default.js';
6
+ import Button from './Button.svelte';
7
+ import type { SearchResolver, SelectOption } from '../../types/attribute.js';
8
+
9
+ const strings = getStrings();
10
+ const icons = $derived(getIconSet() ?? defaultIconSet);
11
+
12
+ let {
13
+ name,
14
+ id,
15
+ value = $bindable([]),
16
+ options = [],
17
+ search: searchFn,
18
+ searchDebounceMs = 300,
19
+ placeholder = strings.selectPlaceholder,
20
+ error = '',
21
+ disabled = false,
22
+ // When passed, the dropdown's top row selects every option instead of
23
+ // clearing the selection (labelled `strings.selectAll` instead of
24
+ // `placeholder`) — clearing moves to the trigger's own × button instead.
25
+ // Left unset, existing callers keep today's behavior unchanged: top row
26
+ // clears, no × button.
27
+ onSelectAll
28
+ }: {
29
+ name?: string;
30
+ id?: string;
31
+ value?: string[];
32
+ options?: SelectOption[];
33
+ search?: SearchResolver;
34
+ searchDebounceMs?: number;
35
+ placeholder?: string;
36
+ error?: string;
37
+ disabled?: boolean;
38
+ onSelectAll?: () => void;
39
+ } = $props();
40
+
41
+ const popId = $props.id();
42
+ const anchorName = `--multiselect-anchor-${popId}`;
43
+
44
+ let query = $state('');
45
+ let open = $state(false);
46
+ let containerEl: HTMLElement | undefined = $state();
47
+ let popoverEl: HTMLElement | undefined = $state();
48
+ let inputEl: HTMLInputElement | undefined = $state();
49
+
50
+ let remoteResults = $state<SelectOption[] | null>(null);
51
+ let searching = $state(false);
52
+ const pickedLabels = new SvelteMap<string, string>();
53
+
54
+ let searchToken = 0;
55
+ $effect(() => {
56
+ if (!searchFn) return;
57
+ const q = query.trim();
58
+ if (!q) {
59
+ remoteResults = null;
60
+ searching = false;
61
+ return;
62
+ }
63
+ const token = ++searchToken;
64
+ searching = true;
65
+ const timer = setTimeout(() => {
66
+ searchFn(q).then((results) => {
67
+ if (token !== searchToken) return; // stale response, a newer query took over
68
+ remoteResults = results;
69
+ searching = false;
70
+ });
71
+ }, searchDebounceMs);
72
+ return () => clearTimeout(timer);
73
+ });
74
+
75
+ const filtered = $derived(
76
+ searchFn
77
+ ? (remoteResults ?? options)
78
+ : query.trim()
79
+ ? options.filter((o) => o.label.toLowerCase().includes(query.toLowerCase()))
80
+ : options
81
+ );
82
+
83
+ const selectedLabels = $derived(
84
+ value.map((v) => options.find((o) => o.value === v)?.label ?? pickedLabels.get(v) ?? v)
85
+ );
86
+ const buttonLabel = $derived(
87
+ value.length === 0 ? placeholder : strings.selectedCount(value.length)
88
+ );
89
+
90
+ function toggle(option: SelectOption) {
91
+ if (value.includes(option.value)) {
92
+ value = value.filter((v) => v !== option.value);
93
+ } else {
94
+ value = [...value, option.value];
95
+ pickedLabels.set(option.value, option.label);
96
+ }
97
+ }
98
+
99
+ function openPopover() {
100
+ open = true;
101
+ query = '';
102
+ popoverEl?.showPopover();
103
+ }
104
+
105
+ function clear() {
106
+ value = [];
107
+ pickedLabels.clear();
108
+ query = '';
109
+ }
110
+
111
+ function onToggle(e: Event) {
112
+ if ((e as ToggleEvent).newState === 'closed') {
113
+ open = false;
114
+ query = '';
115
+ }
116
+ }
117
+
118
+ // Escape can be pressed while an option button has focus (not just the
119
+ // text input), so this is wired at the container level rather than on
120
+ // the input alone — otherwise picking an option and then hitting Escape
121
+ // would leave the popover open with nothing listening for the key.
122
+ function onKeydown(e: KeyboardEvent) {
123
+ if (e.key === 'Enter' && e.target === inputEl) {
124
+ // The trigger used to be a plain button, so Enter never submitted the
125
+ // surrounding form. Now that it's a text input, guard against that.
126
+ e.preventDefault();
127
+ } else if (e.key === 'Escape') {
128
+ // Also suppress the browser's native "revert to last value" behavior
129
+ // for text inputs on Escape, which would otherwise race our own
130
+ // close-and-restore-label update and blank the field.
131
+ e.preventDefault();
132
+ popoverEl?.hidePopover();
133
+ }
134
+ }
135
+
136
+ const hasSelection = $derived(value.length > 0);
137
+
138
+ function clearFromTrigger(e: MouseEvent) {
139
+ // Stop the click from also reaching the input underneath (they're
140
+ // overlapping siblings, not nested) so clearing doesn't focus/open the
141
+ // dropdown at the same time.
142
+ e.preventDefault();
143
+ e.stopPropagation();
144
+ clear();
145
+ }
146
+
147
+ // The trigger is a text input now, not a button, so it can't rely on the
148
+ // native `popovertarget` invoker exclusion to survive its own opening
149
+ // click (that relationship only applies to button-like elements — for a
150
+ // text input, the click that focuses it and opens the popover would
151
+ // immediately light-dismiss it again). `popover="manual"` opts out of
152
+ // that dismiss behavior entirely; this handler reimplements "close when
153
+ // focus leaves the widget" instead.
154
+ function onFocusOut(e: FocusEvent) {
155
+ const next = e.relatedTarget as Node | null;
156
+ if (!next || !containerEl?.contains(next)) {
157
+ popoverEl?.hidePopover();
158
+ }
159
+ }
160
+ </script>
161
+
162
+ <!--
163
+ The keydown/focusout handlers here are event delegation for the input and
164
+ popover option buttons below, not interaction with this div itself — every
165
+ actually-interactive element inside already has correct native semantics.
166
+ -->
167
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
168
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
169
+ <div class="relative w-full" bind:this={containerEl} onfocusout={onFocusOut} onkeydown={onKeydown}>
170
+ {#if name}
171
+ <input type="hidden" {name} value={JSON.stringify(value)} />
172
+ {/if}
173
+
174
+ <div class="relative">
175
+ <input
176
+ bind:this={inputEl}
177
+ {id}
178
+ type="text"
179
+ class="input input-bordered w-full font-normal"
180
+ class:input-error={!!error}
181
+ class:opacity-40={value.length === 0 && !open}
182
+ class:pr-8={hasSelection}
183
+ {disabled}
184
+ {placeholder}
185
+ value={open ? query : buttonLabel}
186
+ oninput={(e) => (query = e.currentTarget.value)}
187
+ onfocus={openPopover}
188
+ style="anchor-name:{anchorName}"
189
+ title={selectedLabels.join(', ')}
190
+ autocomplete="off"
191
+ />
192
+
193
+ {#if hasSelection && !disabled}
194
+ {@const Icon = icons.clear}
195
+ <Button
196
+ variant="ghost"
197
+ class="btn-xs btn-square btn-circle absolute top-1/2 right-6 -translate-y-1/2"
198
+ aria-label={strings.selectClear}
199
+ title={strings.selectClear}
200
+ onclick={clearFromTrigger}
201
+ >
202
+ <Icon class="size-3" />
203
+ </Button>
204
+ {/if}
205
+ </div>
206
+
207
+ <div
208
+ popover="manual"
209
+ id={popId}
210
+ bind:this={popoverEl}
211
+ style="position-anchor:{anchorName}; width:anchor-size(width); position-try-fallbacks:flip-block;"
212
+ class="dropdown mt-1 rounded-box border border-base-content/10 bg-base-100 shadow-lg"
213
+ ontoggle={onToggle}
214
+ >
215
+ <ul class="max-h-48 overflow-y-auto p-1">
216
+ <li>
217
+ <button
218
+ type="button"
219
+ class="w-full rounded-btn px-3 py-2 text-left text-sm text-base-content/40 hover:bg-base-200"
220
+ onclick={onSelectAll ?? clear}
221
+ >
222
+ {onSelectAll ? strings.selectAll : placeholder}
223
+ </button>
224
+ </li>
225
+ {#if searching}
226
+ <li class="px-3 py-2 text-sm text-base-content/40">{strings.selectSearching}</li>
227
+ {/if}
228
+ {#each filtered as option (option.value)}
229
+ {@const checked = value.includes(option.value)}
230
+ <li>
231
+ <button
232
+ type="button"
233
+ class="flex w-full items-center gap-2 rounded-btn px-3 py-2 text-left text-sm hover:bg-base-200"
234
+ class:bg-primary={checked}
235
+ class:text-primary-content={checked}
236
+ onclick={() => toggle(option)}
237
+ >
238
+ <input type="checkbox" class="checkbox checkbox-sm" {checked} tabindex="-1" readonly />
239
+ {option.label}
240
+ </button>
241
+ </li>
242
+ {/each}
243
+ {#if !searching && filtered.length === 0}
244
+ <li class="px-3 py-2 text-sm text-base-content/40">{strings.selectNoResults}</li>
245
+ {/if}
246
+ </ul>
247
+ </div>
248
+ </div>
@@ -1,68 +1,68 @@
1
- <script lang="ts">
2
- import type { FullAutoFill } from 'svelte/elements';
3
- import Label from './Label.svelte';
4
- import Button from './Button.svelte';
5
- import { getIconSet } from '../../icons/context.js';
6
- import { defaultIconSet } from '../../icons/sets/default.js';
7
-
8
- let {
9
- name,
10
- id,
11
- value = $bindable(''),
12
- autocomplete,
13
- required = true,
14
- placeholder = '',
15
- invalid = false,
16
- labelClass,
17
- inputClass,
18
- buttonClass,
19
- }: {
20
- name?: string;
21
- id?: string;
22
- value?: string;
23
- autocomplete?: FullAutoFill;
24
- required?: boolean;
25
- placeholder?: string;
26
- invalid?: boolean;
27
- labelClass?: string;
28
- inputClass?: string;
29
- buttonClass?: string;
30
- } = $props();
31
-
32
- const icons = $derived(getIconSet() ?? defaultIconSet);
33
- let visible = $state(false);
34
- </script>
35
-
36
- <Label
37
- class={[
38
- 'input input-bordered w-full',
39
- { 'input-error': invalid },
40
- labelClass
41
- ]}
42
- >
43
- <input
44
- {name}
45
- {id}
46
- {required}
47
- {placeholder}
48
- bind:value
49
- {autocomplete}
50
- type={visible ? 'text' : 'password'}
51
- class={['grow', inputClass]}
52
- />
53
- <Button
54
- btn={false}
55
- class={['cursor-pointer opacity-60 hover:opacity-100', buttonClass].filter(Boolean).join(' ')}
56
- aria-label={visible ? 'Ocultar contraseña' : 'Mostrar contraseña'}
57
- aria-pressed={visible}
58
- onclick={() => (visible = !visible)}
59
- >
60
- {#if visible}
61
- {@const HideIcon = icons.passwordHide}
62
- <HideIcon class="size-4" />
63
- {:else}
64
- {@const ShowIcon = icons.passwordShow}
65
- <ShowIcon class="size-4" />
66
- {/if}
67
- </Button>
68
- </Label>
1
+ <script lang="ts">
2
+ import type { FullAutoFill } from 'svelte/elements';
3
+ import Label from './Label.svelte';
4
+ import Button from './Button.svelte';
5
+ import { getIconSet } from '../../icons/context.js';
6
+ import { defaultIconSet } from '../../icons/sets/default.js';
7
+
8
+ let {
9
+ name,
10
+ id,
11
+ value = $bindable(''),
12
+ autocomplete,
13
+ required = true,
14
+ placeholder = '',
15
+ invalid = false,
16
+ labelClass,
17
+ inputClass,
18
+ buttonClass,
19
+ }: {
20
+ name?: string;
21
+ id?: string;
22
+ value?: string;
23
+ autocomplete?: FullAutoFill;
24
+ required?: boolean;
25
+ placeholder?: string;
26
+ invalid?: boolean;
27
+ labelClass?: string;
28
+ inputClass?: string;
29
+ buttonClass?: string;
30
+ } = $props();
31
+
32
+ const icons = $derived(getIconSet() ?? defaultIconSet);
33
+ let visible = $state(false);
34
+ </script>
35
+
36
+ <Label
37
+ class={[
38
+ 'input input-bordered w-full',
39
+ { 'input-error': invalid },
40
+ labelClass
41
+ ]}
42
+ >
43
+ <input
44
+ {name}
45
+ {id}
46
+ {required}
47
+ {placeholder}
48
+ bind:value
49
+ {autocomplete}
50
+ type={visible ? 'text' : 'password'}
51
+ class={['grow', inputClass]}
52
+ />
53
+ <Button
54
+ btn={false}
55
+ class={['cursor-pointer opacity-60 hover:opacity-100', buttonClass].filter(Boolean).join(' ')}
56
+ aria-label={visible ? 'Ocultar contraseña' : 'Mostrar contraseña'}
57
+ aria-pressed={visible}
58
+ onclick={() => (visible = !visible)}
59
+ >
60
+ {#if visible}
61
+ {@const HideIcon = icons.passwordHide}
62
+ <HideIcon class="size-4" />
63
+ {:else}
64
+ {@const ShowIcon = icons.passwordShow}
65
+ <ShowIcon class="size-4" />
66
+ {/if}
67
+ </Button>
68
+ </Label>
@@ -1 +1 @@
1
- <span class="text-error ml-0.5">*</span>
1
+ <span class="text-error ml-0.5">*</span>