@streamscloud/kit 0.9.18 → 0.9.19

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.
@@ -130,10 +130,15 @@
130
130
  width: 0;
131
131
  flex: 0;
132
132
  }
133
+
134
+ &--no-search {
135
+ caret-color: transparent;
136
+ cursor: pointer;
137
+ }
133
138
  }
134
139
 
135
140
  &__selection {
136
- flex: 1;
141
+ flex: 0 1 auto;
137
142
  min-width: 0;
138
143
  @include mixins.ellipsis;
139
144
  }
@@ -5,7 +5,7 @@ export type MultiselectAsyncInstance = {
5
5
  };
6
6
  import type { MultiselectBaseProps, SelectOption } from './types';
7
7
  declare function $$render<T>(): {
8
- props: Omit<MultiselectBaseProps<T>, "on" | "loadOptions" | "groupHeader" | "selectionMode" | "parentSource"> & {
8
+ props: Omit<MultiselectBaseProps<T>, "on" | "loadOptions" | "searchable" | "groupHeader" | "selectionMode" | "parentSource"> & {
9
9
  /** Server-side fetcher. Invoked with an empty query on open (initial page) and again — debounced — on every keystroke. Expected to return a flat list (no groups). */
10
10
  loadOptions: (query: string) => Promise<SelectOption<T>[]>;
11
11
  on?: {
@@ -4,7 +4,7 @@
4
4
  <script lang="ts" generics="T">import { default as MultiselectBase } from './multiselect-base.svelte';
5
5
  import { buildFuseIndex, defaultCompare, runFuseSearch } from './select-internals';
6
6
  import { isSelectGroup } from './types';
7
- const { options, value, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, groupHeader = 'toggle-all', selectionMode = 'children-only', selectedDisplay = 'checkbox', canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
7
+ const { options, value, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = false, groupHeader = 'toggle-all', selectionMode = 'children-only', selectedDisplay = 'checkbox', canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
8
8
  const fuse = $derived(buildFuseIndex(options));
9
9
  const loadOptionsShim = (q) => Promise.resolve(runFuseSearch(fuse, q, options));
10
10
  // Full unfiltered group list — fed to the create-with-parent section's parent picker so it
@@ -54,6 +54,7 @@ $effect(() => {
54
54
  placeholder={placeholder}
55
55
  disabled={disabled}
56
56
  readonly={readonly}
57
+ searchable={searchable}
57
58
  inert={inert}
58
59
  error={error}
59
60
  borderless={borderless}
@@ -31,6 +31,8 @@ declare function $$render<T>(): {
31
31
  error?: boolean;
32
32
  /** Removes the field border. @default false */
33
33
  borderless?: boolean;
34
+ /** When true, typing filters the tree; when false, it is a click/keyboard-only picker. @default false */
35
+ searchable?: boolean;
34
36
  /**
35
37
  * Group-header behavior:
36
38
  * - `'static'` — non-interactive label.
@@ -9,7 +9,7 @@ import { defaultCompare } from './select-internals';
9
9
  import { default as SelectListbox } from './select-listbox.svelte';
10
10
  import { SingleselectLocalization } from './singleselect-localization';
11
11
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_16_regular.svg?raw';
12
- const { loadOptions, debounceMs = 400, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, groupHeader = 'static', compare = defaultCompare, canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
12
+ const { loadOptions, debounceMs = 400, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
13
13
  const localization = new SingleselectLocalization();
14
14
  const selectedOption = $derived(mode.value ?? null);
15
15
  const selectionLabel = $derived(selectedOption?.label ?? '');
@@ -22,6 +22,7 @@ const core = createSelectCore({
22
22
  loadOptions: (q) => loadOptions(q),
23
23
  getDebounceMs: () => debounceMs,
24
24
  getCompare: () => compare,
25
+ getSearchable: () => searchable,
25
26
  getCanCreate: () => canCreate,
26
27
  getGroupHeader: () => groupHeader,
27
28
  isPicked: (option) => selectedOption !== null && compare(option.value, selectedOption.value),
@@ -89,6 +90,7 @@ const showClear = $derived(mode.clearable && hasValue && isInteractive && !core.
89
90
  bind:this={inputEl}
90
91
  class="singleselect__input"
91
92
  class:singleselect__input--hidden={selectionSnippet && selectedOption && !core.isOpen}
93
+ class:singleselect__input--no-search={!searchable}
92
94
  type="text"
93
95
  role="combobox"
94
96
  aria-haspopup="listbox"
@@ -290,8 +292,12 @@ background--hover}`.
290
292
  width: 0;
291
293
  flex: 0;
292
294
  }
295
+ .singleselect__input--no-search {
296
+ caret-color: transparent;
297
+ cursor: pointer;
298
+ }
293
299
  .singleselect__selection {
294
- flex: 1;
300
+ flex: 0 1 auto;
295
301
  min-width: 0;
296
302
  text-overflow: ellipsis;
297
303
  max-width: 100%;
@@ -12,6 +12,8 @@ declare function $$render<T>(): {
12
12
  loadOptions: (query: string) => Promise<SelectItem<T>[]>;
13
13
  /** Debounce window for `loadOptions` while typing. @default 400 */
14
14
  debounceMs?: number;
15
+ /** Internal — set by the sync `Singleselect` wrapper. Async typeahead is always searchable. @default true */
16
+ searchable?: boolean;
15
17
  } & {
16
18
  /** Discriminator — `true` enables the X clear button and allows `value` to be `null`. */
17
19
  clearable: true;
@@ -28,6 +30,8 @@ declare function $$render<T>(): {
28
30
  loadOptions: (query: string) => Promise<SelectItem<T>[]>;
29
31
  /** Debounce window for `loadOptions` while typing. @default 400 */
30
32
  debounceMs?: number;
33
+ /** Internal — set by the sync `Singleselect` wrapper. Async typeahead is always searchable. @default true */
34
+ searchable?: boolean;
31
35
  } & {
32
36
  /** Discriminator — `false` removes the X clear button; `value` must always be a non-null `SelectOption<T>`. */
33
37
  clearable: false;
@@ -4,7 +4,7 @@
4
4
  <script lang="ts" generics="T">import { default as SingleselectAsync } from './cmp.singleselect-async.svelte';
5
5
  import { buildFuseIndex, defaultCompare, runFuseSearch } from './select-internals';
6
6
  import { isSelectGroup } from './types';
7
- const { options, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, groupHeader = 'static', canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
7
+ const { options, compare = defaultCompare, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', canCreate, icon, chevronIcon, optionSnippet, selectionSnippet, listbox, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, ...mode } = $props();
8
8
  const fuse = $derived(buildFuseIndex(options));
9
9
  const loadOptionsShim = (q) => Promise.resolve(runFuseSearch(fuse, q, options));
10
10
  const resolvedValue = $derived.by(() => {
@@ -52,6 +52,7 @@ $effect(() => {
52
52
  inert={inert}
53
53
  error={error}
54
54
  borderless={borderless}
55
+ searchable={searchable}
55
56
  groupHeader={groupHeader}
56
57
  canCreate={canCreate}
57
58
  icon={icon}
@@ -84,6 +85,7 @@ $effect(() => {
84
85
  inert={inert}
85
86
  error={error}
86
87
  borderless={borderless}
88
+ searchable={searchable}
87
89
  groupHeader={groupHeader}
88
90
  canCreate={canCreate}
89
91
  icon={icon}
@@ -105,8 +107,9 @@ $effect(() => {
105
107
 
106
108
  <!--
107
109
  @component
108
- Singleselect — always-searchable single-value picker for static options. Click or type to
109
- open; typing filters via Fuse fuzzy match. Internally proxies to `SingleselectAsync` with a
110
+ Singleselect — single-value picker for static options. Searchable by default (click or type
111
+ to open; typing filters via Fuse fuzzy match); pass `searchable={false}` for a click/keyboard-only
112
+ picker with no filter input. Internally proxies to `SingleselectAsync` with a
110
113
  synchronous `Promise.resolve` `loadOptions` shim — visual behavior, accessibility, 600ms
111
114
  spinner gate and race-prevention are inherited verbatim. `clearable` is a required
112
115
  discriminator: `true` allows `null` and shows the X clear button; `false` requires non-null `T`.
@@ -8,6 +8,8 @@ declare function $$render<T>(): {
8
8
  props: (SingleselectCommonProps<T> & {
9
9
  /** Static option list (or grouped). Fuse filters in-memory on every keystroke — no debounce. */
10
10
  options: SelectItem<T>[];
11
+ /** When true, typing filters the options; when false, it is a click/keyboard-only picker. @default true */
12
+ searchable?: boolean;
11
13
  } & {
12
14
  /** Discriminator — `true` enables the X clear button and allows `value` to be `null`. */
13
15
  clearable: true;
@@ -22,6 +24,8 @@ declare function $$render<T>(): {
22
24
  }) | (SingleselectCommonProps<T> & {
23
25
  /** Static option list (or grouped). Fuse filters in-memory on every keystroke — no debounce. */
24
26
  options: SelectItem<T>[];
27
+ /** When true, typing filters the options; when false, it is a click/keyboard-only picker. @default true */
28
+ searchable?: boolean;
25
29
  } & {
26
30
  /** Discriminator — `false` hides the X button; `value` must always be a non-null `T`. */
27
31
  clearable: false;
@@ -62,8 +66,9 @@ interface $$IsomorphicComponent {
62
66
  z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
63
67
  }
64
68
  /**
65
- * Singleselect — always-searchable single-value picker for static options. Click or type to
66
- * open; typing filters via Fuse fuzzy match. Internally proxies to `SingleselectAsync` with a
69
+ * Singleselect — single-value picker for static options. Searchable by default (click or type
70
+ * to open; typing filters via Fuse fuzzy match); pass `searchable={false}` for a click/keyboard-only
71
+ * picker with no filter input. Internally proxies to `SingleselectAsync` with a
67
72
  * synchronous `Promise.resolve` `loadOptions` shim — visual behavior, accessibility, 600ms
68
73
  * spinner gate and race-prevention are inherited verbatim. `clearable` is a required
69
74
  * discriminator: `true` allows `null` and shows the X clear button; `false` requires non-null `T`.
@@ -13,7 +13,7 @@ import { default as SelectListbox } from './select-listbox.svelte';
13
13
  import IconCheckmark from '@fluentui/svg-icons/icons/checkmark_16_regular.svg?raw';
14
14
  import IconChevronDown from '@fluentui/svg-icons/icons/chevron_down_16_regular.svg?raw';
15
15
  import { dndzone } from 'svelte-dnd-action';
16
- const { loadOptions, debounceMs = 400, value, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, groupHeader = 'static', compare = defaultCompare, selectionMode = 'children-only', selectedDisplay = 'checkmark', canCreate, chipMode = 'inline', reorderable = false, maxVisible = Infinity, showFullItem = false, chipVariant, showChevron = true, hideSelected = false, icon, chevronIcon, optionSnippet, chipSnippet, selectionSnippet, parentSource, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
16
+ const { loadOptions, debounceMs = 400, value, size = 'md', placeholder = '', disabled = false, readonly = false, inert = false, error = false, borderless = false, searchable = true, groupHeader = 'static', compare = defaultCompare, selectionMode = 'children-only', selectedDisplay = 'checkmark', canCreate, chipMode = 'inline', reorderable = false, maxVisible = Infinity, showFullItem = false, chipVariant, showChevron = true, hideSelected = false, icon, chevronIcon, optionSnippet, chipSnippet, selectionSnippet, parentSource, id, name, autocomplete = 'off', 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, 'aria-required': ariaRequired, on } = $props();
17
17
  const localization = new MultiselectLocalization();
18
18
  const isPicked = (v) => value.some((existing) => compare(existing.value, v));
19
19
  const hasValue = $derived(value.length > 0);
@@ -34,6 +34,7 @@ const core = createSelectCore({
34
34
  loadOptions: (q) => loadOptions(q),
35
35
  getDebounceMs: () => debounceMs,
36
36
  getCompare: () => compare,
37
+ getSearchable: () => searchable,
37
38
  getCanCreate: () => canCreate,
38
39
  getHideCreateRow: () => showCreateSection,
39
40
  getGroupHeader: () => groupHeader,
@@ -215,6 +216,7 @@ const handleDndFinalize = (e) => {
215
216
  <input
216
217
  bind:this={inputEl}
217
218
  class="multiselect-trigger__input"
219
+ class:multiselect-trigger__input--no-search={!searchable}
218
220
  type="text"
219
221
  role="combobox"
220
222
  aria-haspopup="listbox"
@@ -506,8 +508,12 @@ padding-inline, font-size, gap, max-width, remove-color, remove-color--hover}`.
506
508
  width: 0;
507
509
  flex: 0;
508
510
  }
511
+ .multiselect-trigger__input--no-search {
512
+ caret-color: transparent;
513
+ cursor: pointer;
514
+ }
509
515
  .multiselect-trigger__selection {
510
- flex: 1;
516
+ flex: 0 1 auto;
511
517
  min-width: 0;
512
518
  text-overflow: ellipsis;
513
519
  max-width: 100%;
@@ -11,6 +11,8 @@ export type SelectCoreConfig<T> = {
11
11
  getDebounceMs?: () => number;
12
12
  /** Equality comparator for `T`. */
13
13
  getCompare: () => (a: T, b: T) => boolean;
14
+ /** When false, typing never enters filtering mode — the popover is a click/keyboard-only picker. @default true */
15
+ getSearchable?: () => boolean;
14
16
  /** Optional `canCreate` validator — presence enables creatable mode. */
15
17
  getCanCreate: () => ((q: string) => boolean) | undefined;
16
18
  /** When true, no `kind:'create'` row is emitted (the host renders its own create UI) — keyboard navigation must not reach a row the listbox doesn't show. @default false */
@@ -281,7 +281,7 @@ export function createSelectCore(config) {
281
281
  }
282
282
  };
283
283
  const handleInput = (e) => {
284
- if (!config.getInteractive()) {
284
+ if (!config.getInteractive() || config.getSearchable?.() === false) {
285
285
  return;
286
286
  }
287
287
  const target = e.target;
@@ -327,11 +327,11 @@ export function createSelectCore(config) {
327
327
  closePopover();
328
328
  config.getInputEl()?.focus();
329
329
  }
330
- else if (e.key === 'Backspace' && (!isFiltering || !query)) {
330
+ else if (e.key === 'Backspace' && (!isFiltering || !query) && config.getSearchable?.() !== false) {
331
331
  e.preventDefault();
332
332
  config.onClear();
333
333
  }
334
- else if (!isFiltering && isPrintableKey(e)) {
334
+ else if (config.getSearchable?.() !== false && !isFiltering && isPrintableKey(e)) {
335
335
  e.preventDefault();
336
336
  isFiltering = true;
337
337
  query = e.key;
@@ -127,6 +127,8 @@ export type MultiselectBaseProps<T> = {
127
127
  error?: boolean;
128
128
  /** Removes the field border. @default false */
129
129
  borderless?: boolean;
130
+ /** When false, typing never enters filtering mode — the input stays the combobox anchor (focus/ARIA/keyboard) but is read-only. A click/keyboard-only picker. @default true */
131
+ searchable?: boolean;
130
132
  /**
131
133
  * Group-header behavior:
132
134
  * - `'static'` — non-interactive label (default).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.9.18",
3
+ "version": "0.9.19",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",