@squaredr/fieldcraft-pro 1.6.1 → 1.6.3

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.
@@ -1044,13 +1044,28 @@ function ThemeEditorInner({
1044
1044
  const [theme, setTheme] = useState(resolvedInitial);
1045
1045
  const [activeSection, setActiveSection] = useState("colors");
1046
1046
  const [presetKey, setPresetKey] = useState("custom");
1047
- const [mode, setMode] = useState(
1048
- () => isDarkTheme(resolvedInitial) ? "dark" : "light"
1049
- );
1050
1047
  const [showPalette, setShowPalette] = useState(false);
1051
1048
  const [showComparison, setShowComparison] = useState(false);
1052
1049
  const themeRef = useRef(theme);
1053
1050
  themeRef.current = theme;
1051
+ const hasInitialTheme = useRef(initialTheme != null);
1052
+ useEffect(() => {
1053
+ if (hasInitialTheme.current) return;
1054
+ if (typeof window === "undefined") return;
1055
+ const observer = new MutationObserver(() => {
1056
+ requestAnimationFrame(() => {
1057
+ const resolved = resolveThemeFromDOM();
1058
+ setTheme(resolved);
1059
+ setPresetKey("custom");
1060
+ onChange?.(resolved);
1061
+ });
1062
+ });
1063
+ observer.observe(document.documentElement, {
1064
+ attributes: true,
1065
+ attributeFilter: ["data-theme", "class"]
1066
+ });
1067
+ return () => observer.disconnect();
1068
+ }, [onChange]);
1054
1069
  useEffect(() => {
1055
1070
  function handleKeyDown(e) {
1056
1071
  if ((e.metaKey || e.ctrlKey) && e.key === "s") {
@@ -1076,30 +1091,18 @@ function ThemeEditorInner({
1076
1091
  [onChange]
1077
1092
  );
1078
1093
  const loadPreset = useCallback(
1079
- (key, forceMode) => {
1094
+ (key) => {
1080
1095
  const family = PRESET_FAMILIES[key];
1081
1096
  if (family) {
1082
- const m = forceMode ?? mode;
1083
- const preset = family[m];
1097
+ const variant = isDarkTheme(theme) ? "dark" : "light";
1098
+ const preset = family[variant];
1084
1099
  setTheme(preset);
1085
1100
  setPresetKey(key);
1086
1101
  onChange?.(preset);
1087
1102
  }
1088
1103
  },
1089
- [onChange, mode]
1104
+ [onChange, theme]
1090
1105
  );
1091
- const toggleMode = useCallback(() => {
1092
- setMode((prev) => {
1093
- const next = prev === "light" ? "dark" : "light";
1094
- const family = PRESET_FAMILIES[presetKey];
1095
- if (family) {
1096
- const preset = family[next];
1097
- setTheme(preset);
1098
- onChange?.(preset);
1099
- }
1100
- return next;
1101
- });
1102
- }, [presetKey, onChange]);
1103
1106
  const exportJson = useCallback(() => {
1104
1107
  const blob = new Blob([JSON.stringify(theme, null, 2)], { type: "application/json" });
1105
1108
  const url = URL.createObjectURL(blob);
@@ -1168,15 +1171,6 @@ function ThemeEditorInner({
1168
1171
  Object.entries(PRESET_FAMILIES).map(([k, fam]) => /* @__PURE__ */ jsx("option", { value: k, children: fam.label }, k))
1169
1172
  ]
1170
1173
  }
1171
- ),
1172
- /* @__PURE__ */ jsx(
1173
- "button",
1174
- {
1175
- onClick: toggleMode,
1176
- className: "fcte-btn fcte-btn--secondary fcte-toolbar__mode",
1177
- title: mode === "light" ? "Switch to dark mode" : "Switch to light mode",
1178
- children: mode === "light" ? "\u2600" : "\u263E"
1179
- }
1180
1174
  )
1181
1175
  ] }),
1182
1176
  /* @__PURE__ */ jsxs("div", { className: "fcte-toolbar__right", children: [
package/dist/index.js CHANGED
@@ -8540,13 +8540,28 @@ function ThemeEditorInner({
8540
8540
  const [theme, setTheme] = react.useState(resolvedInitial);
8541
8541
  const [activeSection, setActiveSection] = react.useState("colors");
8542
8542
  const [presetKey, setPresetKey] = react.useState("custom");
8543
- const [mode, setMode] = react.useState(
8544
- () => isDarkTheme(resolvedInitial) ? "dark" : "light"
8545
- );
8546
8543
  const [showPalette, setShowPalette] = react.useState(false);
8547
8544
  const [showComparison, setShowComparison] = react.useState(false);
8548
8545
  const themeRef = react.useRef(theme);
8549
8546
  themeRef.current = theme;
8547
+ const hasInitialTheme = react.useRef(initialTheme != null);
8548
+ react.useEffect(() => {
8549
+ if (hasInitialTheme.current) return;
8550
+ if (typeof window === "undefined") return;
8551
+ const observer = new MutationObserver(() => {
8552
+ requestAnimationFrame(() => {
8553
+ const resolved = resolveThemeFromDOM();
8554
+ setTheme(resolved);
8555
+ setPresetKey("custom");
8556
+ onChange?.(resolved);
8557
+ });
8558
+ });
8559
+ observer.observe(document.documentElement, {
8560
+ attributes: true,
8561
+ attributeFilter: ["data-theme", "class"]
8562
+ });
8563
+ return () => observer.disconnect();
8564
+ }, [onChange]);
8550
8565
  react.useEffect(() => {
8551
8566
  function handleKeyDown(e) {
8552
8567
  if ((e.metaKey || e.ctrlKey) && e.key === "s") {
@@ -8572,30 +8587,18 @@ function ThemeEditorInner({
8572
8587
  [onChange]
8573
8588
  );
8574
8589
  const loadPreset = react.useCallback(
8575
- (key, forceMode) => {
8590
+ (key) => {
8576
8591
  const family = PRESET_FAMILIES[key];
8577
8592
  if (family) {
8578
- const m = forceMode ?? mode;
8579
- const preset = family[m];
8593
+ const variant = isDarkTheme(theme) ? "dark" : "light";
8594
+ const preset = family[variant];
8580
8595
  setTheme(preset);
8581
8596
  setPresetKey(key);
8582
8597
  onChange?.(preset);
8583
8598
  }
8584
8599
  },
8585
- [onChange, mode]
8600
+ [onChange, theme]
8586
8601
  );
8587
- const toggleMode = react.useCallback(() => {
8588
- setMode((prev) => {
8589
- const next = prev === "light" ? "dark" : "light";
8590
- const family = PRESET_FAMILIES[presetKey];
8591
- if (family) {
8592
- const preset = family[next];
8593
- setTheme(preset);
8594
- onChange?.(preset);
8595
- }
8596
- return next;
8597
- });
8598
- }, [presetKey, onChange]);
8599
8602
  const exportJson = react.useCallback(() => {
8600
8603
  const blob = new Blob([JSON.stringify(theme, null, 2)], { type: "application/json" });
8601
8604
  const url = URL.createObjectURL(blob);
@@ -8664,15 +8667,6 @@ function ThemeEditorInner({
8664
8667
  Object.entries(PRESET_FAMILIES).map(([k, fam]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: k, children: fam.label }, k))
8665
8668
  ]
8666
8669
  }
8667
- ),
8668
- /* @__PURE__ */ jsxRuntime.jsx(
8669
- "button",
8670
- {
8671
- onClick: toggleMode,
8672
- className: "fcte-btn fcte-btn--secondary fcte-toolbar__mode",
8673
- title: mode === "light" ? "Switch to dark mode" : "Switch to light mode",
8674
- children: mode === "light" ? "\u2600" : "\u263E"
8675
- }
8676
8670
  )
8677
8671
  ] }),
8678
8672
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-toolbar__right", children: [
@@ -8872,7 +8866,7 @@ var themeEditorDarkPreset = {
8872
8866
  // src/index.ts
8873
8867
  if (typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
8874
8868
  const _fc_banner = `
8875
- %c FieldCraft Pro %c v1.6.1
8869
+ %c FieldCraft Pro %c v1.6.3
8876
8870
 
8877
8871
  %cForm Builder \xB7 Response Viewer \xB7 Theme Editor
8878
8872
 
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  export { DEFAULT_PALETTE, DEFAULT_SCHEMA, FormBuilder, FormBuilderThemeProvider, QUESTION_TYPE_INFO, addOption, addQuestion, addSection, duplicateQuestion, duplicateSection, findQuestion, findSection, generateId, generateOptionId, generateQuestionId, generateSectionId, moveOption, moveQuestion, moveSection, removeOption, removeQuestion, removeSection, updateOption, updateQuestion, updateSection, useBuilderState, useBuilderTheme, useDragDrop, useUndoRedo } from './chunk-ANBMT5AW.mjs';
2
2
  export { ResponseViewer } from './chunk-5LKGCZAW.mjs';
3
3
  export { cn } from './chunk-QQ4JZGTD.mjs';
4
- export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-IKGS2ZXV.mjs';
4
+ export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from './chunk-AXCQWATI.mjs';
5
5
  export { FieldCraftProProvider, UnlicensedOverlay, isProductionEnvironment, requireLicense, useLicense, validateLicense } from './chunk-VECQKSWS.mjs';
6
6
  import { useState, useEffect, Suspense } from 'react';
7
7
  import { FieldWrapper } from '@squaredr/fieldcraft-react';
@@ -277,7 +277,7 @@ var PRO_FIELD_OVERRIDES = {
277
277
  // src/index.ts
278
278
  if (typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV !== "production") {
279
279
  const _fc_banner = `
280
- %c FieldCraft Pro %c v1.6.1
280
+ %c FieldCraft Pro %c v1.6.3
281
281
 
282
282
  %cForm Builder \xB7 Response Viewer \xB7 Theme Editor
283
283
 
@@ -1368,13 +1368,28 @@ function ThemeEditorInner({
1368
1368
  const [theme, setTheme] = react.useState(resolvedInitial);
1369
1369
  const [activeSection, setActiveSection] = react.useState("colors");
1370
1370
  const [presetKey, setPresetKey] = react.useState("custom");
1371
- const [mode, setMode] = react.useState(
1372
- () => isDarkTheme(resolvedInitial) ? "dark" : "light"
1373
- );
1374
1371
  const [showPalette, setShowPalette] = react.useState(false);
1375
1372
  const [showComparison, setShowComparison] = react.useState(false);
1376
1373
  const themeRef = react.useRef(theme);
1377
1374
  themeRef.current = theme;
1375
+ const hasInitialTheme = react.useRef(initialTheme != null);
1376
+ react.useEffect(() => {
1377
+ if (hasInitialTheme.current) return;
1378
+ if (typeof window === "undefined") return;
1379
+ const observer = new MutationObserver(() => {
1380
+ requestAnimationFrame(() => {
1381
+ const resolved = resolveThemeFromDOM();
1382
+ setTheme(resolved);
1383
+ setPresetKey("custom");
1384
+ onChange?.(resolved);
1385
+ });
1386
+ });
1387
+ observer.observe(document.documentElement, {
1388
+ attributes: true,
1389
+ attributeFilter: ["data-theme", "class"]
1390
+ });
1391
+ return () => observer.disconnect();
1392
+ }, [onChange]);
1378
1393
  react.useEffect(() => {
1379
1394
  function handleKeyDown(e) {
1380
1395
  if ((e.metaKey || e.ctrlKey) && e.key === "s") {
@@ -1400,30 +1415,18 @@ function ThemeEditorInner({
1400
1415
  [onChange]
1401
1416
  );
1402
1417
  const loadPreset = react.useCallback(
1403
- (key, forceMode) => {
1418
+ (key) => {
1404
1419
  const family = PRESET_FAMILIES[key];
1405
1420
  if (family) {
1406
- const m = forceMode ?? mode;
1407
- const preset = family[m];
1421
+ const variant = isDarkTheme(theme) ? "dark" : "light";
1422
+ const preset = family[variant];
1408
1423
  setTheme(preset);
1409
1424
  setPresetKey(key);
1410
1425
  onChange?.(preset);
1411
1426
  }
1412
1427
  },
1413
- [onChange, mode]
1428
+ [onChange, theme]
1414
1429
  );
1415
- const toggleMode = react.useCallback(() => {
1416
- setMode((prev) => {
1417
- const next = prev === "light" ? "dark" : "light";
1418
- const family = PRESET_FAMILIES[presetKey];
1419
- if (family) {
1420
- const preset = family[next];
1421
- setTheme(preset);
1422
- onChange?.(preset);
1423
- }
1424
- return next;
1425
- });
1426
- }, [presetKey, onChange]);
1427
1430
  const exportJson = react.useCallback(() => {
1428
1431
  const blob = new Blob([JSON.stringify(theme, null, 2)], { type: "application/json" });
1429
1432
  const url = URL.createObjectURL(blob);
@@ -1492,15 +1495,6 @@ function ThemeEditorInner({
1492
1495
  Object.entries(PRESET_FAMILIES).map(([k, fam]) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: k, children: fam.label }, k))
1493
1496
  ]
1494
1497
  }
1495
- ),
1496
- /* @__PURE__ */ jsxRuntime.jsx(
1497
- "button",
1498
- {
1499
- onClick: toggleMode,
1500
- className: "fcte-btn fcte-btn--secondary fcte-toolbar__mode",
1501
- title: mode === "light" ? "Switch to dark mode" : "Switch to light mode",
1502
- children: mode === "light" ? "\u2600" : "\u263E"
1503
- }
1504
1498
  )
1505
1499
  ] }),
1506
1500
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fcte-toolbar__right", children: [
@@ -1,2 +1,2 @@
1
- export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from '../chunk-IKGS2ZXV.mjs';
1
+ export { PRESET_FAMILIES, PREVIEW_SCHEMA, ThemeEditor, ThemeEditorInner, ThemeEditorThemeProvider, resolveThemeFromDOM, themeEditorDarkPreset, themeEditorLightPreset, useEditorTheme } from '../chunk-AXCQWATI.mjs';
2
2
  import '../chunk-VECQKSWS.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squaredr/fieldcraft-pro",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
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",
@@ -1,8 +1,10 @@
1
1
  /* FieldCraft Pro Theme Editor
2
- Chrome theming follows the FormBuilder pattern:
2
+ Chrome theming uses shadcn variable names (--background, --foreground, etc.)
3
+ matching FormBuilder and ResponseViewer for consistent host page inheritance.
4
+
3
5
  1. ThemeEditorThemeProvider wraps children in <div data-fcte-root="" style={cssVars}>
4
- 2. :where([data-fcte-root]) defines zero-specificity fallback defaults
5
- 3. Host page :root variables naturally cascade through (higher specificity)
6
+ 2. --fcte-* variables reference shadcn vars via var(--shadcn-var, fallback)
7
+ 3. Host page :root variables naturally cascade through
6
8
  4. Provider inline styles override everything when a theme prop is passed
7
9
  5. All editor chrome references var(--fcte-*) — fully themeable
8
10
  */
@@ -13,23 +15,22 @@
13
15
  height: 100%;
14
16
  }
15
17
 
16
- /* ─── Default theme variables (zero specificity) ───────────────── */
17
- /* :where() gives these zero specificity so host page :root
18
- variables naturally override them. When no host defines
19
- these vars, the fallback values apply. Inline styles from
20
- ThemeEditorThemeProvider override everything. */
21
- :where([data-fcte-root]) {
22
- --fcte-bg: var(--bg, #F4F7F8);
23
- --fcte-surface: var(--surface, #FFFFFF);
24
- --fcte-surface-hover: var(--wash, #EDF3F2);
25
- --fcte-text: var(--ink, #12222A);
26
- --fcte-text-muted: var(--muted-text, #6A7B85);
27
- --fcte-text-dim: var(--placeholder, #96A5AD);
28
- --fcte-border: var(--rule, #DCE4E8);
29
- --fcte-border-strong: var(--teal-border, #B9D1CF);
30
- --fcte-input-bg: var(--surface2, #FAFCFC);
31
- --fcte-accent: var(--teal, #1F6B6E);
32
- --fcte-accent-text: var(--teal-on, #FFFFFF);
18
+ /* ─── Default theme variables ──────────────────────────────────── */
19
+ /* References shadcn variable names so the chrome inherits from any
20
+ host page that defines --background, --foreground, --card, etc.
21
+ Fallback values are Drafting Teal light defaults. */
22
+ [data-fcte-root] {
23
+ --fcte-bg: var(--background, #F4F7F8);
24
+ --fcte-surface: var(--card, #FFFFFF);
25
+ --fcte-surface-hover: var(--accent, #EDF3F2);
26
+ --fcte-text: var(--foreground, #12222A);
27
+ --fcte-text-muted: var(--muted-foreground, #6A7B85);
28
+ --fcte-text-dim: var(--muted-foreground, #96A5AD);
29
+ --fcte-border: var(--border, #DCE4E8);
30
+ --fcte-border-strong: var(--border, #B9D1CF);
31
+ --fcte-input-bg: var(--card, #FAFCFC);
32
+ --fcte-accent: var(--primary, #1F6B6E);
33
+ --fcte-accent-text: var(--primary-foreground, #FFFFFF);
33
34
  }
34
35
 
35
36
  /* ─── Root element ──────────────────────────────────────────────── */
@@ -83,16 +84,6 @@
83
84
  cursor: pointer;
84
85
  }
85
86
 
86
- .fcte-toolbar__mode {
87
- padding: 4px 8px;
88
- font-size: 14px;
89
- line-height: 1;
90
- min-width: 28px;
91
- display: flex;
92
- align-items: center;
93
- justify-content: center;
94
- }
95
-
96
87
  /* ─── Buttons ───────────────────────────────────────────────────── */
97
88
  .fcte-btn {
98
89
  padding: 5px 12px;