@bendyline/squisq-editor-react 2.11.15 → 2.11.16

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/README.md CHANGED
@@ -69,7 +69,15 @@ and block-at-a-time / timeline editing primitives (`useBlockNavigator`,
69
69
  (`versioningPrunePolicy`, default keep-last-50); a Version History panel
70
70
  appears in the toolbar.
71
71
  - **Recording** — with a `mediaProvider` wired, a record button appears in the
72
- toolbar (`allowRecording`, default `true`). The recorder's Advanced device
72
+ toolbar (`allowRecording`, default `true`). Screen capture exposes **What to
73
+ record** above Advanced settings: a surface preference, a screen/window picker
74
+ action, and whole-surface or coordinate-region capture. Coordinates are pixels
75
+ relative to the chosen surface's top-left; the live preview and encoded video
76
+ use the same crop, leaving audio unchanged. Invalid or out-of-bounds regions
77
+ fail visibly and release capture. Region recording uses a canvas at 30 fps;
78
+ shrinking a window past the region or stopping screen sharing ends the take.
79
+ The browser or host still owns the source picker and available surface types.
80
+ The recorder's Advanced device
73
81
  settings expando discovers microphones/cameras, shows only capture
74
82
  constraints advertised by the browser, separates camera and screen
75
83
  constraints for dual recordings, and exposes MediaRecorder codec, bitrate,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RecorderModal
3
- } from "./chunk-BJ3TXAIQ.js";
3
+ } from "./chunk-4D7QMRTZ.js";
4
4
 
5
5
  // src/recorder/RecorderButton.tsx
6
6
  import { useCallback, useState } from "react";
@@ -21,6 +21,114 @@ import {
21
21
  useStreamPreview
22
22
  } from "./chunk-34FRFDQ6.js";
23
23
 
24
+ // src/recorder/sources/screenRegion.ts
25
+ function validateScreenCaptureRegion(region, width, height) {
26
+ const { x, y, width: w, height: h } = region;
27
+ if (![x, y, w, h].every((value) => Number.isSafeInteger(value) && value >= 0 && value <= 16384) || w === 0 || h === 0 || w * h > 16777216) {
28
+ throw new Error(
29
+ "Enter a valid recording region: non-negative left/top and positive whole-pixel dimensions (up to 16 megapixels)."
30
+ );
31
+ }
32
+ if (width !== void 0 && x + w > width || height !== void 0 && y + h > height) {
33
+ throw new Error(
34
+ `The recording region is outside the selected surface (${width} \xD7 ${height} pixels). Adjust the coordinates or choose another surface.`
35
+ );
36
+ }
37
+ }
38
+ async function cropScreenStream(source, region) {
39
+ validateScreenCaptureRegion(region);
40
+ const [sourceTrack] = source.getVideoTracks();
41
+ if (!sourceTrack) throw new Error("The selected surface has no video track.");
42
+ const video = document.createElement("video");
43
+ video.muted = true;
44
+ video.playsInline = true;
45
+ const canvas = document.createElement("canvas");
46
+ const context = canvas.getContext("2d");
47
+ if (!context || typeof canvas.captureStream !== "function")
48
+ throw new Error("Recording a region is not supported in this browser.");
49
+ canvas.width = region.width;
50
+ canvas.height = region.height;
51
+ let timer;
52
+ let output;
53
+ let disposed = false;
54
+ const dispose = () => {
55
+ if (disposed) return;
56
+ disposed = true;
57
+ clearInterval(timer);
58
+ sourceTrack.removeEventListener("ended", ended);
59
+ video.pause();
60
+ video.srcObject = null;
61
+ output?.getVideoTracks().forEach((track) => track.stop());
62
+ source.getTracks().forEach((track) => track.stop());
63
+ };
64
+ const ended = () => {
65
+ const track = output?.getVideoTracks()[0];
66
+ dispose();
67
+ track?.dispatchEvent(new Event("ended"));
68
+ };
69
+ try {
70
+ await new Promise((resolve, reject) => {
71
+ const cleanup = () => {
72
+ clearTimeout(timeout);
73
+ video.removeEventListener("loadeddata", ready);
74
+ video.removeEventListener("error", failed);
75
+ sourceTrack.removeEventListener("ended", failed);
76
+ };
77
+ const ready = () => {
78
+ cleanup();
79
+ resolve();
80
+ };
81
+ const failed = () => {
82
+ cleanup();
83
+ reject(new Error("Could not preview the selected screen. Choose it again."));
84
+ };
85
+ const timeout = setTimeout(failed, 1e4);
86
+ video.addEventListener("loadeddata", ready, { once: true });
87
+ video.addEventListener("error", failed, { once: true });
88
+ sourceTrack.addEventListener("ended", failed, { once: true });
89
+ try {
90
+ video.srcObject = source;
91
+ void video.play().catch(failed);
92
+ } catch {
93
+ failed();
94
+ }
95
+ });
96
+ if (sourceTrack.readyState === "ended")
97
+ throw new Error("Screen sharing ended. Choose a surface again.");
98
+ validateScreenCaptureRegion(region, video.videoWidth, video.videoHeight);
99
+ const draw = () => {
100
+ if (region.x + region.width > video.videoWidth || region.y + region.height > video.videoHeight || sourceTrack.readyState === "ended") {
101
+ ended();
102
+ return;
103
+ }
104
+ try {
105
+ context.drawImage(
106
+ video,
107
+ region.x,
108
+ region.y,
109
+ region.width,
110
+ region.height,
111
+ 0,
112
+ 0,
113
+ region.width,
114
+ region.height
115
+ );
116
+ } catch {
117
+ ended();
118
+ }
119
+ };
120
+ draw();
121
+ output = canvas.captureStream(30);
122
+ const stream = new MediaStream([...output.getVideoTracks(), ...source.getAudioTracks()]);
123
+ sourceTrack.addEventListener("ended", ended, { once: true });
124
+ timer = setInterval(draw, 1e3 / 30);
125
+ return { stream, dispose };
126
+ } catch (error) {
127
+ dispose();
128
+ throw error;
129
+ }
130
+ }
131
+
24
132
  // src/recorder/sources/screenStream.ts
25
133
  function mixAudioTracks(streams) {
26
134
  const sources = streams.map((s) => s.getAudioTracks()).flat().filter((t) => t.readyState === "live");
@@ -38,6 +146,25 @@ function mixAudioTracks(streams) {
38
146
  return { track: mixed, context: ctx };
39
147
  }
40
148
  async function requestScreenStream(options) {
149
+ if (options?.crop) validateScreenCaptureRegion(options.crop);
150
+ const handle = await requestFullScreenStream(options);
151
+ if (!options?.crop) return handle;
152
+ try {
153
+ const cropped = await cropScreenStream(handle.stream, options.crop);
154
+ return {
155
+ stream: cropped.stream,
156
+ dispose: () => {
157
+ cropped.dispose();
158
+ handle.dispose();
159
+ }
160
+ };
161
+ } catch (error) {
162
+ handle.stream.getTracks().forEach((track) => track.stop());
163
+ handle.dispose();
164
+ throw error;
165
+ }
166
+ }
167
+ async function requestFullScreenStream(options) {
41
168
  if (!supportsDisplayMedia()) {
42
169
  throw new Error("navigator.mediaDevices.getDisplayMedia is not available in this environment.");
43
170
  }
@@ -198,6 +325,7 @@ async function acquireStream(source, opts) {
198
325
  case "screen+mic": {
199
326
  const handle = await requestScreenStream({
200
327
  video: opts.screenVideoConstraints ?? opts.videoConstraints ?? true,
328
+ crop: opts.screenRegion,
201
329
  systemAudio: opts.systemAudio ?? false,
202
330
  systemAudioConstraints: opts.screenAudioConstraints,
203
331
  includeMicrophone: source === "screen+mic",
@@ -209,6 +337,7 @@ async function acquireStream(source, opts) {
209
337
  }
210
338
  async function acquireDualStreams(opts, isStale) {
211
339
  const screen = await requestScreenStream({
340
+ crop: opts.screenRegion,
212
341
  video: opts.screenVideoConstraints ?? opts.videoConstraints ?? true,
213
342
  systemAudio: opts.systemAudio ?? false,
214
343
  systemAudioConstraints: opts.screenAudioConstraints,
@@ -583,6 +712,15 @@ function useMediaRecorder(options = {}) {
583
712
  };
584
713
  recorderRef.current = recorder;
585
714
  disposeStreamRef.current = dispose;
715
+ if (source === "screen" || source === "screen+mic") {
716
+ const [screenTrack] = nextStream.getVideoTracks();
717
+ if (screenTrack)
718
+ screenTrack.onended = () => {
719
+ if (lifecycle !== lifecycleRef.current) return;
720
+ if (stateRef.current === "recording") void stopFnRef.current?.();
721
+ else if (stateRef.current === "ready") cancelFnRef.current?.();
722
+ };
723
+ }
586
724
  setStream(nextStream);
587
725
  setFormat(resolved);
588
726
  setBlob(null);
@@ -1253,24 +1391,6 @@ function RecorderDeviceSettingsPanel({
1253
1391
  /* @__PURE__ */ jsx("legend", { style: legendStyle, children: screenEnabled ? "Screen capture" : "System audio capture" }),
1254
1392
  /* @__PURE__ */ jsxs("div", { style: gridStyle, children: [
1255
1393
  screenEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
1256
- supports("displaySurface") && /* @__PURE__ */ jsxs("label", { style: { ...fieldStyle, gridColumn: "1 / -1" }, children: [
1257
- "Preferred surface",
1258
- /* @__PURE__ */ jsxs(
1259
- "select",
1260
- {
1261
- value: value.screen.displaySurface ?? "",
1262
- disabled,
1263
- style: inputStyle,
1264
- onChange: (event) => patchScreen({ displaySurface: event.target.value || void 0 }),
1265
- children: [
1266
- /* @__PURE__ */ jsx("option", { value: "", children: "Browser picker default" }),
1267
- /* @__PURE__ */ jsx("option", { value: "monitor", children: "Entire monitor" }),
1268
- /* @__PURE__ */ jsx("option", { value: "window", children: "Application window" }),
1269
- /* @__PURE__ */ jsx("option", { value: "browser", children: "Browser tab" })
1270
- ]
1271
- }
1272
- )
1273
- ] }),
1274
1394
  supports("cursor") && /* @__PURE__ */ jsxs("label", { style: fieldStyle, children: [
1275
1395
  "Mouse cursor",
1276
1396
  /* @__PURE__ */ jsxs(
@@ -2059,6 +2179,134 @@ import {
2059
2179
  recordSlideShown
2060
2180
  } from "@bendyline/squisq/narration";
2061
2181
 
2182
+ // src/recorder/RecorderScreenSettings.tsx
2183
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2184
+ var field = { display: "flex", flexDirection: "column", gap: 4, minWidth: 0 };
2185
+ var input = {
2186
+ width: "100%",
2187
+ boxSizing: "border-box",
2188
+ padding: "6px 8px",
2189
+ font: "inherit",
2190
+ color: "var(--squisq-recorder-text)",
2191
+ background: "var(--squisq-recorder-input)",
2192
+ border: "1px solid var(--squisq-recorder-border)"
2193
+ };
2194
+ function RecorderScreenSettings({
2195
+ value,
2196
+ onChange,
2197
+ disabled,
2198
+ onChoose,
2199
+ hasPreview
2200
+ }) {
2201
+ const crop = value.crop;
2202
+ return /* @__PURE__ */ jsxs4(
2203
+ "fieldset",
2204
+ {
2205
+ disabled,
2206
+ style: {
2207
+ margin: "0 0 12px",
2208
+ padding: 12,
2209
+ border: "1px solid var(--squisq-recorder-border)",
2210
+ fontSize: 13
2211
+ },
2212
+ children: [
2213
+ /* @__PURE__ */ jsx5("legend", { children: "What to record" }),
2214
+ /* @__PURE__ */ jsxs4(
2215
+ "div",
2216
+ {
2217
+ style: {
2218
+ display: "grid",
2219
+ gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
2220
+ gap: 10
2221
+ },
2222
+ children: [
2223
+ /* @__PURE__ */ jsxs4("label", { style: field, children: [
2224
+ "Preferred surface",
2225
+ /* @__PURE__ */ jsxs4(
2226
+ "select",
2227
+ {
2228
+ style: input,
2229
+ value: value.displaySurface ?? "",
2230
+ onChange: (event) => onChange({ ...value, displaySurface: event.target.value || void 0 }),
2231
+ children: [
2232
+ /* @__PURE__ */ jsx5("option", { value: "", children: "Any available surface" }),
2233
+ /* @__PURE__ */ jsx5("option", { value: "monitor", children: "Entire screen" }),
2234
+ /* @__PURE__ */ jsx5("option", { value: "window", children: "Application window" }),
2235
+ /* @__PURE__ */ jsx5("option", { value: "browser", children: "Browser tab (when available)" })
2236
+ ]
2237
+ }
2238
+ )
2239
+ ] }),
2240
+ /* @__PURE__ */ jsxs4("label", { style: field, children: [
2241
+ "Capture area",
2242
+ /* @__PURE__ */ jsxs4(
2243
+ "select",
2244
+ {
2245
+ style: input,
2246
+ value: value.crop ? "region" : "full",
2247
+ onChange: (event) => onChange({
2248
+ ...value,
2249
+ crop: event.target.value === "region" ? { x: 0, y: 0, width: 1280, height: 720 } : void 0
2250
+ }),
2251
+ children: [
2252
+ /* @__PURE__ */ jsx5("option", { value: "full", children: "Whole selected surface" }),
2253
+ /* @__PURE__ */ jsx5("option", { value: "region", children: "Region by coordinates" })
2254
+ ]
2255
+ }
2256
+ )
2257
+ ] }),
2258
+ crop && /* @__PURE__ */ jsx5(
2259
+ "div",
2260
+ {
2261
+ style: {
2262
+ gridColumn: "1 / -1",
2263
+ display: "grid",
2264
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
2265
+ gap: 10
2266
+ },
2267
+ children: ["x", "y", "width", "height"].map((key) => /* @__PURE__ */ jsxs4("label", { style: field, children: [
2268
+ {
2269
+ x: "Left (px)",
2270
+ y: "Top (px)",
2271
+ width: "Region width (px)",
2272
+ height: "Region height (px)"
2273
+ }[key],
2274
+ /* @__PURE__ */ jsx5(
2275
+ "input",
2276
+ {
2277
+ style: input,
2278
+ type: "number",
2279
+ min: key === "x" || key === "y" ? 0 : 1,
2280
+ max: 16384,
2281
+ step: 1,
2282
+ value: Number.isFinite(crop[key]) ? crop[key] : "",
2283
+ onChange: (event) => onChange({ ...value, crop: { ...crop, [key]: event.target.valueAsNumber } })
2284
+ }
2285
+ )
2286
+ ] }, key))
2287
+ }
2288
+ )
2289
+ ]
2290
+ }
2291
+ ),
2292
+ /* @__PURE__ */ jsxs4("p", { style: { margin: "8px 0 0", color: "var(--squisq-recorder-muted)" }, children: [
2293
+ "Choose a screen, window or tab in the picker, then check the preview. Available choices depend on your browser or app.",
2294
+ value.crop && " Coordinates start at the selected surface\u2019s top-left, in captured pixels. Only this region is saved; audio is unchanged."
2295
+ ] }),
2296
+ /* @__PURE__ */ jsx5(
2297
+ "button",
2298
+ {
2299
+ type: "button",
2300
+ style: { ...input, marginTop: 10, width: "auto", cursor: "pointer" },
2301
+ onClick: onChoose,
2302
+ children: hasPreview ? "Change screen or window" : "Choose screen or window"
2303
+ }
2304
+ )
2305
+ ]
2306
+ }
2307
+ );
2308
+ }
2309
+
2062
2310
  // src/modal/useModalDialog.ts
2063
2311
  import { useModalDialog } from "@bendyline/squisq-react";
2064
2312
 
@@ -2085,7 +2333,7 @@ function narrationCaptureSummary(withCamera) {
2085
2333
  }
2086
2334
 
2087
2335
  // src/recorder/RecorderModal.tsx
2088
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2336
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2089
2337
  var overlayStyle = {
2090
2338
  position: "fixed",
2091
2339
  inset: 0,
@@ -2494,6 +2742,7 @@ function RecorderModal({
2494
2742
  audioConstraints,
2495
2743
  videoConstraints: cameraConstraints,
2496
2744
  screenVideoConstraints: screenConstraints,
2745
+ screenRegion: deviceSettings.screen.crop,
2497
2746
  screenAudioConstraints,
2498
2747
  mimeType: (source === "mic" ? encoding.audioMimeType : encoding.videoMimeType).trim() || void 0,
2499
2748
  bitsPerSecond: recorderBitsPerSecond(encoding.bitsPerSecond),
@@ -3082,14 +3331,14 @@ function RecorderModal({
3082
3331
  };
3083
3332
  }
3084
3333
  };
3085
- return /* @__PURE__ */ jsx5(
3334
+ return /* @__PURE__ */ jsx6(
3086
3335
  "div",
3087
3336
  {
3088
3337
  ref: overlayRef,
3089
3338
  className: "squisq-editor-shell squisq-recorder-overlay",
3090
3339
  "data-theme": colorScheme,
3091
3340
  style: { ...overlayStyle, ...recorderThemeStyle(colorScheme) },
3092
- children: /* @__PURE__ */ jsxs4(
3341
+ children: /* @__PURE__ */ jsxs5(
3093
3342
  "div",
3094
3343
  {
3095
3344
  ref: dialogRef,
@@ -3108,15 +3357,15 @@ function RecorderModal({
3108
3357
  "aria-labelledby": headingId,
3109
3358
  tabIndex: -1,
3110
3359
  children: [
3111
- /* @__PURE__ */ jsx5("h2", { id: headingId, style: titleStyle, children: title }),
3112
- /* @__PURE__ */ jsxs4("div", { style: expanded ? bodyRowStyle : void 0, children: [
3113
- /* @__PURE__ */ jsxs4("div", { style: expanded ? leftColStyle : void 0, children: [
3114
- /* @__PURE__ */ jsx5("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs4(Fragment2, { children: [
3115
- groupIndex > 0 && /* @__PURE__ */ jsx5("div", { "aria-hidden": "true", style: groupDividerStyle }),
3116
- /* @__PURE__ */ jsx5("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3360
+ /* @__PURE__ */ jsx6("h2", { id: headingId, style: titleStyle, children: title }),
3361
+ /* @__PURE__ */ jsxs5("div", { style: expanded ? bodyRowStyle : void 0, children: [
3362
+ /* @__PURE__ */ jsxs5("div", { style: expanded ? leftColStyle : void 0, children: [
3363
+ /* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
3364
+ groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
3365
+ /* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3117
3366
  if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
3118
3367
  const props = narrationOn ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
3119
- return /* @__PURE__ */ jsx5(
3368
+ return /* @__PURE__ */ jsx6(
3120
3369
  "button",
3121
3370
  {
3122
3371
  type: "button",
@@ -3131,9 +3380,9 @@ function RecorderModal({
3131
3380
  );
3132
3381
  }) })
3133
3382
  ] }, group.label)) }),
3134
- /* @__PURE__ */ jsx5("p", { style: summaryStyle, children: narrationOn ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3135
- narrationAvailable && /* @__PURE__ */ jsxs4("label", { style: checkboxRowStyle, children: [
3136
- /* @__PURE__ */ jsx5(
3383
+ /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationOn ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3384
+ narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3385
+ /* @__PURE__ */ jsx6(
3137
3386
  "input",
3138
3387
  {
3139
3388
  type: "checkbox",
@@ -3146,8 +3395,8 @@ function RecorderModal({
3146
3395
  ),
3147
3396
  "Show narration mode"
3148
3397
  ] }),
3149
- slidesAvailable && /* @__PURE__ */ jsxs4("label", { style: checkboxRowStyle, children: [
3150
- /* @__PURE__ */ jsx5(
3398
+ slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3399
+ /* @__PURE__ */ jsx6(
3151
3400
  "input",
3152
3401
  {
3153
3402
  type: "checkbox",
@@ -3160,7 +3409,7 @@ function RecorderModal({
3160
3409
  ),
3161
3410
  "Show slides mode"
3162
3411
  ] }),
3163
- /* @__PURE__ */ jsx5(
3412
+ /* @__PURE__ */ jsx6(
3164
3413
  RecorderDeviceQuickPicks,
3165
3414
  {
3166
3415
  devices: mediaDevices,
@@ -3179,7 +3428,20 @@ function RecorderModal({
3179
3428
  disabled: deviceSettingsLocked
3180
3429
  }
3181
3430
  ),
3182
- /* @__PURE__ */ jsx5(
3431
+ !narrationOn && screenOn && /* @__PURE__ */ jsx6(
3432
+ RecorderScreenSettings,
3433
+ {
3434
+ value: deviceSettings.screen,
3435
+ onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
3436
+ disabled: deviceSettingsLocked,
3437
+ hasPreview: recorder.state === "ready",
3438
+ onChoose: () => {
3439
+ recorder.cancel();
3440
+ void handleRequest();
3441
+ }
3442
+ }
3443
+ ),
3444
+ /* @__PURE__ */ jsx6(
3183
3445
  RecorderDeviceSettingsPanel,
3184
3446
  {
3185
3447
  value: deviceSettings,
@@ -3194,12 +3456,12 @@ function RecorderModal({
3194
3456
  cameraStream: activeCameraStream
3195
3457
  }
3196
3458
  ),
3197
- !narrationOn && recorder.error && /* @__PURE__ */ jsx5("div", { style: errorStyle, children: recorder.error.message }),
3198
- !narrationOn && saveError && /* @__PURE__ */ jsxs4("div", { role: "alert", style: errorStyle, children: [
3459
+ !narrationOn && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
3460
+ !narrationOn && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
3199
3461
  saveError,
3200
- /* @__PURE__ */ jsx5("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3462
+ /* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3201
3463
  ] }),
3202
- /* @__PURE__ */ jsxs4("p", { style: summaryStyle, children: [
3464
+ /* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
3203
3465
  "Recording size:",
3204
3466
  " ",
3205
3467
  formatRecordingBytes(
@@ -3213,9 +3475,9 @@ function RecorderModal({
3213
3475
  " ",
3214
3476
  "total. Final encoding may add more data."
3215
3477
  ] }),
3216
- !narrationOn && recorder.limitReached && /* @__PURE__ */ jsx5("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3217
- narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx5("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3218
- narrationOn && narrationCameraStream && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5(
3478
+ !narrationOn && recorder.limitReached && /* @__PURE__ */ jsx6("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3479
+ narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3480
+ narrationOn && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
3219
3481
  "video",
3220
3482
  {
3221
3483
  ref: previewRef,
@@ -3225,10 +3487,10 @@ function RecorderModal({
3225
3487
  style: { width: "100%", height: "100%", objectFit: "contain" }
3226
3488
  }
3227
3489
  ) }),
3228
- narrationOn && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx5("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3229
- narrationOn && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3230
- narrationOn && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs4("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3231
- /* @__PURE__ */ jsx5(
3490
+ narrationOn && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3491
+ narrationOn && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3492
+ narrationOn && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3493
+ /* @__PURE__ */ jsx6(
3232
3494
  "div",
3233
3495
  {
3234
3496
  "aria-hidden": "true",
@@ -3238,11 +3500,11 @@ function RecorderModal({
3238
3500
  }
3239
3501
  }
3240
3502
  ),
3241
- /* @__PURE__ */ jsx5("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3503
+ /* @__PURE__ */ jsx6("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3242
3504
  ] }),
3243
- !narrationOn && !showPreview && /* @__PURE__ */ jsx5("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx5("span", { children: "Click Start Preview to start a recording." }) }),
3244
- !narrationOn && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3245
- /* @__PURE__ */ jsx5(
3505
+ !narrationOn && !showPreview && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: screenOn ? "Choose a screen or window to preview what will be recorded." : "Click Start preview to prepare a recording." }) }),
3506
+ !narrationOn && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3507
+ /* @__PURE__ */ jsx6(
3246
3508
  "video",
3247
3509
  {
3248
3510
  ref: previewRef,
@@ -3252,7 +3514,7 @@ function RecorderModal({
3252
3514
  style: { width: "100%", height: "100%", objectFit: "contain" }
3253
3515
  }
3254
3516
  ),
3255
- isDual && /* @__PURE__ */ jsx5(
3517
+ isDual && /* @__PURE__ */ jsx6(
3256
3518
  "video",
3257
3519
  {
3258
3520
  ref: cameraPreviewRef,
@@ -3273,12 +3535,12 @@ function RecorderModal({
3273
3535
  }
3274
3536
  )
3275
3537
  ] }),
3276
- !narrationOn && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx5("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3538
+ !narrationOn && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3277
3539
  "\u25CF Recording ",
3278
3540
  formatDurationMs(recorder.durationMs)
3279
- ] }) : /* @__PURE__ */ jsx5(Fragment3, { children: "Microphone ready" }) }),
3280
- !narrationOn && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3281
- /* @__PURE__ */ jsx5(
3541
+ ] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
3542
+ !narrationOn && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3543
+ /* @__PURE__ */ jsx6(
3282
3544
  "video",
3283
3545
  {
3284
3546
  src: playbackUrl,
@@ -3290,7 +3552,7 @@ function RecorderModal({
3290
3552
  style: { width: "100%", height: "100%", objectFit: "contain" }
3291
3553
  }
3292
3554
  ),
3293
- /* @__PURE__ */ jsxs4(
3555
+ /* @__PURE__ */ jsxs5(
3294
3556
  "div",
3295
3557
  {
3296
3558
  role: "timer",
@@ -3304,9 +3566,9 @@ function RecorderModal({
3304
3566
  }
3305
3567
  )
3306
3568
  ] }),
3307
- !narrationOn && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 12 }, children: [
3308
- /* @__PURE__ */ jsx5("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3309
- /* @__PURE__ */ jsx5(
3569
+ !narrationOn && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3570
+ /* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3571
+ /* @__PURE__ */ jsx6(
3310
3572
  "video",
3311
3573
  {
3312
3574
  src: cameraPlaybackUrl,
@@ -3322,14 +3584,14 @@ function RecorderModal({
3322
3584
  }
3323
3585
  )
3324
3586
  ] }),
3325
- !narrationOn && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: { marginBottom: 12 }, children: [
3326
- /* @__PURE__ */ jsxs4("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3587
+ !narrationOn && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3588
+ /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3327
3589
  "\u2713 Recorded ",
3328
3590
  formatDurationMs(recorder.durationMs)
3329
3591
  ] }),
3330
- /* @__PURE__ */ jsx5("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3592
+ /* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3331
3593
  ] }),
3332
- !narrationOn && canSave && playbackUrl && /* @__PURE__ */ jsxs4(
3594
+ !narrationOn && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
3333
3595
  "div",
3334
3596
  {
3335
3597
  style: {
@@ -3339,8 +3601,8 @@ function RecorderModal({
3339
3601
  marginBottom: 12
3340
3602
  },
3341
3603
  children: [
3342
- /* @__PURE__ */ jsx5("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3343
- isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx5(
3604
+ /* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3605
+ isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
3344
3606
  "a",
3345
3607
  {
3346
3608
  href: cameraPlaybackUrl,
@@ -3354,7 +3616,7 @@ function RecorderModal({
3354
3616
  children: "Download camera recording"
3355
3617
  }
3356
3618
  ),
3357
- timingDownloadUrl && /* @__PURE__ */ jsx5(
3619
+ timingDownloadUrl && /* @__PURE__ */ jsx6(
3358
3620
  "a",
3359
3621
  {
3360
3622
  href: timingDownloadUrl,
@@ -3366,9 +3628,9 @@ function RecorderModal({
3366
3628
  ]
3367
3629
  }
3368
3630
  ),
3369
- !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3370
- /* @__PURE__ */ jsx5("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3371
- /* @__PURE__ */ jsx5(
3631
+ !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3632
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3633
+ /* @__PURE__ */ jsx6(
3372
3634
  "textarea",
3373
3635
  {
3374
3636
  id: "recorder-source-text",
@@ -3380,8 +3642,8 @@ function RecorderModal({
3380
3642
  }
3381
3643
  )
3382
3644
  ] }),
3383
- /* @__PURE__ */ jsx5("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3384
- /* @__PURE__ */ jsx5(
3645
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3646
+ /* @__PURE__ */ jsx6(
3385
3647
  "input",
3386
3648
  {
3387
3649
  id: "recorder-basename",
@@ -3393,13 +3655,13 @@ function RecorderModal({
3393
3655
  disabled: narrationOn ? !narrationRecorderIdle : recorder.state === "recording"
3394
3656
  }
3395
3657
  ),
3396
- !narrationOn && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs4("div", { style: recordingStatusStyle, children: [
3658
+ !narrationOn && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
3397
3659
  "\u25CF Recording ",
3398
3660
  formatDurationMs(recorder.durationMs)
3399
3661
  ] }),
3400
- showTimingCheckbox && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3401
- /* @__PURE__ */ jsxs4("label", { style: checkboxRowStyle, children: [
3402
- /* @__PURE__ */ jsx5(
3662
+ showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3663
+ /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3664
+ /* @__PURE__ */ jsx6(
3403
3665
  "input",
3404
3666
  {
3405
3667
  type: "checkbox",
@@ -3411,10 +3673,10 @@ function RecorderModal({
3411
3673
  ),
3412
3674
  SLIDE_TIMING_CHECKBOX_LABEL
3413
3675
  ] }),
3414
- applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx5("p", { style: summaryStyle, children: slideCoverageWarning })
3676
+ applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
3415
3677
  ] }),
3416
- /* @__PURE__ */ jsxs4("div", { style: buttonRowStyle, children: [
3417
- /* @__PURE__ */ jsx5(
3678
+ /* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
3679
+ /* @__PURE__ */ jsx6(
3418
3680
  "button",
3419
3681
  {
3420
3682
  type: "button",
@@ -3424,8 +3686,8 @@ function RecorderModal({
3424
3686
  children: "Close"
3425
3687
  }
3426
3688
  ),
3427
- narrationOn && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3428
- narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx5(
3689
+ narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3690
+ narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
3429
3691
  "button",
3430
3692
  {
3431
3693
  type: "button",
@@ -3435,14 +3697,14 @@ function RecorderModal({
3435
3697
  children: narrationRequesting ? "Requesting\u2026" : "Start preview"
3436
3698
  }
3437
3699
  ),
3438
- narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs4("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3439
- /* @__PURE__ */ jsx5(
3700
+ narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3701
+ /* @__PURE__ */ jsx6(
3440
3702
  "span",
3441
3703
  {
3442
3704
  className: "squisq-recorder-record-dot",
3443
3705
  style: recordDotFrameStyle,
3444
3706
  "aria-hidden": "true",
3445
- children: /* @__PURE__ */ jsx5(
3707
+ children: /* @__PURE__ */ jsx6(
3446
3708
  "span",
3447
3709
  {
3448
3710
  className: "squisq-recorder-record-dot-center",
@@ -3453,7 +3715,7 @@ function RecorderModal({
3453
3715
  ),
3454
3716
  "Record"
3455
3717
  ] }),
3456
- (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx5(
3718
+ (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
3457
3719
  "button",
3458
3720
  {
3459
3721
  type: "button",
@@ -3462,11 +3724,11 @@ function RecorderModal({
3462
3724
  children: "Stop"
3463
3725
  }
3464
3726
  ),
3465
- stage.recorder.state === "processing" && /* @__PURE__ */ jsx5("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3466
- stage.recorder.state === "saving" && /* @__PURE__ */ jsx5("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3467
- stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3468
- /* @__PURE__ */ jsx5("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3469
- /* @__PURE__ */ jsx5(
3727
+ stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3728
+ stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3729
+ stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3730
+ /* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3731
+ /* @__PURE__ */ jsx6(
3470
3732
  "button",
3471
3733
  {
3472
3734
  type: "button",
@@ -3477,8 +3739,8 @@ function RecorderModal({
3477
3739
  )
3478
3740
  ] })
3479
3741
  ] }),
3480
- !narrationOn && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3481
- (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx5(
3742
+ !narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3743
+ (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
3482
3744
  "button",
3483
3745
  {
3484
3746
  type: "button",
@@ -3488,14 +3750,14 @@ function RecorderModal({
3488
3750
  children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3489
3751
  }
3490
3752
  ),
3491
- canRecord && /* @__PURE__ */ jsxs4("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3492
- /* @__PURE__ */ jsx5(
3753
+ canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3754
+ /* @__PURE__ */ jsx6(
3493
3755
  "span",
3494
3756
  {
3495
3757
  className: "squisq-recorder-record-dot",
3496
3758
  style: recordDotFrameStyle,
3497
3759
  "aria-hidden": "true",
3498
- children: /* @__PURE__ */ jsx5(
3760
+ children: /* @__PURE__ */ jsx6(
3499
3761
  "span",
3500
3762
  {
3501
3763
  className: "squisq-recorder-record-dot-center",
@@ -3506,9 +3768,9 @@ function RecorderModal({
3506
3768
  ),
3507
3769
  "Record"
3508
3770
  ] }),
3509
- canStop && /* @__PURE__ */ jsx5("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3510
- canSave && /* @__PURE__ */ jsxs4(Fragment3, { children: [
3511
- /* @__PURE__ */ jsx5(
3771
+ canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3772
+ canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3773
+ /* @__PURE__ */ jsx6(
3512
3774
  "button",
3513
3775
  {
3514
3776
  type: "button",
@@ -3518,7 +3780,7 @@ function RecorderModal({
3518
3780
  children: "Discard & re-record"
3519
3781
  }
3520
3782
  ),
3521
- /* @__PURE__ */ jsx5(
3783
+ /* @__PURE__ */ jsx6(
3522
3784
  "button",
3523
3785
  {
3524
3786
  type: "button",
@@ -3532,7 +3794,7 @@ function RecorderModal({
3532
3794
  ] })
3533
3795
  ] })
3534
3796
  ] }),
3535
- narrationOn && narration && /* @__PURE__ */ jsx5("div", { style: rightColStyle, children: /* @__PURE__ */ jsx5(
3797
+ narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3536
3798
  NarrationStage,
3537
3799
  {
3538
3800
  stage,
@@ -3544,7 +3806,7 @@ function RecorderModal({
3544
3806
  showReviewActions: false
3545
3807
  }
3546
3808
  ) }),
3547
- slidesOn && /* @__PURE__ */ jsx5("div", { style: rightColStyle, children: /* @__PURE__ */ jsx5(
3809
+ slidesOn && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3548
3810
  RecorderSlidesPanel,
3549
3811
  {
3550
3812
  slides: slideDeck,
@@ -3568,7 +3830,7 @@ function RecorderModal({
3568
3830
  // src/recorder/RecorderPanel.tsx
3569
3831
  import { useCallback as useCallback5, useState as useState4 } from "react";
3570
3832
  import { createPortal } from "react-dom";
3571
- import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3833
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3572
3834
  function RecorderPanel({
3573
3835
  mediaProvider,
3574
3836
  container = null,
@@ -3594,8 +3856,8 @@ function RecorderPanel({
3594
3856
  [controlledOpen, onOpenChange]
3595
3857
  );
3596
3858
  const handleClose = useCallback5(() => setOpen(false), [setOpen]);
3597
- return /* @__PURE__ */ jsxs5(Fragment4, { children: [
3598
- showTrigger && /* @__PURE__ */ jsx6(
3859
+ return /* @__PURE__ */ jsxs6(Fragment4, { children: [
3860
+ showTrigger && /* @__PURE__ */ jsx7(
3599
3861
  "button",
3600
3862
  {
3601
3863
  type: "button",
@@ -3604,11 +3866,11 @@ function RecorderPanel({
3604
3866
  "aria-label": tooltip,
3605
3867
  "aria-expanded": open,
3606
3868
  onClick: () => setOpen(!open),
3607
- children: /* @__PURE__ */ jsx6(Icon, { icon: "fa-solid fa-microphone" })
3869
+ children: /* @__PURE__ */ jsx7(Icon, { icon: "fa-solid fa-microphone" })
3608
3870
  }
3609
3871
  ),
3610
3872
  open && typeof document !== "undefined" && createPortal(
3611
- /* @__PURE__ */ jsx6(
3873
+ /* @__PURE__ */ jsx7(
3612
3874
  RecorderModal,
3613
3875
  {
3614
3876
  mediaProvider,
@@ -41,7 +41,7 @@ import {
41
41
  documentTitleFromFileName,
42
42
  resolveBlockVisual,
43
43
  useModalDialog
44
- } from "./chunk-BJ3TXAIQ.js";
44
+ } from "./chunk-4D7QMRTZ.js";
45
45
  import {
46
46
  Icon
47
47
  } from "./chunk-GS7QWYFT.js";
package/dist/index.d.ts CHANGED
@@ -26,9 +26,9 @@ import { FenceRendererMap } from '@bendyline/squisq/fence';
26
26
  import { TableViewState } from '@bendyline/squisq/table';
27
27
  import { EditJournal } from '@bendyline/squisq-grid-react';
28
28
  export { JsonEditor, JsonEditorProps } from './json-editor/index.js';
29
- export { C as CameraStreamOptions, D as DEFAULT_RECORDER_DEVICE_SETTINGS, M as MediaDeviceInventory, R as RecordedBookmark, a as RecordedMediaKind, b as RecorderAudioDeviceSettings, c as RecorderButton, d as RecorderButtonProps, e as RecorderCameraDeviceSettings, f as RecorderCameraSaveResult, g as RecorderColorScheme, h as RecorderConstraintMode, i as RecorderDeviceOption, j as RecorderDeviceQuickPicks, k as RecorderDeviceQuickPicksProps, l as RecorderDeviceSettings, m as RecorderDeviceSettingsPanel, n as RecorderDeviceSettingsPanelProps, o as RecorderEncodingSettings, p as RecorderModal, q as RecorderModalProps, r as RecorderNarrationOptions, s as RecorderPanel, t as RecorderPanelProps, u as RecorderSaveResult, v as RecorderScreenAudioSettings, w as RecorderScreenDeviceSettings, x as RecorderSlidesOptions, S as ScreenStreamHandle, y as ScreenStreamOptions, z as SlideTimingSaveResult, T as TimingJson, A as buildRecorderAudioConstraints, B as buildRecorderCameraConstraints, E as buildRecorderScreenAudioConstraints, F as buildRecorderScreenConstraints, G as buildTimingJson, H as encodeTimingJson, I as hasRecorderDeviceChoice, J as recordedMediaKind, K as recorderBitsPerSecond, L as recorderDeviceGroups, N as recorderDeviceOptions, O as requestCameraStream, P as requestMicStream, Q as requestScreenStream, U as timingPathFor, V as useMediaDevices, W as useStreamPreview } from './recorder-CC3VW9Z7.js';
30
- import { R as RecorderState } from './useNarrationStage-D3Sgcf0L.js';
31
- export { C as CanvasSink, a as CaptureKind, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, c as FloatingWindowHandle, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, g as NarrationStageHandle, h as NarrationTake, P as PrompterTransport, i as RecorderAudioBitrateMode, j as RecorderCameraLane, k as RecorderExtendedMediaOptions, l as RecorderSource, m as ResolvedFormat, T as TeleprompterController, n as TeleprompterPrefs, o as TeleprompterRecordingDeps, U as UseMediaRecorderOptions, p as UseMediaRecorderResult, q as UseNarrationStageOptions, r as buildFilename, s as createFloatingWindowManager, t as detectFloatTiers, u as getCaptureKind, v as resolveFormat, w as supportsDisplayMedia, x as supportsMediaRecorder, y as supportsUserMedia, z as useFloatingWindow, A as useMediaRecorder, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from './useNarrationStage-D3Sgcf0L.js';
29
+ export { C as CameraStreamOptions, D as DEFAULT_RECORDER_DEVICE_SETTINGS, M as MediaDeviceInventory, R as RecordedBookmark, a as RecordedMediaKind, b as RecorderAudioDeviceSettings, c as RecorderButton, d as RecorderButtonProps, e as RecorderCameraDeviceSettings, f as RecorderCameraSaveResult, g as RecorderColorScheme, h as RecorderConstraintMode, i as RecorderDeviceOption, j as RecorderDeviceQuickPicks, k as RecorderDeviceQuickPicksProps, l as RecorderDeviceSettings, m as RecorderDeviceSettingsPanel, n as RecorderDeviceSettingsPanelProps, o as RecorderEncodingSettings, p as RecorderModal, q as RecorderModalProps, r as RecorderNarrationOptions, s as RecorderPanel, t as RecorderPanelProps, u as RecorderSaveResult, v as RecorderScreenAudioSettings, w as RecorderScreenDeviceSettings, x as RecorderSlidesOptions, S as ScreenStreamHandle, y as ScreenStreamOptions, z as SlideTimingSaveResult, T as TimingJson, A as buildRecorderAudioConstraints, B as buildRecorderCameraConstraints, E as buildRecorderScreenAudioConstraints, F as buildRecorderScreenConstraints, G as buildTimingJson, H as encodeTimingJson, I as hasRecorderDeviceChoice, J as recordedMediaKind, K as recorderBitsPerSecond, L as recorderDeviceGroups, N as recorderDeviceOptions, O as requestCameraStream, P as requestMicStream, Q as requestScreenStream, U as timingPathFor, V as useMediaDevices, W as useStreamPreview } from './recorder-C6ArYQBa.js';
30
+ import { R as RecorderState } from './useNarrationStage-DopDr_P5.js';
31
+ export { C as CanvasSink, a as CaptureKind, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, c as FloatingWindowHandle, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, g as NarrationStageHandle, h as NarrationTake, P as PrompterTransport, i as RecorderAudioBitrateMode, j as RecorderCameraLane, k as RecorderExtendedMediaOptions, l as RecorderSource, m as ResolvedFormat, T as TeleprompterController, n as TeleprompterPrefs, o as TeleprompterRecordingDeps, U as UseMediaRecorderOptions, p as UseMediaRecorderResult, q as UseNarrationStageOptions, r as buildFilename, s as createFloatingWindowManager, t as detectFloatTiers, u as getCaptureKind, v as resolveFormat, w as supportsDisplayMedia, x as supportsMediaRecorder, y as supportsUserMedia, z as useFloatingWindow, A as useMediaRecorder, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from './useNarrationStage-DopDr_P5.js';
32
32
  export { EYE_LINE_FRACTION, NarrationSavePlan, NarrationSaveResult, NarrationStage, NarrationStageProps, PCM_WORKLET_NAME, PCM_WORKLET_SOURCE, TELEPROMPTER_CSS, TeleprompterControls, TeleprompterControlsProps, TeleprompterSurface, TeleprompterSurfaceProps, TeleprompterView, TeleprompterViewProps, TokenLineMap, buildNarrationSavePlan, cameraVideoLine, ensureTeleprompterStyles, executeNarrationSave, insertNarrationPreamble, measureTokenLines, narrationAnnotationLine, prompterVarsFromTheme, registerPcmWorklet, stepScroll, targetOffsetFor } from './teleprompter/index.js';
33
33
  export { H as HarperProofingConfig, a as ProofingCapability, b as ProofingDocumentRef, c as ProofingIgnoreStore, d as ProofingLanguage, e as ProofingLintOptions, P as ProofingProvider, f as ProofingProviderFactory } from './types-De4z-gGb.js';
34
34
  export { createHarperProofingProvider } from './proofing/index.js';
package/dist/index.js CHANGED
@@ -219,7 +219,7 @@ import {
219
219
  useTreeViewData,
220
220
  viewStateFromBinding,
221
221
  writeHeadingViewState
222
- } from "./chunk-7B46MJNW.js";
222
+ } from "./chunk-5TYCTE33.js";
223
223
  import {
224
224
  EditorProvider,
225
225
  detectLanguageFromFileName,
@@ -252,7 +252,7 @@ import {
252
252
  } from "./chunk-K4Z2GIX4.js";
253
253
  import {
254
254
  RecorderButton
255
- } from "./chunk-DN6XFOY2.js";
255
+ } from "./chunk-27WOOJXR.js";
256
256
  import {
257
257
  DEFAULT_RECORDER_DEVICE_SETTINGS,
258
258
  RecorderDeviceQuickPicks,
@@ -283,7 +283,7 @@ import {
283
283
  unshownSlidesWarning,
284
284
  useMediaDevices,
285
285
  useMediaRecorder
286
- } from "./chunk-BJ3TXAIQ.js";
286
+ } from "./chunk-4D7QMRTZ.js";
287
287
  import "./chunk-GS7QWYFT.js";
288
288
  import {
289
289
  TeleprompterView
@@ -1,5 +1,5 @@
1
- export { C as CameraStreamOptions, D as DEFAULT_RECORDER_DEVICE_SETTINGS, M as MediaDeviceInventory, R as RecordedBookmark, a as RecordedMediaKind, b as RecorderAudioDeviceSettings, c as RecorderButton, d as RecorderButtonProps, e as RecorderCameraDeviceSettings, f as RecorderCameraSaveResult, g as RecorderColorScheme, h as RecorderConstraintMode, i as RecorderDeviceOption, j as RecorderDeviceQuickPicks, k as RecorderDeviceQuickPicksProps, l as RecorderDeviceSettings, m as RecorderDeviceSettingsPanel, n as RecorderDeviceSettingsPanelProps, o as RecorderEncodingSettings, p as RecorderModal, q as RecorderModalProps, s as RecorderPanel, t as RecorderPanelProps, u as RecorderSaveResult, v as RecorderScreenAudioSettings, w as RecorderScreenDeviceSettings, S as ScreenStreamHandle, y as ScreenStreamOptions, T as TimingJson, A as buildRecorderAudioConstraints, B as buildRecorderCameraConstraints, E as buildRecorderScreenAudioConstraints, F as buildRecorderScreenConstraints, G as buildTimingJson, H as encodeTimingJson, I as hasRecorderDeviceChoice, J as recordedMediaKind, K as recorderBitsPerSecond, L as recorderDeviceGroups, N as recorderDeviceOptions, O as requestCameraStream, P as requestMicStream, Q as requestScreenStream, U as timingPathFor, V as useMediaDevices, W as useStreamPreview } from '../recorder-CC3VW9Z7.js';
2
- export { a as CaptureKind, i as RecorderAudioBitrateMode, j as RecorderCameraLane, k as RecorderExtendedMediaOptions, l as RecorderSource, R as RecorderState, m as ResolvedFormat, U as UseMediaRecorderOptions, p as UseMediaRecorderResult, r as buildFilename, u as getCaptureKind, v as resolveFormat, w as supportsDisplayMedia, x as supportsMediaRecorder, y as supportsUserMedia, A as useMediaRecorder } from '../useNarrationStage-D3Sgcf0L.js';
1
+ export { C as CameraStreamOptions, D as DEFAULT_RECORDER_DEVICE_SETTINGS, M as MediaDeviceInventory, R as RecordedBookmark, a as RecordedMediaKind, b as RecorderAudioDeviceSettings, c as RecorderButton, d as RecorderButtonProps, e as RecorderCameraDeviceSettings, f as RecorderCameraSaveResult, g as RecorderColorScheme, h as RecorderConstraintMode, i as RecorderDeviceOption, j as RecorderDeviceQuickPicks, k as RecorderDeviceQuickPicksProps, l as RecorderDeviceSettings, m as RecorderDeviceSettingsPanel, n as RecorderDeviceSettingsPanelProps, o as RecorderEncodingSettings, p as RecorderModal, q as RecorderModalProps, s as RecorderPanel, t as RecorderPanelProps, u as RecorderSaveResult, v as RecorderScreenAudioSettings, w as RecorderScreenDeviceSettings, S as ScreenStreamHandle, y as ScreenStreamOptions, T as TimingJson, A as buildRecorderAudioConstraints, B as buildRecorderCameraConstraints, E as buildRecorderScreenAudioConstraints, F as buildRecorderScreenConstraints, G as buildTimingJson, H as encodeTimingJson, I as hasRecorderDeviceChoice, J as recordedMediaKind, K as recorderBitsPerSecond, L as recorderDeviceGroups, N as recorderDeviceOptions, O as requestCameraStream, P as requestMicStream, Q as requestScreenStream, U as timingPathFor, V as useMediaDevices, W as useStreamPreview } from '../recorder-C6ArYQBa.js';
2
+ export { a as CaptureKind, i as RecorderAudioBitrateMode, j as RecorderCameraLane, k as RecorderExtendedMediaOptions, l as RecorderSource, R as RecorderState, m as ResolvedFormat, U as UseMediaRecorderOptions, p as UseMediaRecorderResult, r as buildFilename, u as getCaptureKind, v as resolveFormat, w as supportsDisplayMedia, x as supportsMediaRecorder, y as supportsUserMedia, A as useMediaRecorder } from '../useNarrationStage-DopDr_P5.js';
3
3
  import 'react/jsx-runtime';
4
4
  import '@bendyline/squisq/schemas';
5
5
  import '@bendyline/squisq/storage';
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RecorderButton
3
- } from "../chunk-DN6XFOY2.js";
3
+ } from "../chunk-27WOOJXR.js";
4
4
  import {
5
5
  DEFAULT_RECORDER_DEVICE_SETTINGS,
6
6
  RecorderDeviceQuickPicks,
@@ -20,7 +20,7 @@ import {
20
20
  requestScreenStream,
21
21
  useMediaDevices,
22
22
  useMediaRecorder
23
- } from "../chunk-BJ3TXAIQ.js";
23
+ } from "../chunk-4D7QMRTZ.js";
24
24
  import "../chunk-GS7QWYFT.js";
25
25
  import {
26
26
  buildFilename,
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Doc, Theme, ViewportConfig, MediaProvider } from '@bendyline/squisq/schemas';
3
3
  import { ContentContainer } from '@bendyline/squisq/storage';
4
- import { l as RecorderSource, o as TeleprompterRecordingDeps } from './useNarrationStage-D3Sgcf0L.js';
4
+ import { l as RecorderSource, o as TeleprompterRecordingDeps, S as ScreenCaptureRegion } from './useNarrationStage-DopDr_P5.js';
5
5
  import { CSSProperties, RefObject } from 'react';
6
6
 
7
7
  type RecordedMediaKind = 'audio' | 'video';
@@ -225,6 +225,7 @@ declare function RecorderPanel({ mediaProvider, container, initialMode, colorSch
225
225
  * capture is the exception: the screen-capture standard rejects exact
226
226
  * constraints during its user picker, so those values always remain ideal.
227
227
  */
228
+
228
229
  type RecorderConstraintMode = 'ideal' | 'exact';
229
230
  interface RecorderAudioDeviceSettings {
230
231
  deviceId: string;
@@ -249,6 +250,8 @@ interface RecorderCameraDeviceSettings {
249
250
  width?: number;
250
251
  }
251
252
  interface RecorderScreenDeviceSettings {
253
+ /** Crop in captured pixels, relative to the chosen surface (not the desktop). */
254
+ crop?: ScreenCaptureRegion;
252
255
  aspectRatio?: number;
253
256
  cursor?: string;
254
257
  displaySurface?: string;
@@ -476,7 +479,9 @@ declare function requestCameraStream(options?: CameraStreamOptions): Promise<Med
476
479
  * `AudioContext`, so the resulting `MediaStream` carries a single audio
477
480
  * track and a single video track.
478
481
  */
482
+
479
483
  interface ScreenStreamOptions {
484
+ crop?: ScreenCaptureRegion;
480
485
  /** Video constraints for the screen surface. Pass `true` for browser default. */
481
486
  video?: boolean | MediaTrackConstraints;
482
487
  /**
@@ -5,7 +5,7 @@ import {
5
5
  RawEditor,
6
6
  WysiwygEditor,
7
7
  useEditorContextMenuItems
8
- } from "../chunk-7B46MJNW.js";
8
+ } from "../chunk-5TYCTE33.js";
9
9
  import {
10
10
  EditorProvider,
11
11
  useEditorContext
@@ -14,7 +14,7 @@ import "../chunk-V4NBQF5C.js";
14
14
  import "../chunk-ZDWZRVAY.js";
15
15
  import "../chunk-EN5BYZOE.js";
16
16
  import "../chunk-K4Z2GIX4.js";
17
- import "../chunk-BJ3TXAIQ.js";
17
+ import "../chunk-4D7QMRTZ.js";
18
18
  import "../chunk-GS7QWYFT.js";
19
19
  import "../chunk-ENDRPV6T.js";
20
20
  import "../chunk-34FRFDQ6.js";
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { Doc, Theme, MediaProvider } from '@bendyline/squisq/schemas';
3
3
  import { ContentContainer } from '@bendyline/squisq/storage';
4
- import { o as TeleprompterRecordingDeps, g as NarrationStageHandle, T as TeleprompterController, c as FloatingWindowHandle } from '../useNarrationStage-D3Sgcf0L.js';
5
- export { C as CanvasSink, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, h as NarrationTake, P as PrompterTransport, J as TELEPROMPTER_PREF_LIMITS, n as TeleprompterPrefs, q as UseNarrationStageOptions, s as createFloatingWindowManager, t as detectFloatTiers, K as normalizeTeleprompterPrefs, z as useFloatingWindow, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from '../useNarrationStage-D3Sgcf0L.js';
4
+ import { o as TeleprompterRecordingDeps, g as NarrationStageHandle, T as TeleprompterController, c as FloatingWindowHandle } from '../useNarrationStage-DopDr_P5.js';
5
+ export { C as CanvasSink, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, b as FloatTier, d as FloatingWindowManager, M as MicAnalysisHandle, e as MicAnalysisStatus, N as NarrationRecorderController, f as NarrationRecorderState, h as NarrationTake, P as PrompterTransport, J as TELEPROMPTER_PREF_LIMITS, n as TeleprompterPrefs, q as UseNarrationStageOptions, s as createFloatingWindowManager, t as detectFloatTiers, K as normalizeTeleprompterPrefs, z as useFloatingWindow, B as useMicAnalysis, E as useNarrationRecorder, G as useNarrationStage, H as useTeleprompter, I as vadConfigForSensitivity } from '../useNarrationStage-DopDr_P5.js';
6
6
  import { NarrationTimingJsonV3, NarrationScript, NarrationAlignment } from '@bendyline/squisq/narration';
7
7
  import { ReactNode } from 'react';
8
8
 
@@ -57,6 +57,13 @@ declare function supportsDisplayMedia(): boolean;
57
57
  type RecordingFilenameSeed = 'audio' | 'camera' | 'camera-audio' | 'screen' | 'screen-audio';
58
58
  declare function buildFilename(kind: CaptureKind, extension: string, basename?: string, seed?: RecordingFilenameSeed): string;
59
59
 
60
+ interface ScreenCaptureRegion {
61
+ x: number;
62
+ y: number;
63
+ width: number;
64
+ height: number;
65
+ }
66
+
60
67
  /**
61
68
  * useMediaRecorder
62
69
  *
@@ -110,6 +117,8 @@ interface RecorderCameraLane {
110
117
  extension: string | null;
111
118
  }
112
119
  interface UseMediaRecorderOptions {
120
+ /** Optional crop applied to the screen lane before preview and encoding. */
121
+ screenRegion?: ScreenCaptureRegion;
113
122
  /**
114
123
  * Combined byte threshold for automatically stopping every capture lane.
115
124
  * Defaults to 90 MiB. Browser chunk delivery can be delayed, so the final
@@ -586,4 +595,4 @@ interface NarrationStageHandle {
586
595
  }
587
596
  declare function useNarrationStage(opts: UseNarrationStageOptions): NarrationStageHandle;
588
597
 
589
- export { useMediaRecorder as A, useMicAnalysis as B, type CanvasSink as C, DEFAULT_TELEPROMPTER_PREFS as D, useNarrationRecorder as E, type FloatOpenOptions as F, useNarrationStage as G, useTeleprompter as H, vadConfigForSensitivity as I, TELEPROMPTER_PREF_LIMITS as J, normalizeTeleprompterPrefs as K, type MicAnalysisHandle as M, type NarrationRecorderController as N, type PrompterTransport as P, type RecorderState as R, type TeleprompterController as T, type UseMediaRecorderOptions as U, type CaptureKind as a, type FloatTier as b, type FloatingWindowHandle as c, type FloatingWindowManager as d, type MicAnalysisStatus as e, type NarrationRecorderState as f, type NarrationStageHandle as g, type NarrationTake as h, type RecorderAudioBitrateMode as i, type RecorderCameraLane as j, type RecorderExtendedMediaOptions as k, type RecorderSource as l, type ResolvedFormat as m, type TeleprompterPrefs as n, type TeleprompterRecordingDeps as o, type UseMediaRecorderResult as p, type UseNarrationStageOptions as q, buildFilename as r, createFloatingWindowManager as s, detectFloatTiers as t, getCaptureKind as u, resolveFormat as v, supportsDisplayMedia as w, supportsMediaRecorder as x, supportsUserMedia as y, useFloatingWindow as z };
598
+ export { useMediaRecorder as A, useMicAnalysis as B, type CanvasSink as C, DEFAULT_TELEPROMPTER_PREFS as D, useNarrationRecorder as E, type FloatOpenOptions as F, useNarrationStage as G, useTeleprompter as H, vadConfigForSensitivity as I, TELEPROMPTER_PREF_LIMITS as J, normalizeTeleprompterPrefs as K, type MicAnalysisHandle as M, type NarrationRecorderController as N, type PrompterTransport as P, type RecorderState as R, type ScreenCaptureRegion as S, type TeleprompterController as T, type UseMediaRecorderOptions as U, type CaptureKind as a, type FloatTier as b, type FloatingWindowHandle as c, type FloatingWindowManager as d, type MicAnalysisStatus as e, type NarrationRecorderState as f, type NarrationStageHandle as g, type NarrationTake as h, type RecorderAudioBitrateMode as i, type RecorderCameraLane as j, type RecorderExtendedMediaOptions as k, type RecorderSource as l, type ResolvedFormat as m, type TeleprompterPrefs as n, type TeleprompterRecordingDeps as o, type UseMediaRecorderResult as p, type UseNarrationStageOptions as q, buildFilename as r, createFloatingWindowManager as s, detectFloatTiers as t, getCaptureKind as u, resolveFormat as v, supportsDisplayMedia as w, supportsMediaRecorder as x, supportsUserMedia as y, useFloatingWindow as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-editor-react",
3
- "version": "2.11.15",
3
+ "version": "2.11.16",
4
4
  "description": "React editor shell with raw/WYSIWYG/preview modes for Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -98,12 +98,12 @@
98
98
  }
99
99
  },
100
100
  "dependencies": {
101
- "@bendyline/squisq": "2.11.12",
101
+ "@bendyline/squisq": "2.11.13",
102
102
  "@bendyline/squisq-calc": "2.11.1",
103
- "@bendyline/squisq-formats": "2.6.12",
104
- "@bendyline/squisq-grid-react": "2.11.13",
105
- "@bendyline/squisq-react": "2.11.14",
106
- "@bendyline/squisq-video-react": "2.4.18",
103
+ "@bendyline/squisq-formats": "2.6.13",
104
+ "@bendyline/squisq-grid-react": "2.11.14",
105
+ "@bendyline/squisq-react": "2.11.15",
106
+ "@bendyline/squisq-video-react": "2.4.19",
107
107
  "@fortawesome/fontawesome-free": "7.2.0",
108
108
  "@tiptap/core": "2.27.3",
109
109
  "@tiptap/extension-heading": "2.27.3",