@squaredr/fieldcraft-pro 1.8.0 → 1.9.1

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.
@@ -380,6 +380,7 @@ var consultationBookingSchema = {
380
380
  required: true,
381
381
  config: {
382
382
  type: "appointment",
383
+ // Example slots — replace with dynamic data or a slots URL in production
383
384
  slots: [
384
385
  {
385
386
  date: "2026-09-01",
@@ -480,6 +481,55 @@ var consultationBookingSchema = {
480
481
  type: "divider",
481
482
  label: ""
482
483
  },
484
+ {
485
+ id: "hourly_rate",
486
+ type: "calculated",
487
+ label: "Hourly Rate",
488
+ config: {
489
+ type: "calculated",
490
+ expression: 'IF({service_type} = "legal", 150, IF({service_type} = "business", 200, IF({service_type} = "technical", 175, 0)))',
491
+ format: "currency",
492
+ decimalPlaces: 2,
493
+ prefix: "$"
494
+ }
495
+ },
496
+ {
497
+ id: "session_cost",
498
+ type: "calculated",
499
+ label: "Session Cost",
500
+ helpText: "Based on service type and session duration",
501
+ config: {
502
+ type: "calculated",
503
+ expression: "{hourly_rate} * {session_duration} / 60",
504
+ format: "currency",
505
+ decimalPlaces: 2,
506
+ prefix: "$"
507
+ }
508
+ },
509
+ {
510
+ id: "rush_fee",
511
+ type: "calculated",
512
+ label: "Rush Fee",
513
+ config: {
514
+ type: "calculated",
515
+ expression: 'IF({urgency} = "rush", 50, 0)',
516
+ format: "currency",
517
+ decimalPlaces: 2,
518
+ prefix: "$"
519
+ }
520
+ },
521
+ {
522
+ id: "total_cost",
523
+ type: "calculated",
524
+ label: "Total",
525
+ config: {
526
+ type: "calculated",
527
+ expression: "{session_cost} + {rush_fee}",
528
+ format: "currency",
529
+ decimalPlaces: 2,
530
+ prefix: "$"
531
+ }
532
+ },
483
533
  {
484
534
  id: "promo_code",
485
535
  type: "short_text",
@@ -499,10 +549,10 @@ var consultationBookingSchema = {
499
549
  provider: "stripe",
500
550
  publicKey: "",
501
551
  // Set your Stripe publishable key (pk_test_... or pk_live_...)
502
- amount: 2e4,
552
+ amountField: "total_cost",
503
553
  currency: "USD",
504
554
  description: "Consultation Session"
505
- // No serverUrl — developer must configure their own endpoint via customProps or serverUrl
555
+ // serverUrl: "" — developer must configure their own payment intent endpoint
506
556
  }
507
557
  }
508
558
  ]
@@ -999,13 +1049,50 @@ var ecommerceCheckoutSchema = {
999
1049
  prefix: "$"
1000
1050
  }
1001
1051
  },
1052
+ {
1053
+ id: "shipping_cost",
1054
+ type: "calculated",
1055
+ label: "Shipping",
1056
+ helpText: "Based on your selected shipping method",
1057
+ config: {
1058
+ type: "calculated",
1059
+ expression: 'IF({shipping_method} = "overnight", 29.99, IF({shipping_method} = "express", 14.99, IF({shipping_method} = "standard", 7.99, 0)))',
1060
+ format: "currency",
1061
+ decimalPlaces: 2,
1062
+ prefix: "$"
1063
+ }
1064
+ },
1065
+ {
1066
+ id: "insurance_cost",
1067
+ type: "calculated",
1068
+ label: "Shipping Insurance",
1069
+ config: {
1070
+ type: "calculated",
1071
+ expression: "IF({shipping_insurance} = true, 4.99, 0)",
1072
+ format: "currency",
1073
+ decimalPlaces: 2,
1074
+ prefix: "$"
1075
+ }
1076
+ },
1077
+ {
1078
+ id: "gift_wrap_cost",
1079
+ type: "calculated",
1080
+ label: "Gift Wrapping",
1081
+ config: {
1082
+ type: "calculated",
1083
+ expression: "IF({gift_wrap} = true, 5.99, 0)",
1084
+ format: "currency",
1085
+ decimalPlaces: 2,
1086
+ prefix: "$"
1087
+ }
1088
+ },
1002
1089
  {
1003
1090
  id: "order_total",
1004
1091
  type: "calculated",
1005
1092
  label: "Order Total",
1006
1093
  config: {
1007
1094
  type: "calculated",
1008
- expression: "{order_subtotal} + {order_tax}",
1095
+ expression: "{order_subtotal} + {order_tax} + {shipping_cost} + {insurance_cost} + {gift_wrap_cost}",
1009
1096
  format: "currency",
1010
1097
  decimalPlaces: 2,
1011
1098
  prefix: "$"
@@ -1027,10 +1114,10 @@ var ecommerceCheckoutSchema = {
1027
1114
  provider: "stripe",
1028
1115
  publicKey: "",
1029
1116
  // Set your Stripe publishable key (pk_test_... or pk_live_...)
1030
- amount: 1e4,
1117
+ amountField: "order_total",
1031
1118
  currency: "USD",
1032
1119
  description: "E-commerce Order"
1033
- // No serverUrl — developer must configure their own endpoint via customProps or serverUrl
1120
+ // serverUrl: "" — developer must configure their own payment intent endpoint
1034
1121
  }
1035
1122
  }
1036
1123
  ]
@@ -1 +1 @@
1
- export { consultationBooking, consultationBookingMeta, consultationBookingSchema, ecommerceCheckout, ecommerceCheckoutMeta, ecommerceCheckoutSchema, proTemplates } from '../chunk-IW654Z3M.mjs';
1
+ export { consultationBooking, consultationBookingMeta, consultationBookingSchema, ecommerceCheckout, ecommerceCheckoutMeta, ecommerceCheckoutSchema, proTemplates } from '../chunk-5QXKDYWY.mjs';
@@ -1,7 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { FormEngineTheme, FormEngineSchema } from '@squaredr/fieldcraft-core';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
4
 
6
5
  /**
7
6
  * ThemeEditor chrome theme configuration.
@@ -65,7 +64,7 @@ type ThemeEditorThemeProviderProps = {
65
64
  theme?: ThemeEditorTheme;
66
65
  children: ReactNode;
67
66
  };
68
- declare function ThemeEditorThemeProvider({ theme, children }: ThemeEditorThemeProviderProps): react_jsx_runtime.JSX.Element;
67
+ declare function ThemeEditorThemeProvider({ theme, children }: ThemeEditorThemeProviderProps): react.JSX.Element;
69
68
 
70
69
  /**
71
70
  * Drafting Teal light chrome preset (default).
@@ -1,7 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { FormEngineTheme, FormEngineSchema } from '@squaredr/fieldcraft-core';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
4
 
6
5
  /**
7
6
  * ThemeEditor chrome theme configuration.
@@ -65,7 +64,7 @@ type ThemeEditorThemeProviderProps = {
65
64
  theme?: ThemeEditorTheme;
66
65
  children: ReactNode;
67
66
  };
68
- declare function ThemeEditorThemeProvider({ theme, children }: ThemeEditorThemeProviderProps): react_jsx_runtime.JSX.Element;
67
+ declare function ThemeEditorThemeProvider({ theme, children }: ThemeEditorThemeProviderProps): react.JSX.Element;
69
68
 
70
69
  /**
71
70
  * Drafting Teal light chrome preset (default).
@@ -720,16 +720,20 @@ function deepMerge(base, partial) {
720
720
 
721
721
  // src/theme-editor/palette-generator.ts
722
722
  function generatePalette(baseHex) {
723
+ const cleaned = baseHex.replace("#", "");
724
+ if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/.test(cleaned)) {
725
+ return generatePalette("#6B7280");
726
+ }
723
727
  const [h, s, l] = hexToHsl(baseHex);
724
728
  const secH = (h + 180) % 360;
725
729
  const isLightBase = l > 50;
726
730
  return {
727
731
  // Primary
728
732
  primary: hslToHex(h, s, clamp(l, 30, 60)),
729
- primaryForeground: isLightBase ? "#ffffff" : "#ffffff",
733
+ primaryForeground: isLightBase ? "#ffffff" : "#000000",
730
734
  // Secondary (complementary)
731
735
  secondary: hslToHex(secH, Math.max(s - 15, 10), clamp(l, 35, 55)),
732
- secondaryForeground: "#ffffff",
736
+ secondaryForeground: isLightBase ? "#ffffff" : "#000000",
733
737
  // Surfaces
734
738
  surface: hslToHex(h, Math.max(s - 35, 3), 97),
735
739
  background: "#F4F7F8",
@@ -1775,6 +1779,12 @@ function resolveThemeFromDOM() {
1775
1779
  const val = styles.getPropertyValue(prop).trim();
1776
1780
  return val || void 0;
1777
1781
  };
1782
+ const getNumber = (prop) => {
1783
+ const val = get(prop);
1784
+ if (val == null) return void 0;
1785
+ const num = parseFloat(val);
1786
+ return isNaN(num) ? void 0 : num;
1787
+ };
1778
1788
  return {
1779
1789
  colors: {
1780
1790
  primary: get("--primary"),
@@ -1807,6 +1817,17 @@ function resolveThemeFromDOM() {
1807
1817
  inputRadius: get("--fc-radius-input"),
1808
1818
  buttonRadius: get("--fc-radius-button"),
1809
1819
  cardRadius: get("--fc-radius-card")
1820
+ },
1821
+ spacing: {
1822
+ base: getNumber("--fc-spacing-base"),
1823
+ sectionGap: getNumber("--fc-section-gap"),
1824
+ fieldGap: getNumber("--fc-field-gap"),
1825
+ inputPaddingX: getNumber("--fc-input-padding-x"),
1826
+ inputPaddingY: getNumber("--fc-input-padding-y")
1827
+ },
1828
+ layout: {
1829
+ maxWidth: get("--fc-max-width")
1830
+ // alignment, progressPosition, sectionLayout are enums — not resolvable from CSS
1810
1831
  }
1811
1832
  };
1812
1833
  }
@@ -1861,6 +1882,9 @@ function detectPresetFamily(theme) {
1861
1882
  }
1862
1883
  return null;
1863
1884
  }
1885
+ function isValidHex(value) {
1886
+ return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
1887
+ }
1864
1888
  var SECTIONS = [
1865
1889
  {
1866
1890
  id: "colors",
@@ -2111,6 +2135,7 @@ function ThemeEditorInner({
2111
2135
  a.click();
2112
2136
  URL.revokeObjectURL(url);
2113
2137
  }, [theme]);
2138
+ const [importError, setImportError] = react.useState(null);
2114
2139
  const importJson = react.useCallback(() => {
2115
2140
  const input = document.createElement("input");
2116
2141
  input.type = "file";
@@ -2122,10 +2147,16 @@ function ThemeEditorInner({
2122
2147
  reader.onload = () => {
2123
2148
  try {
2124
2149
  const parsed = JSON.parse(reader.result);
2150
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || !("colors" in parsed || "typography" in parsed || "shape" in parsed || "spacing" in parsed || "layout" in parsed)) {
2151
+ setImportError("Invalid theme file: expected an object with colors, typography, shape, spacing, or layout.");
2152
+ return;
2153
+ }
2154
+ setImportError(null);
2125
2155
  setTheme(parsed);
2126
2156
  setPresetKey("custom");
2127
2157
  onChange?.(parsed, "custom");
2128
2158
  } catch {
2159
+ setImportError("Failed to parse JSON file. Please check the file format.");
2129
2160
  }
2130
2161
  };
2131
2162
  reader.readAsText(file);
@@ -2203,6 +2234,10 @@ function ThemeEditorInner({
2203
2234
  onSave && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onSave(theme, presetKey), className: "fcte-btn fcte-btn--primary", children: "Save" })
2204
2235
  ] })
2205
2236
  ] }),
2237
+ importError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-import-error", role: "alert", children: [
2238
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: importError }),
2239
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setImportError(null), className: "fcte-import-error__close", children: "\xD7" })
2240
+ ] }),
2206
2241
  showPalette && /* @__PURE__ */ jsxRuntime.jsx(
2207
2242
  PaletteGenerator,
2208
2243
  {
@@ -2236,6 +2271,8 @@ function ThemeEditorInner({
2236
2271
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-fields", children: currentSection.fields.map((field) => {
2237
2272
  const val = sectionValues[field.key];
2238
2273
  if (field.kind === "color") {
2274
+ const colorVal = typeof val === "string" ? val : "#000000";
2275
+ const isInvalid = colorVal.length > 0 && !isValidHex(colorVal);
2239
2276
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-field", children: [
2240
2277
  /* @__PURE__ */ jsxRuntime.jsx("label", { className: "fcte-field__label", children: field.label }),
2241
2278
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-field__color-row", children: [
@@ -2243,7 +2280,7 @@ function ThemeEditorInner({
2243
2280
  "input",
2244
2281
  {
2245
2282
  type: "color",
2246
- value: typeof val === "string" ? val : "#000000",
2283
+ value: isValidHex(colorVal) ? colorVal : "#000000",
2247
2284
  onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
2248
2285
  className: "fcte-field__swatch"
2249
2286
  }
@@ -2252,10 +2289,24 @@ function ThemeEditorInner({
2252
2289
  "input",
2253
2290
  {
2254
2291
  type: "text",
2255
- value: typeof val === "string" ? val : "",
2256
- onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
2257
- className: "fcte-field__text",
2258
- spellCheck: false
2292
+ value: colorVal,
2293
+ onChange: (e) => {
2294
+ const v = e.target.value;
2295
+ if (isValidHex(v)) {
2296
+ updateField(currentSection.themeKey, field.key, v);
2297
+ } else {
2298
+ setTheme((prev) => ({
2299
+ ...prev,
2300
+ [currentSection.themeKey]: {
2301
+ ...prev[currentSection.themeKey],
2302
+ [field.key]: v
2303
+ }
2304
+ }));
2305
+ }
2306
+ },
2307
+ className: `fcte-field__text${isInvalid ? " fcte-field__text--invalid" : ""}`,
2308
+ spellCheck: false,
2309
+ placeholder: "#RRGGBB"
2259
2310
  }
2260
2311
  )
2261
2312
  ] })
@@ -2284,9 +2335,16 @@ function ThemeEditorInner({
2284
2335
  {
2285
2336
  type: "number",
2286
2337
  value: typeof val === "number" ? val : 0,
2287
- onChange: (e) => updateField(currentSection.themeKey, field.key, Number(e.target.value)),
2338
+ onChange: (e) => {
2339
+ let n = Math.round(Number(e.target.value));
2340
+ if (isNaN(n)) n = 0;
2341
+ if (field.min != null) n = Math.max(field.min, n);
2342
+ if (field.max != null) n = Math.min(field.max, n);
2343
+ updateField(currentSection.themeKey, field.key, n);
2344
+ },
2288
2345
  min: field.min,
2289
2346
  max: field.max,
2347
+ step: 1,
2290
2348
  className: "fcte-field__number"
2291
2349
  }
2292
2350
  ),
@@ -2304,7 +2362,8 @@ function ThemeEditorInner({
2304
2362
  onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
2305
2363
  placeholder: field.placeholder,
2306
2364
  className: "fcte-field__text",
2307
- spellCheck: false
2365
+ spellCheck: false,
2366
+ maxLength: 200
2308
2367
  }
2309
2368
  )
2310
2369
  ] }, field.key);
@@ -1,2 +1,2 @@
1
- export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from '../chunk-CYXJGOQH.mjs';
1
+ export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from '../chunk-KTIU7VCP.mjs';
2
2
  import '../chunk-4MMKB2EW.mjs';
@@ -214,4 +214,4 @@ type MutationAction = {
214
214
  updates: Partial<Option>;
215
215
  };
216
216
 
217
- export type { BuilderState as B, DragItem as D, FormBuilderPreviewProps as F, MutationAction as M, PaletteCategory as P, QuestionTypeCategory as Q, SelectedItem as S, DropTarget as a, FormBuilderProps as b, FormBuilderTemplate as c, FormBuilderTheme as d, PropertyPanelTab as e, QuestionTypeInfo as f };
217
+ export type { BuilderState as B, DragItem as D, FormBuilderTheme as F, MutationAction as M, PaletteCategory as P, QuestionTypeCategory as Q, SelectedItem as S, DropTarget as a, FormBuilderPreviewProps as b, FormBuilderProps as c, PropertyPanelTab as d, QuestionTypeInfo as e, FormBuilderTemplate as f };
@@ -214,4 +214,4 @@ type MutationAction = {
214
214
  updates: Partial<Option>;
215
215
  };
216
216
 
217
- export type { BuilderState as B, DragItem as D, FormBuilderPreviewProps as F, MutationAction as M, PaletteCategory as P, QuestionTypeCategory as Q, SelectedItem as S, DropTarget as a, FormBuilderProps as b, FormBuilderTemplate as c, FormBuilderTheme as d, PropertyPanelTab as e, QuestionTypeInfo as f };
217
+ export type { BuilderState as B, DragItem as D, FormBuilderTheme as F, MutationAction as M, PaletteCategory as P, QuestionTypeCategory as Q, SelectedItem as S, DropTarget as a, FormBuilderPreviewProps as b, FormBuilderProps as c, PropertyPanelTab as d, QuestionTypeInfo as e, FormBuilderTemplate as f };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squaredr/fieldcraft-pro",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "Pro components for FieldCraft — visual drag-and-drop Form Builder, Response Viewer, and Theme Editor for React",
5
5
  "keywords": [
6
6
  "fieldcraft",
@@ -59,19 +59,19 @@
59
59
  "./theme-editor/styles.css"
60
60
  ],
61
61
  "dependencies": {
62
- "@dnd-kit/core": "^6.1.0",
62
+ "@dnd-kit/core": "^6.3.1",
63
63
  "@dnd-kit/sortable": "^8.0.0",
64
64
  "@dnd-kit/utilities": "^3.2.2",
65
- "@monaco-editor/react": "^4.6.0",
65
+ "@monaco-editor/react": "^4.7.0",
66
66
  "@radix-ui/react-select": "^2.3.7",
67
- "@xyflow/react": "^12.11.3",
67
+ "@xyflow/react": "^12.11.5",
68
68
  "clsx": "^2.1.1",
69
- "lucide-react": "^1.16.0",
69
+ "lucide-react": "^1.37.0",
70
70
  "tailwind-merge": "^3.6.0"
71
71
  },
72
72
  "peerDependencies": {
73
- "@squaredr/fieldcraft-core": "^1.8.1",
74
- "@squaredr/fieldcraft-react": "^1.9.0",
73
+ "@squaredr/fieldcraft-core": "^1.9.1",
74
+ "@squaredr/fieldcraft-react": "^1.9.2",
75
75
  "@squaredr/paykit": "^0.10.0",
76
76
  "@squaredr/paykit-react": "^1.0.0",
77
77
  "react": "^18 || ^19",
@@ -86,25 +86,25 @@
86
86
  }
87
87
  },
88
88
  "devDependencies": {
89
- "@squaredr/fieldcraft-core": "^1.8.1",
90
- "@squaredr/fieldcraft-react": "^1.9.0",
91
- "@squaredr/paykit": "^0.10.1",
89
+ "@squaredr/fieldcraft-core": "^1.10.0",
90
+ "@squaredr/fieldcraft-react": "^1.10.0",
91
+ "@squaredr/paykit": "^0.10.2",
92
92
  "@squaredr/paykit-react": "^1.1.0",
93
- "@tailwindcss/cli": "^4.3.0",
93
+ "@tailwindcss/cli": "^4.3.3",
94
94
  "@testing-library/jest-dom": "^7.0.1",
95
95
  "@testing-library/react": "^16.3.3",
96
96
  "@testing-library/user-event": "^14.6.6",
97
- "@types/react": "^19.2.14",
98
- "@types/react-dom": "^19.2.3",
97
+ "@types/react": "^19.2.18",
98
+ "@types/react-dom": "^19.2.5",
99
99
  "jsdom": "^30.0.1",
100
- "react": "^19.2.4",
101
- "react-dom": "^19.2.4",
102
- "tailwindcss": "^4.3.0",
103
- "tsup": "^8.0",
104
- "typescript": "^5.5",
105
- "vitest": "^2.0",
100
+ "react": "^19.2.8",
101
+ "react-dom": "^19.2.8",
102
+ "tailwindcss": "^4.3.3",
103
+ "tsup": "^8.5.1",
104
+ "typescript": "^5.9.3",
105
+ "vitest": "^2.1.9",
106
106
  "@squaredr/fieldcraft-pro-tsconfig": "0.0.1",
107
- "@squaredr/fieldcraft-pro-license": "1.4.0"
107
+ "@squaredr/fieldcraft-pro-license": "1.4.1"
108
108
  },
109
109
  "license": "SEE LICENSE IN LICENSE.txt",
110
110
  "author": "SquaredR",
@@ -119,6 +119,35 @@
119
119
  font-size: 11px;
120
120
  }
121
121
 
122
+ /* ─── Import error banner ──────────────────────────────────────── */
123
+ .fcte-import-error {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: space-between;
127
+ padding: 6px 12px;
128
+ background: #fef2f2;
129
+ border-bottom: 1px solid #fca5a5;
130
+ color: #991b1b;
131
+ font-size: 12px;
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ .fcte-import-error__close {
136
+ background: none;
137
+ border: none;
138
+ color: #991b1b;
139
+ font-size: 16px;
140
+ cursor: pointer;
141
+ padding: 0 4px;
142
+ line-height: 1;
143
+ }
144
+
145
+ /* ─── Invalid color input ──────────────────────────────────────── */
146
+ .fcte-field__text--invalid {
147
+ border-color: #ef4444;
148
+ background: #fef2f2;
149
+ }
150
+
122
151
  /* ─── Body split ────────────────────────────────────────────────── */
123
152
  .fcte-body {
124
153
  display: flex;