@human-kit/svelte-components 1.0.0-alpha.12 → 1.0.0-alpha.13

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.
@@ -113,6 +113,7 @@
113
113
  aria-label={ariaLabel}
114
114
  aria-labelledby={ariaLabelledby}
115
115
  aria-describedby={ariaDescribedby}
116
+ autocomplete="off"
116
117
  value={ctx.displayValue}
117
118
  isDisabled={ctx.isDisabled}
118
119
  isReadOnly={ctx.isReadOnly}
@@ -73,4 +73,5 @@
73
73
  value={ctx.selectedValue}
74
74
  onChange={handleSelectionChange}
75
75
  aria-label={ariaLabel}
76
+ disableFocusHandling={true}
76
77
  />
@@ -14,6 +14,7 @@ declare function $$render<T extends object = object>(): {
14
14
  id?: string;
15
15
  'aria-label'?: string;
16
16
  onChange?: ((value: Set<string | number>) => void) | undefined;
17
+ disableFocusHandling?: boolean;
17
18
  } & {
18
19
  context?: ListBoxContext;
19
20
  element?: HTMLElement;
@@ -7,6 +7,7 @@
7
7
  isPending?: boolean;
8
8
  isReadOnly?: boolean;
9
9
  trigger?: 'focus' | 'input' | 'press';
10
+ disabledIds?: string[];
10
11
  };
11
12
 
12
13
  let {
@@ -14,7 +15,8 @@
14
15
  isDisabled = false,
15
16
  isPending = false,
16
17
  isReadOnly = false,
17
- trigger = 'press'
18
+ trigger = 'press',
19
+ disabledIds = []
18
20
  }: Props = $props();
19
21
 
20
22
  let selectedValue = $state<string | number | undefined>();
@@ -40,7 +42,11 @@
40
42
  <ComboBox.Popover>
41
43
  <ComboBox.List emptyPlaceholder="No countries found">
42
44
  {#each countries as country (country.id)}
43
- <ComboBox.Item id={country.id} textValue={country.name}>
45
+ <ComboBox.Item
46
+ id={country.id}
47
+ textValue={country.name}
48
+ disabled={disabledIds.includes(country.id)}
49
+ >
44
50
  {country.name}
45
51
  </ComboBox.Item>
46
52
  {/each}
@@ -4,6 +4,7 @@ type Props = {
4
4
  isPending?: boolean;
5
5
  isReadOnly?: boolean;
6
6
  trigger?: 'focus' | 'input' | 'press';
7
+ disabledIds?: string[];
7
8
  };
8
9
  declare const ComboboxTest: import("svelte").Component<Props, {}, "">;
9
10
  type ComboboxTest = ReturnType<typeof ComboboxTest>;
@@ -211,6 +211,15 @@
211
211
  }
212
212
  }
213
213
 
214
+ function syncInputValue(val: string, options?: { notifyInputChange?: boolean }) {
215
+ inputValueInternal = val;
216
+ inputValue = val;
217
+
218
+ if (options?.notifyInputChange ?? true) {
219
+ onInputChange?.(val);
220
+ }
221
+ }
222
+
214
223
  function selectItem(id: string | number, label: string) {
215
224
  let newSelection: Set<string | number>;
216
225
 
@@ -218,10 +227,9 @@
218
227
  newSelection = new Set([id]);
219
228
  // Save the label persistently for restore on blur/escape
220
229
  selectedLabel = label;
221
- // Update input directly without triggering deselection
222
- inputValueInternal = label;
223
- inputValue = label;
224
- onInputChange?.(label);
230
+ // Keep the selected label visible in the input without re-triggering
231
+ // external filtering during the popover close animation.
232
+ syncInputValue(label, { notifyInputChange: false });
225
233
  if (effectiveCloseOnSelect) {
226
234
  closePopover(true); // Close and keep focus on input
227
235
  }
@@ -318,6 +326,8 @@
318
326
  onInputChange?.('');
319
327
  }
320
328
  // Otherwise user typed, keep their filter
329
+ } else {
330
+ shouldFilter = true;
321
331
  }
322
332
  setIsOpen(true);
323
333
  // Auto-focus the selected item when opening with a selection
@@ -333,8 +343,6 @@
333
343
  setIsOpen(false);
334
344
  // Reset navigation state
335
345
  navigation.reset();
336
- // Re-enable filtering for next open
337
- shouldFilter = true;
338
346
  // Only refocus input when explicitly requested (e.g., after selection)
339
347
  // Never refocus in focus mode to prevent re-opening
340
348
  if (refocusInput && trigger !== 'focus') {
@@ -442,9 +450,7 @@
442
450
  if (currentSelection.size > 0 && selectedLabel) {
443
451
  if (selectedLabel !== currentInputValue) {
444
452
  // Restore the selected label
445
- inputValueInternal = selectedLabel;
446
- inputValue = selectedLabel;
447
- onInputChange?.(selectedLabel);
453
+ syncInputValue(selectedLabel, { notifyInputChange: false });
448
454
  }
449
455
  }
450
456
  }
@@ -145,7 +145,7 @@
145
145
 
146
146
  // Scroll into view when focused (if enabled)
147
147
  $effect(() => {
148
- if (scrollOnFocus && isFocusedComputed && elementRef) {
148
+ if (scrollOnFocus && isFocusedComputed && isFocusVisibleComputed && elementRef) {
149
149
  requestAnimationFrame(() => {
150
150
  elementRef?.scrollIntoView({ block: 'nearest' });
151
151
  });
@@ -193,6 +193,15 @@
193
193
 
194
194
  const label = getResolvedTextValue();
195
195
 
196
+ if (!disableFocusHandling && elementRef) {
197
+ suppressNextFocusVisible = true;
198
+ isFocusVisible = false;
199
+ listboxCtx.setFocusVisible(false);
200
+ listboxCtx.setFocusedId(id);
201
+ listboxCtx.keyboardNav.setCurrentId(id);
202
+ focusWithModality(elementRef, 'pointer');
203
+ }
204
+
196
205
  // Use custom select handler if provided, otherwise use listbox default
197
206
  if (onItemSelect) {
198
207
  onItemSelect(id, label);
@@ -201,7 +210,7 @@
201
210
  }
202
211
 
203
212
  if (!disableFocusHandling) {
204
- listboxCtx.keyboardNav.focusById(id);
213
+ listboxCtx.keyboardNav.setCurrentId(id);
205
214
  }
206
215
  }
207
216
 
@@ -32,6 +32,8 @@
32
32
  'aria-label'?: string;
33
33
  /** Callback fired when the selection changes. */
34
34
  onChange?: (value: Set<string | number>) => void;
35
+ /** Disable DOM focus handling on the root container for virtual-focus compositions. */
36
+ disableFocusHandling?: boolean;
35
37
  };
36
38
 
37
39
  let {
@@ -47,6 +49,7 @@
47
49
  id,
48
50
  'aria-label': ariaLabel,
49
51
  onChange,
52
+ disableFocusHandling = false,
50
53
  context = $bindable(),
51
54
  element = $bindable()
52
55
  }: ListBoxProps & { context?: ListBoxContext; element?: HTMLElement } = $props();
@@ -136,6 +139,9 @@
136
139
  function handleMouseDown(event: MouseEvent) {
137
140
  trackInteractionModality(event, event.target as HTMLElement | null);
138
141
  ctx.setFocusVisible(false);
142
+ if (disableFocusHandling) {
143
+ event.preventDefault();
144
+ }
139
145
  }
140
146
 
141
147
  function handleKeyDown(event: KeyboardEvent) {
@@ -153,7 +159,7 @@
153
159
  aria-multiselectable={selectionMode === 'multiple'}
154
160
  aria-label={ariaLabel}
155
161
  class={className}
156
- tabindex="0"
162
+ tabindex={disableFocusHandling ? undefined : 0}
157
163
  data-focus-within={focusWithin || undefined}
158
164
  use:keyboardAction
159
165
  onfocusin={handleFocusIn}
@@ -26,6 +26,8 @@ declare function $$render<T extends object = object>(): {
26
26
  'aria-label'?: string;
27
27
  /** Callback fired when the selection changes. */
28
28
  onChange?: (value: Set<string | number>) => void;
29
+ /** Disable DOM focus handling on the root container for virtual-focus compositions. */
30
+ disableFocusHandling?: boolean;
29
31
  } & {
30
32
  context?: ListBoxContext;
31
33
  element?: HTMLElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@human-kit/svelte-components",
3
- "version": "1.0.0-alpha.12",
3
+ "version": "1.0.0-alpha.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",