@yoamigo.com/core 0.3.1 → 0.3.3

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.js CHANGED
@@ -116,6 +116,9 @@ var BuilderSelectionManager = class {
116
116
  elementMap = /* @__PURE__ */ new Map();
117
117
  // Current selections from parent (for re-rendering on mode change)
118
118
  currentSelections = [];
119
+ // Throttle screenshot captures to prevent overlapping
120
+ lastCaptureTime = 0;
121
+ CAPTURE_THROTTLE_MS = 3e3;
119
122
  constructor() {
120
123
  if (window.parent === window) {
121
124
  console.log("[BuilderSelection] Not in iframe, skipping initialization");
@@ -239,6 +242,16 @@ var BuilderSelectionManager = class {
239
242
  });
240
243
  };
241
244
  handleKeyDown = (e) => {
245
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
246
+ const modifier = isMac ? e.metaKey : e.ctrlKey;
247
+ if (modifier) {
248
+ if (e.key === "=" || e.key === "+" || e.key === "-" || e.key === "0" || e.key === "1") {
249
+ e.preventDefault();
250
+ const normalizedKey = e.key === "=" ? "+" : e.key;
251
+ this.sendToParent({ type: "IFRAME_KEYBOARD_ZOOM", key: normalizedKey });
252
+ return;
253
+ }
254
+ }
242
255
  if (e.key === "Shift") {
243
256
  const activeElement = document.activeElement;
244
257
  const isEditing = activeElement?.closest(".ya-text-editing") || activeElement?.closest(".ya-link-editing");
@@ -555,10 +568,16 @@ var BuilderSelectionManager = class {
555
568
  /**
556
569
  * Capture a full-page screenshot for loading placeholder
557
570
  * Uses html2canvas to render the page at reduced quality for smaller file size
571
+ * Throttled to prevent overlapping captures (3 second minimum between captures)
558
572
  */
559
573
  async captureFullPageScreenshot() {
574
+ const now = Date.now();
575
+ if (now - this.lastCaptureTime < this.CAPTURE_THROTTLE_MS) {
576
+ console.log("[BuilderSelection] Screenshot throttled");
577
+ return;
578
+ }
579
+ this.lastCaptureTime = now;
560
580
  try {
561
- console.log("[BuilderSelection] Capturing full-page screenshot");
562
581
  const html2canvas = (await import("html2canvas-pro")).default;
563
582
  const overlays = document.querySelectorAll(".builder-selection-container, #builder-hover-overlay");
564
583
  overlays.forEach((el) => {
@@ -571,14 +590,14 @@ var BuilderSelectionManager = class {
571
590
  logging: false,
572
591
  useCORS: true,
573
592
  allowTaint: true,
574
- backgroundColor: null
593
+ backgroundColor: "#ffffff"
594
+ // White background for JPEG (null would render as black)
575
595
  });
576
596
  overlays.forEach((el) => {
577
597
  ;
578
598
  el.style.display = "";
579
599
  });
580
600
  const dataUrl = canvas.toDataURL("image/jpeg", 0.6);
581
- console.log("[BuilderSelection] Full-page screenshot captured, size:", dataUrl.length);
582
601
  this.sendToParent({
583
602
  type: "SCREENSHOT_READY",
584
603
  dataUrl
@@ -698,12 +717,18 @@ var BuilderSelectionManager = class {
698
717
  this.selections.set(selectionId, { element, container, badge, border });
699
718
  }
700
719
  };
720
+ var instance = null;
701
721
  function initBuilderSelection() {
702
722
  if (typeof window !== "undefined" && window.parent !== window) {
723
+ if (instance) {
724
+ return;
725
+ }
703
726
  if (document.readyState === "loading") {
704
- document.addEventListener("DOMContentLoaded", () => new BuilderSelectionManager());
727
+ document.addEventListener("DOMContentLoaded", () => {
728
+ instance = new BuilderSelectionManager();
729
+ });
705
730
  } else {
706
- new BuilderSelectionManager();
731
+ instance = new BuilderSelectionManager();
707
732
  }
708
733
  }
709
734
  }
package/dist/plugin.js CHANGED
@@ -108,6 +108,12 @@ if (import.meta.hot) {
108
108
  outDir: "dist",
109
109
  emptyOutDir: true,
110
110
  rollupOptions: {
111
+ onwarn(warning, warn) {
112
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes('"use client"')) {
113
+ return;
114
+ }
115
+ warn(warning);
116
+ },
111
117
  output: {
112
118
  // Output to build/ to avoid confusion with public/assets/ (source images)
113
119
  entryFileNames: "build/[name].[hash].js",
package/dist/prod.d.ts CHANGED
@@ -1,4 +1,4 @@
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, u as useContentStore } from './MarkdownText-BUTYfqXS.js';
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-Nvkeyr1z.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React, { CSSProperties, ReactNode } from 'react';
4
4
  export { Link, LinkProps, NavigateFunction, Router, RouterProps, useNavigate } from './router.js';
@@ -127,4 +127,43 @@ interface StaticVideoProps {
127
127
  declare function serializeVideoValue(value: VideoFieldValue): string;
128
128
  declare function StaticVideo({ fieldId, className, aspectRatio: propAspectRatio, objectFit: propObjectFit, loading, defaultValue, fallbackSrc, fallbackPoster, }: StaticVideoProps): react_jsx_runtime.JSX.Element;
129
129
 
130
- export { type BackgroundConfig, type BackgroundImageConfig, type OverlayConfig, SafeHtml, type SafeHtmlProps, StaticContainer, type StaticContainerProps, StaticLink, type StaticLinkProps, StaticVideo, type StaticVideoProps, type VideoFieldValue, StaticContainer as YaContainer, type StaticContainerProps as YaContainerProps, StaticLink as YaLink, type StaticLinkProps as YaLinkProps, StaticVideo as YaVideo, type StaticVideoProps as YaVideoProps, parseBackgroundConfig, serializeBackgroundConfig, serializeVideoValue };
130
+ /**
131
+ * StaticEmbed Component - Production version of YaEmbed
132
+ *
133
+ * Renders embeds statically (Spotify, SoundCloud, Twitter, Instagram, custom).
134
+ * No editing capabilities - pure display.
135
+ */
136
+ type EmbedType = 'spotify' | 'soundcloud' | 'twitter' | 'instagram' | 'custom';
137
+ interface EmbedFieldValue {
138
+ /** Embed platform type */
139
+ type: EmbedType;
140
+ /** Embed URL or ID (platform-specific) */
141
+ src: string;
142
+ /** Original URL pasted by user (for editing) */
143
+ originalUrl?: string;
144
+ /** CSS aspect-ratio (e.g., "16/9", "1/1") */
145
+ aspectRatio?: string;
146
+ /** Fixed height in pixels (for audio players) */
147
+ height?: number;
148
+ /** Spotify-specific: content type */
149
+ spotifyType?: 'track' | 'album' | 'playlist' | 'episode' | 'show';
150
+ }
151
+ interface StaticEmbedProps {
152
+ fieldId: string;
153
+ className?: string;
154
+ /** Default aspect ratio from props */
155
+ aspectRatio?: string;
156
+ /** Max width constraint */
157
+ maxWidth?: number;
158
+ /** Loading strategy */
159
+ loading?: 'lazy' | 'eager';
160
+ /** Default embed value (used when nothing in content store) */
161
+ defaultValue?: EmbedFieldValue;
162
+ }
163
+ /**
164
+ * Serialize embed field value for storage
165
+ */
166
+ declare function serializeEmbedValue(value: EmbedFieldValue): string;
167
+ declare function StaticEmbed({ fieldId, className, aspectRatio: propAspectRatio, maxWidth, loading, defaultValue, }: StaticEmbedProps): react_jsx_runtime.JSX.Element;
168
+
169
+ export { type BackgroundConfig, type BackgroundImageConfig, type EmbedFieldValue, type EmbedType, type OverlayConfig, SafeHtml, type SafeHtmlProps, StaticContainer, type StaticContainerProps, StaticEmbed, type StaticEmbedProps, StaticLink, type StaticLinkProps, StaticVideo, type StaticVideoProps, type VideoFieldValue, StaticContainer as YaContainer, type StaticContainerProps as YaContainerProps, StaticEmbed as YaEmbed, type StaticEmbedProps as YaEmbedProps, StaticLink as YaLink, type StaticLinkProps as YaLinkProps, StaticVideo as YaVideo, type StaticVideoProps as YaVideoProps, parseBackgroundConfig, serializeBackgroundConfig, serializeEmbedValue, serializeVideoValue };
package/dist/prod.js CHANGED
@@ -587,12 +587,299 @@ function StaticVideo({
587
587
  );
588
588
  }
589
589
 
590
+ // src/components/StaticEmbed.tsx
591
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
592
+ import { jsx as jsx9, jsxs } from "react/jsx-runtime";
593
+ function parseEmbedValue(value) {
594
+ if (!value) {
595
+ return { type: "custom", src: "" };
596
+ }
597
+ try {
598
+ const parsed = JSON.parse(value);
599
+ if (typeof parsed === "object" && parsed.src) {
600
+ return {
601
+ type: parsed.type || "custom",
602
+ ...parsed
603
+ };
604
+ }
605
+ } catch {
606
+ }
607
+ return { type: "custom", src: value, originalUrl: value };
608
+ }
609
+ function serializeEmbedValue(value) {
610
+ return JSON.stringify(value);
611
+ }
612
+ function buildSpotifyEmbedUrl(src) {
613
+ return `https://open.spotify.com/embed/${src}?utm_source=generator&theme=0`;
614
+ }
615
+ function buildSoundCloudEmbedUrl(src) {
616
+ return `https://w.soundcloud.com/player/?url=${encodeURIComponent(src)}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false`;
617
+ }
618
+ function buildInstagramEmbedUrl(shortcode) {
619
+ return `https://www.instagram.com/p/${shortcode}/embed`;
620
+ }
621
+ function StaticEmbed({
622
+ fieldId,
623
+ className,
624
+ aspectRatio: propAspectRatio,
625
+ maxWidth,
626
+ loading = "lazy",
627
+ defaultValue
628
+ }) {
629
+ const { getValue } = useContentStore();
630
+ const containerRef = useRef2(null);
631
+ const [isInView, setIsInView] = useState2(loading === "eager");
632
+ const rawValue = getValue(fieldId);
633
+ const parsedValue = parseEmbedValue(rawValue);
634
+ const embedData = parsedValue.src ? parsedValue : defaultValue || parsedValue;
635
+ const src = embedData.src || "";
636
+ const embedType = embedData.type || "custom";
637
+ const height = embedData.height;
638
+ const aspectRatio = embedData.aspectRatio || propAspectRatio || "16/9";
639
+ useEffect2(() => {
640
+ if (loading === "eager" || isInView) return;
641
+ const observer = new IntersectionObserver(
642
+ (entries) => {
643
+ if (entries[0]?.isIntersecting) {
644
+ setIsInView(true);
645
+ observer.disconnect();
646
+ }
647
+ },
648
+ { rootMargin: "200px" }
649
+ );
650
+ if (containerRef.current) {
651
+ observer.observe(containerRef.current);
652
+ }
653
+ return () => observer.disconnect();
654
+ }, [loading, isInView]);
655
+ const renderEmbed = () => {
656
+ if (!src) return null;
657
+ if (!isInView && loading === "lazy") {
658
+ return /* @__PURE__ */ jsx9("div", { className: "ya-embed-placeholder", style: { aspectRatio } });
659
+ }
660
+ if (embedType === "spotify" && src) {
661
+ const embedUrl = buildSpotifyEmbedUrl(src);
662
+ return /* @__PURE__ */ jsx9(
663
+ "iframe",
664
+ {
665
+ src: embedUrl,
666
+ style: {
667
+ width: "100%",
668
+ height: height ? `${height}px` : "100%",
669
+ border: "none",
670
+ borderRadius: "12px"
671
+ },
672
+ allow: "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture",
673
+ loading,
674
+ title: "Spotify embed"
675
+ }
676
+ );
677
+ }
678
+ if (embedType === "soundcloud" && src) {
679
+ const embedUrl = buildSoundCloudEmbedUrl(src);
680
+ return /* @__PURE__ */ jsx9(
681
+ "iframe",
682
+ {
683
+ src: embedUrl,
684
+ style: {
685
+ width: "100%",
686
+ height: height ? `${height}px` : "166px",
687
+ border: "none"
688
+ },
689
+ allow: "autoplay",
690
+ loading,
691
+ title: "SoundCloud embed"
692
+ }
693
+ );
694
+ }
695
+ if (embedType === "twitter" && src) {
696
+ return /* @__PURE__ */ jsxs("div", { className: "ya-embed-twitter", children: [
697
+ /* @__PURE__ */ jsx9("blockquote", { className: "twitter-tweet", "data-dnt": "true", children: /* @__PURE__ */ jsx9("a", { href: embedData.originalUrl || `https://twitter.com/i/status/${src}`, children: "Loading tweet..." }) }),
698
+ /* @__PURE__ */ jsx9(TwitterWidgetLoader, {})
699
+ ] });
700
+ }
701
+ if (embedType === "instagram" && src) {
702
+ const embedUrl = buildInstagramEmbedUrl(src);
703
+ return /* @__PURE__ */ jsx9(
704
+ "iframe",
705
+ {
706
+ src: embedUrl,
707
+ style: {
708
+ width: "100%",
709
+ height: "100%",
710
+ aspectRatio,
711
+ border: "none",
712
+ minHeight: "400px"
713
+ },
714
+ allow: "encrypted-media",
715
+ loading,
716
+ title: "Instagram embed"
717
+ }
718
+ );
719
+ }
720
+ if (embedType === "custom" && src) {
721
+ return /* @__PURE__ */ jsx9(
722
+ "iframe",
723
+ {
724
+ src,
725
+ style: {
726
+ width: "100%",
727
+ height: "100%",
728
+ border: "none",
729
+ aspectRatio
730
+ },
731
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms",
732
+ loading,
733
+ title: "Embedded content"
734
+ }
735
+ );
736
+ }
737
+ return null;
738
+ };
739
+ const wrapperStyle = {
740
+ aspectRatio: height ? void 0 : aspectRatio,
741
+ height: height ? `${height}px` : void 0,
742
+ maxWidth: maxWidth ? `${maxWidth}px` : void 0
743
+ };
744
+ return /* @__PURE__ */ jsx9(
745
+ "div",
746
+ {
747
+ ref: containerRef,
748
+ className: `ya-embed-wrapper ${className || ""}`,
749
+ style: wrapperStyle,
750
+ "data-field-id": fieldId,
751
+ "data-embed-type": embedType,
752
+ children: renderEmbed()
753
+ }
754
+ );
755
+ }
756
+ function TwitterWidgetLoader() {
757
+ useEffect2(() => {
758
+ if (window.twttr?.widgets) {
759
+ ;
760
+ window.twttr.widgets.load();
761
+ return;
762
+ }
763
+ if (document.getElementById("twitter-wjs")) {
764
+ return;
765
+ }
766
+ const script = document.createElement("script");
767
+ script.id = "twitter-wjs";
768
+ script.src = "https://platform.twitter.com/widgets.js";
769
+ script.async = true;
770
+ document.body.appendChild(script);
771
+ }, []);
772
+ return null;
773
+ }
774
+
775
+ // src/components/YaEmbed.tsx
776
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
777
+
778
+ // #style-inject:#style-inject
779
+ function styleInject(css, { insertAt } = {}) {
780
+ if (!css || typeof document === "undefined") return;
781
+ const head = document.head || document.getElementsByTagName("head")[0];
782
+ const style = document.createElement("style");
783
+ style.type = "text/css";
784
+ if (insertAt === "top") {
785
+ if (head.firstChild) {
786
+ head.insertBefore(style, head.firstChild);
787
+ } else {
788
+ head.appendChild(style);
789
+ }
790
+ } else {
791
+ head.appendChild(style);
792
+ }
793
+ if (style.styleSheet) {
794
+ style.styleSheet.cssText = css;
795
+ } else {
796
+ style.appendChild(document.createTextNode(css));
797
+ }
798
+ }
799
+
800
+ // src/components/ya-embed.css
801
+ styleInject('.ya-embed-wrapper {\n position: relative;\n display: block;\n width: 100%;\n}\n.ya-embed-wrapper iframe {\n display: block;\n width: 100%;\n height: 100%;\n}\n.ya-embed-container {\n position: relative;\n display: block;\n width: 100%;\n min-width: 80px;\n min-height: 80px;\n cursor: pointer;\n transition: outline 0.15s ease;\n}\n.ya-embed-container iframe {\n display: block;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n.ya-embed-editable {\n cursor: pointer;\n}\n.ya-embed-editable:hover {\n outline: 2px dashed var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-selected {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n background: rgba(0, 0, 0, 0.5);\n opacity: 0;\n transition: opacity 0.2s ease;\n pointer-events: none;\n border-radius: inherit;\n}\n.ya-embed-editable:hover .ya-embed-overlay {\n opacity: 1;\n}\n.ya-embed-selected .ya-embed-overlay {\n opacity: 0;\n}\n.ya-embed-edit-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n background: white;\n border-radius: 50%;\n color: #1a1a1a;\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);\n}\n.ya-embed-edit-icon svg {\n width: 24px;\n height: 24px;\n}\n.ya-embed-edit-label {\n color: white;\n font-size: 14px;\n font-weight: 500;\n text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n}\n.ya-embed-placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 100%;\n min-height: 120px;\n background: #f3f4f6;\n border: 2px dashed #d1d5db;\n border-radius: 8px;\n color: #6b7280;\n font-size: 14px;\n}\n.ya-embed-placeholder img {\n width: 64px;\n height: auto;\n opacity: 0.5;\n}\n@keyframes ya-embed-success {\n 0% {\n outline-color: var(--color-primary, #d4a574);\n }\n 50% {\n outline-color: #22c55e;\n outline-width: 4px;\n }\n 100% {\n outline-color: var(--color-primary, #d4a574);\n outline-width: 2px;\n }\n}\n.ya-embed-success {\n animation: ya-embed-success 0.4s ease;\n}\n.ya-embed-loading::after {\n content: "";\n position: absolute;\n inset: 0;\n background:\n linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(255, 255, 255, 0.3) 50%,\n rgba(255, 255, 255, 0) 100%);\n background-size: 200% 100%;\n animation: ya-embed-shimmer 1.5s infinite;\n}\n@keyframes ya-embed-shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n}\n.ya-embed-container:focus {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-container:focus:not(:focus-visible) {\n outline: none;\n}\n.ya-embed-container:focus-visible {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-small .ya-embed-overlay {\n display: none;\n}\n.ya-embed-twitter {\n min-height: 200px;\n}\n.ya-embed-twitter .twitter-tweet {\n margin: 0 auto !important;\n}\n.ya-embed-wrapper[data-embed-type=spotify],\n.ya-embed-container[data-embed-type=spotify] {\n min-height: 80px;\n}\n.ya-embed-wrapper[data-embed-type=soundcloud],\n.ya-embed-container[data-embed-type=soundcloud] {\n min-height: 166px;\n}\n.ya-embed-wrapper[data-embed-type=instagram] iframe,\n.ya-embed-container[data-embed-type=instagram] iframe {\n min-height: 400px;\n}\n');
802
+
803
+ // src/components/YaEmbed.tsx
804
+ import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
805
+ function parseEmbedUrl(url) {
806
+ if (!url) return null;
807
+ const trimmedUrl = url.trim();
808
+ const spotifyMatch = trimmedUrl.match(
809
+ /open\.spotify\.com\/(track|album|playlist|episode|show)\/([a-zA-Z0-9]+)/
810
+ );
811
+ if (spotifyMatch) {
812
+ const spotifyType = spotifyMatch[1];
813
+ const spotifyId = spotifyMatch[2];
814
+ const height = spotifyType === "track" ? 152 : 352;
815
+ return {
816
+ type: "spotify",
817
+ src: `${spotifyType}/${spotifyId}`,
818
+ originalUrl: trimmedUrl,
819
+ height,
820
+ spotifyType
821
+ };
822
+ }
823
+ const soundcloudMatch = trimmedUrl.match(/soundcloud\.com\/([^/]+)\/([^/?]+)/);
824
+ if (soundcloudMatch) {
825
+ return {
826
+ type: "soundcloud",
827
+ src: trimmedUrl,
828
+ // SoundCloud embeds use full URL
829
+ originalUrl: trimmedUrl,
830
+ height: 166
831
+ };
832
+ }
833
+ const twitterMatch = trimmedUrl.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/);
834
+ if (twitterMatch) {
835
+ return {
836
+ type: "twitter",
837
+ src: twitterMatch[1],
838
+ // Tweet ID
839
+ originalUrl: trimmedUrl,
840
+ aspectRatio: "1/1"
841
+ // Twitter embeds are roughly square
842
+ };
843
+ }
844
+ const instagramMatch = trimmedUrl.match(/instagram\.com\/(?:p|reel)\/([a-zA-Z0-9_-]+)/);
845
+ if (instagramMatch) {
846
+ return {
847
+ type: "instagram",
848
+ src: instagramMatch[1],
849
+ // Post shortcode
850
+ originalUrl: trimmedUrl,
851
+ aspectRatio: "1/1"
852
+ };
853
+ }
854
+ if (trimmedUrl.startsWith("https://")) {
855
+ if (trimmedUrl.startsWith("javascript:") || trimmedUrl.startsWith("data:") || trimmedUrl.includes("<script")) {
856
+ return null;
857
+ }
858
+ return {
859
+ type: "custom",
860
+ src: trimmedUrl,
861
+ originalUrl: trimmedUrl,
862
+ aspectRatio: "16/9"
863
+ };
864
+ }
865
+ return null;
866
+ }
867
+ var PLACEHOLDER_SVG = `data:image/svg+xml,${encodeURIComponent(`
868
+ <svg xmlns="http://www.w3.org/2000/svg" width="400" height="225" viewBox="0 0 400 225">
869
+ <rect fill="#e5e7eb" width="400" height="225"/>
870
+ <g fill="#9ca3af" transform="translate(175, 87)">
871
+ <rect x="5" y="5" width="40" height="30" rx="4" stroke="currentColor" stroke-width="2" fill="none"/>
872
+ <path d="M15 15 L25 22 L15 29 Z" fill="currentColor"/>
873
+ </g>
874
+ </svg>
875
+ `)}`;
876
+
590
877
  // src/router/Link.tsx
591
878
  import { Link as WouterLink } from "wouter";
592
- import { jsx as jsx9 } from "react/jsx-runtime";
879
+ import { jsx as jsx11 } from "react/jsx-runtime";
593
880
  function Link({ to, href, children, className, onClick, replace, ...props }) {
594
881
  const target = href ?? to ?? "/";
595
- return /* @__PURE__ */ jsx9(WouterLink, { href: target, className, onClick, replace, ...props, children });
882
+ return /* @__PURE__ */ jsx11(WouterLink, { href: target, className, onClick, replace, ...props, children });
596
883
  }
597
884
 
598
885
  // src/router/useNavigate.ts
@@ -611,7 +898,7 @@ function useNavigate() {
611
898
 
612
899
  // src/router/Router.tsx
613
900
  import { Router as WouterRouter } from "wouter";
614
- import { jsx as jsx10 } from "react/jsx-runtime";
901
+ import { jsx as jsx12 } from "react/jsx-runtime";
615
902
  function detectBasename() {
616
903
  if (typeof window === "undefined") return "";
617
904
  const sessionMatch = window.location.pathname.match(/^\/session\/[^/]+/);
@@ -626,7 +913,7 @@ function detectBasename() {
626
913
  }
627
914
  function Router({ children, base }) {
628
915
  const basename = base ?? detectBasename();
629
- return /* @__PURE__ */ jsx10(WouterRouter, { base: basename, children });
916
+ return /* @__PURE__ */ jsx12(WouterRouter, { base: basename, children });
630
917
  }
631
918
 
632
919
  // src/router/index.ts
@@ -639,12 +926,14 @@ export {
639
926
  Router,
640
927
  SafeHtml,
641
928
  StaticContainer,
929
+ StaticEmbed,
642
930
  MpImage as StaticImage,
643
931
  StaticLink,
644
932
  MpText as StaticText,
645
933
  StaticVideo,
646
934
  Switch,
647
935
  StaticContainer as YaContainer,
936
+ StaticEmbed as YaEmbed,
648
937
  MpImage as YaImage,
649
938
  StaticLink as YaLink,
650
939
  MpText as YaText,
@@ -654,9 +943,11 @@ export {
654
943
  getContent,
655
944
  hasContent,
656
945
  parseBackgroundConfig,
946
+ parseEmbedUrl,
657
947
  registerContent,
658
948
  resolveAssetUrl,
659
949
  serializeBackgroundConfig,
950
+ serializeEmbedValue,
660
951
  serializeVideoValue,
661
952
  setAssetResolver,
662
953
  useContentStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoamigo.com/core",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Core components, router, and utilities for YoAmigo templates",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",