@casinogate/ui 1.10.6 → 1.10.8
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.
package/dist/assets/css/root.css
CHANGED
|
@@ -369,6 +369,9 @@
|
|
|
369
369
|
.cgui\:h-\(--cg-ui-max-content-height\) {
|
|
370
370
|
height: var(--cg-ui-max-content-height);
|
|
371
371
|
}
|
|
372
|
+
.cgui\:h-1 {
|
|
373
|
+
height: calc(var(--cgui-spacing) * 1);
|
|
374
|
+
}
|
|
372
375
|
.cgui\:h-3 {
|
|
373
376
|
height: calc(var(--cgui-spacing) * 3);
|
|
374
377
|
}
|
|
@@ -450,6 +453,9 @@
|
|
|
450
453
|
.cgui\:max-h-75 {
|
|
451
454
|
max-height: calc(var(--cgui-spacing) * 75);
|
|
452
455
|
}
|
|
456
|
+
.cgui\:max-h-80 {
|
|
457
|
+
max-height: calc(var(--cgui-spacing) * 80);
|
|
458
|
+
}
|
|
453
459
|
.cgui\:min-h-7\.5 {
|
|
454
460
|
min-height: calc(var(--cgui-spacing) * 7.5);
|
|
455
461
|
}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { boolAttr } from '../../internal/utils/attrs.js';
|
|
6
6
|
import { cn } from '../../internal/utils/common.js';
|
|
7
|
-
import { watch } from 'runed';
|
|
7
|
+
import { Debounced, watch } from 'runed';
|
|
8
8
|
import type { AnyFn } from 'svelte-toolbelt';
|
|
9
9
|
import { onMountEffect } from 'svelte-toolbelt';
|
|
10
|
+
import type { Attachment } from 'svelte/attachments';
|
|
10
11
|
import { cubicInOut } from 'svelte/easing';
|
|
11
12
|
import { fly } from 'svelte/transition';
|
|
12
13
|
import { CommandApi, CommandPrimitive } from '../command/index.js';
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
fullWidth = true,
|
|
36
37
|
closeOnSelect = true,
|
|
37
38
|
|
|
39
|
+
maxContentHeight,
|
|
40
|
+
|
|
38
41
|
loading: loadingSnippet,
|
|
39
42
|
pageSize = 10,
|
|
40
43
|
callback,
|
|
@@ -49,13 +52,14 @@
|
|
|
49
52
|
|
|
50
53
|
let items = $state<CommandItem[]>(initialItems);
|
|
51
54
|
let isLoading = $state(false);
|
|
52
|
-
let
|
|
55
|
+
let isSearching = $state(false);
|
|
56
|
+
let hasMore = $state(false);
|
|
53
57
|
let currentPage = $state(1);
|
|
54
58
|
let error = $state<string | null>(null);
|
|
55
|
-
let viewportRef = $state<HTMLElement | null>(null);
|
|
56
|
-
let searchTimeout = $state<ReturnType<typeof setTimeout> | null>(null);
|
|
57
59
|
let lastSearch = $state('');
|
|
58
60
|
|
|
61
|
+
let listRef = $state<HTMLElement | null>(null);
|
|
62
|
+
|
|
59
63
|
const createCollection = (itemList: CommandItem[]): CommandCollection => {
|
|
60
64
|
const groupOrderMap = new Map<string, number>();
|
|
61
65
|
groups.forEach((g, index) => {
|
|
@@ -93,6 +97,7 @@
|
|
|
93
97
|
if (isLoading) return;
|
|
94
98
|
|
|
95
99
|
isLoading = true;
|
|
100
|
+
isSearching = !append;
|
|
96
101
|
error = null;
|
|
97
102
|
|
|
98
103
|
try {
|
|
@@ -107,53 +112,34 @@
|
|
|
107
112
|
currentPage = page;
|
|
108
113
|
hasMore = result.hasMore;
|
|
109
114
|
lastSearch = search;
|
|
110
|
-
|
|
111
|
-
// Check if viewport needs more items after render
|
|
112
|
-
requestAnimationFrame(() => {
|
|
113
|
-
checkNeedMoreItems();
|
|
114
|
-
});
|
|
115
115
|
} catch (err) {
|
|
116
116
|
error = err instanceof Error ? err.message : 'Failed to fetch data';
|
|
117
117
|
} finally {
|
|
118
118
|
isLoading = false;
|
|
119
|
-
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Auto-fill viewport if content doesn't overflow
|
|
123
|
-
const checkNeedMoreItems = () => {
|
|
124
|
-
if (!viewportRef || isLoading || !hasMore) return;
|
|
125
|
-
|
|
126
|
-
const { scrollHeight, clientHeight } = viewportRef;
|
|
127
|
-
|
|
128
|
-
// If content doesn't fill viewport, load more
|
|
129
|
-
if (scrollHeight <= clientHeight) {
|
|
130
|
-
loadMore();
|
|
119
|
+
isSearching = false;
|
|
131
120
|
}
|
|
132
121
|
};
|
|
133
122
|
|
|
134
123
|
// Debounced search handler
|
|
135
|
-
const
|
|
136
|
-
if (searchTimeout) {
|
|
137
|
-
clearTimeout(searchTimeout);
|
|
138
|
-
}
|
|
124
|
+
const debouncedSearchValue = new Debounced(() => searchValue, searchDebounce);
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}, searchDebounce);
|
|
126
|
+
const search = (search: string) => {
|
|
127
|
+
currentPage = 1;
|
|
128
|
+
fetchData(1, search, false);
|
|
144
129
|
};
|
|
145
130
|
|
|
146
131
|
// Watch for search value changes
|
|
147
132
|
watch(
|
|
148
|
-
() =>
|
|
133
|
+
() => debouncedSearchValue.current,
|
|
149
134
|
() => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
search(debouncedSearchValue.current);
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
lazy: true,
|
|
153
139
|
}
|
|
154
140
|
);
|
|
155
141
|
|
|
156
|
-
// Fetch on first open
|
|
142
|
+
// Fetch on first open
|
|
157
143
|
watch(
|
|
158
144
|
() => open,
|
|
159
145
|
() => {
|
|
@@ -164,11 +150,6 @@
|
|
|
164
150
|
fetchData(1, searchValue);
|
|
165
151
|
return;
|
|
166
152
|
}
|
|
167
|
-
|
|
168
|
-
// If already have items, check if viewport needs more after it renders
|
|
169
|
-
requestAnimationFrame(() => {
|
|
170
|
-
checkNeedMoreItems();
|
|
171
|
-
});
|
|
172
153
|
}
|
|
173
154
|
);
|
|
174
155
|
|
|
@@ -181,9 +162,6 @@
|
|
|
181
162
|
$effect(() => {
|
|
182
163
|
return () => {
|
|
183
164
|
currentPage = 1;
|
|
184
|
-
if (searchTimeout) {
|
|
185
|
-
clearTimeout(searchTimeout);
|
|
186
|
-
}
|
|
187
165
|
};
|
|
188
166
|
});
|
|
189
167
|
|
|
@@ -221,13 +199,21 @@
|
|
|
221
199
|
}
|
|
222
200
|
};
|
|
223
201
|
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
|
|
202
|
+
const anchorAttachment: Attachment = (el) => {
|
|
203
|
+
const observer = new IntersectionObserver(
|
|
204
|
+
(entries) => {
|
|
205
|
+
if (entries[0]?.isIntersecting) {
|
|
206
|
+
loadMore();
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{ threshold: 0 }
|
|
210
|
+
);
|
|
227
211
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
observer.observe(el);
|
|
213
|
+
|
|
214
|
+
return () => {
|
|
215
|
+
observer.disconnect();
|
|
216
|
+
};
|
|
231
217
|
};
|
|
232
218
|
|
|
233
219
|
const retry = () => {
|
|
@@ -236,9 +222,9 @@
|
|
|
236
222
|
};
|
|
237
223
|
|
|
238
224
|
const scrollToTop = () => {
|
|
239
|
-
if (!
|
|
225
|
+
if (!listRef) return;
|
|
240
226
|
|
|
241
|
-
|
|
227
|
+
listRef.scrollTo({
|
|
242
228
|
top: 0,
|
|
243
229
|
behavior: 'smooth',
|
|
244
230
|
});
|
|
@@ -343,14 +329,14 @@
|
|
|
343
329
|
|
|
344
330
|
<PopoverPrimitive.Portal disabled={portalDisabled}>
|
|
345
331
|
<PopoverPrimitive.Content class="cgui:overflow-hidden cgui:rounded-md">
|
|
346
|
-
<CommandPrimitive.Root shouldFilter={false}>
|
|
332
|
+
<CommandPrimitive.Root shouldFilter={false} class="cgui:max-h-80">
|
|
347
333
|
<CommandPrimitive.Input bind:value={searchValue} placeholder={searchPlaceholder} />
|
|
348
334
|
|
|
349
|
-
<CommandPrimitive.List class="cgui:rounded-inherit">
|
|
350
|
-
<CommandPrimitive.Viewport
|
|
335
|
+
<CommandPrimitive.List class="cgui:rounded-inherit" bind:ref={listRef} style={{ maxHeight: maxContentHeight }}>
|
|
336
|
+
<CommandPrimitive.Viewport class={cn('cgui:relative')}>
|
|
351
337
|
{#if error}
|
|
352
338
|
{@render errorState()}
|
|
353
|
-
{:else if
|
|
339
|
+
{:else if isSearching}
|
|
354
340
|
{#if loadingSnippet}
|
|
355
341
|
{@render loadingSnippet()}
|
|
356
342
|
{:else}
|
|
@@ -380,22 +366,29 @@
|
|
|
380
366
|
{/if}
|
|
381
367
|
{/each}
|
|
382
368
|
|
|
383
|
-
{#if
|
|
384
|
-
{
|
|
369
|
+
{#if hasMore}
|
|
370
|
+
{#key items.length}
|
|
371
|
+
<div class="cgui:h-1 cgui:w-full" {@attach anchorAttachment}></div>
|
|
372
|
+
{/key}
|
|
385
373
|
{/if}
|
|
386
374
|
|
|
387
|
-
{#if
|
|
388
|
-
|
|
389
|
-
class="cgui:w-full cgui:flex cgui:justify-center cgui:cursor-pointer cgui:items-center cgui:text-xs cgui:text-icon-default cgui:underline cgui:hover:no-underline"
|
|
390
|
-
onclick={scrollToTop}
|
|
391
|
-
>
|
|
392
|
-
<IconComponent.ChevronUp />
|
|
393
|
-
</button>
|
|
375
|
+
{#if isLoading}
|
|
376
|
+
{@render loadingMore()}
|
|
394
377
|
{/if}
|
|
395
378
|
{:else}
|
|
396
379
|
<CommandPrimitive.Empty>No results found</CommandPrimitive.Empty>
|
|
397
380
|
{/if}
|
|
398
381
|
</CommandPrimitive.Viewport>
|
|
382
|
+
|
|
383
|
+
{#if !hasMore && items.length > pageSize}
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
class="cgui:w-full cgui:flex cgui:justify-center cgui:cursor-pointer cgui:items-center cgui:text-xs cgui:text-icon-default cgui:underline cgui:hover:no-underline"
|
|
387
|
+
onclick={scrollToTop}
|
|
388
|
+
>
|
|
389
|
+
<IconComponent.ChevronUp />
|
|
390
|
+
</button>
|
|
391
|
+
{/if}
|
|
399
392
|
</CommandPrimitive.List>
|
|
400
393
|
</CommandPrimitive.Root>
|
|
401
394
|
</PopoverPrimitive.Content>
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
rounded = 'default',
|
|
30
30
|
fullWidth = true,
|
|
31
31
|
closeOnSelect = true,
|
|
32
|
+
maxContentHeight,
|
|
32
33
|
...restProps
|
|
33
34
|
}: ComboboxProps = $props();
|
|
34
35
|
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
<CommandPrimitive.Root>
|
|
127
128
|
<CommandPrimitive.Input bind:value={searchValue} placeholder={searchPlaceholder} />
|
|
128
129
|
|
|
129
|
-
<CommandPrimitive.List class="cgui:rounded-inherit">
|
|
130
|
+
<CommandPrimitive.List class="cgui:rounded-inherit" style={{ maxHeight: maxContentHeight }}>
|
|
130
131
|
<CommandPrimitive.Viewport>
|
|
131
132
|
<CommandPrimitive.Empty>No results found</CommandPrimitive.Empty>
|
|
132
133
|
|
|
@@ -14,6 +14,7 @@ export type ComboboxProps = PrimitivePopoverRootProps & ComboboxTriggerVariantsP
|
|
|
14
14
|
allowDeselect?: boolean;
|
|
15
15
|
triggerClass?: string;
|
|
16
16
|
closeOnSelect?: boolean;
|
|
17
|
+
maxContentHeight?: string;
|
|
17
18
|
trigger?: Snippet<[{
|
|
18
19
|
props: Record<string, unknown>;
|
|
19
20
|
displayValue: string;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { cn } from '../../internal/utils/common.js';
|
|
8
8
|
import { watch } from 'runed';
|
|
9
9
|
import { onMountEffect } from 'svelte-toolbelt';
|
|
10
|
+
import type { Attachment } from 'svelte/attachments';
|
|
10
11
|
import { cubicInOut } from 'svelte/easing';
|
|
11
12
|
import { fly } from 'svelte/transition';
|
|
12
13
|
import Content from './components/select.content.svelte';
|
|
@@ -208,15 +209,32 @@
|
|
|
208
209
|
}
|
|
209
210
|
};
|
|
210
211
|
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
|
|
212
|
+
const anchorAttachment: Attachment = (el) => {
|
|
213
|
+
const observer = new IntersectionObserver(
|
|
214
|
+
(entries) => {
|
|
215
|
+
if (entries[0]?.isIntersecting) {
|
|
216
|
+
loadMore();
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
{ threshold: 0 }
|
|
220
|
+
);
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
observer.observe(el);
|
|
223
|
+
|
|
224
|
+
return () => {
|
|
225
|
+
observer.disconnect();
|
|
226
|
+
};
|
|
218
227
|
};
|
|
219
228
|
|
|
229
|
+
// const handleScroll = (event: Event) => {
|
|
230
|
+
// const viewport = event.target as HTMLElement;
|
|
231
|
+
// const scrollBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
232
|
+
|
|
233
|
+
// if (scrollBottom < 100 && hasMore && !isLoading) {
|
|
234
|
+
// loadMore();
|
|
235
|
+
// }
|
|
236
|
+
// };
|
|
237
|
+
|
|
220
238
|
const retry = () => {
|
|
221
239
|
error = null;
|
|
222
240
|
fetchData(currentPage);
|
|
@@ -272,6 +290,12 @@
|
|
|
272
290
|
</div>
|
|
273
291
|
{/snippet}
|
|
274
292
|
|
|
293
|
+
{#if hasMore}
|
|
294
|
+
{#key items.length}
|
|
295
|
+
<div class="cgui:h-1 cgui:w-full" {@attach anchorAttachment}></div>
|
|
296
|
+
{/key}
|
|
297
|
+
{/if}
|
|
298
|
+
|
|
275
299
|
{#snippet loadingMore()}
|
|
276
300
|
<div class="cgui:p-2 cgui:flex cgui:items-center cgui:justify-center cgui:text-icon-default">
|
|
277
301
|
<Spinner />
|
|
@@ -302,7 +326,7 @@
|
|
|
302
326
|
|
|
303
327
|
<Portal>
|
|
304
328
|
<Content bind:ref={contentRef} class={cn(contentClass)} {...contentProps}>
|
|
305
|
-
<Viewport
|
|
329
|
+
<Viewport bind:ref={viewportRef}>
|
|
306
330
|
{#if error}
|
|
307
331
|
{@render errorState()}
|
|
308
332
|
{:else if isLoading && !hasResults}
|