diff --git a/components/core/FontFrame/Settings/index.js b/components/core/FontFrame/Settings/index.js
index c5f70999..cde14f8a 100644
--- a/components/core/FontFrame/Settings/index.js
+++ b/components/core/FontFrame/Settings/index.js
@@ -19,6 +19,7 @@ const STYLES_CONTROLLER_WRAPPER = (theme) =>
export const Controls = ({
view,
+ customView,
settings,
defaultOptions,
updateView,
@@ -31,6 +32,8 @@ export const Controls = ({
getRandomLayout,
resetLayout,
}) => {
+ const isCustomView = (value) => view === "custom" && customView === value;
+
const arrayToSelectOptions = (arr) =>
arr.reduce((acc, option) => [...acc, { value: option, name: Strings.capitalize(option) }], []);
@@ -120,7 +123,7 @@ export const Controls = ({
value={settings.column}
onChange={(e) => updateColumn(e.target.value)}
selectMinWidth="none"
- disabled={view !== "paragraph"}
+ disabled={!(view === "paragraph" || isCustomView("paragraph"))}
/>
);
diff --git a/components/core/FontFrame/Views/ContentEditable.js b/components/core/FontFrame/Views/ContentEditable.js
new file mode 100644
index 00000000..a82b664c
--- /dev/null
+++ b/components/core/FontFrame/Views/ContentEditable.js
@@ -0,0 +1,51 @@
+import * as React from "react";
+
+function normalizeHtml(str) {
+ return str && str.replace(/ |\u202F|\u00A0/g, " ");
+}
+
+export default class ContentEditable extends React.Component {
+ constructor(props) {
+ super(props);
+ this.myRef = React.createRef();
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ const { html } = nextProps;
+ const el = this.myRef.current;
+
+ if (normalizeHtml(el.innerHTML) !== normalizeHtml(html)) return true;
+ return false;
+ }
+
+ componentDidUpdate() {
+ if (!this.myRef) return;
+ const { html } = this.props;
+ const el = this.myRef.current;
+ /** NOTE(Amine): because we often prevent rerendering,
+ * React doesn't update the Dom, so we do it manually
+ */
+ if (normalizeHtml(el.innerHTML) !== normalizeHtml(html)) {
+ el.innerHTML = this.props.html;
+ }
+ }
+
+ handleChange = () => {
+ const el = this.myRef.current;
+ if (!el) return;
+ this.props.onChange(el.innerHTML);
+ };
+
+ render() {
+ const { onChange, html, ...props } = this.props;
+ return (
+
+ );
+ }
+}
diff --git a/components/core/FontFrame/Views/Paragraph.js b/components/core/FontFrame/Views/Paragraph.js
index 7228a4ec..5863df58 100644
--- a/components/core/FontFrame/Views/Paragraph.js
+++ b/components/core/FontFrame/Views/Paragraph.js
@@ -1,36 +1,34 @@
import * as React from "react";
+import ContentEditable from "./ContentEditable";
+
import { css } from "@emotion/react";
-const STYLES_PARAGRAPH = (theme) => css`
- width: 100%;
- margin-top: 12px;
- color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack};
- padding: 0px 32px 28px;
- word-break: break-word;
- white-space: pre-wrap;
+const STYLES_PARAGRAPH_WRAPPER = (theme) => css`
+ display: flex;
+ height: 100%;
+ .font_frame_paragraph {
+ width: 100%;
+ margin-top: 12px;
+ color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack};
+ padding: 0px 32px 28px;
+ word-break: break-word;
+ white-space: pre-wrap;
- &:focus {
- outline: none;
+ &:focus {
+ outline: none;
+ }
}
`;
const STYLES_TYPE_TO_EDIT = (isFocused) => (theme) => css`
- ::after {
+ .font_frame_paragraph::after {
content: " type to edit";
color: ${theme.fontPreviewDarkMode ? theme.system.textGrayDark : theme.system.textGrayLight};
opacity: ${isFocused ? 0 : 1};
}
`;
-const MemoizedChild = React.memo(
- ({ children }) => {
- return {children}
;
- },
- (prevProps, nextProps) => !nextProps.shouldUpdateView
-);
-
export default function Paragraph({
- shouldUpdateView,
content,
valign,
textAlign,
@@ -51,35 +49,33 @@ export default function Paragraph({
};
return (
-
-
+ e.stopPropagation()}
- onInput={(e) => {
- onChange(e.currentTarget.innerText);
- }}
+ html={content}
+ onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
- >
- {content}
-
+ />
);
}
diff --git a/components/core/FontFrame/Views/Sentence.js b/components/core/FontFrame/Views/Sentence.js
index b55f1e7f..1cbd015c 100644
--- a/components/core/FontFrame/Views/Sentence.js
+++ b/components/core/FontFrame/Views/Sentence.js
@@ -2,31 +2,29 @@ import * as React from "react";
import { css } from "@emotion/react";
-const STYLES_SENTENCE = (theme) => css`
- width: 100%;
- margin-top: 12px;
- color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack};
- padding: 0px 32px 28px;
- word-break: break-word;
- &:focus {
- outline: none;
+import ContentEditable from "./ContentEditable";
+
+const STYLES_SENTENCE_WRAPPER = (theme) => css`
+ .font_frame_sentence {
+ width: 100%;
+ margin-top: 12px;
+ color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack};
+ padding: 0px 32px 28px;
+ word-break: break-word;
+ &:focus {
+ outline: none;
+ }
}
`;
const STYLES_TYPE_TO_EDIT = (isFocused) => (theme) => css`
- ::after {
+ .font_frame_sentence::after {
content: " type to edit";
color: ${theme.fontPreviewDarkMode ? theme.system.textGrayDark : theme.system.textGrayLight};
opacity: ${isFocused ? 0 : 1};
}
`;
-const MemoizedChild = React.memo(
- ({ children }) => children,
- (prevProps, nextProps) => !nextProps.shouldUpdateView
-);
-
export default function Sentence({
- shouldUpdateView,
content,
valign,
textAlign,
@@ -45,29 +43,34 @@ export default function Sentence({
bottom: { marginTop: "auto" },
};
return (
-
-
+ {
e.stopPropagation();
}}
- onInput={(e) => {
- onChange(e.currentTarget.innerHTML);
- }}
+ onChange={onChange}
onFocus={handleFocus}
onBlur={handleBlur}
- >
- {content}
-
+ html={content}
+ />
);
}
diff --git a/components/core/FontFrame/Views/index.js b/components/core/FontFrame/Views/index.js
index 90f19843..525f1c21 100644
--- a/components/core/FontFrame/Views/index.js
+++ b/components/core/FontFrame/Views/index.js
@@ -7,7 +7,6 @@ export default function FontView({
view,
customView,
content: { sentence, paragraph, custom },
- shouldUpdateView,
updateCustomView,
}) {
const isCustomView = (value) => view === "custom" && customView === value;
@@ -19,7 +18,6 @@ export default function FontView({
if (view === "paragraph" || isCustomView("paragraph")) {
return (
{
return {
...state,
view: action.value,
- context: { ...state.context, shouldUpdateView: true },
+ context: { ...state.context },
};
case "UPDATE_CUSTOM_VIEW":
return {
@@ -113,7 +112,6 @@ const reducer = (state, action) => {
context: {
...state.context,
customViewContent: action.payload.customViewContent,
- shouldUpdateView: false,
},
};
case "RESET":
diff --git a/components/core/FontFrame/index.js b/components/core/FontFrame/index.js
index 1638e806..2662ec96 100644
--- a/components/core/FontFrame/index.js
+++ b/components/core/FontFrame/index.js
@@ -86,7 +86,6 @@ export default function FontFrame({ cid, url, ...props }) {
custom: currentState.context.customViewContent,
}}
customView={currentState.customView}
- shouldUpdateView={currentState.context.shouldUpdateView}
settings={currentState.context.settings}
updateCustomView={updateCustomView}
/>
@@ -95,6 +94,7 @@ export default function FontFrame({ cid, url, ...props }) {
{currentState.context.showSettings && (