@saltcorn/builder 0.5.6-beta.3 → 0.6.0-beta.0

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.
@@ -10,6 +10,8 @@ import {
10
10
  SelectUnits,
11
11
  SettingsSectionHeaderRow,
12
12
  SettingsRow,
13
+ reactifyStyles,
14
+ bstyleopt,
13
15
  } from "./utils";
14
16
  import {
15
17
  BorderOuter,
@@ -34,10 +36,14 @@ import {
34
36
  } from "react-bootstrap-icons";
35
37
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
36
38
  import { faScroll, faRobot } from "@fortawesome/free-solid-svg-icons";
39
+ import { BoxModelEditor } from "./BoxModelEditor";
40
+
41
+ function capitalizeFirstLetter(string) {
42
+ return string.charAt(0).toUpperCase() + string.slice(1);
43
+ }
44
+
37
45
  export const Container = ({
38
46
  children,
39
- borderWidth,
40
- borderStyle,
41
47
  minHeight,
42
48
  height,
43
49
  width,
@@ -49,7 +55,7 @@ export const Container = ({
49
55
  bgFileId,
50
56
  imageSize,
51
57
  bgType,
52
- block,
58
+ display,
53
59
  bgColor,
54
60
  setTextColor,
55
61
  textColor,
@@ -58,35 +64,32 @@ export const Container = ({
58
64
  margin,
59
65
  padding,
60
66
  minScreenWidth,
61
- borderRadius,
62
- borderRadiusUnit,
63
- borderDirection,
64
- borderColor,
65
67
  gradStartColor,
66
68
  gradEndColor,
67
69
  gradDirection,
70
+ rotate,
71
+ style,
68
72
  }) => {
69
73
  const {
70
74
  selected,
71
75
  connectors: { connect, drag },
72
76
  } = useNode((node) => ({ selected: node.events.selected }));
77
+ //console.log("container style", style);
73
78
  return (
74
79
  <div
75
80
  ref={(dom) => connect(drag(dom))}
76
- className={`${customClass || ""} text-${hAlign} ${
81
+ className={`${customClass || ""} canvas text-${hAlign} ${
77
82
  vAlign === "middle" ? "d-flex align-items-center" : ""
78
83
  } ${
79
84
  vAlign === "middle" && hAlign === "center" && "justify-content-center"
80
85
  } ${selected ? "selected-node" : ""}`}
81
86
  style={{
82
87
  ...parseStyles(customCSS || ""),
83
- padding: padding.map((p) => p + "px").join(" "),
84
- margin: margin.map((p) => p + "px").join(" "),
88
+ ...reactifyStyles(style),
89
+ display,
90
+ //padding: padding.map((p) => p + "px").join(" "),
91
+ //margin: margin.map((p) => p + "px").join(" "),
85
92
  minHeight: `${Math.max(minHeight, 15)}${minHeightUnit || "px"}`,
86
- [`border${
87
- borderDirection ? `-${borderDirection}` : ""
88
- }`]: `${borderWidth}px ${borderStyle} ${borderColor || "black"}`,
89
- ...(block === false ? { display: "inline-block" } : {}),
90
93
  ...(bgType === "Image" && bgFileId && +bgFileId
91
94
  ? {
92
95
  backgroundImage: `url('/files/serve/${bgFileId}')`,
@@ -116,31 +119,25 @@ export const Container = ({
116
119
  height: `${height}${heightUnit || "px"}`,
117
120
  }
118
121
  : {}),
119
- ...(typeof borderRadius !== "undefined"
122
+ ...(typeof width !== "undefined"
120
123
  ? {
121
- borderRadius: `${borderRadius}${borderRadiusUnit || "px"}`,
124
+ width: `${width}${widthUnit || "px"}`,
122
125
  }
123
126
  : {}),
124
- ...(typeof width !== "undefined"
127
+ ...(rotate
125
128
  ? {
126
- width: `${width}${widthUnit || "px"}`,
129
+ transform: `rotate(${rotate}deg)`,
127
130
  }
128
131
  : {}),
129
132
  }}
130
133
  >
131
- <div className="canvas">{children}</div>
134
+ {children}
132
135
  </div>
133
136
  );
134
137
  };
135
138
 
136
139
  export const ContainerSettings = () => {
137
140
  const node = useNode((node) => ({
138
- borderWidth: node.data.props.borderWidth,
139
- borderStyle: node.data.props.borderStyle,
140
- borderRadius: node.data.props.borderRadius,
141
- borderRadiusUnit: node.data.props.borderRadiusUnit,
142
- borderDirection: node.data.props.borderDirection,
143
- borderColor: node.data.props.borderColor,
144
141
  minHeight: node.data.props.minHeight,
145
142
  height: node.data.props.height,
146
143
  width: node.data.props.width,
@@ -154,7 +151,6 @@ export const ContainerSettings = () => {
154
151
  imageSize: node.data.props.imageSize,
155
152
  vAlign: node.data.props.vAlign,
156
153
  hAlign: node.data.props.hAlign,
157
- block: node.data.props.block,
158
154
  fullPageWidth: node.data.props.fullPageWidth,
159
155
  showIfFormula: node.data.props.showIfFormula,
160
156
  setTextColor: node.data.props.setTextColor,
@@ -173,27 +169,16 @@ export const ContainerSettings = () => {
173
169
  gradEndColor: node.data.props.gradEndColor,
174
170
  gradDirection: node.data.props.gradDirection,
175
171
  overflow: node.data.props.overflow,
172
+ rotate: node.data.props.rotate,
173
+ display: node.data.props.display,
174
+ style: node.data.props.style,
176
175
  }));
177
176
  const {
178
177
  actions: { setProp },
179
- borderWidth,
180
- borderStyle,
181
- borderDirection,
182
- borderRadius,
183
- borderRadiusUnit,
184
- borderColor,
185
- minHeight,
186
- height,
187
- width,
188
- minHeightUnit,
189
- heightUnit,
190
- widthUnit,
191
- vAlign,
192
- hAlign,
193
178
  bgFileId,
194
179
  imageSize,
195
180
  bgType,
196
- block,
181
+ display,
197
182
  bgColor,
198
183
  setTextColor,
199
184
  textColor,
@@ -217,132 +202,67 @@ export const ContainerSettings = () => {
217
202
  } = node;
218
203
  const options = useContext(optionsCtx);
219
204
  const ownership = !!options.ownership;
220
- const bstyleopt = (style) => ({
221
- value: style,
222
- title: style,
223
- label: (
224
- <div
225
- style={{
226
- borderLeftStyle: style,
227
- borderTopStyle: style,
228
- height: "15px",
229
- width: "6px",
230
- }}
231
- ></div>
232
- ),
233
- });
205
+
206
+ const setAProp = (key) => (e) => {
207
+ if (e.target) {
208
+ const target_value = e.target.value;
209
+ setProp((prop) => (prop[key] = target_value));
210
+ }
211
+ };
234
212
  return (
235
213
  <Accordion>
236
- <table className="w-100" accordiontitle="Placement">
214
+ <div accordiontitle="Box" className="w-100">
215
+ <BoxModelEditor setProp={setProp} node={node} />
216
+ <table className="w-100">
217
+ <tbody>
218
+ <tr>
219
+ <td colSpan="2"></td>
220
+ </tr>
221
+ </tbody>
222
+ </table>
223
+ </div>
224
+ <table className="w-100" accordiontitle="Display">
237
225
  <tbody>
238
- <SettingsSectionHeaderRow title="Border" />
239
- <tr>
240
- <td>
241
- <label>Width</label>
242
- </td>
243
- <td>
244
- <div className="input-group input-group-sm w-100">
245
- <input
246
- type="number"
247
- value={borderWidth}
248
- step="1"
249
- className="form-control w-50"
250
- min="0"
251
- max="20"
252
- onChange={(e) =>
253
- setProp((prop) => {
254
- prop.borderWidth = e.target.value;
255
- })
256
- }
257
- />
258
- <div class="input-group-append w-50 d-inline">
259
- <span class="input-group-text">px</span>
260
- </div>
261
- </div>
262
- </td>
263
- </tr>
264
226
  <SettingsRow
265
227
  field={{
266
- name: "borderStyle",
267
- label: "Style",
268
- type: "btn_select",
269
- btnClass: "btnstylesel",
228
+ name: "display",
229
+ label: "Display",
230
+ type: "select",
270
231
  options: [
271
- "solid",
272
- "dotted",
273
- "dashed",
274
- "double",
275
- "groove",
276
- "ridge",
277
- "inset",
278
- "outset",
279
- ].map(bstyleopt),
232
+ "block",
233
+ "inline",
234
+ "inline-block",
235
+ "none",
236
+ "flex",
237
+ "inline-flex",
238
+ ],
280
239
  }}
281
240
  node={node}
282
241
  setProp={setProp}
283
242
  />
284
243
  <SettingsRow
285
244
  field={{
286
- name: "borderDirection",
287
- label: "Direction",
245
+ name: "overflow",
246
+ label: "Overflow",
288
247
  type: "btn_select",
289
- btnClass: "btnstylesel",
290
248
  options: [
291
- { value: "", title: "None", label: <BorderAll /> },
292
- { value: "top", title: "Top", label: <BorderTop /> },
293
- { value: "bottom", title: "Bottom", label: <BorderBottom /> },
294
- { value: "left", title: "Left", label: <BorderLeft /> },
295
- { value: "right", title: "Right", label: <BorderRight /> },
249
+ { value: "visible", title: "Visible", label: <EyeFill /> },
250
+ { value: "hidden", title: "Hidden", label: <EyeSlashFill /> },
251
+ {
252
+ value: "scroll",
253
+ title: "Scroll",
254
+ label: <FontAwesomeIcon icon={faScroll} />,
255
+ },
256
+ {
257
+ value: "auto",
258
+ title: "Auto",
259
+ label: <FontAwesomeIcon icon={faRobot} />,
260
+ },
296
261
  ],
297
262
  }}
298
263
  node={node}
299
264
  setProp={setProp}
300
265
  />
301
-
302
- <tr>
303
- <td>
304
- <label>Color</label>
305
- </td>
306
- <td>
307
- <input
308
- type="color"
309
- value={borderColor}
310
- className="form-control-sm w-50 mr-2"
311
- onChange={(e) =>
312
- setProp((prop) => {
313
- prop.borderColor = e.target.value;
314
- })
315
- }
316
- />
317
- <small>{borderColor}</small>
318
- </td>
319
- </tr>
320
- <SettingsRow
321
- field={{ name: "borderRadius", label: "Radius", type: "DimUnits" }}
322
- node={node}
323
- setProp={setProp}
324
- />
325
- <SettingsSectionHeaderRow title="Size" />
326
- <SettingsRow
327
- field={{ name: "minHeight", label: "Min height", type: "DimUnits" }}
328
- node={node}
329
- setProp={setProp}
330
- />
331
- <SettingsRow
332
- field={{ name: "height", label: "Height", type: "DimUnits" }}
333
- node={node}
334
- setProp={setProp}
335
- />
336
- <SettingsRow
337
- field={{ name: "width", label: "Widths", type: "DimUnits" }}
338
- node={node}
339
- setProp={setProp}
340
- />
341
- <tr>
342
- <td colSpan="2">
343
- <BlockSetting block={block} setProp={setProp} />
344
- </td>
345
- </tr>
346
266
  <tr>
347
267
  <td colSpan="2">
348
268
  <div className="form-check">
@@ -363,49 +283,17 @@ export const ContainerSettings = () => {
363
283
  </tr>
364
284
  </tbody>
365
285
  </table>
366
- <table className="w-100" accordiontitle="Spacing">
367
- <tbody>
368
- <tr>
369
- <th></th>
370
- <th>Margin</th>
371
- <th>Padding</th>
372
- </tr>
373
- {["Top", "Right", "Bottom", "Left"].map((direction, ix) => (
374
- <tr key={ix}>
375
- <td>{direction}</td>
376
- <td>
377
- <input
378
- type="number"
379
- value={margin[ix]}
380
- step="1"
381
- className="form-control-sm w-100"
382
- onChange={(e) =>
383
- setProp((prop) => {
384
- prop.margin[ix] = e.target.value;
385
- })
386
- }
387
- />
388
- </td>
389
- <td>
390
- <input
391
- type="number"
392
- value={padding[ix]}
393
- step="1"
394
- className="form-control-sm w-100"
395
- onChange={(e) =>
396
- setProp((prop) => {
397
- prop.padding[ix] = e.target.value;
398
- })
399
- }
400
- />
401
- </td>
402
- <td>px</td>
403
- </tr>
404
- ))}
405
- </tbody>
406
- </table>
407
286
  <table className="w-100" accordiontitle="Contents">
408
287
  <tbody>
288
+ <SettingsRow
289
+ field={{
290
+ name: "rotate",
291
+ label: "Rotate °",
292
+ type: "Integer",
293
+ }}
294
+ node={node}
295
+ setProp={setProp}
296
+ />
409
297
  <SettingsSectionHeaderRow title="Align" />
410
298
  <SettingsRow
411
299
  field={{
@@ -436,29 +324,6 @@ export const ContainerSettings = () => {
436
324
  node={node}
437
325
  setProp={setProp}
438
326
  />
439
- <SettingsRow
440
- field={{
441
- name: "overflow",
442
- label: "Overflow",
443
- type: "btn_select",
444
- options: [
445
- { value: "visible", title: "Visible", label: <EyeFill /> },
446
- { value: "hidden", title: "Hidden", label: <EyeSlashFill /> },
447
- {
448
- value: "scroll",
449
- title: "Scroll",
450
- label: <FontAwesomeIcon icon={faScroll} />,
451
- },
452
- {
453
- value: "auto",
454
- title: "Auto",
455
- label: <FontAwesomeIcon icon={faRobot} />,
456
- },
457
- ],
458
- }}
459
- node={node}
460
- setProp={setProp}
461
- />
462
327
  <SettingsSectionHeaderRow title="Background" />
463
328
  <SettingsRow
464
329
  field={{
@@ -495,11 +360,7 @@ export const ContainerSettings = () => {
495
360
  type="color"
496
361
  value={gradStartColor}
497
362
  className="form-control-sm w-50"
498
- onChange={(e) =>
499
- setProp((prop) => {
500
- prop.gradStartColor = e.target.value;
501
- })
502
- }
363
+ onChange={setAProp("gradStartColor")}
503
364
  />
504
365
  </OrFormula>
505
366
  </td>
@@ -515,11 +376,7 @@ export const ContainerSettings = () => {
515
376
  type="color"
516
377
  value={gradEndColor}
517
378
  className="form-control-sm w-50"
518
- onChange={(e) =>
519
- setProp((prop) => {
520
- prop.gradEndColor = e.target.value;
521
- })
522
- }
379
+ onChange={setAProp("gradEndColor")}
523
380
  />
524
381
  </OrFormula>
525
382
  </td>
@@ -537,11 +394,7 @@ export const ContainerSettings = () => {
537
394
  max="360"
538
395
  value={gradDirection}
539
396
  className="form-control-sm w-50"
540
- onChange={(e) =>
541
- setProp((prop) => {
542
- prop.gradDirection = e.target.value;
543
- })
544
- }
397
+ onChange={setAProp("gradDirection")}
545
398
  />
546
399
  </OrFormula>
547
400
  </td>
@@ -558,9 +411,7 @@ export const ContainerSettings = () => {
558
411
  <select
559
412
  value={bgFileId}
560
413
  className="form-control-sm w-100"
561
- onChange={(e) =>
562
- setProp((prop) => (prop.bgFileId = e.target.value))
563
- }
414
+ onChange={setAProp("bgFileId")}
564
415
  >
565
416
  {options.images.map((f, ix) => (
566
417
  <option key={ix} value={f.id}>
@@ -579,11 +430,7 @@ export const ContainerSettings = () => {
579
430
  <select
580
431
  value={imageSize}
581
432
  className="form-control-sm"
582
- onChange={(e) =>
583
- setProp((prop) => {
584
- prop.imageSize = e.target.value;
585
- })
586
- }
433
+ onChange={setAProp("imageSize")}
587
434
  >
588
435
  <option>contain</option>
589
436
  <option>cover</option>
@@ -601,11 +448,7 @@ export const ContainerSettings = () => {
601
448
  type="color"
602
449
  value={bgColor}
603
450
  className="form-control-sm w-50"
604
- onChange={(e) =>
605
- setProp((prop) => {
606
- prop.bgColor = e.target.value;
607
- })
608
- }
451
+ onChange={setAProp("bgColor")}
609
452
  />
610
453
  </OrFormula>
611
454
  </td>
@@ -614,7 +457,7 @@ export const ContainerSettings = () => {
614
457
  <tr>
615
458
  <td colSpan="2">
616
459
  <label>
617
- Set text color{" "}
460
+ Set text color
618
461
  <input
619
462
  name="setTextColor"
620
463
  type="checkbox"
@@ -622,10 +465,10 @@ export const ContainerSettings = () => {
622
465
  onChange={(e) =>
623
466
  setProp((prop) => (prop.setTextColor = e.target.checked))
624
467
  }
625
- />{" "}
468
+ />
626
469
  </label>
627
470
  </td>
628
- </tr>{" "}
471
+ </tr>
629
472
  {setTextColor && (
630
473
  <tr>
631
474
  <td>
@@ -636,17 +479,124 @@ export const ContainerSettings = () => {
636
479
  type="color"
637
480
  value={textColor}
638
481
  className="form-control-sm"
639
- onChange={(e) =>
640
- setProp((prop) => {
641
- prop.textColor = e.target.value;
642
- })
643
- }
482
+ onChange={setAProp("textColor")}
644
483
  />
645
484
  </td>
646
485
  </tr>
647
486
  )}
648
487
  </tbody>
649
488
  </table>
489
+ <table className="w-100" accordiontitle="Flex properties">
490
+ <tbody>
491
+ <SettingsSectionHeaderRow title="Flex item" />
492
+ <SettingsRow
493
+ field={{ name: "flex-grow", label: "Grow", type: "Float" }}
494
+ node={node}
495
+ setProp={setProp}
496
+ />
497
+ <SettingsRow
498
+ field={{ name: "flex-shrink", label: "Shrink", type: "Float" }}
499
+ node={node}
500
+ setProp={setProp}
501
+ />
502
+ {display && display.includes("flex") && (
503
+ <Fragment>
504
+ <SettingsSectionHeaderRow title="Flex container" />
505
+ <SettingsRow
506
+ field={{
507
+ name: "flex-direction",
508
+ label: "Direction",
509
+ type: "select",
510
+ options: ["row", "row-reverse", "column", "column-reverse"],
511
+ }}
512
+ node={node}
513
+ setProp={setProp}
514
+ isStyle={true}
515
+ />
516
+ <SettingsRow
517
+ field={{
518
+ name: "flex-wrap",
519
+ label: "Wrap",
520
+ type: "select",
521
+ options: ["nowrap", "wrap", "wrap-reverse"],
522
+ }}
523
+ node={node}
524
+ setProp={setProp}
525
+ isStyle={true}
526
+ />
527
+ <SettingsRow
528
+ field={{
529
+ name: "justify-content",
530
+ label: "Justify content",
531
+ type: "select",
532
+ options: [
533
+ "flex-start",
534
+ "flex-end",
535
+ "center",
536
+ "space-between",
537
+ "space-around",
538
+ "space-evenly",
539
+ "start",
540
+ "end",
541
+ "left",
542
+ "right",
543
+ ],
544
+ }}
545
+ node={node}
546
+ setProp={setProp}
547
+ isStyle={true}
548
+ />
549
+ <SettingsRow
550
+ field={{
551
+ name: "align-items",
552
+ label: "Align items",
553
+ type: "select",
554
+ options: [
555
+ "stretch",
556
+ "flex-start",
557
+ "flex-end",
558
+ "center",
559
+ "baseline",
560
+ "first baseline",
561
+ "last baseline",
562
+ "start",
563
+ "end",
564
+ "self-start",
565
+ "self-end",
566
+ ],
567
+ }}
568
+ node={node}
569
+ setProp={setProp}
570
+ isStyle={true}
571
+ />
572
+ <SettingsRow
573
+ field={{
574
+ name: "align-content",
575
+ label: "Align content",
576
+ type: "select",
577
+ options: [
578
+ "flex-start",
579
+ "flex-end",
580
+ "center",
581
+ "space-between",
582
+ "space-around",
583
+ "space-evenly",
584
+ "stretch",
585
+ "start",
586
+ "end",
587
+ "baseline",
588
+ "first baseline",
589
+ "last baseline",
590
+ ],
591
+ }}
592
+ node={node}
593
+ setProp={setProp}
594
+ isStyle={true}
595
+ />
596
+ </Fragment>
597
+ )}
598
+ </tbody>
599
+ </table>
650
600
  <table className="w-100" accordiontitle="Show if...">
651
601
  <tbody>
652
602
  {["show", "edit"].includes(options.mode) && (
@@ -659,9 +609,7 @@ export const ContainerSettings = () => {
659
609
  type="text"
660
610
  className="form-control text-to-display"
661
611
  value={showIfFormula}
662
- onChange={(e) =>
663
- setProp((prop) => (prop.showIfFormula = e.target.value))
664
- }
612
+ onChange={setAProp("showIfFormula")}
665
613
  />
666
614
  <div style={{ marginTop: "-5px" }}>
667
615
  <small className="text-muted text-monospace">FORMULA</small>
@@ -722,11 +670,7 @@ export const ContainerSettings = () => {
722
670
  <select
723
671
  value={minScreenWidth}
724
672
  className="form-control"
725
- onChange={(e) =>
726
- setProp((prop) => {
727
- prop.minScreenWidth = e.target.value;
728
- })
729
- }
673
+ onChange={setAProp("minScreenWidth")}
730
674
  >
731
675
  <option value="">all</option>
732
676
  <option value="sm">small</option>
@@ -744,11 +688,7 @@ export const ContainerSettings = () => {
744
688
  <select
745
689
  value={maxScreenWidth}
746
690
  className="form-control"
747
- onChange={(e) =>
748
- setProp((prop) => {
749
- prop.maxScreenWidth = e.target.value;
750
- })
751
- }
691
+ onChange={setAProp("maxScreenWidth")}
752
692
  >
753
693
  <option value="">all</option>
754
694
  <option value="md">small</option>
@@ -766,7 +706,7 @@ export const ContainerSettings = () => {
766
706
  type="text"
767
707
  className="form-control"
768
708
  value={url}
769
- onChange={(e) => setProp((prop) => (prop.url = e.target.value))}
709
+ onChange={setAProp("url")}
770
710
  />
771
711
  </OrFormula>
772
712
 
@@ -774,11 +714,7 @@ export const ContainerSettings = () => {
774
714
  <select
775
715
  value={hoverColor}
776
716
  className="form-control"
777
- onChange={(e) =>
778
- setProp((prop) => {
779
- prop.hoverColor = e.target.value;
780
- })
781
- }
717
+ onChange={setAProp("hoverColor")}
782
718
  >
783
719
  <option value="">None</option>
784
720
  <option value="gray">gray</option>
@@ -797,9 +733,7 @@ export const ContainerSettings = () => {
797
733
  type="text"
798
734
  className="form-control text-to-display"
799
735
  value={customClass}
800
- onChange={(e) =>
801
- setProp((prop) => (prop.customClass = e.target.value))
802
- }
736
+ onChange={setAProp("customClass")}
803
737
  />
804
738
  </OrFormula>
805
739
  <div>
@@ -810,7 +744,7 @@ export const ContainerSettings = () => {
810
744
  type="text"
811
745
  className="text-to-display form-control"
812
746
  value={customCSS}
813
- onChange={(e) => setProp((prop) => (prop.customCSS = e.target.value))}
747
+ onChange={setAProp("customCSS")}
814
748
  ></textarea>
815
749
  </div>
816
750
  </Accordion>
@@ -819,15 +753,13 @@ export const ContainerSettings = () => {
819
753
  Container.craft = {
820
754
  displayName: "Container",
821
755
  props: {
822
- borderWidth: 0,
823
- borderStyle: "solid",
824
756
  minHeight: 0,
825
757
  vAlign: "top",
826
758
  hAlign: "left",
827
759
  bgFileId: 0,
760
+ rotate: 0,
828
761
  isFormula: {},
829
762
  bgType: "None",
830
- block: true,
831
763
  fullPageWidth: false,
832
764
  bgColor: "#ffffff",
833
765
  borderColor: "#000000",
@@ -842,7 +774,9 @@ Container.craft = {
842
774
  margin: [0, 0, 0, 0],
843
775
  padding: [0, 0, 0, 0],
844
776
  minScreenWidth: "",
777
+ display: "block",
845
778
  show_for_owner: false,
779
+ style: {},
846
780
  },
847
781
  rules: {
848
782
  canDrag: () => true,