@ai-matrx/meet 0.11.36 → 0.11.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -40,6 +40,7 @@ __export(react_exports, {
40
40
  DATA_TOPICS: () => DATA_TOPICS,
41
41
  DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
42
42
  DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
43
+ DEFAULT_LEAVE_POLICY: () => DEFAULT_LEAVE_POLICY,
43
44
  DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
44
45
  EMPTY_COLLAB: () => EMPTY_COLLAB,
45
46
  END_MEETING_NOT_SERVED: () => END_MEETING_NOT_SERVED,
@@ -83,6 +84,7 @@ __export(react_exports, {
83
84
  NO_SHARE: () => NO_SHARE,
84
85
  ORGANIZATION_HEADER: () => ORGANIZATION_HEADER,
85
86
  PARTICIPANT_CAN_RECORD_ATTRIBUTE: () => PARTICIPANT_CAN_RECORD_ATTRIBUTE,
87
+ PARTICIPANT_HAND_ATTRIBUTE: () => PARTICIPANT_HAND_ATTRIBUTE,
86
88
  PARTICIPANT_ROLE_ATTRIBUTE: () => PARTICIPANT_ROLE_ATTRIBUTE,
87
89
  PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
88
90
  REACTION_TTL_MS: () => REACTION_TTL_MS,
@@ -196,6 +198,8 @@ __export(react_exports, {
196
198
  isWaitingPhase: () => isWaitingPhase,
197
199
  learnName: () => learnName,
198
200
  learnNames: () => learnNames,
201
+ leaveChoices: () => leaveChoices,
202
+ leavePolicyFrom: () => leavePolicyFrom,
199
203
  listenOnlyNotice: () => listenOnlyNotice,
200
204
  loadFollowUpDraft: () => loadFollowUpDraft,
201
205
  loadGuestMeetingRecord: () => loadGuestMeetingRecord,
@@ -297,6 +301,7 @@ __export(react_exports, {
297
301
  useHandQueue: () => useHandQueue,
298
302
  useHostControls: () => useHostControls,
299
303
  useIsHost: () => useIsHost,
304
+ useLeave: () => useLeave,
300
305
  useListenOnlyNotice: () => useListenOnlyNotice,
301
306
  useLobby: () => useLobby,
302
307
  useLocalMedia: () => useLocalMedia,
@@ -1026,6 +1031,30 @@ function invalidResponse(operation, detail) {
1026
1031
  );
1027
1032
  }
1028
1033
 
1034
+ // src/core/leave.ts
1035
+ var DEFAULT_LEAVE_POLICY = { hostLeavePrompt: "leave_or_end", endConfirm: false };
1036
+ var PROMPTS = ["leave_or_end", "must_assign_or_end", "leave_only"];
1037
+ function leavePolicyFrom(raw) {
1038
+ if (typeof raw !== "object" || raw === null) return DEFAULT_LEAVE_POLICY;
1039
+ const value = raw;
1040
+ const prompt = value["host_leave_prompt"];
1041
+ const confirm = value["end_confirm"];
1042
+ return {
1043
+ hostLeavePrompt: PROMPTS.includes(prompt) ? prompt : DEFAULT_LEAVE_POLICY.hostLeavePrompt,
1044
+ endConfirm: typeof confirm === "boolean" ? confirm : DEFAULT_LEAVE_POLICY.endConfirm
1045
+ };
1046
+ }
1047
+ function leaveChoices(args) {
1048
+ if (args.role !== "host") return ["leave"];
1049
+ switch (args.policy.hostLeavePrompt) {
1050
+ case "must_assign_or_end":
1051
+ return args.othersPresent ? ["assign_and_leave", "end_for_all"] : ["leave", "end_for_all"];
1052
+ case "leave_only":
1053
+ case "leave_or_end":
1054
+ return ["leave", "end_for_all"];
1055
+ }
1056
+ }
1057
+
1029
1058
  // src/core/tokens.ts
1030
1059
  function createWebRoomTokenStorage(onDiagnostic) {
1031
1060
  const store = globalThis.localStorage;
@@ -1217,7 +1246,8 @@ function parseRoomToken(body, roomName) {
1217
1246
  pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
1218
1247
  breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
1219
1248
  observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
1220
- connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
1249
+ connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
1250
+ leavePolicy: leavePolicyFrom(payload["leave_policy"])
1221
1251
  };
1222
1252
  }
1223
1253
  function createTokenClient(options) {
@@ -4117,6 +4147,10 @@ var EMPTY = {
4117
4147
  chatHistoryError: null,
4118
4148
  reactions: [],
4119
4149
  spotlight: null,
4150
+ actingHost: null,
4151
+ presenter: null,
4152
+ waitingCount: 0,
4153
+ leavePolicy: DEFAULT_LEAVE_POLICY,
4120
4154
  moderationNotice: null,
4121
4155
  actionNotice: null,
4122
4156
  privateChatRequest: null,
@@ -4178,8 +4212,6 @@ function createMeetStore(initial = {}) {
4178
4212
  },
4179
4213
  setParticipants(participants) {
4180
4214
  const names = learnNames(state.names, participants);
4181
- const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
4182
- const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
4183
4215
  const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
4184
4216
  commit({
4185
4217
  ...state,
@@ -4192,8 +4224,6 @@ function createMeetStore(initial = {}) {
4192
4224
  // identity could reach a screen, and it is the place that asks the
4193
4225
  // name book. `roster-names.test.ts` fails if a token ever gets past it.
4194
4226
  displayName: displayNameFor(names, participant.identity, participant.displayName),
4195
- handRaised: hands.get(participant.identity) ?? participant.handRaised,
4196
- handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
4197
4227
  quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
4198
4228
  }))
4199
4229
  });
@@ -4216,20 +4246,6 @@ function createMeetStore(initial = {}) {
4216
4246
  )
4217
4247
  });
4218
4248
  },
4219
- setHand(identity, raised, at) {
4220
- commit({
4221
- ...state,
4222
- participants: state.participants.map(
4223
- (participant) => participant.identity === identity ? {
4224
- ...participant,
4225
- handRaised: raised,
4226
- // A hand that is ALREADY up keeps its place in the queue when
4227
- // it is re-announced (to a late joiner): the first time wins.
4228
- handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
4229
- } : participant
4230
- )
4231
- });
4232
- },
4233
4249
  addChat(message) {
4234
4250
  if (state.chat.some((existing) => existing.id === message.id)) return;
4235
4251
  const chat = [...state.chat, message];
@@ -4589,7 +4605,7 @@ function createMeetingSession(options) {
4589
4605
  })();
4590
4606
  return reading;
4591
4607
  }
4592
- async function postLeave(intent) {
4608
+ async function postLeave(intent, extra = {}) {
4593
4609
  const meeting = requireMeeting(`meet.${intent}`);
4594
4610
  const pass = options.resolvePass?.() ?? null;
4595
4611
  const response = await options.api.request({
@@ -4597,7 +4613,7 @@ function createMeetingSession(options) {
4597
4613
  method: "POST",
4598
4614
  requireAuth: false,
4599
4615
  operation: `meet.${intent}`,
4600
- body: { meeting_id: meeting.id, intent },
4616
+ body: { meeting_id: meeting.id, intent, ...extra },
4601
4617
  ...pass !== null ? { headers: { [MEETING_PASS_HEADER]: pass } } : {}
4602
4618
  });
4603
4619
  if (!response.ok) {
@@ -4611,7 +4627,7 @@ function createMeetingSession(options) {
4611
4627
  throw new MeetError(
4612
4628
  "transport",
4613
4629
  refusal2?.reason ?? `meet.${intent}: the server answered ${response.status}`,
4614
- refusal2?.remedy ?? "You are still in the waiting room. Try again."
4630
+ refusal2?.remedy ?? (intent === "withdraw" ? "You are still in the waiting room. Try again." : "Nothing was changed. Try again.")
4615
4631
  );
4616
4632
  }
4617
4633
  }
@@ -4835,6 +4851,10 @@ function createMeetingSession(options) {
4835
4851
  store.dispatch({ type: "admission", admission: "withdrawn" });
4836
4852
  hint();
4837
4853
  },
4854
+ async hostLeave(intent) {
4855
+ if (intent === "end_for_all") await postLeave("end_for_all");
4856
+ else await postLeave("assign_and_leave", { assign_to: intent.assignTo });
4857
+ },
4838
4858
  async askAgain() {
4839
4859
  await options.rejoin?.();
4840
4860
  },
@@ -5295,18 +5315,6 @@ var ROOM_EVENTS = {
5295
5315
  wrapUp: "matrx.meet.wrap_up",
5296
5316
  /** The meeting ended for everyone. Payload `{ meeting_id, ended_at, trigger }`. */
5297
5317
  ended: "matrx.meet.ended",
5298
- /**
5299
- * A `meet_recordings` row changed state. Payload
5300
- * `{ meeting_id, recording: { id, state, started_by } }`.
5301
- *
5302
- * 🚨 THIS IS THE ONLY WAY A GUEST LEARNS A RECORDING IS RUNNING. PreJoin
5303
- * promises every joiner an indicator, and `anon` holds no SELECT grant on
5304
- * `meet_recordings` (MRI-A10 settled that widening it is forbidden), so
5305
- * without this packet the promise was a sentence with nothing behind it: on
5306
- * production 2026-09-09 the guest lane showed no indicator at all while the
5307
- * host recorded (MRI-C8).
5308
- */
5309
- recording: "matrx.meet.recording",
5310
5318
  /**
5311
5319
  * A chat message to everyone was SAVED (Meet wave 2). Payload
5312
5320
  * `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
@@ -5359,8 +5367,6 @@ function applyRoomEvent(args) {
5359
5367
  return applyWrapUp(store, payload, now);
5360
5368
  case ROOM_EVENTS.ended:
5361
5369
  return applyEnded(store, payload);
5362
- case ROOM_EVENTS.recording:
5363
- return applyRecording(store, payload);
5364
5370
  case ROOM_EVENTS.chat:
5365
5371
  return applyChat(store, payload);
5366
5372
  case ROOM_EVENTS.moderation:
@@ -5440,9 +5446,9 @@ function applyWrapUp(store, payload, now) {
5440
5446
  store.announceWrapUp(announcement);
5441
5447
  return HANDLED;
5442
5448
  }
5443
- function applyRecording(store, payload) {
5449
+ function applyRecordingState(store, payload) {
5444
5450
  const row = object(payload["recording"]);
5445
- if (row === null) return ignored("a recording event with no `recording` row");
5451
+ if (row === null) return ignored("room state carries no recording");
5446
5452
  const meetingId = text2(payload["meeting_id"]);
5447
5453
  const meeting = store.snapshot().meeting;
5448
5454
  if (meeting !== null && meetingId !== null && meetingId !== meeting.id) {
@@ -5524,19 +5530,6 @@ function applyModeration(store, payload, now) {
5524
5530
  }
5525
5531
  const kind = text2(payload["kind"]);
5526
5532
  switch (kind) {
5527
- case "lower-hands": {
5528
- const raw = payload["identities"];
5529
- const everyone = raw === "*";
5530
- const named = new Set(
5531
- Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
5532
- );
5533
- for (const participant of snapshot.participants) {
5534
- if (participant.handRaised && (everyone || named.has(participant.identity))) {
5535
- store.setHand(participant.identity, false);
5536
- }
5537
- }
5538
- return HANDLED;
5539
- }
5540
5533
  case "role": {
5541
5534
  const identity = text2(payload["identity"]);
5542
5535
  const role = text2(payload["role"]);
@@ -5630,7 +5623,6 @@ function noteTakerLabel(status) {
5630
5623
 
5631
5624
  // src/core/room-driver.ts
5632
5625
  var DATA_TOPICS = {
5633
- hand: "mx.hand",
5634
5626
  reaction: "mx.reaction",
5635
5627
  chat: "mx.chat"
5636
5628
  };
@@ -5654,6 +5646,11 @@ function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
5654
5646
  return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
5655
5647
  }
5656
5648
  var PARTICIPANT_ROLE_ATTRIBUTE = "matrx.participant.role";
5649
+ var PARTICIPANT_HAND_ATTRIBUTE = "matrx.hand";
5650
+ function handFrom(value) {
5651
+ const at = value === void 0 || value.length === 0 ? Number.NaN : Number(value);
5652
+ return Number.isFinite(at) ? { handRaised: true, handRaisedAt: at } : { handRaised: false, handRaisedAt: null };
5653
+ }
5657
5654
  var PARTICIPANT_CAN_RECORD_ATTRIBUTE = "matrx.participant.can_record";
5658
5655
  var CONNECTION_KIND_ATTRIBUTE = "matrx.participant.connection_kind";
5659
5656
  var PARTICIPANT_STANDING_ATTRIBUTE = "matrx.participant.standing";
@@ -5707,7 +5704,10 @@ function projectParticipant(driver, joinedAt) {
5707
5704
  cameraEnabled: driver.cameraEnabled,
5708
5705
  screenSharing: driver.screenSharing,
5709
5706
  speaking: false,
5710
- handRaised: false,
5707
+ // S8: a hand is the SERVER's (`hand_raised_at` on the person row, `/moderate raise_hand`),
5708
+ // replicated as the `matrx.hand` attribute: epoch ms of the raise, "" when down. It is
5709
+ // therefore delivered to late joiners and survives refresh and reconnect.
5710
+ ...handFrom(driver.attributes[PARTICIPANT_HAND_ATTRIBUTE]),
5711
5711
  quality: "unknown",
5712
5712
  joinedAt
5713
5713
  };
@@ -5837,40 +5837,38 @@ function createRoomEngine(options) {
5837
5837
  },
5838
5838
  participants(list) {
5839
5839
  const joinedAt = new Date(now()).toISOString();
5840
- const before = new Set(store.snapshot().participants.map((p) => p.identity));
5841
5840
  projectRoster(list, joinedAt);
5842
- const snapshot = store.snapshot();
5843
- const newcomer = snapshot.participants.some(
5844
- (participant) => !participant.isLocal && !before.has(participant.identity)
5845
- );
5846
- const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
5847
- if (newcomer && before.size > 0 && me?.handRaised === true) {
5848
- void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
5849
- () => void 0
5850
- );
5851
- }
5852
5841
  },
5853
5842
  participantLeft(who, reason) {
5854
5843
  departures.set(who, reason);
5855
5844
  },
5856
5845
  roomMetadata(metadata) {
5857
- let spotlight = null;
5846
+ let parsed = {};
5858
5847
  if (metadata.length > 0) {
5859
5848
  try {
5860
- const parsed = JSON.parse(metadata);
5861
- if (typeof parsed === "object" && parsed !== null) {
5862
- const value = parsed.spotlight;
5863
- if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
5864
- }
5849
+ const value = JSON.parse(metadata);
5850
+ if (typeof value === "object" && value !== null) parsed = value;
5865
5851
  } catch {
5866
5852
  report(
5867
5853
  "warn",
5868
- "meet: the room's metadata is not JSON, so no spotlight was read from it",
5869
- "Only aidream writes room metadata; check services/meet/moderation.py."
5854
+ "meet: the room's metadata is not JSON, so the room state was not read from it",
5855
+ "Only aidream writes room metadata; check services/meet/room_state.py."
5870
5856
  );
5871
5857
  }
5872
5858
  }
5873
- if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
5859
+ const key = (name) => {
5860
+ const value = parsed[name];
5861
+ return typeof value === "string" && value.length > 0 ? asParticipantIdentity(personOf(value)) : null;
5862
+ };
5863
+ const waiting = typeof parsed["waiting_count"] === "number" ? parsed["waiting_count"] : 0;
5864
+ const before = store.snapshot();
5865
+ const next = { spotlight: key("spotlight"), actingHost: key("acting_host"), presenter: key("presenter"), waitingCount: waiting };
5866
+ if (before.spotlight !== next.spotlight || before.actingHost !== next.actingHost || before.presenter !== next.presenter || before.waitingCount !== next.waitingCount) {
5867
+ store.patch(next);
5868
+ }
5869
+ if (parsed["recording"] !== void 0 && parsed["recording"] !== null) {
5870
+ applyRecordingState(store, { meeting_id: parsed["meeting_id"], recording: parsed["recording"] });
5871
+ }
5874
5872
  },
5875
5873
  activeSpeakers(identities) {
5876
5874
  store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
@@ -5972,13 +5970,6 @@ function createRoomEngine(options) {
5972
5970
  }
5973
5971
  const sender = personOf(from ?? "");
5974
5972
  switch (topic) {
5975
- case DATA_TOPICS.hand:
5976
- store.setHand(
5977
- sender,
5978
- envelope.raised === true,
5979
- typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
5980
- );
5981
- return;
5982
5973
  case DATA_TOPICS.reaction:
5983
5974
  if (typeof envelope.emoji === "string") {
5984
5975
  store.addReaction({
@@ -6077,6 +6068,7 @@ function createRoomEngine(options) {
6077
6068
  current = token;
6078
6069
  lastServerUrl = token.serverUrl;
6079
6070
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
6071
+ if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
6080
6072
  store.patch({
6081
6073
  localIdentity: token.identity,
6082
6074
  breakoutRoomId: token.breakoutRoomId ?? null,
@@ -6443,11 +6435,6 @@ function createRoomEngine(options) {
6443
6435
  dismissShareNotice() {
6444
6436
  if (store.snapshot().share.state === "refused") store.patch({ share: NO_SHARE });
6445
6437
  },
6446
- async raiseHand(raised) {
6447
- const local = store.snapshot().localIdentity;
6448
- if (local !== null) store.setHand(local, raised);
6449
- await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
6450
- },
6451
6438
  async react(emoji) {
6452
6439
  const local = store.snapshot().localIdentity;
6453
6440
  const id = `${local ?? "local"}-${now()}`;
@@ -6576,6 +6563,8 @@ var MODERATION_ACTIONS = [
6576
6563
  "ask_unmute",
6577
6564
  "ask_all_unmute",
6578
6565
  "spotlight",
6566
+ "present",
6567
+ "raise_hand",
6579
6568
  "lower_hand",
6580
6569
  "lower_all_hands",
6581
6570
  "make_cohost",
@@ -6619,7 +6608,8 @@ function createInRoomClient(options) {
6619
6608
  action: request.action,
6620
6609
  ...request.identity != null ? { identity: request.identity } : {},
6621
6610
  ...request.name !== void 0 ? { name: request.name } : {},
6622
- ...request.reason !== void 0 ? { reason: request.reason } : {}
6611
+ ...request.reason !== void 0 ? { reason: request.reason } : {},
6612
+ ...request.on !== void 0 ? { on: request.on } : {}
6623
6613
  },
6624
6614
  ...hdrs !== void 0 ? { headers: hdrs } : {}
6625
6615
  });
@@ -8473,16 +8463,20 @@ function useRoom() {
8473
8463
  async () => requireEngine(engine, "toggleCamera").setCameraEnabled(!(local?.cameraEnabled ?? false)),
8474
8464
  [engine, local]
8475
8465
  ),
8476
- toggleScreenShare: (0, import_react4.useCallback)(
8477
- async () => requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(
8478
- !(local?.screenSharing ?? false)
8479
- ),
8480
- [engine, local]
8481
- ),
8482
- toggleHand: (0, import_react4.useCallback)(
8483
- async () => requireEngine(engine, "toggleHand").raiseHand(!(local?.handRaised ?? false)),
8484
- [engine, local]
8485
- ),
8466
+ toggleScreenShare: (0, import_react4.useCallback)(async () => {
8467
+ const on = !(local?.screenSharing ?? false);
8468
+ await requireEngine(engine, "toggleScreenShare").setScreenShareEnabled(on);
8469
+ const sharing = host?.store.snapshot().participants.find((p) => p.isLocal)?.screenSharing ?? false;
8470
+ if (host !== null && sharing === on) {
8471
+ await host.inRoom.moderate({ action: "present", on }).catch(() => void 0);
8472
+ }
8473
+ }, [engine, host, local]),
8474
+ // S8: a hand is SERVER state (`/moderate raise_hand · lower_hand`, server time), replicated
8475
+ // back as the `matrx.hand` attribute — no client sets its own hand on the wire.
8476
+ toggleHand: (0, import_react4.useCallback)(async () => {
8477
+ if (host === null) throw noRuntime("toggleHand");
8478
+ await host.inRoom.moderate({ action: local?.handRaised === true ? "lower_hand" : "raise_hand" });
8479
+ }, [host, local]),
8486
8480
  react: (0, import_react4.useCallback)(
8487
8481
  async (emoji) => requireEngine(engine, "react").react(emoji),
8488
8482
  [engine]
@@ -8664,6 +8658,31 @@ function useWaiting() {
8664
8658
  askAgain: (0, import_react4.useCallback)(async () => requireSession(session, "askAgain").askAgain(), [session])
8665
8659
  };
8666
8660
  }
8661
+ function useLeave() {
8662
+ const host = useMeetHost();
8663
+ const snapshot = useMeetSnapshot();
8664
+ const session = host?.meetings ?? null;
8665
+ const engine = host?.engine ?? null;
8666
+ const policy = snapshot?.leavePolicy ?? DEFAULT_LEAVE_POLICY;
8667
+ const participants = snapshot?.participants ?? [];
8668
+ const me = participants.find((p) => p.isLocal);
8669
+ const candidates = participants.filter((p) => !p.isLocal && !p.isAgent && p.role !== "viewer");
8670
+ const choices = leaveChoices({ policy, role: me?.role ?? null, othersPresent: candidates.length > 0 });
8671
+ return {
8672
+ choices,
8673
+ policy,
8674
+ candidates,
8675
+ leave: (0, import_react4.useCallback)(async () => requireEngine(engine, "leave").leave(), [engine]),
8676
+ endForAll: (0, import_react4.useCallback)(async () => requireSession(session, "endForAll").hostLeave("end_for_all"), [session]),
8677
+ assignAndLeave: (0, import_react4.useCallback)(
8678
+ async (person) => {
8679
+ await requireSession(session, "assignAndLeave").hostLeave({ assignTo: String(person.identity) });
8680
+ await requireEngine(engine, "leave").leave();
8681
+ },
8682
+ [session, engine]
8683
+ )
8684
+ };
8685
+ }
8667
8686
  function useRecording() {
8668
8687
  const host = useMeetHost();
8669
8688
  const snapshot = useMeetSnapshot();
@@ -11587,19 +11606,75 @@ function ControlBar({ onLeave }) {
11587
11606
  ] }) : null
11588
11607
  ] }) }) : null
11589
11608
  ] }),
11590
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
11591
- ControlButton,
11609
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LeaveControl, { onLeave })
11610
+ ] })
11611
+ ] });
11612
+ }
11613
+ var LEAVE_WORDS = {
11614
+ leave_or_end: { leave: "Leave call", assign_and_leave: "Assign a new host", end_for_all: "End call for everyone" },
11615
+ must_assign_or_end: { leave: "Leave meeting", assign_and_leave: "Assign a new host", end_for_all: "End meeting for all" },
11616
+ leave_only: { leave: "Leave", assign_and_leave: "Assign a new host", end_for_all: "End meeting" }
11617
+ };
11618
+ function LeaveControl({ onLeave }) {
11619
+ const leave = useLeave();
11620
+ const [open, setOpen] = (0, import_react10.useState)("none");
11621
+ const [failure, setFailure] = (0, import_react10.useState)(null);
11622
+ const words = LEAVE_WORDS[leave.policy.hostLeavePrompt];
11623
+ const run = (work, leaving) => {
11624
+ setFailure(null);
11625
+ void work().then(() => {
11626
+ setOpen("none");
11627
+ if (leaving) onLeave?.();
11628
+ }).catch((error) => setFailure(error instanceof Error ? error.message : String(error)));
11629
+ };
11630
+ const leaveNow = () => run(leave.leave, true);
11631
+ const endNow = () => run(leave.endForAll, false);
11632
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
11633
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
11634
+ ControlButton,
11635
+ {
11636
+ label: "Leave",
11637
+ danger: true,
11638
+ active: open !== "none",
11639
+ onClick: () => {
11640
+ if (leave.choices.length === 1 && leave.choices[0] === "leave") leaveNow();
11641
+ else setOpen(open === "none" ? "menu" : "none");
11642
+ },
11643
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PhoneOffIcon, {})
11644
+ }
11645
+ ),
11646
+ open !== "none" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Popover, { label: open === "assign" ? words.assign_and_leave : "Leave", onClose: () => setOpen("none"), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__menu", "data-meet-leave-menu": leave.policy.hostLeavePrompt, children: [
11647
+ open === "menu" ? leave.choices.map((choice) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
11648
+ "button",
11592
11649
  {
11593
- label: "Leave",
11594
- danger: true,
11650
+ type: "button",
11651
+ className: choice === "end_for_all" ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
11595
11652
  onClick: () => {
11596
- void room.leave();
11597
- onLeave?.();
11653
+ if (choice === "leave") leaveNow();
11654
+ else if (choice === "assign_and_leave") setOpen("assign");
11655
+ else if (leave.policy.endConfirm) setOpen("confirm");
11656
+ else endNow();
11598
11657
  },
11599
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PhoneOffIcon, {})
11600
- }
11601
- )
11602
- ] })
11658
+ children: words[choice]
11659
+ },
11660
+ choice
11661
+ )) : null,
11662
+ open === "assign" ? leave.candidates.map((person) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
11663
+ "button",
11664
+ {
11665
+ type: "button",
11666
+ className: "mx-meet__menu-item",
11667
+ onClick: () => run(() => leave.assignAndLeave(person), true),
11668
+ children: person.displayName
11669
+ },
11670
+ String(person.identity)
11671
+ )) : null,
11672
+ open === "confirm" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
11673
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__menu-item mx-meet__menu-item--danger", onClick: endNow, children: words.end_for_all }),
11674
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__menu-item", onClick: () => setOpen("menu"), children: "Cancel" })
11675
+ ] }) : null,
11676
+ failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__menu-note", role: "alert", children: failure }) : null
11677
+ ] }) }) : null
11603
11678
  ] });
11604
11679
  }
11605
11680
  function useUnreadChat(open) {