@unlayer/react-elements 0.1.9 → 0.1.11
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/README.md +13 -10
- package/dist/index.cjs +126 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -61
- package/dist/index.d.ts +165 -61
- package/dist/index.js +126 -12
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -51,14 +51,17 @@ function WelcomeEmail() {
|
|
|
51
51
|
|
|
52
52
|
These props have non-obvious shapes that **must** be followed exactly:
|
|
53
53
|
|
|
54
|
-
- **fontFamily**:
|
|
54
|
+
- **fontFamily**: Accepts a plain family-name string (`fontFamily="Georgia"`) or, for a full stack, `{ label, value }` (recommended).
|
|
55
55
|
```tsx
|
|
56
56
|
fontFamily={{ label: "Arial", value: "arial, sans-serif" }}
|
|
57
57
|
```
|
|
58
|
-
- **fontWeight**:
|
|
58
|
+
- **fontWeight**: Accepts a number (`700`), a numeric string (`"700"`), or a CSS keyword (`"bold"`).
|
|
59
|
+
- **fontSize / padding**: Accept a CSS string (`"28px"`, `"20px 40px"`) or a bare number (treated as px: `fontSize={28}` → `28px`).
|
|
60
|
+
- **lineHeight**: Accepts a CSS string (`"1.4"`, `"140%"`) or a bare number (kept **unitless**: `lineHeight={1.4}` → `"1.4"`).
|
|
59
61
|
- **Wrapper component**: Use `<Email>`, `<Page>`, or `<Document>` as root — they set the rendering mode automatically.
|
|
60
62
|
- **href**: Can be a plain string URL (auto-wrapped) or `{ name: "web", values: { href, target } }`.
|
|
61
|
-
- **Image
|
|
63
|
+
- **Image sizing**: `src` is a plain URL string or `{ url, width?, height?, ... }`, where `width`/`height` are the image's **natural** size. By default an image is **responsive** — it fills its container, capped at its natural size. For a **fixed** display size, use a **percent**: `width="50%"` or `maxWidth="50%"`. A px/number `width` is treated as the natural size, so `width="300px"` shows the image at up to 300px (responsive).
|
|
64
|
+
- **Heading level**: `headingType` (or its alias `level`) accepts `h1`–`h6`.
|
|
62
65
|
- **children**: Text components accept children as shorthand. `<Heading>Hello</Heading>` sets the heading text. `<Paragraph>` supports children for plain text.
|
|
63
66
|
- **Paragraph text**: Use `html` prop for text content (supports inline formatting like `<b>`, `<a>`). Use children for plain text.
|
|
64
67
|
|
|
@@ -154,6 +157,7 @@ Must be child of Row. Count must match layout.
|
|
|
154
157
|
- `fontFamily?: { label: string, value: string }`
|
|
155
158
|
- `padding?: string` — `"10px 20px"`
|
|
156
159
|
- `borderRadius?: string` — `"4px"`
|
|
160
|
+
- `width?: number | string` — display width; `width="100%"` makes the button full-width, `width="200px"` pins it
|
|
157
161
|
- `textAlign?: "left" | "center" | "right"` — `"center"`
|
|
158
162
|
|
|
159
163
|
### Paragraph
|
|
@@ -439,13 +443,12 @@ const monoFont = { label: "Monospace", value: "'SF Mono', 'Fira Code', 'Roboto M
|
|
|
439
443
|
|
|
440
444
|
## Common Mistakes
|
|
441
445
|
|
|
442
|
-
1. **
|
|
443
|
-
2. **
|
|
444
|
-
3. **
|
|
445
|
-
4. **
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
7. **padding without units** — Use `padding="0px"` not `padding="0"` — the type requires the `px` suffix for consistency
|
|
446
|
+
1. **Column count mismatch** — `TwoEqual` layout requires exactly 2 `<Column>` children
|
|
447
|
+
2. **Missing Column** — Items must be inside `<Column>`, never directly in `<Row>`
|
|
448
|
+
3. **Missing Row** — Columns must be inside `<Row>`, never directly in `<Email>`/`<Page>`/`<Document>`
|
|
449
|
+
4. **JSX formatting in children** — `<Heading>Hi <b>x</b></Heading>` is flattened to plain text (the formatting is **not** preserved). For inline formatting use `<Paragraph html="Hi <b>x</b>" />`.
|
|
450
|
+
|
|
451
|
+
> Note: the CSS-idiom forms that used to be mistakes now work — a string `fontFamily`, a string/number `fontWeight`, a numeric `fontSize`, `padding="0"`, and `<Paragraph text="..." />` are all accepted and normalized. The object/numeric forms above are still recommended for clarity.
|
|
449
452
|
|
|
450
453
|
## Development
|
|
451
454
|
|
package/dist/index.cjs
CHANGED
|
@@ -208,16 +208,53 @@ function analyzeNestedStructure(defaultValues) {
|
|
|
208
208
|
nestedStructureCache.set(defaultValues, nestedGroups);
|
|
209
209
|
return nestedGroups;
|
|
210
210
|
}
|
|
211
|
+
var PX_SIZE_KEYS = [
|
|
212
|
+
"fontSize",
|
|
213
|
+
"padding",
|
|
214
|
+
"containerPadding",
|
|
215
|
+
"borderRadius",
|
|
216
|
+
"letterSpacing"
|
|
217
|
+
];
|
|
218
|
+
function normalizeCssProps(props) {
|
|
219
|
+
if (typeof props.fontFamily === "string") {
|
|
220
|
+
const v = props.fontFamily;
|
|
221
|
+
props.fontFamily = { label: v, value: v };
|
|
222
|
+
}
|
|
223
|
+
if (typeof props.fontWeight === "string" && /^\d+$/.test(props.fontWeight.trim())) {
|
|
224
|
+
props.fontWeight = Number(props.fontWeight.trim());
|
|
225
|
+
}
|
|
226
|
+
if (typeof props.lineHeight === "number") {
|
|
227
|
+
props.lineHeight = String(props.lineHeight);
|
|
228
|
+
}
|
|
229
|
+
for (const key of PX_SIZE_KEYS) {
|
|
230
|
+
const v = props[key];
|
|
231
|
+
if (typeof v === "number") {
|
|
232
|
+
props[key] = `${v}px`;
|
|
233
|
+
} else if (typeof v === "string" && /^\d+$/.test(v.trim())) {
|
|
234
|
+
props[key] = `${v.trim()}px`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function flattenChildrenText(node) {
|
|
239
|
+
if (node == null || typeof node === "boolean") return "";
|
|
240
|
+
if (typeof node === "string") return node;
|
|
241
|
+
if (typeof node === "number") return String(node);
|
|
242
|
+
if (Array.isArray(node)) return node.map(flattenChildrenText).join("");
|
|
243
|
+
if (typeof node === "object" && "props" in node) {
|
|
244
|
+
return flattenChildrenText(node.props?.children);
|
|
245
|
+
}
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
211
248
|
function mapSemanticProps(props, defaultValues, componentType) {
|
|
212
249
|
const { children, values, ...restProps } = props;
|
|
213
250
|
const userProps = { ...restProps };
|
|
214
251
|
const result = values ? { ...values } : {};
|
|
215
252
|
if (children !== void 0 && !result.text && !result.textJson) {
|
|
253
|
+
const textContent = typeof children === "string" ? children : flattenChildrenText(children);
|
|
216
254
|
if (componentType === "Paragraph") {
|
|
217
|
-
const textContent = typeof children === "string" ? children : String(children);
|
|
218
255
|
result.textJson = textToTextJson(textContent);
|
|
219
256
|
} else {
|
|
220
|
-
result.text =
|
|
257
|
+
result.text = textContent;
|
|
221
258
|
}
|
|
222
259
|
}
|
|
223
260
|
const textFromEscapeHatch = result.text;
|
|
@@ -243,6 +280,7 @@ function mapSemanticProps(props, defaultValues, componentType) {
|
|
|
243
280
|
};
|
|
244
281
|
}
|
|
245
282
|
}
|
|
283
|
+
normalizeCssProps(userProps);
|
|
246
284
|
const nestedGroups = analyzeNestedStructure(defaultValues);
|
|
247
285
|
const nested = {};
|
|
248
286
|
const flat = {};
|
|
@@ -277,6 +315,19 @@ function mapSemanticProps(props, defaultValues, componentType) {
|
|
|
277
315
|
};
|
|
278
316
|
}
|
|
279
317
|
}
|
|
318
|
+
if (defaultValues && typeof defaultValues === "object" && "border" in defaultValues) {
|
|
319
|
+
const borderSideRe = /^border(Top|Right|Bottom|Left)(Width|Style|Color)$/;
|
|
320
|
+
const collected = {};
|
|
321
|
+
for (const key of Object.keys(final)) {
|
|
322
|
+
if (borderSideRe.test(key)) {
|
|
323
|
+
collected[key] = final[key];
|
|
324
|
+
delete final[key];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (Object.keys(collected).length > 0) {
|
|
328
|
+
final.border = { ...final.border || {}, ...collected };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
280
331
|
return final;
|
|
281
332
|
}
|
|
282
333
|
function normalizeLinkValue(value) {
|
|
@@ -545,7 +596,26 @@ var DEFAULT_VALUES = {
|
|
|
545
596
|
var Button = createItemComponent({
|
|
546
597
|
name: "Button",
|
|
547
598
|
defaultValues: DEFAULT_VALUES,
|
|
548
|
-
propMapper: (props) =>
|
|
599
|
+
propMapper: (props) => {
|
|
600
|
+
const mapped = mapSemanticProps(
|
|
601
|
+
props,
|
|
602
|
+
DEFAULT_VALUES,
|
|
603
|
+
"Button"
|
|
604
|
+
);
|
|
605
|
+
const size = mapped.size;
|
|
606
|
+
if (size && typeof size === "object" && !Array.isArray(size)) {
|
|
607
|
+
const s = size;
|
|
608
|
+
if (typeof s.width === "number") {
|
|
609
|
+
s.width = `${s.width}px`;
|
|
610
|
+
} else if (typeof s.width === "string" && /^\d+(?:\.\d+)?$/.test(s.width.trim())) {
|
|
611
|
+
s.width = `${s.width.trim()}px`;
|
|
612
|
+
}
|
|
613
|
+
if (s.width !== void 0 && s.autoWidth === void 0) {
|
|
614
|
+
s.autoWidth = false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return mapped;
|
|
618
|
+
},
|
|
549
619
|
displayName: "Button",
|
|
550
620
|
exporters: exporters.ButtonExporters
|
|
551
621
|
});
|
|
@@ -610,18 +680,46 @@ var Image = createItemComponent({
|
|
|
610
680
|
defaultValues: DEFAULT_VALUES5,
|
|
611
681
|
propMapper: (props) => {
|
|
612
682
|
const { alt, src, ...rest } = props;
|
|
683
|
+
const restValues = rest.values;
|
|
684
|
+
const normalizedRest = restValues && typeof restValues.src === "string" ? { ...rest, values: { ...restValues, src: { url: restValues.src } } } : rest;
|
|
613
685
|
const base = mapSemanticProps(
|
|
614
|
-
|
|
686
|
+
normalizedRest,
|
|
615
687
|
DEFAULT_VALUES5,
|
|
616
688
|
"Image"
|
|
617
689
|
);
|
|
618
690
|
if (alt !== void 0) {
|
|
619
691
|
base.altText = alt;
|
|
620
692
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
693
|
+
const baseSrc = base.src;
|
|
694
|
+
const fromValues = baseSrc && typeof baseSrc === "object" && !Array.isArray(baseSrc) ? baseSrc : typeof baseSrc === "string" ? { url: baseSrc } : {};
|
|
695
|
+
const fromProp = typeof src === "string" ? { url: src } : src ?? {};
|
|
696
|
+
const userSrc = { ...fromValues, ...fromProp };
|
|
697
|
+
if (src !== void 0 || baseSrc !== void 0) {
|
|
698
|
+
const isStringUrl = typeof src === "string" || src === void 0 && typeof baseSrc === "string";
|
|
699
|
+
const start = isStringUrl ? { autoWidth: true, maxWidth: "100%" } : { ...DEFAULT_VALUES5.src };
|
|
700
|
+
const merged = { ...start, ...userSrc };
|
|
701
|
+
const pctRe = /^\d+(?:\.\d+)?%$/;
|
|
702
|
+
if (typeof merged.width === "string") {
|
|
703
|
+
const t = merged.width.trim();
|
|
704
|
+
if (pctRe.test(t)) {
|
|
705
|
+
if (userSrc.maxWidth === void 0) merged.maxWidth = t;
|
|
706
|
+
delete merged.width;
|
|
707
|
+
} else {
|
|
708
|
+
const px = /^(\d+(?:\.\d+)?)(?:px)?$/.exec(t);
|
|
709
|
+
if (px) merged.width = parseFloat(px[1]);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const displayPct = typeof merged.maxWidth === "string" && pctRe.test(merged.maxWidth.trim()) ? merged.maxWidth.trim() : void 0;
|
|
713
|
+
if (userSrc.autoWidth === void 0) {
|
|
714
|
+
if (displayPct && displayPct !== "100%") {
|
|
715
|
+
merged.autoWidth = false;
|
|
716
|
+
merged.maxWidth = displayPct;
|
|
717
|
+
} else {
|
|
718
|
+
merged.autoWidth = true;
|
|
719
|
+
merged.maxWidth = "100%";
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
base.src = merged;
|
|
625
723
|
}
|
|
626
724
|
return base;
|
|
627
725
|
},
|
|
@@ -732,25 +830,37 @@ var Table = createItemComponent({
|
|
|
732
830
|
name: "Table",
|
|
733
831
|
defaultValues: DEFAULT_VALUES9,
|
|
734
832
|
propMapper: (props) => {
|
|
735
|
-
const { headers, data, ...rest } = props;
|
|
736
|
-
if (headers || data) {
|
|
833
|
+
const { headers, data, columns, rows, ...rest } = props;
|
|
834
|
+
if (headers || data || typeof columns === "number" || typeof rows === "number") {
|
|
737
835
|
const base = mapSemanticProps(
|
|
738
836
|
rest,
|
|
739
837
|
DEFAULT_VALUES9,
|
|
740
838
|
"Table"
|
|
741
839
|
);
|
|
840
|
+
const colCount = headers ? headers.length : typeof columns === "number" ? columns : data?.[0]?.length ?? 0;
|
|
841
|
+
const blankCells = (n) => Array.from({ length: n }, () => ({ text: "", width: 0 }));
|
|
742
842
|
const tableHeaders = headers ? [{ cells: headers.map((text) => ({ text, width: 0 })), height: 0 }] : [];
|
|
743
843
|
const tableRows = data ? data.map((row) => ({
|
|
744
844
|
cells: row.map((text) => ({ text, width: 0 })),
|
|
745
845
|
height: 0
|
|
746
|
-
})) :
|
|
846
|
+
})) : typeof rows === "number" ? (
|
|
847
|
+
// No data: build an empty grid sized by `columns` × `rows`.
|
|
848
|
+
Array.from({ length: rows }, () => ({
|
|
849
|
+
cells: blankCells(colCount),
|
|
850
|
+
height: 0
|
|
851
|
+
}))
|
|
852
|
+
) : [];
|
|
747
853
|
base.table = { headers: tableHeaders, rows: tableRows, footers: [] };
|
|
854
|
+
if (headers || typeof columns === "number") {
|
|
855
|
+
base.columns = colCount;
|
|
856
|
+
}
|
|
748
857
|
if (headers) {
|
|
749
|
-
base.columns = headers.length;
|
|
750
858
|
base.enableHeader = true;
|
|
751
859
|
}
|
|
752
860
|
if (data) {
|
|
753
861
|
base.rows = data.length;
|
|
862
|
+
} else if (typeof rows === "number") {
|
|
863
|
+
base.rows = rows;
|
|
754
864
|
}
|
|
755
865
|
return base;
|
|
756
866
|
}
|
|
@@ -1619,6 +1729,10 @@ function processBody(element, counters) {
|
|
|
1619
1729
|
const semanticProps = extractSemanticProps2(element.props);
|
|
1620
1730
|
const mapped = mapSemanticProps(semanticProps, BODY_DEFAULTS, "Body");
|
|
1621
1731
|
const values = mergeValues(BODY_DEFAULTS, mapped);
|
|
1732
|
+
const previewText = element.props.previewText;
|
|
1733
|
+
if (previewText !== void 0) {
|
|
1734
|
+
values.preheaderText = previewText;
|
|
1735
|
+
}
|
|
1622
1736
|
const valuesWithMeta = {
|
|
1623
1737
|
...values,
|
|
1624
1738
|
_meta: {
|