@tastic/profile 0.13.0 → 0.14.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/README.md CHANGED
@@ -51,8 +51,12 @@ see its own doc comment for why that component is generic over your own `Profile
51
51
  only - creating, renaming, and deleting all live in `ProfilesManager`. Generic over your own
52
52
  `Profile` type (see above). Placement props (all optional, see "Fake-rotated frames" below):
53
53
  `align` (force a horizontal side), `alignOverride` (substitute the whole placement result), and
54
- `rotation` (forwarded to the automatic measurement). Also exports
55
- `getProfilePickerContentSize(profileCount, hasManageRow?)` - the popover's
54
+ `rotation` (forwarded to the automatic measurement). `colorSection`
55
+ (`{ value, onChange, previewValue?, swatches?, label?, caption? }`) puts a Color row at the top of
56
+ the popover that opens a page of swatches (with a back row), and turns the trigger into one
57
+ button that leads with a chip in `color` with the selected profile's tag; `triggerStyle` styles
58
+ that button. Also exports
59
+ `getProfilePickerContentSize(profileCount, hasManageRow?, colorSection?)` - the popover's
56
60
  `{ width, height }`, for sizing your own placement hook.
57
61
  - **`ProfilesManager`**: the roster screen itself: create/rename/recolor/retag/delete, editing
58
62
  inline in the row you tap, with a destructive-delete confirmation. No navigation of its own (no
@@ -142,7 +146,10 @@ const placement = useMyRotatedAlign(open, width, height) // returns a ProfilePic
142
146
 
143
147
  `getProfilePickerContentSize(profileCount, hasManageRow = false)` returns `{ width, height }`: 220
144
148
  wide, and `8 * 2 + (profileCount + 1 + (hasManageRow ? 1 : 0)) * 36` tall (the null/"guest" row, one
145
- row per profile, and the Manage row when `onManage` is passed).
149
+ row per profile, and the Manage row when `onManage` is passed), or with a color section (pass the same
150
+ object the picker gets) the taller of its two pages: that list plus the Color row (48 with a
151
+ caption, 36 without) and its 9pt divider, or the color page (a 36pt back row and the swatches, 187
152
+ for 20 colors), plus the padding.
146
153
 
147
154
 
148
155
  #### The Profiles screen's color swatch: `useColorPickerAlign`
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, RefObject, Ref } from 'react';
3
+ import { SeedColor } from '@rific/auto-paper';
3
4
  import { PopoverHost, PopoverRotation } from '@tastic/hud';
4
- import { View } from 'react-native';
5
+ import { View, StyleProp, ViewStyle } from 'react-native';
5
6
 
6
7
  interface Profile {
7
8
  id: string;
@@ -27,6 +28,14 @@ declare function ProfileColorProvider({ preview, children }: ProfileColorProvide
27
28
  declare function useProfileColorPreview(): ((hex: string) => string) | undefined;
28
29
  declare function useProfileDisplayColor(hex: string): string;
29
30
 
31
+ interface ProfilePickerColorSection {
32
+ value: string;
33
+ onChange: (hex: string) => void;
34
+ previewValue?: (hex: string) => string;
35
+ swatches?: SeedColor[];
36
+ label?: string;
37
+ caption?: string;
38
+ }
30
39
  interface ProfilePickerAlignResult {
31
40
  align: 'left' | 'right' | 'center';
32
41
  verticalAlign: 'below' | 'above';
@@ -38,7 +47,7 @@ interface ProfilePickerContentSize {
38
47
  width: number;
39
48
  height: number;
40
49
  }
41
- declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean): ProfilePickerContentSize;
50
+ declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
42
51
  interface ProfilePickerProps<P extends Profile> {
43
52
  idPrefix: string;
44
53
  host: PopoverHost;
@@ -55,8 +64,10 @@ interface ProfilePickerProps<P extends Profile> {
55
64
  nullIcon?: string;
56
65
  onSelect: (profile: P | null) => void;
57
66
  onManage?: () => void;
67
+ colorSection?: ProfilePickerColorSection;
68
+ triggerStyle?: StyleProp<ViewStyle>;
58
69
  }
59
- declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage }: ProfilePickerProps<P>): react.JSX.Element;
70
+ declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
60
71
 
61
72
  type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
62
73
  interface ProfileEditPatch {
@@ -217,4 +228,4 @@ declare function readSharedProfiles(groupId: string): Promise<Profile[] | null>;
217
228
  declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
218
229
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
219
230
 
220
- export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
231
+ export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, RefObject, Ref } from 'react';
3
+ import { SeedColor } from '@rific/auto-paper';
3
4
  import { PopoverHost, PopoverRotation } from '@tastic/hud';
4
- import { View } from 'react-native';
5
+ import { View, StyleProp, ViewStyle } from 'react-native';
5
6
 
6
7
  interface Profile {
7
8
  id: string;
@@ -27,6 +28,14 @@ declare function ProfileColorProvider({ preview, children }: ProfileColorProvide
27
28
  declare function useProfileColorPreview(): ((hex: string) => string) | undefined;
28
29
  declare function useProfileDisplayColor(hex: string): string;
29
30
 
31
+ interface ProfilePickerColorSection {
32
+ value: string;
33
+ onChange: (hex: string) => void;
34
+ previewValue?: (hex: string) => string;
35
+ swatches?: SeedColor[];
36
+ label?: string;
37
+ caption?: string;
38
+ }
30
39
  interface ProfilePickerAlignResult {
31
40
  align: 'left' | 'right' | 'center';
32
41
  verticalAlign: 'below' | 'above';
@@ -38,7 +47,7 @@ interface ProfilePickerContentSize {
38
47
  width: number;
39
48
  height: number;
40
49
  }
41
- declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean): ProfilePickerContentSize;
50
+ declare function getProfilePickerContentSize(profileCount: number, hasManageRow?: boolean, colorSection?: Pick<ProfilePickerColorSection, 'swatches' | 'caption'>): ProfilePickerContentSize;
42
51
  interface ProfilePickerProps<P extends Profile> {
43
52
  idPrefix: string;
44
53
  host: PopoverHost;
@@ -55,8 +64,10 @@ interface ProfilePickerProps<P extends Profile> {
55
64
  nullIcon?: string;
56
65
  onSelect: (profile: P | null) => void;
57
66
  onManage?: () => void;
67
+ colorSection?: ProfilePickerColorSection;
68
+ triggerStyle?: StyleProp<ViewStyle>;
58
69
  }
59
- declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage }: ProfilePickerProps<P>): react.JSX.Element;
70
+ declare function ProfilePicker<P extends Profile>({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation, guestLabel, nullLabel, nullIcon, onSelect, onManage, colorSection, triggerStyle }: ProfilePickerProps<P>): react.JSX.Element;
60
71
 
61
72
  type MD3TextVariant$1 = 'displayLarge' | 'displayMedium' | 'displaySmall' | 'headlineLarge' | 'headlineMedium' | 'headlineSmall' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'labelLarge' | 'labelMedium' | 'labelSmall' | 'bodyLarge' | 'bodyMedium' | 'bodySmall';
62
73
  interface ProfileEditPatch {
@@ -217,4 +228,4 @@ declare function readSharedProfiles(groupId: string): Promise<Profile[] | null>;
217
228
  declare function loadSharedProfiles(groupId: string): Promise<Profile[]>;
218
229
  declare function saveSharedProfiles(groupId: string, profiles: Profile[]): Promise<void>;
219
230
 
220
- export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
231
+ export { type CreateProfileInput, MAX_PROFILE_NAME_LENGTH, MAX_TAG_LENGTH, type Profile, ProfileChip, ProfileColorProvider, type ProfileEditPatch, type ProfileExtensionActions, type ProfileExtensionSlice, type ProfileExtensionState, ProfilePicker, type ProfilePickerAlignResult, type ProfilePickerColorSection, type ProfilePickerContentSize, type ProfileSelectionActions, type ProfileSelectionSlice, type ProfileSelectionState, ProfilesManager, type ProfilesManagerHandle, type ProfilesManagerProps, ProfilesScreen, type ProfilesScreenProps, type SeatColorsActions, type SeatColorsSlice, type SeatColorsState, type SelectProfilePayload, type SetLastGuestColorPayload, type SetProfileExtensionPayload, type SetProfileOverridePayload, type UpdateProfilePayload, type UseSharedProfilesSyncOptions, type UseSharedProfilesSyncResult, avoidSeatColorClash, createProfileExtensionSlice, createProfileRecord, createProfileSelectionSlice, createSeatColorsSlice, getProfilePickerContentSize, isSharedProfileStoreAvailable, isValidProfile, isValidTag, loadSharedProfiles, profilesActions, profilesReducer, readSharedProfiles, resolveInitialProfiles, saveSharedProfiles, useProfileColorPreview, useProfileDisplayColor, useSharedProfilesSync };
package/dist/index.js CHANGED
@@ -48,17 +48,34 @@ __export(index_exports, {
48
48
  });
49
49
  module.exports = __toCommonJS(index_exports);
50
50
 
51
- // src/ProfileChip.tsx
51
+ // src/IdentityChip.tsx
52
52
  var import_auto_paper = require("@rific/auto-paper");
53
53
  var import_react_native = require("react-native");
54
54
  var import_react_native_paper = require("react-native-paper");
55
+ var import_jsx_runtime = require("react/jsx-runtime");
56
+ var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
57
+ function IdentityChip({ color, tag, icon, size, filled = true }) {
58
+ const contrastColor = (0, import_auto_paper.getContrastColor)(color);
59
+ const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
60
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: tag ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor }) });
61
+ }
62
+ var styles = import_react_native.StyleSheet.create({
63
+ chip: {
64
+ alignItems: "center",
65
+ justifyContent: "center"
66
+ },
67
+ tag: {
68
+ fontWeight: "700",
69
+ paddingHorizontal: 2
70
+ }
71
+ });
55
72
 
56
73
  // src/ProfileColorContext.tsx
57
74
  var import_react = require("react");
58
- var import_jsx_runtime = require("react/jsx-runtime");
75
+ var import_jsx_runtime2 = require("react/jsx-runtime");
59
76
  var ProfileColorContext = (0, import_react.createContext)(void 0);
60
77
  function ProfileColorProvider({ preview, children }) {
61
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileColorContext.Provider, { value: preview, children });
78
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProfileColorContext.Provider, { value: preview, children });
62
79
  }
63
80
  function useProfileColorPreview() {
64
81
  return (0, import_react.useContext)(ProfileColorContext);
@@ -69,24 +86,11 @@ function useProfileDisplayColor(hex) {
69
86
  }
70
87
 
71
88
  // src/ProfileChip.tsx
72
- var import_jsx_runtime2 = require("react/jsx-runtime");
73
- var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
89
+ var import_jsx_runtime3 = require("react/jsx-runtime");
74
90
  function ProfileChip({ profile, size = 22, filled = true }) {
75
91
  const color = useProfileDisplayColor(profile.color);
76
- const contrastColor = (0, import_auto_paper.getContrastColor)(color);
77
- const tagFontSize = size * (profile.tag && EMOJI_PATTERN.test(profile.tag) ? 0.6 : 0.4);
78
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: color }], children: profile.tag ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: profile.tag }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper.Icon, { source: "account", size: size * 0.6, color: contrastColor }) });
92
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
79
93
  }
80
- var styles = import_react_native.StyleSheet.create({
81
- chip: {
82
- alignItems: "center",
83
- justifyContent: "center"
84
- },
85
- tag: {
86
- fontWeight: "700",
87
- paddingHorizontal: 2
88
- }
89
- });
90
94
 
91
95
  // src/ProfilePicker.tsx
92
96
  var import_auto_paper2 = require("@rific/auto-paper");
@@ -95,16 +99,40 @@ var import_hud = require("@tastic/hud");
95
99
  var import_react2 = require("react");
96
100
  var import_react_native2 = require("react-native");
97
101
  var import_react_native_paper2 = require("react-native-paper");
98
- var import_jsx_runtime3 = require("react/jsx-runtime");
102
+ var import_jsx_runtime4 = require("react/jsx-runtime");
99
103
  var TRIGGER_HEIGHT = 28;
100
104
  var POPOVER_WIDTH = 220;
101
105
  var ROW_HEIGHT = 36;
102
106
  var LIST_PADDING = 8;
103
- function getProfilePickerContentSize(profileCount, hasManageRow = false) {
107
+ var MENU_BORDER_WIDTH = 2;
108
+ var ROW_INSET = 12;
109
+ var CHIP_SIZE = 32;
110
+ var CHIP_TRIGGER_PADDING = 6;
111
+ var SWATCH_SIZE = 28;
112
+ var SWATCH_MIN_GAP = 6;
113
+ var COLOR_ROW_CAPTIONED_HEIGHT = 48;
114
+ var COLOR_LABEL_LINE = 18;
115
+ var COLOR_CAPTION_LINE = 16;
116
+ var COLOR_ROW_DIVIDER = 9;
117
+ var GRID_TOP = 4;
118
+ var GRID_BOTTOM = 8;
119
+ var GRID_WIDTH = POPOVER_WIDTH - 2 * MENU_BORDER_WIDTH - 2 * LIST_PADDING - 2 * ROW_INSET;
120
+ var GRID_COLUMNS = Math.floor((GRID_WIDTH + SWATCH_MIN_GAP) / (SWATCH_SIZE + SWATCH_MIN_GAP));
121
+ var GRID_GAP = (GRID_WIDTH - GRID_COLUMNS * SWATCH_SIZE) / (GRID_COLUMNS - 1);
122
+ function colorRowHeight(caption) {
123
+ return caption ? COLOR_ROW_CAPTIONED_HEIGHT : ROW_HEIGHT;
124
+ }
125
+ function colorPageHeight(swatches) {
126
+ const rows = Math.ceil(swatches.length / GRID_COLUMNS);
127
+ return ROW_HEIGHT + GRID_TOP + rows * SWATCH_SIZE + (rows - 1) * GRID_GAP + GRID_BOTTOM;
128
+ }
129
+ function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
104
130
  const rowCount = profileCount + 1 + (hasManageRow ? 1 : 0);
105
- return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + rowCount * ROW_HEIGHT };
131
+ const list = rowCount * ROW_HEIGHT + (colorSection ? colorRowHeight(colorSection.caption) + COLOR_ROW_DIVIDER : 0);
132
+ const content = colorSection ? Math.max(list, colorPageHeight(colorSection.swatches ?? import_auto_paper2.defaultColors)) : list;
133
+ return { width: POPOVER_WIDTH, height: LIST_PADDING * 2 + content };
106
134
  }
107
- function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage }) {
135
+ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: forcedAlign, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
108
136
  const id = `${idPrefix}-profile`;
109
137
  const colorPreview = useProfileColorPreview();
110
138
  const menuBg = dark ? "#000000" : "#FFFFFF";
@@ -112,7 +140,7 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
112
140
  const open = host.openId === id;
113
141
  const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
114
142
  const sortedProfiles = (0, import_react2.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
115
- const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage);
143
+ const { width: contentWidth, height: contentHeight } = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
116
144
  const auto = (0, import_hud.useAutoAlign)(open, contentWidth, contentHeight, rotation);
117
145
  const placement = alignOverride ?? auto;
118
146
  const { maxHeight, measured, triggerRef, verticalAlign } = placement;
@@ -130,13 +158,23 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
130
158
  host.close();
131
159
  onManage?.();
132
160
  };
133
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], children: [
134
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: () => host.toggle(id), style: styles2.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: styles2.triggerInner, children: [
135
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color }], numberOfLines: 1, children: (selectedProfile?.name ?? guestLabel).toUpperCase() }),
136
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color })
161
+ const chipTrigger = !!colorSection;
162
+ const [triggerWidth, setTriggerWidth] = (0, import_react2.useState)(0);
163
+ const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
164
+ const [colorPage, setColorPage] = (0, import_react2.useState)(false);
165
+ const handleTriggerPress = () => {
166
+ setColorPage(false);
167
+ host.toggle(id);
168
+ };
169
+ const triggerLabel = (selectedProfile?.name ?? guestLabel).toUpperCase();
170
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles2.trigger, chipTrigger && styles2.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
172
+ chipTrigger && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IdentityChip, { color, tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon, size: CHIP_SIZE }),
173
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
137
175
  ] }) }),
138
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
139
- import_react_native2.ScrollView,
176
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
177
+ import_react_native2.View,
140
178
  {
141
179
  style: [
142
180
  styles2.menu,
@@ -147,20 +185,19 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
147
185
  maxHeight
148
186
  }
149
187
  ],
150
- contentContainerStyle: styles2.menuContent,
151
- showsVerticalScrollIndicator: false,
152
- children: [
153
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: nullIcon, size: 13, color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }) }),
155
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.rowLabel, { color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }], children: nullLabel })
188
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: styles2.menuContent, showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorPage, { section: colorSection, profileColor: selectedProfile?.color, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
189
+ colorSection && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
190
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
191
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: nullIcon, size: 13, color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }) }),
192
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.rowLabel, { color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }], children: nullLabel })
156
193
  ] }) }),
157
194
  sortedProfiles.map((profile) => {
158
195
  const taken = profile.id === takenId;
159
196
  const selected = selectedId === profile.id;
160
197
  const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
161
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
162
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProfileChip, { profile, filled: !selected }),
163
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
198
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
199
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileChip, { profile, filled: !selected }),
200
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
164
201
  import_react_native_paper2.Text,
165
202
  {
166
203
  style: [
@@ -175,18 +212,49 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
175
212
  )
176
213
  ] }) }, profile.id);
177
214
  }),
178
- onManage && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
180
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press.TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: styles2.manageRowInner, children: [
181
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
182
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color }], children: "Manage" })
215
+ onManage && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
216
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
217
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.manageRowInner, children: [
218
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color: fg }], children: "Manage" })
183
220
  ] }) })
184
221
  ] })
185
- ]
222
+ ] }) }, colorSection && colorPage ? "color" : "list")
186
223
  }
187
224
  ) })
188
225
  ] });
189
226
  }
227
+ function ColorRow({ section, fg, dividerColor, onPress }) {
228
+ const { value, previewValue, label = "Color", caption } = section;
229
+ const drawn = previewValue ? previewValue(value) : value;
230
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
231
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [styles2.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
232
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
233
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.colorRowText, children: [
234
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
235
+ caption ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
236
+ ] }),
237
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
238
+ ] }) }),
239
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: dividerColor }] })
240
+ ] });
241
+ }
242
+ function ColorPage({ section, profileColor, fg, onBack }) {
243
+ const { value, onChange, previewValue, swatches = import_auto_paper2.defaultColors, label = "Color" } = section;
244
+ const displayFor = previewValue ?? ((hex) => hex);
245
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
246
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: onBack, style: styles2.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
247
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-left", size: 20, color: fg }) }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
249
+ ] }) }),
250
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: styles2.swatches, children: swatches.map((swatch) => {
251
+ const selected = swatch.value.toLowerCase() === value.toLowerCase();
252
+ const isProfileColor = !!profileColor && swatch.value.toLowerCase() === profileColor.toLowerCase();
253
+ const swatchDisplay = displayFor(swatch.value);
254
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: () => onChange(swatch.value), borderless: true, style: [styles2.swatch, { backgroundColor: swatchDisplay }, selected && styles2.swatchSelected], accessibilityRole: "button", accessibilityLabel: isProfileColor ? `${swatch.label}, profile color` : swatch.label, accessibilityState: { selected }, children: selected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: (0, import_auto_paper2.getContrastColor)(swatchDisplay) }) : isProfileColor ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, { style: [styles2.profileDot, { backgroundColor: (0, import_auto_paper2.getContrastColor)(swatchDisplay) }] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native2.View, {}) }, swatch.value);
255
+ }) })
256
+ ] });
257
+ }
190
258
  var styles2 = import_react_native2.StyleSheet.create({
191
259
  anchor: {
192
260
  position: "relative"
@@ -196,6 +264,40 @@ var styles2 = import_react_native2.StyleSheet.create({
196
264
  anchorOpen: {
197
265
  zIndex: 100
198
266
  },
267
+ // The pill, rounded at both ends. The host adds its own background through `triggerStyle`.
268
+ chipTrigger: {
269
+ borderRadius: (CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING) / 2
270
+ },
271
+ // The padding lives on the measured view, not the pressable around it, so the placement measures
272
+ // the pill's real edge: measured from inside the padding, a popover opening toward a screen edge
273
+ // was sized as if the pill were 6pt further from it, and ran 2pt past that edge.
274
+ chipTriggerInner: {
275
+ alignItems: "center",
276
+ flexDirection: "row",
277
+ gap: 8,
278
+ height: CHIP_SIZE + 2 * CHIP_TRIGGER_PADDING,
279
+ paddingLeft: CHIP_TRIGGER_PADDING,
280
+ paddingRight: CHIP_TRIGGER_PADDING + 8
281
+ },
282
+ colorRowCaption: {
283
+ fontSize: 12,
284
+ lineHeight: COLOR_CAPTION_LINE,
285
+ opacity: 0.6
286
+ },
287
+ // A row's label, with a line height so the captioned Color row's height is exact.
288
+ colorRowLabel: {
289
+ fontSize: 14,
290
+ lineHeight: COLOR_LABEL_LINE
291
+ },
292
+ // The color in effect, in the same 22pt slot as every row's chip.
293
+ colorRowSwatch: {
294
+ borderRadius: 11,
295
+ height: 22,
296
+ width: 22
297
+ },
298
+ colorRowText: {
299
+ flex: 1
300
+ },
199
301
  // Separates Manage (a navigation action) from the selectable rows above it - different kind of
200
302
  // action, not just another row in the same list. Tinted with the seat's own color (passed in as
201
303
  // backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
@@ -230,7 +332,7 @@ var styles2 = import_react_native2.StyleSheet.create({
230
332
  },
231
333
  menu: {
232
334
  borderRadius: 12,
233
- borderWidth: 2,
335
+ borderWidth: MENU_BORDER_WIDTH,
234
336
  elevation: 8,
235
337
  shadowColor: "#000000",
236
338
  shadowOffset: { height: 2, width: 0 },
@@ -243,6 +345,12 @@ var styles2 = import_react_native2.StyleSheet.create({
243
345
  menuContent: {
244
346
  padding: LIST_PADDING
245
347
  },
348
+ // The selected profile's own color (see ColorSection).
349
+ profileDot: {
350
+ borderRadius: 4,
351
+ height: 8,
352
+ width: 8
353
+ },
246
354
  row: {
247
355
  borderRadius: 8,
248
356
  height: ROW_HEIGHT
@@ -255,12 +363,33 @@ var styles2 = import_react_native2.StyleSheet.create({
255
363
  flexDirection: "row",
256
364
  flex: 1,
257
365
  gap: 8,
258
- paddingHorizontal: 12
366
+ paddingHorizontal: ROW_INSET
259
367
  },
260
368
  rowLabel: {
261
369
  flex: 1,
262
370
  fontSize: 14
263
371
  },
372
+ swatch: {
373
+ alignItems: "center",
374
+ borderRadius: SWATCH_SIZE / 2,
375
+ height: SWATCH_SIZE,
376
+ justifyContent: "center",
377
+ width: SWATCH_SIZE
378
+ },
379
+ swatchSelected: {
380
+ borderColor: "#ffffff",
381
+ borderWidth: 2
382
+ },
383
+ swatches: {
384
+ columnGap: GRID_GAP,
385
+ flexDirection: "row",
386
+ flexWrap: "wrap",
387
+ marginBottom: GRID_BOTTOM,
388
+ marginHorizontal: ROW_INSET,
389
+ marginTop: GRID_TOP,
390
+ rowGap: GRID_GAP,
391
+ width: GRID_WIDTH
392
+ },
264
393
  trigger: {
265
394
  alignSelf: "flex-start"
266
395
  },
@@ -321,8 +450,8 @@ function isValidProfile(value) {
321
450
  }
322
451
 
323
452
  // src/ProfilesManager.tsx
324
- var import_jsx_runtime4 = require("react/jsx-runtime");
325
- var CHIP_SIZE = 40;
453
+ var import_jsx_runtime5 = require("react/jsx-runtime");
454
+ var CHIP_SIZE2 = 40;
326
455
  function deriveTag(name) {
327
456
  const trimmed = name.trim();
328
457
  return trimmed ? trimmed[0].toUpperCase() : "";
@@ -398,38 +527,38 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
398
527
  }, [confirmDeleteId, editingId, onDelete]);
399
528
  const sortedProfiles = (0, import_react3.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
400
529
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
401
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
402
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
530
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
531
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
403
532
  headerLeft,
404
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
533
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: titleVariant, style: [styles3.title, { color: fg }], children: "Profiles" })
405
534
  ] }),
406
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
535
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
407
536
  if (row.kind === "new") {
408
537
  if (editingId !== "new") {
409
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
410
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Icon, { source: "plus", size: 20, color: fg }) }),
411
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
538
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native3.View, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: "plus", size: 20, color: fg }) }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
412
541
  ] }) }, "new");
413
542
  }
414
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
543
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
415
544
  }
416
545
  const { profile } = row;
417
546
  if (editingId === profile.id) {
418
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
547
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
419
548
  }
420
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileChip, { profile, size: CHIP_SIZE }),
422
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
549
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
423
552
  ] }) }, profile.id);
424
553
  }) }) }),
425
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud2.ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
554
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.ConfirmDialog, { visible: confirmDeleteProfile !== null, title: "Delete Profile?", message: confirmDeleteProfile ? `This permanently deletes ${confirmDeleteProfile.name}\u2019s profile. This cannot be undone.` : "", confirmLabel: "Delete", cancelLabel: "Cancel", icon: "alert-outline", destructive: true, onConfirm: handleConfirmDelete, onCancel: () => setConfirmDeleteId(null) })
426
555
  ] });
427
556
  }
428
557
  function AlignedColorPicker({ useAlign, ...props }) {
429
558
  const { width: windowWidth } = (0, import_react_native3.useWindowDimensions)();
430
559
  const { width, height } = (0, import_hud2.getInlineColorPickerContentSize)(import_auto_paper3.defaultColors.length, windowWidth);
431
560
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
432
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
561
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
433
562
  }
434
563
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
435
564
  const register = (0, import_focus_chain.useFocusChain)();
@@ -442,11 +571,11 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
442
571
  (0, import_react3.useEffect)(() => {
443
572
  if (host.openId === "color") tagFocusRef.current();
444
573
  }, [host.openId]);
445
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
446
- useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
447
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
448
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles3.nameInput }),
449
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
574
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
575
+ useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
576
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles3.hiddenTagInput, pointerEvents: "none" }),
577
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.TextInput, { ref: name.ref, ...name.props, onSubmitEditing: onSubmit, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles3.nameInput }),
578
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
450
579
  ] });
451
580
  }
452
581
  var styles3 = import_react_native3.StyleSheet.create({
@@ -454,11 +583,17 @@ var styles3 = import_react_native3.StyleSheet.create({
454
583
  gap: 4,
455
584
  paddingHorizontal: 20
456
585
  },
586
+ // zIndex: the color popover lives inside this row, and a zIndex only orders siblings of one parent
587
+ // (React Native Web also gives every positioned view its own stacking context), so the popover's
588
+ // own zIndex never reached the rows after this one: they were drawn over it (a Kid row and the New
589
+ // Profile row across the swatches, 2026-10-16). Raising the row lifts the popover with it. There
590
+ // is only ever one edit row.
457
591
  editRow: {
458
592
  alignItems: "center",
459
593
  flexDirection: "row",
460
594
  gap: 8,
461
- paddingVertical: 6
595
+ paddingVertical: 6,
596
+ zIndex: 1
462
597
  },
463
598
  header: {
464
599
  alignItems: "center",
@@ -499,10 +634,10 @@ var styles3 = import_react_native3.StyleSheet.create({
499
634
  },
500
635
  rowChip: {
501
636
  alignItems: "center",
502
- borderRadius: CHIP_SIZE / 2,
503
- height: CHIP_SIZE,
637
+ borderRadius: CHIP_SIZE2 / 2,
638
+ height: CHIP_SIZE2,
504
639
  justifyContent: "center",
505
- width: CHIP_SIZE
640
+ width: CHIP_SIZE2
506
641
  },
507
642
  rowInner: {
508
643
  alignItems: "center",
@@ -526,7 +661,7 @@ var import_auto_paper4 = require("@rific/auto-paper");
526
661
  var import_feedback_press3 = require("@rific/feedback-press");
527
662
  var import_react4 = require("react");
528
663
  var import_react_native4 = require("react-native");
529
- var import_jsx_runtime5 = require("react/jsx-runtime");
664
+ var import_jsx_runtime6 = require("react/jsx-runtime");
530
665
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
531
666
  const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
532
667
  const managerRef = (0, import_react4.useRef)(null);
@@ -536,7 +671,7 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
536
671
  managerRef.current?.commitPendingEdit();
537
672
  onBack();
538
673
  };
539
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.View, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
674
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native4.View, { style: [styles4.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
540
675
  }
541
676
  var styles4 = import_react_native4.StyleSheet.create({
542
677
  container: {