@tastic/hud 0.1.5 → 0.2.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/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +309 -105
- package/dist/index.mjs +274 -71
- package/package.json +13 -11
- package/src/BaseSettingsDialog.tsx +371 -0
- package/src/index.ts +1 -0
package/dist/index.mjs
CHANGED
|
@@ -1,39 +1,241 @@
|
|
|
1
|
+
// src/BaseSettingsDialog.tsx
|
|
2
|
+
import { AutoAppearancePicker, Dialog, useAutoPaperTheme } from "@rific/auto-paper";
|
|
3
|
+
import { Button, SoundContext, TouchableRipple, useHapticSettings, useSoundSettings, useVibration } from "@rific/feedback-press";
|
|
4
|
+
import { useUpdater } from "@rific/updater";
|
|
5
|
+
import { useIsTouchPrimaryDevice } from "@tastic/core";
|
|
6
|
+
import { useContext, useState } from "react";
|
|
7
|
+
import { Platform, ScrollView, StyleSheet, View } from "react-native";
|
|
8
|
+
import { Icon, Portal, Text } from "react-native-paper";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
function SettingIcon({ source, color, containerColor }) {
|
|
11
|
+
return /* @__PURE__ */ jsx(View, { style: [styles.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ jsx(Icon, { source, size: 18, color }) });
|
|
12
|
+
}
|
|
13
|
+
function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
|
|
14
|
+
const { dark, colors } = useAutoPaperTheme();
|
|
15
|
+
const isTouchPrimary = useIsTouchPrimaryDevice();
|
|
16
|
+
const { settings: hapticSettings, set: setHapticSettings } = useHapticSettings();
|
|
17
|
+
const { settings: soundSettings, set: setSoundSettings } = useSoundSettings();
|
|
18
|
+
const sound = useContext(SoundContext);
|
|
19
|
+
const { forceShort: forceHapticFeedback } = useVibration();
|
|
20
|
+
const [infoMessage, setInfoMessage] = useState(null);
|
|
21
|
+
const { check, checking, updateReady } = useUpdater({
|
|
22
|
+
autoCheck: false,
|
|
23
|
+
autoPrompt: false,
|
|
24
|
+
onError: onUpdateError,
|
|
25
|
+
onInfo: (title, message) => setInfoMessage({ title, message })
|
|
26
|
+
});
|
|
27
|
+
const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
|
|
28
|
+
const showEdgeGuard = Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
|
|
29
|
+
const showHaptics = Platform.OS !== "web" && !hideHaptics;
|
|
30
|
+
const showSound = !hideSound;
|
|
31
|
+
const showAppearance = !hideAppearance;
|
|
32
|
+
const showUpdateCheck = Platform.OS !== "web" && !hideUpdateCheck;
|
|
33
|
+
const fg = dark ? "#FFFFFF" : "#000000";
|
|
34
|
+
const cardBg = dark ? "#111111" : "#F2F2F2";
|
|
35
|
+
const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
|
|
36
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
|
+
/* @__PURE__ */ jsxs(Dialog, { visible, onDismiss, style: [styles.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
|
|
38
|
+
/* @__PURE__ */ jsx(Dialog.Title, { children: "Settings" }),
|
|
39
|
+
/* @__PURE__ */ jsx(Dialog.ScrollArea, { style: styles.scrollArea, children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.content, showsVerticalScrollIndicator: false, children: [
|
|
40
|
+
/* @__PURE__ */ jsxs(View, { style: styles.toggleGroup, children: [
|
|
41
|
+
showLockOrientation && /* @__PURE__ */ jsx(TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ jsxs(View, { style: styles.toggleContent, children: [
|
|
42
|
+
/* @__PURE__ */ jsx(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
|
|
43
|
+
/* @__PURE__ */ jsxs(View, { style: styles.flexShrink, children: [
|
|
44
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
|
|
45
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
|
|
46
|
+
] })
|
|
47
|
+
] }) }),
|
|
48
|
+
showEdgeGuard && /* @__PURE__ */ jsx(TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ jsxs(View, { style: styles.toggleContent, children: [
|
|
49
|
+
/* @__PURE__ */ jsx(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
|
|
50
|
+
/* @__PURE__ */ jsxs(View, { style: styles.flexShrink, children: [
|
|
51
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
|
|
52
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
|
|
53
|
+
] })
|
|
54
|
+
] }) }),
|
|
55
|
+
(showSound || showHaptics) && /* @__PURE__ */ jsxs(View, { style: styles.toggleRow, children: [
|
|
56
|
+
showSound && /* @__PURE__ */ jsx(
|
|
57
|
+
TouchableRipple,
|
|
58
|
+
{
|
|
59
|
+
soundDisabled: true,
|
|
60
|
+
onPress: () => {
|
|
61
|
+
const enabled = !soundSettings.enabled;
|
|
62
|
+
setSoundSettings({ enabled });
|
|
63
|
+
if (enabled) sound.selection?.();
|
|
64
|
+
},
|
|
65
|
+
style: styles.toggleButtonInRow,
|
|
66
|
+
accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
|
|
67
|
+
children: /* @__PURE__ */ jsxs(View, { style: styles.toggleContent, children: [
|
|
68
|
+
/* @__PURE__ */ jsx(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
69
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
|
|
70
|
+
] })
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
showHaptics && /* @__PURE__ */ jsx(
|
|
74
|
+
TouchableRipple,
|
|
75
|
+
{
|
|
76
|
+
hapticDisabled: true,
|
|
77
|
+
onPress: () => {
|
|
78
|
+
const vibrate = !hapticSettings.vibrate;
|
|
79
|
+
setHapticSettings({ vibrate });
|
|
80
|
+
if (vibrate) forceHapticFeedback();
|
|
81
|
+
},
|
|
82
|
+
style: styles.toggleButtonInRow,
|
|
83
|
+
accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
|
|
84
|
+
children: /* @__PURE__ */ jsxs(View, { style: styles.toggleContent, children: [
|
|
85
|
+
/* @__PURE__ */ jsx(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
|
|
86
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
|
|
87
|
+
] })
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
] })
|
|
91
|
+
] }),
|
|
92
|
+
showAppearance && /* @__PURE__ */ jsxs(View, { style: styles.section, children: [
|
|
93
|
+
/* @__PURE__ */ jsx(Text, { variant: "labelMedium", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
|
|
94
|
+
/* @__PURE__ */ jsx(AutoAppearancePicker, { showLabels: false })
|
|
95
|
+
] }),
|
|
96
|
+
children,
|
|
97
|
+
showUpdateCheck && /* @__PURE__ */ jsxs(View, { style: styles.section, children: [
|
|
98
|
+
/* @__PURE__ */ jsxs(Text, { variant: "labelSmall", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: [
|
|
99
|
+
"VERSION ",
|
|
100
|
+
version,
|
|
101
|
+
updateReady ? " \xB7 UPDATE READY" : ""
|
|
102
|
+
] }),
|
|
103
|
+
/* @__PURE__ */ jsx(Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
|
|
104
|
+
] })
|
|
105
|
+
] }) })
|
|
106
|
+
] }),
|
|
107
|
+
infoMessage && /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(View, { style: styles.overlay, children: /* @__PURE__ */ jsxs(View, { style: [styles.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
|
|
108
|
+
/* @__PURE__ */ jsx(Icon, { source: "information-outline", size: 64, color: colors.secondary }),
|
|
109
|
+
/* @__PURE__ */ jsx(Text, { variant: "headlineLarge", style: [styles.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
|
|
110
|
+
/* @__PURE__ */ jsx(Text, { variant: "bodyLarge", style: [styles.overlayBody, { color: fg }], children: infoMessage.message }),
|
|
111
|
+
/* @__PURE__ */ jsx(Button, { mode: "contained", onPress: () => setInfoMessage(null), style: styles.overlayButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: "OK" })
|
|
112
|
+
] }) }) })
|
|
113
|
+
] });
|
|
114
|
+
}
|
|
115
|
+
var styles = StyleSheet.create({
|
|
116
|
+
content: {
|
|
117
|
+
gap: 24,
|
|
118
|
+
paddingVertical: 20
|
|
119
|
+
},
|
|
120
|
+
// Caps the card so it never grows past the screen — without this the dialog just keeps
|
|
121
|
+
// growing to fit its content and the overflow gets clipped by the screen edge, which is
|
|
122
|
+
// what happened in landscape where there's less height to work with. Dialog.ScrollArea +
|
|
123
|
+
// ScrollView below then take over and let the content scroll within that bound.
|
|
124
|
+
dialog: {
|
|
125
|
+
maxHeight: "90%"
|
|
126
|
+
},
|
|
127
|
+
flexShrink: {
|
|
128
|
+
flexShrink: 1
|
|
129
|
+
},
|
|
130
|
+
iconBadge: {
|
|
131
|
+
alignItems: "center",
|
|
132
|
+
borderRadius: 10,
|
|
133
|
+
height: 36,
|
|
134
|
+
justifyContent: "center",
|
|
135
|
+
width: 36
|
|
136
|
+
},
|
|
137
|
+
overlay: {
|
|
138
|
+
alignItems: "center",
|
|
139
|
+
backgroundColor: "rgba(0,0,0,0.72)",
|
|
140
|
+
bottom: 0,
|
|
141
|
+
justifyContent: "center",
|
|
142
|
+
left: 0,
|
|
143
|
+
position: "absolute",
|
|
144
|
+
right: 0,
|
|
145
|
+
top: 0
|
|
146
|
+
},
|
|
147
|
+
overlayBody: { textAlign: "center" },
|
|
148
|
+
overlayButton: { width: 160 },
|
|
149
|
+
overlayCard: {
|
|
150
|
+
alignItems: "center",
|
|
151
|
+
borderRadius: 20,
|
|
152
|
+
borderWidth: 1,
|
|
153
|
+
gap: 16,
|
|
154
|
+
maxWidth: 360,
|
|
155
|
+
padding: 32
|
|
156
|
+
},
|
|
157
|
+
overlayTitle: { fontWeight: "bold" },
|
|
158
|
+
scrollArea: {
|
|
159
|
+
marginBottom: 0
|
|
160
|
+
},
|
|
161
|
+
section: {
|
|
162
|
+
gap: 12
|
|
163
|
+
},
|
|
164
|
+
sectionLabel: {
|
|
165
|
+
letterSpacing: 2
|
|
166
|
+
},
|
|
167
|
+
// Negative margin cancels the padding so the icon still lines up with APPEARANCE/SOUND &
|
|
168
|
+
// HAPTICS below, while the ripple/hover highlight itself gets room to breathe on both sides
|
|
169
|
+
// instead of a flush edge-to-edge slab. overflow: 'hidden' makes sure that highlight actually
|
|
170
|
+
// clips to borderRadius instead of drawing as a plain rectangle.
|
|
171
|
+
toggleButton: {
|
|
172
|
+
borderRadius: 12,
|
|
173
|
+
marginHorizontal: -12,
|
|
174
|
+
overflow: "hidden",
|
|
175
|
+
paddingHorizontal: 12,
|
|
176
|
+
paddingVertical: 10
|
|
177
|
+
},
|
|
178
|
+
// Same shape as toggleButton but `flex: 1` instead of its own `marginHorizontal: -12` — two of
|
|
179
|
+
// these side by side in toggleRow would otherwise both pull inward and collide in the middle.
|
|
180
|
+
// toggleRow carries that edge-alignment margin once, for the row as a whole, instead.
|
|
181
|
+
toggleButtonInRow: {
|
|
182
|
+
borderRadius: 12,
|
|
183
|
+
flex: 1,
|
|
184
|
+
overflow: "hidden",
|
|
185
|
+
paddingHorizontal: 12,
|
|
186
|
+
paddingVertical: 10
|
|
187
|
+
},
|
|
188
|
+
toggleContent: {
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
flexDirection: "row",
|
|
191
|
+
gap: 12
|
|
192
|
+
},
|
|
193
|
+
toggleGroup: {
|
|
194
|
+
gap: 4
|
|
195
|
+
},
|
|
196
|
+
toggleRow: {
|
|
197
|
+
flexDirection: "row",
|
|
198
|
+
gap: 4,
|
|
199
|
+
marginHorizontal: -12
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
1
203
|
// src/fonts.ts
|
|
2
|
-
import { Platform } from "react-native";
|
|
3
|
-
var MONO_FONT =
|
|
204
|
+
import { Platform as Platform2 } from "react-native";
|
|
205
|
+
var MONO_FONT = Platform2.select({ ios: "Menlo", android: "monospace", default: "monospace" });
|
|
4
206
|
|
|
5
207
|
// src/InlineColorPicker.tsx
|
|
6
208
|
import { defaultColors, getContrastColor } from "@rific/auto-paper";
|
|
7
|
-
import { TouchableRipple } from "@rific/feedback-press";
|
|
209
|
+
import { TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
|
|
8
210
|
import { clamp } from "@tastic/core";
|
|
9
|
-
import { ScrollView, StyleSheet as
|
|
10
|
-
import { Icon, Text } from "react-native-paper";
|
|
211
|
+
import { ScrollView as ScrollView2, StyleSheet as StyleSheet3, useWindowDimensions as useWindowDimensions2, View as View4 } from "react-native";
|
|
212
|
+
import { Icon as Icon2, Text as Text2 } from "react-native-paper";
|
|
11
213
|
|
|
12
214
|
// src/PopoverBody.tsx
|
|
13
|
-
import { StyleSheet, View } from "react-native";
|
|
14
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
215
|
+
import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
|
|
216
|
+
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
15
217
|
var CARET_SIZE = 7;
|
|
16
218
|
var CARET_RING_OFFSET = 2;
|
|
17
219
|
var CARET_DIP = 1;
|
|
18
220
|
function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
|
|
19
221
|
if (!visible) return null;
|
|
20
222
|
const above = verticalAlign === "above";
|
|
21
|
-
const alignStyle = align === "left" ?
|
|
22
|
-
const verticalStyle = above ?
|
|
223
|
+
const alignStyle = align === "left" ? styles2.contentLeft : align === "right" ? styles2.contentRight : styles2.contentCenter;
|
|
224
|
+
const verticalStyle = above ? styles2.contentAbove : styles2.contentBelow;
|
|
23
225
|
const ringSize = CARET_SIZE + caretBorderWidth;
|
|
24
226
|
const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
|
|
25
227
|
const caretFill = above ? "borderTopColor" : "borderBottomColor";
|
|
26
228
|
const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
|
|
27
229
|
const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
|
|
28
|
-
return /* @__PURE__ */
|
|
230
|
+
return /* @__PURE__ */ jsxs2(View2, { style: [styles2.content, alignStyle, verticalStyle], children: [
|
|
29
231
|
children,
|
|
30
|
-
caretColor && /* @__PURE__ */
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
-
/* @__PURE__ */
|
|
232
|
+
caretColor && /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
233
|
+
/* @__PURE__ */ jsx2(View2, { style: [styles2.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
|
|
234
|
+
/* @__PURE__ */ jsx2(View2, { style: [styles2.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
|
|
33
235
|
] })
|
|
34
236
|
] });
|
|
35
237
|
}
|
|
36
|
-
var
|
|
238
|
+
var styles2 = StyleSheet2.create({
|
|
37
239
|
caret: {
|
|
38
240
|
borderLeftColor: "transparent",
|
|
39
241
|
borderRightColor: "transparent",
|
|
@@ -76,17 +278,17 @@ var styles = StyleSheet.create({
|
|
|
76
278
|
});
|
|
77
279
|
|
|
78
280
|
// src/useAutoAlign.ts
|
|
79
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
281
|
+
import { useCallback, useEffect, useRef, useState as useState2 } from "react";
|
|
80
282
|
import { useWindowDimensions } from "react-native";
|
|
81
283
|
var EDGE_MARGIN = 12;
|
|
82
284
|
function useAutoAlign(open, contentWidth, contentHeight) {
|
|
83
285
|
const triggerRef = useRef(null);
|
|
84
286
|
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
|
|
85
|
-
const [align, setAlign] =
|
|
86
|
-
const [verticalAlign, setVerticalAlign] =
|
|
87
|
-
const [maxHeight, setMaxHeight] =
|
|
88
|
-
const [measured, setMeasured] =
|
|
89
|
-
const [prevOpen, setPrevOpen] =
|
|
287
|
+
const [align, setAlign] = useState2("center");
|
|
288
|
+
const [verticalAlign, setVerticalAlign] = useState2("below");
|
|
289
|
+
const [maxHeight, setMaxHeight] = useState2(contentHeight);
|
|
290
|
+
const [measured, setMeasured] = useState2(false);
|
|
291
|
+
const [prevOpen, setPrevOpen] = useState2(open);
|
|
90
292
|
if (open !== prevOpen) {
|
|
91
293
|
setPrevOpen(open);
|
|
92
294
|
if (open) setMeasured(false);
|
|
@@ -114,7 +316,7 @@ function useAutoAlign(open, contentWidth, contentHeight) {
|
|
|
114
316
|
}
|
|
115
317
|
|
|
116
318
|
// src/InlineColorPicker.tsx
|
|
117
|
-
import { jsx as
|
|
319
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
118
320
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
119
321
|
var DEFAULT_SIZE = 48;
|
|
120
322
|
var SWATCH_SIZE = 28;
|
|
@@ -140,14 +342,14 @@ function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors
|
|
|
140
342
|
const align = alignOverride ?? autoAlign;
|
|
141
343
|
const contrastColor = getContrastColor(value);
|
|
142
344
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
|
|
143
|
-
return /* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
/* @__PURE__ */
|
|
345
|
+
return /* @__PURE__ */ jsxs3(View4, { style: [styles3.anchor, open && styles3.anchorOpen], children: [
|
|
346
|
+
/* @__PURE__ */ jsx3(TouchableRipple2, { onPress: () => host.toggle(id), borderless: true, style: [styles3.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ jsx3(View4, { ref: triggerRef, collapsable: false, style: styles3.triggerMeasure, children: tag ? /* @__PURE__ */ jsx3(Text2, { style: [styles3.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx3(Icon2, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
|
|
347
|
+
/* @__PURE__ */ jsx3(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ jsx3(ScrollView2, { style: [styles3.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles3.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
|
|
146
348
|
const selected = swatch.value.toLowerCase() === value.toLowerCase();
|
|
147
349
|
const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
|
|
148
350
|
const swappable = taken && allowSwapTaken;
|
|
149
|
-
return /* @__PURE__ */
|
|
150
|
-
|
|
351
|
+
return /* @__PURE__ */ jsx3(
|
|
352
|
+
TouchableRipple2,
|
|
151
353
|
{
|
|
152
354
|
disabled: taken && !swappable,
|
|
153
355
|
onPress: () => {
|
|
@@ -155,15 +357,15 @@ function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors
|
|
|
155
357
|
if (autoDismiss) host.close();
|
|
156
358
|
},
|
|
157
359
|
borderless: true,
|
|
158
|
-
style: [
|
|
159
|
-
children: selected ? /* @__PURE__ */
|
|
360
|
+
style: [styles3.swatch, { backgroundColor: swatch.value }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken],
|
|
361
|
+
children: selected ? /* @__PURE__ */ jsx3(Icon2, { source: "check", size: 16, color: getContrastColor(swatch.value) }) : swappable ? /* @__PURE__ */ jsx3(Icon2, { source: "swap-horizontal", size: 16, color: getContrastColor(swatch.value) }) : taken ? /* @__PURE__ */ jsx3(Icon2, { source: "close", size: 16, color: getContrastColor(swatch.value) }) : /* @__PURE__ */ jsx3(View4, {})
|
|
160
362
|
},
|
|
161
363
|
swatch.value
|
|
162
364
|
);
|
|
163
365
|
}) }) })
|
|
164
366
|
] });
|
|
165
367
|
}
|
|
166
|
-
var
|
|
368
|
+
var styles3 = StyleSheet3.create({
|
|
167
369
|
anchor: {
|
|
168
370
|
position: "relative"
|
|
169
371
|
},
|
|
@@ -240,25 +442,25 @@ function loadSkiaWeb() {
|
|
|
240
442
|
}
|
|
241
443
|
|
|
242
444
|
// src/PressAwayOverlay.tsx
|
|
243
|
-
import { Pressable, StyleSheet as
|
|
244
|
-
import { jsx as
|
|
445
|
+
import { Pressable, StyleSheet as StyleSheet4 } from "react-native";
|
|
446
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
245
447
|
function PressAwayOverlay({ active, onPress, style }) {
|
|
246
448
|
if (!active) return null;
|
|
247
|
-
return /* @__PURE__ */
|
|
449
|
+
return /* @__PURE__ */ jsx4(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet4.absoluteFill, style] });
|
|
248
450
|
}
|
|
249
451
|
|
|
250
452
|
// src/ReadyButton.tsx
|
|
251
|
-
import { TouchableRipple as
|
|
252
|
-
import { StyleSheet as
|
|
253
|
-
import { Text as
|
|
254
|
-
import { jsx as
|
|
453
|
+
import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
|
|
454
|
+
import { StyleSheet as StyleSheet5 } from "react-native";
|
|
455
|
+
import { Text as Text3 } from "react-native-paper";
|
|
456
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
255
457
|
function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
|
|
256
|
-
const readyButtonStyle = [
|
|
458
|
+
const readyButtonStyle = [styles4.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
|
|
257
459
|
const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
|
|
258
460
|
const labelColorStyle = { color: ready ? "#000000" : color };
|
|
259
|
-
return /* @__PURE__ */
|
|
461
|
+
return /* @__PURE__ */ jsx5(TouchableRipple3, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx5(Text3, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
|
|
260
462
|
}
|
|
261
|
-
var
|
|
463
|
+
var styles4 = StyleSheet5.create({
|
|
262
464
|
readyButton: {
|
|
263
465
|
alignItems: "center",
|
|
264
466
|
borderRadius: 12,
|
|
@@ -272,20 +474,20 @@ var styles3 = StyleSheet4.create({
|
|
|
272
474
|
|
|
273
475
|
// src/SectionedDropdown.tsx
|
|
274
476
|
import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
|
|
275
|
-
import { IconButton, TouchableRipple as
|
|
276
|
-
import { ScrollView as
|
|
277
|
-
import { Icon as
|
|
477
|
+
import { IconButton, TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
|
|
478
|
+
import { ScrollView as ScrollView3, StyleSheet as StyleSheet6, View as View5 } from "react-native";
|
|
479
|
+
import { Icon as Icon3, Text as Text4 } from "react-native-paper";
|
|
278
480
|
|
|
279
481
|
// src/TriggerGaugeHost.tsx
|
|
280
482
|
import { lazy, Suspense } from "react";
|
|
281
|
-
import { jsx as
|
|
483
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
282
484
|
var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-2OJOOF6I.mjs").then((m) => ({ default: m.TriggerGauge }))));
|
|
283
485
|
function TriggerGaugeHost(props) {
|
|
284
|
-
return /* @__PURE__ */
|
|
486
|
+
return /* @__PURE__ */ jsx6(Suspense, { fallback: null, children: /* @__PURE__ */ jsx6(LazyTriggerGauge, { ...props }) });
|
|
285
487
|
}
|
|
286
488
|
|
|
287
489
|
// src/SectionedDropdown.tsx
|
|
288
|
-
import { Fragment as
|
|
490
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
289
491
|
var MENU_BORDER_WIDTH = 1;
|
|
290
492
|
var TRIGGER_SIZE = 44;
|
|
291
493
|
var MENU_MIN_WIDTH = 200;
|
|
@@ -331,17 +533,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
|
|
|
331
533
|
}
|
|
332
534
|
gaugeOffset += section.options.length;
|
|
333
535
|
}
|
|
334
|
-
const anchorStyle = [
|
|
536
|
+
const anchorStyle = [styles5.anchor, open && styles5.anchorOpen];
|
|
335
537
|
const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
|
|
336
538
|
const dividerStyle = { backgroundColor: menuBorderColor };
|
|
337
|
-
return /* @__PURE__ */
|
|
338
|
-
/* @__PURE__ */
|
|
339
|
-
/* @__PURE__ */
|
|
340
|
-
/* @__PURE__ */
|
|
539
|
+
return /* @__PURE__ */ jsxs4(View5, { style: anchorStyle, children: [
|
|
540
|
+
/* @__PURE__ */ jsxs4(View5, { ref: triggerRef, collapsable: false, style: styles5.triggerBox, children: [
|
|
541
|
+
/* @__PURE__ */ jsx7(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
|
|
542
|
+
/* @__PURE__ */ jsx7(IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
|
|
341
543
|
] }),
|
|
342
|
-
/* @__PURE__ */
|
|
343
|
-
sectionIndex > 0 && /* @__PURE__ */
|
|
344
|
-
section.kind === "single" ? /* @__PURE__ */
|
|
544
|
+
/* @__PURE__ */ jsx7(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ jsx7(ScrollView3, { style: [styles5.menu, menuStyle], contentContainerStyle: styles5.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs4(View5, { children: [
|
|
545
|
+
sectionIndex > 0 && /* @__PURE__ */ jsx7(View5, { style: [styles5.divider, dividerStyle] }),
|
|
546
|
+
section.kind === "single" ? /* @__PURE__ */ jsx7(
|
|
345
547
|
SingleSection,
|
|
346
548
|
{
|
|
347
549
|
section,
|
|
@@ -354,7 +556,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
|
|
|
354
556
|
if (autoDismiss) host.close();
|
|
355
557
|
}
|
|
356
558
|
}
|
|
357
|
-
) : /* @__PURE__ */
|
|
559
|
+
) : /* @__PURE__ */ jsx7(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
|
|
358
560
|
] }, section.id)) }) })
|
|
359
561
|
] });
|
|
360
562
|
}
|
|
@@ -366,17 +568,17 @@ function SingleSection({
|
|
|
366
568
|
labelFontFamily,
|
|
367
569
|
onSelect
|
|
368
570
|
}) {
|
|
369
|
-
return /* @__PURE__ */
|
|
571
|
+
return /* @__PURE__ */ jsx7(Fragment3, { children: section.options.map((option) => {
|
|
370
572
|
const selected = option.value === section.value;
|
|
371
573
|
const rowColor = selected ? onAccentColor : mutedColor;
|
|
372
|
-
const itemStyle = [
|
|
574
|
+
const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }];
|
|
373
575
|
const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
|
|
374
576
|
const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
|
|
375
|
-
return /* @__PURE__ */
|
|
376
|
-
option.icon && /* @__PURE__ */
|
|
377
|
-
/* @__PURE__ */
|
|
378
|
-
/* @__PURE__ */
|
|
379
|
-
option.description && /* @__PURE__ */
|
|
577
|
+
return /* @__PURE__ */ jsx7(TouchableRipple4, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.itemRow, children: [
|
|
578
|
+
option.icon && /* @__PURE__ */ jsx7(Icon3, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
|
|
579
|
+
/* @__PURE__ */ jsxs4(View5, { style: styles5.itemLabelColumn, children: [
|
|
580
|
+
/* @__PURE__ */ jsx7(Text4, { variant: "labelLarge", style: labelStyle, children: option.label }),
|
|
581
|
+
option.description && /* @__PURE__ */ jsx7(Text4, { variant: "labelSmall", style: descriptionStyle, children: option.description })
|
|
380
582
|
] })
|
|
381
583
|
] }) }, option.value);
|
|
382
584
|
}) });
|
|
@@ -390,23 +592,23 @@ function MultiSection({
|
|
|
390
592
|
allClearLabels
|
|
391
593
|
}) {
|
|
392
594
|
const allSelected = section.value.length === section.options.length;
|
|
393
|
-
return /* @__PURE__ */
|
|
595
|
+
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
394
596
|
section.options.map((option, i) => {
|
|
395
597
|
const selected = section.value.includes(option.value);
|
|
396
598
|
const rowColor = selected ? onAccentColor : mutedColor;
|
|
397
599
|
const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
|
|
398
600
|
const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
|
|
399
|
-
const itemStyle = [
|
|
601
|
+
const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
|
|
400
602
|
const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
|
|
401
|
-
return /* @__PURE__ */
|
|
402
|
-
option.icon && /* @__PURE__ */
|
|
403
|
-
/* @__PURE__ */
|
|
603
|
+
return /* @__PURE__ */ jsx7(TouchableRipple4, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.itemRow, children: [
|
|
604
|
+
option.icon && /* @__PURE__ */ jsx7(Icon3, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
|
|
605
|
+
/* @__PURE__ */ jsx7(Text4, { variant: "labelLarge", style: [styles5.itemLabelFlex, labelStyle], children: option.label })
|
|
404
606
|
] }) }, option.value);
|
|
405
607
|
}),
|
|
406
|
-
section.allClear && /* @__PURE__ */
|
|
608
|
+
section.allClear && /* @__PURE__ */ jsx7(TouchableRipple4, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles5.item, children: /* @__PURE__ */ jsx7(Text4, { variant: "labelLarge", style: [styles5.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
|
|
407
609
|
] });
|
|
408
610
|
}
|
|
409
|
-
var
|
|
611
|
+
var styles5 = StyleSheet6.create({
|
|
410
612
|
allClearLabel: {
|
|
411
613
|
fontWeight: "bold",
|
|
412
614
|
textAlign: "center"
|
|
@@ -423,7 +625,7 @@ var styles4 = StyleSheet5.create({
|
|
|
423
625
|
zIndex: 100
|
|
424
626
|
},
|
|
425
627
|
divider: {
|
|
426
|
-
height:
|
|
628
|
+
height: StyleSheet6.hairlineWidth,
|
|
427
629
|
marginVertical: 4
|
|
428
630
|
},
|
|
429
631
|
item: {
|
|
@@ -471,9 +673,9 @@ var styles4 = StyleSheet5.create({
|
|
|
471
673
|
});
|
|
472
674
|
|
|
473
675
|
// src/usePopoverHost.ts
|
|
474
|
-
import { useCallback as useCallback2, useState as
|
|
676
|
+
import { useCallback as useCallback2, useState as useState3 } from "react";
|
|
475
677
|
function usePopoverHost() {
|
|
476
|
-
const [openId, setOpenId] =
|
|
678
|
+
const [openId, setOpenId] = useState3(null);
|
|
477
679
|
const toggle = useCallback2((id) => {
|
|
478
680
|
setOpenId((prev) => prev === id ? null : id);
|
|
479
681
|
}, []);
|
|
@@ -481,6 +683,7 @@ function usePopoverHost() {
|
|
|
481
683
|
return { openId, toggle, close };
|
|
482
684
|
}
|
|
483
685
|
export {
|
|
686
|
+
BaseSettingsDialog,
|
|
484
687
|
InlineColorPicker,
|
|
485
688
|
MONO_FONT,
|
|
486
689
|
PopoverBody,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tastic/hud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -46,9 +46,8 @@
|
|
|
46
46
|
"!src/__mocks__"
|
|
47
47
|
],
|
|
48
48
|
"scripts": {
|
|
49
|
-
"build": "tsup
|
|
50
|
-
"build:watch": "tsup
|
|
51
|
-
"ci": "npm run lint && npm test && npm run typecheck && npm run build",
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"build:watch": "tsup --watch",
|
|
52
51
|
"fix": "eslint --fix",
|
|
53
52
|
"lint": "eslint",
|
|
54
53
|
"prepublishOnly": "npm run build",
|
|
@@ -59,16 +58,19 @@
|
|
|
59
58
|
"test": "jest",
|
|
60
59
|
"test:watch": "jest --watchAll",
|
|
61
60
|
"typecheck": "tsc --noEmit",
|
|
62
|
-
"
|
|
61
|
+
"verify": "npm run lint && npm test && npm run typecheck && npm run build",
|
|
62
|
+
"preversion": "npm run verify"
|
|
63
63
|
},
|
|
64
64
|
"prettier": "@infinitetoken/eslint-config/prettier",
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@infinitetoken/eslint-config": "^0.1.
|
|
67
|
-
"@infinitetoken/
|
|
68
|
-
"@
|
|
69
|
-
"@rific/
|
|
66
|
+
"@infinitetoken/eslint-config": "^0.1.8",
|
|
67
|
+
"@infinitetoken/jest-config": "^0.2.0",
|
|
68
|
+
"@infinitetoken/tsconfig": "^0.3.0",
|
|
69
|
+
"@rific/auto-paper": "^0.10.3",
|
|
70
|
+
"@rific/feedback-press": "^0.10.8",
|
|
71
|
+
"@rific/updater": "^0.4.2",
|
|
70
72
|
"@shopify/react-native-skia": "^2.6.2",
|
|
71
|
-
"@tastic/core": "^0.1.
|
|
73
|
+
"@tastic/core": "^0.1.5",
|
|
72
74
|
"@testing-library/dom": "^10.4.1",
|
|
73
75
|
"@testing-library/react": "^16.3.2",
|
|
74
76
|
"@types/jest": "^30.0.0",
|
|
@@ -84,13 +86,13 @@
|
|
|
84
86
|
"react-native": "^0.85.3",
|
|
85
87
|
"react-native-paper": "^5.15.2",
|
|
86
88
|
"react-native-reanimated": "^4.5.1",
|
|
87
|
-
"ts-jest": "^29.4.9",
|
|
88
89
|
"tsup": "^8.0.0",
|
|
89
90
|
"typescript": "^6.0.3"
|
|
90
91
|
},
|
|
91
92
|
"peerDependencies": {
|
|
92
93
|
"@rific/auto-paper": ">=0.9.0",
|
|
93
94
|
"@rific/feedback-press": ">=0.10.0",
|
|
95
|
+
"@rific/updater": ">=0.4.0",
|
|
94
96
|
"@shopify/react-native-skia": ">=1.5.0",
|
|
95
97
|
"@tastic/core": ">=0.1.0",
|
|
96
98
|
"react": ">=19.0.0",
|