@tastic/profile 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -45,8 +45,10 @@ __export(index_exports, {
45
45
  readSharedProfiles: () => readSharedProfiles,
46
46
  resolveInitialProfiles: () => resolveInitialProfiles,
47
47
  saveSharedProfiles: () => saveSharedProfiles,
48
+ subscribeSharedProfiles: () => subscribeSharedProfiles,
48
49
  useProfileColorPreview: () => useProfileColorPreview,
49
50
  useProfileDisplayColor: () => useProfileDisplayColor,
51
+ useProfileSwatchRenderer: () => useProfileSwatchRenderer,
50
52
  useSharedProfilesSync: () => useSharedProfilesSync
51
53
  });
52
54
  module.exports = __toCommonJS(index_exports);
@@ -54,23 +56,64 @@ module.exports = __toCommonJS(index_exports);
54
56
  // src/PickerMenu.tsx
55
57
  var import_auto_paper2 = require("@rific/auto-paper");
56
58
  var import_feedback_press = require("@rific/feedback-press");
57
- var import_hud = require("@tastic/hud");
58
- var import_react = require("react");
59
- var import_react_native2 = require("react-native");
59
+ var import_chrome = require("@tastic/hud/chrome");
60
+ var import_react2 = require("react");
61
+ var import_react_native3 = require("react-native");
60
62
  var import_react_native_paper2 = require("react-native-paper");
61
63
 
62
64
  // src/IdentityChip.tsx
63
65
  var import_auto_paper = require("@rific/auto-paper");
64
- var import_react_native = require("react-native");
66
+ var import_react_native2 = require("react-native");
65
67
  var import_react_native_paper = require("react-native-paper");
68
+
69
+ // src/ProfileColorContext.tsx
70
+ var import_react = require("react");
66
71
  var import_jsx_runtime = require("react/jsx-runtime");
72
+ var ProfileColorContext = (0, import_react.createContext)({});
73
+ function ProfileColorProvider({ preview, renderSwatch, children }) {
74
+ const value = (0, import_react.useMemo)(() => ({ preview, renderSwatch }), [preview, renderSwatch]);
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProfileColorContext.Provider, { value, children });
76
+ }
77
+ function useProfileColorPreview() {
78
+ return (0, import_react.useContext)(ProfileColorContext).preview;
79
+ }
80
+ function useProfileSwatchRenderer() {
81
+ return (0, import_react.useContext)(ProfileColorContext).renderSwatch;
82
+ }
83
+ function useProfileDisplayColor(hex) {
84
+ const preview = useProfileColorPreview();
85
+ return preview ? preview(hex) : hex;
86
+ }
87
+
88
+ // src/SwatchLayer.tsx
89
+ var import_react_native = require("react-native");
90
+ var import_jsx_runtime2 = require("react/jsx-runtime");
91
+ function SwatchLayer({ render, color, size }) {
92
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native.View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children: render(color, size) });
93
+ }
94
+ var styles = import_react_native.StyleSheet.create({
95
+ layer: {
96
+ left: 0,
97
+ overflow: "hidden",
98
+ pointerEvents: "none",
99
+ position: "absolute",
100
+ top: 0
101
+ }
102
+ });
103
+
104
+ // src/IdentityChip.tsx
105
+ var import_jsx_runtime3 = require("react/jsx-runtime");
67
106
  var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
68
107
  function IdentityChip({ color, tag, icon, size, filled = true }) {
108
+ const renderSwatch = useProfileSwatchRenderer();
69
109
  const contrastColor = (0, import_auto_paper.getContrastColor)(color);
70
110
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
71
- 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 }) });
111
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native2.View, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled && !renderSwatch && { backgroundColor: color }], children: [
112
+ filled && renderSwatch && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SwatchLayer, { render: renderSwatch, color, size }),
113
+ tag ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Text, { style: [styles2.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native_paper.Icon, { source: icon, size: size * 0.6, color: contrastColor })
114
+ ] });
72
115
  }
73
- var styles = import_react_native.StyleSheet.create({
116
+ var styles2 = import_react_native2.StyleSheet.create({
74
117
  chip: {
75
118
  alignItems: "center",
76
119
  justifyContent: "center"
@@ -82,7 +125,7 @@ var styles = import_react_native.StyleSheet.create({
82
125
  });
83
126
 
84
127
  // src/PickerMenu.tsx
85
- var import_jsx_runtime2 = require("react/jsx-runtime");
128
+ var import_jsx_runtime4 = require("react/jsx-runtime");
86
129
  var TRIGGER_HEIGHT = 28;
87
130
  var POPOVER_WIDTH = 220;
88
131
  var ROW_HEIGHT = 36;
@@ -93,6 +136,8 @@ var CHIP_SIZE = 32;
93
136
  var CHIP_TRIGGER_PADDING = 6;
94
137
  var SWATCH_SIZE = 28;
95
138
  var SWATCH_MIN_GAP = 6;
139
+ var SWATCH_RING_WIDTH = 2;
140
+ var COLOR_ROW_SWATCH_SIZE = 22;
96
141
  var COLOR_ROW_CAPTIONED_HEIGHT = 48;
97
142
  var COLOR_LABEL_LINE = 18;
98
143
  var COLOR_CAPTION_LINE = 16;
@@ -118,14 +163,14 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
118
163
  const menuBg = dark ? "#000000" : "#FFFFFF";
119
164
  const fg = dark ? "#FFFFFF" : "#000000";
120
165
  const open = host.openId === id;
121
- const auto = (0, import_hud.useAutoAlign)(open, contentSize.width, contentSize.height, rotation);
166
+ const auto = (0, import_chrome.useAutoAlign)(open, contentSize.width, contentSize.height, rotation);
122
167
  const placement = alignOverride ?? auto;
123
168
  const { maxHeight, measured, triggerRef, verticalAlign } = placement;
124
169
  const align = forcedAlign ?? placement.align;
125
170
  const chipTrigger = !!chip;
126
- const [triggerWidth, setTriggerWidth] = (0, import_react.useState)(0);
171
+ const [triggerWidth, setTriggerWidth] = (0, import_react2.useState)(0);
127
172
  const handleAnchorLayout = chipTrigger ? (e) => setTriggerWidth(e.nativeEvent.layout.width) : void 0;
128
- const [colorPage, setColorPage] = (0, import_react.useState)(false);
173
+ const [colorPage, setColorPage] = (0, import_react2.useState)(false);
129
174
  const showColorPage = !!colorSection && colorPage;
130
175
  const handleTriggerPress = () => {
131
176
  setColorPage(false);
@@ -133,17 +178,17 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
133
178
  };
134
179
  const triggerLabel = label.toUpperCase();
135
180
  const showFooter = !!footer && !showColorPage;
136
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.anchor, open && styles2.anchorOpen], onLayout: handleAnchorLayout, children: [
137
- /* @__PURE__ */ (0, import_jsx_runtime2.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_runtime2.jsxs)(import_react_native2.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles2.chipTriggerInner : styles2.triggerInner, children: [
138
- chip && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
139
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
140
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
181
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [styles3.anchor, open && styles3.anchorOpen], onLayout: handleAnchorLayout, children: [
182
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles3.trigger, chipTrigger && styles3.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles3.chipTriggerInner : styles3.triggerInner, children: [
183
+ chip && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
141
186
  ] }) }),
142
- /* @__PURE__ */ (0, import_jsx_runtime2.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_runtime2.jsxs)(
143
- import_react_native2.View,
187
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_chrome.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.jsxs)(
188
+ import_react_native3.View,
144
189
  {
145
190
  style: [
146
- styles2.menu,
191
+ styles3.menu,
147
192
  {
148
193
  backgroundColor: menuBg,
149
194
  borderColor: color,
@@ -152,12 +197,12 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
152
197
  }
153
198
  ],
154
199
  children: [
155
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: [styles2.menuContent, showFooter && styles2.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColorPage, { section: colorSection, homeColor, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
156
- colorSection && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
200
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_auto_paper2.FadingScrollView, { contentContainerStyle: [styles3.menuContent, showFooter && styles3.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorPage, { section: colorSection, homeColor, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
201
+ colorSection && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
157
202
  children
158
203
  ] }) }, showColorPage ? "color" : "list"),
159
- showFooter && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.menuFooter, children: [
160
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: color }] }),
204
+ showFooter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.menuFooter, children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.divider, { backgroundColor: color }] }),
161
206
  footer
162
207
  ] })
163
208
  ]
@@ -167,35 +212,37 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
167
212
  }
168
213
  function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
169
214
  const textColor = selected ? (0, import_auto_paper2.getContrastColor)(color) : fg;
170
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
171
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: icon, size: 13, color: textColor }) }),
172
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
215
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
216
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: icon, size: 13, color: textColor }) }),
217
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
173
218
  ] }) });
174
219
  }
175
220
  function ColorRow({ section, fg, dividerColor, onPress }) {
176
221
  const { value, previewValue, label = "Color", caption } = section;
222
+ const renderSwatch = useProfileSwatchRenderer();
177
223
  const drawn = previewValue ? previewValue(value) : value;
178
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: pickerMenuStyles.rowInner, children: [
180
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.colorRowSwatch, { backgroundColor: drawn }] }),
181
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.colorRowText, children: [
182
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
183
- caption ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
224
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
225
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: pickerMenuStyles.rowInner, children: [
226
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.colorRowSwatch, !renderSwatch && { backgroundColor: drawn }], children: renderSwatch && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { render: renderSwatch, color: drawn, size: COLOR_ROW_SWATCH_SIZE }) }),
227
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.colorRowText, children: [
228
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
229
+ caption ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
184
230
  ] }),
185
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
231
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-right", size: 18, color: fg })
186
232
  ] }) }),
187
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.divider, { backgroundColor: dividerColor }] })
233
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.divider, { backgroundColor: dividerColor }] })
188
234
  ] });
189
235
  }
190
236
  function ColorPage({ section, homeColor, fg, onBack }) {
191
237
  const { value, onChange, previewValue, swatches = import_auto_paper2.defaultColors, label = "Color", takenValue, allowSwapTaken } = section;
238
+ const renderSwatch = useProfileSwatchRenderer();
192
239
  const displayFor = previewValue ?? ((hex) => hex);
193
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
194
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { onPress: onBack, style: pickerMenuStyles.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: pickerMenuStyles.rowInner, children: [
195
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "chevron-left", size: 20, color: fg }) }),
196
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { style: [styles2.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
240
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { onPress: onBack, style: pickerMenuStyles.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: pickerMenuStyles.rowInner, children: [
242
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.iconSlot, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "chevron-left", size: 20, color: fg }) }),
243
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Text, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
197
244
  ] }) }),
198
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.swatches, children: swatches.map((swatch) => {
245
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: styles3.swatches, children: swatches.map((swatch) => {
199
246
  const selected = swatch.value.toLowerCase() === value.toLowerCase();
200
247
  const isHomeColor = !!homeColor && swatch.value.toLowerCase() === homeColor.toLowerCase();
201
248
  const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
@@ -203,11 +250,15 @@ function ColorPage({ section, homeColor, fg, onBack }) {
203
250
  const swatchDisplay = displayFor(swatch.value);
204
251
  const contrast = (0, import_auto_paper2.getContrastColor)(swatchDisplay);
205
252
  const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
206
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles2.swatch, { backgroundColor: swatchDisplay }, selected && styles2.swatchSelected, taken && !swappable && styles2.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles2.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, {}) }, swatch.value);
253
+ const mark = selected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper2.Icon, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles3.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, {});
254
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_feedback_press.TouchableRipple, { disabled: taken && !swappable, onPress: () => onChange(swatch.value), borderless: true, style: [styles3.swatch, !renderSwatch && { backgroundColor: swatchDisplay }, selected && styles3.swatchSelected, taken && !swappable && styles3.swatchTaken], accessibilityRole: "button", accessibilityLabel, accessibilityState: { selected, disabled: taken && !swappable }, children: renderSwatch ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native3.View, { style: styles3.swatchFace, children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SwatchLayer, { render: renderSwatch, color: swatchDisplay, size: selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE }),
256
+ mark
257
+ ] }) : mark }, swatch.value);
207
258
  }) })
208
259
  ] });
209
260
  }
210
- var pickerMenuStyles = import_react_native2.StyleSheet.create({
261
+ var pickerMenuStyles = import_react_native3.StyleSheet.create({
211
262
  row: {
212
263
  borderRadius: 8,
213
264
  height: ROW_HEIGHT
@@ -227,7 +278,7 @@ var pickerMenuStyles = import_react_native2.StyleSheet.create({
227
278
  fontSize: 14
228
279
  }
229
280
  });
230
- var styles2 = import_react_native2.StyleSheet.create({
281
+ var styles3 = import_react_native3.StyleSheet.create({
231
282
  anchor: {
232
283
  position: "relative"
233
284
  },
@@ -263,9 +314,9 @@ var styles2 = import_react_native2.StyleSheet.create({
263
314
  },
264
315
  // The color in effect, in the same 22pt slot as every row's chip.
265
316
  colorRowSwatch: {
266
- borderRadius: 11,
267
- height: 22,
268
- width: 22
317
+ borderRadius: COLOR_ROW_SWATCH_SIZE / 2,
318
+ height: COLOR_ROW_SWATCH_SIZE,
319
+ width: COLOR_ROW_SWATCH_SIZE
269
320
  },
270
321
  colorRowText: {
271
322
  flex: 1
@@ -325,9 +376,19 @@ var styles2 = import_react_native2.StyleSheet.create({
325
376
  justifyContent: "center",
326
377
  width: SWATCH_SIZE
327
378
  },
379
+ // A drawn swatch (see ColorPage) and its mark: the whole circle inside the selected one's ring.
380
+ swatchFace: {
381
+ alignItems: "center",
382
+ bottom: 0,
383
+ justifyContent: "center",
384
+ left: 0,
385
+ position: "absolute",
386
+ right: 0,
387
+ top: 0
388
+ },
328
389
  swatchSelected: {
329
390
  borderColor: "#ffffff",
330
- borderWidth: 2
391
+ borderWidth: SWATCH_RING_WIDTH
331
392
  },
332
393
  swatchTaken: {
333
394
  opacity: 0.35
@@ -359,7 +420,7 @@ var styles2 = import_react_native2.StyleSheet.create({
359
420
  });
360
421
 
361
422
  // src/OptionPicker.tsx
362
- var import_jsx_runtime3 = require("react/jsx-runtime");
423
+ var import_jsx_runtime5 = require("react/jsx-runtime");
363
424
  function getOptionPickerContentSize(optionCount, colorSection) {
364
425
  return pickerContentSize(optionCount, colorSection);
365
426
  }
@@ -370,54 +431,40 @@ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, r
370
431
  onChange(option.value);
371
432
  host.close();
372
433
  };
373
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
374
- }
375
-
376
- // src/ProfileColorContext.tsx
377
- var import_react2 = require("react");
378
- var import_jsx_runtime4 = require("react/jsx-runtime");
379
- var ProfileColorContext = (0, import_react2.createContext)(void 0);
380
- function ProfileColorProvider({ preview, children }) {
381
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ProfileColorContext.Provider, { value: preview, children });
382
- }
383
- function useProfileColorPreview() {
384
- return (0, import_react2.useContext)(ProfileColorContext);
385
- }
386
- function useProfileDisplayColor(hex) {
387
- const preview = useProfileColorPreview();
388
- return preview ? preview(hex) : hex;
434
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PickerMenu, { id, host, color, dark, label: selected?.label ?? "", chip: { icon }, colorSection, contentSize: getOptionPickerContentSize(options.length, colorSection), align, alignOverride, rotation, triggerStyle, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IconRow, { icon: option.icon ?? rowIcon, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
389
435
  }
390
436
 
391
437
  // src/ProfileChip.tsx
392
- var import_jsx_runtime5 = require("react/jsx-runtime");
438
+ var import_jsx_runtime6 = require("react/jsx-runtime");
393
439
  function ProfileChip({ profile, size = 22, filled = true }) {
394
440
  const color = useProfileDisplayColor(profile.color);
395
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
441
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
396
442
  }
397
443
 
398
444
  // src/ProfilePicker.tsx
399
445
  var import_auto_paper3 = require("@rific/auto-paper");
400
446
  var import_feedback_press2 = require("@rific/feedback-press");
401
447
  var import_react3 = require("react");
402
- var import_react_native3 = require("react-native");
448
+ var import_react_native4 = require("react-native");
403
449
  var import_react_native_paper3 = require("react-native-paper");
404
- var import_jsx_runtime6 = require("react/jsx-runtime");
450
+ var import_jsx_runtime7 = require("react/jsx-runtime");
405
451
  function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
406
452
  return pickerContentSize(profileCount + 1 + (hasManageRow ? 1 : 0), colorSection);
407
453
  }
408
- function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
454
+ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, takenIds, color, dark, align, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
409
455
  const id = `${idPrefix}-profile`;
410
456
  const colorPreview = useProfileColorPreview();
411
457
  const fg = dark ? "#FFFFFF" : "#000000";
412
458
  const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
413
459
  const sortedProfiles = (0, import_react3.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
414
460
  const contentSize = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
461
+ const isTaken = (profileId) => profileId === takenId || profileId !== selectedId && !!takenIds?.includes(profileId);
415
462
  const handleSelectGuest = () => {
416
463
  onSelect(null);
417
464
  host.close();
418
465
  };
419
466
  const handleSelectExisting = (profile) => {
420
- if (profile.id === takenId) return;
467
+ if (isTaken(profile.id)) return;
421
468
  onSelect(profile);
422
469
  host.close();
423
470
  };
@@ -425,19 +472,19 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
425
472
  host.close();
426
473
  onManage?.();
427
474
  };
428
- const manageFooter = onManage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press2.TouchableRipple, { onPress: handleManage, style: pickerMenuStyles.row, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: styles3.manageRowInner, children: [
429
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper3.Icon, { source: "account-cog-outline", size: 16, color }),
430
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper3.Text, { style: [styles3.manageLabel, { color: fg }], children: "Manage" })
475
+ const manageFooter = onManage ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press2.TouchableRipple, { onPress: handleManage, style: pickerMenuStyles.row, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.manageRowInner, children: [
476
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Icon, { source: "account-cog-outline", size: 16, color }),
477
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper3.Text, { style: [styles4.manageLabel, { color: fg }], children: "Manage" })
431
478
  ] }) }) : void 0;
432
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(PickerMenu, { id, host, color, dark, label: selectedProfile?.name ?? guestLabel, chip: colorSection ? { tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon } : void 0, colorSection, homeColor: selectedProfile?.color, contentSize, align, alignOverride, rotation, triggerStyle, footer: manageFooter, children: [
433
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(IconRow, { icon: nullIcon, label: nullLabel, selected: selectedId === null, color, fg, onPress: handleSelectGuest }),
479
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(PickerMenu, { id, host, color, dark, label: selectedProfile?.name ?? guestLabel, chip: colorSection ? { tag: selectedProfile?.tag, icon: selectedProfile ? "account" : nullIcon } : void 0, colorSection, homeColor: selectedProfile?.color, contentSize, align, alignOverride, rotation, triggerStyle, footer: manageFooter, children: [
480
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconRow, { icon: nullIcon, label: nullLabel, selected: selectedId === null, color, fg, onPress: handleSelectGuest }),
434
481
  sortedProfiles.map((profile) => {
435
- const taken = profile.id === takenId;
482
+ const taken = isTaken(profile.id);
436
483
  const selected = selectedId === profile.id;
437
484
  const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
438
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_feedback_press2.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [pickerMenuStyles.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native3.View, { style: [pickerMenuStyles.rowInner, taken && pickerMenuStyles.rowDisabled], children: [
439
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProfileChip, { profile, filled: !selected }),
440
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
485
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press2.TouchableRipple, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [pickerMenuStyles.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: [pickerMenuStyles.rowInner, taken && pickerMenuStyles.rowDisabled], children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ProfileChip, { profile, filled: !selected }),
487
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
441
488
  import_react_native_paper3.Text,
442
489
  {
443
490
  style: [
@@ -454,7 +501,7 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
454
501
  })
455
502
  ] });
456
503
  }
457
- var styles3 = import_react_native3.StyleSheet.create({
504
+ var styles4 = import_react_native4.StyleSheet.create({
458
505
  manageLabel: {
459
506
  fontSize: 13,
460
507
  fontWeight: "700",
@@ -476,9 +523,9 @@ var import_auto_paper4 = require("@rific/auto-paper");
476
523
  var import_feedback_press3 = require("@rific/feedback-press");
477
524
  var import_focus_chain = require("@rific/focus-chain");
478
525
  var import_toaster = require("@rific/toaster");
479
- var import_hud2 = require("@tastic/hud");
526
+ var import_chrome2 = require("@tastic/hud/chrome");
480
527
  var import_react4 = require("react");
481
- var import_react_native4 = require("react-native");
528
+ var import_react_native5 = require("react-native");
482
529
  var import_react_native_paper4 = require("react-native-paper");
483
530
  var import_react_native_safe_area_context = require("react-native-safe-area-context");
484
531
 
@@ -515,7 +562,7 @@ function isValidProfile(value) {
515
562
  }
516
563
 
517
564
  // src/ProfilesManager.tsx
518
- var import_jsx_runtime7 = require("react/jsx-runtime");
565
+ var import_jsx_runtime8 = require("react/jsx-runtime");
519
566
  var CHIP_SIZE2 = 40;
520
567
  function deriveTag(name) {
521
568
  const trimmed = name.trim();
@@ -527,7 +574,7 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
527
574
  const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
528
575
  const insets = (0, import_react_native_safe_area_context.useSafeAreaInsets)();
529
576
  const { error: showErrorToast } = (0, import_toaster.useToast)();
530
- const host = (0, import_hud2.usePopoverHost)();
577
+ const host = (0, import_chrome2.usePopoverHost)();
531
578
  const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
532
579
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
533
580
  const [editingId, setEditingId] = (0, import_react4.useState)(null);
@@ -592,38 +639,38 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
592
639
  }, [confirmDeleteId, editingId, onDelete]);
593
640
  const sortedProfiles = (0, import_react4.useMemo)(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
594
641
  const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
595
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
596
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: [styles4.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
642
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
643
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: [styles5.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
597
644
  headerLeft,
598
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles4.title, { color: fg }], children: "Profiles" })
645
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: titleVariant, style: [styles5.title, { color: fg }], children: "Profiles" })
599
646
  ] }),
600
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native4.KeyboardAvoidingView, { behavior: import_react_native4.Platform.OS === "ios" ? "padding" : void 0, style: styles4.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_auto_paper4.FadingScrollView, { style: styles4.scrollView, contentContainerStyle: [styles4.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
647
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.KeyboardAvoidingView, { behavior: import_react_native5.Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper4.FadingScrollView, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
601
648
  if (row.kind === "new") {
602
649
  if (editingId !== "new") {
603
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit("new"), style: styles4.row, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.rowInner, children: [
604
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native4.View, { style: [styles4.rowChip, styles4.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Icon, { source: "plus", size: 20, color: fg }) }),
605
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
650
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit("new"), style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.rowInner, children: [
651
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { style: [styles5.rowChip, styles5.newChip, { borderColor: fg }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Icon, { source: "plus", size: 20, color: fg }) }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
606
653
  ] }) }, "new");
607
654
  }
608
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
655
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
609
656
  }
610
657
  const { profile } = row;
611
658
  if (editingId === profile.id) {
612
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
659
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
613
660
  }
614
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit(profile), style: styles4.row, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.rowInner, children: [
615
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
616
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: [styles4.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
661
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: () => startEdit(profile), style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.rowInner, children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ProfileChip, { profile, size: CHIP_SIZE2 }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Text, { variant: "bodyLarge", style: [styles5.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
617
664
  ] }) }, profile.id);
618
665
  }) }) }),
619
- /* @__PURE__ */ (0, import_jsx_runtime7.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) })
666
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.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) })
620
667
  ] });
621
668
  }
622
669
  function AlignedColorPicker({ useAlign, ...props }) {
623
- const { width: windowWidth } = (0, import_react_native4.useWindowDimensions)();
624
- const { width, height } = (0, import_hud2.getInlineColorPickerContentSize)(import_auto_paper4.defaultColors.length, windowWidth);
670
+ const { width: windowWidth } = (0, import_react_native5.useWindowDimensions)();
671
+ const { width, height } = (0, import_chrome2.getInlineColorPickerContentSize)(import_auto_paper4.defaultColors.length, windowWidth);
625
672
  const alignOverride = useAlign(props.host.openId === props.id, width, height);
626
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_hud2.InlineColorPicker, { ...props, alignOverride });
673
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.InlineColorPicker, { ...props, alignOverride });
627
674
  }
628
675
  function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
629
676
  const register = (0, import_focus_chain.useFocusChain)();
@@ -636,14 +683,14 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
636
683
  (0, import_react4.useEffect)(() => {
637
684
  if (host.openId === "color") tagFocusRef.current();
638
685
  }, [host.openId]);
639
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native4.View, { style: styles4.editRow, children: [
640
- useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_hud2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
641
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles4.hiddenTagInput, pointerEvents: "none" }),
642
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.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: styles4.nameInput }),
643
- onDelete && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_feedback_press3.TouchableRipple, { onPress: onDelete, style: styles4.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper4.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
686
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native5.View, { style: styles5.editRow, children: [
687
+ useColorPickerAlign ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_chrome2.InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
689
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.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: styles5.nameInput }),
690
+ onDelete && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press3.TouchableRipple, { onPress: onDelete, style: styles5.iconButton, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native_paper4.Icon, { source: "trash-can-outline", size: 18, color: fgMuted }) })
644
691
  ] });
645
692
  }
646
- var styles4 = import_react_native4.StyleSheet.create({
693
+ var styles5 = import_react_native5.StyleSheet.create({
647
694
  content: {
648
695
  gap: 4,
649
696
  paddingHorizontal: 20
@@ -725,8 +772,8 @@ var styles4 = import_react_native4.StyleSheet.create({
725
772
  var import_auto_paper5 = require("@rific/auto-paper");
726
773
  var import_feedback_press4 = require("@rific/feedback-press");
727
774
  var import_react5 = require("react");
728
- var import_react_native5 = require("react-native");
729
- var import_jsx_runtime8 = require("react/jsx-runtime");
775
+ var import_react_native6 = require("react-native");
776
+ var import_jsx_runtime9 = require("react/jsx-runtime");
730
777
  function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
731
778
  const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
732
779
  const managerRef = (0, import_react5.useRef)(null);
@@ -736,9 +783,9 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
736
783
  managerRef.current?.commitPendingEdit();
737
784
  onBack();
738
785
  };
739
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native5.View, { style: [styles5.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_feedback_press4.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
786
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native6.View, { style: [styles6.container, { backgroundColor: bg }], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_feedback_press4.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
740
787
  }
741
- var styles5 = import_react_native5.StyleSheet.create({
788
+ var styles6 = import_react_native6.StyleSheet.create({
742
789
  container: {
743
790
  flex: 1
744
791
  }
@@ -849,6 +896,7 @@ function createProfileRecord(input) {
849
896
  }
850
897
 
851
898
  // src/sharedProfileStore.ts
899
+ var import_react_native7 = require("react-native");
852
900
  var native = null;
853
901
  try {
854
902
  const { requireOptionalNativeModule } = require("expo-modules-core");
@@ -857,15 +905,42 @@ try {
857
905
  native = null;
858
906
  }
859
907
  var PROFILES_KEY = "sharedProfiles";
860
- var isSharedProfileStoreAvailable = native !== null;
861
- async function readSharedProfiles(groupId) {
862
- if (!native) return [];
863
- let json = null;
908
+ var webKey = (groupId, key) => `${groupId}:${key}`;
909
+ var createWebStore = () => {
910
+ if (import_react_native7.Platform.OS !== "web") return null;
864
911
  try {
865
- json = await native.getSharedJSON(groupId, PROFILES_KEY);
912
+ const storage = globalThis.localStorage;
913
+ if (!storage) return null;
914
+ const probe = webKey("@tastic/profile", "probe");
915
+ storage.setItem(probe, "1");
916
+ storage.removeItem(probe);
917
+ let lastWriteFailed = false;
918
+ return {
919
+ storage,
920
+ store: {
921
+ getSharedJSON: async (groupId, key) => {
922
+ if (lastWriteFailed) throw new Error("The last write to the shared roster failed");
923
+ return storage.getItem(webKey(groupId, key));
924
+ },
925
+ setSharedJSON: async (groupId, key, value) => {
926
+ try {
927
+ storage.setItem(webKey(groupId, key), value);
928
+ lastWriteFailed = false;
929
+ } catch (error) {
930
+ lastWriteFailed = true;
931
+ throw error;
932
+ }
933
+ }
934
+ }
935
+ };
866
936
  } catch {
867
937
  return null;
868
938
  }
939
+ };
940
+ var web = native ? null : createWebStore();
941
+ if (web) native = web.store;
942
+ var isSharedProfileStoreAvailable = native !== null;
943
+ var parseRoster = (json) => {
869
944
  if (!json) return [];
870
945
  try {
871
946
  const parsed = JSON.parse(json);
@@ -875,6 +950,16 @@ async function readSharedProfiles(groupId) {
875
950
  } catch {
876
951
  return null;
877
952
  }
953
+ };
954
+ async function readSharedProfiles(groupId) {
955
+ if (!native) return [];
956
+ let json = null;
957
+ try {
958
+ json = await native.getSharedJSON(groupId, PROFILES_KEY);
959
+ } catch {
960
+ return null;
961
+ }
962
+ return parseRoster(json);
878
963
  }
879
964
  async function loadSharedProfiles(groupId) {
880
965
  return await readSharedProfiles(groupId) ?? [];
@@ -886,6 +971,19 @@ async function saveSharedProfiles(groupId, profiles) {
886
971
  } catch {
887
972
  }
888
973
  }
974
+ function subscribeSharedProfiles(groupId, listener) {
975
+ if (!web || typeof window === "undefined" || typeof window.addEventListener !== "function") return () => {
976
+ };
977
+ const storageKey = webKey(groupId, PROFILES_KEY);
978
+ const storage = web.storage;
979
+ const onStorage = (event) => {
980
+ if (event.key !== storageKey || event.storageArea !== storage || !event.newValue) return;
981
+ const profiles = parseRoster(event.newValue);
982
+ if (profiles !== null) listener(profiles);
983
+ };
984
+ window.addEventListener("storage", onStorage);
985
+ return () => window.removeEventListener("storage", onStorage);
986
+ }
889
987
 
890
988
  // src/redux/resolveInitialProfiles.ts
891
989
  async function resolveInitialProfiles(groupId, localFallback) {
@@ -952,7 +1050,7 @@ function createSeatColorsSlice(namespace, defaultState, mountKey = "seatColors")
952
1050
 
953
1051
  // src/redux/useSharedProfilesSync.ts
954
1052
  var import_react6 = require("react");
955
- var import_react_native6 = require("react-native");
1053
+ var import_react_native8 = require("react-native");
956
1054
  function useSharedProfilesSync({ groupId, onRemoteChange }) {
957
1055
  const pendingWriteRef = (0, import_react6.useRef)(false);
958
1056
  const onRemoteChangeRef = (0, import_react6.useRef)(onRemoteChange);
@@ -962,15 +1060,19 @@ function useSharedProfilesSync({ groupId, onRemoteChange }) {
962
1060
  (0, import_react6.useEffect)(() => {
963
1061
  if (!isSharedProfileStoreAvailable) return;
964
1062
  let cancelled = false;
965
- const sub = import_react_native6.AppState.addEventListener("change", (next) => {
1063
+ const sub = import_react_native8.AppState.addEventListener("change", (next) => {
966
1064
  if (next !== "active" || pendingWriteRef.current) return;
967
1065
  readSharedProfiles(groupId).then((shared) => {
968
1066
  if (!cancelled && shared !== null) onRemoteChangeRef.current(shared);
969
1067
  });
970
1068
  });
1069
+ const unsubscribe = subscribeSharedProfiles(groupId, (shared) => {
1070
+ if (!cancelled && !pendingWriteRef.current) onRemoteChangeRef.current(shared);
1071
+ });
971
1072
  return () => {
972
1073
  cancelled = true;
973
1074
  sub.remove();
1075
+ unsubscribe();
974
1076
  };
975
1077
  }, [groupId]);
976
1078
  const syncToShared = (0, import_react6.useCallback)(
@@ -1020,7 +1122,9 @@ function avoidSeatColorClash(p1Color, p2Color, defaults) {
1020
1122
  readSharedProfiles,
1021
1123
  resolveInitialProfiles,
1022
1124
  saveSharedProfiles,
1125
+ subscribeSharedProfiles,
1023
1126
  useProfileColorPreview,
1024
1127
  useProfileDisplayColor,
1128
+ useProfileSwatchRenderer,
1025
1129
  useSharedProfilesSync
1026
1130
  });