@tastic/hud 0.5.0 → 0.7.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.mjs CHANGED
@@ -232,25 +232,25 @@ var styles2 = StyleSheet2.create({
232
232
  sectionLabel: {
233
233
  letterSpacing: 2
234
234
  },
235
- // Negative margin cancels the padding so the icon still lines up with APPEARANCE/SOUND &
236
- // HAPTICS below, while the ripple/hover highlight itself gets room to breathe on both sides
237
- // instead of a flush edge-to-edge slab. overflow: 'hidden' makes sure that highlight actually
238
- // clips to borderRadius instead of drawing as a plain rectangle.
235
+ // Used to carry marginHorizontal: -12 + paddingHorizontal: 12 (netting to the same inset as
236
+ // APPEARANCE/SOUND & HAPTICS below, while giving the ripple/hover highlight room to breathe
237
+ // into the dialog's own gutter beyond that). Removed: this dialog scrolls (Dialog.ScrollArea +
238
+ // ScrollView above), and on web a ScrollView's own content wrapper clips to its own bounds
239
+ // regardless of a child's negative margin - confirmed live, the icon's own rounded-square badge
240
+ // was getting its left edge sliced off by exactly that overhang. No horizontal inset of its own
241
+ // now - it relies purely on the same dialog padding APPEARANCE/every other row already does,
242
+ // which can never overflow because it's never negative.
239
243
  toggleButton: {
240
244
  borderRadius: 12,
241
- marginHorizontal: -12,
242
245
  overflow: "hidden",
243
- paddingHorizontal: 12,
244
246
  paddingVertical: 10
245
247
  },
246
- // Same shape as toggleButton but `flex: 1` instead of its own `marginHorizontal: -12` — two of
247
- // these side by side in toggleRow would otherwise both pull inward and collide in the middle.
248
- // toggleRow carries that edge-alignment margin once, for the row as a whole, instead.
248
+ // Same shape as toggleButton, `flex: 1` instead of its own width - two of these side by side in
249
+ // toggleRow share the row's own edge alignment instead of each carrying their own.
249
250
  toggleButtonInRow: {
250
251
  borderRadius: 12,
251
252
  flex: 1,
252
253
  overflow: "hidden",
253
- paddingHorizontal: 12,
254
254
  paddingVertical: 10
255
255
  },
256
256
  toggleContent: {
@@ -261,10 +261,10 @@ var styles2 = StyleSheet2.create({
261
261
  toggleGroup: {
262
262
  gap: 4
263
263
  },
264
+ // No horizontal margin of its own now either - see toggleButton's identical note.
264
265
  toggleRow: {
265
266
  flexDirection: "row",
266
- gap: 4,
267
- marginHorizontal: -12
267
+ gap: 4
268
268
  }
269
269
  });
270
270
 
@@ -369,61 +369,128 @@ var styles3 = StyleSheet3.create({
369
369
  }
370
370
  });
371
371
 
372
- // src/CornerActionButtons.tsx
373
- import { IconButton as IconButton2 } from "@rific/feedback-press";
374
- import { StyleSheet as StyleSheet4 } from "react-native";
375
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
376
- function CornerActionButtons({ onBack, onSettings, fg, insets }) {
377
- return /* @__PURE__ */ jsxs4(Fragment2, { children: [
378
- /* @__PURE__ */ jsx4(IconButton2, { icon: "arrow-left", iconColor: fg, size: 24, style: [styles4.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: "Back" }),
379
- /* @__PURE__ */ jsx4(IconButton2, { icon: "cog", iconColor: fg, size: 24, style: [styles4.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
380
- ] });
372
+ // src/ConfirmDialog.tsx
373
+ import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
374
+ import { Button as Button3 } from "@rific/feedback-press";
375
+ import { StyleSheet as StyleSheet4, View as View4 } from "react-native";
376
+ import { Icon as Icon4, Portal as Portal3, Text as Text4 } from "react-native-paper";
377
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
378
+ function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
379
+ const { dark, colors } = useAutoPaperTheme4();
380
+ if (!visible) return null;
381
+ const fg = dark ? "#FFFFFF" : "#000000";
382
+ const cardBg = dark ? "#111111" : "#F2F2F2";
383
+ const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
384
+ const accent = destructive ? colors.danger : colors.primary;
385
+ const onAccent = destructive ? colors.onDanger : colors.onPrimary;
386
+ return /* @__PURE__ */ jsx4(Portal3, { children: /* @__PURE__ */ jsx4(View4, { style: styles4.overlay, children: /* @__PURE__ */ jsxs4(View4, { style: [styles4.card, { backgroundColor: cardBg, borderColor: cardBorder }, rotation % 360 !== 0 && { transform: [{ rotate: `${rotation}deg` }] }], children: [
387
+ /* @__PURE__ */ jsx4(Icon4, { source: icon, size: 64, color: accent }),
388
+ /* @__PURE__ */ jsx4(Text4, { variant: "headlineLarge", style: [styles4.title, { color: accent }], children: title }),
389
+ /* @__PURE__ */ jsx4(Text4, { variant: "bodyLarge", style: [styles4.body, { color: fg }], children: message }),
390
+ /* @__PURE__ */ jsxs4(View4, { style: styles4.actions, children: [
391
+ /* @__PURE__ */ jsx4(Button3, { mode: "outlined", onPress: onCancel, style: styles4.actionButton, children: cancelLabel }),
392
+ /* @__PURE__ */ jsx4(Button3, { mode: "contained", onPress: onConfirm, style: styles4.actionButton, buttonColor: accent, textColor: onAccent, children: confirmLabel })
393
+ ] })
394
+ ] }) }) });
381
395
  }
382
396
  var styles4 = StyleSheet4.create({
383
- back: {
384
- position: "absolute"
397
+ actionButton: {
398
+ flex: 1
385
399
  },
386
- topRight: {
387
- position: "absolute"
400
+ actions: {
401
+ flexDirection: "row",
402
+ gap: 12,
403
+ width: "100%"
404
+ },
405
+ body: {
406
+ textAlign: "center"
407
+ },
408
+ card: {
409
+ alignItems: "center",
410
+ borderRadius: 20,
411
+ borderWidth: 1,
412
+ gap: 16,
413
+ maxWidth: 360,
414
+ padding: 32
415
+ },
416
+ overlay: {
417
+ alignItems: "center",
418
+ backgroundColor: "rgba(0,0,0,0.72)",
419
+ bottom: 0,
420
+ justifyContent: "center",
421
+ left: 0,
422
+ position: "absolute",
423
+ right: 0,
424
+ top: 0
425
+ },
426
+ title: {
427
+ fontWeight: "bold"
388
428
  }
389
429
  });
390
430
 
431
+ // src/ContentGutter.tsx
432
+ import { computeContentBounds } from "@tastic/core";
433
+ import { StyleSheet as StyleSheet5, useWindowDimensions, View as View5 } from "react-native";
434
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
435
+ function ContentGutter({ maxContentWidth, leftGutter, rightGutter, style, children }) {
436
+ const { width: windowWidth } = useWindowDimensions();
437
+ const { contentWidth, gutterWidth } = computeContentBounds(windowWidth, maxContentWidth ?? Infinity);
438
+ return /* @__PURE__ */ jsxs5(View5, { style: [styles5.row, style], children: [
439
+ /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: leftGutter }),
440
+ /* @__PURE__ */ jsx5(View5, { style: { width: contentWidth }, children }),
441
+ /* @__PURE__ */ jsx5(View5, { style: [styles5.gutter, { width: gutterWidth }], children: rightGutter })
442
+ ] });
443
+ }
444
+ var styles5 = StyleSheet5.create({
445
+ // overflow:'hidden' keeps an oversized leftGutter/rightGutter from bleeding into the content
446
+ // column instead of clipping cleanly at its own real width.
447
+ gutter: {
448
+ overflow: "hidden"
449
+ },
450
+ row: {
451
+ flexDirection: "row"
452
+ }
453
+ });
454
+
455
+ // src/ControlSchemePicker.tsx
456
+ import { useIsTouchPrimaryDevice as useIsTouchPrimaryDevice2 } from "@tastic/core";
457
+ import { Platform as Platform3 } from "react-native";
458
+
459
+ // src/SectionedDropdown.tsx
460
+ import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
461
+ import { IconButton as IconButton2, TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
462
+ import { ScrollView as ScrollView3, StyleSheet as StyleSheet7, View as View8 } from "react-native";
463
+ import { Icon as Icon5, Text as Text5 } from "react-native-paper";
464
+
391
465
  // src/fonts.ts
392
466
  import { Platform as Platform2 } from "react-native";
393
467
  var MONO_FONT = Platform2.select({ ios: "Menlo", android: "monospace", default: "monospace" });
394
468
 
395
- // src/InlineColorPicker.tsx
396
- import { defaultColors, getContrastColor } from "@rific/auto-paper";
397
- import { TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
398
- import { clamp } from "@tastic/core";
399
- import { ScrollView as ScrollView3, StyleSheet as StyleSheet6, useWindowDimensions as useWindowDimensions2, View as View6 } from "react-native";
400
- import { Icon as Icon4, Text as Text4 } from "react-native-paper";
401
-
402
469
  // src/PopoverBody.tsx
403
- import { StyleSheet as StyleSheet5, View as View4 } from "react-native";
404
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
470
+ import { StyleSheet as StyleSheet6, View as View6 } from "react-native";
471
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
405
472
  var CARET_SIZE = 7;
406
473
  var CARET_RING_OFFSET = 2;
407
474
  var CARET_DIP = 1;
408
475
  function PopoverBody({ visible, children, align = "center", verticalAlign = "below", caretColor, caretBorderColor, caretBorderWidth = 1, triggerSize = 0 }) {
409
476
  if (!visible) return null;
410
477
  const above = verticalAlign === "above";
411
- const alignStyle = align === "left" ? styles5.contentLeft : align === "right" ? styles5.contentRight : styles5.contentCenter;
412
- const verticalStyle = above ? styles5.contentAbove : styles5.contentBelow;
478
+ const alignStyle = align === "left" ? styles6.contentLeft : align === "right" ? styles6.contentRight : styles6.contentCenter;
479
+ const verticalStyle = above ? styles6.contentAbove : styles6.contentBelow;
413
480
  const ringSize = CARET_SIZE + caretBorderWidth;
414
481
  const caretOffset = (halfFootprint) => align === "right" ? { right: triggerSize / 2 - halfFootprint } : { left: triggerSize / 2 - halfFootprint };
415
482
  const caretFill = above ? "borderTopColor" : "borderBottomColor";
416
483
  const caretWidthProp = above ? "borderTopWidth" : "borderBottomWidth";
417
484
  const caretEdge = (size) => above ? { bottom: -size + CARET_DIP } : { top: -size + CARET_DIP };
418
- return /* @__PURE__ */ jsxs5(View4, { style: [styles5.content, alignStyle, verticalStyle], children: [
485
+ return /* @__PURE__ */ jsxs6(View6, { style: [styles6.content, alignStyle, verticalStyle], children: [
419
486
  children,
420
- caretColor && /* @__PURE__ */ jsxs5(Fragment3, { children: [
421
- /* @__PURE__ */ jsx5(View4, { style: [styles5.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
422
- /* @__PURE__ */ jsx5(View4, { style: [styles5.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
487
+ caretColor && /* @__PURE__ */ jsxs6(Fragment2, { children: [
488
+ /* @__PURE__ */ jsx6(View6, { style: [styles6.caret, caretOffset(ringSize), caretEdge(ringSize), { [caretFill]: caretBorderColor ?? caretColor, [caretWidthProp]: ringSize, borderLeftWidth: ringSize, borderRightWidth: ringSize }] }),
489
+ /* @__PURE__ */ jsx6(View6, { style: [styles6.caret, caretOffset(CARET_SIZE), caretEdge(CARET_SIZE - CARET_RING_OFFSET), { [caretFill]: caretColor, [caretWidthProp]: CARET_SIZE, borderLeftWidth: CARET_SIZE, borderRightWidth: CARET_SIZE }] })
423
490
  ] })
424
491
  ] });
425
492
  }
426
- var styles5 = StyleSheet5.create({
493
+ var styles6 = StyleSheet6.create({
427
494
  caret: {
428
495
  borderLeftColor: "transparent",
429
496
  borderRightColor: "transparent",
@@ -465,13 +532,28 @@ var styles5 = StyleSheet5.create({
465
532
  }
466
533
  });
467
534
 
535
+ // src/TriggerGaugeHost.tsx
536
+ import { lazy, Suspense } from "react";
537
+
538
+ // src/loadSkiaWeb.ts
539
+ function loadSkiaWeb() {
540
+ return Promise.resolve();
541
+ }
542
+
543
+ // src/TriggerGaugeHost.tsx
544
+ import { jsx as jsx7 } from "react/jsx-runtime";
545
+ var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-2OJOOF6I.mjs").then((m) => ({ default: m.TriggerGauge }))));
546
+ function TriggerGaugeHost(props) {
547
+ return /* @__PURE__ */ jsx7(Suspense, { fallback: null, children: /* @__PURE__ */ jsx7(LazyTriggerGauge, { ...props }) });
548
+ }
549
+
468
550
  // src/useAutoAlign.ts
469
551
  import { useCallback, useEffect, useRef, useState as useState3 } from "react";
470
- import { useWindowDimensions } from "react-native";
552
+ import { useWindowDimensions as useWindowDimensions2 } from "react-native";
471
553
  var EDGE_MARGIN = 12;
472
554
  function useAutoAlign(open, contentWidth, contentHeight) {
473
555
  const triggerRef = useRef(null);
474
- const { width: windowWidth, height: windowHeight } = useWindowDimensions();
556
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions2();
475
557
  const [align, setAlign] = useState3("center");
476
558
  const [verticalAlign, setVerticalAlign] = useState3("below");
477
559
  const [maxHeight, setMaxHeight] = useState3(contentHeight);
@@ -503,8 +585,266 @@ function useAutoAlign(open, contentWidth, contentHeight) {
503
585
  return { align, maxHeight, measured, triggerRef, verticalAlign };
504
586
  }
505
587
 
588
+ // src/SectionedDropdown.tsx
589
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
590
+ var MENU_BORDER_WIDTH = 1;
591
+ var TRIGGER_SIZE = 44;
592
+ var MENU_MIN_WIDTH = 200;
593
+ var MENU_MAX_WIDTH = 220;
594
+ var MENU_ITEM_ICON_SIZE = 18;
595
+ var MENU_ITEM_LABEL_MAX_WIDTH = 150;
596
+ var MENU_ITEM_HEIGHT = 40;
597
+ var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
598
+ var MENU_DIVIDER_HEIGHT = 9;
599
+ var MENU_VERTICAL_PADDING = 12;
600
+ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
601
+ const menuBg = dark ? "#000000" : "#FFFFFF";
602
+ const menuBorderColor = getBlendedColor(mutedColor, menuBg, 0.5);
603
+ const resolvedOnAccentColor = onAccentColor ?? getColorRoles(accentColor, menuBg).onColor;
604
+ const fg = dark ? "#FFFFFF" : "#000000";
605
+ const open = host.openId === id;
606
+ const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
607
+ const rows = section.options.reduce((rowSum, option) => rowSum + (option.description ? MENU_ITEM_HEIGHT_WITH_DESCRIPTION : MENU_ITEM_HEIGHT), 0);
608
+ const footer = section.kind === "multi" && section.allClear ? MENU_ITEM_HEIGHT : 0;
609
+ const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
610
+ return sum + rows + footer + divider;
611
+ }, 0);
612
+ const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight);
613
+ const align = alignOverride ?? autoAlign;
614
+ const onlySection = sections.length === 1 ? sections[0] : null;
615
+ const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
616
+ const triggerIcon = selectedOption?.icon ?? icon;
617
+ const triggerIconSize = selectedOption?.iconSize ?? 22;
618
+ const hasMultiSection = sections.some((s) => s.kind === "multi");
619
+ const anyMultiSelected = sections.some((s) => s.kind === "multi" && s.value.length > 0);
620
+ const triggerIconColor = hasMultiSection ? anyMultiSelected ? accentColor : fg : fg;
621
+ const gaugeSegments = sections.reduce((sum, section) => sum + section.options.length, 0);
622
+ const gaugeLitIndices = [];
623
+ let gaugeOffset = 0;
624
+ for (const section of sections) {
625
+ if (section.kind === "single") {
626
+ const index = section.options.findIndex((o) => o.value === section.value);
627
+ if (index >= 0) gaugeLitIndices.push(gaugeOffset + index);
628
+ } else {
629
+ section.options.forEach((o, i) => {
630
+ if (section.value.includes(o.value)) gaugeLitIndices.push(gaugeOffset + i);
631
+ });
632
+ }
633
+ gaugeOffset += section.options.length;
634
+ }
635
+ const anchorStyle = [styles7.anchor, open && styles7.anchorOpen];
636
+ const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
637
+ const dividerStyle = { backgroundColor: menuBorderColor };
638
+ return /* @__PURE__ */ jsxs7(View8, { style: anchorStyle, children: [
639
+ /* @__PURE__ */ jsxs7(View8, { ref: triggerRef, collapsable: false, style: styles7.triggerBox, children: [
640
+ /* @__PURE__ */ jsx8(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
641
+ /* @__PURE__ */ jsx8(IconButton2, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
642
+ ] }),
643
+ /* @__PURE__ */ jsx8(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ jsx8(ScrollView3, { style: [styles7.menu, menuStyle], contentContainerStyle: styles7.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs7(View8, { children: [
644
+ sectionIndex > 0 && /* @__PURE__ */ jsx8(View8, { style: [styles7.divider, dividerStyle] }),
645
+ section.kind === "single" ? /* @__PURE__ */ jsx8(
646
+ SingleSection,
647
+ {
648
+ section,
649
+ accentColor,
650
+ onAccentColor: resolvedOnAccentColor,
651
+ mutedColor,
652
+ labelFontFamily,
653
+ onSelect: (value) => {
654
+ section.onChange(value);
655
+ if (autoDismiss) host.close();
656
+ }
657
+ }
658
+ ) : /* @__PURE__ */ jsx8(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
659
+ ] }, section.id)) }) })
660
+ ] });
661
+ }
662
+ function SingleSection({
663
+ section,
664
+ accentColor,
665
+ onAccentColor,
666
+ mutedColor,
667
+ labelFontFamily,
668
+ onSelect
669
+ }) {
670
+ return /* @__PURE__ */ jsx8(Fragment3, { children: section.options.map((option) => {
671
+ const selected = option.value === section.value;
672
+ const taken = !selected && option.value === section.takenValue;
673
+ const rowColor = selected ? onAccentColor : mutedColor;
674
+ const itemStyle = [styles7.item, selected && { backgroundColor: accentColor }, taken && styles7.itemTaken];
675
+ const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
676
+ const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
677
+ return /* @__PURE__ */ jsx8(TouchableRipple2, { disabled: taken, onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs7(View8, { style: styles7.itemRow, children: [
678
+ option.icon && /* @__PURE__ */ jsx8(Icon5, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
679
+ /* @__PURE__ */ jsxs7(View8, { style: styles7.itemLabelColumn, children: [
680
+ /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: labelStyle, children: option.label }),
681
+ option.description && /* @__PURE__ */ jsx8(Text5, { variant: "labelSmall", style: descriptionStyle, children: option.description })
682
+ ] })
683
+ ] }) }, option.value);
684
+ }) });
685
+ }
686
+ function MultiSection({
687
+ section,
688
+ accentColor,
689
+ onAccentColor,
690
+ mutedColor,
691
+ labelFontFamily,
692
+ allClearLabels
693
+ }) {
694
+ const allSelected = section.value.length === section.options.length;
695
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
696
+ section.options.map((option, i) => {
697
+ const selected = section.value.includes(option.value);
698
+ const rowColor = selected ? onAccentColor : mutedColor;
699
+ const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
700
+ const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
701
+ const itemStyle = [styles7.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
702
+ const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
703
+ return /* @__PURE__ */ jsx8(TouchableRipple2, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ jsxs7(View8, { style: styles7.itemRow, children: [
704
+ option.icon && /* @__PURE__ */ jsx8(Icon5, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
705
+ /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: [styles7.itemLabelFlex, labelStyle], children: option.label })
706
+ ] }) }, option.value);
707
+ }),
708
+ section.allClear && /* @__PURE__ */ jsx8(TouchableRipple2, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles7.item, children: /* @__PURE__ */ jsx8(Text5, { variant: "labelLarge", style: [styles7.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
709
+ ] });
710
+ }
711
+ var styles7 = StyleSheet7.create({
712
+ allClearLabel: {
713
+ fontWeight: "bold",
714
+ textAlign: "center"
715
+ },
716
+ anchor: {
717
+ position: "relative"
718
+ },
719
+ // React Native Web gives every position:'relative' view an explicit zIndex (0, not 'auto'),
720
+ // which makes each one its own stacking context — so a zIndex set only on the popover content
721
+ // below only wins comparisons within this anchor's own box, not against this anchor's own later
722
+ // siblings (e.g. a Ready button), which are otherwise painted on top by DOM-order tiebreak.
723
+ // Elevating the anchor itself, only while its popover is open, fixes that at the right level.
724
+ anchorOpen: {
725
+ zIndex: 100
726
+ },
727
+ divider: {
728
+ height: StyleSheet7.hairlineWidth,
729
+ marginVertical: 4
730
+ },
731
+ item: {
732
+ borderRadius: 8,
733
+ paddingHorizontal: 12,
734
+ paddingVertical: 8
735
+ },
736
+ itemLabelColumn: {
737
+ maxWidth: MENU_ITEM_LABEL_MAX_WIDTH
738
+ },
739
+ itemLabelFlex: {
740
+ flex: 1
741
+ },
742
+ itemRow: {
743
+ alignItems: "center",
744
+ flexDirection: "row",
745
+ gap: 8
746
+ },
747
+ // Same opacity as ProfilePicker's rowDisabled / InlineColorPicker's swatchTaken — the fleet's
748
+ // established "shown, not removed" treatment for another seat's own current pick.
749
+ itemTaken: {
750
+ opacity: 0.35
751
+ },
752
+ menu: {
753
+ borderRadius: 12,
754
+ borderWidth: MENU_BORDER_WIDTH,
755
+ elevation: 8,
756
+ maxWidth: MENU_MAX_WIDTH,
757
+ minWidth: MENU_MIN_WIDTH,
758
+ shadowColor: "#000000",
759
+ shadowOffset: { height: 2, width: 0 },
760
+ shadowOpacity: 0.3,
761
+ shadowRadius: 8
762
+ },
763
+ // Separate from `menu` itself — a ScrollView's own style sets the scrolling frame's bounds
764
+ // (including the maxHeight that actually caps it), while padding/gap belong to its
765
+ // contentContainerStyle, which sizes to the *unclamped* content instead of the frame.
766
+ menuContent: {
767
+ gap: 4,
768
+ padding: 6
769
+ },
770
+ // Fixed hit-area regardless of the current option's iconSize, so a small-vs-large trigger icon
771
+ // doesn't shift this picker's position within its row.
772
+ triggerBox: {
773
+ alignItems: "center",
774
+ height: TRIGGER_SIZE,
775
+ justifyContent: "center",
776
+ width: TRIGGER_SIZE
777
+ }
778
+ });
779
+
780
+ // src/ControlSchemePicker.tsx
781
+ import { jsx as jsx9 } from "react/jsx-runtime";
782
+ function ControlSchemePicker({ id, host, value, onChange, options, takenValue, accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily }) {
783
+ const isTouchPrimary = useIsTouchPrimaryDevice2();
784
+ if (Platform3.OS !== "web" || isTouchPrimary) return null;
785
+ return /* @__PURE__ */ jsx9(SectionedDropdown, { id, host, icon: "keyboard-outline", accessibilityLabel: "Control scheme", sections: [{ kind: "single", id: "controls", options, value, onChange, takenValue }], accentColor, mutedColor, onAccentColor, dark, align, labelFontFamily });
786
+ }
787
+
788
+ // src/CornerActionButtons.tsx
789
+ import { IconButton as IconButton3 } from "@rific/feedback-press";
790
+ import { StyleSheet as StyleSheet8 } from "react-native";
791
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
792
+ function CornerActionButtons({ onBack, onSettings, fg, insets, backIcon = "arrow-left", backLabel = "Back" }) {
793
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
794
+ /* @__PURE__ */ jsx10(IconButton3, { icon: backIcon, iconColor: fg, size: 24, style: [styles8.back, { top: 8 + insets.top, left: 8 + insets.left }], onPress: onBack, accessibilityLabel: backLabel }),
795
+ /* @__PURE__ */ jsx10(IconButton3, { icon: "cog", iconColor: fg, size: 24, style: [styles8.topRight, { top: 8 + insets.top, right: 8 + insets.right }], onPress: onSettings, accessibilityLabel: "Settings" })
796
+ ] });
797
+ }
798
+ var styles8 = StyleSheet8.create({
799
+ back: {
800
+ position: "absolute"
801
+ },
802
+ topRight: {
803
+ position: "absolute"
804
+ }
805
+ });
806
+
807
+ // src/CornerStatusBadges.tsx
808
+ import { StyleSheet as StyleSheet9, View as View9 } from "react-native";
809
+ import { Icon as Icon6 } from "react-native-paper";
810
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
811
+ var BADGE_SIZE = 30;
812
+ function StatusBadge({ icon }) {
813
+ return /* @__PURE__ */ jsx11(View9, { style: styles9.badge, children: icon && /* @__PURE__ */ jsx11(Icon6, { source: icon, size: 16, color: "rgba(255,255,255,0.85)" }) });
814
+ }
815
+ function CornerStatusBadges({ orientationMode, p1OnRight, p1, p2 }) {
816
+ const p1OnRightSide = orientationMode === "sideBySide" && p1OnRight;
817
+ const p2OnLeftSide = orientationMode === "sideBySide" && !p1OnRight;
818
+ return /* @__PURE__ */ jsxs9(Fragment5, { children: [
819
+ p1 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p1OnRightSide ? styles9.bottomRight : styles9.bottomLeft, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p1.icon }) }),
820
+ p2 && /* @__PURE__ */ jsx11(View9, { pointerEvents: "none", style: p2OnLeftSide ? styles9.topLeft : styles9.topRight, children: /* @__PURE__ */ jsx11(StatusBadge, { icon: p2.icon }) })
821
+ ] });
822
+ }
823
+ var styles9 = StyleSheet9.create({
824
+ badge: {
825
+ alignItems: "center",
826
+ backgroundColor: "rgba(0,0,0,0.28)",
827
+ borderColor: "rgba(255,255,255,0.18)",
828
+ borderRadius: BADGE_SIZE / 2,
829
+ borderWidth: 1,
830
+ height: BADGE_SIZE,
831
+ justifyContent: "center",
832
+ opacity: 0.7,
833
+ width: BADGE_SIZE
834
+ },
835
+ bottomLeft: { bottom: 12, left: 12, position: "absolute" },
836
+ bottomRight: { bottom: 12, position: "absolute", right: 12 },
837
+ topLeft: { left: 12, position: "absolute", top: 12 },
838
+ topRight: { position: "absolute", right: 12, top: 12 }
839
+ });
840
+
506
841
  // src/InlineColorPicker.tsx
507
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
842
+ import { defaultColors, getContrastColor } from "@rific/auto-paper";
843
+ import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
844
+ import { clamp } from "@tastic/core";
845
+ import { ScrollView as ScrollView4, StyleSheet as StyleSheet10, useWindowDimensions as useWindowDimensions3, View as View10 } from "react-native";
846
+ import { Icon as Icon7, Text as Text6 } from "react-native-paper";
847
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
508
848
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
509
849
  var DEFAULT_SIZE = 48;
510
850
  var SWATCH_SIZE = 28;
@@ -517,9 +857,9 @@ var SCREEN_MARGIN = 40;
517
857
  function widthForColumns(columns) {
518
858
  return SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * columns + SWATCHES_GAP * (columns - 1);
519
859
  }
520
- function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
860
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = defaultColors, takenValue, allowSwapTaken, dark, align: alignOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
521
861
  const menuBg = dark ? "#000000" : "#FFFFFF";
522
- const { width: windowWidth } = useWindowDimensions2();
862
+ const { width: windowWidth } = useWindowDimensions3();
523
863
  const autoColumns = clamp(Math.floor((windowWidth - 2 * SCREEN_MARGIN + SWATCHES_GAP) / (SWATCH_SIZE + SWATCHES_GAP)), MIN_COLUMNS, MAX_COLUMNS);
524
864
  const resolvedColumns = columns ?? autoColumns;
525
865
  const swatchesWidth = widthForColumns(resolvedColumns);
@@ -528,16 +868,19 @@ function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors
528
868
  const open = host.openId === id;
529
869
  const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, swatchesWidth, swatchesHeight);
530
870
  const align = alignOverride ?? autoAlign;
531
- const contrastColor = getContrastColor(value);
871
+ const displayFor = previewValue ?? ((hex) => hex);
872
+ const displayValue = displayFor(value);
873
+ const contrastColor = getContrastColor(displayValue);
532
874
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
533
- return /* @__PURE__ */ jsxs6(View6, { style: [styles6.anchor, open && styles6.anchorOpen], children: [
534
- /* @__PURE__ */ jsx6(TouchableRipple2, { onPress: () => host.toggle(id), borderless: true, style: [styles6.trigger, { backgroundColor: value, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ jsx6(View6, { ref: triggerRef, collapsable: false, style: styles6.triggerMeasure, children: tag ? /* @__PURE__ */ jsx6(Text4, { style: [styles6.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx6(Icon4, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
535
- /* @__PURE__ */ jsx6(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: value, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ jsx6(ScrollView3, { style: [styles6.swatches, { backgroundColor: menuBg, borderColor: value, width: swatchesWidth, maxHeight }], contentContainerStyle: styles6.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
875
+ return /* @__PURE__ */ jsxs10(View10, { style: [styles10.anchor, open && styles10.anchorOpen], children: [
876
+ /* @__PURE__ */ jsx12(TouchableRipple3, { onPress: () => host.toggle(id), borderless: true, style: [styles10.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ jsx12(View10, { ref: triggerRef, collapsable: false, style: styles10.triggerMeasure, children: tag ? /* @__PURE__ */ jsx12(Text6, { style: [styles10.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx12(Icon7, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
877
+ /* @__PURE__ */ jsx12(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: displayValue, caretBorderWidth: SWATCHES_BORDER_WIDTH, triggerSize: size, children: /* @__PURE__ */ jsx12(ScrollView4, { style: [styles10.swatches, { backgroundColor: menuBg, borderColor: displayValue, width: swatchesWidth, maxHeight }], contentContainerStyle: styles10.swatchesContent, showsVerticalScrollIndicator: false, children: swatches.map((swatch) => {
536
878
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
537
879
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
538
880
  const swappable = taken && allowSwapTaken;
539
- return /* @__PURE__ */ jsx6(
540
- TouchableRipple2,
881
+ const swatchDisplay = displayFor(swatch.value);
882
+ return /* @__PURE__ */ jsx12(
883
+ TouchableRipple3,
541
884
  {
542
885
  disabled: taken && !swappable,
543
886
  onPress: () => {
@@ -545,15 +888,15 @@ function InlineColorPicker({ id, host, value, onChange, swatches = defaultColors
545
888
  if (autoDismiss) host.close();
546
889
  },
547
890
  borderless: true,
548
- style: [styles6.swatch, { backgroundColor: swatch.value }, selected && styles6.swatchSelected, taken && !swappable && styles6.swatchTaken],
549
- children: selected ? /* @__PURE__ */ jsx6(Icon4, { source: "check", size: 16, color: getContrastColor(swatch.value) }) : swappable ? /* @__PURE__ */ jsx6(Icon4, { source: "swap-horizontal", size: 16, color: getContrastColor(swatch.value) }) : taken ? /* @__PURE__ */ jsx6(Icon4, { source: "close", size: 16, color: getContrastColor(swatch.value) }) : /* @__PURE__ */ jsx6(View6, {})
891
+ style: [styles10.swatch, { backgroundColor: swatchDisplay }, selected && styles10.swatchSelected, taken && !swappable && styles10.swatchTaken],
892
+ children: selected ? /* @__PURE__ */ jsx12(Icon7, { source: "check", size: 16, color: getContrastColor(swatchDisplay) }) : swappable ? /* @__PURE__ */ jsx12(Icon7, { source: "swap-horizontal", size: 16, color: getContrastColor(swatchDisplay) }) : taken ? /* @__PURE__ */ jsx12(Icon7, { source: "close", size: 16, color: getContrastColor(swatchDisplay) }) : /* @__PURE__ */ jsx12(View10, {})
550
893
  },
551
894
  swatch.value
552
895
  );
553
896
  }) }) })
554
897
  ] });
555
898
  }
556
- var styles6 = StyleSheet6.create({
899
+ var styles10 = StyleSheet10.create({
557
900
  anchor: {
558
901
  position: "relative"
559
902
  },
@@ -599,6 +942,10 @@ var styles6 = StyleSheet6.create({
599
942
  gap: SWATCHES_GAP,
600
943
  padding: SWATCHES_PADDING
601
944
  },
945
+ tagLabel: {
946
+ fontWeight: "700",
947
+ letterSpacing: 0.5
948
+ },
602
949
  // borderRadius/height/width come from the `size` prop instead (see render-site override) — no
603
950
  // fixed default here, since this style object is shared by every caller regardless of size.
604
951
  trigger: {
@@ -617,19 +964,15 @@ var styles6 = StyleSheet6.create({
617
964
  justifyContent: "center",
618
965
  paddingHorizontal: 4,
619
966
  width: "100%"
620
- },
621
- tagLabel: {
622
- fontWeight: "700",
623
- letterSpacing: 0.5
624
967
  }
625
968
  });
626
969
 
627
970
  // src/LabeledDropdown.tsx
628
971
  import { getContrastColor as getContrastColor2 } from "@rific/auto-paper";
629
- import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
630
- import { ScrollView as ScrollView4, StyleSheet as StyleSheet7, View as View7 } from "react-native";
631
- import { Icon as Icon5, Text as Text5 } from "react-native-paper";
632
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
972
+ import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
973
+ import { ScrollView as ScrollView5, StyleSheet as StyleSheet11, View as View11 } from "react-native";
974
+ import { Icon as Icon8, Text as Text7 } from "react-native-paper";
975
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
633
976
  var TRIGGER_HEIGHT = 28;
634
977
  var POPOVER_WIDTH = 180;
635
978
  var ROW_HEIGHT = 36;
@@ -652,22 +995,22 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
652
995
  onChange(option.value);
653
996
  host.close();
654
997
  };
655
- return /* @__PURE__ */ jsxs7(View7, { style: [styles7.anchor, open && styles7.anchorOpen], children: [
656
- /* @__PURE__ */ jsx7(TouchableRipple3, { onPress: () => host.toggle(id), style: styles7.trigger, children: /* @__PURE__ */ jsxs7(View7, { ref: triggerRef, collapsable: false, style: styles7.triggerInner, children: [
657
- /* @__PURE__ */ jsx7(Text5, { style: [styles7.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
658
- /* @__PURE__ */ jsx7(Icon5, { source: "menu-down", size: 16, color })
998
+ return /* @__PURE__ */ jsxs11(View11, { style: [styles11.anchor, open && styles11.anchorOpen], children: [
999
+ /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => host.toggle(id), style: styles11.trigger, children: /* @__PURE__ */ jsxs11(View11, { ref: triggerRef, collapsable: false, style: styles11.triggerInner, children: [
1000
+ /* @__PURE__ */ jsx13(Text7, { style: [styles11.triggerLabel, { color }], numberOfLines: 1, children: (selected?.label ?? "").toUpperCase() }),
1001
+ /* @__PURE__ */ jsx13(Icon8, { source: "menu-down", size: 16, color })
659
1002
  ] }) }),
660
- /* @__PURE__ */ jsx7(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ jsx7(ScrollView4, { style: [styles7.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles7.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1003
+ /* @__PURE__ */ jsx13(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ jsx13(ScrollView5, { style: [styles11.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles11.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
661
1004
  const isSelected = option.value === value;
662
1005
  const onColor = getContrastColor2(color);
663
- return /* @__PURE__ */ jsx7(TouchableRipple3, { onPress: () => handleSelect(option), style: [styles7.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs7(View7, { style: styles7.rowInner, children: [
664
- option.icon && /* @__PURE__ */ jsx7(Icon5, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
665
- /* @__PURE__ */ jsx7(Text5, { style: [styles7.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
1006
+ return /* @__PURE__ */ jsx13(TouchableRipple4, { onPress: () => handleSelect(option), style: [styles11.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs11(View11, { style: styles11.rowInner, children: [
1007
+ option.icon && /* @__PURE__ */ jsx13(Icon8, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1008
+ /* @__PURE__ */ jsx13(Text7, { style: [styles11.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
666
1009
  ] }) }, option.value);
667
1010
  }) }) })
668
1011
  ] });
669
1012
  }
670
- var styles7 = StyleSheet7.create({
1013
+ var styles11 = StyleSheet11.create({
671
1014
  anchor: {
672
1015
  position: "relative"
673
1016
  },
@@ -719,31 +1062,26 @@ var styles7 = StyleSheet7.create({
719
1062
  }
720
1063
  });
721
1064
 
722
- // src/loadSkiaWeb.ts
723
- function loadSkiaWeb() {
724
- return Promise.resolve();
725
- }
726
-
727
1065
  // src/PressAwayOverlay.tsx
728
- import { Pressable, StyleSheet as StyleSheet8 } from "react-native";
729
- import { jsx as jsx8 } from "react/jsx-runtime";
1066
+ import { Pressable, StyleSheet as StyleSheet12 } from "react-native";
1067
+ import { jsx as jsx14 } from "react/jsx-runtime";
730
1068
  function PressAwayOverlay({ active, onPress, style }) {
731
1069
  if (!active) return null;
732
- return /* @__PURE__ */ jsx8(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet8.absoluteFill, style] });
1070
+ return /* @__PURE__ */ jsx14(Pressable, { accessibilityLabel: "Dismiss", accessibilityRole: "button", onPress, style: [StyleSheet12.absoluteFill, style] });
733
1071
  }
734
1072
 
735
1073
  // src/ReadyButton.tsx
736
- import { TouchableRipple as TouchableRipple4 } from "@rific/feedback-press";
737
- import { StyleSheet as StyleSheet9 } from "react-native";
738
- import { Text as Text6 } from "react-native-paper";
739
- import { jsx as jsx9 } from "react/jsx-runtime";
1074
+ import { TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
1075
+ import { StyleSheet as StyleSheet13 } from "react-native";
1076
+ import { Text as Text8 } from "react-native-paper";
1077
+ import { jsx as jsx15 } from "react/jsx-runtime";
740
1078
  function ReadyButton({ color, ready, onToggleReady, style, labelFontFamily = MONO_FONT }) {
741
- const readyButtonStyle = [styles8.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
1079
+ const readyButtonStyle = [styles12.readyButton, { borderColor: color }, ready && { backgroundColor: color }, style];
742
1080
  const labelTextStyle = { fontFamily: labelFontFamily, fontWeight: "bold" };
743
1081
  const labelColorStyle = { color: ready ? "#000000" : color };
744
- return /* @__PURE__ */ jsx9(TouchableRipple4, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx9(Text6, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
1082
+ return /* @__PURE__ */ jsx15(TouchableRipple5, { onPress: onToggleReady, borderless: true, style: readyButtonStyle, children: /* @__PURE__ */ jsx15(Text8, { variant: "labelLarge", style: [labelTextStyle, labelColorStyle], children: ready ? "READY \u2713" : "READY?" }) });
745
1083
  }
746
- var styles8 = StyleSheet9.create({
1084
+ var styles12 = StyleSheet13.create({
747
1085
  readyButton: {
748
1086
  alignItems: "center",
749
1087
  borderRadius: 12,
@@ -755,222 +1093,22 @@ var styles8 = StyleSheet9.create({
755
1093
  }
756
1094
  });
757
1095
 
758
- // src/SectionedDropdown.tsx
759
- import { getBlendedColor, getColorRoles } from "@rific/auto-paper";
760
- import { IconButton as IconButton3, TouchableRipple as TouchableRipple5 } from "@rific/feedback-press";
761
- import { ScrollView as ScrollView5, StyleSheet as StyleSheet10, View as View8 } from "react-native";
762
- import { Icon as Icon6, Text as Text7 } from "react-native-paper";
763
-
764
- // src/TriggerGaugeHost.tsx
765
- import { lazy, Suspense } from "react";
766
- import { jsx as jsx10 } from "react/jsx-runtime";
767
- var LazyTriggerGauge = lazy(() => loadSkiaWeb().then(() => import("./TriggerGauge-2OJOOF6I.mjs").then((m) => ({ default: m.TriggerGauge }))));
768
- function TriggerGaugeHost(props) {
769
- return /* @__PURE__ */ jsx10(Suspense, { fallback: null, children: /* @__PURE__ */ jsx10(LazyTriggerGauge, { ...props }) });
770
- }
771
-
772
- // src/SectionedDropdown.tsx
773
- import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
774
- var MENU_BORDER_WIDTH = 1;
775
- var TRIGGER_SIZE = 44;
776
- var MENU_MIN_WIDTH = 200;
777
- var MENU_MAX_WIDTH = 220;
778
- var MENU_ITEM_ICON_SIZE = 18;
779
- var MENU_ITEM_LABEL_MAX_WIDTH = 150;
780
- var MENU_ITEM_HEIGHT = 40;
781
- var MENU_ITEM_HEIGHT_WITH_DESCRIPTION = 58;
782
- var MENU_DIVIDER_HEIGHT = 9;
783
- var MENU_VERTICAL_PADDING = 12;
784
- function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accentColor, mutedColor, onAccentColor, dark, align: alignOverride, autoDismiss = true, labelFontFamily = MONO_FONT, allClearLabels = { all: "All", clear: "Clear" } }) {
785
- const menuBg = dark ? "#000000" : "#FFFFFF";
786
- const menuBorderColor = getBlendedColor(mutedColor, menuBg, 0.5);
787
- const resolvedOnAccentColor = onAccentColor ?? getColorRoles(accentColor, menuBg).onColor;
788
- const fg = dark ? "#FFFFFF" : "#000000";
789
- const open = host.openId === id;
790
- const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
791
- const rows = section.options.reduce((rowSum, option) => rowSum + (option.description ? MENU_ITEM_HEIGHT_WITH_DESCRIPTION : MENU_ITEM_HEIGHT), 0);
792
- const footer = section.kind === "multi" && section.allClear ? MENU_ITEM_HEIGHT : 0;
793
- const divider = sectionIndex > 0 ? MENU_DIVIDER_HEIGHT : 0;
794
- return sum + rows + footer + divider;
795
- }, 0);
796
- const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = useAutoAlign(open, MENU_MAX_WIDTH, estimatedHeight);
797
- const align = alignOverride ?? autoAlign;
798
- const onlySection = sections.length === 1 ? sections[0] : null;
799
- const selectedOption = onlySection?.kind === "single" ? onlySection.options.find((o) => o.value === onlySection.value) : void 0;
800
- const triggerIcon = selectedOption?.icon ?? icon;
801
- const triggerIconSize = selectedOption?.iconSize ?? 22;
802
- const hasMultiSection = sections.some((s) => s.kind === "multi");
803
- const anyMultiSelected = sections.some((s) => s.kind === "multi" && s.value.length > 0);
804
- const triggerIconColor = hasMultiSection ? anyMultiSelected ? accentColor : fg : fg;
805
- const gaugeSegments = sections.reduce((sum, section) => sum + section.options.length, 0);
806
- const gaugeLitIndices = [];
807
- let gaugeOffset = 0;
808
- for (const section of sections) {
809
- if (section.kind === "single") {
810
- const index = section.options.findIndex((o) => o.value === section.value);
811
- if (index >= 0) gaugeLitIndices.push(gaugeOffset + index);
812
- } else {
813
- section.options.forEach((o, i) => {
814
- if (section.value.includes(o.value)) gaugeLitIndices.push(gaugeOffset + i);
815
- });
816
- }
817
- gaugeOffset += section.options.length;
818
- }
819
- const anchorStyle = [styles9.anchor, open && styles9.anchorOpen];
820
- const menuStyle = { backgroundColor: menuBg, borderColor: menuBorderColor, maxHeight };
821
- const dividerStyle = { backgroundColor: menuBorderColor };
822
- return /* @__PURE__ */ jsxs8(View8, { style: anchorStyle, children: [
823
- /* @__PURE__ */ jsxs8(View8, { ref: triggerRef, collapsable: false, style: styles9.triggerBox, children: [
824
- /* @__PURE__ */ jsx11(TriggerGaugeHost, { segments: gaugeSegments, litIndices: gaugeLitIndices, size: TRIGGER_SIZE, accentColor, mutedColor }),
825
- /* @__PURE__ */ jsx11(IconButton3, { icon: triggerIcon, iconColor: triggerIconColor, size: triggerIconSize, accessibilityLabel, onPress: () => host.toggle(id) })
826
- ] }),
827
- /* @__PURE__ */ jsx11(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: menuBorderColor, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: TRIGGER_SIZE, children: /* @__PURE__ */ jsx11(ScrollView5, { style: [styles9.menu, menuStyle], contentContainerStyle: styles9.menuContent, showsVerticalScrollIndicator: false, children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs8(View8, { children: [
828
- sectionIndex > 0 && /* @__PURE__ */ jsx11(View8, { style: [styles9.divider, dividerStyle] }),
829
- section.kind === "single" ? /* @__PURE__ */ jsx11(
830
- SingleSection,
831
- {
832
- section,
833
- accentColor,
834
- onAccentColor: resolvedOnAccentColor,
835
- mutedColor,
836
- labelFontFamily,
837
- onSelect: (value) => {
838
- section.onChange(value);
839
- if (autoDismiss) host.close();
840
- }
841
- }
842
- ) : /* @__PURE__ */ jsx11(MultiSection, { section, accentColor, onAccentColor: resolvedOnAccentColor, mutedColor, labelFontFamily, allClearLabels })
843
- ] }, section.id)) }) })
844
- ] });
845
- }
846
- function SingleSection({
847
- section,
848
- accentColor,
849
- onAccentColor,
850
- mutedColor,
851
- labelFontFamily,
852
- onSelect
853
- }) {
854
- return /* @__PURE__ */ jsx11(Fragment4, { children: section.options.map((option) => {
855
- const selected = option.value === section.value;
856
- const rowColor = selected ? onAccentColor : mutedColor;
857
- const itemStyle = [styles9.item, selected && { backgroundColor: accentColor }];
858
- const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
859
- const descriptionStyle = { fontFamily: labelFontFamily, color: rowColor };
860
- return /* @__PURE__ */ jsx11(TouchableRipple5, { onPress: () => onSelect(option.value), style: itemStyle, children: /* @__PURE__ */ jsxs8(View8, { style: styles9.itemRow, children: [
861
- option.icon && /* @__PURE__ */ jsx11(Icon6, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
862
- /* @__PURE__ */ jsxs8(View8, { style: styles9.itemLabelColumn, children: [
863
- /* @__PURE__ */ jsx11(Text7, { variant: "labelLarge", style: labelStyle, children: option.label }),
864
- option.description && /* @__PURE__ */ jsx11(Text7, { variant: "labelSmall", style: descriptionStyle, children: option.description })
865
- ] })
866
- ] }) }, option.value);
867
- }) });
868
- }
869
- function MultiSection({
870
- section,
871
- accentColor,
872
- onAccentColor,
873
- mutedColor,
874
- labelFontFamily,
875
- allClearLabels
876
- }) {
877
- const allSelected = section.value.length === section.options.length;
878
- return /* @__PURE__ */ jsxs8(Fragment4, { children: [
879
- section.options.map((option, i) => {
880
- const selected = section.value.includes(option.value);
881
- const rowColor = selected ? onAccentColor : mutedColor;
882
- const prevSelected = selected && i > 0 && section.value.includes(section.options[i - 1].value);
883
- const nextSelected = selected && i < section.options.length - 1 && section.value.includes(section.options[i + 1].value);
884
- const itemStyle = [styles9.item, selected && { backgroundColor: accentColor }, prevSelected && { borderTopLeftRadius: 0, borderTopRightRadius: 0 }, nextSelected && { borderBottomLeftRadius: 0, borderBottomRightRadius: 0 }];
885
- const labelStyle = { fontFamily: labelFontFamily, color: rowColor, fontWeight: selected ? "bold" : "normal" };
886
- return /* @__PURE__ */ jsx11(TouchableRipple5, { onPress: () => section.onChange(selected ? section.value.filter((v) => v !== option.value) : [...section.value, option.value]), style: itemStyle, children: /* @__PURE__ */ jsxs8(View8, { style: styles9.itemRow, children: [
887
- option.icon && /* @__PURE__ */ jsx11(Icon6, { source: option.icon, size: MENU_ITEM_ICON_SIZE, color: rowColor }),
888
- /* @__PURE__ */ jsx11(Text7, { variant: "labelLarge", style: [styles9.itemLabelFlex, labelStyle], children: option.label })
889
- ] }) }, option.value);
890
- }),
891
- section.allClear && /* @__PURE__ */ jsx11(TouchableRipple5, { onPress: () => section.onChange(allSelected ? [] : section.options.map((o) => o.value)), style: styles9.item, children: /* @__PURE__ */ jsx11(Text7, { variant: "labelLarge", style: [styles9.allClearLabel, { fontFamily: labelFontFamily, color: accentColor }], children: allSelected ? allClearLabels.clear : allClearLabels.all }) })
892
- ] });
893
- }
894
- var styles9 = StyleSheet10.create({
895
- allClearLabel: {
896
- fontWeight: "bold",
897
- textAlign: "center"
898
- },
899
- anchor: {
900
- position: "relative"
901
- },
902
- // React Native Web gives every position:'relative' view an explicit zIndex (0, not 'auto'),
903
- // which makes each one its own stacking context — so a zIndex set only on the popover content
904
- // below only wins comparisons within this anchor's own box, not against this anchor's own later
905
- // siblings (e.g. a Ready button), which are otherwise painted on top by DOM-order tiebreak.
906
- // Elevating the anchor itself, only while its popover is open, fixes that at the right level.
907
- anchorOpen: {
908
- zIndex: 100
909
- },
910
- divider: {
911
- height: StyleSheet10.hairlineWidth,
912
- marginVertical: 4
913
- },
914
- item: {
915
- borderRadius: 8,
916
- paddingHorizontal: 12,
917
- paddingVertical: 8
918
- },
919
- itemLabelColumn: {
920
- maxWidth: MENU_ITEM_LABEL_MAX_WIDTH
921
- },
922
- itemLabelFlex: {
923
- flex: 1
924
- },
925
- itemRow: {
926
- alignItems: "center",
927
- flexDirection: "row",
928
- gap: 8
929
- },
930
- menu: {
931
- borderRadius: 12,
932
- borderWidth: MENU_BORDER_WIDTH,
933
- elevation: 8,
934
- maxWidth: MENU_MAX_WIDTH,
935
- minWidth: MENU_MIN_WIDTH,
936
- shadowColor: "#000000",
937
- shadowOffset: { height: 2, width: 0 },
938
- shadowOpacity: 0.3,
939
- shadowRadius: 8
940
- },
941
- // Separate from `menu` itself — a ScrollView's own style sets the scrolling frame's bounds
942
- // (including the maxHeight that actually caps it), while padding/gap belong to its
943
- // contentContainerStyle, which sizes to the *unclamped* content instead of the frame.
944
- menuContent: {
945
- gap: 4,
946
- padding: 6
947
- },
948
- // Fixed hit-area regardless of the current option's iconSize, so a small-vs-large trigger icon
949
- // doesn't shift this picker's position within its row.
950
- triggerBox: {
951
- alignItems: "center",
952
- height: TRIGGER_SIZE,
953
- justifyContent: "center",
954
- width: TRIGGER_SIZE
955
- }
956
- });
957
-
958
1096
  // src/SharedActionBand.tsx
959
1097
  import { IconButton as IconButton4 } from "@rific/feedback-press";
960
- import { StyleSheet as StyleSheet11, View as View9 } from "react-native";
961
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1098
+ import { StyleSheet as StyleSheet14, View as View12 } from "react-native";
1099
+ import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
962
1100
  function SharedActionBand({ onBack, onSettings, onRandomize, onReset, fg, popoverOpen, children }) {
963
- return /* @__PURE__ */ jsxs9(View9, { style: [styles10.column, popoverOpen && styles10.columnOpen], children: [
964
- /* @__PURE__ */ jsxs9(View9, { style: styles10.actionsRow, children: [
965
- /* @__PURE__ */ jsx12(IconButton4, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
966
- onRandomize && /* @__PURE__ */ jsx12(IconButton4, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
967
- onReset && /* @__PURE__ */ jsx12(IconButton4, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
968
- /* @__PURE__ */ jsx12(IconButton4, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
1101
+ return /* @__PURE__ */ jsxs12(View12, { style: [styles13.column, popoverOpen && styles13.columnOpen], children: [
1102
+ /* @__PURE__ */ jsxs12(View12, { style: styles13.actionsRow, children: [
1103
+ /* @__PURE__ */ jsx16(IconButton4, { icon: "arrow-left", iconColor: fg, size: 20, onPress: onBack, accessibilityLabel: "Back" }),
1104
+ onRandomize && /* @__PURE__ */ jsx16(IconButton4, { icon: "dice-multiple", iconColor: fg, size: 18, onPress: onRandomize, accessibilityLabel: "Randomize match settings" }),
1105
+ onReset && /* @__PURE__ */ jsx16(IconButton4, { icon: "restore", iconColor: fg, size: 18, onPress: onReset, accessibilityLabel: "Reset match settings to defaults" }),
1106
+ /* @__PURE__ */ jsx16(IconButton4, { icon: "cog", iconColor: fg, size: 20, onPress: onSettings, accessibilityLabel: "Settings" })
969
1107
  ] }),
970
1108
  children
971
1109
  ] });
972
1110
  }
973
- var styles10 = StyleSheet11.create({
1111
+ var styles13 = StyleSheet14.create({
974
1112
  actionsRow: {
975
1113
  alignItems: "center",
976
1114
  flexDirection: "row",
@@ -986,20 +1124,20 @@ var styles10 = StyleSheet11.create({
986
1124
  });
987
1125
 
988
1126
  // src/StatRow.tsx
989
- import { useAutoPaperTheme as useAutoPaperTheme4 } from "@rific/auto-paper";
990
- import { StyleSheet as StyleSheet12, View as View10 } from "react-native";
991
- import { Text as Text8 } from "react-native-paper";
992
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1127
+ import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1128
+ import { StyleSheet as StyleSheet15, View as View13 } from "react-native";
1129
+ import { Text as Text9 } from "react-native-paper";
1130
+ import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
993
1131
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
994
- const { dark } = useAutoPaperTheme4();
1132
+ const { dark } = useAutoPaperTheme5();
995
1133
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
996
1134
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
997
- return /* @__PURE__ */ jsxs10(View10, { style: styles11.row, children: [
998
- /* @__PURE__ */ jsx13(Text8, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
999
- /* @__PURE__ */ jsx13(Text8, { variant: "bodyMedium", style: [styles11.value, { color: fg }], children: value })
1135
+ return /* @__PURE__ */ jsxs13(View13, { style: styles14.row, children: [
1136
+ /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
1137
+ /* @__PURE__ */ jsx17(Text9, { variant: "bodyMedium", style: [styles14.value, { color: fg }], children: value })
1000
1138
  ] });
1001
1139
  }
1002
- var styles11 = StyleSheet12.create({
1140
+ var styles14 = StyleSheet15.create({
1003
1141
  row: {
1004
1142
  alignItems: "center",
1005
1143
  flexDirection: "row",
@@ -1011,20 +1149,20 @@ var styles11 = StyleSheet12.create({
1011
1149
  });
1012
1150
 
1013
1151
  // src/StatSection.tsx
1014
- import { useAutoPaperTheme as useAutoPaperTheme5 } from "@rific/auto-paper";
1015
- import { StyleSheet as StyleSheet13, View as View11 } from "react-native";
1016
- import { Text as Text9 } from "react-native-paper";
1017
- import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1152
+ import { useAutoPaperTheme as useAutoPaperTheme6 } from "@rific/auto-paper";
1153
+ import { StyleSheet as StyleSheet16, View as View14 } from "react-native";
1154
+ import { Text as Text10 } from "react-native-paper";
1155
+ import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
1018
1156
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
1019
- const { dark } = useAutoPaperTheme5();
1157
+ const { dark } = useAutoPaperTheme6();
1020
1158
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
1021
1159
  const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
1022
- return /* @__PURE__ */ jsxs11(View11, { style: [styles12.section, { backgroundColor: sectionBg }], children: [
1023
- /* @__PURE__ */ jsx14(Text9, { variant: "labelMedium", style: [styles12.label, { color: fg }], children: label }),
1160
+ return /* @__PURE__ */ jsxs14(View14, { style: [styles15.section, { backgroundColor: sectionBg }], children: [
1161
+ /* @__PURE__ */ jsx18(Text10, { variant: "labelMedium", style: [styles15.label, { color: fg }], children: label }),
1024
1162
  children
1025
1163
  ] });
1026
1164
  }
1027
- var styles12 = StyleSheet13.create({
1165
+ var styles15 = StyleSheet16.create({
1028
1166
  // Bold and full-contrast (fg), not the original fontless/fgMuted treatment — that read at the
1029
1167
  // same visual weight as StatRow's own row labels below it (both used the identical muted color;
1030
1168
  // letterSpacing/variant alone didn't separate them enough to read as "this is the group header"
@@ -1054,7 +1192,11 @@ export {
1054
1192
  AchievementRow,
1055
1193
  BaseSettingsDialog,
1056
1194
  BaseStatsScreen,
1195
+ ConfirmDialog,
1196
+ ContentGutter,
1197
+ ControlSchemePicker,
1057
1198
  CornerActionButtons,
1199
+ CornerStatusBadges,
1058
1200
  InlineColorPicker,
1059
1201
  LABELED_DROPDOWN_POPOVER_WIDTH,
1060
1202
  LOCKED_BADGE_COLOR,