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