@saltcorn/builder 1.6.0-alpha.12 → 1.6.0-alpha.13

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-alpha.12",
3
+ "version": "1.6.0-alpha.13",
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-alpha.12",
33
+ "@saltcorn/common-code": "1.6.0-alpha.13",
34
34
  "@tippyjs/react": "4.2.6",
35
35
  "babel-jest": "^29.7.0",
36
36
  "babel-loader": "9.2.1",
@@ -73,6 +73,7 @@ import { InitNewElement, Library, LibraryElem } from "./Library";
73
73
  import { RenderNode } from "./RenderNode";
74
74
  import { ListColumn } from "./elements/ListColumn";
75
75
  import { ListColumns } from "./elements/ListColumns";
76
+ import { Prompt } from "./elements/Prompt";
76
77
  import { recursivelyCloneToElems } from "./elements/Clone";
77
78
 
78
79
  const { Provider } = optionsCtx;
@@ -353,6 +354,113 @@ const SettingsPanel = ({ isEnlarged, setIsEnlarged }) => {
353
354
  );
354
355
  };
355
356
 
357
+ const [generating, setGenerating] = useState(false);
358
+ const [generateError, setGenerateError] = useState(null);
359
+
360
+ // Find prompt nodes: check children of selected, or siblings if selected is a Prompt
361
+ const findPromptContext = () => {
362
+ if (!selected) return { promptNodes: [], targetParent: null };
363
+ const isSelectedPrompt = selected.displayName === "Prompt";
364
+
365
+ if (isSelectedPrompt && selected.parent) {
366
+ // Selected node is a Prompt — find all Prompt siblings in same parent
367
+ try {
368
+ const siblingIds = query.node(selected.parent).childNodes();
369
+ const promptIds = siblingIds.filter((id) => {
370
+ const n = query.node(id).get();
371
+ return n?.data?.displayName === "Prompt";
372
+ });
373
+ return { promptNodes: promptIds, targetParent: selected.parent };
374
+ } catch {
375
+ return { promptNodes: [], targetParent: null };
376
+ }
377
+ }
378
+
379
+ // Selected node is a container — check its direct children
380
+ if (selected.children && selected.children.length > 0) {
381
+ const promptIds = selected.children.filter((id) => {
382
+ try {
383
+ const n = query.node(id).get();
384
+ return n?.data?.displayName === "Prompt";
385
+ } catch {
386
+ return false;
387
+ }
388
+ });
389
+ if (promptIds.length > 0) {
390
+ return { promptNodes: promptIds, targetParent: selected.id };
391
+ }
392
+ }
393
+
394
+ // Check linked nodes (e.g. Card's inner Column)
395
+ try {
396
+ const nodeData = query.node(selected.id).get();
397
+ const linkedNodes = nodeData?.data?.linkedNodes;
398
+ if (linkedNodes) {
399
+ for (const linkedId of Object.values(linkedNodes)) {
400
+ const linkedChildIds = query.node(linkedId).childNodes();
401
+ const promptIds = linkedChildIds.filter((id) => {
402
+ try {
403
+ const n = query.node(id).get();
404
+ return n?.data?.displayName === "Prompt";
405
+ } catch {
406
+ return false;
407
+ }
408
+ });
409
+ if (promptIds.length > 0) {
410
+ return { promptNodes: promptIds, targetParent: linkedId };
411
+ }
412
+ }
413
+ }
414
+ } catch {
415
+ // ignore
416
+ }
417
+
418
+ return { promptNodes: [], targetParent: null };
419
+ };
420
+
421
+ const { promptNodes, targetParent } = selected
422
+ ? findPromptContext()
423
+ : { promptNodes: [], targetParent: null };
424
+ const hasPromptNodes = promptNodes.length > 0;
425
+
426
+ const handleGenerate = async () => {
427
+ setGenerating(true);
428
+ setGenerateError(null);
429
+ try {
430
+ const prompts = promptNodes.map((childId) => {
431
+ const n = query.node(childId).get();
432
+ const { promptType, promptText } = n.data.props;
433
+ return `[${promptType}]: ${promptText}`;
434
+ });
435
+ const combinedPrompt = prompts.join("\n");
436
+
437
+ const res = await fetch("/viewedit/copilot-generate-layout", {
438
+ method: "POST",
439
+ headers: {
440
+ "Content-Type": "application/json",
441
+ "CSRF-Token": options.csrfToken,
442
+ "X-Requested-With": "XMLHttpRequest",
443
+ },
444
+ body: JSON.stringify({
445
+ prompt: combinedPrompt,
446
+ mode: options.mode,
447
+ table: options.tableName,
448
+ }),
449
+ });
450
+ const data = await res.json();
451
+ if (data.error) {
452
+ setGenerateError(data.error);
453
+ } else if (data.layout) {
454
+ promptNodes.forEach((id) => actions.delete(id));
455
+ layoutToNodes(data.layout, query, actions, targetParent, options);
456
+ }
457
+ } catch (err) {
458
+ setGenerateError(err.message || "Generation failed");
459
+ } finally {
460
+ setGenerating(false);
461
+ }
462
+ };
463
+
356
464
  return (
357
465
  <div className="settings-panel card mt-1">
358
466
  <div className="card-header px-2 py-1 d-flex justify-content-between align-items-center">
@@ -409,8 +517,9 @@ const SettingsPanel = ({ isEnlarged, setIsEnlarged }) => {
409
517
  {t("Clone")}
410
518
  </button>
411
519
  )}
412
- <hr className="my-2" />
413
- {selected.settings && React.createElement(selected.settings)}
520
+ <div className="mt-2">
521
+ {selected.settings && React.createElement(selected.settings)}
522
+ </div>
414
523
  </Fragment>
415
524
  ) : (
416
525
  t("No element selected")
@@ -582,7 +691,7 @@ const AddColumnButton = () => {
582
691
  const DEVICE_WIDTHS = {
583
692
  desktop: null,
584
693
  tablet: 768,
585
- mobile: 375,
694
+ mobile: 576,
586
695
  };
587
696
 
588
697
  const DevicePreviewToolbar = ({ previewDevice, setPreviewDevice }) => {
@@ -766,6 +875,7 @@ const Builder = ({ options, layout, mode }) => {
766
875
  ListColumn,
767
876
  ListColumns,
768
877
  LibraryElem,
878
+ Prompt,
769
879
  }}
770
880
  >
771
881
  <Provider value={options}>
@@ -852,10 +962,10 @@ const Builder = ({ options, layout, mode }) => {
852
962
  <div className="device-preview-scroll-area">
853
963
  <div
854
964
  className={`device-preview-canvas-wrapper ${
855
- previewDevice !== "desktop" ? "device-preview-constrained" : ""
965
+ previewDevice !== "desktop" && options.mode !== "list" ? "device-preview-constrained" : ""
856
966
  }`}
857
967
  style={{
858
- maxWidth: DEVICE_WIDTHS[previewDevice]
968
+ maxWidth: options.mode !== "list" && DEVICE_WIDTHS[previewDevice]
859
969
  ? `${DEVICE_WIDTHS[previewDevice]}px`
860
970
  : "none",
861
971
  }}
@@ -873,10 +983,6 @@ const Builder = ({ options, layout, mode }) => {
873
983
  </div>
874
984
  <div className="col-sm-auto builder-sidebar">
875
985
  <div style={{ width: isEnlarged ? "28rem" : "16rem" }}>
876
- <DevicePreviewToolbar
877
- previewDevice={previewDevice}
878
- setPreviewDevice={setPreviewDevice}
879
- />
880
986
  {document.getElementById("builder-header-actions") &&
881
987
  createPortal(
882
988
  <Fragment>
@@ -890,6 +996,12 @@ const Builder = ({ options, layout, mode }) => {
890
996
  className={savingState.error ? "d-inline" : "d-none"}
891
997
  />
892
998
  <HistoryPanel />
999
+ {options.mode !== "list" && (
1000
+ <DevicePreviewToolbar
1001
+ previewDevice={previewDevice}
1002
+ setPreviewDevice={setPreviewDevice}
1003
+ />
1004
+ )}
893
1005
  <NextButton layout={layout} />
894
1006
  </Fragment>,
895
1007
  document.getElementById("builder-header-actions")
@@ -29,6 +29,7 @@ import { View } from "./elements/View";
29
29
  import { SearchBar } from "./elements/SearchBar";
30
30
  import { Link } from "./elements/Link";
31
31
  import { Page } from "./elements/Page";
32
+ import { Prompt } from "./elements/Prompt";
32
33
  import optionsCtx from "./context";
33
34
  import {
34
35
  BoundingBox,
@@ -581,8 +582,64 @@ const TableElem = ({ connectors }) => {
581
582
  );
582
583
  };
583
584
 
585
+ const PromptContainerElem = ({ connectors }) => {
586
+ const { t } = useTranslation();
587
+ return (
588
+ <WrapElem
589
+ connectors={connectors}
590
+ icon="fas fa-robot"
591
+ title={t("Generate with AI")}
592
+ label={t("Generate")}
593
+ >
594
+ <Prompt promptType="container" promptText="" />
595
+ </WrapElem>
596
+ );
597
+ };
598
+
599
+ const PromptViewElem = ({ connectors }) => {
600
+ const { t } = useTranslation();
601
+ return (
602
+ <WrapElem
603
+ connectors={connectors}
604
+ icon="fas fa-eye"
605
+ title={t("Prompt View")}
606
+ label={t("Prompt View")}
607
+ >
608
+ <Prompt promptType="view" promptText="" />
609
+ </WrapElem>
610
+ );
611
+ };
612
+
613
+ const PromptFieldElem = ({ connectors }) => {
614
+ const { t } = useTranslation();
615
+ return (
616
+ <WrapElem
617
+ connectors={connectors}
618
+ icon="fas fa-i-cursor"
619
+ title={t("Prompt Field")}
620
+ label={t("Prompt Field")}
621
+ >
622
+ <Prompt promptType="field" promptText="" />
623
+ </WrapElem>
624
+ );
625
+ };
626
+
627
+ const PromptActionElem = ({ connectors }) => {
628
+ const { t } = useTranslation();
629
+ return (
630
+ <WrapElem
631
+ connectors={connectors}
632
+ icon="fas fa-bolt"
633
+ title={t("Prompt Action")}
634
+ label={t("Prompt Action")}
635
+ >
636
+ <Prompt promptType="action" promptText="" />
637
+ </WrapElem>
638
+ );
639
+ };
640
+
584
641
  const chunkToolBox = (elems, expanded) => {
585
- const chunks = chunk(elems, expanded ? 3 : 2);
642
+ const chunks = chunk(elems.filter(Boolean), expanded ? 3 : 2);
586
643
  return chunks.map((es, ix) => (
587
644
  <div className="toolbar-row" key={ix}>
588
645
  {es.map((e, j) => (
@@ -638,6 +695,10 @@ const ToolboxShow = ({ expanded }) => {
638
695
  <DropMenuElem connectors={connectors} />,
639
696
  <TableElem connectors={connectors} />,
640
697
  <PageElem connectors={connectors} pages={pages} />,
698
+ options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
699
+ // <PromptViewElem connectors={connectors} />,
700
+ // <PromptFieldElem connectors={connectors} />,
701
+ // <PromptActionElem connectors={connectors} />,
641
702
  ],
642
703
  expanded
643
704
  );
@@ -696,6 +757,10 @@ const ToolboxList = ({ expanded }) => {
696
757
  <DropMenuElem connectors={connectors} />
697
758
  ),
698
759
  // <TableElem connectors={connectors} />,
760
+ options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
761
+ // <PromptViewElem connectors={connectors} />,
762
+ // <PromptFieldElem connectors={connectors} />,
763
+ // <PromptActionElem connectors={connectors} />,
699
764
  options.allowMultipleElementsPerColumn &&
700
765
  !disable_toolbox?.line_break && (
701
766
  <LineBreakElem connectors={connectors} />
@@ -750,6 +815,10 @@ const ToolboxFilter = ({ expanded }) => {
750
815
  <TableElem connectors={connectors} />,
751
816
  <DropMenuElem connectors={connectors} />,
752
817
  <PageElem connectors={connectors} pages={pages} />,
818
+ options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
819
+ // <PromptViewElem connectors={connectors} />,
820
+ // <PromptFieldElem connectors={connectors} />,
821
+ // <PromptActionElem connectors={connectors} />,
753
822
  ],
754
823
  expanded
755
824
  );
@@ -787,6 +856,10 @@ const ToolboxEdit = ({ expanded }) => {
787
856
  <DropMenuElem connectors={connectors} />,
788
857
  <TableElem connectors={connectors} />,
789
858
  <ViewLinkElem connectors={connectors} options={options} />,
859
+ options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
860
+ // <PromptViewElem connectors={connectors} />,
861
+ // <PromptFieldElem connectors={connectors} />,
862
+ // <PromptActionElem connectors={connectors} />,
790
863
  ],
791
864
  expanded
792
865
  );
@@ -819,6 +892,10 @@ const ToolboxPage = ({ expanded }) => {
819
892
  <DropMenuElem connectors={connectors} />,
820
893
  <PageElem connectors={connectors} pages={pages} />,
821
894
  <TableElem connectors={connectors} />,
895
+ options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
896
+ // <PromptViewElem connectors={connectors} />,
897
+ // <PromptFieldElem connectors={connectors} />,
898
+ // <PromptActionElem connectors={connectors} />,
822
899
  ],
823
900
  expanded
824
901
  );
@@ -7,6 +7,12 @@
7
7
  import React, { useContext, Fragment, useState } from "react";
8
8
  import useTranslation from "../../hooks/useTranslation";
9
9
  import { SettingsRow, SettingsSectionHeaderRow, bstyleopt } from "./utils";
10
+ import PreviewCtx from "../preview_context";
11
+ import {
12
+ getDeviceSizeNode,
13
+ getDeviceSizeSetProp,
14
+ getDisplaySize,
15
+ } from "../../utils/responsive_utils";
10
16
  /*
11
17
  Contains code from https://github.com/tpaksu/boxmodel
12
18
  Copyright (c) 2017 Taha Paksu
@@ -24,6 +30,7 @@ export /**
24
30
  */
25
31
  const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
26
32
  const { t } = useTranslation();
33
+ const { previewDevice } = useContext(PreviewCtx);
27
34
  const [selectedCategory, setSelectedCategory] = useState(false);
28
35
  const [selectedDirection, setSelectedDirection] = useState(false);
29
36
  const selectedProperty = !selectedCategory
@@ -35,7 +42,13 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
35
42
  setSelectedCategory(c);
36
43
  setSelectedDirection(d);
37
44
  };
38
- //console.log(node.style);
45
+ const isDesktop = !previewDevice || previewDevice === "desktop";
46
+ const deviceLabel = previewDevice === "mobile"
47
+ ? ` (${t("mobile")})`
48
+ : previewDevice === "tablet"
49
+ ? ` (${t("tablet")})`
50
+ : "";
51
+
39
52
  const style = node.style;
40
53
  return (
41
54
  <Fragment>
@@ -147,13 +160,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
147
160
  autoComplete="off"
148
161
  name="boxmodel-ex-1_width"
149
162
  size="3"
150
- value={
151
- sizeWithStyle
152
- ? style["width"]
153
- : node.width
154
- ? `${node.width}${node.widthUnit || "px"}`
155
- : ""
156
- }
163
+ value={getDisplaySize(node, "width", previewDevice, sizeWithStyle)}
157
164
  />
158
165
  x
159
166
  <input
@@ -162,13 +169,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
162
169
  autoComplete="off"
163
170
  name="boxmodel-ex-1_height"
164
171
  size="3"
165
- value={
166
- sizeWithStyle
167
- ? style["height"]
168
- : node.height
169
- ? `${node.height}${node.heightUnit || "px"}`
170
- : ""
171
- }
172
+ value={getDisplaySize(node, "height", previewDevice, sizeWithStyle)}
172
173
  />
173
174
  </div>
174
175
  <span
@@ -276,13 +277,13 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
276
277
  <SettingsRow
277
278
  field={{
278
279
  name: "width",
279
- label: t("width"),
280
+ label: t("width") + deviceLabel,
280
281
  type: "DimUnits",
281
282
  horiz: true,
282
283
  }}
283
- node={node}
284
- setProp={setProp}
285
- isStyle={!!sizeWithStyle}
284
+ node={isDesktop ? node : getDeviceSizeNode(node, "width", previewDevice)}
285
+ setProp={isDesktop ? setProp : getDeviceSizeSetProp(setProp, "width", previewDevice)}
286
+ isStyle={isDesktop ? !!sizeWithStyle : true}
286
287
  />
287
288
  <SettingsRow
288
289
  field={{
@@ -307,10 +308,10 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
307
308
  isStyle={true}
308
309
  />
309
310
  <SettingsRow
310
- field={{ name: "height", label: t("height"), type: "DimUnits" }}
311
- node={node}
312
- setProp={setProp}
313
- isStyle={!!sizeWithStyle}
311
+ field={{ name: "height", label: t("height") + deviceLabel, type: "DimUnits" }}
312
+ node={isDesktop ? node : getDeviceSizeNode(node, "height", previewDevice)}
313
+ setProp={isDesktop ? setProp : getDeviceSizeSetProp(setProp, "height", previewDevice)}
314
+ isStyle={isDesktop ? !!sizeWithStyle : true}
314
315
  />
315
316
  <SettingsRow
316
317
  field={{
@@ -75,7 +75,27 @@ const Card = ({
75
75
  const {
76
76
  selected,
77
77
  connectors: { connect, drag },
78
- } = useNode((node) => ({ selected: node.events.selected }));
78
+ mobileWidth,
79
+ tabletWidth,
80
+ mobileHeight,
81
+ tabletHeight,
82
+ } = useNode((node) => ({
83
+ selected: node.events.selected,
84
+ _style: node.data.props.style,
85
+ mobileWidth: node.data.props.mobileWidth,
86
+ tabletWidth: node.data.props.tabletWidth,
87
+ mobileHeight: node.data.props.mobileHeight,
88
+ tabletHeight: node.data.props.tabletHeight,
89
+ }));
90
+ const { previewDevice } = useContext(previewCtx);
91
+ const deviceSizeOverrides = {};
92
+ if (previewDevice === "mobile") {
93
+ if (mobileWidth) deviceSizeOverrides.width = mobileWidth;
94
+ if (mobileHeight) deviceSizeOverrides.height = mobileHeight;
95
+ } else if (previewDevice === "tablet") {
96
+ if (tabletWidth) deviceSizeOverrides.width = tabletWidth;
97
+ if (tabletHeight) deviceSizeOverrides.height = tabletHeight;
98
+ }
79
99
 
80
100
  return (
81
101
  <div
@@ -105,6 +125,7 @@ const Card = ({
105
125
  }deg, ${gradStartColor}, ${gradEndColor})`,
106
126
  }
107
127
  : {}),
128
+ ...deviceSizeOverrides,
108
129
  }}
109
130
  ref={(dom) => connect(drag(dom))}
110
131
  >
@@ -486,6 +507,10 @@ const fields = [
486
507
  { label: "Title Right", name: "titleRight", type: "Nodes", nodeID: "titleRight" },
487
508
  { label: "Footer", name: "footer", type: "Nodes", nodeID: "cardfooter" },
488
509
  { name: "style", default: {} },
510
+ { name: "mobileWidth" },
511
+ { name: "tabletWidth" },
512
+ { name: "mobileHeight" },
513
+ { name: "tabletHeight" },
489
514
  { label: "Class", name: "class", type: "String", canBeFormula: true },
490
515
  { name: "hAlign" },
491
516
  { name: "bgType" },
@@ -20,6 +20,7 @@ import {
20
20
  } from "./utils";
21
21
  import { BoxModelEditor } from "./BoxModelEditor";
22
22
  import { ArrayManager } from "./ArrayManager";
23
+ import { getAlignClass } from "../../utils/responsive_utils";
23
24
  import {
24
25
  AlignTop,
25
26
  AlignMiddle,
@@ -81,7 +82,7 @@ const BREAKPOINT_MIN_WIDTH = {
81
82
  const PREVIEW_DEVICE_WIDTH = {
82
83
  desktop: Infinity,
83
84
  tablet: 768,
84
- mobile: 375,
85
+ mobile: 576,
85
86
  };
86
87
 
87
88
  const getColClass = (width, breakpoint, previewDevice) => {
@@ -114,6 +115,8 @@ const Columns = ({
114
115
  gx,
115
116
  gy,
116
117
  aligns,
118
+ mobileAligns,
119
+ tabletAligns,
117
120
  vAligns,
118
121
  colClasses,
119
122
  colStyles,
@@ -123,15 +126,34 @@ const Columns = ({
123
126
  const {
124
127
  selected,
125
128
  connectors: { connect, drag },
126
- } = useNode((node) => ({ selected: node.events.selected }));
129
+ mobileWidth,
130
+ tabletWidth,
131
+ mobileHeight,
132
+ tabletHeight,
133
+ } = useNode((node) => ({
134
+ selected: node.events.selected,
135
+ _style: node.data.props.style,
136
+ mobileWidth: node.data.props.mobileWidth,
137
+ tabletWidth: node.data.props.tabletWidth,
138
+ mobileHeight: node.data.props.mobileHeight,
139
+ tabletHeight: node.data.props.tabletHeight,
140
+ }));
127
141
  const { previewDevice } = useContext(PreviewCtx);
142
+ const canvasStyle = { ...reactifyStyles(style || {}) };
143
+ if (previewDevice === "mobile") {
144
+ if (mobileWidth) canvasStyle.width = mobileWidth;
145
+ if (mobileHeight) canvasStyle.height = mobileHeight;
146
+ } else if (previewDevice === "tablet") {
147
+ if (tabletWidth) canvasStyle.width = tabletWidth;
148
+ if (tabletHeight) canvasStyle.height = tabletHeight;
149
+ }
128
150
  return (
129
151
  <div
130
152
  className={`row builder-columns ${customClass || ""} ${selected ? "selected-node" : ""} ${
131
153
  typeof gx !== "undefined" && gx !== null ? `gx-${gx}` : ""
132
154
  } ${typeof gy !== "undefined" && gy !== null ? `gy-${gy}` : ""}`}
133
155
  ref={(dom) => connect(drag(dom))}
134
- style={reactifyStyles(style || {})}
156
+ style={canvasStyle}
135
157
  >
136
158
  {ntimes(ncols, (ix) => (
137
159
  <div
@@ -140,8 +162,8 @@ const Columns = ({
140
162
  getWidth(widths, ix),
141
163
  breakpoints?.[ix],
142
164
  previewDevice
143
- )} text-${
144
- aligns?.[ix]
165
+ )} ${
166
+ getAlignClass(aligns, mobileAligns, tabletAligns, ix, previewDevice)
145
167
  } align-items-${vAligns?.[ix]} ${colClasses?.[ix] || ""}`}
146
168
  style={parseStyles(colStyles?.[ix] || "")}
147
169
  >
@@ -162,16 +184,23 @@ export /**
162
184
  */
163
185
  const ColumnsSettings = () => {
164
186
  const { t } = useTranslation();
187
+ const { previewDevice } = useContext(PreviewCtx);
165
188
  const node = useNode((node) => ({
166
189
  widths: node.data.props.widths,
167
190
  ncols: node.data.props.ncols,
168
191
  breakpoints: node.data.props.breakpoints,
169
192
  style: node.data.props.style,
193
+ mobileWidth: node.data.props.mobileWidth,
194
+ tabletWidth: node.data.props.tabletWidth,
195
+ mobileHeight: node.data.props.mobileHeight,
196
+ tabletHeight: node.data.props.tabletHeight,
170
197
  setting_col_n: node.data.props.setting_col_n,
171
198
  gx: node.data.props.gx,
172
199
  gy: node.data.props.gy,
173
200
  vAligns: node.data.props.vAligns,
174
201
  aligns: node.data.props.aligns,
202
+ mobileAligns: node.data.props.mobileAligns,
203
+ tabletAligns: node.data.props.tabletAligns,
175
204
  colClasses: node.data.props.colClasses,
176
205
  colStyles: node.data.props.colStyles,
177
206
  customClass: node.data.props.customClass,
@@ -186,14 +215,24 @@ const ColumnsSettings = () => {
186
215
  setting_col_n,
187
216
  vAligns,
188
217
  aligns,
218
+ mobileAligns,
219
+ tabletAligns,
189
220
  colClasses,
190
221
  colStyles,
191
222
  customClass,
192
223
  currentSettingsTab,
193
224
  } = node;
225
+
226
+ const activeAlignProp =
227
+ previewDevice === "mobile" ? "mobileAligns" :
228
+ previewDevice === "tablet" ? "tabletAligns" : "aligns";
229
+ const activeAligns =
230
+ previewDevice === "mobile" ? mobileAligns :
231
+ previewDevice === "tablet" ? tabletAligns : aligns;
232
+
194
233
  const colSetsNode = {
195
234
  vAlign: vAligns?.[setting_col_n],
196
- hAlign: aligns?.[setting_col_n],
235
+ hAlign: activeAligns?.[setting_col_n],
197
236
  colClass: colClasses?.[setting_col_n] || "",
198
237
  colStyle: colStyles?.[setting_col_n] || "",
199
238
  };
@@ -208,7 +247,7 @@ const ColumnsSettings = () => {
208
247
  setProp={setProp}
209
248
  countProp={"ncols"}
210
249
  currentProp={"setting_col_n"}
211
- managedArrays={["widths", "breakpoints", "aligns", "vAligns", "colClasses", "colStyles"]}
250
+ managedArrays={["widths", "breakpoints", "aligns", "mobileAligns", "tabletAligns", "vAligns", "colClasses", "colStyles"]}
212
251
  manageContents={true}
213
252
  contentsKey={"besides"}
214
253
  initialAddProps={{
@@ -293,7 +332,9 @@ const ColumnsSettings = () => {
293
332
  <SettingsRow
294
333
  field={{
295
334
  name: "hAlign",
296
- label: t("Horizontal"),
335
+ label: previewDevice !== "desktop"
336
+ ? `${t("Horizontal")} (${previewDevice})`
337
+ : t("Horizontal"),
297
338
  type: "btn_select",
298
339
  options: [
299
340
  { value: "start", title: t("Left"), label: <AlignStart /> },
@@ -305,8 +346,8 @@ const ColumnsSettings = () => {
305
346
  setProp={setProp}
306
347
  onChange={(k, v) =>
307
348
  setProp((prop) => {
308
- if (!prop.aligns) prop.aligns = [];
309
- prop.aligns[setting_col_n] = v;
349
+ if (!prop[activeAlignProp]) prop[activeAlignProp] = [];
350
+ prop[activeAlignProp][setting_col_n] = v;
310
351
  })
311
352
  }
312
353
  />