@bendyline/squisq-video-react 2.5.11 → 2.5.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.
@@ -21,6 +21,38 @@ function formatRealtimeMultiplier(processingFps, outputFps) {
21
21
  function resolveVideoSaveActionLabel(format, formatter) {
22
22
  return formatter?.(format) ?? `Save ${format.toUpperCase()} to Downloads`;
23
23
  }
24
+ var VIDEO_PRESETS = [
25
+ {
26
+ id: "presentation",
27
+ label: "Presentation",
28
+ description: "Landscape video with smooth motion for presenting and sharing.",
29
+ quality: "normal",
30
+ fps: 30,
31
+ orientation: "landscape",
32
+ captionMode: "off",
33
+ animationsEnabled: true
34
+ },
35
+ {
36
+ id: "social",
37
+ label: "Social portrait",
38
+ description: "Vertical video with large captions for phone viewing.",
39
+ quality: "normal",
40
+ fps: 30,
41
+ orientation: "portrait",
42
+ captionMode: "social",
43
+ animationsEnabled: true
44
+ },
45
+ {
46
+ id: "small",
47
+ label: "Small file",
48
+ description: "Lower quality and less motion for a smaller file and faster export.",
49
+ quality: "draft",
50
+ fps: 15,
51
+ orientation: "landscape",
52
+ captionMode: "off",
53
+ animationsEnabled: false
54
+ }
55
+ ];
24
56
  var FRAME_PREVIEW_INTERVAL = 15;
25
57
  var FRAME_PREVIEW_WIDTH = 480;
26
58
  var FRAME_PREVIEW_HEIGHT = 270;
@@ -85,9 +117,13 @@ var overlayStyle = {
85
117
  var modalStyle = {
86
118
  position: "relative",
87
119
  borderRadius: 0,
88
- padding: "24px 28px",
89
- minWidth: 380,
90
- maxWidth: 480,
120
+ boxSizing: "border-box",
121
+ padding: "20px clamp(12px, 4vw, 28px)",
122
+ width: "min(480px, calc(100vw - 24px))",
123
+ minWidth: 0,
124
+ maxHeight: "calc(100dvh - 24px)",
125
+ overflowY: "auto",
126
+ overscrollBehavior: "contain",
91
127
  boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
92
128
  fontFamily: "system-ui, -apple-system, sans-serif"
93
129
  };
@@ -118,7 +154,9 @@ var labelStyle = {
118
154
  marginBottom: 4
119
155
  };
120
156
  var selectStyle = {
157
+ boxSizing: "border-box",
121
158
  width: "100%",
159
+ minWidth: 0,
122
160
  padding: "6px 8px",
123
161
  fontSize: 13,
124
162
  fontFamily: "inherit",
@@ -150,6 +188,7 @@ var progressBarOuterStyle = {
150
188
  };
151
189
  var footerStyle = {
152
190
  display: "flex",
191
+ flexWrap: "wrap",
153
192
  justifyContent: "flex-end",
154
193
  gap: 8,
155
194
  marginTop: 20
@@ -187,6 +226,18 @@ function VideoExportModal({
187
226
  defaultConfig?.animationsEnabled ?? initialOutputFormat === "mp4"
188
227
  );
189
228
  const [includeAudio, setIncludeAudio] = useState(defaultConfig?.audioPolicy !== "omit");
229
+ const preset = VIDEO_PRESETS.find(
230
+ (entry) => entry.quality === quality && entry.fps === fps && entry.orientation === orientation && entry.captionMode === captionMode && entry.animationsEnabled === animationsEnabled
231
+ );
232
+ const applyPreset = (id) => {
233
+ const next = VIDEO_PRESETS.find((entry) => entry.id === id);
234
+ if (!next) return;
235
+ setQuality(next.quality);
236
+ setFps(next.fps);
237
+ setOrientation(next.orientation);
238
+ setCaptionMode(next.captionMode);
239
+ setAnimationsEnabled(next.animationsEnabled);
240
+ };
190
241
  const palette = {
191
242
  ...VIDEO_EXPORT_PALETTES[colorScheme],
192
243
  ...uiPalette
@@ -406,127 +457,162 @@ function VideoExportModal({
406
457
  }
407
458
  )
408
459
  ] }),
409
- /* @__PURE__ */ jsxs("div", { children: [
410
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Quality" }),
411
- /* @__PURE__ */ jsxs(
412
- "select",
413
- {
414
- "aria-label": "Quality",
415
- style: themedSelectStyle,
416
- value: quality,
417
- onChange: (e) => setQuality(e.target.value),
418
- children: [
419
- /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft \u2014 fast, lower quality" }),
420
- /* @__PURE__ */ jsx("option", { value: "normal", children: "Normal \u2014 balanced" }),
421
- /* @__PURE__ */ jsx("option", { value: "high", children: "High \u2014 best quality, slower" })
422
- ]
423
- }
424
- )
425
- ] }),
426
- /* @__PURE__ */ jsxs("div", { children: [
427
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Frame Rate" }),
428
- /* @__PURE__ */ jsxs(
429
- "select",
430
- {
431
- "aria-label": "Frame Rate",
432
- style: themedSelectStyle,
433
- value: fps,
434
- onChange: (e) => setFps(Number(e.target.value)),
435
- children: [
436
- /* @__PURE__ */ jsx("option", { value: 10, children: "10 fps \u2014 recommended for GIF" }),
437
- /* @__PURE__ */ jsx("option", { value: 15, children: "15 fps \u2014 fast export" }),
438
- /* @__PURE__ */ jsx("option", { value: 24, children: "24 fps \u2014 cinematic" }),
439
- /* @__PURE__ */ jsx("option", { value: 30, children: "30 fps \u2014 smooth" })
440
- ]
441
- }
442
- )
443
- ] }),
444
- /* @__PURE__ */ jsxs("div", { children: [
445
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Orientation" }),
460
+ outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
461
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Preset" }),
446
462
  /* @__PURE__ */ jsxs(
447
463
  "select",
448
464
  {
449
- "aria-label": "Orientation",
465
+ "aria-label": "Preset",
450
466
  style: themedSelectStyle,
451
- value: orientation,
452
- onChange: (e) => setOrientation(e.target.value),
467
+ value: preset?.id ?? "custom",
468
+ onChange: (event) => applyPreset(event.target.value),
453
469
  children: [
454
- /* @__PURE__ */ jsxs("option", { value: "landscape", children: [
455
- "Landscape (",
456
- outputFormat === "gif" ? "960 \xD7 540" : "1920 \xD7 1080",
457
- ")"
458
- ] }),
459
- /* @__PURE__ */ jsxs("option", { value: "portrait", children: [
460
- "Portrait (",
461
- outputFormat === "gif" ? "540 \xD7 960" : "1080 \xD7 1920",
462
- ")"
463
- ] })
470
+ VIDEO_PRESETS.map((entry) => /* @__PURE__ */ jsx("option", { value: entry.id, children: entry.label }, entry.id)),
471
+ !preset && /* @__PURE__ */ jsx("option", { value: "custom", disabled: true, children: "Custom settings" })
464
472
  ]
465
473
  }
466
- )
474
+ ),
475
+ /* @__PURE__ */ jsx("p", { style: { fontSize: 13, color: palette.label, margin: "0 0 12px" }, children: preset?.description ?? "Your settings are kept in Advanced options." })
467
476
  ] }),
468
- /* @__PURE__ */ jsxs("div", { children: [
469
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Captions" }),
470
- /* @__PURE__ */ jsxs(
471
- "select",
472
- {
473
- "aria-label": "Captions",
474
- style: themedSelectStyle,
475
- value: captionMode,
476
- onChange: (e) => setCaptionMode(e.target.value),
477
- children: [
478
- /* @__PURE__ */ jsx("option", { value: "off", children: "None" }),
479
- /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard (top bar)" }),
480
- /* @__PURE__ */ jsx("option", { value: "social", children: "Social media (large words)" })
481
- ]
482
- }
483
- )
477
+ /* @__PURE__ */ jsxs("p", { style: { fontSize: 13, color: palette.label, margin: "0 0 12px" }, children: [
478
+ formatDuration(Math.ceil(doc.duration)),
479
+ " \xB7",
480
+ " ",
481
+ orientation === "portrait" ? "Portrait" : "Landscape",
482
+ " \xB7",
483
+ " ",
484
+ outputFormat === "gif" || !includeAudio ? "No audio" : "Document audio enabled"
484
485
  ] }),
485
- outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
486
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Audio" }),
487
- /* @__PURE__ */ jsxs(
488
- "label",
489
- {
490
- style: {
491
- display: "flex",
492
- alignItems: "center",
493
- gap: 8,
494
- marginBottom: 12,
495
- fontSize: 14,
496
- color: palette.text,
497
- cursor: "pointer"
498
- },
499
- children: [
500
- /* @__PURE__ */ jsx(
501
- "input",
502
- {
503
- type: "checkbox",
504
- "aria-label": "Include audio",
505
- checked: includeAudio,
506
- onChange: (e) => setIncludeAudio(e.target.checked)
507
- }
508
- ),
509
- "Include audio"
510
- ]
511
- }
512
- )
513
- ] }),
514
- /* @__PURE__ */ jsxs("div", { children: [
515
- /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Animations & transitions" }),
516
- /* @__PURE__ */ jsxs(
517
- "select",
486
+ /* @__PURE__ */ jsxs("details", { children: [
487
+ /* @__PURE__ */ jsx(
488
+ "summary",
518
489
  {
519
- "aria-label": "Animations and transitions",
520
- style: themedSelectStyle,
521
- value: animationsEnabled ? "enabled" : "disabled",
522
- onChange: (e) => setAnimationsEnabled(e.target.value === "enabled"),
523
- children: [
524
- /* @__PURE__ */ jsx("option", { value: "enabled", children: "Enabled" }),
525
- /* @__PURE__ */ jsx("option", { value: "disabled", children: "Disabled \u2014 smaller files" })
526
- ]
490
+ style: { cursor: "pointer", color: palette.label, fontSize: 14, marginBottom: 12 },
491
+ children: "Advanced options"
527
492
  }
528
493
  ),
529
- outputFormat === "gif" && animationsEnabled && /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "-6px 0 12px" }, children: "Disabling motion is recommended for much smaller GIFs." })
494
+ /* @__PURE__ */ jsxs("div", { children: [
495
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Quality" }),
496
+ /* @__PURE__ */ jsxs(
497
+ "select",
498
+ {
499
+ "aria-label": "Quality",
500
+ style: themedSelectStyle,
501
+ value: quality,
502
+ onChange: (e) => setQuality(e.target.value),
503
+ children: [
504
+ /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft \u2014 fast, lower quality" }),
505
+ /* @__PURE__ */ jsx("option", { value: "normal", children: "Normal \u2014 balanced" }),
506
+ /* @__PURE__ */ jsx("option", { value: "high", children: "High \u2014 best quality, slower" })
507
+ ]
508
+ }
509
+ )
510
+ ] }),
511
+ /* @__PURE__ */ jsxs("div", { children: [
512
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Frame Rate" }),
513
+ /* @__PURE__ */ jsxs(
514
+ "select",
515
+ {
516
+ "aria-label": "Frame Rate",
517
+ style: themedSelectStyle,
518
+ value: fps,
519
+ onChange: (e) => setFps(Number(e.target.value)),
520
+ children: [
521
+ /* @__PURE__ */ jsx("option", { value: 10, children: "10 fps \u2014 recommended for GIF" }),
522
+ /* @__PURE__ */ jsx("option", { value: 15, children: "15 fps \u2014 fast export" }),
523
+ /* @__PURE__ */ jsx("option", { value: 24, children: "24 fps \u2014 cinematic" }),
524
+ /* @__PURE__ */ jsx("option", { value: 30, children: "30 fps \u2014 smooth" })
525
+ ]
526
+ }
527
+ )
528
+ ] }),
529
+ /* @__PURE__ */ jsxs("div", { children: [
530
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Orientation" }),
531
+ /* @__PURE__ */ jsxs(
532
+ "select",
533
+ {
534
+ "aria-label": "Orientation",
535
+ style: themedSelectStyle,
536
+ value: orientation,
537
+ onChange: (e) => setOrientation(e.target.value),
538
+ children: [
539
+ /* @__PURE__ */ jsxs("option", { value: "landscape", children: [
540
+ "Landscape (",
541
+ outputFormat === "gif" ? "960 \xD7 540" : "1920 \xD7 1080",
542
+ ")"
543
+ ] }),
544
+ /* @__PURE__ */ jsxs("option", { value: "portrait", children: [
545
+ "Portrait (",
546
+ outputFormat === "gif" ? "540 \xD7 960" : "1080 \xD7 1920",
547
+ ")"
548
+ ] })
549
+ ]
550
+ }
551
+ )
552
+ ] }),
553
+ /* @__PURE__ */ jsxs("div", { children: [
554
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Captions" }),
555
+ /* @__PURE__ */ jsxs(
556
+ "select",
557
+ {
558
+ "aria-label": "Captions",
559
+ style: themedSelectStyle,
560
+ value: captionMode,
561
+ onChange: (e) => setCaptionMode(e.target.value),
562
+ children: [
563
+ /* @__PURE__ */ jsx("option", { value: "off", children: "None" }),
564
+ /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard (top bar)" }),
565
+ /* @__PURE__ */ jsx("option", { value: "social", children: "Social media (large words)" })
566
+ ]
567
+ }
568
+ )
569
+ ] }),
570
+ outputFormat === "mp4" && /* @__PURE__ */ jsxs("div", { children: [
571
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Audio" }),
572
+ /* @__PURE__ */ jsxs(
573
+ "label",
574
+ {
575
+ style: {
576
+ display: "flex",
577
+ alignItems: "center",
578
+ gap: 8,
579
+ marginBottom: 12,
580
+ fontSize: 14,
581
+ color: palette.text,
582
+ cursor: "pointer"
583
+ },
584
+ children: [
585
+ /* @__PURE__ */ jsx(
586
+ "input",
587
+ {
588
+ type: "checkbox",
589
+ "aria-label": "Include audio",
590
+ checked: includeAudio,
591
+ onChange: (e) => setIncludeAudio(e.target.checked)
592
+ }
593
+ ),
594
+ "Include audio"
595
+ ]
596
+ }
597
+ )
598
+ ] }),
599
+ /* @__PURE__ */ jsxs("div", { children: [
600
+ /* @__PURE__ */ jsx("label", { style: themedLabelStyle, children: "Animations & transitions" }),
601
+ /* @__PURE__ */ jsxs(
602
+ "select",
603
+ {
604
+ "aria-label": "Animations and transitions",
605
+ style: themedSelectStyle,
606
+ value: animationsEnabled ? "enabled" : "disabled",
607
+ onChange: (e) => setAnimationsEnabled(e.target.value === "enabled"),
608
+ children: [
609
+ /* @__PURE__ */ jsx("option", { value: "enabled", children: "Enabled" }),
610
+ /* @__PURE__ */ jsx("option", { value: "disabled", children: "Disabled \u2014 smaller files" })
611
+ ]
612
+ }
613
+ ),
614
+ outputFormat === "gif" && animationsEnabled && /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "-6px 0 12px" }, children: "Disabling motion is recommended for much smaller GIFs." })
615
+ ] })
530
616
  ] }),
531
617
  preparingEdits && /* @__PURE__ */ jsx(
532
618
  "p",
@@ -650,6 +736,32 @@ function VideoExportModal({
650
736
  "Video only",
651
737
  audioSkippedReason ? ` \u2014 ${audioSkippedReason}` : ""
652
738
  ] }),
739
+ downloadUrl && /* @__PURE__ */ jsxs("figure", { style: { margin: "12px 0" }, children: [
740
+ completedOutputFormat === "gif" ? /* @__PURE__ */ jsx(
741
+ "img",
742
+ {
743
+ src: downloadUrl,
744
+ alt: "Exported animation preview",
745
+ style: {
746
+ display: "block",
747
+ width: "100%",
748
+ maxHeight: 240,
749
+ objectFit: "contain"
750
+ }
751
+ }
752
+ ) : /* @__PURE__ */ jsx(
753
+ "video",
754
+ {
755
+ src: downloadUrl,
756
+ controls: true,
757
+ playsInline: true,
758
+ preload: "metadata",
759
+ "aria-label": "Exported video preview",
760
+ style: { display: "block", width: "100%", maxHeight: 240 }
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsx("figcaption", { style: { fontSize: 12, color: palette.label, marginTop: 6 }, children: "Preview your export before saving." })
764
+ ] }),
653
765
  saveOptions && /* @__PURE__ */ jsx("fieldset", { disabled: saving, style: { border: 0, padding: 0, margin: "12px 0 0" }, children: saveOptions }),
654
766
  /* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
655
767
  /* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Close" }),
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "../chunk-2MBRKTDE.js";
4
+ } from "../chunk-V4HVNA2E.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "./chunk-2MBRKTDE.js";
4
+ } from "./chunk-V4HVNA2E.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video-react",
3
- "version": "2.5.11",
3
+ "version": "2.5.13",
4
4
  "description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -77,9 +77,9 @@
77
77
  "react-dom": "^18.0.0 || ^19.0.0"
78
78
  },
79
79
  "dependencies": {
80
- "@bendyline/squisq": "2.13.1",
81
- "@bendyline/squisq-video": "2.4.7",
82
- "@bendyline/squisq-react": "2.12.10",
80
+ "@bendyline/squisq": "2.13.3",
81
+ "@bendyline/squisq-video": "2.4.9",
82
+ "@bendyline/squisq-react": "2.12.12",
83
83
  "@ffmpeg/core": "0.12.10",
84
84
  "@ffmpeg/ffmpeg": "0.12.15",
85
85
  "@ffmpeg/util": "0.12.2",