@snowcone-app/ui 0.4.7 → 0.4.9

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.d.cts CHANGED
@@ -2929,6 +2929,24 @@ interface RealtimeContextValue {
2929
2929
  tiles?: number;
2930
2930
  alignment?: string;
2931
2931
  }>) => boolean;
2932
+ /**
2933
+ * Declare spread-mode crop geometry (ADR-0060) for canvases that run
2934
+ * combined spread/group artboards: artboard name → artboard dims + piece
2935
+ * rects keyed by the product's placement labels. Sent on the realtime
2936
+ * config (re-sent when it changes) so the server can slice each rendered
2937
+ * spread canvas_state into per-placement images. Pass undefined to clear.
2938
+ */
2939
+ setSpreadCrops: (crops: Record<string, {
2940
+ width: number;
2941
+ height: number;
2942
+ pieces: Array<{
2943
+ placement: string;
2944
+ x: number;
2945
+ y: number;
2946
+ width: number;
2947
+ height: number;
2948
+ }>;
2949
+ }> | undefined) => void;
2932
2950
  getMockupResultsImmediate: () => MockupResult[];
2933
2951
  subscribeMockupResults: (callback: (results: MockupResult[]) => void) => () => void;
2934
2952
  subscribeMockupResultById: (mockupId: string, callback: (result: MockupResult) => void) => () => void;
package/dist/index.d.ts CHANGED
@@ -2929,6 +2929,24 @@ interface RealtimeContextValue {
2929
2929
  tiles?: number;
2930
2930
  alignment?: string;
2931
2931
  }>) => boolean;
2932
+ /**
2933
+ * Declare spread-mode crop geometry (ADR-0060) for canvases that run
2934
+ * combined spread/group artboards: artboard name → artboard dims + piece
2935
+ * rects keyed by the product's placement labels. Sent on the realtime
2936
+ * config (re-sent when it changes) so the server can slice each rendered
2937
+ * spread canvas_state into per-placement images. Pass undefined to clear.
2938
+ */
2939
+ setSpreadCrops: (crops: Record<string, {
2940
+ width: number;
2941
+ height: number;
2942
+ pieces: Array<{
2943
+ placement: string;
2944
+ x: number;
2945
+ y: number;
2946
+ width: number;
2947
+ height: number;
2948
+ }>;
2949
+ }> | undefined) => void;
2932
2950
  getMockupResultsImmediate: () => MockupResult[];
2933
2951
  subscribeMockupResults: (callback: (results: MockupResult[]) => void) => () => void;
2934
2952
  subscribeMockupResultById: (mockupId: string, callback: (result: MockupResult) => void) => () => void;
package/dist/index.js CHANGED
@@ -5549,8 +5549,8 @@ import { useRef as useRef13, useEffect as useEffect18, useMemo as useMemo8, useS
5549
5549
 
5550
5550
  // src/patterns/RealtimeProvider.tsx
5551
5551
  import {
5552
- createContext as createContext7,
5553
- useContext as useContext5,
5552
+ createContext as createContext8,
5553
+ useContext as useContext6,
5554
5554
  useState as useState17,
5555
5555
  useEffect as useEffect17,
5556
5556
  useCallback as useCallback10,
@@ -5559,10 +5559,23 @@ import {
5559
5559
  } from "react";
5560
5560
  import { useRealtimeMockup as useRealtimeMockup2 } from "@snowcone-app/sdk/react";
5561
5561
  import { resolveVariantId as resolveVariantIdUtil } from "@snowcone-app/sdk";
5562
+
5563
+ // src/patterns/MockupBackground.tsx
5564
+ import { createContext as createContext7, useContext as useContext5 } from "react";
5562
5565
  import { jsx as jsx51 } from "react/jsx-runtime";
5566
+ var MockupBackgroundContext = createContext7(void 0);
5567
+ function MockupBackground({ bg, children }) {
5568
+ return /* @__PURE__ */ jsx51(MockupBackgroundContext.Provider, { value: bg, children });
5569
+ }
5570
+ function useMockupBackgroundOptional() {
5571
+ return useContext5(MockupBackgroundContext);
5572
+ }
5573
+
5574
+ // src/patterns/RealtimeProvider.tsx
5575
+ import { jsx as jsx52 } from "react/jsx-runtime";
5563
5576
  var DEFAULT_ARTBOARD_SIZE = 2e3;
5564
5577
  var MAX_CONCURRENT_BLOBS = 5;
5565
- var RealtimeContext = createContext7(
5578
+ var RealtimeContext = createContext8(
5566
5579
  void 0
5567
5580
  );
5568
5581
  function RealtimeProvider({
@@ -5576,6 +5589,7 @@ function RealtimeProvider({
5576
5589
  grantUrl
5577
5590
  }) {
5578
5591
  const productContext = useProductOptional();
5592
+ const mockupBg = useMockupBackgroundOptional();
5579
5593
  const [isEnabled, setIsEnabled] = useState17(autoEnable);
5580
5594
  const useSessionGrant = Boolean(shop && grantUrl);
5581
5595
  const getToken = useCallback10(async () => {
@@ -5596,6 +5610,10 @@ function RealtimeProvider({
5596
5610
  const configSentRef = useRef12(false);
5597
5611
  const placementSettingsOverridesRef = useRef12({});
5598
5612
  const lastSentVariantIdRef = useRef12(null);
5613
+ const lastSentBgRef = useRef12(void 0);
5614
+ const spreadCropsRef = useRef12(void 0);
5615
+ const [spreadCropsVersion, setSpreadCropsVersion] = useState17(0);
5616
+ const lastSentSpreadCropsRef = useRef12("null");
5599
5617
  const lastSentColorsRef = useRef12({});
5600
5618
  const realtimeMockup = useRealtimeMockup2({
5601
5619
  wsUrl,
@@ -5781,6 +5799,8 @@ function RealtimeProvider({
5781
5799
  disconnect();
5782
5800
  configSentRef.current = false;
5783
5801
  lastSentVariantIdRef.current = null;
5802
+ lastSentBgRef.current = void 0;
5803
+ lastSentSpreadCropsRef.current = "null";
5784
5804
  lastSentColorsRef.current = {};
5785
5805
  };
5786
5806
  }
@@ -5802,7 +5822,10 @@ function RealtimeProvider({
5802
5822
  );
5803
5823
  const isFirstSend = !configSentRef.current;
5804
5824
  const variantChanged = lastSentVariantIdRef.current !== null && lastSentVariantIdRef.current !== variantId;
5805
- if (!isFirstSend && !variantChanged) {
5825
+ const bgChanged = configSentRef.current && lastSentBgRef.current !== mockupBg;
5826
+ const spreadCropsJson = JSON.stringify(spreadCropsRef.current ?? null);
5827
+ const spreadCropsChanged = configSentRef.current && lastSentSpreadCropsRef.current !== spreadCropsJson;
5828
+ if (!isFirstSend && !variantChanged && !bgChanged && !spreadCropsChanged) {
5806
5829
  return;
5807
5830
  }
5808
5831
  const mockupIds = [];
@@ -5819,6 +5842,8 @@ function RealtimeProvider({
5819
5842
  variantId,
5820
5843
  shop,
5821
5844
  width: mockupWidth,
5845
+ ...mockupBg ? { bg: mockupBg } : {},
5846
+ ...spreadCropsRef.current ? { spreadCrops: spreadCropsRef.current } : {},
5822
5847
  placementSettings: { ...placementSettingsOverridesRef.current }
5823
5848
  } : {
5824
5849
  productId,
@@ -5827,6 +5852,8 @@ function RealtimeProvider({
5827
5852
  shop: "hTidnpNnNa",
5828
5853
  seal: "bypass-sig-for-k6-load-test",
5829
5854
  width: mockupWidth,
5855
+ ...mockupBg ? { bg: mockupBg } : {},
5856
+ ...spreadCropsRef.current ? { spreadCrops: spreadCropsRef.current } : {},
5830
5857
  placementSettings: { ...placementSettingsOverridesRef.current }
5831
5858
  };
5832
5859
  if (variantChanged) {
@@ -5838,6 +5865,8 @@ function RealtimeProvider({
5838
5865
  sendConfigRaw(configPayload);
5839
5866
  configSentRef.current = true;
5840
5867
  lastSentVariantIdRef.current = variantId;
5868
+ lastSentBgRef.current = mockupBg;
5869
+ lastSentSpreadCropsRef.current = spreadCropsJson;
5841
5870
  setTimeout(() => {
5842
5871
  const colorPlacements = placements.filter((p) => p.type === "color");
5843
5872
  colorPlacements.forEach((p) => {
@@ -5855,7 +5884,9 @@ function RealtimeProvider({
5855
5884
  productContext?.selection,
5856
5885
  sendConfigRaw,
5857
5886
  sendColorBlobTracked,
5858
- mockupWidth
5887
+ mockupWidth,
5888
+ mockupBg,
5889
+ spreadCropsVersion
5859
5890
  ]);
5860
5891
  useEffect17(() => {
5861
5892
  if (!isEnabled || !isConnected || !isConfigured || !productContext?.product)
@@ -5889,6 +5920,17 @@ function RealtimeProvider({
5889
5920
  },
5890
5921
  [updatePlacementSettingsRaw]
5891
5922
  );
5923
+ const setSpreadCrops = useCallback10(
5924
+ (crops) => {
5925
+ const next = crops && Object.keys(crops).length > 0 ? crops : void 0;
5926
+ if (JSON.stringify(next ?? null) === JSON.stringify(spreadCropsRef.current ?? null)) {
5927
+ return;
5928
+ }
5929
+ spreadCropsRef.current = next;
5930
+ setSpreadCropsVersion((v) => v + 1);
5931
+ },
5932
+ []
5933
+ );
5892
5934
  const enableRealtime = useCallback10(() => {
5893
5935
  setIsEnabled(true);
5894
5936
  }, []);
@@ -6174,6 +6216,7 @@ function RealtimeProvider({
6174
6216
  sendCanvasState,
6175
6217
  updateMockupIds: updateMockupIdsRaw,
6176
6218
  updatePlacementSettings: updatePlacementSettingsWrapped,
6219
+ setSpreadCrops,
6177
6220
  getMockupResultsImmediate: () => mockupResultsRef.current,
6178
6221
  subscribeMockupResults: stableSubscribeMockupResults,
6179
6222
  subscribeMockupResultById: stableSubscribeMockupResultById,
@@ -6210,6 +6253,7 @@ function RealtimeProvider({
6210
6253
  sendCanvasState,
6211
6254
  updateMockupIdsRaw,
6212
6255
  updatePlacementSettingsWrapped,
6256
+ setSpreadCrops,
6213
6257
  stableSubscribeMockupResults,
6214
6258
  stableSubscribeMockupResultById,
6215
6259
  notifyPendingExport,
@@ -6223,17 +6267,17 @@ function RealtimeProvider({
6223
6267
  registerBlobUrl
6224
6268
  ]
6225
6269
  );
6226
- return /* @__PURE__ */ jsx51(RealtimeContext.Provider, { value: contextValue, children });
6270
+ return /* @__PURE__ */ jsx52(RealtimeContext.Provider, { value: contextValue, children });
6227
6271
  }
6228
6272
  function useRealtime() {
6229
- const context = useContext5(RealtimeContext);
6273
+ const context = useContext6(RealtimeContext);
6230
6274
  if (!context) {
6231
6275
  throw new Error("useRealtime must be used within a RealtimeProvider");
6232
6276
  }
6233
6277
  return context;
6234
6278
  }
6235
6279
  function useRealtimeOptional() {
6236
- return useContext5(RealtimeContext);
6280
+ return useContext6(RealtimeContext);
6237
6281
  }
6238
6282
 
6239
6283
  // src/hooks/useCanvasContext.ts
@@ -7043,19 +7087,19 @@ import { useCallback as useCallback15, useMemo as useMemo10 } from "react";
7043
7087
 
7044
7088
  // src/patterns/MockupPriorityProvider.tsx
7045
7089
  import {
7046
- createContext as createContext8,
7047
- useContext as useContext6,
7090
+ createContext as createContext9,
7091
+ useContext as useContext7,
7048
7092
  useCallback as useCallback14,
7049
7093
  useRef as useRef18,
7050
7094
  useMemo as useMemo9,
7051
7095
  useState as useState23,
7052
7096
  useEffect as useEffect23
7053
7097
  } from "react";
7054
- import { jsx as jsx52 } from "react/jsx-runtime";
7098
+ import { jsx as jsx53 } from "react/jsx-runtime";
7055
7099
  var MAX_LEVEL3_PREFETCH = 5;
7056
7100
  var PRIORITY_RECALC_DEBOUNCE_MS = 50;
7057
7101
  var PRELOAD_MARGIN_PX = 750;
7058
- var MockupPriorityContext = createContext8(
7102
+ var MockupPriorityContext = createContext9(
7059
7103
  void 0
7060
7104
  );
7061
7105
  function MockupPriorityProvider({ children }) {
@@ -7635,10 +7679,10 @@ function MockupPriorityProvider({ children }) {
7635
7679
  subscribeLevel1Change
7636
7680
  ]
7637
7681
  );
7638
- return /* @__PURE__ */ jsx52(MockupPriorityContext.Provider, { value: contextValue, children });
7682
+ return /* @__PURE__ */ jsx53(MockupPriorityContext.Provider, { value: contextValue, children });
7639
7683
  }
7640
7684
  function useMockupPriority() {
7641
- const context = useContext6(MockupPriorityContext);
7685
+ const context = useContext7(MockupPriorityContext);
7642
7686
  if (!context) {
7643
7687
  throw new Error(
7644
7688
  "useMockupPriority must be used within a MockupPriorityProvider"
@@ -7647,7 +7691,7 @@ function useMockupPriority() {
7647
7691
  return context;
7648
7692
  }
7649
7693
  function useMockupPriorityOptional() {
7650
- return useContext6(MockupPriorityContext);
7694
+ return useContext7(MockupPriorityContext);
7651
7695
  }
7652
7696
 
7653
7697
  // src/hooks/useProductPage.ts
@@ -8373,8 +8417,8 @@ var CanvasExportService = class _CanvasExportService {
8373
8417
  var canvasExportService = new CanvasExportService();
8374
8418
 
8375
8419
  // src/components/CanvasIsolationBoundary.tsx
8376
- import React40, { useEffect as useEffect26 } from "react";
8377
- import { Fragment as Fragment9, jsx as jsx53, jsxs as jsxs33 } from "react/jsx-runtime";
8420
+ import React41, { useEffect as useEffect26 } from "react";
8421
+ import { Fragment as Fragment9, jsx as jsx54, jsxs as jsxs33 } from "react/jsx-runtime";
8378
8422
  function CanvasIsolationConfigurator({
8379
8423
  placementName: placementNameProp,
8380
8424
  onArtworkChange,
@@ -8421,9 +8465,9 @@ function CanvasIsolationConfigurator({
8421
8465
  }, []);
8422
8466
  return null;
8423
8467
  }
8424
- var IsolatedChildren = React40.memo(
8468
+ var IsolatedChildren = React41.memo(
8425
8469
  function IsolatedChildren2({ children }) {
8426
- return /* @__PURE__ */ jsx53(Fragment9, { children });
8470
+ return /* @__PURE__ */ jsx54(Fragment9, { children });
8427
8471
  },
8428
8472
  // Custom comparison: ALWAYS return true (never re-render due to parent changes)
8429
8473
  // This is intentional - children should use refs/services for updates that need to bypass this
@@ -8437,7 +8481,7 @@ function CanvasIsolationBoundary({
8437
8481
  serializeStateForServer
8438
8482
  }) {
8439
8483
  return /* @__PURE__ */ jsxs33(Fragment9, { children: [
8440
- /* @__PURE__ */ jsx53(
8484
+ /* @__PURE__ */ jsx54(
8441
8485
  CanvasIsolationConfigurator,
8442
8486
  {
8443
8487
  placementName,
@@ -8446,7 +8490,7 @@ function CanvasIsolationBoundary({
8446
8490
  serializeStateForServer
8447
8491
  }
8448
8492
  ),
8449
- /* @__PURE__ */ jsx53(IsolatedChildren, { children })
8493
+ /* @__PURE__ */ jsx54(IsolatedChildren, { children })
8450
8494
  ] });
8451
8495
  }
8452
8496
  function useIsolatedCanvas() {
@@ -8470,7 +8514,7 @@ function useIsolatedCanvas() {
8470
8514
 
8471
8515
  // src/components/LoadingOverlayPrism.tsx
8472
8516
  import { useState as useState26, useEffect as useEffect27, useCallback as useCallback16, useRef as useRef19 } from "react";
8473
- import { jsx as jsx54, jsxs as jsxs34 } from "react/jsx-runtime";
8517
+ import { jsx as jsx55, jsxs as jsxs34 } from "react/jsx-runtime";
8474
8518
  function generateParticles() {
8475
8519
  return Array.from({ length: 16 }, () => ({
8476
8520
  left: 10 + Math.random() * 80,
@@ -8614,13 +8658,13 @@ function LoadingOverlayPrism({
8614
8658
  onTransitionEnd: handleTransitionEnd,
8615
8659
  children: [
8616
8660
  /* @__PURE__ */ jsxs34("div", { className: "prism-loading-rainbow", children: [
8617
- /* @__PURE__ */ jsx54("div", { className: "prism-loading-band" }),
8618
- /* @__PURE__ */ jsx54("div", { className: "prism-loading-band" }),
8619
- /* @__PURE__ */ jsx54("div", { className: "prism-loading-band" }),
8620
- /* @__PURE__ */ jsx54("div", { className: "prism-loading-band" }),
8621
- /* @__PURE__ */ jsx54("div", { className: "prism-loading-band" })
8661
+ /* @__PURE__ */ jsx55("div", { className: "prism-loading-band" }),
8662
+ /* @__PURE__ */ jsx55("div", { className: "prism-loading-band" }),
8663
+ /* @__PURE__ */ jsx55("div", { className: "prism-loading-band" }),
8664
+ /* @__PURE__ */ jsx55("div", { className: "prism-loading-band" }),
8665
+ /* @__PURE__ */ jsx55("div", { className: "prism-loading-band" })
8622
8666
  ] }),
8623
- particles.map((p, i) => /* @__PURE__ */ jsx54(
8667
+ particles.map((p, i) => /* @__PURE__ */ jsx55(
8624
8668
  "div",
8625
8669
  {
8626
8670
  className: "prism-loading-particle",
@@ -8682,7 +8726,7 @@ import {
8682
8726
  useCallback as useCallback17
8683
8727
  } from "react";
8684
8728
  import { createPortal as createPortal2 } from "react-dom";
8685
- import { Fragment as Fragment10, jsx as jsx55, jsxs as jsxs35 } from "react/jsx-runtime";
8729
+ import { Fragment as Fragment10, jsx as jsx56, jsxs as jsxs35 } from "react/jsx-runtime";
8686
8730
  var BANDS = [
8687
8731
  { rgb: "230, 45, 55", delay: "-0.0s" },
8688
8732
  // red
@@ -8863,8 +8907,8 @@ function CandyVisual({
8863
8907
  style,
8864
8908
  onTransitionEnd,
8865
8909
  children: [
8866
- BANDS.map((_, i) => /* @__PURE__ */ jsx55("div", { className: "candy-band" }, `b-${i}`)),
8867
- particles.map((p, i) => /* @__PURE__ */ jsx55(
8910
+ BANDS.map((_, i) => /* @__PURE__ */ jsx56("div", { className: "candy-band" }, `b-${i}`)),
8911
+ particles.map((p, i) => /* @__PURE__ */ jsx56(
8868
8912
  "div",
8869
8913
  {
8870
8914
  className: "candy-particle",
@@ -8930,7 +8974,7 @@ var LoadingOverlayPrismCandy = memo2(function LoadingOverlayPrismCandy2({
8930
8974
  if (!visible) fadeClass = "candy-fade-out";
8931
8975
  else if (mounted) fadeClass = "candy-fade-in";
8932
8976
  return /* @__PURE__ */ jsxs35(Fragment10, { children: [
8933
- /* @__PURE__ */ jsx55(
8977
+ /* @__PURE__ */ jsx56(
8934
8978
  "div",
8935
8979
  {
8936
8980
  ref: anchorRef,
@@ -8943,7 +8987,7 @@ var LoadingOverlayPrismCandy = memo2(function LoadingOverlayPrismCandy2({
8943
8987
  }
8944
8988
  ),
8945
8989
  bounds && typeof document !== "undefined" && createPortal2(
8946
- /* @__PURE__ */ jsx55(
8990
+ /* @__PURE__ */ jsx56(
8947
8991
  CandyVisual,
8948
8992
  {
8949
8993
  fadeClass,
@@ -8984,7 +9028,7 @@ var LoadingOverlayPrismCandyInline = memo2(
8984
9028
  let fadeClass = "";
8985
9029
  if (!visible) fadeClass = "candy-fade-out";
8986
9030
  else if (mounted) fadeClass = "candy-fade-in";
8987
- return /* @__PURE__ */ jsx55(
9031
+ return /* @__PURE__ */ jsx56(
8988
9032
  CandyVisual,
8989
9033
  {
8990
9034
  fadeClass,
@@ -9004,17 +9048,6 @@ var LoadingOverlayPrismCandyInline = memo2(
9004
9048
  import { useRealtimeMockup as useRealtimeMockup3 } from "@snowcone-app/sdk/react";
9005
9049
  import { getProduct as getProduct3, listProducts as listProducts4, getMockupUrl as getMockupUrl2 } from "@snowcone-app/sdk";
9006
9050
 
9007
- // src/patterns/MockupBackground.tsx
9008
- import { createContext as createContext9, useContext as useContext7 } from "react";
9009
- import { jsx as jsx56 } from "react/jsx-runtime";
9010
- var MockupBackgroundContext = createContext9(void 0);
9011
- function MockupBackground({ bg, children }) {
9012
- return /* @__PURE__ */ jsx56(MockupBackgroundContext.Provider, { value: bg, children });
9013
- }
9014
- function useMockupBackgroundOptional() {
9015
- return useContext7(MockupBackgroundContext);
9016
- }
9017
-
9018
9051
  // src/patterns/ProductPageProvider.tsx
9019
9052
  import { jsx as jsx57 } from "react/jsx-runtime";
9020
9053
  function ProductPageProvider({