@saltcorn/builder 1.6.0-alpha.10 → 1.6.0-alpha.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.
@@ -4,14 +4,14 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { Fragment } from "react";
7
+ import React, { Fragment, useContext } from "react";
8
8
  import { Column } from "./Column";
9
9
  import useTranslation from "../../hooks/useTranslation";
10
+ import PreviewCtx from "../preview_context";
10
11
 
11
12
  import { Element, useNode } from "@craftjs/core";
12
13
  import {
13
14
  Accordion,
14
- ConfigField,
15
15
  SettingsRow,
16
16
  reactifyStyles,
17
17
  SettingsSectionHeaderRow,
@@ -19,6 +19,7 @@ import {
19
19
  parseStyles,
20
20
  } from "./utils";
21
21
  import { BoxModelEditor } from "./BoxModelEditor";
22
+ import { ArrayManager } from "./ArrayManager";
22
23
  import {
23
24
  AlignTop,
24
25
  AlignMiddle,
@@ -66,6 +67,35 @@ const resetWidths = (ncols) => ntimes(ncols - 1, () => Math.floor(12 / ncols));
66
67
  const getWidth = (widths, colix) =>
67
68
  colix < widths.length ? widths[colix] : 12 - sum(widths);
68
69
 
70
+ /**
71
+ * Bootstrap breakpoint minimum widths (px)
72
+ */
73
+ const BREAKPOINT_MIN_WIDTH = {
74
+ "": 0,
75
+ sm: 576,
76
+ md: 768,
77
+ lg: 992,
78
+ xl: 1200,
79
+ };
80
+
81
+ const PREVIEW_DEVICE_WIDTH = {
82
+ desktop: Infinity,
83
+ tablet: 768,
84
+ mobile: 375,
85
+ };
86
+
87
+ const getColClass = (width, breakpoint, previewDevice) => {
88
+ if (!previewDevice || previewDevice === "desktop") {
89
+ const bp = breakpoint || "sm";
90
+ return bp ? `col-${bp}-${width}` : `col-${width}`;
91
+ }
92
+ const deviceWidth = PREVIEW_DEVICE_WIDTH[previewDevice] || Infinity;
93
+ const bpMin = BREAKPOINT_MIN_WIDTH[breakpoint || "sm"] || 0;
94
+ if (deviceWidth < bpMin) return "col-12";
95
+ const bp = breakpoint || "sm";
96
+ return bp ? `col-${bp}-${width}` : `col-${width}`;
97
+ };
98
+
69
99
  export /**
70
100
  * @param {object} opts
71
101
  * @param {number[]} opts.widths
@@ -88,11 +118,13 @@ const Columns = ({
88
118
  colClasses,
89
119
  colStyles,
90
120
  customClass,
121
+ breakpoints,
91
122
  }) => {
92
123
  const {
93
124
  selected,
94
125
  connectors: { connect, drag },
95
126
  } = useNode((node) => ({ selected: node.events.selected }));
127
+ const { previewDevice } = useContext(PreviewCtx);
96
128
  return (
97
129
  <div
98
130
  className={`row builder-columns ${customClass || ""} ${selected ? "selected-node" : ""} ${
@@ -104,7 +136,11 @@ const Columns = ({
104
136
  {ntimes(ncols, (ix) => (
105
137
  <div
106
138
  key={ix}
107
- className={`split-col col-sm-${getWidth(widths, ix)} text-${
139
+ className={`split-col ${getColClass(
140
+ getWidth(widths, ix),
141
+ breakpoints?.[ix],
142
+ previewDevice
143
+ )} text-${
108
144
  aligns?.[ix]
109
145
  } align-items-${vAligns?.[ix]} ${colClasses?.[ix] || ""}`}
110
146
  style={parseStyles(colStyles?.[ix] || "")}
@@ -156,112 +192,83 @@ const ColumnsSettings = () => {
156
192
  currentSettingsTab,
157
193
  } = node;
158
194
  const colSetsNode = {
159
- vAlign: vAligns?.[setting_col_n - 1],
160
- hAlign: aligns?.[setting_col_n - 1],
161
- colClass: colClasses?.[setting_col_n - 1] || "",
162
- colStyle: colStyles?.[setting_col_n - 1] || "",
195
+ vAlign: vAligns?.[setting_col_n],
196
+ hAlign: aligns?.[setting_col_n],
197
+ colClass: colClasses?.[setting_col_n] || "",
198
+ colStyle: colStyles?.[setting_col_n] || "",
163
199
  };
164
200
  return (
165
201
  <Accordion
166
202
  value={currentSettingsTab}
167
203
  onChange={(ix) => setProp((prop) => (prop.currentSettingsTab = ix))}
168
204
  >
169
- <table accordiontitle={t("Column properties")}>
170
- <tbody>
171
- <tr>
172
- <td colSpan="3">
173
- <label>{t("Number of columns")}</label>
174
- </td>
175
- <td>
176
- <input
177
- type="number"
178
- value={ncols}
179
- className="form-control"
180
- step="1"
181
- min="1"
182
- max="6"
183
- onChange={(e) => {
184
- if (!e.target) return;
185
- const value = e.target.value;
186
- setProp((prop) => {
187
- prop.ncols = value;
188
- prop.widths = resetWidths(value);
189
- });
190
- }}
191
- />
192
- </td>
193
- </tr>
194
- <tr>
195
- <th colSpan="4">{t("Widths & Breakpoint")}</th>
196
- </tr>
197
- {ntimes(ncols, (ix) => (
198
- <Fragment key={ix}>
199
- <tr>
200
- <th colSpan="4">Column {ix + 1}</th>
201
- </tr>
202
- <tr>
203
- <td>
204
- <label>{t("Width")}</label>
205
- </td>
206
- <td align="right">
207
- {ix < ncols - 1 ? (
208
- <input
209
- type="number"
210
- value={widths[ix]}
211
- className="form-control"
212
- step="1"
213
- min="1"
214
- max={12 - (sum(widths) - widths[ix]) - 1}
215
- onChange={(e) => {
216
- if (!e.target) return;
217
- const value = e.target.value;
218
- setProp((prop) => (prop.widths[ix] = +value));
219
- }}
220
- />
221
- ) : (
222
- `${12 - sum(widths)}`
223
- )}
224
- </td>
225
- <td>/12</td>
226
- <td>
227
- <select
228
- className="form-control form-select"
229
- value={breakpoints[ix]}
205
+ <div accordiontitle={t("Column properties")}>
206
+ <ArrayManager
207
+ node={node}
208
+ setProp={setProp}
209
+ countProp={"ncols"}
210
+ currentProp={"setting_col_n"}
211
+ managedArrays={["widths", "breakpoints", "aligns", "vAligns", "colClasses", "colStyles"]}
212
+ manageContents={true}
213
+ contentsKey={"besides"}
214
+ initialAddProps={{
215
+ breakpoints: "sm",
216
+ }}
217
+ onLayoutChange={(layout, action) => {
218
+ if (action === "add" || action === "delete") {
219
+ const n = layout.besides.length;
220
+ layout.widths = ntimes(n, () => Math.floor(12 / n));
221
+ }
222
+ }}
223
+ />
224
+ <table className="w-100 mt-2">
225
+ <tbody>
226
+ <tr>
227
+ <th colSpan="4">{t("Width & Breakpoint")}</th>
228
+ </tr>
229
+ <tr>
230
+ <td>
231
+ <label>{t("Width")}</label>
232
+ </td>
233
+ <td colSpan="3" align="right">
234
+ {setting_col_n < ncols - 1 ? (
235
+ <input
236
+ type="number"
237
+ value={widths[setting_col_n]}
238
+ className="form-control"
239
+ step="1"
240
+ min="1"
241
+ max={12 - (sum(widths) - widths[setting_col_n]) - 1}
230
242
  onChange={(e) => {
231
243
  if (!e.target) return;
232
244
  const value = e.target.value;
233
- setProp((prop) => (prop.breakpoints[ix] = value));
245
+ setProp((prop) => (prop.widths[setting_col_n] = +value));
234
246
  }}
235
- >
236
- <option disabled>{t("Breakpoint")}</option>
237
- <option value="">{t("none")}</option>
238
- {buildBootstrapOptions(["sm", "md", "lg"])}
239
- </select>
240
- </td>
241
- </tr>
242
- </Fragment>
243
- ))}
244
- </tbody>
245
- </table>
246
- <div accordiontitle={t("Column settings")}>
247
- {t("Settings for column #")}
248
- <ConfigField
249
- field={{
250
- name: "setting_col_n",
251
- label: t("Column number"),
252
- type: "btn_select",
253
- options: ntimes(ncols, (i) => ({
254
- value: i + 1,
255
- title: `${i + 1}`,
256
- label: `${i + 1}`,
257
- })),
258
- }}
259
- node={node}
260
- setProp={setProp}
261
- props={node}
262
- ></ConfigField>
263
- <table className="w-100">
264
- <tbody>
247
+ />
248
+ ) : (
249
+ `${12 - sum(widths)}`
250
+ )}
251
+ </td>
252
+ </tr>
253
+ <tr>
254
+ <td>
255
+ <label>{t("Breakpoint")}</label>
256
+ </td>
257
+ <td colSpan="3">
258
+ <select
259
+ className="form-control form-select"
260
+ value={breakpoints[setting_col_n]}
261
+ onChange={(e) => {
262
+ if (!e.target) return;
263
+ const value = e.target.value;
264
+ setProp((prop) => (prop.breakpoints[setting_col_n] = value));
265
+ }}
266
+ >
267
+ <option value="">{t("none")}</option>
268
+ {buildBootstrapOptions(["sm", "md", "lg"])}
269
+ </select>
270
+ </td>
271
+ </tr>
265
272
  <SettingsSectionHeaderRow title={t("Align")} />
266
273
  <SettingsRow
267
274
  field={{
@@ -279,7 +286,7 @@ const ColumnsSettings = () => {
279
286
  onChange={(k, v) =>
280
287
  setProp((prop) => {
281
288
  if (!prop.vAligns) prop.vAligns = [];
282
- prop.vAligns[setting_col_n - 1] = v;
289
+ prop.vAligns[setting_col_n] = v;
283
290
  })
284
291
  }
285
292
  />
@@ -299,7 +306,7 @@ const ColumnsSettings = () => {
299
306
  onChange={(k, v) =>
300
307
  setProp((prop) => {
301
308
  if (!prop.aligns) prop.aligns = [];
302
- prop.aligns[setting_col_n - 1] = v;
309
+ prop.aligns[setting_col_n] = v;
303
310
  })
304
311
  }
305
312
  />
@@ -314,7 +321,7 @@ const ColumnsSettings = () => {
314
321
  onChange={(k, v) =>
315
322
  setProp((prop) => {
316
323
  if (!prop.colClasses) prop.colClasses = [];
317
- prop.colClasses[setting_col_n - 1] = v;
324
+ prop.colClasses[setting_col_n] = v;
318
325
  })
319
326
  }
320
327
  />
@@ -322,14 +329,14 @@ const ColumnsSettings = () => {
322
329
  field={{
323
330
  name: "colStyle",
324
331
  label: t("CSS"),
325
- type: "textarea",
332
+ type: "String",
326
333
  }}
327
334
  node={colSetsNode}
328
335
  setProp={setProp}
329
336
  onChange={(k, v) =>
330
337
  setProp((prop) => {
331
338
  if (!prop.colStyles) prop.colStyles = [];
332
- prop.colStyles[setting_col_n - 1] = v;
339
+ prop.colStyles[setting_col_n] = v;
333
340
  })
334
341
  }
335
342
  />
@@ -384,7 +391,7 @@ Columns.craft = {
384
391
  ncols: 2,
385
392
  style: {},
386
393
  breakpoints: ["sm", "sm"],
387
- setting_col_n: 1,
394
+ setting_col_n: 0,
388
395
  customClass: "",
389
396
  },
390
397
  related: {
@@ -136,6 +136,19 @@ const Container = ({
136
136
  selected,
137
137
  connectors: { connect, drag },
138
138
  } = useNode((node) => ({ selected: node.events.selected }));
139
+ const { previewDevice } = useContext(previewCtx);
140
+
141
+ const BP_MIN = { "": 0, sm: 576, md: 768, lg: 992, xl: 1200 };
142
+ const DEVICE_W = { desktop: Infinity, tablet: 768, mobile: 375 };
143
+ {
144
+ const dw = DEVICE_W[previewDevice] || Infinity;
145
+ if (minScreenWidth && dw < (BP_MIN[minScreenWidth] || 0)) {
146
+ return <div ref={(dom) => connect(drag(dom))} style={{ display: "none" }} />;
147
+ }
148
+ if (maxScreenWidth && dw >= (BP_MIN[maxScreenWidth] || Infinity)) {
149
+ return <div ref={(dom) => connect(drag(dom))} style={{ display: "none" }} />;
150
+ }
151
+ }
139
152
 
140
153
  const actualChildren = contents || children;
141
154
 
@@ -163,8 +176,6 @@ const Container = ({
163
176
  ...parseStyles(customCSS || ""),
164
177
  ...reactifyStyles(style, transform, rotate),
165
178
  display,
166
- //padding: padding.map((p) => p + "px").join(" "),
167
- //margin: margin.map((p) => p + "px").join(" "),
168
179
  minHeight: minHeight ? `${minHeight}${minHeightUnit || "px"}` : null,
169
180
  ...(bgType === "Image" && bgFileId
170
181
  ? {
@@ -208,7 +219,9 @@ const Container = ({
208
219
  : {}),
209
220
  },
210
221
  },
211
- React.createElement(Element, { canvas: true, id: "container-canvas", is: Column }, renderChildren())
222
+ <Element canvas id="container-canvas" is={Column}>
223
+ {renderChildren()}
224
+ </Element>
212
225
  );
213
226
  };
214
227
 
@@ -106,7 +106,7 @@ const layoutToNodes = (
106
106
  * @returns {Element|Text|View|Action|Tabs|Columns}
107
107
  */
108
108
  function toTag(segment, ix) {
109
- if (!segment) return <Empty key={ix} />;
109
+ if (!segment) return null;
110
110
 
111
111
  if (
112
112
  (segment.type === "card" || segment.type === "container") &&
@@ -318,7 +318,7 @@ const layoutToNodes = (
318
318
  colClasses={segment.colClasses}
319
319
  colStyles={segment.colStyles}
320
320
  aligns={segment.aligns}
321
- setting_col_n={1}
321
+ setting_col_n={segment.setting_col_n !== undefined ? segment.setting_col_n : 0}
322
322
  contents={segment.besides.map(toTag)}
323
323
  />
324
324
  );
@@ -354,7 +354,7 @@ const layoutToNodes = (
354
354
  colClasses={segment.colClasses}
355
355
  colStyles={segment.colStyles}
356
356
  aligns={segment.aligns}
357
- setting_col_n={1}
357
+ setting_col_n={segment.setting_col_n !== undefined ? segment.setting_col_n : 0}
358
358
  contents={segment.besides.map(toTag)}
359
359
  />
360
360
  )
@@ -522,14 +522,15 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
522
522
  besides: widths.map((w, ix) => go(nodes[node.linkedNodes["Col" + ix]])),
523
523
  breakpoints: node.props.breakpoints,
524
524
  customClass: node.props.customClass,
525
- gx: +node.props.gx,
526
- gy: +node.props.gy,
525
+ gx: node.props.gx != null ? +node.props.gx : undefined,
526
+ gy: node.props.gy != null ? +node.props.gy : undefined,
527
527
  aligns: node.props.aligns,
528
528
  vAligns: node.props.vAligns,
529
529
  colClasses: node.props.colClasses,
530
530
  colStyles: node.props.colStyles,
531
531
  style: node.props.style,
532
532
  widths,
533
+ setting_col_n: node.props.setting_col_n,
533
534
  ...customProps,
534
535
  };
535
536
  }