@marimo-team/islands 0.17.8 → 0.18.0
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/{Combination-BH_L276x.js → Combination-D68fi0fY.js} +22 -21
- package/dist/{ConnectedDataExplorerComponent-WbiFXhKG.js → ConnectedDataExplorerComponent-BUgUSo2B.js} +7 -7
- package/dist/{any-language-editor-YPQMljy9.js → any-language-editor-BS-Z5AY5.js} +3 -3
- package/dist/assets/__vite-browser-external-CSegkGa0.js +1 -0
- package/dist/assets/{worker-BrDpRi2I.js → worker-CiT2i-Vo.js} +2 -2
- package/dist/{error-banner-BqE1uF21.js → error-banner-CPLhCPHA.js} +24 -24
- package/dist/{esm-hR1r0nyt.js → esm-DxgKy8Wv.js} +1 -1
- package/dist/{formats-dvT8nDgH.js → formats-oddMfm9_.js} +27 -7
- package/dist/{glide-data-editor-B26PhZvE.js → glide-data-editor-BFv4VQnc.js} +4 -4
- package/dist/{label-D3LNCORf.js → label-Dsm6T1fr.js} +72 -72
- package/dist/main.js +359 -250
- package/dist/{mermaid-Dl3ywmV2.js → mermaid-BeGlg1JH.js} +2 -2
- package/dist/{react-vega-ypEMYp9o.js → react-vega-DDXWt_PN.js} +852 -1544
- package/dist/{react-vega-BIDT9Ttp.js → react-vega-DV2IwPx_.js} +1 -1
- package/dist/{spec-qDDGe5hl.js → spec-BotzCMo3.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{types-2eTEqSwS.js → types-IRrkdH-H.js} +14 -14
- package/dist/{useAsyncData-6gisQ4pR.js → useAsyncData-CsSW6_Zh.js} +1 -1
- package/dist/{useTheme-B-2frT0L.js → useTheme-D56Xlrez.js} +1 -0
- package/dist/{vega-component-C-bCSv1b.js → vega-component-CLjz4see.js} +6 -6
- package/package.json +2 -2
- package/src/components/chat/chat-panel.tsx +6 -2
- package/src/components/data-table/TableActions.tsx +18 -14
- package/src/components/data-table/data-table.tsx +3 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +3 -1
- package/src/components/editor/file-tree/__tests__/file-expolorer.test.ts +178 -0
- package/src/components/editor/file-tree/file-explorer.tsx +70 -1
- package/src/components/pages/home-page.tsx +8 -3
- package/src/core/ai/tools/__tests__/registry.test.ts +6 -2
- package/src/core/ai/tools/registry.ts +5 -2
- package/src/core/cells/__tests__/session.test.ts +0 -9
- package/src/core/cells/session.ts +0 -1
- package/src/core/codemirror/copilot/client.ts +21 -1
- package/src/core/codemirror/copilot/copilot-config.tsx +29 -1
- package/src/core/config/__tests__/config-schema.test.ts +2 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/packages/__tests__/package-input-utils.test.ts +93 -0
- package/src/core/packages/package-input-utils.ts +36 -0
- package/src/css/md.css +5 -0
- package/src/plugins/core/__test__/sanitize.test.ts +1 -1
- package/src/plugins/core/sanitize.ts +3 -1
- package/src/plugins/impl/DataTablePlugin.tsx +10 -1
- package/src/plugins/impl/chat/ChatPlugin.tsx +1 -0
- package/src/plugins/impl/chat/chat-ui.tsx +140 -10
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -0
- package/src/plugins/layout/NavigationMenuPlugin.tsx +14 -3
- package/src/plugins/layout/ProgressPlugin.tsx +8 -5
- package/src/plugins/layout/StatPlugin.tsx +11 -4
- package/src/plugins/layout/__test__/ProgressPlugin.test.ts +37 -21
- package/src/utils/__tests__/urls.test.ts +165 -1
- package/src/utils/urls.ts +120 -0
- package/src/utils/vitals.ts +1 -1
- package/dist/assets/__vite-browser-external-BTNiCQ6O.js +0 -1
|
@@ -17,6 +17,7 @@ interface Data {
|
|
|
17
17
|
caption?: string;
|
|
18
18
|
bordered?: boolean;
|
|
19
19
|
direction?: "increase" | "decrease";
|
|
20
|
+
target_direction?: "increase" | "decrease";
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export class StatPlugin implements IStatelessPlugin<Data> {
|
|
@@ -28,6 +29,7 @@ export class StatPlugin implements IStatelessPlugin<Data> {
|
|
|
28
29
|
caption: z.string().optional(),
|
|
29
30
|
bordered: z.boolean().default(false),
|
|
30
31
|
direction: z.enum(["increase", "decrease"]).optional(),
|
|
32
|
+
target_direction: z.enum(["increase", "decrease"]).default("increase"),
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
render({ data }: IStatelessPluginProps<Data>): JSX.Element {
|
|
@@ -41,6 +43,7 @@ export const StatComponent: React.FC<Data> = ({
|
|
|
41
43
|
caption,
|
|
42
44
|
bordered,
|
|
43
45
|
direction,
|
|
46
|
+
target_direction,
|
|
44
47
|
}) => {
|
|
45
48
|
const { locale } = useLocale();
|
|
46
49
|
|
|
@@ -64,6 +67,10 @@ export const StatComponent: React.FC<Data> = ({
|
|
|
64
67
|
return String(value);
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
const onTarget = direction === target_direction;
|
|
71
|
+
const fillColor = onTarget ? "var(--grass-8)" : "var(--red-8)";
|
|
72
|
+
const strokeColor = onTarget ? "var(--grass-9)" : "var(--red-9)";
|
|
73
|
+
|
|
67
74
|
return (
|
|
68
75
|
<div
|
|
69
76
|
className={cn(
|
|
@@ -83,15 +90,15 @@ export const StatComponent: React.FC<Data> = ({
|
|
|
83
90
|
{direction === "increase" && (
|
|
84
91
|
<TriangleIcon
|
|
85
92
|
className="w-4 h-4 mr-1 p-0.5"
|
|
86
|
-
fill=
|
|
87
|
-
stroke=
|
|
93
|
+
fill={fillColor}
|
|
94
|
+
stroke={strokeColor}
|
|
88
95
|
/>
|
|
89
96
|
)}
|
|
90
97
|
{direction === "decrease" && (
|
|
91
98
|
<TriangleIcon
|
|
92
99
|
className="w-4 h-4 mr-1 p-0.5 transform rotate-180"
|
|
93
|
-
fill=
|
|
94
|
-
stroke=
|
|
100
|
+
fill={fillColor}
|
|
101
|
+
stroke={strokeColor}
|
|
95
102
|
/>
|
|
96
103
|
)}
|
|
97
104
|
{caption}
|
|
@@ -2,25 +2,41 @@
|
|
|
2
2
|
import { expect, test } from "vitest";
|
|
3
3
|
import { prettyTime } from "../ProgressPlugin";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
const Cases: Array<[number, string]> = [
|
|
6
|
+
// exact values
|
|
7
|
+
[0, "0s"],
|
|
8
|
+
[1, "1s"],
|
|
9
|
+
[5, "5s"],
|
|
10
|
+
[15, "15s"],
|
|
11
|
+
[60, "1m"],
|
|
12
|
+
[100, "1m, 40s"],
|
|
13
|
+
[60 * 60, "1h"],
|
|
14
|
+
[60 * 60 * 24, "1d"],
|
|
15
|
+
[60 * 60 * 24 * 7, "1w"],
|
|
16
|
+
[60 * 60 * 24 * 8, "1w, 1d"],
|
|
17
|
+
[60 * 60 * 24 * 30, "4w, 2d"],
|
|
18
|
+
[60 * 60 * 24 * 366, "1y, 18h"],
|
|
19
|
+
[60 * 60 * 24 * 466, "1y, 3mo"],
|
|
20
|
+
// decimal values
|
|
21
|
+
[0.5, "0.5s"],
|
|
22
|
+
[1.5, "1.5s"],
|
|
23
|
+
[5.2, "5.2s"],
|
|
24
|
+
[5.33, "5.33s"],
|
|
25
|
+
[15.2, "15s"],
|
|
26
|
+
[60 * 1.5, "1m, 30s"],
|
|
27
|
+
[100.2, "1m, 40s"],
|
|
28
|
+
[60 * 60 * 1.5, "1h, 30m"],
|
|
29
|
+
[60 * 60 * 24 * 1.5, "1d, 12h"],
|
|
21
30
|
// edge cases
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
[0, "0s"],
|
|
32
|
+
[0.0001, "0s"],
|
|
33
|
+
[0.001, "0s"],
|
|
34
|
+
[0.01, "0.01s"],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// generate one test per pair
|
|
38
|
+
for (const [input, expected] of Cases) {
|
|
39
|
+
test(`prettyTime(${input}) → ${expected}`, () => {
|
|
40
|
+
expect(prettyTime(input)).toBe(expected);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
EDGE_CASE_FILENAMES,
|
|
5
5
|
URL_SPECIAL_CHAR_FILENAMES,
|
|
6
6
|
} from "../../__tests__/mocks";
|
|
7
|
-
import { isUrl, updateQueryParams } from "../urls";
|
|
7
|
+
import { appendQueryParams, isUrl, updateQueryParams } from "../urls";
|
|
8
8
|
|
|
9
9
|
describe("isUrl", () => {
|
|
10
10
|
it("should return true for a valid URL", () => {
|
|
@@ -63,3 +63,167 @@ describe("URL parameter handling with edge case filenames", () => {
|
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
});
|
|
66
|
+
|
|
67
|
+
describe("appendQueryParams", () => {
|
|
68
|
+
it("should append params to a simple path", () => {
|
|
69
|
+
const result = appendQueryParams({
|
|
70
|
+
href: "/about",
|
|
71
|
+
queryParams: new URLSearchParams("file=test.py"),
|
|
72
|
+
});
|
|
73
|
+
expect(result).toBe("/about?file=test.py");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should append params to a hash-based path", () => {
|
|
77
|
+
const result = appendQueryParams({
|
|
78
|
+
href: "#/about",
|
|
79
|
+
queryParams: new URLSearchParams("file=test.py"),
|
|
80
|
+
});
|
|
81
|
+
expect(result).toBe("/?file=test.py#/about");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should accept query string instead of URLSearchParams", () => {
|
|
85
|
+
const result = appendQueryParams({
|
|
86
|
+
href: "/about",
|
|
87
|
+
queryParams: "file=test.py&mode=edit",
|
|
88
|
+
});
|
|
89
|
+
expect(result).toBe("/about?file=test.py&mode=edit");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should filter params by keys when provided", () => {
|
|
93
|
+
const result = appendQueryParams({
|
|
94
|
+
href: "/about",
|
|
95
|
+
queryParams: "file=test.py&mode=edit&extra=data",
|
|
96
|
+
keys: ["file", "mode"],
|
|
97
|
+
});
|
|
98
|
+
expect(result).toBe("/about?file=test.py&mode=edit");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should filter params by keys in the middle of the query string", () => {
|
|
102
|
+
const result = appendQueryParams({
|
|
103
|
+
href: "/about",
|
|
104
|
+
queryParams: "file=test.py&mode=edit&extra=data",
|
|
105
|
+
keys: ["file", "extra"],
|
|
106
|
+
});
|
|
107
|
+
expect(result).toBe("/about?file=test.py&extra=data");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should preserve existing query params", () => {
|
|
111
|
+
const result = appendQueryParams({
|
|
112
|
+
href: "/about?existing=1",
|
|
113
|
+
queryParams: "file=test.py",
|
|
114
|
+
});
|
|
115
|
+
expect(result).toBe("/about?existing=1&file=test.py");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should overwrite existing params with same key", () => {
|
|
119
|
+
const result = appendQueryParams({
|
|
120
|
+
href: "/about?file=old.py",
|
|
121
|
+
queryParams: "file=new.py",
|
|
122
|
+
});
|
|
123
|
+
expect(result).toBe("/about?file=new.py");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should preserve hash fragment and put params before it", () => {
|
|
127
|
+
const result = appendQueryParams({
|
|
128
|
+
href: "/about#section",
|
|
129
|
+
queryParams: "file=test.py",
|
|
130
|
+
});
|
|
131
|
+
expect(result).toBe("/about?file=test.py#section");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should handle hash-based path with existing params and hash", () => {
|
|
135
|
+
const result = appendQueryParams({
|
|
136
|
+
href: "#/about?existing=1",
|
|
137
|
+
queryParams: "file=test.py",
|
|
138
|
+
});
|
|
139
|
+
expect(result).toBe("#/about?existing=1&file=test.py");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should handle hash-based path", () => {
|
|
143
|
+
const result = appendQueryParams({
|
|
144
|
+
href: "#/about",
|
|
145
|
+
queryParams: "file=test.py",
|
|
146
|
+
});
|
|
147
|
+
expect(result).toBe("/?file=test.py#/about");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should not modify external links", () => {
|
|
151
|
+
const httpUrl = "http://example.com/page";
|
|
152
|
+
const httpsUrl = "https://example.com/page";
|
|
153
|
+
|
|
154
|
+
expect(
|
|
155
|
+
appendQueryParams({
|
|
156
|
+
href: httpUrl,
|
|
157
|
+
queryParams: "file=test.py",
|
|
158
|
+
}),
|
|
159
|
+
).toBe(httpUrl);
|
|
160
|
+
|
|
161
|
+
expect(
|
|
162
|
+
appendQueryParams({
|
|
163
|
+
href: httpsUrl,
|
|
164
|
+
queryParams: "file=test.py",
|
|
165
|
+
}),
|
|
166
|
+
).toBe(httpsUrl);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should return original href when no params to append", () => {
|
|
170
|
+
const result = appendQueryParams({
|
|
171
|
+
href: "/about",
|
|
172
|
+
queryParams: new URLSearchParams(),
|
|
173
|
+
});
|
|
174
|
+
expect(result).toBe("/about");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should handle complex scenarios with all features", () => {
|
|
178
|
+
const result = appendQueryParams({
|
|
179
|
+
href: "#/dashboard?view=grid#top",
|
|
180
|
+
queryParams: "file=notebook.py&view=list&mode=edit",
|
|
181
|
+
keys: ["file", "mode"],
|
|
182
|
+
});
|
|
183
|
+
// view=grid should remain, file and mode should be added (view is not in keys)
|
|
184
|
+
expect(result).toBe("#/dashboard?view=grid&file=notebook.py&mode=edit#top");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should handle empty path", () => {
|
|
188
|
+
const result = appendQueryParams({
|
|
189
|
+
href: "",
|
|
190
|
+
queryParams: "file=test.py",
|
|
191
|
+
});
|
|
192
|
+
expect(result).toBe("?file=test.py");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should handle just a hash", () => {
|
|
196
|
+
const result = appendQueryParams({
|
|
197
|
+
href: "#",
|
|
198
|
+
queryParams: "file=test.py",
|
|
199
|
+
});
|
|
200
|
+
expect(result).toBe("/?file=test.py#");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should handle unicode filenames in params", () => {
|
|
204
|
+
const result = appendQueryParams({
|
|
205
|
+
href: "/about",
|
|
206
|
+
queryParams: new URLSearchParams([
|
|
207
|
+
["file", "文件.py"],
|
|
208
|
+
["name", "テスト"],
|
|
209
|
+
]),
|
|
210
|
+
});
|
|
211
|
+
expect(result).toContain("/about?");
|
|
212
|
+
// Verify the params are properly encoded
|
|
213
|
+
const url = new URL(result, "http://example.com");
|
|
214
|
+
expect(url.searchParams.get("file")).toBe("文件.py");
|
|
215
|
+
expect(url.searchParams.get("name")).toBe("テスト");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should handle special characters in param values", () => {
|
|
219
|
+
const result = appendQueryParams({
|
|
220
|
+
href: "/about",
|
|
221
|
+
queryParams: new URLSearchParams([
|
|
222
|
+
["path", "folder/file with spaces.py"],
|
|
223
|
+
]),
|
|
224
|
+
});
|
|
225
|
+
expect(result).toContain("/about?");
|
|
226
|
+
const url = new URL(result, "http://example.com");
|
|
227
|
+
expect(url.searchParams.get("path")).toBe("folder/file with spaces.py");
|
|
228
|
+
});
|
|
229
|
+
});
|
package/src/utils/urls.ts
CHANGED
|
@@ -31,3 +31,123 @@ const urlRegex = /^(https?:\/\/\S+)$/;
|
|
|
31
31
|
export function isUrl(value: unknown): boolean {
|
|
32
32
|
return typeof value === "string" && urlRegex.test(value);
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Appends query parameters to an href, handling various edge cases.
|
|
37
|
+
*
|
|
38
|
+
* @param href - The href to append params to (e.g., "/path", "#/hash", "/path?existing=1", "/path#hash")
|
|
39
|
+
* @param queryParams - URLSearchParams or query string to append
|
|
40
|
+
* @param keys - Optional array of keys to filter which params to append
|
|
41
|
+
* @returns The href with appended query parameters
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* appendQueryParams({ href: "/about", queryParams: new URLSearchParams("file=test.py") })
|
|
45
|
+
* // Returns: "/about?file=test.py"
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* appendQueryParams({ href: "#/about", queryParams: "file=test.py&mode=edit", keys: ["file"] })
|
|
49
|
+
* // Returns: "/?file=test.py#/about"
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* appendQueryParams({ href: "#/about?existing=1", queryParams: "file=test.py" })
|
|
53
|
+
* // Returns: "#/about?existing=1&file=test.py"
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* appendQueryParams({ href: "/about?existing=1", queryParams: "file=test.py" })
|
|
57
|
+
* // Returns: "/about?existing=1&file=test.py"
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* appendQueryParams({ href: "/about#section", queryParams: "file=test.py" })
|
|
61
|
+
* // Returns: "/about?file=test.py#section"
|
|
62
|
+
*/
|
|
63
|
+
export function appendQueryParams({
|
|
64
|
+
href,
|
|
65
|
+
queryParams,
|
|
66
|
+
keys,
|
|
67
|
+
}: {
|
|
68
|
+
href: string;
|
|
69
|
+
queryParams: URLSearchParams | string;
|
|
70
|
+
keys?: string[];
|
|
71
|
+
}): string {
|
|
72
|
+
// Convert queryParams to URLSearchParams if it's a string
|
|
73
|
+
const params =
|
|
74
|
+
typeof queryParams === "string"
|
|
75
|
+
? new URLSearchParams(queryParams)
|
|
76
|
+
: queryParams;
|
|
77
|
+
|
|
78
|
+
// If no params to append, return as is
|
|
79
|
+
if (params.size === 0) {
|
|
80
|
+
return href;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Don't modify external links (full URLs)
|
|
84
|
+
if (href.startsWith("http://") || href.startsWith("https://")) {
|
|
85
|
+
return href;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Special handling for hash-based routing
|
|
89
|
+
const isHashBased = href.startsWith("#");
|
|
90
|
+
const hasQueryInHash = isHashBased && href.includes("?");
|
|
91
|
+
|
|
92
|
+
if (isHashBased && !hasQueryInHash) {
|
|
93
|
+
// For hash-based hrefs without query params (e.g., #/about),
|
|
94
|
+
// put query params on the main path before the hash: /?params#/route
|
|
95
|
+
// This is common in SPAs where query params on the main URL need to be preserved
|
|
96
|
+
const paramsToAdd = keys
|
|
97
|
+
? [...params.entries()].filter(([key]) => keys.includes(key))
|
|
98
|
+
: [...params.entries()];
|
|
99
|
+
|
|
100
|
+
const queryParams = new URLSearchParams();
|
|
101
|
+
for (const [key, value] of paramsToAdd) {
|
|
102
|
+
queryParams.set(key, value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const queryString = queryParams.toString();
|
|
106
|
+
if (!queryString) {
|
|
107
|
+
return href;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `/?${queryString}${href}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Parse the href to extract parts for all other cases
|
|
114
|
+
let basePath = href;
|
|
115
|
+
let hash = "";
|
|
116
|
+
let existingParams = new URLSearchParams();
|
|
117
|
+
|
|
118
|
+
// For hash-based routing with query params, look for a second # to find actual hash fragment
|
|
119
|
+
const hashSearchStart = isHashBased ? 1 : 0;
|
|
120
|
+
const hashIndex = href.indexOf("#", hashSearchStart);
|
|
121
|
+
|
|
122
|
+
if (hashIndex !== -1) {
|
|
123
|
+
hash = href.slice(hashIndex);
|
|
124
|
+
basePath = href.slice(0, hashIndex);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Extract existing query params
|
|
128
|
+
const queryIndex = basePath.indexOf("?");
|
|
129
|
+
if (queryIndex !== -1) {
|
|
130
|
+
existingParams = new URLSearchParams(basePath.slice(queryIndex + 1));
|
|
131
|
+
basePath = basePath.slice(0, queryIndex);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Merge params (new params overwrite existing ones with same key)
|
|
135
|
+
const mergedParams = new URLSearchParams(existingParams);
|
|
136
|
+
|
|
137
|
+
// Filter params by keys if provided
|
|
138
|
+
const paramsToAdd = keys
|
|
139
|
+
? [...params.entries()].filter(([key]) => keys.includes(key))
|
|
140
|
+
: [...params.entries()];
|
|
141
|
+
|
|
142
|
+
for (const [key, value] of paramsToAdd) {
|
|
143
|
+
mergedParams.set(key, value);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Build the final URL
|
|
147
|
+
const queryString = mergedParams.toString();
|
|
148
|
+
if (!queryString) {
|
|
149
|
+
return href;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return `${basePath}?${queryString}${hash}`;
|
|
153
|
+
}
|
package/src/utils/vitals.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./worker-BrDpRi2I.js";var t=e(((e,t)=>{t.exports={}}));export default t();
|