@saltcorn/markup 1.6.0-alpha.11 → 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/dist/layout.js CHANGED
@@ -73,27 +73,74 @@ const applyTextStyle = (segment, inner) => {
73
73
  style.display = "inline-block";
74
74
  if (segment.customClass && segment.type !== "container")
75
75
  klasses.push(segment.customClass);
76
+ // Per-device font size: generate scoped responsive CSS
77
+ let responsiveFontStyle = "";
78
+ if (segment.mobileFontSize || segment.tabletFontSize) {
79
+ const rndCls = `fs-${Math.floor(Math.random() * 16777215).toString(16)}`;
80
+ klasses.push(rndCls);
81
+ const desktopFs = style["font-size"];
82
+ let css = "";
83
+ if (segment.mobileFontSize)
84
+ css += `.${rndCls}{font-size:${segment.mobileFontSize} !important}`;
85
+ if (segment.tabletFontSize)
86
+ css += `@media(min-width:768px){.${rndCls}{font-size:${segment.tabletFontSize} !important}}`;
87
+ if (desktopFs)
88
+ css += `@media(min-width:992px){.${rndCls}{font-size:${desktopFs} !important}}`;
89
+ responsiveFontStyle = `<style>${css}</style>`;
90
+ }
76
91
  const klass = klasses.join(" ");
92
+ let result;
77
93
  switch (hs) {
78
94
  case "h1":
79
- return h1({ style, class: klass }, inner);
95
+ result = h1({ style, class: klass }, inner);
96
+ break;
80
97
  case "h2":
81
- return h2({ style, class: klass }, inner);
98
+ result = h2({ style, class: klass }, inner);
99
+ break;
82
100
  case "h3":
83
- return h3({ style, class: klass }, inner);
101
+ result = h3({ style, class: klass }, inner);
102
+ break;
84
103
  case "h4":
85
- return h4({ style, class: klass }, inner);
104
+ result = h4({ style, class: klass }, inner);
105
+ break;
86
106
  case "h5":
87
- return h5({ style, class: klass }, inner);
107
+ result = h5({ style, class: klass }, inner);
108
+ break;
88
109
  case "h6":
89
- return h6({ style, class: klass }, inner);
110
+ result = h6({ style, class: klass }, inner);
111
+ break;
90
112
  default:
91
- return segment.block || (segment.display === "block" && hasStyle)
113
+ result = segment.block || (segment.display === "block" && hasStyle)
92
114
  ? div({ class: klass, style }, inner)
93
115
  : segment.textStyle || hasStyle || klass
94
116
  ? span({ class: klass, style }, inner)
95
117
  : inner;
96
118
  }
119
+ return responsiveFontStyle + result;
120
+ };
121
+ const responsiveSizeStyle = (segment, desktopStyle) => {
122
+ const { mobileWidth, tabletWidth, mobileHeight, tabletHeight } = segment;
123
+ if (!mobileWidth && !tabletWidth && !mobileHeight && !tabletHeight)
124
+ return { className: "", styleTag: "" };
125
+ const rndCls = `rs-${Math.floor(Math.random() * 16777215).toString(16)}`;
126
+ let css = "";
127
+ // mobile only (< 768px)
128
+ const mobileRules = [];
129
+ if (mobileWidth)
130
+ mobileRules.push(`width:${mobileWidth} !important`);
131
+ if (mobileHeight)
132
+ mobileRules.push(`height:${mobileHeight} !important`);
133
+ if (mobileRules.length)
134
+ css += `@media(max-width:767.98px){.${rndCls}{${mobileRules.join(";")}}}`;
135
+ // tablet only (768px - 991.98px)
136
+ const tabletRules = [];
137
+ if (tabletWidth)
138
+ tabletRules.push(`width:${tabletWidth} !important`);
139
+ if (tabletHeight)
140
+ tabletRules.push(`height:${tabletHeight} !important`);
141
+ if (tabletRules.length)
142
+ css += `@media(min-width:768px) and (max-width:991.98px){.${rndCls}{${tabletRules.join(";")}}}`;
143
+ return { className: rndCls, styleTag: css ? `<style>${css}</style>` : "" };
97
144
  };
98
145
  /**
99
146
  * @param {object} opts
@@ -142,7 +189,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
142
189
  return wrap(segment, isTop, ix, segment.contents || "");
143
190
  }
144
191
  if (segment.type === "breadcrumbs") {
145
- return wrap(segment, isTop, ix, breadcrumbs(segment.crumbs || [], segment.right, segment.after));
192
+ return wrap(segment, isTop, ix, breadcrumbs(segment.crumbs || [], segment.right, segment.after, segment.center));
146
193
  }
147
194
  if (segment.type === "view") {
148
195
  return wrap(segment, isTop, ix, segment.contents || "");
@@ -272,119 +319,124 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
272
319
  }
273
320
  if (segment.type === "card") {
274
321
  const { vAlign, hAlign, bgType, gradDirection, gradStartColor, gradEndColor, bgFileId, imageSize, imageLocation, } = segment;
275
- return wrap(segment, isTop, ix, div({
276
- class: [
277
- "card",
278
- !(segment.class || "").includes("mt-") && "mt-4",
279
- segment.shadow === false ? false : "shadow",
280
- segment.class,
281
- segment.url && "with-link",
282
- hints.cardClass,
283
- hAlign && `text-${hAlign}`,
284
- ],
285
- ...(segment.id ? { id: segment.id } : {}),
286
- onclick: segment.url
287
- ? isWeb
288
- ? segment.url?.startsWith?.("javascript:")
289
- ? text_attr(segment.url.replace("javascript:", ""))
290
- : `location.href='${segment.url}'`
291
- : `execLink('${segment.url}')`
292
- : false,
293
- style: {
294
- ...segment.style,
295
- ...(bgType === "Color"
296
- ? { backgroundColor: segment.bgColor }
297
- : bgType === "Gradient"
298
- ? {
299
- backgroundImage: `linear-gradient(${gradDirection || 0}deg, ${gradStartColor}, ${gradEndColor});`,
300
- }
301
- : bgType === "Image" && bgFileId && imageLocation === "Card"
302
- ? {
303
- backgroundImage: `url('/files/serve/${bgFileId}')`,
304
- backgroundSize: imageSize === "repeat"
305
- ? undefined
306
- : imageSize || "contain",
307
- backgroundRepeat: imageSize === "repeat" ? imageSize : "no-repeat",
308
- }
309
- : {}),
310
- },
311
- }, bgType === "Image" &&
312
- bgFileId &&
313
- imageLocation === "Top" &&
314
- img({
315
- src: `/files/serve/${bgFileId}`,
316
- class: "card-img-top",
317
- }), segment.title &&
318
- span({ class: ["card-header", segment.titleRight && "right-section"] }, typeof segment.title === "string"
319
- ? hints.cardTitleWrapDiv
320
- ? div({ class: "card-title" }, genericElement(`h${hints.cardTitleHeader || 5}`, segment.title))
321
- : genericElement(`h${hints.cardTitleHeader || 5}`, {
322
- class: hints.cardTitleClass ||
323
- "m-0 fw-bold text-primary d-inline",
324
- }, segment.title)
325
- : segment.title, segment.titleRight
326
- ? div({ class: "title-right" }, go(segment.titleRight))
327
- : "", segment.subtitle ? span(segment.subtitle) : "", segment.titleAjaxIndicator &&
328
- span({
329
- class: "float-end ms-auto sc-ajax-indicator",
330
- style: { display: "none" },
331
- }, i({ class: "fas fa-save" })), segment.titleErrorInidicator &&
332
- span({
333
- class: "float-end sc-error-indicator",
334
- style: { display: "none", color: "#ff0033" },
335
- }, i({ class: "fas fa-exclamation-triangle" }))), segment.tabContents && // TODO remove all calls to this, use tab in content instead
336
- div({ class: "card-header" }, ul({ class: ["nav nav-tabs card-header-tabs", hints.tabClass] }, Object.keys(segment.tabContents).map((title, ix) => li({ class: "nav-item" }, a({
337
- class: ["nav-link", ix === 0 && "active"],
338
- href: `#tab-${title}`,
339
- "data-bs-toggle": "tab",
340
- role: "tab",
341
- }, title))))) +
342
- div({
343
- class: [
344
- "card-body",
345
- segment.bodyClass,
346
- segment.noPadding && "p-0",
347
- ],
348
- }, div({ class: "tab-content", id: "myTabContent" }, Object.entries(segment.tabContents).map(([title, contents], ix) => div({
349
- class: ["tab-pane", ix == 0 && "show active"],
350
- id: `tab-${title}`,
351
- }, contents)))), segment.contents &&
352
- (segment.contents.type === "tabs" &&
353
- segment.contents.tabsStyle !== "Value switch"
354
- ? renderTabs({
355
- tabClass: "card-header-tabs",
356
- headerWrapperClass: "card-header",
357
- contentWrapperClass: [
358
- "card-body",
359
- segment.bodyClass,
360
- segment.noPadding && "p-0",
361
- ],
362
- ...segment.contents,
363
- }, go, segment.serverRendered
364
- ? req?.query?.[segment.tabId || "_tab"]
365
- : undefined, hints)
366
- : div({
367
- class: [
368
- "card-body",
369
- segment.bodyClass,
370
- segment.noPadding && "p-0",
371
- ],
372
- style: {
373
- ...(bgType === "Image" &&
374
- bgFileId &&
375
- imageLocation === "Body"
322
+ const cardSize = responsiveSizeStyle(segment);
323
+ return wrap(segment, isTop, ix, cardSize.styleTag +
324
+ div({
325
+ class: [
326
+ "card",
327
+ !(segment.class || "").includes("mt-") &&
328
+ !segment.style?.["margin-top"] &&
329
+ "mt-4",
330
+ segment.shadow === false ? false : "shadow",
331
+ segment.class,
332
+ segment.url && "with-link",
333
+ hints.cardClass,
334
+ hAlign && `text-${hAlign}`,
335
+ cardSize.className,
336
+ ],
337
+ ...(segment.id ? { id: segment.id } : {}),
338
+ onclick: segment.url
339
+ ? isWeb
340
+ ? segment.url?.startsWith?.("javascript:")
341
+ ? text_attr(segment.url.replace("javascript:", ""))
342
+ : `location.href='${segment.url}'`
343
+ : `execLink('${segment.url}')`
344
+ : false,
345
+ style: {
346
+ ...segment.style,
347
+ ...(bgType === "Color"
348
+ ? { backgroundColor: segment.bgColor }
349
+ : bgType === "Gradient"
376
350
  ? {
377
- backgroundImage: `url('/files/serve/${bgFileId}')`,
378
- backgroundSize: imageSize === "repeat"
379
- ? undefined
380
- : imageSize || "contain",
381
- backgroundRepeat: imageSize === "repeat" ? imageSize : "no-repeat",
351
+ backgroundImage: `linear-gradient(${gradDirection || 0}deg, ${gradStartColor}, ${gradEndColor});`,
382
352
  }
383
- : {}),
384
- },
385
- }, go(segment.contents))), (segment.hasFooter ||
386
- (segment.footer && segment.hasFooter !== false)) &&
387
- div({ class: "card-footer" }, go(segment.footer))));
353
+ : bgType === "Image" && bgFileId && imageLocation === "Card"
354
+ ? {
355
+ backgroundImage: `url('/files/serve/${bgFileId}')`,
356
+ backgroundSize: imageSize === "repeat"
357
+ ? undefined
358
+ : imageSize || "contain",
359
+ backgroundRepeat: imageSize === "repeat" ? imageSize : "no-repeat",
360
+ }
361
+ : {}),
362
+ },
363
+ }, bgType === "Image" &&
364
+ bgFileId &&
365
+ imageLocation === "Top" &&
366
+ img({
367
+ src: `/files/serve/${bgFileId}`,
368
+ class: "card-img-top",
369
+ }), segment.title &&
370
+ span({ class: ["card-header", segment.titleRight && "right-section"] }, typeof segment.title === "string"
371
+ ? hints.cardTitleWrapDiv
372
+ ? div({ class: "card-title" }, genericElement(`h${hints.cardTitleHeader || 5}`, segment.title))
373
+ : genericElement(`h${hints.cardTitleHeader || 5}`, {
374
+ class: hints.cardTitleClass ||
375
+ "m-0 fw-bold text-primary d-inline",
376
+ }, segment.title)
377
+ : segment.title, segment.titleRight
378
+ ? div({ class: "title-right" }, go(segment.titleRight))
379
+ : "", segment.subtitle ? span(segment.subtitle) : "", segment.titleAjaxIndicator &&
380
+ span({
381
+ class: "float-end ms-auto sc-ajax-indicator",
382
+ style: { display: "none" },
383
+ }, i({ class: "fas fa-save" })), segment.titleErrorInidicator &&
384
+ span({
385
+ class: "float-end sc-error-indicator",
386
+ style: { display: "none", color: "#ff0033" },
387
+ }, i({ class: "fas fa-exclamation-triangle" }))), segment.tabContents && // TODO remove all calls to this, use tab in content instead
388
+ div({ class: "card-header" }, ul({ class: ["nav nav-tabs card-header-tabs", hints.tabClass] }, Object.keys(segment.tabContents).map((title, ix) => li({ class: "nav-item" }, a({
389
+ class: ["nav-link", ix === 0 && "active"],
390
+ href: `#tab-${title}`,
391
+ "data-bs-toggle": "tab",
392
+ role: "tab",
393
+ }, title))))) +
394
+ div({
395
+ class: [
396
+ "card-body",
397
+ segment.bodyClass,
398
+ segment.noPadding && "p-0",
399
+ ],
400
+ }, div({ class: "tab-content", id: "myTabContent" }, Object.entries(segment.tabContents).map(([title, contents], ix) => div({
401
+ class: ["tab-pane", ix == 0 && "show active"],
402
+ id: `tab-${title}`,
403
+ }, contents)))), segment.contents &&
404
+ (segment.contents.type === "tabs" &&
405
+ segment.contents.tabsStyle !== "Value switch"
406
+ ? renderTabs({
407
+ tabClass: "card-header-tabs",
408
+ headerWrapperClass: "card-header",
409
+ contentWrapperClass: [
410
+ "card-body",
411
+ segment.bodyClass,
412
+ segment.noPadding && "p-0",
413
+ ],
414
+ ...segment.contents,
415
+ }, go, segment.serverRendered
416
+ ? req?.query?.[segment.tabId || "_tab"]
417
+ : undefined, hints)
418
+ : div({
419
+ class: [
420
+ "card-body",
421
+ segment.bodyClass,
422
+ segment.noPadding && "p-0",
423
+ ],
424
+ style: {
425
+ ...(bgType === "Image" &&
426
+ bgFileId &&
427
+ imageLocation === "Body"
428
+ ? {
429
+ backgroundImage: `url('/files/serve/${bgFileId}')`,
430
+ backgroundSize: imageSize === "repeat"
431
+ ? undefined
432
+ : imageSize || "contain",
433
+ backgroundRepeat: imageSize === "repeat" ? imageSize : "no-repeat",
434
+ }
435
+ : {}),
436
+ },
437
+ }, go(segment.contents))), (segment.hasFooter ||
438
+ (segment.footer && segment.hasFooter !== false)) &&
439
+ div({ class: "card-footer" }, go(segment.footer))));
388
440
  }
389
441
  if (segment.type === "tabs") {
390
442
  return wrap(segment, isTop, ix, renderTabs(segment, go, segment.serverRendered
@@ -463,57 +515,63 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
463
515
  .map(([k, v]) => `${k}(${v})`)
464
516
  .join(" ")
465
517
  : "";
466
- return wrap(segment, isTop, ix, genericElement(htmlElement || "div", {
467
- class: [
468
- customClass || false,
469
- hAlign && `text-${to_bs5(hAlign)}`,
470
- vAlign === "middle" && "d-flex align-items-center",
471
- vAlign === "bottom" && "d-flex align-items-end",
472
- vAlign === "middle" &&
473
- hAlign === "center" &&
474
- "justify-content-center",
475
- displayClass,
476
- url && "with-link",
477
- hoverColor && `hover-${hoverColor}`,
478
- fullPageWidth && "full-page-width",
479
- ],
480
- id: customId || undefined,
481
- onclick: segment.url
482
- ? isWeb
483
- ? segment.url?.startsWith?.("javascript:")
484
- ? text_attr(segment.url.replace("javascript:", ""))
485
- : `location.href='${segment.url}'`
486
- : `execLink('${segment.url}')`
487
- : false,
488
- "data-animate": animateName && animateName !== "None" ? animateName : undefined,
489
- "data-animate-delay": animateDelay || undefined,
490
- "data-animate-initial-hide": animateInitialHide || undefined,
491
- "data-animate-duration": animateDuration || undefined,
492
- style: `${flexStyles}${ppCustomCSS(customCSS || "")}${sizeProp("minHeight", "min-height")}${sizeProp("height", "height")}${sizeProp("width", "width")}${sizeProp("widthPct", "width", "%")}${legacyBorder}${sizeProp("borderRadius", "border-radius")}${ppBox("padding")}${ppBox("margin")}${overflow && overflow !== "visible"
493
- ? ` overflow: ${overflow};`
494
- : ""} ${hasImgBg && !useImgTagAsBg
495
- ? ` ${isWeb
496
- ? `background-image: url('/files/serve/${bgFileId}');`
497
- : ""} background-size: ${imageSize === "repeat" ? "auto" : imageSize || "contain"}; background-repeat: ${imageSize === "repeat" ? "repeat" : "no-repeat"};`
498
- : ""} ${renderBg && bgType === "Color"
499
- ? `background-color: ${bgColor};`
500
- : ""} ${renderBg && bgType === "Gradient"
501
- ? `background-image: linear-gradient(${gradDirection || 0}deg, ${gradStartColor}, ${gradEndColor});`
502
- : ""} ${setTextColor ? `color: ${textColor};` : ""}${stransform}${showIfFormulaInputs ? ` display: none;` : ``}`,
503
- ...(showIfFormulaInputs
504
- ? {
505
- "data-show-if": encodeURIComponent(`showIfFormulaInputs(e, '${showIfFormulaInputs.replaceAll("'", "\\'")}')`),
506
- }
507
- : {}),
508
- ...(showIfFormulaJoinFields
509
- ? {
510
- "data-show-if-joinfields": encodeURIComponent(JSON.stringify(showIfFormulaJoinFields)),
511
- }
512
- : {}),
513
- ...(!isWeb && hasImgBg && !useImgTagAsBg
514
- ? { "mobile-bg-img-path": bgFileId }
515
- : {}),
516
- }, hasImgBg && useImgTagAsBg && image, go(segment.contents)));
518
+ const containerSize = responsiveSizeStyle(segment, {
519
+ width: segment.width ? `${segment.width}${segment.widthUnit || "px"}` : undefined,
520
+ height: segment.height ? `${segment.height}${segment.heightUnit || "px"}` : undefined,
521
+ });
522
+ return wrap(segment, isTop, ix, containerSize.styleTag +
523
+ genericElement(htmlElement || "div", {
524
+ class: [
525
+ customClass || false,
526
+ hAlign && `text-${to_bs5(hAlign)}`,
527
+ vAlign === "middle" && "d-flex align-items-center",
528
+ vAlign === "bottom" && "d-flex align-items-end",
529
+ vAlign === "middle" &&
530
+ hAlign === "center" &&
531
+ "justify-content-center",
532
+ displayClass,
533
+ url && "with-link",
534
+ hoverColor && `hover-${hoverColor}`,
535
+ fullPageWidth && "full-page-width",
536
+ containerSize.className,
537
+ ],
538
+ id: customId || undefined,
539
+ onclick: segment.url
540
+ ? isWeb
541
+ ? segment.url?.startsWith?.("javascript:")
542
+ ? text_attr(segment.url.replace("javascript:", ""))
543
+ : `location.href='${segment.url}'`
544
+ : `execLink('${segment.url}')`
545
+ : false,
546
+ "data-animate": animateName && animateName !== "None" ? animateName : undefined,
547
+ "data-animate-delay": animateDelay || undefined,
548
+ "data-animate-initial-hide": animateInitialHide || undefined,
549
+ "data-animate-duration": animateDuration || undefined,
550
+ style: `${flexStyles}${ppCustomCSS(customCSS || "")}${sizeProp("minHeight", "min-height")}${sizeProp("height", "height")}${sizeProp("width", "width")}${sizeProp("widthPct", "width", "%")}${legacyBorder}${sizeProp("borderRadius", "border-radius")}${ppBox("padding")}${ppBox("margin")}${overflow && overflow !== "visible"
551
+ ? ` overflow: ${overflow};`
552
+ : ""} ${hasImgBg && !useImgTagAsBg
553
+ ? ` ${isWeb
554
+ ? `background-image: url('/files/serve/${bgFileId}');`
555
+ : ""} background-size: ${imageSize === "repeat" ? "auto" : imageSize || "contain"}; background-repeat: ${imageSize === "repeat" ? "repeat" : "no-repeat"};`
556
+ : ""} ${renderBg && bgType === "Color"
557
+ ? `background-color: ${bgColor};`
558
+ : ""} ${renderBg && bgType === "Gradient"
559
+ ? `background-image: linear-gradient(${gradDirection || 0}deg, ${gradStartColor}, ${gradEndColor});`
560
+ : ""} ${setTextColor ? `color: ${textColor};` : ""}${stransform}${showIfFormulaInputs ? ` display: none;` : ``}`,
561
+ ...(showIfFormulaInputs
562
+ ? {
563
+ "data-show-if": encodeURIComponent(`showIfFormulaInputs(e, '${showIfFormulaInputs.replaceAll("'", "\\'")}')`),
564
+ }
565
+ : {}),
566
+ ...(showIfFormulaJoinFields
567
+ ? {
568
+ "data-show-if-joinfields": encodeURIComponent(JSON.stringify(showIfFormulaJoinFields)),
569
+ }
570
+ : {}),
571
+ ...(!isWeb && hasImgBg && !useImgTagAsBg
572
+ ? { "mobile-bg-img-path": bgFileId }
573
+ : {}),
574
+ }, hasImgBg && useImgTagAsBg && image, go(segment.contents)));
517
575
  }
518
576
  if (segment.type === "line_break") {
519
577
  if (segment.hr)
@@ -539,6 +597,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
539
597
  .join("");
540
598
  }
541
599
  else if (segment.besides) {
600
+ const colsSize = responsiveSizeStyle(segment);
542
601
  const defwidth = Math.round(12 / segment.besides.length);
543
602
  //legacy, for empty (null) in the columns
544
603
  const isOneCard = (segs) => segs.length === 1 && segs[0].type === "card";
@@ -551,7 +610,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
551
610
  if (cardDeck) {
552
611
  const sameWidths = !segment.widths ||
553
612
  segment.widths.every((w) => w === defwidth);
554
- markup = div({
613
+ markup = colsSize.styleTag + div({
555
614
  class: [
556
615
  "row",
557
616
  segment.class,
@@ -564,6 +623,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
564
623
  segment.gy !== null &&
565
624
  `gy-${segment.gy}`,
566
625
  !segment.style?.["margin-bottom"] && `mb-3`,
626
+ colsSize.className,
567
627
  ],
568
628
  style: segment.style,
569
629
  }, segment.besides.map((t, ixb) => {
@@ -587,7 +647,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
587
647
  }));
588
648
  }
589
649
  else
590
- markup = div({
650
+ markup = colsSize.styleTag + div({
591
651
  class: [
592
652
  "row",
593
653
  segment.class,
@@ -598,6 +658,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
598
658
  typeof segment.gy !== "undefined" &&
599
659
  segment.gy !== null &&
600
660
  `gy-${segment.gy}`,
661
+ colsSize.className,
601
662
  ],
602
663
  style: segment.style,
603
664
  }, segment.besides.map((t, ixb) => div({
@@ -607,7 +668,22 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
607
668
  ? segment.breakpoint + "-"
608
669
  : segment.breakpoints && segment.breakpoints[ixb]
609
670
  ? segment.breakpoints[ixb] + "-"
610
- : ""}${segment.widths ? segment.widths[ixb] : defwidth}${segment.aligns ? " text-" + segment.aligns[ixb] : ""}${segment.vAligns
671
+ : ""}${segment.widths ? segment.widths[ixb] : defwidth}${(() => {
672
+ const desktop = segment.aligns?.[ixb];
673
+ const tablet = segment.tabletAligns?.[ixb];
674
+ const mobile = segment.mobileAligns?.[ixb];
675
+ if (!mobile && !tablet)
676
+ return desktop ? " text-" + desktop : "";
677
+ let cls = "";
678
+ const base = mobile || desktop;
679
+ if (base)
680
+ cls += " text-" + base;
681
+ if (tablet && tablet !== base)
682
+ cls += " text-md-" + tablet;
683
+ if (desktop && desktop !== (tablet || base))
684
+ cls += " text-lg-" + desktop;
685
+ return cls;
686
+ })()}${segment.vAligns
611
687
  ? " align-items-" + segment.vAligns[ixb]
612
688
  : ""}${segment.colClasses?.[ixb]
613
689
  ? " " + segment.colClasses[ixb]
@@ -618,6 +694,10 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner, req, hints = {}
618
694
  ? wrap({ ...segment, customClass: null }, isTop, ix, markup)
619
695
  : markup;
620
696
  }
697
+ else if (segment.type === "prompt") {
698
+ // Prompt segments are builder-only placeholders, skip rendering
699
+ return "";
700
+ }
621
701
  else
622
702
  throw new Error("unknown layout segment" + JSON.stringify(segment));
623
703
  }