@campxdev/pdfme 1.2.2 → 1.3.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.
- package/dist/cjs/chunks/{index-C8qZMUOU.js → fontSizePxWidget-Drk8HKGH.js} +138 -9
- package/dist/cjs/chunks/fontSizeTransform-CQQ_O42f.js +37 -0
- package/dist/cjs/chunks/{helper-BfoMn47R.js → helper-DGH62Z2s.js} +4 -0
- package/dist/cjs/chunks/{index-COKtXyPp.js → index-CoNR0xQU.js} +6 -2
- package/dist/cjs/chunks/{index-CVqJfcgy.js → index-CsEKt088.js} +697 -547
- package/dist/cjs/chunks/{pluginRegistry-C8bMreez.js → pluginRegistry-D2vr9MUy.js} +1 -1
- package/dist/cjs/common.js +7 -3
- package/dist/cjs/converter.js +1 -1
- package/dist/cjs/generator.js +3 -3
- package/dist/cjs/index.js +23 -16
- package/dist/cjs/print-designer-editor.js +3320 -3296
- package/dist/cjs/schemas.js +500 -38
- package/dist/cjs/ui.js +2007 -1868
- package/dist/esm/chunks/{index-C4F7EwBG.js → fontSizePxWidget-CbzQrSSM.js} +130 -5
- package/dist/esm/chunks/fontSizeTransform-CkTVJdRF.js +34 -0
- package/dist/esm/chunks/{helper-D5PPN6Bv.js → helper-DSxGxZ0j.js} +4 -1
- package/dist/esm/chunks/{index-CDhErAtE.js → index-DJkUkUo9.js} +4 -3
- package/dist/esm/chunks/{index-C7jr4GIK.js → index-D_-j4c4P.js} +691 -544
- package/dist/esm/chunks/{pluginRegistry-B-XSNgmK.js → pluginRegistry-Bgrz5qWG.js} +1 -1
- package/dist/esm/common.js +4 -3
- package/dist/esm/converter.js +1 -1
- package/dist/esm/generator.js +3 -3
- package/dist/esm/index.js +7 -6
- package/dist/esm/print-designer-editor.js +3307 -3286
- package/dist/esm/schemas.js +472 -13
- package/dist/esm/ui.js +2007 -1868
- package/dist/types/_vendors/common/fontSizeTransform.d.ts +5 -0
- package/dist/types/_vendors/common/helper.d.ts +1 -0
- package/dist/types/_vendors/common/index.d.ts +3 -2
- package/dist/types/_vendors/print-designer-editor/index.d.ts +2 -1
- package/dist/types/_vendors/print-designer-editor/types.d.ts +1 -1
- package/dist/types/_vendors/print-designer-editor/useDesigner.d.ts +1 -1
- package/dist/types/_vendors/schemas/index.d.ts +8 -2
- package/dist/types/_vendors/schemas/richText/helper.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/index.d.ts +4 -0
- package/dist/types/_vendors/schemas/richText/pdfRender.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/propPanel.d.ts +3 -0
- package/dist/types/_vendors/schemas/richText/types.d.ts +7 -0
- package/dist/types/_vendors/schemas/richText/uiRender.d.ts +3 -0
- package/dist/types/_vendors/schemas/singleVariableText/index.d.ts +4 -0
- package/dist/types/_vendors/schemas/singleVariableText/propPanel.d.ts +3 -0
- package/dist/types/_vendors/schemas/singleVariableText/types.d.ts +4 -0
- package/dist/types/_vendors/schemas/text/fontSizePxWidget.d.ts +9 -0
- package/dist/types/_vendors/ui/components/CtlBar.d.ts +1 -1
- package/dist/types/_vendors/ui/components/Paper.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var helper = require('./helper-
|
|
3
|
+
var helper = require('./helper-DGH62Z2s.js');
|
|
4
4
|
var fontkit = require('fontkit');
|
|
5
5
|
var buffer = require('buffer');
|
|
6
6
|
var colors = require('./colors-BeBcxfhb.js');
|
|
@@ -1810,74 +1810,6 @@ const pdfRender$1 = async (arg) => {
|
|
|
1810
1810
|
});
|
|
1811
1811
|
};
|
|
1812
1812
|
|
|
1813
|
-
const substituteVariables = (text, variablesIn, extraContext) => {
|
|
1814
|
-
if (!text) {
|
|
1815
|
-
return '';
|
|
1816
|
-
}
|
|
1817
|
-
let variables;
|
|
1818
|
-
try {
|
|
1819
|
-
variables =
|
|
1820
|
-
typeof variablesIn === 'string'
|
|
1821
|
-
? JSON.parse(variablesIn || '{}')
|
|
1822
|
-
: variablesIn;
|
|
1823
|
-
}
|
|
1824
|
-
catch {
|
|
1825
|
-
throw new SyntaxError(`[@campxdev/schemas] MVT: invalid JSON string '${variablesIn}'`);
|
|
1826
|
-
}
|
|
1827
|
-
// Merge extra context (e.g. currentPage, totalPages) with user variables
|
|
1828
|
-
// System context takes precedence over user variables
|
|
1829
|
-
const merged = extraContext ? { ...variables, ...extraContext } : variables;
|
|
1830
|
-
// Use the full JS expression evaluator — supports {varName}, {expr * 2}, {str.toUpperCase()}, etc.
|
|
1831
|
-
const result = expression.replacePlaceholders({ content: text, variables: merged, schemas: [] });
|
|
1832
|
-
// Strip any remaining unresolved {placeholders} for clean output
|
|
1833
|
-
return result.replace(/\{[^{}]+\}/g, '');
|
|
1834
|
-
};
|
|
1835
|
-
const validateVariables = (value, schema) => {
|
|
1836
|
-
if (!schema.variables || schema.variables.length === 0) {
|
|
1837
|
-
return true;
|
|
1838
|
-
}
|
|
1839
|
-
let values;
|
|
1840
|
-
try {
|
|
1841
|
-
values = value ? JSON.parse(value) : {};
|
|
1842
|
-
}
|
|
1843
|
-
catch {
|
|
1844
|
-
throw new SyntaxError(`[@campxdev/generator] invalid JSON string '${value}' for variables in field ${schema.name}`);
|
|
1845
|
-
}
|
|
1846
|
-
for (const variable of schema.variables) {
|
|
1847
|
-
if (!values[variable]) {
|
|
1848
|
-
if (schema.required) {
|
|
1849
|
-
throw new Error(`[@campxdev/generator] variable ${variable} is missing for field ${schema.name}`);
|
|
1850
|
-
}
|
|
1851
|
-
return false;
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
return true;
|
|
1855
|
-
};
|
|
1856
|
-
|
|
1857
|
-
const pdfRender = async (arg) => {
|
|
1858
|
-
const { value, schema, pageContext, ...rest } = arg;
|
|
1859
|
-
// Static mode: no template text → render value directly as plain text
|
|
1860
|
-
if (!schema.text) {
|
|
1861
|
-
await pdfRender$1({ value, schema, ...rest });
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
1864
|
-
// readOnly: value is already resolved by generate.ts via replacePlaceholders
|
|
1865
|
-
if (schema.readOnly) {
|
|
1866
|
-
await pdfRender$1({ value, schema, ...rest });
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
// Dynamic mode (form): substitute variables in template
|
|
1870
|
-
if (!validateVariables(value, schema)) {
|
|
1871
|
-
return;
|
|
1872
|
-
}
|
|
1873
|
-
const renderArgs = {
|
|
1874
|
-
value: substituteVariables(schema.text, value || '{}', pageContext),
|
|
1875
|
-
schema,
|
|
1876
|
-
...rest,
|
|
1877
|
-
};
|
|
1878
|
-
await pdfRender$1(renderArgs);
|
|
1879
|
-
};
|
|
1880
|
-
|
|
1881
1813
|
createSvgStr(lucide.Bold);
|
|
1882
1814
|
createSvgStr(lucide.Italic);
|
|
1883
1815
|
const TextStrikethroughIcon = createSvgStr(lucide.Strikethrough);
|
|
@@ -1961,128 +1893,111 @@ const UseDynamicFontSize = (props) => {
|
|
|
1961
1893
|
label.appendChild(span);
|
|
1962
1894
|
rootElement.appendChild(label);
|
|
1963
1895
|
};
|
|
1964
|
-
const propPanel$
|
|
1896
|
+
const propPanel$2 = {
|
|
1965
1897
|
schema: ({ options, activeSchema, i18n }) => {
|
|
1966
1898
|
const font = options.font || { [helper.DEFAULT_FONT_NAME]: { data: '', fallback: true } };
|
|
1967
1899
|
const fontNames = Object.keys(font);
|
|
1968
1900
|
const fallbackFontName = helper.getFallbackFontName(font);
|
|
1969
1901
|
const enableDynamicFont = Boolean(activeSchema?.dynamicFontSize);
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
},
|
|
1986
|
-
span: 24,
|
|
1987
|
-
},
|
|
1988
|
-
fontSize: {
|
|
1989
|
-
title: i18n('schemas.text.size'),
|
|
1990
|
-
type: 'number',
|
|
1991
|
-
widget: 'inputNumber',
|
|
1992
|
-
span: 6,
|
|
1993
|
-
disabled: enableDynamicFont,
|
|
1994
|
-
props: { min: 0 },
|
|
1995
|
-
},
|
|
1996
|
-
characterSpacing: {
|
|
1997
|
-
title: i18n('schemas.text.spacing'),
|
|
1998
|
-
type: 'number',
|
|
1999
|
-
widget: 'inputNumber',
|
|
2000
|
-
span: 6,
|
|
2001
|
-
props: { min: 0 },
|
|
2002
|
-
},
|
|
2003
|
-
formatter: getExtraFormatterSchema(i18n),
|
|
2004
|
-
lineHeight: {
|
|
2005
|
-
title: i18n('schemas.text.lineHeight'),
|
|
2006
|
-
type: 'number',
|
|
2007
|
-
widget: 'inputNumber',
|
|
2008
|
-
props: { step: 0.1, min: 0 },
|
|
2009
|
-
span: 8,
|
|
1902
|
+
const result = {};
|
|
1903
|
+
result.fontName = {
|
|
1904
|
+
title: i18n('schemas.text.fontName'),
|
|
1905
|
+
type: 'string',
|
|
1906
|
+
widget: 'select',
|
|
1907
|
+
default: fallbackFontName,
|
|
1908
|
+
placeholder: fallbackFontName,
|
|
1909
|
+
props: {
|
|
1910
|
+
options: fontNames.map((name) => ({ label: name, value: name })),
|
|
1911
|
+
showSearch: true,
|
|
1912
|
+
virtual: true,
|
|
1913
|
+
filterOption: (input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
|
1914
|
+
popupMatchSelectWidth: false,
|
|
1915
|
+
listHeight: 300,
|
|
1916
|
+
style: { width: '100%' },
|
|
2010
1917
|
},
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
1918
|
+
span: 24,
|
|
1919
|
+
};
|
|
1920
|
+
result.fontSize = {
|
|
1921
|
+
title: i18n('schemas.text.size'),
|
|
1922
|
+
type: 'number',
|
|
1923
|
+
widget: 'inputNumber',
|
|
1924
|
+
span: 6,
|
|
1925
|
+
disabled: enableDynamicFont,
|
|
1926
|
+
props: { min: 0 },
|
|
1927
|
+
};
|
|
1928
|
+
result.characterSpacing = {
|
|
1929
|
+
title: i18n('schemas.text.spacing'),
|
|
1930
|
+
type: 'number',
|
|
1931
|
+
widget: 'inputNumber',
|
|
1932
|
+
span: 6,
|
|
1933
|
+
props: { min: 0 },
|
|
1934
|
+
};
|
|
1935
|
+
result.formatter = getExtraFormatterSchema(i18n);
|
|
1936
|
+
result.lineHeight = {
|
|
1937
|
+
title: i18n('schemas.text.lineHeight'),
|
|
1938
|
+
type: 'number',
|
|
1939
|
+
widget: 'inputNumber',
|
|
1940
|
+
props: { step: 0.1, min: 0 },
|
|
1941
|
+
span: 8,
|
|
1942
|
+
};
|
|
1943
|
+
result.useDynamicFontSize = { type: 'boolean', widget: 'UseDynamicFontSize', bind: false, span: 16 };
|
|
1944
|
+
result.dynamicFontSize = {
|
|
1945
|
+
type: 'object',
|
|
1946
|
+
widget: 'card',
|
|
1947
|
+
column: 3,
|
|
1948
|
+
properties: {
|
|
1949
|
+
min: {
|
|
1950
|
+
title: i18n('schemas.text.min'),
|
|
1951
|
+
type: 'number',
|
|
1952
|
+
widget: 'inputNumber',
|
|
1953
|
+
hidden: !enableDynamicFont,
|
|
1954
|
+
props: { min: 0 },
|
|
2043
1955
|
},
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
disabledAlpha: true,
|
|
1956
|
+
max: {
|
|
1957
|
+
title: i18n('schemas.text.max'),
|
|
1958
|
+
type: 'number',
|
|
1959
|
+
widget: 'inputNumber',
|
|
1960
|
+
hidden: !enableDynamicFont,
|
|
1961
|
+
props: { min: 0 },
|
|
2051
1962
|
},
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
1963
|
+
fit: {
|
|
1964
|
+
title: i18n('schemas.text.fit'),
|
|
1965
|
+
type: 'string',
|
|
1966
|
+
widget: 'select',
|
|
1967
|
+
hidden: !enableDynamicFont,
|
|
1968
|
+
props: {
|
|
1969
|
+
options: [
|
|
1970
|
+
{ label: i18n('schemas.horizontal'), value: DYNAMIC_FIT_HORIZONTAL },
|
|
1971
|
+
{ label: i18n('schemas.vertical'), value: DYNAMIC_FIT_VERTICAL },
|
|
1972
|
+
],
|
|
2056
1973
|
},
|
|
2057
|
-
],
|
|
2058
|
-
},
|
|
2059
|
-
backgroundColor: {
|
|
2060
|
-
title: i18n('schemas.bgColor'),
|
|
2061
|
-
type: 'string',
|
|
2062
|
-
widget: 'color',
|
|
2063
|
-
props: {
|
|
2064
|
-
disabledAlpha: true,
|
|
2065
1974
|
},
|
|
2066
|
-
rules: [
|
|
2067
|
-
{
|
|
2068
|
-
pattern: HEX_COLOR_PATTERN,
|
|
2069
|
-
message: i18n('validation.hexColor'),
|
|
2070
|
-
},
|
|
2071
|
-
],
|
|
2072
1975
|
},
|
|
2073
1976
|
};
|
|
2074
|
-
|
|
1977
|
+
result.fontColor = {
|
|
1978
|
+
title: i18n('schemas.textColor'),
|
|
1979
|
+
type: 'string',
|
|
1980
|
+
widget: 'color',
|
|
1981
|
+
props: { disabledAlpha: true },
|
|
1982
|
+
rules: [{ pattern: HEX_COLOR_PATTERN, message: i18n('validation.hexColor') }],
|
|
1983
|
+
};
|
|
1984
|
+
result.backgroundColor = {
|
|
1985
|
+
title: i18n('schemas.bgColor'),
|
|
1986
|
+
type: 'string',
|
|
1987
|
+
widget: 'color',
|
|
1988
|
+
props: { disabledAlpha: true },
|
|
1989
|
+
rules: [{ pattern: HEX_COLOR_PATTERN, message: i18n('validation.hexColor') }],
|
|
1990
|
+
};
|
|
1991
|
+
return result;
|
|
2075
1992
|
},
|
|
2076
1993
|
widgets: { UseDynamicFontSize },
|
|
2077
1994
|
defaultSchema: {
|
|
2078
1995
|
name: '',
|
|
2079
1996
|
type: 'text',
|
|
2080
|
-
content: '
|
|
1997
|
+
content: '',
|
|
2081
1998
|
position: { x: 0, y: 0 },
|
|
2082
1999
|
width: 45,
|
|
2083
|
-
height:
|
|
2084
|
-
// If the value of "rotate" is set to undefined or not set at all, rotation will be disabled in the UI.
|
|
2085
|
-
// Check this document: https://pdfme.com//docs/custom-schemas#learning-how-to-create-from-pdfmeschemas-code
|
|
2000
|
+
height: 7,
|
|
2086
2001
|
rotate: 0,
|
|
2087
2002
|
alignment: DEFAULT_ALIGNMENT,
|
|
2088
2003
|
verticalAlignment: DEFAULT_VERTICAL_ALIGNMENT,
|
|
@@ -2091,259 +2006,14 @@ const propPanel$1 = {
|
|
|
2091
2006
|
characterSpacing: DEFAULT_CHARACTER_SPACING,
|
|
2092
2007
|
dynamicFontSize: undefined,
|
|
2093
2008
|
fontColor: DEFAULT_FONT_COLOR,
|
|
2094
|
-
fontName: undefined,
|
|
2095
|
-
backgroundColor: '',
|
|
2096
|
-
opacity: DEFAULT_OPACITY,
|
|
2097
|
-
bold: false,
|
|
2098
|
-
italic: false,
|
|
2099
|
-
strikethrough: false,
|
|
2100
|
-
underline: false,
|
|
2101
|
-
},
|
|
2102
|
-
};
|
|
2103
|
-
|
|
2104
|
-
/**
|
|
2105
|
-
* Factory function that creates an "Insert Variable" widget for a specific schema field.
|
|
2106
|
-
* The widget renders a dropdown of available variables and appends {varName} to the field value.
|
|
2107
|
-
*
|
|
2108
|
-
* @param targetKey - The schema field key to insert variables into (e.g., 'text', 'content')
|
|
2109
|
-
* @returns A PropPanelWidgetProps function that renders the variable picker
|
|
2110
|
-
*/
|
|
2111
|
-
const createInsertVariableWidget = (targetKey) => {
|
|
2112
|
-
return (props) => {
|
|
2113
|
-
const { rootElement, changeSchemas, activeSchema, options } = props;
|
|
2114
|
-
const variables = options.variables?.textVariables ?? [];
|
|
2115
|
-
console.log('[insertVariableWidget] targetKey:', targetKey);
|
|
2116
|
-
console.log('[insertVariableWidget] options:', options);
|
|
2117
|
-
console.log('[insertVariableWidget] options.variables:', options.variables);
|
|
2118
|
-
console.log('[insertVariableWidget] variables:', variables);
|
|
2119
|
-
console.log('[insertVariableWidget] variables.length:', variables.length);
|
|
2120
|
-
if (variables.length === 0) {
|
|
2121
|
-
console.log('[insertVariableWidget] NO VARIABLES - returning early');
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
console.log('[insertVariableWidget] Creating widget UI...');
|
|
2125
|
-
const container = document.createElement('div');
|
|
2126
|
-
container.style.cssText =
|
|
2127
|
-
'display:flex; gap:6px; align-items:center; margin-bottom:10px; z-index:9999; position:relative;';
|
|
2128
|
-
console.log('[insertVariableWidget] rootElement:', rootElement);
|
|
2129
|
-
console.log('[insertVariableWidget] rootElement.parentElement:', rootElement.parentElement);
|
|
2130
|
-
console.log('[insertVariableWidget] rootElement computed style:', window.getComputedStyle(rootElement));
|
|
2131
|
-
const label = document.createElement('span');
|
|
2132
|
-
label.textContent = 'Insert Variable:';
|
|
2133
|
-
label.style.cssText = 'font-size:12px; color:#666; white-space:nowrap;';
|
|
2134
|
-
const select = document.createElement('select');
|
|
2135
|
-
select.style.cssText =
|
|
2136
|
-
'flex:1; height:30px; border:1px solid #E0E0E0; border-radius:4px; padding:0 8px; font-size:13px; background:#fff; cursor:pointer; z-index:9999;';
|
|
2137
|
-
const blank = document.createElement('option');
|
|
2138
|
-
blank.value = '';
|
|
2139
|
-
blank.textContent = '— pick variable —';
|
|
2140
|
-
select.appendChild(blank);
|
|
2141
|
-
for (const v of variables) {
|
|
2142
|
-
const opt = document.createElement('option');
|
|
2143
|
-
opt.value = v.value;
|
|
2144
|
-
opt.textContent = v.label;
|
|
2145
|
-
select.appendChild(opt);
|
|
2146
|
-
}
|
|
2147
|
-
select.onchange = (e) => {
|
|
2148
|
-
const varName = e.target.value;
|
|
2149
|
-
if (!varName)
|
|
2150
|
-
return;
|
|
2151
|
-
const current = String(activeSchema[targetKey] ?? '');
|
|
2152
|
-
changeSchemas([{ key: targetKey, value: current + `{${varName}}`, schemaId: activeSchema.id }]);
|
|
2153
|
-
select.value = '';
|
|
2154
|
-
};
|
|
2155
|
-
container.appendChild(label);
|
|
2156
|
-
container.appendChild(select);
|
|
2157
|
-
rootElement.appendChild(container);
|
|
2158
|
-
console.log('[insertVariableWidget] Successfully created and appended widget');
|
|
2159
|
-
};
|
|
2160
|
-
};
|
|
2161
|
-
|
|
2162
|
-
const insertVariableWidget = createInsertVariableWidget('text');
|
|
2163
|
-
const mapDynamicVariables = (props) => {
|
|
2164
|
-
const { rootElement, changeSchemas, activeSchema } = props;
|
|
2165
|
-
const mvtSchema = activeSchema;
|
|
2166
|
-
const text = mvtSchema.text ?? '';
|
|
2167
|
-
if (!text) {
|
|
2168
|
-
rootElement.style.display = 'none';
|
|
2169
|
-
return;
|
|
2170
|
-
}
|
|
2171
|
-
const variables = JSON.parse(mvtSchema.content || '{}');
|
|
2172
|
-
const variablesChanged = updateVariablesFromText(text, variables);
|
|
2173
|
-
const varNames = Object.keys(variables);
|
|
2174
|
-
if (variablesChanged) {
|
|
2175
|
-
changeSchemas([
|
|
2176
|
-
{ key: 'content', value: JSON.stringify(variables), schemaId: activeSchema.id },
|
|
2177
|
-
{ key: 'variables', value: varNames, schemaId: activeSchema.id },
|
|
2178
|
-
{ key: 'readOnly', value: varNames.length === 0, schemaId: activeSchema.id },
|
|
2179
|
-
]);
|
|
2180
|
-
}
|
|
2181
|
-
// No UI needed — sample data is auto-generated from variable paths
|
|
2182
|
-
rootElement.style.display = 'none';
|
|
2183
|
-
};
|
|
2184
|
-
const propPanel = {
|
|
2185
|
-
schema: (propPanelProps) => {
|
|
2186
|
-
if (typeof propPanel$1.schema !== 'function') {
|
|
2187
|
-
throw new Error('Oops, is text schema no longer a function?');
|
|
2188
|
-
}
|
|
2189
|
-
// Safely call schema function with proper type handling
|
|
2190
|
-
const parentSchema = typeof propPanel$1.schema === 'function' ? propPanel$1.schema(propPanelProps) : {};
|
|
2191
|
-
return {
|
|
2192
|
-
insertVariablePicker: {
|
|
2193
|
-
type: 'void',
|
|
2194
|
-
widget: 'insertVariableWidget',
|
|
2195
|
-
bind: false,
|
|
2196
|
-
span: 24,
|
|
2197
|
-
},
|
|
2198
|
-
'----': { type: 'void', widget: 'Divider' },
|
|
2199
|
-
...parentSchema,
|
|
2200
|
-
dynamicVariables: {
|
|
2201
|
-
type: 'object',
|
|
2202
|
-
widget: 'mapDynamicVariables',
|
|
2203
|
-
bind: false,
|
|
2204
|
-
span: 0,
|
|
2205
|
-
},
|
|
2206
|
-
};
|
|
2207
|
-
},
|
|
2208
|
-
widgets: { ...(propPanel$1.widgets || {}), mapDynamicVariables, insertVariableWidget },
|
|
2209
|
-
defaultSchema: {
|
|
2210
|
-
...propPanel$1.defaultSchema,
|
|
2211
|
-
readOnly: false,
|
|
2212
|
-
type: 'text',
|
|
2213
|
-
text: 'Type Something...',
|
|
2214
|
-
width: 50,
|
|
2215
|
-
height: 15,
|
|
2216
|
-
content: '{}',
|
|
2217
|
-
variables: [],
|
|
2218
|
-
},
|
|
2219
|
-
};
|
|
2220
|
-
/** Known JS globals/keywords that should NOT be treated as user-defined variables */
|
|
2221
|
-
const RESERVED_NAMES$1 = new Set([
|
|
2222
|
-
'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'in',
|
|
2223
|
-
'void', 'delete', 'new', 'this', 'NaN', 'Infinity',
|
|
2224
|
-
'Math', 'String', 'Number', 'Boolean', 'Array', 'Object', 'Date', 'JSON',
|
|
2225
|
-
'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent',
|
|
2226
|
-
'encodeURI', 'encodeURIComponent', 'date', 'dateTime',
|
|
2227
|
-
'currentPage', 'totalPages',
|
|
2228
|
-
]);
|
|
2229
|
-
/**
|
|
2230
|
-
* Extract full dot-notation paths from an expression string.
|
|
2231
|
-
* E.g. "student.marks.sem1 > 80" → ["student.marks.sem1"]
|
|
2232
|
-
* Handles method calls: "student.name.toUpperCase()" → ["student.name"]
|
|
2233
|
-
* Skips string literals and reserved names.
|
|
2234
|
-
*/
|
|
2235
|
-
const extractDotPaths = (expr) => {
|
|
2236
|
-
// Replace string literals with spaces (preserving positions for nextChar lookup)
|
|
2237
|
-
const cleaned = expr.replace(/'[^']*'|"[^"]*"|`[^`]*`/g, (m) => ' '.repeat(m.length));
|
|
2238
|
-
const pathRegex = /[a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/g;
|
|
2239
|
-
const paths = new Set();
|
|
2240
|
-
let m;
|
|
2241
|
-
while ((m = pathRegex.exec(cleaned)) !== null) {
|
|
2242
|
-
let path = m[0];
|
|
2243
|
-
// If followed by '(', the last segment is a method call — trim it
|
|
2244
|
-
const nextChar = cleaned[m.index + path.length];
|
|
2245
|
-
if (nextChar === '(') {
|
|
2246
|
-
const lastDot = path.lastIndexOf('.');
|
|
2247
|
-
if (lastDot !== -1) {
|
|
2248
|
-
path = path.substring(0, lastDot);
|
|
2249
|
-
}
|
|
2250
|
-
else {
|
|
2251
|
-
// Standalone function call like parseInt(...) — skip
|
|
2252
|
-
continue;
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
const root = path.split('.')[0];
|
|
2256
|
-
if (!RESERVED_NAMES$1.has(root))
|
|
2257
|
-
paths.add(path);
|
|
2258
|
-
}
|
|
2259
|
-
return Array.from(paths);
|
|
2260
|
-
};
|
|
2261
|
-
/**
|
|
2262
|
-
* Build a nested default object from dot-paths.
|
|
2263
|
-
* E.g. ["student.name", "student.marks.sem1"] →
|
|
2264
|
-
* { name: "NAME", marks: { sem1: "SEM1" } }
|
|
2265
|
-
* Merges into an existing object, only adding missing leaves.
|
|
2266
|
-
* Returns true if anything was added.
|
|
2267
|
-
*/
|
|
2268
|
-
const buildNestedDefault = (obj, paths) => {
|
|
2269
|
-
let added = false;
|
|
2270
|
-
for (const path of paths) {
|
|
2271
|
-
const parts = path.split('.');
|
|
2272
|
-
if (parts.length <= 1)
|
|
2273
|
-
continue; // no nested parts
|
|
2274
|
-
let current = obj;
|
|
2275
|
-
for (let i = 1; i < parts.length - 1; i++) {
|
|
2276
|
-
if (!(parts[i] in current) || typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
2277
|
-
current[parts[i]] = {};
|
|
2278
|
-
added = true;
|
|
2279
|
-
}
|
|
2280
|
-
current = current[parts[i]];
|
|
2281
|
-
}
|
|
2282
|
-
const leaf = parts[parts.length - 1];
|
|
2283
|
-
if (!(leaf in current)) {
|
|
2284
|
-
current[leaf] = path.replace(/\./g, '_').toUpperCase();
|
|
2285
|
-
added = true;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
return added;
|
|
2289
|
-
};
|
|
2290
|
-
const updateVariablesFromText = (text, variables) => {
|
|
2291
|
-
// Find all {...} blocks and extract dot-notation paths from each
|
|
2292
|
-
const blockRegex = /\{([^{}]+)\}/g;
|
|
2293
|
-
const allPaths = new Set();
|
|
2294
|
-
let blockMatch;
|
|
2295
|
-
while ((blockMatch = blockRegex.exec(text)) !== null) {
|
|
2296
|
-
for (const path of extractDotPaths(blockMatch[1])) {
|
|
2297
|
-
allPaths.add(path);
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
// Group paths by root identifier
|
|
2301
|
-
const rootToPaths = new Map();
|
|
2302
|
-
for (const path of allPaths) {
|
|
2303
|
-
const root = path.split('.')[0];
|
|
2304
|
-
if (!rootToPaths.has(root))
|
|
2305
|
-
rootToPaths.set(root, []);
|
|
2306
|
-
rootToPaths.get(root).push(path);
|
|
2307
|
-
}
|
|
2308
|
-
const allRoots = new Set(rootToPaths.keys());
|
|
2309
|
-
let changed = false;
|
|
2310
|
-
for (const [root, paths] of rootToPaths) {
|
|
2311
|
-
const hasNested = paths.some((p) => p.includes('.'));
|
|
2312
|
-
if (hasNested) {
|
|
2313
|
-
// Parse existing value or start fresh
|
|
2314
|
-
let obj = {};
|
|
2315
|
-
if (root in variables) {
|
|
2316
|
-
try {
|
|
2317
|
-
const parsed = JSON.parse(variables[root]);
|
|
2318
|
-
if (typeof parsed === 'object' && parsed !== null) {
|
|
2319
|
-
obj = parsed;
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
catch {
|
|
2323
|
-
/* not JSON, will rebuild */
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
const added = buildNestedDefault(obj, paths);
|
|
2327
|
-
if (!(root in variables) || added) {
|
|
2328
|
-
variables[root] = JSON.stringify(obj);
|
|
2329
|
-
changed = true;
|
|
2330
|
-
}
|
|
2331
|
-
}
|
|
2332
|
-
else {
|
|
2333
|
-
if (!(root in variables)) {
|
|
2334
|
-
variables[root] = root.toUpperCase();
|
|
2335
|
-
changed = true;
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
|
-
// Remove variables whose root is no longer referenced
|
|
2340
|
-
for (const varName of Object.keys(variables)) {
|
|
2341
|
-
if (!allRoots.has(varName)) {
|
|
2342
|
-
delete variables[varName];
|
|
2343
|
-
changed = true;
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
return changed;
|
|
2009
|
+
fontName: undefined,
|
|
2010
|
+
backgroundColor: '',
|
|
2011
|
+
opacity: DEFAULT_OPACITY,
|
|
2012
|
+
bold: false,
|
|
2013
|
+
italic: false,
|
|
2014
|
+
strikethrough: false,
|
|
2015
|
+
underline: false,
|
|
2016
|
+
},
|
|
2347
2017
|
};
|
|
2348
2018
|
|
|
2349
2019
|
const _loadedGoogleFonts = new Set();
|
|
@@ -2384,9 +2054,10 @@ const replaceUnsupportedChars = (text, fontKitFont) => {
|
|
|
2384
2054
|
})
|
|
2385
2055
|
.join('');
|
|
2386
2056
|
};
|
|
2387
|
-
const uiRender$
|
|
2057
|
+
const uiRender$2 = async (arg) => {
|
|
2388
2058
|
const { value, schema, mode, onChange, stopEditing, tabIndex, placeholder, options, _cache } = arg;
|
|
2389
|
-
const
|
|
2059
|
+
const effectivePlaceholder = placeholder || 'Type Something...';
|
|
2060
|
+
const usePlaceholder = isEditable(mode, schema) && !value;
|
|
2390
2061
|
const getText = (element) => {
|
|
2391
2062
|
let text = element.innerText;
|
|
2392
2063
|
if (text.endsWith('\n')) {
|
|
@@ -2408,7 +2079,7 @@ const uiRender$1 = async (arg) => {
|
|
|
2408
2079
|
arg.rootElement.style.transition = 'opacity 0.15s ease-out';
|
|
2409
2080
|
}
|
|
2410
2081
|
const fontKitFont = await getFontKitFont(resolvedFontName, font, _cache);
|
|
2411
|
-
const textBlock = buildStyledTextContainer(arg, fontKitFont, usePlaceholder ?
|
|
2082
|
+
const textBlock = buildStyledTextContainer(arg, fontKitFont, usePlaceholder ? effectivePlaceholder : value);
|
|
2412
2083
|
// Fade in once the font is ready
|
|
2413
2084
|
if (isLoading) {
|
|
2414
2085
|
// Force a reflow so the transition triggers from the dimmed state
|
|
@@ -2458,7 +2129,7 @@ const uiRender$1 = async (arg) => {
|
|
|
2458
2129
|
if (usePlaceholder) {
|
|
2459
2130
|
textBlock.style.color = PLACEHOLDER_FONT_COLOR;
|
|
2460
2131
|
textBlock.addEventListener('focus', () => {
|
|
2461
|
-
if (textBlock.innerText ===
|
|
2132
|
+
if (textBlock.innerText === effectivePlaceholder) {
|
|
2462
2133
|
textBlock.innerText = '';
|
|
2463
2134
|
textBlock.style.color = schema.fontColor ?? DEFAULT_FONT_COLOR;
|
|
2464
2135
|
}
|
|
@@ -2476,123 +2147,413 @@ const uiRender$1 = async (arg) => {
|
|
|
2476
2147
|
selection?.removeAllRanges();
|
|
2477
2148
|
selection?.addRange(range);
|
|
2478
2149
|
}
|
|
2479
|
-
});
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
const buildStyledTextContainer = (arg, fontKitFont, value) => {
|
|
2154
|
+
const { schema, rootElement, mode } = arg;
|
|
2155
|
+
let dynamicFontSize = undefined;
|
|
2156
|
+
if (schema.dynamicFontSize && value) {
|
|
2157
|
+
dynamicFontSize = calculateDynamicFontSize({
|
|
2158
|
+
textSchema: schema,
|
|
2159
|
+
fontKitFont,
|
|
2160
|
+
value,
|
|
2161
|
+
startingFontSize: dynamicFontSize,
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
// Depending on vertical alignment, we need to move the top or bottom of the font to keep
|
|
2165
|
+
// it within it's defined box and align it with the generated pdf.
|
|
2166
|
+
const { topAdj, bottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE, schema.lineHeight ?? DEFAULT_LINE_HEIGHT, schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT);
|
|
2167
|
+
const topAdjustment = topAdj.toString();
|
|
2168
|
+
const bottomAdjustment = bottomAdj.toString();
|
|
2169
|
+
const container = document.createElement('div');
|
|
2170
|
+
const containerStyle = {
|
|
2171
|
+
padding: 0,
|
|
2172
|
+
resize: 'none',
|
|
2173
|
+
backgroundColor: getBackgroundColor(value, schema),
|
|
2174
|
+
border: 'none',
|
|
2175
|
+
display: 'flex',
|
|
2176
|
+
flexDirection: 'column',
|
|
2177
|
+
justifyContent: mapVerticalAlignToFlex(schema.verticalAlignment),
|
|
2178
|
+
width: '100%',
|
|
2179
|
+
height: '100%',
|
|
2180
|
+
cursor: isEditable(mode, schema) ? 'text' : 'default',
|
|
2181
|
+
};
|
|
2182
|
+
Object.assign(container.style, containerStyle);
|
|
2183
|
+
rootElement.innerHTML = '';
|
|
2184
|
+
rootElement.appendChild(container);
|
|
2185
|
+
// text decoration
|
|
2186
|
+
const textDecorations = [];
|
|
2187
|
+
if (schema.strikethrough)
|
|
2188
|
+
textDecorations.push('line-through');
|
|
2189
|
+
if (schema.underline)
|
|
2190
|
+
textDecorations.push('underline');
|
|
2191
|
+
const textBlockStyle = {
|
|
2192
|
+
// Font formatting styles
|
|
2193
|
+
fontFamily: schema.fontName ? `'${schema.fontName}'` : 'inherit',
|
|
2194
|
+
fontWeight: schema.bold ? 'bold' : 'normal',
|
|
2195
|
+
fontStyle: schema.italic ? 'italic' : 'normal',
|
|
2196
|
+
color: schema.fontColor ? schema.fontColor : DEFAULT_FONT_COLOR,
|
|
2197
|
+
fontSize: `${dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE}pt`,
|
|
2198
|
+
letterSpacing: `${schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING}pt`,
|
|
2199
|
+
lineHeight: `${schema.lineHeight ?? DEFAULT_LINE_HEIGHT}em`,
|
|
2200
|
+
textAlign: schema.alignment ?? DEFAULT_ALIGNMENT,
|
|
2201
|
+
whiteSpace: 'pre-wrap',
|
|
2202
|
+
wordBreak: 'break-word',
|
|
2203
|
+
// Block layout styles
|
|
2204
|
+
resize: 'none',
|
|
2205
|
+
border: 'none',
|
|
2206
|
+
outline: 'none',
|
|
2207
|
+
marginBottom: `${bottomAdjustment}px`,
|
|
2208
|
+
paddingTop: `${topAdjustment}px`,
|
|
2209
|
+
backgroundColor: 'transparent',
|
|
2210
|
+
textDecoration: textDecorations.join(' '),
|
|
2211
|
+
};
|
|
2212
|
+
const textBlock = document.createElement('div');
|
|
2213
|
+
textBlock.id = 'text-' + String(schema.id);
|
|
2214
|
+
Object.assign(textBlock.style, textBlockStyle);
|
|
2215
|
+
container.appendChild(textBlock);
|
|
2216
|
+
return textBlock;
|
|
2217
|
+
};
|
|
2218
|
+
/**
|
|
2219
|
+
* Firefox doesn't support 'plaintext-only' contentEditable mode, which we want to avoid mark-up.
|
|
2220
|
+
* This function adds a workaround for Firefox to make the contentEditable element behave like 'plaintext-only'.
|
|
2221
|
+
*/
|
|
2222
|
+
const makeElementPlainTextContentEditable = (element) => {
|
|
2223
|
+
if (!isFirefox()) {
|
|
2224
|
+
element.contentEditable = 'plaintext-only';
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
element.contentEditable = 'true';
|
|
2228
|
+
element.addEventListener('keydown', (e) => {
|
|
2229
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
2230
|
+
e.preventDefault();
|
|
2231
|
+
document.execCommand('insertLineBreak', false, undefined);
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
element.addEventListener('paste', (e) => {
|
|
2235
|
+
e.preventDefault();
|
|
2236
|
+
const paste = e.clipboardData?.getData('text');
|
|
2237
|
+
const selection = window.getSelection();
|
|
2238
|
+
if (!selection?.rangeCount)
|
|
2239
|
+
return;
|
|
2240
|
+
selection.deleteFromDocument();
|
|
2241
|
+
selection.getRangeAt(0).insertNode(document.createTextNode(paste || ''));
|
|
2242
|
+
selection.collapseToEnd();
|
|
2243
|
+
});
|
|
2244
|
+
};
|
|
2245
|
+
const mapVerticalAlignToFlex = (verticalAlignmentValue) => {
|
|
2246
|
+
switch (verticalAlignmentValue) {
|
|
2247
|
+
case VERTICAL_ALIGN_TOP:
|
|
2248
|
+
return 'flex-start';
|
|
2249
|
+
case VERTICAL_ALIGN_MIDDLE:
|
|
2250
|
+
return 'center';
|
|
2251
|
+
case VERTICAL_ALIGN_BOTTOM:
|
|
2252
|
+
return 'flex-end';
|
|
2253
|
+
}
|
|
2254
|
+
return 'flex-start';
|
|
2255
|
+
};
|
|
2256
|
+
const getBackgroundColor = (value, schema) => {
|
|
2257
|
+
if (!value || !schema.backgroundColor)
|
|
2258
|
+
return 'transparent';
|
|
2259
|
+
return schema.backgroundColor;
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
const substituteVariables = (text, variablesIn, extraContext) => {
|
|
2263
|
+
if (!text) {
|
|
2264
|
+
return '';
|
|
2265
|
+
}
|
|
2266
|
+
let variables;
|
|
2267
|
+
try {
|
|
2268
|
+
variables =
|
|
2269
|
+
typeof variablesIn === 'string'
|
|
2270
|
+
? JSON.parse(variablesIn || '{}')
|
|
2271
|
+
: variablesIn;
|
|
2272
|
+
}
|
|
2273
|
+
catch {
|
|
2274
|
+
throw new SyntaxError(`[@campxdev/schemas] richText: invalid JSON string '${variablesIn}'`);
|
|
2275
|
+
}
|
|
2276
|
+
// Merge extra context (e.g. currentPage, totalPages) with user variables
|
|
2277
|
+
// System context takes precedence over user variables
|
|
2278
|
+
const merged = extraContext ? { ...variables, ...extraContext } : variables;
|
|
2279
|
+
// Use the full JS expression evaluator — supports {varName}, {expr * 2}, {str.toUpperCase()}, etc.
|
|
2280
|
+
const result = expression.replacePlaceholders({ content: text, variables: merged, schemas: [] });
|
|
2281
|
+
// Strip any remaining unresolved {placeholders} for clean output
|
|
2282
|
+
return result.replace(/\{[^{}]+\}/g, '');
|
|
2283
|
+
};
|
|
2284
|
+
const validateVariables = (value, schema) => {
|
|
2285
|
+
if (!schema.variables || schema.variables.length === 0) {
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2288
|
+
let values;
|
|
2289
|
+
try {
|
|
2290
|
+
values = value ? JSON.parse(value) : {};
|
|
2291
|
+
}
|
|
2292
|
+
catch {
|
|
2293
|
+
throw new SyntaxError(`[@campxdev/generator] invalid JSON string '${value}' for variables in field ${schema.name}`);
|
|
2294
|
+
}
|
|
2295
|
+
for (const variable of schema.variables) {
|
|
2296
|
+
if (!values[variable]) {
|
|
2297
|
+
if (schema.required) {
|
|
2298
|
+
throw new Error(`[@campxdev/generator] variable ${variable} is missing for field ${schema.name}`);
|
|
2299
|
+
}
|
|
2300
|
+
return false;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return true;
|
|
2304
|
+
};
|
|
2305
|
+
|
|
2306
|
+
const pdfRender = async (arg) => {
|
|
2307
|
+
const { value, schema, pageContext, ...rest } = arg;
|
|
2308
|
+
// Static mode: no template text → render value directly as plain text
|
|
2309
|
+
if (!schema.text) {
|
|
2310
|
+
await pdfRender$1({ value, schema, ...rest });
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
// readOnly: value is already resolved by generate.ts via replacePlaceholders
|
|
2314
|
+
if (schema.readOnly) {
|
|
2315
|
+
await pdfRender$1({ value, schema, ...rest });
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
// Dynamic mode (form): substitute variables in template
|
|
2319
|
+
if (!validateVariables(value, schema)) {
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
const renderArgs = {
|
|
2323
|
+
value: substituteVariables(schema.text, value || '{}', pageContext),
|
|
2324
|
+
schema,
|
|
2325
|
+
...rest,
|
|
2326
|
+
};
|
|
2327
|
+
await pdfRender$1(renderArgs);
|
|
2328
|
+
};
|
|
2329
|
+
|
|
2330
|
+
const variablePrefixSuffixWidget = (props) => {
|
|
2331
|
+
const { rootElement, changeSchemas, activeSchema, options } = props;
|
|
2332
|
+
const schema = activeSchema;
|
|
2333
|
+
const variables = options.variables?.textVariables ?? [];
|
|
2334
|
+
if (variables.length === 0) {
|
|
2335
|
+
const hint = document.createElement('div');
|
|
2336
|
+
hint.textContent = 'Type {variableName} directly in the text field';
|
|
2337
|
+
hint.style.cssText = 'font-size:11px;color:#aaa;margin-bottom:8px;font-style:italic;';
|
|
2338
|
+
rootElement.appendChild(hint);
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
const container = document.createElement('div');
|
|
2342
|
+
container.style.cssText = 'display:flex;flex-direction:column;gap:4px;margin-bottom:8px;';
|
|
2343
|
+
const title = document.createElement('div');
|
|
2344
|
+
title.textContent = 'Insert Variable:';
|
|
2345
|
+
title.style.cssText = 'font-size:12px;color:#666;margin-bottom:2px;';
|
|
2346
|
+
container.appendChild(title);
|
|
2347
|
+
for (const v of variables) {
|
|
2348
|
+
const row = document.createElement('div');
|
|
2349
|
+
row.style.cssText = 'display:flex;align-items:center;gap:6px;';
|
|
2350
|
+
const makeBtn = (side) => {
|
|
2351
|
+
const btn = document.createElement('button');
|
|
2352
|
+
btn.textContent = '+';
|
|
2353
|
+
btn.title = side === 'prefix' ? `Add ${v.label} as prefix` : `Add ${v.label} as suffix`;
|
|
2354
|
+
btn.style.cssText =
|
|
2355
|
+
'width:22px;height:22px;cursor:pointer;border:1px solid #d9d9d9;border-radius:4px;' +
|
|
2356
|
+
'background:#fff;font-size:14px;line-height:1;display:flex;align-items:center;justify-content:center;padding:0;';
|
|
2357
|
+
btn.onclick = () => {
|
|
2358
|
+
const current = String(schema.text ?? '');
|
|
2359
|
+
const next = side === 'prefix' ? `{${v.value}} ${current}` : `${current} {${v.value}}`;
|
|
2360
|
+
changeSchemas([{ key: 'text', value: next, schemaId: activeSchema.id }]);
|
|
2361
|
+
};
|
|
2362
|
+
return btn;
|
|
2363
|
+
};
|
|
2364
|
+
const label = document.createElement('span');
|
|
2365
|
+
label.textContent = v.label;
|
|
2366
|
+
label.style.cssText = 'font-size:12px;flex:1;color:#333;';
|
|
2367
|
+
row.appendChild(makeBtn('prefix'));
|
|
2368
|
+
row.appendChild(label);
|
|
2369
|
+
row.appendChild(makeBtn('suffix'));
|
|
2370
|
+
container.appendChild(row);
|
|
2371
|
+
}
|
|
2372
|
+
rootElement.appendChild(container);
|
|
2373
|
+
};
|
|
2374
|
+
const mapDynamicVariables$1 = (props) => {
|
|
2375
|
+
const { rootElement, changeSchemas, activeSchema } = props;
|
|
2376
|
+
const rtSchema = activeSchema;
|
|
2377
|
+
const text = rtSchema.text ?? '';
|
|
2378
|
+
if (!text) {
|
|
2379
|
+
rootElement.style.display = 'none';
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
const variables = JSON.parse((rtSchema.content && rtSchema.content !== '' ? rtSchema.content : '{}'));
|
|
2383
|
+
const variablesChanged = updateVariablesFromText(text, variables);
|
|
2384
|
+
const varNames = Object.keys(variables);
|
|
2385
|
+
if (variablesChanged) {
|
|
2386
|
+
changeSchemas([
|
|
2387
|
+
{ key: 'content', value: JSON.stringify(variables), schemaId: activeSchema.id },
|
|
2388
|
+
{ key: 'variables', value: varNames, schemaId: activeSchema.id },
|
|
2389
|
+
{ key: 'readOnly', value: varNames.length === 0, schemaId: activeSchema.id },
|
|
2390
|
+
]);
|
|
2391
|
+
}
|
|
2392
|
+
rootElement.style.display = 'none';
|
|
2393
|
+
};
|
|
2394
|
+
const propPanel$1 = {
|
|
2395
|
+
schema: (propPanelProps) => {
|
|
2396
|
+
if (typeof propPanel$2.schema !== 'function') {
|
|
2397
|
+
throw new Error('Oops, is text schema no longer a function?');
|
|
2398
|
+
}
|
|
2399
|
+
const parentSchema = typeof propPanel$2.schema === 'function' ? propPanel$2.schema(propPanelProps) : {};
|
|
2400
|
+
return {
|
|
2401
|
+
insertVariablePicker: {
|
|
2402
|
+
type: 'void',
|
|
2403
|
+
widget: 'variablePrefixSuffixWidget',
|
|
2404
|
+
bind: false,
|
|
2405
|
+
span: 24,
|
|
2406
|
+
},
|
|
2407
|
+
'----': { type: 'void', widget: 'Divider' },
|
|
2408
|
+
...parentSchema,
|
|
2409
|
+
dynamicVariables: {
|
|
2410
|
+
type: 'object',
|
|
2411
|
+
widget: 'mapDynamicVariables',
|
|
2412
|
+
bind: false,
|
|
2413
|
+
span: 0,
|
|
2414
|
+
},
|
|
2415
|
+
};
|
|
2416
|
+
},
|
|
2417
|
+
widgets: { ...(propPanel$2.widgets || {}), mapDynamicVariables: mapDynamicVariables$1, variablePrefixSuffixWidget },
|
|
2418
|
+
defaultSchema: {
|
|
2419
|
+
...propPanel$2.defaultSchema,
|
|
2420
|
+
readOnly: false,
|
|
2421
|
+
type: 'richText',
|
|
2422
|
+
text: '',
|
|
2423
|
+
width: 50,
|
|
2424
|
+
height: 7,
|
|
2425
|
+
content: '',
|
|
2426
|
+
variables: [],
|
|
2427
|
+
},
|
|
2428
|
+
};
|
|
2429
|
+
/** Known JS globals/keywords that should NOT be treated as user-defined variables */
|
|
2430
|
+
const RESERVED_NAMES$1 = new Set([
|
|
2431
|
+
'true', 'false', 'null', 'undefined', 'typeof', 'instanceof', 'in',
|
|
2432
|
+
'void', 'delete', 'new', 'this', 'NaN', 'Infinity',
|
|
2433
|
+
'Math', 'String', 'Number', 'Boolean', 'Array', 'Object', 'Date', 'JSON',
|
|
2434
|
+
'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent',
|
|
2435
|
+
'encodeURI', 'encodeURIComponent', 'date', 'dateTime',
|
|
2436
|
+
'currentPage', 'totalPages',
|
|
2437
|
+
]);
|
|
2438
|
+
const extractDotPaths = (expr) => {
|
|
2439
|
+
const cleaned = expr.replace(/'[^']*'|"[^"]*"|`[^`]*`/g, (m) => ' '.repeat(m.length));
|
|
2440
|
+
const pathRegex = /[a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/g;
|
|
2441
|
+
const paths = new Set();
|
|
2442
|
+
let m;
|
|
2443
|
+
while ((m = pathRegex.exec(cleaned)) !== null) {
|
|
2444
|
+
let path = m[0];
|
|
2445
|
+
const nextChar = cleaned[m.index + path.length];
|
|
2446
|
+
if (nextChar === '(') {
|
|
2447
|
+
const lastDot = path.lastIndexOf('.');
|
|
2448
|
+
if (lastDot !== -1) {
|
|
2449
|
+
path = path.substring(0, lastDot);
|
|
2450
|
+
}
|
|
2451
|
+
else {
|
|
2452
|
+
continue;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const root = path.split('.')[0];
|
|
2456
|
+
if (!RESERVED_NAMES$1.has(root))
|
|
2457
|
+
paths.add(path);
|
|
2480
2458
|
}
|
|
2459
|
+
return Array.from(paths);
|
|
2481
2460
|
};
|
|
2482
|
-
const
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2461
|
+
const buildNestedDefault = (obj, paths) => {
|
|
2462
|
+
let added = false;
|
|
2463
|
+
for (const path of paths) {
|
|
2464
|
+
const parts = path.split('.');
|
|
2465
|
+
if (parts.length <= 1)
|
|
2466
|
+
continue;
|
|
2467
|
+
let current = obj;
|
|
2468
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
2469
|
+
if (!(parts[i] in current) || typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
2470
|
+
current[parts[i]] = {};
|
|
2471
|
+
added = true;
|
|
2472
|
+
}
|
|
2473
|
+
current = current[parts[i]];
|
|
2474
|
+
}
|
|
2475
|
+
const leaf = parts[parts.length - 1];
|
|
2476
|
+
const isOldLeafFormat = leaf in current && (/^[A-Z][A-Z0-9_]+$/.test(String(current[leaf])) || /^\{\{.*\}\}$/.test(String(current[leaf])));
|
|
2477
|
+
if (!(leaf in current) || isOldLeafFormat) {
|
|
2478
|
+
current[leaf] = path.replace(/\./g, ' ');
|
|
2479
|
+
added = true;
|
|
2480
|
+
}
|
|
2492
2481
|
}
|
|
2493
|
-
|
|
2494
|
-
// it within it's defined box and align it with the generated pdf.
|
|
2495
|
-
const { topAdj, bottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE, schema.lineHeight ?? DEFAULT_LINE_HEIGHT, schema.verticalAlignment ?? DEFAULT_VERTICAL_ALIGNMENT);
|
|
2496
|
-
const topAdjustment = topAdj.toString();
|
|
2497
|
-
const bottomAdjustment = bottomAdj.toString();
|
|
2498
|
-
const container = document.createElement('div');
|
|
2499
|
-
const containerStyle = {
|
|
2500
|
-
padding: 0,
|
|
2501
|
-
resize: 'none',
|
|
2502
|
-
backgroundColor: getBackgroundColor(value, schema),
|
|
2503
|
-
border: 'none',
|
|
2504
|
-
display: 'flex',
|
|
2505
|
-
flexDirection: 'column',
|
|
2506
|
-
justifyContent: mapVerticalAlignToFlex(schema.verticalAlignment),
|
|
2507
|
-
width: '100%',
|
|
2508
|
-
height: '100%',
|
|
2509
|
-
cursor: isEditable(mode, schema) ? 'text' : 'default',
|
|
2510
|
-
};
|
|
2511
|
-
Object.assign(container.style, containerStyle);
|
|
2512
|
-
rootElement.innerHTML = '';
|
|
2513
|
-
rootElement.appendChild(container);
|
|
2514
|
-
// text decoration
|
|
2515
|
-
const textDecorations = [];
|
|
2516
|
-
if (schema.strikethrough)
|
|
2517
|
-
textDecorations.push('line-through');
|
|
2518
|
-
if (schema.underline)
|
|
2519
|
-
textDecorations.push('underline');
|
|
2520
|
-
const textBlockStyle = {
|
|
2521
|
-
// Font formatting styles
|
|
2522
|
-
fontFamily: schema.fontName ? `'${schema.fontName}'` : 'inherit',
|
|
2523
|
-
fontWeight: schema.bold ? 'bold' : 'normal',
|
|
2524
|
-
fontStyle: schema.italic ? 'italic' : 'normal',
|
|
2525
|
-
color: schema.fontColor ? schema.fontColor : DEFAULT_FONT_COLOR,
|
|
2526
|
-
fontSize: `${dynamicFontSize ?? schema.fontSize ?? DEFAULT_FONT_SIZE}pt`,
|
|
2527
|
-
letterSpacing: `${schema.characterSpacing ?? DEFAULT_CHARACTER_SPACING}pt`,
|
|
2528
|
-
lineHeight: `${schema.lineHeight ?? DEFAULT_LINE_HEIGHT}em`,
|
|
2529
|
-
textAlign: schema.alignment ?? DEFAULT_ALIGNMENT,
|
|
2530
|
-
whiteSpace: 'pre-wrap',
|
|
2531
|
-
wordBreak: 'break-word',
|
|
2532
|
-
// Block layout styles
|
|
2533
|
-
resize: 'none',
|
|
2534
|
-
border: 'none',
|
|
2535
|
-
outline: 'none',
|
|
2536
|
-
marginBottom: `${bottomAdjustment}px`,
|
|
2537
|
-
paddingTop: `${topAdjustment}px`,
|
|
2538
|
-
backgroundColor: 'transparent',
|
|
2539
|
-
textDecoration: textDecorations.join(' '),
|
|
2540
|
-
};
|
|
2541
|
-
const textBlock = document.createElement('div');
|
|
2542
|
-
textBlock.id = 'text-' + String(schema.id);
|
|
2543
|
-
Object.assign(textBlock.style, textBlockStyle);
|
|
2544
|
-
container.appendChild(textBlock);
|
|
2545
|
-
return textBlock;
|
|
2482
|
+
return added;
|
|
2546
2483
|
};
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2484
|
+
const updateVariablesFromText = (text, variables) => {
|
|
2485
|
+
const blockRegex = /\{([^{}]+)\}/g;
|
|
2486
|
+
const allPaths = new Set();
|
|
2487
|
+
let blockMatch;
|
|
2488
|
+
while ((blockMatch = blockRegex.exec(text)) !== null) {
|
|
2489
|
+
for (const path of extractDotPaths(blockMatch[1])) {
|
|
2490
|
+
allPaths.add(path);
|
|
2491
|
+
}
|
|
2555
2492
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2493
|
+
const rootToPaths = new Map();
|
|
2494
|
+
for (const path of allPaths) {
|
|
2495
|
+
const root = path.split('.')[0];
|
|
2496
|
+
if (!rootToPaths.has(root))
|
|
2497
|
+
rootToPaths.set(root, []);
|
|
2498
|
+
rootToPaths.get(root).push(path);
|
|
2499
|
+
}
|
|
2500
|
+
const allRoots = new Set(rootToPaths.keys());
|
|
2501
|
+
let changed = false;
|
|
2502
|
+
for (const [root, paths] of rootToPaths) {
|
|
2503
|
+
const hasNested = paths.some((p) => p.includes('.'));
|
|
2504
|
+
if (hasNested) {
|
|
2505
|
+
let obj = {};
|
|
2506
|
+
if (root in variables) {
|
|
2507
|
+
try {
|
|
2508
|
+
const parsed = JSON.parse(variables[root]);
|
|
2509
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
2510
|
+
obj = parsed;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
catch {
|
|
2514
|
+
/* not JSON, will rebuild */
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
const added = buildNestedDefault(obj, paths);
|
|
2518
|
+
if (!(root in variables) || added) {
|
|
2519
|
+
variables[root] = JSON.stringify(obj);
|
|
2520
|
+
changed = true;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
else {
|
|
2524
|
+
const existingVal = variables[root];
|
|
2525
|
+
const isStaleObject = typeof existingVal === 'string' && (() => {
|
|
2526
|
+
try {
|
|
2527
|
+
const p = JSON.parse(existingVal);
|
|
2528
|
+
return typeof p === 'object' && p !== null;
|
|
2529
|
+
}
|
|
2530
|
+
catch {
|
|
2531
|
+
return false;
|
|
2532
|
+
}
|
|
2533
|
+
})();
|
|
2534
|
+
const isOldFormat = typeof existingVal === 'string' && (/^[A-Z][A-Z0-9_]*$/.test(existingVal) || /^\{\{.*\}\}$/.test(existingVal));
|
|
2535
|
+
if (!(root in variables) || isStaleObject || isOldFormat) {
|
|
2536
|
+
variables[root] = root;
|
|
2537
|
+
changed = true;
|
|
2538
|
+
}
|
|
2561
2539
|
}
|
|
2562
|
-
});
|
|
2563
|
-
element.addEventListener('paste', (e) => {
|
|
2564
|
-
e.preventDefault();
|
|
2565
|
-
const paste = e.clipboardData?.getData('text');
|
|
2566
|
-
const selection = window.getSelection();
|
|
2567
|
-
if (!selection?.rangeCount)
|
|
2568
|
-
return;
|
|
2569
|
-
selection.deleteFromDocument();
|
|
2570
|
-
selection.getRangeAt(0).insertNode(document.createTextNode(paste || ''));
|
|
2571
|
-
selection.collapseToEnd();
|
|
2572
|
-
});
|
|
2573
|
-
};
|
|
2574
|
-
const mapVerticalAlignToFlex = (verticalAlignmentValue) => {
|
|
2575
|
-
switch (verticalAlignmentValue) {
|
|
2576
|
-
case VERTICAL_ALIGN_TOP:
|
|
2577
|
-
return 'flex-start';
|
|
2578
|
-
case VERTICAL_ALIGN_MIDDLE:
|
|
2579
|
-
return 'center';
|
|
2580
|
-
case VERTICAL_ALIGN_BOTTOM:
|
|
2581
|
-
return 'flex-end';
|
|
2582
2540
|
}
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2541
|
+
for (const varName of Object.keys(variables)) {
|
|
2542
|
+
if (!allRoots.has(varName)) {
|
|
2543
|
+
delete variables[varName];
|
|
2544
|
+
changed = true;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
return changed;
|
|
2589
2548
|
};
|
|
2590
2549
|
|
|
2591
|
-
|
|
2550
|
+
/** Format {expr} → {{ expr with dots as spaces }} for display */
|
|
2551
|
+
const formatTemplateDisplay$1 = (text) => text.replace(/\{([^{}]+)\}/g, (_, expr) => '{{ ' + expr.trim().replace(/\./g, ' ') + ' }}');
|
|
2552
|
+
const uiRender$1 = async (arg) => {
|
|
2592
2553
|
const { value, schema, rootElement, mode, onChange, pageContext, ...rest } = arg;
|
|
2593
2554
|
// Static mode: no template text → delegate to plain text behavior
|
|
2594
2555
|
if (!schema.text) {
|
|
2595
|
-
await uiRender$
|
|
2556
|
+
await uiRender$2({ ...arg, value: value || '' });
|
|
2596
2557
|
return;
|
|
2597
2558
|
}
|
|
2598
2559
|
// Dynamic mode: template with optional variables
|
|
@@ -2602,8 +2563,8 @@ const uiRender = async (arg) => {
|
|
|
2602
2563
|
await formUiRender(arg);
|
|
2603
2564
|
return;
|
|
2604
2565
|
}
|
|
2605
|
-
await uiRender$
|
|
2606
|
-
value: isEditable(mode, schema) ? text :
|
|
2566
|
+
await uiRender$2({
|
|
2567
|
+
value: isEditable(mode, schema) ? text : formatTemplateDisplay$1(text),
|
|
2607
2568
|
schema,
|
|
2608
2569
|
mode: mode === 'form' ? 'viewer' : mode, // if no variables for form it's just a viewer
|
|
2609
2570
|
rootElement,
|
|
@@ -2624,11 +2585,34 @@ const uiRender = async (arg) => {
|
|
|
2624
2585
|
throw new Error('Text block not found. Ensure the text block has an id of "text-" + schema.id');
|
|
2625
2586
|
}
|
|
2626
2587
|
if (mode === 'designer') {
|
|
2627
|
-
|
|
2588
|
+
// Show formatted display initially: {value} → {{ value }}
|
|
2589
|
+
if (text) {
|
|
2590
|
+
textBlock.textContent = formatTemplateDisplay$1(text);
|
|
2591
|
+
}
|
|
2592
|
+
textBlock.addEventListener('focus', () => {
|
|
2593
|
+
// Switch to raw template for editing
|
|
2594
|
+
textBlock.textContent = text;
|
|
2595
|
+
const sel = window.getSelection();
|
|
2596
|
+
const range = document.createRange();
|
|
2597
|
+
range.selectNodeContents(textBlock);
|
|
2598
|
+
range.collapse(false);
|
|
2599
|
+
sel?.removeAllRanges();
|
|
2600
|
+
sel?.addRange(range);
|
|
2601
|
+
});
|
|
2602
|
+
textBlock.addEventListener('blur', () => {
|
|
2628
2603
|
text = textBlock.textContent || '';
|
|
2604
|
+
if (onChange) {
|
|
2605
|
+
onChange({ key: 'text', value: text });
|
|
2606
|
+
}
|
|
2607
|
+
// Show formatted display again
|
|
2608
|
+
textBlock.textContent = formatTemplateDisplay$1(text);
|
|
2609
|
+
});
|
|
2610
|
+
textBlock.addEventListener('keyup', (event) => {
|
|
2611
|
+
const currentText = textBlock.textContent || '';
|
|
2629
2612
|
if (keyPressShouldBeChecked(event)) {
|
|
2630
|
-
const newNumVariables = countUniqueVariableNames(
|
|
2613
|
+
const newNumVariables = countUniqueVariableNames(currentText);
|
|
2631
2614
|
if (numVariables !== newNumVariables) {
|
|
2615
|
+
text = currentText;
|
|
2632
2616
|
if (onChange) {
|
|
2633
2617
|
onChange({ key: 'text', value: text });
|
|
2634
2618
|
}
|
|
@@ -2813,7 +2797,6 @@ const countUniqueVariableNames = (content) => {
|
|
|
2813
2797
|
/**
|
|
2814
2798
|
* An optimisation to try to minimise jank while typing.
|
|
2815
2799
|
* Only check whether variables were modified based on certain key presses.
|
|
2816
|
-
* Regex would otherwise be performed on every key press (which isn't terrible, but this code helps).
|
|
2817
2800
|
*/
|
|
2818
2801
|
const keyPressShouldBeChecked = (event) => {
|
|
2819
2802
|
if (event.key === 'ArrowUp' ||
|
|
@@ -2835,15 +2818,179 @@ const keyPressShouldBeChecked = (event) => {
|
|
|
2835
2818
|
return true;
|
|
2836
2819
|
};
|
|
2837
2820
|
|
|
2821
|
+
const schema$1 = {
|
|
2822
|
+
pdf: pdfRender,
|
|
2823
|
+
ui: uiRender$1,
|
|
2824
|
+
propPanel: propPanel$1,
|
|
2825
|
+
icon: createSvgStr(lucide.FileText),
|
|
2826
|
+
uninterruptedEditMode: true,
|
|
2827
|
+
};
|
|
2828
|
+
|
|
2829
|
+
/** Replaces any existing {var} in the template with {newVar}, or appends if none exists */
|
|
2830
|
+
const replaceOrAppendVariable = (currentText, varName) => {
|
|
2831
|
+
// Replace any existing {identifier} block with the new variable
|
|
2832
|
+
const replaced = currentText.replace(/\{[a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*\}/g, `{${varName}}`);
|
|
2833
|
+
if (replaced !== currentText)
|
|
2834
|
+
return replaced;
|
|
2835
|
+
// No existing variable — append with a space separator
|
|
2836
|
+
return currentText ? `${currentText} {${varName}}` : `{${varName}}`;
|
|
2837
|
+
};
|
|
2838
|
+
const singleVariablePickerWidget = (props) => {
|
|
2839
|
+
const { rootElement, changeSchemas, activeSchema, options } = props;
|
|
2840
|
+
const schema = activeSchema;
|
|
2841
|
+
const variables = options.variables?.textVariables ?? [];
|
|
2842
|
+
if (variables.length === 0)
|
|
2843
|
+
return;
|
|
2844
|
+
const container = document.createElement('div');
|
|
2845
|
+
container.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:10px;';
|
|
2846
|
+
const label = document.createElement('span');
|
|
2847
|
+
label.textContent = 'Variable:';
|
|
2848
|
+
label.style.cssText = 'font-size:12px;color:#666;white-space:nowrap;';
|
|
2849
|
+
const select = document.createElement('select');
|
|
2850
|
+
select.style.cssText =
|
|
2851
|
+
'flex:1;height:30px;border:1px solid #E0E0E0;border-radius:4px;padding:0 8px;font-size:13px;background:#fff;cursor:pointer;';
|
|
2852
|
+
const blank = document.createElement('option');
|
|
2853
|
+
blank.value = '';
|
|
2854
|
+
blank.textContent = '— pick variable —';
|
|
2855
|
+
select.appendChild(blank);
|
|
2856
|
+
for (const v of variables) {
|
|
2857
|
+
const opt = document.createElement('option');
|
|
2858
|
+
opt.value = v.value;
|
|
2859
|
+
opt.textContent = v.label;
|
|
2860
|
+
select.appendChild(opt);
|
|
2861
|
+
}
|
|
2862
|
+
select.onchange = (e) => {
|
|
2863
|
+
const varName = e.target.value;
|
|
2864
|
+
if (!varName)
|
|
2865
|
+
return;
|
|
2866
|
+
const current = String(schema.text ?? '');
|
|
2867
|
+
const next = replaceOrAppendVariable(current, varName);
|
|
2868
|
+
changeSchemas([{ key: 'text', value: next, schemaId: activeSchema.id }]);
|
|
2869
|
+
select.value = '';
|
|
2870
|
+
};
|
|
2871
|
+
container.appendChild(label);
|
|
2872
|
+
container.appendChild(select);
|
|
2873
|
+
rootElement.appendChild(container);
|
|
2874
|
+
};
|
|
2875
|
+
const mapDynamicVariables = (props) => {
|
|
2876
|
+
const { rootElement, changeSchemas, activeSchema } = props;
|
|
2877
|
+
const svtSchema = activeSchema;
|
|
2878
|
+
const text = svtSchema.text ?? '';
|
|
2879
|
+
if (!text) {
|
|
2880
|
+
rootElement.style.display = 'none';
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
// Extract all simple variable references from the template
|
|
2884
|
+
const varMatches = [...text.matchAll(/\{([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)\}/g)].map(m => m[1]);
|
|
2885
|
+
// Keep only the first unique variable (enforce single-variable restriction)
|
|
2886
|
+
const uniqueVars = [...new Set(varMatches)];
|
|
2887
|
+
const variable = uniqueVars[0];
|
|
2888
|
+
const varNames = variable ? [variable] : [];
|
|
2889
|
+
const variables = {};
|
|
2890
|
+
if (variable) {
|
|
2891
|
+
const existing = JSON.parse(svtSchema.content && svtSchema.content !== '' ? svtSchema.content : '{}');
|
|
2892
|
+
const existingVal = existing[variable];
|
|
2893
|
+
const isOldFormat = typeof existingVal === 'string' && (/^[A-Z][A-Z0-9_]*$/.test(existingVal) || /^\{\{.*\}\}$/.test(existingVal));
|
|
2894
|
+
const isStaleObject = typeof existingVal === 'string' && (() => {
|
|
2895
|
+
try {
|
|
2896
|
+
const p = JSON.parse(existingVal);
|
|
2897
|
+
return typeof p === 'object' && p !== null;
|
|
2898
|
+
}
|
|
2899
|
+
catch {
|
|
2900
|
+
return false;
|
|
2901
|
+
}
|
|
2902
|
+
})();
|
|
2903
|
+
variables[variable] = (!existingVal || isOldFormat || isStaleObject) ? variable.replace(/\./g, ' ') : existingVal;
|
|
2904
|
+
}
|
|
2905
|
+
const content = varNames.length > 0 ? JSON.stringify(variables) : '';
|
|
2906
|
+
const changed = JSON.stringify(svtSchema.variables ?? []) !== JSON.stringify(varNames) ||
|
|
2907
|
+
(svtSchema.content ?? '') !== content;
|
|
2908
|
+
if (changed) {
|
|
2909
|
+
changeSchemas([
|
|
2910
|
+
{ key: 'content', value: content, schemaId: activeSchema.id },
|
|
2911
|
+
{ key: 'variables', value: varNames, schemaId: activeSchema.id },
|
|
2912
|
+
{ key: 'readOnly', value: varNames.length === 0, schemaId: activeSchema.id },
|
|
2913
|
+
]);
|
|
2914
|
+
}
|
|
2915
|
+
rootElement.style.display = 'none';
|
|
2916
|
+
};
|
|
2917
|
+
const propPanel = {
|
|
2918
|
+
schema: (propPanelProps) => {
|
|
2919
|
+
if (typeof propPanel$1.schema !== 'function') {
|
|
2920
|
+
throw new Error('richText propPanel schema is not a function');
|
|
2921
|
+
}
|
|
2922
|
+
const parentSchema = propPanel$1.schema(propPanelProps);
|
|
2923
|
+
// Strip out richText-specific structural keys; we'll re-add our own versions
|
|
2924
|
+
const { insertVariablePicker: _ip, '----': _div, dynamicVariables: _dv, ...textFields } = parentSchema;
|
|
2925
|
+
return {
|
|
2926
|
+
insertVariablePicker: {
|
|
2927
|
+
type: 'void',
|
|
2928
|
+
widget: 'singleVariablePickerWidget',
|
|
2929
|
+
bind: false,
|
|
2930
|
+
span: 24,
|
|
2931
|
+
},
|
|
2932
|
+
'----': { type: 'void', widget: 'Divider' },
|
|
2933
|
+
...textFields,
|
|
2934
|
+
dynamicVariables: {
|
|
2935
|
+
type: 'object',
|
|
2936
|
+
widget: 'mapDynamicVariables',
|
|
2937
|
+
bind: false,
|
|
2938
|
+
span: 0,
|
|
2939
|
+
},
|
|
2940
|
+
};
|
|
2941
|
+
},
|
|
2942
|
+
widgets: {
|
|
2943
|
+
...(propPanel$1.widgets || {}),
|
|
2944
|
+
singleVariablePickerWidget,
|
|
2945
|
+
mapDynamicVariables,
|
|
2946
|
+
},
|
|
2947
|
+
defaultSchema: {
|
|
2948
|
+
...propPanel$1.defaultSchema,
|
|
2949
|
+
type: 'singleVariableText',
|
|
2950
|
+
text: '',
|
|
2951
|
+
content: '',
|
|
2952
|
+
height: 7,
|
|
2953
|
+
variables: [],
|
|
2954
|
+
},
|
|
2955
|
+
};
|
|
2956
|
+
|
|
2957
|
+
/** Format {expr} → {{ expr with dots as spaces }} */
|
|
2958
|
+
const formatTemplateDisplay = (text) => text.replace(/\{([^{}]+)\}/g, (_, expr) => '{{ ' + expr.trim().replace(/\./g, ' ') + ' }}');
|
|
2959
|
+
const uiRender = async (arg) => {
|
|
2960
|
+
const { mode, schema, options, _cache } = arg;
|
|
2961
|
+
if (mode === 'designer') {
|
|
2962
|
+
// ReadOnly preview — show {{ variable }} formatted display
|
|
2963
|
+
const text = schema.text ?? '';
|
|
2964
|
+
const displayText = text ? formatTemplateDisplay(text) : '';
|
|
2965
|
+
const font = options?.font || helper.getDefaultFont();
|
|
2966
|
+
const fontKitFont = await getFontKitFont(schema.fontName, font, _cache);
|
|
2967
|
+
const textBlock = buildStyledTextContainer(arg, fontKitFont, displayText);
|
|
2968
|
+
textBlock.textContent = displayText;
|
|
2969
|
+
textBlock.style.cursor = 'default';
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
// Viewer/form modes: delegate to richText render
|
|
2973
|
+
await uiRender$1(arg);
|
|
2974
|
+
};
|
|
2838
2975
|
const schema = {
|
|
2839
2976
|
pdf: pdfRender,
|
|
2840
2977
|
ui: uiRender,
|
|
2841
2978
|
propPanel,
|
|
2842
|
-
icon: createSvgStr(lucide.
|
|
2843
|
-
|
|
2979
|
+
icon: createSvgStr(lucide.BetweenHorizontalStart),
|
|
2980
|
+
};
|
|
2981
|
+
|
|
2982
|
+
const textSchema = {
|
|
2983
|
+
pdf: pdfRender$1,
|
|
2984
|
+
ui: uiRender$2,
|
|
2985
|
+
propPanel: propPanel$2,
|
|
2986
|
+
icon: createSvgStr(lucide.TextCursorInput),
|
|
2844
2987
|
};
|
|
2845
2988
|
|
|
2846
|
-
const builtInPlugins = {
|
|
2989
|
+
const builtInPlugins = {
|
|
2990
|
+
Text: textSchema,
|
|
2991
|
+
'Dynamic Text': schema,
|
|
2992
|
+
'Rich Text': schema$1,
|
|
2993
|
+
};
|
|
2847
2994
|
|
|
2848
2995
|
exports.DEFAULT_ALIGNMENT = DEFAULT_ALIGNMENT;
|
|
2849
2996
|
exports.DEFAULT_CHARACTER_SPACING = DEFAULT_CHARACTER_SPACING;
|
|
@@ -2854,10 +3001,10 @@ exports.DEFAULT_OPACITY = DEFAULT_OPACITY;
|
|
|
2854
3001
|
exports.HEX_COLOR_PATTERN = HEX_COLOR_PATTERN;
|
|
2855
3002
|
exports.VERTICAL_ALIGN_MIDDLE = VERTICAL_ALIGN_MIDDLE;
|
|
2856
3003
|
exports.addAlphaToHex = addAlphaToHex;
|
|
3004
|
+
exports.buildStyledTextContainer = buildStyledTextContainer;
|
|
2857
3005
|
exports.builtInPlugins = builtInPlugins;
|
|
2858
3006
|
exports.convertForPdfLayoutProps = convertForPdfLayoutProps;
|
|
2859
3007
|
exports.createErrorElm = createErrorElm;
|
|
2860
|
-
exports.createInsertVariableWidget = createInsertVariableWidget;
|
|
2861
3008
|
exports.createSingleTable = createSingleTable;
|
|
2862
3009
|
exports.createSvgStr = createSvgStr;
|
|
2863
3010
|
exports.getBody = getBody;
|
|
@@ -2867,13 +3014,16 @@ exports.getColumnStylesPropPanelSchema = getColumnStylesPropPanelSchema;
|
|
|
2867
3014
|
exports.getDefaultCellStyles = getDefaultCellStyles;
|
|
2868
3015
|
exports.getDynamicHeightsForTable = getDynamicHeightsForTable;
|
|
2869
3016
|
exports.getExtraFormatterSchema = getExtraFormatterSchema;
|
|
3017
|
+
exports.getFontKitFont = getFontKitFont;
|
|
2870
3018
|
exports.hex2PrintingColor = hex2PrintingColor;
|
|
2871
3019
|
exports.isEditable = isEditable;
|
|
3020
|
+
exports.makeElementPlainTextContentEditable = makeElementPlainTextContentEditable;
|
|
2872
3021
|
exports.mapVerticalAlignToFlex = mapVerticalAlignToFlex;
|
|
2873
3022
|
exports.pdfRender = pdfRender$1;
|
|
2874
|
-
exports.propPanel = propPanel$
|
|
3023
|
+
exports.propPanel = propPanel$2;
|
|
2875
3024
|
exports.readFile = readFile;
|
|
2876
3025
|
exports.rotatePoint = rotatePoint;
|
|
2877
|
-
exports.schema = schema;
|
|
2878
|
-
exports.
|
|
2879
|
-
exports.
|
|
3026
|
+
exports.schema = schema$1;
|
|
3027
|
+
exports.schema$1 = schema;
|
|
3028
|
+
exports.textSchema = textSchema;
|
|
3029
|
+
exports.uiRender = uiRender$2;
|