@officexapp/catalogs-cli 0.4.0 → 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.
- package/dist/index.js +124 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1275,8 +1275,8 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1275
1275
|
}
|
|
1276
1276
|
|
|
1277
1277
|
// --- Component Renderers ---
|
|
1278
|
-
function RenderComponent({ comp, isCover, formState, onFieldChange }) {
|
|
1279
|
-
const props = comp.props || {};
|
|
1278
|
+
function RenderComponent({ comp, isCover, formState, onFieldChange, onSubmit, propOverrides }) {
|
|
1279
|
+
const props = { ...(comp.props || {}), ...(propOverrides?.[comp.id] || {}) };
|
|
1280
1280
|
const type = comp.type;
|
|
1281
1281
|
const compClass = comp.className || '';
|
|
1282
1282
|
const compStyle = comp.style || {};
|
|
@@ -1340,7 +1340,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1340
1340
|
}
|
|
1341
1341
|
|
|
1342
1342
|
case 'html':
|
|
1343
|
-
return h('
|
|
1343
|
+
return h(HtmlBlock, { content: props.content || '', className: compClass, style: compStyle, formState });
|
|
1344
1344
|
|
|
1345
1345
|
case 'banner': {
|
|
1346
1346
|
const variants = {
|
|
@@ -1452,7 +1452,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1452
1452
|
case 'url':
|
|
1453
1453
|
case 'number':
|
|
1454
1454
|
case 'password':
|
|
1455
|
-
return h(TextInput, { comp, type, formState, onFieldChange, isCover, compClass, compStyle });
|
|
1455
|
+
return h(TextInput, { comp, type, formState, onFieldChange, isCover, compClass, compStyle, onSubmit });
|
|
1456
1456
|
|
|
1457
1457
|
case 'long_text':
|
|
1458
1458
|
return h('div', { className: 'space-y-1.5 ' + compClass, style: compStyle },
|
|
@@ -1723,7 +1723,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1723
1723
|
);
|
|
1724
1724
|
}
|
|
1725
1725
|
|
|
1726
|
-
function TextInput({ comp, type, formState, onFieldChange, isCover, compClass, compStyle }) {
|
|
1726
|
+
function TextInput({ comp, type, formState, onFieldChange, isCover, compClass, compStyle, onSubmit }) {
|
|
1727
1727
|
const props = comp.props || {};
|
|
1728
1728
|
const inputType = type === 'email' ? 'email' : type === 'phone' ? 'tel' : type === 'url' ? 'url' : type === 'number' ? 'number' : type === 'password' ? 'password' : 'text';
|
|
1729
1729
|
return h('div', { className: 'space-y-1.5 ' + compClass, style: compStyle },
|
|
@@ -1739,6 +1739,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1739
1739
|
placeholder: props.placeholder || '',
|
|
1740
1740
|
value: formState[comp.id] ?? '',
|
|
1741
1741
|
onChange: (e) => onFieldChange(comp.id, e.target.value),
|
|
1742
|
+
onKeyDown: (e) => { if (e.key === 'Enter' && onSubmit) { e.preventDefault(); onSubmit(); } },
|
|
1742
1743
|
})
|
|
1743
1744
|
);
|
|
1744
1745
|
}
|
|
@@ -1975,6 +1976,38 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
1975
1976
|
);
|
|
1976
1977
|
}
|
|
1977
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
|
+
|
|
1978
2011
|
function ActionButton({ action, themeColor, onAction }) {
|
|
1979
2012
|
const st = action.style || 'primary';
|
|
1980
2013
|
const hasSide = !!action.side_statement;
|
|
@@ -2020,7 +2053,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2020
2053
|
);
|
|
2021
2054
|
}
|
|
2022
2055
|
|
|
2023
|
-
function StickyBottomBar({ config, page, formState, cartItems, themeColor, onNext, onAction, onBack, historyLen }) {
|
|
2056
|
+
function StickyBottomBar({ config, page, formState, cartItems, themeColor, onNext, onAction, onFieldAndNavigate, onBack, historyLen }) {
|
|
2024
2057
|
const [visible, setVisible] = React.useState(!config.delay_ms);
|
|
2025
2058
|
const [scrollDir, setScrollDir] = React.useState('down');
|
|
2026
2059
|
React.useEffect(() => {
|
|
@@ -2043,15 +2076,27 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2043
2076
|
glass_dark: { backgroundColor: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(16px)', color: 'white' },
|
|
2044
2077
|
gradient: { background: 'linear-gradient(135deg, ' + themeColor + ' 0%, ' + themeColor + 'dd 100%)', color: 'white' },
|
|
2045
2078
|
};
|
|
2046
|
-
const
|
|
2047
|
-
const
|
|
2048
|
-
if (
|
|
2049
|
-
if (
|
|
2050
|
-
const actionId =
|
|
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);
|
|
2051
2084
|
const action = page.actions?.find(a => a.id === actionId);
|
|
2052
2085
|
if (action) onAction(action); else onNext();
|
|
2053
|
-
|
|
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();
|
|
2054
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;
|
|
2055
2100
|
return h('div', {
|
|
2056
2101
|
className: 'cf-sticky-bar' + (show ? '' : ' hidden'),
|
|
2057
2102
|
style: bgStyles[config.style || 'solid'] || bgStyles.solid,
|
|
@@ -2065,12 +2110,19 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2065
2110
|
h('div', { className: 'flex items-center gap-3' },
|
|
2066
2111
|
config.cart_badge && cartItems.length > 0
|
|
2067
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,
|
|
2068
2120
|
h('button', {
|
|
2069
2121
|
className: 'cf-btn-primary text-white text-sm',
|
|
2070
2122
|
style: { backgroundColor: config.style === 'gradient' ? 'rgba(255,255,255,0.9)' : themeColor, color: config.style === 'gradient' ? themeColor : 'white' },
|
|
2071
2123
|
disabled: config.disabled,
|
|
2072
|
-
onClick:
|
|
2073
|
-
},
|
|
2124
|
+
onClick: () => dispatchAction(config.primary),
|
|
2125
|
+
}, primaryLabel)
|
|
2074
2126
|
)
|
|
2075
2127
|
)
|
|
2076
2128
|
);
|
|
@@ -2414,6 +2466,8 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2414
2466
|
const historyRef = React.useRef(history);
|
|
2415
2467
|
historyRef.current = history;
|
|
2416
2468
|
const autoAdvanceTimer = React.useRef(null);
|
|
2469
|
+
const globalsRef = React.useRef({});
|
|
2470
|
+
const [compPropOverrides, setCompPropOverrides] = React.useState({});
|
|
2417
2471
|
|
|
2418
2472
|
// --- Cart logic ---
|
|
2419
2473
|
const addToCart = React.useCallback((pageId) => {
|
|
@@ -2557,7 +2611,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2557
2611
|
getField: (id) => formStateRef.current[id],
|
|
2558
2612
|
getAllFields: () => ({ ...formStateRef.current }),
|
|
2559
2613
|
getPageId: () => currentPageId,
|
|
2560
|
-
setField: (id, value) =>
|
|
2614
|
+
setField: (id, value) => onFieldChangeRef.current?.(id, value),
|
|
2561
2615
|
goNext: () => handleNextRef.current?.(),
|
|
2562
2616
|
goBack: () => handleBackRef.current?.(),
|
|
2563
2617
|
on: (event, cb) => { if (!listeners[event]) listeners[event] = new Set(); listeners[event].add(cb); },
|
|
@@ -2565,6 +2619,11 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2565
2619
|
openCart: () => setCartOpen(true),
|
|
2566
2620
|
closeCart: () => setCartOpen(false),
|
|
2567
2621
|
getCartItems: () => [...cartItems],
|
|
2622
|
+
getGlobal: (key) => globalsRef.current[key],
|
|
2623
|
+
setGlobal: (key, value) => { globalsRef.current[key] = value; },
|
|
2624
|
+
setComponentProp: (id, prop, value) => {
|
|
2625
|
+
setCompPropOverrides(prev => ({ ...prev, [id]: { ...(prev[id] || {}), [prop]: value } }));
|
|
2626
|
+
},
|
|
2568
2627
|
setValidationError: (id, msg) => {
|
|
2569
2628
|
setValidationErrors(prev => {
|
|
2570
2629
|
const next = prev.filter(e => e.componentId !== id);
|
|
@@ -2573,7 +2632,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2573
2632
|
});
|
|
2574
2633
|
},
|
|
2575
2634
|
};
|
|
2576
|
-
window.CatalogKit = { get: () => instance, getField: instance.getField, setField: instance.setField, getPageId: instance.getPageId, goNext: instance.goNext, goBack: instance.goBack, on: instance.on, off: instance.off };
|
|
2635
|
+
window.CatalogKit = { get: () => instance, getField: instance.getField, setField: instance.setField, getPageId: instance.getPageId, goNext: instance.goNext, goBack: instance.goBack, on: instance.on, off: instance.off, setGlobal: instance.setGlobal, getGlobal: instance.getGlobal, setComponentProp: instance.setComponentProp };
|
|
2577
2636
|
window.__catalogKitListeners = listeners;
|
|
2578
2637
|
return () => { delete window.CatalogKit; delete window.__catalogKitListeners; };
|
|
2579
2638
|
}, []);
|
|
@@ -2615,9 +2674,11 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2615
2674
|
const onFieldChange = React.useCallback((id, value) => {
|
|
2616
2675
|
setFormState(prev => ({ ...prev, [id]: value }));
|
|
2617
2676
|
devEvents.emit('field_change', { field_id: id, value, page_id: currentPageId });
|
|
2618
|
-
// Fire CatalogKit fieldchange
|
|
2677
|
+
// Fire CatalogKit fieldchange (unscoped + scoped)
|
|
2619
2678
|
const ckListeners = window.__catalogKitListeners || {};
|
|
2620
|
-
const
|
|
2679
|
+
const fcPayload = { fieldId: id, value, pageId: currentPageId };
|
|
2680
|
+
const fcSet = ckListeners['fieldchange']; if (fcSet?.size) for (const cb of fcSet) { try { cb(fcPayload); } catch {} }
|
|
2681
|
+
const scopedSet = ckListeners['fieldchange:' + id]; if (scopedSet?.size) for (const cb of scopedSet) { try { cb(fcPayload); } catch {} }
|
|
2621
2682
|
|
|
2622
2683
|
// Auto-advance: if page has auto_advance and this is a selection-type input
|
|
2623
2684
|
const pg = pages[currentPageId];
|
|
@@ -2647,6 +2708,8 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2647
2708
|
}
|
|
2648
2709
|
}
|
|
2649
2710
|
}, [currentPageId, pages, formState, routing, navigateTo]);
|
|
2711
|
+
const onFieldChangeRef = React.useRef(onFieldChange);
|
|
2712
|
+
onFieldChangeRef.current = onFieldChange;
|
|
2650
2713
|
|
|
2651
2714
|
// --- Validation ---
|
|
2652
2715
|
const runValidation = React.useCallback(() => {
|
|
@@ -2684,6 +2747,21 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2684
2747
|
if (currentPage?.offer) {
|
|
2685
2748
|
if (!currentPage.offer.accept_field) addToCart(currentPageId);
|
|
2686
2749
|
}
|
|
2750
|
+
// Fire CatalogKit beforenext event (scoped: "beforenext" + "beforenext:<pageId>")
|
|
2751
|
+
let prevented = false;
|
|
2752
|
+
let nextPageOverride;
|
|
2753
|
+
const beforeNextPayload = {
|
|
2754
|
+
pageId: currentPageId,
|
|
2755
|
+
preventDefault: () => { prevented = true; },
|
|
2756
|
+
setNextPage: (id) => { nextPageOverride = id; },
|
|
2757
|
+
};
|
|
2758
|
+
const ckListeners = window.__catalogKitListeners || {};
|
|
2759
|
+
for (const key of ['beforenext', 'beforenext:' + currentPageId]) {
|
|
2760
|
+
const set = ckListeners[key]; if (!set?.size) continue;
|
|
2761
|
+
for (const cb of set) { try { cb(beforeNextPayload); } catch (e) { console.error('[CatalogKit]', key, e); } }
|
|
2762
|
+
}
|
|
2763
|
+
if (prevented) return;
|
|
2764
|
+
if (nextPageOverride !== undefined) { navigateTo(nextPageOverride); return; }
|
|
2687
2765
|
const nextId = getNextPage(routing, currentPageId, formState, devContext);
|
|
2688
2766
|
navigateTo(nextId);
|
|
2689
2767
|
}, [currentPageId, routing, formState, pages, addToCart, navigateTo, runValidation]);
|
|
@@ -2719,6 +2797,24 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2719
2797
|
navigateTo(nextPageId);
|
|
2720
2798
|
}, [currentPageId, routing, formState, pages, addToCart, navigateTo, runValidation]);
|
|
2721
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
|
+
|
|
2722
2818
|
// --- Resume prompt ---
|
|
2723
2819
|
if (showResumeModal) {
|
|
2724
2820
|
return h('div', { className: 'cf-resume-backdrop' },
|
|
@@ -2879,6 +2975,11 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2879
2975
|
if (c.prefill_mode === 'hidden' && formState[c.id] != null && formState[c.id] !== '') return false;
|
|
2880
2976
|
return true;
|
|
2881
2977
|
});
|
|
2978
|
+
// Find the last input component for Enter-to-submit
|
|
2979
|
+
const inputTypes = ['short_text', 'long_text', 'email', 'phone', 'url', 'number', 'password', 'currency', 'date', 'datetime', 'time', 'address'];
|
|
2980
|
+
const lastInputComp = [...components].reverse().find(c => inputTypes.includes(c.type));
|
|
2981
|
+
const lastInputId = lastInputComp?.id;
|
|
2982
|
+
|
|
2882
2983
|
const bgImage = page.background_image || catalog.settings?.theme?.background_image;
|
|
2883
2984
|
|
|
2884
2985
|
// Cart UI (shared between cover and standard)
|
|
@@ -2910,8 +3011,9 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2910
3011
|
);
|
|
2911
3012
|
}
|
|
2912
3013
|
const fieldError = validationErrors.find(e => e.componentId === comp.id);
|
|
3014
|
+
const submitHandler = comp.id === lastInputId ? handleNext : undefined;
|
|
2913
3015
|
return h('div', { key: comp.id || i, 'data-component-id': comp.id, 'data-component-type': comp.type, className: fieldError ? 'cf-field-error' : '' },
|
|
2914
|
-
h(RenderComponent, { comp, isCover: true, formState, onFieldChange }),
|
|
3016
|
+
h(RenderComponent, { comp, isCover: true, formState, onFieldChange, onSubmit: submitHandler, propOverrides: compPropOverrides }),
|
|
2915
3017
|
fieldError ? h('p', { className: 'text-xs text-red-300 font-medium mt-1', role: 'alert' }, fieldError.message) : null
|
|
2916
3018
|
);
|
|
2917
3019
|
}),
|
|
@@ -2972,8 +3074,9 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
2972
3074
|
);
|
|
2973
3075
|
}
|
|
2974
3076
|
const fieldError = validationErrors.find(e => e.componentId === comp.id);
|
|
3077
|
+
const submitHandler = comp.id === lastInputId ? handleNext : undefined;
|
|
2975
3078
|
return h('div', { key: comp.id || i, 'data-component-id': comp.id, 'data-component-type': comp.type, className: fieldError ? 'cf-field-error' : '' },
|
|
2976
|
-
h(RenderComponent, { comp, isCover: false, formState, onFieldChange }),
|
|
3079
|
+
h(RenderComponent, { comp, isCover: false, formState, onFieldChange, onSubmit: submitHandler, propOverrides: compPropOverrides }),
|
|
2977
3080
|
fieldError ? h('p', { className: 'text-xs text-red-500 font-medium mt-1', role: 'alert' }, fieldError.message) : null
|
|
2978
3081
|
);
|
|
2979
3082
|
}),
|
|
@@ -3003,7 +3106,7 @@ function buildPreviewHtml(schema, port, validation, devConfig) {
|
|
|
3003
3106
|
? h(StickyBottomBar, {
|
|
3004
3107
|
config: { ...(catalog.settings?.sticky_bar || {}), ...(page.sticky_bar || {}) },
|
|
3005
3108
|
page, formState, cartItems, themeColor,
|
|
3006
|
-
onNext: handleNext, onAction: handleAction, onBack: handleBack,
|
|
3109
|
+
onNext: handleNext, onAction: handleAction, onFieldAndNavigate: handleFieldAndNavigate, onBack: handleBack,
|
|
3007
3110
|
historyLen: history.length,
|
|
3008
3111
|
})
|
|
3009
3112
|
: null
|