@numueg/theme-sdk 0.6.1 → 0.10.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/CHANGELOG.md +144 -0
- package/dist/chunk-4TMO5Y5W.mjs +183 -0
- package/dist/chunk-4TMO5Y5W.mjs.map +1 -0
- package/dist/chunk-EB2ESBLD.mjs +397 -0
- package/dist/chunk-EB2ESBLD.mjs.map +1 -0
- package/dist/chunk-IBNAZRUT.cjs +207 -0
- package/dist/chunk-IBNAZRUT.cjs.map +1 -0
- package/dist/chunk-KWODVI7F.cjs +408 -0
- package/dist/chunk-KWODVI7F.cjs.map +1 -0
- package/dist/chunk-TBSNHHFH.cjs +96 -0
- package/dist/chunk-TBSNHHFH.cjs.map +1 -0
- package/dist/chunk-XF2FGIVS.mjs +94 -0
- package/dist/chunk-XF2FGIVS.mjs.map +1 -0
- package/dist/{entities-6MGANln7.d.mts → entities-B8378GKp.d.mts} +12 -0
- package/dist/{entities-6MGANln7.d.ts → entities-B8378GKp.d.ts} +12 -0
- package/dist/index.cjs +693 -968
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +286 -94
- package/dist/index.d.ts +286 -94
- package/dist/index.mjs +531 -911
- package/dist/index.mjs.map +1 -1
- package/dist/{mount-BGumg1JM.d.mts → mount-HbnOAYZ-.d.ts} +90 -14
- package/dist/{mount-Bt-4ken5.d.ts → mount-eAhGgGtZ.d.mts} +90 -14
- package/dist/normalize.cjs +7 -91
- package/dist/normalize.cjs.map +1 -1
- package/dist/normalize.d.mts +1 -1
- package/dist/normalize.d.ts +1 -1
- package/dist/normalize.mjs +1 -92
- package/dist/normalize.mjs.map +1 -1
- package/dist/{theme-D8MOopvi.d.mts → theme-CNTB4KnU.d.mts} +29 -3
- package/dist/{theme-D8MOopvi.d.ts → theme-CNTB4KnU.d.ts} +29 -3
- package/dist/types.d.mts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/v2-compat.cjs +6 -163
- package/dist/v2-compat.cjs.map +1 -1
- package/dist/v2-compat.d.mts +1 -1
- package/dist/v2-compat.d.ts +1 -1
- package/dist/v2-compat.mjs +2 -159
- package/dist/v2-compat.mjs.map +1 -1
- package/dist/validation.cjs +43 -403
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.mts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.mjs +1 -395
- package/dist/validation.mjs.map +1 -1
- package/dist/verify.cjs +6 -21
- package/dist/verify.cjs.map +1 -1
- package/dist/verify.d.mts +3 -3
- package/dist/verify.d.ts +3 -3
- package/dist/verify.mjs +3 -20
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { i as Store, e as Product, b as Collection,
|
|
2
|
-
export { A as Address, a as CartItem, O as Order, d as OrderItem, f as ProductImage, j as ProductOption, h as SizeChartMode } from './entities-
|
|
3
|
-
import { T as ThemeSettingsV3, c as SectionInstance, M as MountResult, B as BlockInstance, b as BlockSchema, a as BlockProps$1, f as SectionSchema, e as SectionProps$1 } from './theme-
|
|
4
|
-
export { E as ExternalThemeMetadata, h as MAX_BLOCK_DEPTH, P as PageTemplate, i as PresetBlock, S as SectionGroup, d as SectionPreset, g as SettingDefinition } from './theme-
|
|
1
|
+
import { i as Store, e as Product, b as Collection, c as Customer, P as Page, S as SizeChart, g as ProductVariant, C as Cart } from './entities-B8378GKp.mjs';
|
|
2
|
+
export { A as Address, a as CartItem, O as Order, d as OrderItem, f as ProductImage, j as ProductOption, h as SizeChartMode } from './entities-B8378GKp.mjs';
|
|
3
|
+
import { T as ThemeSettingsV3, c as SectionInstance, M as MountResult, B as BlockInstance, b as BlockSchema, a as BlockProps$1, f as SectionSchema, e as SectionProps$1 } from './theme-CNTB4KnU.mjs';
|
|
4
|
+
export { E as ExternalThemeMetadata, h as MAX_BLOCK_DEPTH, P as PageTemplate, i as PresetBlock, S as SectionGroup, d as SectionPreset, g as SettingDefinition } from './theme-CNTB4KnU.mjs';
|
|
5
5
|
export { KNOWN_SETTING_TYPES, KNOWN_TEMPLATES, REQUIRED_TEMPLATES, SDK_VERSION, THEME_CONTRACT_VERSION, ValidationIssue, ValidationResult, mergeResults, validateBuiltManifest, validateManifest, validateSectionSchema, validateSettingsAgainstSchema } from './validation.mjs';
|
|
6
|
-
import { L as LocalizationState, T as ThemeMountContext,
|
|
7
|
-
export {
|
|
6
|
+
import { C as CartContextValue, L as LocalizationState, a as CurrencyState, T as ThemeMountContext, b as ThemeRenderArgs, M as MenuItemData } from './mount-eAhGgGtZ.mjs';
|
|
7
|
+
export { c as CartContext, d as CartMutationResult, e as CollectionContext, f as CurrencyConfig, g as CurrencyContext, h as CustomerContext, i as LocalizationContext, N as NavigationContext, P as PageContext, j as ProductContext, S as ShopContext, k as ThemeMountPage, l as ThemeSettingsContext, m as buildThemeElement, n as mountTheme } from './mount-eAhGgGtZ.mjs';
|
|
8
8
|
import * as react from 'react';
|
|
9
9
|
import { ReactElement, ReactNode, ElementType, CSSProperties, ImgHTMLAttributes, AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, FormHTMLAttributes, ComponentType } from 'react';
|
|
10
10
|
export { resolveThemeSettings } from './normalize.mjs';
|
|
@@ -43,17 +43,7 @@ declare function useProductOptional(): Product | null;
|
|
|
43
43
|
declare function useCollection(): Collection;
|
|
44
44
|
declare function useCollectionOptional(): Collection | null;
|
|
45
45
|
|
|
46
|
-
declare function useCart():
|
|
47
|
-
cart: Cart;
|
|
48
|
-
addItem: (productId: string, variantId?: string, quantity?: number) => Promise<void>;
|
|
49
|
-
removeItem: (itemId: string) => Promise<void>;
|
|
50
|
-
updateQuantity: (itemId: string, quantity: number) => Promise<void>;
|
|
51
|
-
applyDiscount: (code: string) => Promise<void>;
|
|
52
|
-
removeDiscount: () => Promise<void>;
|
|
53
|
-
updateNote: (note: string) => Promise<void>;
|
|
54
|
-
clearCart: () => Promise<void>;
|
|
55
|
-
loading: boolean;
|
|
56
|
-
};
|
|
46
|
+
declare function useCart(): CartContextValue;
|
|
57
47
|
|
|
58
48
|
declare function useCustomer(): Customer | null;
|
|
59
49
|
|
|
@@ -136,14 +126,67 @@ declare function useSection(): SectionInstance;
|
|
|
136
126
|
declare function useSectionOptional(): SectionInstance | null;
|
|
137
127
|
|
|
138
128
|
/**
|
|
139
|
-
*
|
|
140
|
-
* the
|
|
129
|
+
* A section instance from a section group, carrying its own `id` (the key it
|
|
130
|
+
* had inside the group's `sections` map). The raw `SectionInstance` drops the
|
|
131
|
+
* id — but a theme needs it to key its React list, wire `<Section id={…}>`
|
|
132
|
+
* click-to-select, and look the type up in its own registry — so
|
|
133
|
+
* `useSectionGroup` re-attaches it here.
|
|
134
|
+
*/
|
|
135
|
+
interface SectionGroupInstance extends SectionInstance {
|
|
136
|
+
/** The instance's key within the group's `sections` map. */
|
|
137
|
+
id: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* useSectionGroup — read the ordered section instances for a named section
|
|
141
|
+
* group ("header", "footer", or any custom global group) out of
|
|
142
|
+
* `themeSettings.section_groups[group]`.
|
|
143
|
+
*
|
|
144
|
+
* This is the SDK side of "global sections shared across pages": the host
|
|
145
|
+
* ships one `section_groups` map on every page, and a theme renders a given
|
|
146
|
+
* group in its layout (header/footer) or wherever it wants a page-independent
|
|
147
|
+
* section list. The returned array is in the group's `order`, and each entry
|
|
148
|
+
* carries its `id` so the theme can key + select + dispatch on it:
|
|
149
|
+
*
|
|
150
|
+
* ```tsx
|
|
151
|
+
* const headerSections = useSectionGroup("header");
|
|
152
|
+
* return headerSections.map(({ id, type, settings }) => {
|
|
153
|
+
* const Comp = registry[type]?.render; // theme owns the registry
|
|
154
|
+
* return Comp ? (
|
|
155
|
+
* <Section key={id} id={id} type={type} groupId="header">
|
|
156
|
+
* <Comp settings={settings} />
|
|
157
|
+
* </Section>
|
|
158
|
+
* ) : null;
|
|
159
|
+
* });
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* Pure data hook — it does NOT map types to components. The SDK has no section
|
|
163
|
+
* registry (a theme builds its own via `collectSections`), so there is no
|
|
164
|
+
* generic `<GlobalSections>` renderer to ship: the theme walks the returned
|
|
165
|
+
* instances through its own registry, exactly as it already does for template
|
|
166
|
+
* sections. Returns `[]` (a stable reference) when there is no provider, no
|
|
167
|
+
* `section_groups`, the named group is absent, or its `order` is empty.
|
|
168
|
+
*
|
|
169
|
+
* Disabled instances are INCLUDED (a `disabled` flag is preserved on each
|
|
170
|
+
* entry); filter with `.filter((s) => !s.disabled)` if the theme hides them.
|
|
171
|
+
* Ids listed in `order` but missing from `sections` are skipped defensively.
|
|
172
|
+
*/
|
|
173
|
+
declare function useSectionGroup(group: string): SectionGroupInstance[];
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* useMoney — convenience hook that returns a formatter bound to the active
|
|
177
|
+
* store's currency.
|
|
141
178
|
*
|
|
142
179
|
* Useful when a section needs to format multiple amounts in a render:
|
|
143
180
|
*
|
|
144
181
|
* const money = useMoney();
|
|
145
182
|
* return <td>{money(item.price * item.quantity)}</td>
|
|
146
183
|
*
|
|
184
|
+
* Multi-currency: like <Money>, when the store has `auto_convert` on and no
|
|
185
|
+
* `currencyOverride` is passed, amounts are converted to and formatted in the
|
|
186
|
+
* visitor's SELECTED presentment currency (shared via `useCurrency()`), so a
|
|
187
|
+
* `<CurrencySwitcher>` change reflows these too. Pass a `currencyOverride` to
|
|
188
|
+
* pin a specific currency and skip conversion.
|
|
189
|
+
*
|
|
147
190
|
* For one-off price displays prefer the <Money> component which handles
|
|
148
191
|
* compare-at and inline rendering.
|
|
149
192
|
*/
|
|
@@ -579,10 +622,19 @@ declare function useAnalytics(): AnalyticsApi;
|
|
|
579
622
|
* `{ available: false }` rather than as a network error — themes
|
|
580
623
|
* branch on availability without try/catch.
|
|
581
624
|
*
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
*
|
|
625
|
+
* Phase 3 (client-data layer): the fetch now runs through the shared
|
|
626
|
+
* `useCachedResource` store keyed by `numu:app:<store_id>:<slug>`. Two upgrades
|
|
627
|
+
* over the old per-instance `useState` + fetch:
|
|
628
|
+
* - DEDUP + SYNC: N consumers of the same app slug share ONE request and one
|
|
629
|
+
* result, instead of each firing its own `/apps/{slug}` fetch.
|
|
630
|
+
* - CANCELLATION / ORDERING: the fetcher receives an AbortSignal and the
|
|
631
|
+
* cache seq-guards results, so a slow response for a superseded slug (or a
|
|
632
|
+
* superseded `refresh()`) can no longer apply stale state over a newer one
|
|
633
|
+
* — the audited "out-of-order responses apply stale state" bug.
|
|
634
|
+
*
|
|
635
|
+
* `loading` reflects the FIRST load only; a `refresh()` revalidates in the
|
|
636
|
+
* background (`isValidating`) while keeping the last-good data visible, rather
|
|
637
|
+
* than blanking to a skeleton on every manual refresh.
|
|
586
638
|
*/
|
|
587
639
|
interface AppManifestBlock {
|
|
588
640
|
type: string;
|
|
@@ -628,8 +680,17 @@ declare function useApp<T = unknown>(slug: string): AppState<T>;
|
|
|
628
680
|
* v1 implementation: localStorage only. The /api/customer/me/wishlist
|
|
629
681
|
* endpoint isn't wired yet; an authed visitor still gets the local
|
|
630
682
|
* fallback so themes work end-to-end. When the endpoint lands, the
|
|
631
|
-
*
|
|
632
|
-
*
|
|
683
|
+
* fetcher below swaps its localStorage read for the server fetch and the
|
|
684
|
+
* mutation methods gain a `{ revalidate: true }` write-through.
|
|
685
|
+
*
|
|
686
|
+
* Phase 3 (client-data layer): the items list now lives in the shared
|
|
687
|
+
* `useCachedResource` store keyed by `numu_wishlist_<store_id>`, NOT in
|
|
688
|
+
* per-instance `useState`. Previously two `<Heart>`s for the same product
|
|
689
|
+
* each held their own copy, so adding via one never re-rendered the other —
|
|
690
|
+
* the two hearts DESYNCED. Now every `useWishlist(storeId)` consumer reads
|
|
691
|
+
* and writes ONE shared entry, so an add/remove anywhere reflows every heart.
|
|
692
|
+
* Writes are optimistic (the shared store updates instantly) and roll back if
|
|
693
|
+
* persistence throws.
|
|
633
694
|
*/
|
|
634
695
|
interface WishlistItem {
|
|
635
696
|
product_id: string;
|
|
@@ -661,11 +722,17 @@ declare function useWishlist(storeId: string): WishlistState;
|
|
|
661
722
|
* together"; v1 ships the simpler same-category-minus-self heuristic.
|
|
662
723
|
*
|
|
663
724
|
* Backend contract: GET /api/storefront/products/{id}/related?limit=N
|
|
664
|
-
* → { items: Product[] } | Product[]
|
|
725
|
+
* → { items: Product[] } | { data: Product[] } | Product[]
|
|
665
726
|
*
|
|
666
727
|
* Returns an empty list (no error) when the endpoint is missing or the
|
|
667
728
|
* product has no siblings — themes should branch on `items.length` and
|
|
668
729
|
* either render the section or skip it entirely.
|
|
730
|
+
*
|
|
731
|
+
* Phase 3 (client-data layer): routed through the shared `useCachedResource`
|
|
732
|
+
* store keyed by `numu:related:<productId>:<limit>`. Two "related products"
|
|
733
|
+
* sections on the same PDP now share ONE request + one result instead of each
|
|
734
|
+
* refetching, and the AbortSignal + cache seq-guard drop a superseded response
|
|
735
|
+
* (e.g. after a rapid product switch) so it can't overwrite the newer list.
|
|
669
736
|
*/
|
|
670
737
|
interface RelatedProductsState {
|
|
671
738
|
items: Product[];
|
|
@@ -676,6 +743,103 @@ declare function useRelatedProducts(productId: string | null | undefined, option
|
|
|
676
743
|
limit?: number;
|
|
677
744
|
}): RelatedProductsState;
|
|
678
745
|
|
|
746
|
+
/**
|
|
747
|
+
* Shared client-data layer (Phase 3) — a tiny SWR-style cache with ZERO
|
|
748
|
+
* runtime dependencies.
|
|
749
|
+
*
|
|
750
|
+
* Why this exists (from the audit): several hooks fetched + held per-instance
|
|
751
|
+
* state, so N components rendering the same resource fired N identical requests
|
|
752
|
+
* AND drifted out of sync (e.g. two wishlist hearts for one product; `useApp`
|
|
753
|
+
* applying an out-of-order response over a newer one). `useCachedResource`
|
|
754
|
+
* centralizes fetching behind one module-level store keyed by a string:
|
|
755
|
+
*
|
|
756
|
+
* (a) DEDUP — the first consumer of a key starts the fetch and stashes the
|
|
757
|
+
* in-flight promise on the entry; every other consumer that revalidates
|
|
758
|
+
* the same key while it's pending joins that promise instead of firing a
|
|
759
|
+
* second request. N consumers → ONE network call.
|
|
760
|
+
* (b) CACHE — results live on the entry keyed by string, so a later mount
|
|
761
|
+
* reads the cached value immediately (and skips refetch within
|
|
762
|
+
* `dedupeIntervalMs`).
|
|
763
|
+
* (c) SUBSCRIBE / NOTIFY — every hook instance subscribes to its key via
|
|
764
|
+
* `useSyncExternalStore`. Any update (fetch resolve, `mutate`) rebuilds
|
|
765
|
+
* the entry's immutable snapshot and notifies ALL subscribers, so every
|
|
766
|
+
* instance re-renders with the same value — cross-instance state sync.
|
|
767
|
+
* (d) REVALIDATE + CANCELLATION — each fetch reserves a monotonic sequence
|
|
768
|
+
* number and an `AbortController`. Starting a new (forced) fetch aborts
|
|
769
|
+
* the previous one; when any fetch settles, its result is applied ONLY if
|
|
770
|
+
* its sequence is still the latest (`mySeq === entry.seq`). So a slow,
|
|
771
|
+
* superseded response can never overwrite a newer result regardless of
|
|
772
|
+
* the order the network resolves them in.
|
|
773
|
+
*
|
|
774
|
+
* SSR-safe: the fetch is triggered from `useEffect` (never runs under
|
|
775
|
+
* `renderToString`), and `useSyncExternalStore`'s server snapshot returns the
|
|
776
|
+
* `initialData` fallback without ever touching the module cache — so nothing is
|
|
777
|
+
* written to the shared map on the server and there is no cross-request bleed.
|
|
778
|
+
*
|
|
779
|
+
* This is intentionally NOT a full SWR clone (no focus/reconnect revalidation,
|
|
780
|
+
* no suspense, no infinite pagination). It's the smallest primitive that fixes
|
|
781
|
+
* the audited dedup + sync + ordering bugs and that themes can reuse.
|
|
782
|
+
*/
|
|
783
|
+
/** The reactive slice a consumer reads. */
|
|
784
|
+
interface CachedResourceState<T> {
|
|
785
|
+
/** Latest cached value, or `initialData` before the first fetch settles. */
|
|
786
|
+
data: T | undefined;
|
|
787
|
+
/** Error from the most recent settled fetch (cleared on success / mutate). */
|
|
788
|
+
error: Error | undefined;
|
|
789
|
+
/** True until the first fetch (or `mutate`) settles a value for this key. */
|
|
790
|
+
isLoading: boolean;
|
|
791
|
+
/** True whenever a fetch is currently in flight (initial OR background). */
|
|
792
|
+
isValidating: boolean;
|
|
793
|
+
}
|
|
794
|
+
/** Fetcher receives an AbortSignal so a superseding fetch can cancel it. */
|
|
795
|
+
type CacheFetcher<T> = (signal: AbortSignal) => Promise<T>;
|
|
796
|
+
/** `mutate` accepts a value or an updater `(current) => next`. */
|
|
797
|
+
type CacheMutator<T> = T | undefined | ((current: T | undefined) => T | undefined);
|
|
798
|
+
interface MutateOptions {
|
|
799
|
+
/** After applying the local value, kick a fresh (forced) revalidation. */
|
|
800
|
+
revalidate?: boolean;
|
|
801
|
+
}
|
|
802
|
+
interface CachedResource<T> extends CachedResourceState<T> {
|
|
803
|
+
/**
|
|
804
|
+
* Optimistically write the cached value for this key (all subscribers
|
|
805
|
+
* re-render). Pass `{ revalidate: true }` to also refetch afterwards.
|
|
806
|
+
*/
|
|
807
|
+
mutate: (next: CacheMutator<T>, opts?: MutateOptions) => void;
|
|
808
|
+
/** Force a fresh fetch, superseding + aborting any in-flight request. */
|
|
809
|
+
revalidate: () => Promise<void>;
|
|
810
|
+
}
|
|
811
|
+
interface UseCachedResourceOptions<T> {
|
|
812
|
+
/**
|
|
813
|
+
* Value shown on the server and on the first client paint before the fetch
|
|
814
|
+
* resolves. Used as the SSR snapshot (SSR-safe: no fetch on the server).
|
|
815
|
+
*/
|
|
816
|
+
initialData?: T;
|
|
817
|
+
/** When `false`, the hook holds no subscription and never fetches. */
|
|
818
|
+
enabled?: boolean;
|
|
819
|
+
/** Auto-fetch on mount / key change (default `true`). */
|
|
820
|
+
revalidateOnMount?: boolean;
|
|
821
|
+
/**
|
|
822
|
+
* Skip the mount fetch if a value settled within this window (ms). Prevents
|
|
823
|
+
* a staggered second mount from refetching a still-fresh key. Default 2000.
|
|
824
|
+
*/
|
|
825
|
+
dedupeIntervalMs?: number;
|
|
826
|
+
/** Called on a non-abort fetch error, with the key. */
|
|
827
|
+
onError?: (error: Error, key: string) => void;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Subscribe to a cached resource keyed by `key`, deduping the fetch across all
|
|
831
|
+
* instances and keeping them in sync. See the module docblock for the full
|
|
832
|
+
* contract.
|
|
833
|
+
*
|
|
834
|
+
* @param key Cache key. `null`/`undefined`/`""` (or `enabled: false`)
|
|
835
|
+
* disables the hook — no subscription, no fetch, `initialData`
|
|
836
|
+
* is returned.
|
|
837
|
+
* @param fetcher `(signal) => Promise<T>`. Recreated-per-render is fine; the
|
|
838
|
+
* latest is captured in a ref so it doesn't retrigger fetches.
|
|
839
|
+
* @param options See {@link UseCachedResourceOptions}.
|
|
840
|
+
*/
|
|
841
|
+
declare function useCachedResource<T>(key: string | null | undefined, fetcher: CacheFetcher<T>, options?: UseCachedResourceOptions<T>): CachedResource<T>;
|
|
842
|
+
|
|
679
843
|
/**
|
|
680
844
|
* useProductSizeChart — resolve the size chart to show for a product.
|
|
681
845
|
*
|
|
@@ -703,53 +867,6 @@ declare function useProductSizeChart(productOverride?: Product | null): SizeChar
|
|
|
703
867
|
*/
|
|
704
868
|
declare function resolveSizeChart(productAttributes: Record<string, unknown> | undefined, storeSettings: Record<string, unknown> | undefined): SizeChart | null;
|
|
705
869
|
|
|
706
|
-
/**
|
|
707
|
-
* Multi-currency presentment — Phase 6.
|
|
708
|
-
*
|
|
709
|
-
* The store's *capture* currency (what Paymob/Stripe/etc. charges)
|
|
710
|
-
* never changes mid-session. This hook is purely about **display**:
|
|
711
|
-
* letting visitors browse prices in a currency they recognize.
|
|
712
|
-
*
|
|
713
|
-
* Usage:
|
|
714
|
-
*
|
|
715
|
-
* const { base, selected, presentment, convert, setSelected } = useCurrency();
|
|
716
|
-
* <p>{convert(product.price.amount_cents)} {selected}</p>
|
|
717
|
-
* {presentment.length > 1 && (
|
|
718
|
-
* <select value={selected} onChange={(e) => setSelected(e.target.value)}>
|
|
719
|
-
* {presentment.map(c => <option key={c}>{c}</option>)}
|
|
720
|
-
* </select>
|
|
721
|
-
* )}
|
|
722
|
-
*
|
|
723
|
-
* Behavior:
|
|
724
|
-
* - `selected` defaults to the persisted `numu_currency` cookie if
|
|
725
|
-
* valid, else `default_presentment`, else `base`.
|
|
726
|
-
* - `setSelected` writes the cookie (path=/, 30d) so navigation
|
|
727
|
-
* preserves the choice across pages.
|
|
728
|
-
* - `convert(cents)` returns the converted cents in `selected`,
|
|
729
|
-
* using the rates from the API. When no rate exists, returns
|
|
730
|
-
* the input unchanged (theme renders in base — better than a
|
|
731
|
-
* wrong number).
|
|
732
|
-
*
|
|
733
|
-
* Use `<CurrencySwitcher>` from the SDK for an opinionated UI, or
|
|
734
|
-
* read this hook directly for full control.
|
|
735
|
-
*/
|
|
736
|
-
interface CurrencyConfig {
|
|
737
|
-
base: string;
|
|
738
|
-
default_presentment: string;
|
|
739
|
-
presentment: string[];
|
|
740
|
-
rates: Record<string, string>;
|
|
741
|
-
auto_convert: boolean;
|
|
742
|
-
}
|
|
743
|
-
interface CurrencyState {
|
|
744
|
-
base: string;
|
|
745
|
-
selected: string;
|
|
746
|
-
presentment: string[];
|
|
747
|
-
rates: Record<string, number>;
|
|
748
|
-
autoConvert: boolean;
|
|
749
|
-
loading: boolean;
|
|
750
|
-
setSelected: (currency: string) => void;
|
|
751
|
-
convert: (cents: number, target?: string) => number;
|
|
752
|
-
}
|
|
753
870
|
declare function useCurrency(): CurrencyState;
|
|
754
871
|
|
|
755
872
|
/**
|
|
@@ -811,10 +928,30 @@ interface UseVariantSelection {
|
|
|
811
928
|
* Shopify's behavior. Themes that prefer "Choose your size" empty
|
|
812
929
|
* state can pass `autoSelect: false`.
|
|
813
930
|
*/
|
|
814
|
-
declare function useVariantSelection(product: Pick<Product, "options" | "variants"
|
|
931
|
+
declare function useVariantSelection(product: Pick<Product, "options" | "variants"> & {
|
|
932
|
+
id?: string;
|
|
933
|
+
}, opts?: {
|
|
815
934
|
autoSelect?: boolean;
|
|
816
935
|
}): UseVariantSelection;
|
|
817
936
|
|
|
937
|
+
/**
|
|
938
|
+
* Live variant-picker selection registry.
|
|
939
|
+
*
|
|
940
|
+
* `useVariantSelection` publishes its current axis→value map here keyed by
|
|
941
|
+
* product id; `AddToCartButton` reads it back at click time. This is what
|
|
942
|
+
* lets the selection reach the cart even when no real variant row matches —
|
|
943
|
+
* legacy products keep their axes in attributes JSON with a single
|
|
944
|
+
* placeholder variant whose option_values is {}, so `findVariantByOptions`
|
|
945
|
+
* returns null and there'd otherwise be nothing to send.
|
|
946
|
+
*
|
|
947
|
+
* Module-scoped (not React context) on purpose: the hook instance lives in
|
|
948
|
+
* the theme's PDP section while the button may be a sibling — they share no
|
|
949
|
+
* provider of ours. Bounded: one entry per product id, last write wins.
|
|
950
|
+
*/
|
|
951
|
+
declare function publishVariantSelection(productId: string | undefined | null, selection: Record<string, string>): void;
|
|
952
|
+
/** Latest non-empty selection for a product, or null. */
|
|
953
|
+
declare function readVariantSelection(productId: string | undefined | null): Record<string, string> | null;
|
|
954
|
+
|
|
818
955
|
/**
|
|
819
956
|
* Gift card balance check — Phase 8.3.
|
|
820
957
|
*
|
|
@@ -1092,6 +1229,15 @@ interface NuMuProviderProps {
|
|
|
1092
1229
|
* built before the prop existed keep rendering their home template.
|
|
1093
1230
|
*/
|
|
1094
1231
|
currentTemplate?: string;
|
|
1232
|
+
/**
|
|
1233
|
+
* I3 — resolved alternate template key for the current page (e.g.
|
|
1234
|
+
* `"product.wholesale"`) when the storefront routed it to a template
|
|
1235
|
+
* suffix. Published on the synthesized `PageContext` value so themes read
|
|
1236
|
+
* it via `usePage()?.template`. Distinct from `currentTemplate`, which is
|
|
1237
|
+
* the base route type (`"product"`). Omit for pages on their default
|
|
1238
|
+
* template; additive, so themes/hosts predating it are unaffected.
|
|
1239
|
+
*/
|
|
1240
|
+
pageTemplate?: string;
|
|
1095
1241
|
/**
|
|
1096
1242
|
* Pre-fetched product list for the current page. Themes that call
|
|
1097
1243
|
* `useProducts()` will read these from PageContext without needing
|
|
@@ -1117,7 +1263,7 @@ interface NuMuProviderProps {
|
|
|
1117
1263
|
navigation?: Record<string, MenuItemData[]>;
|
|
1118
1264
|
children: ReactNode;
|
|
1119
1265
|
}
|
|
1120
|
-
declare function NuMuProvider({ store, themeSettings, initialCart, customer, locale: initialLocale, translations: initialTranslations, currentTemplate, initialProducts, initialCollections, navigation, children, }: NuMuProviderProps): react.JSX.Element;
|
|
1266
|
+
declare function NuMuProvider({ store, themeSettings, initialCart, customer, locale: initialLocale, translations: initialTranslations, currentTemplate, pageTemplate, initialProducts, initialCollections, navigation, children, }: NuMuProviderProps): react.JSX.Element;
|
|
1121
1267
|
|
|
1122
1268
|
interface ProductProviderProps {
|
|
1123
1269
|
product: Product;
|
|
@@ -1154,6 +1300,13 @@ interface MoneyProps {
|
|
|
1154
1300
|
* Intl.NumberFormat. Renders inline with `dir="auto"` so the digits
|
|
1155
1301
|
* flow naturally in RTL (Arabic) without flipping the currency symbol.
|
|
1156
1302
|
*
|
|
1303
|
+
* Multi-currency: when the store has `auto_convert` on and the caller did NOT
|
|
1304
|
+
* pin an explicit `currency`, the amount is presented in the visitor's
|
|
1305
|
+
* SELECTED currency (converted via the shared rates from `useCurrency()`). A
|
|
1306
|
+
* `<CurrencySwitcher>` change therefore re-renders every `<Money>` on the page
|
|
1307
|
+
* without a reload. An explicit `currency` prop means "this amount is already
|
|
1308
|
+
* in that currency" — it is respected and never converted.
|
|
1309
|
+
*
|
|
1157
1310
|
* Usage:
|
|
1158
1311
|
* <Money amount={product.price} compareAt={product.compare_at_price} />
|
|
1159
1312
|
*/
|
|
@@ -1383,27 +1536,66 @@ interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"
|
|
|
1383
1536
|
to: string;
|
|
1384
1537
|
children: ReactNode;
|
|
1385
1538
|
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Event dispatched on `window` when a Link is clicked and eligible for
|
|
1541
|
+
* client-side (soft) navigation. `detail.href` is the target path.
|
|
1542
|
+
*
|
|
1543
|
+
* Contract: the event is CANCELABLE. A host that can perform soft
|
|
1544
|
+
* navigation (e.g. the Next.js storefront routing through its client
|
|
1545
|
+
* router) listens for it and calls `event.preventDefault()` to claim
|
|
1546
|
+
* the navigation. If no listener claims it, the Link falls back to
|
|
1547
|
+
* default anchor behavior — a normal full-page navigation — so themes
|
|
1548
|
+
* running under hosts without the bridge (older storefronts, the CLI
|
|
1549
|
+
* dev server, static previews) keep working unchanged.
|
|
1550
|
+
*/
|
|
1551
|
+
declare const NAVIGATE_EVENT = "numu:navigate";
|
|
1552
|
+
interface NavigateEventDetail {
|
|
1553
|
+
href: string;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Ask the host to soft-navigate to `href`. Returns true when a host
|
|
1557
|
+
* listener claimed the navigation (the caller should suppress its own
|
|
1558
|
+
* default behavior), false when no handler is present (the caller
|
|
1559
|
+
* should fall back to a full navigation). Exposed for themes that
|
|
1560
|
+
* navigate programmatically (e.g. after a search submit).
|
|
1561
|
+
*/
|
|
1562
|
+
declare function requestNavigate(href: string): boolean;
|
|
1386
1563
|
/**
|
|
1387
1564
|
* Route-aware <Link>. Themes write paths as `/products/<slug>` (matches
|
|
1388
1565
|
* the production subdomain root). The storefront proxy rewrites those
|
|
1389
1566
|
* under `/<subdomain>/...` in dev path-segment routing; in production
|
|
1390
1567
|
* the subdomain hostname does the same job at the edge.
|
|
1391
1568
|
*
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
*
|
|
1400
|
-
|
|
1401
|
-
|
|
1569
|
+
* Navigation is a two-tier contract:
|
|
1570
|
+
* 1. Soft (preferred): on an eligible plain left-click we dispatch a
|
|
1571
|
+
* cancelable NAVIGATE_EVENT. A router-aware host claims it with
|
|
1572
|
+
* preventDefault() and performs a client-side transition — React,
|
|
1573
|
+
* the SDK runtime, and the evaluated theme bundle all stay warm,
|
|
1574
|
+
* so page-to-page moves skip the full document reload + remount.
|
|
1575
|
+
* 2. Hard (fallback): no listener claims the event → default anchor
|
|
1576
|
+
* behavior, a normal full-page navigation. Identical to the
|
|
1577
|
+
* pre-0.10 behavior, so themes never break on hosts without the
|
|
1578
|
+
* bridge.
|
|
1579
|
+
*
|
|
1580
|
+
* Soft navigation is only attempted for storefront-internal paths and
|
|
1581
|
+
* unmodified left-clicks: external URLs (protocol or `//`), hash-only
|
|
1582
|
+
* anchors, modified clicks (ctrl/cmd/shift/alt — "open in new tab"),
|
|
1583
|
+
* non-left buttons, `target` other than `_self`, and `download` links
|
|
1584
|
+
* all keep default browser behavior.
|
|
1585
|
+
*/
|
|
1586
|
+
declare function Link({ to, children, onClick, ...rest }: LinkProps): react.JSX.Element;
|
|
1402
1587
|
|
|
1403
1588
|
interface AddToCartButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onClick" | "disabled"> {
|
|
1404
1589
|
product: Product;
|
|
1405
1590
|
variant?: ProductVariant;
|
|
1406
1591
|
quantity?: number;
|
|
1592
|
+
/**
|
|
1593
|
+
* Picker axes to persist on the cart line ({Color: "Black"}). Optional —
|
|
1594
|
+
* when omitted, the button reads the live useVariantSelection state for
|
|
1595
|
+
* this product from the SDK registry, so existing themes get the variant
|
|
1596
|
+
* label end-to-end (cart → checkout → order → email) with no changes.
|
|
1597
|
+
*/
|
|
1598
|
+
selectedOptions?: Record<string, string>;
|
|
1407
1599
|
/** Custom labels — fallbacks are English defaults. */
|
|
1408
1600
|
label?: ReactNode;
|
|
1409
1601
|
loadingLabel?: ReactNode;
|
|
@@ -1426,7 +1618,7 @@ interface AddToCartButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonEleme
|
|
|
1426
1618
|
* Doesn't trap navigation — for "buy now" flows that should redirect
|
|
1427
1619
|
* to checkout, themes wrap this in their own `<a>` after onAdded.
|
|
1428
1620
|
*/
|
|
1429
|
-
declare function AddToCartButton({ product, variant, quantity, label, loadingLabel, soldOutLabel, errorLabel, onAdded, ...rest }: AddToCartButtonProps): react.JSX.Element;
|
|
1621
|
+
declare function AddToCartButton({ product, variant, quantity, selectedOptions, label, loadingLabel, soldOutLabel, errorLabel, onAdded, ...rest }: AddToCartButtonProps): react.JSX.Element;
|
|
1430
1622
|
|
|
1431
1623
|
interface SectionProps extends HTMLAttributes<HTMLElement> {
|
|
1432
1624
|
/** Section instance id (the order key in templates). Required for the
|
|
@@ -1670,13 +1862,13 @@ interface RichTextProps {
|
|
|
1670
1862
|
* Sanitize an HTML string against the allowlist above. Runs server-
|
|
1671
1863
|
* AND client-side because rich-text fields are server-rendered for SEO.
|
|
1672
1864
|
*
|
|
1673
|
-
* Implementation note:
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
*
|
|
1679
|
-
*
|
|
1865
|
+
* Implementation note: in the browser we use DOMParser (structural allowlist
|
|
1866
|
+
* — see `sanitizeHtmlClient`/`walk`). On the server (no DOM) we fall through
|
|
1867
|
+
* to `sanitizeHtmlServer`, a hardened regex denylist that strips dangerous
|
|
1868
|
+
* element blocks to a fixpoint, removes inline event handlers (incl.
|
|
1869
|
+
* slash-separated ones), and drops non-allowlisted `href`/`src` URLs. Themes
|
|
1870
|
+
* needing server-rendered rich content with edge-case structure should
|
|
1871
|
+
* sanitize on the API tier and pass already-safe HTML through.
|
|
1680
1872
|
*/
|
|
1681
1873
|
declare function sanitizeHtml(input: string): string;
|
|
1682
1874
|
declare function RichText({ html, className, as }: RichTextProps): react.JSX.Element | null;
|
|
@@ -2267,4 +2459,4 @@ declare function pickTranslations(bundle: LocaleBundle, locale: string): LocaleM
|
|
|
2267
2459
|
*/
|
|
2268
2460
|
declare function buildLocaleBundle<T extends Record<string, unknown>>(modules: T): LocaleBundle;
|
|
2269
2461
|
|
|
2270
|
-
export { AddToCartButton, type AddressInput, type AnalyticsApi, type AnalyticsPayload, type AppManifestBlock, type AppPayload, type AppState, Block, BlockInstance, BlockProps$1 as BlockProps, BlockSchema, Cart, type CheckoutAddress, type CheckoutApi, type CheckoutSessionState, type CheckoutStep, Collection, CollectionCard, type CollectionCardProps, type CollectionCardSlots, CollectionProvider, type ComputedStyleTokens,
|
|
2462
|
+
export { AddToCartButton, type AddressInput, type AnalyticsApi, type AnalyticsPayload, type AppManifestBlock, type AppPayload, type AppState, Block, BlockInstance, BlockProps$1 as BlockProps, BlockSchema, type CacheFetcher, type CacheMutator, type CachedResource, type CachedResourceState, Cart, CartContextValue, type CheckoutAddress, type CheckoutApi, type CheckoutSessionState, type CheckoutStep, Collection, CollectionCard, type CollectionCardProps, type CollectionCardSlots, CollectionProvider, type ComputedStyleTokens, CurrencyState, CurrencySwitcher, type CurrencySwitcherProps, Customer, type CustomerAddress, type CustomerAddressesState, type DefineBlockInput, type DefineSectionInput, type DefinedBlock, type DefinedSection, type DynamicResolveContext, type DynamicSourceRef, EditableImage, type EditableImageProps, EditableText, type EditableTextProps, type FocalSrcOptions, Form, type GiftCardBalance, HeroMedia, type HeroMediaProps, ICON_NAMES, Icon, IconMap, type IconProps, Image, type ImageTransform, LOGO_SHAPE_OPTIONS, LOGO_SIZE_OPTIONS, Link, type LocaleBundle, type LocaleMessages, LocaleSwitcher, type LocaleSwitcherProps, Logo, type LogoProps, type LogoShape, type LogoSize, MenuItemData, Money, MountResult, type MutateOptions, NAVIGATE_EVENT, type NavigateEventDetail, type NavigationItem, type NavigationState, NuMuProvider, type OrderDetail, type OrderListEntry, type OrderListState, type OrderState, Page, type PlaceOrderResult, Product, ProductCard, type ProductCardProps, type ProductCardSlots, ProductProvider, ProductVariant, type RelatedProductsState, type ReorderResult, type ReorderSkipReason, type ReorderSkippedItem, RichText, type RichTextProps, type SearchResults, type SearchState, Section, SectionContext, type SectionGroupInstance, SectionInstance, SectionProps$1 as SectionProps, SectionSchema, type ShippingRateOption, type ShopWithHelpers, SizeChart, Store, type ThemeEntry, ThemeMountContext, ThemeRenderArgs, ThemeSettingsV3, type UseCachedResourceOptions, type UseGiftCardBalance, type UseReorder, type UseSearchOptions, type UseShippingRatesOptions, type UseShippingRatesState, type UseVariantSelection, type WishlistItem, type WishlistState, applyGlobalStyleTokens, applyImageTransform, asImageTransform, assetUrl, availableValues, buildLocaleBundle, clearSdkSingleton, collectBlocks, collectSections, computeGlobalStyleTokens, defaultVariant, defineBlock, defineSection, defineThemeEntry, dynamicSource, findVariantByOptions, flattenMessages, focalSrc, getReactSingleton, getSdkSingleton, isDefinedBlock, isDefinedSection, isDynamicSource, isSdkAvailable, logoImgStyle, logoStyleTokens, pickTranslations, publishVariantSelection, readVariantSelection, registerReactSingleton, registerSdkSingleton, requestNavigate, resolveDynamicValue, resolveFontStack, resolveSettingsMap, resolveSizeChart, resolveSourcePath, sanitizeHtml, useAnalytics, useApp, useCachedResource, useCart, useCheckout, useCollection, useCollectionOptional, useCollections, useCurrency, useCurrentTemplate, useCustomer, useCustomerActions, useCustomerAddresses, useDirection, useFieldTranslation, useGiftCardBalance, useImage, useLocale, useLocalization, useMoney, useNavigation, useNumberFormat, useOrder, useOrders, usePage, useProduct, useProductOptional, useProductSizeChart, useProducts, useRelatedProducts, useReorder, useResolvedSettings, useSearch, useSection, useSectionGroup, useSectionOptional, useShippingRates, useShop, useThemeSettings, useTranslation, useVariantSelection, useWishlist };
|