@ksp-gonogo/kerbcast-react 1.5.0 → 1.5.1
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/CameraFeed.js +8 -4
- package/package.json +1 -1
- package/src/CameraFeed.test.tsx +33 -0
- package/src/CameraFeed.tsx +25 -21
package/dist/CameraFeed.js
CHANGED
|
@@ -512,7 +512,7 @@ const CameraFeedInner = forwardRef(function CameraFeedInner({ flightId: requeste
|
|
|
512
512
|
void client.camera(flightId).setQuality(preset);
|
|
513
513
|
closeQualityMenu();
|
|
514
514
|
}, [client, flightId, closeQualityMenu]);
|
|
515
|
-
const topOverlay = (_jsxs(TopOverlay, { children: [
|
|
515
|
+
const topOverlay = (_jsxs(TopOverlay, { children: [_jsx(TitleRow, { children: _jsx(TopTitle, { children: hasCameras ? (_jsxs(TitleButton, { ref: cameraMenu.triggerRef, type: "button", "aria-haspopup": "menu", "aria-expanded": cameraMenu.open, "aria-controls": menuId, onClick: cameraMenu.toggle, children: [_jsx(TitleButton__Text, { children: title }), _jsx(ChevronDownIcon, { "aria-hidden": "true" })] })) : (title) }) }), cameraMenu.open &&
|
|
516
516
|
hasCameras &&
|
|
517
517
|
cameraMenu.position &&
|
|
518
518
|
createPortal(_jsx(CameraMenu, { ref: cameraMenu.menuRef, id: menuId, role: "menu", "aria-label": "Camera", style: cameraMenu.position, children: cameras.map((c) => (_jsxs(CameraMenuItem, { type: "button", role: "menuitemradio", "aria-checked": c.flightId === flightId, "$selected": c.flightId === flightId, onClick: () => {
|
|
@@ -522,10 +522,11 @@ const CameraFeedInner = forwardRef(function CameraFeedInner({ flightId: requeste
|
|
|
522
522
|
(camera ? (_jsxs(TopMeta, { children: [camera.vesselName, " \u00B7 ", camera.renderWidth, "\u00D7", camera.renderHeight, bitrateLabel, adaptiveLabel] })) : (_jsx(TopMeta, { children: "no cameras on this vessel" })))] }));
|
|
523
523
|
const renderAction = (a) => (_jsx(OverlayIconButton, { type: "button", "aria-label": a.label, "aria-pressed": a.active ?? undefined, title: a.label, "$active": a.active ?? false, onClick: a.onClick, children: a.icon }, a.id));
|
|
524
524
|
const builtInActions = flightId !== null && (pipAvailable || fullscreenAvailable || qualityAvailable);
|
|
525
|
-
const hasActionBar =
|
|
525
|
+
const hasActionBar = hasCameras ||
|
|
526
|
+
(actions && actions.length > 0) ||
|
|
526
527
|
(trailingActions && trailingActions.length > 0) ||
|
|
527
528
|
builtInActions;
|
|
528
|
-
const actionBar = hasActionBar ? (_jsxs(ActionBar, { children: [actions?.map(renderAction), qualityAvailable && (_jsxs(OverlayIconButton, { ref: qualityMenu.triggerRef, type: "button", "aria-label": "Quality", "aria-haspopup": "menu", "aria-expanded": qualityMenu.open, "aria-controls": qualityMenuId, title: qualityThrottled
|
|
529
|
+
const actionBar = hasActionBar ? (_jsxs(ActionBar, { children: [hasCameras && (_jsxs(StepButtons, { children: [_jsx(OverlayIconButton, { type: "button", "aria-label": "Previous camera", disabled: !canStep, onClick: () => stepCamera(-1), children: "\u2039" }), _jsx(OverlayIconButton, { type: "button", "aria-label": "Next camera", disabled: !canStep, onClick: () => stepCamera(1), children: "\u203A" })] })), actions?.map(renderAction), qualityAvailable && (_jsxs(OverlayIconButton, { ref: qualityMenu.triggerRef, type: "button", "aria-label": "Quality", "aria-haspopup": "menu", "aria-expanded": qualityMenu.open, "aria-controls": qualityMenuId, title: qualityThrottled
|
|
529
530
|
? "Quality (throttled by adaptive performance)"
|
|
530
531
|
: "Quality", "$active": qualityMenu.open, onClick: qualityMenu.toggle, children: [_jsx(QualityIcon, {}), qualityThrottled && _jsx(ThrottledDot, { "aria-hidden": "true" })] })), flightId !== null && pipAvailable && (_jsx(OverlayIconButton, { type: "button", "aria-label": isPip ? "Exit picture in picture" : "Picture in picture", "aria-pressed": isPip, title: isPip ? "Exit picture in picture" : "Picture in picture", "$active": isPip, onClick: togglePip, children: _jsx(PictureInPictureIcon, {}) })), flightId !== null && fullscreenAvailable && (_jsx(OverlayIconButton, { type: "button", "aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen", "aria-pressed": isFullscreen, title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen", "$active": isFullscreen, onClick: toggleFullscreen, children: isFullscreen ? _jsx(FullscreenExitIcon, {}) : _jsx(FullscreenEnterIcon, {}) })), trailingActions?.map(renderAction)] })) : null;
|
|
531
532
|
return (_jsx(Stage, { ref: wrapRef, "$pinned": chromePinned, children: flightId === null ? (_jsxs(_Fragment, { children: [_jsx(Empty, { children: emptyMessage }), topOverlay, actionBar] })) : (_jsxs(_Fragment, { children: [_jsx(StyledVideo, { ref: videoRef, autoPlay: true, playsInline: true, muted: true, controls: false, onClick: () => setChromePinned((v) => !v) }), topOverlay, actionBar, qualityAvailable &&
|
|
@@ -746,8 +747,11 @@ const TopOverlay = styled.div `
|
|
|
746
747
|
const TitleRow = styled.div `
|
|
747
748
|
display: flex;
|
|
748
749
|
align-items: flex-start;
|
|
749
|
-
justify-content: space-between;
|
|
750
750
|
gap: 8px;
|
|
751
|
+
/* Reserve the top-right corner where the ActionBar overlay sits (now home
|
|
752
|
+
to the camera step buttons too) so a long title ellipsizes rather than
|
|
753
|
+
sliding underneath the controls. */
|
|
754
|
+
padding-right: 96px;
|
|
751
755
|
`;
|
|
752
756
|
const TopTitle = styled.h3 `
|
|
753
757
|
margin: 0;
|
package/package.json
CHANGED
package/src/CameraFeed.test.tsx
CHANGED
|
@@ -445,6 +445,39 @@ describe("CameraFeed - camera selection", () => {
|
|
|
445
445
|
).toBe(true);
|
|
446
446
|
});
|
|
447
447
|
|
|
448
|
+
it("renders step buttons inside the action bar alongside injected actions", async () => {
|
|
449
|
+
// Regression: the step buttons used to sit in the title row and were
|
|
450
|
+
// covered by the separately-positioned action bar (and any consumer
|
|
451
|
+
// action buttons) in the top-right corner. They now share the action
|
|
452
|
+
// bar so the two lay out in one flex row instead of overlapping.
|
|
453
|
+
const { client } = await buildConnectedSource(THREE_CAMERAS);
|
|
454
|
+
|
|
455
|
+
render(
|
|
456
|
+
<KerbcastProvider client={client}>
|
|
457
|
+
<CameraFeed
|
|
458
|
+
flightId={42}
|
|
459
|
+
actions={[
|
|
460
|
+
{
|
|
461
|
+
id: "custom",
|
|
462
|
+
label: "Custom action",
|
|
463
|
+
icon: <span>x</span>,
|
|
464
|
+
onClick: () => {},
|
|
465
|
+
},
|
|
466
|
+
]}
|
|
467
|
+
/>
|
|
468
|
+
</KerbcastProvider>,
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
const next = screen.getByRole("button", { name: /next camera/i });
|
|
472
|
+
const custom = screen.getByRole("button", { name: /custom action/i });
|
|
473
|
+
|
|
474
|
+
// The injected action's parent is the action bar; the step buttons live
|
|
475
|
+
// in a wrapper within that same action bar, so it contains both.
|
|
476
|
+
const actionBar = custom.parentElement;
|
|
477
|
+
expect(actionBar).not.toBeNull();
|
|
478
|
+
expect(actionBar?.contains(next)).toBe(true);
|
|
479
|
+
});
|
|
480
|
+
|
|
448
481
|
it("Escape closes the open camera menu", async () => {
|
|
449
482
|
const { client } = await buildConnectedSource(THREE_CAMERAS);
|
|
450
483
|
|
package/src/CameraFeed.tsx
CHANGED
|
@@ -796,26 +796,6 @@ const CameraFeedInner = forwardRef<CameraFeedHandle, CameraFeedProps>(
|
|
|
796
796
|
title
|
|
797
797
|
)}
|
|
798
798
|
</TopTitle>
|
|
799
|
-
{hasCameras && (
|
|
800
|
-
<StepButtons>
|
|
801
|
-
<OverlayIconButton
|
|
802
|
-
type="button"
|
|
803
|
-
aria-label="Previous camera"
|
|
804
|
-
disabled={!canStep}
|
|
805
|
-
onClick={() => stepCamera(-1)}
|
|
806
|
-
>
|
|
807
|
-
‹
|
|
808
|
-
</OverlayIconButton>
|
|
809
|
-
<OverlayIconButton
|
|
810
|
-
type="button"
|
|
811
|
-
aria-label="Next camera"
|
|
812
|
-
disabled={!canStep}
|
|
813
|
-
onClick={() => stepCamera(1)}
|
|
814
|
-
>
|
|
815
|
-
›
|
|
816
|
-
</OverlayIconButton>
|
|
817
|
-
</StepButtons>
|
|
818
|
-
)}
|
|
819
799
|
</TitleRow>
|
|
820
800
|
|
|
821
801
|
{cameraMenu.open &&
|
|
@@ -879,11 +859,32 @@ const CameraFeedInner = forwardRef<CameraFeedHandle, CameraFeedProps>(
|
|
|
879
859
|
const builtInActions =
|
|
880
860
|
flightId !== null && (pipAvailable || fullscreenAvailable || qualityAvailable);
|
|
881
861
|
const hasActionBar =
|
|
862
|
+
hasCameras ||
|
|
882
863
|
(actions && actions.length > 0) ||
|
|
883
864
|
(trailingActions && trailingActions.length > 0) ||
|
|
884
865
|
builtInActions;
|
|
885
866
|
const actionBar = hasActionBar ? (
|
|
886
867
|
<ActionBar>
|
|
868
|
+
{hasCameras && (
|
|
869
|
+
<StepButtons>
|
|
870
|
+
<OverlayIconButton
|
|
871
|
+
type="button"
|
|
872
|
+
aria-label="Previous camera"
|
|
873
|
+
disabled={!canStep}
|
|
874
|
+
onClick={() => stepCamera(-1)}
|
|
875
|
+
>
|
|
876
|
+
‹
|
|
877
|
+
</OverlayIconButton>
|
|
878
|
+
<OverlayIconButton
|
|
879
|
+
type="button"
|
|
880
|
+
aria-label="Next camera"
|
|
881
|
+
disabled={!canStep}
|
|
882
|
+
onClick={() => stepCamera(1)}
|
|
883
|
+
>
|
|
884
|
+
›
|
|
885
|
+
</OverlayIconButton>
|
|
886
|
+
</StepButtons>
|
|
887
|
+
)}
|
|
887
888
|
{actions?.map(renderAction)}
|
|
888
889
|
{qualityAvailable && (
|
|
889
890
|
<OverlayIconButton
|
|
@@ -1393,8 +1394,11 @@ const TopOverlay = styled.div`
|
|
|
1393
1394
|
const TitleRow = styled.div`
|
|
1394
1395
|
display: flex;
|
|
1395
1396
|
align-items: flex-start;
|
|
1396
|
-
justify-content: space-between;
|
|
1397
1397
|
gap: 8px;
|
|
1398
|
+
/* Reserve the top-right corner where the ActionBar overlay sits (now home
|
|
1399
|
+
to the camera step buttons too) so a long title ellipsizes rather than
|
|
1400
|
+
sliding underneath the controls. */
|
|
1401
|
+
padding-right: 96px;
|
|
1398
1402
|
`;
|
|
1399
1403
|
|
|
1400
1404
|
const TopTitle = styled.h3`
|