@aglyn/shared-ui-jsx 1.0.0-beta.221 → 1.0.0-beta.223
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/package.json +5 -5
- package/src/lib/components/usage-meter.component.d.ts +12 -0
- package/src/lib/components/usage-meter.component.js +14 -2
- package/src/lib/components/usage-meter.component.js.map +1 -1
- package/src/lib/hooks/use-soft-get-submit.d.ts +41 -0
- package/src/lib/hooks/use-soft-get-submit.js +69 -0
- package/src/lib/hooks/use-soft-get-submit.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/shared-ui-jsx",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.223",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.223",
|
|
29
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.223",
|
|
30
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.223",
|
|
31
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.223",
|
|
32
32
|
"@mdi/js": "^7.4.47",
|
|
33
33
|
"@swc/helpers": "0.5.23",
|
|
34
34
|
"change-case": "^5.4.4",
|
|
@@ -18,6 +18,18 @@ export interface UsageMeterProps {
|
|
|
18
18
|
/** Where "Upgrade" points once the meter warns; the plans anchor by default. */
|
|
19
19
|
upgradeHref?: string;
|
|
20
20
|
}
|
|
21
|
+
/** The share of a limit at which a meter turns amber and offers the upgrade. */
|
|
22
|
+
export declare const USAGE_METER_WARNING_PCT = 80;
|
|
23
|
+
/** A meter's bar color: the plan's own, amber once it nears the limit, red at it. */
|
|
24
|
+
export type UsageMeterTone = 'primary' | 'warning' | 'error';
|
|
25
|
+
/**
|
|
26
|
+
* The tone a meter at `percent` of its limit is drawn in.
|
|
27
|
+
*
|
|
28
|
+
* Exported so a readout drawn somewhere other than this row warns at the same
|
|
29
|
+
* point as every meter on the Billing page, rather than at a threshold of its
|
|
30
|
+
* own.
|
|
31
|
+
*/
|
|
32
|
+
export declare function usageMeterTone(percent: number): UsageMeterTone;
|
|
21
33
|
/**
|
|
22
34
|
* One quota meter: used/limit progress with warning at ≥80%, error at the
|
|
23
35
|
* cap, an "Upgrade" link once warning, "Unlimited" for uncapped plans, and
|
|
@@ -18,6 +18,18 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import { LinearProgress, Link, Stack, Typography } from "@mui/material";
|
|
20
20
|
import { HelpTip } from "./help-tip.component.js";
|
|
21
|
+
/** The share of a limit at which a meter turns amber and offers the upgrade. */ export const USAGE_METER_WARNING_PCT = 80;
|
|
22
|
+
/**
|
|
23
|
+
* The tone a meter at `percent` of its limit is drawn in.
|
|
24
|
+
*
|
|
25
|
+
* Exported so a readout drawn somewhere other than this row warns at the same
|
|
26
|
+
* point as every meter on the Billing page, rather than at a threshold of its
|
|
27
|
+
* own.
|
|
28
|
+
*/ export function usageMeterTone(percent) {
|
|
29
|
+
if (!Number.isFinite(percent)) return 'primary';
|
|
30
|
+
if (percent >= 100) return 'error';
|
|
31
|
+
return percent >= USAGE_METER_WARNING_PCT ? 'warning' : 'primary';
|
|
32
|
+
}
|
|
21
33
|
function formatLimit(limit, unit, unlimited) {
|
|
22
34
|
if (unlimited) return 'Unlimited';
|
|
23
35
|
return unit ? `${limit} ${unit}` : String(limit);
|
|
@@ -34,7 +46,7 @@ function formatLimit(limit, unit, unlimited) {
|
|
|
34
46
|
const unlimited = props.unlimited === true;
|
|
35
47
|
const unmetered = used == null;
|
|
36
48
|
const pct = unlimited || unmetered || limit <= 0 ? 0 : Math.min(100, used / limit * 100);
|
|
37
|
-
const warning = !unlimited && !unmetered && pct >=
|
|
49
|
+
const warning = !unlimited && !unmetered && pct >= USAGE_METER_WARNING_PCT;
|
|
38
50
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
39
51
|
spacing: 0.5,
|
|
40
52
|
sx: {
|
|
@@ -82,7 +94,7 @@ function formatLimit(limit, unit, unlimited) {
|
|
|
82
94
|
unlimited || unmetered ? null : /*#__PURE__*/ _jsx(LinearProgress, {
|
|
83
95
|
variant: "determinate",
|
|
84
96
|
value: pct,
|
|
85
|
-
color: pct
|
|
97
|
+
color: usageMeterTone(pct)
|
|
86
98
|
})
|
|
87
99
|
]
|
|
88
100
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/usage-meter.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { LinearProgress, Link, Stack, Typography } from '@mui/material'\nimport { HelpTip, type HelpTipContent } from './help-tip.component'\n\nexport interface UsageMeterProps {\n label: string\n /** `null` while the usage source has not answered — \"not yet metered\". */\n used: number | null\n limit: number\n unit?: string\n /** True when the plan caps nothing here; the bar is omitted and the limit reads \"Unlimited\". */\n unlimited?: boolean\n /**\n * Optional help affordance beside the label.\n *\n * A meter is a number against a limit and says nothing about what happens\n * when the two meet — which for bandwidth is the whole question, and is\n * different on Free (the site pauses) than on a paid plan (the extra bills).\n */\n help?: HelpTipContent\n /** Where \"Upgrade\" points once the meter warns; the plans anchor by default. */\n upgradeHref?: string\n}\n\nfunction formatLimit(limit: number, unit: string | undefined, unlimited: boolean): string {\n if (unlimited) return 'Unlimited'\n return unit ? `${limit} ${unit}` : String(limit)\n}\n\n/**\n * One quota meter: used/limit progress with warning at ≥80%, error at the\n * cap, an \"Upgrade\" link once warning, \"Unlimited\" for uncapped plans, and\n * a \"not yet metered\" state for usage sources that have not answered.\n *\n * Shared (AGL-2939) so a plugin's own meter — the AI credits band — draws\n * beside the platform's on the same terms rather than as a lookalike.\n */\nexport function UsageMeter(props: UsageMeterProps) {\n const { label, used, limit, unit, help, upgradeHref = '#plans' } = props\n const unlimited = props.unlimited === true\n const unmetered = used == null\n const pct =\n unlimited || unmetered || limit <= 0 ? 0 : Math.min(100, (used / limit) * 100)\n const warning = !unlimited && !unmetered && pct >=
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/usage-meter.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { LinearProgress, Link, Stack, Typography } from '@mui/material'\nimport { HelpTip, type HelpTipContent } from './help-tip.component'\n\nexport interface UsageMeterProps {\n label: string\n /** `null` while the usage source has not answered — \"not yet metered\". */\n used: number | null\n limit: number\n unit?: string\n /** True when the plan caps nothing here; the bar is omitted and the limit reads \"Unlimited\". */\n unlimited?: boolean\n /**\n * Optional help affordance beside the label.\n *\n * A meter is a number against a limit and says nothing about what happens\n * when the two meet — which for bandwidth is the whole question, and is\n * different on Free (the site pauses) than on a paid plan (the extra bills).\n */\n help?: HelpTipContent\n /** Where \"Upgrade\" points once the meter warns; the plans anchor by default. */\n upgradeHref?: string\n}\n\n/** The share of a limit at which a meter turns amber and offers the upgrade. */\nexport const USAGE_METER_WARNING_PCT = 80\n\n/** A meter's bar color: the plan's own, amber once it nears the limit, red at it. */\nexport type UsageMeterTone = 'primary' | 'warning' | 'error'\n\n/**\n * The tone a meter at `percent` of its limit is drawn in.\n *\n * Exported so a readout drawn somewhere other than this row warns at the same\n * point as every meter on the Billing page, rather than at a threshold of its\n * own.\n */\nexport function usageMeterTone(percent: number): UsageMeterTone {\n if (!Number.isFinite(percent)) return 'primary'\n if (percent >= 100) return 'error'\n return percent >= USAGE_METER_WARNING_PCT ? 'warning' : 'primary'\n}\n\nfunction formatLimit(limit: number, unit: string | undefined, unlimited: boolean): string {\n if (unlimited) return 'Unlimited'\n return unit ? `${limit} ${unit}` : String(limit)\n}\n\n/**\n * One quota meter: used/limit progress with warning at ≥80%, error at the\n * cap, an \"Upgrade\" link once warning, \"Unlimited\" for uncapped plans, and\n * a \"not yet metered\" state for usage sources that have not answered.\n *\n * Shared (AGL-2939) so a plugin's own meter — the AI credits band — draws\n * beside the platform's on the same terms rather than as a lookalike.\n */\nexport function UsageMeter(props: UsageMeterProps) {\n const { label, used, limit, unit, help, upgradeHref = '#plans' } = props\n const unlimited = props.unlimited === true\n const unmetered = used == null\n const pct =\n unlimited || unmetered || limit <= 0 ? 0 : Math.min(100, (used / limit) * 100)\n const warning = !unlimited && !unmetered && pct >= USAGE_METER_WARNING_PCT\n return (\n <Stack spacing={0.5} sx={{ mb: 2 }}>\n <Stack direction=\"row\" sx={{ justifyContent: 'space-between' }}>\n {/*\n The tip renders INSIDE the label, not beside it in a wrapper: the\n console's meter specs find a meter by `getByText(label).parentElement`\n and read the row's text from it, and a wrapper would make that\n parent the wrapper, whose text is the label alone.\n */}\n <Typography variant=\"body2\">\n {label}\n {help ? <HelpTip {...help} sx={{ ml: 0.5, fontSize: '0.8em' }} /> : null}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {unmetered\n ? `not yet metered · limit ${formatLimit(limit, unit, unlimited)}`\n : `${used} / ${formatLimit(limit, unit, unlimited)}`}\n {warning ? (\n <>\n {' · '}\n <Link href={upgradeHref} color=\"primary\" underline=\"hover\">\n {'Upgrade'}\n </Link>\n </>\n ) : null}\n </Typography>\n </Stack>\n {unlimited || unmetered ? null : (\n <LinearProgress\n variant=\"determinate\"\n value={pct}\n color={usageMeterTone(pct)}\n />\n )}\n </Stack>\n )\n}\n\nexport default UsageMeter\n"],"names":["LinearProgress","Link","Stack","Typography","HelpTip","USAGE_METER_WARNING_PCT","usageMeterTone","percent","Number","isFinite","formatLimit","limit","unit","unlimited","String","UsageMeter","props","label","used","help","upgradeHref","unmetered","pct","Math","min","warning","spacing","sx","mb","direction","justifyContent","variant","ml","fontSize","color","href","underline","value"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,EAAEC,IAAI,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACvE,SAASC,OAAO,QAA6B,0BAAsB;AAsBnE,8EAA8E,GAC9E,OAAO,MAAMC,0BAA0B,GAAE;AAKzC;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,OAAe;IAC5C,IAAI,CAACC,OAAOC,QAAQ,CAACF,UAAU,OAAO;IACtC,IAAIA,WAAW,KAAK,OAAO;IAC3B,OAAOA,WAAWF,0BAA0B,YAAY;AAC1D;AAEA,SAASK,YAAYC,KAAa,EAAEC,IAAwB,EAAEC,SAAkB;IAC9E,IAAIA,WAAW,OAAO;IACtB,OAAOD,OAAO,GAAGD,MAAM,CAAC,EAAEC,MAAM,GAAGE,OAAOH;AAC5C;AAEA;;;;;;;CAOC,GACD,OAAO,SAASI,WAAWC,KAAsB;IAC/C,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAEP,KAAK,EAAEC,IAAI,EAAEO,IAAI,EAAEC,cAAc,QAAQ,EAAE,GAAGJ;IACnE,MAAMH,YAAYG,MAAMH,SAAS,KAAK;IACtC,MAAMQ,YAAYH,QAAQ;IAC1B,MAAMI,MACJT,aAAaQ,aAAaV,SAAS,IAAI,IAAIY,KAAKC,GAAG,CAAC,KAAK,AAACN,OAAOP,QAAS;IAC5E,MAAMc,UAAU,CAACZ,aAAa,CAACQ,aAAaC,OAAOjB;IACnD,qBACE,MAACH;QAAMwB,SAAS;QAAKC,IAAI;YAAEC,IAAI;QAAE;;0BAC/B,MAAC1B;gBAAM2B,WAAU;gBAAMF,IAAI;oBAAEG,gBAAgB;gBAAgB;;kCAO3D,MAAC3B;wBAAW4B,SAAQ;;4BACjBd;4BACAE,qBAAO,KAACf,sBAAYe;gCAAMQ,IAAI;oCAAEK,IAAI;oCAAKC,UAAU;gCAAQ;kCAAQ;;;kCAEtE,MAAC9B;wBAAW4B,SAAQ;wBAAQG,OAAM;;4BAC/Bb,YACG,CAAC,wBAAwB,EAAEX,YAAYC,OAAOC,MAAMC,YAAY,GAChE,GAAGK,KAAK,GAAG,EAAER,YAAYC,OAAOC,MAAMC,YAAY;4BACrDY,wBACC;;oCACG;kDACD,KAACxB;wCAAKkC,MAAMf;wCAAac,OAAM;wCAAUE,WAAU;kDAChD;;;iCAGH;;;;;YAGPvB,aAAaQ,YAAY,qBACxB,KAACrB;gBACC+B,SAAQ;gBACRM,OAAOf;gBACPY,OAAO5B,eAAegB;;;;AAKhC;AAEA,eAAeP,WAAU"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type FormEvent } from 'react';
|
|
18
|
+
/**
|
|
19
|
+
* The URL a `method="get"` form would load, or `null` when the submit should
|
|
20
|
+
* be left to the browser: a POST, a `target` other than this window, or an
|
|
21
|
+
* action on another origin, none of which the client router can stand in for.
|
|
22
|
+
*
|
|
23
|
+
* Built the way the browser builds it — the action's own query string is
|
|
24
|
+
* REPLACED by the form's fields, not merged with them — so the soft
|
|
25
|
+
* navigation lands on exactly the page a full load would have.
|
|
26
|
+
*/
|
|
27
|
+
export declare const softGetSubmitHref: (form: HTMLFormElement, submitter?: HTMLElement | null) => string | null;
|
|
28
|
+
/**
|
|
29
|
+
* An `onSubmit` for a plain GET form that navigates with the Next router
|
|
30
|
+
* instead of reloading the document.
|
|
31
|
+
*
|
|
32
|
+
* The form keeps its `action` and `method`, so it still works before
|
|
33
|
+
* hydration and with scripting off — this only takes over once React is
|
|
34
|
+
* listening, the same progressive enhancement `AppLink` gives an `<a>`.
|
|
35
|
+
*
|
|
36
|
+
* The router is read from its context rather than through `useRouter`,
|
|
37
|
+
* which throws when no App Router is mounted. Site elements render in
|
|
38
|
+
* places that have none (component specs, axe sweeps, server-rendered
|
|
39
|
+
* previews), and there the submit stays the browser's.
|
|
40
|
+
*/
|
|
41
|
+
export declare const useSoftGetSubmit: () => (event: FormEvent<HTMLFormElement>) => void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { AppRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
|
18
|
+
import { useCallback, useContext } from "react";
|
|
19
|
+
/**
|
|
20
|
+
* The URL a `method="get"` form would load, or `null` when the submit should
|
|
21
|
+
* be left to the browser: a POST, a `target` other than this window, or an
|
|
22
|
+
* action on another origin, none of which the client router can stand in for.
|
|
23
|
+
*
|
|
24
|
+
* Built the way the browser builds it — the action's own query string is
|
|
25
|
+
* REPLACED by the form's fields, not merged with them — so the soft
|
|
26
|
+
* navigation lands on exactly the page a full load would have.
|
|
27
|
+
*/ export const softGetSubmitHref = (form, submitter)=>{
|
|
28
|
+
var _form_getAttribute;
|
|
29
|
+
if (((_form_getAttribute = form.getAttribute('method')) != null ? _form_getAttribute : 'get').toLowerCase() !== 'get') {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const target = form.getAttribute('target');
|
|
33
|
+
if (target && target !== '_self') return null;
|
|
34
|
+
const url = new URL(form.action, window.location.href);
|
|
35
|
+
if (url.origin !== window.location.origin) return null;
|
|
36
|
+
const params = new URLSearchParams();
|
|
37
|
+
for (const [name, value] of new FormData(form, submitter != null ? submitter : null)){
|
|
38
|
+
if (typeof value === 'string') params.append(name, value);
|
|
39
|
+
}
|
|
40
|
+
const search = params.toString();
|
|
41
|
+
return `${url.pathname}${search ? `?${search}` : ''}`;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* An `onSubmit` for a plain GET form that navigates with the Next router
|
|
45
|
+
* instead of reloading the document.
|
|
46
|
+
*
|
|
47
|
+
* The form keeps its `action` and `method`, so it still works before
|
|
48
|
+
* hydration and with scripting off — this only takes over once React is
|
|
49
|
+
* listening, the same progressive enhancement `AppLink` gives an `<a>`.
|
|
50
|
+
*
|
|
51
|
+
* The router is read from its context rather than through `useRouter`,
|
|
52
|
+
* which throws when no App Router is mounted. Site elements render in
|
|
53
|
+
* places that have none (component specs, axe sweeps, server-rendered
|
|
54
|
+
* previews), and there the submit stays the browser's.
|
|
55
|
+
*/ export const useSoftGetSubmit = ()=>{
|
|
56
|
+
const router = useContext(AppRouterContext);
|
|
57
|
+
return useCallback((event)=>{
|
|
58
|
+
if (!router || event.defaultPrevented) return;
|
|
59
|
+
const submitter = event.nativeEvent.submitter;
|
|
60
|
+
const href = softGetSubmitHref(event.currentTarget, submitter);
|
|
61
|
+
if (!href) return;
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
router.push(href);
|
|
64
|
+
}, [
|
|
65
|
+
router
|
|
66
|
+
]);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=use-soft-get-submit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-soft-get-submit.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { AppRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'\nimport { type FormEvent, useCallback, useContext } from 'react'\n\n/**\n * The URL a `method=\"get\"` form would load, or `null` when the submit should\n * be left to the browser: a POST, a `target` other than this window, or an\n * action on another origin, none of which the client router can stand in for.\n *\n * Built the way the browser builds it — the action's own query string is\n * REPLACED by the form's fields, not merged with them — so the soft\n * navigation lands on exactly the page a full load would have.\n */\nexport const softGetSubmitHref = (\n form: HTMLFormElement,\n submitter?: HTMLElement | null,\n): string | null => {\n if ((form.getAttribute('method') ?? 'get').toLowerCase() !== 'get') {\n return null\n }\n const target = form.getAttribute('target')\n if (target && target !== '_self') return null\n const url = new URL(form.action, window.location.href)\n if (url.origin !== window.location.origin) return null\n const params = new URLSearchParams()\n for (const [name, value] of new FormData(form, submitter ?? null)) {\n if (typeof value === 'string') params.append(name, value)\n }\n const search = params.toString()\n return `${url.pathname}${search ? `?${search}` : ''}`\n}\n\n/**\n * An `onSubmit` for a plain GET form that navigates with the Next router\n * instead of reloading the document.\n *\n * The form keeps its `action` and `method`, so it still works before\n * hydration and with scripting off — this only takes over once React is\n * listening, the same progressive enhancement `AppLink` gives an `<a>`.\n *\n * The router is read from its context rather than through `useRouter`,\n * which throws when no App Router is mounted. Site elements render in\n * places that have none (component specs, axe sweeps, server-rendered\n * previews), and there the submit stays the browser's.\n */\nexport const useSoftGetSubmit = () => {\n const router = useContext(AppRouterContext)\n return useCallback(\n (event: FormEvent<HTMLFormElement>) => {\n if (!router || event.defaultPrevented) return\n const submitter = (event.nativeEvent as SubmitEvent).submitter\n const href = softGetSubmitHref(event.currentTarget, submitter)\n if (!href) return\n event.preventDefault()\n router.push(href)\n },\n [router],\n )\n}\n"],"names":["AppRouterContext","useCallback","useContext","softGetSubmitHref","form","submitter","getAttribute","toLowerCase","target","url","URL","action","window","location","href","origin","params","URLSearchParams","name","value","FormData","append","search","toString","pathname","useSoftGetSubmit","router","event","defaultPrevented","nativeEvent","currentTarget","preventDefault","push"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;AAEA,SAASA,gBAAgB,QAAQ,yDAAwD;AACzF,SAAyBC,WAAW,EAAEC,UAAU,QAAQ,QAAO;AAE/D;;;;;;;;CAQC,GACD,OAAO,MAAMC,oBAAoB,CAC/BC,MACAC;QAEKD;IAAL,IAAI,EAACA,qBAAAA,KAAKE,YAAY,CAAC,qBAAlBF,qBAA+B,OAAOG,WAAW,OAAO,OAAO;QAClE,OAAO;IACT;IACA,MAAMC,SAASJ,KAAKE,YAAY,CAAC;IACjC,IAAIE,UAAUA,WAAW,SAAS,OAAO;IACzC,MAAMC,MAAM,IAAIC,IAAIN,KAAKO,MAAM,EAAEC,OAAOC,QAAQ,CAACC,IAAI;IACrD,IAAIL,IAAIM,MAAM,KAAKH,OAAOC,QAAQ,CAACE,MAAM,EAAE,OAAO;IAClD,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAM,CAACC,MAAMC,MAAM,IAAI,IAAIC,SAAShB,MAAMC,oBAAAA,YAAa,MAAO;QACjE,IAAI,OAAOc,UAAU,UAAUH,OAAOK,MAAM,CAACH,MAAMC;IACrD;IACA,MAAMG,SAASN,OAAOO,QAAQ;IAC9B,OAAO,GAAGd,IAAIe,QAAQ,GAAGF,SAAS,CAAC,CAAC,EAAEA,QAAQ,GAAG,IAAI;AACvD,EAAC;AAED;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMG,mBAAmB;IAC9B,MAAMC,SAASxB,WAAWF;IAC1B,OAAOC,YACL,CAAC0B;QACC,IAAI,CAACD,UAAUC,MAAMC,gBAAgB,EAAE;QACvC,MAAMvB,YAAY,AAACsB,MAAME,WAAW,CAAiBxB,SAAS;QAC9D,MAAMS,OAAOX,kBAAkBwB,MAAMG,aAAa,EAAEzB;QACpD,IAAI,CAACS,MAAM;QACXa,MAAMI,cAAc;QACpBL,OAAOM,IAAI,CAAClB;IACd,GACA;QAACY;KAAO;AAEZ,EAAC"}
|