@q9labsai/chalk-react 5.2.3 → 5.2.5

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
@@ -139,4 +139,4 @@ Each message's `reactions` contains `{ emoji, count, participantIds }`; IDs iden
139
139
 
140
140
  Message reactions are stored separately from recording facts and are not rendered or added to recording timelines yet. Recording validators accept all 24 call-reaction emoji.
141
141
 
142
- `useChat()` returns the same state and `react(input)` action. Each hook message always has a reactions array; each group adds `isMine` for the current participant. Public types are `MessageReaction`, `ReactChatMessage`, and `UseChatResult`. No reaction UI is added by this change.
142
+ `useChat()` returns the same state and `react(input)` action. Each hook message always has a reactions array; each group adds `isMine` for the current participant. Public types are `MessageReaction`, `ReactChatMessage`, and `UseChatResult`.
@@ -36,7 +36,7 @@ import {
36
36
  useRecording,
37
37
  useSpaceClient,
38
38
  useWhiteboard
39
- } from "./chunk-34SIWIY5.js";
39
+ } from "./chunk-WRKIKIXF.js";
40
40
  import {
41
41
  WHITEBOARD_ITEM_ID,
42
42
  screenShareItemId
@@ -326,7 +326,21 @@ function SpaceExperience(props) {
326
326
  if (connection.status === "failed" && connection.lastError?.code === "episode.ended")
327
327
  return /* @__PURE__ */ jsx(StatusSurface, { phase: "episode-ended", callName, episodeEndReason: connection.episodeEndReason, endAction: props.timeLimit?.endAction, theme: statusTheme, onHome: props.onHome, onReportProblem: props.onOpenReport });
328
328
  if (connection.status === "failed") {
329
- return /* @__PURE__ */ jsx(StatusSurface, { phase: "failed", callName, errorCode: connection.lastError?.code, sync: connection.lastError?.sync, theme: statusTheme, onHome: props.onHome, onReportProblem: props.onOpenReport, onRetry: retryAutomaticJoin, retryPending });
329
+ return /* @__PURE__ */ jsx(
330
+ StatusSurface,
331
+ {
332
+ phase: "failed",
333
+ callName,
334
+ errorCode: connection.lastError?.code,
335
+ reason: connection.lastError?.code === "access.invalid" ? connection.lastError.message : void 0,
336
+ sync: connection.lastError?.sync,
337
+ theme: statusTheme,
338
+ onHome: props.onHome,
339
+ onReportProblem: props.onOpenReport,
340
+ onRetry: retryAutomaticJoin,
341
+ retryPending
342
+ }
343
+ );
330
344
  }
331
345
  if (connection.status === "leaving" || connection.status === "left")
332
346
  return /* @__PURE__ */ jsx(
@@ -4323,7 +4323,8 @@ function DevicePopover({
4323
4323
  className,
4324
4324
  disabled,
4325
4325
  toggleDisabled,
4326
- busy
4326
+ busy,
4327
+ onCancelPending
4327
4328
  }) {
4328
4329
  const [open, setOpen] = useState18(false);
4329
4330
  const mic = type === "mic";
@@ -4333,8 +4334,20 @@ function DevicePopover({
4333
4334
  /* @__PURE__ */ jsx38(
4334
4335
  ChalkTooltip,
4335
4336
  {
4336
- trigger: /* @__PURE__ */ jsx38("button", { type: "button", className: "chalk-shell-main", "aria-label": busy ? `Updating ${mic ? "microphone" : "camera"}` : label, "aria-pressed": isActive, disabled: disabled || toggleDisabled || busy, onClick: onToggle, children: /* @__PURE__ */ jsx38(ShellIcon, { name: mic ? isActive ? "mic" : "micOff" : isActive ? "cam" : "camOff" }) }),
4337
- children: label
4337
+ trigger: /* @__PURE__ */ jsx38(
4338
+ "button",
4339
+ {
4340
+ type: "button",
4341
+ className: "chalk-shell-main",
4342
+ "aria-label": busy ? `${onCancelPending ? "Cancel connecting" : "Updating"} ${mic ? "microphone" : "camera"}` : label,
4343
+ "aria-pressed": isActive,
4344
+ "aria-busy": busy,
4345
+ disabled: disabled || toggleDisabled && !(busy && onCancelPending) || busy && !onCancelPending,
4346
+ onClick: busy && onCancelPending ? onCancelPending : onToggle,
4347
+ children: busy ? /* @__PURE__ */ jsx38("span", { "aria-hidden": "true", children: "\u2026" }) : /* @__PURE__ */ jsx38(ShellIcon, { name: mic ? isActive ? "mic" : "micOff" : isActive ? "cam" : "camOff" })
4348
+ }
4349
+ ),
4350
+ children: warning ? `${mic ? "Microphone" : "Camera"} didn\u2019t reach the call. Try turning it on again.` : busy ? `Connecting ${mic ? "microphone" : "camera"}\u2026` : label
4338
4351
  }
4339
4352
  ),
4340
4353
  /* @__PURE__ */ jsxs20(
@@ -4367,7 +4380,18 @@ function DevicePopover({
4367
4380
  ]
4368
4381
  }
4369
4382
  ),
4370
- warning ? /* @__PURE__ */ jsx38("span", { className: "chalk-shell-warning", role: "img", "aria-label": `${mic ? "Microphone" : "Camera"} blocked or unavailable`, children: "!" }) : null,
4383
+ warning ? /* @__PURE__ */ jsxs20("span", { className: "chalk-shell-warning", role: "status", "aria-label": `${mic ? "Microphone" : "Camera"} didn\u2019t reach the call. Try turning it on again.`, children: [
4384
+ /* @__PURE__ */ jsx38("span", { "aria-hidden": "true", children: "!" }),
4385
+ /* @__PURE__ */ jsxs20("span", { className: "sr-only", children: [
4386
+ mic ? "Microphone" : "Camera",
4387
+ " didn\u2019t reach the call. Try turning it on again."
4388
+ ] })
4389
+ ] }) : null,
4390
+ busy ? /* @__PURE__ */ jsxs20("span", { className: "sr-only", role: "status", children: [
4391
+ "Connecting ",
4392
+ mic ? "microphone" : "camera",
4393
+ "\u2026"
4394
+ ] }) : null,
4371
4395
  mic && speakingWhileMuted ? /* @__PURE__ */ jsx38("span", { className: "chalk-shell-muted-hint", role: "status", children: "You\u2019re muted" }) : null
4372
4396
  ] });
4373
4397
  }
@@ -4509,6 +4533,14 @@ function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisab
4509
4533
 
4510
4534
  // src/components/control-bar/control-bar-behavior.ts
4511
4535
  import { useCallback as useCallback5, useEffect as useEffect16, useMemo as useMemo9, useState as useState21 } from "react";
4536
+
4537
+ // src/components/control-bar/media-toggle.ts
4538
+ function mediaToggleTarget(state, live) {
4539
+ if (state === "enabled" || state === "requesting") return false;
4540
+ return live ? true : void 0;
4541
+ }
4542
+
4543
+ // src/components/control-bar/control-bar-behavior.ts
4512
4544
  var DEFAULT_CONTROL_BAR_BUTTONS = ["mic", "video", "screenshare", "record", "whiteboard", "handraise", "leave", "participants", "chat", "reactions"];
4513
4545
  var EMPTY_DETECTED_DEVICES = {
4514
4546
  audioinput: [],
@@ -4600,6 +4632,8 @@ function useConnectedControlBarBehavior(props) {
4600
4632
  const live = useConnection().status === "live";
4601
4633
  const microphoneEnabled = media.local.microphone.state === "enabled";
4602
4634
  const cameraEnabled = media.local.camera.state === "enabled";
4635
+ const microphoneTarget = mediaToggleTarget(media.local.microphone.state, live);
4636
+ const cameraTarget = mediaToggleTarget(media.local.camera.state, live);
4603
4637
  const screenSharing = media.local.screen.state === "enabled" || media.local.screen.state === "requesting";
4604
4638
  const recordingStatus = recording.current?.status;
4605
4639
  const isRecording = recordingStatus === "recording" || recordingStatus === "stopping";
@@ -4649,6 +4683,8 @@ function useConnectedControlBarBehavior(props) {
4649
4683
  const audioInputDevices = media.devices.microphones.map((device2) => ({ ...device2, kind: "audioinput" }));
4650
4684
  const audioOutputDevices = media.devices.speakers.map((device2) => ({ ...device2, kind: "audiooutput" }));
4651
4685
  const videoInputDevices = media.devices.cameras.map((device2) => ({ ...device2, kind: "videoinput" }));
4686
+ const toggleMicrophone = microphoneTarget === void 0 ? void 0 : () => void run(() => client.media.setMicrophoneEnabled(microphoneTarget), "microphone", microphoneTarget ? "start" : "stop");
4687
+ const toggleCamera = cameraTarget === void 0 ? void 0 : () => void run(() => client.media.setCameraEnabled(cameraTarget), "camera", cameraTarget ? "start" : "stop");
4652
4688
  const surfaceProps = {
4653
4689
  ...props,
4654
4690
  detectDevices: false,
@@ -4675,9 +4711,11 @@ function useConnectedControlBarBehavior(props) {
4675
4711
  shareUrl: props.shareUrl,
4676
4712
  isMuted: !microphoneEnabled,
4677
4713
  microphonePending: media.local.microphone.state === "requesting",
4714
+ onCancelMicrophonePending: toggleMicrophone,
4678
4715
  mediaEnableDisabled: !live,
4679
4716
  reactionsDisabled: !live,
4680
4717
  cameraPending: media.local.camera.state === "requesting",
4718
+ onCancelCameraPending: toggleCamera,
4681
4719
  isVideoEnabled: cameraEnabled,
4682
4720
  isScreenSharing: screenSharing,
4683
4721
  isRecording,
@@ -4724,8 +4762,8 @@ function useConnectedControlBarBehavior(props) {
4724
4762
  "speaker",
4725
4763
  "test"
4726
4764
  ),
4727
- onToggleMute: () => void run(() => client.media.setMicrophoneEnabled(!microphoneEnabled), "microphone", microphoneEnabled ? "stop" : "start"),
4728
- onToggleVideo: () => void run(() => client.media.setCameraEnabled(!cameraEnabled), "camera", cameraEnabled ? "stop" : "start"),
4765
+ onToggleMute: toggleMicrophone,
4766
+ onToggleVideo: toggleCamera,
4729
4767
  onAudioInputChange: (deviceId) => void run(() => client.media.selectMicrophone(deviceId), "microphone", "select"),
4730
4768
  onAudioOutputChange: (deviceId) => void run(() => client.media.selectSpeaker(deviceId), "speaker", "select"),
4731
4769
  onVideoInputChange: (deviceId) => void run(() => client.media.selectCamera(deviceId), "camera", "select"),
@@ -4739,7 +4777,12 @@ function useConnectedControlBarBehavior(props) {
4739
4777
  onLeft: leave,
4740
4778
  onEndForEveryone: props.onEndForEveryone
4741
4779
  };
4742
- return { surfaceProps, commandError, commandSource, commandAction };
4780
+ return { surfaceProps, commandError, commandSource, commandAction, mediaNotice: failedMediaNotice(media.local) };
4781
+ }
4782
+ function failedMediaNotice(media) {
4783
+ if (media.microphone.state === "failed") return "Microphone didn\u2019t reach the call. Turn it on to try again.";
4784
+ if (media.camera.state === "failed") return "Camera didn\u2019t reach the call. Turn it on to try again.";
4785
+ return void 0;
4743
4786
  }
4744
4787
 
4745
4788
  // src/components/control-bar/next-camera.ts
@@ -4804,6 +4847,7 @@ var ControlBarSurface = React12.memo((props) => {
4804
4847
  type: "mic",
4805
4848
  isActive: !props.isMuted,
4806
4849
  busy: props.microphonePending,
4850
+ onCancelPending: props.onCancelMicrophonePending,
4807
4851
  toggleDisabled: props.mediaEnableDisabled && props.isMuted,
4808
4852
  onToggle: props.onToggleMute ?? (() => {
4809
4853
  }),
@@ -4828,6 +4872,7 @@ var ControlBarSurface = React12.memo((props) => {
4828
4872
  type: "video",
4829
4873
  isActive: props.isVideoEnabled ?? true,
4830
4874
  busy: props.cameraPending,
4875
+ onCancelPending: props.onCancelCameraPending,
4831
4876
  toggleDisabled: props.mediaEnableDisabled && !props.isVideoEnabled,
4832
4877
  onToggle: props.onToggleVideo ?? (() => {
4833
4878
  }),
@@ -4911,10 +4956,10 @@ var ControlBarSurface = React12.memo((props) => {
4911
4956
  });
4912
4957
  ControlBarSurface.displayName = "ControlBarSurface";
4913
4958
  function ControlBar(props) {
4914
- const { surfaceProps, commandError, commandSource, commandAction } = useConnectedControlBarBehavior(props);
4959
+ const { surfaceProps, commandError, commandSource, commandAction, mediaNotice } = useConnectedControlBarBehavior(props);
4915
4960
  return /* @__PURE__ */ jsxs25(Fragment15, { children: [
4916
4961
  /* @__PURE__ */ jsx43(ControlBarSurface, { ...surfaceProps }),
4917
- !props.onCommandError ? /* @__PURE__ */ jsx43(CommandErrorAlert, { message: commandError ? commandFailureMessage(commandError, commandSource, commandAction) : void 0 }) : null
4962
+ /* @__PURE__ */ jsx43(CommandErrorAlert, { message: mediaNotice ?? (!props.onCommandError && commandError ? commandFailureMessage(commandError, commandSource, commandAction) : void 0) })
4918
4963
  ] });
4919
4964
  }
4920
4965
 
@@ -8595,6 +8640,8 @@ function SpaceView({
8595
8640
  else callToasts.dismiss(id);
8596
8641
  };
8597
8642
  const [foldedNotices, setFoldedNotices] = useState44([]);
8643
+ const microphoneTarget = mediaToggleTarget(media.local.microphone.state, connection.status === "live");
8644
+ const cameraTarget = mediaToggleTarget(media.local.camera.state, connection.status === "live");
8598
8645
  const folded = toasts.filter((toast) => foldedNotices.includes(toast.id));
8599
8646
  return /* @__PURE__ */ jsx69(ParticipantVolumeProvider, { children: /* @__PURE__ */ jsx69(BoardToastContext2.Provider, { value: showBoardToast, children: /* @__PURE__ */ jsx69(
8600
8647
  SpacePresentation,
@@ -8617,8 +8664,8 @@ function SpaceView({
8617
8664
  diagnostics: callDiagnostics(connection, media),
8618
8665
  shortcuts: {
8619
8666
  isMuted: media.local.microphone.state !== "enabled",
8620
- onToggleMute: connection.status === "live" || media.local.microphone.state === "enabled" ? () => void runCommand(() => client.media.setMicrophoneEnabled(media.local.microphone.state !== "enabled"), "microphone", media.local.microphone.state === "enabled" ? "stop" : "start") : void 0,
8621
- onToggleVideo: connection.status === "live" || media.local.camera.state === "enabled" ? () => void runCommand(() => client.media.setCameraEnabled(media.local.camera.state !== "enabled"), "camera", media.local.camera.state === "enabled" ? "stop" : "start") : void 0,
8667
+ onToggleMute: microphoneTarget === void 0 ? void 0 : () => void runCommand(() => client.media.setMicrophoneEnabled(microphoneTarget), "microphone", microphoneTarget ? "start" : "stop"),
8668
+ onToggleVideo: cameraTarget === void 0 ? void 0 : () => void runCommand(() => client.media.setCameraEnabled(cameraTarget), "camera", cameraTarget ? "start" : "stop"),
8622
8669
  onToggleHand: feature("handRaise") && canRaiseHand ? () => void runCommand(() => self.handRaised ? client.participants.lowerHand() : client.participants.raiseHand()) : void 0,
8623
8670
  onToggleChat: feature("chat") ? () => setActivePanel((current) => current === "chat" ? null : "chat") : void 0,
8624
8671
  onTogglePeople: feature("participants") ? () => setActivePanel((current) => current === "participants" ? null : "participants") : void 0,
@@ -34,9 +34,11 @@ export interface ControlBarSurfaceProps {
34
34
  knockCount?: number;
35
35
  isMuted?: boolean;
36
36
  microphonePending?: boolean;
37
+ onCancelMicrophonePending?: () => void;
37
38
  mediaEnableDisabled?: boolean;
38
39
  reactionsDisabled?: boolean;
39
40
  cameraPending?: boolean;
41
+ onCancelCameraPending?: () => void;
40
42
  isVideoEnabled?: boolean;
41
43
  isScreenSharing?: boolean;
42
44
  isRecording?: boolean;
@@ -126,6 +128,7 @@ export declare function useControlBarSurfaceBehavior({ density, buttons, audioIn
126
128
  interface ConnectedControlBarBehavior {
127
129
  readonly surfaceProps: ControlBarSurfaceProps;
128
130
  readonly commandError: ClientFailure | null;
131
+ readonly mediaNotice?: string;
129
132
  readonly commandSource?: MediaCommandSource;
130
133
  readonly commandAction?: MediaCommandAction;
131
134
  }
@@ -0,0 +1,2 @@
1
+ import type { LocalMedia } from "@q9labsai/chalk-client";
2
+ export declare function mediaToggleTarget(state: LocalMedia["state"], live: boolean): boolean | undefined;
@@ -25,8 +25,9 @@ export interface DevicePopoverProps {
25
25
  disabled?: boolean;
26
26
  toggleDisabled?: boolean;
27
27
  busy?: boolean;
28
+ onCancelPending?: () => void;
28
29
  haptic?: HapticInput | false;
29
30
  size?: "sm" | "md" | "lg";
30
31
  appearance?: "default" | "floating";
31
32
  }
32
- export declare function DevicePopover({ type, isActive, onToggle, devices, selectedDeviceId, onDeviceChange, secondaryDevices, selectedSecondaryDeviceId, onSecondaryDeviceChange, track, captureCameraPreview, warning, speakingWhileMuted, noiseSuppression, onNoiseSuppressionChange, mirrored, onMirrorChange, onTestSpeaker, orientation, className, disabled, toggleDisabled, busy, }: DevicePopoverProps): import("react").JSX.Element;
33
+ export declare function DevicePopover({ type, isActive, onToggle, devices, selectedDeviceId, onDeviceChange, secondaryDevices, selectedSecondaryDeviceId, onSecondaryDeviceChange, track, captureCameraPreview, warning, speakingWhileMuted, noiseSuppression, onNoiseSuppressionChange, mirrored, onMirrorChange, onTestSpeaker, orientation, className, disabled, toggleDisabled, busy, onCancelPending, }: DevicePopoverProps): import("react").JSX.Element;
@@ -5,7 +5,7 @@ import {
5
5
  RecordingHistoryPanel,
6
6
  RecordingSpaceView,
7
7
  RecordingWhiteboardView
8
- } from "../chunk-IZ44PX4W.js";
8
+ } from "../chunk-6AUMKXPU.js";
9
9
  import {
10
10
  loadRecordingWhiteboardFiles,
11
11
  parseRecordingWhiteboardStateV1,
@@ -47,7 +47,7 @@ import {
47
47
  ReactionsOverlay,
48
48
  ScreenShareView,
49
49
  Stage
50
- } from "../chunk-34SIWIY5.js";
50
+ } from "../chunk-WRKIKIXF.js";
51
51
  import {
52
52
  buildStageItems
53
53
  } from "../chunk-HWQLW4JR.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  RecordingHistoryPanel,
6
6
  RecordingSpaceView,
7
7
  RecordingWhiteboardView
8
- } from "./chunk-IZ44PX4W.js";
8
+ } from "./chunk-6AUMKXPU.js";
9
9
  import {
10
10
  loadRecordingWhiteboardFiles,
11
11
  parseRecordingWhiteboardStateV1,
@@ -59,7 +59,7 @@ import {
59
59
  useSelf,
60
60
  useSpaceClient,
61
61
  useWhiteboard
62
- } from "./chunk-34SIWIY5.js";
62
+ } from "./chunk-WRKIKIXF.js";
63
63
  import "./chunk-HWQLW4JR.js";
64
64
  import "./chunk-4U7QZC72.js";
65
65
 
@@ -6,7 +6,7 @@ import {
6
6
  SpaceView,
7
7
  ToastStack,
8
8
  WhiteboardView
9
- } from "../chunk-34SIWIY5.js";
9
+ } from "../chunk-WRKIKIXF.js";
10
10
  import "../chunk-HWQLW4JR.js";
11
11
  import "../chunk-4U7QZC72.js";
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q9labsai/chalk-react",
3
- "version": "5.2.3",
3
+ "version": "5.2.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -75,10 +75,10 @@
75
75
  "@base-ui/react": "^1.7.0",
76
76
  "@hugeicons/core-free-icons": "^4.2.3",
77
77
  "@hugeicons/react": "^1.1.9",
78
- "@q9labsai/chalk-client": "^5.2.3",
79
- "@q9labsai/chalk-ui": "^5.2.3",
80
- "@q9labsai/chalk-whiteboard": "^5.2.3",
81
- "@q9labsai/facehash": "^5.2.3",
78
+ "@q9labsai/chalk-client": "^5.2.5",
79
+ "@q9labsai/chalk-ui": "^5.2.5",
80
+ "@q9labsai/chalk-whiteboard": "^5.2.5",
81
+ "@q9labsai/facehash": "^5.2.5",
82
82
  "@q9labsai/recording-presentation": "^0.1.1",
83
83
  "class-variance-authority": "^0.7.1",
84
84
  "clsx": "^2.1.1",
@@ -99,7 +99,7 @@
99
99
  },
100
100
  "peerDependenciesMeta": {},
101
101
  "scripts": {
102
- "test": "pnpm exec vitest run",
102
+ "test": "node ../../../scripts/test/run.mjs pnpm exec vitest run",
103
103
  "build": "rm -rf ./dist && NODE_ENV=production tsup ./src/index.ts ./src/components/index.ts ./src/components/ui/index.ts ./src/utils/index.ts ./src/headless.ts ./src/test-support/index.ts --format esm --out-dir ./dist --platform browser --external react --external react-dom --external @base-ui/react --external @hugeicons/core-free-icons --external @hugeicons/react --external @q9labsai/chalk-client --external @q9labsai/recording-presentation --external @q9labsai/chalk-ui --external @q9labsai/chalk-ui/reactions --external @q9labsai/chalk-ui/assets --external @q9labsai/chalk-whiteboard --external @q9labsai/facehash --external @q9labsai/facehash/react --external class-variance-authority --external clsx --external tailwind-merge && pnpm run build:fix-use-client && pnpm run build:types",
104
104
  "build:fix-use-client": "node -e \"const fs=require('fs');const files=['./dist/index.js','./dist/components/index.js','./dist/components/ui/index.js','./dist/utils/index.js','./dist/test-support/index.js'];for(const f of files){if(!fs.existsSync(f))continue;let c=fs.readFileSync(f,'utf8');c=c.replace(/['\\\"]use client['\\\"];?\\n?/g,'');c='\\\"use client\\\";\\n'+c;fs.writeFileSync(f,c);}\"",
105
105
  "build:types": "rm -f tsconfig.tsbuildinfo && tsc --build tsconfig.json",