@officexapp/catalogs-cli 0.4.1 → 0.4.2

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.
Files changed (2) hide show
  1. package/dist/index.js +80 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1340,7 +1340,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
1340
1340
  }
1341
1341
 
1342
1342
  case 'html':
1343
- return h('div', { className: compClass, style: compStyle, dangerouslySetInnerHTML: { __html: props.content || '' } });
1343
+ return h(HtmlBlock, { content: props.content || '', className: compClass, style: compStyle, formState });
1344
1344
 
1345
1345
  case 'banner': {
1346
1346
  const variants = {
@@ -1976,6 +1976,38 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
1976
1976
  );
1977
1977
  }
1978
1978
 
1979
+ // --- HtmlBlock: renders HTML content and executes inline <script> tags ---
1980
+ function HtmlBlock({ content, className, style, formState }) {
1981
+ const ref = React.useRef(null);
1982
+ const executedRef = React.useRef(new Set());
1983
+ // Template interpolation: replace {{field_id}} with form state values
1984
+ const interpolated = React.useMemo(() =>
1985
+ (content || '').replace(/{{(w+)}}/g, (_, id) => formState?.[id] ?? ''),
1986
+ [content, formState]
1987
+ );
1988
+ React.useEffect(() => {
1989
+ const container = ref.current;
1990
+ if (!container) return;
1991
+ const scripts = container.querySelectorAll('script');
1992
+ scripts.forEach((orig) => {
1993
+ const key = orig.src || orig.textContent || '';
1994
+ if (executedRef.current.has(key)) return;
1995
+ executedRef.current.add(key);
1996
+ if (orig.src) {
1997
+ const s = document.createElement('script');
1998
+ s.src = orig.src;
1999
+ if (orig.type) s.type = orig.type;
2000
+ s.async = true;
2001
+ container.appendChild(s);
2002
+ } else if (orig.textContent) {
2003
+ try { new Function(orig.textContent)(); }
2004
+ catch (e) { console.error('[CatalogKit:dev] Inline script error:', e); }
2005
+ }
2006
+ });
2007
+ }, [interpolated]);
2008
+ return h('div', { ref, className: 'prose prose-sm max-w-none ' + (className || ''), style, dangerouslySetInnerHTML: { __html: interpolated } });
2009
+ }
2010
+
1979
2011
  function ActionButton({ action, themeColor, onAction }) {
1980
2012
  const st = action.style || 'primary';
1981
2013
  const hasSide = !!action.side_statement;
@@ -2021,7 +2053,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
2021
2053
  );
2022
2054
  }
2023
2055
 
2024
- function StickyBottomBar({ config, page, formState, cartItems, themeColor, onNext, onAction, onBack, historyLen }) {
2056
+ function StickyBottomBar({ config, page, formState, cartItems, themeColor, onNext, onAction, onFieldAndNavigate, onBack, historyLen }) {
2025
2057
  const [visible, setVisible] = React.useState(!config.delay_ms);
2026
2058
  const [scrollDir, setScrollDir] = React.useState('down');
2027
2059
  React.useEffect(() => {
@@ -2044,15 +2076,27 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
2044
2076
  glass_dark: { backgroundColor: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(16px)', color: 'white' },
2045
2077
  gradient: { background: 'linear-gradient(135deg, ' + themeColor + ' 0%, ' + themeColor + 'dd 100%)', color: 'white' },
2046
2078
  };
2047
- const handlePrimary = () => {
2048
- const dispatch = config.primary_action?.dispatch;
2049
- if (!dispatch || dispatch === 'next') { onNext(); return; }
2050
- if (dispatch.startsWith('action:')) {
2051
- const actionId = dispatch.slice(7);
2079
+ const dispatchAction = (act) => {
2080
+ const cmd = act?.action || 'next';
2081
+ if (cmd === 'next') { onNext(); return; }
2082
+ if (cmd.startsWith('action:')) {
2083
+ const actionId = cmd.slice(7);
2052
2084
  const action = page.actions?.find(a => a.id === actionId);
2053
2085
  if (action) onAction(action); else onNext();
2054
- } else { onNext(); }
2086
+ return;
2087
+ }
2088
+ if (cmd.startsWith('field:')) {
2089
+ const parts = cmd.slice(6).split(':');
2090
+ if (parts.length >= 2) { onFieldAndNavigate(parts[0], parts.slice(1).join(':')); }
2091
+ return;
2092
+ }
2093
+ onNext();
2055
2094
  };
2095
+ const primaryLabel = config.primary?.label
2096
+ ? interpolate(config.primary.label)
2097
+ : config.button_text || page.submit_label || 'Continue';
2098
+ const secondaryAction = config.secondary;
2099
+ const secondaryLabel = secondaryAction?.label ? interpolate(secondaryAction.label) : null;
2056
2100
  return h('div', {
2057
2101
  className: 'cf-sticky-bar' + (show ? '' : ' hidden'),
2058
2102
  style: bgStyles[config.style || 'solid'] || bgStyles.solid,
@@ -2066,12 +2110,19 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
2066
2110
  h('div', { className: 'flex items-center gap-3' },
2067
2111
  config.cart_badge && cartItems.length > 0
2068
2112
  ? h('span', { className: 'bg-red-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center' }, cartItems.length) : null,
2113
+ secondaryLabel
2114
+ ? h('button', {
2115
+ className: 'text-sm font-medium hover:opacity-80 transition-opacity',
2116
+ style: { color: config.style === 'glass_dark' ? 'rgba(255,255,255,0.6)' : '#6b7280' },
2117
+ onClick: () => dispatchAction(secondaryAction),
2118
+ }, secondaryLabel)
2119
+ : null,
2069
2120
  h('button', {
2070
2121
  className: 'cf-btn-primary text-white text-sm',
2071
2122
  style: { backgroundColor: config.style === 'gradient' ? 'rgba(255,255,255,0.9)' : themeColor, color: config.style === 'gradient' ? themeColor : 'white' },
2072
2123
  disabled: config.disabled,
2073
- onClick: handlePrimary,
2074
- }, interpolate(config.primary_action?.label || page.submit_label || 'Continue'))
2124
+ onClick: () => dispatchAction(config.primary),
2125
+ }, primaryLabel)
2075
2126
  )
2076
2127
  )
2077
2128
  );
@@ -2746,6 +2797,24 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
2746
2797
  navigateTo(nextPageId);
2747
2798
  }, [currentPageId, routing, formState, pages, addToCart, navigateTo, runValidation]);
2748
2799
 
2800
+ // --- Field + Navigate (for sticky bar field: dispatch) ---
2801
+ const handleFieldAndNavigate = React.useCallback((fieldId, value) => {
2802
+ const newFormState = { ...formState, [fieldId]: value };
2803
+ setFormState(newFormState);
2804
+ const page = pages[currentPageId];
2805
+ if (page) {
2806
+ const errors = validatePage(page, newFormState, devContext);
2807
+ setValidationErrors(errors);
2808
+ if (errors.length > 0) {
2809
+ const el = document.querySelector('[data-component-id="' + errors[0].componentId + '"]');
2810
+ if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
2811
+ return;
2812
+ }
2813
+ }
2814
+ const nextPageId = getNextPage(routing, currentPageId, newFormState, devContext);
2815
+ navigateTo(nextPageId);
2816
+ }, [currentPageId, routing, formState, pages, navigateTo]);
2817
+
2749
2818
  // --- Resume prompt ---
2750
2819
  if (showResumeModal) {
2751
2820
  return h('div', { className: 'cf-resume-backdrop' },
@@ -3037,7 +3106,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
3037
3106
  ? h(StickyBottomBar, {
3038
3107
  config: { ...(catalog.settings?.sticky_bar || {}), ...(page.sticky_bar || {}) },
3039
3108
  page, formState, cartItems, themeColor,
3040
- onNext: handleNext, onAction: handleAction, onBack: handleBack,
3109
+ onNext: handleNext, onAction: handleAction, onFieldAndNavigate: handleFieldAndNavigate, onBack: handleBack,
3041
3110
  historyLen: history.length,
3042
3111
  })
3043
3112
  : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@officexapp/catalogs-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "CLI for Catalog Kit — upload videos, push catalogs, manage assets",
5
5
  "type": "module",
6
6
  "bin": {