@placeholderco/placeholder-ui 3.0.1 → 3.0.3
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/form/Autocomplete.svelte +2 -0
- package/dist/form/AutocompleteMulti.svelte +1 -0
- package/dist/form/Select.svelte +1 -0
- package/dist/form/SelectMulti.svelte +1 -0
- package/dist/form/comboBoxCore.svelte.d.ts +7 -1
- package/dist/form/comboBoxCore.svelte.js +31 -4
- package/dist/models/ComboBoxItem.d.ts +6 -1
- package/dist/ui/Button.svelte +8 -0
- package/dist/ui/Button.svelte.d.ts +4 -0
- package/dist/ui/Link.svelte +4 -0
- package/dist/ui/Link.svelte.d.ts +2 -0
- package/dist/ui/Table.svelte +1 -1
- package/dist/util/NavigateTo.d.ts +6 -1
- package/dist/util/NavigateTo.js +24 -2
- package/dist/util/interceptLinkClick.js +19 -2
- package/package.json +7 -7
|
@@ -242,6 +242,7 @@
|
|
|
242
242
|
|
|
243
243
|
function onFilterChange(filterValue: string) {
|
|
244
244
|
if (filterValue.length === 0) {
|
|
245
|
+
core.cancelSearch();
|
|
245
246
|
core.filterLocal('');
|
|
246
247
|
open = true;
|
|
247
248
|
return;
|
|
@@ -250,6 +251,7 @@
|
|
|
250
251
|
if (searchFunction && filterValue) {
|
|
251
252
|
core.search(filterValue, () => (open = true));
|
|
252
253
|
} else {
|
|
254
|
+
core.cancelSearch();
|
|
253
255
|
core.filterLocal(filterValue);
|
|
254
256
|
open = true;
|
|
255
257
|
}
|
package/dist/form/Select.svelte
CHANGED
|
@@ -82,8 +82,14 @@ export declare abstract class ComboBoxDataCore<TConfig extends ComboBoxDataConfi
|
|
|
82
82
|
abstract preloadValues(): boolean;
|
|
83
83
|
convertOptions(options: ComboBoxItem[]): void;
|
|
84
84
|
convertGroupOptions(groups: ComboBoxGroup[]): void;
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Debounced remote search via searchFunction. Each call supersedes the previous one:
|
|
87
|
+
* a pending debounce is dropped and an in-flight request is aborted, so only the
|
|
88
|
+
* latest query's results are ever applied.
|
|
89
|
+
*/
|
|
86
90
|
search(filterValue: string, onComplete?: () => void): void;
|
|
91
|
+
/** Drop any pending or in-flight remote search, e.g. when the input switches to local filtering. */
|
|
92
|
+
cancelSearch(): void;
|
|
87
93
|
/** Case-insensitive local filter over already-loaded options. */
|
|
88
94
|
filterLocal(filterValue: string): void;
|
|
89
95
|
}
|
|
@@ -21,6 +21,7 @@ export class ComboBoxDataCore {
|
|
|
21
21
|
searching = $state(false);
|
|
22
22
|
config;
|
|
23
23
|
#timeout;
|
|
24
|
+
#searchController;
|
|
24
25
|
#fetchInitialized = false;
|
|
25
26
|
#lastOptionsRef;
|
|
26
27
|
#lastGroupedRef;
|
|
@@ -62,7 +63,7 @@ export class ComboBoxDataCore {
|
|
|
62
63
|
this.preloadValues();
|
|
63
64
|
cfg.onOptionsChanged?.();
|
|
64
65
|
});
|
|
65
|
-
$effect(() => () =>
|
|
66
|
+
$effect(() => () => this.#abortPendingSearch());
|
|
66
67
|
}
|
|
67
68
|
convertOptions(options) {
|
|
68
69
|
const items = options.map((option) => ({ ...option, selected: this.isSelected(option) }));
|
|
@@ -80,18 +81,44 @@ export class ComboBoxDataCore {
|
|
|
80
81
|
}));
|
|
81
82
|
this.filteredGroups = [...this.allGroups];
|
|
82
83
|
}
|
|
83
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Debounced remote search via searchFunction. Each call supersedes the previous one:
|
|
86
|
+
* a pending debounce is dropped and an in-flight request is aborted, so only the
|
|
87
|
+
* latest query's results are ever applied.
|
|
88
|
+
*/
|
|
84
89
|
search(filterValue, onComplete) {
|
|
85
|
-
|
|
90
|
+
this.#abortPendingSearch();
|
|
86
91
|
this.searching = true;
|
|
87
92
|
this.#timeout = setTimeout(() => {
|
|
88
|
-
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
this.#searchController = controller;
|
|
95
|
+
this.config.getSearchFunction()(filterValue, controller.signal).then((response) => {
|
|
96
|
+
if (controller.signal.aborted)
|
|
97
|
+
return;
|
|
98
|
+
this.#searchController = undefined;
|
|
89
99
|
this.convertOptions(response.object ?? []);
|
|
90
100
|
this.searching = false;
|
|
91
101
|
onComplete?.();
|
|
102
|
+
}, (error) => {
|
|
103
|
+
// Aborted requests reject by design (e.g. fetch's AbortError) — swallow those.
|
|
104
|
+
if (controller.signal.aborted)
|
|
105
|
+
return;
|
|
106
|
+
this.#searchController = undefined;
|
|
107
|
+
this.searching = false;
|
|
108
|
+
throw error;
|
|
92
109
|
});
|
|
93
110
|
}, 300);
|
|
94
111
|
}
|
|
112
|
+
/** Drop any pending or in-flight remote search, e.g. when the input switches to local filtering. */
|
|
113
|
+
cancelSearch() {
|
|
114
|
+
this.#abortPendingSearch();
|
|
115
|
+
this.searching = false;
|
|
116
|
+
}
|
|
117
|
+
#abortPendingSearch() {
|
|
118
|
+
clearTimeout(this.#timeout);
|
|
119
|
+
this.#searchController?.abort();
|
|
120
|
+
this.#searchController = undefined;
|
|
121
|
+
}
|
|
95
122
|
/** Case-insensitive local filter over already-loaded options. */
|
|
96
123
|
filterLocal(filterValue) {
|
|
97
124
|
if (filterValue.length === 0) {
|
|
@@ -23,5 +23,10 @@ export interface ComboBoxItem {
|
|
|
23
23
|
isAddNew?: boolean;
|
|
24
24
|
}
|
|
25
25
|
export type FetchFunctionType = () => Promise<NotifyModel<ComboBoxItem[]>>;
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Remote search. `signal` is aborted when a newer search supersedes this one (the user
|
|
28
|
+
* kept typing, cleared the input, or the component unmounted) — pass it to `fetch` to
|
|
29
|
+
* cancel the request. Results from superseded searches are discarded either way.
|
|
30
|
+
*/
|
|
31
|
+
export type SearchFunctionType = (query: string, signal?: AbortSignal) => Promise<NotifyModel<ComboBoxItem[]>>;
|
|
27
32
|
export type RetrieveLabelFunctionType = (value: any) => Promise<NotifyModel<string | undefined>>;
|
package/dist/ui/Button.svelte
CHANGED
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
iconSize?: string;
|
|
29
29
|
/** Link target attribute (when href is provided) */
|
|
30
30
|
target?: undefined | '_self' | '_blank';
|
|
31
|
+
/** Relationship attribute (when href is provided). `rel="external"` skips client-side navigation */
|
|
32
|
+
rel?: string;
|
|
33
|
+
/** Download the linked URL instead of navigating (when href is provided). `true` keeps the server's filename */
|
|
34
|
+
download?: boolean | string;
|
|
31
35
|
/** Button type attribute (when rendered as a button, not a link) */
|
|
32
36
|
type?: 'button' | 'submit' | 'reset';
|
|
33
37
|
/** Additional CSS classes */
|
|
@@ -51,6 +55,8 @@
|
|
|
51
55
|
rightSvg = undefined,
|
|
52
56
|
iconSize = '18px',
|
|
53
57
|
target = undefined,
|
|
58
|
+
rel = undefined,
|
|
59
|
+
download = undefined,
|
|
54
60
|
type = 'button',
|
|
55
61
|
class: buttonClass = '',
|
|
56
62
|
children,
|
|
@@ -89,6 +95,8 @@
|
|
|
89
95
|
{#if isLink && href && !disabled}
|
|
90
96
|
<a
|
|
91
97
|
{target}
|
|
98
|
+
{rel}
|
|
99
|
+
download={download === true ? '' : download || undefined}
|
|
92
100
|
class={elClass}
|
|
93
101
|
aria-label={ariaLabel}
|
|
94
102
|
href={href?.startsWith('/') ? resolve(href as any) : href}
|
|
@@ -22,6 +22,10 @@ export interface ButtonProps {
|
|
|
22
22
|
iconSize?: string;
|
|
23
23
|
/** Link target attribute (when href is provided) */
|
|
24
24
|
target?: undefined | '_self' | '_blank';
|
|
25
|
+
/** Relationship attribute (when href is provided). `rel="external"` skips client-side navigation */
|
|
26
|
+
rel?: string;
|
|
27
|
+
/** Download the linked URL instead of navigating (when href is provided). `true` keeps the server's filename */
|
|
28
|
+
download?: boolean | string;
|
|
25
29
|
/** Button type attribute (when rendered as a button, not a link) */
|
|
26
30
|
type?: 'button' | 'submit' | 'reset';
|
|
27
31
|
/** Additional CSS classes */
|
package/dist/ui/Link.svelte
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
rel?: string;
|
|
11
11
|
/** Link target attribute */
|
|
12
12
|
target?: '_self' | '_blank' | undefined;
|
|
13
|
+
/** Download the linked URL instead of navigating. `true` keeps the server's filename */
|
|
14
|
+
download?: boolean | string;
|
|
13
15
|
/** Additional CSS classes */
|
|
14
16
|
class?: string;
|
|
15
17
|
/** Link content */
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
href,
|
|
25
27
|
rel = '',
|
|
26
28
|
target = undefined,
|
|
29
|
+
download = undefined,
|
|
27
30
|
class: classes = '',
|
|
28
31
|
children,
|
|
29
32
|
onclick,
|
|
@@ -36,6 +39,7 @@
|
|
|
36
39
|
class={classes}
|
|
37
40
|
href={href?.startsWith('/') ? resolve(href as any) : href}
|
|
38
41
|
{rel}
|
|
42
|
+
download={download === true ? '' : download || undefined}
|
|
39
43
|
aria-current={ariaCurrent}
|
|
40
44
|
onclick={(e) => interceptLinkClick(e, { href, target, onclick })}
|
|
41
45
|
data-sveltekit-preload-data="hover"
|
package/dist/ui/Link.svelte.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface LinkProps {
|
|
|
6
6
|
rel?: string;
|
|
7
7
|
/** Link target attribute */
|
|
8
8
|
target?: '_self' | '_blank' | undefined;
|
|
9
|
+
/** Download the linked URL instead of navigating. `true` keeps the server's filename */
|
|
10
|
+
download?: boolean | string;
|
|
9
11
|
/** Additional CSS classes */
|
|
10
12
|
class?: string;
|
|
11
13
|
/** Link content */
|
package/dist/ui/Table.svelte
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Navigates client-side when the URL is a page route in the app. URLs the client router can't
|
|
3
|
+
* reach (`+server.ts` endpoints, static files, external URLs) fall back to a full browser
|
|
4
|
+
* navigation. Cancelled navigations resolve quietly; any other failure rejects.
|
|
5
|
+
*/
|
|
6
|
+
export declare function navigateTo(url: string): Promise<void>;
|
package/dist/util/NavigateTo.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { goto } from '$app/navigation';
|
|
2
2
|
import { resolve } from '$app/paths';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// SvelteKit puts the error code in the message in both dev and prod builds
|
|
4
|
+
const FALLBACK_ERRORS = ['/e/kit/navigation_route_missing', '/e/kit/navigation_external_url'];
|
|
5
|
+
// Rejections for navigations superseded by another one, or cancelled via beforeNavigate
|
|
6
|
+
const CANCELLED_ERRORS = ['navigation cancelled', 'navigation aborted'];
|
|
7
|
+
/**
|
|
8
|
+
* Navigates client-side when the URL is a page route in the app. URLs the client router can't
|
|
9
|
+
* reach (`+server.ts` endpoints, static files, external URLs) fall back to a full browser
|
|
10
|
+
* navigation. Cancelled navigations resolve quietly; any other failure rejects.
|
|
11
|
+
*/
|
|
12
|
+
export async function navigateTo(url) {
|
|
13
|
+
const resolved = resolve(url);
|
|
14
|
+
try {
|
|
15
|
+
await goto(resolved);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
+
if (FALLBACK_ERRORS.some((code) => message.includes(code))) {
|
|
20
|
+
window.location.assign(resolved);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (CANCELLED_ERRORS.includes(message))
|
|
24
|
+
return;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
5
27
|
}
|
|
@@ -26,8 +26,25 @@ export function interceptLinkClick(event, options) {
|
|
|
26
26
|
if (event.button !== 0) {
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
|
+
// Honour SvelteKit's standard opt-outs from client-side navigation
|
|
30
|
+
const anchor = event.currentTarget instanceof HTMLAnchorElement ? event.currentTarget : null;
|
|
31
|
+
if (anchor && isClientNavOptOut(anchor)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
29
34
|
// Prevent default navigation and use SvelteKit's client-side navigation
|
|
30
35
|
event.preventDefault();
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
navigateTo(href).catch((error) => {
|
|
37
|
+
console.error(error);
|
|
38
|
+
// The click must still go somewhere, as a plain anchor would
|
|
39
|
+
window.location.assign(anchor?.href ?? href);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** `download`, `rel="external"` and `data-sveltekit-reload` (on the link or an ancestor) */
|
|
43
|
+
function isClientNavOptOut(anchor) {
|
|
44
|
+
if (anchor.hasAttribute('download'))
|
|
45
|
+
return true;
|
|
46
|
+
if (anchor.relList.contains('external'))
|
|
47
|
+
return true;
|
|
48
|
+
const reload = anchor.closest('[data-sveltekit-reload]')?.getAttribute('data-sveltekit-reload');
|
|
49
|
+
return reload != null && reload !== 'off';
|
|
33
50
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@placeholderco/placeholder-ui",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "A modern, customizable Svelte 5 UI component library with comprehensive theming support. Re-brand it with a single config object.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Placeholderco",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@sveltejs/adapter-static": "^4.0.0",
|
|
56
|
-
"@sveltejs/kit": "^3.0.
|
|
56
|
+
"@sveltejs/kit": "^3.0.1",
|
|
57
57
|
"@sveltejs/package": "^3.0.0",
|
|
58
58
|
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
59
59
|
"@types/node": "^26.6.4",
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"prettier-plugin-svelte": "^4.1.1",
|
|
63
63
|
"publint": "^0.3.25",
|
|
64
64
|
"rimraf": "^6.1.3",
|
|
65
|
-
"svelte": "^5.57.
|
|
65
|
+
"svelte": "^5.57.2",
|
|
66
66
|
"svelte-check": "^4.7.6",
|
|
67
67
|
"typescript": "^6.0.3",
|
|
68
|
-
"vite": "^8.3.
|
|
68
|
+
"vite": "^8.3.3"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@codemirror/autocomplete": "^6.20.3",
|
|
@@ -76,13 +76,13 @@
|
|
|
76
76
|
"@codemirror/lang-json": "^6.0.2",
|
|
77
77
|
"@codemirror/lang-markdown": "^6.5.2",
|
|
78
78
|
"@codemirror/lang-sql": "^6.10.0",
|
|
79
|
-
"@codemirror/language": "^6.
|
|
80
|
-
"@codemirror/legacy-modes": "^6.5.
|
|
79
|
+
"@codemirror/language": "^6.13.1",
|
|
80
|
+
"@codemirror/legacy-modes": "^6.5.5",
|
|
81
81
|
"@codemirror/lint": "^6.9.7",
|
|
82
82
|
"@codemirror/search": "^6.7.2",
|
|
83
83
|
"@codemirror/state": "^6.7.6",
|
|
84
84
|
"@codemirror/theme-one-dark": "^6.1.3",
|
|
85
|
-
"@codemirror/view": "^6.43.
|
|
85
|
+
"@codemirror/view": "^6.43.14",
|
|
86
86
|
"@lezer/highlight": "^1.2.5",
|
|
87
87
|
"@tiptap/core": "^3.31.4",
|
|
88
88
|
"@tiptap/extension-bubble-menu": "^3.31.4",
|