beez-ui 0.8.2 → 0.9.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +12 -10
  3. package/dist/components/account-menu.d.ts +10 -0
  4. package/dist/components/account-menu.js +139 -118
  5. package/dist/components/file-upload.d.ts +14 -4
  6. package/dist/components/file-upload.js +304 -206
  7. package/dist/components/filter-presets-bar.d.ts +73 -0
  8. package/dist/components/filter-presets-bar.js +510 -0
  9. package/dist/components/reaction-button.d.ts +7 -2
  10. package/dist/components/reaction-button.js +59 -48
  11. package/dist/hooks.d.ts +10 -0
  12. package/dist/hooks.js +10 -0
  13. package/dist/index.d.ts +1 -23
  14. package/dist/index.js +1 -23
  15. package/dist/styles.css +1 -1
  16. package/package.json +11 -126
  17. package/dist/components/bouncing-dots-loader.d.ts +0 -19
  18. package/dist/components/bouncing-dots-loader.js +0 -47
  19. package/dist/components/confirm-delete-button.d.ts +0 -29
  20. package/dist/components/confirm-delete-button.js +0 -254
  21. package/dist/components/empty-state.d.ts +0 -24
  22. package/dist/components/empty-state.js +0 -37
  23. package/dist/components/error-state.d.ts +0 -20
  24. package/dist/components/error-state.js +0 -145
  25. package/dist/components/external-browser-handoff.d.ts +0 -27
  26. package/dist/components/external-browser-handoff.js +0 -306
  27. package/dist/components/info-popover.d.ts +0 -23
  28. package/dist/components/info-popover.js +0 -158
  29. package/dist/components/month-calendar-header.d.ts +0 -65
  30. package/dist/components/month-calendar-header.js +0 -337
  31. package/dist/components/month-grid.d.ts +0 -59
  32. package/dist/components/month-grid.js +0 -301
  33. package/dist/components/notification-bell.d.ts +0 -50
  34. package/dist/components/notification-bell.js +0 -338
  35. package/dist/components/notification-panel.d.ts +0 -56
  36. package/dist/components/notification-panel.js +0 -281
  37. package/dist/components/open-in-browser-cta.d.ts +0 -22
  38. package/dist/components/open-in-browser-cta.js +0 -61
  39. package/dist/components/pwa-update-control.d.ts +0 -18
  40. package/dist/components/pwa-update-control.js +0 -119
  41. package/dist/emoji-picker.d.ts +0 -27
  42. package/dist/emoji-picker.js +0 -283
  43. package/dist/hooks/use-minute-clock.d.ts +0 -23
  44. package/dist/hooks/use-minute-clock.js +0 -147
  45. package/dist/hooks/use-month-transition-direction.d.ts +0 -21
  46. package/dist/hooks/use-month-transition-direction.js +0 -110
  47. package/dist/lib/browser-navigation.d.ts +0 -21
  48. package/dist/lib/browser-navigation.js +0 -42
  49. package/dist/lib/in-app-browser.d.ts +0 -35
  50. package/dist/lib/in-app-browser.js +0 -80
  51. package/dist/lib/month-grid.d.ts +0 -42
  52. package/dist/lib/month-grid.js +0 -74
@@ -1,283 +0,0 @@
1
- "use client";
2
- import { c as _c } from "react/compiler-runtime";
3
- /**
4
- * Isolates the optional `emoji-picker-react` peer from the package root: only apps importing
5
- * `beez-ui/emoji-picker` need it installed.
6
- */
7
- import { lazy, Suspense, useState } from "react";
8
- import { SmilePlusIcon } from "lucide-react";
9
- import { useTheme } from "next-themes";
10
- import { cn } from "./lib/utils.js";
11
- import { Button } from "./components/button.js";
12
- import { Popover, PopoverContent, PopoverTrigger } from "./components/popover.js";
13
- import { PresenceSwap } from "./components/presence-swap.js";
14
- import { BouncingDotsLoader } from "./components/bouncing-dots-loader.js";
15
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
- /** Loads the picker bundle (and its emoji data) only when the popover opens for the first time. */
17
- const LazyEmojiPickerReact = lazy(async () => {
18
- const emojiPickerModule = await import("emoji-picker-react");
19
- const moduleDefault = emojiPickerModule.default;
20
- return { default: "default" in moduleDefault ? moduleDefault.default : moduleDefault };
21
- });
22
- const EMOJI_PICKER_HEIGHT_PX = 360;
23
- /** Fills the popover, which already caps its width to the viewport on phones. */
24
- const EMOJI_PICKER_WIDTH = "100%";
25
- /** Presence key of the placeholder icon shown before an emoji is chosen. */
26
- const EMPTY_SELECTION_KEY = "empty";
27
- const DARK_THEME = "dark";
28
- /**
29
- * Picker options as the string values of the library enums. Only types are imported, so the
30
- * library is not evaluated until the lazy import runs (never during server rendering).
31
- */
32
- const EMOJI_PICKER_OPTIONS = {
33
- emojiStyle: "apple",
34
- defaultSkinTone: "neutral",
35
- skinTonePickerLocation: "SEARCH",
36
- suggestedEmojisMode: "recent",
37
- darkTheme: "dark",
38
- lightTheme: "light"
39
- };
40
- const EMOJI_PICKER_DEFAULT_LABELS = {
41
- chooseEmoji: "Elegir emoji",
42
- picker: "Selector de emojis",
43
- searchPlaceholder: "Buscar emoji",
44
- loading: "Cargando emojis..."
45
- };
46
- /**
47
- * Renders an outline trigger showing the current emoji that opens a searchable emoji popover. The
48
- * chosen emoji swaps in with a short cross-fade; the picker follows the resolved color theme.
49
- * @param props - Current value, change handler, label and optional copy.
50
- * @returns The labelled emoji picker.
51
- */
52
- export function EmojiPicker(t0) {
53
- const $ = _c(46);
54
- const { value, onChange, label, isLabelVisuallyHidden: t1, disabled: t2, labels, className } = t0;
55
- const isLabelVisuallyHidden = t1 === undefined ? false : t1;
56
- const disabled = t2 === undefined ? false : t2;
57
- let t3;
58
- if ($[0] !== labels) {
59
- t3 = {
60
- ...EMOJI_PICKER_DEFAULT_LABELS,
61
- ...labels
62
- };
63
- $[0] = labels;
64
- $[1] = t3;
65
- } else {
66
- t3 = $[1];
67
- }
68
- const resolvedLabels = t3;
69
- const [isOpen, setIsOpen] = useState(false);
70
- const { resolvedTheme } = useTheme();
71
- let t4;
72
- if ($[2] !== value) {
73
- t4 = value.trim();
74
- $[2] = value;
75
- $[3] = t4;
76
- } else {
77
- t4 = $[3];
78
- }
79
- const selectedEmoji = t4;
80
- let t5;
81
- if ($[4] !== onChange) {
82
- t5 = (emojiData) => {
83
- onChange(emojiData.emoji);
84
- setIsOpen(false);
85
- };
86
- $[4] = onChange;
87
- $[5] = t5;
88
- } else {
89
- t5 = $[5];
90
- }
91
- const handleEmojiClick = t5;
92
- let t6;
93
- if ($[6] !== className) {
94
- t6 = cn("grid w-fit min-w-0 gap-[0.4rem]", className);
95
- $[6] = className;
96
- $[7] = t6;
97
- } else {
98
- t6 = $[7];
99
- }
100
- const t7 = isLabelVisuallyHidden && "sr-only";
101
- let t8;
102
- if ($[8] !== t7) {
103
- t8 = cn("text-[0.82rem] leading-tight font-semibold text-muted-foreground", t7);
104
- $[8] = t7;
105
- $[9] = t8;
106
- } else {
107
- t8 = $[9];
108
- }
109
- let t9;
110
- if ($[10] !== label || $[11] !== t8) {
111
- t9 = /* @__PURE__ */ _jsx("span", {
112
- className: t8,
113
- children: label
114
- });
115
- $[10] = label;
116
- $[11] = t8;
117
- $[12] = t9;
118
- } else {
119
- t9 = $[12];
120
- }
121
- const t10 = selectedEmoji || EMPTY_SELECTION_KEY;
122
- let t11;
123
- if ($[13] !== selectedEmoji) {
124
- t11 = selectedEmoji || /* @__PURE__ */ _jsx(SmilePlusIcon, {});
125
- $[13] = selectedEmoji;
126
- $[14] = t11;
127
- } else {
128
- t11 = $[14];
129
- }
130
- let t12;
131
- if ($[15] !== t11) {
132
- t12 = /* @__PURE__ */ _jsx("span", {
133
- "aria-hidden": true,
134
- children: t11
135
- });
136
- $[15] = t11;
137
- $[16] = t12;
138
- } else {
139
- t12 = $[16];
140
- }
141
- let t13;
142
- if ($[17] !== t10 || $[18] !== t12) {
143
- t13 = /* @__PURE__ */ _jsx(PresenceSwap, {
144
- as: "span",
145
- className: "inline-grid min-h-5 min-w-5 place-items-center text-[1.15rem] leading-none",
146
- mode: "popLayout",
147
- presenceKey: t10,
148
- children: t12
149
- });
150
- $[17] = t10;
151
- $[18] = t12;
152
- $[19] = t13;
153
- } else {
154
- t13 = $[19];
155
- }
156
- let t14;
157
- if ($[20] !== disabled || $[21] !== resolvedLabels.chooseEmoji || $[22] !== t13) {
158
- t14 = /* @__PURE__ */ _jsx(PopoverTrigger, {
159
- asChild: true,
160
- children: /* @__PURE__ */ _jsx(Button, {
161
- "aria-label": resolvedLabels.chooseEmoji,
162
- className: "min-h-[2.7rem] w-fit min-w-[3.25rem] justify-center gap-[0.55rem] border-border bg-background text-[0.95rem] text-foreground data-[state=open]:border-ring data-[state=open]:shadow-[0_0_0_2px_color-mix(in_srgb,var(--ring)_18%,transparent)]",
163
- disabled,
164
- type: "button",
165
- variant: "outline",
166
- children: t13
167
- })
168
- });
169
- $[20] = disabled;
170
- $[21] = resolvedLabels.chooseEmoji;
171
- $[22] = t13;
172
- $[23] = t14;
173
- } else {
174
- t14 = $[23];
175
- }
176
- let t15;
177
- if ($[24] !== resolvedLabels.loading) {
178
- t15 = /* @__PURE__ */ _jsx(BouncingDotsLoader, {
179
- className: "py-10",
180
- label: resolvedLabels.loading,
181
- size: "sm"
182
- });
183
- $[24] = resolvedLabels.loading;
184
- $[25] = t15;
185
- } else {
186
- t15 = $[25];
187
- }
188
- let t16;
189
- if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
190
- t16 = [];
191
- $[26] = t16;
192
- } else {
193
- t16 = $[26];
194
- }
195
- let t17;
196
- if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
197
- t17 = { showPreview: false };
198
- $[27] = t17;
199
- } else {
200
- t17 = $[27];
201
- }
202
- const t18 = resolvedTheme === DARK_THEME ? EMOJI_PICKER_OPTIONS.darkTheme : EMOJI_PICKER_OPTIONS.lightTheme;
203
- let t19;
204
- if ($[28] !== handleEmojiClick || $[29] !== resolvedLabels.searchPlaceholder || $[30] !== t18) {
205
- t19 = /* @__PURE__ */ _jsx(LazyEmojiPickerReact, {
206
- autoFocusSearch: true,
207
- customEmojis: t16,
208
- defaultSkinTone: EMOJI_PICKER_OPTIONS.defaultSkinTone,
209
- emojiStyle: EMOJI_PICKER_OPTIONS.emojiStyle,
210
- height: EMOJI_PICKER_HEIGHT_PX,
211
- lazyLoadEmojis: true,
212
- onEmojiClick: handleEmojiClick,
213
- previewConfig: t17,
214
- searchPlaceholder: resolvedLabels.searchPlaceholder,
215
- skinTonePickerLocation: EMOJI_PICKER_OPTIONS.skinTonePickerLocation,
216
- suggestedEmojisMode: EMOJI_PICKER_OPTIONS.suggestedEmojisMode,
217
- theme: t18,
218
- width: EMOJI_PICKER_WIDTH
219
- });
220
- $[28] = handleEmojiClick;
221
- $[29] = resolvedLabels.searchPlaceholder;
222
- $[30] = t18;
223
- $[31] = t19;
224
- } else {
225
- t19 = $[31];
226
- }
227
- let t20;
228
- if ($[32] !== t15 || $[33] !== t19) {
229
- t20 = /* @__PURE__ */ _jsx(Suspense, {
230
- fallback: t15,
231
- children: t19
232
- });
233
- $[32] = t15;
234
- $[33] = t19;
235
- $[34] = t20;
236
- } else {
237
- t20 = $[34];
238
- }
239
- let t21;
240
- if ($[35] !== resolvedLabels.picker || $[36] !== t20) {
241
- t21 = /* @__PURE__ */ _jsx(PopoverContent, {
242
- "data-slot": "emoji-picker-content",
243
- align: "start",
244
- "aria-label": resolvedLabels.picker,
245
- className: "w-[min(20rem,calc(100vw-2rem))] overflow-hidden p-0",
246
- children: t20
247
- });
248
- $[35] = resolvedLabels.picker;
249
- $[36] = t20;
250
- $[37] = t21;
251
- } else {
252
- t21 = $[37];
253
- }
254
- let t22;
255
- if ($[38] !== isOpen || $[39] !== t14 || $[40] !== t21) {
256
- t22 = /* @__PURE__ */ _jsxs(Popover, {
257
- open: isOpen,
258
- onOpenChange: setIsOpen,
259
- children: [t14, t21]
260
- });
261
- $[38] = isOpen;
262
- $[39] = t14;
263
- $[40] = t21;
264
- $[41] = t22;
265
- } else {
266
- t22 = $[41];
267
- }
268
- let t23;
269
- if ($[42] !== t22 || $[43] !== t6 || $[44] !== t9) {
270
- t23 = /* @__PURE__ */ _jsxs("div", {
271
- "data-slot": "emoji-picker",
272
- className: t6,
273
- children: [t9, t22]
274
- });
275
- $[42] = t22;
276
- $[43] = t6;
277
- $[44] = t9;
278
- $[45] = t23;
279
- } else {
280
- t23 = $[45];
281
- }
282
- return t23;
283
- }
@@ -1,23 +0,0 @@
1
- /**
2
- * Moves the clock forward to `instantTime` (epoch ms) and re-renders every
3
- * consumer. Earlier instants are ignored, so the clock never goes back. Use
4
- * it when a scheduled boundary is reached or the server proved that an
5
- * instant already passed (for example an occurrence it reported as ended
6
- * while the local clock still lagged behind).
7
- *
8
- * @param instantTime - Instant the clock must reach, in epoch milliseconds.
9
- */
10
- export declare function advanceMinuteClockTo(instantTime: number): void;
11
- /**
12
- * Current time, refreshed on every minute boundary and at the exact instant of
13
- * each wake-up time that falls before the next boundary. Between boundaries it
14
- * holds the minute (or the last wake-up instant) it settled on. Returns null
15
- * during server rendering and hydration so time-dependent UI never produces a
16
- * server/client markup mismatch; it settles to a number right after mount.
17
- *
18
- * @param wakeUpTimes - Instants (epoch ms) where time-dependent UI changes,
19
- * such as the end of an occurrence on screen; the clock re-renders exactly
20
- * then instead of waiting for the next minute.
21
- * @returns The clock value in epoch milliseconds, or null before hydration.
22
- */
23
- export declare function useMinuteClock(wakeUpTimes?: readonly number[]): number | null;
@@ -1,147 +0,0 @@
1
- "use client";
2
- import { c as _c } from "react/compiler-runtime";
3
- /** Shares one minute-aligned clock across components, hydration-safe and with exact wake-ups. */
4
- import { useEffect, useSyncExternalStore } from "react";
5
- const MILLISECONDS_PER_MINUTE = 6e4;
6
- const NO_WAKE_UP_TIMES = [];
7
- const clockListeners = new Set();
8
- /**
9
- * Latest instant the clock settled on. It only moves forward: minute ticks
10
- * raise it to the current minute and wake-ups (or an instant the server
11
- * confirmed) raise it to that exact instant. Null while nobody observes the
12
- * clock, so a later mount starts again from the real time.
13
- */
14
- let settledClockTime = null;
15
- let minuteTickTimeoutId = null;
16
- function truncateToMinute(time) {
17
- return Math.floor(time / MILLISECONDS_PER_MINUTE) * MILLISECONDS_PER_MINUTE;
18
- }
19
- function notifyClockListeners() {
20
- for (const listener of clockListeners) {
21
- listener();
22
- }
23
- }
24
- /**
25
- * Moves the clock forward to `instantTime` (epoch ms) and re-renders every
26
- * consumer. Earlier instants are ignored, so the clock never goes back. Use
27
- * it when a scheduled boundary is reached or the server proved that an
28
- * instant already passed (for example an occurrence it reported as ended
29
- * while the local clock still lagged behind).
30
- *
31
- * @param instantTime - Instant the clock must reach, in epoch milliseconds.
32
- */
33
- export function advanceMinuteClockTo(instantTime) {
34
- if (settledClockTime !== null && instantTime <= settledClockTime) {
35
- return;
36
- }
37
- settledClockTime = instantTime;
38
- notifyClockListeners();
39
- }
40
- /**
41
- * Schedules the next tick on the next minute boundary. Each tick measures the
42
- * remaining time again instead of relying on a fixed interval, so the clock
43
- * never lags by the mount offset and a timer that fires a little early or
44
- * late re-aligns on the following boundary.
45
- */
46
- function scheduleNextMinuteTick() {
47
- const currentTime = Date.now();
48
- const millisecondsToNextMinute = truncateToMinute(currentTime) + MILLISECONDS_PER_MINUTE - currentTime;
49
- minuteTickTimeoutId = window.setTimeout(() => {
50
- scheduleNextMinuteTick();
51
- notifyClockListeners();
52
- }, millisecondsToNextMinute);
53
- }
54
- function stopMinuteTicks() {
55
- if (minuteTickTimeoutId !== null) {
56
- window.clearTimeout(minuteTickTimeoutId);
57
- minuteTickTimeoutId = null;
58
- }
59
- }
60
- function subscribe(onStoreChange) {
61
- clockListeners.add(onStoreChange);
62
- if (clockListeners.size === 1) {
63
- scheduleNextMinuteTick();
64
- }
65
- return () => {
66
- clockListeners.delete(onStoreChange);
67
- if (clockListeners.size === 0) {
68
- stopMinuteTicks();
69
- settledClockTime = null;
70
- }
71
- };
72
- }
73
- function getSnapshot() {
74
- const currentMinuteTime = truncateToMinute(Date.now());
75
- if (settledClockTime === null || currentMinuteTime > settledClockTime) {
76
- settledClockTime = currentMinuteTime;
77
- }
78
- return settledClockTime;
79
- }
80
- function getServerSnapshot() {
81
- return null;
82
- }
83
- function findNextWakeUpTime(wakeUpTimes, nowTime) {
84
- let nextWakeUpTime = null;
85
- for (const wakeUpTime of wakeUpTimes) {
86
- if (wakeUpTime > nowTime && (nextWakeUpTime === null || wakeUpTime < nextWakeUpTime)) {
87
- nextWakeUpTime = wakeUpTime;
88
- }
89
- }
90
- return nextWakeUpTime;
91
- }
92
- /**
93
- * Current time, refreshed on every minute boundary and at the exact instant of
94
- * each wake-up time that falls before the next boundary. Between boundaries it
95
- * holds the minute (or the last wake-up instant) it settled on. Returns null
96
- * during server rendering and hydration so time-dependent UI never produces a
97
- * server/client markup mismatch; it settles to a number right after mount.
98
- *
99
- * @param wakeUpTimes - Instants (epoch ms) where time-dependent UI changes,
100
- * such as the end of an occurrence on screen; the clock re-renders exactly
101
- * then instead of waiting for the next minute.
102
- * @returns The clock value in epoch milliseconds, or null before hydration.
103
- */
104
- export function useMinuteClock(t0) {
105
- const $ = _c(8);
106
- const wakeUpTimes = t0 === undefined ? NO_WAKE_UP_TIMES : t0;
107
- const nowTime = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
108
- let t1;
109
- if ($[0] !== nowTime || $[1] !== wakeUpTimes) {
110
- t1 = nowTime === null ? null : findNextWakeUpTime(wakeUpTimes, nowTime);
111
- $[0] = nowTime;
112
- $[1] = wakeUpTimes;
113
- $[2] = t1;
114
- } else {
115
- t1 = $[2];
116
- }
117
- const nextWakeUpTime = t1;
118
- let t2;
119
- if ($[3] !== nextWakeUpTime) {
120
- t2 = () => {
121
- if (nextWakeUpTime === null) {
122
- return;
123
- }
124
- const millisecondsToWakeUp = nextWakeUpTime - Date.now();
125
- if (millisecondsToWakeUp > MILLISECONDS_PER_MINUTE) {
126
- return;
127
- }
128
- const wakeUpTimeoutId = window.setTimeout(() => advanceMinuteClockTo(nextWakeUpTime), Math.max(0, millisecondsToWakeUp));
129
- return () => window.clearTimeout(wakeUpTimeoutId);
130
- };
131
- $[3] = nextWakeUpTime;
132
- $[4] = t2;
133
- } else {
134
- t2 = $[4];
135
- }
136
- let t3;
137
- if ($[5] !== nextWakeUpTime || $[6] !== nowTime) {
138
- t3 = [nextWakeUpTime, nowTime];
139
- $[5] = nextWakeUpTime;
140
- $[6] = nowTime;
141
- $[7] = t3;
142
- } else {
143
- t3 = $[7];
144
- }
145
- useEffect(t2, t3);
146
- return nowTime;
147
- }
@@ -1,21 +0,0 @@
1
- export declare const MONTH_TRANSITION_DIRECTION: {
2
- readonly next: "next";
3
- readonly none: "none";
4
- readonly previous: "previous";
5
- };
6
- export type MonthTransitionDirection = (typeof MONTH_TRANSITION_DIRECTION)[keyof typeof MONTH_TRANSITION_DIRECTION];
7
- /**
8
- * Compares two `YYYY-MM` keys, which sort chronologically as plain strings.
9
- * @param fromMonth - Month shown before, or null when there is none.
10
- * @param toMonth - Month shown now.
11
- * @returns `next` or `previous` when the month changed, `none` otherwise.
12
- */
13
- export declare function resolveMonthTransitionDirection(fromMonth: string | null, toMonth: string): MonthTransitionDirection;
14
- /**
15
- * Direction of the latest month change of a calendar: a new `month` on the same instance, or a
16
- * fresh mount right after another month of the same calendar was on screen.
17
- * @param scopeKey - Calendar identity, so different calendars do not orient each other.
18
- * @param month - Visible `YYYY-MM` month.
19
- * @returns Direction to animate the month entrance with.
20
- */
21
- export declare function useMonthTransitionDirection(scopeKey: string, month: string): MonthTransitionDirection;
@@ -1,110 +0,0 @@
1
- "use client";
2
- import { c as _c } from "react/compiler-runtime";
3
- /** Tracks which way the visible month moved, to orient month entrance animations. */
4
- import { useEffect, useState } from "react";
5
- export const MONTH_TRANSITION_DIRECTION = {
6
- next: "next",
7
- none: "none",
8
- previous: "previous"
9
- };
10
- /**
11
- * How long, in milliseconds, the last shown month still orients the next calendar mount. Month
12
- * links may remount the calendar behind a route loading state, so the memory must outlive that
13
- * gap but not a later, unrelated visit.
14
- */
15
- const SHOWN_MONTH_MEMORY_MS = 5e3;
16
- /**
17
- * Last month shown by a calendar in this tab. It is written only from effects, which never run on
18
- * the server, so server renders and the hydration render always read `null` and match.
19
- */
20
- let lastShownMonth = null;
21
- /**
22
- * Records the month a calendar is showing.
23
- * @param scopeKey - Calendar identity, such as a group or resource id.
24
- * @param month - Visible `YYYY-MM` month.
25
- */
26
- function rememberShownMonth(scopeKey, month) {
27
- lastShownMonth = {
28
- month,
29
- shownAt: Date.now(),
30
- scopeKey
31
- };
32
- }
33
- /**
34
- * Reads the month recently shown by the same calendar, if still within the memory window.
35
- * @param scopeKey - Calendar identity.
36
- * @returns The month, or null.
37
- */
38
- function readRecentlyShownMonth(scopeKey) {
39
- if (lastShownMonth === null || lastShownMonth.scopeKey !== scopeKey || Date.now() - lastShownMonth.shownAt > SHOWN_MONTH_MEMORY_MS) {
40
- return null;
41
- }
42
- return lastShownMonth.month;
43
- }
44
- /**
45
- * Compares two `YYYY-MM` keys, which sort chronologically as plain strings.
46
- * @param fromMonth - Month shown before, or null when there is none.
47
- * @param toMonth - Month shown now.
48
- * @returns `next` or `previous` when the month changed, `none` otherwise.
49
- */
50
- export function resolveMonthTransitionDirection(fromMonth, toMonth) {
51
- if (fromMonth === null || fromMonth === toMonth) return MONTH_TRANSITION_DIRECTION.none;
52
- return toMonth > fromMonth ? MONTH_TRANSITION_DIRECTION.next : MONTH_TRANSITION_DIRECTION.previous;
53
- }
54
- /**
55
- * Direction of the latest month change of a calendar: a new `month` on the same instance, or a
56
- * fresh mount right after another month of the same calendar was on screen.
57
- * @param scopeKey - Calendar identity, so different calendars do not orient each other.
58
- * @param month - Visible `YYYY-MM` month.
59
- * @returns Direction to animate the month entrance with.
60
- */
61
- export function useMonthTransitionDirection(scopeKey, month) {
62
- const $ = _c(11);
63
- let t0;
64
- if ($[0] !== month || $[1] !== scopeKey) {
65
- t0 = () => ({
66
- direction: resolveMonthTransitionDirection(readRecentlyShownMonth(scopeKey), month),
67
- month
68
- });
69
- $[0] = month;
70
- $[1] = scopeKey;
71
- $[2] = t0;
72
- } else {
73
- t0 = $[2];
74
- }
75
- const [transition, setTransition] = useState(t0);
76
- if (transition.month !== month) {
77
- setTransition({
78
- direction: resolveMonthTransitionDirection(transition.month, month),
79
- month
80
- });
81
- }
82
- let t1;
83
- let t2;
84
- if ($[3] !== month || $[4] !== scopeKey) {
85
- t1 = () => {
86
- rememberShownMonth(scopeKey, month);
87
- return () => rememberShownMonth(scopeKey, month);
88
- };
89
- t2 = [month, scopeKey];
90
- $[3] = month;
91
- $[4] = scopeKey;
92
- $[5] = t1;
93
- $[6] = t2;
94
- } else {
95
- t1 = $[5];
96
- t2 = $[6];
97
- }
98
- useEffect(t1, t2);
99
- let t3;
100
- if ($[7] !== month || $[8] !== transition.direction || $[9] !== transition.month) {
101
- t3 = transition.month === month ? transition.direction : resolveMonthTransitionDirection(transition.month, month);
102
- $[7] = month;
103
- $[8] = transition.direction;
104
- $[9] = transition.month;
105
- $[10] = t3;
106
- } else {
107
- t3 = $[10];
108
- }
109
- return t3;
110
- }
@@ -1,21 +0,0 @@
1
- /** Browser navigation helpers: full navigations and address-bar updates without history entries. */
2
- export declare function reloadCurrentPage(): void;
3
- export declare function navigateToUrl(url: string): void;
4
- /**
5
- * Sets (or removes, with `null`) query parameters of the current URL in a
6
- * single `history.replaceState`, which the Next.js router tracks, so the
7
- * address bar reflects UI state without a navigation or a history entry.
8
- * Parameters not listed keep their current value, as do the path and hash.
9
- *
10
- * @param searchParams - New value per query parameter name, or null to remove it.
11
- */
12
- export declare function replaceCurrentUrlSearchParams(searchParams: Readonly<Record<string, string | null>>): void;
13
- /**
14
- * Replaces every value of one repeatable query parameter of the current URL
15
- * (an empty list removes it) through `history.replaceState`, like
16
- * {@link replaceCurrentUrlSearchParams}.
17
- *
18
- * @param name - Query parameter name.
19
- * @param values - New values, in order.
20
- */
21
- export declare function replaceCurrentUrlSearchParamValues(name: string, values: readonly string[]): void;
@@ -1,42 +0,0 @@
1
- /** Browser navigation helpers: full navigations and address-bar updates without history entries. */
2
- export function reloadCurrentPage() {
3
- window.location.reload();
4
- }
5
- export function navigateToUrl(url) {
6
- window.location.href = url;
7
- }
8
- /**
9
- * Sets (or removes, with `null`) query parameters of the current URL in a
10
- * single `history.replaceState`, which the Next.js router tracks, so the
11
- * address bar reflects UI state without a navigation or a history entry.
12
- * Parameters not listed keep their current value, as do the path and hash.
13
- *
14
- * @param searchParams - New value per query parameter name, or null to remove it.
15
- */
16
- export function replaceCurrentUrlSearchParams(searchParams) {
17
- const url = new URL(window.location.href);
18
- for (const [name, value] of Object.entries(searchParams)) {
19
- if (value === null) {
20
- url.searchParams.delete(name);
21
- } else {
22
- url.searchParams.set(name, value);
23
- }
24
- }
25
- window.history.replaceState(null, "", url.pathname + url.search + url.hash);
26
- }
27
- /**
28
- * Replaces every value of one repeatable query parameter of the current URL
29
- * (an empty list removes it) through `history.replaceState`, like
30
- * {@link replaceCurrentUrlSearchParams}.
31
- *
32
- * @param name - Query parameter name.
33
- * @param values - New values, in order.
34
- */
35
- export function replaceCurrentUrlSearchParamValues(name, values) {
36
- const url = new URL(window.location.href);
37
- url.searchParams.delete(name);
38
- for (const value of values) {
39
- url.searchParams.append(name, value);
40
- }
41
- window.history.replaceState(null, "", url.pathname + url.search + url.hash);
42
- }
@@ -1,35 +0,0 @@
1
- /** Detects in-app browsers and builds deep links that reopen a URL in the default browser. */
2
- export type InAppBrowserDetectionResult = {
3
- isInAppBrowser: boolean;
4
- isIos: boolean;
5
- isAndroid: boolean;
6
- isMercadoPago: boolean;
7
- };
8
- /**
9
- * Classifies a user agent: in-app browsers (social, messaging and payment apps, iOS web views)
10
- * often block sign-in popups or lose sessions, so callers can offer to continue elsewhere.
11
- * Safe on the server: pass the request `User-Agent` header.
12
- *
13
- * @param userAgent - User agent string, or nothing when unknown.
14
- * @returns Whether it is an in-app browser and which platform it runs on.
15
- */
16
- export declare function detectInAppBrowser(userAgent: string | null | undefined): InAppBrowserDetectionResult;
17
- export declare const EXTERNAL_BROWSER_PLATFORM: {
18
- readonly android: "android";
19
- readonly ios: "ios";
20
- };
21
- export type ExternalBrowserPlatform = (typeof EXTERNAL_BROWSER_PLATFORM)[keyof typeof EXTERNAL_BROWSER_PLATFORM];
22
- type BuildExternalBrowserUrlInput = {
23
- platform: ExternalBrowserPlatform;
24
- targetHttpsUrl: string;
25
- };
26
- /**
27
- * Builds a deep link that asks the OS to open the given https URL in the
28
- * user's default external browser instead of the in-app browser.
29
- *
30
- * @param input - Target URL and detected platform.
31
- * @returns A deep-link URL for iOS Safari or Android Chrome. Returns null when
32
- * the target URL does not use the https scheme.
33
- */
34
- export declare function buildExternalBrowserUrl({ platform, targetHttpsUrl, }: BuildExternalBrowserUrlInput): string | null;
35
- export {};