@r2digisolutions/components 0.16.2 → 0.16.4

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.
@@ -31,6 +31,9 @@
31
31
  onfocus?: (e: FocusEvent) => void;
32
32
  onblur?: (e: FocusEvent) => void;
33
33
  onkeydown?: (e: KeyboardEvent) => void;
34
+ autocomplete?: string;
35
+ /** Native form value only (no Svelte bind). Needed for login + autofill. */
36
+ uncontrolled?: boolean;
34
37
  }
35
38
 
36
39
  let {
@@ -58,7 +61,9 @@
58
61
  onchange,
59
62
  onfocus,
60
63
  onblur,
61
- onkeydown
64
+ onkeydown,
65
+ autocomplete,
66
+ uncontrolled = false
62
67
  }: InputProps = $props();
63
68
 
64
69
  let autoId = $state<string | undefined>(undefined);
@@ -67,7 +72,9 @@
67
72
  });
68
73
  const inputId = $derived(id ?? autoId);
69
74
  const helperId = $derived(inputId ? `${inputId}-helper` : undefined);
70
- const hasClear = $derived(clearable && value.length > 0 && !disabled && !readonly);
75
+ const hasClear = $derived(
76
+ !uncontrolled && clearable && value.length > 0 && !disabled && !readonly
77
+ );
71
78
 
72
79
  const wrapperSizeClasses: Record<InputSize, string> = {
73
80
  sm: 'h-8 text-sm',
@@ -137,32 +144,62 @@
137
144
  </span>
138
145
  {/if}
139
146
 
140
- <input
141
- id={inputId}
142
- {name}
143
- {type}
144
- {placeholder}
145
- {disabled}
146
- {readonly}
147
- {required}
148
- {autofocus}
149
- {min}
150
- {max}
151
- {step}
152
- bind:value
153
- aria-describedby={helperText ? helperId : undefined}
154
- aria-invalid={status === 'error'}
155
- class={[
156
- 'min-w-0 text-primary placeholder:text-muted h-full flex-1 self-stretch bg-transparent outline-none disabled:cursor-not-allowed',
157
- type === 'search' &&
158
- '[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none'
159
- ]}
160
- {oninput}
161
- {onchange}
162
- {onfocus}
163
- {onblur}
164
- {onkeydown}
165
- />
147
+ {#if uncontrolled}
148
+ <input
149
+ id={inputId}
150
+ {name}
151
+ {type}
152
+ {placeholder}
153
+ {disabled}
154
+ {readonly}
155
+ {required}
156
+ {autofocus}
157
+ {min}
158
+ {max}
159
+ {step}
160
+ {autocomplete}
161
+ aria-describedby={helperText ? helperId : undefined}
162
+ aria-invalid={status === 'error'}
163
+ class={[
164
+ 'min-w-0 text-primary placeholder:text-muted h-full flex-1 self-stretch bg-transparent outline-none disabled:cursor-not-allowed',
165
+ type === 'search' &&
166
+ '[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none'
167
+ ]}
168
+ {oninput}
169
+ {onchange}
170
+ {onfocus}
171
+ {onblur}
172
+ {onkeydown}
173
+ />
174
+ {:else}
175
+ <input
176
+ id={inputId}
177
+ {name}
178
+ {type}
179
+ {placeholder}
180
+ {disabled}
181
+ {readonly}
182
+ {required}
183
+ {autofocus}
184
+ {min}
185
+ {max}
186
+ {step}
187
+ {autocomplete}
188
+ bind:value
189
+ aria-describedby={helperText ? helperId : undefined}
190
+ aria-invalid={status === 'error'}
191
+ class={[
192
+ 'min-w-0 text-primary placeholder:text-muted h-full flex-1 self-stretch bg-transparent outline-none disabled:cursor-not-allowed',
193
+ type === 'search' &&
194
+ '[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none'
195
+ ]}
196
+ {oninput}
197
+ {onchange}
198
+ {onfocus}
199
+ {onblur}
200
+ {onkeydown}
201
+ />
202
+ {/if}
166
203
  </label>
167
204
 
168
205
  {#if hasClear}
@@ -27,6 +27,9 @@ interface InputProps {
27
27
  onfocus?: (e: FocusEvent) => void;
28
28
  onblur?: (e: FocusEvent) => void;
29
29
  onkeydown?: (e: KeyboardEvent) => void;
30
+ autocomplete?: string;
31
+ /** Native form value only (no Svelte bind). Needed for login + autofill. */
32
+ uncontrolled?: boolean;
30
33
  }
31
34
  declare const Input: import("svelte").Component<InputProps, {}, "value">;
32
35
  type Input = ReturnType<typeof Input>;
@@ -71,6 +71,8 @@
71
71
  /** Absorbed if a Kit `.as()` object is spread by mistake. */
72
72
  defaultValue?: unknown;
73
73
  'aria-invalid'?: boolean | 'true' | 'false';
74
+ autocomplete?: string;
75
+ uncontrolled?: boolean;
74
76
  }
75
77
 
76
78
  let {
@@ -97,7 +99,9 @@
97
99
  oninput,
98
100
  onchange,
99
101
  defaultValue: _kitDefaultValue = undefined,
100
- 'aria-invalid': _ariaInvalid = undefined
102
+ 'aria-invalid': _ariaInvalid = undefined,
103
+ autocomplete,
104
+ uncontrolled = false
101
105
  }: FormFieldProps = $props();
102
106
 
103
107
  const form = getFormContext();
@@ -186,6 +190,8 @@
186
190
  {onchange}
187
191
  status={resolved.status}
188
192
  helperText={resolved.helperText}
193
+ {autocomplete}
194
+ {uncontrolled}
189
195
  bind:value
190
196
  />
191
197
  {/if}
@@ -60,6 +60,8 @@ interface FormFieldProps {
60
60
  /** Absorbed if a Kit `.as()` object is spread by mistake. */
61
61
  defaultValue?: unknown;
62
62
  'aria-invalid'?: boolean | 'true' | 'false';
63
+ autocomplete?: string;
64
+ uncontrolled?: boolean;
63
65
  }
64
66
  declare const FormField: import("svelte").Component<FormFieldProps, {}, "value">;
65
67
  type FormField = ReturnType<typeof FormField>;
@@ -32,6 +32,8 @@
32
32
  defaultValue?: unknown;
33
33
  type?: string;
34
34
  'aria-invalid'?: boolean | 'true' | 'false';
35
+ autocomplete?: string;
36
+ uncontrolled?: boolean;
35
37
  }
36
38
 
37
39
  let {
@@ -53,7 +55,9 @@
53
55
  onchange,
54
56
  defaultValue: _kitDefaultValue = undefined,
55
57
  type: _kitType = undefined,
56
- 'aria-invalid': _ariaInvalid = undefined
58
+ 'aria-invalid': _ariaInvalid = undefined,
59
+ autocomplete,
60
+ uncontrolled = false
57
61
  }: FormPasswordInputProps = $props();
58
62
 
59
63
  const form = getFormContext();
@@ -111,6 +115,8 @@
111
115
  helperText={resolved.helperText}
112
116
  oninput={handleInput}
113
117
  {onchange}
118
+ {autocomplete}
119
+ {uncontrolled}
114
120
  bind:value
115
121
  />
116
122
  </div>
@@ -22,6 +22,8 @@ interface FormPasswordInputProps {
22
22
  defaultValue?: unknown;
23
23
  type?: string;
24
24
  'aria-invalid'?: boolean | 'true' | 'false';
25
+ autocomplete?: string;
26
+ uncontrolled?: boolean;
25
27
  }
26
28
  declare const FormPasswordInput: import("svelte").Component<FormPasswordInputProps, {}, "value">;
27
29
  type FormPasswordInput = ReturnType<typeof FormPasswordInput>;
@@ -15,6 +15,8 @@
15
15
  class?: string;
16
16
  oninput?: (e: Event) => void;
17
17
  onchange?: (e: Event) => void;
18
+ autocomplete?: string;
19
+ uncontrolled?: boolean;
18
20
  }
19
21
 
20
22
  let {
@@ -30,7 +32,9 @@
30
32
  status = 'default',
31
33
  class: className = '',
32
34
  oninput,
33
- onchange
35
+ onchange,
36
+ autocomplete,
37
+ uncontrolled = false
34
38
  }: PasswordInputProps = $props();
35
39
 
36
40
  let visible = $state(false);
@@ -48,6 +52,8 @@
48
52
  {size}
49
53
  {status}
50
54
  type={visible ? 'text' : 'password'}
55
+ {autocomplete}
56
+ {uncontrolled}
51
57
  bind:value
52
58
  {oninput}
53
59
  {onchange}
@@ -12,6 +12,8 @@ interface PasswordInputProps {
12
12
  class?: string;
13
13
  oninput?: (e: Event) => void;
14
14
  onchange?: (e: Event) => void;
15
+ autocomplete?: string;
16
+ uncontrolled?: boolean;
15
17
  }
16
18
  declare const PasswordInput: import("svelte").Component<PasswordInputProps, {}, "value">;
17
19
  type PasswordInput = ReturnType<typeof PasswordInput>;
@@ -29,6 +29,9 @@
29
29
  messages?: AssistantMessage[];
30
30
  draft?: string;
31
31
  loading?: boolean;
32
+ loadingLabel?: string;
33
+ /** When false, input stays disabled via `loading` but the spinner bubble is hidden (parent paints status in a message). */
34
+ showLoadingBubble?: boolean;
32
35
  error?: string | null;
33
36
  voiceEnabled?: boolean;
34
37
  voiceOutput?: boolean;
@@ -61,6 +64,8 @@
61
64
  messages = [],
62
65
  draft = $bindable(''),
63
66
  loading = false,
67
+ loadingLabel = 'Procesando…',
68
+ showLoadingBubble = true,
64
69
  error = null,
65
70
  voiceEnabled = true,
66
71
  voiceOutput = $bindable(true),
@@ -218,7 +223,7 @@
218
223
  {/if}
219
224
  {/each}
220
225
 
221
- {#if loading}
226
+ {#if loading && showLoadingBubble}
222
227
  <div class="flex items-center gap-2">
223
228
  <span
224
229
  class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-brand-100 text-brand-600 dark:bg-brand-900/40 dark:text-brand-400"
@@ -230,18 +235,10 @@
230
235
  class="flex items-center gap-2 rounded-2xl rounded-bl-sm bg-surface-overlay px-3.5 py-2.5"
231
236
  >
232
237
  <LoaderCircle size={14} strokeWidth={2} class="animate-spin text-muted" />
233
- <span class="text-xs text-muted">Procesando…</span>
238
+ <span class="text-xs text-muted">{loadingLabel}</span>
234
239
  </div>
235
240
  </div>
236
241
  {/if}
237
-
238
- {#if error}
239
- <div
240
- class="rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-600 dark:border-red-800/40 dark:bg-red-950/20 dark:text-red-400"
241
- >
242
- {error}
243
- </div>
244
- {/if}
245
242
  </div>
246
243
  {/if}
247
244
  </div>
@@ -252,8 +249,11 @@
252
249
  <input
253
250
  type="text"
254
251
  bind:value={draft}
255
- {placeholder}
256
- class="h-9 min-w-0 flex-1 rounded-xl border border-border bg-surface px-3 text-sm text-primary outline-none transition-shadow placeholder:text-muted focus:border-brand-400 focus:ring-2 focus:ring-brand-500/20"
252
+ placeholder={listening ? statusLabel || placeholder : placeholder}
253
+ class={[
254
+ 'h-9 min-w-0 flex-1 rounded-xl border bg-surface px-3 text-sm text-primary outline-none transition-shadow placeholder:text-muted focus:border-brand-400 focus:ring-2 focus:ring-brand-500/20',
255
+ listening ? 'border-red-400 ring-2 ring-red-500/20' : 'border-border'
256
+ ]}
257
257
  disabled={loading}
258
258
  onkeydown={handleKeydown}
259
259
  aria-label={placeholder}
@@ -300,6 +300,10 @@
300
300
  </div>
301
301
  </div>
302
302
 
303
+ {#if error}
304
+ <p class="mt-1.5 px-1 text-xs text-red-600 dark:text-red-400">{error}</p>
305
+ {/if}
306
+
303
307
  {#if footerExtra}
304
308
  {@render footerExtra()}
305
309
  {/if}
@@ -14,6 +14,9 @@ interface AssistantChatProps {
14
14
  messages?: AssistantMessage[];
15
15
  draft?: string;
16
16
  loading?: boolean;
17
+ loadingLabel?: string;
18
+ /** When false, input stays disabled via `loading` but the spinner bubble is hidden (parent paints status in a message). */
19
+ showLoadingBubble?: boolean;
17
20
  error?: string | null;
18
21
  voiceEnabled?: boolean;
19
22
  voiceOutput?: boolean;
@@ -2,7 +2,9 @@
2
2
  import type { Component } from 'svelte';
3
3
  import { untrack } from 'svelte';
4
4
  import Kbd from '../../atoms/Kbd/Kbd.svelte';
5
+ import IconButton from '../../atoms/IconButton/IconButton.svelte';
5
6
  import { i18n } from '../../../utils/i18n.svelte';
7
+ import { Mic, MicOff } from '@lucide/svelte';
6
8
 
7
9
  export type CommandIcon = Component<{
8
10
  class?: string;
@@ -24,29 +26,39 @@
24
26
 
25
27
  interface CommandPaletteProps {
26
28
  open?: boolean;
29
+ query?: string;
27
30
  items?: CommandItem[];
28
31
  placeholder?: string;
29
32
  emptyLabel?: string;
30
33
  loading?: boolean;
34
+ voiceEnabled?: boolean;
35
+ listening?: boolean;
36
+ micLabel?: string;
37
+ stopMicLabel?: string;
31
38
  class?: string;
32
39
  onselect?: (item: CommandItem) => void;
33
40
  onclose?: () => void;
34
41
  onquery?: (query: string) => void;
42
+ onmicclick?: () => void;
35
43
  }
36
44
 
37
45
  let {
38
46
  open = $bindable(false),
47
+ query = $bindable(''),
39
48
  items = [],
40
49
  placeholder,
41
50
  emptyLabel,
42
51
  loading = false,
52
+ voiceEnabled = false,
53
+ listening = false,
54
+ micLabel = 'Hablar',
55
+ stopMicLabel = 'Parar micrófono',
43
56
  class: className = '',
44
57
  onselect,
45
58
  onclose,
46
- onquery
59
+ onquery,
60
+ onmicclick
47
61
  }: CommandPaletteProps = $props();
48
-
49
- let query = $state('');
50
62
  let activeIndex = $state(0);
51
63
  let dialogEl = $state<HTMLDialogElement | null>(null);
52
64
  let listEl = $state<HTMLDivElement | null>(null);
@@ -232,6 +244,25 @@
232
244
  oninput={onInput}
233
245
  onkeydown={onKeydown}
234
246
  />
247
+ {#if voiceEnabled}
248
+ <IconButton
249
+ variant="ghost"
250
+ size="xs"
251
+ label={listening ? stopMicLabel : micLabel}
252
+ aria-pressed={listening}
253
+ onclick={(e) => {
254
+ e.stopPropagation();
255
+ onmicclick?.();
256
+ }}
257
+ class={listening ? 'text-red-500 hover:text-red-600' : 'text-muted'}
258
+ >
259
+ {#if listening}
260
+ <MicOff size={16} strokeWidth={2} />
261
+ {:else}
262
+ <Mic size={16} strokeWidth={2} />
263
+ {/if}
264
+ </IconButton>
265
+ {/if}
235
266
  {#if loading}
236
267
  <span
237
268
  class="h-4 w-4 animate-spin border-border border-t-brand-500 shrink-0 rounded-full border-2"
@@ -17,15 +17,21 @@ export interface CommandItem {
17
17
  }
18
18
  interface CommandPaletteProps {
19
19
  open?: boolean;
20
+ query?: string;
20
21
  items?: CommandItem[];
21
22
  placeholder?: string;
22
23
  emptyLabel?: string;
23
24
  loading?: boolean;
25
+ voiceEnabled?: boolean;
26
+ listening?: boolean;
27
+ micLabel?: string;
28
+ stopMicLabel?: string;
24
29
  class?: string;
25
30
  onselect?: (item: CommandItem) => void;
26
31
  onclose?: () => void;
27
32
  onquery?: (query: string) => void;
33
+ onmicclick?: () => void;
28
34
  }
29
- declare const CommandPalette: Component<CommandPaletteProps, {}, "open">;
35
+ declare const CommandPalette: Component<CommandPaletteProps, {}, "open" | "query">;
30
36
  type CommandPalette = ReturnType<typeof CommandPalette>;
31
37
  export default CommandPalette;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.16.2",
3
+ "version": "0.16.4",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",