@tastic/hud 0.1.6 → 0.3.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.js CHANGED
@@ -28,22 +28,22 @@ __export(TriggerGauge_exports, {
28
28
  function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, dashWidth, startDeg = DEFAULT_START_DEG, endDeg = startDeg }) {
29
29
  const totalDurationMs = MOUNT_DURATION_MS + Math.max(0, segments - 1) * MOUNT_STAGGER_MS;
30
30
  const mountProgress = (0, import_react_native_reanimated.useSharedValue)(0);
31
- (0, import_react2.useLayoutEffect)(() => {
31
+ (0, import_react3.useLayoutEffect)(() => {
32
32
  mountProgress.value = (0, import_react_native_reanimated.withTiming)(1, { duration: totalDurationMs, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
33
33
  }, []);
34
34
  const rawSweepDeg = ((endDeg - startDeg) % 360 + 360) % 360;
35
35
  const totalSweepDeg = rawSweepDeg === 0 ? 360 : rawSweepDeg;
36
36
  const step = segments > 0 ? totalSweepDeg / segments : 0;
37
- const centerDeg = (0, import_react2.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
37
+ const centerDeg = (0, import_react3.useCallback)((i) => startDeg + (i + 0.5) * step, [startDeg, step]);
38
38
  const singleSelect = litIndices.length === 1;
39
39
  const angleDeg = (0, import_react_native_reanimated.useSharedValue)(centerDeg(litIndices[0] ?? 0));
40
40
  const litIndicesKey = [...litIndices].sort((a, b) => a - b).join(",");
41
- const [transitionFrom, setTransitionFrom] = (0, import_react2.useState)(litIndices);
41
+ const [transitionFrom, setTransitionFrom] = (0, import_react3.useState)(litIndices);
42
42
  const transitionFromKey = [...transitionFrom].sort((a, b) => a - b).join(",");
43
- const lastKeyRef = (0, import_react2.useRef)(litIndicesKey);
44
- const lastIndicesRef = (0, import_react2.useRef)(litIndices);
43
+ const lastKeyRef = (0, import_react3.useRef)(litIndicesKey);
44
+ const lastIndicesRef = (0, import_react3.useRef)(litIndices);
45
45
  const selectionProgress = (0, import_react_native_reanimated.useSharedValue)(1);
46
- (0, import_react2.useLayoutEffect)(() => {
46
+ (0, import_react3.useLayoutEffect)(() => {
47
47
  if (litIndicesKey !== lastKeyRef.current) {
48
48
  if (singleSelect && lastIndicesRef.current.length === 1) {
49
49
  angleDeg.value = (0, import_react_native_reanimated.withTiming)(centerDeg(litIndices[0]), { duration: SELECTION_TRANSITION_DURATION_MS, easing: import_react_native_reanimated.Easing.out(import_react_native_reanimated.Easing.cubic) });
@@ -72,7 +72,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
72
72
  if (mountProgress.value < 1) {
73
73
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
74
74
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
75
- local = (0, import_core2.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
75
+ local = (0, import_core3.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
76
76
  }
77
77
  const sweep = dashAngleDeg * local;
78
78
  if (sweep <= 0) continue;
@@ -96,7 +96,7 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
96
96
  if (!isLitNow) continue;
97
97
  const startFrac = i * MOUNT_STAGGER_MS / totalDurationMs;
98
98
  const endFrac = startFrac + MOUNT_DURATION_MS / totalDurationMs;
99
- local = (0, import_core2.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
99
+ local = (0, import_core3.clamp)((mountProgress.value - startFrac) / (endFrac - startFrac), 0, 1);
100
100
  } else if (isLitNow === wasLit) {
101
101
  local = 1;
102
102
  } else if (isLitNow) {
@@ -120,21 +120,21 @@ function TriggerGauge({ segments, litIndices, size, accentColor, mutedColor, das
120
120
  height: canvasSize,
121
121
  width: canvasSize
122
122
  };
123
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.View, { style: [styles4.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
125
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
123
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native10.View, { style: [styles7.container, gaugeSizeStyle], pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native_skia.Canvas, { style: canvasSizeStyle, children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_skia.Path, { path: unlitPath, style: "stroke", strokeWidth: UNLIT_THICKNESS, strokeCap: "round", color: mutedColor, opacity: 0.35 }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_skia.Path, { path: litPath, style: "stroke", strokeWidth: LIT_THICKNESS, strokeCap: "round", color: accentColor, opacity: 1 })
126
126
  ] }) });
127
127
  }
128
- var import_react_native_skia, import_core2, import_react2, import_react_native7, import_react_native_reanimated, import_jsx_runtime5, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles4;
128
+ var import_react_native_skia, import_core3, import_react3, import_react_native10, import_react_native_reanimated, import_jsx_runtime8, UNLIT_THICKNESS, LIT_THICKNESS, DASH_GAP_PX, ARC_GAP, DEFAULT_START_DEG, MIN_DASH_ANGLE_DEG, MOUNT_DURATION_MS, MOUNT_STAGGER_MS, SELECTION_TRANSITION_DURATION_MS, styles7;
129
129
  var init_TriggerGauge = __esm({
130
130
  "src/TriggerGauge.tsx"() {
131
131
  "use strict";
132
132
  import_react_native_skia = require("@shopify/react-native-skia");
133
- import_core2 = require("@tastic/core");
134
- import_react2 = require("react");
135
- import_react_native7 = require("react-native");
133
+ import_core3 = require("@tastic/core");
134
+ import_react3 = require("react");
135
+ import_react_native10 = require("react-native");
136
136
  import_react_native_reanimated = require("react-native-reanimated");
137
- import_jsx_runtime5 = require("react/jsx-runtime");
137
+ import_jsx_runtime8 = require("react/jsx-runtime");
138
138
  UNLIT_THICKNESS = 3;
139
139
  LIT_THICKNESS = 7;
140
140
  DASH_GAP_PX = 9;
@@ -144,7 +144,7 @@ var init_TriggerGauge = __esm({
144
144
  MOUNT_DURATION_MS = 450;
145
145
  MOUNT_STAGGER_MS = 40;
146
146
  SELECTION_TRANSITION_DURATION_MS = 260;
147
- styles4 = import_react_native7.StyleSheet.create({
147
+ styles7 = import_react_native10.StyleSheet.create({
148
148
  container: {
149
149
  alignItems: "center",
150
150
  justifyContent: "center",
@@ -157,55 +157,282 @@ var init_TriggerGauge = __esm({
157
157
  // src/index.ts
158
158
  var index_exports = {};
159
159
  __export(index_exports, {
160
+ BaseSettingsDialog: () => BaseSettingsDialog,
161
+ CornerActionButtons: () => CornerActionButtons,
160
162
  InlineColorPicker: () => InlineColorPicker,
163
+ LABELED_DROPDOWN_POPOVER_WIDTH: () => LABELED_DROPDOWN_POPOVER_WIDTH,
164
+ LabeledDropdown: () => LabeledDropdown,
161
165
  MONO_FONT: () => MONO_FONT,
162
166
  PopoverBody: () => PopoverBody,
163
167
  PressAwayOverlay: () => PressAwayOverlay,
164
168
  ReadyButton: () => ReadyButton,
165
169
  SectionedDropdown: () => SectionedDropdown,
170
+ SharedActionBand: () => SharedActionBand,
166
171
  TriggerGaugeHost: () => TriggerGaugeHost,
172
+ getLabeledDropdownContentHeight: () => getLabeledDropdownContentHeight,
167
173
  loadSkiaWeb: () => loadSkiaWeb,
168
174
  useAutoAlign: () => useAutoAlign,
169
175
  usePopoverHost: () => usePopoverHost
170
176
  });
171
177
  module.exports = __toCommonJS(index_exports);
172
178
 
173
- // src/fonts.ts
174
- var import_react_native = require("react-native");
175
- var MONO_FONT = import_react_native.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
176
-
177
- // src/InlineColorPicker.tsx
179
+ // src/BaseSettingsDialog.tsx
178
180
  var import_auto_paper = require("@rific/auto-paper");
179
181
  var import_feedback_press = require("@rific/feedback-press");
182
+ var import_updater = require("@rific/updater");
180
183
  var import_core = require("@tastic/core");
181
- var import_react_native4 = require("react-native");
184
+ var import_react = require("react");
185
+ var import_react_native = require("react-native");
182
186
  var import_react_native_paper = require("react-native-paper");
187
+ var import_jsx_runtime = require("react/jsx-runtime");
188
+ function SettingIcon({ source, color, containerColor }) {
189
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source, size: 18, color }) });
190
+ }
191
+ function BaseSettingsDialog({ visible, onDismiss, rotation = 0, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, children }) {
192
+ const { dark, colors } = (0, import_auto_paper.useAutoPaperTheme)();
193
+ const isTouchPrimary = (0, import_core.useIsTouchPrimaryDevice)();
194
+ const { settings: hapticSettings, set: setHapticSettings } = (0, import_feedback_press.useHapticSettings)();
195
+ const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press.useSoundSettings)();
196
+ const sound = (0, import_react.useContext)(import_feedback_press.SoundContext);
197
+ const { forceShort: forceHapticFeedback } = (0, import_feedback_press.useVibration)();
198
+ const [infoMessage, setInfoMessage] = (0, import_react.useState)(null);
199
+ const { check, checking, updateReady } = (0, import_updater.useUpdater)({
200
+ autoCheck: false,
201
+ autoPrompt: false,
202
+ onError: onUpdateError,
203
+ onInfo: (title, message) => setInfoMessage({ title, message })
204
+ });
205
+ const showLockOrientation = isTouchPrimary && lockOrientation !== void 0 && onLockOrientationChange !== void 0;
206
+ const showEdgeGuard = import_react_native.Platform.OS === "ios" && deferBottomEdgeGestures !== void 0 && onDeferBottomEdgeGestures !== void 0;
207
+ const showHaptics = import_react_native.Platform.OS !== "web" && !hideHaptics;
208
+ const showSound = !hideSound;
209
+ const showAppearance = !hideAppearance;
210
+ const showUpdateCheck = import_react_native.Platform.OS !== "web" && !hideUpdateCheck;
211
+ const fg = dark ? "#FFFFFF" : "#000000";
212
+ const cardBg = dark ? "#111111" : "#F2F2F2";
213
+ const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
214
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_auto_paper.Dialog, { visible, onDismiss, style: [styles.dialog, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
216
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.Dialog.Title, { children: "Settings" }),
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.Dialog.ScrollArea, { style: styles.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.ScrollView, { contentContainerStyle: styles.content, showsVerticalScrollIndicator: false, children: [
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleGroup, children: [
219
+ showLockOrientation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onLockOrientationChange(!lockOrientation), style: styles.toggleButton, accessibilityLabel: `Lock orientation ${lockOrientation ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: lockOrientation ? "lock" : "lock-open-variant-outline", color: lockOrientation ? colors.secondary : colors.onSurfaceVariant, containerColor: lockOrientation ? colors.secondaryContainer : colors.surfaceVariant }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.flexShrink, children: [
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Lock Orientation" }),
223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Pins the current layout" })
224
+ ] })
225
+ ] }) }),
226
+ showEdgeGuard && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onDeferBottomEdgeGestures(!deferBottomEdgeGestures), style: styles.toggleButton, accessibilityLabel: `Edge guard ${deferBottomEdgeGestures ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: deferBottomEdgeGestures ? "shield-check-outline" : "shield-off-outline", color: deferBottomEdgeGestures ? colors.secondary : colors.onSurfaceVariant, containerColor: deferBottomEdgeGestures ? colors.secondaryContainer : colors.surfaceVariant }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.flexShrink, children: [
229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Edge Guard" }),
230
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodySmall", numberOfLines: 1, style: { color: colors.onSurfaceVariant }, children: "Requires a second swipe" })
231
+ ] })
232
+ ] }) }),
233
+ (showSound || showHaptics) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleRow, children: [
234
+ showSound && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
235
+ import_feedback_press.TouchableRipple,
236
+ {
237
+ soundDisabled: true,
238
+ onPress: () => {
239
+ const enabled = !soundSettings.enabled;
240
+ setSoundSettings({ enabled });
241
+ if (enabled) sound.selection?.();
242
+ },
243
+ style: styles.toggleButtonInRow,
244
+ accessibilityLabel: `Sound ${soundSettings.enabled ? "on" : "off"}`,
245
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: soundSettings.enabled ? "volume-high" : "volume-off", color: soundSettings.enabled ? colors.tertiary : colors.onSurfaceVariant, containerColor: soundSettings.enabled ? colors.tertiaryContainer : colors.surfaceVariant }),
247
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Sound" })
248
+ ] })
249
+ }
250
+ ),
251
+ showHaptics && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
252
+ import_feedback_press.TouchableRipple,
253
+ {
254
+ hapticDisabled: true,
255
+ onPress: () => {
256
+ const vibrate = !hapticSettings.vibrate;
257
+ setHapticSettings({ vibrate });
258
+ if (vibrate) forceHapticFeedback();
259
+ },
260
+ style: styles.toggleButtonInRow,
261
+ accessibilityLabel: `Haptics ${hapticSettings.vibrate ? "on" : "off"}`,
262
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.toggleContent, children: [
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingIcon, { source: hapticSettings.vibrate ? "vibrate" : "vibrate-off", color: hapticSettings.vibrate ? colors.tertiary : colors.onSurfaceVariant, containerColor: hapticSettings.vibrate ? colors.tertiaryContainer : colors.surfaceVariant }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: { color: colors.onSurface }, children: "Haptics" })
265
+ ] })
266
+ }
267
+ )
268
+ ] })
269
+ ] }),
270
+ showAppearance && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.section, children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "labelMedium", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
272
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_auto_paper.AutoAppearancePicker, { showLabels: false })
273
+ ] }),
274
+ children,
275
+ showUpdateCheck && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: styles.section, children: [
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native_paper.Text, { variant: "labelSmall", style: [styles.sectionLabel, { color: colors.onSurfaceVariant }], children: [
277
+ "VERSION ",
278
+ version,
279
+ updateReady ? " \xB7 UPDATE READY" : ""
280
+ ] }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.Button, { mode: "outlined", onPress: check, loading: checking, disabled: checking, children: "Check for Updates" })
282
+ ] })
283
+ ] }) })
284
+ ] }),
285
+ infoMessage && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: styles.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
286
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "information-outline", size: 64, color: colors.secondary }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "headlineLarge", style: [styles.overlayTitle, { color: colors.secondary }], children: infoMessage.title }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { variant: "bodyLarge", style: [styles.overlayBody, { color: fg }], children: infoMessage.message }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_feedback_press.Button, { mode: "contained", onPress: () => setInfoMessage(null), style: styles.overlayButton, buttonColor: colors.primary, textColor: colors.onPrimary, children: "OK" })
290
+ ] }) }) })
291
+ ] });
292
+ }
293
+ var styles = import_react_native.StyleSheet.create({
294
+ content: {
295
+ gap: 24,
296
+ paddingVertical: 20
297
+ },
298
+ // Caps the card so it never grows past the screen — without this the dialog just keeps
299
+ // growing to fit its content and the overflow gets clipped by the screen edge, which is
300
+ // what happened in landscape where there's less height to work with. Dialog.ScrollArea +
301
+ // ScrollView below then take over and let the content scroll within that bound.
302
+ dialog: {
303
+ maxHeight: "90%"
304
+ },
305
+ flexShrink: {
306
+ flexShrink: 1
307
+ },
308
+ iconBadge: {
309
+ alignItems: "center",
310
+ borderRadius: 10,
311
+ height: 36,
312
+ justifyContent: "center",
313
+ width: 36
314
+ },
315
+ overlay: {
316
+ alignItems: "center",
317
+ backgroundColor: "rgba(0,0,0,0.72)",
318
+ bottom: 0,
319
+ justifyContent: "center",
320
+ left: 0,
321
+ position: "absolute",
322
+ right: 0,
323
+ top: 0
324
+ },
325
+ overlayBody: { textAlign: "center" },
326
+ overlayButton: { width: 160 },
327
+ overlayCard: {
328
+ alignItems: "center",
329
+ borderRadius: 20,
330
+ borderWidth: 1,
331
+ gap: 16,
332
+ maxWidth: 360,
333
+ padding: 32
334
+ },
335
+ overlayTitle: { fontWeight: "bold" },
336
+ scrollArea: {
337
+ marginBottom: 0
338
+ },
339
+ section: {
340
+ gap: 12
341
+ },
342
+ sectionLabel: {
343
+ letterSpacing: 2
344
+ },
345
+ // Negative margin cancels the padding so the icon still lines up with APPEARANCE/SOUND &
346
+ // HAPTICS below, while the ripple/hover highlight itself gets room to breathe on both sides
347
+ // instead of a flush edge-to-edge slab. overflow: 'hidden' makes sure that highlight actually
348
+ // clips to borderRadius instead of drawing as a plain rectangle.
349
+ toggleButton: {
350
+ borderRadius: 12,
351
+ marginHorizontal: -12,
352
+ overflow: "hidden",
353
+ paddingHorizontal: 12,
354
+ paddingVertical: 10
355
+ },
356
+ // Same shape as toggleButton but `flex: 1` instead of its own `marginHorizontal: -12` — two of
357
+ // these side by side in toggleRow would otherwise both pull inward and collide in the middle.
358
+ // toggleRow carries that edge-alignment margin once, for the row as a whole, instead.
359
+ toggleButtonInRow: {
360
+ borderRadius: 12,
361
+ flex: 1,
362
+ overflow: "hidden",
363
+ paddingHorizontal: 12,
364
+ paddingVertical: 10
365
+ },
366
+ toggleContent: {
367
+ alignItems: "center",
368
+ flexDirection: "row",
369
+ gap: 12
370
+ },
371
+ toggleGroup: {
372
+ gap: 4
373
+ },
374
+ toggleRow: {
375
+ flexDirection: "row",
376
+ gap: 4,
377
+ marginHorizontal: -12
378
+ }
379
+ });
183
380
 
184
- // src/PopoverBody.tsx
381
+ // src/CornerActionButtons.tsx
382
+ var import_feedback_press2 = require("@rific/feedback-press");
185
383
  var import_react_native2 = require("react-native");
186
- var import_jsx_runtime = require("react/jsx-runtime");
384
+ var import_jsx_runtime2 = require("react/jsx-runtime");
385
+ function CornerActionButtons({ onBack, onSettings, fg, insets }) {
386
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press2.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, style: [styles2.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: "Back" }),
388
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press2.IconButton, { icon: "cog", iconColor: fg, size: 24, style: [styles2.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
389
+ ] });
390
+ }
391
+ var styles2 = import_react_native2.StyleSheet.create({
392
+ back: {
393
+ position: "absolute"
394
+ },
395
+ topRight: {
396
+ position: "absolute"
397
+ }
398
+ });
399
+
400
+ // src/fonts.ts
401
+ var import_react_native3 = require("react-native");
402
+ var MONO_FONT = import_react_native3.Platform.select({ ios: "Menlo", android: "monospace", default: "monospace" });
403
+
404
+ // src/InlineColorPicker.tsx
405
+ var import_auto_paper2 = require("@rific/auto-paper");
406
+ var import_feedback_press3 = require("@rific/feedback-press");
407
+ var import_core2 = require("@tastic/core");
408
+ var import_react_native6 = require("react-native");
409
+ var import_react_native_paper2 = require("react-native-paper");
410
+
411
+ // src/PopoverBody.tsx
412
+ var import_react_native4 = require("react-native");
413
+ var import_jsx_runtime3 = require("react/jsx-runtime");
187
414
  var CARET_SIZE = 7;
188
415
  var CARET_RING_OFFSET = 2;
189
416
  var CARET_DIP = 1;
190
417
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
191
418
  if (!visible) return null;
192
419
  const above = verticalAlign === "above";
193
- const alignStyle = align === "left" ? styles.contentLeft : align === "right" ? styles.contentRight : styles.contentCenter;
194
- const verticalStyle = above ? styles.contentAbove : styles.contentBelow;
420
+ const alignStyle = align === "left" ? styles3.contentLeft : align === "right" ? styles3.contentRight : styles3.contentCenter;
421
+ const verticalStyle = above ? styles3.contentAbove : styles3.contentBelow;
195
422
  const ringSize = CARET_SIZE + caretBorderWidth;
196
423
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
197
424
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
198
425
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
199
426
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
200
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [styles.content, alignStyle, verticalStyle], children: [
427
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native4.View, { style: [styles3.content, alignStyle, verticalStyle], children: [
201
428
  children,
202
- caretColor && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
203
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [styles.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
204
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [styles.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
429
+ caretColor && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
431
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native4.View, { style: [styles3.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
205
432
  ] })
206
433
  ] });
207
434
  }
208
- var styles = import_react_native2.StyleSheet.create({
435
+ var styles3 = import_react_native4.StyleSheet.create({
209
436
  caret: {
210
437
  borderLeftColor: "transparent",
211
438
  borderRightColor: "transparent",
@@ -248,22 +475,22 @@ var styles = import_react_native2.StyleSheet.create({
248
475
  });
249
476
 
250
477
  // src/useAutoAlign.ts
251
- var import_react = require("react");
252
- var import_react_native3 = require("react-native");
478
+ var import_react2 = require("react");
479
+ var import_react_native5 = require("react-native");
253
480
  var EDGE_MARGIN = 12;
254
481
  function useAutoAlign(open, contentWidth, contentHeight) {
255
- const triggerRef = (0, import_react.useRef)(null);
256
- const { width: windowWidth, height: windowHeight } = (0, import_react_native3.useWindowDimensions)();
257
- const [align, setAlign] = (0, import_react.useState)("center");
258
- const [verticalAlign, setVerticalAlign] = (0, import_react.useState)("below");
259
- const [maxHeight, setMaxHeight] = (0, import_react.useState)(contentHeight);
260
- const [measured, setMeasured] = (0, import_react.useState)(false);
261
- const [prevOpen, setPrevOpen] = (0, import_react.useState)(open);
482
+ const triggerRef = (0, import_react2.useRef)(null);
483
+ const { width: windowWidth, height: windowHeight } = (0, import_react_native5.useWindowDimensions)();
484
+ const [align, setAlign] = (0, import_react2.useState)("center");
485
+ const [verticalAlign, setVerticalAlign] = (0, import_react2.useState)("below");
486
+ const [maxHeight, setMaxHeight] = (0, import_react2.useState)(contentHeight);
487
+ const [measured, setMeasured] = (0, import_react2.useState)(false);
488
+ const [prevOpen, setPrevOpen] = (0, import_react2.useState)(open);
262
489
  if (open !== prevOpen) {
263
490
  setPrevOpen(open);
264
491
  if (open) setMeasured(false);
265
492
  }
266
- const measure = (0, import_react.useCallback)(() => {
493
+ const measure = (0, import_react2.useCallback)(() => {
267
494
  triggerRef.current?.measureInWindow((x, y, triggerWidth, triggerHeight) => {
268
495
  const centerX = x + triggerWidth / 2;
269
496
  const overflowsRight = centerX + contentWidth / 2 > windowWidth - EDGE_MARGIN;
@@ -279,14 +506,14 @@ function useAutoAlign(open, contentWidth, contentHeight) {
279
506
  setMeasured(true);
280
507
  });
281
508
  }, [contentWidth, contentHeight, windowWidth, windowHeight]);
282
- (0, import_react.useEffect)(() => {
509
+ (0, import_react2.useEffect)(() => {
283
510
  if (open) measure();
284
511
  }, [open, measure]);
285
512
  return { align, maxHeight, measured, triggerRef, verticalAlign };
286
513
  }
287
514
 
288
515
  // src/InlineColorPicker.tsx
289
- var import_jsx_runtime2 = require("react/jsx-runtime");
516
+ var import_jsx_runtime4 = require("react/jsx-runtime");
290
517
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
291
518
  var DEFAULT_SIZE = 48;
292
519
  var SWATCH_SIZE = 28;
@@ -299,10 +526,10 @@ var SCREEN_MARGIN = 40;
299
526
  function widthForColumns(columns) {
300
527
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
301
528
  }
302
- function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_paper.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
529
+ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_paper2.defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
303
530
  const menuBg = dark ? "#000000" : "#FFFFFF";
304
- const { width: windowWidth } = (0, import_react_native4.useWindowDimensions)();
305
- const autoColumns = (0, import_core.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
531
+ const { width: windowWidth } = (0, import_react_native6.useWindowDimensions)();
532
+ const autoColumns = (0, import_core2.clamp)(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
306
533
  const resolvedColumns = columns ?? autoColumns;
307
534
  const swatchesWidth = widthForColumns(resolvedColumns);
308
535
  const swatchRows = Math.ceil(swatches.length / resolvedColumns);
@@ -310,16 +537,16 @@ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_p
310
537
  const open = host.openId === id;
311
538
  const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight);
312
539
  const align = alignOverride ?? autoAlign;
313
- const contrastColor = (0, import_auto_paper.getContrastColor)(value);
540
+ const contrastColor = (0, import_auto_paper2.getContrastColor)(value);
314
541
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
315
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native4.View, { style: [styles2.anchor, open && styles2.anchorOpen], children: [
316
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles2.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, { ref: triggerRef, collapsable: false, style: styles2.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Text, { style: [styles2.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
317
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.ScrollView, { style: [styles2.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles2.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
542
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native6.View, { style: [styles4.anchor, open && styles4.anchorOpen], children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles4.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, { ref: triggerRef, collapsable: false, style: styles4.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles4.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.ScrollView, { style: [styles4.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles4.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
318
545
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
319
546
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
320
547
  const swappable = taken && allowSwapTaken;
321
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
322
- import_feedback_press.TouchableRipple,
548
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
549
+ import_feedback_press3.TouchableRipple,
323
550
  {
324
551
  disabled: taken && !swappable,
325
552
  onPress: () => {
@@ -327,15 +554,15 @@ function InlineColorPicker({ id, host, value, onChange, swatches = import_auto_p
327
554
  if (autoDismiss) host.close();
328
555
  },
329
556
  borderless: true,
330
- style: [styles2.swatch, { backgroundColor: swatch.value }, selected && styles2.swatchSelected, taken && !swappable && styles2.swatchTaken],
331
- children: selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: "check", size: 16, color: (0, import_auto_paper.getContrastColor)(swatch.value) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper.getContrastColor)(swatch.value) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: "close", size: 16, color: (0, import_auto_paper.getContrastColor)(swatch.value) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native4.View, {})
557
+ style: [styles4.swatch, { backgroundColor: swatch.value }, selected && styles4.swatchSelected, taken && !swappable && styles4.swatchTaken],
558
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatch.value) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native6.View, {})
332
559
  },
333
560
  swatch.value
334
561
  );
335
562
  }) }) })
336
563
  ] });
337
564
  }
338
- var styles2 = import_react_native4.StyleSheet.create({
565
+ var styles4 = import_react_native6.StyleSheet.create({
339
566
  anchor: {
340
567
  position: "relative"
341
568
  },
@@ -406,31 +633,126 @@ var styles2 = import_react_native4.StyleSheet.create({
406
633
  }
407
634
  });
408
635
 
636
+ // src/LabeledDropdown.tsx
637
+ var import_auto_paper3 = require("@rific/auto-paper");
638
+ var import_feedback_press4 = require("@rific/feedback-press");
639
+ var import_react_native7 = require("react-native");
640
+ var import_react_native_paper3 = require("react-native-paper");
641
+ var import_jsx_runtime5 = require("react/jsx-runtime");
642
+ var TRIGGER_HEIGHT = 28;
643
+ var POPOVER_WIDTH = 180;
644
+ var ROW_HEIGHT = 36;
645
+ var LIST_PADDING = 8;
646
+ var LABELED_DROPDOWN_POPOVER_WIDTH = POPOVER_WIDTH;
647
+ function getLabeledDropdownContentHeight(optionCount) {
648
+ return LIST_PADDING * 2 + optionCount * ROW_HEIGHT;
649
+ }
650
+ function LabeledDropdown({ id, host, options, value, onChange, color, dark, align: forcedAlign, alignOverride }) {
651
+ const menuBg = dark ? "#000000" : "#FFFFFF";
652
+ const fg = dark ? "#FFFFFF" : "#000000";
653
+ const open = host.openId === id;
654
+ const selected = options.find((o) => o.value === value) ?? null;
655
+ const contentHeight = LIST_PADDING * 2 + options.length * ROW_HEIGHT;
656
+ const auto = useAutoAlign(open, POPOVER_WIDTH, contentHeight);
657
+ const placement = alignOverride ?? auto;
658
+ const { maxHeight, measured, triggerRef, verticalAlign } = placement;
659
+ const align = forcedAlign ?? placement.align;
660
+ const handleSelect = (option) => {
661
+ onChange(option.value);
662
+ host.close();
663
+ };
664
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: [styles5.anchor, open && styles5.anchorOpen], children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => host.toggle(id), style: styles5.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { ref: triggerRef, collapsable: false, style: styles5.triggerInner, children: [
666
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { style: [styles5.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
667
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: "menu-down", size: 16, color })
668
+ ] }) }),
669
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native7.ScrollView, { style: [styles5.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles5.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
670
+ const isSelected = option.value === value;
671
+ const onColor = (0, import_auto_paper3.getContrastColor)(color);
672
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press4.TouchableRipple, { onPress: () => handleSelect(option), style: [styles5.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native7.View, { style: styles5.rowInner, children: [
673
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
674
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { style: [styles5.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
675
+ ] }) }, option.value);
676
+ }) }) })
677
+ ] });
678
+ }
679
+ var styles5 = import_react_native7.StyleSheet.create({
680
+ anchor: {
681
+ position: "relative"
682
+ },
683
+ // React Native Web gives every position:'relative' view its own stacking context, so the
684
+ // elevation has to live on the anchor itself — see PopoverBody's own stacking-context notes.
685
+ anchorOpen: {
686
+ zIndex: 100
687
+ },
688
+ menu: {
689
+ borderRadius: 12,
690
+ borderWidth: 2,
691
+ elevation: 8,
692
+ shadowColor: "#000000",
693
+ shadowOffset: { height: 2, width: 0 },
694
+ shadowOpacity: 0.3,
695
+ shadowRadius: 8
696
+ },
697
+ menuContent: {
698
+ padding: LIST_PADDING
699
+ },
700
+ row: {
701
+ borderRadius: 8,
702
+ height: ROW_HEIGHT
703
+ },
704
+ rowInner: {
705
+ alignItems: "center",
706
+ flexDirection: "row",
707
+ flex: 1,
708
+ gap: 8,
709
+ paddingHorizontal: 12
710
+ },
711
+ rowLabel: {
712
+ flex: 1,
713
+ fontSize: 14
714
+ },
715
+ trigger: {
716
+ alignSelf: "flex-start"
717
+ },
718
+ triggerInner: {
719
+ alignItems: "center",
720
+ flexDirection: "row",
721
+ gap: 2,
722
+ height: TRIGGER_HEIGHT
723
+ },
724
+ triggerLabel: {
725
+ fontSize: 13,
726
+ fontWeight: "700",
727
+ letterSpacing: 1
728
+ }
729
+ });
730
+
409
731
  // src/loadSkiaWeb.ts
410
732
  function loadSkiaWeb() {
411
733
  return Promise.resolve();
412
734
  }
413
735
 
414
736
  // src/PressAwayOverlay.tsx
415
- var import_react_native5 = require("react-native");
416
- var import_jsx_runtime3 = require("react/jsx-runtime");
737
+ var import_react_native8 = require("react-native");
738
+ var import_jsx_runtime6 = require("react/jsx-runtime");
417
739
  function PressAwayOverlay({ active, onPress, style }) {
418
740
  if (!active) return null;
419
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native5.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native5.StyleSheet.absoluteFill, style] });
741
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native8.Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [import_react_native8.StyleSheet.absoluteFill, style] });
420
742
  }
421
743
 
422
744
  // src/ReadyButton.tsx
423
- var import_feedback_press2 = require("@rific/feedback-press");
424
- var import_react_native6 = require("react-native");
425
- var import_react_native_paper2 = require("react-native-paper");
426
- var import_jsx_runtime4 = require("react/jsx-runtime");
745
+ var import_feedback_press5 = require("@rific/feedback-press");
746
+ var import_react_native9 = require("react-native");
747
+ var import_react_native_paper4 = require("react-native-paper");
748
+ var import_jsx_runtime7 = require("react/jsx-runtime");
427
749
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
428
- const readyButtonStyle = [styles3.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
750
+ const readyButtonStyle = [styles6.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
429
751
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
430
752
  const labelColorStyle = { color: ready ? "#000000" : color };
431
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
753
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press5.TouchableRipple, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
432
754
  }
433
- var styles3 = import_react_native6.StyleSheet.create({
755
+ var styles6 = import_react_native9.StyleSheet.create({
434
756
  readyButton: {
435
757
  alignItems: "center",
436
758
  borderRadius: 12,
@@ -443,21 +765,21 @@ var styles3 = import_react_native6.StyleSheet.create({
443
765
  });
444
766
 
445
767
  // src/SectionedDropdown.tsx
446
- var import_auto_paper2 = require("@rific/auto-paper");
447
- var import_feedback_press3 = require("@rific/feedback-press");
448
- var import_react_native8 = require("react-native");
449
- var import_react_native_paper3 = require("react-native-paper");
768
+ var import_auto_paper4 = require("@rific/auto-paper");
769
+ var import_feedback_press6 = require("@rific/feedback-press");
770
+ var import_react_native11 = require("react-native");
771
+ var import_react_native_paper5 = require("react-native-paper");
450
772
 
451
773
  // src/TriggerGaugeHost.tsx
452
- var import_react3 = require("react");
453
- var import_jsx_runtime6 = require("react/jsx-runtime");
454
- var LazyTriggerGauge = (0, import_react3.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((m) => ({ default: m.TriggerGauge }))));
774
+ var import_react4 = require("react");
775
+ var import_jsx_runtime9 = require("react/jsx-runtime");
776
+ var LazyTriggerGauge = (0, import_react4.lazy)(() => loadSkiaWeb().then(() => Promise.resolve().then(() => (init_TriggerGauge(), TriggerGauge_exports)).then((m) => ({ default: m.TriggerGauge }))));
455
777
  function TriggerGaugeHost(props) {
456
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react3.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LazyTriggerGauge, { ...props }) });
778
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react4.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(LazyTriggerGauge, { ...props }) });
457
779
  }
458
780
 
459
781
  // src/SectionedDropdown.tsx
460
- var import_jsx_runtime7 = require("react/jsx-runtime");
782
+ var import_jsx_runtime10 = require("react/jsx-runtime");
461
783
  var MENU_BORDER_WIDTH = 1;
462
784
  var TRIGGER_SIZE = 44;
463
785
  var MENU_MIN_WIDTH = 200;
@@ -470,8 +792,8 @@ var MENU_DIVIDER_HEIGHT = 9;
470
792
  var MENU_VERTICAL_PADDING = 12;
471
793
  function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
472
794
  const menuBg = dark ? "#000000" : "#FFFFFF";
473
- const menuBorderColor = (0, import_auto_paper2.getBlendedColor)(mutedColor, menuBg, 0.5);
474
- const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper2.getColorRoles)(accentColor, menuBg).onColor;
795
+ const menuBorderColor = (0, import_auto_paper4.getBlendedColor)(mutedColor, menuBg, 0.5);
796
+ const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper4.getColorRoles)(accentColor, menuBg).onColor;
475
797
  const fg = dark ? "#FFFFFF" : "#000000";
476
798
  const open = host.openId === id;
477
799
  const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
@@ -503,17 +825,17 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
503
825
  }
504
826
  gaugeOffset += section.options.length;
505
827
  }
506
- const anchorStyle = [styles5.anchor, open && styles5.anchorOpen];
828
+ const anchorStyle = [styles8.anchor, open && styles8.anchorOpen];
507
829
  const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
508
830
  const dividerStyle = { backgroundColor: menuBorderColor };
509
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: anchorStyle, children: [
510
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { ref: triggerRef, collapsable: false, style: styles5.triggerBox, children: [
511
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
512
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
831
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: anchorStyle, children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { ref: triggerRef, collapsable: false, style: styles8.triggerBox, children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
834
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press6.IconButton, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
513
835
  ] }),
514
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.ScrollView, { style: [styles5.menu, menuStyle], contentContainerStyle: styles5.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { children: [
515
- sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native8.View, { style: [styles5.divider, dividerStyle] }),
516
- section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
836
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.ScrollView, { style: [styles8.menu, menuStyle], contentContainerStyle: styles8.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { children: [
837
+ sectionIndex > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native11.View, { style: [styles8.divider, dividerStyle] }),
838
+ section.kind === "single" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
517
839
  SingleSection,
518
840
  {
519
841
  section,
@@ -526,7 +848,7 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
526
848
  if (autoDismiss) host.close();
527
849
  }
528
850
  }
529
- ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
851
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
530
852
  ] }, section.id)) }) })
531
853
  ] });
532
854
  }
@@ -538,17 +860,17 @@ function SingleSection({
538
860
  labelFontFamily,
539
861
  onSelect
540
862
  }) {
541
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: section.options.map((option) => {
863
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: section.options.map((option) => {
542
864
  const selected = option.value === section.value;
543
865
  const rowColor = selected ? onAccentColor : mutedColor;
544
- const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }];
866
+ const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }];
545
867
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
546
868
  const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
547
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemRow, children: [
548
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
549
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemLabelColumn, children: [
550
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
551
- option.description && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
869
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemRow, children: [
870
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
871
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemLabelColumn, children: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: labelStyle, children: option.label }),
873
+ option.description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelSmall", style: descriptionStyle, children: option.description })
552
874
  ] })
553
875
  ] }) }, option.value);
554
876
  }) });
@@ -562,23 +884,23 @@ function MultiSection({
562
884
  allClearLabels
563
885
  }) {
564
886
  const allSelected = section.value.length === section.options.length;
565
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
887
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
566
888
  section.options.map((option, i) => {
567
889
  const selected = section.value.includes(option.value);
568
890
  const rowColor = selected ? onAccentColor : mutedColor;
569
891
  const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
570
892
  const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
571
- const itemStyle = [styles5.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
893
+ const itemStyle = [styles8.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
572
894
  const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
573
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native8.View, { style: styles5.itemRow, children: [
574
- option.icon && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
575
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: [styles5.itemLabelFlex, labelStyle], children: option.label })
895
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native11.View, { style: styles8.itemRow, children: [
896
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Icon, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
897
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.itemLabelFlex, labelStyle], children: option.label })
576
898
  ] }) }, option.value);
577
899
  }),
578
- section.allClear && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles5.item, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { variant: "labelLarge", style: [styles5.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
900
+ section.allClear && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles8.item, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_paper5.Text, { variant: "labelLarge", style: [styles8.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
579
901
  ] });
580
902
  }
581
- var styles5 = import_react_native8.StyleSheet.create({
903
+ var styles8 = import_react_native11.StyleSheet.create({
582
904
  allClearLabel: {
583
905
  fontWeight: "bold",
584
906
  textAlign: "center"
@@ -595,7 +917,7 @@ var styles5 = import_react_native8.StyleSheet.create({
595
917
  zIndex: 100
596
918
  },
597
919
  divider: {
598
- height: import_react_native8.StyleSheet.hairlineWidth,
920
+ height: import_react_native11.StyleSheet.hairlineWidth,
599
921
  marginVertical: 4
600
922
  },
601
923
  item: {
@@ -642,25 +964,61 @@ var styles5 = import_react_native8.StyleSheet.create({
642
964
  }
643
965
  });
644
966
 
967
+ // src/SharedActionBand.tsx
968
+ var import_feedback_press7 = require("@rific/feedback-press");
969
+ var import_react_native12 = require("react-native");
970
+ var import_jsx_runtime11 = require("react/jsx-runtime");
971
+ function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
972
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native12.View, { style: [styles9.column, popoverOpen && styles9.columnOpen], children: [
973
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native12.View, { style: styles9.actionsRow, children: [
974
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press7.IconButton, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
975
+ onRandomize && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press7.IconButton, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
976
+ onReset && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press7.IconButton, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
977
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press7.IconButton, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
978
+ ] }),
979
+ children
980
+ ] });
981
+ }
982
+ var styles9 = import_react_native12.StyleSheet.create({
983
+ actionsRow: {
984
+ alignItems: "center",
985
+ flexDirection: "row",
986
+ gap: 16
987
+ },
988
+ column: {
989
+ alignItems: "center",
990
+ gap: 8
991
+ },
992
+ columnOpen: {
993
+ zIndex: 100
994
+ }
995
+ });
996
+
645
997
  // src/usePopoverHost.ts
646
- var import_react4 = require("react");
998
+ var import_react5 = require("react");
647
999
  function usePopoverHost() {
648
- const [openId, setOpenId] = (0, import_react4.useState)(null);
649
- const toggle = (0, import_react4.useCallback)((id) => {
1000
+ const [openId, setOpenId] = (0, import_react5.useState)(null);
1001
+ const toggle = (0, import_react5.useCallback)((id) => {
650
1002
  setOpenId((prev) => prev === id ? null : id);
651
1003
  }, []);
652
- const close = (0, import_react4.useCallback)(() => setOpenId(null), []);
1004
+ const close = (0, import_react5.useCallback)(() => setOpenId(null), []);
653
1005
  return { openId, toggle, close };
654
1006
  }
655
1007
  // Annotate the CommonJS export names for ESM import in node:
656
1008
  0 && (module.exports = {
1009
+ BaseSettingsDialog,
1010
+ CornerActionButtons,
657
1011
  InlineColorPicker,
1012
+ LABELED_DROPDOWN_POPOVER_WIDTH,
1013
+ LabeledDropdown,
658
1014
  MONO_FONT,
659
1015
  PopoverBody,
660
1016
  PressAwayOverlay,
661
1017
  ReadyButton,
662
1018
  SectionedDropdown,
1019
+ SharedActionBand,
663
1020
  TriggerGaugeHost,
1021
+ getLabeledDropdownContentHeight,
664
1022
  loadSkiaWeb,
665
1023
  useAutoAlign,
666
1024
  usePopoverHost