@yoamigo.com/core 1.3.0 → 1.4.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/plugin.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/plugin/index.ts
2
2
  import react from "@vitejs/plugin-react";
3
3
  import path from "path";
4
+ import { createRequire } from "module";
5
+ var require2 = createRequire(import.meta.url);
4
6
  function yoamigoPlugin(options = {}) {
5
7
  const templateDir = process.cwd();
6
8
  const resolveFromTemplate = (relativePath) => path.resolve(templateDir, relativePath);
@@ -44,6 +46,12 @@ if (import.meta.hot) {
44
46
  name: "yoamigo:config",
45
47
  config(config, { mode }) {
46
48
  const isProd = mode === "production";
49
+ let tablerIconsPath;
50
+ try {
51
+ tablerIconsPath = path.dirname(require2.resolve("@tabler/icons-react/package.json"));
52
+ } catch {
53
+ console.warn("[yoamigo] @tabler/icons-react not found - icon loading may fail");
54
+ }
47
55
  const aliasArray = [
48
56
  // Production aliases must come FIRST for priority
49
57
  ...isProd ? [
@@ -59,6 +67,14 @@ if (import.meta.hot) {
59
67
  replacement: "@yoamigo.com/core/lib/prod"
60
68
  }
61
69
  ] : [],
70
+ // === @tabler/icons-react resolution for pnpm ===
71
+ // Deep imports need explicit resolution in pnpm workspaces
72
+ ...tablerIconsPath ? [
73
+ {
74
+ find: /^@tabler\/icons-react\/(.*)$/,
75
+ replacement: `${tablerIconsPath}/$1`
76
+ }
77
+ ] : [],
62
78
  // === Template-specific path aliases ===
63
79
  { find: "@", replacement: resolveFromTemplate("./src") },
64
80
  { find: "@components", replacement: resolveFromTemplate("./src/components") },
package/dist/prod.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- export { k as ContentStore, E as ContentStoreMode, C as ContentStoreProvider, D as DynamicIcon, a as DynamicIconProps, I as IconComponent, h as IconLoader, o as MarkdownText, q as MarkdownTextProps, P as PageInfo, m as StaticImage, n as StaticImageProps, M as StaticText, S as StaticTextProps, m as YaImage, n as YaImageProps, M as YaText, S as YaTextProps, b as background, e as embed, g as getIcon, j as getIconLabel, i as image, f as isIconLoaded, l as link, d as loadIcon, s as parseEmbedUrl, p as preloadIcons, r as registerIcon, c as registerIcons, t as text, u as useContentStore, v as video } from './icon-metadata-D-2LGr22.js';
1
+ export { c as ContentStore, E as ContentStoreMode, C as ContentStoreProvider, g as MarkdownText, h as MarkdownTextProps, P as PageInfo, d as StaticImage, f as StaticImageProps, M as StaticText, S as StaticTextProps, d as YaImage, f as YaImageProps, M as YaText, S as YaTextProps, b as background, e as embed, i as image, l as link, p as parseEmbedUrl, t as text, a as useContentStore, u as useSafeTriangle, v as video } from './useSafeTriangle-D5RnB8wa.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
- import React__default, { CSSProperties, ReactNode } from 'react';
3
+ import React__default, { CSSProperties, ReactNode, ComponentType } from 'react';
4
4
  export { Link, LinkProps, NavigateFunction, RouteDefinition, Router, RouterProps, ScrollRestoration, createRouteDefinition, extractRouteParams, filePathToRoutePath, generatePath, sortRoutesBySpecificity, useNavigate } from './router.js';
5
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
- import '@tabler/icons-react';
7
+ import { IconProps } from '@tabler/icons-react';
8
8
 
9
9
  interface StaticLinkProps {
10
10
  fieldId: string;
@@ -197,4 +197,89 @@ interface StaticEmbedProps {
197
197
  declare function serializeEmbedValue(value: EmbedFieldValue): string;
198
198
  declare function StaticEmbed({ fieldId, className, aspectRatio: propAspectRatio, maxWidth, loading, defaultValue, }: StaticEmbedProps): react_jsx_runtime.JSX.Element;
199
199
 
200
- export { type BackgroundConfig, type BackgroundImageConfig, type EmbedFieldValue, type EmbedType, type OverlayConfig, SafeHtml, type SafeHtmlProps, StaticContainer, type StaticContainerProps, StaticEmbed, type StaticEmbedProps, StaticIcon, type StaticIconProps, StaticLink, type StaticLinkProps, StaticVideo, type StaticVideoProps, type VideoFieldValue, StaticContainer as YaContainer, type StaticContainerProps as YaContainerProps, StaticEmbed as YaEmbed, type StaticEmbedProps as YaEmbedProps, StaticIcon as YaIcon, type StaticIconProps as YaIconProps, StaticLink as YaLink, type StaticLinkProps as YaLinkProps, StaticVideo as YaVideo, type StaticVideoProps as YaVideoProps, parseBackgroundConfig, serializeBackgroundConfig, serializeEmbedValue, serializeVideoValue };
200
+ /**
201
+ * DynamicIcon Component - Production version
202
+ *
203
+ * Same as DynamicIcon but uses the production icon registry
204
+ * which doesn't include all 6000 generated loaders.
205
+ */
206
+
207
+ interface DynamicIconProps extends Omit<IconProps, 'ref'> {
208
+ /** Name of the Tabler icon (e.g., "IconBrandTwitter", "IconHeart") */
209
+ name: string;
210
+ /** Optional fallback element to show while loading */
211
+ fallback?: React__default.ReactNode;
212
+ /** Additional class name for the icon wrapper */
213
+ className?: string;
214
+ }
215
+ /**
216
+ * Renders a Tabler icon by name with lazy loading.
217
+ * Production version - only pre-registered icons are available.
218
+ */
219
+ declare function DynamicIconComponent({ name, fallback, className, size, stroke, ...props }: DynamicIconProps): React__default.ReactElement | null;
220
+ declare const DynamicIcon: React__default.MemoExoticComponent<typeof DynamicIconComponent>;
221
+
222
+ /**
223
+ * Icon Registry - Production version (no generated loaders)
224
+ *
225
+ * This lightweight version does NOT include the generated 6000 icon loaders.
226
+ * Templates must pre-register icons they use via registerIcon().
227
+ *
228
+ * For the full dynamic loading with all icons, use the dev version (icon-registry.ts).
229
+ */
230
+
231
+ type IconComponent = ComponentType<IconProps>;
232
+ type IconLoader = () => Promise<{
233
+ default: IconComponent;
234
+ }>;
235
+ /**
236
+ * Pre-register an icon with a custom loader.
237
+ * REQUIRED for production builds - icons must be pre-registered to be available.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * // In your main.tsx or icons.ts
242
+ * import { registerIcon } from '@yoamigo.com/core'
243
+ * import { IconBrandTwitter } from '@tabler/icons-react'
244
+ *
245
+ * // Register icons your template uses
246
+ * registerIcon('IconBrandTwitter', () => Promise.resolve({ default: IconBrandTwitter }))
247
+ * ```
248
+ */
249
+ declare function registerIcon(name: string, loader: IconLoader): void;
250
+ /**
251
+ * Pre-register multiple icons at once.
252
+ */
253
+ declare function registerIcons(loaders: Record<string, IconLoader>): void;
254
+ /**
255
+ * Get a cached icon component synchronously.
256
+ * Returns null if the icon hasn't been loaded yet.
257
+ */
258
+ declare function getIcon(name: string): IconComponent | null;
259
+ /**
260
+ * Load an icon dynamically with caching.
261
+ * In production, only pre-registered icons are available.
262
+ */
263
+ declare function loadIcon(name: string): Promise<IconComponent | null>;
264
+ /**
265
+ * Preload multiple icons in parallel.
266
+ */
267
+ declare function preloadIcons(names: string[]): Promise<void>;
268
+ /**
269
+ * Check if an icon is loaded and cached.
270
+ */
271
+ declare function isIconLoaded(name: string): boolean;
272
+
273
+ /**
274
+ * Icon Metadata - Production version (minimal, no generated icon list)
275
+ *
276
+ * Only includes utilities needed at runtime, not the full search/picker functionality.
277
+ */
278
+ /**
279
+ * Get human-readable label from icon name
280
+ * e.g., "IconBrandTwitter" -> "Twitter"
281
+ * e.g., "IconShoppingCart" -> "Shopping Cart"
282
+ */
283
+ declare function getIconLabel(name: string): string;
284
+
285
+ export { type BackgroundConfig, type BackgroundImageConfig, DynamicIcon, type DynamicIconProps, type EmbedFieldValue, type EmbedType, type IconComponent, type IconLoader, type OverlayConfig, SafeHtml, type SafeHtmlProps, StaticContainer, type StaticContainerProps, StaticEmbed, type StaticEmbedProps, StaticIcon, type StaticIconProps, StaticLink, type StaticLinkProps, StaticVideo, type StaticVideoProps, type VideoFieldValue, StaticContainer as YaContainer, type StaticContainerProps as YaContainerProps, StaticEmbed as YaEmbed, type StaticEmbedProps as YaEmbedProps, StaticIcon as YaIcon, type StaticIconProps as YaIconProps, StaticLink as YaLink, type StaticLinkProps as YaLinkProps, StaticVideo as YaVideo, type StaticVideoProps as YaVideoProps, getIcon, getIconLabel, isIconLoaded, loadIcon, parseBackgroundConfig, preloadIcons, registerIcon, registerIcons, serializeBackgroundConfig, serializeEmbedValue, serializeVideoValue };
package/dist/prod.js CHANGED
@@ -233,10 +233,10 @@ function SafeHtml({ content, className }) {
233
233
  );
234
234
  }
235
235
 
236
- // src/icons/DynamicIcon.tsx
236
+ // src/icons/DynamicIcon.prod.tsx
237
237
  import { useEffect, useState, memo } from "react";
238
238
 
239
- // src/icons/icon-registry.ts
239
+ // src/icons/icon-registry.prod.ts
240
240
  var iconCache = /* @__PURE__ */ new Map();
241
241
  var pendingLoads = /* @__PURE__ */ new Map();
242
242
  var registeredLoaders = /* @__PURE__ */ new Map();
@@ -270,19 +270,12 @@ async function loadIcon(name) {
270
270
  const module = await loader();
271
271
  IconComponent = module.default;
272
272
  } else {
273
- try {
274
- const module = await import(
275
- /* @vite-ignore */
276
- `@tabler/icons-react/dist/esm/icons/${name}.mjs`
277
- );
278
- IconComponent = module.default;
279
- } catch {
280
- console.warn(`Icon "${name}" not found in @tabler/icons-react`);
281
- return null;
282
- }
273
+ console.warn(
274
+ `[yoamigo] Icon "${name}" not registered. In production builds, icons must be pre-registered with registerIcon(). Add to your main.tsx: registerIcon('${name}', () => import('@tabler/icons-react').then(m => ({ default: m.${name} })))`
275
+ );
276
+ return null;
283
277
  }
284
278
  if (!IconComponent) {
285
- console.warn(`Icon "${name}" not found in @tabler/icons-react or custom icons`);
286
279
  return null;
287
280
  }
288
281
  if (iconCache.size >= MAX_CACHE_SIZE) {
@@ -310,7 +303,7 @@ function isIconLoaded(name) {
310
303
  return iconCache.has(name);
311
304
  }
312
305
 
313
- // src/icons/DynamicIcon.tsx
306
+ // src/icons/DynamicIcon.prod.tsx
314
307
  import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
315
308
  function DynamicIconComponent({
316
309
  name,
@@ -1395,7 +1388,218 @@ function link(href) {
1395
1388
  return href;
1396
1389
  }
1397
1390
 
1398
- // src/icons/icon-metadata.ts
1391
+ // src/hooks/useSafeTriangle.ts
1392
+ import { useState as useState5, useRef as useRef5, useCallback as useCallback3, useEffect as useEffect6, useId } from "react";
1393
+
1394
+ // src/lib/PopoverManager.ts
1395
+ function createPopoverManager() {
1396
+ const manager = {
1397
+ activeId: null,
1398
+ state: "idle",
1399
+ popovers: /* @__PURE__ */ new Map(),
1400
+ version: 0,
1401
+ hideTimer: null,
1402
+ hideDelay: 150,
1403
+ register(popover) {
1404
+ this.popovers.set(popover.id, popover);
1405
+ },
1406
+ unregister(id) {
1407
+ this.popovers.delete(id);
1408
+ if (this.activeId === id) {
1409
+ this.version++;
1410
+ this.activeId = null;
1411
+ this.state = "idle";
1412
+ clearTimer(this);
1413
+ }
1414
+ },
1415
+ /**
1416
+ * Called when mouse enters a trigger element.
1417
+ * Immediately shows this popover, hiding any other active one.
1418
+ */
1419
+ onTriggerEnter(id) {
1420
+ const popover = this.popovers.get(id);
1421
+ if (!popover) return;
1422
+ this.version++;
1423
+ clearTimer(this);
1424
+ if (this.activeId === id) {
1425
+ this.state = "trigger-hover";
1426
+ return;
1427
+ }
1428
+ if (this.activeId !== null) {
1429
+ const currentPopover = this.popovers.get(this.activeId);
1430
+ if (currentPopover) {
1431
+ currentPopover.onHide();
1432
+ }
1433
+ }
1434
+ this.activeId = id;
1435
+ this.state = "trigger-hover";
1436
+ popover.onShow();
1437
+ },
1438
+ /**
1439
+ * Called when mouse leaves a trigger element.
1440
+ * Starts a hide timer - user has `hideDelay` ms to reach the popover.
1441
+ */
1442
+ onTriggerLeave(id) {
1443
+ if (this.activeId !== id) return;
1444
+ clearTimer(this);
1445
+ const capturedVersion = this.version;
1446
+ this.hideTimer = setTimeout(() => {
1447
+ this.hideTimer = null;
1448
+ if (this.version === capturedVersion && this.activeId === id) {
1449
+ hidePopover(this, id);
1450
+ }
1451
+ }, this.hideDelay);
1452
+ },
1453
+ /**
1454
+ * Called when mouse enters a popover element.
1455
+ *
1456
+ * Two cases:
1457
+ * 1. Active popover: Cancel hide timer, user successfully reached it
1458
+ * 2. Stale popover (in exit animation): Cancel active popover's hide timer
1459
+ * This treats stale popover areas as "safe zones" - if user's mouse
1460
+ * passes through a stale popover while moving, don't hide the active one
1461
+ */
1462
+ onPopoverEnter(id) {
1463
+ if (this.activeId === id) {
1464
+ clearTimer(this);
1465
+ this.state = "popover-hover";
1466
+ return;
1467
+ }
1468
+ if (this.activeId !== null && this.hideTimer) {
1469
+ clearTimer(this);
1470
+ return;
1471
+ }
1472
+ },
1473
+ /**
1474
+ * Called when mouse leaves a popover element.
1475
+ * Starts a hide timer. Note: useSafeTriangle filters out leave events
1476
+ * from stale popovers, so this should only be called for active popovers.
1477
+ */
1478
+ onPopoverLeave(id) {
1479
+ if (this.activeId !== id) return;
1480
+ clearTimer(this);
1481
+ const capturedVersion = this.version;
1482
+ this.hideTimer = setTimeout(() => {
1483
+ this.hideTimer = null;
1484
+ if (this.version === capturedVersion && this.activeId === id) {
1485
+ hidePopover(this, id);
1486
+ }
1487
+ }, this.hideDelay);
1488
+ },
1489
+ /**
1490
+ * Immediately hide a popover without delay (e.g., when editing starts)
1491
+ */
1492
+ forceHide(id) {
1493
+ if (this.activeId === id) {
1494
+ this.version++;
1495
+ hidePopover(this, id);
1496
+ }
1497
+ },
1498
+ /**
1499
+ * Check if a popover is currently active.
1500
+ * Used by useSafeTriangle to filter out stale popover events.
1501
+ */
1502
+ isActive(id) {
1503
+ return this.activeId === id;
1504
+ }
1505
+ };
1506
+ return manager;
1507
+ }
1508
+ function hidePopover(manager, id) {
1509
+ const popover = manager.popovers.get(id);
1510
+ if (popover) {
1511
+ popover.onHide();
1512
+ }
1513
+ if (manager.activeId === id) {
1514
+ manager.activeId = null;
1515
+ manager.state = "idle";
1516
+ }
1517
+ clearTimer(manager);
1518
+ }
1519
+ function clearTimer(manager) {
1520
+ if (manager.hideTimer) {
1521
+ clearTimeout(manager.hideTimer);
1522
+ manager.hideTimer = null;
1523
+ }
1524
+ }
1525
+ var popoverManager = createPopoverManager();
1526
+
1527
+ // src/hooks/useSafeTriangle.ts
1528
+ function useSafeTriangle(options = {}) {
1529
+ const { enabled = true } = options;
1530
+ const instanceId = useId();
1531
+ const [isVisible, setIsVisible] = useState5(false);
1532
+ const triggerRef = useRef5(null);
1533
+ const popoverRef = useRef5(null);
1534
+ const isVisibleRef = useRef5(isVisible);
1535
+ isVisibleRef.current = isVisible;
1536
+ useEffect6(() => {
1537
+ if (!enabled) return;
1538
+ popoverManager.register({
1539
+ id: instanceId,
1540
+ triggerRef,
1541
+ popoverRef,
1542
+ onShow: () => setIsVisible(true),
1543
+ onHide: () => setIsVisible(false)
1544
+ });
1545
+ return () => {
1546
+ popoverManager.unregister(instanceId);
1547
+ };
1548
+ }, [instanceId, enabled]);
1549
+ useEffect6(() => {
1550
+ if (!enabled && isVisibleRef.current) {
1551
+ setIsVisible(false);
1552
+ popoverManager.forceHide(instanceId);
1553
+ }
1554
+ }, [enabled, instanceId]);
1555
+ const show = useCallback3(() => {
1556
+ if (!enabled) return;
1557
+ setIsVisible(true);
1558
+ }, [enabled]);
1559
+ const hide = useCallback3(() => {
1560
+ setIsVisible(false);
1561
+ popoverManager.forceHide(instanceId);
1562
+ }, [instanceId]);
1563
+ const handleMouseEnter = useCallback3(() => {
1564
+ if (!enabled) return;
1565
+ popoverManager.onTriggerEnter(instanceId);
1566
+ }, [enabled, instanceId]);
1567
+ const handleMouseLeave = useCallback3(() => {
1568
+ if (!enabled) return;
1569
+ popoverManager.onTriggerLeave(instanceId);
1570
+ }, [enabled, instanceId]);
1571
+ const handleFocus = useCallback3(() => {
1572
+ if (!enabled) return;
1573
+ popoverManager.onTriggerEnter(instanceId);
1574
+ }, [enabled, instanceId]);
1575
+ const handlePopoverEnter = useCallback3(() => {
1576
+ if (!enabled) return;
1577
+ popoverManager.onPopoverEnter(instanceId);
1578
+ }, [enabled, instanceId]);
1579
+ const handlePopoverLeave = useCallback3(() => {
1580
+ if (!enabled) return;
1581
+ if (!popoverManager.isActive(instanceId)) return;
1582
+ popoverManager.onPopoverLeave(instanceId);
1583
+ }, [enabled, instanceId]);
1584
+ return {
1585
+ triggerRef,
1586
+ popoverRef,
1587
+ isVisible,
1588
+ handlers: {
1589
+ onMouseEnter: handleMouseEnter,
1590
+ onMouseLeave: handleMouseLeave,
1591
+ onFocus: handleFocus
1592
+ },
1593
+ popoverHandlers: {
1594
+ onMouseEnter: handlePopoverEnter,
1595
+ onMouseLeave: handlePopoverLeave
1596
+ },
1597
+ show,
1598
+ hide
1599
+ };
1600
+ }
1601
+
1602
+ // src/icons/icon-metadata.prod.ts
1399
1603
  function getIconLabel(name) {
1400
1604
  let label = name.replace(/^Icon/, "");
1401
1605
  label = label.replace(/^Brand/, "");
@@ -1459,5 +1663,6 @@ export {
1459
1663
  useLocation4 as useLocation,
1460
1664
  useNavigate,
1461
1665
  useParams,
1666
+ useSafeTriangle,
1462
1667
  video
1463
1668
  };