@tastic/hud 0.20.0 → 0.21.0
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/chrome/index.js +2 -2
- package/dist/chrome/index.mjs +1 -1
- package/dist/{chunk-DK3HOZC5.mjs → chunk-UOA35OWJ.mjs} +4 -4
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +80 -70
- package/dist/index.mjs +61 -52
- package/package.json +10 -10
- package/src/BaseStatsScreen.tsx +7 -5
- package/src/PlayerSetupPanel.tsx +77 -56
- package/src/index.ts +1 -1
package/dist/chrome/index.js
CHANGED
|
@@ -740,7 +740,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
|
|
|
740
740
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
|
|
741
741
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper9.Text, { variant: titleVariant, style: [styles10.title, { color: fg }], children: title })
|
|
742
742
|
] }),
|
|
743
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
743
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper9.FadingScrollView, { style: styles10.scrollView, contentContainerStyle: [styles10.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
|
|
744
744
|
children,
|
|
745
745
|
showReset && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles10.resetButton, children: resetLabel })
|
|
746
746
|
] }),
|
|
@@ -781,7 +781,7 @@ var styles10 = import_react_native13.StyleSheet.create({
|
|
|
781
781
|
resetButton: {
|
|
782
782
|
marginTop: 16
|
|
783
783
|
},
|
|
784
|
-
// Bounds the
|
|
784
|
+
// Bounds the scroller (FadingScrollView puts it on its outer box) to the space `container`'s flex:1 actually gives it - without this, a
|
|
785
785
|
// ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
|
|
786
786
|
// can render at its full unclipped content height instead of the screen's), which leaves the
|
|
787
787
|
// header's back button touch target unreliable underneath it.
|
package/dist/chrome/index.mjs
CHANGED
|
@@ -539,11 +539,11 @@ var styles7 = StyleSheet7.create({
|
|
|
539
539
|
});
|
|
540
540
|
|
|
541
541
|
// src/BaseStatsScreen.tsx
|
|
542
|
-
import { useAutoPaperTheme as useAutoPaperTheme7 } from "@rific/auto-paper";
|
|
542
|
+
import { FadingScrollView as FadingScrollView2, useAutoPaperTheme as useAutoPaperTheme7 } from "@rific/auto-paper";
|
|
543
543
|
import { Button as Button3, IconButton } from "@rific/feedback-press";
|
|
544
544
|
import { useRotation as useRotation3 } from "@tastic/core";
|
|
545
545
|
import { useState as useState4 } from "react";
|
|
546
|
-
import {
|
|
546
|
+
import { StyleSheet as StyleSheet8, View as View7 } from "react-native";
|
|
547
547
|
import { Text as Text7 } from "react-native-paper";
|
|
548
548
|
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
549
549
|
function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, backLabel = "Back", hitSlop, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", fg: fgOverride, bg: bgOverride, children }) {
|
|
@@ -559,7 +559,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
|
|
|
559
559
|
/* @__PURE__ */ jsx9(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
|
|
560
560
|
/* @__PURE__ */ jsx9(Text7, { variant: titleVariant, style: [styles8.title, { color: fg }], children: title })
|
|
561
561
|
] }),
|
|
562
|
-
/* @__PURE__ */ jsxs8(
|
|
562
|
+
/* @__PURE__ */ jsxs8(FadingScrollView2, { style: styles8.scrollView, contentContainerStyle: [styles8.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
|
|
563
563
|
children,
|
|
564
564
|
showReset && /* @__PURE__ */ jsx9(Button3, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles8.resetButton, children: resetLabel })
|
|
565
565
|
] }),
|
|
@@ -600,7 +600,7 @@ var styles8 = StyleSheet8.create({
|
|
|
600
600
|
resetButton: {
|
|
601
601
|
marginTop: 16
|
|
602
602
|
},
|
|
603
|
-
// Bounds the
|
|
603
|
+
// Bounds the scroller (FadingScrollView puts it on its outer box) to the space `container`'s flex:1 actually gives it - without this, a
|
|
604
604
|
// ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
|
|
605
605
|
// can render at its full unclipped content height instead of the screen's), which leaves the
|
|
606
606
|
// header's back button touch target unreliable underneath it.
|
package/dist/index.d.mts
CHANGED
|
@@ -178,6 +178,7 @@ declare function LabeledDropdown<T extends string>({ id, host, options, value, o
|
|
|
178
178
|
declare function loadSkiaWeb(): Promise<void>;
|
|
179
179
|
|
|
180
180
|
declare function getColorPopoverId(idPrefix: string): string;
|
|
181
|
+
declare function getProfilePopoverId(idPrefix: string): string;
|
|
181
182
|
declare function getCpuDifficultyPopoverId(idPrefix: string): string;
|
|
182
183
|
interface PlayerSetupPanelProps<C extends string = string> {
|
|
183
184
|
idPrefix: string;
|
|
@@ -189,6 +190,8 @@ interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
189
190
|
swatches: SeedColor[];
|
|
190
191
|
takenColor?: string;
|
|
191
192
|
allowSwapTaken?: boolean;
|
|
193
|
+
colorCaption?: string;
|
|
194
|
+
showColor?: boolean;
|
|
192
195
|
colorAlignOverride?: AlignResult;
|
|
193
196
|
colorRotation?: PopoverRotation;
|
|
194
197
|
isHuman: boolean;
|
|
@@ -207,11 +210,10 @@ interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
207
210
|
cpuDifficulty?: C;
|
|
208
211
|
cpuDifficultyOptions?: LabeledDropdownOption<C>[];
|
|
209
212
|
onCpuDifficultyChange?: (value: C) => void;
|
|
210
|
-
cpuDifficultyAlignOverride?: AlignResult;
|
|
211
213
|
humanIcon?: string;
|
|
212
214
|
cpuIcon?: string;
|
|
213
215
|
}
|
|
214
|
-
declare function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange,
|
|
216
|
+
declare function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorCaption, showColor, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, humanIcon, cpuIcon }: PlayerSetupPanelProps<C>): react.JSX.Element;
|
|
215
217
|
|
|
216
218
|
interface Props$3 {
|
|
217
219
|
visible: boolean;
|
|
@@ -277,4 +279,4 @@ declare function useQuitConfirmation(hasProgress: () => boolean, onConfirmedBack
|
|
|
277
279
|
|
|
278
280
|
declare function useZoneClampedAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): ReturnType<typeof useAutoAlign>;
|
|
279
281
|
|
|
280
|
-
export { AchievementUnlockList, type AchievementUnlockOwner, type AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getInlineColorPickerContentSize, getLabeledDropdownContentHeight, loadSkiaWeb, useAutoAlign, usePopoverHost, useQuitConfirmation, useZoneClampedAlign };
|
|
282
|
+
export { AchievementUnlockList, type AchievementUnlockOwner, type AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getInlineColorPickerContentSize, getLabeledDropdownContentHeight, getProfilePopoverId, loadSkiaWeb, useAutoAlign, usePopoverHost, useQuitConfirmation, useZoneClampedAlign };
|
package/dist/index.d.ts
CHANGED
|
@@ -178,6 +178,7 @@ declare function LabeledDropdown<T extends string>({ id, host, options, value, o
|
|
|
178
178
|
declare function loadSkiaWeb(): Promise<void>;
|
|
179
179
|
|
|
180
180
|
declare function getColorPopoverId(idPrefix: string): string;
|
|
181
|
+
declare function getProfilePopoverId(idPrefix: string): string;
|
|
181
182
|
declare function getCpuDifficultyPopoverId(idPrefix: string): string;
|
|
182
183
|
interface PlayerSetupPanelProps<C extends string = string> {
|
|
183
184
|
idPrefix: string;
|
|
@@ -189,6 +190,8 @@ interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
189
190
|
swatches: SeedColor[];
|
|
190
191
|
takenColor?: string;
|
|
191
192
|
allowSwapTaken?: boolean;
|
|
193
|
+
colorCaption?: string;
|
|
194
|
+
showColor?: boolean;
|
|
192
195
|
colorAlignOverride?: AlignResult;
|
|
193
196
|
colorRotation?: PopoverRotation;
|
|
194
197
|
isHuman: boolean;
|
|
@@ -207,11 +210,10 @@ interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
207
210
|
cpuDifficulty?: C;
|
|
208
211
|
cpuDifficultyOptions?: LabeledDropdownOption<C>[];
|
|
209
212
|
onCpuDifficultyChange?: (value: C) => void;
|
|
210
|
-
cpuDifficultyAlignOverride?: AlignResult;
|
|
211
213
|
humanIcon?: string;
|
|
212
214
|
cpuIcon?: string;
|
|
213
215
|
}
|
|
214
|
-
declare function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange,
|
|
216
|
+
declare function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorCaption, showColor, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, humanIcon, cpuIcon }: PlayerSetupPanelProps<C>): react.JSX.Element;
|
|
215
217
|
|
|
216
218
|
interface Props$3 {
|
|
217
219
|
visible: boolean;
|
|
@@ -277,4 +279,4 @@ declare function useQuitConfirmation(hasProgress: () => boolean, onConfirmedBack
|
|
|
277
279
|
|
|
278
280
|
declare function useZoneClampedAlign(open: boolean, contentWidth: number, contentHeight: number, rotation?: PopoverRotation): ReturnType<typeof useAutoAlign>;
|
|
279
281
|
|
|
280
|
-
export { AchievementUnlockList, type AchievementUnlockOwner, type AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getInlineColorPickerContentSize, getLabeledDropdownContentHeight, loadSkiaWeb, useAutoAlign, usePopoverHost, useQuitConfirmation, useZoneClampedAlign };
|
|
282
|
+
export { AchievementUnlockList, type AchievementUnlockOwner, type AlignResult, ControlSchemePicker, type ControlSchemePickerProps, type CornerStatusBadgeSeat, CornerStatusBadges, type CornerStatusBadgesProps, InlineColorPicker, LABELED_DROPDOWN_POPOVER_WIDTH, LabeledDropdown, type LabeledDropdownOption, type MenuOption, type MenuSection, type MultiSelectSection, type OrientationMode, PlayerSetupPanel, type PlayerSetupPanelProps, type PopoverAlign, PopoverBody, type PopoverHost, type PopoverRotation, type PopoverVerticalAlign, PressAwayOverlay, type QuitConfirmation, ReadyButton, SectionedDropdown, SharedActionBand, type SingleSelectSection, TriggerGaugeHost, type TriggerGaugeProps, type UnlockedAchievement, getColorPopoverId, getCpuDifficultyPopoverId, getInlineColorPickerContentSize, getLabeledDropdownContentHeight, getProfilePopoverId, loadSkiaWeb, useAutoAlign, usePopoverHost, useQuitConfirmation, useZoneClampedAlign };
|
package/dist/index.js
CHANGED
|
@@ -204,6 +204,7 @@ __export(index_exports, {
|
|
|
204
204
|
getCpuDifficultyPopoverId: () => getCpuDifficultyPopoverId,
|
|
205
205
|
getInlineColorPickerContentSize: () => getInlineColorPickerContentSize,
|
|
206
206
|
getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
|
|
207
|
+
getProfilePopoverId: () => getProfilePopoverId,
|
|
207
208
|
getStaggeredWordDuration: () => getStaggeredWordDuration,
|
|
208
209
|
isCompactDialogHeight: () => isCompactDialogHeight,
|
|
209
210
|
loadSkiaWeb: () => loadSkiaWeb,
|
|
@@ -944,7 +945,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
|
|
|
944
945
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
|
|
945
946
|
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper10.Text, { variant: titleVariant, style: [styles11.title, { color: fg }], children: title })
|
|
946
947
|
] }),
|
|
947
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_auto_paper10.FadingScrollView, { style: styles11.scrollView, contentContainerStyle: [styles11.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
|
|
948
949
|
children,
|
|
949
950
|
showReset && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles11.resetButton, children: resetLabel })
|
|
950
951
|
] }),
|
|
@@ -985,7 +986,7 @@ var styles11 = import_react_native14.StyleSheet.create({
|
|
|
985
986
|
resetButton: {
|
|
986
987
|
marginTop: 16
|
|
987
988
|
},
|
|
988
|
-
// Bounds the
|
|
989
|
+
// Bounds the scroller (FadingScrollView puts it on its outer box) to the space `container`'s flex:1 actually gives it - without this, a
|
|
989
990
|
// ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
|
|
990
991
|
// can render at its full unclipped content height instead of the screen's), which leaves the
|
|
991
992
|
// header's back button touch target unreliable underneath it.
|
|
@@ -1668,7 +1669,7 @@ function loadSkiaWeb() {
|
|
|
1668
1669
|
|
|
1669
1670
|
// src/PlayerSetupPanel.tsx
|
|
1670
1671
|
var import_profile2 = require("@tastic/profile");
|
|
1671
|
-
var
|
|
1672
|
+
var import_react_native27 = require("react-native");
|
|
1672
1673
|
var import_react_native_paper16 = require("react-native-paper");
|
|
1673
1674
|
|
|
1674
1675
|
// src/ReadyButton.tsx
|
|
@@ -1705,32 +1706,79 @@ function usePopoverHost() {
|
|
|
1705
1706
|
return { openId, toggle, close };
|
|
1706
1707
|
}
|
|
1707
1708
|
|
|
1709
|
+
// src/useZoneClampedAlign.ts
|
|
1710
|
+
var import_split_screen = require("@tastic/split-screen");
|
|
1711
|
+
var import_react12 = require("react");
|
|
1712
|
+
var import_react_native26 = require("react-native");
|
|
1713
|
+
var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
|
|
1714
|
+
var EDGE_MARGIN2 = 12;
|
|
1715
|
+
function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
1716
|
+
const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
|
|
1717
|
+
const zone = (0, import_split_screen.useZoneBounds)();
|
|
1718
|
+
const { height: windowHeight } = (0, import_react_native26.useWindowDimensions)();
|
|
1719
|
+
const insets = (0, import_react_native_safe_area_context3.useSafeAreaInsets)();
|
|
1720
|
+
const shouldMeasure = open && auto.measured && zone !== null;
|
|
1721
|
+
const [ownBounds, setOwnBounds] = (0, import_react12.useState)(null);
|
|
1722
|
+
const [prevShouldMeasure, setPrevShouldMeasure] = (0, import_react12.useState)(shouldMeasure);
|
|
1723
|
+
if (shouldMeasure !== prevShouldMeasure) {
|
|
1724
|
+
setPrevShouldMeasure(shouldMeasure);
|
|
1725
|
+
setOwnBounds(null);
|
|
1726
|
+
}
|
|
1727
|
+
(0, import_react12.useEffect)(() => {
|
|
1728
|
+
if (!shouldMeasure) return;
|
|
1729
|
+
auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
|
|
1730
|
+
const belowShared = zone.zoneSide === "belowShared";
|
|
1731
|
+
const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
|
|
1732
|
+
const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
|
|
1733
|
+
const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
|
|
1734
|
+
let awaySide = belowShared ? "below" : "above";
|
|
1735
|
+
let towardSide = belowShared ? "above" : "below";
|
|
1736
|
+
if (zone.rotated) {
|
|
1737
|
+
awaySide = awaySide === "above" ? "below" : "above";
|
|
1738
|
+
towardSide = towardSide === "above" ? "below" : "above";
|
|
1739
|
+
}
|
|
1740
|
+
setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
|
|
1741
|
+
});
|
|
1742
|
+
}, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
|
|
1743
|
+
if (!zone) return auto;
|
|
1744
|
+
if (!ownBounds) return { ...auto, measured: false };
|
|
1745
|
+
return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1708
1748
|
// src/PlayerSetupPanel.tsx
|
|
1709
1749
|
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1710
1750
|
function getColorPopoverId(idPrefix) {
|
|
1711
1751
|
return `${idPrefix}-color`;
|
|
1712
1752
|
}
|
|
1753
|
+
function getProfilePopoverId(idPrefix) {
|
|
1754
|
+
return `${idPrefix}-profile`;
|
|
1755
|
+
}
|
|
1713
1756
|
function getCpuDifficultyPopoverId(idPrefix) {
|
|
1714
1757
|
return `${idPrefix}-difficulty`;
|
|
1715
1758
|
}
|
|
1716
|
-
function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange,
|
|
1759
|
+
function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorCaption, showColor = true, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, humanIcon = "face-man", cpuIcon = "robot" }) {
|
|
1717
1760
|
const ownHost = usePopoverHost();
|
|
1718
1761
|
const popover = host ?? ownHost;
|
|
1719
1762
|
const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false;
|
|
1720
1763
|
const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || secondPickerId !== void 0 && popover.openId === secondPickerId;
|
|
1721
1764
|
const showProfilePicker = isHuman && profiles !== void 0 && !!onProfileSelect;
|
|
1722
1765
|
const showCpuDifficulty = !isHuman && cpuDifficulty !== void 0 && cpuDifficultyOptions !== void 0 && !!onCpuDifficultyChange;
|
|
1766
|
+
const seatButton = showProfilePicker || showCpuDifficulty;
|
|
1723
1767
|
const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null;
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1768
|
+
const colorSection = showColor ? { value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, caption: showProfilePicker && selectedProfile ? colorCaption : void 0 } : void 0;
|
|
1769
|
+
const seatPickerId = showProfilePicker ? getProfilePopoverId(idPrefix) : getCpuDifficultyPopoverId(idPrefix);
|
|
1770
|
+
const seatPickerSize = showProfilePicker ? (0, import_profile2.getProfilePickerContentSize)(profiles.length, !!onManageProfiles, colorSection) : (0, import_profile2.getOptionPickerContentSize)(cpuDifficultyOptions?.length ?? 0, colorSection);
|
|
1771
|
+
const seatPickerAlign = useZoneClampedAlign(seatButton && popover.openId === seatPickerId, seatPickerSize.width, seatPickerSize.height);
|
|
1772
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react_native27.View, { style: [styles21.panel, ownPopoverOpen && styles21.panelOpen], children: [
|
|
1773
|
+
showProfilePicker ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_profile2.ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles, colorSection, alignOverride: seatPickerAlign }) : showCpuDifficulty ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_profile2.OptionPicker, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, icon: cpuIcon, colorSection, alignOverride: seatPickerAlign }) : label && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_react_native_paper16.Text, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
|
|
1774
|
+
(!seatButton || secondPicker) && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_react_native27.View, { style: [styles21.pickerRow, pickerRowPopoverOpen && styles21.pickerRowOpen], children: [
|
|
1775
|
+
!seatButton && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(InlineColorPicker, { id: getColorPopoverId(idPrefix), host: popover, value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, alignOverride: colorAlignOverride, rotation: colorRotation, dark, tag: isHuman ? selectedProfile?.tag : void 0, icon: isHuman ? humanIcon : cpuIcon }),
|
|
1728
1776
|
secondPicker
|
|
1729
1777
|
] }),
|
|
1730
1778
|
isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReadyButton, { color, ready: ready ?? false, onToggleReady })
|
|
1731
1779
|
] });
|
|
1732
1780
|
}
|
|
1733
|
-
var styles21 =
|
|
1781
|
+
var styles21 = import_react_native27.StyleSheet.create({
|
|
1734
1782
|
panel: {
|
|
1735
1783
|
alignItems: "center",
|
|
1736
1784
|
gap: 12
|
|
@@ -1741,9 +1789,9 @@ var styles21 = import_react_native26.StyleSheet.create({
|
|
|
1741
1789
|
panelOpen: {
|
|
1742
1790
|
zIndex: 100
|
|
1743
1791
|
},
|
|
1744
|
-
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the color
|
|
1745
|
-
// present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
1746
|
-
// column.
|
|
1792
|
+
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the fallback color swatch and
|
|
1793
|
+
// (when present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
1794
|
+
// own column.
|
|
1747
1795
|
pickerRow: {
|
|
1748
1796
|
alignItems: "center",
|
|
1749
1797
|
flexDirection: "row",
|
|
@@ -1762,20 +1810,20 @@ var styles21 = import_react_native26.StyleSheet.create({
|
|
|
1762
1810
|
});
|
|
1763
1811
|
|
|
1764
1812
|
// src/PressAwayOverlay.tsx
|
|
1765
|
-
var
|
|
1813
|
+
var import_react_native28 = require("react-native");
|
|
1766
1814
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
1767
1815
|
function PressAwayOverlay({ active, onPress, style }) {
|
|
1768
1816
|
if (!active) return null;
|
|
1769
|
-
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
1817
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_react_native28.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native28.StyleSheet.absoluteFill, style] });
|
|
1770
1818
|
}
|
|
1771
1819
|
|
|
1772
1820
|
// src/SharedActionBand.tsx
|
|
1773
1821
|
var import_feedback_press9 = require("@rific/feedback-press");
|
|
1774
|
-
var
|
|
1822
|
+
var import_react_native29 = require("react-native");
|
|
1775
1823
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1776
1824
|
function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
|
|
1777
|
-
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1778
|
-
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
|
|
1825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react_native29.View, { style: [styles22.column, popoverOpen && styles22.columnOpen], children: [
|
|
1826
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react_native29.View, { style: styles22.actionsRow, children: [
|
|
1779
1827
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_feedback_press9.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
|
|
1780
1828
|
onRandomize && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_feedback_press9.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
|
|
1781
1829
|
onReset && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_feedback_press9.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
|
|
@@ -1784,7 +1832,7 @@ function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popove
|
|
|
1784
1832
|
children
|
|
1785
1833
|
] });
|
|
1786
1834
|
}
|
|
1787
|
-
var styles22 =
|
|
1835
|
+
var styles22 = import_react_native29.StyleSheet.create({
|
|
1788
1836
|
actionsRow: {
|
|
1789
1837
|
alignItems: "center",
|
|
1790
1838
|
flexDirection: "row",
|
|
@@ -1800,8 +1848,8 @@ var styles22 = import_react_native28.StyleSheet.create({
|
|
|
1800
1848
|
});
|
|
1801
1849
|
|
|
1802
1850
|
// src/StaggeredWord.tsx
|
|
1803
|
-
var
|
|
1804
|
-
var
|
|
1851
|
+
var import_react13 = require("react");
|
|
1852
|
+
var import_react_native30 = require("react-native");
|
|
1805
1853
|
var import_react_native_reanimated2 = __toESM(require("react-native-reanimated"));
|
|
1806
1854
|
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1807
1855
|
var STAGGER_MS = 45;
|
|
@@ -1811,7 +1859,7 @@ function getStaggeredWordDuration(letterCount) {
|
|
|
1811
1859
|
}
|
|
1812
1860
|
function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }) {
|
|
1813
1861
|
const progress = (0, import_react_native_reanimated2.useSharedValue)(reducedMotion ? 1 : 0);
|
|
1814
|
-
(0,
|
|
1862
|
+
(0, import_react13.useEffect)(() => {
|
|
1815
1863
|
if (reducedMotion) return;
|
|
1816
1864
|
progress.value = (0, import_react_native_reanimated2.withDelay)(index * STAGGER_MS, (0, import_react_native_reanimated2.withTiming)(1, { duration: LETTER_DURATION_MS, easing: import_react_native_reanimated2.Easing.out(import_react_native_reanimated2.Easing.back(1.5)) }));
|
|
1817
1865
|
}, [reducedMotion]);
|
|
@@ -1822,9 +1870,9 @@ function AnimatedLetter({ char, index, color, reducedMotion, fontFamily, fontSiz
|
|
|
1822
1870
|
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native_reanimated2.default.Text, { allowFontScaling: false, style: [{ color, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, style], children: char });
|
|
1823
1871
|
}
|
|
1824
1872
|
function StaggeredWord({ word, color, fontFamily, fontSize, lineHeight, reducedMotion, startIndex = 0, fontWeight = "bold", letterSpacing, style, onLayout }) {
|
|
1825
|
-
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
1873
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_react_native30.View, { style: [styles23.row, style], onLayout, children: word.split("").map((char, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AnimatedLetter, { char, index: startIndex + i, color, reducedMotion, fontFamily, fontSize, lineHeight, fontWeight, letterSpacing }, i)) });
|
|
1826
1874
|
}
|
|
1827
|
-
var styles23 =
|
|
1875
|
+
var styles23 = import_react_native30.StyleSheet.create({
|
|
1828
1876
|
row: {
|
|
1829
1877
|
flexDirection: "row"
|
|
1830
1878
|
}
|
|
@@ -1841,63 +1889,24 @@ function UpdateDialog({ rotation = 0, autoCheck = true, autoPrompt = true, onErr
|
|
|
1841
1889
|
}
|
|
1842
1890
|
|
|
1843
1891
|
// src/useQuitConfirmation.ts
|
|
1844
|
-
var
|
|
1892
|
+
var import_react14 = require("react");
|
|
1845
1893
|
function useQuitConfirmation(hasProgress, onConfirmedBack) {
|
|
1846
|
-
const [confirmVisible, setConfirmVisible] = (0,
|
|
1847
|
-
const hasProgressRef = (0,
|
|
1848
|
-
(0,
|
|
1894
|
+
const [confirmVisible, setConfirmVisible] = (0, import_react14.useState)(false);
|
|
1895
|
+
const hasProgressRef = (0, import_react14.useRef)(hasProgress);
|
|
1896
|
+
(0, import_react14.useEffect)(() => {
|
|
1849
1897
|
hasProgressRef.current = hasProgress;
|
|
1850
1898
|
});
|
|
1851
|
-
const onConfirmedBackRef = (0,
|
|
1852
|
-
(0,
|
|
1899
|
+
const onConfirmedBackRef = (0, import_react14.useRef)(onConfirmedBack);
|
|
1900
|
+
(0, import_react14.useEffect)(() => {
|
|
1853
1901
|
onConfirmedBackRef.current = onConfirmedBack;
|
|
1854
1902
|
});
|
|
1855
|
-
const requestBack = (0,
|
|
1903
|
+
const requestBack = (0, import_react14.useCallback)(() => {
|
|
1856
1904
|
if (hasProgressRef.current()) setConfirmVisible(true);
|
|
1857
1905
|
else onConfirmedBackRef.current();
|
|
1858
1906
|
}, []);
|
|
1859
|
-
const cancelBack = (0,
|
|
1907
|
+
const cancelBack = (0, import_react14.useCallback)(() => setConfirmVisible(false), []);
|
|
1860
1908
|
return { confirmVisible, requestBack, cancelBack };
|
|
1861
1909
|
}
|
|
1862
|
-
|
|
1863
|
-
// src/useZoneClampedAlign.ts
|
|
1864
|
-
var import_split_screen = require("@tastic/split-screen");
|
|
1865
|
-
var import_react14 = require("react");
|
|
1866
|
-
var import_react_native30 = require("react-native");
|
|
1867
|
-
var import_react_native_safe_area_context3 = require("react-native-safe-area-context");
|
|
1868
|
-
var EDGE_MARGIN2 = 12;
|
|
1869
|
-
function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
1870
|
-
const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
|
|
1871
|
-
const zone = (0, import_split_screen.useZoneBounds)();
|
|
1872
|
-
const { height: windowHeight } = (0, import_react_native30.useWindowDimensions)();
|
|
1873
|
-
const insets = (0, import_react_native_safe_area_context3.useSafeAreaInsets)();
|
|
1874
|
-
const shouldMeasure = open && auto.measured && zone !== null;
|
|
1875
|
-
const [ownBounds, setOwnBounds] = (0, import_react14.useState)(null);
|
|
1876
|
-
const [prevShouldMeasure, setPrevShouldMeasure] = (0, import_react14.useState)(shouldMeasure);
|
|
1877
|
-
if (shouldMeasure !== prevShouldMeasure) {
|
|
1878
|
-
setPrevShouldMeasure(shouldMeasure);
|
|
1879
|
-
setOwnBounds(null);
|
|
1880
|
-
}
|
|
1881
|
-
(0, import_react14.useEffect)(() => {
|
|
1882
|
-
if (!shouldMeasure) return;
|
|
1883
|
-
auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
|
|
1884
|
-
const belowShared = zone.zoneSide === "belowShared";
|
|
1885
|
-
const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
|
|
1886
|
-
const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
|
|
1887
|
-
const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
|
|
1888
|
-
let awaySide = belowShared ? "below" : "above";
|
|
1889
|
-
let towardSide = belowShared ? "above" : "below";
|
|
1890
|
-
if (zone.rotated) {
|
|
1891
|
-
awaySide = awaySide === "above" ? "below" : "above";
|
|
1892
|
-
towardSide = towardSide === "above" ? "below" : "above";
|
|
1893
|
-
}
|
|
1894
|
-
setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
|
|
1895
|
-
});
|
|
1896
|
-
}, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
|
|
1897
|
-
if (!zone) return auto;
|
|
1898
|
-
if (!ownBounds) return { ...auto, measured: false };
|
|
1899
|
-
return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
|
|
1900
|
-
}
|
|
1901
1910
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1902
1911
|
0 && (module.exports = {
|
|
1903
1912
|
AchievementCatalogSection,
|
|
@@ -1937,6 +1946,7 @@ function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
|
1937
1946
|
getCpuDifficultyPopoverId,
|
|
1938
1947
|
getInlineColorPickerContentSize,
|
|
1939
1948
|
getLabeledDropdownContentHeight,
|
|
1949
|
+
getProfilePopoverId,
|
|
1940
1950
|
getStaggeredWordDuration,
|
|
1941
1951
|
isCompactDialogHeight,
|
|
1942
1952
|
loadSkiaWeb,
|
package/dist/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
UpdateDialog,
|
|
20
20
|
getStaggeredWordDuration,
|
|
21
21
|
useCompactDialog
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-UOA35OWJ.mjs";
|
|
23
23
|
import {
|
|
24
24
|
COMPACT_DIALOG_HEIGHT,
|
|
25
25
|
DialogHeader,
|
|
@@ -683,7 +683,7 @@ function loadSkiaWeb() {
|
|
|
683
683
|
}
|
|
684
684
|
|
|
685
685
|
// src/PlayerSetupPanel.tsx
|
|
686
|
-
import { ProfilePicker } from "@tastic/profile";
|
|
686
|
+
import { getOptionPickerContentSize, getProfilePickerContentSize, OptionPicker, ProfilePicker } from "@tastic/profile";
|
|
687
687
|
import { StyleSheet as StyleSheet8, View as View8 } from "react-native";
|
|
688
688
|
import { Text as Text6 } from "react-native-paper";
|
|
689
689
|
|
|
@@ -721,26 +721,73 @@ function usePopoverHost() {
|
|
|
721
721
|
return { openId, toggle, close };
|
|
722
722
|
}
|
|
723
723
|
|
|
724
|
+
// src/useZoneClampedAlign.ts
|
|
725
|
+
import { useZoneBounds } from "@tastic/split-screen";
|
|
726
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
727
|
+
import { useWindowDimensions as useWindowDimensions3 } from "react-native";
|
|
728
|
+
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
729
|
+
var EDGE_MARGIN2 = 12;
|
|
730
|
+
function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
731
|
+
const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
|
|
732
|
+
const zone = useZoneBounds();
|
|
733
|
+
const { height: windowHeight } = useWindowDimensions3();
|
|
734
|
+
const insets = useSafeAreaInsets();
|
|
735
|
+
const shouldMeasure = open && auto.measured && zone !== null;
|
|
736
|
+
const [ownBounds, setOwnBounds] = useState3(null);
|
|
737
|
+
const [prevShouldMeasure, setPrevShouldMeasure] = useState3(shouldMeasure);
|
|
738
|
+
if (shouldMeasure !== prevShouldMeasure) {
|
|
739
|
+
setPrevShouldMeasure(shouldMeasure);
|
|
740
|
+
setOwnBounds(null);
|
|
741
|
+
}
|
|
742
|
+
useEffect2(() => {
|
|
743
|
+
if (!shouldMeasure) return;
|
|
744
|
+
auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
|
|
745
|
+
const belowShared = zone.zoneSide === "belowShared";
|
|
746
|
+
const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
|
|
747
|
+
const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
|
|
748
|
+
const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
|
|
749
|
+
let awaySide = belowShared ? "below" : "above";
|
|
750
|
+
let towardSide = belowShared ? "above" : "below";
|
|
751
|
+
if (zone.rotated) {
|
|
752
|
+
awaySide = awaySide === "above" ? "below" : "above";
|
|
753
|
+
towardSide = towardSide === "above" ? "below" : "above";
|
|
754
|
+
}
|
|
755
|
+
setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
|
|
756
|
+
});
|
|
757
|
+
}, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
|
|
758
|
+
if (!zone) return auto;
|
|
759
|
+
if (!ownBounds) return { ...auto, measured: false };
|
|
760
|
+
return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
|
|
761
|
+
}
|
|
762
|
+
|
|
724
763
|
// src/PlayerSetupPanel.tsx
|
|
725
764
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
726
765
|
function getColorPopoverId(idPrefix) {
|
|
727
766
|
return `${idPrefix}-color`;
|
|
728
767
|
}
|
|
768
|
+
function getProfilePopoverId(idPrefix) {
|
|
769
|
+
return `${idPrefix}-profile`;
|
|
770
|
+
}
|
|
729
771
|
function getCpuDifficultyPopoverId(idPrefix) {
|
|
730
772
|
return `${idPrefix}-difficulty`;
|
|
731
773
|
}
|
|
732
|
-
function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange,
|
|
774
|
+
function PlayerSetupPanel({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorCaption, showColor = true, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, humanIcon = "face-man", cpuIcon = "robot" }) {
|
|
733
775
|
const ownHost = usePopoverHost();
|
|
734
776
|
const popover = host ?? ownHost;
|
|
735
777
|
const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false;
|
|
736
778
|
const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || secondPickerId !== void 0 && popover.openId === secondPickerId;
|
|
737
779
|
const showProfilePicker = isHuman && profiles !== void 0 && !!onProfileSelect;
|
|
738
780
|
const showCpuDifficulty = !isHuman && cpuDifficulty !== void 0 && cpuDifficultyOptions !== void 0 && !!onCpuDifficultyChange;
|
|
781
|
+
const seatButton = showProfilePicker || showCpuDifficulty;
|
|
739
782
|
const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null;
|
|
783
|
+
const colorSection = showColor ? { value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, caption: showProfilePicker && selectedProfile ? colorCaption : void 0 } : void 0;
|
|
784
|
+
const seatPickerId = showProfilePicker ? getProfilePopoverId(idPrefix) : getCpuDifficultyPopoverId(idPrefix);
|
|
785
|
+
const seatPickerSize = showProfilePicker ? getProfilePickerContentSize(profiles.length, !!onManageProfiles, colorSection) : getOptionPickerContentSize(cpuDifficultyOptions?.length ?? 0, colorSection);
|
|
786
|
+
const seatPickerAlign = useZoneClampedAlign(seatButton && popover.openId === seatPickerId, seatPickerSize.width, seatPickerSize.height);
|
|
740
787
|
return /* @__PURE__ */ jsxs7(View8, { style: [styles8.panel, ownPopoverOpen && styles8.panelOpen], children: [
|
|
741
|
-
showProfilePicker ? /* @__PURE__ */ jsx10(ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles }) : showCpuDifficulty ? /* @__PURE__ */ jsx10(
|
|
742
|
-
/* @__PURE__ */ jsxs7(View8, { style: [styles8.pickerRow, pickerRowPopoverOpen && styles8.pickerRowOpen], children: [
|
|
743
|
-
/* @__PURE__ */ jsx10(InlineColorPicker, { id: getColorPopoverId(idPrefix), host: popover, value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, alignOverride: colorAlignOverride, rotation: colorRotation, dark, tag: isHuman ? selectedProfile?.tag : void 0, icon: isHuman ? humanIcon : cpuIcon }),
|
|
788
|
+
showProfilePicker ? /* @__PURE__ */ jsx10(ProfilePicker, { idPrefix, host: popover, profiles, selectedId: selectedProfileId ?? null, takenId: takenProfileId, color, dark, guestLabel: guestLabel ?? "GUEST", onSelect: (profile) => onProfileSelect?.(profile), onManage: onManageProfiles, colorSection, alignOverride: seatPickerAlign }) : showCpuDifficulty ? /* @__PURE__ */ jsx10(OptionPicker, { id: getCpuDifficultyPopoverId(idPrefix), host: popover, options: cpuDifficultyOptions, value: cpuDifficulty, onChange: onCpuDifficultyChange, color, dark, icon: cpuIcon, colorSection, alignOverride: seatPickerAlign }) : label && /* @__PURE__ */ jsx10(Text6, { variant: "labelSmall", style: { color, fontFamily: labelFontFamily }, children: label }),
|
|
789
|
+
(!seatButton || secondPicker) && /* @__PURE__ */ jsxs7(View8, { style: [styles8.pickerRow, pickerRowPopoverOpen && styles8.pickerRowOpen], children: [
|
|
790
|
+
!seatButton && /* @__PURE__ */ jsx10(InlineColorPicker, { id: getColorPopoverId(idPrefix), host: popover, value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, alignOverride: colorAlignOverride, rotation: colorRotation, dark, tag: isHuman ? selectedProfile?.tag : void 0, icon: isHuman ? humanIcon : cpuIcon }),
|
|
744
791
|
secondPicker
|
|
745
792
|
] }),
|
|
746
793
|
isHuman && showReadyButton && onToggleReady && /* @__PURE__ */ jsx10(ReadyButton, { color, ready: ready ?? false, onToggleReady })
|
|
@@ -757,9 +804,9 @@ var styles8 = StyleSheet8.create({
|
|
|
757
804
|
panelOpen: {
|
|
758
805
|
zIndex: 100
|
|
759
806
|
},
|
|
760
|
-
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the color
|
|
761
|
-
// present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
762
|
-
// column.
|
|
807
|
+
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the fallback color swatch and
|
|
808
|
+
// (when present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
809
|
+
// own column.
|
|
763
810
|
pickerRow: {
|
|
764
811
|
alignItems: "center",
|
|
765
812
|
flexDirection: "row",
|
|
@@ -816,15 +863,15 @@ var styles9 = StyleSheet10.create({
|
|
|
816
863
|
});
|
|
817
864
|
|
|
818
865
|
// src/useQuitConfirmation.ts
|
|
819
|
-
import { useCallback as useCallback3, useEffect as
|
|
866
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
|
|
820
867
|
function useQuitConfirmation(hasProgress, onConfirmedBack) {
|
|
821
|
-
const [confirmVisible, setConfirmVisible] =
|
|
868
|
+
const [confirmVisible, setConfirmVisible] = useState4(false);
|
|
822
869
|
const hasProgressRef = useRef2(hasProgress);
|
|
823
|
-
|
|
870
|
+
useEffect3(() => {
|
|
824
871
|
hasProgressRef.current = hasProgress;
|
|
825
872
|
});
|
|
826
873
|
const onConfirmedBackRef = useRef2(onConfirmedBack);
|
|
827
|
-
|
|
874
|
+
useEffect3(() => {
|
|
828
875
|
onConfirmedBackRef.current = onConfirmedBack;
|
|
829
876
|
});
|
|
830
877
|
const requestBack = useCallback3(() => {
|
|
@@ -834,45 +881,6 @@ function useQuitConfirmation(hasProgress, onConfirmedBack) {
|
|
|
834
881
|
const cancelBack = useCallback3(() => setConfirmVisible(false), []);
|
|
835
882
|
return { confirmVisible, requestBack, cancelBack };
|
|
836
883
|
}
|
|
837
|
-
|
|
838
|
-
// src/useZoneClampedAlign.ts
|
|
839
|
-
import { useZoneBounds } from "@tastic/split-screen";
|
|
840
|
-
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
841
|
-
import { useWindowDimensions as useWindowDimensions3 } from "react-native";
|
|
842
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
843
|
-
var EDGE_MARGIN2 = 12;
|
|
844
|
-
function useZoneClampedAlign(open, contentWidth, contentHeight, rotation = 0) {
|
|
845
|
-
const auto = useAutoAlign(open, contentWidth, contentHeight, rotation);
|
|
846
|
-
const zone = useZoneBounds();
|
|
847
|
-
const { height: windowHeight } = useWindowDimensions3();
|
|
848
|
-
const insets = useSafeAreaInsets();
|
|
849
|
-
const shouldMeasure = open && auto.measured && zone !== null;
|
|
850
|
-
const [ownBounds, setOwnBounds] = useState4(null);
|
|
851
|
-
const [prevShouldMeasure, setPrevShouldMeasure] = useState4(shouldMeasure);
|
|
852
|
-
if (shouldMeasure !== prevShouldMeasure) {
|
|
853
|
-
setPrevShouldMeasure(shouldMeasure);
|
|
854
|
-
setOwnBounds(null);
|
|
855
|
-
}
|
|
856
|
-
useEffect3(() => {
|
|
857
|
-
if (!shouldMeasure) return;
|
|
858
|
-
auto.triggerRef.current?.measureInWindow((_x, y, _width, height) => {
|
|
859
|
-
const belowShared = zone.zoneSide === "belowShared";
|
|
860
|
-
const awayRoom = belowShared ? windowHeight - insets.bottom - (y + height) - EDGE_MARGIN2 : y - insets.top - EDGE_MARGIN2;
|
|
861
|
-
const towardRoom = belowShared ? y - zone.sharedEdgeY - EDGE_MARGIN2 : zone.sharedEdgeY - (y + height) - EDGE_MARGIN2;
|
|
862
|
-
const useToward = contentHeight > awayRoom && towardRoom > awayRoom;
|
|
863
|
-
let awaySide = belowShared ? "below" : "above";
|
|
864
|
-
let towardSide = belowShared ? "above" : "below";
|
|
865
|
-
if (zone.rotated) {
|
|
866
|
-
awaySide = awaySide === "above" ? "below" : "above";
|
|
867
|
-
towardSide = towardSide === "above" ? "below" : "above";
|
|
868
|
-
}
|
|
869
|
-
setOwnBounds({ maxHeight: Math.max(useToward ? towardRoom : awayRoom, 0), verticalAlign: useToward ? towardSide : awaySide });
|
|
870
|
-
});
|
|
871
|
-
}, [shouldMeasure, zone, auto.triggerRef, contentHeight, windowHeight, insets.top, insets.bottom]);
|
|
872
|
-
if (!zone) return auto;
|
|
873
|
-
if (!ownBounds) return { ...auto, measured: false };
|
|
874
|
-
return { ...auto, maxHeight: ownBounds.maxHeight, measured: auto.measured, verticalAlign: ownBounds.verticalAlign };
|
|
875
|
-
}
|
|
876
884
|
export {
|
|
877
885
|
AchievementCatalogSection,
|
|
878
886
|
AchievementRow,
|
|
@@ -911,6 +919,7 @@ export {
|
|
|
911
919
|
getCpuDifficultyPopoverId,
|
|
912
920
|
getInlineColorPickerContentSize,
|
|
913
921
|
getLabeledDropdownContentHeight,
|
|
922
|
+
getProfilePopoverId,
|
|
914
923
|
getStaggeredWordDuration,
|
|
915
924
|
isCompactDialogHeight,
|
|
916
925
|
loadSkiaWeb,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -85,16 +85,16 @@
|
|
|
85
85
|
"prettier": "@infinitetoken/eslint-config/prettier",
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@infinitetoken/eslint-config": "^0.2.0",
|
|
88
|
-
"@infinitetoken/jest-config": "^0.
|
|
89
|
-
"@infinitetoken/tsconfig": "^0.
|
|
90
|
-
"@rific/auto-paper": "^0.13.
|
|
88
|
+
"@infinitetoken/jest-config": "^0.5.2",
|
|
89
|
+
"@infinitetoken/tsconfig": "^0.6.0",
|
|
90
|
+
"@rific/auto-paper": "^0.13.3",
|
|
91
91
|
"@rific/core": "^0.3.1",
|
|
92
92
|
"@rific/feedback-press": "^0.13.0",
|
|
93
93
|
"@rific/updater": "^0.7.1",
|
|
94
|
-
"@shopify/react-native-skia": "^2.
|
|
94
|
+
"@shopify/react-native-skia": "^2.14.0",
|
|
95
95
|
"@tastic/achievements": "^0.6.0",
|
|
96
96
|
"@tastic/core": "^0.14.0",
|
|
97
|
-
"@tastic/profile": "^0.
|
|
97
|
+
"@tastic/profile": "^0.15.0",
|
|
98
98
|
"@tastic/split-screen": "^0.5.2",
|
|
99
99
|
"@testing-library/dom": "^10.4.1",
|
|
100
100
|
"@testing-library/react": "^16.3.2",
|
|
@@ -108,10 +108,10 @@
|
|
|
108
108
|
"prettier": "^3.8.3",
|
|
109
109
|
"react": "^19.0.0",
|
|
110
110
|
"react-dom": "^19.0.0",
|
|
111
|
-
"react-native": "^0.
|
|
111
|
+
"react-native": "^0.88.0",
|
|
112
112
|
"react-native-paper": "^5.15.2",
|
|
113
|
-
"react-native-reanimated": "^4.
|
|
114
|
-
"react-native-safe-area-context": "^5.10.
|
|
113
|
+
"react-native-reanimated": "^4.7.1",
|
|
114
|
+
"react-native-safe-area-context": "^5.10.1",
|
|
115
115
|
"tsup": "^8.0.0",
|
|
116
116
|
"typescript": "^6.0.3"
|
|
117
117
|
},
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"@shopify/react-native-skia": ">=2.6.0",
|
|
123
123
|
"@tastic/achievements": ">=0.2.0",
|
|
124
124
|
"@tastic/core": ">=0.12.0",
|
|
125
|
-
"@tastic/profile": ">=0.
|
|
125
|
+
"@tastic/profile": ">=0.15.0",
|
|
126
126
|
"@tastic/split-screen": ">=0.5.0",
|
|
127
127
|
"react": ">=19.0.0",
|
|
128
128
|
"react-native": ">=0.76.0",
|
package/src/BaseStatsScreen.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useAutoPaperTheme } from '@rific/auto-paper'
|
|
1
|
+
import { FadingScrollView, useAutoPaperTheme } from '@rific/auto-paper'
|
|
2
2
|
import { Button, IconButton } from '@rific/feedback-press'
|
|
3
3
|
import { useRotation, type ViewRotation } from '@tastic/core'
|
|
4
4
|
import { ReactNode, useState } from 'react'
|
|
5
|
-
import { type Insets,
|
|
5
|
+
import { type Insets, StyleSheet, View } from 'react-native'
|
|
6
6
|
import { Text } from 'react-native-paper'
|
|
7
7
|
|
|
8
8
|
import { ConfirmDialog } from './ConfirmDialog'
|
|
@@ -101,7 +101,9 @@ export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achieveme
|
|
|
101
101
|
</Text>
|
|
102
102
|
</View>
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
{/* Fades out at an edge while there is more past it, the same cue as the Settings sheet's rows and the
|
|
105
|
+
Profiles screen's (Jay, 2026-10-16: every scrolling screen alike). */}
|
|
106
|
+
<FadingScrollView style={styles.scrollView} contentContainerStyle={[styles.content, { paddingBottom: 32 + insets.bottom }]} showsVerticalScrollIndicator={false}>
|
|
105
107
|
{children}
|
|
106
108
|
|
|
107
109
|
{showReset && (
|
|
@@ -109,7 +111,7 @@ export function BaseStatsScreen({ rotation: rotationOverride, title = 'Achieveme
|
|
|
109
111
|
{resetLabel}
|
|
110
112
|
</Button>
|
|
111
113
|
)}
|
|
112
|
-
</
|
|
114
|
+
</FadingScrollView>
|
|
113
115
|
|
|
114
116
|
<ConfirmDialog
|
|
115
117
|
visible={confirmResetVisible}
|
|
@@ -147,7 +149,7 @@ const styles = StyleSheet.create({
|
|
|
147
149
|
resetButton: {
|
|
148
150
|
marginTop: 16
|
|
149
151
|
},
|
|
150
|
-
// Bounds the
|
|
152
|
+
// Bounds the scroller (FadingScrollView puts it on its outer box) to the space `container`'s flex:1 actually gives it - without this, a
|
|
151
153
|
// ScrollView with only a contentContainerStyle isn't reliably height-constrained on native (it
|
|
152
154
|
// can render at its full unclipped content height instead of the screen's), which leaves the
|
|
153
155
|
// header's back button touch target unreliable underneath it.
|
package/src/PlayerSetupPanel.tsx
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
import { SeedColor } from '@rific/auto-paper'
|
|
2
|
-
import { Profile, ProfilePicker } from '@tastic/profile'
|
|
2
|
+
import { getOptionPickerContentSize, getProfilePickerContentSize, OptionPicker, Profile, ProfilePicker, type ProfilePickerColorSection } from '@tastic/profile'
|
|
3
3
|
import { ReactNode } from 'react'
|
|
4
4
|
import { StyleSheet, View } from 'react-native'
|
|
5
5
|
import { Text } from 'react-native-paper'
|
|
6
6
|
|
|
7
7
|
import { InlineColorPicker } from './InlineColorPicker'
|
|
8
|
-
import {
|
|
8
|
+
import { LabeledDropdownOption } from './LabeledDropdown'
|
|
9
9
|
import { ReadyButton } from './ReadyButton'
|
|
10
10
|
import { AlignResult, PopoverRotation } from './useAutoAlign'
|
|
11
11
|
import { PopoverHost, usePopoverHost } from './usePopoverHost'
|
|
12
|
+
import { useZoneClampedAlign } from './useZoneClampedAlign'
|
|
12
13
|
|
|
13
|
-
// This panel's own internal
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// colorAlignOverride/cpuDifficultyAlignOverride below) doesn't have to hardcode or guess this
|
|
17
|
-
// panel's own internal naming convention. Matches secondPickerId's own job for a caller's own
|
|
14
|
+
// This panel's own internal popover ids, derived from idPrefix - exported so a caller that needs to
|
|
15
|
+
// know whether one of them is currently open (e.g. to elevate the zone or row holding the panel)
|
|
16
|
+
// doesn't have to hardcode or guess this panel's own internal naming convention. Matches secondPickerId's own job for a caller's own
|
|
18
17
|
// externally-constructed picker - this is the equivalent for the two pickers this panel owns
|
|
19
18
|
// itself, which have no prop of their own to name their id with.
|
|
20
19
|
export function getColorPopoverId(idPrefix: string): string {
|
|
21
20
|
return `${idPrefix}-color`
|
|
22
21
|
}
|
|
22
|
+
// The seat button's dropdown on a human seat (@tastic/profile's ProfilePicker opens under
|
|
23
|
+
// `${idPrefix}-profile`). A CPU seat's opens under getCpuDifficultyPopoverId.
|
|
24
|
+
export function getProfilePopoverId(idPrefix: string): string {
|
|
25
|
+
return `${idPrefix}-profile`
|
|
26
|
+
}
|
|
23
27
|
export function getCpuDifficultyPopoverId(idPrefix: string): string {
|
|
24
28
|
return `${idPrefix}-difficulty`
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
export interface PlayerSetupPanelProps<C extends string = string> {
|
|
28
|
-
// Namespaces this panel's popover ids ('color', whatever secondPickerId names
|
|
32
|
+
// Namespaces this panel's popover ids ('profile', 'difficulty', 'color', whatever secondPickerId names) so
|
|
29
33
|
// two panels can safely share one host (see `host` below) without their ids colliding.
|
|
30
34
|
idPrefix: string
|
|
31
35
|
// Shared popover host, when this panel's own popovers should be mutually exclusive with another
|
|
@@ -35,7 +39,7 @@ export interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
35
39
|
// real people editing at once is the point.
|
|
36
40
|
host?: PopoverHost
|
|
37
41
|
label?: string
|
|
38
|
-
// Applied only to the plain `label` fallback text below (ProfilePicker/
|
|
42
|
+
// Applied only to the plain `label` fallback text below (ProfilePicker/OptionPicker render
|
|
39
43
|
// their own fixed chrome regardless of this prop) - omit to inherit whichever font the calling
|
|
40
44
|
// screen's own theme resolves for a labelSmall Text, rather than this package forcing its own
|
|
41
45
|
// MONO_FONT onto what's really the host app's own copy.
|
|
@@ -44,21 +48,24 @@ export interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
44
48
|
onColorChange: (hex: string) => void
|
|
45
49
|
swatches: SeedColor[]
|
|
46
50
|
// The other player's current color - stays visible in the swatch grid but disabled, rather than
|
|
47
|
-
// removed from it entirely. Unless allowSwapTaken is set
|
|
51
|
+
// removed from it entirely. Unless allowSwapTaken is set (the other seat is the CPU), which lets
|
|
52
|
+
// it be picked so the caller can swap the two - see InlineColorPicker's own doc.
|
|
48
53
|
takenColor?: string
|
|
49
54
|
allowSwapTaken?: boolean
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
55
|
+
// A short line under the seat button's Color row while a profile is selected, saying where the
|
|
56
|
+
// color applies (e.g. 'Snake only': the pick is kept in this game, not on the profile). Left out
|
|
57
|
+
// for a guest or the CPU, who have no color anywhere else.
|
|
58
|
+
colorCaption?: string
|
|
59
|
+
// False leaves the Color row out of the seat button's dropdown (e.g. a CPU seat set to "None",
|
|
60
|
+
// with no second player to color). Defaults to true.
|
|
61
|
+
showColor?: boolean
|
|
62
|
+
// The fallback color swatch's placement and rotation, passed straight through to its
|
|
63
|
+
// InlineColorPicker. That swatch is only drawn on a seat with neither a profile picker nor a CPU
|
|
64
|
+
// difficulty picker (see the header below); the seat button places its own dropdown.
|
|
54
65
|
colorAlignOverride?: AlignResult
|
|
55
|
-
// Passed straight through to the internal InlineColorPicker's own `rotation` - same reasoning as
|
|
56
|
-
// colorAlignOverride above (no other way for a caller to reach that picker directly), for a
|
|
57
|
-
// caller whose own rotation reading needs to differ from InlineColorPicker's default ambient
|
|
58
|
-
// useRotation() read (see that component's own `rotation` doc for when that's worth doing).
|
|
59
66
|
colorRotation?: PopoverRotation
|
|
60
67
|
isHuman: boolean
|
|
61
|
-
// Whatever sits
|
|
68
|
+
// Whatever sits under the seat button in pickerRow, if anything - a ControlSchemePicker, a raw
|
|
62
69
|
// SectionedDropdown, or nothing at all. The five apps this component was extracted from each
|
|
63
70
|
// duplicated this entire panel and differed only in what (if anything) occupied this one slot,
|
|
64
71
|
// each wanting a genuinely different concept there (which physical input drives a seat, vs. a
|
|
@@ -69,7 +76,7 @@ export interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
69
76
|
secondPicker?: ReactNode
|
|
70
77
|
// The popover id secondPicker opens under, if it opens one at all - lets pickerRow's own
|
|
71
78
|
// "elevate above later siblings while one of my own popovers is open" logic (see pickerRowOpen)
|
|
72
|
-
// react to secondPicker's popover the same way it already does for the
|
|
79
|
+
// react to secondPicker's popover the same way it already does for the fallback swatch's, without
|
|
73
80
|
// this component needing any opinion on what secondPicker actually renders. Omit when secondPicker
|
|
74
81
|
// has no popover of its own, or isn't provided at all.
|
|
75
82
|
secondPickerId?: string
|
|
@@ -82,7 +89,7 @@ export interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
82
89
|
profiles?: Profile[]
|
|
83
90
|
selectedProfileId?: string | null
|
|
84
91
|
takenProfileId?: string | null
|
|
85
|
-
// What the
|
|
92
|
+
// What the seat button shows before any profile's selected - this seat's own 'P1'/'P2' (see
|
|
86
93
|
// ProfilePicker's own doc for why it's never "Player"). Required whenever profiles is provided,
|
|
87
94
|
// since the trigger always needs some idle text.
|
|
88
95
|
guestLabel?: string
|
|
@@ -95,29 +102,30 @@ export interface PlayerSetupPanelProps<C extends string = string> {
|
|
|
95
102
|
cpuDifficulty?: C
|
|
96
103
|
cpuDifficultyOptions?: LabeledDropdownOption<C>[]
|
|
97
104
|
onCpuDifficultyChange?: (value: C) => void
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
// consumer's own convention. A caller with its own extra CPU states (e.g. a "no CPU" slot) computes
|
|
104
|
-
// its own icon string and passes it through cpuIcon rather than this component needing any opinion
|
|
105
|
-
// on what those states are.
|
|
105
|
+
// The icon on a non-human seat's button chip, and on the fallback color swatch for a human vs. a
|
|
106
|
+
// non-human seat (overridden there by a selected profile's own `tag`). A human seat's button chip
|
|
107
|
+
// shows the selected profile's tag, or ProfilePicker's guest icon. A caller with its own extra CPU
|
|
108
|
+
// states (e.g. a "no CPU" slot) computes its own icon string and passes it through cpuIcon rather
|
|
109
|
+
// than this component needing any opinion on what those states are.
|
|
106
110
|
humanIcon?: string
|
|
107
111
|
cpuIcon?: string
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
// One panel per player slot in a local-multiplayer loadout/lobby screen:
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
114
|
+
// One panel per player slot in a local-multiplayer loadout/lobby screen: one seat button, an
|
|
115
|
+
// optional second, per-app picker under it (see `secondPicker`'s own doc), and a Ready toggle for
|
|
116
|
+
// human seats. A human seat's button is @tastic/profile's ProfilePicker and a CPU seat's is its
|
|
117
|
+
// OptionPicker (the difficulty levels), each with the seat's color behind a Color row at the top of
|
|
118
|
+
// the same dropdown: Solitaire's player button, made the fleet's seat control (Jay, 2026-10-16). It
|
|
119
|
+
// used to be a profile (or difficulty) dropdown with a separate color swatch beside it, which read
|
|
120
|
+
// as two unrelated things. A seat with neither picker wired keeps the plain label and the separate
|
|
121
|
+
// swatch. Ported from five apps' own near-identical PlayerSetupPanel.tsx/LobbyPlayerPanel.tsx
|
|
122
|
+
// (AirHockey, BoxHockey, Pong, LightCycles, Snake), which had all independently converged on this
|
|
123
|
+
// exact skeleton.
|
|
116
124
|
//
|
|
117
|
-
// Owns its own popover host shared by its own
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
export function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange,
|
|
125
|
+
// Owns its own popover host shared by its own pickers unless a `host` is passed in to share with
|
|
126
|
+
// another panel (or a shared-controls row) instead - same convention as every popover-hosting
|
|
127
|
+
// component in this package.
|
|
128
|
+
export function PlayerSetupPanel<C extends string = string>({ idPrefix, host, label, labelFontFamily, color, onColorChange, swatches, takenColor, allowSwapTaken, colorCaption, showColor = true, colorAlignOverride, colorRotation, isHuman, secondPicker, secondPickerId, ready, onToggleReady, dark, showReadyButton = true, profiles, selectedProfileId, takenProfileId, guestLabel, onProfileSelect, onManageProfiles, cpuDifficulty, cpuDifficultyOptions, onCpuDifficultyChange, humanIcon = 'face-man', cpuIcon = 'robot' }: PlayerSetupPanelProps<C>) {
|
|
121
129
|
const ownHost = usePopoverHost()
|
|
122
130
|
const popover = host ?? ownHost
|
|
123
131
|
// Checking *this panel's own* ids, not just "is anything open on the host" - when `host` is
|
|
@@ -126,22 +134,33 @@ export function PlayerSetupPanel<C extends string = string>({ idPrefix, host, la
|
|
|
126
134
|
// letting DOM order (wrongly) decide which one paints on top.
|
|
127
135
|
const ownPopoverOpen = popover.openId?.startsWith(`${idPrefix}-`) ?? false
|
|
128
136
|
// Narrower than ownPopoverOpen on purpose - only true for a popover actually inside pickerRow
|
|
129
|
-
// itself (color, or secondPicker's own popover when it has one and
|
|
130
|
-
// pickerRowOpen's own style comment for why pickerRow needs this in
|
|
137
|
+
// itself (the fallback color swatch, or secondPicker's own popover when it has one and
|
|
138
|
+
// secondPickerId names it). See pickerRowOpen's own style comment for why pickerRow needs this in
|
|
139
|
+
// addition to panelOpen above.
|
|
131
140
|
const pickerRowPopoverOpen = popover.openId === getColorPopoverId(idPrefix) || (secondPickerId !== undefined && popover.openId === secondPickerId)
|
|
132
141
|
const showProfilePicker = isHuman && profiles !== undefined && !!onProfileSelect
|
|
133
142
|
const showCpuDifficulty = !isHuman && cpuDifficulty !== undefined && cpuDifficultyOptions !== undefined && !!onCpuDifficultyChange
|
|
134
|
-
|
|
135
|
-
// profile rather than snapshotted at selection time, since a caller
|
|
136
|
-
// editing surface for it on this same screen.
|
|
143
|
+
const seatButton = showProfilePicker || showCpuDifficulty
|
|
144
|
+
// Read live from the selected profile rather than snapshotted at selection time, since a caller
|
|
145
|
+
// typically has no other editing surface for it on this same screen.
|
|
137
146
|
const selectedProfile = profiles?.find((p) => p.id === selectedProfileId) ?? null
|
|
138
147
|
|
|
148
|
+
const colorSection: ProfilePickerColorSection | undefined = showColor ? { value: color, onChange: onColorChange, swatches, takenValue: takenColor, allowSwapTaken, caption: showProfilePicker && selectedProfile ? colorCaption : undefined } : undefined
|
|
149
|
+
// The seat button's dropdown is placed here rather than by the caller: inside a
|
|
150
|
+
// @tastic/split-screen zone (2 Player) it has to stay in that player's half, which
|
|
151
|
+
// useZoneClampedAlign does, and outside one it's useAutoAlign's own placement. Sized from the same
|
|
152
|
+
// section and rows the picker draws. Rotation 0, the same as the profile, color and difficulty
|
|
153
|
+
// dropdowns this button replaced were all placed with.
|
|
154
|
+
const seatPickerId = showProfilePicker ? getProfilePopoverId(idPrefix) : getCpuDifficultyPopoverId(idPrefix)
|
|
155
|
+
const seatPickerSize = showProfilePicker ? getProfilePickerContentSize(profiles!.length, !!onManageProfiles, colorSection) : getOptionPickerContentSize(cpuDifficultyOptions?.length ?? 0, colorSection)
|
|
156
|
+
const seatPickerAlign = useZoneClampedAlign(seatButton && popover.openId === seatPickerId, seatPickerSize.width, seatPickerSize.height)
|
|
157
|
+
|
|
139
158
|
return (
|
|
140
159
|
<View style={[styles.panel, ownPopoverOpen && styles.panelOpen]}>
|
|
141
160
|
{showProfilePicker ? (
|
|
142
|
-
<ProfilePicker idPrefix={idPrefix} host={popover} profiles={profiles!} selectedId={selectedProfileId ?? null} takenId={takenProfileId} color={color} dark={dark} guestLabel={guestLabel ?? 'GUEST'} onSelect={(profile: Profile | null) => onProfileSelect?.(profile)} onManage={onManageProfiles} />
|
|
161
|
+
<ProfilePicker idPrefix={idPrefix} host={popover} profiles={profiles!} selectedId={selectedProfileId ?? null} takenId={takenProfileId} color={color} dark={dark} guestLabel={guestLabel ?? 'GUEST'} onSelect={(profile: Profile | null) => onProfileSelect?.(profile)} onManage={onManageProfiles} colorSection={colorSection} alignOverride={seatPickerAlign} />
|
|
143
162
|
) : showCpuDifficulty ? (
|
|
144
|
-
<
|
|
163
|
+
<OptionPicker id={getCpuDifficultyPopoverId(idPrefix)} host={popover} options={cpuDifficultyOptions!} value={cpuDifficulty!} onChange={onCpuDifficultyChange!} color={color} dark={dark} icon={cpuIcon} colorSection={colorSection} alignOverride={seatPickerAlign} />
|
|
145
164
|
) : (
|
|
146
165
|
label && (
|
|
147
166
|
<Text variant='labelSmall' style={{ color, fontFamily: labelFontFamily }}>
|
|
@@ -150,14 +169,16 @@ export function PlayerSetupPanel<C extends string = string>({ idPrefix, host, la
|
|
|
150
169
|
)
|
|
151
170
|
)}
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
{(!seatButton || secondPicker) && (
|
|
173
|
+
<View style={[styles.pickerRow, pickerRowPopoverOpen && styles.pickerRowOpen]}>
|
|
174
|
+
{/* The fallback swatch, for a seat with no button: a human slot shows the selected
|
|
175
|
+
profile's own tag when it has one, falling back to humanIcon; a non-human slot always
|
|
176
|
+
falls back to cpuIcon. */}
|
|
177
|
+
{!seatButton && <InlineColorPicker id={getColorPopoverId(idPrefix)} host={popover} value={color} onChange={onColorChange} swatches={swatches} takenValue={takenColor} allowSwapTaken={allowSwapTaken} alignOverride={colorAlignOverride} rotation={colorRotation} dark={dark} tag={isHuman ? selectedProfile?.tag : undefined} icon={isHuman ? humanIcon : cpuIcon} />}
|
|
158
178
|
|
|
159
|
-
|
|
160
|
-
|
|
179
|
+
{secondPicker}
|
|
180
|
+
</View>
|
|
181
|
+
)}
|
|
161
182
|
|
|
162
183
|
{isHuman && showReadyButton && onToggleReady && <ReadyButton color={color} ready={ready ?? false} onToggleReady={onToggleReady} />}
|
|
163
184
|
</View>
|
|
@@ -175,9 +196,9 @@ const styles = StyleSheet.create({
|
|
|
175
196
|
panelOpen: {
|
|
176
197
|
zIndex: 100
|
|
177
198
|
},
|
|
178
|
-
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the color
|
|
179
|
-
// present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
180
|
-
// column.
|
|
199
|
+
// Same gap as `panel`'s own vertical rhythm, reused horizontally - the fallback color swatch and
|
|
200
|
+
// (when present) secondPicker sit side by side within this row instead of stacked in the panel's
|
|
201
|
+
// own column.
|
|
181
202
|
pickerRow: {
|
|
182
203
|
alignItems: 'center',
|
|
183
204
|
flexDirection: 'row',
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ export { getLabeledDropdownContentHeight, LABELED_DROPDOWN_POPOVER_WIDTH, Labele
|
|
|
16
16
|
export { loadSkiaWeb } from './loadSkiaWeb'
|
|
17
17
|
export { overlayActionStyles, overlayStyles } from './overlayCard'
|
|
18
18
|
export { OverlayBackdrop, type OverlayBackdropProps, OverlayCard, type OverlayCardProps, type OverlayCardThemeColors, useOverlayCardColors } from './OverlaySurface'
|
|
19
|
-
export { getColorPopoverId, getCpuDifficultyPopoverId, PlayerSetupPanel, type PlayerSetupPanelProps } from './PlayerSetupPanel'
|
|
19
|
+
export { getColorPopoverId, getCpuDifficultyPopoverId, getProfilePopoverId, PlayerSetupPanel, type PlayerSetupPanelProps } from './PlayerSetupPanel'
|
|
20
20
|
export { PopoverBody } from './PopoverBody'
|
|
21
21
|
export { PressAwayOverlay } from './PressAwayOverlay'
|
|
22
22
|
export { ReadyButton } from './ReadyButton'
|