Skip to content

Commit 09ded62

Browse files
author
Illia Obukhau
committed
fix(html-element-web): add void element handling
1 parent e4e4494 commit 09ded62

File tree

4 files changed

+75
-45
lines changed

4 files changed

+75
-45
lines changed

packages/pluggableWidgets/html-element-web/src/HTMLElement.editorConfig.ts

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,17 @@
11
import { AttributeValueTypeEnum, HTMLElementPreviewProps } from "../typings/HTMLElementProps";
22
import { hideNestedPropertiesIn, hidePropertiesIn, Problem, Properties } from "@mendix/pluggable-widgets-tools";
3-
import { container, datasource, dropzone, StructurePreviewProps, text } from "@mendix/pluggable-widgets-commons";
4-
import { prepareTag } from "./utils/props-utils";
3+
import {
4+
container,
5+
ContainerProps,
6+
datasource,
7+
dropzone,
8+
StructurePreviewProps,
9+
text
10+
} from "@mendix/pluggable-widgets-commons";
11+
import { isVoidElement, prepareTag } from "./utils/props-utils";
512

613
type TagAttributeValuePropName = keyof HTMLElementPreviewProps["attributes"][number];
714

8-
const voidElements = [
9-
"area",
10-
"base",
11-
"br",
12-
"col",
13-
"embed",
14-
"hr",
15-
"img",
16-
"input",
17-
"link",
18-
"meta",
19-
"source",
20-
"track",
21-
"wbr",
22-
// react specific, it uses `value` prop
23-
"textarea"
24-
];
25-
2615
const disabledElements = ["script"];
2716

2817
function isValidHtmlTagName(name: string): boolean {
@@ -86,7 +75,7 @@ export function getProperties(values: HTMLElementPreviewProps, defaultProperties
8675

8776
const tagName = values.tagName === "__customTag__" ? values.tagNameCustom : values.tagName;
8877

89-
if (voidElements.includes(tagName)) {
78+
if (isVoidElement(tagName)) {
9079
// void elements don't allow children, hide all content props and the content mode switch
9180
propsToHide.push(
9281
"tagContentMode",
@@ -180,8 +169,10 @@ export function check(values: HTMLElementPreviewProps): Problem[] {
180169
export function getPreview(values: HTMLElementPreviewProps, _isDarkMode: boolean): StructurePreviewProps | null {
181170
const tagName = prepareTag(values.tagName, values.tagNameCustom);
182171

183-
return container({ grow: 1, borders: true, borderWidth: 1 })(
184-
values.tagContentRepeatDataSource ? datasource(values.tagContentRepeatDataSource)() : container()(),
172+
const voidElementPreview = (tagName: keyof JSX.IntrinsicElements): ContainerProps =>
173+
container({ padding: 4 })(text()(`<${tagName} />`));
174+
175+
const flowElementPreview = (): ContainerProps =>
185176
values.tagContentMode === "innerHTML"
186177
? container({ padding: 4 })(
187178
text()(
@@ -194,6 +185,10 @@ export function getPreview(values: HTMLElementPreviewProps, _isDarkMode: boolean
194185
text()(`<${tagName}>`),
195186
dropzone(values.tagUseRepeat ? values.tagContentRepeatContainer : values.tagContentContainer),
196187
text()(`</${tagName}>`)
197-
)
188+
);
189+
190+
return container({ grow: 1, borders: true, borderWidth: 1 })(
191+
values.tagContentRepeatDataSource ? datasource(values.tagContentRepeatDataSource)() : container()(),
192+
isVoidElement(tagName) ? voidElementPreview(tagName) : flowElementPreview()
198193
);
199194
}

packages/pluggableWidgets/html-element-web/src/HTMLElement.editorPreview.tsx

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
import { ReactElement, createElement, Fragment } from "react";
22
import { HTMLElementPreviewProps } from "../typings/HTMLElementProps";
33
import { HTMLTag } from "./components/HTMLTag";
4-
import { prepareTag } from "./utils/props-utils";
4+
import { isVoidElement, prepareTag } from "./utils/props-utils";
55

66
export function preview(props: HTMLElementPreviewProps): ReactElement {
7+
console.dir(props, { depth: 4 });
78
const tag = prepareTag(props.tagName, props.tagNameCustom);
89

910
const items = props.tagUseRepeat ? [1, 2, 3] : [1];
1011

1112
return (
1213
<Fragment>
13-
{items.map(i => (
14-
<HTMLTag
15-
key={i}
16-
tagName={tag}
17-
attributes={{
18-
className: props.className,
19-
style: props.styleObject
20-
}}
21-
>
22-
{props.tagContentRepeatHTML}
23-
{props.tagContentHTML}
24-
<props.tagContentRepeatContainer.renderer>
25-
<div />
26-
</props.tagContentRepeatContainer.renderer>
27-
<props.tagContentContainer.renderer>
28-
<div />
29-
</props.tagContentContainer.renderer>
30-
</HTMLTag>
31-
))}
14+
{items.map(i =>
15+
isVoidElement(tag) ? (
16+
createElement(tag, { className: props.className, style: props.styleObject })
17+
) : (
18+
<HTMLTag
19+
key={i}
20+
tagName={tag}
21+
attributes={{
22+
className: props.className,
23+
style: props.styleObject
24+
}}
25+
>
26+
{props.tagContentRepeatHTML}
27+
{props.tagContentHTML}
28+
<props.tagContentRepeatContainer.renderer>
29+
<div />
30+
</props.tagContentRepeatContainer.renderer>
31+
<props.tagContentContainer.renderer>
32+
<div />
33+
</props.tagContentContainer.renderer>
34+
</HTMLTag>
35+
)
36+
)}
3237
</Fragment>
3338
);
3439
}

packages/pluggableWidgets/html-element-web/src/utils/props-utils.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,26 @@ export function prepareChildren(
116116

117117
return props.tagContentRepeatContainer?.get(item);
118118
}
119+
120+
const voidElements = [
121+
"area",
122+
"base",
123+
"br",
124+
"col",
125+
"embed",
126+
"hr",
127+
"img",
128+
"input",
129+
"link",
130+
"meta",
131+
"source",
132+
"track",
133+
"wbr",
134+
"textarea"
135+
] as const;
136+
137+
export type VoidElement = typeof voidElements[number];
138+
139+
export function isVoidElement(tag: unknown): tag is VoidElement {
140+
return voidElements.includes(tag as VoidElement);
141+
}

packages/pluggableWidgets/html-element-web/src/utils/style-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React from "react";
22

3+
// We need regexp to split rows in prop/value pairs
4+
// split by : not work for all cases, eg "background-image: url(http://localhost:8080);"
5+
const cssPropRegex = /(?<prop>[^:]+):\s+?(?<value>[^;]+);?/m;
6+
37
export function convertInlineCssToReactStyle(inlineStyle: string): React.CSSProperties {
48
return Object.fromEntries(
59
inlineStyle
610
.split(";") // split by ;
711
.filter(r => r.length) // filter out empty
8-
.map(r => r.split(":").map(v => v.trim())) // split by key and value by :
12+
.map(r => {
13+
const { prop = "", value = "" } = cssPropRegex.exec(r.trim())?.groups ?? {};
14+
return [prop, value];
15+
})
916
.filter(v => v.length === 2 && v[0].length && v[1].length) // filter out broken lines
1017
.map(([key, value]) => [convertStylePropNameToReactPropName(key), value] as [string, string])
1118
);

0 commit comments

Comments
 (0)