@unisim/sdk 0.152.1 → 0.154.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/dist/UserCountBreakdown.d.ts +22 -0
- package/dist/UserCountBreakdown.d.ts.map +1 -0
- package/dist/UserCountBreakdown.js +264 -0
- package/dist/UserCountBreakdown.js.map +1 -0
- package/dist/UserCountLine.d.ts +23 -1
- package/dist/UserCountLine.d.ts.map +1 -1
- package/dist/UserCountLine.js +132 -33
- package/dist/UserCountLine.js.map +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +59 -0
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/presence.d.ts +25 -0
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +74 -0
- package/dist/presence.js.map +1 -1
- package/dist/productNames.d.ts +18 -0
- package/dist/productNames.d.ts.map +1 -0
- package/dist/productNames.js +73 -0
- package/dist/productNames.js.map +1 -0
- package/dist/security.d.ts +39 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +93 -5
- package/dist/security.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -318,16 +318,33 @@ reports the app as in use (guests included) and the menu reads the figure.
|
|
|
318
318
|
is open. The line hides until a real number arrives.
|
|
319
319
|
- **It opens on the WHOLE SUITE** since 0.150.0 — `suite_user_counts()`, "There
|
|
320
320
|
are a total of 1,234 users across all UNI·SIM apps (12 live)" (migration
|
|
321
|
-
0177) — and a tap switches to this app's own figure and back.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
0177) — and a tap switches to this app's own figure and back. An account is
|
|
322
|
+
one person suite-wide; a guest is one person per browser origin, so the suite
|
|
323
|
+
total is a utilisation figure, not a headcount.
|
|
324
|
+
⚠️ **The tap is NOT remembered** (since 0.152.0). It used to be, in
|
|
325
|
+
`unisim:user-count-scope`, and one tap months earlier is what made every app
|
|
326
|
+
on a browser open on its own figure with the suite hidden behind a click. The
|
|
327
|
+
key is now deleted on mount; `COUNT_SCOPE_KEY` is exported only so it can be
|
|
328
|
+
cleared. [Correction 2026-09-19: this bullet still described the remembered
|
|
329
|
+
choice.]
|
|
330
|
+
- **Press and hold for the breakdown** (since 0.154.0, migration 0184):
|
|
331
|
+
`suite_user_counts_by_product()` → one row per app, in a popup over the menu.
|
|
332
|
+
Right-click and Shift+Enter open it too, so the gesture is not pointer-only.
|
|
333
|
+
⚠️ **The rows do not add up to the suite total and are not meant to** — an
|
|
334
|
+
account using four apps is four rows there and one person in the suite
|
|
335
|
+
figure. The popup says so; never sum them to produce a total. App names come
|
|
336
|
+
from the switcher catalogue (`productDisplayName`), and a product code this
|
|
337
|
+
build has never heard of is listed under a prettified name rather than
|
|
338
|
+
dropped, because the database enum gains values without the SDK being
|
|
339
|
+
rebuilt. A database without 0184 shows "not available" and nothing else
|
|
340
|
+
breaks. Turn it off with `<UserCountLine breakdown={false} />`.
|
|
326
341
|
- **Off switches**: `presence: false` in `UniversalConfig`; `showUserCount={false}`
|
|
327
342
|
on `<UserProfile>`. The hubs (`product: 'central'`) never beat or show it.
|
|
328
|
-
- **Elsewhere**: `<UserCountLine />` / `useUserCounts()` for a home screen
|
|
329
|
-
|
|
330
|
-
|
|
343
|
+
- **Elsewhere**: `<UserCountLine />` / `useUserCounts()` for a home screen, and
|
|
344
|
+
`<UserCountBreakdown />` / `useUserCountsByProduct()` for the per-app list.
|
|
345
|
+
Apps without `<UniversalProvider>` use `startPresence(supabase, product)`,
|
|
346
|
+
`fetchUserCounts`, `fetchUserCountsByProduct` and
|
|
347
|
+
`formatUserCounts(language, counts)` directly.
|
|
331
348
|
|
|
332
349
|
### Languages
|
|
333
350
|
- `SUPPORTED_LANGUAGES`: `en`, `en-gb`, `fr`, `es`, `it`, `de`, **`pt-BR`**, **`pt-PT`**, `tr`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type MenuTheme } from './menuTheme.js';
|
|
2
|
+
import { type ProductCounts, type UserCounts } from './presence.js';
|
|
3
|
+
/**
|
|
4
|
+
* `{ product, total, live }` per app, refreshed on the presence beat while
|
|
5
|
+
* `active`. Starts from the last breakdown this origin read so the popup has
|
|
6
|
+
* something in it the moment it opens.
|
|
7
|
+
*
|
|
8
|
+
* `null` while there is nothing to show — which covers both "the read has not
|
|
9
|
+
* landed yet" and "this database has not got migration 0184". The popup tells
|
|
10
|
+
* those two apart with its own `loading` flag; a caller that just wants the
|
|
11
|
+
* numbers does not need to.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useUserCountsByProduct(active?: boolean): ProductCounts[] | null;
|
|
14
|
+
export interface UserCountBreakdownProps {
|
|
15
|
+
open: boolean;
|
|
16
|
+
onClose: () => void;
|
|
17
|
+
/** The suite figure the line was showing, repeated at the top for context. */
|
|
18
|
+
suite?: UserCounts | null;
|
|
19
|
+
theme?: MenuTheme;
|
|
20
|
+
}
|
|
21
|
+
export declare function UserCountBreakdown({ open, onClose, suite, theme }: UserCountBreakdownProps): import("react").ReactPortal | null;
|
|
22
|
+
//# sourceMappingURL=UserCountBreakdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserCountBreakdown.d.ts","sourceRoot":"","sources":["../src/UserCountBreakdown.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAGrD,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,eAAe,CAAA;AAqCtB;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,UAAO,GAAG,aAAa,EAAE,GAAG,IAAI,CAmB5E;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IACzB,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAe,EAAE,EAAE,uBAAuB,sCA4GpG"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { NAV_OVERLAY_Z_INDEX } from './DropdownSurface.js';
|
|
6
|
+
import { t } from './i18n.js';
|
|
7
|
+
import { MENU } from './menuTheme.js';
|
|
8
|
+
import { productDisplayName } from './productNames.js';
|
|
9
|
+
import { useLanguage, useUniversal } from './provider.js';
|
|
10
|
+
import { PRESENCE_BEAT_MS, cachedUserCountsByProduct, fetchUserCountsByProduct, formatUserCounts, } from './presence.js';
|
|
11
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
// <UserCountBreakdown /> — "…and which apps are those people in?"
|
|
13
|
+
//
|
|
14
|
+
// James, 2026-09-19: "on the user count, can you add it so if you long press on
|
|
15
|
+
// the X people use UNI·SIM then you see a pop up with the breakdown of users per
|
|
16
|
+
// app". So the count line now has three depths rather than two: the suite figure
|
|
17
|
+
// it opens on, this app's figure on a tap (2026-09-17), and this list on a press
|
|
18
|
+
// and hold.
|
|
19
|
+
//
|
|
20
|
+
// Three things worth knowing before changing it:
|
|
21
|
+
//
|
|
22
|
+
// 1. **The rows do not add up to the suite total, and the note says so.** An
|
|
23
|
+
// account that uses four apps is four rows here and one person in the suite
|
|
24
|
+
// figure — that is the whole point of the suite figure. A reader who adds the
|
|
25
|
+
// column up and gets a bigger number has found the documented behaviour, not
|
|
26
|
+
// a bug, so the sentence stating it is part of the popup rather than a
|
|
27
|
+
// footnote somebody might cut.
|
|
28
|
+
//
|
|
29
|
+
// 2. **An app this build has never heard of still gets a row.** The names come
|
|
30
|
+
// from the switcher catalogue (productNames.ts), the rows come from the
|
|
31
|
+
// database enum, and the two have gone out of step twice already (0176 added
|
|
32
|
+
// `family`, 0179 the two Bipolar apps). An unknown code renders prettified —
|
|
33
|
+
// never dropped, because a live app missing from a list of live apps is worse
|
|
34
|
+
// than one with a plain-looking name.
|
|
35
|
+
//
|
|
36
|
+
// 3. **It is portaled to <body> and guards the menu it was opened from.** Same
|
|
37
|
+
// problem AccountLimits' flyout has and the same fix: the parent menu's
|
|
38
|
+
// outside-click test asks whether the event happened inside its panel, and a
|
|
39
|
+
// portal is not, so without the capture-phase guard below the first click in
|
|
40
|
+
// here would close the menu and take this popup down with it.
|
|
41
|
+
//
|
|
42
|
+
// ⚠️ Inline styles, not Tailwind — this ships inside node_modules and the apps'
|
|
43
|
+
// Tailwind builds do not scan it. Same as AboutAppDialog and PrivacyNote.
|
|
44
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* `{ product, total, live }` per app, refreshed on the presence beat while
|
|
47
|
+
* `active`. Starts from the last breakdown this origin read so the popup has
|
|
48
|
+
* something in it the moment it opens.
|
|
49
|
+
*
|
|
50
|
+
* `null` while there is nothing to show — which covers both "the read has not
|
|
51
|
+
* landed yet" and "this database has not got migration 0184". The popup tells
|
|
52
|
+
* those two apart with its own `loading` flag; a caller that just wants the
|
|
53
|
+
* numbers does not need to.
|
|
54
|
+
*/
|
|
55
|
+
export function useUserCountsByProduct(active = true) {
|
|
56
|
+
const { supabase } = useUniversal();
|
|
57
|
+
const [rows, setRows] = useState(() => cachedUserCountsByProduct());
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!active)
|
|
60
|
+
return;
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
const load = () => {
|
|
63
|
+
if (typeof document !== 'undefined' && document.visibilityState === 'hidden')
|
|
64
|
+
return;
|
|
65
|
+
void fetchUserCountsByProduct(supabase).then((r) => {
|
|
66
|
+
if (!cancelled && r)
|
|
67
|
+
setRows(r);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
load();
|
|
71
|
+
const timer = setInterval(load, PRESENCE_BEAT_MS);
|
|
72
|
+
return () => { cancelled = true; clearInterval(timer); };
|
|
73
|
+
}, [active, supabase]);
|
|
74
|
+
return rows;
|
|
75
|
+
}
|
|
76
|
+
export function UserCountBreakdown({ open, onClose, suite, theme = 'light' }) {
|
|
77
|
+
const { language } = useLanguage();
|
|
78
|
+
const p = MENU[theme];
|
|
79
|
+
const rows = useUserCountsByProduct(open);
|
|
80
|
+
const cardRef = useRef(null);
|
|
81
|
+
// Only ever true on a FIRST open on this origin — every later open paints the
|
|
82
|
+
// cached rows immediately and swaps them when the fresh read lands.
|
|
83
|
+
const [settled, setSettled] = useState(false);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!open) {
|
|
86
|
+
setSettled(false);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Long enough for the read to answer, short enough that a database without
|
|
90
|
+
// 0184 says "not available" rather than spinning for ever.
|
|
91
|
+
const timer = setTimeout(() => setSettled(true), 4000);
|
|
92
|
+
return () => clearTimeout(timer);
|
|
93
|
+
}, [open]);
|
|
94
|
+
// Two CAPTURE-phase listeners on document, copied from AccountLimits' flyout
|
|
95
|
+
// for the same reason (note 3).
|
|
96
|
+
//
|
|
97
|
+
// ⚠️ They have to be real listeners, not React's `onMouseDownCapture`: React
|
|
98
|
+
// attaches one listener at its root container, so a document-level capture
|
|
99
|
+
// handler — which is where DropdownSurface and UserProfile put theirs — has
|
|
100
|
+
// already run by the time any React handler sees the event.
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!open)
|
|
103
|
+
return;
|
|
104
|
+
// A press inside this card is not a click "outside" the menu that opened it.
|
|
105
|
+
function onDown(e) {
|
|
106
|
+
if (cardRef.current?.contains(e.target))
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
}
|
|
109
|
+
// Escape backs out one level: the first press closes this popup, a second
|
|
110
|
+
// closes the menu underneath.
|
|
111
|
+
function onKey(e) {
|
|
112
|
+
if (e.key !== 'Escape')
|
|
113
|
+
return;
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
onClose();
|
|
116
|
+
}
|
|
117
|
+
document.addEventListener('mousedown', onDown, true);
|
|
118
|
+
document.addEventListener('keydown', onKey, true);
|
|
119
|
+
return () => {
|
|
120
|
+
document.removeEventListener('mousedown', onDown, true);
|
|
121
|
+
document.removeEventListener('keydown', onKey, true);
|
|
122
|
+
};
|
|
123
|
+
}, [open, onClose]);
|
|
124
|
+
if (!open || typeof document === 'undefined')
|
|
125
|
+
return null;
|
|
126
|
+
const loading = !rows && !settled;
|
|
127
|
+
return createPortal(_jsx("div", { style: overlayStyle, role: "presentation",
|
|
128
|
+
// ⚠️ pointerDOWN, not click: with a mouse, the press that opened this
|
|
129
|
+
// popup is still down when it appears, so the mouseup lands on the
|
|
130
|
+
// backdrop. An onClick here would close the popup in the same gesture
|
|
131
|
+
// that opened it. A fresh press on the backdrop sends its own
|
|
132
|
+
// pointerdown, which is the dismissal we actually want.
|
|
133
|
+
onPointerDown: onClose, children: _jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": t(language, 'users.breakdown_title'), ref: cardRef, style: cardStyle(p), onPointerDown: (e) => e.stopPropagation(), children: [_jsxs("div", { style: headerRowStyle, children: [_jsx("h2", { style: titleStyle(p), children: t(language, 'users.breakdown_title') }), _jsx("button", { type: "button", onClick: onClose, "aria-label": t(language, 'nav.menu_close'), style: closeButtonStyle(p), children: _jsx(IconClose, {}) })] }), suite && _jsx("p", { style: suiteLineStyle(p), children: formatUserCounts(language, suite, 'suite') }), loading && _jsx("p", { style: quietStyle(p), children: t(language, 'users.breakdown_loading') }), !loading && !rows && _jsx("p", { style: quietStyle(p), children: t(language, 'users.breakdown_empty') }), rows && (_jsx("ul", { style: listStyle, children: rows.map((row) => (_jsxs("li", { style: rowStyle(p), children: [_jsx("span", { style: nameStyle(p), children: productDisplayName(row.product) }), _jsxs("span", { style: figureStyle(p), children: [row.live > 0 && _jsx("span", { style: liveDotStyle, "aria-hidden": true }), formatRow(language, row)] })] }, row.product))) })), rows && _jsx("p", { style: noteStyle(p), children: t(language, 'users.breakdown_note') })] }) }), document.body);
|
|
134
|
+
}
|
|
135
|
+
/** "1,234 users · 5 live", grouped the way the reader's language groups. */
|
|
136
|
+
function formatRow(language, row) {
|
|
137
|
+
let nf;
|
|
138
|
+
try {
|
|
139
|
+
nf = new Intl.NumberFormat(language ?? undefined);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
nf = new Intl.NumberFormat('en');
|
|
143
|
+
}
|
|
144
|
+
const key = row.total === 1 ? 'users.breakdown_row_one' : 'users.breakdown_row';
|
|
145
|
+
return t(language, key)
|
|
146
|
+
.replace('{total}', nf.format(row.total))
|
|
147
|
+
.replace('{live}', nf.format(row.live));
|
|
148
|
+
}
|
|
149
|
+
function IconClose() {
|
|
150
|
+
return (_jsx("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", "aria-hidden": "true", children: _jsx("path", { d: "M6 6 18 18M18 6 6 18" }) }));
|
|
151
|
+
}
|
|
152
|
+
// ── Styles — the menu palette, so the popup matches the dropdown it came from.
|
|
153
|
+
const overlayStyle = {
|
|
154
|
+
position: 'fixed',
|
|
155
|
+
inset: 0,
|
|
156
|
+
background: 'rgba(15, 23, 42, 0.45)',
|
|
157
|
+
display: 'flex',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
justifyContent: 'center',
|
|
160
|
+
padding: 16,
|
|
161
|
+
// Above the menu it was opened from, which already sits above everything the
|
|
162
|
+
// host app draws.
|
|
163
|
+
zIndex: NAV_OVERLAY_Z_INDEX + 100,
|
|
164
|
+
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif",
|
|
165
|
+
};
|
|
166
|
+
const cardStyle = (p) => ({
|
|
167
|
+
position: 'relative',
|
|
168
|
+
width: '100%',
|
|
169
|
+
maxWidth: 380,
|
|
170
|
+
// Thirty apps on a short phone outruns the viewport, and a list that cannot
|
|
171
|
+
// be scrolled is a list whose bottom half does not exist.
|
|
172
|
+
maxHeight: 'calc(100vh - 32px)',
|
|
173
|
+
overflowY: 'auto',
|
|
174
|
+
background: p.surface,
|
|
175
|
+
color: p.text,
|
|
176
|
+
borderRadius: 16,
|
|
177
|
+
border: `1px solid ${p.border}`,
|
|
178
|
+
boxShadow: p.shadow,
|
|
179
|
+
padding: '16px 18px 18px',
|
|
180
|
+
boxSizing: 'border-box',
|
|
181
|
+
});
|
|
182
|
+
const headerRowStyle = {
|
|
183
|
+
display: 'flex',
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
justifyContent: 'space-between',
|
|
186
|
+
gap: 12,
|
|
187
|
+
};
|
|
188
|
+
const titleStyle = (p) => ({
|
|
189
|
+
margin: 0,
|
|
190
|
+
fontSize: 15,
|
|
191
|
+
fontWeight: 700,
|
|
192
|
+
letterSpacing: '-0.01em',
|
|
193
|
+
color: p.text,
|
|
194
|
+
});
|
|
195
|
+
const closeButtonStyle = (p) => ({
|
|
196
|
+
display: 'inline-flex',
|
|
197
|
+
alignItems: 'center',
|
|
198
|
+
justifyContent: 'center',
|
|
199
|
+
width: 28,
|
|
200
|
+
height: 28,
|
|
201
|
+
flexShrink: 0,
|
|
202
|
+
borderRadius: 8,
|
|
203
|
+
border: 0,
|
|
204
|
+
background: 'transparent',
|
|
205
|
+
color: p.muted,
|
|
206
|
+
cursor: 'pointer',
|
|
207
|
+
});
|
|
208
|
+
const suiteLineStyle = (p) => ({
|
|
209
|
+
margin: '6px 0 0',
|
|
210
|
+
fontSize: 11,
|
|
211
|
+
lineHeight: 1.4,
|
|
212
|
+
color: p.muted,
|
|
213
|
+
});
|
|
214
|
+
const quietStyle = (p) => ({
|
|
215
|
+
margin: '14px 0 0',
|
|
216
|
+
fontSize: 12,
|
|
217
|
+
color: p.muted,
|
|
218
|
+
});
|
|
219
|
+
const listStyle = {
|
|
220
|
+
listStyle: 'none',
|
|
221
|
+
margin: '12px 0 0',
|
|
222
|
+
padding: 0,
|
|
223
|
+
display: 'grid',
|
|
224
|
+
gap: 2,
|
|
225
|
+
};
|
|
226
|
+
const rowStyle = (p) => ({
|
|
227
|
+
display: 'flex',
|
|
228
|
+
alignItems: 'baseline',
|
|
229
|
+
justifyContent: 'space-between',
|
|
230
|
+
gap: 12,
|
|
231
|
+
padding: '5px 0',
|
|
232
|
+
borderBottom: `1px solid ${p.divider}`,
|
|
233
|
+
});
|
|
234
|
+
const nameStyle = (p) => ({
|
|
235
|
+
fontSize: 13,
|
|
236
|
+
color: p.body,
|
|
237
|
+
minWidth: 0,
|
|
238
|
+
});
|
|
239
|
+
const figureStyle = (p) => ({
|
|
240
|
+
display: 'inline-flex',
|
|
241
|
+
alignItems: 'baseline',
|
|
242
|
+
gap: 5,
|
|
243
|
+
flexShrink: 0,
|
|
244
|
+
fontSize: 12,
|
|
245
|
+
color: p.muted,
|
|
246
|
+
// A grouped figure must not be broken across two lines by the flex row.
|
|
247
|
+
whiteSpace: 'nowrap',
|
|
248
|
+
});
|
|
249
|
+
const liveDotStyle = {
|
|
250
|
+
display: 'inline-block',
|
|
251
|
+
width: 6,
|
|
252
|
+
height: 6,
|
|
253
|
+
borderRadius: '50%',
|
|
254
|
+
// The same green every "live" dot in the suite uses; decorative, and never
|
|
255
|
+
// the only thing carrying the meaning — the words say "live" next to it.
|
|
256
|
+
background: '#22c55e',
|
|
257
|
+
};
|
|
258
|
+
const noteStyle = (p) => ({
|
|
259
|
+
margin: '12px 0 0',
|
|
260
|
+
fontSize: 11,
|
|
261
|
+
lineHeight: 1.45,
|
|
262
|
+
color: p.faint,
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=UserCountBreakdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserCountBreakdown.js","sourceRoot":"","sources":["../src/UserCountBreakdown.tsx"],"names":[],"mappings":"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"}
|
package/dist/UserCountLine.d.ts
CHANGED
|
@@ -8,12 +8,29 @@ import { type CountScope, type UserCounts } from './presence.js';
|
|
|
8
8
|
* is a real, non-zero total.
|
|
9
9
|
*/
|
|
10
10
|
export declare function useUserCounts(active?: boolean, scope?: CountScope): UserCounts | null;
|
|
11
|
+
/**
|
|
12
|
+
* How long a press has to last to be a press-and-hold rather than a tap.
|
|
13
|
+
*
|
|
14
|
+
* 500 ms is the platform figure — it is what iOS and Android use for their own
|
|
15
|
+
* long press, and what the browsers' `contextmenu` on touch fires at — so a
|
|
16
|
+
* person who already knows the gesture from every other app gets the same
|
|
17
|
+
* timing here. Shorter and an ordinary slow tap opens the popup by accident;
|
|
18
|
+
* longer and the gesture feels broken before it fires.
|
|
19
|
+
*/
|
|
20
|
+
export declare const LONG_PRESS_MS = 500;
|
|
11
21
|
export interface UserCountLineProps {
|
|
12
22
|
/** Fetch only while this is true (the menu is open). Default true. */
|
|
13
23
|
active?: boolean;
|
|
14
24
|
theme?: MenuTheme;
|
|
15
25
|
style?: CSSProperties;
|
|
16
26
|
className?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Offer the per-app breakdown on a press and hold. Default true.
|
|
29
|
+
*
|
|
30
|
+
* Turn it off where the popup has nowhere sensible to land — a surface with
|
|
31
|
+
* no <UniversalProvider> above it cannot read the breakdown at all.
|
|
32
|
+
*/
|
|
33
|
+
breakdown?: boolean;
|
|
17
34
|
}
|
|
18
35
|
/**
|
|
19
36
|
* "There are a total of 1,234 users across all UNI·SIM apps (5 live)" — one
|
|
@@ -26,11 +43,16 @@ export interface UserCountLineProps {
|
|
|
26
43
|
* width — see the style below. It is the longest string in the menu by some
|
|
27
44
|
* way, and a dropdown sizes itself to its content.
|
|
28
45
|
*
|
|
46
|
+
* **Press and hold** (or right-click, or Shift+Enter) opens the per-app
|
|
47
|
+
* breakdown behind the suite figure — <UserCountBreakdown /> (James,
|
|
48
|
+
* 2026-09-19). The click that ends a long press is swallowed rather than
|
|
49
|
+
* flipping the figure; see `heldRef`.
|
|
50
|
+
*
|
|
29
51
|
* Renders nothing until a real number arrives, and nothing on the hubs
|
|
30
52
|
* (`central`), which are not an app anybody "uses" in this sense.
|
|
31
53
|
*
|
|
32
54
|
* The navbars' profile menu already shows it, at the bottom; mount this
|
|
33
55
|
* yourself only to show the figure somewhere else too (a home screen).
|
|
34
56
|
*/
|
|
35
|
-
export declare function UserCountLine({ active, theme, style, className }: UserCountLineProps): import("react/jsx-runtime").JSX.Element | null;
|
|
57
|
+
export declare function UserCountLine({ active, theme, style, className, breakdown, }: UserCountLineProps): import("react/jsx-runtime").JSX.Element | null;
|
|
36
58
|
//# sourceMappingURL=UserCountLine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserCountLine.d.ts","sourceRoot":"","sources":["../src/UserCountLine.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"UserCountLine.d.ts","sourceRoot":"","sources":["../src/UserCountLine.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAGpF,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,EAML,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,eAAe,CAAA;AAEtB;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,UAAO,EAAE,KAAK,GAAE,UAAoB,GAAG,UAAU,GAAG,IAAI,CAwB3F;AA0BD;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,MAAM,CAAA;AAEhC,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAa,EAAE,KAAe,EAAE,KAAK,EAAE,SAAS,EAAE,SAAgB,GACnE,EAAE,kBAAkB,kDA6IpB"}
|
package/dist/UserCountLine.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { useLanguage, useUniversal } from './provider.js';
|
|
5
|
+
import { t } from './i18n.js';
|
|
5
6
|
import { MENU } from './menuTheme.js';
|
|
7
|
+
import { UserCountBreakdown } from './UserCountBreakdown.js';
|
|
6
8
|
import { COUNT_SCOPE_KEY, PRESENCE_BEAT_MS, cachedUserCounts, fetchUserCounts, formatUserCounts, } from './presence.js';
|
|
7
9
|
/**
|
|
8
10
|
* `{ total, live }` for every UNI·SIM app at once — or, with `scope: 'app'`,
|
|
@@ -61,6 +63,16 @@ function forgetStoredScope() {
|
|
|
61
63
|
}
|
|
62
64
|
catch { /* ignore */ }
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* How long a press has to last to be a press-and-hold rather than a tap.
|
|
68
|
+
*
|
|
69
|
+
* 500 ms is the platform figure — it is what iOS and Android use for their own
|
|
70
|
+
* long press, and what the browsers' `contextmenu` on touch fires at — so a
|
|
71
|
+
* person who already knows the gesture from every other app gets the same
|
|
72
|
+
* timing here. Shorter and an ordinary slow tap opens the popup by accident;
|
|
73
|
+
* longer and the gesture feels broken before it fires.
|
|
74
|
+
*/
|
|
75
|
+
export const LONG_PRESS_MS = 500;
|
|
64
76
|
/**
|
|
65
77
|
* "There are a total of 1,234 users across all UNI·SIM apps (5 live)" — one
|
|
66
78
|
* quiet sentence, and a tap switches it to this app's own figure and back
|
|
@@ -72,51 +84,138 @@ function forgetStoredScope() {
|
|
|
72
84
|
* width — see the style below. It is the longest string in the menu by some
|
|
73
85
|
* way, and a dropdown sizes itself to its content.
|
|
74
86
|
*
|
|
87
|
+
* **Press and hold** (or right-click, or Shift+Enter) opens the per-app
|
|
88
|
+
* breakdown behind the suite figure — <UserCountBreakdown /> (James,
|
|
89
|
+
* 2026-09-19). The click that ends a long press is swallowed rather than
|
|
90
|
+
* flipping the figure; see `heldRef`.
|
|
91
|
+
*
|
|
75
92
|
* Renders nothing until a real number arrives, and nothing on the hubs
|
|
76
93
|
* (`central`), which are not an app anybody "uses" in this sense.
|
|
77
94
|
*
|
|
78
95
|
* The navbars' profile menu already shows it, at the bottom; mount this
|
|
79
96
|
* yourself only to show the figure somewhere else too (a home screen).
|
|
80
97
|
*/
|
|
81
|
-
export function UserCountLine({ active = true, theme = 'light', style, className }) {
|
|
98
|
+
export function UserCountLine({ active = true, theme = 'light', style, className, breakdown = true, }) {
|
|
82
99
|
const { config } = useUniversal();
|
|
83
100
|
const { language } = useLanguage();
|
|
84
101
|
const [scope, setScope] = useState('suite');
|
|
102
|
+
const [listOpen, setListOpen] = useState(false);
|
|
85
103
|
const hub = config.product === 'central';
|
|
86
104
|
const counts = useUserCounts(active && !hub, scope);
|
|
105
|
+
// The popup heads with the SUITE figure whichever figure the line is on,
|
|
106
|
+
// because the list breaks down the suite. ⚠️ Read from the cache rather than
|
|
107
|
+
// with a second useUserCounts('suite'): the line ALWAYS opens on the suite
|
|
108
|
+
// figure and writes it there, so by the time anyone has tapped through to
|
|
109
|
+
// this app's number the suite's is already stored — and a second hook would
|
|
110
|
+
// put a duplicate `suite_user_counts` on the wire every 45 s in every app,
|
|
111
|
+
// for a popup almost nobody opens.
|
|
112
|
+
const suite = scope === 'suite' ? counts : cachedUserCounts('suite');
|
|
113
|
+
const pressTimer = useRef(null);
|
|
114
|
+
// A long press ends in a click (mouse) or a tap (touch) that the browser
|
|
115
|
+
// reports exactly like a short one. Without this the gesture would open the
|
|
116
|
+
// popup AND flip the figure underneath it.
|
|
117
|
+
const heldRef = useRef(false);
|
|
118
|
+
const cancelPress = useCallback(() => {
|
|
119
|
+
if (pressTimer.current) {
|
|
120
|
+
clearTimeout(pressTimer.current);
|
|
121
|
+
pressTimer.current = null;
|
|
122
|
+
}
|
|
123
|
+
}, []);
|
|
124
|
+
const startPress = useCallback(() => {
|
|
125
|
+
if (!breakdown)
|
|
126
|
+
return;
|
|
127
|
+
cancelPress();
|
|
128
|
+
heldRef.current = false;
|
|
129
|
+
pressTimer.current = setTimeout(() => {
|
|
130
|
+
pressTimer.current = null;
|
|
131
|
+
heldRef.current = true;
|
|
132
|
+
setListOpen(true);
|
|
133
|
+
}, LONG_PRESS_MS);
|
|
134
|
+
}, [breakdown, cancelPress]);
|
|
135
|
+
// A press interrupted by unmounting (the menu closing under it) must not fire
|
|
136
|
+
// afterwards into a component that has gone.
|
|
137
|
+
useEffect(() => cancelPress, [cancelPress]);
|
|
87
138
|
useEffect(forgetStoredScope, []);
|
|
88
139
|
const toggle = () => setScope((s) => (s === 'app' ? 'suite' : 'app'));
|
|
89
140
|
if (hub || !counts)
|
|
90
141
|
return null;
|
|
91
|
-
return (_jsx("button", { type: "button",
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
142
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button",
|
|
143
|
+
// Not a menuitem: it is a fact about the app rather than a command, and a
|
|
144
|
+
// menu's arrow keys should not stop on it. The press only swaps the
|
|
145
|
+
// figure, so the menu stays open under it.
|
|
146
|
+
role: "none",
|
|
147
|
+
// ⚠️ Two gestures on one control, and the second must not trigger the
|
|
148
|
+
// first: a press held for LONG_PRESS_MS opens the per-app breakdown
|
|
149
|
+
// (James, 2026-09-19), and the click that ends it is swallowed here
|
|
150
|
+
// rather than flipping the figure.
|
|
151
|
+
onClick: () => {
|
|
152
|
+
if (heldRef.current) {
|
|
153
|
+
heldRef.current = false;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
toggle();
|
|
157
|
+
}, onPointerDown: startPress, onPointerUp: cancelPress, onPointerLeave: cancelPress, onPointerCancel: cancelPress,
|
|
158
|
+
// A touch long press raises the platform's own context menu on some
|
|
159
|
+
// browsers; answering it with the breakdown is closer to what the person
|
|
160
|
+
// asked for than a "copy / share" sheet over the top of it. On a desktop
|
|
161
|
+
// this is the right-click path, which is the same gesture without the
|
|
162
|
+
// wait.
|
|
163
|
+
onContextMenu: (e) => {
|
|
164
|
+
if (!breakdown)
|
|
165
|
+
return;
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
cancelPress();
|
|
168
|
+
heldRef.current = true;
|
|
169
|
+
setListOpen(true);
|
|
170
|
+
},
|
|
171
|
+
// The keyboard has no press-and-hold, so it gets a modifier instead —
|
|
172
|
+
// Enter and Space still do what they did. Without this the breakdown
|
|
173
|
+
// would be reachable by pointer only, which is the failure the suite
|
|
174
|
+
// swept for on 2026-09-18.
|
|
175
|
+
onKeyDown: (e) => {
|
|
176
|
+
// A right-click sets `heldRef` with no pointerdown to clear it (a
|
|
177
|
+
// context menu ends in no click at all), so the next keyboard Enter
|
|
178
|
+
// would be swallowed. Every key press clears it; a pointer press
|
|
179
|
+
// clears it in startPress.
|
|
180
|
+
heldRef.current = false;
|
|
181
|
+
if (!breakdown || !e.shiftKey)
|
|
182
|
+
return;
|
|
183
|
+
if (e.key !== 'Enter' && e.key !== ' ' && e.key !== 'Spacebar')
|
|
184
|
+
return;
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
setListOpen(true);
|
|
187
|
+
}, title: breakdown ? t(language, 'users.breakdown_hint') : undefined, className: className, style: {
|
|
188
|
+
display: 'block', textAlign: 'left', border: 0, background: 'transparent',
|
|
189
|
+
padding: '6px 12px 4px', fontFamily: 'inherit', fontSize: 11, lineHeight: 1.4,
|
|
190
|
+
color: MENU[theme].muted, cursor: 'pointer',
|
|
191
|
+
// ⚠️ `width: 0` + `minWidth: '100%'`, not `width: '100%'` (James,
|
|
192
|
+
// 2026-09-18: "it needs to wrap the text so it doesn't stretch the
|
|
193
|
+
// dropdown too far"). A nav dropdown has no width of its own — it is
|
|
194
|
+
// shrink-to-fit around its rows, with a 220px floor — so the widest
|
|
195
|
+
// thing in it decides how wide it is, and a percentage width does NOT
|
|
196
|
+
// exempt a child from that: the sentence still counts at its
|
|
197
|
+
// max-content width. "There are a total of 9,876 users across all
|
|
198
|
+
// UNI·SIM apps (12 live)" is ~350px on one line, so this one line,
|
|
199
|
+
// which is not even a row, was dragging every app's Actions menu 130px
|
|
200
|
+
// wider than its own rows asked for.
|
|
201
|
+
//
|
|
202
|
+
// A definite `width: 0` contributes nothing to that measurement;
|
|
203
|
+
// `minWidth: 100%` is a percentage, so it resolves against the panel's
|
|
204
|
+
// width AFTER the panel has been sized, handing the line the full width
|
|
205
|
+
// back to paint in. The sentence then wraps inside the panel instead of
|
|
206
|
+
// setting its width. Wrapping is the browser default and is only spelt
|
|
207
|
+
// out here because the whole point is that it now has somewhere to wrap;
|
|
208
|
+
// `overflowWrap` covers a long unbroken figure in a locale that groups
|
|
209
|
+
// without spaces.
|
|
210
|
+
width: 0, minWidth: '100%', whiteSpace: 'normal', overflowWrap: 'break-word',
|
|
211
|
+
// A press held on text is a SELECTION to a mobile browser: iOS raises
|
|
212
|
+
// the copy/look-up callout and Android starts a text handle, either of
|
|
213
|
+
// which cancels the gesture before LONG_PRESS_MS is up. These three
|
|
214
|
+
// hand the hold to the button instead, and `manipulation` drops the
|
|
215
|
+
// 300 ms double-tap wait on the ordinary tap.
|
|
216
|
+
WebkitUserSelect: 'none', userSelect: 'none', WebkitTouchCallout: 'none',
|
|
217
|
+
touchAction: 'manipulation',
|
|
218
|
+
...style,
|
|
219
|
+
}, children: _jsx("span", { role: "status", children: formatUserCounts(language, counts, scope) }) }), breakdown && (_jsx(UserCountBreakdown, { open: listOpen, onClose: () => setListOpen(false), suite: suite, theme: theme }))] }));
|
|
121
220
|
}
|
|
122
221
|
//# sourceMappingURL=UserCountLine.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserCountLine.js","sourceRoot":"","sources":["../src/UserCountLine.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"UserCountLine.js","sourceRoot":"","sources":["../src/UserCountLine.tsx"],"names":[],"mappings":"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"}
|