@tastic/profile 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,515 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ MAX_PROFILE_NAME_LENGTH: () => MAX_PROFILE_NAME_LENGTH,
24
+ MAX_TAG_LENGTH: () => MAX_TAG_LENGTH,
25
+ ProfileChip: () => ProfileChip,
26
+ ProfilePicker: () => ProfilePicker,
27
+ ProfilesManager: () => ProfilesManager,
28
+ isValidProfile: () => isValidProfile,
29
+ isValidTag: () => isValidTag
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/ProfileChip.tsx
34
+ var import_auto_paper = require("@rific/auto-paper");
35
+ var import_react_native = require("react-native");
36
+ var import_react_native_paper = require("react-native-paper");
37
+ var import_jsx_runtime = require("react/jsx-runtime");
38
+ var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
39
+ function ProfileChip({ profile, size = 22, filled = true }) {
40
+ const contrastColor = (0, import_auto_paper.getContrastColor)(profile.color);
41
+ const tagFontSize = size * (profile.tag && EMOJI_PATTERN.test(profile.tag) ? 0.6 : 0.4);
42
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.chip, { borderRadius: size / 2, height: size, width: size }, filled && { backgroundColor: profile.color }], children: profile.tag ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Text, { style: [styles.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: profile.tag }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: "account", size: size * 0.6, color: contrastColor }) });
43
+ }
44
+ var styles = import_react_native.StyleSheet.create({
45
+ chip: {
46
+ alignItems: "center",
47
+ justifyContent: "center"
48
+ },
49
+ tag: {
50
+ fontWeight: "700",
51
+ paddingHorizontal: 2
52
+ }
53
+ });
54
+
55
+ // src/ProfilePicker.tsx
56
+ var import_auto_paper2 = require("@rific/auto-paper");
57
+ var import_feedback_press = require("@rific/feedback-press");
58
+ var import_hud = require("@tastic/hud");
59
+ var import_react = require("react");
60
+ var import_react_native2 = require("react-native");
61
+ var import_react_native_paper2 = require("react-native-paper");
62
+ var import_jsx_runtime2 = require("react/jsx-runtime");
63
+ var TRIGGER_HEIGHT = 28;
64
+ var POPOVER_WIDTH = 220;
65
+ var ROW_HEIGHT = 36;
66
+ var LIST_PADDING = 8;
67
+ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align: alignOverride, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage }) {
68
+ const id = `${idPrefix}-profile`;
69
+ const menuBg = dark ? "#000000" : "#FFFFFF";
70
+ const fg = dark ? "#FFFFFF" : "#000000";
71
+ const open = host.openId === id;
72
+ const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
73
+ const sortedProfiles = (0, import_react.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
74
+ const rowCount = profiles.length + 1 + (onManage ? 1 : 0);
75
+ const contentHeight = LIST_PADDING * 2 + rowCount * ROW_HEIGHT;
76
+ const { align: autoAlign, maxHeight, measured, triggerRef, verticalAlign } = (0, import_hud.useAutoAlign)(open, POPOVER_WIDTH, contentHeight);
77
+ const align = alignOverride ?? autoAlign;
78
+ const handleSelectGuest = () => {
79
+ onSelect(null);
80
+ host.close();
81
+ };
82
+ const handleSelectExisting = (profile) => {
83
+ if (profile.id === takenId) return;
84
+ onSelect(profile);
85
+ host.close();
86
+ };
87
+ const handleManage = () => {
88
+ host.close();
89
+ onManage?.();
90
+ };
91
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], children: [
92
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: () => host.toggle(id), style: styles2.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: styles2.triggerInner, children: [
93
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color }], numberOfLines: 1, children: (selectedProfile?.name ?? guestLabel).toUpperCase() }),
94
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color })
95
+ ] }) }),
96
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_hud.PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
97
+ import_react_native2.ScrollView,
98
+ {
99
+ style: [
100
+ styles2.menu,
101
+ {
102
+ backgroundColor: menuBg,
103
+ borderColor: color,
104
+ width: POPOVER_WIDTH,
105
+ maxHeight
106
+ }
107
+ ],
108
+ contentContainerStyle: styles2.menuContent,
109
+ showsVerticalScrollIndicator: false,
110
+ children: [
111
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: handleSelectGuest, style: [styles2.row, selectedId === null && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.rowInner, children: [
112
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.guestChip, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: nullIcon, size: 13, color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }) }),
113
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.rowLabel, { color: selectedId === null ? (0, import_auto_paper2.getContrastColor)(color) : fg }], children: nullLabel })
114
+ ] }) }),
115
+ sortedProfiles.map((profile) => {
116
+ const taken = profile.id === takenId;
117
+ const selected = selectedId === profile.id;
118
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [styles2.row, selected && { backgroundColor: profile.color }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.rowInner, taken && styles2.rowDisabled], children: [
119
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ProfileChip, { profile, filled: !selected }),
120
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
121
+ import_react_native_paper2.Text,
122
+ {
123
+ style: [
124
+ styles2.rowLabel,
125
+ {
126
+ color: selected ? (0, import_auto_paper2.getContrastColor)(profile.color) : fg
127
+ }
128
+ ],
129
+ numberOfLines: 1,
130
+ children: profile.name
131
+ }
132
+ )
133
+ ] }) }, profile.id);
134
+ }),
135
+ onManage && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
136
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
137
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: handleManage, style: styles2.row, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.manageRowInner, children: [
138
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "account-cog-outline", size: 16, color }),
139
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.manageLabel, { color }], children: "Manage" })
140
+ ] }) })
141
+ ] })
142
+ ]
143
+ }
144
+ ) })
145
+ ] });
146
+ }
147
+ var styles2 = import_react_native2.StyleSheet.create({
148
+ anchor: {
149
+ position: "relative"
150
+ },
151
+ // See InlineColorPicker's identical comment — React Native Web gives every position:'relative'
152
+ // view its own stacking context, so the elevation has to live on the anchor itself.
153
+ anchorOpen: {
154
+ zIndex: 100
155
+ },
156
+ // Separates Manage (a navigation action) from the selectable rows above it — different kind of
157
+ // action, not just another row in the same list. Tinted with the seat's own color (passed in as
158
+ // backgroundColor) rather than a flat gray, matching the border/caret's own accent-tinted look.
159
+ divider: {
160
+ height: 1,
161
+ marginVertical: 4,
162
+ opacity: 0.25
163
+ },
164
+ // Matches ProfileChip's own default size/shape exactly (see this row's own render-site comment)
165
+ // — no backgroundColor, unlike ProfileChip's own default: this row has no identity color of its
166
+ // own to fill it with, and the render site now colors the icon directly instead.
167
+ guestChip: {
168
+ alignItems: "center",
169
+ borderRadius: 11,
170
+ height: 22,
171
+ justifyContent: "center",
172
+ width: 22
173
+ },
174
+ manageLabel: {
175
+ fontSize: 13,
176
+ fontWeight: "700",
177
+ letterSpacing: 0.5
178
+ },
179
+ // Centered rather than left-aligned like a normal row — see this row's own render-site comment
180
+ // for why (a footer action, not another selectable identity).
181
+ manageRowInner: {
182
+ alignItems: "center",
183
+ flexDirection: "row",
184
+ gap: 6,
185
+ height: "100%",
186
+ justifyContent: "center"
187
+ },
188
+ menu: {
189
+ borderRadius: 12,
190
+ borderWidth: 2,
191
+ elevation: 8,
192
+ shadowColor: "#000000",
193
+ shadowOffset: { height: 2, width: 0 },
194
+ shadowOpacity: 0.3,
195
+ shadowRadius: 8
196
+ },
197
+ // Padded on every side (not just top/bottom) — a selected row's own full-color background (see
198
+ // `row`'s borderRadius) needs real margin from the menu's own straight edges to read as a rounded
199
+ // pill instead of looking clipped against them.
200
+ menuContent: {
201
+ padding: LIST_PADDING
202
+ },
203
+ row: {
204
+ borderRadius: 8,
205
+ height: ROW_HEIGHT
206
+ },
207
+ rowDisabled: {
208
+ opacity: 0.35
209
+ },
210
+ rowInner: {
211
+ alignItems: "center",
212
+ flexDirection: "row",
213
+ flex: 1,
214
+ gap: 8,
215
+ paddingHorizontal: 12
216
+ },
217
+ rowLabel: {
218
+ flex: 1,
219
+ fontSize: 14
220
+ },
221
+ trigger: {
222
+ alignSelf: "flex-start"
223
+ },
224
+ triggerInner: {
225
+ alignItems: "center",
226
+ flexDirection: "row",
227
+ gap: 2,
228
+ height: TRIGGER_HEIGHT
229
+ },
230
+ triggerLabel: {
231
+ fontSize: 13,
232
+ fontWeight: "700",
233
+ letterSpacing: 1
234
+ }
235
+ });
236
+
237
+ // src/ProfilesManager.tsx
238
+ var import_auto_paper3 = require("@rific/auto-paper");
239
+ var import_feedback_press2 = require("@rific/feedback-press");
240
+ var import_focus_chain = require("@rific/focus-chain");
241
+ var import_toaster = require("@rific/toaster");
242
+ var import_hud2 = require("@tastic/hud");
243
+ var import_react2 = require("react");
244
+ var import_react_native3 = require("react-native");
245
+ var import_react_native_paper3 = require("react-native-paper");
246
+ var import_react_native_safe_area_context = require("react-native-safe-area-context");
247
+
248
+ // src/profilesValidation.ts
249
+ var MAX_PROFILE_NAME_LENGTH = 20;
250
+ var MAX_TAG_LENGTH = 3;
251
+ function isValidHexColor(value) {
252
+ return typeof value === "string" && /^#[0-9a-f]{6}$/i.test(value);
253
+ }
254
+ var VARIATION_SELECTOR = String.fromCodePoint(65039);
255
+ var ZERO_WIDTH_JOINER = String.fromCodePoint(8205);
256
+ var EMOJI_PATTERN2 = new RegExp(`^\\p{Extended_Pictographic}${VARIATION_SELECTOR}?(${ZERO_WIDTH_JOINER}\\p{Extended_Pictographic}${VARIATION_SELECTOR}?)*$`, "u");
257
+ function isValidTag(value) {
258
+ if (typeof value !== "string") return false;
259
+ if (value.length === 0) return true;
260
+ if (EMOJI_PATTERN2.test(value)) return true;
261
+ return !/\p{Extended_Pictographic}/u.test(value) && Array.from(value).length <= MAX_TAG_LENGTH;
262
+ }
263
+ function isValidProfile(value) {
264
+ if (!value || typeof value !== "object") return false;
265
+ const v = value;
266
+ return typeof v.id === "string" && v.id.length > 0 && typeof v.name === "string" && v.name.trim().length > 0 && v.name.length <= MAX_PROFILE_NAME_LENGTH && isValidHexColor(v.color) && isValidTag(v.tag) && typeof v.createdAt === "number" && typeof v.updatedAt === "number";
267
+ }
268
+
269
+ // src/ProfilesManager.tsx
270
+ var import_jsx_runtime3 = require("react/jsx-runtime");
271
+ var CHIP_SIZE = 40;
272
+ function deriveTag(name) {
273
+ const trimmed = name.trim();
274
+ return trimmed ? trimmed[0].toUpperCase() : "";
275
+ }
276
+ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, headerLeft }) {
277
+ const { dark, colors } = (0, import_auto_paper3.useAutoPaperTheme)();
278
+ const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
279
+ const { error: showErrorToast } = (0, import_toaster.useToast)();
280
+ const host = (0, import_hud2.usePopoverHost)();
281
+ const fg = dark ? "#FFFFFF" : "#000000";
282
+ const fgMuted = dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
283
+ const cardBg = dark ? "#111111" : "#F2F2F2";
284
+ const cardBorder = dark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)";
285
+ const cancelBg = dark ? "#2A2A2A" : "#E0E0E0";
286
+ const cancelFg = dark ? "#FFFFFF" : "#000000";
287
+ const [editingId, setEditingId] = (0, import_react2.useState)(null);
288
+ const [draftName, setDraftName] = (0, import_react2.useState)("");
289
+ const [draftColor, setDraftColor] = (0, import_react2.useState)(defaultColor);
290
+ const [draftTag, setDraftTag] = (0, import_react2.useState)("");
291
+ const [tagManuallySet, setTagManuallySet] = (0, import_react2.useState)(false);
292
+ const [confirmDeleteId, setConfirmDeleteId] = (0, import_react2.useState)(null);
293
+ const commitEdit = (0, import_react2.useCallback)(() => {
294
+ const name = draftName.trim();
295
+ if (editingId && name) {
296
+ const tagValid = isValidTag(draftTag);
297
+ if (draftTag && !tagValid) showErrorToast("Invalid tag", "Use up to 3 letters or a single emoji");
298
+ const tag = tagValid ? draftTag : "";
299
+ if (editingId === "new") onCreate({ color: draftColor, name, tag });
300
+ else onSave(editingId, { color: draftColor, name, tag });
301
+ }
302
+ setEditingId(null);
303
+ }, [editingId, draftName, draftColor, draftTag, onCreate, onSave, showErrorToast]);
304
+ const commitEditRef = (0, import_react2.useRef)(commitEdit);
305
+ (0, import_react2.useEffect)(() => {
306
+ commitEditRef.current = commitEdit;
307
+ });
308
+ (0, import_react2.useEffect)(() => () => commitEditRef.current(), []);
309
+ const startEdit = (0, import_react2.useCallback)(
310
+ (target) => {
311
+ commitEdit();
312
+ if (target === "new") {
313
+ setDraftName("");
314
+ setDraftColor(defaultColor);
315
+ setDraftTag("");
316
+ setTagManuallySet(false);
317
+ } else {
318
+ setDraftName(target.name);
319
+ setDraftColor(target.color);
320
+ setDraftTag(target.tag);
321
+ setTagManuallySet(target.tag !== "");
322
+ }
323
+ setEditingId(target === "new" ? "new" : target.id);
324
+ },
325
+ [commitEdit, defaultColor]
326
+ );
327
+ const handleNameChange = (0, import_react2.useCallback)(
328
+ (name) => {
329
+ setDraftName(name);
330
+ if (!tagManuallySet) setDraftTag(deriveTag(name));
331
+ },
332
+ [tagManuallySet]
333
+ );
334
+ const handleTagChange = (0, import_react2.useCallback)((tag) => {
335
+ setDraftTag(tag);
336
+ setTagManuallySet(true);
337
+ }, []);
338
+ const confirmDeleteProfile = profiles.find((p) => p.id === confirmDeleteId) ?? null;
339
+ const handleConfirmDelete = (0, import_react2.useCallback)(() => {
340
+ if (confirmDeleteId) {
341
+ onDelete(confirmDeleteId);
342
+ if (editingId === confirmDeleteId) setEditingId(null);
343
+ }
344
+ setConfirmDeleteId(null);
345
+ }, [confirmDeleteId, editingId, onDelete]);
346
+ const sortedProfiles = (0, import_react2.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
347
+ const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
348
+ const tagInvalid = draftTag.length > 0 && !isValidTag(draftTag);
349
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
350
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
351
+ headerLeft,
352
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "displaySmall", style: [styles3.title, { color: fg }], children: "Profiles" })
353
+ ] }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.KeyboardAvoidingView, { behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0, style: styles3.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ScrollView, { style: styles3.scrollView, contentContainerStyle: [styles3.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
355
+ if (row.kind === "new") {
356
+ if (editingId !== "new") {
357
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit("new"), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
358
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles3.rowChip, styles3.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "plus", size: 20, color: fg }) }),
359
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
360
+ ] }) }, "new");
361
+ }
362
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, tagInvalid, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark }, "new");
363
+ }
364
+ const { profile } = row;
365
+ if (editingId === profile.id) {
366
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, tagInvalid, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark }, profile.id);
367
+ }
368
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => startEdit(profile), style: styles3.row, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.rowInner, children: [
369
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProfileChip, { profile, size: CHIP_SIZE }),
370
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
371
+ ] }) }, profile.id);
372
+ }) }) }),
373
+ confirmDeleteProfile && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles3.overlayCard, { backgroundColor: cardBg, borderColor: cardBorder }], children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "alert-outline", size: 64, color: colors.danger }),
375
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Text, { variant: "headlineLarge", style: [styles3.overlayTitle, { color: colors.danger }], children: "Delete Profile?" }),
376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native_paper3.Text, { variant: "bodyLarge", style: [styles3.overlayBody, { color: fg }], children: [
377
+ "This permanently deletes ",
378
+ confirmDeleteProfile.name,
379
+ "\u2019s profile. This cannot be undone."
380
+ ] }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Button, { mode: "contained", onPress: () => setConfirmDeleteId(null), style: styles3.overlayButton, buttonColor: cancelBg, textColor: cancelFg, children: "Cancel" }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Button, { mode: "contained", onPress: handleConfirmDelete, style: styles3.overlayButton, buttonColor: colors.danger, textColor: colors.onDanger, children: "Delete" })
383
+ ] }) }) })
384
+ ] });
385
+ }
386
+ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, tagInvalid, onSubmit, onDelete, autoFocus, fgMuted, host, dark }) {
387
+ const register = (0, import_focus_chain.useFocusChain)();
388
+ const tag = register();
389
+ const name = register();
390
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.editRow, children: [
391
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, dark, size: CHIP_SIZE }),
392
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.TextInput, { ref: tag.ref, ...tag.props, mode: "outlined", dense: true, value: draftTag, onChangeText: onTagChange, placeholder: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, error: tagInvalid, returnKeyType: "next", style: styles3.tagInput, contentStyle: styles3.tagInputContent }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.TextInput, { ref: name.ref, ...name.props, mode: "outlined", dense: true, value: draftName, onChangeText: onNameChange, maxLength: MAX_PROFILE_NAME_LENGTH, placeholder: "Name", autoFocus, returnKeyType: "done", style: styles3.nameInput, onSubmitEditing: onSubmit }),
394
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_feedback_press2.TouchableRipple, { onPress: onDelete, style: styles3.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper3.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
395
+ ] });
396
+ }
397
+ var styles3 = import_react_native3.StyleSheet.create({
398
+ content: {
399
+ gap: 4,
400
+ paddingHorizontal: 20
401
+ },
402
+ editRow: {
403
+ alignItems: "center",
404
+ flexDirection: "row",
405
+ gap: 8,
406
+ paddingVertical: 6
407
+ },
408
+ header: {
409
+ alignItems: "center",
410
+ flexDirection: "row",
411
+ gap: 4,
412
+ paddingBottom: 8
413
+ },
414
+ iconButton: {
415
+ alignItems: "center",
416
+ borderRadius: 8,
417
+ height: 36,
418
+ justifyContent: "center",
419
+ width: 36
420
+ },
421
+ keyboardAvoiding: {
422
+ flex: 1
423
+ },
424
+ nameInput: {
425
+ flex: 1
426
+ },
427
+ newChip: {
428
+ backgroundColor: "transparent",
429
+ borderStyle: "dashed",
430
+ borderWidth: 2
431
+ },
432
+ overlay: {
433
+ alignItems: "center",
434
+ backgroundColor: "rgba(0,0,0,0.72)",
435
+ bottom: 0,
436
+ justifyContent: "center",
437
+ left: 0,
438
+ position: "absolute",
439
+ right: 0,
440
+ top: 0
441
+ },
442
+ overlayBody: { textAlign: "center" },
443
+ overlayButton: { width: 160 },
444
+ overlayCard: {
445
+ alignItems: "center",
446
+ borderRadius: 20,
447
+ borderWidth: 1,
448
+ gap: 16,
449
+ maxWidth: 360,
450
+ padding: 32
451
+ },
452
+ overlayTitle: { fontWeight: "bold", marginBottom: -8 },
453
+ row: {
454
+ borderRadius: 8,
455
+ paddingVertical: 8
456
+ },
457
+ rowChip: {
458
+ alignItems: "center",
459
+ borderRadius: CHIP_SIZE / 2,
460
+ height: CHIP_SIZE,
461
+ justifyContent: "center",
462
+ width: CHIP_SIZE
463
+ },
464
+ rowInner: {
465
+ alignItems: "center",
466
+ flexDirection: "row",
467
+ gap: 12
468
+ },
469
+ rowName: {
470
+ flex: 1
471
+ },
472
+ scrollView: {
473
+ flex: 1
474
+ },
475
+ // Wide enough for MAX_TAG_LENGTH monospace characters (or one emoji) plus its own padding/border
476
+ // — matched to CHIP_SIZE (the same footprint every other circle on this screen uses), not a
477
+ // fraction of the row's width, since the name field next to it is what should actually get the
478
+ // row's remaining space.
479
+ tagInput: {
480
+ height: CHIP_SIZE,
481
+ width: CHIP_SIZE
482
+ },
483
+ tagInputContent: {
484
+ // react-native-paper's outlined TextInput sets its own native input's minWidth from the
485
+ // placeholder/label's measured layout width (sized for a real label, not this field's own
486
+ // 3-character placeholder) — left alone, that floor is wider than CHIP_SIZE, so the actual
487
+ // editable box silently overflows past this field's own visible outline and centers within
488
+ // that wider, partly invisible box instead. Zeroing it here lets the input size to CHIP_SIZE
489
+ // instead, which is what actually makes `textAlign: 'center'` below center against the
490
+ // visible box rather than an overflowing invisible one.
491
+ minWidth: 0,
492
+ // Same reasoning as minWidth above, for the horizontal direction: the outlined input's own
493
+ // default padding (16px a side) is sized for a normal-width field, and at CHIP_SIZE's 40px
494
+ // that leaves only 8px total for actual text — not enough to fit the "Tag" placeholder itself
495
+ // (3 characters), which is what was clipping it to just "T" once minWidth stopped letting the
496
+ // box silently overflow wider to compensate.
497
+ paddingHorizontal: 2,
498
+ textAlign: "center",
499
+ textAlignVertical: "center"
500
+ },
501
+ title: {
502
+ flexShrink: 1,
503
+ fontWeight: "bold"
504
+ }
505
+ });
506
+ // Annotate the CommonJS export names for ESM import in node:
507
+ 0 && (module.exports = {
508
+ MAX_PROFILE_NAME_LENGTH,
509
+ MAX_TAG_LENGTH,
510
+ ProfileChip,
511
+ ProfilePicker,
512
+ ProfilesManager,
513
+ isValidProfile,
514
+ isValidTag
515
+ });