@yoamigo.com/core 0.3.7 → 0.3.9
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/index.d.ts +1 -1
- package/dist/index.js +320 -8
- package/dist/lib.js +223 -8
- package/dist/plugin.js +4 -0
- package/dist/prod.d.ts +1 -1
- package/dist/prod.js +97 -0
- package/dist/router.d.ts +12 -1
- package/dist/router.js +97 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React$1, { ReactNode, CSSProperties } from 'react';
|
|
3
3
|
export { C as ContentStoreProviderProd, g as EmbedFieldValue, h as EmbedType, d as MarkdownText, e as MarkdownTextProps, P as PageInfo, b as StaticImage, c as StaticImageProps, M as StaticText, S as StaticTextProps, Y as YaEmbed, f as YaEmbedProps, i as YaLink, j as YaLinkProps, p as parseEmbedUrl, s as serializeEmbedValue, u as useContentStoreProd } from './MarkdownText-DHJo0ofY.js';
|
|
4
|
-
export { Link, LinkProps, NavigateFunction, Router, RouterProps, useNavigate } from './router.js';
|
|
4
|
+
export { Link, LinkProps, NavigateFunction, Router, RouterProps, ScrollRestoration, useNavigate } from './router.js';
|
|
5
5
|
export { Route, Switch, useParams } from 'wouter';
|
|
6
6
|
export { A as AssetResolverFn, C as ContentRegistry, c as contentRegistry, a as getAllContent, g as getContent, h as hasContent, r as registerContent, b as resolveAssetUrl, s as setAssetResolver } from './asset-resolver-BnIvDkVv.js';
|
|
7
7
|
export { i as initBuilderSelection } from './builder-selection-CYP91nRu.js';
|
package/dist/index.js
CHANGED
|
@@ -181,9 +181,13 @@ var BuilderSelectionManager = class {
|
|
|
181
181
|
page: window.location.pathname
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
|
-
sendToParent(message) {
|
|
184
|
+
sendToParent(message, transfer) {
|
|
185
185
|
try {
|
|
186
|
-
|
|
186
|
+
if (transfer && transfer.length > 0) {
|
|
187
|
+
window.parent.postMessage(message, "*", transfer);
|
|
188
|
+
} else {
|
|
189
|
+
window.parent.postMessage(message, "*");
|
|
190
|
+
}
|
|
187
191
|
} catch (error) {
|
|
188
192
|
console.error("[BuilderSelection] Failed to send message to parent:", error);
|
|
189
193
|
}
|
|
@@ -210,6 +214,25 @@ var BuilderSelectionManager = class {
|
|
|
210
214
|
this.captureRegionScreenshot(data.region);
|
|
211
215
|
}
|
|
212
216
|
break;
|
|
217
|
+
case "NAVIGATE":
|
|
218
|
+
if (data.path) {
|
|
219
|
+
this.handleNavigate(data.path);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
case "CAPTURE_ALL_PAGES":
|
|
223
|
+
if (data.checkpointId && data.viewport) {
|
|
224
|
+
this.captureAllPagesScreenshots(data.checkpointId, data.viewport);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
case "GET_SCROLL_POSITION":
|
|
228
|
+
this.sendToParent({ type: "SCROLL_POSITION", scrollY: window.scrollY });
|
|
229
|
+
break;
|
|
230
|
+
case "GET_ALL_ROUTES":
|
|
231
|
+
this.sendToParent({ type: "ALL_ROUTES", routes: this.getAllRoutes() });
|
|
232
|
+
break;
|
|
233
|
+
case "GET_CURRENT_PAGE":
|
|
234
|
+
this.sendToParent({ type: "CURRENT_PAGE", pagePath: window.location.pathname });
|
|
235
|
+
break;
|
|
213
236
|
}
|
|
214
237
|
});
|
|
215
238
|
}
|
|
@@ -285,6 +308,25 @@ var BuilderSelectionManager = class {
|
|
|
285
308
|
handleKeyDown = (e) => {
|
|
286
309
|
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
287
310
|
const modifier = isMac ? e.metaKey : e.ctrlKey;
|
|
311
|
+
const activeEl = document.activeElement;
|
|
312
|
+
const isEditing = activeEl?.tagName === "INPUT" || activeEl?.tagName === "TEXTAREA" || activeEl?.getAttribute("contenteditable") === "true" || activeEl?.closest(".ya-text-editing") || activeEl?.closest(".ya-link-editing");
|
|
313
|
+
if (!isEditing && modifier) {
|
|
314
|
+
if (e.key === "z" && !e.shiftKey) {
|
|
315
|
+
e.preventDefault();
|
|
316
|
+
this.sendToParent({ type: "IFRAME_UNDO" });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (e.key === "z" && e.shiftKey) {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
this.sendToParent({ type: "IFRAME_REDO" });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (!isEditing && e.ctrlKey && e.key === "y") {
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
this.sendToParent({ type: "IFRAME_REDO" });
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
288
330
|
if (modifier) {
|
|
289
331
|
if (e.key === "=" || e.key === "+" || e.key === "-" || e.key === "0" || e.key === "1") {
|
|
290
332
|
e.preventDefault();
|
|
@@ -293,12 +335,8 @@ var BuilderSelectionManager = class {
|
|
|
293
335
|
return;
|
|
294
336
|
}
|
|
295
337
|
}
|
|
296
|
-
if (e.key === "Shift") {
|
|
297
|
-
|
|
298
|
-
const isEditing = activeElement?.closest(".ya-text-editing") || activeElement?.closest(".ya-link-editing");
|
|
299
|
-
if (!isEditing) {
|
|
300
|
-
this.sendToParent({ type: "SHIFT_KEY_PRESSED" });
|
|
301
|
-
}
|
|
338
|
+
if (e.key === "Shift" && !isEditing) {
|
|
339
|
+
this.sendToParent({ type: "SHIFT_KEY_PRESSED" });
|
|
302
340
|
}
|
|
303
341
|
if (e.key === "Escape") {
|
|
304
342
|
this.sendToParent({ type: "ESCAPE_KEY_PRESSED" });
|
|
@@ -517,6 +555,35 @@ var BuilderSelectionManager = class {
|
|
|
517
555
|
this.notifyPageReady();
|
|
518
556
|
};
|
|
519
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Navigate to a new route with retry logic for HMR timing
|
|
560
|
+
* Used by AI agent to navigate after creating new pages
|
|
561
|
+
* Retries navigation if route doesn't exist yet (new page being created)
|
|
562
|
+
*/
|
|
563
|
+
handleNavigate(path) {
|
|
564
|
+
console.log("[BuilderSelection] Navigate request:", path);
|
|
565
|
+
const MAX_RETRIES = 10;
|
|
566
|
+
const RETRY_DELAY_MS = 300;
|
|
567
|
+
let retries = 0;
|
|
568
|
+
const attemptNavigation = () => {
|
|
569
|
+
history.pushState({}, "", path);
|
|
570
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
571
|
+
setTimeout(() => {
|
|
572
|
+
if (window.location.pathname === path) {
|
|
573
|
+
console.log("[BuilderSelection] Navigation successful:", path);
|
|
574
|
+
this.notifyPageReady();
|
|
575
|
+
} else if (retries < MAX_RETRIES) {
|
|
576
|
+
retries++;
|
|
577
|
+
console.log(`[BuilderSelection] Navigation retry ${retries}/${MAX_RETRIES} for path: ${path}`);
|
|
578
|
+
setTimeout(attemptNavigation, RETRY_DELAY_MS);
|
|
579
|
+
} else {
|
|
580
|
+
console.warn("[BuilderSelection] Navigation failed after max retries:", path);
|
|
581
|
+
this.notifyPageReady();
|
|
582
|
+
}
|
|
583
|
+
}, 100);
|
|
584
|
+
};
|
|
585
|
+
attemptNavigation();
|
|
586
|
+
}
|
|
520
587
|
showHoverOverlay(element) {
|
|
521
588
|
if (!this.hoverOverlay) return;
|
|
522
589
|
const rect = element.getBoundingClientRect();
|
|
@@ -757,6 +824,154 @@ var BuilderSelectionManager = class {
|
|
|
757
824
|
document.body.appendChild(container);
|
|
758
825
|
this.selections.set(selectionId, { element, container, badge, border });
|
|
759
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* Get all available routes from the page
|
|
829
|
+
* Looks for routes registered via wouter or standard link patterns
|
|
830
|
+
*/
|
|
831
|
+
getAllRoutes() {
|
|
832
|
+
const wouterRoutes = window.__wouterRoutes;
|
|
833
|
+
if (wouterRoutes && Array.isArray(wouterRoutes)) {
|
|
834
|
+
return wouterRoutes;
|
|
835
|
+
}
|
|
836
|
+
const links = document.querySelectorAll('a[href^="/"]');
|
|
837
|
+
const routes = /* @__PURE__ */ new Set(["/"]);
|
|
838
|
+
links.forEach((link) => {
|
|
839
|
+
const href = link.getAttribute("href");
|
|
840
|
+
if (href && href.startsWith("/") && !href.includes("http")) {
|
|
841
|
+
const path = href.split("?")[0].replace(/\/$/, "") || "/";
|
|
842
|
+
routes.add(path);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
const navLinks = document.querySelectorAll("nav a[href]");
|
|
846
|
+
navLinks.forEach((link) => {
|
|
847
|
+
const href = link.getAttribute("href");
|
|
848
|
+
if (href && href.startsWith("/")) {
|
|
849
|
+
const path = href.split("?")[0].replace(/\/$/, "") || "/";
|
|
850
|
+
routes.add(path);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
return Array.from(routes).sort();
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Capture full-page screenshots for all pages
|
|
857
|
+
* Non-blocking: uses requestIdleCallback between pages
|
|
858
|
+
* Transfers ArrayBuffer directly (zero-copy) for performance
|
|
859
|
+
*/
|
|
860
|
+
async captureAllPagesScreenshots(checkpointId, viewport) {
|
|
861
|
+
console.log("[BuilderSelection] Starting multi-page capture for checkpoint:", checkpointId);
|
|
862
|
+
const routes = this.getAllRoutes();
|
|
863
|
+
const originalPath = window.location.pathname;
|
|
864
|
+
const total = routes.length;
|
|
865
|
+
this.sendToParent({
|
|
866
|
+
type: "CAPTURE_PROGRESS",
|
|
867
|
+
checkpointId,
|
|
868
|
+
completed: 0,
|
|
869
|
+
total
|
|
870
|
+
});
|
|
871
|
+
for (let i = 0; i < routes.length; i++) {
|
|
872
|
+
const pagePath = routes[i];
|
|
873
|
+
try {
|
|
874
|
+
await new Promise((resolve) => {
|
|
875
|
+
if ("requestIdleCallback" in window) {
|
|
876
|
+
requestIdleCallback(() => resolve(), { timeout: 1e3 });
|
|
877
|
+
} else {
|
|
878
|
+
setTimeout(resolve, 50);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
if (window.location.pathname !== pagePath) {
|
|
882
|
+
await this.navigateAndWait(pagePath);
|
|
883
|
+
}
|
|
884
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
885
|
+
const buffer = await this.capturePageAsArrayBuffer();
|
|
886
|
+
if (buffer) {
|
|
887
|
+
this.sendToParent(
|
|
888
|
+
{
|
|
889
|
+
type: "PAGE_SCREENSHOT_READY",
|
|
890
|
+
checkpointId,
|
|
891
|
+
pagePath,
|
|
892
|
+
buffer
|
|
893
|
+
},
|
|
894
|
+
[buffer]
|
|
895
|
+
// Transfer list - moves ownership, no copy
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
this.sendToParent({
|
|
899
|
+
type: "CAPTURE_PROGRESS",
|
|
900
|
+
checkpointId,
|
|
901
|
+
completed: i + 1,
|
|
902
|
+
total
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
console.error(`[BuilderSelection] Failed to capture page ${pagePath}:`, error);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (window.location.pathname !== originalPath) {
|
|
909
|
+
await this.navigateAndWait(originalPath);
|
|
910
|
+
}
|
|
911
|
+
this.sendToParent({
|
|
912
|
+
type: "ALL_SCREENSHOTS_COMPLETE",
|
|
913
|
+
checkpointId
|
|
914
|
+
});
|
|
915
|
+
console.log("[BuilderSelection] Multi-page capture complete:", checkpointId);
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Navigate to a path and wait for page ready
|
|
919
|
+
*/
|
|
920
|
+
async navigateAndWait(path) {
|
|
921
|
+
return new Promise((resolve) => {
|
|
922
|
+
history.pushState({}, "", path);
|
|
923
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
924
|
+
requestAnimationFrame(() => {
|
|
925
|
+
requestAnimationFrame(() => {
|
|
926
|
+
resolve();
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Capture current page as ArrayBuffer (for zero-copy transfer)
|
|
933
|
+
*/
|
|
934
|
+
async capturePageAsArrayBuffer() {
|
|
935
|
+
try {
|
|
936
|
+
const html2canvas = (await import("html2canvas-pro")).default;
|
|
937
|
+
const overlays = document.querySelectorAll(".builder-selection-container, #builder-hover-overlay");
|
|
938
|
+
overlays.forEach((el) => {
|
|
939
|
+
;
|
|
940
|
+
el.style.display = "none";
|
|
941
|
+
});
|
|
942
|
+
const originalScrollY = window.scrollY;
|
|
943
|
+
window.scrollTo(0, 0);
|
|
944
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
945
|
+
const canvas = await html2canvas(document.body, {
|
|
946
|
+
scale: 0.5,
|
|
947
|
+
// Lower resolution for reasonable file size
|
|
948
|
+
logging: false,
|
|
949
|
+
useCORS: true,
|
|
950
|
+
allowTaint: true,
|
|
951
|
+
backgroundColor: "#ffffff",
|
|
952
|
+
windowHeight: document.body.scrollHeight,
|
|
953
|
+
// Full page height
|
|
954
|
+
height: document.body.scrollHeight
|
|
955
|
+
});
|
|
956
|
+
window.scrollTo(0, originalScrollY);
|
|
957
|
+
overlays.forEach((el) => {
|
|
958
|
+
;
|
|
959
|
+
el.style.display = "";
|
|
960
|
+
});
|
|
961
|
+
const blob = await new Promise((resolve) => {
|
|
962
|
+
canvas.toBlob((b) => resolve(b), "image/jpeg", 0.6);
|
|
963
|
+
});
|
|
964
|
+
if (!blob) {
|
|
965
|
+
console.error("[BuilderSelection] Failed to create blob from canvas");
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
const buffer = await blob.arrayBuffer();
|
|
969
|
+
return buffer;
|
|
970
|
+
} catch (error) {
|
|
971
|
+
console.error("[BuilderSelection] Page capture failed:", error);
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
760
975
|
};
|
|
761
976
|
var instance = null;
|
|
762
977
|
function initBuilderSelection() {
|
|
@@ -5909,6 +6124,102 @@ function Router({ children, base }) {
|
|
|
5909
6124
|
return /* @__PURE__ */ jsx21(WouterRouter, { base: basename, children });
|
|
5910
6125
|
}
|
|
5911
6126
|
|
|
6127
|
+
// src/router/ScrollRestoration.tsx
|
|
6128
|
+
import { useEffect as useEffect15, useRef as useRef15 } from "react";
|
|
6129
|
+
import { useLocation as useLocation3 } from "wouter";
|
|
6130
|
+
var SCROLL_POSITIONS_KEY = "yoamigo-scroll-positions";
|
|
6131
|
+
var HISTORY_INDEX_KEY = "yoamigo-history-index";
|
|
6132
|
+
function getScrollPositions() {
|
|
6133
|
+
if (typeof sessionStorage === "undefined") return {};
|
|
6134
|
+
try {
|
|
6135
|
+
const stored = sessionStorage.getItem(SCROLL_POSITIONS_KEY);
|
|
6136
|
+
return stored ? JSON.parse(stored) : {};
|
|
6137
|
+
} catch {
|
|
6138
|
+
return {};
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
function saveScrollPositions(positions) {
|
|
6142
|
+
if (typeof sessionStorage === "undefined") return;
|
|
6143
|
+
try {
|
|
6144
|
+
sessionStorage.setItem(SCROLL_POSITIONS_KEY, JSON.stringify(positions));
|
|
6145
|
+
} catch {
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
function getHistoryIndex() {
|
|
6149
|
+
if (typeof history === "undefined") return 0;
|
|
6150
|
+
const state = history.state;
|
|
6151
|
+
if (state && typeof state[HISTORY_INDEX_KEY] === "number") {
|
|
6152
|
+
return state[HISTORY_INDEX_KEY];
|
|
6153
|
+
}
|
|
6154
|
+
return 0;
|
|
6155
|
+
}
|
|
6156
|
+
function setHistoryIndex(index) {
|
|
6157
|
+
if (typeof history === "undefined") return;
|
|
6158
|
+
const newState = { ...history.state, [HISTORY_INDEX_KEY]: index };
|
|
6159
|
+
history.replaceState(newState, "");
|
|
6160
|
+
}
|
|
6161
|
+
var globalHistoryIndex = 0;
|
|
6162
|
+
function ScrollRestoration() {
|
|
6163
|
+
const [location] = useLocation3();
|
|
6164
|
+
const previousLocation = useRef15(location);
|
|
6165
|
+
const isPopState = useRef15(false);
|
|
6166
|
+
const scrollPositionsRef = useRef15({});
|
|
6167
|
+
useEffect15(() => {
|
|
6168
|
+
if (typeof history !== "undefined" && "scrollRestoration" in history) {
|
|
6169
|
+
history.scrollRestoration = "manual";
|
|
6170
|
+
}
|
|
6171
|
+
scrollPositionsRef.current = getScrollPositions();
|
|
6172
|
+
const existingIndex = getHistoryIndex();
|
|
6173
|
+
if (existingIndex === 0 && !history.state?.[HISTORY_INDEX_KEY]) {
|
|
6174
|
+
globalHistoryIndex = 1;
|
|
6175
|
+
setHistoryIndex(globalHistoryIndex);
|
|
6176
|
+
} else {
|
|
6177
|
+
globalHistoryIndex = existingIndex;
|
|
6178
|
+
}
|
|
6179
|
+
const handlePopState = () => {
|
|
6180
|
+
isPopState.current = true;
|
|
6181
|
+
};
|
|
6182
|
+
window.addEventListener("popstate", handlePopState);
|
|
6183
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
6184
|
+
}, []);
|
|
6185
|
+
useEffect15(() => {
|
|
6186
|
+
if (previousLocation.current === location) return;
|
|
6187
|
+
const prevHistoryIndex = globalHistoryIndex;
|
|
6188
|
+
const currentScrollY = window.scrollY;
|
|
6189
|
+
if (isPopState.current) {
|
|
6190
|
+
isPopState.current = false;
|
|
6191
|
+
globalHistoryIndex = getHistoryIndex() || globalHistoryIndex;
|
|
6192
|
+
const savedScroll = scrollPositionsRef.current[`${globalHistoryIndex}-${location}`];
|
|
6193
|
+
requestAnimationFrame(() => {
|
|
6194
|
+
window.scrollTo({ top: savedScroll ?? 0, behavior: "instant" });
|
|
6195
|
+
});
|
|
6196
|
+
} else {
|
|
6197
|
+
scrollPositionsRef.current[`${prevHistoryIndex}-${previousLocation.current}`] = currentScrollY;
|
|
6198
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
6199
|
+
globalHistoryIndex = prevHistoryIndex + 1;
|
|
6200
|
+
setHistoryIndex(globalHistoryIndex);
|
|
6201
|
+
window.scrollTo({ top: 0, behavior: "instant" });
|
|
6202
|
+
}
|
|
6203
|
+
previousLocation.current = location;
|
|
6204
|
+
}, [location]);
|
|
6205
|
+
useEffect15(() => {
|
|
6206
|
+
let timeoutId;
|
|
6207
|
+
const handleScroll = () => {
|
|
6208
|
+
clearTimeout(timeoutId);
|
|
6209
|
+
timeoutId = setTimeout(() => {
|
|
6210
|
+
scrollPositionsRef.current[`${globalHistoryIndex}-${location}`] = window.scrollY;
|
|
6211
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
6212
|
+
}, 100);
|
|
6213
|
+
};
|
|
6214
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
6215
|
+
return () => {
|
|
6216
|
+
clearTimeout(timeoutId);
|
|
6217
|
+
window.removeEventListener("scroll", handleScroll);
|
|
6218
|
+
};
|
|
6219
|
+
}, [location]);
|
|
6220
|
+
return null;
|
|
6221
|
+
}
|
|
6222
|
+
|
|
5912
6223
|
// src/router/index.ts
|
|
5913
6224
|
import { Route, Switch, useParams } from "wouter";
|
|
5914
6225
|
export {
|
|
@@ -5921,6 +6232,7 @@ export {
|
|
|
5921
6232
|
Router,
|
|
5922
6233
|
SafeHtml,
|
|
5923
6234
|
SafeTriangleBelow,
|
|
6235
|
+
ScrollRestoration,
|
|
5924
6236
|
MpImage as StaticImage,
|
|
5925
6237
|
MpText as StaticText,
|
|
5926
6238
|
Switch,
|
package/dist/lib.js
CHANGED
|
@@ -140,9 +140,13 @@ var BuilderSelectionManager = class {
|
|
|
140
140
|
page: window.location.pathname
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
|
-
sendToParent(message) {
|
|
143
|
+
sendToParent(message, transfer) {
|
|
144
144
|
try {
|
|
145
|
-
|
|
145
|
+
if (transfer && transfer.length > 0) {
|
|
146
|
+
window.parent.postMessage(message, "*", transfer);
|
|
147
|
+
} else {
|
|
148
|
+
window.parent.postMessage(message, "*");
|
|
149
|
+
}
|
|
146
150
|
} catch (error) {
|
|
147
151
|
console.error("[BuilderSelection] Failed to send message to parent:", error);
|
|
148
152
|
}
|
|
@@ -169,6 +173,25 @@ var BuilderSelectionManager = class {
|
|
|
169
173
|
this.captureRegionScreenshot(data.region);
|
|
170
174
|
}
|
|
171
175
|
break;
|
|
176
|
+
case "NAVIGATE":
|
|
177
|
+
if (data.path) {
|
|
178
|
+
this.handleNavigate(data.path);
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
case "CAPTURE_ALL_PAGES":
|
|
182
|
+
if (data.checkpointId && data.viewport) {
|
|
183
|
+
this.captureAllPagesScreenshots(data.checkpointId, data.viewport);
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
case "GET_SCROLL_POSITION":
|
|
187
|
+
this.sendToParent({ type: "SCROLL_POSITION", scrollY: window.scrollY });
|
|
188
|
+
break;
|
|
189
|
+
case "GET_ALL_ROUTES":
|
|
190
|
+
this.sendToParent({ type: "ALL_ROUTES", routes: this.getAllRoutes() });
|
|
191
|
+
break;
|
|
192
|
+
case "GET_CURRENT_PAGE":
|
|
193
|
+
this.sendToParent({ type: "CURRENT_PAGE", pagePath: window.location.pathname });
|
|
194
|
+
break;
|
|
172
195
|
}
|
|
173
196
|
});
|
|
174
197
|
}
|
|
@@ -244,6 +267,25 @@ var BuilderSelectionManager = class {
|
|
|
244
267
|
handleKeyDown = (e) => {
|
|
245
268
|
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
|
|
246
269
|
const modifier = isMac ? e.metaKey : e.ctrlKey;
|
|
270
|
+
const activeEl = document.activeElement;
|
|
271
|
+
const isEditing = activeEl?.tagName === "INPUT" || activeEl?.tagName === "TEXTAREA" || activeEl?.getAttribute("contenteditable") === "true" || activeEl?.closest(".ya-text-editing") || activeEl?.closest(".ya-link-editing");
|
|
272
|
+
if (!isEditing && modifier) {
|
|
273
|
+
if (e.key === "z" && !e.shiftKey) {
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
this.sendToParent({ type: "IFRAME_UNDO" });
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (e.key === "z" && e.shiftKey) {
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
this.sendToParent({ type: "IFRAME_REDO" });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (!isEditing && e.ctrlKey && e.key === "y") {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
this.sendToParent({ type: "IFRAME_REDO" });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
247
289
|
if (modifier) {
|
|
248
290
|
if (e.key === "=" || e.key === "+" || e.key === "-" || e.key === "0" || e.key === "1") {
|
|
249
291
|
e.preventDefault();
|
|
@@ -252,12 +294,8 @@ var BuilderSelectionManager = class {
|
|
|
252
294
|
return;
|
|
253
295
|
}
|
|
254
296
|
}
|
|
255
|
-
if (e.key === "Shift") {
|
|
256
|
-
|
|
257
|
-
const isEditing = activeElement?.closest(".ya-text-editing") || activeElement?.closest(".ya-link-editing");
|
|
258
|
-
if (!isEditing) {
|
|
259
|
-
this.sendToParent({ type: "SHIFT_KEY_PRESSED" });
|
|
260
|
-
}
|
|
297
|
+
if (e.key === "Shift" && !isEditing) {
|
|
298
|
+
this.sendToParent({ type: "SHIFT_KEY_PRESSED" });
|
|
261
299
|
}
|
|
262
300
|
if (e.key === "Escape") {
|
|
263
301
|
this.sendToParent({ type: "ESCAPE_KEY_PRESSED" });
|
|
@@ -476,6 +514,35 @@ var BuilderSelectionManager = class {
|
|
|
476
514
|
this.notifyPageReady();
|
|
477
515
|
};
|
|
478
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* Navigate to a new route with retry logic for HMR timing
|
|
519
|
+
* Used by AI agent to navigate after creating new pages
|
|
520
|
+
* Retries navigation if route doesn't exist yet (new page being created)
|
|
521
|
+
*/
|
|
522
|
+
handleNavigate(path) {
|
|
523
|
+
console.log("[BuilderSelection] Navigate request:", path);
|
|
524
|
+
const MAX_RETRIES = 10;
|
|
525
|
+
const RETRY_DELAY_MS = 300;
|
|
526
|
+
let retries = 0;
|
|
527
|
+
const attemptNavigation = () => {
|
|
528
|
+
history.pushState({}, "", path);
|
|
529
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
530
|
+
setTimeout(() => {
|
|
531
|
+
if (window.location.pathname === path) {
|
|
532
|
+
console.log("[BuilderSelection] Navigation successful:", path);
|
|
533
|
+
this.notifyPageReady();
|
|
534
|
+
} else if (retries < MAX_RETRIES) {
|
|
535
|
+
retries++;
|
|
536
|
+
console.log(`[BuilderSelection] Navigation retry ${retries}/${MAX_RETRIES} for path: ${path}`);
|
|
537
|
+
setTimeout(attemptNavigation, RETRY_DELAY_MS);
|
|
538
|
+
} else {
|
|
539
|
+
console.warn("[BuilderSelection] Navigation failed after max retries:", path);
|
|
540
|
+
this.notifyPageReady();
|
|
541
|
+
}
|
|
542
|
+
}, 100);
|
|
543
|
+
};
|
|
544
|
+
attemptNavigation();
|
|
545
|
+
}
|
|
479
546
|
showHoverOverlay(element) {
|
|
480
547
|
if (!this.hoverOverlay) return;
|
|
481
548
|
const rect = element.getBoundingClientRect();
|
|
@@ -716,6 +783,154 @@ var BuilderSelectionManager = class {
|
|
|
716
783
|
document.body.appendChild(container);
|
|
717
784
|
this.selections.set(selectionId, { element, container, badge, border });
|
|
718
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Get all available routes from the page
|
|
788
|
+
* Looks for routes registered via wouter or standard link patterns
|
|
789
|
+
*/
|
|
790
|
+
getAllRoutes() {
|
|
791
|
+
const wouterRoutes = window.__wouterRoutes;
|
|
792
|
+
if (wouterRoutes && Array.isArray(wouterRoutes)) {
|
|
793
|
+
return wouterRoutes;
|
|
794
|
+
}
|
|
795
|
+
const links = document.querySelectorAll('a[href^="/"]');
|
|
796
|
+
const routes = /* @__PURE__ */ new Set(["/"]);
|
|
797
|
+
links.forEach((link) => {
|
|
798
|
+
const href = link.getAttribute("href");
|
|
799
|
+
if (href && href.startsWith("/") && !href.includes("http")) {
|
|
800
|
+
const path = href.split("?")[0].replace(/\/$/, "") || "/";
|
|
801
|
+
routes.add(path);
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
const navLinks = document.querySelectorAll("nav a[href]");
|
|
805
|
+
navLinks.forEach((link) => {
|
|
806
|
+
const href = link.getAttribute("href");
|
|
807
|
+
if (href && href.startsWith("/")) {
|
|
808
|
+
const path = href.split("?")[0].replace(/\/$/, "") || "/";
|
|
809
|
+
routes.add(path);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
return Array.from(routes).sort();
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Capture full-page screenshots for all pages
|
|
816
|
+
* Non-blocking: uses requestIdleCallback between pages
|
|
817
|
+
* Transfers ArrayBuffer directly (zero-copy) for performance
|
|
818
|
+
*/
|
|
819
|
+
async captureAllPagesScreenshots(checkpointId, viewport) {
|
|
820
|
+
console.log("[BuilderSelection] Starting multi-page capture for checkpoint:", checkpointId);
|
|
821
|
+
const routes = this.getAllRoutes();
|
|
822
|
+
const originalPath = window.location.pathname;
|
|
823
|
+
const total = routes.length;
|
|
824
|
+
this.sendToParent({
|
|
825
|
+
type: "CAPTURE_PROGRESS",
|
|
826
|
+
checkpointId,
|
|
827
|
+
completed: 0,
|
|
828
|
+
total
|
|
829
|
+
});
|
|
830
|
+
for (let i = 0; i < routes.length; i++) {
|
|
831
|
+
const pagePath = routes[i];
|
|
832
|
+
try {
|
|
833
|
+
await new Promise((resolve) => {
|
|
834
|
+
if ("requestIdleCallback" in window) {
|
|
835
|
+
requestIdleCallback(() => resolve(), { timeout: 1e3 });
|
|
836
|
+
} else {
|
|
837
|
+
setTimeout(resolve, 50);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
if (window.location.pathname !== pagePath) {
|
|
841
|
+
await this.navigateAndWait(pagePath);
|
|
842
|
+
}
|
|
843
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
844
|
+
const buffer = await this.capturePageAsArrayBuffer();
|
|
845
|
+
if (buffer) {
|
|
846
|
+
this.sendToParent(
|
|
847
|
+
{
|
|
848
|
+
type: "PAGE_SCREENSHOT_READY",
|
|
849
|
+
checkpointId,
|
|
850
|
+
pagePath,
|
|
851
|
+
buffer
|
|
852
|
+
},
|
|
853
|
+
[buffer]
|
|
854
|
+
// Transfer list - moves ownership, no copy
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
this.sendToParent({
|
|
858
|
+
type: "CAPTURE_PROGRESS",
|
|
859
|
+
checkpointId,
|
|
860
|
+
completed: i + 1,
|
|
861
|
+
total
|
|
862
|
+
});
|
|
863
|
+
} catch (error) {
|
|
864
|
+
console.error(`[BuilderSelection] Failed to capture page ${pagePath}:`, error);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (window.location.pathname !== originalPath) {
|
|
868
|
+
await this.navigateAndWait(originalPath);
|
|
869
|
+
}
|
|
870
|
+
this.sendToParent({
|
|
871
|
+
type: "ALL_SCREENSHOTS_COMPLETE",
|
|
872
|
+
checkpointId
|
|
873
|
+
});
|
|
874
|
+
console.log("[BuilderSelection] Multi-page capture complete:", checkpointId);
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Navigate to a path and wait for page ready
|
|
878
|
+
*/
|
|
879
|
+
async navigateAndWait(path) {
|
|
880
|
+
return new Promise((resolve) => {
|
|
881
|
+
history.pushState({}, "", path);
|
|
882
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
883
|
+
requestAnimationFrame(() => {
|
|
884
|
+
requestAnimationFrame(() => {
|
|
885
|
+
resolve();
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Capture current page as ArrayBuffer (for zero-copy transfer)
|
|
892
|
+
*/
|
|
893
|
+
async capturePageAsArrayBuffer() {
|
|
894
|
+
try {
|
|
895
|
+
const html2canvas = (await import("html2canvas-pro")).default;
|
|
896
|
+
const overlays = document.querySelectorAll(".builder-selection-container, #builder-hover-overlay");
|
|
897
|
+
overlays.forEach((el) => {
|
|
898
|
+
;
|
|
899
|
+
el.style.display = "none";
|
|
900
|
+
});
|
|
901
|
+
const originalScrollY = window.scrollY;
|
|
902
|
+
window.scrollTo(0, 0);
|
|
903
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
904
|
+
const canvas = await html2canvas(document.body, {
|
|
905
|
+
scale: 0.5,
|
|
906
|
+
// Lower resolution for reasonable file size
|
|
907
|
+
logging: false,
|
|
908
|
+
useCORS: true,
|
|
909
|
+
allowTaint: true,
|
|
910
|
+
backgroundColor: "#ffffff",
|
|
911
|
+
windowHeight: document.body.scrollHeight,
|
|
912
|
+
// Full page height
|
|
913
|
+
height: document.body.scrollHeight
|
|
914
|
+
});
|
|
915
|
+
window.scrollTo(0, originalScrollY);
|
|
916
|
+
overlays.forEach((el) => {
|
|
917
|
+
;
|
|
918
|
+
el.style.display = "";
|
|
919
|
+
});
|
|
920
|
+
const blob = await new Promise((resolve) => {
|
|
921
|
+
canvas.toBlob((b) => resolve(b), "image/jpeg", 0.6);
|
|
922
|
+
});
|
|
923
|
+
if (!blob) {
|
|
924
|
+
console.error("[BuilderSelection] Failed to create blob from canvas");
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
const buffer = await blob.arrayBuffer();
|
|
928
|
+
return buffer;
|
|
929
|
+
} catch (error) {
|
|
930
|
+
console.error("[BuilderSelection] Page capture failed:", error);
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
719
934
|
};
|
|
720
935
|
var instance = null;
|
|
721
936
|
function initBuilderSelection() {
|
package/dist/plugin.js
CHANGED
|
@@ -10,6 +10,7 @@ function yoamigoPlugin(options = {}) {
|
|
|
10
10
|
name: "yoamigo:content-hmr",
|
|
11
11
|
handleHotUpdate({ file, server }) {
|
|
12
12
|
if (!file.endsWith("/content.ts") && !file.endsWith("\\content.ts")) return;
|
|
13
|
+
console.log("[yoamigo:content-hmr] content.ts changed, sending HMR event to clients");
|
|
13
14
|
server.ws.send({
|
|
14
15
|
type: "custom",
|
|
15
16
|
event: "yoamigo:content-update"
|
|
@@ -21,12 +22,15 @@ function yoamigoPlugin(options = {}) {
|
|
|
21
22
|
const hmrCode = `
|
|
22
23
|
// YoAmigo Content HMR - injected by yoamigo:content-hmr plugin
|
|
23
24
|
if (import.meta.hot) {
|
|
25
|
+
console.log('[yoamigo:content-hmr] HMR listener registered')
|
|
24
26
|
import.meta.hot.on('yoamigo:content-update', async () => {
|
|
27
|
+
console.log('[yoamigo:content-hmr] Received content update event')
|
|
25
28
|
try {
|
|
26
29
|
const { default: newContent } = await import(/* @vite-ignore */ './content?t=' + Date.now())
|
|
27
30
|
const { registerContent } = await import('@yoamigo.com/core')
|
|
28
31
|
registerContent(newContent)
|
|
29
32
|
window.dispatchEvent(new CustomEvent('content-updated'))
|
|
33
|
+
console.log('[yoamigo:content-hmr] Content updated successfully')
|
|
30
34
|
} catch (err) {
|
|
31
35
|
console.error('[yoamigo:content-hmr] Failed to hot reload content:', err)
|
|
32
36
|
}
|
package/dist/prod.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { a as ContentStore, E as ContentStoreMode, C as ContentStoreProvider, d as MarkdownText, e as MarkdownTextProps, P as PageInfo, b as StaticImage, c as StaticImageProps, M as StaticText, S as StaticTextProps, b as YaImage, c as YaImageProps, M as YaText, S as YaTextProps, p as parseEmbedUrl, u as useContentStore } from './MarkdownText-DHJo0ofY.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React, { CSSProperties, ReactNode } from 'react';
|
|
4
|
-
export { Link, LinkProps, NavigateFunction, Router, RouterProps, useNavigate } from './router.js';
|
|
4
|
+
export { Link, LinkProps, NavigateFunction, Router, RouterProps, ScrollRestoration, useNavigate } from './router.js';
|
|
5
5
|
export { Route, Switch, useParams } from 'wouter';
|
|
6
6
|
export { A as AssetResolverFn, C as ContentRegistry, c as contentRegistry, a as getAllContent, g as getContent, h as hasContent, r as registerContent, b as resolveAssetUrl, s as setAssetResolver } from './asset-resolver-BnIvDkVv.js';
|
|
7
7
|
|
package/dist/prod.js
CHANGED
|
@@ -953,6 +953,102 @@ function Router({ children, base }) {
|
|
|
953
953
|
return /* @__PURE__ */ jsx12(WouterRouter, { base: basename, children });
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
+
// src/router/ScrollRestoration.tsx
|
|
957
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
958
|
+
import { useLocation as useLocation3 } from "wouter";
|
|
959
|
+
var SCROLL_POSITIONS_KEY = "yoamigo-scroll-positions";
|
|
960
|
+
var HISTORY_INDEX_KEY = "yoamigo-history-index";
|
|
961
|
+
function getScrollPositions() {
|
|
962
|
+
if (typeof sessionStorage === "undefined") return {};
|
|
963
|
+
try {
|
|
964
|
+
const stored = sessionStorage.getItem(SCROLL_POSITIONS_KEY);
|
|
965
|
+
return stored ? JSON.parse(stored) : {};
|
|
966
|
+
} catch {
|
|
967
|
+
return {};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function saveScrollPositions(positions) {
|
|
971
|
+
if (typeof sessionStorage === "undefined") return;
|
|
972
|
+
try {
|
|
973
|
+
sessionStorage.setItem(SCROLL_POSITIONS_KEY, JSON.stringify(positions));
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
function getHistoryIndex() {
|
|
978
|
+
if (typeof history === "undefined") return 0;
|
|
979
|
+
const state = history.state;
|
|
980
|
+
if (state && typeof state[HISTORY_INDEX_KEY] === "number") {
|
|
981
|
+
return state[HISTORY_INDEX_KEY];
|
|
982
|
+
}
|
|
983
|
+
return 0;
|
|
984
|
+
}
|
|
985
|
+
function setHistoryIndex(index) {
|
|
986
|
+
if (typeof history === "undefined") return;
|
|
987
|
+
const newState = { ...history.state, [HISTORY_INDEX_KEY]: index };
|
|
988
|
+
history.replaceState(newState, "");
|
|
989
|
+
}
|
|
990
|
+
var globalHistoryIndex = 0;
|
|
991
|
+
function ScrollRestoration() {
|
|
992
|
+
const [location] = useLocation3();
|
|
993
|
+
const previousLocation = useRef4(location);
|
|
994
|
+
const isPopState = useRef4(false);
|
|
995
|
+
const scrollPositionsRef = useRef4({});
|
|
996
|
+
useEffect4(() => {
|
|
997
|
+
if (typeof history !== "undefined" && "scrollRestoration" in history) {
|
|
998
|
+
history.scrollRestoration = "manual";
|
|
999
|
+
}
|
|
1000
|
+
scrollPositionsRef.current = getScrollPositions();
|
|
1001
|
+
const existingIndex = getHistoryIndex();
|
|
1002
|
+
if (existingIndex === 0 && !history.state?.[HISTORY_INDEX_KEY]) {
|
|
1003
|
+
globalHistoryIndex = 1;
|
|
1004
|
+
setHistoryIndex(globalHistoryIndex);
|
|
1005
|
+
} else {
|
|
1006
|
+
globalHistoryIndex = existingIndex;
|
|
1007
|
+
}
|
|
1008
|
+
const handlePopState = () => {
|
|
1009
|
+
isPopState.current = true;
|
|
1010
|
+
};
|
|
1011
|
+
window.addEventListener("popstate", handlePopState);
|
|
1012
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
1013
|
+
}, []);
|
|
1014
|
+
useEffect4(() => {
|
|
1015
|
+
if (previousLocation.current === location) return;
|
|
1016
|
+
const prevHistoryIndex = globalHistoryIndex;
|
|
1017
|
+
const currentScrollY = window.scrollY;
|
|
1018
|
+
if (isPopState.current) {
|
|
1019
|
+
isPopState.current = false;
|
|
1020
|
+
globalHistoryIndex = getHistoryIndex() || globalHistoryIndex;
|
|
1021
|
+
const savedScroll = scrollPositionsRef.current[`${globalHistoryIndex}-${location}`];
|
|
1022
|
+
requestAnimationFrame(() => {
|
|
1023
|
+
window.scrollTo({ top: savedScroll ?? 0, behavior: "instant" });
|
|
1024
|
+
});
|
|
1025
|
+
} else {
|
|
1026
|
+
scrollPositionsRef.current[`${prevHistoryIndex}-${previousLocation.current}`] = currentScrollY;
|
|
1027
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
1028
|
+
globalHistoryIndex = prevHistoryIndex + 1;
|
|
1029
|
+
setHistoryIndex(globalHistoryIndex);
|
|
1030
|
+
window.scrollTo({ top: 0, behavior: "instant" });
|
|
1031
|
+
}
|
|
1032
|
+
previousLocation.current = location;
|
|
1033
|
+
}, [location]);
|
|
1034
|
+
useEffect4(() => {
|
|
1035
|
+
let timeoutId;
|
|
1036
|
+
const handleScroll = () => {
|
|
1037
|
+
clearTimeout(timeoutId);
|
|
1038
|
+
timeoutId = setTimeout(() => {
|
|
1039
|
+
scrollPositionsRef.current[`${globalHistoryIndex}-${location}`] = window.scrollY;
|
|
1040
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
1041
|
+
}, 100);
|
|
1042
|
+
};
|
|
1043
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
1044
|
+
return () => {
|
|
1045
|
+
clearTimeout(timeoutId);
|
|
1046
|
+
window.removeEventListener("scroll", handleScroll);
|
|
1047
|
+
};
|
|
1048
|
+
}, [location]);
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
956
1052
|
// src/router/index.ts
|
|
957
1053
|
import { Route, Switch, useParams } from "wouter";
|
|
958
1054
|
export {
|
|
@@ -962,6 +1058,7 @@ export {
|
|
|
962
1058
|
Route,
|
|
963
1059
|
Router,
|
|
964
1060
|
SafeHtml,
|
|
1061
|
+
ScrollRestoration,
|
|
965
1062
|
StaticContainer,
|
|
966
1063
|
StaticEmbed,
|
|
967
1064
|
MpImage as StaticImage,
|
package/dist/router.d.ts
CHANGED
|
@@ -44,4 +44,15 @@ interface RouterProps {
|
|
|
44
44
|
}
|
|
45
45
|
declare function Router({ children, base }: RouterProps): react_jsx_runtime.JSX.Element;
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* ScrollRestoration Component
|
|
49
|
+
*
|
|
50
|
+
* Handles scroll position management for SPA navigation:
|
|
51
|
+
* - Forward navigation (link clicks) → instantly at top (no scroll animation)
|
|
52
|
+
* - Back navigation (browser back) → restore previous scroll position
|
|
53
|
+
*
|
|
54
|
+
* Uses sessionStorage to persist scroll positions across page refreshes.
|
|
55
|
+
*/
|
|
56
|
+
declare function ScrollRestoration(): null;
|
|
57
|
+
|
|
58
|
+
export { Link, type LinkProps, type NavigateFunction, Router, type RouterProps$1 as RouterProps, ScrollRestoration, useNavigate };
|
package/dist/router.js
CHANGED
|
@@ -40,12 +40,109 @@ function Router({ children, base }) {
|
|
|
40
40
|
return /* @__PURE__ */ jsx2(WouterRouter, { base: basename, children });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// src/router/ScrollRestoration.tsx
|
|
44
|
+
import { useEffect, useRef } from "react";
|
|
45
|
+
import { useLocation as useLocation2 } from "wouter";
|
|
46
|
+
var SCROLL_POSITIONS_KEY = "yoamigo-scroll-positions";
|
|
47
|
+
var HISTORY_INDEX_KEY = "yoamigo-history-index";
|
|
48
|
+
function getScrollPositions() {
|
|
49
|
+
if (typeof sessionStorage === "undefined") return {};
|
|
50
|
+
try {
|
|
51
|
+
const stored = sessionStorage.getItem(SCROLL_POSITIONS_KEY);
|
|
52
|
+
return stored ? JSON.parse(stored) : {};
|
|
53
|
+
} catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function saveScrollPositions(positions) {
|
|
58
|
+
if (typeof sessionStorage === "undefined") return;
|
|
59
|
+
try {
|
|
60
|
+
sessionStorage.setItem(SCROLL_POSITIONS_KEY, JSON.stringify(positions));
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function getHistoryIndex() {
|
|
65
|
+
if (typeof history === "undefined") return 0;
|
|
66
|
+
const state = history.state;
|
|
67
|
+
if (state && typeof state[HISTORY_INDEX_KEY] === "number") {
|
|
68
|
+
return state[HISTORY_INDEX_KEY];
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
function setHistoryIndex(index) {
|
|
73
|
+
if (typeof history === "undefined") return;
|
|
74
|
+
const newState = { ...history.state, [HISTORY_INDEX_KEY]: index };
|
|
75
|
+
history.replaceState(newState, "");
|
|
76
|
+
}
|
|
77
|
+
var globalHistoryIndex = 0;
|
|
78
|
+
function ScrollRestoration() {
|
|
79
|
+
const [location] = useLocation2();
|
|
80
|
+
const previousLocation = useRef(location);
|
|
81
|
+
const isPopState = useRef(false);
|
|
82
|
+
const scrollPositionsRef = useRef({});
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (typeof history !== "undefined" && "scrollRestoration" in history) {
|
|
85
|
+
history.scrollRestoration = "manual";
|
|
86
|
+
}
|
|
87
|
+
scrollPositionsRef.current = getScrollPositions();
|
|
88
|
+
const existingIndex = getHistoryIndex();
|
|
89
|
+
if (existingIndex === 0 && !history.state?.[HISTORY_INDEX_KEY]) {
|
|
90
|
+
globalHistoryIndex = 1;
|
|
91
|
+
setHistoryIndex(globalHistoryIndex);
|
|
92
|
+
} else {
|
|
93
|
+
globalHistoryIndex = existingIndex;
|
|
94
|
+
}
|
|
95
|
+
const handlePopState = () => {
|
|
96
|
+
isPopState.current = true;
|
|
97
|
+
};
|
|
98
|
+
window.addEventListener("popstate", handlePopState);
|
|
99
|
+
return () => window.removeEventListener("popstate", handlePopState);
|
|
100
|
+
}, []);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (previousLocation.current === location) return;
|
|
103
|
+
const prevHistoryIndex = globalHistoryIndex;
|
|
104
|
+
const currentScrollY = window.scrollY;
|
|
105
|
+
if (isPopState.current) {
|
|
106
|
+
isPopState.current = false;
|
|
107
|
+
globalHistoryIndex = getHistoryIndex() || globalHistoryIndex;
|
|
108
|
+
const savedScroll = scrollPositionsRef.current[`${globalHistoryIndex}-${location}`];
|
|
109
|
+
requestAnimationFrame(() => {
|
|
110
|
+
window.scrollTo({ top: savedScroll ?? 0, behavior: "instant" });
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
scrollPositionsRef.current[`${prevHistoryIndex}-${previousLocation.current}`] = currentScrollY;
|
|
114
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
115
|
+
globalHistoryIndex = prevHistoryIndex + 1;
|
|
116
|
+
setHistoryIndex(globalHistoryIndex);
|
|
117
|
+
window.scrollTo({ top: 0, behavior: "instant" });
|
|
118
|
+
}
|
|
119
|
+
previousLocation.current = location;
|
|
120
|
+
}, [location]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
let timeoutId;
|
|
123
|
+
const handleScroll = () => {
|
|
124
|
+
clearTimeout(timeoutId);
|
|
125
|
+
timeoutId = setTimeout(() => {
|
|
126
|
+
scrollPositionsRef.current[`${globalHistoryIndex}-${location}`] = window.scrollY;
|
|
127
|
+
saveScrollPositions(scrollPositionsRef.current);
|
|
128
|
+
}, 100);
|
|
129
|
+
};
|
|
130
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
131
|
+
return () => {
|
|
132
|
+
clearTimeout(timeoutId);
|
|
133
|
+
window.removeEventListener("scroll", handleScroll);
|
|
134
|
+
};
|
|
135
|
+
}, [location]);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
43
139
|
// src/router/index.ts
|
|
44
140
|
import { Route, Switch, useParams } from "wouter";
|
|
45
141
|
export {
|
|
46
142
|
Link,
|
|
47
143
|
Route,
|
|
48
144
|
Router,
|
|
145
|
+
ScrollRestoration,
|
|
49
146
|
Switch,
|
|
50
147
|
useNavigate,
|
|
51
148
|
useParams
|