@squaredr/fieldcraft-pro 1.3.0 → 1.5.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.
@@ -379,7 +379,587 @@ var PREVIEW_SCHEMA = {
379
379
  ],
380
380
  submitAction: { type: "adapter" }
381
381
  };
382
+
383
+ // src/theme-editor/css-utils.ts
384
+ var COLOR_MAP = {
385
+ primary: "--fc-primary",
386
+ primaryForeground: "--fc-primary-foreground",
387
+ secondary: "--fc-secondary",
388
+ secondaryForeground: "--fc-secondary-foreground",
389
+ error: "--fc-error",
390
+ errorForeground: "--fc-error-foreground",
391
+ warning: "--fc-warning",
392
+ success: "--fc-success",
393
+ surface: "--fc-surface",
394
+ background: "--fc-background",
395
+ text: "--fc-text",
396
+ textMuted: "--fc-text-muted",
397
+ textDisabled: "--fc-text-disabled",
398
+ border: "--fc-border",
399
+ borderFocus: "--fc-border-focus",
400
+ inputBackground: "--fc-input-background"
401
+ };
402
+ var TYPOGRAPHY_MAP = {
403
+ fontFamily: "--fc-font-family",
404
+ scale: "--fc-scale",
405
+ questionSize: "--fc-question-size",
406
+ labelSize: "--fc-label-size",
407
+ helpTextSize: "--fc-help-text-size",
408
+ bodySize: "--fc-body-size"
409
+ };
410
+ var SHAPE_MAP = {
411
+ radius: "--fc-radius",
412
+ inputRadius: "--fc-input-radius",
413
+ buttonRadius: "--fc-button-radius",
414
+ cardRadius: "--fc-card-radius"
415
+ };
416
+ var SPACING_MAP = {
417
+ base: "--fc-spacing-base",
418
+ sectionGap: "--fc-section-gap",
419
+ fieldGap: "--fc-field-gap",
420
+ inputPaddingX: "--fc-input-padding-x",
421
+ inputPaddingY: "--fc-input-padding-y"
422
+ };
423
+ var LAYOUT_MAP = {
424
+ maxWidth: "--fc-max-width",
425
+ alignment: "--fc-alignment",
426
+ progressPosition: "--fc-progress-position",
427
+ sectionLayout: "--fc-section-layout"
428
+ };
429
+ function themeToCss(theme) {
430
+ const lines = [];
431
+ function addSection(obj, map, comment) {
432
+ if (!obj) return;
433
+ const entries = [];
434
+ for (const [key, cssVar] of Object.entries(map)) {
435
+ const val = obj[key];
436
+ if (val == null) continue;
437
+ if (typeof val === "number") {
438
+ entries.push(` ${cssVar}: ${val}px;`);
439
+ } else {
440
+ entries.push(` ${cssVar}: ${String(val)};`);
441
+ }
442
+ }
443
+ if (entries.length > 0) {
444
+ lines.push(` /* ${comment} */`);
445
+ lines.push(...entries);
446
+ lines.push("");
447
+ }
448
+ }
449
+ lines.push(":root {");
450
+ addSection(theme.colors, COLOR_MAP, "Colors");
451
+ addSection(theme.typography, TYPOGRAPHY_MAP, "Typography");
452
+ addSection(theme.shape, SHAPE_MAP, "Shape");
453
+ addSection(theme.spacing, SPACING_MAP, "Spacing");
454
+ addSection(theme.layout, LAYOUT_MAP, "Layout");
455
+ if (lines.length > 1 && lines[lines.length - 1] === "") {
456
+ lines.pop();
457
+ }
458
+ lines.push("}");
459
+ return lines.join("\n") + "\n";
460
+ }
461
+ var REVERSE_MAP = {};
462
+ function buildReverseMap() {
463
+ if (Object.keys(REVERSE_MAP).length > 0) return;
464
+ const sections = [
465
+ ["colors", COLOR_MAP],
466
+ ["typography", TYPOGRAPHY_MAP],
467
+ ["shape", SHAPE_MAP],
468
+ ["spacing", SPACING_MAP],
469
+ ["layout", LAYOUT_MAP]
470
+ ];
471
+ for (const [sectionKey, map] of sections) {
472
+ for (const [propKey, cssVar] of Object.entries(map)) {
473
+ REVERSE_MAP[cssVar] = [sectionKey, propKey];
474
+ }
475
+ }
476
+ }
477
+ var NUMERIC_PROPS = /* @__PURE__ */ new Set([
478
+ "base",
479
+ "sectionGap",
480
+ "fieldGap",
481
+ "inputPaddingX",
482
+ "inputPaddingY"
483
+ ]);
484
+ function cssToTheme(css) {
485
+ buildReverseMap();
486
+ const theme = {};
487
+ const varRegex = /(--fc-[\w-]+)\s*:\s*([^;]+);/g;
488
+ let match;
489
+ while ((match = varRegex.exec(css)) !== null) {
490
+ const cssVar = match[1];
491
+ let value = match[2].trim();
492
+ const mapping = REVERSE_MAP[cssVar];
493
+ if (!mapping) continue;
494
+ const [sectionKey, propKey] = mapping;
495
+ if (NUMERIC_PROPS.has(propKey)) {
496
+ const num = parseFloat(value);
497
+ if (!isNaN(num)) {
498
+ value = num;
499
+ }
500
+ }
501
+ if (!theme[sectionKey]) {
502
+ theme[sectionKey] = {};
503
+ }
504
+ theme[sectionKey][propKey] = value;
505
+ }
506
+ return theme;
507
+ }
508
+ function exportCssFile(theme, filename = "fieldcraft-theme.css") {
509
+ const css = themeToCss(theme);
510
+ const blob = new Blob([css], { type: "text/css" });
511
+ const url = URL.createObjectURL(blob);
512
+ const a = document.createElement("a");
513
+ a.href = url;
514
+ a.download = filename;
515
+ a.click();
516
+ URL.revokeObjectURL(url);
517
+ }
518
+ function importCssFile(baseTheme, onImport) {
519
+ const input = document.createElement("input");
520
+ input.type = "file";
521
+ input.accept = ".css";
522
+ input.onchange = () => {
523
+ const file = input.files?.[0];
524
+ if (!file) return;
525
+ const reader = new FileReader();
526
+ reader.onload = () => {
527
+ const cssText = reader.result;
528
+ const partial = cssToTheme(cssText);
529
+ const merged = deepMerge(baseTheme, partial);
530
+ onImport(merged);
531
+ };
532
+ reader.readAsText(file);
533
+ };
534
+ input.click();
535
+ }
536
+ function deepMerge(base, partial) {
537
+ const result = { ...base };
538
+ for (const key of Object.keys(partial)) {
539
+ const partialSection = partial[key];
540
+ if (partialSection && typeof partialSection === "object") {
541
+ result[key] = {
542
+ ...base[key],
543
+ ...partialSection
544
+ };
545
+ }
546
+ }
547
+ return result;
548
+ }
549
+
550
+ // src/theme-editor/palette-generator.ts
551
+ function generatePalette(baseHex) {
552
+ const [h, s, l] = hexToHsl(baseHex);
553
+ const secH = (h + 180) % 360;
554
+ const isLightBase = l > 50;
555
+ return {
556
+ // Primary
557
+ primary: hslToHex(h, s, clamp(l, 30, 60)),
558
+ primaryForeground: isLightBase ? "#ffffff" : "#ffffff",
559
+ // Secondary (complementary)
560
+ secondary: hslToHex(secH, Math.max(s - 15, 10), clamp(l, 35, 55)),
561
+ secondaryForeground: "#ffffff",
562
+ // Surfaces
563
+ surface: hslToHex(h, Math.max(s - 35, 3), 97),
564
+ background: "#F4F7F8",
565
+ inputBackground: hslToHex(h, Math.max(s - 40, 2), 99),
566
+ // Text
567
+ text: hslToHex(h, Math.max(s - 30, 5), 12),
568
+ textMuted: hslToHex(h, Math.max(s - 30, 5), 45),
569
+ textDisabled: hslToHex(h, Math.max(s - 35, 3), 65),
570
+ // Borders
571
+ border: hslToHex(h, Math.max(s - 30, 5), 85),
572
+ borderFocus: hslToHex(h, s, clamp(l, 35, 55)),
573
+ // Semantic — Drafting Teal palette
574
+ error: "#B04A3C",
575
+ errorForeground: "#FFFFFF",
576
+ warning: "#C98A2E",
577
+ success: "#2E7D5B"
578
+ };
579
+ }
580
+ function applyPaletteToTheme(theme, palette) {
581
+ return {
582
+ ...theme,
583
+ colors: {
584
+ ...theme.colors,
585
+ ...palette
586
+ }
587
+ };
588
+ }
589
+ function hexToHsl(hex) {
590
+ const rgb = hexToRgb(hex);
591
+ const r = rgb[0] / 255;
592
+ const g = rgb[1] / 255;
593
+ const b = rgb[2] / 255;
594
+ const max = Math.max(r, g, b);
595
+ const min = Math.min(r, g, b);
596
+ const l = (max + min) / 2;
597
+ if (max === min) {
598
+ return [0, 0, Math.round(l * 100)];
599
+ }
600
+ const d = max - min;
601
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
602
+ let h = 0;
603
+ if (max === r) {
604
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
605
+ } else if (max === g) {
606
+ h = ((b - r) / d + 2) / 6;
607
+ } else {
608
+ h = ((r - g) / d + 4) / 6;
609
+ }
610
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
611
+ }
612
+ function hslToHex(h, s, l) {
613
+ const sNorm = s / 100;
614
+ const lNorm = l / 100;
615
+ const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
616
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
617
+ const m = lNorm - c / 2;
618
+ let r = 0, g = 0, b = 0;
619
+ if (h < 60) {
620
+ r = c;
621
+ g = x;
622
+ b = 0;
623
+ } else if (h < 120) {
624
+ r = x;
625
+ g = c;
626
+ b = 0;
627
+ } else if (h < 180) {
628
+ r = 0;
629
+ g = c;
630
+ b = x;
631
+ } else if (h < 240) {
632
+ r = 0;
633
+ g = x;
634
+ b = c;
635
+ } else if (h < 300) {
636
+ r = x;
637
+ g = 0;
638
+ b = c;
639
+ } else {
640
+ r = c;
641
+ g = 0;
642
+ b = x;
643
+ }
644
+ return rgbToHex(
645
+ Math.round((r + m) * 255),
646
+ Math.round((g + m) * 255),
647
+ Math.round((b + m) * 255)
648
+ );
649
+ }
650
+ function hexToRgb(hex) {
651
+ const cleaned = hex.replace("#", "");
652
+ const full = cleaned.length === 3 ? cleaned[0] + cleaned[0] + cleaned[1] + cleaned[1] + cleaned[2] + cleaned[2] : cleaned;
653
+ return [
654
+ parseInt(full.substring(0, 2), 16),
655
+ parseInt(full.substring(2, 4), 16),
656
+ parseInt(full.substring(4, 6), 16)
657
+ ];
658
+ }
659
+ function rgbToHex(r, g, b) {
660
+ return "#" + [r, g, b].map((v) => v.toString(16).padStart(2, "0")).join("");
661
+ }
662
+ function clamp(value, min, max) {
663
+ return Math.max(min, Math.min(max, value));
664
+ }
665
+ var SWATCH_KEYS = [
666
+ { key: "primary", label: "Primary" },
667
+ { key: "secondary", label: "Secondary" },
668
+ { key: "surface", label: "Surface" },
669
+ { key: "background", label: "Background" },
670
+ { key: "text", label: "Text" },
671
+ { key: "textMuted", label: "Muted" },
672
+ { key: "border", label: "Border" },
673
+ { key: "error", label: "Error" },
674
+ { key: "warning", label: "Warning" },
675
+ { key: "success", label: "Success" }
676
+ ];
677
+ function PaletteGenerator({ theme, onApply, onClose }) {
678
+ const [baseColor, setBaseColor] = react.useState(theme.colors?.primary ?? "#3b82f6");
679
+ const palette = react.useMemo(() => generatePalette(baseColor), [baseColor]);
680
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette", children: [
681
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__header", children: [
682
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-palette__title", children: "Generate Palette" }),
683
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
684
+ ] }),
685
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__body", children: [
686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__picker", children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "fcte-palette__label", children: "Base Color" }),
688
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__input-row", children: [
689
+ /* @__PURE__ */ jsxRuntime.jsx(
690
+ "input",
691
+ {
692
+ type: "color",
693
+ value: baseColor,
694
+ onChange: (e) => setBaseColor(e.target.value),
695
+ className: "fcte-field__swatch"
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsxRuntime.jsx(
699
+ "input",
700
+ {
701
+ type: "text",
702
+ value: baseColor,
703
+ onChange: (e) => setBaseColor(e.target.value),
704
+ className: "fcte-field__text",
705
+ spellCheck: false
706
+ }
707
+ )
708
+ ] })
709
+ ] }),
710
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatches", children: SWATCH_KEYS.map(({ key, label }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-palette__swatch", children: [
711
+ /* @__PURE__ */ jsxRuntime.jsx(
712
+ "div",
713
+ {
714
+ className: "fcte-palette__swatch-color",
715
+ style: { backgroundColor: palette[key] }
716
+ }
717
+ ),
718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatch-label", children: label }),
719
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-palette__swatch-hex", children: palette[key] })
720
+ ] }, key)) }),
721
+ /* @__PURE__ */ jsxRuntime.jsx(
722
+ "button",
723
+ {
724
+ className: "fcte-btn fcte-btn--primary",
725
+ onClick: () => onApply(applyPaletteToTheme(theme, palette)),
726
+ children: "Apply to Theme"
727
+ }
728
+ )
729
+ ] })
730
+ ] });
731
+ }
732
+
733
+ // src/theme-editor/presets.ts
734
+ var draftingTealDarkPreset = {
735
+ colors: {
736
+ primary: "#63BDB4",
737
+ primaryForeground: "#0F1A1F",
738
+ secondary: "#2F4F4C",
739
+ secondaryForeground: "#E8EFF1",
740
+ error: "#E08072",
741
+ errorForeground: "#0F1A1F",
742
+ warning: "#E0A94F",
743
+ success: "#63BDB4",
744
+ surface: "#16242A",
745
+ background: "#0F1A1F",
746
+ text: "#E8EFF1",
747
+ textMuted: "#8CA1A9",
748
+ textDisabled: "#3D5259",
749
+ border: "#2A3B42",
750
+ borderFocus: "#63BDB4",
751
+ inputBackground: "#16242A"
752
+ },
753
+ typography: {
754
+ fontFamily: "'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif",
755
+ scale: "comfortable",
756
+ questionSize: "1.1875rem",
757
+ labelSize: "0.8125rem",
758
+ helpTextSize: "0.8125rem",
759
+ bodySize: "0.9375rem"
760
+ },
761
+ shape: {
762
+ radius: "none",
763
+ inputRadius: "0px",
764
+ buttonRadius: "0px",
765
+ cardRadius: "0px"
766
+ },
767
+ spacing: {
768
+ base: 16,
769
+ sectionGap: 32,
770
+ fieldGap: 24,
771
+ inputPaddingX: 12,
772
+ inputPaddingY: 10
773
+ },
774
+ layout: {
775
+ maxWidth: "640px",
776
+ alignment: "left",
777
+ progressPosition: "top",
778
+ sectionLayout: "flat"
779
+ }
780
+ };
781
+ var draftingTealPreset = {
782
+ colors: {
783
+ primary: "#1F6B6E",
784
+ primaryForeground: "#FFFFFF",
785
+ secondary: "#B9D1CF",
786
+ secondaryForeground: "#12222A",
787
+ error: "#B04A3C",
788
+ errorForeground: "#FFFFFF",
789
+ warning: "#C98A2E",
790
+ success: "#2E7D5B",
791
+ surface: "#FFFFFF",
792
+ background: "#F4F7F8",
793
+ text: "#12222A",
794
+ textMuted: "#6A7B85",
795
+ textDisabled: "#B9D1CF",
796
+ border: "#DCE4E8",
797
+ borderFocus: "#1F6B6E",
798
+ inputBackground: "#FFFFFF"
799
+ },
800
+ typography: {
801
+ fontFamily: "'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif",
802
+ scale: "comfortable",
803
+ questionSize: "1.1875rem",
804
+ labelSize: "0.8125rem",
805
+ helpTextSize: "0.8125rem",
806
+ bodySize: "0.9375rem"
807
+ },
808
+ shape: {
809
+ radius: "none",
810
+ inputRadius: "0px",
811
+ buttonRadius: "0px",
812
+ cardRadius: "0px"
813
+ },
814
+ spacing: {
815
+ base: 16,
816
+ sectionGap: 32,
817
+ fieldGap: 24,
818
+ inputPaddingX: 12,
819
+ inputPaddingY: 10
820
+ },
821
+ layout: {
822
+ maxWidth: "640px",
823
+ alignment: "left",
824
+ progressPosition: "top",
825
+ sectionLayout: "flat"
826
+ }
827
+ };
828
+ var COMPARISON_PRESETS = {
829
+ "drafting-teal": { label: "Drafting Teal", theme: draftingTealPreset },
830
+ clean: { label: "Clean", theme: fieldcraftReact.cleanPreset },
831
+ dark: { label: "Dark", theme: fieldcraftReact.darkPreset },
832
+ modern: { label: "Modern", theme: fieldcraftReact.modernPreset },
833
+ "high-contrast": { label: "High Contrast", theme: fieldcraftReact.highContrastPreset },
834
+ clinical: { label: "Clinical", theme: fieldcraftReact.clinicalPreset },
835
+ playful: { label: "Playful", theme: fieldcraftReact.playfulPreset }
836
+ };
837
+ function ThemeComparison({ currentTheme, onClose }) {
838
+ const [compareKey, setCompareKey] = react.useState("clean");
839
+ const compareTheme = COMPARISON_PRESETS[compareKey]?.theme ?? fieldcraftReact.cleanPreset;
840
+ const diffs = getColorDiffs(currentTheme, compareTheme);
841
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare", children: [
842
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__header", children: [
843
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__title", children: "Theme Comparison" }),
844
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__controls", children: [
845
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "fcte-compare__label", children: "Compare with:" }),
846
+ /* @__PURE__ */ jsxRuntime.jsx(
847
+ "select",
848
+ {
849
+ value: compareKey,
850
+ onChange: (e) => setCompareKey(e.target.value),
851
+ className: "fcte-toolbar__preset",
852
+ children: Object.entries(COMPARISON_PRESETS).map(([key, { label }]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: key, children: label }, key))
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "fcte-btn fcte-btn--secondary fcte-btn--sm", onClick: onClose, children: "Close" })
856
+ ] })
857
+ ] }),
858
+ diffs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diffs", children: [
859
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fcte-compare__diff-count", children: [
860
+ diffs.length,
861
+ " color",
862
+ diffs.length !== 1 ? "s" : "",
863
+ " differ"
864
+ ] }),
865
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-list", children: [
866
+ diffs.slice(0, 8).map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-item", children: [
867
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__diff-label", children: d.key }),
868
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__diff-swatches", children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx(
870
+ "div",
871
+ {
872
+ className: "fcte-compare__diff-swatch",
873
+ style: { backgroundColor: d.current },
874
+ title: `Current: ${d.current}`
875
+ }
876
+ ),
877
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "fcte-compare__diff-arrow", children: "\u2192" }),
878
+ /* @__PURE__ */ jsxRuntime.jsx(
879
+ "div",
880
+ {
881
+ className: "fcte-compare__diff-swatch",
882
+ style: { backgroundColor: d.compare },
883
+ title: `${COMPARISON_PRESETS[compareKey]?.label}: ${d.compare}`
884
+ }
885
+ )
886
+ ] })
887
+ ] }, d.key)),
888
+ diffs.length > 8 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "fcte-compare__diff-more", children: [
889
+ "+",
890
+ diffs.length - 8,
891
+ " more"
892
+ ] })
893
+ ] })
894
+ ] }),
895
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panels", children: [
896
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panel", children: [
897
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-label", children: "Current Theme" }),
898
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsxRuntime.jsx(
899
+ fieldcraftReact.FormEngineRenderer,
900
+ {
901
+ schema: PREVIEW_SCHEMA,
902
+ theme: currentTheme,
903
+ onSubmit: () => {
904
+ }
905
+ }
906
+ ) })
907
+ ] }),
908
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-compare__panel", children: [
909
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-label", children: COMPARISON_PRESETS[compareKey]?.label ?? "Preset" }),
910
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-compare__panel-preview", children: /* @__PURE__ */ jsxRuntime.jsx(
911
+ fieldcraftReact.FormEngineRenderer,
912
+ {
913
+ schema: PREVIEW_SCHEMA,
914
+ theme: compareTheme,
915
+ onSubmit: () => {
916
+ }
917
+ }
918
+ ) })
919
+ ] })
920
+ ] })
921
+ ] });
922
+ }
923
+ function getColorDiffs(a, b) {
924
+ const diffs = [];
925
+ const aColors = a.colors ?? {};
926
+ const bColors = b.colors ?? {};
927
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(aColors), ...Object.keys(bColors)]);
928
+ for (const key of allKeys) {
929
+ const aVal = aColors[key] ?? "";
930
+ const bVal = bColors[key] ?? "";
931
+ if (aVal.toLowerCase() !== bVal.toLowerCase() && (aVal || bVal)) {
932
+ diffs.push({ key, current: aVal || "(unset)", compare: bVal || "(unset)" });
933
+ }
934
+ }
935
+ return diffs;
936
+ }
937
+ var ThemeCtx = react.createContext({});
938
+ function useEditorTheme() {
939
+ return react.useContext(ThemeCtx);
940
+ }
941
+ function themeToCssVars(theme) {
942
+ const vars = {};
943
+ if (theme.background) vars["--fcte-bg"] = theme.background;
944
+ if (theme.surface) vars["--fcte-surface"] = theme.surface;
945
+ if (theme.surfaceHover) vars["--fcte-surface-hover"] = theme.surfaceHover;
946
+ if (theme.text) vars["--fcte-text"] = theme.text;
947
+ if (theme.textMuted) vars["--fcte-text-muted"] = theme.textMuted;
948
+ if (theme.textDim) vars["--fcte-text-dim"] = theme.textDim;
949
+ if (theme.border) vars["--fcte-border"] = theme.border;
950
+ if (theme.borderStrong) vars["--fcte-border-strong"] = theme.borderStrong;
951
+ if (theme.inputBackground) vars["--fcte-input-bg"] = theme.inputBackground;
952
+ if (theme.accent) vars["--fcte-accent"] = theme.accent;
953
+ if (theme.accentForeground) vars["--fcte-accent-text"] = theme.accentForeground;
954
+ return vars;
955
+ }
956
+ function ThemeEditorThemeProvider({ theme, children }) {
957
+ const resolved = theme ?? {};
958
+ const cssVars = react.useMemo(() => themeToCssVars(resolved), [resolved]);
959
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeCtx.Provider, { value: resolved, children: /* @__PURE__ */ jsxRuntime.jsx("div", { "data-fcte-root": "", style: cssVars, className: "fcte-provider-root", children }) });
960
+ }
382
961
  var PRESETS = {
962
+ "drafting-teal": draftingTealPreset,
383
963
  clean: fieldcraftReact.cleanPreset,
384
964
  dark: fieldcraftReact.darkPreset,
385
965
  modern: fieldcraftReact.modernPreset,
@@ -509,15 +1089,18 @@ function ThemeEditorInner({
509
1089
  initialTheme,
510
1090
  onChange,
511
1091
  onSave,
1092
+ theme: chromeTheme,
512
1093
  height,
513
1094
  width,
514
1095
  className,
515
1096
  toolbarExtra,
516
1097
  showPreview = true
517
1098
  }) {
518
- const [theme, setTheme] = react.useState(initialTheme ?? fieldcraftReact.cleanPreset);
1099
+ const [theme, setTheme] = react.useState(initialTheme ?? draftingTealPreset);
519
1100
  const [activeSection, setActiveSection] = react.useState("colors");
520
- const [presetKey, setPresetKey] = react.useState("custom");
1101
+ const [presetKey, setPresetKey] = react.useState(initialTheme ? "custom" : "drafting-teal");
1102
+ const [showPalette, setShowPalette] = react.useState(false);
1103
+ const [showComparison, setShowComparison] = react.useState(false);
521
1104
  const themeRef = react.useRef(theme);
522
1105
  themeRef.current = theme;
523
1106
  react.useEffect(() => {
@@ -585,12 +1168,22 @@ function ThemeEditorInner({
585
1168
  };
586
1169
  input.click();
587
1170
  }, [onChange]);
1171
+ const exportCss = react.useCallback(() => {
1172
+ exportCssFile(theme);
1173
+ }, [theme]);
1174
+ const importCss = react.useCallback(() => {
1175
+ importCssFile(theme, (merged) => {
1176
+ setTheme(merged);
1177
+ setPresetKey("custom");
1178
+ onChange?.(merged);
1179
+ });
1180
+ }, [theme, onChange]);
588
1181
  const currentSection = react.useMemo(
589
1182
  () => SECTIONS.find((s) => s.id === activeSection),
590
1183
  [activeSection]
591
1184
  );
592
1185
  const sectionValues = theme[currentSection.themeKey] ?? {};
593
- return /* @__PURE__ */ jsxRuntime.jsxs(
1186
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeEditorThemeProvider, { theme: chromeTheme, children: /* @__PURE__ */ jsxRuntime.jsxs(
594
1187
  "div",
595
1188
  {
596
1189
  className: `fcte-root${className ? ` ${className}` : ""}`,
@@ -617,22 +1210,65 @@ function ThemeEditorInner({
617
1210
  ] }),
618
1211
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-toolbar__right", children: [
619
1212
  toolbarExtra,
620
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import" }),
621
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export" }),
1213
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importJson, className: "fcte-btn fcte-btn--secondary", children: "Import JSON" }),
1214
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportJson, className: "fcte-btn fcte-btn--secondary", children: "Export JSON" }),
1215
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: importCss, className: "fcte-btn fcte-btn--secondary", children: "Import CSS" }),
1216
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: exportCss, className: "fcte-btn fcte-btn--secondary", children: "Export CSS" }),
1217
+ /* @__PURE__ */ jsxRuntime.jsx(
1218
+ "button",
1219
+ {
1220
+ onClick: () => {
1221
+ setShowPalette((v) => !v);
1222
+ setShowComparison(false);
1223
+ },
1224
+ className: `fcte-btn ${showPalette ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
1225
+ children: "Palette"
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ jsxRuntime.jsx(
1229
+ "button",
1230
+ {
1231
+ onClick: () => {
1232
+ setShowComparison((v) => !v);
1233
+ setShowPalette(false);
1234
+ },
1235
+ className: `fcte-btn ${showComparison ? "fcte-btn--primary" : "fcte-btn--secondary"}`,
1236
+ children: "Compare"
1237
+ }
1238
+ ),
622
1239
  onSave && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onSave(theme), className: "fcte-btn fcte-btn--primary", children: "Save" })
623
1240
  ] })
624
1241
  ] }),
625
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-body", children: [
1242
+ showPalette && /* @__PURE__ */ jsxRuntime.jsx(
1243
+ PaletteGenerator,
1244
+ {
1245
+ theme,
1246
+ onApply: (newTheme) => {
1247
+ setTheme(newTheme);
1248
+ setPresetKey("custom");
1249
+ onChange?.(newTheme);
1250
+ setShowPalette(false);
1251
+ },
1252
+ onClose: () => setShowPalette(false)
1253
+ }
1254
+ ),
1255
+ showComparison ? /* @__PURE__ */ jsxRuntime.jsx(
1256
+ ThemeComparison,
1257
+ {
1258
+ currentTheme: theme,
1259
+ onClose: () => setShowComparison(false)
1260
+ }
1261
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-body", children: [
626
1262
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-editor", children: [
627
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-tabs", children: SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
628
- "button",
1263
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-section-select", children: /* @__PURE__ */ jsxRuntime.jsx(
1264
+ "select",
629
1265
  {
630
- className: `fcte-tab${activeSection === s.id ? " fcte-tab--active" : ""}`,
631
- onClick: () => setActiveSection(s.id),
632
- children: s.label
633
- },
634
- s.id
635
- )) }),
1266
+ value: activeSection,
1267
+ onChange: (e) => setActiveSection(e.target.value),
1268
+ className: "fcte-field__select",
1269
+ children: SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s.id, children: s.label }, s.id))
1270
+ }
1271
+ ) }),
636
1272
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-fields", children: currentSection.fields.map((field) => {
637
1273
  const val = sectionValues[field.key];
638
1274
  if (field.kind === "color") {
@@ -710,24 +1346,68 @@ function ThemeEditorInner({
710
1346
  ] }, field.key);
711
1347
  }) })
712
1348
  ] }),
713
- showPreview && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsxRuntime.jsx(
714
- fieldcraftReact.FormEngineRenderer,
1349
+ showPreview && /* @__PURE__ */ jsxRuntime.jsx(
1350
+ "div",
715
1351
  {
716
- schema: PREVIEW_SCHEMA,
717
- theme,
718
- onSubmit: () => {
719
- }
1352
+ className: "fcte-preview",
1353
+ style: {
1354
+ background: theme.colors?.background || void 0,
1355
+ color: theme.colors?.text || void 0
1356
+ },
1357
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fcte-preview__inner", children: /* @__PURE__ */ jsxRuntime.jsx(
1358
+ fieldcraftReact.FormEngineRenderer,
1359
+ {
1360
+ schema: PREVIEW_SCHEMA,
1361
+ theme,
1362
+ onSubmit: () => {
1363
+ }
1364
+ }
1365
+ ) })
720
1366
  }
721
- ) }) })
1367
+ )
722
1368
  ] })
723
1369
  ]
724
1370
  }
725
- );
1371
+ ) });
726
1372
  }
727
1373
 
728
1374
  // src/theme-editor/ThemeEditor.tsx
729
1375
  var ThemeEditor = requireLicense(ThemeEditorInner, "ThemeEditor");
730
1376
 
1377
+ // src/theme-editor/theme/presets.ts
1378
+ var themeEditorLightPreset = {
1379
+ background: "#F4F7F8",
1380
+ surface: "#FFFFFF",
1381
+ surfaceHover: "#EDF3F2",
1382
+ text: "#12222A",
1383
+ textMuted: "#6A7B85",
1384
+ textDim: "#96A5AD",
1385
+ border: "#DCE4E8",
1386
+ borderStrong: "#B9D1CF",
1387
+ inputBackground: "#FAFCFC",
1388
+ accent: "#1F6B6E",
1389
+ accentForeground: "#FFFFFF"
1390
+ };
1391
+ var themeEditorDarkPreset = {
1392
+ background: "#0F1A1F",
1393
+ surface: "#16242A",
1394
+ surfaceHover: "#182F31",
1395
+ text: "#E8EFF1",
1396
+ textMuted: "#8CA1A9",
1397
+ textDim: "#5E7680",
1398
+ border: "#2A3B42",
1399
+ borderStrong: "#2F4F4C",
1400
+ inputBackground: "#12222A",
1401
+ accent: "#63BDB4",
1402
+ accentForeground: "#0F1A1F"
1403
+ };
1404
+
731
1405
  exports.PREVIEW_SCHEMA = PREVIEW_SCHEMA;
732
1406
  exports.ThemeEditor = ThemeEditor;
733
1407
  exports.ThemeEditorInner = ThemeEditorInner;
1408
+ exports.ThemeEditorThemeProvider = ThemeEditorThemeProvider;
1409
+ exports.draftingTealDarkPreset = draftingTealDarkPreset;
1410
+ exports.draftingTealPreset = draftingTealPreset;
1411
+ exports.themeEditorDarkPreset = themeEditorDarkPreset;
1412
+ exports.themeEditorLightPreset = themeEditorLightPreset;
1413
+ exports.useEditorTheme = useEditorTheme;