Skip to content

Commit 0623052

Browse files
committed
feat: add onExpressionToValue and onValueToExpression
1 parent f66391d commit 0623052

24 files changed

+1074
-1024
lines changed

dist/index.cjs.js

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.cjs.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.es.js

+962-959
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.es.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/lib/UiEditor.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IObservable } from 'react-observing';
22
import { IUiEditorContextProps } from './UiEditorContext';
33

4-
interface IUIEditorProps<D> extends Pick<IUiEditorContextProps<D>, 'components' | 'styles' | 'value' | 'onDragStart' | 'onDragEnd' | 'onDrop' | 'onKeyDown' | 'onDuplicate' | 'onRemove' | 'onAddSlotContent'> {
4+
interface IUIEditorProps<D> extends Pick<IUiEditorContextProps<D>, 'components' | 'styles' | 'value' | 'onDragStart' | 'onDragEnd' | 'onDrop' | 'onKeyDown' | 'onDuplicate' | 'onRemove' | 'onAddSlotContent' | 'onExpressionToValue' | 'onValueToExpression'> {
55
onHover: (id: string | undefined) => void;
66
onSelect: (id: string | undefined) => void;
77
hoveredId: IObservable<string | undefined>;

dist/lib/UiEditorContext.d.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { IObservable } from 'react-observing';
2-
import { TComponent, TDropFunctionProps, TElement, TStyle } from './types';
2+
import { TComponent, TDropFunctionProps, TElement, TElementTypesDefault, TStyle } from './types';
33

4+
export type TValueParseFunction<D = any> = <A = any, B = any>(value: A, ownerName: string, type: 'attribute' | 'textContent' | 'style', element: TElement<TElementTypesDefault, D>) => B;
45
export interface IUiEditorContextProps<D = any> {
56
styles: IObservable<TStyle[]>;
67
value: IObservable<TElement[]>;
78
components: IObservable<TComponent[]>;
9+
onExpressionToValue: TValueParseFunction<D>;
10+
onValueToExpression: TValueParseFunction<D>;
811
onDragEnd: () => void;
912
onDragStart: () => void;
10-
onRemove: (element: TElement) => void;
11-
onDuplicate: (element: TElement) => void;
1213
onKeyDown: (event: KeyboardEvent) => void;
13-
onDrop: (props: TDropFunctionProps) => void;
14+
onDrop: (props: TDropFunctionProps<D>) => void;
15+
onRemove: (element: TElement<TElementTypesDefault, D>) => void;
16+
onDuplicate: (element: TElement<TElementTypesDefault, D>) => void;
1417
onAddSlotContent: (element: TElement<'slot', D>, referenceComponent: TElement<'component', D>) => void;
1518
}
1619
export declare function UiEditorContextProvider({ children, ...props }: React.PropsWithChildren<IUiEditorContextProps<any>>): import("react/jsx-runtime").JSX.Element;
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { TElement } from '../../../types';
22

3-
export declare const useElementAttributes: (attributesObservable: TElement<'html'>['attributes']) => Record<string, string | number | boolean | null | undefined>[];
3+
export declare const useElementAttributes: (element: TElement<'html'>) => Record<string, string | number | boolean | null | undefined>[];
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import { TElement } from '../../../types';
22

3-
export declare const useUIElementInlineStyle: (elementStyle: TElement<'html'>['style']) => Record<string, string | number | boolean>;
3+
export declare const useUIElementInlineStyle: (element: TElement<'html'>) => Record<string, string | number | boolean>;

dist/lib/components/element/text/View.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ interface IEditProps {
44
element: TElement<'text'>;
55
parents: TParentElement[];
66
}
7-
export declare const View: ({ element }: IEditProps) => string;
7+
export declare const View: ({ element }: IEditProps) => any;
88
export {};

dist/lib/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export type { TStyle, TComponent, TDraggableElement, TDropFunctionProps, TExternalDraggableElement, TElement } from './types';
2+
export type { TValueParseFunction } from './UiEditorContext';
23
export * from './UiEditor';

dist/lib/types/DraggableElement.d.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { TElement, TParentElement } from './Element';
1+
import { TElement, TElementTypesDefault, TElementTypesParents, TParentElement } from './Element';
22

33
export type TExternalDraggableElement = {
44
id: string;
55
};
6-
export type TDraggableElement<E extends "html" | "component" | "slot" | "text" = "html" | "component" | "slot" | "text", D = any> = {
6+
export type TDraggableElement<E extends TElementTypesDefault = TElementTypesDefault, D = any> = {
77
element: TElement<E, D>;
8-
parents: TParentElement<'html' | 'component' | 'slot-content', D>[] | null;
8+
parents: TParentElement<TElementTypesParents, D>[] | null;
99
};
1010
export type TDropFunctionProps<D = any> = {
11-
element: TElement<"html" | "component" | "slot" | "text", D> | string;
11+
element: TElement<TElementTypesDefault, D> | string;
1212
from: {
1313
position: number;
1414
element: null | 'root' | TElement<'html' | 'slot-content', D>;

dist/lib/types/Element.d.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ type TComponent<D = any> = {
111111
/** Extra data can be passed here, things like a full instance or extra information */
112112
customData?: D;
113113
};
114-
export type TElement<T extends 'html' | 'component' | 'slot' | 'slot-content' | 'text' = 'html' | 'component' | 'slot' | 'text', D = any> = T extends 'html' ? THtml<D> : T extends 'slot' ? TSlot<D> : T extends 'text' ? TText<D> : T extends 'slot-content' ? TSlotContent<D> : TComponent<D>;
115-
export type TParentElement<T extends 'html' | 'component' | 'slot-content' = 'html' | 'component' | 'slot-content', D = any> = T extends 'html' ? THtml<D> : T extends 'slot-content' ? TSlotContent<D> : TComponent<D>;
114+
export type TElementTypesParents = 'html' | 'component' | 'slot-content';
115+
export type TElementTypesDefault = 'html' | 'component' | 'slot' | 'text';
116+
export type TElementTypesAll = 'html' | 'component' | 'slot' | 'slot-content' | 'text';
117+
export type TElement<T extends TElementTypesAll = TElementTypesDefault, D = any> = T extends 'html' ? THtml<D> : T extends 'slot' ? TSlot<D> : T extends 'text' ? TText<D> : T extends 'slot-content' ? TSlotContent<D> : TComponent<D>;
118+
export type TParentElement<T extends TElementTypesParents = TElementTypesParents, D = any> = T extends 'html' ? THtml<D> : T extends 'slot-content' ? TSlotContent<D> : TComponent<D>;
116119
export {};

example/src/app/index.tsx

+15-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DragAndDropProvider } from 'react-use-drag-and-drop';
33
import { observe, set } from 'react-observing';
44
import { v4 } from 'uuid';
55

6-
import { UIEditor, TComponent, TDropFunctionProps, TElement, TStyle } from 'ui-editor/src';
6+
import { UIEditor, TComponent, TDropFunctionProps, TElement, TStyle, TValueParseFunction } from 'ui-editor/src';
77
import { Component } from './components/Component';
88
import { Html } from './components/Html';
99
import { Text } from './components/Text';
@@ -299,14 +299,15 @@ export const App = () => {
299299
style: observe(undefined),
300300
attributes: observe([
301301
{ name: observe('hidden'), value: observe(false) },
302+
{ name: observe('title'), value: observe('"Some title in a button"') },
302303
]),
303304
children: observe([
304305
{
305306
id: observe(v4()),
306307
type: observe('text'),
307308
name: observe('text'),
308309
customData: { teste: 88 },
309-
text: observe('Button'),
310+
text: observe('"Button"'),
310311
}
311312
]),
312313
},
@@ -612,6 +613,15 @@ export const App = () => {
612613
}, []);
613614

614615

616+
const handleExpressionToValue: TValueParseFunction = useCallback((value, ownerName, type, element) => {
617+
return eval(String(value));
618+
}, []);
619+
620+
const handleValueToExpression: TValueParseFunction = useCallback((value, ownerName, type, element) => {
621+
return `"${value}"`;
622+
}, []);
623+
624+
615625
return (
616626
<div className='w-screen h-screen bg-paper flex justify-center items-center gap-4'>
617627
<DragAndDropProvider>
@@ -666,6 +676,9 @@ export const App = () => {
666676

667677
onRemove={handleRemove}
668678
onDuplicate={(...rest) => console.log('duplicate', ...rest)}
679+
680+
onExpressionToValue={handleExpressionToValue}
681+
onValueToExpression={handleValueToExpression}
669682
/>
670683
</div>
671684
</div>

src/lib/UiEditor.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { HoverBarContextProvider } from './components/hover-bar';
88
import { UIEditorContent } from './UiEditorContent';
99

1010

11-
interface IUIEditorProps<D> extends Pick<IUiEditorContextProps<D>, 'components' | 'styles' | 'value' | 'onDragStart' | 'onDragEnd' | 'onDrop' | 'onKeyDown' | 'onDuplicate' | 'onRemove' | 'onAddSlotContent'> {
11+
interface IUIEditorProps<D> extends Pick<IUiEditorContextProps<D>, 'components' | 'styles' | 'value' | 'onDragStart' | 'onDragEnd' | 'onDrop' | 'onKeyDown' | 'onDuplicate' | 'onRemove' | 'onAddSlotContent' | 'onExpressionToValue' | 'onValueToExpression'> {
1212
onHover: (id: string | undefined) => void;
1313
onSelect: (id: string | undefined) => void;
1414
hoveredId: IObservable<string | undefined>;

src/lib/UiEditorContext.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
import { createContext, useContext } from 'react';
22
import { IObservable } from 'react-observing';
33

4-
import { TComponent, TDropFunctionProps, TElement, TStyle } from './types';
4+
import { TComponent, TDropFunctionProps, TElement, TElementTypesDefault, TStyle } from './types';
55

66

7+
export type TValueParseFunction<D = any> = <A = any, B = any>(value: A, ownerName: string, type: 'attribute' | 'textContent' | 'style', element: TElement<TElementTypesDefault, D>) => B;
8+
79
export interface IUiEditorContextProps<D = any> {
810
styles: IObservable<TStyle[]>;
911
value: IObservable<TElement[]>;
1012
components: IObservable<TComponent[]>;
1113

14+
onExpressionToValue: TValueParseFunction<D>;
15+
onValueToExpression: TValueParseFunction<D>;
16+
1217
onDragEnd: () => void;
1318
onDragStart: () => void;
14-
onRemove: (element: TElement) => void;
15-
onDuplicate: (element: TElement) => void;
1619
onKeyDown: (event: KeyboardEvent) => void
17-
onDrop: (props: TDropFunctionProps) => void;
20+
onDrop: (props: TDropFunctionProps<D>) => void;
21+
onRemove: (element: TElement<TElementTypesDefault, D>) => void;
22+
onDuplicate: (element: TElement<TElementTypesDefault, D>) => void;
1823
onAddSlotContent: (element: TElement<'slot', D>, referenceComponent: TElement<'component', D>) => void;
1924
}
2025

src/lib/components/element/html/Edit.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ export const Edit = ({ element, parents, onMouseOver, onMouseLeave, onSelect, on
3535

3636
const [forceEnable, setForceEnable] = useState(false);
3737

38-
const [elementProps, /* elementSpecialProps */] = useElementAttributes(element.attributes);
39-
const styles = useUIElementInlineStyle(element.style);
38+
const [elementProps, /* elementSpecialProps */] = useElementAttributes(element);
4039
const children = useObserverValue(element.children);
40+
const styles = useUIElementInlineStyle(element);
4141
const name = useObserverValue(element.name);
4242
const tag = useObserverValue(element.tag);
4343
const id = useObserverValue(element.id);

src/lib/components/element/html/UseElementAttributes.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { useMemo } from "react";
22
import { selector, useObserverValue } from "react-observing";
33

4+
import { useUiEditorContext } from '../../../UiEditorContext';
45
import { toCamelCase } from './../../../helpers';
56
import { TElement } from '../../../types';
67

78

8-
export const useElementAttributes = (attributesObservable: TElement<'html'>['attributes']) => {
9+
export const useElementAttributes = (element: TElement<'html'>) => {
10+
const { onExpressionToValue } = useUiEditorContext();
11+
912

1013
const propsObservable = useMemo(() => {
1114
return selector({
1215
get({ get }) {
13-
const attributes = get(attributesObservable);
16+
const attributes = get(element.attributes);
1417
if (!attributes) return [{}, {}];
1518

1619
const props: Record<string, string | number | boolean | undefined | null> = {};
@@ -24,7 +27,7 @@ export const useElementAttributes = (attributesObservable: TElement<'html'>['att
2427
if (name === 'style') return;
2528

2629

27-
const value = get(attribute.value);
30+
const value = onExpressionToValue(get(attribute.value), name, 'attribute', element);
2831

2932
const attributeAsCamelCase = toCamelCase(name);
3033
switch (attributeAsCamelCase) {
@@ -45,7 +48,7 @@ export const useElementAttributes = (attributesObservable: TElement<'html'>['att
4548
if (name.startsWith('data-')) {
4649
props[name] = value;
4750
return;
48-
} else {
51+
} else {
4952
props[attributeAsCamelCase] = value;
5053
}
5154
break;
@@ -55,7 +58,7 @@ export const useElementAttributes = (attributesObservable: TElement<'html'>['att
5558
return [props, specialProps];
5659
},
5760
});
58-
}, [attributesObservable]);
61+
}, [element, onExpressionToValue]);
5962

6063

6164
return useObserverValue(propsObservable);

src/lib/components/element/html/UseUIElementInlineStyle.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { useMemo } from 'react'
22
import { selector, useObserverValue } from 'react-observing';
33

4+
import { useUiEditorContext } from '../../../UiEditorContext';
45
import { toCamelCase } from '../../../helpers';
56
import { TElement } from '../../../types';
67

78

8-
export const useUIElementInlineStyle = (elementStyle: TElement<'html'>['style']) => {
9+
export const useUIElementInlineStyle = (element: TElement<'html'>) => {
10+
const { onExpressionToValue } = useUiEditorContext();
911

1012
const stylesObservable = useMemo(() => {
1113
return selector(({ get }) => {
12-
const style = get(elementStyle);
14+
const style = get(element.style);
1315
if (!style) return {};
1416

1517

@@ -21,12 +23,12 @@ export const useUIElementInlineStyle = (elementStyle: TElement<'html'>['style'])
2123

2224
if (!name) return;
2325

24-
result[toCamelCase(name)] = value;
26+
result[toCamelCase(name)] = onExpressionToValue(value, name, 'style', element);
2527
});
2628

2729
return result;
2830
});
29-
}, [elementStyle]);
31+
}, [element, onExpressionToValue, element]);
3032

3133

3234
return useObserverValue(stylesObservable);

src/lib/components/element/html/View.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ interface IEditProps {
1313
parents: TParentElement[];
1414
}
1515
export const View = ({ element, parents }: IEditProps) => {
16-
const [elementProps, /* elementSpecialProps */] = useElementAttributes(element.attributes);
17-
const styles = useUIElementInlineStyle(element.style);
16+
const [elementProps, /* elementSpecialProps */] = useElementAttributes(element);
1817
const children = useObserverValue(element.children);
18+
const styles = useUIElementInlineStyle(element);
1919
const tag = useObserverValue(element.tag);
2020

2121

src/lib/components/element/text/Edit.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MouseEvent, RefObject, useCallback, useEffect, useRef, useState } from 'react';
1+
import { MouseEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import { TMonitor, useDrag, useDrop } from 'react-use-drag-and-drop';
33
import { useObserver, useObserverValue } from 'react-observing';
44
import { useFrame } from 'react-frame-component';
@@ -35,6 +35,7 @@ export const Edit = ({ element, parents, onMouseOver, onMouseLeave, onSelect, on
3535
const name = useObserverValue(element.name);
3636
const id = useObserverValue(element.id);
3737

38+
const { onExpressionToValue, onValueToExpression } = useUiEditorContext();
3839
const { onDragStart, onDragEnd } = useUiEditorContext();
3940
const { hideInsertBar } = useInsertBar();
4041
const { selectedId } = useSelectBar();
@@ -59,7 +60,7 @@ export const Edit = ({ element, parents, onMouseOver, onMouseLeave, onSelect, on
5960
value: selectedId,
6061
matchWidthValue: element?.id,
6162
effect: () => onSelectBar(element, elementRef.current),
62-
}, [selectedId, element, text]);
63+
}, [selectedId, element]);
6364

6465

6566
const { isDragging, preview } = useDrag<TDraggableElement>({
@@ -105,22 +106,27 @@ export const Edit = ({ element, parents, onMouseOver, onMouseLeave, onSelect, on
105106
e.stopPropagation();
106107

107108
if (e.code === 'Escape' || e.code === 'Enter' || e.code === 'NumpadEnter') {
108-
setEditable(false);
109+
e.currentTarget.blur();
109110
onSelectBar(element, e.currentTarget)
110111
}
111112
}, [onSelectBar, element]);
112113

113114
const handleBlur = useCallback((e: React.FocusEvent<HTMLSpanElement>) => {
114115
setEditable(false);
115-
setText(e.currentTarget.innerText);
116+
setText(String(onValueToExpression(e.currentTarget.innerText, 'text', 'textContent', element)));
116117
onSelectBar(element, e.currentTarget)
117-
}, [onSelectBar, element]);
118+
}, [onSelectBar, onValueToExpression, element]);
119+
120+
121+
const renderedText = useMemo(() => {
122+
return onExpressionToValue(text, 'text', 'textContent', element) ?? '';
123+
}, [text, onExpressionToValue, element]);
118124

119125

120126
return (
121127
<span
122128
contentEditable={editable}
123-
dangerouslySetInnerHTML={{ __html: text }}
129+
dangerouslySetInnerHTML={{ __html: renderedText }}
124130

125131
onBlur={handleBlur}
126132
onFocus={handleFocus}
+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { useMemo } from 'react';
12
import { useObserverValue } from 'react-observing';
23

4+
import { useUiEditorContext } from '../../../UiEditorContext';
35
import { TElement, TParentElement } from '../../../types';
46

57

@@ -8,7 +10,11 @@ interface IEditProps {
810
parents: TParentElement[];
911
}
1012
export const View = ({ element }: IEditProps) => {
13+
const { onExpressionToValue } = useUiEditorContext();
14+
1115
const text = useObserverValue(element.text);
1216

13-
return text;
17+
return useMemo(() => {
18+
return onExpressionToValue(text, 'text', 'textContent', element);
19+
}, [text, onExpressionToValue, element]);
1420
};

src/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export type { TStyle, TComponent, TDraggableElement, TDropFunctionProps, TExternalDraggableElement, TElement } from './types';
2+
export type { TValueParseFunction } from './UiEditorContext';
23
export * from './UiEditor';

src/lib/types/DraggableElement.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { TElement, TParentElement } from './Element';
1+
import { TElement, TElementTypesDefault, TElementTypesParents, TParentElement } from './Element';
22

33

44
export type TExternalDraggableElement = {
55
id: string;
66
}
77

8-
export type TDraggableElement<E extends "html" | "component" | "slot" | "text" = "html" | "component" | "slot" | "text", D = any> = {
8+
export type TDraggableElement<E extends TElementTypesDefault = TElementTypesDefault, D = any> = {
99
element: TElement<E, D>;
10-
parents: TParentElement<'html' | 'component' | 'slot-content', D>[] | null;
10+
parents: TParentElement<TElementTypesParents, D>[] | null;
1111
}
1212

1313
export type TDropFunctionProps<D = any> = {
14-
element: TElement<"html" | "component" | "slot" | "text", D> | string;
14+
element: TElement<TElementTypesDefault, D> | string;
1515
from: {
1616
position: number;
1717
element: null | 'root' | TElement<'html' | 'slot-content', D>;

0 commit comments

Comments
 (0)