@squaredr/fieldcraft-pro 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1081 @@
1
+ import { requireLicense } from './chunk-VECQKSWS.mjs';
2
+ import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
3
+ import { playfulPreset, clinicalPreset, highContrastPreset, modernPreset, darkPreset, cleanPreset, FormEngineRenderer } from '@squaredr/fieldcraft-react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/theme-editor/preview-schema.ts
7
+ var PREVIEW_SCHEMA = {
8
+ id: "theme-preview",
9
+ version: "1.0.0",
10
+ title: "Theme Preview",
11
+ description: "See how your theme looks on a real form.",
12
+ sections: [
13
+ {
14
+ id: "s1",
15
+ title: "Contact Information",
16
+ questions: [
17
+ {
18
+ id: "name",
19
+ type: "short_text",
20
+ label: "Full Name",
21
+ required: true,
22
+ placeholder: "Jane Doe"
23
+ },
24
+ {
25
+ id: "email",
26
+ type: "email",
27
+ label: "Email Address",
28
+ required: true,
29
+ placeholder: "jane@example.com"
30
+ },
31
+ {
32
+ id: "department",
33
+ type: "dropdown",
34
+ label: "Department",
35
+ options: [
36
+ { label: "Engineering", value: "engineering" },
37
+ { label: "Design", value: "design" },
38
+ { label: "Marketing", value: "marketing" }
39
+ ]
40
+ },
41
+ {
42
+ id: "rating",
43
+ type: "rating",
44
+ label: "How would you rate this experience?",
45
+ config: { type: "rating", max: 5 }
46
+ },
47
+ {
48
+ id: "notes",
49
+ type: "long_text",
50
+ label: "Additional Notes",
51
+ placeholder: "Any other feedback..."
52
+ }
53
+ ]
54
+ }
55
+ ],
56
+ submitAction: { type: "adapter" }
57
+ };
58
+
59
+ // src/theme-editor/css-utils.ts
60
+ var COLOR_MAP = {
61
+ primary: "--fc-primary",
62
+ primaryForeground: "--fc-primary-foreground",
63
+ secondary: "--fc-secondary",
64
+ secondaryForeground: "--fc-secondary-foreground",
65
+ error: "--fc-error",
66
+ errorForeground: "--fc-error-foreground",
67
+ warning: "--fc-warning",
68
+ success: "--fc-success",
69
+ surface: "--fc-surface",
70
+ background: "--fc-background",
71
+ text: "--fc-text",
72
+ textMuted: "--fc-text-muted",
73
+ textDisabled: "--fc-text-disabled",
74
+ border: "--fc-border",
75
+ borderFocus: "--fc-border-focus",
76
+ inputBackground: "--fc-input-background"
77
+ };
78
+ var TYPOGRAPHY_MAP = {
79
+ fontFamily: "--fc-font-family",
80
+ scale: "--fc-scale",
81
+ questionSize: "--fc-question-size",
82
+ labelSize: "--fc-label-size",
83
+ helpTextSize: "--fc-help-text-size",
84
+ bodySize: "--fc-body-size"
85
+ };
86
+ var SHAPE_MAP = {
87
+ radius: "--fc-radius",
88
+ inputRadius: "--fc-input-radius",
89
+ buttonRadius: "--fc-button-radius",
90
+ cardRadius: "--fc-card-radius"
91
+ };
92
+ var SPACING_MAP = {
93
+ base: "--fc-spacing-base",
94
+ sectionGap: "--fc-section-gap",
95
+ fieldGap: "--fc-field-gap",
96
+ inputPaddingX: "--fc-input-padding-x",
97
+ inputPaddingY: "--fc-input-padding-y"
98
+ };
99
+ var LAYOUT_MAP = {
100
+ maxWidth: "--fc-max-width",
101
+ alignment: "--fc-alignment",
102
+ progressPosition: "--fc-progress-position",
103
+ sectionLayout: "--fc-section-layout"
104
+ };
105
+ function themeToCss(theme) {
106
+ const lines = [];
107
+ function addSection(obj, map, comment) {
108
+ if (!obj) return;
109
+ const entries = [];
110
+ for (const [key, cssVar] of Object.entries(map)) {
111
+ const val = obj[key];
112
+ if (val == null) continue;
113
+ if (typeof val === "number") {
114
+ entries.push(` ${cssVar}: ${val}px;`);
115
+ } else {
116
+ entries.push(` ${cssVar}: ${String(val)};`);
117
+ }
118
+ }
119
+ if (entries.length > 0) {
120
+ lines.push(` /* ${comment} */`);
121
+ lines.push(...entries);
122
+ lines.push("");
123
+ }
124
+ }
125
+ lines.push(":root {");
126
+ addSection(theme.colors, COLOR_MAP, "Colors");
127
+ addSection(theme.typography, TYPOGRAPHY_MAP, "Typography");
128
+ addSection(theme.shape, SHAPE_MAP, "Shape");
129
+ addSection(theme.spacing, SPACING_MAP, "Spacing");
130
+ addSection(theme.layout, LAYOUT_MAP, "Layout");
131
+ if (lines.length > 1 && lines[lines.length - 1] === "") {
132
+ lines.pop();
133
+ }
134
+ lines.push("}");
135
+ return lines.join("\n") + "\n";
136
+ }
137
+ var REVERSE_MAP = {};
138
+ function buildReverseMap() {
139
+ if (Object.keys(REVERSE_MAP).length > 0) return;
140
+ const sections = [
141
+ ["colors", COLOR_MAP],
142
+ ["typography", TYPOGRAPHY_MAP],
143
+ ["shape", SHAPE_MAP],
144
+ ["spacing", SPACING_MAP],
145
+ ["layout", LAYOUT_MAP]
146
+ ];
147
+ for (const [sectionKey, map] of sections) {
148
+ for (const [propKey, cssVar] of Object.entries(map)) {
149
+ REVERSE_MAP[cssVar] = [sectionKey, propKey];
150
+ }
151
+ }
152
+ }
153
+ var NUMERIC_PROPS = /* @__PURE__ */ new Set([
154
+ "base",
155
+ "sectionGap",
156
+ "fieldGap",
157
+ "inputPaddingX",
158
+ "inputPaddingY"
159
+ ]);
160
+ function cssToTheme(css) {
161
+ buildReverseMap();
162
+ const theme = {};
163
+ const varRegex = /(--fc-[\w-]+)\s*:\s*([^;]+);/g;
164
+ let match;
165
+ while ((match = varRegex.exec(css)) !== null) {
166
+ const cssVar = match[1];
167
+ let value = match[2].trim();
168
+ const mapping = REVERSE_MAP[cssVar];
169
+ if (!mapping) continue;
170
+ const [sectionKey, propKey] = mapping;
171
+ if (NUMERIC_PROPS.has(propKey)) {
172
+ const num = parseFloat(value);
173
+ if (!isNaN(num)) {
174
+ value = num;
175
+ }
176
+ }
177
+ if (!theme[sectionKey]) {
178
+ theme[sectionKey] = {};
179
+ }
180
+ theme[sectionKey][propKey] = value;
181
+ }
182
+ return theme;
183
+ }
184
+ function exportCssFile(theme, filename = "fieldcraft-theme.css") {
185
+ const css = themeToCss(theme);
186
+ const blob = new Blob([css], { type: "text/css" });
187
+ const url = URL.createObjectURL(blob);
188
+ const a = document.createElement("a");
189
+ a.href = url;
190
+ a.download = filename;
191
+ a.click();
192
+ URL.revokeObjectURL(url);
193
+ }
194
+ function importCssFile(baseTheme, onImport) {
195
+ const input = document.createElement("input");
196
+ input.type = "file";
197
+ input.accept = ".css";
198
+ input.onchange = () => {
199
+ const file = input.files?.[0];
200
+ if (!file) return;
201
+ const reader = new FileReader();
202
+ reader.onload = () => {
203
+ const cssText = reader.result;
204
+ const partial = cssToTheme(cssText);
205
+ const merged = deepMerge(baseTheme, partial);
206
+ onImport(merged);
207
+ };
208
+ reader.readAsText(file);
209
+ };
210
+ input.click();
211
+ }
212
+ function deepMerge(base, partial) {
213
+ const result = { ...base };
214
+ for (const key of Object.keys(partial)) {
215
+ const partialSection = partial[key];
216
+ if (partialSection && typeof partialSection === "object") {
217
+ result[key] = {
218
+ ...base[key],
219
+ ...partialSection
220
+ };
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+
226
+ // src/theme-editor/palette-generator.ts
227
+ function generatePalette(baseHex) {
228
+ const [h, s, l] = hexToHsl(baseHex);
229
+ const secH = (h + 180) % 360;
230
+ const isLightBase = l > 50;
231
+ return {
232
+ // Primary
233
+ primary: hslToHex(h, s, clamp(l, 30, 60)),
234
+ primaryForeground: isLightBase ? "#ffffff" : "#ffffff",
235
+ // Secondary (complementary)
236
+ secondary: hslToHex(secH, Math.max(s - 15, 10), clamp(l, 35, 55)),
237
+ secondaryForeground: "#ffffff",
238
+ // Surfaces
239
+ surface: hslToHex(h, Math.max(s - 35, 3), 97),
240
+ background: "#F4F7F8",
241
+ inputBackground: hslToHex(h, Math.max(s - 40, 2), 99),
242
+ // Text
243
+ text: hslToHex(h, Math.max(s - 30, 5), 12),
244
+ textMuted: hslToHex(h, Math.max(s - 30, 5), 45),
245
+ textDisabled: hslToHex(h, Math.max(s - 35, 3), 65),
246
+ // Borders
247
+ border: hslToHex(h, Math.max(s - 30, 5), 85),
248
+ borderFocus: hslToHex(h, s, clamp(l, 35, 55)),
249
+ // Semantic — Drafting Teal palette
250
+ error: "#B04A3C",
251
+ errorForeground: "#FFFFFF",
252
+ warning: "#C98A2E",
253
+ success: "#2E7D5B"
254
+ };
255
+ }
256
+ function applyPaletteToTheme(theme, palette) {
257
+ return {
258
+ ...theme,
259
+ colors: {
260
+ ...theme.colors,
261
+ ...palette
262
+ }
263
+ };
264
+ }
265
+ function hexToHsl(hex) {
266
+ const rgb = hexToRgb(hex);
267
+ const r = rgb[0] / 255;
268
+ const g = rgb[1] / 255;
269
+ const b = rgb[2] / 255;
270
+ const max = Math.max(r, g, b);
271
+ const min = Math.min(r, g, b);
272
+ const l = (max + min) / 2;
273
+ if (max === min) {
274
+ return [0, 0, Math.round(l * 100)];
275
+ }
276
+ const d = max - min;
277
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
278
+ let h = 0;
279
+ if (max === r) {
280
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
281
+ } else if (max === g) {
282
+ h = ((b - r) / d + 2) / 6;
283
+ } else {
284
+ h = ((r - g) / d + 4) / 6;
285
+ }
286
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
287
+ }
288
+ function hslToHex(h, s, l) {
289
+ const sNorm = s / 100;
290
+ const lNorm = l / 100;
291
+ const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
292
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
293
+ const m = lNorm - c / 2;
294
+ let r = 0, g = 0, b = 0;
295
+ if (h < 60) {
296
+ r = c;
297
+ g = x;
298
+ b = 0;
299
+ } else if (h < 120) {
300
+ r = x;
301
+ g = c;
302
+ b = 0;
303
+ } else if (h < 180) {
304
+ r = 0;
305
+ g = c;
306
+ b = x;
307
+ } else if (h < 240) {
308
+ r = 0;
309
+ g = x;
310
+ b = c;
311
+ } else if (h < 300) {
312
+ r = x;
313
+ g = 0;
314
+ b = c;
315
+ } else {
316
+ r = c;
317
+ g = 0;
318
+ b = x;
319
+ }
320
+ return rgbToHex(
321
+ Math.round((r + m) * 255),
322
+ Math.round((g + m) * 255),
323
+ Math.round((b + m) * 255)
324
+ );
325
+ }
326
+ function hexToRgb(hex) {
327
+ const cleaned = hex.replace("#", "");
328
+ const full = cleaned.length === 3 ? cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2] : cleaned;
329
+ return [
330
+ parseInt(full.substring(0, 2), 16),
331
+ parseInt(full.substring(2, 4), 16),
332
+ parseInt(full.substring(4, 6), 16)
333
+ ];
334
+ }
335
+ function rgbToHex(r, g, b) {
336
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
337
+ }
338
+ function clamp(value, min, max) {
339
+ return Math.max(min, Math.min(max, value));
340
+ }
341
+ var SWATCH_KEYS = [
342
+ { key: "primary", label: "Primary" },
343
+ { key: "secondary", label: "Secondary" },
344
+ { key: "surface", label: "Surface" },
345
+ { key: "background", label: "Background" },
346
+ { key: "text", label: "Text" },
347
+ { key: "textMuted", label: "Muted" },
348
+ { key: "border", label: "Border" },
349
+ { key: "error", label: "Error" },
350
+ { key: "warning", label: "Warning" },
351
+ { key: "success", label: "Success" }
352
+ ];
353
+ function PaletteGenerator({ theme, onApply, onClose }) {
354
+ const [baseColor, setBaseColor] = useState(theme.colors?.primary ?? "#3b82f6");
355
+ const palette = useMemo(() => generatePalette(baseColor), [baseColor]);
356
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-palette", children: [
357
+ /* @__PURE__ */ jsxs("div", { className: "fcte-palette__header", children: [
358
+ /* @__PURE__ */ jsx("span", { className: "fcte-palette__title", children: "Generate Palette" }),
359
+ /* @__PURE__ */ jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
360
+ ] }),
361
+ /* @__PURE__ */ jsxs("div", { className: "fcte-palette__body", children: [
362
+ /* @__PURE__ */ jsxs("div", { className: "fcte-palette__picker", children: [
363
+ /* @__PURE__ */ jsx("label", { className: "fcte-palette__label", children: "Base Color" }),
364
+ /* @__PURE__ */ jsxs("div", { className: "fcte-palette__input-row", children: [
365
+ /* @__PURE__ */ jsx(
366
+ "input",
367
+ {
368
+ type: "color",
369
+ value: baseColor,
370
+ onChange: (e) => setBaseColor(e.target.value),
371
+ className: "fcte-field__swatch"
372
+ }
373
+ ),
374
+ /* @__PURE__ */ jsx(
375
+ "input",
376
+ {
377
+ type: "text",
378
+ value: baseColor,
379
+ onChange: (e) => setBaseColor(e.target.value),
380
+ className: "fcte-field__text",
381
+ spellCheck: false
382
+ }
383
+ )
384
+ ] })
385
+ ] }),
386
+ /* @__PURE__ */ jsx("div", { className: "fcte-palette__swatches", children: SWATCH_KEYS.map(({ key, label }) => /* @__PURE__ */ jsxs("div", { className: "fcte-palette__swatch", children: [
387
+ /* @__PURE__ */ jsx(
388
+ "div",
389
+ {
390
+ className: "fcte-palette__swatch-color",
391
+ style: { backgroundColor: palette[key] }
392
+ }
393
+ ),
394
+ /* @__PURE__ */ jsx("div", { className: "fcte-palette__swatch-label", children: label }),
395
+ /* @__PURE__ */ jsx("div", { className: "fcte-palette__swatch-hex", children: palette[key] })
396
+ ] }, key)) }),
397
+ /* @__PURE__ */ jsx(
398
+ "button",
399
+ {
400
+ className: "fcte-btn fcte-btn--primary",
401
+ onClick: () => onApply(applyPaletteToTheme(theme, palette)),
402
+ children: "Apply to Theme"
403
+ }
404
+ )
405
+ ] })
406
+ ] });
407
+ }
408
+
409
+ // src/theme-editor/presets.ts
410
+ var draftingTealDarkPreset = {
411
+ colors: {
412
+ primary: "#63BDB4",
413
+ primaryForeground: "#0F1A1F",
414
+ secondary: "#2F4F4C",
415
+ secondaryForeground: "#E8EFF1",
416
+ error: "#E08072",
417
+ errorForeground: "#0F1A1F",
418
+ warning: "#E0A94F",
419
+ success: "#63BDB4",
420
+ surface: "#16242A",
421
+ background: "#0F1A1F",
422
+ text: "#E8EFF1",
423
+ textMuted: "#8CA1A9",
424
+ textDisabled: "#3D5259",
425
+ border: "#2A3B42",
426
+ borderFocus: "#63BDB4",
427
+ inputBackground: "#16242A"
428
+ },
429
+ typography: {
430
+ fontFamily: "'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif",
431
+ scale: "comfortable",
432
+ questionSize: "1.1875rem",
433
+ labelSize: "0.8125rem",
434
+ helpTextSize: "0.8125rem",
435
+ bodySize: "0.9375rem"
436
+ },
437
+ shape: {
438
+ radius: "none",
439
+ inputRadius: "0px",
440
+ buttonRadius: "0px",
441
+ cardRadius: "0px"
442
+ },
443
+ spacing: {
444
+ base: 16,
445
+ sectionGap: 32,
446
+ fieldGap: 24,
447
+ inputPaddingX: 12,
448
+ inputPaddingY: 10
449
+ },
450
+ layout: {
451
+ maxWidth: "640px",
452
+ alignment: "left",
453
+ progressPosition: "top",
454
+ sectionLayout: "flat"
455
+ }
456
+ };
457
+ var draftingTealPreset = {
458
+ colors: {
459
+ primary: "#1F6B6E",
460
+ primaryForeground: "#FFFFFF",
461
+ secondary: "#B9D1CF",
462
+ secondaryForeground: "#12222A",
463
+ error: "#B04A3C",
464
+ errorForeground: "#FFFFFF",
465
+ warning: "#C98A2E",
466
+ success: "#2E7D5B",
467
+ surface: "#FFFFFF",
468
+ background: "#F4F7F8",
469
+ text: "#12222A",
470
+ textMuted: "#6A7B85",
471
+ textDisabled: "#B9D1CF",
472
+ border: "#DCE4E8",
473
+ borderFocus: "#1F6B6E",
474
+ inputBackground: "#FFFFFF"
475
+ },
476
+ typography: {
477
+ fontFamily: "'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif",
478
+ scale: "comfortable",
479
+ questionSize: "1.1875rem",
480
+ labelSize: "0.8125rem",
481
+ helpTextSize: "0.8125rem",
482
+ bodySize: "0.9375rem"
483
+ },
484
+ shape: {
485
+ radius: "none",
486
+ inputRadius: "0px",
487
+ buttonRadius: "0px",
488
+ cardRadius: "0px"
489
+ },
490
+ spacing: {
491
+ base: 16,
492
+ sectionGap: 32,
493
+ fieldGap: 24,
494
+ inputPaddingX: 12,
495
+ inputPaddingY: 10
496
+ },
497
+ layout: {
498
+ maxWidth: "640px",
499
+ alignment: "left",
500
+ progressPosition: "top",
501
+ sectionLayout: "flat"
502
+ }
503
+ };
504
+ var COMPARISON_PRESETS = {
505
+ "drafting-teal": { label: "Drafting Teal", theme: draftingTealPreset },
506
+ clean: { label: "Clean", theme: cleanPreset },
507
+ dark: { label: "Dark", theme: darkPreset },
508
+ modern: { label: "Modern", theme: modernPreset },
509
+ "high-contrast": { label: "High Contrast", theme: highContrastPreset },
510
+ clinical: { label: "Clinical", theme: clinicalPreset },
511
+ playful: { label: "Playful", theme: playfulPreset }
512
+ };
513
+ function ThemeComparison({ currentTheme, onClose }) {
514
+ const [compareKey, setCompareKey] = useState("clean");
515
+ const compareTheme = COMPARISON_PRESETS[compareKey]?.theme ?? cleanPreset;
516
+ const diffs = getColorDiffs(currentTheme, compareTheme);
517
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-compare", children: [
518
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__header", children: [
519
+ /* @__PURE__ */ jsx("span", { className: "fcte-compare__title", children: "Theme Comparison" }),
520
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__controls", children: [
521
+ /* @__PURE__ */ jsx("label", { className: "fcte-compare__label", children: "Compare with:" }),
522
+ /* @__PURE__ */ jsx(
523
+ "select",
524
+ {
525
+ value: compareKey,
526
+ onChange: (e) => setCompareKey(e.target.value),
527
+ className: "fcte-toolbar__preset",
528
+ children: Object.entries(COMPARISON_PRESETS).map(([key, { label }]) => /* @__PURE__ */ jsx("option", { value: key, children: label }, key))
529
+ }
530
+ ),
531
+ /* @__PURE__ */ jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
532
+ ] })
533
+ ] }),
534
+ diffs.length > 0 && /* @__PURE__ */ jsxs("div", { className: "fcte-compare__diffs", children: [
535
+ /* @__PURE__ */ jsxs("span", { className: "fcte-compare__diff-count", children: [
536
+ diffs.length,
537
+ " color",
538
+ diffs.length !== 1 ? "s" : "",
539
+ " differ"
540
+ ] }),
541
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__diff-list", children: [
542
+ diffs.slice(0, 8).map((d) => /* @__PURE__ */ jsxs("div", { className: "fcte-compare__diff-item", children: [
543
+ /* @__PURE__ */ jsx("span", { className: "fcte-compare__diff-label", children: d.key }),
544
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__diff-swatches", children: [
545
+ /* @__PURE__ */ jsx(
546
+ "div",
547
+ {
548
+ className: "fcte-compare__diff-swatch",
549
+ style: { backgroundColor: d.current },
550
+ title: `Current: ${d.current}`
551
+ }
552
+ ),
553
+ /* @__PURE__ */ jsx("span", { className: "fcte-compare__diff-arrow", children: "\u2192" }),
554
+ /* @__PURE__ */ jsx(
555
+ "div",
556
+ {
557
+ className: "fcte-compare__diff-swatch",
558
+ style: { backgroundColor: d.compare },
559
+ title: `${COMPARISON_PRESETS[compareKey]?.label}: ${d.compare}`
560
+ }
561
+ )
562
+ ] })
563
+ ] }, d.key)),
564
+ diffs.length > 8 && /* @__PURE__ */ jsxs("span", { className: "fcte-compare__diff-more", children: [
565
+ "+",
566
+ diffs.length - 8,
567
+ " more"
568
+ ] })
569
+ ] })
570
+ ] }),
571
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__panels", children: [
572
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__panel", children: [
573
+ /* @__PURE__ */ jsx("div", { className: "fcte-compare__panel-label", children: "Current Theme" }),
574
+ /* @__PURE__ */ jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsx(
575
+ FormEngineRenderer,
576
+ {
577
+ schema: PREVIEW_SCHEMA,
578
+ theme: currentTheme,
579
+ onSubmit: () => {
580
+ }
581
+ }
582
+ ) })
583
+ ] }),
584
+ /* @__PURE__ */ jsxs("div", { className: "fcte-compare__panel", children: [
585
+ /* @__PURE__ */ jsx("div", { className: "fcte-compare__panel-label", children: COMPARISON_PRESETS[compareKey]?.label ?? "Preset" }),
586
+ /* @__PURE__ */ jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsx(
587
+ FormEngineRenderer,
588
+ {
589
+ schema: PREVIEW_SCHEMA,
590
+ theme: compareTheme,
591
+ onSubmit: () => {
592
+ }
593
+ }
594
+ ) })
595
+ ] })
596
+ ] })
597
+ ] });
598
+ }
599
+ function getColorDiffs(a, b) {
600
+ const diffs = [];
601
+ const aColors = a.colors ?? {};
602
+ const bColors = b.colors ?? {};
603
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(aColors), ...Object.keys(bColors)]);
604
+ for (const key of allKeys) {
605
+ const aVal = aColors[key] ?? "";
606
+ const bVal = bColors[key] ?? "";
607
+ if (aVal.toLowerCase() !== bVal.toLowerCase() && (aVal || bVal)) {
608
+ diffs.push({ key, current: aVal || "(unset)", compare: bVal || "(unset)" });
609
+ }
610
+ }
611
+ return diffs;
612
+ }
613
+ var ThemeCtx = createContext({});
614
+ function useEditorTheme() {
615
+ return useContext(ThemeCtx);
616
+ }
617
+ function themeToCssVars(theme) {
618
+ const vars = {};
619
+ if (theme.background) vars["--fcte-bg"] = theme.background;
620
+ if (theme.surface) vars["--fcte-surface"] = theme.surface;
621
+ if (theme.surfaceHover) vars["--fcte-surface-hover"] = theme.surfaceHover;
622
+ if (theme.text) vars["--fcte-text"] = theme.text;
623
+ if (theme.textMuted) vars["--fcte-text-muted"] = theme.textMuted;
624
+ if (theme.textDim) vars["--fcte-text-dim"] = theme.textDim;
625
+ if (theme.border) vars["--fcte-border"] = theme.border;
626
+ if (theme.borderStrong) vars["--fcte-border-strong"] = theme.borderStrong;
627
+ if (theme.inputBackground) vars["--fcte-input-bg"] = theme.inputBackground;
628
+ if (theme.accent) vars["--fcte-accent"] = theme.accent;
629
+ if (theme.accentForeground) vars["--fcte-accent-text"] = theme.accentForeground;
630
+ return vars;
631
+ }
632
+ function ThemeEditorThemeProvider({ theme, children }) {
633
+ const resolved = theme ?? {};
634
+ const cssVars = useMemo(() => themeToCssVars(resolved), [resolved]);
635
+ return /* @__PURE__ */ jsx(ThemeCtx.Provider, { value: resolved, children: /* @__PURE__ */ jsx("div", { "data-fcte-root": "", style: cssVars, className: "fcte-provider-root", children }) });
636
+ }
637
+ var PRESETS = {
638
+ "drafting-teal": draftingTealPreset,
639
+ clean: cleanPreset,
640
+ dark: darkPreset,
641
+ modern: modernPreset,
642
+ "high-contrast": highContrastPreset,
643
+ clinical: clinicalPreset,
644
+ playful: playfulPreset
645
+ };
646
+ var SECTIONS = [
647
+ {
648
+ id: "colors",
649
+ label: "Colors",
650
+ themeKey: "colors",
651
+ fields: [
652
+ { kind: "color", key: "primary", label: "Primary" },
653
+ { kind: "color", key: "primaryForeground", label: "Primary Foreground" },
654
+ { kind: "color", key: "secondary", label: "Secondary" },
655
+ { kind: "color", key: "secondaryForeground", label: "Secondary Foreground" },
656
+ { kind: "color", key: "error", label: "Error" },
657
+ { kind: "color", key: "errorForeground", label: "Error Foreground" },
658
+ { kind: "color", key: "warning", label: "Warning" },
659
+ { kind: "color", key: "success", label: "Success" },
660
+ { kind: "color", key: "surface", label: "Surface" },
661
+ { kind: "color", key: "background", label: "Background" },
662
+ { kind: "color", key: "text", label: "Text" },
663
+ { kind: "color", key: "textMuted", label: "Text Muted" },
664
+ { kind: "color", key: "textDisabled", label: "Text Disabled" },
665
+ { kind: "color", key: "border", label: "Border" },
666
+ { kind: "color", key: "borderFocus", label: "Border Focus" },
667
+ { kind: "color", key: "inputBackground", label: "Input Background" }
668
+ ]
669
+ },
670
+ {
671
+ id: "typography",
672
+ label: "Typography",
673
+ themeKey: "typography",
674
+ fields: [
675
+ { kind: "text", key: "fontFamily", label: "Font Family", placeholder: "Inter, system-ui, sans-serif" },
676
+ {
677
+ kind: "select",
678
+ key: "scale",
679
+ label: "Scale",
680
+ options: [
681
+ { label: "Compact", value: "compact" },
682
+ { label: "Comfortable", value: "comfortable" },
683
+ { label: "Spacious", value: "spacious" }
684
+ ]
685
+ },
686
+ { kind: "text", key: "questionSize", label: "Question Size", placeholder: "1.125rem" },
687
+ { kind: "text", key: "labelSize", label: "Label Size", placeholder: "0.875rem" },
688
+ { kind: "text", key: "helpTextSize", label: "Help Text Size", placeholder: "0.8125rem" },
689
+ { kind: "text", key: "bodySize", label: "Body Size", placeholder: "0.9375rem" }
690
+ ]
691
+ },
692
+ {
693
+ id: "shape",
694
+ label: "Shape",
695
+ themeKey: "shape",
696
+ fields: [
697
+ {
698
+ kind: "select",
699
+ key: "radius",
700
+ label: "Radius",
701
+ options: [
702
+ { label: "None", value: "none" },
703
+ { label: "Small", value: "sm" },
704
+ { label: "Medium", value: "md" },
705
+ { label: "Large", value: "lg" },
706
+ { label: "Full", value: "full" }
707
+ ]
708
+ },
709
+ { kind: "text", key: "inputRadius", label: "Input Radius", placeholder: "8px" },
710
+ { kind: "text", key: "buttonRadius", label: "Button Radius", placeholder: "8px" },
711
+ { kind: "text", key: "cardRadius", label: "Card Radius", placeholder: "12px" }
712
+ ]
713
+ },
714
+ {
715
+ id: "spacing",
716
+ label: "Spacing",
717
+ themeKey: "spacing",
718
+ fields: [
719
+ { kind: "number", key: "base", label: "Base", min: 4, max: 32, suffix: "px" },
720
+ { kind: "number", key: "sectionGap", label: "Section Gap", min: 0, max: 64, suffix: "px" },
721
+ { kind: "number", key: "fieldGap", label: "Field Gap", min: 0, max: 64, suffix: "px" },
722
+ { kind: "number", key: "inputPaddingX", label: "Input Padding X", min: 0, max: 32, suffix: "px" },
723
+ { kind: "number", key: "inputPaddingY", label: "Input Padding Y", min: 0, max: 32, suffix: "px" }
724
+ ]
725
+ },
726
+ {
727
+ id: "layout",
728
+ label: "Layout",
729
+ themeKey: "layout",
730
+ fields: [
731
+ { kind: "text", key: "maxWidth", label: "Max Width", placeholder: "640px" },
732
+ {
733
+ kind: "select",
734
+ key: "alignment",
735
+ label: "Alignment",
736
+ options: [
737
+ { label: "Left", value: "left" },
738
+ { label: "Center", value: "center" }
739
+ ]
740
+ },
741
+ {
742
+ kind: "select",
743
+ key: "progressPosition",
744
+ label: "Progress Position",
745
+ options: [
746
+ { label: "Top", value: "top" },
747
+ { label: "Bottom", value: "bottom" },
748
+ { label: "None", value: "none" }
749
+ ]
750
+ },
751
+ {
752
+ kind: "select",
753
+ key: "sectionLayout",
754
+ label: "Section Layout",
755
+ options: [
756
+ { label: "Card", value: "card" },
757
+ { label: "Flat", value: "flat" },
758
+ { label: "Bordered", value: "bordered" }
759
+ ]
760
+ }
761
+ ]
762
+ }
763
+ ];
764
+ function ThemeEditorInner({
765
+ initialTheme,
766
+ onChange,
767
+ onSave,
768
+ theme: chromeTheme,
769
+ height,
770
+ width,
771
+ className,
772
+ toolbarExtra,
773
+ showPreview = true
774
+ }) {
775
+ const [theme, setTheme] = useState(initialTheme ?? draftingTealPreset);
776
+ const [activeSection, setActiveSection] = useState("colors");
777
+ const [presetKey, setPresetKey] = useState(initialTheme ? "custom" : "drafting-teal");
778
+ const [showPalette, setShowPalette] = useState(false);
779
+ const [showComparison, setShowComparison] = useState(false);
780
+ const themeRef = useRef(theme);
781
+ themeRef.current = theme;
782
+ useEffect(() => {
783
+ function handleKeyDown(e) {
784
+ if ((e.metaKey || e.ctrlKey) && e.key === "s") {
785
+ e.preventDefault();
786
+ onSave?.(themeRef.current);
787
+ }
788
+ }
789
+ window.addEventListener("keydown", handleKeyDown);
790
+ return () => window.removeEventListener("keydown", handleKeyDown);
791
+ }, [onSave]);
792
+ const updateField = useCallback(
793
+ (section, key, value) => {
794
+ setTheme((prev) => {
795
+ const next = {
796
+ ...prev,
797
+ [section]: { ...prev[section], [key]: value }
798
+ };
799
+ onChange?.(next);
800
+ return next;
801
+ });
802
+ setPresetKey("custom");
803
+ },
804
+ [onChange]
805
+ );
806
+ const loadPreset = useCallback(
807
+ (key) => {
808
+ const preset = PRESETS[key];
809
+ if (preset) {
810
+ setTheme(preset);
811
+ setPresetKey(key);
812
+ onChange?.(preset);
813
+ }
814
+ },
815
+ [onChange]
816
+ );
817
+ const exportJson = useCallback(() => {
818
+ const blob = new Blob([JSON.stringify(theme, null, 2)], { type: "application/json" });
819
+ const url = URL.createObjectURL(blob);
820
+ const a = document.createElement("a");
821
+ a.href = url;
822
+ a.download = "fieldcraft-theme.json";
823
+ a.click();
824
+ URL.revokeObjectURL(url);
825
+ }, [theme]);
826
+ const importJson = useCallback(() => {
827
+ const input = document.createElement("input");
828
+ input.type = "file";
829
+ input.accept = ".json";
830
+ input.onchange = () => {
831
+ const file = input.files?.[0];
832
+ if (!file) return;
833
+ const reader = new FileReader();
834
+ reader.onload = () => {
835
+ try {
836
+ const parsed = JSON.parse(reader.result);
837
+ setTheme(parsed);
838
+ setPresetKey("custom");
839
+ onChange?.(parsed);
840
+ } catch {
841
+ }
842
+ };
843
+ reader.readAsText(file);
844
+ };
845
+ input.click();
846
+ }, [onChange]);
847
+ const exportCss = useCallback(() => {
848
+ exportCssFile(theme);
849
+ }, [theme]);
850
+ const importCss = useCallback(() => {
851
+ importCssFile(theme, (merged) => {
852
+ setTheme(merged);
853
+ setPresetKey("custom");
854
+ onChange?.(merged);
855
+ });
856
+ }, [theme, onChange]);
857
+ const currentSection = useMemo(
858
+ () => SECTIONS.find((s) => s.id === activeSection),
859
+ [activeSection]
860
+ );
861
+ const sectionValues = theme[currentSection.themeKey] ?? {};
862
+ return /* @__PURE__ */ jsx(ThemeEditorThemeProvider, { theme: chromeTheme, children: /* @__PURE__ */ jsxs(
863
+ "div",
864
+ {
865
+ className: `fcte-root${className ? ` ${className}` : ""}`,
866
+ style: {
867
+ height: height ?? "100%",
868
+ width: width ?? "100%"
869
+ },
870
+ children: [
871
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar", children: [
872
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar__left", children: [
873
+ /* @__PURE__ */ jsx("span", { className: "fcte-toolbar__title", children: "Theme Editor" }),
874
+ /* @__PURE__ */ jsxs(
875
+ "select",
876
+ {
877
+ value: presetKey,
878
+ onChange: (e) => loadPreset(e.target.value),
879
+ className: "fcte-toolbar__preset",
880
+ children: [
881
+ /* @__PURE__ */ jsx("option", { value: "custom", disabled: true, children: "Custom" }),
882
+ Object.keys(PRESETS).map((k) => /* @__PURE__ */ jsx("option", { value: k, children: k.charAt(0).toUpperCase() + k.slice(1).replace("-", " ") }, k))
883
+ ]
884
+ }
885
+ )
886
+ ] }),
887
+ /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar__right", children: [
888
+ toolbarExtra,
889
+ /* @__PURE__ */ jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import JSON" }),
890
+ /* @__PURE__ */ jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export JSON" }),
891
+ /* @__PURE__ */ jsx("button", { onClick: importCss, className: "fcte-btn fcte-btn--secondary", children: "Import CSS" }),
892
+ /* @__PURE__ */ jsx("button", { onClick: exportCss, className: "fcte-btn fcte-btn--secondary", children: "Export CSS" }),
893
+ /* @__PURE__ */ jsx(
894
+ "button",
895
+ {
896
+ onClick: () => {
897
+ setShowPalette((v) => !v);
898
+ setShowComparison(false);
899
+ },
900
+ className: `fcte-btn ${showPalette ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
901
+ children: "Palette"
902
+ }
903
+ ),
904
+ /* @__PURE__ */ jsx(
905
+ "button",
906
+ {
907
+ onClick: () => {
908
+ setShowComparison((v) => !v);
909
+ setShowPalette(false);
910
+ },
911
+ className: `fcte-btn ${showComparison ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
912
+ children: "Compare"
913
+ }
914
+ ),
915
+ onSave && /* @__PURE__ */ jsx("button", { onClick: () => onSave(theme), className: "fcte-btn fcte-btn--primary", children: "Save" })
916
+ ] })
917
+ ] }),
918
+ showPalette && /* @__PURE__ */ jsx(
919
+ PaletteGenerator,
920
+ {
921
+ theme,
922
+ onApply: (newTheme) => {
923
+ setTheme(newTheme);
924
+ setPresetKey("custom");
925
+ onChange?.(newTheme);
926
+ setShowPalette(false);
927
+ },
928
+ onClose: () => setShowPalette(false)
929
+ }
930
+ ),
931
+ showComparison ? /* @__PURE__ */ jsx(
932
+ ThemeComparison,
933
+ {
934
+ currentTheme: theme,
935
+ onClose: () => setShowComparison(false)
936
+ }
937
+ ) : /* @__PURE__ */ jsxs("div", { className: "fcte-body", children: [
938
+ /* @__PURE__ */ jsxs("div", { className: "fcte-editor", children: [
939
+ /* @__PURE__ */ jsx("div", { className: "fcte-section-select", children: /* @__PURE__ */ jsx(
940
+ "select",
941
+ {
942
+ value: activeSection,
943
+ onChange: (e) => setActiveSection(e.target.value),
944
+ className: "fcte-field__select",
945
+ children: SECTIONS.map((s) => /* @__PURE__ */ jsx("option", { value: s.id, children: s.label }, s.id))
946
+ }
947
+ ) }),
948
+ /* @__PURE__ */ jsx("div", { className: "fcte-fields", children: currentSection.fields.map((field) => {
949
+ const val = sectionValues[field.key];
950
+ if (field.kind === "color") {
951
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
952
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
953
+ /* @__PURE__ */ jsxs("div", { className: "fcte-field__color-row", children: [
954
+ /* @__PURE__ */ jsx(
955
+ "input",
956
+ {
957
+ type: "color",
958
+ value: typeof val === "string" ? val : "#000000",
959
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
960
+ className: "fcte-field__swatch"
961
+ }
962
+ ),
963
+ /* @__PURE__ */ jsx(
964
+ "input",
965
+ {
966
+ type: "text",
967
+ value: typeof val === "string" ? val : "",
968
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
969
+ className: "fcte-field__text",
970
+ spellCheck: false
971
+ }
972
+ )
973
+ ] })
974
+ ] }, field.key);
975
+ }
976
+ if (field.kind === "select") {
977
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
978
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
979
+ /* @__PURE__ */ jsx(
980
+ "select",
981
+ {
982
+ value: typeof val === "string" ? val : "",
983
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
984
+ className: "fcte-field__select",
985
+ children: field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
986
+ }
987
+ )
988
+ ] }, field.key);
989
+ }
990
+ if (field.kind === "number") {
991
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
992
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
993
+ /* @__PURE__ */ jsxs("div", { className: "fcte-field__number-row", children: [
994
+ /* @__PURE__ */ jsx(
995
+ "input",
996
+ {
997
+ type: "number",
998
+ value: typeof val === "number" ? val : 0,
999
+ onChange: (e) => updateField(currentSection.themeKey, field.key, Number(e.target.value)),
1000
+ min: field.min,
1001
+ max: field.max,
1002
+ className: "fcte-field__number"
1003
+ }
1004
+ ),
1005
+ field.suffix && /* @__PURE__ */ jsx("span", { className: "fcte-field__suffix", children: field.suffix })
1006
+ ] })
1007
+ ] }, field.key);
1008
+ }
1009
+ return /* @__PURE__ */ jsxs("div", { className: "fcte-field", children: [
1010
+ /* @__PURE__ */ jsx("label", { className: "fcte-field__label", children: field.label }),
1011
+ /* @__PURE__ */ jsx(
1012
+ "input",
1013
+ {
1014
+ type: "text",
1015
+ value: typeof val === "string" ? val : "",
1016
+ onChange: (e) => updateField(currentSection.themeKey, field.key, e.target.value),
1017
+ placeholder: field.placeholder,
1018
+ className: "fcte-field__text",
1019
+ spellCheck: false
1020
+ }
1021
+ )
1022
+ ] }, field.key);
1023
+ }) })
1024
+ ] }),
1025
+ showPreview && /* @__PURE__ */ jsx(
1026
+ "div",
1027
+ {
1028
+ className: "fcte-preview",
1029
+ style: {
1030
+ background: theme.colors?.background || void 0,
1031
+ color: theme.colors?.text || void 0
1032
+ },
1033
+ children: /* @__PURE__ */ jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsx(
1034
+ FormEngineRenderer,
1035
+ {
1036
+ schema: PREVIEW_SCHEMA,
1037
+ theme,
1038
+ onSubmit: () => {
1039
+ }
1040
+ }
1041
+ ) })
1042
+ }
1043
+ )
1044
+ ] })
1045
+ ]
1046
+ }
1047
+ ) });
1048
+ }
1049
+
1050
+ // src/theme-editor/ThemeEditor.tsx
1051
+ var ThemeEditor = requireLicense(ThemeEditorInner, "ThemeEditor");
1052
+
1053
+ // src/theme-editor/theme/presets.ts
1054
+ var themeEditorLightPreset = {
1055
+ background: "#F4F7F8",
1056
+ surface: "#FFFFFF",
1057
+ surfaceHover: "#EDF3F2",
1058
+ text: "#12222A",
1059
+ textMuted: "#6A7B85",
1060
+ textDim: "#96A5AD",
1061
+ border: "#DCE4E8",
1062
+ borderStrong: "#B9D1CF",
1063
+ inputBackground: "#FAFCFC",
1064
+ accent: "#1F6B6E",
1065
+ accentForeground: "#FFFFFF"
1066
+ };
1067
+ var themeEditorDarkPreset = {
1068
+ background: "#0F1A1F",
1069
+ surface: "#16242A",
1070
+ surfaceHover: "#182F31",
1071
+ text: "#E8EFF1",
1072
+ textMuted: "#8CA1A9",
1073
+ textDim: "#5E7680",
1074
+ border: "#2A3B42",
1075
+ borderStrong: "#2F4F4C",
1076
+ inputBackground: "#12222A",
1077
+ accent: "#63BDB4",
1078
+ accentForeground: "#0F1A1F"
1079
+ };
1080
+
1081
+ export { PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner, ThemeEditorThemeProvider, draftingTealDarkPreset, draftingTealPreset, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme };