@snowcone-app/ui 0.4.8 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/ui",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "React components for merchandise visualization and customization",
5
5
  "keywords": [
6
6
  "react",
@@ -103,7 +103,7 @@
103
103
  "react-instantsearch": "^7.15.5",
104
104
  "react-zoom-pan-pinch": "^3.6.4",
105
105
  "tailwind-merge": "^3.0.0",
106
- "@snowcone-app/sdk": "0.20.1"
106
+ "@snowcone-app/sdk": "0.20.2"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@chromatic-com/storybook": "^4.1.2",
@@ -127,6 +127,26 @@ export interface RealtimeContextValue {
127
127
  */
128
128
  updatePlacementSettings: (settings: Record<string, { scaleMode?: 'fill' | 'fit'; tiles?: number; alignment?: string }>) => boolean;
129
129
 
130
+ /**
131
+ * Declare spread-mode crop geometry (ADR-0060) for canvases that run
132
+ * combined spread/group artboards: artboard name → artboard dims + piece
133
+ * rects keyed by the product's placement labels. Sent on the realtime
134
+ * config (re-sent when it changes) so the server can slice each rendered
135
+ * spread canvas_state into per-placement images. Pass undefined to clear.
136
+ */
137
+ setSpreadCrops: (
138
+ crops:
139
+ | Record<
140
+ string,
141
+ {
142
+ width: number;
143
+ height: number;
144
+ pieces: Array<{ placement: string; x: number; y: number; width: number; height: number }>;
145
+ }
146
+ >
147
+ | undefined
148
+ ) => void;
149
+
130
150
  // PERFORMANCE: Direct access without state updates
131
151
  getMockupResultsImmediate: () => MockupResult[];
132
152
 
@@ -310,6 +330,21 @@ export function RealtimeProvider({
310
330
  // Track last sent variantId to detect when we need to re-send config
311
331
  const lastSentVariantIdRef = useRef<string | null>(null);
312
332
  const lastSentBgRef = useRef<string | undefined>(undefined);
333
+ // Spread crop geometry (ADR-0060) declared via setSpreadCrops. Ref holds
334
+ // the payload; the version state re-runs the config effect on change.
335
+ const spreadCropsRef = useRef<
336
+ | Record<
337
+ string,
338
+ {
339
+ width: number;
340
+ height: number;
341
+ pieces: Array<{ placement: string; x: number; y: number; width: number; height: number }>;
342
+ }
343
+ >
344
+ | undefined
345
+ >(undefined);
346
+ const [spreadCropsVersion, setSpreadCropsVersion] = useState(0);
347
+ const lastSentSpreadCropsRef = useRef<string>("null");
313
348
 
314
349
  // Track last sent colors to prevent duplicates
315
350
  const lastSentColorsRef = useRef<Record<string, string>>({});
@@ -583,6 +618,7 @@ export function RealtimeProvider({
583
618
  configSentRef.current = false;
584
619
  lastSentVariantIdRef.current = null;
585
620
  lastSentBgRef.current = undefined;
621
+ lastSentSpreadCropsRef.current = "null";
586
622
  lastSentColorsRef.current = {};
587
623
  };
588
624
  }
@@ -619,8 +655,12 @@ export function RealtimeProvider({
619
655
  lastSentVariantIdRef.current !== variantId;
620
656
  // 3. Background changed (theme flip swaps the MockupBackground hex)
621
657
  const bgChanged = configSentRef.current && lastSentBgRef.current !== mockupBg;
658
+ // 4. Spread crop geometry changed (spread-mode editor mounted/unmounted)
659
+ const spreadCropsJson = JSON.stringify(spreadCropsRef.current ?? null);
660
+ const spreadCropsChanged =
661
+ configSentRef.current && lastSentSpreadCropsRef.current !== spreadCropsJson;
622
662
 
623
- if (!isFirstSend && !variantChanged && !bgChanged) {
663
+ if (!isFirstSend && !variantChanged && !bgChanged && !spreadCropsChanged) {
624
664
  return; // Nothing changed, skip
625
665
  }
626
666
 
@@ -646,6 +686,7 @@ export function RealtimeProvider({
646
686
  shop: shop as string,
647
687
  width: mockupWidth,
648
688
  ...(mockupBg ? { bg: mockupBg } : {}),
689
+ ...(spreadCropsRef.current ? { spreadCrops: spreadCropsRef.current } : {}),
649
690
  placementSettings: { ...placementSettingsOverridesRef.current },
650
691
  }
651
692
  : {
@@ -656,6 +697,7 @@ export function RealtimeProvider({
656
697
  seal: "bypass-sig-for-k6-load-test",
657
698
  width: mockupWidth,
658
699
  ...(mockupBg ? { bg: mockupBg } : {}),
700
+ ...(spreadCropsRef.current ? { spreadCrops: spreadCropsRef.current } : {}),
659
701
  placementSettings: { ...placementSettingsOverridesRef.current },
660
702
  };
661
703
 
@@ -673,6 +715,7 @@ export function RealtimeProvider({
673
715
  configSentRef.current = true;
674
716
  lastSentVariantIdRef.current = variantId;
675
717
  lastSentBgRef.current = mockupBg;
718
+ lastSentSpreadCropsRef.current = spreadCropsJson;
676
719
 
677
720
  // Send default colors for color placements
678
721
  setTimeout(() => {
@@ -694,6 +737,7 @@ export function RealtimeProvider({
694
737
  sendColorBlobTracked,
695
738
  mockupWidth,
696
739
  mockupBg,
740
+ spreadCropsVersion,
697
741
  ]);
698
742
 
699
743
  // Watch for color placement changes
@@ -737,6 +781,32 @@ export function RealtimeProvider({
737
781
  [updatePlacementSettingsRaw]
738
782
  );
739
783
 
784
+ // Declare spread crop geometry (ADR-0060). Stored in a ref; bumping the
785
+ // version state re-runs the config effect, which re-sends the config when
786
+ // the serialized geometry actually changed.
787
+ const setSpreadCrops = useCallback(
788
+ (
789
+ crops:
790
+ | Record<
791
+ string,
792
+ {
793
+ width: number;
794
+ height: number;
795
+ pieces: Array<{ placement: string; x: number; y: number; width: number; height: number }>;
796
+ }
797
+ >
798
+ | undefined
799
+ ) => {
800
+ const next = crops && Object.keys(crops).length > 0 ? crops : undefined;
801
+ if (JSON.stringify(next ?? null) === JSON.stringify(spreadCropsRef.current ?? null)) {
802
+ return;
803
+ }
804
+ spreadCropsRef.current = next;
805
+ setSpreadCropsVersion((v) => v + 1);
806
+ },
807
+ []
808
+ );
809
+
740
810
  const enableRealtime = useCallback(() => {
741
811
  setIsEnabled(true);
742
812
  }, []);
@@ -1111,6 +1181,7 @@ export function RealtimeProvider({
1111
1181
  sendCanvasState,
1112
1182
  updateMockupIds: updateMockupIdsRaw,
1113
1183
  updatePlacementSettings: updatePlacementSettingsWrapped,
1184
+ setSpreadCrops,
1114
1185
  getMockupResultsImmediate: () => mockupResultsRef.current,
1115
1186
  subscribeMockupResults: stableSubscribeMockupResults,
1116
1187
  subscribeMockupResultById: stableSubscribeMockupResultById,
@@ -1147,6 +1218,7 @@ export function RealtimeProvider({
1147
1218
  sendCanvasState,
1148
1219
  updateMockupIdsRaw,
1149
1220
  updatePlacementSettingsWrapped,
1221
+ setSpreadCrops,
1150
1222
  stableSubscribeMockupResults,
1151
1223
  stableSubscribeMockupResultById,
1152
1224
  notifyPendingExport,