@x33025/sveltely 0.1.0 → 0.1.1

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.
@@ -8,7 +8,7 @@
8
8
  <script lang="ts">
9
9
  import Dropdown from './Dropdown.svelte';
10
10
 
11
- const items = [
11
+ const allItems = [
12
12
  {
13
13
  type: 'group' as const,
14
14
  label: 'Publishing',
@@ -48,7 +48,35 @@
48
48
  }
49
49
  ];
50
50
 
51
+ const filterEntries = (entries: typeof allItems, query: string): typeof allItems => {
52
+ const normalizedQuery = query.trim().toLowerCase();
53
+ if (!normalizedQuery) return allItems;
54
+
55
+ return entries.flatMap((entry) => {
56
+ if (entry.type === 'group') {
57
+ const nextItems = filterEntries(entry.items, normalizedQuery);
58
+ return nextItems.length > 0 ? [{ ...entry, items: nextItems }] : [];
59
+ }
60
+
61
+ if (entry.type === 'submenu') {
62
+ const nextItems = filterEntries(entry.items, normalizedQuery);
63
+ const matches = entry.label.toLowerCase().includes(normalizedQuery);
64
+ if (!matches && nextItems.length === 0) {
65
+ return [];
66
+ }
67
+ return [{ ...entry, items: matches ? entry.items : nextItems }];
68
+ }
69
+
70
+ return entry.label.toLowerCase().includes(normalizedQuery) ? [entry] : [];
71
+ });
72
+ };
73
+
74
+ let items = $state(allItems);
51
75
  let value = $state<string | null>('draft');
76
+
77
+ const handleSearch = async (query: string) => {
78
+ items = filterEntries(allItems, query);
79
+ };
52
80
  </script>
53
81
 
54
- <Dropdown {items} bind:value placeholder="Choose status" searchable={true} />
82
+ <Dropdown {items} bind:value placeholder="Choose status" onSearch={handleSearch} />
@@ -21,9 +21,9 @@
21
21
  disabled?: boolean;
22
22
  closeOnSelect?: boolean;
23
23
  showCheck?: boolean;
24
- searchable?: boolean;
25
24
  searchPlaceholder?: string;
26
25
  placement?: Anchor;
26
+ onSearch?: (query: string) => void | Promise<void>;
27
27
  onSelect?: (item: DropdownItem<T>) => void;
28
28
  } & StyleProps;
29
29
 
@@ -43,9 +43,9 @@
43
43
  disabled = false,
44
44
  closeOnSelect = true,
45
45
  showCheck = true,
46
- searchable = false,
47
46
  searchPlaceholder = 'Search',
48
47
  placement = 'bottom',
48
+ onSearch,
49
49
  onSelect,
50
50
  ...styleProps
51
51
  }: Props = $props();
@@ -53,6 +53,9 @@
53
53
  const dropdownStyle = $derived.by(() => surfaceStyle(styleProps, 'dropdown'));
54
54
 
55
55
  let query = $state('');
56
+ let lastSearchedQuery = '';
57
+
58
+ const searchEnabled = $derived(!!onSearch);
56
59
 
57
60
  const flattenItems = (
58
61
  entries: DropdownEntry<T>[],
@@ -69,25 +72,6 @@
69
72
  return [{ ...entry, disabled: nextDisabled || entry.disabled }];
70
73
  });
71
74
 
72
- const filterEntries = (entries: DropdownEntry<T>[]): DropdownEntry<T>[] =>
73
- entries.flatMap((entry) => {
74
- if (isGroup(entry)) {
75
- const nextItems = filterEntries(entry.items);
76
- return nextItems.length > 0 ? [{ ...entry, items: nextItems }] : [];
77
- }
78
-
79
- if (isSubmenu(entry)) {
80
- const nextItems = filterEntries(entry.items);
81
- const matches = entry.label.toLowerCase().includes(normalizedQuery);
82
- if (!matches && nextItems.length === 0) {
83
- return [];
84
- }
85
- return [{ ...entry, items: matches ? entry.items : nextItems }];
86
- }
87
-
88
- return entry.label.toLowerCase().includes(normalizedQuery) ? [entry] : [];
89
- });
90
-
91
75
  const findFirstRenderableLabel = (entries: DropdownEntry<T>[]): string | null => {
92
76
  for (const entry of entries) {
93
77
  if (isGroup(entry)) {
@@ -100,8 +84,6 @@
100
84
  return null;
101
85
  };
102
86
 
103
- const normalizedQuery = $derived(query.trim().toLowerCase());
104
-
105
87
  const flatItems = $derived.by(() => flattenItems(items));
106
88
 
107
89
  const selectedItem = $derived.by(
@@ -110,14 +92,12 @@
110
92
 
111
93
  const triggerText = $derived(selectedItem?.label ?? placeholder);
112
94
 
113
- const filteredItems = $derived.by(() =>
114
- normalizedQuery ? filterEntries(items) : items
115
- );
95
+ const filteredItems = $derived(items);
116
96
 
117
97
  const firstRenderableLabel = $derived.by(() => findFirstRenderableLabel(filteredItems));
118
98
 
119
99
  const itemRadiusSourceEnabled = $derived.by(() => {
120
- if (searchable) return false;
100
+ if (searchEnabled) return false;
121
101
  const firstEntry = filteredItems[0];
122
102
  if (!firstEntry) return false;
123
103
  if (isGroup(firstEntry)) {
@@ -143,6 +123,13 @@
143
123
  disabled || inheritedDisabled || !!entry.disabled;
144
124
 
145
125
  const isRadiusSource = (label: string) => itemRadiusSourceEnabled && label === firstRenderableLabel;
126
+
127
+ $effect(() => {
128
+ if (!onSearch) return;
129
+ if (query === lastSearchedQuery) return;
130
+ lastSearchedQuery = query;
131
+ void onSearch(query);
132
+ });
146
133
  </script>
147
134
 
148
135
  <Floating
@@ -174,9 +161,9 @@
174
161
 
175
162
  <div
176
163
  class="dropdown-content vstack"
177
- style={`${dropdownStyle} --dropdown-item-radius: ${searchable ? 'var(--sveltely-border-radius)' : 'var(--sveltely-border-radius-nested)'};`}
164
+ style={`${dropdownStyle} --dropdown-item-radius: ${searchEnabled ? 'var(--sveltely-border-radius)' : 'var(--sveltely-border-radius-nested)'};`}
178
165
  >
179
- {#if searchable}
166
+ {#if searchEnabled}
180
167
  <SearchInput
181
168
  bind:value={query}
182
169
  placeholder={searchPlaceholder}
@@ -11,9 +11,9 @@ declare function $$render<T extends string | number = string>(): {
11
11
  disabled?: boolean;
12
12
  closeOnSelect?: boolean;
13
13
  showCheck?: boolean;
14
- searchable?: boolean;
15
14
  searchPlaceholder?: string;
16
15
  placement?: Anchor;
16
+ onSearch?: (query: string) => void | Promise<void>;
17
17
  onSelect?: (item: DropdownItem<T>) => void;
18
18
  } & StyleProps;
19
19
  exports: {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x33025/sveltely",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",