@lovrozagar/flare 0.9.12 → 0.9.14
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/README.md +2 -0
- package/package.json +1 -1
- package/src/form/index.tsx +9 -2
- package/src/navigation/index.ts +17 -11
package/README.md
CHANGED
|
@@ -380,6 +380,8 @@ export const route = createLayout("_root_/(blog)")
|
|
|
380
380
|
));
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
+
A layout stays mounted while navigation changes only params its own path does not declare (`/blog/a` → `/blog/b` keeps `(blog)`; `[locale]` layouts remount on a locale switch). Its loader still re-runs and `props.loaderData` updates reactively, so read data in JSX or memos, not once in the render body. Pages remount on any param change.
|
|
384
|
+
|
|
383
385
|
```ts
|
|
384
386
|
import { createPathSegment } from "@lovrozagar/flare/path-segment";
|
|
385
387
|
|
package/package.json
CHANGED
package/src/form/index.tsx
CHANGED
|
@@ -50,8 +50,9 @@ interface FormOwnProps<TInput, TOutput> {
|
|
|
50
50
|
onSuccess?: (data: TOutput) => void;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/* The element's own `onError` (an ErrorEvent) gives way to the action's. */
|
|
53
54
|
export type FormProps<TInput, TOutput> = FormOwnProps<TInput, TOutput> &
|
|
54
|
-
Omit<JSX.FormHTMLAttributes<HTMLFormElement>, "action" | "children" | "enctype" | "method">;
|
|
55
|
+
Omit<JSX.FormHTMLAttributes<HTMLFormElement>, "action" | "children" | "enctype" | "method" | "onError">;
|
|
55
56
|
|
|
56
57
|
/** Seed `form.error()` after a no-JS PE POST with form-level validation errors. */
|
|
57
58
|
export function seedFormErrorFromSsr(ssrCtx: FormActionContext | undefined): Error | null {
|
|
@@ -66,7 +67,7 @@ export function seedFormErrorFromSsr(ssrCtx: FormActionContext | undefined): Err
|
|
|
66
67
|
|
|
67
68
|
export function Form<TInput, TOutput>(props: FormProps<TInput, TOutput>): JSX.Element {
|
|
68
69
|
const local = props as FormOwnProps<TInput, TOutput> & Record<string, unknown>;
|
|
69
|
-
const rest = omit(local, "action", "children", "enctype", "onError", "onSuccess");
|
|
70
|
+
const rest = omit(local, "action", "children", "enctype", "onError", "onSubmit", "onSuccess");
|
|
70
71
|
|
|
71
72
|
const reg = createMemo(() => local.action._registration);
|
|
72
73
|
const fnId = createMemo(() => reg()?.id ?? "");
|
|
@@ -107,6 +108,12 @@ export function Form<TInput, TOutput>(props: FormProps<TInput, TOutput>): JSX.El
|
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
async function handleSubmit(event: SubmitEvent): Promise<void> {
|
|
111
|
+
/* The consumer's handler runs first (client validation, e.g. Base UI Form composed through
|
|
112
|
+
`render`); preventing the default cancels the server call. */
|
|
113
|
+
const consumer = props.onSubmit as JSX.EventHandlerUnion<HTMLFormElement, SubmitEvent> | undefined;
|
|
114
|
+
if (typeof consumer === "function") consumer(event as never);
|
|
115
|
+
else if (Array.isArray(consumer)) (consumer[0] as (data: unknown, event: SubmitEvent) => void)(consumer[1], event);
|
|
116
|
+
if (event.defaultPrevented) return;
|
|
110
117
|
event.preventDefault();
|
|
111
118
|
if (pending()) return;
|
|
112
119
|
|
package/src/navigation/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ import { executeRewriteInput, executeRewriteOutput, type LocationRewrite } from
|
|
|
42
42
|
import type { HeadConfig } from "../route-builder/types.ts";
|
|
43
43
|
import {
|
|
44
44
|
computeMatchId,
|
|
45
|
+
deriveParams,
|
|
45
46
|
isRootLayoutPath,
|
|
46
47
|
matchRoute,
|
|
47
48
|
matchRoutePartial,
|
|
@@ -629,10 +630,13 @@ function commitCachedShell(
|
|
|
629
630
|
return { hadShell: true, keepMatchIds };
|
|
630
631
|
}
|
|
631
632
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
633
|
+
/** Whether the params a route's own path declares kept their values. */
|
|
634
|
+
function ownParamsUnchanged(
|
|
635
|
+
virtualPath: string,
|
|
636
|
+
a: Record<string, string | string[]>,
|
|
637
|
+
b: Record<string, string | string[]>,
|
|
638
|
+
): boolean {
|
|
639
|
+
for (const key of deriveParams(virtualPath)) {
|
|
636
640
|
const av = a[key];
|
|
637
641
|
const bv = b[key];
|
|
638
642
|
if (av === bv) continue;
|
|
@@ -655,7 +659,7 @@ function buildClientMatches(
|
|
|
655
659
|
params: Record<string, string | string[]>,
|
|
656
660
|
) {
|
|
657
661
|
const current = ctx?.matches() ?? [];
|
|
658
|
-
const
|
|
662
|
+
const prevParams = ctx?.params() ?? {};
|
|
659
663
|
return allModules.map((mod, i) => {
|
|
660
664
|
const matchId = matchIdForModule(mod, search, params);
|
|
661
665
|
const cached = ctx?.matchCache.get(matchId);
|
|
@@ -676,15 +680,17 @@ function buildClientMatches(
|
|
|
676
680
|
};
|
|
677
681
|
/* Reuse the previous object when the route slot is unchanged so
|
|
678
682
|
Outlet <Show keyed when={match()}> does not remount (local page
|
|
679
|
-
signals).
|
|
680
|
-
that snapshot loaderData (translators,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
683
|
+
signals). A change to a param the route's own path declares is a
|
|
684
|
+
new instance — render bodies that snapshot loaderData (translators,
|
|
685
|
+
query keys) must re-run. A layout whose own params did not change
|
|
686
|
+
stays mounted when a child param changes; like search-only updates,
|
|
687
|
+
its data refreshes via matchesEpoch. Error identity must change —
|
|
688
|
+
Errored stays in fallback if the same object is mutated from error
|
|
689
|
+
to success. */
|
|
684
690
|
const prev = current[i];
|
|
685
691
|
if (
|
|
686
692
|
prev &&
|
|
687
|
-
|
|
693
|
+
ownParamsUnchanged(mod.virtualPath, prevParams, params) &&
|
|
688
694
|
prev.virtualPath === next.virtualPath &&
|
|
689
695
|
prev._type === next._type &&
|
|
690
696
|
prev.error === next.error
|