@numueg/theme-sdk 0.1.0 → 0.2.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/index.cjs CHANGED
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
+ var client = require('react-dom/client');
4
5
  var jsxRuntime = require('react/jsx-runtime');
5
6
 
6
- // src/hooks/useShop.ts
7
+ // src/types/theme.ts
8
+ var MAX_BLOCK_DEPTH = 5;
7
9
  var ShopContext = react.createContext(null);
8
10
  var ProductContext = react.createContext(null);
9
11
  var CollectionContext = react.createContext(null);
@@ -12,6 +14,10 @@ var CustomerContext = react.createContext(null);
12
14
  var ThemeSettingsContext = react.createContext(null);
13
15
  var LocalizationContext = react.createContext(null);
14
16
  var PageContext = react.createContext(null);
17
+ var CurrentTemplateContext = react.createContext("home");
18
+ var NavigationContext = react.createContext(
19
+ {}
20
+ );
15
21
  function useLocalization() {
16
22
  const ctx = react.useContext(LocalizationContext);
17
23
  if (!ctx) throw new Error("useLocalization must be used within NuMuProvider");
@@ -120,6 +126,9 @@ function useThemeSettings() {
120
126
  if (!ctx) throw new Error("useThemeSettings must be used within NuMuProvider");
121
127
  return ctx;
122
128
  }
129
+ function useCurrentTemplate() {
130
+ return react.useContext(CurrentTemplateContext);
131
+ }
123
132
  function usePage() {
124
133
  return react.useContext(PageContext);
125
134
  }
@@ -489,15 +498,67 @@ function useCustomerAddresses() {
489
498
  };
490
499
  }
491
500
  var cache = /* @__PURE__ */ new Map();
501
+ function localizeLabel(label, locale) {
502
+ if (!label) return "";
503
+ return label[locale] || label.en || label.ar || Object.values(label).find((v) => !!v) || "";
504
+ }
505
+ function mapResourceType(type) {
506
+ switch (type) {
507
+ case "product":
508
+ return "product";
509
+ case "collection":
510
+ return "collection";
511
+ case "page":
512
+ return "page";
513
+ case "blog":
514
+ return "blog";
515
+ case "article":
516
+ return "article";
517
+ case "http":
518
+ case "url":
519
+ return "url";
520
+ default:
521
+ return null;
522
+ }
523
+ }
524
+ function toNavigationItem(raw, locale) {
525
+ return {
526
+ id: raw.id,
527
+ title: localizeLabel(raw.label, locale),
528
+ url: raw.url || "/",
529
+ resource_type: mapResourceType(raw.type),
530
+ resource_handle: raw.resource_id ?? null,
531
+ children: (raw.children ?? []).map(
532
+ (child) => toNavigationItem(child, locale)
533
+ )
534
+ };
535
+ }
492
536
  function useNavigation(handle, options) {
537
+ const navMap = react.useContext(NavigationContext);
538
+ const localization = react.useContext(LocalizationContext);
539
+ const locale = localization?.locale ?? "en";
540
+ const hostProvidedAny = !!navMap && Object.keys(navMap).length > 0;
541
+ const rawItems = handle ? navMap?.[handle] : void 0;
542
+ const hostItems = react.useMemo(() => {
543
+ if (rawItems) return rawItems.map((it) => toNavigationItem(it, locale));
544
+ if (hostProvidedAny) return [];
545
+ return null;
546
+ }, [rawItems, hostProvidedAny, locale]);
493
547
  const [items, setItems] = react.useState(
494
- options?.initialItems ?? cache.get(handle) ?? []
548
+ hostItems ?? options?.initialItems ?? cache.get(handle) ?? []
495
549
  );
496
550
  const [loading, setLoading] = react.useState(
497
- !options?.initialItems && !cache.has(handle)
551
+ hostItems === null && !options?.initialItems && !cache.has(handle)
498
552
  );
499
553
  const [error, setError] = react.useState(null);
500
554
  react.useEffect(() => {
555
+ if (hostItems === null) return;
556
+ setItems(hostItems);
557
+ setLoading(false);
558
+ setError(null);
559
+ }, [hostItems]);
560
+ react.useEffect(() => {
561
+ if (hostItems !== null) return;
501
562
  if (typeof window === "undefined") return;
502
563
  if (!handle) {
503
564
  setItems([]);
@@ -540,7 +601,7 @@ function useNavigation(handle, options) {
540
601
  return () => {
541
602
  cancelled = true;
542
603
  };
543
- }, [handle]);
604
+ }, [handle, hostItems === null]);
544
605
  return { items, loading, error };
545
606
  }
546
607
  var EMPTY = {
@@ -614,34 +675,92 @@ function useSearch(query, options = {}) {
614
675
  }, [query, mode, debounceMs, immediate, limit, types?.join(",")]);
615
676
  return { query, results, loading, error };
616
677
  }
678
+ var EVENT_TO_FUNNEL_STEP = {
679
+ page_view: "page_view",
680
+ view_item: "product_view",
681
+ view_collection: "page_view",
682
+ add_to_cart: "add_to_cart",
683
+ begin_checkout: "checkout_started",
684
+ purchase: "order_completed",
685
+ // Wave 2+ standard Meta events — backend's FUNNEL_STEP_TO_META_EVENT
686
+ // table accepts these as funnel steps directly.
687
+ search: "search",
688
+ lead: "lead",
689
+ sign_up: "complete_registration",
690
+ add_to_wishlist: "add_to_wishlist",
691
+ add_payment_info: "add_payment_info"
692
+ };
693
+ function readAttribution() {
694
+ if (typeof window === "undefined") return null;
695
+ try {
696
+ return window.__numu_attribution?.get?.() ?? null;
697
+ } catch {
698
+ return null;
699
+ }
700
+ }
701
+ function readCustomerId() {
702
+ if (typeof window === "undefined") return null;
703
+ try {
704
+ return window.__numu_customer?.getId?.() ?? null;
705
+ } catch {
706
+ return null;
707
+ }
708
+ }
709
+ function getFingerprint() {
710
+ if (typeof window === "undefined") return "ssr";
711
+ const env = readAttribution();
712
+ if (env?.session_id) return env.session_id;
713
+ const w = window;
714
+ if (w.__numu_session_fp) return w.__numu_session_fp;
715
+ const fp = crypto.randomUUID();
716
+ w.__numu_session_fp = fp;
717
+ return fp;
718
+ }
719
+ function dispatchAnalyticsEvent(eventName, payload = {}) {
720
+ if (typeof window === "undefined") return;
721
+ try {
722
+ window.dispatchEvent(
723
+ new CustomEvent("numu:analytics:event", {
724
+ detail: { event: eventName, payload, ts: Date.now() }
725
+ })
726
+ );
727
+ } catch {
728
+ }
729
+ const step = EVENT_TO_FUNNEL_STEP[eventName] ?? null;
730
+ const customerId = readCustomerId();
731
+ const body = step ? {
732
+ event_id: crypto.randomUUID(),
733
+ path: window.location.pathname,
734
+ fingerprint: getFingerprint(),
735
+ step,
736
+ step_data: payload,
737
+ referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
738
+ attribution: readAttribution() ?? void 0,
739
+ customer_id: customerId ?? void 0
740
+ } : {
741
+ event: eventName,
742
+ payload,
743
+ ts: Date.now(),
744
+ attribution: readAttribution() ?? void 0,
745
+ customer_id: customerId ?? void 0
746
+ };
747
+ void (async () => {
748
+ try {
749
+ await fetch("/api/storefront/track", {
750
+ method: "POST",
751
+ headers: { "Content-Type": "application/json" },
752
+ body: JSON.stringify(body),
753
+ keepalive: true
754
+ // survive page-unload (e.g. begin_checkout → redirect)
755
+ });
756
+ } catch {
757
+ }
758
+ })();
759
+ }
617
760
  function useAnalytics() {
618
761
  const track = react.useCallback(
619
762
  (eventName, payload = {}) => {
620
- if (typeof window === "undefined") return;
621
- try {
622
- window.dispatchEvent(
623
- new CustomEvent("numu:analytics:event", {
624
- detail: { event: eventName, payload, ts: Date.now() }
625
- })
626
- );
627
- } catch {
628
- }
629
- void (async () => {
630
- try {
631
- await fetch("/api/storefront/track", {
632
- method: "POST",
633
- headers: { "Content-Type": "application/json" },
634
- body: JSON.stringify({
635
- event: eventName,
636
- payload,
637
- ts: Date.now()
638
- }),
639
- keepalive: true
640
- // survive page-unload (e.g. begin_checkout → redirect)
641
- });
642
- } catch {
643
- }
644
- })();
763
+ dispatchAnalyticsEvent(eventName, payload);
645
764
  },
646
765
  []
647
766
  );
@@ -1357,8 +1476,23 @@ function NuMuProvider({
1357
1476
  customer,
1358
1477
  locale: initialLocale,
1359
1478
  translations: initialTranslations,
1479
+ currentTemplate = "home",
1480
+ initialProducts,
1481
+ initialCollections,
1482
+ navigation,
1360
1483
  children
1361
1484
  }) {
1485
+ const pageValue = react.useMemo(
1486
+ () => ({
1487
+ type: currentTemplate,
1488
+ title: store?.name ?? "",
1489
+ data: {
1490
+ products: initialProducts ?? [],
1491
+ collections: initialCollections ?? []
1492
+ }
1493
+ }),
1494
+ [currentTemplate, store?.name, initialProducts, initialCollections]
1495
+ );
1362
1496
  const [cart, setCart] = react.useState(
1363
1497
  initialCart || { ...EMPTY_CART, currency: store.currency }
1364
1498
  );
@@ -1728,11 +1862,238 @@ function NuMuProvider({
1728
1862
  defaultNumberFmt
1729
1863
  ]
1730
1864
  );
1731
- return /* @__PURE__ */ jsxRuntime.jsx(ShopContext.Provider, { value: store, children: /* @__PURE__ */ jsxRuntime.jsx(ThemeSettingsContext.Provider, { value: themeSettings, children: /* @__PURE__ */ jsxRuntime.jsx(LocalizationContext.Provider, { value: localization, children: /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value: cartValue, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerContext.Provider, { value: customerState, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerActionsContext.Provider, { value: customerActions, children }) }) }) }) }) });
1865
+ return /* @__PURE__ */ jsxRuntime.jsx(ShopContext.Provider, { value: store, children: /* @__PURE__ */ jsxRuntime.jsx(ThemeSettingsContext.Provider, { value: themeSettings, children: /* @__PURE__ */ jsxRuntime.jsx(CurrentTemplateContext.Provider, { value: currentTemplate, children: /* @__PURE__ */ jsxRuntime.jsx(PageContext.Provider, { value: pageValue, children: /* @__PURE__ */ jsxRuntime.jsx(LocalizationContext.Provider, { value: localization, children: /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value: cartValue, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerContext.Provider, { value: customerState, children: /* @__PURE__ */ jsxRuntime.jsx(CustomerActionsContext.Provider, { value: customerActions, children: /* @__PURE__ */ jsxRuntime.jsx(NavigationContext.Provider, { value: navigation ?? {}, children }) }) }) }) }) }) }) }) });
1732
1866
  }
1733
1867
  function ProductProvider({ product, children }) {
1734
1868
  return /* @__PURE__ */ jsxRuntime.jsx(ProductContext.Provider, { value: product, children });
1735
1869
  }
1870
+
1871
+ // src/utils/styleTokens.ts
1872
+ var COLOR_ROLE_ALIASES = {
1873
+ background_color: "background",
1874
+ color_background: "background",
1875
+ bg_color: "background",
1876
+ text_color: "text",
1877
+ color_text: "text",
1878
+ primary_color: "primary",
1879
+ color_primary: "primary",
1880
+ secondary_color: "secondary",
1881
+ color_secondary: "secondary",
1882
+ accent_color: "accent",
1883
+ color_accent: "accent",
1884
+ border_color: "border",
1885
+ color_border: "border",
1886
+ button_color: "button",
1887
+ color_button: "button",
1888
+ button_text_color: "button-text",
1889
+ color_button_text: "button-text"
1890
+ };
1891
+ var FONT_ROLE_ALIASES = {
1892
+ heading_font: "heading",
1893
+ font_heading: "heading",
1894
+ headings_font: "heading",
1895
+ body_font: "body",
1896
+ font_body: "body",
1897
+ text_font: "body"
1898
+ };
1899
+ var FONT_REGISTRY = {
1900
+ cormorant: {
1901
+ stack: '"Cormorant Garamond", Georgia, "Times New Roman", serif',
1902
+ href: "https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap"
1903
+ },
1904
+ "dm-sans": {
1905
+ stack: '"DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif',
1906
+ href: "https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,700&display=swap"
1907
+ },
1908
+ playfair: {
1909
+ stack: '"Playfair Display", Georgia, serif',
1910
+ href: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap"
1911
+ },
1912
+ inter: {
1913
+ stack: '"Inter", system-ui, -apple-system, sans-serif',
1914
+ href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
1915
+ },
1916
+ poppins: {
1917
+ stack: '"Poppins", system-ui, sans-serif',
1918
+ href: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"
1919
+ },
1920
+ montserrat: {
1921
+ stack: '"Montserrat", system-ui, sans-serif',
1922
+ href: "https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap"
1923
+ },
1924
+ lora: {
1925
+ stack: '"Lora", Georgia, serif',
1926
+ href: "https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;1,400&display=swap"
1927
+ },
1928
+ cairo: {
1929
+ stack: '"Cairo", system-ui, sans-serif',
1930
+ href: "https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700&display=swap"
1931
+ },
1932
+ tajawal: {
1933
+ stack: '"Tajawal", system-ui, sans-serif',
1934
+ href: "https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap"
1935
+ }
1936
+ };
1937
+ var COLOR_RE = /^(#([0-9a-f]{3,8})|rgba?\(|hsla?\(|color\(|var\()/i;
1938
+ function isColorValue(v) {
1939
+ return typeof v === "string" && COLOR_RE.test(v.trim());
1940
+ }
1941
+ function isFontToken(v) {
1942
+ return typeof v === "string" && Object.prototype.hasOwnProperty.call(FONT_REGISTRY, v);
1943
+ }
1944
+ function injectFontLink(href) {
1945
+ if (typeof document === "undefined" || !href) return;
1946
+ const existing = document.querySelector(
1947
+ `link[data-numu-font][href="${href}"]`
1948
+ );
1949
+ if (existing) return;
1950
+ const link = document.createElement("link");
1951
+ link.rel = "stylesheet";
1952
+ link.href = href;
1953
+ link.setAttribute("data-numu-font", "");
1954
+ document.head.appendChild(link);
1955
+ }
1956
+ function resolveFontStack(value) {
1957
+ const entry = FONT_REGISTRY[value];
1958
+ if (entry) {
1959
+ if (entry.href) injectFontLink(entry.href);
1960
+ return entry.stack;
1961
+ }
1962
+ return value;
1963
+ }
1964
+ function applyGlobalStyleTokens(globalSettings, el) {
1965
+ if (!el || !globalSettings || typeof globalSettings !== "object") return;
1966
+ const style = el.style;
1967
+ for (const [key, value] of Object.entries(globalSettings)) {
1968
+ if (!key || key.startsWith("__")) continue;
1969
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1970
+ for (const [role, c] of Object.entries(value)) {
1971
+ if (isColorValue(c)) style.setProperty(`--scheme-${key}-${role}`, c);
1972
+ }
1973
+ continue;
1974
+ }
1975
+ if (isColorValue(value)) {
1976
+ style.setProperty(`--theme-${key}`, value.trim());
1977
+ const role = COLOR_ROLE_ALIASES[key];
1978
+ if (role) style.setProperty(`--theme-color-${role}`, value.trim());
1979
+ continue;
1980
+ }
1981
+ if (isFontToken(value)) {
1982
+ const stack = resolveFontStack(value);
1983
+ style.setProperty(`--theme-${key}`, stack);
1984
+ const role = FONT_ROLE_ALIASES[key];
1985
+ if (role) style.setProperty(`--theme-font-${role}`, stack);
1986
+ continue;
1987
+ }
1988
+ if (typeof value === "string" || typeof value === "number") {
1989
+ const v = String(value).trim();
1990
+ if (v) style.setProperty(`--theme-${key}`, v);
1991
+ }
1992
+ }
1993
+ }
1994
+ function pickStore(ctx) {
1995
+ const s = ctx.storeData ?? ctx.store;
1996
+ if (s) return s;
1997
+ return {
1998
+ id: "unknown",
1999
+ name: "Store",
2000
+ slug: "store",
2001
+ currency: "EGP",
2002
+ default_language: "en",
2003
+ use_nextjs_storefront: true
2004
+ };
2005
+ }
2006
+ function pickTemplate(ctx) {
2007
+ if (typeof ctx.currentTemplate === "string" && ctx.currentTemplate) {
2008
+ return ctx.currentTemplate;
2009
+ }
2010
+ const pageType = ctx.page?.type;
2011
+ if (typeof pageType === "string" && pageType) return pageType;
2012
+ return "home";
2013
+ }
2014
+ function pickDemo(ctx, themeSettings) {
2015
+ if (typeof ctx.demo === "boolean") return ctx.demo;
2016
+ const t = themeSettings.templates;
2017
+ return !t || Object.keys(t).length === 0;
2018
+ }
2019
+ var ThemeMountBridge = react.forwardRef(function ThemeMountBridge2({ ctx, mountEl, renderApp }, ref) {
2020
+ const [themeSettings, setThemeSettings] = react.useState(
2021
+ ctx.themeSettings
2022
+ );
2023
+ react.useImperativeHandle(
2024
+ ref,
2025
+ () => ({
2026
+ applyDraft: (next) => setThemeSettings((prev) => prev === next ? prev : next)
2027
+ }),
2028
+ []
2029
+ );
2030
+ react.useEffect(() => {
2031
+ const gs = themeSettings.global_settings ?? {};
2032
+ applyGlobalStyleTokens(gs, mountEl);
2033
+ const headingFont = gs.heading_font;
2034
+ if (typeof headingFont === "string" && headingFont.trim()) {
2035
+ mountEl.style.setProperty(
2036
+ "--theme-heading_font",
2037
+ resolveFontStack(headingFont)
2038
+ );
2039
+ }
2040
+ const bodyFont = gs.body_font;
2041
+ if (typeof bodyFont === "string" && bodyFont.trim()) {
2042
+ mountEl.style.setProperty("--theme-body_font", resolveFontStack(bodyFont));
2043
+ }
2044
+ }, [themeSettings, mountEl]);
2045
+ const store = pickStore(ctx);
2046
+ const template = pickTemplate(ctx);
2047
+ const demo = pickDemo(ctx, themeSettings);
2048
+ const pageData = ctx.page?.data ?? {};
2049
+ const app = renderApp({
2050
+ currentTemplate: template,
2051
+ demo,
2052
+ page: ctx.page ?? null,
2053
+ store,
2054
+ themeSettings
2055
+ });
2056
+ return /* @__PURE__ */ jsxRuntime.jsx(
2057
+ NuMuProvider,
2058
+ {
2059
+ store,
2060
+ themeSettings,
2061
+ initialCart: ctx.initialCart,
2062
+ customer: ctx.customer,
2063
+ locale: ctx.locale,
2064
+ translations: ctx.translations,
2065
+ navigation: ctx.navigation,
2066
+ initialProducts: pageData.products,
2067
+ initialCollections: pageData.collections,
2068
+ currentTemplate: template,
2069
+ children: pageData.product ? /* @__PURE__ */ jsxRuntime.jsx(ProductProvider, { product: pageData.product, children: app }) : app
2070
+ }
2071
+ );
2072
+ });
2073
+ function mountTheme(el, ctx, renderApp) {
2074
+ const root = client.createRoot(el);
2075
+ const handleRef = { current: null };
2076
+ root.render(
2077
+ /* @__PURE__ */ jsxRuntime.jsx(react.StrictMode, { children: /* @__PURE__ */ jsxRuntime.jsx(
2078
+ ThemeMountBridge,
2079
+ {
2080
+ ctx,
2081
+ mountEl: el,
2082
+ renderApp,
2083
+ ref: (h) => {
2084
+ handleRef.current = h;
2085
+ }
2086
+ }
2087
+ ) })
2088
+ );
2089
+ return {
2090
+ applyDraft: (next) => handleRef.current?.applyDraft(next),
2091
+ cleanup: () => {
2092
+ root.unmount();
2093
+ handleRef.current = null;
2094
+ }
2095
+ };
2096
+ }
1736
2097
  function CollectionProvider({ collection, children }) {
1737
2098
  return /* @__PURE__ */ jsxRuntime.jsx(CollectionContext.Provider, { value: collection, children });
1738
2099
  }
@@ -2375,6 +2736,239 @@ function LocaleSwitcher({
2375
2736
  }
2376
2737
  );
2377
2738
  }
2739
+ function useIsEditor() {
2740
+ const [isEditor, setIsEditor] = react.useState(false);
2741
+ react.useEffect(() => {
2742
+ if (typeof window === "undefined") return;
2743
+ try {
2744
+ const params = new URLSearchParams(window.location.search);
2745
+ const flag = params.get("editor") === "v3" || params.get("preview") === "true";
2746
+ const inFrame = window.parent !== window;
2747
+ setIsEditor(flag && inFrame);
2748
+ } catch {
2749
+ }
2750
+ }, []);
2751
+ return isEditor;
2752
+ }
2753
+ function postFieldSelected(sectionId, settingId, blockId) {
2754
+ if (typeof window === "undefined") return;
2755
+ if (window.parent === window) return;
2756
+ try {
2757
+ window.parent.postMessage(
2758
+ {
2759
+ type: "numu:editor:select-field",
2760
+ payload: {
2761
+ sectionId,
2762
+ blockId: blockId ?? null,
2763
+ settingId
2764
+ }
2765
+ },
2766
+ "*"
2767
+ );
2768
+ } catch {
2769
+ }
2770
+ }
2771
+ var EditableText = react.forwardRef(
2772
+ function EditableText2({
2773
+ sectionId,
2774
+ blockId,
2775
+ settingId,
2776
+ value,
2777
+ as: Component2 = "span",
2778
+ html = false,
2779
+ placeholder,
2780
+ className,
2781
+ onClick,
2782
+ style,
2783
+ ...rest
2784
+ }, ref) {
2785
+ const isEditor = useIsEditor();
2786
+ const handleClick = react.useCallback(
2787
+ (e) => {
2788
+ onClick?.(e);
2789
+ if (!isEditor) return;
2790
+ e.stopPropagation();
2791
+ postFieldSelected(sectionId, settingId, blockId);
2792
+ },
2793
+ [isEditor, onClick, sectionId, settingId, blockId]
2794
+ );
2795
+ const editorAttrs = react.useMemo(
2796
+ () => isEditor ? {
2797
+ "data-numu-editable": "text",
2798
+ "data-numu-section-id": sectionId,
2799
+ "data-numu-setting-id": settingId,
2800
+ "data-numu-block-id": blockId ?? void 0,
2801
+ role: "button",
2802
+ tabIndex: 0
2803
+ } : {},
2804
+ [isEditor, sectionId, settingId, blockId]
2805
+ );
2806
+ const effectiveStyle = isEditor ? {
2807
+ ...style,
2808
+ cursor: "text",
2809
+ // Subtle dotted underline so the merchant discovers what's
2810
+ // editable. The dashed style + inherited color keeps the
2811
+ // affordance from clashing with the theme's typography.
2812
+ textDecoration: "underline dotted rgba(99, 102, 241, 0.7)",
2813
+ textUnderlineOffset: "4px"
2814
+ } : style;
2815
+ const isEmpty = value == null || value === "";
2816
+ const display = isEmpty && placeholder !== void 0 ? placeholder : value ?? "";
2817
+ if (html && typeof display === "string") {
2818
+ return /* @__PURE__ */ jsxRuntime.jsx(
2819
+ Component2,
2820
+ {
2821
+ ref,
2822
+ className,
2823
+ style: effectiveStyle,
2824
+ onClick: handleClick,
2825
+ dangerouslySetInnerHTML: { __html: display },
2826
+ ...editorAttrs,
2827
+ ...rest
2828
+ }
2829
+ );
2830
+ }
2831
+ return /* @__PURE__ */ jsxRuntime.jsx(
2832
+ Component2,
2833
+ {
2834
+ ref,
2835
+ className,
2836
+ style: effectiveStyle,
2837
+ onClick: handleClick,
2838
+ ...editorAttrs,
2839
+ ...rest,
2840
+ children: display
2841
+ }
2842
+ );
2843
+ }
2844
+ );
2845
+ var EMPTY_GIF = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
2846
+ var EditableImage = react.forwardRef(
2847
+ function EditableImage2({
2848
+ sectionId,
2849
+ blockId,
2850
+ settingId,
2851
+ src,
2852
+ alt,
2853
+ emptyPlaceholderSrc,
2854
+ className,
2855
+ onClick,
2856
+ style,
2857
+ ...rest
2858
+ }, ref) {
2859
+ const isEditor = useIsEditor();
2860
+ const isEmpty = !src;
2861
+ const handleClick = react.useCallback(
2862
+ (e) => {
2863
+ onClick?.(e);
2864
+ if (!isEditor) return;
2865
+ e.stopPropagation();
2866
+ postFieldSelected(sectionId, settingId, blockId);
2867
+ },
2868
+ [isEditor, onClick, sectionId, settingId, blockId]
2869
+ );
2870
+ const editorAttrs = react.useMemo(
2871
+ () => isEditor ? {
2872
+ "data-numu-editable": "image",
2873
+ "data-numu-section-id": sectionId,
2874
+ "data-numu-setting-id": settingId,
2875
+ "data-numu-block-id": blockId ?? void 0,
2876
+ role: "button",
2877
+ tabIndex: 0
2878
+ } : {},
2879
+ [isEditor, sectionId, settingId, blockId]
2880
+ );
2881
+ const effectiveStyle = isEditor ? {
2882
+ ...style,
2883
+ cursor: "pointer",
2884
+ outline: isEmpty ? "2px dashed rgba(99, 102, 241, 0.6)" : "2px dashed transparent",
2885
+ outlineOffset: "-2px"
2886
+ } : style;
2887
+ const effectiveSrc = src || emptyPlaceholderSrc || EMPTY_GIF;
2888
+ const effectiveAlt = alt ?? "";
2889
+ return /* @__PURE__ */ jsxRuntime.jsx(
2890
+ "img",
2891
+ {
2892
+ ref,
2893
+ src: effectiveSrc,
2894
+ alt: effectiveAlt,
2895
+ className,
2896
+ style: effectiveStyle,
2897
+ onClick: handleClick,
2898
+ ...editorAttrs,
2899
+ ...rest
2900
+ }
2901
+ );
2902
+ }
2903
+ );
2904
+ var ICON_DEFS = {
2905
+ star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
2906
+ heart: '<path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.29 1.51 4.04 3 5.5l7 7Z"/>',
2907
+ check: '<path d="M20 6 9 17l-5-5"/>',
2908
+ "check-circle": '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
2909
+ shield: '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/>',
2910
+ "shield-check": '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="m9 12 2 2 4-4"/>',
2911
+ truck: '<path d="M10 17h4V5H2v12h3"/><path d="M20 17h2v-3.34a4 4 0 0 0-1.17-2.83L19 9h-5v8h1"/><circle cx="7.5" cy="17.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/>',
2912
+ "shopping-bag": '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4Z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
2913
+ "shopping-cart": '<circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/>',
2914
+ gift: '<rect x="3" y="8" width="18" height="4" rx="1"/><path d="M12 8v13"/><path d="M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7"/><path d="M7.5 8a2.5 2.5 0 0 1 0-5C9 3 10.5 4.5 12 8c1.5-3.5 3-5 4.5-5a2.5 2.5 0 0 1 0 5"/>',
2915
+ tag: '<path d="M20.59 13.41 13.42 20.6a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z"/><circle cx="7" cy="7" r="1"/>',
2916
+ zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
2917
+ sparkles: '<path d="M12 3 13.9 9.2 20 11l-6.1 1.8L12 19l-1.9-6.2L4 11l6.1-1.8L12 3Z"/><path d="M5 3v4"/><path d="M3 5h4"/><path d="M19 17v4"/><path d="M17 19h4"/>',
2918
+ flame: '<path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.07-2.14-.22-4.05 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.15.43-2.29 1-3a2.5 2.5 0 0 0 2.5 2.5Z"/>',
2919
+ leaf: '<path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"/><path d="M2 21c0-3 1.85-5.36 5.08-6"/>',
2920
+ globe: '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10Z"/>',
2921
+ "map-pin": '<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>',
2922
+ phone: '<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92Z"/>',
2923
+ mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
2924
+ clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
2925
+ lock: '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
2926
+ headphones: '<path d="M3 14h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a9 9 0 0 1 18 0v7a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3"/>',
2927
+ percent: '<line x1="19" x2="5" y1="5" y2="19"/><circle cx="6.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="17.5" r="2.5"/>',
2928
+ users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
2929
+ home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>',
2930
+ store: '<path d="M2 7l2-4h16l2 4"/><path d="M4 7v13a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V7"/><path d="M2 7h20"/><path d="M9 21v-6h6v6"/>',
2931
+ bell: '<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/>',
2932
+ camera: '<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/>',
2933
+ image: '<rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.09-3.09a2 2 0 0 0-2.82 0L6 21"/>',
2934
+ smile: '<circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/>',
2935
+ send: '<path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/>',
2936
+ "message-circle": '<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5Z"/>',
2937
+ search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
2938
+ package: '<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>',
2939
+ award: '<circle cx="12" cy="8" r="6"/><path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11"/>',
2940
+ coffee: '<path d="M10 2v2"/><path d="M14 2v2"/><path d="M6 2v2"/><path d="M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1"/>'
2941
+ };
2942
+ var ICON_NAMES = Object.keys(ICON_DEFS);
2943
+ var IconMap = ICON_DEFS;
2944
+ var FALLBACK = ICON_DEFS["check-circle"];
2945
+ function Icon({
2946
+ name,
2947
+ size = 24,
2948
+ strokeWidth = 2,
2949
+ className,
2950
+ title
2951
+ }) {
2952
+ const inner = ICON_DEFS[name] ?? FALLBACK;
2953
+ return /* @__PURE__ */ jsxRuntime.jsx(
2954
+ "svg",
2955
+ {
2956
+ width: size,
2957
+ height: size,
2958
+ viewBox: "0 0 24 24",
2959
+ fill: "none",
2960
+ stroke: "currentColor",
2961
+ strokeWidth,
2962
+ strokeLinecap: "round",
2963
+ strokeLinejoin: "round",
2964
+ className,
2965
+ role: title ? "img" : void 0,
2966
+ "aria-hidden": title ? void 0 : true,
2967
+ "aria-label": title,
2968
+ dangerouslySetInnerHTML: { __html: inner }
2969
+ }
2970
+ );
2971
+ }
2378
2972
 
2379
2973
  // src/utils/normalize.ts
2380
2974
  var DEFAULT_THEME_ID = typeof process !== "undefined" && process.env?.NUMU_DEFAULT_THEME_ID || "modern";
@@ -2468,22 +3062,11 @@ function resolveThemeSettings(raw) {
2468
3062
  }
2469
3063
 
2470
3064
  // src/utils/federation.ts
2471
- var SDK_SYMBOL_KEY = "__NUMU_SDK_SLOT__";
2472
- var REACT_SYMBOL_KEY = "__NUMU_REACT_SLOT__";
2473
- function globalSlot(name, slotKey) {
2474
- const g = globalThis;
2475
- let sym = g[slotKey];
2476
- if (!sym) {
2477
- sym = Symbol.for(name);
2478
- g[slotKey] = sym;
2479
- }
2480
- return sym;
2481
- }
2482
3065
  function sdkSlot() {
2483
- return globalSlot("@numueg/theme-sdk:singleton", SDK_SYMBOL_KEY);
3066
+ return /* @__PURE__ */ Symbol.for("@numueg/theme-sdk:singleton");
2484
3067
  }
2485
3068
  function reactSlot() {
2486
- return globalSlot("@numueg/theme-sdk:react", REACT_SYMBOL_KEY);
3069
+ return /* @__PURE__ */ Symbol.for("@numueg/theme-sdk:react");
2487
3070
  }
2488
3071
  function registerSdkSingleton(sdk) {
2489
3072
  if (typeof globalThis === "undefined") return;
@@ -2493,6 +3076,10 @@ function getSdkSingleton() {
2493
3076
  if (typeof globalThis === "undefined") return null;
2494
3077
  return globalThis[sdkSlot()] ?? null;
2495
3078
  }
3079
+ function clearSdkSingleton() {
3080
+ if (typeof globalThis === "undefined") return;
3081
+ delete globalThis[sdkSlot()];
3082
+ }
2496
3083
  function registerReactSingleton(react, reactDom) {
2497
3084
  if (typeof globalThis === "undefined") return;
2498
3085
  globalThis[reactSlot()] = {
@@ -2508,6 +3095,121 @@ function isSdkAvailable() {
2508
3095
  return getSdkSingleton() !== null;
2509
3096
  }
2510
3097
 
3098
+ // src/utils/dynamicSources.ts
3099
+ function isDynamicSource(value) {
3100
+ return typeof value === "object" && value !== null && typeof value.__numu_source === "string";
3101
+ }
3102
+ function dynamicSource(path) {
3103
+ return { __numu_source: path };
3104
+ }
3105
+ function resolveSourcePath(path, ctx) {
3106
+ const [root, ...rest] = path.split(".");
3107
+ switch (root) {
3108
+ case "product": {
3109
+ const p = ctx.product;
3110
+ if (!p) return null;
3111
+ return resolveProductField(p, rest.join("."));
3112
+ }
3113
+ case "collection": {
3114
+ const c = ctx.collection;
3115
+ if (!c) return null;
3116
+ return resolveCollectionField(c, rest.join("."));
3117
+ }
3118
+ case "store": {
3119
+ const s = ctx.store;
3120
+ if (!s) return null;
3121
+ return resolveStoreField(s, rest.join("."));
3122
+ }
3123
+ default:
3124
+ return null;
3125
+ }
3126
+ }
3127
+ function resolveProductField(p, field) {
3128
+ switch (field) {
3129
+ case "title":
3130
+ case "name":
3131
+ return p.name;
3132
+ case "description":
3133
+ return p.description ?? "";
3134
+ case "description_snippet":
3135
+ return (p.description ?? "").replace(/<[^>]+>/g, " ").trim().slice(0, 200);
3136
+ case "price":
3137
+ return p.price;
3138
+ case "sku":
3139
+ return p.variants?.[0]?.sku ?? null;
3140
+ case "image":
3141
+ case "first_image_url":
3142
+ return p.images?.[0]?.url ?? null;
3143
+ case "slug":
3144
+ return p.slug;
3145
+ default:
3146
+ return null;
3147
+ }
3148
+ }
3149
+ function resolveCollectionField(c, field) {
3150
+ switch (field) {
3151
+ case "title":
3152
+ case "name":
3153
+ return c.name;
3154
+ case "description":
3155
+ return c.description ?? "";
3156
+ case "description_snippet":
3157
+ return (c.description ?? "").replace(/<[^>]+>/g, " ").trim().slice(0, 200);
3158
+ case "image":
3159
+ return c.image_url ?? null;
3160
+ case "slug":
3161
+ return c.slug;
3162
+ case "product_count":
3163
+ return c.product_count ?? null;
3164
+ default:
3165
+ return null;
3166
+ }
3167
+ }
3168
+ function resolveStoreField(s, field) {
3169
+ switch (field) {
3170
+ case "name":
3171
+ return s.name ?? "";
3172
+ case "description":
3173
+ return s.description ?? "";
3174
+ case "logo":
3175
+ return s.logo_url ?? null;
3176
+ default:
3177
+ return null;
3178
+ }
3179
+ }
3180
+ function resolveDynamicValue(value, ctx) {
3181
+ if (!isDynamicSource(value)) return value;
3182
+ return resolveSourcePath(value.__numu_source, ctx);
3183
+ }
3184
+ function resolveSettingsMap(settings, ctx) {
3185
+ if (!settings || typeof settings !== "object") return settings;
3186
+ const input = settings;
3187
+ const out = {};
3188
+ for (const key of Object.keys(input)) {
3189
+ const v = input[key];
3190
+ if (isDynamicSource(v)) {
3191
+ const resolved = resolveSourcePath(v.__numu_source, ctx);
3192
+ out[key] = resolved;
3193
+ } else {
3194
+ out[key] = v;
3195
+ }
3196
+ }
3197
+ return out;
3198
+ }
3199
+ function useResolvedSettings(instance) {
3200
+ const product = react.useContext(ProductContext);
3201
+ const collection = react.useContext(CollectionContext);
3202
+ const store = react.useContext(ShopContext);
3203
+ const ctx = react.useMemo(
3204
+ () => ({ product, collection, store }),
3205
+ [product, collection, store]
3206
+ );
3207
+ return react.useMemo(() => {
3208
+ const settings = instance?.settings ?? {};
3209
+ return resolveSettingsMap(settings, ctx);
3210
+ }, [instance, ctx]);
3211
+ }
3212
+
2511
3213
  // src/utils/defineSection.ts
2512
3214
  var SECTION_MARKER = /* @__PURE__ */ Symbol.for("numu.theme.section");
2513
3215
  var BLOCK_MARKER = /* @__PURE__ */ Symbol.for("numu.theme.block");
@@ -2674,12 +3376,19 @@ exports.CollectionContext = CollectionContext;
2674
3376
  exports.CollectionProvider = CollectionProvider;
2675
3377
  exports.CurrencySwitcher = CurrencySwitcher;
2676
3378
  exports.CustomerContext = CustomerContext;
3379
+ exports.EditableImage = EditableImage;
3380
+ exports.EditableText = EditableText;
2677
3381
  exports.Form = Form;
3382
+ exports.ICON_NAMES = ICON_NAMES;
3383
+ exports.Icon = Icon;
3384
+ exports.IconMap = IconMap;
2678
3385
  exports.Image = Image;
2679
3386
  exports.Link = Link;
2680
3387
  exports.LocaleSwitcher = LocaleSwitcher;
2681
3388
  exports.LocalizationContext = LocalizationContext;
3389
+ exports.MAX_BLOCK_DEPTH = MAX_BLOCK_DEPTH;
2682
3390
  exports.Money = Money;
3391
+ exports.NavigationContext = NavigationContext;
2683
3392
  exports.NuMuProvider = NuMuProvider;
2684
3393
  exports.PageContext = PageContext;
2685
3394
  exports.ProductCard = ProductCard;
@@ -2690,24 +3399,33 @@ exports.Section = Section;
2690
3399
  exports.SectionContext = SectionContext;
2691
3400
  exports.ShopContext = ShopContext;
2692
3401
  exports.ThemeSettingsContext = ThemeSettingsContext;
3402
+ exports.applyGlobalStyleTokens = applyGlobalStyleTokens;
2693
3403
  exports.assetUrl = assetUrl;
2694
3404
  exports.availableValues = availableValues;
2695
3405
  exports.buildLocaleBundle = buildLocaleBundle;
3406
+ exports.clearSdkSingleton = clearSdkSingleton;
2696
3407
  exports.collectBlocks = collectBlocks;
2697
3408
  exports.collectSections = collectSections;
2698
3409
  exports.defaultVariant = defaultVariant;
2699
3410
  exports.defineBlock = defineBlock;
2700
3411
  exports.defineSection = defineSection;
3412
+ exports.dynamicSource = dynamicSource;
2701
3413
  exports.findVariantByOptions = findVariantByOptions;
2702
3414
  exports.flattenMessages = flattenMessages;
2703
3415
  exports.getReactSingleton = getReactSingleton;
2704
3416
  exports.getSdkSingleton = getSdkSingleton;
2705
3417
  exports.isDefinedBlock = isDefinedBlock;
2706
3418
  exports.isDefinedSection = isDefinedSection;
3419
+ exports.isDynamicSource = isDynamicSource;
2707
3420
  exports.isSdkAvailable = isSdkAvailable;
3421
+ exports.mountTheme = mountTheme;
2708
3422
  exports.pickTranslations = pickTranslations;
2709
3423
  exports.registerReactSingleton = registerReactSingleton;
2710
3424
  exports.registerSdkSingleton = registerSdkSingleton;
3425
+ exports.resolveDynamicValue = resolveDynamicValue;
3426
+ exports.resolveFontStack = resolveFontStack;
3427
+ exports.resolveSettingsMap = resolveSettingsMap;
3428
+ exports.resolveSourcePath = resolveSourcePath;
2711
3429
  exports.resolveThemeSettings = resolveThemeSettings;
2712
3430
  exports.sanitizeHtml = sanitizeHtml;
2713
3431
  exports.useAnalytics = useAnalytics;
@@ -2718,6 +3436,7 @@ exports.useCollection = useCollection;
2718
3436
  exports.useCollectionOptional = useCollectionOptional;
2719
3437
  exports.useCollections = useCollections;
2720
3438
  exports.useCurrency = useCurrency;
3439
+ exports.useCurrentTemplate = useCurrentTemplate;
2721
3440
  exports.useCustomer = useCustomer;
2722
3441
  exports.useCustomerActions = useCustomerActions;
2723
3442
  exports.useCustomerAddresses = useCustomerAddresses;
@@ -2738,6 +3457,7 @@ exports.useProductOptional = useProductOptional;
2738
3457
  exports.useProducts = useProducts;
2739
3458
  exports.useRelatedProducts = useRelatedProducts;
2740
3459
  exports.useReorder = useReorder;
3460
+ exports.useResolvedSettings = useResolvedSettings;
2741
3461
  exports.useSearch = useSearch;
2742
3462
  exports.useSection = useSection;
2743
3463
  exports.useSectionOptional = useSectionOptional;