@tastic/profile 0.15.0 → 0.16.1
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 +46 -11
- package/dist/index.d.mts +11 -5
- package/dist/index.d.ts +11 -5
- package/dist/index.js +220 -116
- package/dist/index.mjs +213 -111
- package/package.json +6 -6
- package/src/IdentityChip.tsx +8 -2
- package/src/OptionPicker.tsx +6 -2
- package/src/PickerMenu.tsx +46 -12
- package/src/ProfileColorContext.tsx +30 -8
- package/src/ProfilePicker.tsx +10 -4
- package/src/ProfilesManager.tsx +2 -1
- package/src/SwatchLayer.tsx +28 -0
- package/src/index.ts +2 -2
- package/src/redux/resolveInitialProfiles.ts +3 -2
- package/src/redux/useSharedProfilesSync.ts +12 -6
- package/src/sharedProfileStore.ts +87 -11
package/dist/index.mjs
CHANGED
|
@@ -8,23 +8,64 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
// src/PickerMenu.tsx
|
|
9
9
|
import { defaultColors, FadingScrollView, getContrastColor as getContrastColor2 } from "@rific/auto-paper";
|
|
10
10
|
import { TouchableRipple } from "@rific/feedback-press";
|
|
11
|
-
import { PopoverBody, useAutoAlign } from "@tastic/hud";
|
|
11
|
+
import { PopoverBody, useAutoAlign } from "@tastic/hud/chrome";
|
|
12
12
|
import { useState } from "react";
|
|
13
|
-
import { StyleSheet as
|
|
13
|
+
import { StyleSheet as StyleSheet3, View as View3 } from "react-native";
|
|
14
14
|
import { Icon as Icon2, Text as Text2 } from "react-native-paper";
|
|
15
15
|
|
|
16
16
|
// src/IdentityChip.tsx
|
|
17
17
|
import { getContrastColor } from "@rific/auto-paper";
|
|
18
|
-
import { StyleSheet, View } from "react-native";
|
|
18
|
+
import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
|
|
19
19
|
import { Icon, Text } from "react-native-paper";
|
|
20
|
+
|
|
21
|
+
// src/ProfileColorContext.tsx
|
|
22
|
+
import { createContext, useContext, useMemo } from "react";
|
|
20
23
|
import { jsx } from "react/jsx-runtime";
|
|
24
|
+
var ProfileColorContext = createContext({});
|
|
25
|
+
function ProfileColorProvider({ preview, renderSwatch, children }) {
|
|
26
|
+
const value = useMemo(() => ({ preview, renderSwatch }), [preview, renderSwatch]);
|
|
27
|
+
return /* @__PURE__ */ jsx(ProfileColorContext.Provider, { value, children });
|
|
28
|
+
}
|
|
29
|
+
function useProfileColorPreview() {
|
|
30
|
+
return useContext(ProfileColorContext).preview;
|
|
31
|
+
}
|
|
32
|
+
function useProfileSwatchRenderer() {
|
|
33
|
+
return useContext(ProfileColorContext).renderSwatch;
|
|
34
|
+
}
|
|
35
|
+
function useProfileDisplayColor(hex) {
|
|
36
|
+
const preview = useProfileColorPreview();
|
|
37
|
+
return preview ? preview(hex) : hex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/SwatchLayer.tsx
|
|
41
|
+
import { StyleSheet, View } from "react-native";
|
|
42
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
43
|
+
function SwatchLayer({ render, color, size }) {
|
|
44
|
+
return /* @__PURE__ */ jsx2(View, { style: [styles.layer, { borderRadius: size / 2, height: size, width: size }], children: render(color, size) });
|
|
45
|
+
}
|
|
46
|
+
var styles = StyleSheet.create({
|
|
47
|
+
layer: {
|
|
48
|
+
left: 0,
|
|
49
|
+
overflow: "hidden",
|
|
50
|
+
pointerEvents: "none",
|
|
51
|
+
position: "absolute",
|
|
52
|
+
top: 0
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// src/IdentityChip.tsx
|
|
57
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
21
58
|
var EMOJI_PATTERN = /\p{Extended_Pictographic}/u;
|
|
22
59
|
function IdentityChip({ color, tag, icon, size, filled = true }) {
|
|
60
|
+
const renderSwatch = useProfileSwatchRenderer();
|
|
23
61
|
const contrastColor = getContrastColor(color);
|
|
24
62
|
const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : tag && Array.from(tag).length >= 3 ? 0.32 : 0.4);
|
|
25
|
-
return /* @__PURE__ */
|
|
63
|
+
return /* @__PURE__ */ jsxs(View2, { style: [styles2.chip, { borderRadius: size / 2, height: size, width: size }, filled && !renderSwatch && { backgroundColor: color }], children: [
|
|
64
|
+
filled && renderSwatch && /* @__PURE__ */ jsx3(SwatchLayer, { render: renderSwatch, color, size }),
|
|
65
|
+
tag ? /* @__PURE__ */ jsx3(Text, { style: [styles2.tag, { color: contrastColor, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ jsx3(Icon, { source: icon, size: size * 0.6, color: contrastColor })
|
|
66
|
+
] });
|
|
26
67
|
}
|
|
27
|
-
var
|
|
68
|
+
var styles2 = StyleSheet2.create({
|
|
28
69
|
chip: {
|
|
29
70
|
alignItems: "center",
|
|
30
71
|
justifyContent: "center"
|
|
@@ -36,7 +77,7 @@ var styles = StyleSheet.create({
|
|
|
36
77
|
});
|
|
37
78
|
|
|
38
79
|
// src/PickerMenu.tsx
|
|
39
|
-
import { Fragment, jsx as
|
|
80
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
40
81
|
var TRIGGER_HEIGHT = 28;
|
|
41
82
|
var POPOVER_WIDTH = 220;
|
|
42
83
|
var ROW_HEIGHT = 36;
|
|
@@ -47,6 +88,8 @@ var CHIP_SIZE = 32;
|
|
|
47
88
|
var CHIP_TRIGGER_PADDING = 6;
|
|
48
89
|
var SWATCH_SIZE = 28;
|
|
49
90
|
var SWATCH_MIN_GAP = 6;
|
|
91
|
+
var SWATCH_RING_WIDTH = 2;
|
|
92
|
+
var COLOR_ROW_SWATCH_SIZE = 22;
|
|
50
93
|
var COLOR_ROW_CAPTIONED_HEIGHT = 48;
|
|
51
94
|
var COLOR_LABEL_LINE = 18;
|
|
52
95
|
var COLOR_CAPTION_LINE = 16;
|
|
@@ -87,17 +130,17 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
|
|
|
87
130
|
};
|
|
88
131
|
const triggerLabel = label.toUpperCase();
|
|
89
132
|
const showFooter = !!footer && !showColorPage;
|
|
90
|
-
return /* @__PURE__ */
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
chip && /* @__PURE__ */
|
|
93
|
-
/* @__PURE__ */
|
|
94
|
-
/* @__PURE__ */
|
|
133
|
+
return /* @__PURE__ */ jsxs2(View3, { style: [styles3.anchor, open && styles3.anchorOpen], onLayout: handleAnchorLayout, children: [
|
|
134
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: handleTriggerPress, borderless: chipTrigger, style: [styles3.trigger, chipTrigger && styles3.chipTrigger, triggerStyle], accessibilityRole: "button", accessibilityLabel: triggerLabel, children: /* @__PURE__ */ jsxs2(View3, { ref: triggerRef, collapsable: false, style: chipTrigger ? styles3.chipTriggerInner : styles3.triggerInner, children: [
|
|
135
|
+
chip && /* @__PURE__ */ jsx4(IdentityChip, { color, tag: chip.tag, icon: chip.icon, size: CHIP_SIZE }),
|
|
136
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles3.triggerLabel, { color: chipTrigger ? fg : color }], numberOfLines: 1, children: triggerLabel }),
|
|
137
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "menu-down", size: 16, color: chipTrigger ? fg : color })
|
|
95
138
|
] }) }),
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
|
|
139
|
+
/* @__PURE__ */ jsx4(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: MENU_BORDER_WIDTH, triggerSize: chipTrigger ? triggerWidth : TRIGGER_HEIGHT, children: /* @__PURE__ */ jsxs2(
|
|
140
|
+
View3,
|
|
98
141
|
{
|
|
99
142
|
style: [
|
|
100
|
-
|
|
143
|
+
styles3.menu,
|
|
101
144
|
{
|
|
102
145
|
backgroundColor: menuBg,
|
|
103
146
|
borderColor: color,
|
|
@@ -106,12 +149,12 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
|
|
|
106
149
|
}
|
|
107
150
|
],
|
|
108
151
|
children: [
|
|
109
|
-
/* @__PURE__ */
|
|
110
|
-
colorSection && /* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ jsx4(FadingScrollView, { contentContainerStyle: [styles3.menuContent, showFooter && styles3.menuContentAboveFooter], showsVerticalScrollIndicator: false, children: colorSection && colorPage ? /* @__PURE__ */ jsx4(ColorPage, { section: colorSection, homeColor, fg, onBack: () => setColorPage(false) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
153
|
+
colorSection && /* @__PURE__ */ jsx4(ColorRow, { section: colorSection, fg, dividerColor: color, onPress: () => setColorPage(true) }),
|
|
111
154
|
children
|
|
112
155
|
] }) }, showColorPage ? "color" : "list"),
|
|
113
|
-
showFooter && /* @__PURE__ */
|
|
114
|
-
/* @__PURE__ */
|
|
156
|
+
showFooter && /* @__PURE__ */ jsxs2(View3, { style: styles3.menuFooter, children: [
|
|
157
|
+
/* @__PURE__ */ jsx4(View3, { style: [styles3.divider, { backgroundColor: color }] }),
|
|
115
158
|
footer
|
|
116
159
|
] })
|
|
117
160
|
]
|
|
@@ -119,37 +162,39 @@ function PickerMenu({ id, host, color, dark, label, chip, colorSection, homeColo
|
|
|
119
162
|
) })
|
|
120
163
|
] });
|
|
121
164
|
}
|
|
122
|
-
function IconRow({ icon, label, selected, color, fg, disabled = false, onPress }) {
|
|
165
|
+
function IconRow({ icon, iconColor, label, selected, color, fg, disabled = false, onPress }) {
|
|
123
166
|
const textColor = selected ? getContrastColor2(color) : fg;
|
|
124
|
-
return /* @__PURE__ */
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
167
|
+
return /* @__PURE__ */ jsx4(TouchableRipple, { disabled, onPress, style: [pickerMenuStyles.row, selected && { backgroundColor: color }], children: /* @__PURE__ */ jsxs2(View3, { style: [pickerMenuStyles.rowInner, disabled && pickerMenuStyles.rowDisabled], children: [
|
|
168
|
+
/* @__PURE__ */ jsx4(View3, { style: styles3.iconSlot, children: /* @__PURE__ */ jsx4(Icon2, { source: icon, size: 13, color: selected ? textColor : iconColor ?? textColor }) }),
|
|
169
|
+
/* @__PURE__ */ jsx4(Text2, { style: [pickerMenuStyles.rowLabel, { color: textColor }], numberOfLines: 1, children: label })
|
|
127
170
|
] }) });
|
|
128
171
|
}
|
|
129
172
|
function ColorRow({ section, fg, dividerColor, onPress }) {
|
|
130
173
|
const { value, previewValue, label = "Color", caption } = section;
|
|
174
|
+
const renderSwatch = useProfileSwatchRenderer();
|
|
131
175
|
const drawn = previewValue ? previewValue(value) : value;
|
|
132
|
-
return /* @__PURE__ */
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
/* @__PURE__ */
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
caption ? /* @__PURE__ */
|
|
176
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
177
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress, style: [pickerMenuStyles.row, { height: colorRowHeight(caption) }], accessibilityRole: "button", accessibilityLabel: caption ? `${label}, ${caption}` : label, children: /* @__PURE__ */ jsxs2(View3, { style: pickerMenuStyles.rowInner, children: [
|
|
178
|
+
/* @__PURE__ */ jsx4(View3, { style: [styles3.colorRowSwatch, !renderSwatch && { backgroundColor: drawn }], children: renderSwatch && /* @__PURE__ */ jsx4(SwatchLayer, { render: renderSwatch, color: drawn, size: COLOR_ROW_SWATCH_SIZE }) }),
|
|
179
|
+
/* @__PURE__ */ jsxs2(View3, { style: styles3.colorRowText, children: [
|
|
180
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label }),
|
|
181
|
+
caption ? /* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowCaption, { color: fg }], numberOfLines: 1, children: caption }) : null
|
|
138
182
|
] }),
|
|
139
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ jsx4(Icon2, { source: "chevron-right", size: 18, color: fg })
|
|
140
184
|
] }) }),
|
|
141
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ jsx4(View3, { style: [styles3.divider, { backgroundColor: dividerColor }] })
|
|
142
186
|
] });
|
|
143
187
|
}
|
|
144
188
|
function ColorPage({ section, homeColor, fg, onBack }) {
|
|
145
189
|
const { value, onChange, previewValue, swatches = defaultColors, label = "Color", takenValue, allowSwapTaken } = section;
|
|
190
|
+
const renderSwatch = useProfileSwatchRenderer();
|
|
146
191
|
const displayFor = previewValue ?? ((hex) => hex);
|
|
147
|
-
return /* @__PURE__ */
|
|
148
|
-
/* @__PURE__ */
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
/* @__PURE__ */
|
|
192
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
193
|
+
/* @__PURE__ */ jsx4(TouchableRipple, { onPress: onBack, style: pickerMenuStyles.row, accessibilityRole: "button", accessibilityLabel: "Back", children: /* @__PURE__ */ jsxs2(View3, { style: pickerMenuStyles.rowInner, children: [
|
|
194
|
+
/* @__PURE__ */ jsx4(View3, { style: styles3.iconSlot, children: /* @__PURE__ */ jsx4(Icon2, { source: "chevron-left", size: 20, color: fg }) }),
|
|
195
|
+
/* @__PURE__ */ jsx4(Text2, { style: [styles3.colorRowLabel, { color: fg }], numberOfLines: 1, children: label })
|
|
151
196
|
] }) }),
|
|
152
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ jsx4(View3, { style: styles3.swatches, children: swatches.map((swatch) => {
|
|
153
198
|
const selected = swatch.value.toLowerCase() === value.toLowerCase();
|
|
154
199
|
const isHomeColor = !!homeColor && swatch.value.toLowerCase() === homeColor.toLowerCase();
|
|
155
200
|
const taken = !selected && !!takenValue && swatch.value.toLowerCase() === takenValue.toLowerCase();
|
|
@@ -157,11 +202,15 @@ function ColorPage({ section, homeColor, fg, onBack }) {
|
|
|
157
202
|
const swatchDisplay = displayFor(swatch.value);
|
|
158
203
|
const contrast = getContrastColor2(swatchDisplay);
|
|
159
204
|
const accessibilityLabel = swappable ? `${swatch.label}, swap` : taken ? `${swatch.label}, taken` : isHomeColor ? `${swatch.label}, profile color` : swatch.label;
|
|
160
|
-
|
|
205
|
+
const mark = selected ? /* @__PURE__ */ jsx4(Icon2, { source: "check", size: 16, color: contrast }) : swappable ? /* @__PURE__ */ jsx4(Icon2, { source: "swap-horizontal", size: 16, color: contrast }) : taken ? /* @__PURE__ */ jsx4(Icon2, { source: "close", size: 16, color: contrast }) : isHomeColor ? /* @__PURE__ */ jsx4(View3, { style: [styles3.homeDot, { backgroundColor: contrast }] }) : /* @__PURE__ */ jsx4(View3, {});
|
|
206
|
+
return /* @__PURE__ */ jsx4(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__ */ jsxs2(View3, { style: styles3.swatchFace, children: [
|
|
207
|
+
/* @__PURE__ */ jsx4(SwatchLayer, { render: renderSwatch, color: swatchDisplay, size: selected ? SWATCH_SIZE - 2 * SWATCH_RING_WIDTH : SWATCH_SIZE }),
|
|
208
|
+
mark
|
|
209
|
+
] }) : mark }, swatch.value);
|
|
161
210
|
}) })
|
|
162
211
|
] });
|
|
163
212
|
}
|
|
164
|
-
var pickerMenuStyles =
|
|
213
|
+
var pickerMenuStyles = StyleSheet3.create({
|
|
165
214
|
row: {
|
|
166
215
|
borderRadius: 8,
|
|
167
216
|
height: ROW_HEIGHT
|
|
@@ -181,7 +230,7 @@ var pickerMenuStyles = StyleSheet2.create({
|
|
|
181
230
|
fontSize: 14
|
|
182
231
|
}
|
|
183
232
|
});
|
|
184
|
-
var
|
|
233
|
+
var styles3 = StyleSheet3.create({
|
|
185
234
|
anchor: {
|
|
186
235
|
position: "relative"
|
|
187
236
|
},
|
|
@@ -217,9 +266,9 @@ var styles2 = StyleSheet2.create({
|
|
|
217
266
|
},
|
|
218
267
|
// The color in effect, in the same 22pt slot as every row's chip.
|
|
219
268
|
colorRowSwatch: {
|
|
220
|
-
borderRadius:
|
|
221
|
-
height:
|
|
222
|
-
width:
|
|
269
|
+
borderRadius: COLOR_ROW_SWATCH_SIZE / 2,
|
|
270
|
+
height: COLOR_ROW_SWATCH_SIZE,
|
|
271
|
+
width: COLOR_ROW_SWATCH_SIZE
|
|
223
272
|
},
|
|
224
273
|
colorRowText: {
|
|
225
274
|
flex: 1
|
|
@@ -279,9 +328,19 @@ var styles2 = StyleSheet2.create({
|
|
|
279
328
|
justifyContent: "center",
|
|
280
329
|
width: SWATCH_SIZE
|
|
281
330
|
},
|
|
331
|
+
// A drawn swatch (see ColorPage) and its mark: the whole circle inside the selected one's ring.
|
|
332
|
+
swatchFace: {
|
|
333
|
+
alignItems: "center",
|
|
334
|
+
bottom: 0,
|
|
335
|
+
justifyContent: "center",
|
|
336
|
+
left: 0,
|
|
337
|
+
position: "absolute",
|
|
338
|
+
right: 0,
|
|
339
|
+
top: 0
|
|
340
|
+
},
|
|
282
341
|
swatchSelected: {
|
|
283
342
|
borderColor: "#ffffff",
|
|
284
|
-
borderWidth:
|
|
343
|
+
borderWidth: SWATCH_RING_WIDTH
|
|
285
344
|
},
|
|
286
345
|
swatchTaken: {
|
|
287
346
|
opacity: 0.35
|
|
@@ -313,7 +372,7 @@ var styles2 = StyleSheet2.create({
|
|
|
313
372
|
});
|
|
314
373
|
|
|
315
374
|
// src/OptionPicker.tsx
|
|
316
|
-
import { jsx as
|
|
375
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
317
376
|
function getOptionPickerContentSize(optionCount, colorSection) {
|
|
318
377
|
return pickerContentSize(optionCount, colorSection);
|
|
319
378
|
}
|
|
@@ -324,54 +383,40 @@ function OptionPicker({ id, host, options, value, onChange, color, dark, icon, r
|
|
|
324
383
|
onChange(option.value);
|
|
325
384
|
host.close();
|
|
326
385
|
};
|
|
327
|
-
return /* @__PURE__ */
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// src/ProfileColorContext.tsx
|
|
331
|
-
import { createContext, useContext } from "react";
|
|
332
|
-
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
333
|
-
var ProfileColorContext = createContext(void 0);
|
|
334
|
-
function ProfileColorProvider({ preview, children }) {
|
|
335
|
-
return /* @__PURE__ */ jsx4(ProfileColorContext.Provider, { value: preview, children });
|
|
336
|
-
}
|
|
337
|
-
function useProfileColorPreview() {
|
|
338
|
-
return useContext(ProfileColorContext);
|
|
339
|
-
}
|
|
340
|
-
function useProfileDisplayColor(hex) {
|
|
341
|
-
const preview = useProfileColorPreview();
|
|
342
|
-
return preview ? preview(hex) : hex;
|
|
386
|
+
return /* @__PURE__ */ jsx5(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__ */ jsx5(IconRow, { icon: option.icon ?? rowIcon, iconColor: option.color, label: option.label, selected: option.value === value, color, fg, onPress: () => handleSelect(option) }, option.value)) });
|
|
343
387
|
}
|
|
344
388
|
|
|
345
389
|
// src/ProfileChip.tsx
|
|
346
|
-
import { jsx as
|
|
390
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
347
391
|
function ProfileChip({ profile, size = 22, filled = true }) {
|
|
348
392
|
const color = useProfileDisplayColor(profile.color);
|
|
349
|
-
return /* @__PURE__ */
|
|
393
|
+
return /* @__PURE__ */ jsx6(IdentityChip, { color, tag: profile.tag, icon: "account", size, filled });
|
|
350
394
|
}
|
|
351
395
|
|
|
352
396
|
// src/ProfilePicker.tsx
|
|
353
397
|
import { getContrastColor as getContrastColor3 } from "@rific/auto-paper";
|
|
354
398
|
import { TouchableRipple as TouchableRipple2 } from "@rific/feedback-press";
|
|
355
|
-
import { useMemo } from "react";
|
|
356
|
-
import { StyleSheet as
|
|
399
|
+
import { useMemo as useMemo2 } from "react";
|
|
400
|
+
import { StyleSheet as StyleSheet4, View as View4 } from "react-native";
|
|
357
401
|
import { Icon as Icon3, Text as Text3 } from "react-native-paper";
|
|
358
|
-
import { jsx as
|
|
402
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
359
403
|
function getProfilePickerContentSize(profileCount, hasManageRow = false, colorSection) {
|
|
360
404
|
return pickerContentSize(profileCount + 1 + (hasManageRow ? 1 : 0), colorSection);
|
|
361
405
|
}
|
|
362
|
-
function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, dark, align, alignOverride, rotation = 0, guestLabel, nullLabel = "Player", nullIcon = "account-off-outline", onSelect, onManage, colorSection, triggerStyle }) {
|
|
406
|
+
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 }) {
|
|
363
407
|
const id = `${idPrefix}-profile`;
|
|
364
408
|
const colorPreview = useProfileColorPreview();
|
|
365
409
|
const fg = dark ? "#FFFFFF" : "#000000";
|
|
366
410
|
const selectedProfile = profiles.find((p) => p.id === selectedId) ?? null;
|
|
367
|
-
const sortedProfiles =
|
|
411
|
+
const sortedProfiles = useMemo2(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
|
|
368
412
|
const contentSize = getProfilePickerContentSize(profiles.length, !!onManage, colorSection);
|
|
413
|
+
const isTaken = (profileId) => profileId === takenId || profileId !== selectedId && !!takenIds?.includes(profileId);
|
|
369
414
|
const handleSelectGuest = () => {
|
|
370
415
|
onSelect(null);
|
|
371
416
|
host.close();
|
|
372
417
|
};
|
|
373
418
|
const handleSelectExisting = (profile) => {
|
|
374
|
-
if (profile.id
|
|
419
|
+
if (isTaken(profile.id)) return;
|
|
375
420
|
onSelect(profile);
|
|
376
421
|
host.close();
|
|
377
422
|
};
|
|
@@ -379,19 +424,19 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
379
424
|
host.close();
|
|
380
425
|
onManage?.();
|
|
381
426
|
};
|
|
382
|
-
const manageFooter = onManage ? /* @__PURE__ */
|
|
383
|
-
/* @__PURE__ */
|
|
384
|
-
/* @__PURE__ */
|
|
427
|
+
const manageFooter = onManage ? /* @__PURE__ */ jsx7(TouchableRipple2, { onPress: handleManage, style: pickerMenuStyles.row, children: /* @__PURE__ */ jsxs3(View4, { style: styles4.manageRowInner, children: [
|
|
428
|
+
/* @__PURE__ */ jsx7(Icon3, { source: "account-cog-outline", size: 16, color }),
|
|
429
|
+
/* @__PURE__ */ jsx7(Text3, { style: [styles4.manageLabel, { color: fg }], children: "Manage" })
|
|
385
430
|
] }) }) : void 0;
|
|
386
|
-
return /* @__PURE__ */
|
|
387
|
-
/* @__PURE__ */
|
|
431
|
+
return /* @__PURE__ */ jsxs3(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: [
|
|
432
|
+
/* @__PURE__ */ jsx7(IconRow, { icon: nullIcon, label: nullLabel, selected: selectedId === null, color, fg, onPress: handleSelectGuest }),
|
|
388
433
|
sortedProfiles.map((profile) => {
|
|
389
|
-
const taken = profile.id
|
|
434
|
+
const taken = isTaken(profile.id);
|
|
390
435
|
const selected = selectedId === profile.id;
|
|
391
436
|
const rowColor = colorPreview ? colorPreview(profile.color) : profile.color;
|
|
392
|
-
return /* @__PURE__ */
|
|
393
|
-
/* @__PURE__ */
|
|
394
|
-
/* @__PURE__ */
|
|
437
|
+
return /* @__PURE__ */ jsx7(TouchableRipple2, { disabled: taken, onPress: () => handleSelectExisting(profile), style: [pickerMenuStyles.row, selected && { backgroundColor: rowColor }], children: /* @__PURE__ */ jsxs3(View4, { style: [pickerMenuStyles.rowInner, taken && pickerMenuStyles.rowDisabled], children: [
|
|
438
|
+
/* @__PURE__ */ jsx7(ProfileChip, { profile, filled: !selected }),
|
|
439
|
+
/* @__PURE__ */ jsx7(
|
|
395
440
|
Text3,
|
|
396
441
|
{
|
|
397
442
|
style: [
|
|
@@ -408,7 +453,7 @@ function ProfilePicker({ idPrefix, host, profiles, selectedId, takenId, color, d
|
|
|
408
453
|
})
|
|
409
454
|
] });
|
|
410
455
|
}
|
|
411
|
-
var
|
|
456
|
+
var styles4 = StyleSheet4.create({
|
|
412
457
|
manageLabel: {
|
|
413
458
|
fontSize: 13,
|
|
414
459
|
fontWeight: "700",
|
|
@@ -430,9 +475,9 @@ import { defaultColors as defaultColors2, FadingScrollView as FadingScrollView2,
|
|
|
430
475
|
import { TouchableRipple as TouchableRipple3 } from "@rific/feedback-press";
|
|
431
476
|
import { useFocusChain } from "@rific/focus-chain";
|
|
432
477
|
import { useToast } from "@rific/toaster";
|
|
433
|
-
import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud";
|
|
434
|
-
import { useCallback, useEffect, useImperativeHandle, useMemo as
|
|
435
|
-
import { KeyboardAvoidingView, Platform, StyleSheet as
|
|
478
|
+
import { ConfirmDialog, getInlineColorPickerContentSize, InlineColorPicker, usePopoverHost } from "@tastic/hud/chrome";
|
|
479
|
+
import { useCallback, useEffect, useImperativeHandle, useMemo as useMemo3, useRef, useState as useState2 } from "react";
|
|
480
|
+
import { KeyboardAvoidingView, Platform, StyleSheet as StyleSheet5, useWindowDimensions, View as View5 } from "react-native";
|
|
436
481
|
import { Icon as Icon4, Text as Text4, TextInput } from "react-native-paper";
|
|
437
482
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
438
483
|
|
|
@@ -469,7 +514,7 @@ function isValidProfile(value) {
|
|
|
469
514
|
}
|
|
470
515
|
|
|
471
516
|
// src/ProfilesManager.tsx
|
|
472
|
-
import { Fragment as Fragment2, jsx as
|
|
517
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
473
518
|
var CHIP_SIZE2 = 40;
|
|
474
519
|
function deriveTag(name) {
|
|
475
520
|
const trimmed = name.trim();
|
|
@@ -544,40 +589,40 @@ function ProfilesManager({ profiles, defaultColor, onCreate, onSave, onDelete, h
|
|
|
544
589
|
}
|
|
545
590
|
setConfirmDeleteId(null);
|
|
546
591
|
}, [confirmDeleteId, editingId, onDelete]);
|
|
547
|
-
const sortedProfiles =
|
|
592
|
+
const sortedProfiles = useMemo3(() => [...profiles].sort((a, b) => a.name.localeCompare(b.name)), [profiles]);
|
|
548
593
|
const rows = [...sortedProfiles.map((profile) => ({ kind: "profile", profile })), { kind: "new" }];
|
|
549
|
-
return /* @__PURE__ */
|
|
550
|
-
/* @__PURE__ */
|
|
594
|
+
return /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
595
|
+
/* @__PURE__ */ jsxs4(View5, { style: [styles5.header, { paddingTop: 8 + insets.top, paddingLeft: 8 + insets.left }], children: [
|
|
551
596
|
headerLeft,
|
|
552
|
-
/* @__PURE__ */
|
|
597
|
+
/* @__PURE__ */ jsx8(Text4, { variant: titleVariant, style: [styles5.title, { color: fg }], children: "Profiles" })
|
|
553
598
|
] }),
|
|
554
|
-
/* @__PURE__ */
|
|
599
|
+
/* @__PURE__ */ jsx8(KeyboardAvoidingView, { behavior: Platform.OS === "ios" ? "padding" : void 0, style: styles5.keyboardAvoiding, children: /* @__PURE__ */ jsx8(FadingScrollView2, { style: styles5.scrollView, contentContainerStyle: [styles5.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: rows.map((row) => {
|
|
555
600
|
if (row.kind === "new") {
|
|
556
601
|
if (editingId !== "new") {
|
|
557
|
-
return /* @__PURE__ */
|
|
558
|
-
/* @__PURE__ */
|
|
559
|
-
/* @__PURE__ */
|
|
602
|
+
return /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: () => startEdit("new"), style: styles5.row, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.rowInner, children: [
|
|
603
|
+
/* @__PURE__ */ jsx8(View5, { style: [styles5.rowChip, styles5.newChip, { borderColor: fg }], children: /* @__PURE__ */ jsx8(Icon4, { source: "plus", size: 20, color: fg }) }),
|
|
604
|
+
/* @__PURE__ */ jsx8(Text4, { variant: "bodyLarge", style: { color: fg }, children: "New Profile" })
|
|
560
605
|
] }) }, "new");
|
|
561
606
|
}
|
|
562
|
-
return /* @__PURE__ */
|
|
607
|
+
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, autoFocus: true, fgMuted, host, dark, colorPreview, useColorPickerAlign }, "new");
|
|
563
608
|
}
|
|
564
609
|
const { profile } = row;
|
|
565
610
|
if (editingId === profile.id) {
|
|
566
|
-
return /* @__PURE__ */
|
|
611
|
+
return /* @__PURE__ */ jsx8(EditRow, { draftName, onNameChange: handleNameChange, draftColor, onColorChange: setDraftColor, draftTag, onTagChange: handleTagChange, onSubmit: commitEdit, onDelete: () => setConfirmDeleteId(profile.id), fgMuted, host, dark, colorPreview, useColorPickerAlign }, profile.id);
|
|
567
612
|
}
|
|
568
|
-
return /* @__PURE__ */
|
|
569
|
-
/* @__PURE__ */
|
|
570
|
-
/* @__PURE__ */
|
|
613
|
+
return /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: () => startEdit(profile), style: styles5.row, children: /* @__PURE__ */ jsxs4(View5, { style: styles5.rowInner, children: [
|
|
614
|
+
/* @__PURE__ */ jsx8(ProfileChip, { profile, size: CHIP_SIZE2 }),
|
|
615
|
+
/* @__PURE__ */ jsx8(Text4, { variant: "bodyLarge", style: [styles5.rowName, { color: fg }], numberOfLines: 1, children: profile.name })
|
|
571
616
|
] }) }, profile.id);
|
|
572
617
|
}) }) }),
|
|
573
|
-
/* @__PURE__ */
|
|
618
|
+
/* @__PURE__ */ jsx8(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) })
|
|
574
619
|
] });
|
|
575
620
|
}
|
|
576
621
|
function AlignedColorPicker({ useAlign, ...props }) {
|
|
577
622
|
const { width: windowWidth } = useWindowDimensions();
|
|
578
623
|
const { width, height } = getInlineColorPickerContentSize(defaultColors2.length, windowWidth);
|
|
579
624
|
const alignOverride = useAlign(props.host.openId === props.id, width, height);
|
|
580
|
-
return /* @__PURE__ */
|
|
625
|
+
return /* @__PURE__ */ jsx8(InlineColorPicker, { ...props, alignOverride });
|
|
581
626
|
}
|
|
582
627
|
function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag, onTagChange, onSubmit, onDelete, autoFocus, fgMuted, host, dark, colorPreview, useColorPickerAlign }) {
|
|
583
628
|
const register = useFocusChain();
|
|
@@ -590,14 +635,14 @@ function EditRow({ draftName, onNameChange, draftColor, onColorChange, draftTag,
|
|
|
590
635
|
useEffect(() => {
|
|
591
636
|
if (host.openId === "color") tagFocusRef.current();
|
|
592
637
|
}, [host.openId]);
|
|
593
|
-
return /* @__PURE__ */
|
|
594
|
-
useColorPickerAlign ? /* @__PURE__ */
|
|
595
|
-
/* @__PURE__ */
|
|
596
|
-
/* @__PURE__ */
|
|
597
|
-
onDelete && /* @__PURE__ */
|
|
638
|
+
return /* @__PURE__ */ jsxs4(View5, { style: styles5.editRow, children: [
|
|
639
|
+
useColorPickerAlign ? /* @__PURE__ */ jsx8(AlignedColorPicker, { useAlign: useColorPickerAlign, id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }) : /* @__PURE__ */ jsx8(InlineColorPicker, { id: "color", host, value: draftColor, onChange: onColorChange, previewValue: colorPreview, tag: draftTag, dark }),
|
|
640
|
+
/* @__PURE__ */ jsx8(TextInput, { ref: tag.ref, ...tag.props, value: draftTag, onChangeText: onTagChange, accessibilityLabel: "Tag", autoCapitalize: "characters", selectTextOnFocus: true, returnKeyType: "next", style: styles5.hiddenTagInput, pointerEvents: "none" }),
|
|
641
|
+
/* @__PURE__ */ jsx8(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 }),
|
|
642
|
+
onDelete && /* @__PURE__ */ jsx8(TouchableRipple3, { onPress: onDelete, style: styles5.iconButton, children: /* @__PURE__ */ jsx8(Icon4, { source: "trash-can-outline", size: 18, color: fgMuted }) })
|
|
598
643
|
] });
|
|
599
644
|
}
|
|
600
|
-
var
|
|
645
|
+
var styles5 = StyleSheet5.create({
|
|
601
646
|
content: {
|
|
602
647
|
gap: 4,
|
|
603
648
|
paddingHorizontal: 20
|
|
@@ -679,8 +724,8 @@ var styles4 = StyleSheet4.create({
|
|
|
679
724
|
import { useAutoPaperTheme as useAutoPaperTheme2 } from "@rific/auto-paper";
|
|
680
725
|
import { IconButton } from "@rific/feedback-press";
|
|
681
726
|
import { useRef as useRef2 } from "react";
|
|
682
|
-
import { StyleSheet as
|
|
683
|
-
import { jsx as
|
|
727
|
+
import { StyleSheet as StyleSheet6, View as View6 } from "react-native";
|
|
728
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
684
729
|
function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, onBack, fg: fgOverride, fgMuted, bg: bgOverride, titleVariant, colorPreview, useColorPickerAlign }) {
|
|
685
730
|
const { dark } = useAutoPaperTheme2();
|
|
686
731
|
const managerRef = useRef2(null);
|
|
@@ -690,9 +735,9 @@ function ProfilesScreen({ profiles, defaultColor, onCreate, onSave, onDelete, on
|
|
|
690
735
|
managerRef.current?.commitPendingEdit();
|
|
691
736
|
onBack();
|
|
692
737
|
};
|
|
693
|
-
return /* @__PURE__ */
|
|
738
|
+
return /* @__PURE__ */ jsx9(View6, { style: [styles6.container, { backgroundColor: bg }], children: /* @__PURE__ */ jsx9(ProfilesManager, { ref: managerRef, profiles, defaultColor, onCreate, onSave, onDelete, fg, fgMuted, titleVariant, colorPreview, useColorPickerAlign, headerLeft: /* @__PURE__ */ jsx9(IconButton, { icon: "arrow-left", iconColor: fg, size: 24, onPress: handleBack, accessibilityLabel: "Back" }) }) });
|
|
694
739
|
}
|
|
695
|
-
var
|
|
740
|
+
var styles6 = StyleSheet6.create({
|
|
696
741
|
container: {
|
|
697
742
|
flex: 1
|
|
698
743
|
}
|
|
@@ -803,6 +848,7 @@ function createProfileRecord(input) {
|
|
|
803
848
|
}
|
|
804
849
|
|
|
805
850
|
// src/sharedProfileStore.ts
|
|
851
|
+
import { Platform as Platform2 } from "react-native";
|
|
806
852
|
var native = null;
|
|
807
853
|
try {
|
|
808
854
|
const { requireOptionalNativeModule } = __require("expo-modules-core");
|
|
@@ -811,15 +857,42 @@ try {
|
|
|
811
857
|
native = null;
|
|
812
858
|
}
|
|
813
859
|
var PROFILES_KEY = "sharedProfiles";
|
|
814
|
-
var
|
|
815
|
-
|
|
816
|
-
if (
|
|
817
|
-
let json = null;
|
|
860
|
+
var webKey = (groupId, key) => `${groupId}:${key}`;
|
|
861
|
+
var createWebStore = () => {
|
|
862
|
+
if (Platform2.OS !== "web") return null;
|
|
818
863
|
try {
|
|
819
|
-
|
|
864
|
+
const storage = globalThis.localStorage;
|
|
865
|
+
if (!storage) return null;
|
|
866
|
+
const probe = webKey("@tastic/profile", "probe");
|
|
867
|
+
storage.setItem(probe, "1");
|
|
868
|
+
storage.removeItem(probe);
|
|
869
|
+
let lastWriteFailed = false;
|
|
870
|
+
return {
|
|
871
|
+
storage,
|
|
872
|
+
store: {
|
|
873
|
+
getSharedJSON: async (groupId, key) => {
|
|
874
|
+
if (lastWriteFailed) throw new Error("The last write to the shared roster failed");
|
|
875
|
+
return storage.getItem(webKey(groupId, key));
|
|
876
|
+
},
|
|
877
|
+
setSharedJSON: async (groupId, key, value) => {
|
|
878
|
+
try {
|
|
879
|
+
storage.setItem(webKey(groupId, key), value);
|
|
880
|
+
lastWriteFailed = false;
|
|
881
|
+
} catch (error) {
|
|
882
|
+
lastWriteFailed = true;
|
|
883
|
+
throw error;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
820
888
|
} catch {
|
|
821
889
|
return null;
|
|
822
890
|
}
|
|
891
|
+
};
|
|
892
|
+
var web = native ? null : createWebStore();
|
|
893
|
+
if (web) native = web.store;
|
|
894
|
+
var isSharedProfileStoreAvailable = native !== null;
|
|
895
|
+
var parseRoster = (json) => {
|
|
823
896
|
if (!json) return [];
|
|
824
897
|
try {
|
|
825
898
|
const parsed = JSON.parse(json);
|
|
@@ -829,6 +902,16 @@ async function readSharedProfiles(groupId) {
|
|
|
829
902
|
} catch {
|
|
830
903
|
return null;
|
|
831
904
|
}
|
|
905
|
+
};
|
|
906
|
+
async function readSharedProfiles(groupId) {
|
|
907
|
+
if (!native) return [];
|
|
908
|
+
let json = null;
|
|
909
|
+
try {
|
|
910
|
+
json = await native.getSharedJSON(groupId, PROFILES_KEY);
|
|
911
|
+
} catch {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
return parseRoster(json);
|
|
832
915
|
}
|
|
833
916
|
async function loadSharedProfiles(groupId) {
|
|
834
917
|
return await readSharedProfiles(groupId) ?? [];
|
|
@@ -840,6 +923,19 @@ async function saveSharedProfiles(groupId, profiles) {
|
|
|
840
923
|
} catch {
|
|
841
924
|
}
|
|
842
925
|
}
|
|
926
|
+
function subscribeSharedProfiles(groupId, listener) {
|
|
927
|
+
if (!web || typeof window === "undefined" || typeof window.addEventListener !== "function") return () => {
|
|
928
|
+
};
|
|
929
|
+
const storageKey = webKey(groupId, PROFILES_KEY);
|
|
930
|
+
const storage = web.storage;
|
|
931
|
+
const onStorage = (event) => {
|
|
932
|
+
if (event.key !== storageKey || event.storageArea !== storage || !event.newValue) return;
|
|
933
|
+
const profiles = parseRoster(event.newValue);
|
|
934
|
+
if (profiles !== null) listener(profiles);
|
|
935
|
+
};
|
|
936
|
+
window.addEventListener("storage", onStorage);
|
|
937
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
938
|
+
}
|
|
843
939
|
|
|
844
940
|
// src/redux/resolveInitialProfiles.ts
|
|
845
941
|
async function resolveInitialProfiles(groupId, localFallback) {
|
|
@@ -922,9 +1018,13 @@ function useSharedProfilesSync({ groupId, onRemoteChange }) {
|
|
|
922
1018
|
if (!cancelled && shared !== null) onRemoteChangeRef.current(shared);
|
|
923
1019
|
});
|
|
924
1020
|
});
|
|
1021
|
+
const unsubscribe = subscribeSharedProfiles(groupId, (shared) => {
|
|
1022
|
+
if (!cancelled && !pendingWriteRef.current) onRemoteChangeRef.current(shared);
|
|
1023
|
+
});
|
|
925
1024
|
return () => {
|
|
926
1025
|
cancelled = true;
|
|
927
1026
|
sub.remove();
|
|
1027
|
+
unsubscribe();
|
|
928
1028
|
};
|
|
929
1029
|
}, [groupId]);
|
|
930
1030
|
const syncToShared = useCallback2(
|
|
@@ -973,7 +1073,9 @@ export {
|
|
|
973
1073
|
readSharedProfiles,
|
|
974
1074
|
resolveInitialProfiles,
|
|
975
1075
|
saveSharedProfiles,
|
|
1076
|
+
subscribeSharedProfiles,
|
|
976
1077
|
useProfileColorPreview,
|
|
977
1078
|
useProfileDisplayColor,
|
|
1079
|
+
useProfileSwatchRenderer,
|
|
978
1080
|
useSharedProfilesSync
|
|
979
1081
|
};
|