merchi_cart 1.3.6 → 1.3.7

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.
@@ -0,0 +1,297 @@
1
+ /** Shared helpers for Area (height × width) variation fields. Stored value is always millimetres. */
2
+
3
+ var MM_PER_INCH = 25.4;
4
+ var MM_PER_FOOT = 304.8;
5
+ var MM_PER_CM = 10;
6
+ var MM_PER_M = 1000;
7
+ export function parseAreaValue(value) {
8
+ if (value == null) return null;
9
+ var text = String(value).trim();
10
+ if (!text) return null;
11
+ var parts = text.split(',').map(function (p) {
12
+ return p.trim();
13
+ });
14
+ if (parts.length !== 2) return null;
15
+ var heightMm = Number(parts[0]);
16
+ var widthMm = Number(parts[1]);
17
+ if (!Number.isFinite(heightMm) || !Number.isFinite(widthMm)) return null;
18
+ if (heightMm <= 0 || widthMm <= 0) return null;
19
+ return {
20
+ heightMm: heightMm,
21
+ widthMm: widthMm
22
+ };
23
+ }
24
+ export function formatAreaValue(heightMm, widthMm) {
25
+ var h = Number(heightMm);
26
+ var w = Number(widthMm);
27
+ if (!Number.isFinite(h) || !Number.isFinite(w) || h <= 0 || w <= 0) return '';
28
+ return "".concat(trimNum(h), ",").concat(trimNum(w));
29
+ }
30
+ function trimNum(n) {
31
+ return String(Number(n.toFixed(6)));
32
+ }
33
+ export function isImperialUnit(unit) {
34
+ var u = String(unit || 'mm').toLowerCase();
35
+ return u === 'in' || u === 'ft';
36
+ }
37
+ export function normaliseAreaUnit(unit) {
38
+ var u = String(unit || 'mm').toLowerCase();
39
+ if (u === 'cm' || u === 'm' || u === 'in' || u === 'ft' || u === 'mm') {
40
+ return u;
41
+ }
42
+ return 'mm';
43
+ }
44
+
45
+ /** Imperial for en-US / en-LR / en-MM; otherwise metric (incl. en-GB). */
46
+ export function localePrefersImperial(locale) {
47
+ var lang = locale || (typeof navigator !== 'undefined' ? navigator.language : undefined) || '';
48
+ var normalized = lang.toLowerCase().replace('_', '-');
49
+ return normalized === 'en-us' || normalized === 'en-lr' || normalized === 'en-mm' || normalized.endsWith('-us') || normalized.endsWith('-lr') || normalized.endsWith('-mm');
50
+ }
51
+
52
+ /** Millimetres per one unit of the given area unit. */
53
+ export function unitFactorMm() {
54
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
55
+ switch (normaliseAreaUnit(unit)) {
56
+ case 'cm':
57
+ return MM_PER_CM;
58
+ case 'm':
59
+ return MM_PER_M;
60
+ case 'in':
61
+ return MM_PER_INCH;
62
+ case 'ft':
63
+ return MM_PER_FOOT;
64
+ default:
65
+ return 1;
66
+ }
67
+ }
68
+
69
+ /** @deprecated use unitFactorMm */
70
+ export function metricUnitFactor() {
71
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
72
+ return unitFactorMm(unit);
73
+ }
74
+ export function mmToUnit(mm) {
75
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
76
+ return mm / unitFactorMm(unit);
77
+ }
78
+ export function unitToMm(value) {
79
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
80
+ return value * unitFactorMm(unit);
81
+ }
82
+
83
+ /** @deprecated use mmToUnit */
84
+ export function mmToMetricDisplay(mm) {
85
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
86
+ return mmToUnit(mm, unit);
87
+ }
88
+
89
+ /** @deprecated use unitToMm */
90
+ export function metricDisplayToMm(value) {
91
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
92
+ return unitToMm(value, unit);
93
+ }
94
+ export function mmToInches(mm) {
95
+ return mm / MM_PER_INCH;
96
+ }
97
+ export function inchesToMm(inches) {
98
+ return inches * MM_PER_INCH;
99
+ }
100
+
101
+ /**
102
+ * Unit shown for the current buyer modality.
103
+ * Metric → field unit if metric, else mm.
104
+ * Imperial → field unit if imperial, else inches.
105
+ */
106
+ export function activeDisplayUnit(modality) {
107
+ var areaUnit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
108
+ var unit = normaliseAreaUnit(areaUnit);
109
+ if (modality === 'imperial') {
110
+ return isImperialUnit(unit) ? unit : 'in';
111
+ }
112
+ return isImperialUnit(unit) ? 'mm' : unit;
113
+ }
114
+ export function defaultModalityForAreaUnit() {
115
+ var areaUnit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
116
+ if (isImperialUnit(areaUnit)) return 'imperial';
117
+ return localePrefersImperial() ? 'imperial' : 'metric';
118
+ }
119
+ export function mmToDisplay(mm, modality) {
120
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
121
+ return mmToUnit(mm, activeDisplayUnit(modality, areaUnit));
122
+ }
123
+ export function displayToMm(value, modality) {
124
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
125
+ return unitToMm(value, activeDisplayUnit(modality, areaUnit));
126
+ }
127
+ export function unitLabel(modality) {
128
+ var areaUnit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
129
+ return activeDisplayUnit(modality, areaUnit);
130
+ }
131
+ export function defaultAreaStep() {
132
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
133
+ switch (normaliseAreaUnit(unit)) {
134
+ case 'm':
135
+ return 0.001;
136
+ case 'cm':
137
+ return 0.1;
138
+ case 'in':
139
+ return 0.125;
140
+ case 'ft':
141
+ return 0.01;
142
+ default:
143
+ return 1;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Step for inputs/sliders in the current display unit.
149
+ * `stepMm` is the admin-configured step stored in millimetres.
150
+ */
151
+ export function stepInDisplayUnit(stepMm, modality) {
152
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
153
+ var unit = activeDisplayUnit(modality, areaUnit);
154
+ var raw = Number(stepMm);
155
+ if (Number.isFinite(raw) && raw > 0) {
156
+ return raw / unitFactorMm(unit);
157
+ }
158
+ return defaultAreaStep(unit);
159
+ }
160
+ function decimalsForUnit(unit) {
161
+ if (unit === 'm') return 4;
162
+ if (unit === 'ft') return 3;
163
+ if (unit === 'in' || unit === 'cm') return 2;
164
+ return 2;
165
+ }
166
+
167
+ /** Format a length in mm for buyer-facing summary. */
168
+ export function formatLengthFromMm(mm, modality) {
169
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
170
+ var unit = activeDisplayUnit(modality, areaUnit);
171
+ if (unit === 'ft') {
172
+ var feet = mmToUnit(mm, 'ft');
173
+ return "".concat(trimNum(Number(feet.toFixed(3))), " ft");
174
+ }
175
+ if (unit === 'in') {
176
+ var inches = mmToInches(mm);
177
+ if (inches >= 12) {
178
+ var _feet = Math.floor(inches / 12);
179
+ var rem = inches - _feet * 12;
180
+ if (rem < 0.05) return "".concat(_feet, " ft");
181
+ return "".concat(_feet, " ft ").concat(trimNum(Number(rem.toFixed(2))), " in");
182
+ }
183
+ return "".concat(trimNum(Number(inches.toFixed(2))), " in");
184
+ }
185
+ var display = mmToUnit(mm, unit);
186
+ return "".concat(trimNum(Number(display.toFixed(decimalsForUnit(unit)))), " ").concat(unit);
187
+ }
188
+ export function formatAreaParts(value) {
189
+ var modality = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'metric';
190
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
191
+ var parsed = parseAreaValue(value);
192
+ if (!parsed) return null;
193
+ var heightMm = parsed.heightMm,
194
+ widthMm = parsed.widthMm;
195
+ var height = formatLengthFromMm(heightMm, modality, areaUnit);
196
+ var width = formatLengthFromMm(widthMm, modality, areaUnit);
197
+ var areaMm2 = heightMm * widthMm;
198
+ var unit = activeDisplayUnit(modality, areaUnit);
199
+ var areaLabel;
200
+ if (unit === 'ft') {
201
+ var sqFt = areaMm2 / (MM_PER_FOOT * MM_PER_FOOT);
202
+ areaLabel = "".concat(trimNum(Number(sqFt.toFixed(3))), " sq ft");
203
+ } else if (unit === 'in') {
204
+ var sqIn = areaMm2 / (MM_PER_INCH * MM_PER_INCH);
205
+ if (sqIn >= 144) {
206
+ areaLabel = "".concat(trimNum(Number((sqIn / 144).toFixed(3))), " sq ft");
207
+ } else {
208
+ areaLabel = "".concat(trimNum(Number(sqIn.toFixed(2))), " sq in");
209
+ }
210
+ } else if (unit === 'm') {
211
+ areaLabel = "".concat(trimNum(Number((areaMm2 / 1000000).toFixed(4))), " m\xB2");
212
+ } else if (unit === 'cm') {
213
+ areaLabel = "".concat(trimNum(Number((areaMm2 / 100).toFixed(2))), " cm\xB2");
214
+ } else {
215
+ areaLabel = "".concat(trimNum(Number(areaMm2.toFixed(2))), " mm\xB2");
216
+ }
217
+ return {
218
+ width: width,
219
+ height: height,
220
+ areaLabel: areaLabel
221
+ };
222
+ }
223
+ export function formatAreaSummary(value) {
224
+ var modality = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'metric';
225
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
226
+ var parts = formatAreaParts(value, modality, areaUnit);
227
+ if (!parts) return null;
228
+ // Width × height (landscape-first) to match the buyer control order.
229
+ return "Width ".concat(parts.width, " \xD7 Height ").concat(parts.height, " (").concat(parts.areaLabel, ")");
230
+ }
231
+ export function clamp(n, min, max) {
232
+ var v = n;
233
+ if (min != null && Number.isFinite(min)) v = Math.max(v, min);
234
+ if (max != null && Number.isFinite(max)) v = Math.min(v, max);
235
+ return v;
236
+ }
237
+
238
+ /**
239
+ * Client-side Area cost estimate (pre-discount), matching API formula:
240
+ * onceOff = heightCost × widthCost × height_u × width_u
241
+ * unitCost = heightUnit × widthUnit × height_u × width_u
242
+ * (dimensions expressed in the field's areaUnit)
243
+ */
244
+ export function estimateAreaCosts(variationField, value) {
245
+ var _ref, _variationField$areaU, _variationField$heigh, _variationField$width, _variationField$heigh2, _variationField$width2;
246
+ var parsed = parseAreaValue(value);
247
+ if (!parsed) return null;
248
+ var areaUnit = normaliseAreaUnit((_ref = (_variationField$areaU = variationField === null || variationField === void 0 ? void 0 : variationField.areaUnit) !== null && _variationField$areaU !== void 0 ? _variationField$areaU : variationField === null || variationField === void 0 ? void 0 : variationField.area_unit) !== null && _ref !== void 0 ? _ref : 'mm');
249
+ var heightU = mmToUnit(parsed.heightMm, areaUnit);
250
+ var widthU = mmToUnit(parsed.widthMm, areaUnit);
251
+ var area = heightU * widthU;
252
+ var heightCost = Number((_variationField$heigh = variationField === null || variationField === void 0 ? void 0 : variationField.heightVariationCost) !== null && _variationField$heigh !== void 0 ? _variationField$heigh : variationField === null || variationField === void 0 ? void 0 : variationField.height_variation_cost) || 0;
253
+ var widthCost = Number((_variationField$width = variationField === null || variationField === void 0 ? void 0 : variationField.widthVariationCost) !== null && _variationField$width !== void 0 ? _variationField$width : variationField === null || variationField === void 0 ? void 0 : variationField.width_variation_cost) || 0;
254
+ var heightUnit = Number((_variationField$heigh2 = variationField === null || variationField === void 0 ? void 0 : variationField.heightVariationUnitCost) !== null && _variationField$heigh2 !== void 0 ? _variationField$heigh2 : variationField === null || variationField === void 0 ? void 0 : variationField.height_variation_unit_cost) || 0;
255
+ var widthUnit = Number((_variationField$width2 = variationField === null || variationField === void 0 ? void 0 : variationField.widthVariationUnitCost) !== null && _variationField$width2 !== void 0 ? _variationField$width2 : variationField === null || variationField === void 0 ? void 0 : variationField.width_variation_unit_cost) || 0;
256
+ var onceOffCost = heightCost * widthCost * area;
257
+ var unitCost = heightUnit * widthUnit * area;
258
+ if (!(onceOffCost > 0) && !(unitCost > 0)) return null;
259
+ return {
260
+ onceOffCost: Number(onceOffCost.toFixed(3)),
261
+ unitCost: Number(unitCost.toFixed(3))
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Keep width/height linked by aspectRatio (width/height).
267
+ * `changed` indicates which dimension the user edited.
268
+ */
269
+ export function clampWithAspectRatio(args) {
270
+ var ratio = args.aspectRatio;
271
+ if (!Number.isFinite(ratio) || ratio <= 0) {
272
+ return {
273
+ heightMm: clamp(args.heightMm, args.heightMin, args.heightMax),
274
+ widthMm: clamp(args.widthMm, args.widthMin, args.widthMax)
275
+ };
276
+ }
277
+ var heightMm = args.heightMm;
278
+ var widthMm = args.widthMm;
279
+ if (args.changed === 'height') {
280
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
281
+ widthMm = heightMm * ratio;
282
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
283
+ heightMm = widthMm / ratio;
284
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
285
+ widthMm = heightMm * ratio;
286
+ } else {
287
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
288
+ heightMm = widthMm / ratio;
289
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
290
+ widthMm = heightMm * ratio;
291
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
292
+ }
293
+ return {
294
+ heightMm: heightMm,
295
+ widthMm: widthMm
296
+ };
297
+ }
@@ -1,3 +1,4 @@
1
+ import { formatAreaSummary, localePrefersImperial } from './area';
1
2
  export var FieldType = /*#__PURE__*/function (FieldType) {
2
3
  FieldType[FieldType["TEXT_INPUT"] = 1] = "TEXT_INPUT";
3
4
  FieldType[FieldType["SELECT"] = 2] = "SELECT";
@@ -10,10 +11,13 @@ export var FieldType = /*#__PURE__*/function (FieldType) {
10
11
  FieldType[FieldType["IMAGE_SELECT"] = 9] = "IMAGE_SELECT";
11
12
  FieldType[FieldType["COLOUR_PICKER"] = 10] = "COLOUR_PICKER";
12
13
  FieldType[FieldType["COLOUR_SELECT"] = 11] = "COLOUR_SELECT";
14
+ FieldType[FieldType["TURNAROUND_TIME"] = 12] = "TURNAROUND_TIME";
15
+ FieldType[FieldType["COLOUR_EXTRACT"] = 13] = "COLOUR_EXTRACT";
16
+ FieldType[FieldType["AREA"] = 14] = "AREA";
13
17
  return FieldType;
14
18
  }({});
15
19
  export function isSelectable(fieldType) {
16
- return [FieldType.SELECT, FieldType.CHECKBOX, FieldType.RADIO, FieldType.IMAGE_SELECT, FieldType.COLOUR_SELECT].includes(fieldType);
20
+ return [FieldType.SELECT, FieldType.CHECKBOX, FieldType.RADIO, FieldType.IMAGE_SELECT, FieldType.COLOUR_SELECT, FieldType.COLOUR_EXTRACT].includes(fieldType);
17
21
  }
18
22
  function concatinatedSelectedOptionValues(variation) {
19
23
  var selectableOptions = variation.selectableOptions,
@@ -40,8 +44,15 @@ export function valueString(variation) {
40
44
  value = variation.value;
41
45
  if (isSelectable(field.fieldType)) {
42
46
  return concatinatedSelectedOptionValues(variation);
43
- } else if (field.fieldType === FieldType.FILE_UPLOAD && variationFiles) {
47
+ } else if ((field.fieldType === FieldType.FILE_UPLOAD || field.fieldType === FieldType.COLOUR_EXTRACT) && variationFiles) {
48
+ if (field.fieldType === FieldType.COLOUR_EXTRACT) {
49
+ var colours = concatinatedSelectedOptionValues(variation);
50
+ var fileLabel = variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
51
+ return colours ? "".concat(fileLabel, " (").concat(colours, ")") : fileLabel;
52
+ }
44
53
  return variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
54
+ } else if (field.fieldType === FieldType.AREA) {
55
+ return formatAreaSummary(value, localePrefersImperial() ? 'imperial' : 'metric', field.areaUnit || 'mm') || value;
45
56
  }
46
57
  return value;
47
58
  }
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.activeDisplayUnit = activeDisplayUnit;
7
+ exports.clamp = clamp;
8
+ exports.clampWithAspectRatio = clampWithAspectRatio;
9
+ exports.defaultAreaStep = defaultAreaStep;
10
+ exports.defaultModalityForAreaUnit = defaultModalityForAreaUnit;
11
+ exports.displayToMm = displayToMm;
12
+ exports.estimateAreaCosts = estimateAreaCosts;
13
+ exports.formatAreaParts = formatAreaParts;
14
+ exports.formatAreaSummary = formatAreaSummary;
15
+ exports.formatAreaValue = formatAreaValue;
16
+ exports.formatLengthFromMm = formatLengthFromMm;
17
+ exports.inchesToMm = inchesToMm;
18
+ exports.isImperialUnit = isImperialUnit;
19
+ exports.localePrefersImperial = localePrefersImperial;
20
+ exports.metricDisplayToMm = metricDisplayToMm;
21
+ exports.metricUnitFactor = metricUnitFactor;
22
+ exports.mmToDisplay = mmToDisplay;
23
+ exports.mmToInches = mmToInches;
24
+ exports.mmToMetricDisplay = mmToMetricDisplay;
25
+ exports.mmToUnit = mmToUnit;
26
+ exports.normaliseAreaUnit = normaliseAreaUnit;
27
+ exports.parseAreaValue = parseAreaValue;
28
+ exports.stepInDisplayUnit = stepInDisplayUnit;
29
+ exports.unitFactorMm = unitFactorMm;
30
+ exports.unitLabel = unitLabel;
31
+ exports.unitToMm = unitToMm;
32
+ /** Shared helpers for Area (height × width) variation fields. Stored value is always millimetres. */
33
+
34
+ var MM_PER_INCH = 25.4;
35
+ var MM_PER_FOOT = 304.8;
36
+ var MM_PER_CM = 10;
37
+ var MM_PER_M = 1000;
38
+ function parseAreaValue(value) {
39
+ if (value == null) return null;
40
+ var text = String(value).trim();
41
+ if (!text) return null;
42
+ var parts = text.split(',').map(function (p) {
43
+ return p.trim();
44
+ });
45
+ if (parts.length !== 2) return null;
46
+ var heightMm = Number(parts[0]);
47
+ var widthMm = Number(parts[1]);
48
+ if (!Number.isFinite(heightMm) || !Number.isFinite(widthMm)) return null;
49
+ if (heightMm <= 0 || widthMm <= 0) return null;
50
+ return {
51
+ heightMm: heightMm,
52
+ widthMm: widthMm
53
+ };
54
+ }
55
+ function formatAreaValue(heightMm, widthMm) {
56
+ var h = Number(heightMm);
57
+ var w = Number(widthMm);
58
+ if (!Number.isFinite(h) || !Number.isFinite(w) || h <= 0 || w <= 0) return '';
59
+ return "".concat(trimNum(h), ",").concat(trimNum(w));
60
+ }
61
+ function trimNum(n) {
62
+ return String(Number(n.toFixed(6)));
63
+ }
64
+ function isImperialUnit(unit) {
65
+ var u = String(unit || 'mm').toLowerCase();
66
+ return u === 'in' || u === 'ft';
67
+ }
68
+ function normaliseAreaUnit(unit) {
69
+ var u = String(unit || 'mm').toLowerCase();
70
+ if (u === 'cm' || u === 'm' || u === 'in' || u === 'ft' || u === 'mm') {
71
+ return u;
72
+ }
73
+ return 'mm';
74
+ }
75
+
76
+ /** Imperial for en-US / en-LR / en-MM; otherwise metric (incl. en-GB). */
77
+ function localePrefersImperial(locale) {
78
+ var lang = locale || (typeof navigator !== 'undefined' ? navigator.language : undefined) || '';
79
+ var normalized = lang.toLowerCase().replace('_', '-');
80
+ return normalized === 'en-us' || normalized === 'en-lr' || normalized === 'en-mm' || normalized.endsWith('-us') || normalized.endsWith('-lr') || normalized.endsWith('-mm');
81
+ }
82
+
83
+ /** Millimetres per one unit of the given area unit. */
84
+ function unitFactorMm() {
85
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
86
+ switch (normaliseAreaUnit(unit)) {
87
+ case 'cm':
88
+ return MM_PER_CM;
89
+ case 'm':
90
+ return MM_PER_M;
91
+ case 'in':
92
+ return MM_PER_INCH;
93
+ case 'ft':
94
+ return MM_PER_FOOT;
95
+ default:
96
+ return 1;
97
+ }
98
+ }
99
+
100
+ /** @deprecated use unitFactorMm */
101
+ function metricUnitFactor() {
102
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
103
+ return unitFactorMm(unit);
104
+ }
105
+ function mmToUnit(mm) {
106
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
107
+ return mm / unitFactorMm(unit);
108
+ }
109
+ function unitToMm(value) {
110
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
111
+ return value * unitFactorMm(unit);
112
+ }
113
+
114
+ /** @deprecated use mmToUnit */
115
+ function mmToMetricDisplay(mm) {
116
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
117
+ return mmToUnit(mm, unit);
118
+ }
119
+
120
+ /** @deprecated use unitToMm */
121
+ function metricDisplayToMm(value) {
122
+ var unit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
123
+ return unitToMm(value, unit);
124
+ }
125
+ function mmToInches(mm) {
126
+ return mm / MM_PER_INCH;
127
+ }
128
+ function inchesToMm(inches) {
129
+ return inches * MM_PER_INCH;
130
+ }
131
+
132
+ /**
133
+ * Unit shown for the current buyer modality.
134
+ * Metric → field unit if metric, else mm.
135
+ * Imperial → field unit if imperial, else inches.
136
+ */
137
+ function activeDisplayUnit(modality) {
138
+ var areaUnit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
139
+ var unit = normaliseAreaUnit(areaUnit);
140
+ if (modality === 'imperial') {
141
+ return isImperialUnit(unit) ? unit : 'in';
142
+ }
143
+ return isImperialUnit(unit) ? 'mm' : unit;
144
+ }
145
+ function defaultModalityForAreaUnit() {
146
+ var areaUnit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
147
+ if (isImperialUnit(areaUnit)) return 'imperial';
148
+ return localePrefersImperial() ? 'imperial' : 'metric';
149
+ }
150
+ function mmToDisplay(mm, modality) {
151
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
152
+ return mmToUnit(mm, activeDisplayUnit(modality, areaUnit));
153
+ }
154
+ function displayToMm(value, modality) {
155
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
156
+ return unitToMm(value, activeDisplayUnit(modality, areaUnit));
157
+ }
158
+ function unitLabel(modality) {
159
+ var areaUnit = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'mm';
160
+ return activeDisplayUnit(modality, areaUnit);
161
+ }
162
+ function defaultAreaStep() {
163
+ var unit = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'mm';
164
+ switch (normaliseAreaUnit(unit)) {
165
+ case 'm':
166
+ return 0.001;
167
+ case 'cm':
168
+ return 0.1;
169
+ case 'in':
170
+ return 0.125;
171
+ case 'ft':
172
+ return 0.01;
173
+ default:
174
+ return 1;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Step for inputs/sliders in the current display unit.
180
+ * `stepMm` is the admin-configured step stored in millimetres.
181
+ */
182
+ function stepInDisplayUnit(stepMm, modality) {
183
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
184
+ var unit = activeDisplayUnit(modality, areaUnit);
185
+ var raw = Number(stepMm);
186
+ if (Number.isFinite(raw) && raw > 0) {
187
+ return raw / unitFactorMm(unit);
188
+ }
189
+ return defaultAreaStep(unit);
190
+ }
191
+ function decimalsForUnit(unit) {
192
+ if (unit === 'm') return 4;
193
+ if (unit === 'ft') return 3;
194
+ if (unit === 'in' || unit === 'cm') return 2;
195
+ return 2;
196
+ }
197
+
198
+ /** Format a length in mm for buyer-facing summary. */
199
+ function formatLengthFromMm(mm, modality) {
200
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
201
+ var unit = activeDisplayUnit(modality, areaUnit);
202
+ if (unit === 'ft') {
203
+ var feet = mmToUnit(mm, 'ft');
204
+ return "".concat(trimNum(Number(feet.toFixed(3))), " ft");
205
+ }
206
+ if (unit === 'in') {
207
+ var inches = mmToInches(mm);
208
+ if (inches >= 12) {
209
+ var _feet = Math.floor(inches / 12);
210
+ var rem = inches - _feet * 12;
211
+ if (rem < 0.05) return "".concat(_feet, " ft");
212
+ return "".concat(_feet, " ft ").concat(trimNum(Number(rem.toFixed(2))), " in");
213
+ }
214
+ return "".concat(trimNum(Number(inches.toFixed(2))), " in");
215
+ }
216
+ var display = mmToUnit(mm, unit);
217
+ return "".concat(trimNum(Number(display.toFixed(decimalsForUnit(unit)))), " ").concat(unit);
218
+ }
219
+ function formatAreaParts(value) {
220
+ var modality = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'metric';
221
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
222
+ var parsed = parseAreaValue(value);
223
+ if (!parsed) return null;
224
+ var heightMm = parsed.heightMm,
225
+ widthMm = parsed.widthMm;
226
+ var height = formatLengthFromMm(heightMm, modality, areaUnit);
227
+ var width = formatLengthFromMm(widthMm, modality, areaUnit);
228
+ var areaMm2 = heightMm * widthMm;
229
+ var unit = activeDisplayUnit(modality, areaUnit);
230
+ var areaLabel;
231
+ if (unit === 'ft') {
232
+ var sqFt = areaMm2 / (MM_PER_FOOT * MM_PER_FOOT);
233
+ areaLabel = "".concat(trimNum(Number(sqFt.toFixed(3))), " sq ft");
234
+ } else if (unit === 'in') {
235
+ var sqIn = areaMm2 / (MM_PER_INCH * MM_PER_INCH);
236
+ if (sqIn >= 144) {
237
+ areaLabel = "".concat(trimNum(Number((sqIn / 144).toFixed(3))), " sq ft");
238
+ } else {
239
+ areaLabel = "".concat(trimNum(Number(sqIn.toFixed(2))), " sq in");
240
+ }
241
+ } else if (unit === 'm') {
242
+ areaLabel = "".concat(trimNum(Number((areaMm2 / 1000000).toFixed(4))), " m\xB2");
243
+ } else if (unit === 'cm') {
244
+ areaLabel = "".concat(trimNum(Number((areaMm2 / 100).toFixed(2))), " cm\xB2");
245
+ } else {
246
+ areaLabel = "".concat(trimNum(Number(areaMm2.toFixed(2))), " mm\xB2");
247
+ }
248
+ return {
249
+ width: width,
250
+ height: height,
251
+ areaLabel: areaLabel
252
+ };
253
+ }
254
+ function formatAreaSummary(value) {
255
+ var modality = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'metric';
256
+ var areaUnit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'mm';
257
+ var parts = formatAreaParts(value, modality, areaUnit);
258
+ if (!parts) return null;
259
+ // Width × height (landscape-first) to match the buyer control order.
260
+ return "Width ".concat(parts.width, " \xD7 Height ").concat(parts.height, " (").concat(parts.areaLabel, ")");
261
+ }
262
+ function clamp(n, min, max) {
263
+ var v = n;
264
+ if (min != null && Number.isFinite(min)) v = Math.max(v, min);
265
+ if (max != null && Number.isFinite(max)) v = Math.min(v, max);
266
+ return v;
267
+ }
268
+
269
+ /**
270
+ * Client-side Area cost estimate (pre-discount), matching API formula:
271
+ * onceOff = heightCost × widthCost × height_u × width_u
272
+ * unitCost = heightUnit × widthUnit × height_u × width_u
273
+ * (dimensions expressed in the field's areaUnit)
274
+ */
275
+ function estimateAreaCosts(variationField, value) {
276
+ var _ref, _variationField$areaU, _variationField$heigh, _variationField$width, _variationField$heigh2, _variationField$width2;
277
+ var parsed = parseAreaValue(value);
278
+ if (!parsed) return null;
279
+ var areaUnit = normaliseAreaUnit((_ref = (_variationField$areaU = variationField === null || variationField === void 0 ? void 0 : variationField.areaUnit) !== null && _variationField$areaU !== void 0 ? _variationField$areaU : variationField === null || variationField === void 0 ? void 0 : variationField.area_unit) !== null && _ref !== void 0 ? _ref : 'mm');
280
+ var heightU = mmToUnit(parsed.heightMm, areaUnit);
281
+ var widthU = mmToUnit(parsed.widthMm, areaUnit);
282
+ var area = heightU * widthU;
283
+ var heightCost = Number((_variationField$heigh = variationField === null || variationField === void 0 ? void 0 : variationField.heightVariationCost) !== null && _variationField$heigh !== void 0 ? _variationField$heigh : variationField === null || variationField === void 0 ? void 0 : variationField.height_variation_cost) || 0;
284
+ var widthCost = Number((_variationField$width = variationField === null || variationField === void 0 ? void 0 : variationField.widthVariationCost) !== null && _variationField$width !== void 0 ? _variationField$width : variationField === null || variationField === void 0 ? void 0 : variationField.width_variation_cost) || 0;
285
+ var heightUnit = Number((_variationField$heigh2 = variationField === null || variationField === void 0 ? void 0 : variationField.heightVariationUnitCost) !== null && _variationField$heigh2 !== void 0 ? _variationField$heigh2 : variationField === null || variationField === void 0 ? void 0 : variationField.height_variation_unit_cost) || 0;
286
+ var widthUnit = Number((_variationField$width2 = variationField === null || variationField === void 0 ? void 0 : variationField.widthVariationUnitCost) !== null && _variationField$width2 !== void 0 ? _variationField$width2 : variationField === null || variationField === void 0 ? void 0 : variationField.width_variation_unit_cost) || 0;
287
+ var onceOffCost = heightCost * widthCost * area;
288
+ var unitCost = heightUnit * widthUnit * area;
289
+ if (!(onceOffCost > 0) && !(unitCost > 0)) return null;
290
+ return {
291
+ onceOffCost: Number(onceOffCost.toFixed(3)),
292
+ unitCost: Number(unitCost.toFixed(3))
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Keep width/height linked by aspectRatio (width/height).
298
+ * `changed` indicates which dimension the user edited.
299
+ */
300
+ function clampWithAspectRatio(args) {
301
+ var ratio = args.aspectRatio;
302
+ if (!Number.isFinite(ratio) || ratio <= 0) {
303
+ return {
304
+ heightMm: clamp(args.heightMm, args.heightMin, args.heightMax),
305
+ widthMm: clamp(args.widthMm, args.widthMin, args.widthMax)
306
+ };
307
+ }
308
+ var heightMm = args.heightMm;
309
+ var widthMm = args.widthMm;
310
+ if (args.changed === 'height') {
311
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
312
+ widthMm = heightMm * ratio;
313
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
314
+ heightMm = widthMm / ratio;
315
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
316
+ widthMm = heightMm * ratio;
317
+ } else {
318
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
319
+ heightMm = widthMm / ratio;
320
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
321
+ widthMm = heightMm * ratio;
322
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
323
+ }
324
+ return {
325
+ heightMm: heightMm,
326
+ widthMm: widthMm
327
+ };
328
+ }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.FieldType = void 0;
7
7
  exports.isSelectable = isSelectable;
8
8
  exports.valueString = valueString;
9
+ var _area = require("./area");
9
10
  var FieldType = exports.FieldType = /*#__PURE__*/function (FieldType) {
10
11
  FieldType[FieldType["TEXT_INPUT"] = 1] = "TEXT_INPUT";
11
12
  FieldType[FieldType["SELECT"] = 2] = "SELECT";
@@ -18,10 +19,13 @@ var FieldType = exports.FieldType = /*#__PURE__*/function (FieldType) {
18
19
  FieldType[FieldType["IMAGE_SELECT"] = 9] = "IMAGE_SELECT";
19
20
  FieldType[FieldType["COLOUR_PICKER"] = 10] = "COLOUR_PICKER";
20
21
  FieldType[FieldType["COLOUR_SELECT"] = 11] = "COLOUR_SELECT";
22
+ FieldType[FieldType["TURNAROUND_TIME"] = 12] = "TURNAROUND_TIME";
23
+ FieldType[FieldType["COLOUR_EXTRACT"] = 13] = "COLOUR_EXTRACT";
24
+ FieldType[FieldType["AREA"] = 14] = "AREA";
21
25
  return FieldType;
22
26
  }({});
23
27
  function isSelectable(fieldType) {
24
- return [FieldType.SELECT, FieldType.CHECKBOX, FieldType.RADIO, FieldType.IMAGE_SELECT, FieldType.COLOUR_SELECT].includes(fieldType);
28
+ return [FieldType.SELECT, FieldType.CHECKBOX, FieldType.RADIO, FieldType.IMAGE_SELECT, FieldType.COLOUR_SELECT, FieldType.COLOUR_EXTRACT].includes(fieldType);
25
29
  }
26
30
  function concatinatedSelectedOptionValues(variation) {
27
31
  var selectableOptions = variation.selectableOptions,
@@ -48,8 +52,15 @@ function valueString(variation) {
48
52
  value = variation.value;
49
53
  if (isSelectable(field.fieldType)) {
50
54
  return concatinatedSelectedOptionValues(variation);
51
- } else if (field.fieldType === FieldType.FILE_UPLOAD && variationFiles) {
55
+ } else if ((field.fieldType === FieldType.FILE_UPLOAD || field.fieldType === FieldType.COLOUR_EXTRACT) && variationFiles) {
56
+ if (field.fieldType === FieldType.COLOUR_EXTRACT) {
57
+ var colours = concatinatedSelectedOptionValues(variation);
58
+ var fileLabel = variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
59
+ return colours ? "".concat(fileLabel, " (").concat(colours, ")") : fileLabel;
60
+ }
52
61
  return variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
62
+ } else if (field.fieldType === FieldType.AREA) {
63
+ return (0, _area.formatAreaSummary)(value, (0, _area.localePrefersImperial)() ? 'imperial' : 'metric', field.areaUnit || 'mm') || value;
53
64
  }
54
65
  return value;
55
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merchi_cart",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "Merchi's cart",
5
5
  "source": "src/index.ts",
6
6
  "main": "lib/index.js",
@@ -90,9 +90,9 @@
90
90
  "browser-or-node": "^3.0.0-pre.0",
91
91
  "js-cookie": "^3.0.5",
92
92
  "lodash": "^4.17.21",
93
- "merchi_product_form": "^1.16.0",
94
- "merchi_sdk_product_form": "^1.0.7",
95
- "merchi_sdk_ts": "^1.25.0",
93
+ "merchi_product_form": "^1.18.0",
94
+ "merchi_sdk_product_form": "^1.0.8",
95
+ "merchi_sdk_ts": "^1.28.0",
96
96
  "react-country-region-selector": "^3.6.1",
97
97
  "react-geosuggest": "^2.14.1",
98
98
  "react-hook-form": "7.45.4",
@@ -0,0 +1,359 @@
1
+ /** Shared helpers for Area (height × width) variation fields. Stored value is always millimetres. */
2
+
3
+ export type AreaUnit = 'mm' | 'cm' | 'm' | 'in' | 'ft';
4
+ export type AreaInputType = 'input' | 'slider';
5
+ export type DisplayModality = 'metric' | 'imperial';
6
+
7
+ const MM_PER_INCH = 25.4;
8
+ const MM_PER_FOOT = 304.8;
9
+ const MM_PER_CM = 10;
10
+ const MM_PER_M = 1000;
11
+
12
+ export function parseAreaValue(
13
+ value: string | null | undefined
14
+ ): { heightMm: number; widthMm: number } | null {
15
+ if (value == null) return null;
16
+ const text = String(value).trim();
17
+ if (!text) return null;
18
+ const parts = text.split(',').map((p) => p.trim());
19
+ if (parts.length !== 2) return null;
20
+ const heightMm = Number(parts[0]);
21
+ const widthMm = Number(parts[1]);
22
+ if (!Number.isFinite(heightMm) || !Number.isFinite(widthMm)) return null;
23
+ if (heightMm <= 0 || widthMm <= 0) return null;
24
+ return { heightMm, widthMm };
25
+ }
26
+
27
+ export function formatAreaValue(heightMm: number, widthMm: number): string {
28
+ const h = Number(heightMm);
29
+ const w = Number(widthMm);
30
+ if (!Number.isFinite(h) || !Number.isFinite(w) || h <= 0 || w <= 0) return '';
31
+ return `${trimNum(h)},${trimNum(w)}`;
32
+ }
33
+
34
+ function trimNum(n: number): string {
35
+ return String(Number(n.toFixed(6)));
36
+ }
37
+
38
+ export function isImperialUnit(unit: AreaUnit | string | null | undefined): boolean {
39
+ const u = String(unit || 'mm').toLowerCase();
40
+ return u === 'in' || u === 'ft';
41
+ }
42
+
43
+ export function normaliseAreaUnit(unit?: string | null): AreaUnit {
44
+ const u = String(unit || 'mm').toLowerCase();
45
+ if (u === 'cm' || u === 'm' || u === 'in' || u === 'ft' || u === 'mm') {
46
+ return u;
47
+ }
48
+ return 'mm';
49
+ }
50
+
51
+ /** Imperial for en-US / en-LR / en-MM; otherwise metric (incl. en-GB). */
52
+ export function localePrefersImperial(locale?: string): boolean {
53
+ const lang =
54
+ locale ||
55
+ (typeof navigator !== 'undefined' ? navigator.language : undefined) ||
56
+ '';
57
+ const normalized = lang.toLowerCase().replace('_', '-');
58
+ return (
59
+ normalized === 'en-us' ||
60
+ normalized === 'en-lr' ||
61
+ normalized === 'en-mm' ||
62
+ normalized.endsWith('-us') ||
63
+ normalized.endsWith('-lr') ||
64
+ normalized.endsWith('-mm')
65
+ );
66
+ }
67
+
68
+ /** Millimetres per one unit of the given area unit. */
69
+ export function unitFactorMm(unit: AreaUnit | string = 'mm'): number {
70
+ switch (normaliseAreaUnit(unit)) {
71
+ case 'cm':
72
+ return MM_PER_CM;
73
+ case 'm':
74
+ return MM_PER_M;
75
+ case 'in':
76
+ return MM_PER_INCH;
77
+ case 'ft':
78
+ return MM_PER_FOOT;
79
+ default:
80
+ return 1;
81
+ }
82
+ }
83
+
84
+ /** @deprecated use unitFactorMm */
85
+ export function metricUnitFactor(unit: AreaUnit = 'mm'): number {
86
+ return unitFactorMm(unit);
87
+ }
88
+
89
+ export function mmToUnit(mm: number, unit: AreaUnit | string = 'mm'): number {
90
+ return mm / unitFactorMm(unit);
91
+ }
92
+
93
+ export function unitToMm(value: number, unit: AreaUnit | string = 'mm'): number {
94
+ return value * unitFactorMm(unit);
95
+ }
96
+
97
+ /** @deprecated use mmToUnit */
98
+ export function mmToMetricDisplay(mm: number, unit: AreaUnit = 'mm'): number {
99
+ return mmToUnit(mm, unit);
100
+ }
101
+
102
+ /** @deprecated use unitToMm */
103
+ export function metricDisplayToMm(value: number, unit: AreaUnit = 'mm'): number {
104
+ return unitToMm(value, unit);
105
+ }
106
+
107
+ export function mmToInches(mm: number): number {
108
+ return mm / MM_PER_INCH;
109
+ }
110
+
111
+ export function inchesToMm(inches: number): number {
112
+ return inches * MM_PER_INCH;
113
+ }
114
+
115
+ /**
116
+ * Unit shown for the current buyer modality.
117
+ * Metric → field unit if metric, else mm.
118
+ * Imperial → field unit if imperial, else inches.
119
+ */
120
+ export function activeDisplayUnit(
121
+ modality: DisplayModality,
122
+ areaUnit: AreaUnit | string = 'mm'
123
+ ): AreaUnit {
124
+ const unit = normaliseAreaUnit(areaUnit);
125
+ if (modality === 'imperial') {
126
+ return isImperialUnit(unit) ? unit : 'in';
127
+ }
128
+ return isImperialUnit(unit) ? 'mm' : unit;
129
+ }
130
+
131
+ export function defaultModalityForAreaUnit(
132
+ areaUnit: AreaUnit | string = 'mm'
133
+ ): DisplayModality {
134
+ if (isImperialUnit(areaUnit)) return 'imperial';
135
+ return localePrefersImperial() ? 'imperial' : 'metric';
136
+ }
137
+
138
+ export function mmToDisplay(
139
+ mm: number,
140
+ modality: DisplayModality,
141
+ areaUnit: AreaUnit | string = 'mm'
142
+ ): number {
143
+ return mmToUnit(mm, activeDisplayUnit(modality, areaUnit));
144
+ }
145
+
146
+ export function displayToMm(
147
+ value: number,
148
+ modality: DisplayModality,
149
+ areaUnit: AreaUnit | string = 'mm'
150
+ ): number {
151
+ return unitToMm(value, activeDisplayUnit(modality, areaUnit));
152
+ }
153
+
154
+ export function unitLabel(
155
+ modality: DisplayModality,
156
+ areaUnit: AreaUnit | string = 'mm'
157
+ ): string {
158
+ return activeDisplayUnit(modality, areaUnit);
159
+ }
160
+
161
+ export function defaultAreaStep(unit: AreaUnit | string = 'mm'): number {
162
+ switch (normaliseAreaUnit(unit)) {
163
+ case 'm':
164
+ return 0.001;
165
+ case 'cm':
166
+ return 0.1;
167
+ case 'in':
168
+ return 0.125;
169
+ case 'ft':
170
+ return 0.01;
171
+ default:
172
+ return 1;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Step for inputs/sliders in the current display unit.
178
+ * `stepMm` is the admin-configured step stored in millimetres.
179
+ */
180
+ export function stepInDisplayUnit(
181
+ stepMm: number | null | undefined,
182
+ modality: DisplayModality,
183
+ areaUnit: AreaUnit | string = 'mm'
184
+ ): number {
185
+ const unit = activeDisplayUnit(modality, areaUnit);
186
+ const raw = Number(stepMm);
187
+ if (Number.isFinite(raw) && raw > 0) {
188
+ return raw / unitFactorMm(unit);
189
+ }
190
+ return defaultAreaStep(unit);
191
+ }
192
+
193
+ function decimalsForUnit(unit: AreaUnit): number {
194
+ if (unit === 'm') return 4;
195
+ if (unit === 'ft') return 3;
196
+ if (unit === 'in' || unit === 'cm') return 2;
197
+ return 2;
198
+ }
199
+
200
+ /** Format a length in mm for buyer-facing summary. */
201
+ export function formatLengthFromMm(
202
+ mm: number,
203
+ modality: DisplayModality,
204
+ areaUnit: AreaUnit | string = 'mm'
205
+ ): string {
206
+ const unit = activeDisplayUnit(modality, areaUnit);
207
+ if (unit === 'ft') {
208
+ const feet = mmToUnit(mm, 'ft');
209
+ return `${trimNum(Number(feet.toFixed(3)))} ft`;
210
+ }
211
+ if (unit === 'in') {
212
+ const inches = mmToInches(mm);
213
+ if (inches >= 12) {
214
+ const feet = Math.floor(inches / 12);
215
+ const rem = inches - feet * 12;
216
+ if (rem < 0.05) return `${feet} ft`;
217
+ return `${feet} ft ${trimNum(Number(rem.toFixed(2)))} in`;
218
+ }
219
+ return `${trimNum(Number(inches.toFixed(2)))} in`;
220
+ }
221
+ const display = mmToUnit(mm, unit);
222
+ return `${trimNum(Number(display.toFixed(decimalsForUnit(unit))))} ${unit}`;
223
+ }
224
+
225
+ export function formatAreaParts(
226
+ value: string | null | undefined,
227
+ modality: DisplayModality = 'metric',
228
+ areaUnit: AreaUnit | string = 'mm'
229
+ ): { width: string; height: string; areaLabel: string } | null {
230
+ const parsed = parseAreaValue(value);
231
+ if (!parsed) return null;
232
+ const { heightMm, widthMm } = parsed;
233
+ const height = formatLengthFromMm(heightMm, modality, areaUnit);
234
+ const width = formatLengthFromMm(widthMm, modality, areaUnit);
235
+ const areaMm2 = heightMm * widthMm;
236
+ const unit = activeDisplayUnit(modality, areaUnit);
237
+ let areaLabel: string;
238
+ if (unit === 'ft') {
239
+ const sqFt = areaMm2 / (MM_PER_FOOT * MM_PER_FOOT);
240
+ areaLabel = `${trimNum(Number(sqFt.toFixed(3)))} sq ft`;
241
+ } else if (unit === 'in') {
242
+ const sqIn = areaMm2 / (MM_PER_INCH * MM_PER_INCH);
243
+ if (sqIn >= 144) {
244
+ areaLabel = `${trimNum(Number((sqIn / 144).toFixed(3)))} sq ft`;
245
+ } else {
246
+ areaLabel = `${trimNum(Number(sqIn.toFixed(2)))} sq in`;
247
+ }
248
+ } else if (unit === 'm') {
249
+ areaLabel = `${trimNum(Number((areaMm2 / 1_000_000).toFixed(4)))} m²`;
250
+ } else if (unit === 'cm') {
251
+ areaLabel = `${trimNum(Number((areaMm2 / 100).toFixed(2)))} cm²`;
252
+ } else {
253
+ areaLabel = `${trimNum(Number(areaMm2.toFixed(2)))} mm²`;
254
+ }
255
+ return { width, height, areaLabel };
256
+ }
257
+
258
+ export function formatAreaSummary(
259
+ value: string | null | undefined,
260
+ modality: DisplayModality = 'metric',
261
+ areaUnit: AreaUnit | string = 'mm'
262
+ ): string | null {
263
+ const parts = formatAreaParts(value, modality, areaUnit);
264
+ if (!parts) return null;
265
+ // Width × height (landscape-first) to match the buyer control order.
266
+ return `Width ${parts.width} × Height ${parts.height} (${parts.areaLabel})`;
267
+ }
268
+
269
+ export function clamp(n: number, min?: number | null, max?: number | null): number {
270
+ let v = n;
271
+ if (min != null && Number.isFinite(min)) v = Math.max(v, min);
272
+ if (max != null && Number.isFinite(max)) v = Math.min(v, max);
273
+ return v;
274
+ }
275
+
276
+ /**
277
+ * Client-side Area cost estimate (pre-discount), matching API formula:
278
+ * onceOff = heightCost × widthCost × height_u × width_u
279
+ * unitCost = heightUnit × widthUnit × height_u × width_u
280
+ * (dimensions expressed in the field's areaUnit)
281
+ */
282
+ export function estimateAreaCosts(
283
+ variationField: any,
284
+ value: string | null | undefined
285
+ ): { onceOffCost: number; unitCost: number } | null {
286
+ const parsed = parseAreaValue(value);
287
+ if (!parsed) return null;
288
+ const areaUnit = normaliseAreaUnit(
289
+ variationField?.areaUnit ?? variationField?.area_unit ?? 'mm'
290
+ );
291
+ const heightU = mmToUnit(parsed.heightMm, areaUnit);
292
+ const widthU = mmToUnit(parsed.widthMm, areaUnit);
293
+ const area = heightU * widthU;
294
+ const heightCost = Number(
295
+ variationField?.heightVariationCost ??
296
+ variationField?.height_variation_cost
297
+ ) || 0;
298
+ const widthCost = Number(
299
+ variationField?.widthVariationCost ?? variationField?.width_variation_cost
300
+ ) || 0;
301
+ const heightUnit = Number(
302
+ variationField?.heightVariationUnitCost ??
303
+ variationField?.height_variation_unit_cost
304
+ ) || 0;
305
+ const widthUnit = Number(
306
+ variationField?.widthVariationUnitCost ??
307
+ variationField?.width_variation_unit_cost
308
+ ) || 0;
309
+ const onceOffCost = heightCost * widthCost * area;
310
+ const unitCost = heightUnit * widthUnit * area;
311
+ if (!(onceOffCost > 0) && !(unitCost > 0)) return null;
312
+ return {
313
+ onceOffCost: Number(onceOffCost.toFixed(3)),
314
+ unitCost: Number(unitCost.toFixed(3)),
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Keep width/height linked by aspectRatio (width/height).
320
+ * `changed` indicates which dimension the user edited.
321
+ */
322
+ export function clampWithAspectRatio(args: {
323
+ heightMm: number;
324
+ widthMm: number;
325
+ changed: 'height' | 'width';
326
+ aspectRatio: number;
327
+ heightMin?: number | null;
328
+ heightMax?: number | null;
329
+ widthMin?: number | null;
330
+ widthMax?: number | null;
331
+ }): { heightMm: number; widthMm: number } {
332
+ const ratio = args.aspectRatio;
333
+ if (!Number.isFinite(ratio) || ratio <= 0) {
334
+ return {
335
+ heightMm: clamp(args.heightMm, args.heightMin, args.heightMax),
336
+ widthMm: clamp(args.widthMm, args.widthMin, args.widthMax),
337
+ };
338
+ }
339
+
340
+ let heightMm = args.heightMm;
341
+ let widthMm = args.widthMm;
342
+
343
+ if (args.changed === 'height') {
344
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
345
+ widthMm = heightMm * ratio;
346
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
347
+ heightMm = widthMm / ratio;
348
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
349
+ widthMm = heightMm * ratio;
350
+ } else {
351
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
352
+ heightMm = widthMm / ratio;
353
+ heightMm = clamp(heightMm, args.heightMin, args.heightMax);
354
+ widthMm = heightMm * ratio;
355
+ widthMm = clamp(widthMm, args.widthMin, args.widthMax);
356
+ }
357
+
358
+ return { heightMm, widthMm };
359
+ }
@@ -1,3 +1,5 @@
1
+ import { formatAreaSummary, localePrefersImperial } from './area';
2
+
1
3
  export enum FieldType {
2
4
  TEXT_INPUT = 1,
3
5
  SELECT = 2,
@@ -10,6 +12,9 @@ export enum FieldType {
10
12
  IMAGE_SELECT = 9,
11
13
  COLOUR_PICKER = 10,
12
14
  COLOUR_SELECT = 11,
15
+ TURNAROUND_TIME = 12,
16
+ COLOUR_EXTRACT = 13,
17
+ AREA = 14,
13
18
  }
14
19
 
15
20
 
@@ -19,7 +24,8 @@ export function isSelectable(fieldType: number) {
19
24
  FieldType.CHECKBOX,
20
25
  FieldType.RADIO,
21
26
  FieldType.IMAGE_SELECT,
22
- FieldType.COLOUR_SELECT
27
+ FieldType.COLOUR_SELECT,
28
+ FieldType.COLOUR_EXTRACT,
23
29
  ].includes(fieldType);
24
30
  }
25
31
 
@@ -49,8 +55,26 @@ export function valueString(variation: any) {
49
55
  } = variation;
50
56
  if (isSelectable(field.fieldType)) {
51
57
  return concatinatedSelectedOptionValues(variation);
52
- } else if (field.fieldType === FieldType.FILE_UPLOAD && variationFiles) {
58
+ } else if (
59
+ (field.fieldType === FieldType.FILE_UPLOAD ||
60
+ field.fieldType === FieldType.COLOUR_EXTRACT) &&
61
+ variationFiles
62
+ ) {
63
+ if (field.fieldType === FieldType.COLOUR_EXTRACT) {
64
+ const colours = concatinatedSelectedOptionValues(variation);
65
+ const fileLabel =
66
+ variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
67
+ return colours ? `${fileLabel} (${colours})` : fileLabel;
68
+ }
53
69
  return variationFiles.length > 1 ? 'uploaded files' : 'uploaded file';
70
+ } else if (field.fieldType === FieldType.AREA) {
71
+ return (
72
+ formatAreaSummary(
73
+ value,
74
+ localePrefersImperial() ? 'imperial' : 'metric',
75
+ field.areaUnit || 'mm'
76
+ ) || value
77
+ );
54
78
  }
55
79
  return value;
56
80
  }