@yoamigo.com/core 0.4.7 → 1.0.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/lib.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  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';
2
- export { i as initBuilderSelection } from './builder-selection-CYP91nRu.js';
2
+ import { C as ChangeSource } from './cart-storage-DFdGPcwm.js';
3
+ export { o as CartItem, a as CollectionClient, d as CollectionClientConfig, b as CollectionRecord, L as ListOptions, c as ListResponse, Q as QueryFilter, S as SingleResponse, l as addLocalCartItem, j as clearLocalCart, f as clearSessionId, g as getCollectionClient, h as getLocalCart, n as getLocalCartItemCount, k as getLocalCartItems, e as getSessionId, i as initBuilderSelection, m as removeLocalCartItem, r as resetCollectionClient, s as saveLocalCart, u as updateLocalCartItem } from './cart-storage-DFdGPcwm.js';
3
4
  export { C as ContactFormData, S as SiteMetadata, g as getSiteMetadata, a as submitContactForm, s as subscribeToNewsletter } from './api-client-D8FkeBAI.js';
5
+ import 'react/jsx-runtime';
6
+ import 'react';
4
7
 
5
8
  /**
6
9
  * Image Cache - In-memory cache for preloaded images
@@ -81,4 +84,92 @@ declare function hasCachedImage(src: string): boolean;
81
84
  */
82
85
  declare function initImageCacheListener(): () => void;
83
86
 
84
- export { type CachedImageEntry, clearImageCache, getCacheStats, getCachedUrl, hasCachedImage, initImageCacheListener, setCachedImages };
87
+ /**
88
+ * Collection Context Registry
89
+ *
90
+ * Global registry for active CollectionContentProvider contexts.
91
+ * Enables window.yaContentStore to route setValue/getValue calls
92
+ * to the correct collection context based on fieldId prefix.
93
+ *
94
+ * When a CollectionContentProvider mounts with edit props, it registers
95
+ * itself here. YaText/YaImage and the global API check this registry
96
+ * to determine if a fieldId belongs to a collection.
97
+ */
98
+
99
+ /**
100
+ * Collection context value stored in registry
101
+ * Mirrors the context shape from CollectionContentProvider
102
+ */
103
+ interface RegisteredCollectionContext {
104
+ /** Get a value from the collection record */
105
+ getValue: (fieldId: string) => string;
106
+ /** Set a value (triggers local update + backend save) */
107
+ setValue?: (fieldId: string, value: string, source?: ChangeSource) => void;
108
+ /** Save to backend (creates/updates StagedAppRecord) */
109
+ saveToBackend?: (fieldId: string, value: string) => Promise<void>;
110
+ /** The prefix used for field IDs (e.g., "product") */
111
+ prefix: string;
112
+ /** The record ID (for UPDATE) or undefined (for CREATE) */
113
+ recordId?: string;
114
+ /** Collection slug for API calls */
115
+ collectionSlug?: string;
116
+ /** App ID for API calls */
117
+ appId?: string;
118
+ }
119
+ /**
120
+ * Summary info about a registered collection context
121
+ * Used by listEditableFields() for AI discovery
122
+ */
123
+ interface CollectionFieldInfo {
124
+ prefix: string;
125
+ recordId?: string;
126
+ collectionSlug?: string;
127
+ appId?: string;
128
+ }
129
+ /**
130
+ * Register a collection context.
131
+ * Called by CollectionContentProvider on mount when edit props are provided.
132
+ *
133
+ * @param prefix - The fieldId prefix (e.g., "product")
134
+ * @param context - The collection context value
135
+ */
136
+ declare function registerCollectionContext(prefix: string, context: RegisteredCollectionContext): void;
137
+ /**
138
+ * Unregister a collection context.
139
+ * Called by CollectionContentProvider on unmount.
140
+ *
141
+ * @param prefix - The fieldId prefix to remove
142
+ */
143
+ declare function unregisterCollectionContext(prefix: string): void;
144
+ /**
145
+ * Get the collection context for a given fieldId.
146
+ * Returns null if the fieldId doesn't belong to any registered collection.
147
+ *
148
+ * @param fieldId - The full fieldId (e.g., "product.name")
149
+ * @returns The collection context or null
150
+ */
151
+ declare function getCollectionContextForField(fieldId: string): RegisteredCollectionContext | null;
152
+ /**
153
+ * Check if a fieldId belongs to a collection.
154
+ *
155
+ * @param fieldId - The full fieldId to check
156
+ * @returns True if the fieldId is from a collection
157
+ */
158
+ declare function isCollectionField(fieldId: string): boolean;
159
+ /**
160
+ * Get summary info about all registered collection contexts.
161
+ * Used by window.yaContentStore.listEditableFields() for AI discovery.
162
+ *
163
+ * @returns Array of collection field info objects
164
+ */
165
+ declare function getAllCollectionFields(): CollectionFieldInfo[];
166
+ /**
167
+ * Get the number of registered contexts (for debugging)
168
+ */
169
+ declare function getRegistrySize(): number;
170
+ /**
171
+ * Clear all registered contexts (for testing)
172
+ */
173
+ declare function clearRegistry(): void;
174
+
175
+ export { type CachedImageEntry, type CollectionFieldInfo, type RegisteredCollectionContext, clearImageCache, clearRegistry, getAllCollectionFields, getCacheStats, getCachedUrl, getCollectionContextForField, getRegistrySize, hasCachedImage, initImageCacheListener, isCollectionField, registerCollectionContext, setCachedImages, unregisterCollectionContext };
package/dist/lib.js CHANGED
@@ -854,7 +854,7 @@ var BuilderSelectionManager = class {
854
854
  const navLinks = document.querySelectorAll("nav a[href]");
855
855
  navLinks.forEach((link) => {
856
856
  const href = link.getAttribute("href");
857
- if (href && href.startsWith("/")) {
857
+ if (href?.startsWith("/")) {
858
858
  const path = href.split("?")[0].replace(/\/$/, "") || "/";
859
859
  routes.add(path);
860
860
  }
@@ -866,7 +866,7 @@ var BuilderSelectionManager = class {
866
866
  * Non-blocking: uses requestIdleCallback between pages
867
867
  * Transfers ArrayBuffer directly (zero-copy) for performance
868
868
  */
869
- async captureAllPagesScreenshots(checkpointId, viewport) {
869
+ async captureAllPagesScreenshots(checkpointId, _viewport) {
870
870
  console.log("[BuilderSelection] Starting multi-page capture for checkpoint:", checkpointId);
871
871
  const routes = this.getAllRoutes();
872
872
  const originalPath = window.location.pathname;
@@ -882,7 +882,8 @@ var BuilderSelectionManager = class {
882
882
  try {
883
883
  await new Promise((resolve) => {
884
884
  if ("requestIdleCallback" in window) {
885
- requestIdleCallback(() => resolve(), { timeout: 1e3 });
885
+ ;
886
+ window.requestIdleCallback(() => resolve(), { timeout: 1e3 });
886
887
  } else {
887
888
  setTimeout(resolve, 50);
888
889
  }
@@ -1193,22 +1194,354 @@ async function getSiteMetadata() {
1193
1194
  const runtimeConfig = window.YOAMIGO_CONFIG;
1194
1195
  return runtimeConfig || null;
1195
1196
  }
1197
+
1198
+ // src/lib/collection-client.ts
1199
+ function getEnvVar(key) {
1200
+ try {
1201
+ const env = import.meta.env;
1202
+ return env?.[key];
1203
+ } catch {
1204
+ return void 0;
1205
+ }
1206
+ }
1207
+ function getConfig() {
1208
+ if (typeof window === "undefined") {
1209
+ throw new Error("CollectionClient requires browser environment");
1210
+ }
1211
+ const runtimeConfig = window.YOAMIGO_CONFIG;
1212
+ const appId = runtimeConfig?.appId || runtimeConfig?.siteId || getEnvVar("YA_APP_ID") || getEnvVar("YA_SITE_ID");
1213
+ const apiUrl = runtimeConfig?.apiUrl || getEnvVar("YA_API_URL");
1214
+ if (!appId) {
1215
+ throw new Error("App ID not configured (check YOAMIGO_CONFIG.appId or YA_APP_ID)");
1216
+ }
1217
+ if (!apiUrl) {
1218
+ throw new Error("API URL not configured (check YOAMIGO_CONFIG.apiUrl or YA_API_URL)");
1219
+ }
1220
+ return { apiUrl, appId };
1221
+ }
1222
+ var CollectionClient = class {
1223
+ apiUrl;
1224
+ appId;
1225
+ constructor(config) {
1226
+ this.apiUrl = config.apiUrl;
1227
+ this.appId = config.appId;
1228
+ }
1229
+ /**
1230
+ * List records from a collection with optional filtering, pagination, and sorting.
1231
+ */
1232
+ async list(collection, options = {}) {
1233
+ const { filters, limit, offset, orderBy, orderDir } = options;
1234
+ const url = new URL(`${this.apiUrl}/api/apps/${this.appId}/collections/${collection}/records`);
1235
+ if (filters && filters.length > 0) {
1236
+ url.searchParams.set("filters", JSON.stringify(filters));
1237
+ }
1238
+ if (limit !== void 0) {
1239
+ url.searchParams.set("limit", String(limit));
1240
+ }
1241
+ if (offset !== void 0) {
1242
+ url.searchParams.set("offset", String(offset));
1243
+ }
1244
+ if (orderBy) {
1245
+ url.searchParams.set("orderBy", orderBy);
1246
+ }
1247
+ if (orderDir) {
1248
+ url.searchParams.set("orderDir", orderDir);
1249
+ }
1250
+ try {
1251
+ const response = await fetch(url.toString(), {
1252
+ method: "GET",
1253
+ headers: {
1254
+ "Content-Type": "application/json"
1255
+ }
1256
+ });
1257
+ const result = await response.json();
1258
+ if (!response.ok) {
1259
+ return {
1260
+ success: false,
1261
+ error: result.error || `HTTP ${response.status}`,
1262
+ errorCode: result.errorCode
1263
+ };
1264
+ }
1265
+ return {
1266
+ success: true,
1267
+ data: result.data,
1268
+ totalCount: result.totalCount
1269
+ };
1270
+ } catch (error) {
1271
+ return {
1272
+ success: false,
1273
+ error: error instanceof Error ? error.message : "Network error"
1274
+ };
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Get a single record by its ID.
1279
+ */
1280
+ async getById(collection, id) {
1281
+ const url = `${this.apiUrl}/api/apps/${this.appId}/collections/${collection}/records/${id}`;
1282
+ try {
1283
+ const response = await fetch(url, {
1284
+ method: "GET",
1285
+ headers: {
1286
+ "Content-Type": "application/json"
1287
+ }
1288
+ });
1289
+ const result = await response.json();
1290
+ if (!response.ok) {
1291
+ return {
1292
+ success: false,
1293
+ error: result.error || `HTTP ${response.status}`,
1294
+ errorCode: result.errorCode
1295
+ };
1296
+ }
1297
+ return {
1298
+ success: true,
1299
+ data: result.data
1300
+ };
1301
+ } catch (error) {
1302
+ return {
1303
+ success: false,
1304
+ error: error instanceof Error ? error.message : "Network error"
1305
+ };
1306
+ }
1307
+ }
1308
+ /**
1309
+ * Get a single record by a slug field value.
1310
+ * Convenience method that uses list with a filter.
1311
+ */
1312
+ async getBySlug(collection, slugField, slugValue) {
1313
+ const result = await this.list(collection, {
1314
+ filters: [
1315
+ { field: slugField, operator: "eq", value: slugValue }
1316
+ ],
1317
+ limit: 1
1318
+ });
1319
+ if (!result.success) {
1320
+ return {
1321
+ success: false,
1322
+ error: result.error,
1323
+ errorCode: result.errorCode
1324
+ };
1325
+ }
1326
+ if (!result.data || result.data.length === 0) {
1327
+ return {
1328
+ success: false,
1329
+ error: "Record not found",
1330
+ errorCode: "NOT_FOUND"
1331
+ };
1332
+ }
1333
+ return {
1334
+ success: true,
1335
+ data: result.data[0]
1336
+ };
1337
+ }
1338
+ };
1339
+ var cachedClient = null;
1340
+ function getCollectionClient() {
1341
+ if (!cachedClient) {
1342
+ const config = getConfig();
1343
+ cachedClient = new CollectionClient(config);
1344
+ }
1345
+ return cachedClient;
1346
+ }
1347
+ function resetCollectionClient() {
1348
+ cachedClient = null;
1349
+ }
1350
+
1351
+ // src/lib/cart-storage.ts
1352
+ var CART_STORAGE_KEY_PREFIX = "yoamigo_cart_";
1353
+ var SESSION_ID_KEY = "yoamigo_cart_session";
1354
+ var CART_EXPIRY_DAYS = 7;
1355
+ function generateSessionId() {
1356
+ return `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
1357
+ }
1358
+ function getSessionId() {
1359
+ if (typeof window === "undefined") {
1360
+ throw new Error("getSessionId requires browser environment");
1361
+ }
1362
+ let sessionId = localStorage.getItem(SESSION_ID_KEY);
1363
+ if (!sessionId) {
1364
+ sessionId = generateSessionId();
1365
+ localStorage.setItem(SESSION_ID_KEY, sessionId);
1366
+ }
1367
+ return sessionId;
1368
+ }
1369
+ function clearSessionId() {
1370
+ if (typeof window === "undefined") return;
1371
+ localStorage.removeItem(SESSION_ID_KEY);
1372
+ }
1373
+ function getStorageKey(appId) {
1374
+ return `${CART_STORAGE_KEY_PREFIX}${appId}`;
1375
+ }
1376
+ function getLocalCart(appId) {
1377
+ if (typeof window === "undefined") return null;
1378
+ try {
1379
+ const key = getStorageKey(appId);
1380
+ const data = localStorage.getItem(key);
1381
+ if (!data) return null;
1382
+ const cart = JSON.parse(data);
1383
+ const updatedAt = new Date(cart.updatedAt);
1384
+ const expiryDate = /* @__PURE__ */ new Date();
1385
+ expiryDate.setDate(expiryDate.getDate() - CART_EXPIRY_DAYS);
1386
+ if (updatedAt < expiryDate) {
1387
+ localStorage.removeItem(key);
1388
+ return null;
1389
+ }
1390
+ return cart;
1391
+ } catch (error) {
1392
+ console.warn("Failed to read cart from localStorage:", error);
1393
+ return null;
1394
+ }
1395
+ }
1396
+ function saveLocalCart(appId, items) {
1397
+ if (typeof window === "undefined") return;
1398
+ try {
1399
+ const key = getStorageKey(appId);
1400
+ const cart = {
1401
+ sessionId: getSessionId(),
1402
+ items,
1403
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1404
+ };
1405
+ localStorage.setItem(key, JSON.stringify(cart));
1406
+ } catch (error) {
1407
+ console.warn("Failed to save cart to localStorage:", error);
1408
+ }
1409
+ }
1410
+ function clearLocalCart(appId) {
1411
+ if (typeof window === "undefined") return;
1412
+ try {
1413
+ const key = getStorageKey(appId);
1414
+ localStorage.removeItem(key);
1415
+ } catch (error) {
1416
+ console.warn("Failed to clear cart from localStorage:", error);
1417
+ }
1418
+ }
1419
+ function getLocalCartItems(appId) {
1420
+ const cart = getLocalCart(appId);
1421
+ return cart?.items || [];
1422
+ }
1423
+ function addLocalCartItem(appId, productId, collectionSlug, quantity = 1, variantId, data) {
1424
+ const items = getLocalCartItems(appId);
1425
+ const existingIndex = items.findIndex(
1426
+ (item) => item.productId === productId && item.variantId === variantId
1427
+ );
1428
+ if (existingIndex >= 0) {
1429
+ items[existingIndex].quantity += quantity;
1430
+ } else {
1431
+ items.push({
1432
+ id: `local_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
1433
+ productId,
1434
+ collectionSlug,
1435
+ quantity,
1436
+ variantId,
1437
+ addedAt: (/* @__PURE__ */ new Date()).toISOString(),
1438
+ data
1439
+ });
1440
+ }
1441
+ saveLocalCart(appId, items);
1442
+ return items;
1443
+ }
1444
+ function updateLocalCartItem(appId, itemId, quantity) {
1445
+ const items = getLocalCartItems(appId);
1446
+ const itemIndex = items.findIndex((item) => item.id === itemId);
1447
+ if (itemIndex < 0) return items;
1448
+ if (quantity === 0) {
1449
+ items.splice(itemIndex, 1);
1450
+ } else {
1451
+ items[itemIndex].quantity = quantity;
1452
+ }
1453
+ saveLocalCart(appId, items);
1454
+ return items;
1455
+ }
1456
+ function removeLocalCartItem(appId, itemId) {
1457
+ const items = getLocalCartItems(appId);
1458
+ const filteredItems = items.filter((item) => item.id !== itemId);
1459
+ saveLocalCart(appId, filteredItems);
1460
+ return filteredItems;
1461
+ }
1462
+ function getLocalCartItemCount(appId) {
1463
+ const items = getLocalCartItems(appId);
1464
+ return items.reduce((sum, item) => sum + item.quantity, 0);
1465
+ }
1466
+
1467
+ // src/lib/collection-context-registry.ts
1468
+ var registry = /* @__PURE__ */ new Map();
1469
+ function registerCollectionContext(prefix, context) {
1470
+ if (registry.has(prefix)) {
1471
+ console.warn(
1472
+ `[collection-registry] Overwriting existing context for prefix "${prefix}". This usually means multiple CollectionContentProviders with the same prefix are mounted.`
1473
+ );
1474
+ }
1475
+ registry.set(prefix, context);
1476
+ }
1477
+ function unregisterCollectionContext(prefix) {
1478
+ registry.delete(prefix);
1479
+ }
1480
+ function getCollectionContextForField(fieldId) {
1481
+ for (const [prefix, context] of registry) {
1482
+ if (fieldId === prefix || fieldId.startsWith(prefix + ".")) {
1483
+ return context;
1484
+ }
1485
+ }
1486
+ return null;
1487
+ }
1488
+ function isCollectionField(fieldId) {
1489
+ return getCollectionContextForField(fieldId) !== null;
1490
+ }
1491
+ function getAllCollectionFields() {
1492
+ const result = [];
1493
+ for (const [prefix, context] of registry) {
1494
+ result.push({
1495
+ prefix,
1496
+ recordId: context.recordId,
1497
+ collectionSlug: context.collectionSlug,
1498
+ appId: context.appId
1499
+ });
1500
+ }
1501
+ return result;
1502
+ }
1503
+ function getRegistrySize() {
1504
+ return registry.size;
1505
+ }
1506
+ function clearRegistry() {
1507
+ registry.clear();
1508
+ }
1196
1509
  export {
1510
+ CollectionClient,
1511
+ addLocalCartItem,
1197
1512
  clearImageCache,
1513
+ clearLocalCart,
1514
+ clearRegistry,
1515
+ clearSessionId,
1198
1516
  contentRegistry,
1517
+ getAllCollectionFields,
1199
1518
  getAllContent,
1200
1519
  getCacheStats,
1201
1520
  getCachedUrl,
1521
+ getCollectionClient,
1522
+ getCollectionContextForField,
1202
1523
  getContent,
1524
+ getLocalCart,
1525
+ getLocalCartItemCount,
1526
+ getLocalCartItems,
1527
+ getRegistrySize,
1528
+ getSessionId,
1203
1529
  getSiteMetadata,
1204
1530
  hasCachedImage,
1205
1531
  hasContent,
1206
1532
  initBuilderSelection,
1207
1533
  initImageCacheListener,
1534
+ isCollectionField,
1535
+ registerCollectionContext,
1208
1536
  registerContent,
1537
+ removeLocalCartItem,
1538
+ resetCollectionClient,
1209
1539
  resolveAssetUrl,
1540
+ saveLocalCart,
1210
1541
  setAssetResolver,
1211
1542
  setCachedImages,
1212
1543
  submitContactForm,
1213
- subscribeToNewsletter
1544
+ subscribeToNewsletter,
1545
+ unregisterCollectionContext,
1546
+ updateLocalCartItem
1214
1547
  };
package/dist/prod.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { a as ContentStore, E as ContentStoreMode, C as ContentStoreProvider, f as MarkdownText, g as MarkdownTextProps, P as PageInfo, c as StaticImage, d as StaticImageProps, M as StaticText, S as StaticTextProps, c as YaImage, d as YaImageProps, M as YaText, S as YaTextProps, b as background, e as embed, i as image, p as parseEmbedUrl, t as text, u as useContentStore, v as video } from './content-helpers-DOUKazMz.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, ScrollRestoration, useNavigate } from './router.js';
5
- export { Route, Switch, useParams } from 'wouter';
4
+ export { Link, LinkProps, NavigateFunction, RouteDefinition, Router, RouterProps, ScrollRestoration, createRouteDefinition, extractRouteParams, filePathToRoutePath, generatePath, sortRoutesBySpecificity, useNavigate } from './router.js';
5
+ export { Route, Switch, useLocation, 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
 
8
8
  interface StaticLinkProps {
package/dist/prod.js CHANGED
@@ -1056,7 +1056,74 @@ function ScrollRestoration() {
1056
1056
  }
1057
1057
 
1058
1058
  // src/router/index.ts
1059
- import { Route, Switch, useParams } from "wouter";
1059
+ import { Route, Switch, useParams, useLocation as useLocation4 } from "wouter";
1060
+
1061
+ // src/router/route-utils.ts
1062
+ function filePathToRoutePath(filePath, options = {}) {
1063
+ const {
1064
+ pagesDir = "/src/pages",
1065
+ extensions = [".tsx", ".ts", ".jsx", ".js"]
1066
+ } = options;
1067
+ let path = filePath;
1068
+ if (path.startsWith(pagesDir)) {
1069
+ path = path.slice(pagesDir.length);
1070
+ }
1071
+ for (const ext of extensions) {
1072
+ if (path.endsWith(ext)) {
1073
+ path = path.slice(0, -ext.length);
1074
+ break;
1075
+ }
1076
+ }
1077
+ if (path.endsWith("/index")) {
1078
+ path = path.slice(0, -6) || "/";
1079
+ }
1080
+ path = path.replace(/\[\.\.\.([^\]]+)\]/g, ":$1*");
1081
+ path = path.replace(/\[([^\]]+)\]/g, ":$1");
1082
+ if (!path.startsWith("/")) {
1083
+ path = "/" + path;
1084
+ }
1085
+ if (path === "" || path === "/index") {
1086
+ return "/";
1087
+ }
1088
+ return path;
1089
+ }
1090
+ function extractRouteParams(filePath) {
1091
+ const matches = filePath.matchAll(/\[\.\.\.([^\]]+)\]|\[([^\]]+)\]/g);
1092
+ const params = [];
1093
+ for (const match of matches) {
1094
+ params.push(match[1] || match[2]);
1095
+ }
1096
+ return params;
1097
+ }
1098
+ function createRouteDefinition(filePath, options) {
1099
+ const path = filePathToRoutePath(filePath, options);
1100
+ const params = extractRouteParams(filePath);
1101
+ return {
1102
+ path,
1103
+ filePath,
1104
+ params,
1105
+ isDynamic: params.length > 0
1106
+ };
1107
+ }
1108
+ function sortRoutesBySpecificity(routes) {
1109
+ return [...routes].sort((a, b) => {
1110
+ if (!a.isDynamic && b.isDynamic) return -1;
1111
+ if (a.isDynamic && !b.isDynamic) return 1;
1112
+ const aSegments = a.path.split("/").filter(Boolean);
1113
+ const bSegments = b.path.split("/").filter(Boolean);
1114
+ if (aSegments.length !== bSegments.length) {
1115
+ return bSegments.length - aSegments.length;
1116
+ }
1117
+ return a.params.length - b.params.length;
1118
+ });
1119
+ }
1120
+ function generatePath(routePath, params) {
1121
+ let path = routePath;
1122
+ for (const [key, value] of Object.entries(params)) {
1123
+ path = path.replace(new RegExp(`:${key}\\*?`, "g"), encodeURIComponent(value));
1124
+ }
1125
+ return path;
1126
+ }
1060
1127
 
1061
1128
  // src/lib/content-helpers.ts
1062
1129
  function text(content) {
@@ -1097,7 +1164,11 @@ export {
1097
1164
  StaticVideo as YaVideo,
1098
1165
  background,
1099
1166
  contentRegistry,
1167
+ createRouteDefinition,
1100
1168
  embed,
1169
+ extractRouteParams,
1170
+ filePathToRoutePath,
1171
+ generatePath,
1101
1172
  getAllContent,
1102
1173
  getContent,
1103
1174
  hasContent,
@@ -1110,8 +1181,10 @@ export {
1110
1181
  serializeEmbedValue,
1111
1182
  serializeVideoValue,
1112
1183
  setAssetResolver,
1184
+ sortRoutesBySpecificity,
1113
1185
  text,
1114
1186
  useContentStore,
1187
+ useLocation4 as useLocation,
1115
1188
  useNavigate,
1116
1189
  useParams,
1117
1190
  video
package/dist/router.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode, MouseEvent } from 'react';
3
- export { Route, Switch, useParams } from 'wouter';
3
+ export { Route, Switch, useLocation, useParams } from 'wouter';
4
4
 
5
5
  /**
6
6
  * Router Abstraction - Shared Types
@@ -55,4 +55,82 @@ declare function Router({ children, base }: RouterProps): react_jsx_runtime.JSX.
55
55
  */
56
56
  declare function ScrollRestoration(): null;
57
57
 
58
- export { Link, type LinkProps, type NavigateFunction, Router, type RouterProps$1 as RouterProps, ScrollRestoration, useNavigate };
58
+ /**
59
+ * Route Utilities
60
+ *
61
+ * Utilities for working with file-based routing conventions.
62
+ * Converts file paths with [param] syntax to wouter-compatible route paths.
63
+ *
64
+ * @example
65
+ * ```
66
+ * /src/pages/contact.tsx → /contact
67
+ * /src/pages/products/index.tsx → /products
68
+ * /src/pages/products/[slug].tsx → /products/:slug
69
+ * /src/pages/blog/[category]/[slug].tsx → /blog/:category/:slug
70
+ * ```
71
+ */
72
+ interface RouteDefinition {
73
+ /** The wouter-compatible route path (e.g., '/products/:slug') */
74
+ path: string;
75
+ /** The original file path */
76
+ filePath: string;
77
+ /** Parameter names extracted from the path */
78
+ params: string[];
79
+ /** Whether this is a dynamic route (has params) */
80
+ isDynamic: boolean;
81
+ }
82
+ /**
83
+ * Convert a file path to a wouter-compatible route path.
84
+ *
85
+ * Supports:
86
+ * - `/src/pages/contact.tsx` → `/contact`
87
+ * - `/src/pages/index.tsx` → `/`
88
+ * - `/src/pages/products/index.tsx` → `/products`
89
+ * - `/src/pages/products/[slug].tsx` → `/products/:slug`
90
+ * - `/src/pages/blog/[category]/[slug].tsx` → `/blog/:category/:slug`
91
+ * - `/src/pages/[...rest].tsx` → `/:rest*` (catch-all)
92
+ *
93
+ * @param filePath - The file path (e.g., '/src/pages/products/[slug].tsx')
94
+ * @param options - Options for path conversion
95
+ * @returns The wouter-compatible route path
96
+ */
97
+ declare function filePathToRoutePath(filePath: string, options?: {
98
+ /** Pages directory prefix to strip (default: '/src/pages') */
99
+ pagesDir?: string;
100
+ /** File extensions to remove (default: ['.tsx', '.ts', '.jsx', '.js']) */
101
+ extensions?: string[];
102
+ }): string;
103
+ /**
104
+ * Extract parameter names from a file path.
105
+ *
106
+ * @param filePath - The file path (e.g., '/src/pages/blog/[category]/[slug].tsx')
107
+ * @returns Array of parameter names (e.g., ['category', 'slug'])
108
+ */
109
+ declare function extractRouteParams(filePath: string): string[];
110
+ /**
111
+ * Create a RouteDefinition from a file path.
112
+ *
113
+ * @param filePath - The file path (e.g., '/src/pages/products/[slug].tsx')
114
+ * @param options - Options for path conversion
115
+ * @returns RouteDefinition object
116
+ */
117
+ declare function createRouteDefinition(filePath: string, options?: Parameters<typeof filePathToRoutePath>[1]): RouteDefinition;
118
+ /**
119
+ * Sort route definitions by specificity (most specific first).
120
+ * Static routes come before dynamic routes.
121
+ * More specific dynamic routes come before less specific ones.
122
+ *
123
+ * @param routes - Array of RouteDefinition objects
124
+ * @returns Sorted array (most specific first)
125
+ */
126
+ declare function sortRoutesBySpecificity(routes: RouteDefinition[]): RouteDefinition[];
127
+ /**
128
+ * Generate a path with parameters filled in.
129
+ *
130
+ * @param routePath - The route path with params (e.g., '/products/:slug')
131
+ * @param params - Object with parameter values (e.g., { slug: 'guitar' })
132
+ * @returns The filled path (e.g., '/products/guitar')
133
+ */
134
+ declare function generatePath(routePath: string, params: Record<string, string>): string;
135
+
136
+ export { Link, type LinkProps, type NavigateFunction, type RouteDefinition, Router, type RouterProps$1 as RouterProps, ScrollRestoration, createRouteDefinition, extractRouteParams, filePathToRoutePath, generatePath, sortRoutesBySpecificity, useNavigate };