gemi 0.47.0 → 0.48.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/{chunk-732aak4d.js → chunk-1ng8cg7d.js} +1 -1
- package/dist/{chunk-bk76mah1.js → chunk-c705601x.js} +2 -2
- package/dist/{chunk-bk76mah1.js.map → chunk-c705601x.js.map} +1 -1
- package/dist/{chunk-3wjd8x9t.js → chunk-ktcg2j50.js} +1 -1
- package/dist/client/ClientRouter.d.ts.map +1 -1
- package/dist/client/ClientRouterContext.d.ts +11 -0
- package/dist/client/ClientRouterContext.d.ts.map +1 -1
- package/dist/client/Link.d.ts +19 -0
- package/dist/client/Link.d.ts.map +1 -1
- package/dist/client/PrefetchCache.d.ts +50 -0
- package/dist/client/PrefetchCache.d.ts.map +1 -0
- package/dist/client/helpers/loadRoutePayload.d.ts +28 -0
- package/dist/client/helpers/loadRoutePayload.d.ts.map +1 -0
- package/dist/client/helpers/routeDataUrl.d.ts +16 -0
- package/dist/client/helpers/routeDataUrl.d.ts.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +838 -551
- package/dist/client/index.js.map +1 -1
- package/dist/client/useMutation.d.ts.map +1 -1
- package/dist/client/usePrefetch.d.ts +21 -0
- package/dist/client/usePrefetch.d.ts.map +1 -0
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-732aak4d.js.map → chunk-1ng8cg7d.js.map} +0 -0
- /package/dist/{chunk-3wjd8x9t.js.map → chunk-ktcg2j50.js.map} +0 -0
package/dist/client/index.js
CHANGED
|
@@ -460,391 +460,6 @@ function useQuery(url, ...args) {
|
|
|
460
460
|
};
|
|
461
461
|
}
|
|
462
462
|
//#endregion
|
|
463
|
-
//#region client/useMutation.ts
|
|
464
|
-
function applyParams(url, params = {}) {
|
|
465
|
-
let out = url;
|
|
466
|
-
for (const [key, value] of Object.entries(params)) out = out.replace(`:${key}?`, value).replace(`:${key}`, value);
|
|
467
|
-
return out;
|
|
468
|
-
}
|
|
469
|
-
var defaultOptions = {
|
|
470
|
-
autoInvalidate: false,
|
|
471
|
-
onSuccess: () => {},
|
|
472
|
-
onError: (_) => {},
|
|
473
|
-
onCanceled: () => {}
|
|
474
|
-
};
|
|
475
|
-
function useMutation(method, url, ...args) {
|
|
476
|
-
const _params = useParams();
|
|
477
|
-
const [state, setState] = useState({
|
|
478
|
-
data: null,
|
|
479
|
-
error: null,
|
|
480
|
-
loading: false
|
|
481
|
-
});
|
|
482
|
-
const [abortController, setAbortController] = useState(() => new AbortController());
|
|
483
|
-
const formData = useRef(new FormData());
|
|
484
|
-
async function trigger(input) {
|
|
485
|
-
setState({
|
|
486
|
-
data: state.data,
|
|
487
|
-
error: state.error,
|
|
488
|
-
loading: true
|
|
489
|
-
});
|
|
490
|
-
const [inputs = {}, options = defaultOptions] = args ?? [];
|
|
491
|
-
const params = "params" in inputs ? {
|
|
492
|
-
..._params,
|
|
493
|
-
...inputs.params
|
|
494
|
-
} : _params;
|
|
495
|
-
const search = "search" in inputs ? inputs.search : {};
|
|
496
|
-
const searchParams = new URLSearchParams(search);
|
|
497
|
-
const finalUrl = [applyParams(String(url).replace(`${method}:`, ""), params), searchParams.toString()].filter(Boolean).join("?");
|
|
498
|
-
let body = null;
|
|
499
|
-
const contentType = typeof input === "undefined" || input instanceof FormData ? {} : { "Content-Type": "application/json" };
|
|
500
|
-
if (input instanceof FormData) body = input;
|
|
501
|
-
else if (typeof input === "undefined") body = formData.current;
|
|
502
|
-
else if (input) body = JSON.stringify(input);
|
|
503
|
-
try {
|
|
504
|
-
const response = await fetch(`/api${finalUrl}`, {
|
|
505
|
-
method,
|
|
506
|
-
headers: { ...contentType },
|
|
507
|
-
...body ? { body } : {},
|
|
508
|
-
signal: abortController.signal
|
|
509
|
-
});
|
|
510
|
-
formData.current = new FormData();
|
|
511
|
-
const data = await response.json();
|
|
512
|
-
if (!response.ok) {
|
|
513
|
-
setState({
|
|
514
|
-
data: null,
|
|
515
|
-
error: data.error,
|
|
516
|
-
loading: false
|
|
517
|
-
});
|
|
518
|
-
options?.onError?.(data);
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
options.onSuccess(data);
|
|
522
|
-
setState({
|
|
523
|
-
data,
|
|
524
|
-
error: null,
|
|
525
|
-
loading: false
|
|
526
|
-
});
|
|
527
|
-
return data;
|
|
528
|
-
} catch (error) {
|
|
529
|
-
formData.current = new FormData();
|
|
530
|
-
options?.onError?.(error);
|
|
531
|
-
setState({
|
|
532
|
-
data: null,
|
|
533
|
-
error,
|
|
534
|
-
loading: false
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
trigger.formData = (formData) => {
|
|
539
|
-
return trigger(formData);
|
|
540
|
-
};
|
|
541
|
-
return {
|
|
542
|
-
data: state.data,
|
|
543
|
-
error: state.error,
|
|
544
|
-
loading: state.loading,
|
|
545
|
-
formData: formData.current,
|
|
546
|
-
cancel: () => {
|
|
547
|
-
const [, options = defaultOptions] = args ?? [];
|
|
548
|
-
abortController.abort();
|
|
549
|
-
setAbortController(new AbortController());
|
|
550
|
-
setState({
|
|
551
|
-
data: state.data,
|
|
552
|
-
error: state.error,
|
|
553
|
-
loading: false
|
|
554
|
-
});
|
|
555
|
-
formData.current = new FormData();
|
|
556
|
-
options.onCanceled();
|
|
557
|
-
},
|
|
558
|
-
trigger
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function usePost(url, ...args) {
|
|
562
|
-
return useMutation("POST", url, ...args);
|
|
563
|
-
}
|
|
564
|
-
function usePut(url, ...args) {
|
|
565
|
-
return useMutation("PUT", url, ...args);
|
|
566
|
-
}
|
|
567
|
-
function usePatch(url, ...args) {
|
|
568
|
-
return useMutation("PATCH", url, ...args);
|
|
569
|
-
}
|
|
570
|
-
function useDelete(url, ...args) {
|
|
571
|
-
return useMutation("DELETE", url, ...args);
|
|
572
|
-
}
|
|
573
|
-
function useUpload(url, ...args) {
|
|
574
|
-
const [state, setState] = useState("idle");
|
|
575
|
-
const [progress, setProgress] = useState(0);
|
|
576
|
-
const _params = useParams();
|
|
577
|
-
const abortRef = useRef(null);
|
|
578
|
-
const [inputs = {}, options = defaultOptions] = args ?? [];
|
|
579
|
-
const cancel = () => {
|
|
580
|
-
if (abortRef.current) {
|
|
581
|
-
abortRef.current();
|
|
582
|
-
options.onCanceled?.();
|
|
583
|
-
setState("idle");
|
|
584
|
-
setProgress(0);
|
|
585
|
-
}
|
|
586
|
-
};
|
|
587
|
-
const trigger = async (fileList) => {
|
|
588
|
-
if (!fileList) return;
|
|
589
|
-
const params = "params" in inputs ? {
|
|
590
|
-
..._params,
|
|
591
|
-
...inputs.params
|
|
592
|
-
} : _params;
|
|
593
|
-
const finalUrl = applyParams(String(url).replace("POST:", ""), params);
|
|
594
|
-
const method = "POST";
|
|
595
|
-
const action = `/api${finalUrl}`;
|
|
596
|
-
const data = new FormData();
|
|
597
|
-
if (fileList instanceof FileList) for (const file of Array.from(fileList)) data.append("file", file);
|
|
598
|
-
else data.append("file", fileList);
|
|
599
|
-
const xhr = new XMLHttpRequest();
|
|
600
|
-
abortRef.current = () => {
|
|
601
|
-
xhr.abort();
|
|
602
|
-
};
|
|
603
|
-
try {
|
|
604
|
-
const result = await new Promise((resolve, reject) => {
|
|
605
|
-
xhr.responseType = "blob";
|
|
606
|
-
xhr.onreadystatechange = async () => {
|
|
607
|
-
if (xhr.readyState !== 4) return;
|
|
608
|
-
resolve(new Response(xhr.response, {
|
|
609
|
-
status: xhr.status,
|
|
610
|
-
statusText: xhr.statusText
|
|
611
|
-
}));
|
|
612
|
-
};
|
|
613
|
-
xhr.addEventListener("error", () => {
|
|
614
|
-
reject(/* @__PURE__ */ new TypeError("Failed to fetch"));
|
|
615
|
-
});
|
|
616
|
-
xhr.upload.addEventListener("loadstart", () => {
|
|
617
|
-
setProgress(0);
|
|
618
|
-
});
|
|
619
|
-
xhr.upload.addEventListener("loadend", () => {
|
|
620
|
-
setProgress(1);
|
|
621
|
-
});
|
|
622
|
-
xhr.upload.addEventListener("progress", (event) => {
|
|
623
|
-
setProgress(event.loaded / event.total);
|
|
624
|
-
});
|
|
625
|
-
xhr.open(method, action, true);
|
|
626
|
-
xhr.send(data);
|
|
627
|
-
});
|
|
628
|
-
setState("uploading");
|
|
629
|
-
if (!result.ok) {
|
|
630
|
-
let error = {
|
|
631
|
-
kind: "server_error",
|
|
632
|
-
message: result.statusText
|
|
633
|
-
};
|
|
634
|
-
try {
|
|
635
|
-
error = (await result.json()).error;
|
|
636
|
-
} catch (e) {}
|
|
637
|
-
setState("error");
|
|
638
|
-
options?.onError?.(error);
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
const json = await result.json();
|
|
642
|
-
options?.onSuccess?.(json);
|
|
643
|
-
return json;
|
|
644
|
-
} catch (error) {
|
|
645
|
-
setState("error");
|
|
646
|
-
options?.onError?.(error);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
return {
|
|
651
|
-
state,
|
|
652
|
-
progress,
|
|
653
|
-
trigger,
|
|
654
|
-
cancel
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
//#endregion
|
|
658
|
-
//#region client/useMutate.ts
|
|
659
|
-
function useMutate() {
|
|
660
|
-
const { getResource } = useContext(QueryManagerContext);
|
|
661
|
-
return function mutate(options, fn) {
|
|
662
|
-
const { path, params = {}, search = {} } = options ?? {};
|
|
663
|
-
const normalPath = applyParams$1(path, params);
|
|
664
|
-
const resource = getResource(normalPath);
|
|
665
|
-
const searchParams = new URLSearchParams(omitNullishValues(search));
|
|
666
|
-
searchParams.sort();
|
|
667
|
-
const variantKey = searchParams.toString();
|
|
668
|
-
return resource.mutate.call(resource, variantKey, (data) => {
|
|
669
|
-
if (data === void 0 || data === null) {
|
|
670
|
-
console.warn("Mutate function called before the query.");
|
|
671
|
-
return data;
|
|
672
|
-
}
|
|
673
|
-
if (!fn) return data;
|
|
674
|
-
const updatedData = typeof fn === "function" ? fn(data) : fn;
|
|
675
|
-
if (isPlainObject(data)) {
|
|
676
|
-
if (isPlainObject(updatedData)) return updatedData;
|
|
677
|
-
throw new Error("Mutate function must return an object when the current data is an object.");
|
|
678
|
-
}
|
|
679
|
-
if (Array.isArray(data)) {
|
|
680
|
-
if (Array.isArray(updatedData)) return updatedData;
|
|
681
|
-
throw new Error("Mutate function must return an array when the current data is an array.");
|
|
682
|
-
}
|
|
683
|
-
if (typeof data !== typeof updatedData) throw new Error("Mutate function must return the same type as the current data.");
|
|
684
|
-
return updatedData;
|
|
685
|
-
});
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
//#endregion
|
|
689
|
-
//#region client/ServerDataProvider.tsx
|
|
690
|
-
var ServerDataContext = createContext({});
|
|
691
|
-
var ServerDataProvider = (props) => {
|
|
692
|
-
let _value = props.value;
|
|
693
|
-
if (props.value) _value = props.value;
|
|
694
|
-
else _value = window.__GEMI_DATA__;
|
|
695
|
-
return /* @__PURE__ */ jsx(ServerDataContext.Provider, {
|
|
696
|
-
value: _value,
|
|
697
|
-
children: props.children
|
|
698
|
-
});
|
|
699
|
-
};
|
|
700
|
-
//#endregion
|
|
701
|
-
//#region client/Mutation.tsx
|
|
702
|
-
var MutationContext = createContext({
|
|
703
|
-
isPending: false,
|
|
704
|
-
result: null
|
|
705
|
-
});
|
|
706
|
-
function Form(props) {
|
|
707
|
-
const _params = useParams();
|
|
708
|
-
const { method = "POST", action, onSuccess = () => {}, onError = () => {}, params, search = {}, className, dynamicInputs = () => ({}), ...formProps } = "params" in props ? {
|
|
709
|
-
...props,
|
|
710
|
-
params: {
|
|
711
|
-
..._params,
|
|
712
|
-
...props.params
|
|
713
|
-
}
|
|
714
|
-
} : {
|
|
715
|
-
...props,
|
|
716
|
-
params: _params
|
|
717
|
-
};
|
|
718
|
-
const formRef = useRef(null);
|
|
719
|
-
const { __csrf } = useContext(ServerDataContext);
|
|
720
|
-
const formDataSubject = useRef(new Subject(new FormData()));
|
|
721
|
-
const updateFormData = useCallback(() => {
|
|
722
|
-
formDataSubject.current.next(new FormData(formRef.current));
|
|
723
|
-
}, []);
|
|
724
|
-
useEffect(() => {
|
|
725
|
-
if (!formRef.current) return;
|
|
726
|
-
formRef.current.addEventListener("input", updateFormData);
|
|
727
|
-
const observer = new MutationObserver(() => {
|
|
728
|
-
const formData = new FormData(formRef.current);
|
|
729
|
-
formDataSubject.current.next(formData);
|
|
730
|
-
});
|
|
731
|
-
formRef.current.querySelectorAll("input").forEach((input) => observer.observe(input, {
|
|
732
|
-
attributes: true,
|
|
733
|
-
attributeFilter: ["value"]
|
|
734
|
-
}));
|
|
735
|
-
formRef.current.querySelectorAll("select").forEach((input) => observer.observe(input, {
|
|
736
|
-
attributes: true,
|
|
737
|
-
attributeFilter: ["value"]
|
|
738
|
-
}));
|
|
739
|
-
formRef.current.querySelectorAll("textarea").forEach((input) => observer.observe(input, {
|
|
740
|
-
attributes: true,
|
|
741
|
-
attributeFilter: ["value"]
|
|
742
|
-
}));
|
|
743
|
-
return () => {
|
|
744
|
-
observer.disconnect();
|
|
745
|
-
if (formRef.current) formRef.current.removeEventListener("input", updateFormData);
|
|
746
|
-
};
|
|
747
|
-
}, [updateFormData]);
|
|
748
|
-
const { trigger, data, error, loading } = useMutation(method, String(action), {
|
|
749
|
-
params,
|
|
750
|
-
search
|
|
751
|
-
}, {
|
|
752
|
-
onSuccess: (data) => onSuccess(data, formRef.current),
|
|
753
|
-
onError: (error) => onError(error, formRef.current)
|
|
754
|
-
});
|
|
755
|
-
const handleSubmit = async (e) => {
|
|
756
|
-
if (loading) return;
|
|
757
|
-
e.preventDefault();
|
|
758
|
-
if (!formRef.current) return;
|
|
759
|
-
const formData = new FormData(formRef.current);
|
|
760
|
-
for (const [key, value] of Object.entries(dynamicInputs(formData))) formData.append(key, value);
|
|
761
|
-
trigger(formData);
|
|
762
|
-
};
|
|
763
|
-
const validationErrors = error?.kind === "validation_error" ? error.messages : {};
|
|
764
|
-
const formError = error?.kind === "form_error" ? error.message : null;
|
|
765
|
-
return /* @__PURE__ */ jsx(MutationContext.Provider, {
|
|
766
|
-
value: {
|
|
767
|
-
isPending: loading,
|
|
768
|
-
result: data,
|
|
769
|
-
validationErrors,
|
|
770
|
-
formError,
|
|
771
|
-
formDataSubject
|
|
772
|
-
},
|
|
773
|
-
children: /* @__PURE__ */ jsxs("form", {
|
|
774
|
-
className: ["group", className].filter(Boolean).join(" "),
|
|
775
|
-
"data-loading": loading,
|
|
776
|
-
ref: formRef,
|
|
777
|
-
onSubmit: handleSubmit,
|
|
778
|
-
...formProps,
|
|
779
|
-
children: [/* @__PURE__ */ jsx("input", {
|
|
780
|
-
type: "hidden",
|
|
781
|
-
name: "__csrf",
|
|
782
|
-
value: __csrf
|
|
783
|
-
}), props.children]
|
|
784
|
-
})
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
function useMutationStatus() {
|
|
788
|
-
const { isPending } = useContext(MutationContext);
|
|
789
|
-
return { isPending };
|
|
790
|
-
}
|
|
791
|
-
function useFormStatus() {
|
|
792
|
-
const { isPending, validationErrors, formError } = useContext(MutationContext);
|
|
793
|
-
return {
|
|
794
|
-
isPending,
|
|
795
|
-
validationErrors,
|
|
796
|
-
formError
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
function useFormData() {
|
|
800
|
-
const { formDataSubject } = useContext(MutationContext);
|
|
801
|
-
return useSyncExternalStore(formDataSubject.current.subscribe.bind(formDataSubject.current), formDataSubject.current.getValue.bind(formDataSubject.current), formDataSubject.current.getValue.bind(formDataSubject.current));
|
|
802
|
-
}
|
|
803
|
-
var ValidationErrors = (props) => {
|
|
804
|
-
const { render = (props) => /* @__PURE__ */ jsx("div", { ...props }), name } = props;
|
|
805
|
-
const { validationErrors } = useContext(MutationContext);
|
|
806
|
-
const Comp = render;
|
|
807
|
-
if (validationErrors[name]?.length > 0) return /* @__PURE__ */ jsx(Fragment$1, { children: validationErrors[name].map((error) => {
|
|
808
|
-
return /* @__PURE__ */ jsx(Comp, {
|
|
809
|
-
className: props.className,
|
|
810
|
-
children: error
|
|
811
|
-
}, error);
|
|
812
|
-
}) });
|
|
813
|
-
return null;
|
|
814
|
-
};
|
|
815
|
-
var FormFieldContainer = (props) => {
|
|
816
|
-
const { name, children, ...rest } = props;
|
|
817
|
-
const { validationErrors } = useContext(MutationContext);
|
|
818
|
-
return /* @__PURE__ */ jsx("div", {
|
|
819
|
-
"data-has-error": (validationErrors[name] || []).length > 0,
|
|
820
|
-
...rest,
|
|
821
|
-
children
|
|
822
|
-
});
|
|
823
|
-
};
|
|
824
|
-
var FormError = (props) => {
|
|
825
|
-
const { formError } = useContext(MutationContext);
|
|
826
|
-
if (formError) return /* @__PURE__ */ jsx("div", {
|
|
827
|
-
...props,
|
|
828
|
-
children: formError
|
|
829
|
-
});
|
|
830
|
-
return null;
|
|
831
|
-
};
|
|
832
|
-
//#endregion
|
|
833
|
-
//#region client/useLocation.ts
|
|
834
|
-
function useLocation() {
|
|
835
|
-
const ctx = useContext(RouteStateContext);
|
|
836
|
-
if (!ctx) throw new Error("Router context not found");
|
|
837
|
-
const { hash, pathname, search, state, locale } = ctx;
|
|
838
|
-
return {
|
|
839
|
-
hash,
|
|
840
|
-
key: pathname,
|
|
841
|
-
pathname,
|
|
842
|
-
search,
|
|
843
|
-
state,
|
|
844
|
-
locale
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
//#endregion
|
|
848
463
|
//#region ../../node_modules/.bun/@babel+runtime@7.29.2/node_modules/@babel/runtime/helpers/esm/extends.js
|
|
849
464
|
function _extends() {
|
|
850
465
|
return _extends = Object.assign ? Object.assign.bind() : function(n) {
|
|
@@ -2103,45 +1718,31 @@ var ProgressManager = class {
|
|
|
2103
1718
|
}
|
|
2104
1719
|
};
|
|
2105
1720
|
//#endregion
|
|
2106
|
-
//#region client/
|
|
2107
|
-
function
|
|
2108
|
-
const
|
|
1721
|
+
//#region client/useLocation.ts
|
|
1722
|
+
function useLocation() {
|
|
1723
|
+
const ctx = useContext(RouteStateContext);
|
|
1724
|
+
if (!ctx) throw new Error("Router context not found");
|
|
1725
|
+
const { hash, pathname, search, state, locale } = ctx;
|
|
2109
1726
|
return {
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
1727
|
+
hash,
|
|
1728
|
+
key: pathname,
|
|
1729
|
+
pathname,
|
|
1730
|
+
search,
|
|
1731
|
+
state,
|
|
1732
|
+
locale
|
|
2114
1733
|
};
|
|
2115
1734
|
}
|
|
2116
1735
|
//#endregion
|
|
2117
|
-
//#region client/
|
|
2118
|
-
var
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
else el.remove();
|
|
2128
|
-
});
|
|
2129
|
-
setReloading(true);
|
|
2130
|
-
};
|
|
2131
|
-
useEffect(() => {
|
|
2132
|
-
if (import.meta.hot) import.meta.hot.on("http-reload", handleReload);
|
|
2133
|
-
return () => {
|
|
2134
|
-
if (import.meta.hot) import.meta.hot.off("http-reload", handleReload);
|
|
2135
|
-
};
|
|
2136
|
-
}, [handleReload]);
|
|
2137
|
-
if (!reloading || typeof document === "undefined") return null;
|
|
2138
|
-
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
2139
|
-
className: "fixed z-[1000] bottom-0 right-0 p-2",
|
|
2140
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
2141
|
-
className: "p-2 bg-white text-black rounded-md shadow-md",
|
|
2142
|
-
children: "..."
|
|
2143
|
-
})
|
|
2144
|
-
}), document.body);
|
|
1736
|
+
//#region client/ServerDataProvider.tsx
|
|
1737
|
+
var ServerDataContext = createContext({});
|
|
1738
|
+
var ServerDataProvider = (props) => {
|
|
1739
|
+
let _value = props.value;
|
|
1740
|
+
if (props.value) _value = props.value;
|
|
1741
|
+
else _value = window.__GEMI_DATA__;
|
|
1742
|
+
return /* @__PURE__ */ jsx(ServerDataContext.Provider, {
|
|
1743
|
+
value: _value,
|
|
1744
|
+
children: props.children
|
|
1745
|
+
});
|
|
2145
1746
|
};
|
|
2146
1747
|
//#endregion
|
|
2147
1748
|
//#region client/I18nContext.tsx
|
|
@@ -2195,6 +1796,246 @@ var I18nProvider = (props) => {
|
|
|
2195
1796
|
});
|
|
2196
1797
|
};
|
|
2197
1798
|
//#endregion
|
|
1799
|
+
//#region client/useNavigate.ts
|
|
1800
|
+
function useNavigate() {
|
|
1801
|
+
const { history, setNavigationAbortController } = useContext(ClientRouterContext);
|
|
1802
|
+
const { defaultLocale } = useContext(I18nContext);
|
|
1803
|
+
const location = useLocation();
|
|
1804
|
+
function action(pushOrReplace) {
|
|
1805
|
+
return async (path, ...args) => {
|
|
1806
|
+
const navigationAbortController = new AbortController();
|
|
1807
|
+
if (setNavigationAbortController) setNavigationAbortController(navigationAbortController);
|
|
1808
|
+
const [options = {}] = args;
|
|
1809
|
+
const { search = {}, params = {}, shallow, locale, hash } = {
|
|
1810
|
+
params: {},
|
|
1811
|
+
shallow: false,
|
|
1812
|
+
locale: null,
|
|
1813
|
+
hash: "",
|
|
1814
|
+
...options
|
|
1815
|
+
};
|
|
1816
|
+
const urlSearchParams = new URLSearchParams(search);
|
|
1817
|
+
let localeSegment = location.locale;
|
|
1818
|
+
if (locale) localeSegment = locale;
|
|
1819
|
+
if (localeSegment === defaultLocale) localeSegment = "";
|
|
1820
|
+
const routePath = applyParams$1(path, params);
|
|
1821
|
+
const finalPath = [[`${localeSegment ? `/${localeSegment}` : ""}${routePath === "/" ? "" : routePath}`, urlSearchParams.toString()].filter((s) => s.length > 0).join("?"), hash].filter(Boolean).join("");
|
|
1822
|
+
if (shallow) {
|
|
1823
|
+
history?.[pushOrReplace](finalPath, { shallow });
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
history?.[pushOrReplace](finalPath === "" ? "/" : finalPath);
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
return {
|
|
1830
|
+
push: action("push"),
|
|
1831
|
+
replace: action("replace")
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region client/useSearchParams.ts
|
|
1836
|
+
var SearchParams = class {
|
|
1837
|
+
searchParams;
|
|
1838
|
+
callback;
|
|
1839
|
+
constructor(searchParams, callback) {
|
|
1840
|
+
this.searchParams = searchParams;
|
|
1841
|
+
this.callback = callback;
|
|
1842
|
+
}
|
|
1843
|
+
get(key) {
|
|
1844
|
+
return this.searchParams.get(key);
|
|
1845
|
+
}
|
|
1846
|
+
set(key, value) {
|
|
1847
|
+
let entries = {};
|
|
1848
|
+
if (typeof key === "string") {
|
|
1849
|
+
let _value = value;
|
|
1850
|
+
if (typeof value === "function") _value = value(this.get(key) ?? "");
|
|
1851
|
+
entries[key] = _value;
|
|
1852
|
+
} else entries = key ?? {};
|
|
1853
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
1854
|
+
let _value = value;
|
|
1855
|
+
if (typeof value === "function") _value = value(this.get(key) ?? "");
|
|
1856
|
+
this.searchParams.set(key, _value);
|
|
1857
|
+
}
|
|
1858
|
+
return this;
|
|
1859
|
+
}
|
|
1860
|
+
append(key, value) {
|
|
1861
|
+
this.searchParams.append(key, value);
|
|
1862
|
+
return this;
|
|
1863
|
+
}
|
|
1864
|
+
sort() {
|
|
1865
|
+
this.searchParams.sort();
|
|
1866
|
+
return this;
|
|
1867
|
+
}
|
|
1868
|
+
clear() {
|
|
1869
|
+
this.searchParams = new URLSearchParams();
|
|
1870
|
+
return this;
|
|
1871
|
+
}
|
|
1872
|
+
delete(key) {
|
|
1873
|
+
const keys = Array.isArray(key) ? key : [key];
|
|
1874
|
+
for (const key of keys) this.searchParams.delete(key);
|
|
1875
|
+
return this;
|
|
1876
|
+
}
|
|
1877
|
+
toJSON() {
|
|
1878
|
+
const map = /* @__PURE__ */ new Map();
|
|
1879
|
+
for (const [key, value] of this.searchParams) if (map.has(key)) {
|
|
1880
|
+
const currentValue = map.get(key);
|
|
1881
|
+
if (Array.isArray(currentValue)) {
|
|
1882
|
+
currentValue.push(value);
|
|
1883
|
+
map.set(key, currentValue);
|
|
1884
|
+
} else map.set(key, [currentValue, value]);
|
|
1885
|
+
} else map.set(key, value);
|
|
1886
|
+
return Object.fromEntries(map.entries());
|
|
1887
|
+
}
|
|
1888
|
+
toString() {
|
|
1889
|
+
return this.searchParams.toString();
|
|
1890
|
+
}
|
|
1891
|
+
push(mode = "soft") {
|
|
1892
|
+
this.callback(this.toJSON(), mode === "soft");
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
function useSearchParams() {
|
|
1896
|
+
const { push } = useNavigate();
|
|
1897
|
+
const { search, pathname } = useContext(RouteStateContext);
|
|
1898
|
+
const params = useParams();
|
|
1899
|
+
const callback = (search, shallow) => {
|
|
1900
|
+
push(pathname, {
|
|
1901
|
+
params,
|
|
1902
|
+
search,
|
|
1903
|
+
shallow
|
|
1904
|
+
});
|
|
1905
|
+
};
|
|
1906
|
+
return new SearchParams(new URLSearchParams(search), callback);
|
|
1907
|
+
}
|
|
1908
|
+
//#endregion
|
|
1909
|
+
//#region client/useRoute.ts
|
|
1910
|
+
function useRoute() {
|
|
1911
|
+
const { pathname: _pathname } = useContext(RouteStateContext);
|
|
1912
|
+
return {
|
|
1913
|
+
pathname: _pathname,
|
|
1914
|
+
startsWith: (pathname) => {
|
|
1915
|
+
return _pathname.startsWith(pathname);
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
//#endregion
|
|
1920
|
+
//#region client/HttpReload.tsx
|
|
1921
|
+
var HttpReload = () => {
|
|
1922
|
+
const { replace } = useNavigate();
|
|
1923
|
+
useSearchParams();
|
|
1924
|
+
const { pathname } = useRoute();
|
|
1925
|
+
useParams();
|
|
1926
|
+
const [reloading, setReloading] = useState(false);
|
|
1927
|
+
const handleReload = () => {
|
|
1928
|
+
if (typeof document !== "undefined") document.querySelectorAll("vite-error-overlay").forEach((el) => {
|
|
1929
|
+
if (typeof el.close === "function") el.close();
|
|
1930
|
+
else el.remove();
|
|
1931
|
+
});
|
|
1932
|
+
setReloading(true);
|
|
1933
|
+
};
|
|
1934
|
+
useEffect(() => {
|
|
1935
|
+
if (import.meta.hot) import.meta.hot.on("http-reload", handleReload);
|
|
1936
|
+
return () => {
|
|
1937
|
+
if (import.meta.hot) import.meta.hot.off("http-reload", handleReload);
|
|
1938
|
+
};
|
|
1939
|
+
}, [handleReload]);
|
|
1940
|
+
if (!reloading || typeof document === "undefined") return null;
|
|
1941
|
+
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
1942
|
+
className: "fixed z-[1000] bottom-0 right-0 p-2",
|
|
1943
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1944
|
+
className: "p-2 bg-white text-black rounded-md shadow-md",
|
|
1945
|
+
children: "..."
|
|
1946
|
+
})
|
|
1947
|
+
}), document.body);
|
|
1948
|
+
};
|
|
1949
|
+
//#endregion
|
|
1950
|
+
//#region client/PrefetchCache.ts
|
|
1951
|
+
/**
|
|
1952
|
+
* How long a prefetched payload stays usable. Long enough to cover the gap
|
|
1953
|
+
* between hovering a link and clicking it, short enough that a navigation is
|
|
1954
|
+
* not served a snapshot the visitor would notice as out of date.
|
|
1955
|
+
*
|
|
1956
|
+
* A payload cached here is committed wholesale on navigation — including into
|
|
1957
|
+
* the query cache via `hydrate` — so anything that invalidates page data has to
|
|
1958
|
+
* `clear()` this too. `useMutation` does exactly that on every successful
|
|
1959
|
+
* write; locale needs no such call, since the locale segment is part of the key.
|
|
1960
|
+
*/
|
|
1961
|
+
var PREFETCH_TTL = 1e4;
|
|
1962
|
+
/**
|
|
1963
|
+
* Payloads fetched ahead of a navigation, keyed by the `.json` URL that
|
|
1964
|
+
* navigation would request. Entries are handed over once — a navigation that
|
|
1965
|
+
* consumes one becomes the live route data, so keeping a copy around would only
|
|
1966
|
+
* let a later visit render from a stale snapshot.
|
|
1967
|
+
*/
|
|
1968
|
+
var PrefetchCache = class {
|
|
1969
|
+
entries = /* @__PURE__ */ new Map();
|
|
1970
|
+
isFresh(entry) {
|
|
1971
|
+
return Date.now() - entry.createdAt < PREFETCH_TTL;
|
|
1972
|
+
}
|
|
1973
|
+
/** Drops what has expired, then the oldest of whatever is still over budget. */
|
|
1974
|
+
evict() {
|
|
1975
|
+
for (const [url, entry] of this.entries) if (!this.isFresh(entry)) this.entries.delete(url);
|
|
1976
|
+
while (this.entries.size >= 12) {
|
|
1977
|
+
const oldest = this.entries.keys().next().value;
|
|
1978
|
+
if (oldest === void 0) return;
|
|
1979
|
+
this.entries.delete(oldest);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Runs `load` unless the same URL is already in flight or freshly cached, so
|
|
1984
|
+
* a link hovered repeatedly — or a screenful of eagerly prefetched links
|
|
1985
|
+
* pointing at one route — costs a single request.
|
|
1986
|
+
*/
|
|
1987
|
+
prime(url, load) {
|
|
1988
|
+
const existing = this.entries.get(url);
|
|
1989
|
+
if (existing && this.isFresh(existing)) return existing.promise;
|
|
1990
|
+
this.evict();
|
|
1991
|
+
const entry = {
|
|
1992
|
+
createdAt: Date.now(),
|
|
1993
|
+
promise: null
|
|
1994
|
+
};
|
|
1995
|
+
entry.promise = load().catch(() => null).then((payload) => {
|
|
1996
|
+
if (payload == null && this.entries.get(url) === entry) this.entries.delete(url);
|
|
1997
|
+
return payload;
|
|
1998
|
+
});
|
|
1999
|
+
this.entries.set(url, entry);
|
|
2000
|
+
return entry.promise;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Hands the payload for `url` to a navigation, if one was prefetched and is
|
|
2004
|
+
* still fresh. Resolves to `null` when the prefetch failed, which callers
|
|
2005
|
+
* must treat as a miss.
|
|
2006
|
+
*/
|
|
2007
|
+
take(url) {
|
|
2008
|
+
const entry = this.entries.get(url);
|
|
2009
|
+
if (!entry) return null;
|
|
2010
|
+
this.entries.delete(url);
|
|
2011
|
+
return this.isFresh(entry) ? entry.promise : null;
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Drops everything, for when the data behind these payloads may have moved
|
|
2015
|
+
* on. In-flight loads still settle; their entries are simply gone by then.
|
|
2016
|
+
*/
|
|
2017
|
+
clear() {
|
|
2018
|
+
this.entries.clear();
|
|
2019
|
+
}
|
|
2020
|
+
/** Retained entries, fresh or not. Exposed for tests. */
|
|
2021
|
+
get size() {
|
|
2022
|
+
return this.entries.size;
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
//#endregion
|
|
2026
|
+
//#region client/helpers/routeDataUrl.ts
|
|
2027
|
+
/**
|
|
2028
|
+
* The `.json` URL a route's page data is served from.
|
|
2029
|
+
*
|
|
2030
|
+
* Prefetching and navigation have to agree on this string exactly — the
|
|
2031
|
+
* prefetch cache is keyed by it, and any disagreement silently turns every
|
|
2032
|
+
* prefetch into a wasted request plus a full fetch on click.
|
|
2033
|
+
*/
|
|
2034
|
+
function routeDataUrl(options) {
|
|
2035
|
+
const { pathname, search = "", localeSegment = "" } = options;
|
|
2036
|
+
return `${localeSegment}${localeSegment.length > 0 && pathname === "/" ? "" : pathname}.json${search}`;
|
|
2037
|
+
}
|
|
2038
|
+
//#endregion
|
|
2198
2039
|
//#region client/ClientRouterContext.tsx
|
|
2199
2040
|
var ClientRouterContext = createContext({});
|
|
2200
2041
|
var ClientRouterProvider = (props) => {
|
|
@@ -2205,6 +2046,7 @@ var ClientRouterProvider = (props) => {
|
|
|
2205
2046
|
});
|
|
2206
2047
|
const { supportedLocales = [], locale } = useContext(I18nContext);
|
|
2207
2048
|
const [progressManager] = useState(new ProgressManager(isNavigatingSubject));
|
|
2049
|
+
const [prefetchCache] = useState(() => new PrefetchCache());
|
|
2208
2050
|
const pageDataRef = useRef(structuredClone(pageData));
|
|
2209
2051
|
const scrollHistoryRef = useRef(/* @__PURE__ */ new Map());
|
|
2210
2052
|
const breadcrumbsCache = useRef(new Map(Object.entries(breadcrumbs)));
|
|
@@ -2320,9 +2162,43 @@ var ClientRouterProvider = (props) => {
|
|
|
2320
2162
|
document.head.appendChild(style);
|
|
2321
2163
|
}
|
|
2322
2164
|
};
|
|
2165
|
+
/**
|
|
2166
|
+
* Warms everything a navigation to `target` would need: the route's page
|
|
2167
|
+
* data, its stylesheets and its component chunks.
|
|
2168
|
+
*
|
|
2169
|
+
* The payload is requested *without* the partial-render header, because the
|
|
2170
|
+
* route on screen when the link is prefetched is not necessarily the one it
|
|
2171
|
+
* will be clicked from — a partial response computed against the wrong base
|
|
2172
|
+
* has nothing sound to merge onto. A full payload is always safe to commit.
|
|
2173
|
+
*
|
|
2174
|
+
* It carries `Purpose: prefetch` so applications can tell speculative traffic
|
|
2175
|
+
* from a real visit — a route's handlers run either way, and a `viewport`
|
|
2176
|
+
* page multiplies that by the number of links on it.
|
|
2177
|
+
*/
|
|
2178
|
+
const prefetchRoute = async (target) => {
|
|
2179
|
+
if (typeof window === "undefined") return;
|
|
2180
|
+
const { pathname, search = "", localeSegment = "" } = target;
|
|
2181
|
+
const routePath = getRoutePathnameFromHref(pathname);
|
|
2182
|
+
if (!routePath) return;
|
|
2183
|
+
const url = routeDataUrl({
|
|
2184
|
+
pathname,
|
|
2185
|
+
search,
|
|
2186
|
+
localeSegment
|
|
2187
|
+
});
|
|
2188
|
+
fetchRouteCSS(routePath).catch(() => {});
|
|
2189
|
+
for (const view of routeManifest[routePath] ?? []) window?.loaders?.[view]?.();
|
|
2190
|
+
await prefetchCache.prime(url, async () => {
|
|
2191
|
+
const response = await fetch(url, { headers: { Purpose: "prefetch" } });
|
|
2192
|
+
if (!response.ok) return null;
|
|
2193
|
+
return await response.json();
|
|
2194
|
+
});
|
|
2195
|
+
};
|
|
2323
2196
|
return /* @__PURE__ */ jsxs(ClientRouterContext.Provider, {
|
|
2324
2197
|
value: {
|
|
2325
2198
|
isNavigatingSubject,
|
|
2199
|
+
prefetchRoute,
|
|
2200
|
+
takePrefetched: (url) => prefetchCache.take(url),
|
|
2201
|
+
clearPrefetchCache: () => prefetchCache.clear(),
|
|
2326
2202
|
getViewPathsFromPathname,
|
|
2327
2203
|
history,
|
|
2328
2204
|
getScrollPosition: (path) => {
|
|
@@ -2343,115 +2219,367 @@ var ClientRouterProvider = (props) => {
|
|
|
2343
2219
|
});
|
|
2344
2220
|
};
|
|
2345
2221
|
//#endregion
|
|
2346
|
-
//#region client/
|
|
2347
|
-
function
|
|
2348
|
-
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2222
|
+
//#region client/useMutation.ts
|
|
2223
|
+
function applyParams(url, params = {}) {
|
|
2224
|
+
let out = url;
|
|
2225
|
+
for (const [key, value] of Object.entries(params)) out = out.replace(`:${key}?`, value).replace(`:${key}`, value);
|
|
2226
|
+
return out;
|
|
2227
|
+
}
|
|
2228
|
+
var defaultOptions = {
|
|
2229
|
+
autoInvalidate: false,
|
|
2230
|
+
onSuccess: () => {},
|
|
2231
|
+
onError: (_) => {},
|
|
2232
|
+
onCanceled: () => {}
|
|
2233
|
+
};
|
|
2234
|
+
function useMutation(method, url, ...args) {
|
|
2235
|
+
const _params = useParams();
|
|
2236
|
+
const { clearPrefetchCache } = useContext(ClientRouterContext);
|
|
2237
|
+
const [state, setState] = useState({
|
|
2238
|
+
data: null,
|
|
2239
|
+
error: null,
|
|
2240
|
+
loading: false
|
|
2241
|
+
});
|
|
2242
|
+
const [abortController, setAbortController] = useState(() => new AbortController());
|
|
2243
|
+
const formData = useRef(new FormData());
|
|
2244
|
+
async function trigger(input) {
|
|
2245
|
+
setState({
|
|
2246
|
+
data: state.data,
|
|
2247
|
+
error: state.error,
|
|
2248
|
+
loading: true
|
|
2249
|
+
});
|
|
2250
|
+
const [inputs = {}, options = defaultOptions] = args ?? [];
|
|
2251
|
+
const params = "params" in inputs ? {
|
|
2252
|
+
..._params,
|
|
2253
|
+
...inputs.params
|
|
2254
|
+
} : _params;
|
|
2255
|
+
const search = "search" in inputs ? inputs.search : {};
|
|
2256
|
+
const searchParams = new URLSearchParams(search);
|
|
2257
|
+
const finalUrl = [applyParams(String(url).replace(`${method}:`, ""), params), searchParams.toString()].filter(Boolean).join("?");
|
|
2258
|
+
let body = null;
|
|
2259
|
+
const contentType = typeof input === "undefined" || input instanceof FormData ? {} : { "Content-Type": "application/json" };
|
|
2260
|
+
if (input instanceof FormData) body = input;
|
|
2261
|
+
else if (typeof input === "undefined") body = formData.current;
|
|
2262
|
+
else if (input) body = JSON.stringify(input);
|
|
2263
|
+
try {
|
|
2264
|
+
const response = await fetch(`/api${finalUrl}`, {
|
|
2265
|
+
method,
|
|
2266
|
+
headers: { ...contentType },
|
|
2267
|
+
...body ? { body } : {},
|
|
2268
|
+
signal: abortController.signal
|
|
2269
|
+
});
|
|
2270
|
+
formData.current = new FormData();
|
|
2271
|
+
const data = await response.json();
|
|
2272
|
+
if (!response.ok) {
|
|
2273
|
+
setState({
|
|
2274
|
+
data: null,
|
|
2275
|
+
error: data.error,
|
|
2276
|
+
loading: false
|
|
2277
|
+
});
|
|
2278
|
+
options?.onError?.(data);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
clearPrefetchCache?.();
|
|
2282
|
+
options.onSuccess(data);
|
|
2283
|
+
setState({
|
|
2284
|
+
data,
|
|
2285
|
+
error: null,
|
|
2286
|
+
loading: false
|
|
2287
|
+
});
|
|
2288
|
+
return data;
|
|
2289
|
+
} catch (error) {
|
|
2290
|
+
formData.current = new FormData();
|
|
2291
|
+
options?.onError?.(error);
|
|
2292
|
+
setState({
|
|
2293
|
+
data: null,
|
|
2294
|
+
error,
|
|
2295
|
+
loading: false
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
trigger.formData = (formData) => {
|
|
2300
|
+
return trigger(formData);
|
|
2301
|
+
};
|
|
2302
|
+
return {
|
|
2303
|
+
data: state.data,
|
|
2304
|
+
error: state.error,
|
|
2305
|
+
loading: state.loading,
|
|
2306
|
+
formData: formData.current,
|
|
2307
|
+
cancel: () => {
|
|
2308
|
+
const [, options = defaultOptions] = args ?? [];
|
|
2309
|
+
abortController.abort();
|
|
2310
|
+
setAbortController(new AbortController());
|
|
2311
|
+
setState({
|
|
2312
|
+
data: state.data,
|
|
2313
|
+
error: state.error,
|
|
2314
|
+
loading: false
|
|
2315
|
+
});
|
|
2316
|
+
formData.current = new FormData();
|
|
2317
|
+
options.onCanceled();
|
|
2318
|
+
},
|
|
2319
|
+
trigger
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
function usePost(url, ...args) {
|
|
2323
|
+
return useMutation("POST", url, ...args);
|
|
2324
|
+
}
|
|
2325
|
+
function usePut(url, ...args) {
|
|
2326
|
+
return useMutation("PUT", url, ...args);
|
|
2327
|
+
}
|
|
2328
|
+
function usePatch(url, ...args) {
|
|
2329
|
+
return useMutation("PATCH", url, ...args);
|
|
2330
|
+
}
|
|
2331
|
+
function useDelete(url, ...args) {
|
|
2332
|
+
return useMutation("DELETE", url, ...args);
|
|
2333
|
+
}
|
|
2334
|
+
function useUpload(url, ...args) {
|
|
2335
|
+
const [state, setState] = useState("idle");
|
|
2336
|
+
const [progress, setProgress] = useState(0);
|
|
2337
|
+
const _params = useParams();
|
|
2338
|
+
const { clearPrefetchCache } = useContext(ClientRouterContext);
|
|
2339
|
+
const abortRef = useRef(null);
|
|
2340
|
+
const [inputs = {}, options = defaultOptions] = args ?? [];
|
|
2341
|
+
const cancel = () => {
|
|
2342
|
+
if (abortRef.current) {
|
|
2343
|
+
abortRef.current();
|
|
2344
|
+
options.onCanceled?.();
|
|
2345
|
+
setState("idle");
|
|
2346
|
+
setProgress(0);
|
|
2347
|
+
}
|
|
2348
|
+
};
|
|
2349
|
+
const trigger = async (fileList) => {
|
|
2350
|
+
if (!fileList) return;
|
|
2351
|
+
const params = "params" in inputs ? {
|
|
2352
|
+
..._params,
|
|
2353
|
+
...inputs.params
|
|
2354
|
+
} : _params;
|
|
2355
|
+
const finalUrl = applyParams(String(url).replace("POST:", ""), params);
|
|
2356
|
+
const method = "POST";
|
|
2357
|
+
const action = `/api${finalUrl}`;
|
|
2358
|
+
const data = new FormData();
|
|
2359
|
+
if (fileList instanceof FileList) for (const file of Array.from(fileList)) data.append("file", file);
|
|
2360
|
+
else data.append("file", fileList);
|
|
2361
|
+
const xhr = new XMLHttpRequest();
|
|
2362
|
+
abortRef.current = () => {
|
|
2363
|
+
xhr.abort();
|
|
2364
|
+
};
|
|
2365
|
+
try {
|
|
2366
|
+
const result = await new Promise((resolve, reject) => {
|
|
2367
|
+
xhr.responseType = "blob";
|
|
2368
|
+
xhr.onreadystatechange = async () => {
|
|
2369
|
+
if (xhr.readyState !== 4) return;
|
|
2370
|
+
resolve(new Response(xhr.response, {
|
|
2371
|
+
status: xhr.status,
|
|
2372
|
+
statusText: xhr.statusText
|
|
2373
|
+
}));
|
|
2374
|
+
};
|
|
2375
|
+
xhr.addEventListener("error", () => {
|
|
2376
|
+
reject(/* @__PURE__ */ new TypeError("Failed to fetch"));
|
|
2377
|
+
});
|
|
2378
|
+
xhr.upload.addEventListener("loadstart", () => {
|
|
2379
|
+
setProgress(0);
|
|
2380
|
+
});
|
|
2381
|
+
xhr.upload.addEventListener("loadend", () => {
|
|
2382
|
+
setProgress(1);
|
|
2383
|
+
});
|
|
2384
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
2385
|
+
setProgress(event.loaded / event.total);
|
|
2386
|
+
});
|
|
2387
|
+
xhr.open(method, action, true);
|
|
2388
|
+
xhr.send(data);
|
|
2389
|
+
});
|
|
2390
|
+
setState("uploading");
|
|
2391
|
+
if (!result.ok) {
|
|
2392
|
+
let error = {
|
|
2393
|
+
kind: "server_error",
|
|
2394
|
+
message: result.statusText
|
|
2395
|
+
};
|
|
2396
|
+
try {
|
|
2397
|
+
error = (await result.json()).error;
|
|
2398
|
+
} catch (e) {}
|
|
2399
|
+
setState("error");
|
|
2400
|
+
options?.onError?.(error);
|
|
2371
2401
|
return;
|
|
2372
2402
|
}
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2403
|
+
const json = await result.json();
|
|
2404
|
+
clearPrefetchCache?.();
|
|
2405
|
+
options?.onSuccess?.(json);
|
|
2406
|
+
return json;
|
|
2407
|
+
} catch (error) {
|
|
2408
|
+
setState("error");
|
|
2409
|
+
options?.onError?.(error);
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
return {
|
|
2414
|
+
state,
|
|
2415
|
+
progress,
|
|
2416
|
+
trigger,
|
|
2417
|
+
cancel
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
//#endregion
|
|
2421
|
+
//#region client/useMutate.ts
|
|
2422
|
+
function useMutate() {
|
|
2423
|
+
const { getResource } = useContext(QueryManagerContext);
|
|
2424
|
+
return function mutate(options, fn) {
|
|
2425
|
+
const { path, params = {}, search = {} } = options ?? {};
|
|
2426
|
+
const normalPath = applyParams$1(path, params);
|
|
2427
|
+
const resource = getResource(normalPath);
|
|
2428
|
+
const searchParams = new URLSearchParams(omitNullishValues(search));
|
|
2429
|
+
searchParams.sort();
|
|
2430
|
+
const variantKey = searchParams.toString();
|
|
2431
|
+
return resource.mutate.call(resource, variantKey, (data) => {
|
|
2432
|
+
if (data === void 0 || data === null) {
|
|
2433
|
+
console.warn("Mutate function called before the query.");
|
|
2434
|
+
return data;
|
|
2435
|
+
}
|
|
2436
|
+
if (!fn) return data;
|
|
2437
|
+
const updatedData = typeof fn === "function" ? fn(data) : fn;
|
|
2438
|
+
if (isPlainObject(data)) {
|
|
2439
|
+
if (isPlainObject(updatedData)) return updatedData;
|
|
2440
|
+
throw new Error("Mutate function must return an object when the current data is an object.");
|
|
2441
|
+
}
|
|
2442
|
+
if (Array.isArray(data)) {
|
|
2443
|
+
if (Array.isArray(updatedData)) return updatedData;
|
|
2444
|
+
throw new Error("Mutate function must return an array when the current data is an array.");
|
|
2445
|
+
}
|
|
2446
|
+
if (typeof data !== typeof updatedData) throw new Error("Mutate function must return the same type as the current data.");
|
|
2447
|
+
return updatedData;
|
|
2448
|
+
});
|
|
2379
2449
|
};
|
|
2380
2450
|
}
|
|
2381
2451
|
//#endregion
|
|
2382
|
-
//#region client/
|
|
2383
|
-
var
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
let entries = {};
|
|
2395
|
-
if (typeof key === "string") {
|
|
2396
|
-
let _value = value;
|
|
2397
|
-
if (typeof value === "function") _value = value(this.get(key) ?? "");
|
|
2398
|
-
entries[key] = _value;
|
|
2399
|
-
} else entries = key ?? {};
|
|
2400
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
2401
|
-
let _value = value;
|
|
2402
|
-
if (typeof value === "function") _value = value(this.get(key) ?? "");
|
|
2403
|
-
this.searchParams.set(key, _value);
|
|
2452
|
+
//#region client/Mutation.tsx
|
|
2453
|
+
var MutationContext = createContext({
|
|
2454
|
+
isPending: false,
|
|
2455
|
+
result: null
|
|
2456
|
+
});
|
|
2457
|
+
function Form(props) {
|
|
2458
|
+
const _params = useParams();
|
|
2459
|
+
const { method = "POST", action, onSuccess = () => {}, onError = () => {}, params, search = {}, className, dynamicInputs = () => ({}), ...formProps } = "params" in props ? {
|
|
2460
|
+
...props,
|
|
2461
|
+
params: {
|
|
2462
|
+
..._params,
|
|
2463
|
+
...props.params
|
|
2404
2464
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
for (const key of keys) this.searchParams.delete(key);
|
|
2422
|
-
return this;
|
|
2423
|
-
}
|
|
2424
|
-
toJSON() {
|
|
2425
|
-
const map = /* @__PURE__ */ new Map();
|
|
2426
|
-
for (const [key, value] of this.searchParams) if (map.has(key)) {
|
|
2427
|
-
const currentValue = map.get(key);
|
|
2428
|
-
if (Array.isArray(currentValue)) {
|
|
2429
|
-
currentValue.push(value);
|
|
2430
|
-
map.set(key, currentValue);
|
|
2431
|
-
} else map.set(key, [currentValue, value]);
|
|
2432
|
-
} else map.set(key, value);
|
|
2433
|
-
return Object.fromEntries(map.entries());
|
|
2434
|
-
}
|
|
2435
|
-
toString() {
|
|
2436
|
-
return this.searchParams.toString();
|
|
2437
|
-
}
|
|
2438
|
-
push(mode = "soft") {
|
|
2439
|
-
this.callback(this.toJSON(), mode === "soft");
|
|
2440
|
-
}
|
|
2441
|
-
};
|
|
2442
|
-
function useSearchParams() {
|
|
2443
|
-
const { push } = useNavigate();
|
|
2444
|
-
const { search, pathname } = useContext(RouteStateContext);
|
|
2445
|
-
const params = useParams();
|
|
2446
|
-
const callback = (search, shallow) => {
|
|
2447
|
-
push(pathname, {
|
|
2448
|
-
params,
|
|
2449
|
-
search,
|
|
2450
|
-
shallow
|
|
2465
|
+
} : {
|
|
2466
|
+
...props,
|
|
2467
|
+
params: _params
|
|
2468
|
+
};
|
|
2469
|
+
const formRef = useRef(null);
|
|
2470
|
+
const { __csrf } = useContext(ServerDataContext);
|
|
2471
|
+
const formDataSubject = useRef(new Subject(new FormData()));
|
|
2472
|
+
const updateFormData = useCallback(() => {
|
|
2473
|
+
formDataSubject.current.next(new FormData(formRef.current));
|
|
2474
|
+
}, []);
|
|
2475
|
+
useEffect(() => {
|
|
2476
|
+
if (!formRef.current) return;
|
|
2477
|
+
formRef.current.addEventListener("input", updateFormData);
|
|
2478
|
+
const observer = new MutationObserver(() => {
|
|
2479
|
+
const formData = new FormData(formRef.current);
|
|
2480
|
+
formDataSubject.current.next(formData);
|
|
2451
2481
|
});
|
|
2482
|
+
formRef.current.querySelectorAll("input").forEach((input) => observer.observe(input, {
|
|
2483
|
+
attributes: true,
|
|
2484
|
+
attributeFilter: ["value"]
|
|
2485
|
+
}));
|
|
2486
|
+
formRef.current.querySelectorAll("select").forEach((input) => observer.observe(input, {
|
|
2487
|
+
attributes: true,
|
|
2488
|
+
attributeFilter: ["value"]
|
|
2489
|
+
}));
|
|
2490
|
+
formRef.current.querySelectorAll("textarea").forEach((input) => observer.observe(input, {
|
|
2491
|
+
attributes: true,
|
|
2492
|
+
attributeFilter: ["value"]
|
|
2493
|
+
}));
|
|
2494
|
+
return () => {
|
|
2495
|
+
observer.disconnect();
|
|
2496
|
+
if (formRef.current) formRef.current.removeEventListener("input", updateFormData);
|
|
2497
|
+
};
|
|
2498
|
+
}, [updateFormData]);
|
|
2499
|
+
const { trigger, data, error, loading } = useMutation(method, String(action), {
|
|
2500
|
+
params,
|
|
2501
|
+
search
|
|
2502
|
+
}, {
|
|
2503
|
+
onSuccess: (data) => onSuccess(data, formRef.current),
|
|
2504
|
+
onError: (error) => onError(error, formRef.current)
|
|
2505
|
+
});
|
|
2506
|
+
const handleSubmit = async (e) => {
|
|
2507
|
+
if (loading) return;
|
|
2508
|
+
e.preventDefault();
|
|
2509
|
+
if (!formRef.current) return;
|
|
2510
|
+
const formData = new FormData(formRef.current);
|
|
2511
|
+
for (const [key, value] of Object.entries(dynamicInputs(formData))) formData.append(key, value);
|
|
2512
|
+
trigger(formData);
|
|
2513
|
+
};
|
|
2514
|
+
const validationErrors = error?.kind === "validation_error" ? error.messages : {};
|
|
2515
|
+
const formError = error?.kind === "form_error" ? error.message : null;
|
|
2516
|
+
return /* @__PURE__ */ jsx(MutationContext.Provider, {
|
|
2517
|
+
value: {
|
|
2518
|
+
isPending: loading,
|
|
2519
|
+
result: data,
|
|
2520
|
+
validationErrors,
|
|
2521
|
+
formError,
|
|
2522
|
+
formDataSubject
|
|
2523
|
+
},
|
|
2524
|
+
children: /* @__PURE__ */ jsxs("form", {
|
|
2525
|
+
className: ["group", className].filter(Boolean).join(" "),
|
|
2526
|
+
"data-loading": loading,
|
|
2527
|
+
ref: formRef,
|
|
2528
|
+
onSubmit: handleSubmit,
|
|
2529
|
+
...formProps,
|
|
2530
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
2531
|
+
type: "hidden",
|
|
2532
|
+
name: "__csrf",
|
|
2533
|
+
value: __csrf
|
|
2534
|
+
}), props.children]
|
|
2535
|
+
})
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
function useMutationStatus() {
|
|
2539
|
+
const { isPending } = useContext(MutationContext);
|
|
2540
|
+
return { isPending };
|
|
2541
|
+
}
|
|
2542
|
+
function useFormStatus() {
|
|
2543
|
+
const { isPending, validationErrors, formError } = useContext(MutationContext);
|
|
2544
|
+
return {
|
|
2545
|
+
isPending,
|
|
2546
|
+
validationErrors,
|
|
2547
|
+
formError
|
|
2452
2548
|
};
|
|
2453
|
-
return new SearchParams(new URLSearchParams(search), callback);
|
|
2454
2549
|
}
|
|
2550
|
+
function useFormData() {
|
|
2551
|
+
const { formDataSubject } = useContext(MutationContext);
|
|
2552
|
+
return useSyncExternalStore(formDataSubject.current.subscribe.bind(formDataSubject.current), formDataSubject.current.getValue.bind(formDataSubject.current), formDataSubject.current.getValue.bind(formDataSubject.current));
|
|
2553
|
+
}
|
|
2554
|
+
var ValidationErrors = (props) => {
|
|
2555
|
+
const { render = (props) => /* @__PURE__ */ jsx("div", { ...props }), name } = props;
|
|
2556
|
+
const { validationErrors } = useContext(MutationContext);
|
|
2557
|
+
const Comp = render;
|
|
2558
|
+
if (validationErrors[name]?.length > 0) return /* @__PURE__ */ jsx(Fragment$1, { children: validationErrors[name].map((error) => {
|
|
2559
|
+
return /* @__PURE__ */ jsx(Comp, {
|
|
2560
|
+
className: props.className,
|
|
2561
|
+
children: error
|
|
2562
|
+
}, error);
|
|
2563
|
+
}) });
|
|
2564
|
+
return null;
|
|
2565
|
+
};
|
|
2566
|
+
var FormFieldContainer = (props) => {
|
|
2567
|
+
const { name, children, ...rest } = props;
|
|
2568
|
+
const { validationErrors } = useContext(MutationContext);
|
|
2569
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2570
|
+
"data-has-error": (validationErrors[name] || []).length > 0,
|
|
2571
|
+
...rest,
|
|
2572
|
+
children
|
|
2573
|
+
});
|
|
2574
|
+
};
|
|
2575
|
+
var FormError = (props) => {
|
|
2576
|
+
const { formError } = useContext(MutationContext);
|
|
2577
|
+
if (formError) return /* @__PURE__ */ jsx("div", {
|
|
2578
|
+
...props,
|
|
2579
|
+
children: formError
|
|
2580
|
+
});
|
|
2581
|
+
return null;
|
|
2582
|
+
};
|
|
2455
2583
|
//#endregion
|
|
2456
2584
|
//#region client/useIsNavigationPending.ts
|
|
2457
2585
|
function useIsNavigationPending() {
|
|
@@ -2472,6 +2600,64 @@ function useNavigationProgress() {
|
|
|
2472
2600
|
return progress;
|
|
2473
2601
|
}
|
|
2474
2602
|
//#endregion
|
|
2603
|
+
//#region client/usePrefetch.ts
|
|
2604
|
+
/**
|
|
2605
|
+
* A prefetch spends the visitor's data on a page they may never open, so it
|
|
2606
|
+
* stands down when they have asked for less of that or the connection cannot
|
|
2607
|
+
* spare it. `navigator.connection` only exists in Chromium — everywhere else
|
|
2608
|
+
* there is nothing to go on and prefetching proceeds.
|
|
2609
|
+
*/
|
|
2610
|
+
function connectionRefusesPrefetch() {
|
|
2611
|
+
const connection = navigator?.connection;
|
|
2612
|
+
if (!connection) return false;
|
|
2613
|
+
if (connection.saveData) return true;
|
|
2614
|
+
return ["slow-2g", "2g"].includes(connection.effectiveType);
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Warms a route ahead of the navigation to it: its page data, its stylesheets
|
|
2618
|
+
* and its component chunks. A navigation that lands on a prefetched route
|
|
2619
|
+
* renders from the cached payload instead of waiting on a request.
|
|
2620
|
+
*
|
|
2621
|
+
* The URL is built exactly the way `useNavigate` builds it — the prefetch is
|
|
2622
|
+
* only ever used by a navigation that asks for the same one.
|
|
2623
|
+
*/
|
|
2624
|
+
function usePrefetch() {
|
|
2625
|
+
const { prefetchRoute } = useContext(ClientRouterContext);
|
|
2626
|
+
const { defaultLocale } = useContext(I18nContext);
|
|
2627
|
+
const location = useLocation();
|
|
2628
|
+
const currentPathname = location.pathname;
|
|
2629
|
+
const currentSearch = location.search;
|
|
2630
|
+
const currentLocale = location.locale;
|
|
2631
|
+
return useCallback(async (path, ...args) => {
|
|
2632
|
+
if (typeof window === "undefined" || !prefetchRoute) return;
|
|
2633
|
+
if (connectionRefusesPrefetch()) return;
|
|
2634
|
+
const [options = {}] = args;
|
|
2635
|
+
const { search = {}, params = {}, locale = null } = {
|
|
2636
|
+
params: {},
|
|
2637
|
+
search: {},
|
|
2638
|
+
locale: null,
|
|
2639
|
+
...options
|
|
2640
|
+
};
|
|
2641
|
+
let localeSegment = locale ?? currentLocale;
|
|
2642
|
+
if (localeSegment === defaultLocale) localeSegment = "";
|
|
2643
|
+
const pathname = applyParams$1(path, params) || "/";
|
|
2644
|
+
const queryString = new URLSearchParams(search).toString();
|
|
2645
|
+
const searchSegment = queryString.length > 0 ? `?${queryString}` : "";
|
|
2646
|
+
if (pathname === currentPathname && searchSegment === currentSearch) return;
|
|
2647
|
+
await prefetchRoute({
|
|
2648
|
+
pathname,
|
|
2649
|
+
search: searchSegment,
|
|
2650
|
+
localeSegment: localeSegment ? `/${localeSegment}` : ""
|
|
2651
|
+
});
|
|
2652
|
+
}, [
|
|
2653
|
+
prefetchRoute,
|
|
2654
|
+
defaultLocale,
|
|
2655
|
+
currentLocale,
|
|
2656
|
+
currentPathname,
|
|
2657
|
+
currentSearch
|
|
2658
|
+
]);
|
|
2659
|
+
}
|
|
2660
|
+
//#endregion
|
|
2475
2661
|
//#region client/useBreadcrumbs.ts
|
|
2476
2662
|
function useBreadcrumbs() {
|
|
2477
2663
|
const { pathname } = useRoute();
|
|
@@ -2506,13 +2692,17 @@ function useRouteTransition() {
|
|
|
2506
2692
|
}
|
|
2507
2693
|
//#endregion
|
|
2508
2694
|
//#region client/Link.tsx
|
|
2695
|
+
/** How long the pointer has to rest on an `intent` link before it counts. */
|
|
2696
|
+
var INTENT_DELAY = 100;
|
|
2697
|
+
/** How far ahead of the viewport a `viewport` link starts warming. */
|
|
2698
|
+
var VIEWPORT_MARGIN = "200px";
|
|
2509
2699
|
function normalizeSearch(search) {
|
|
2510
2700
|
return Object.fromEntries(Object.entries(search).filter(([_k, v]) => v !== void 0 && v !== null).map(([k, v]) => [k, String(v)]));
|
|
2511
2701
|
}
|
|
2512
2702
|
var Link = memo((props) => {
|
|
2513
2703
|
const _params = useParams();
|
|
2514
2704
|
const { isTransitioning, targetPath } = useRouteTransition();
|
|
2515
|
-
const { href, onClick, hash = "", active = false, params = {}, search = {}, ...rest } = {
|
|
2705
|
+
const { href, onClick, onMouseEnter, onMouseLeave, onTouchStart, onFocus, onBlur, ref, hash = "", active = false, prefetch, params = {}, search = {}, ...rest } = {
|
|
2516
2706
|
params: _params,
|
|
2517
2707
|
search: {},
|
|
2518
2708
|
...props
|
|
@@ -2520,6 +2710,7 @@ var Link = memo((props) => {
|
|
|
2520
2710
|
const { defaultLocale } = useContext(I18nContext);
|
|
2521
2711
|
const { push } = useNavigate();
|
|
2522
2712
|
const location = useLocation();
|
|
2713
|
+
const prefetchRoute = usePrefetch();
|
|
2523
2714
|
const searchParams = new URLSearchParams(normalizeSearch(search));
|
|
2524
2715
|
const path = applyParams$1(href, params);
|
|
2525
2716
|
let urlLocaleSegment = location.locale;
|
|
@@ -2531,7 +2722,70 @@ var Link = memo((props) => {
|
|
|
2531
2722
|
location.search,
|
|
2532
2723
|
location.hash
|
|
2533
2724
|
].filter((item) => !!item).join("");
|
|
2725
|
+
const prefetchRouteRef = useRef(prefetchRoute);
|
|
2726
|
+
useEffect(() => {
|
|
2727
|
+
prefetchRouteRef.current = prefetchRoute;
|
|
2728
|
+
});
|
|
2729
|
+
const isShallowTarget = (path || "/") === location.pathname;
|
|
2730
|
+
const strategies = !prefetch ? [] : Array.isArray(prefetch) ? prefetch : [prefetch];
|
|
2731
|
+
const uses = (strategy) => strategies.includes(strategy);
|
|
2732
|
+
const strategyKey = strategies.join(",");
|
|
2733
|
+
const runPrefetch = () => {
|
|
2734
|
+
if (strategies.length === 0 || isShallowTarget) return;
|
|
2735
|
+
prefetchRouteRef.current(href, {
|
|
2736
|
+
params,
|
|
2737
|
+
search
|
|
2738
|
+
});
|
|
2739
|
+
};
|
|
2740
|
+
useEffect(() => {
|
|
2741
|
+
if (uses("render")) runPrefetch();
|
|
2742
|
+
}, [strategyKey, targetHref]);
|
|
2743
|
+
const anchorRef = useRef(null);
|
|
2744
|
+
const setAnchorRef = useCallback((node) => {
|
|
2745
|
+
anchorRef.current = node;
|
|
2746
|
+
if (typeof ref === "function") ref(node);
|
|
2747
|
+
else if (ref) ref.current = node;
|
|
2748
|
+
}, [ref]);
|
|
2749
|
+
useEffect(() => {
|
|
2750
|
+
if (!uses("viewport")) return;
|
|
2751
|
+
const element = anchorRef.current;
|
|
2752
|
+
if (!element || typeof IntersectionObserver === "undefined") return;
|
|
2753
|
+
const observer = new IntersectionObserver((entries) => {
|
|
2754
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
2755
|
+
observer.disconnect();
|
|
2756
|
+
runPrefetch();
|
|
2757
|
+
}
|
|
2758
|
+
}, { rootMargin: VIEWPORT_MARGIN });
|
|
2759
|
+
observer.observe(element);
|
|
2760
|
+
return () => observer.disconnect();
|
|
2761
|
+
}, [strategyKey, targetHref]);
|
|
2762
|
+
const intentTimerRef = useRef(null);
|
|
2763
|
+
const cancelIntent = () => {
|
|
2764
|
+
if (intentTimerRef.current !== null) {
|
|
2765
|
+
clearTimeout(intentTimerRef.current);
|
|
2766
|
+
intentTimerRef.current = null;
|
|
2767
|
+
}
|
|
2768
|
+
};
|
|
2769
|
+
useEffect(() => cancelIntent, []);
|
|
2770
|
+
/** A pointer arriving — the one signal `intent` waits on before believing. */
|
|
2771
|
+
const pointerArrived = () => {
|
|
2772
|
+
if (uses("hover")) runPrefetch();
|
|
2773
|
+
else if (uses("intent")) {
|
|
2774
|
+
cancelIntent();
|
|
2775
|
+
intentTimerRef.current = setTimeout(runPrefetch, INTENT_DELAY);
|
|
2776
|
+
}
|
|
2777
|
+
};
|
|
2778
|
+
/** Focus and touch are deliberate, so neither strategy makes them wait. */
|
|
2779
|
+
const linkTargeted = () => {
|
|
2780
|
+
if (uses("hover") || uses("intent")) runPrefetch();
|
|
2781
|
+
};
|
|
2782
|
+
/** Runs the caller's own handler first, then the prefetch trigger. */
|
|
2783
|
+
const prefetchOn = (handler, trigger) => (event) => {
|
|
2784
|
+
handler?.(event);
|
|
2785
|
+
trigger();
|
|
2786
|
+
};
|
|
2534
2787
|
return /* @__PURE__ */ jsx("a", {
|
|
2788
|
+
ref: setAnchorRef,
|
|
2535
2789
|
"data-active": active || currentHref === targetHref,
|
|
2536
2790
|
"data-pending": href === targetPath && isTransitioning,
|
|
2537
2791
|
href: targetHref === "" ? "/" : targetHref,
|
|
@@ -2546,7 +2800,6 @@ var Link = memo((props) => {
|
|
|
2546
2800
|
currentPath = currentPath === "" ? "/" : currentPath;
|
|
2547
2801
|
onClick?.(e);
|
|
2548
2802
|
if (hash === "") e.preventDefault();
|
|
2549
|
-
onClick?.(e);
|
|
2550
2803
|
push(href, {
|
|
2551
2804
|
hash,
|
|
2552
2805
|
search,
|
|
@@ -2554,6 +2807,11 @@ var Link = memo((props) => {
|
|
|
2554
2807
|
shallow: path === currentPath
|
|
2555
2808
|
});
|
|
2556
2809
|
},
|
|
2810
|
+
onMouseEnter: prefetchOn(onMouseEnter, pointerArrived),
|
|
2811
|
+
onMouseLeave: prefetchOn(onMouseLeave, cancelIntent),
|
|
2812
|
+
onTouchStart: prefetchOn(onTouchStart, linkTargeted),
|
|
2813
|
+
onFocus: prefetchOn(onFocus, linkTargeted),
|
|
2814
|
+
onBlur: prefetchOn(onBlur, cancelIntent),
|
|
2557
2815
|
...rest
|
|
2558
2816
|
});
|
|
2559
2817
|
});
|
|
@@ -2864,6 +3122,47 @@ function mergeCarriedSegments(previous, next, carriedViews) {
|
|
|
2864
3122
|
};
|
|
2865
3123
|
}
|
|
2866
3124
|
//#endregion
|
|
3125
|
+
//#region client/helpers/loadRoutePayload.ts
|
|
3126
|
+
/**
|
|
3127
|
+
* The page data for a navigation, from whichever source can produce it.
|
|
3128
|
+
*
|
|
3129
|
+
* A prefetched payload is always a full render, so it can be committed as-is
|
|
3130
|
+
* and the `x-gemi-from` round trip skipped entirely. Everything else — no
|
|
3131
|
+
* prefetch, a prefetch that failed, a partial response computed against a route
|
|
3132
|
+
* that has since been navigated away from — falls through to a request.
|
|
3133
|
+
*
|
|
3134
|
+
* Returns `null` when nothing usable came back; the caller leaves the current
|
|
3135
|
+
* route on screen.
|
|
3136
|
+
*/
|
|
3137
|
+
async function loadRoutePayload(options) {
|
|
3138
|
+
const { url, from, takePrefetched, renderedRoute } = options;
|
|
3139
|
+
const prefetched = takePrefetched?.(url);
|
|
3140
|
+
if (prefetched) {
|
|
3141
|
+
const payload = await prefetched;
|
|
3142
|
+
if (payload) return payload;
|
|
3143
|
+
}
|
|
3144
|
+
let response = {
|
|
3145
|
+
ok: false,
|
|
3146
|
+
json: async () => ({})
|
|
3147
|
+
};
|
|
3148
|
+
try {
|
|
3149
|
+
response = await fetch(url, { headers: { [PARTIAL_RENDER_HEADER]: from } });
|
|
3150
|
+
} catch (e) {
|
|
3151
|
+
console.error(e);
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
if (!response.ok) return null;
|
|
3155
|
+
const payload = await response.json();
|
|
3156
|
+
const claimed = payload?.partial ?? null;
|
|
3157
|
+
if (claimed && claimed.from !== renderedRoute()) try {
|
|
3158
|
+
const full = await fetch(url);
|
|
3159
|
+
if (full.ok) return await full.json();
|
|
3160
|
+
} catch (e) {
|
|
3161
|
+
console.error(e);
|
|
3162
|
+
}
|
|
3163
|
+
return payload;
|
|
3164
|
+
}
|
|
3165
|
+
//#endregion
|
|
2867
3166
|
//#region client/ClientRouter.tsx
|
|
2868
3167
|
function restoreScroll(action = null, _pathname = "no path") {
|
|
2869
3168
|
if (action === null) return;
|
|
@@ -2929,7 +3228,7 @@ var Routes = (props) => {
|
|
|
2929
3228
|
const { componentTree } = props;
|
|
2930
3229
|
const [isPending, startTransition] = useTransition();
|
|
2931
3230
|
const [isFetching, setIsFetching] = useState(false);
|
|
2932
|
-
const { routerSubject, fetchRouteCSS } = useContext(ClientRouterContext);
|
|
3231
|
+
const { routerSubject, fetchRouteCSS, takePrefetched } = useContext(ClientRouterContext);
|
|
2933
3232
|
const { hydrate } = useContext(QueryManagerContext);
|
|
2934
3233
|
const [transitionPath, setTransitionPath] = useState([null, routerSubject?.getValue().pathname]);
|
|
2935
3234
|
const { breadcrumbs, pageData, i18n, prefetchedData, appId: currentAppId } = useContext(ServerDataContext);
|
|
@@ -2975,35 +3274,22 @@ var Routes = (props) => {
|
|
|
2975
3274
|
}));
|
|
2976
3275
|
return;
|
|
2977
3276
|
}
|
|
2978
|
-
const
|
|
2979
|
-
|
|
3277
|
+
const url = routeDataUrl({
|
|
3278
|
+
pathname,
|
|
3279
|
+
search,
|
|
3280
|
+
localeSegment: routerState.locale ? `/${routerState.locale}` : ""
|
|
3281
|
+
});
|
|
2980
3282
|
const from = renderedRouteRef.current;
|
|
2981
3283
|
setIsFetching(true);
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
if (!window?.loaders) return Promise.resolve();
|
|
2992
|
-
(window?.loaders?.[component] ?? (() => ({})))();
|
|
2993
|
-
})
|
|
2994
|
-
]))[0];
|
|
2995
|
-
} catch (e) {
|
|
2996
|
-
console.error(e);
|
|
2997
|
-
}
|
|
2998
|
-
if (res.ok) {
|
|
2999
|
-
let payload = await res.json();
|
|
3000
|
-
const claimed = payload.partial ?? null;
|
|
3001
|
-
if (claimed && claimed.from !== renderedRouteRef.current) try {
|
|
3002
|
-
const full = await fetch(url);
|
|
3003
|
-
if (full.ok) payload = await full.json();
|
|
3004
|
-
} catch (e) {
|
|
3005
|
-
console.error(e);
|
|
3006
|
-
}
|
|
3284
|
+
fetchRouteCSS(routerState.routePath).catch((e) => console.error(e));
|
|
3285
|
+
for (const component of views) window?.loaders?.[component]?.();
|
|
3286
|
+
const payload = await loadRoutePayload({
|
|
3287
|
+
url,
|
|
3288
|
+
from,
|
|
3289
|
+
takePrefetched,
|
|
3290
|
+
renderedRoute: () => renderedRouteRef.current
|
|
3291
|
+
});
|
|
3292
|
+
if (payload) {
|
|
3007
3293
|
const { data, i18n, prefetchedData, breadcrumbs, meta, directive = {}, is404 = false, appId } = payload;
|
|
3008
3294
|
updateMeta(meta);
|
|
3009
3295
|
if (directive?.kind === "Redirect") {
|
|
@@ -3040,6 +3326,7 @@ var Routes = (props) => {
|
|
|
3040
3326
|
}, [
|
|
3041
3327
|
routerSubject,
|
|
3042
3328
|
fetchRouteCSS,
|
|
3329
|
+
takePrefetched,
|
|
3043
3330
|
replace,
|
|
3044
3331
|
hydrate
|
|
3045
3332
|
]);
|
|
@@ -3435,6 +3722,6 @@ function useAppIdMissmatch() {
|
|
|
3435
3722
|
return current !== next;
|
|
3436
3723
|
}
|
|
3437
3724
|
//#endregion
|
|
3438
|
-
export { Form, FormError, FormFieldContainer, Head, Image, Link, OpenGraphImage, QueryManagerProvider, Redirect, ValidationErrors, create, createRoot, init, useAppIdMissmatch, useBreadcrumbs, useBroadcast, useDelete, useForgotPassword, useFormData, useFormStatus, useIsNavigationPending, useLocale, useLocation, useMutate, useMutation, useMutationStatus, useNavigate, useNavigationProgress, useParams, usePatch, usePost, usePut, useQuery, useResetPassword, useRoute, useRouteTransition, useSearchParams, useSignIn, useSignOut, useSignUp, useSubscription, useTheme, useTranslator, useUpload, useUser };
|
|
3725
|
+
export { Form, FormError, FormFieldContainer, Head, Image, Link, OpenGraphImage, QueryManagerProvider, Redirect, ValidationErrors, create, createRoot, init, useAppIdMissmatch, useBreadcrumbs, useBroadcast, useDelete, useForgotPassword, useFormData, useFormStatus, useIsNavigationPending, useLocale, useLocation, useMutate, useMutation, useMutationStatus, useNavigate, useNavigationProgress, useParams, usePatch, usePost, usePrefetch, usePut, useQuery, useResetPassword, useRoute, useRouteTransition, useSearchParams, useSignIn, useSignOut, useSignUp, useSubscription, useTheme, useTranslator, useUpload, useUser };
|
|
3439
3726
|
|
|
3440
3727
|
//# sourceMappingURL=index.js.map
|