@saltcorn/builder 1.6.0-beta.1 → 1.6.0-beta.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/builder",
3
- "version": "1.6.0-beta.1",
3
+ "version": "1.6.0-beta.3",
4
4
  "description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
5
5
  "main": "index.js",
6
6
  "homepage": "https://saltcorn.com",
@@ -30,7 +30,7 @@
30
30
  "@fortawesome/free-solid-svg-icons": "5.15.2",
31
31
  "@fortawesome/react-fontawesome": "0.1.14",
32
32
  "@monaco-editor/react": "4.7.0",
33
- "@saltcorn/common-code": "1.6.0-beta.1",
33
+ "@saltcorn/common-code": "1.6.0-beta.3",
34
34
  "@tippyjs/react": "4.2.6",
35
35
  "babel-jest": "^29.7.0",
36
36
  "babel-loader": "9.2.1",
@@ -826,6 +826,9 @@ const Builder = ({ options, layout, mode }) => {
826
826
 
827
827
  const canvasHeight =
828
828
  Math.max(windowHeight - builderTop, builderHeight, 600) - 10;
829
+
830
+ const smallSidebarWidth = options.isRTL ? '17.5rem' : '16.5rem'
831
+
829
832
  return (
830
833
  <ErrorBoundary>
831
834
  <Editor
@@ -885,7 +888,7 @@ const Builder = ({ options, layout, mode }) => {
885
888
  layoutToNodes,
886
889
  }}
887
890
  >
888
- <div className="row" ref={ref} style={{ marginTop: "-5px" }}>
891
+ <div className="row" ref={ref} style={{ marginTop: "-5px" }} dir={options.isRTL ? "rtl" : "ltr"}>
889
892
  <div
890
893
  className={`col-sm-auto left-builder-col ${
891
894
  isLeftEnlarged
@@ -973,7 +976,7 @@ const Builder = ({ options, layout, mode }) => {
973
976
  </div>
974
977
  </div>
975
978
  <div className="col-sm-auto builder-sidebar">
976
- <div style={{ width: isEnlarged ? "28rem" : "16.5rem" }}>
979
+ <div style={{ width: isEnlarged ? "28rem" : smallSidebarWidth }}>
977
980
  {document.getElementById("builder-header-actions") &&
978
981
  createPortal(
979
982
  <Fragment>
@@ -6,9 +6,10 @@
6
6
 
7
7
  import { useNode, useEditor } from "@craftjs/core";
8
8
  //import { ROOT_NODE } from "@craftjs/utils";
9
- import React, { useEffect, useRef, useCallback, Fragment } from "react";
9
+ import React, { useEffect, useRef, useCallback, Fragment, useContext } from "react";
10
10
  import ReactDOM from "react-dom";
11
11
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
12
+ import optionsCtx from "./context";
12
13
  import {
13
14
  faCopy,
14
15
  faUndo,
@@ -32,6 +33,7 @@ export /**
32
33
  */
33
34
  const RenderNode = ({ render }) => {
34
35
  const { id } = useNode();
36
+ const options = useContext(optionsCtx);
35
37
  const { actions, query, isActive } = useEditor((state) => ({
36
38
  isActive: state.nodes[id].events.selected,
37
39
  }));
@@ -60,14 +62,16 @@ const RenderNode = ({ render }) => {
60
62
  const { top, left, bottom, height, width, right } = dom
61
63
  ? dom.getBoundingClientRect()
62
64
  : { top: 0, left: 0, bottom: 0, right: 0, height: 0, width: 0 };
65
+ const rightPos = window.innerWidth - right;
63
66
  return {
64
67
  top: `${top > 0 ? top : bottom}px`,
65
68
  left: `${left}px`,
69
+ right: `${rightPos}px`,
66
70
  topn: top,
67
71
  leftn: left,
72
+ rightn: rightPos,
68
73
  height,
69
74
  width,
70
- right,
71
75
  bottom,
72
76
  };
73
77
  }, []);
@@ -75,10 +79,16 @@ const RenderNode = ({ render }) => {
75
79
  const scroll = useCallback(() => {
76
80
  const { current: currentDOM } = currentRef;
77
81
  if (!currentDOM) return;
78
- const { top, left } = getPos(dom);
79
- currentDOM.style.top = top;
80
- currentDOM.style.left = left;
81
- }, [dom, getPos]);
82
+ const pos = getPos(dom);
83
+ currentDOM.style.top = pos.top;
84
+ if (options.isRTL) {
85
+ currentDOM.style.right = pos.right;
86
+ currentDOM.style.left = 'auto';
87
+ } else {
88
+ currentDOM.style.left = pos.left;
89
+ currentDOM.style.right = 'auto';
90
+ }
91
+ }, [dom, getPos, options.isRTL]);
82
92
 
83
93
  const hiddenColumnParents = new Set(["Card", "Container", "Table", "DropMenu"]);
84
94
  useEffect(() => {
@@ -145,7 +155,7 @@ const RenderNode = ({ render }) => {
145
155
  isActive ? "activeind" : "hoverind"
146
156
  } px-1 text-white`}
147
157
  style={{
148
- left: getPos(dom).left,
158
+ ...(options.isRTL ? { right: getPos(dom).right, left: 'auto' } : { left: getPos(dom).left, right: 'auto' }),
149
159
  top: getPos(dom).top,
150
160
  zIndex: 1029,
151
161
  }}
@@ -5,7 +5,13 @@
5
5
  */
6
6
  /* globals validate_expression_elem */
7
7
 
8
- import React, { Fragment, useState, useContext, useEffect, useRef } from "react";
8
+ import React, {
9
+ Fragment,
10
+ useState,
11
+ useContext,
12
+ useEffect,
13
+ useRef,
14
+ } from "react";
9
15
  import useTranslation from "../../hooks/useTranslation";
10
16
  import { useNode } from "@craftjs/core";
11
17
  import optionsCtx from "../context";
@@ -157,7 +163,9 @@ const AggregationSettings = () => {
157
163
  <tr>
158
164
  <td>
159
165
  <label>
160
- {options.mode === "filter" ? t("Field") : t("Child table field")}
166
+ {options.mode === "filter"
167
+ ? t("Field")
168
+ : t("Child table field")}
161
169
  </label>
162
170
  </td>
163
171
  <td>
@@ -187,13 +195,13 @@ const AggregationSettings = () => {
187
195
  >
188
196
  {buildOptions(
189
197
  [
190
- t("Count"),
191
- t("CountUnique"),
192
- t("Avg"),
193
- t("Sum"),
194
- t("Max"),
195
- t("Min"),
196
- t("Array_Agg"),
198
+ "Count",
199
+ "CountUnique",
200
+ "Avg",
201
+ "Sum",
202
+ "Max",
203
+ "Min",
204
+ "Array_Agg",
197
205
  ],
198
206
  { valAttr: true }
199
207
  )}
@@ -1,11 +1,14 @@
1
1
 
2
- import React, { useState, useEffect } from "react";
2
+ import React, { useState, useEffect, useContext } from "react";
3
3
  import { useEditor } from "@craftjs/core";
4
4
  import { useLayer } from "@craftjs/layers";
5
5
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
6
  import { faChevronDown, faChevronUp, faArrowUp, faArrowDown, faLevelUpAlt } from "@fortawesome/free-solid-svg-icons";
7
+ import optionsCtx from "../context";
7
8
 
8
9
  const CustomLayer = ({ children }) => {
10
+ const options = useContext(optionsCtx);
11
+ const isRTL = options?.isRTL;
9
12
  const [isEditing, setIsEditing] = useState(false);
10
13
  const [editValue, setEditValue] = useState("");
11
14
  const [isMouseOver, setIsMouseOver] = useState(false);
@@ -136,7 +139,7 @@ const CustomLayer = ({ children }) => {
136
139
  return (
137
140
  <div
138
141
  ref={(dom) => { layer(dom); if (dom) editorConnectors.drop(dom, id); }}
139
- style={isHiddenColumn ? { marginLeft: "-10px" } : undefined}
142
+ style={isHiddenColumn ? (isRTL ? { marginRight: "-10px" } : { marginLeft: "-10px" }) : undefined}
140
143
  >
141
144
  <div
142
145
  ref={(dom) => { drag(dom); layerHeader(dom); }}
@@ -145,7 +148,7 @@ const CustomLayer = ({ children }) => {
145
148
  cursor: isHiddenColumn ? "default" : "pointer",
146
149
  display: isHiddenColumn ? "none" : "flex",
147
150
  alignItems: "center",
148
- padding: `4px 4px 4px ${depth * 10 + 6}px`,
151
+ padding: isRTL ? `4px ${depth * 10 + 6}px 4px 4px` : `4px 4px 4px ${depth * 10 + 6}px`,
149
152
  overflow: "hidden",
150
153
  }}
151
154
  onClick={() => editorActions.selectNode(id)}
@@ -208,7 +211,7 @@ const CustomLayer = ({ children }) => {
208
211
  )}
209
212
 
210
213
  {isMouseOver && !isEditing && parentId && childIndex >= 0 && (
211
- <span className="layer-move-buttons" style={{ display: "inline-flex", gap: 2, marginLeft: 4, flexShrink: 0 }}>
214
+ <span className="layer-move-buttons" style={{ display: "inline-flex", gap: 2, marginInlineStart: 4, flexShrink: 0 }}>
212
215
  {childIndex > 0 && (
213
216
  <span
214
217
  title="Move up"
@@ -25,6 +25,7 @@ export /**
25
25
  */
26
26
  const SearchBar = ({ has_dropdown, children, contents, show_badges }) => {
27
27
  const { t } = useTranslation();
28
+ const options = useContext(optionsCtx);
28
29
  const {
29
30
  selected,
30
31
  connectors: { connect, drag },
@@ -80,7 +81,7 @@ const SearchBar = ({ has_dropdown, children, contents, show_badges }) => {
80
81
  className={`dropdown-menu searchbar-dropdown ${
81
82
  showDropdown ? "show" : ""
82
83
  }`}
83
- style={{ width: dropWidth, left: 0 }}
84
+ style={{ width: dropWidth, ...(options?.isRTL ? { right: 0 } : { left: 0 }) }}
84
85
  >
85
86
  <Element canvas id="searchbar-contents" is={Column}>
86
87
  {renderContents()}
@@ -4,7 +4,7 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { useState, useContext, useEffect, Fragment } from "react";
7
+ import React, { useState, useContext, useEffect, useRef, Fragment } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import {
10
10
  blockProps,
@@ -82,7 +82,7 @@ export /**
82
82
  * @subcategory components
83
83
  */
84
84
  const Text = ({
85
- text,
85
+ text: propText,
86
86
  block,
87
87
  inline,
88
88
  isFormula,
@@ -97,13 +97,20 @@ const Text = ({
97
97
  const {
98
98
  connectors: { connect, drag },
99
99
  selected,
100
+ nodeText,
100
101
  actions: { setProp },
101
102
  } = useNode((state) => ({
102
103
  selected: state.events.selected,
103
104
  dragged: state.events.dragged,
105
+ nodeText: state.data.props.text,
104
106
  }));
107
+ // Use nodeText from store (reacts to undo) with fallback to prop
108
+ const text = nodeText !== undefined ? nodeText : propText;
105
109
  const [editable, setEditable] = useState(false);
106
110
  const { previewDevice } = useContext(PreviewCtx);
111
+ const ckInitRef = useRef(true);
112
+ const lastSavedTextRef = useRef(text);
113
+ const skipDestroyRef = useRef(false);
107
114
 
108
115
  const baseStyle = {
109
116
  ...(font ? { fontFamily: font } : {}),
@@ -117,6 +124,19 @@ const Text = ({
117
124
  );
118
125
  if (activeFontSize) baseStyle.fontSize = activeFontSize;
119
126
 
127
+ useEffect(() => {
128
+ if (editable) {
129
+ ckInitRef.current = true;
130
+ lastSavedTextRef.current = text;
131
+ }
132
+ }, [editable]);
133
+ // Close CKEditor when text changes externally (e.g. undo/redo)
134
+ useEffect(() => {
135
+ if (editable && text !== lastSavedTextRef.current) {
136
+ skipDestroyRef.current = true;
137
+ setEditable(false);
138
+ }
139
+ }, [text]);
120
140
  useEffect(() => {
121
141
  !selected && setEditable(false);
122
142
  }, [selected]);
@@ -149,9 +169,29 @@ const Text = ({
149
169
  <CKEditor
150
170
  initData={text || ""}
151
171
  style={{ display: "inline" }}
152
- onChange={(e) =>
153
- setProp((props) => (props.text = e.editor.getData()))
154
- }
172
+ onChange={(e) => {
173
+ if (ckInitRef.current) {
174
+ ckInitRef.current = false;
175
+ return;
176
+ }
177
+ if (e?.editor) {
178
+ const newText = e.editor.getData();
179
+ setProp((props) => (props.text = newText), 500);
180
+ lastSavedTextRef.current = newText;
181
+ }
182
+ }}
183
+ onBeforeDestroy={(e) => {
184
+ if (skipDestroyRef.current) {
185
+ skipDestroyRef.current = false;
186
+ return;
187
+ }
188
+ if (e?.editor) {
189
+ const newText = e.editor.getData();
190
+ if (newText !== lastSavedTextRef.current) {
191
+ setProp((props) => (props.text = newText));
192
+ }
193
+ }
194
+ }}
155
195
  config={ckConfig}
156
196
  type="inline"
157
197
  />