rei-kit 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_plugin-vue_export-helper-3AcMDTtW.js +339 -0
- package/dist/_plugin-vue_export-helper-3AcMDTtW.js.map +1 -0
- package/dist/app/AuthShell.vue.d.ts +29 -0
- package/dist/app/index.d.ts +16 -0
- package/dist/app/use-tab-transition.d.ts +52 -0
- package/dist/app/use-theme-sync.d.ts +23 -0
- package/dist/app.js +140 -0
- package/dist/app.js.map +1 -0
- package/dist/index.js +9 -433
- package/dist/index.js.map +1 -1
- package/dist/pwa/InstallPrompt.vue.d.ts +26 -0
- package/dist/pwa/UpdatePrompt.vue.d.ts +48 -0
- package/dist/pwa/index.d.ts +15 -0
- package/dist/pwa/use-install.d.ts +41 -0
- package/dist/pwa/use-snooze.d.ts +22 -0
- package/dist/pwa.js +236 -0
- package/dist/pwa.js.map +1 -0
- package/dist/styles.css +69 -0
- package/dist/use-theme-_MnaDD5v.js +94 -0
- package/dist/use-theme-_MnaDD5v.js.map +1 -0
- package/package.json +9 -1
package/dist/app.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { a as useTheme, n as isThemePreference } from "./use-theme-_MnaDD5v.js";
|
|
2
|
+
import { createCommentVNode, createElementBlock, createElementVNode, defineComponent, openBlock, readonly, ref, renderSlot, watch } from "vue";
|
|
3
|
+
//#region src/app/AuthShell.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var _hoisted_1 = { class: "flex min-h-0 w-full flex-1 flex-col items-center gap-8 overflow-y-auto px-6 pt-10 pb-10" };
|
|
5
|
+
var _hoisted_2 = { class: "w-full max-w-[22rem]" };
|
|
6
|
+
var _hoisted_3 = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "mt-auto"
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/app/AuthShell.vue
|
|
12
|
+
var AuthShell_default = /* @__PURE__ */ defineComponent({
|
|
13
|
+
__name: "AuthShell",
|
|
14
|
+
setup(__props) {
|
|
15
|
+
/**
|
|
16
|
+
* The frame every sign-in screen sits in.
|
|
17
|
+
*
|
|
18
|
+
* A brand mark, a narrow column, and the language links pinned to the bottom.
|
|
19
|
+
* The two phone apps had this file character for character — thirty-seven
|
|
20
|
+
* lines, no difference at all — and the only thing either would want to change
|
|
21
|
+
* is what goes in the slots.
|
|
22
|
+
*
|
|
23
|
+
* The language links matter more than they look. Sign-in is the first screen a
|
|
24
|
+
* new user sees and Settings is behind it, so without a way to switch here,
|
|
25
|
+
* somebody who does not read the browser's language cannot get to one.
|
|
26
|
+
*/
|
|
27
|
+
return (_ctx, _cache) => {
|
|
28
|
+
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
29
|
+
renderSlot(_ctx.$slots, "brand"),
|
|
30
|
+
createElementVNode("main", _hoisted_2, [renderSlot(_ctx.$slots, "default")]),
|
|
31
|
+
_ctx.$slots.foot ? (openBlock(), createElementBlock("div", _hoisted_3, [renderSlot(_ctx.$slots, "foot")])) : createCommentVNode("", true)
|
|
32
|
+
]);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/app/use-tab-transition.ts
|
|
38
|
+
/**
|
|
39
|
+
* Which way a tabbed app is moving.
|
|
40
|
+
*
|
|
41
|
+
* A phone app slides sideways between its tabs, and the direction has to come
|
|
42
|
+
* from somewhere: going from the second tab to the fourth is forward, the
|
|
43
|
+
* other way is back, and arriving from nowhere is neither. That is index
|
|
44
|
+
* arithmetic over the tab order, and it was written twice, identically, in the
|
|
45
|
+
* two phone apps this kit came from — thirty-four lines each, byte for byte
|
|
46
|
+
* the same.
|
|
47
|
+
*
|
|
48
|
+
* Generic over the tab key, so the app keeps its own union and the kit never
|
|
49
|
+
* learns what a tab is called.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* // shared/lib/tabs.ts
|
|
54
|
+
* export const tabs = createTabTransition(['today', 'week', 'year', 'profile'] as const)
|
|
55
|
+
*
|
|
56
|
+
* // the router guard
|
|
57
|
+
* router.afterEach((to, from) => tabs.resolve(to.meta.tab, from.meta.tab))
|
|
58
|
+
*
|
|
59
|
+
* // App.vue
|
|
60
|
+
* const name = computed(() =>
|
|
61
|
+
* tabs.direction.value === 'none' ? '' : `slide-${tabs.direction.value}`,
|
|
62
|
+
* )
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* The `slide-forward-*` and `slide-backward-*` classes those names refer to
|
|
66
|
+
* ship in `rei-kit/shell/mobile.css`.
|
|
67
|
+
*/
|
|
68
|
+
function createTabTransition(order) {
|
|
69
|
+
const direction = ref("none");
|
|
70
|
+
let override = null;
|
|
71
|
+
return {
|
|
72
|
+
/** Direction of the current tab change. Read by the route transition. */
|
|
73
|
+
direction: readonly(direction),
|
|
74
|
+
/**
|
|
75
|
+
* Resolves the direction for a navigation. Call once per route change.
|
|
76
|
+
*
|
|
77
|
+
* @param to - Tab being entered, if the route has one.
|
|
78
|
+
* @param from - Tab being left, if the route had one.
|
|
79
|
+
*/
|
|
80
|
+
resolve(to, from) {
|
|
81
|
+
if (override) {
|
|
82
|
+
direction.value = override;
|
|
83
|
+
override = null;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!to || !from || to === from) {
|
|
87
|
+
direction.value = "none";
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
direction.value = order.indexOf(to) > order.indexOf(from) ? "forward" : "backward";
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Forces the next navigation's direction, whatever the indices say.
|
|
94
|
+
*
|
|
95
|
+
* For the navigations that are not a tab change at heart: going back from
|
|
96
|
+
* a detail screen, or being sent to sign-in. Without it, leaving a detail
|
|
97
|
+
* page under the fourth tab for the first tab slides backward, which is
|
|
98
|
+
* right, and arriving there slides forward, which is not.
|
|
99
|
+
*/
|
|
100
|
+
force(next) {
|
|
101
|
+
override = next;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/app/use-theme-sync.ts
|
|
107
|
+
/**
|
|
108
|
+
* Adopts the theme stored on the account, once, as soon as it arrives.
|
|
109
|
+
*
|
|
110
|
+
* Two things make this worth a component rather than four lines at a call
|
|
111
|
+
* site, and both are about *once*.
|
|
112
|
+
*
|
|
113
|
+
* It has to run at the app root rather than on the settings screen, or a user
|
|
114
|
+
* on a fresh device keeps the system theme until they happen to open Profile.
|
|
115
|
+
* And it has to run once and never again, or a later refetch of the profile
|
|
116
|
+
* undoes a choice the user has just made locally — the theme flips back under
|
|
117
|
+
* them a second after they set it, which reads as the app fighting them.
|
|
118
|
+
*
|
|
119
|
+
* The source is a ref rather than a query, so the kit never learns what a
|
|
120
|
+
* profile is or where it came from.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* const { data: profile } = useProfile()
|
|
125
|
+
* useThemeSync(computed(() => profile.value?.theme))
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
function useThemeSync(stored) {
|
|
129
|
+
const theme = useTheme();
|
|
130
|
+
let adopted = false;
|
|
131
|
+
watch(stored, (next) => {
|
|
132
|
+
if (adopted || next === null || next === void 0) return;
|
|
133
|
+
adopted = true;
|
|
134
|
+
if (isThemePreference(next) && next !== theme.value) theme.value = next;
|
|
135
|
+
}, { immediate: true });
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { AuthShell_default as AuthShell, createTabTransition, useThemeSync };
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","names":["$slots"],"sources":["../src/app/AuthShell.vue","../src/app/AuthShell.vue","../src/app/use-tab-transition.ts","../src/app/use-theme-sync.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The frame every sign-in screen sits in.\n *\n * A brand mark, a narrow column, and the language links pinned to the bottom.\n * The two phone apps had this file character for character — thirty-seven\n * lines, no difference at all — and the only thing either would want to change\n * is what goes in the slots.\n *\n * The language links matter more than they look. Sign-in is the first screen a\n * new user sees and Settings is behind it, so without a way to switch here,\n * somebody who does not read the browser's language cannot get to one.\n */\ndefineSlots<{\n /** The brand mark. */\n brand?: () => unknown\n /** The form. */\n default: () => unknown\n /** The language links, or anything else that belongs at the foot. */\n foot?: () => unknown\n}>()\n</script>\n\n<template>\n <div\n class=\"flex min-h-0 w-full flex-1 flex-col items-center gap-8 overflow-y-auto px-6 pt-10 pb-10\"\n >\n <slot name=\"brand\" />\n\n <main class=\"w-full max-w-[22rem]\">\n <slot />\n </main>\n\n <div v-if=\"$slots.foot\" class=\"mt-auto\">\n <slot name=\"foot\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * The frame every sign-in screen sits in.\n *\n * A brand mark, a narrow column, and the language links pinned to the bottom.\n * The two phone apps had this file character for character — thirty-seven\n * lines, no difference at all — and the only thing either would want to change\n * is what goes in the slots.\n *\n * The language links matter more than they look. Sign-in is the first screen a\n * new user sees and Settings is behind it, so without a way to switch here,\n * somebody who does not read the browser's language cannot get to one.\n */\ndefineSlots<{\n /** The brand mark. */\n brand?: () => unknown\n /** The form. */\n default: () => unknown\n /** The language links, or anything else that belongs at the foot. */\n foot?: () => unknown\n}>()\n</script>\n\n<template>\n <div\n class=\"flex min-h-0 w-full flex-1 flex-col items-center gap-8 overflow-y-auto px-6 pt-10 pb-10\"\n >\n <slot name=\"brand\" />\n\n <main class=\"w-full max-w-[22rem]\">\n <slot />\n </main>\n\n <div v-if=\"$slots.foot\" class=\"mt-auto\">\n <slot name=\"foot\" />\n </div>\n </div>\n</template>\n","import { readonly, ref } from 'vue'\n\n/** Which way the screens slide during a tab change. */\nexport type SlideDirection = 'forward' | 'backward' | 'none'\n\n/**\n * Which way a tabbed app is moving.\n *\n * A phone app slides sideways between its tabs, and the direction has to come\n * from somewhere: going from the second tab to the fourth is forward, the\n * other way is back, and arriving from nowhere is neither. That is index\n * arithmetic over the tab order, and it was written twice, identically, in the\n * two phone apps this kit came from — thirty-four lines each, byte for byte\n * the same.\n *\n * Generic over the tab key, so the app keeps its own union and the kit never\n * learns what a tab is called.\n *\n * @example\n * ```ts\n * // shared/lib/tabs.ts\n * export const tabs = createTabTransition(['today', 'week', 'year', 'profile'] as const)\n *\n * // the router guard\n * router.afterEach((to, from) => tabs.resolve(to.meta.tab, from.meta.tab))\n *\n * // App.vue\n * const name = computed(() =>\n * tabs.direction.value === 'none' ? '' : `slide-${tabs.direction.value}`,\n * )\n * ```\n *\n * The `slide-forward-*` and `slide-backward-*` classes those names refer to\n * ship in `rei-kit/shell/mobile.css`.\n */\nexport function createTabTransition<K extends string>(order: readonly K[]) {\n const direction = ref<SlideDirection>('none')\n let override: SlideDirection | null = null\n\n return {\n /** Direction of the current tab change. Read by the route transition. */\n direction: readonly(direction),\n\n /**\n * Resolves the direction for a navigation. Call once per route change.\n *\n * @param to - Tab being entered, if the route has one.\n * @param from - Tab being left, if the route had one.\n */\n resolve(to: K | undefined, from: K | undefined): void {\n if (override) {\n direction.value = override\n override = null\n\n return\n }\n\n if (!to || !from || to === from) {\n direction.value = 'none'\n\n return\n }\n\n direction.value = order.indexOf(to) > order.indexOf(from) ? 'forward' : 'backward'\n },\n\n /**\n * Forces the next navigation's direction, whatever the indices say.\n *\n * For the navigations that are not a tab change at heart: going back from\n * a detail screen, or being sent to sign-in. Without it, leaving a detail\n * page under the fourth tab for the first tab slides backward, which is\n * right, and arriving there slides forward, which is not.\n */\n force(next: SlideDirection): void {\n override = next\n },\n }\n}\n","import { watch } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { isThemePreference, useTheme } from '../composables/use-theme'\n\n/**\n * Adopts the theme stored on the account, once, as soon as it arrives.\n *\n * Two things make this worth a component rather than four lines at a call\n * site, and both are about *once*.\n *\n * It has to run at the app root rather than on the settings screen, or a user\n * on a fresh device keeps the system theme until they happen to open Profile.\n * And it has to run once and never again, or a later refetch of the profile\n * undoes a choice the user has just made locally — the theme flips back under\n * them a second after they set it, which reads as the app fighting them.\n *\n * The source is a ref rather than a query, so the kit never learns what a\n * profile is or where it came from.\n *\n * @example\n * ```ts\n * const { data: profile } = useProfile()\n * useThemeSync(computed(() => profile.value?.theme))\n * ```\n */\nexport function useThemeSync(stored: Ref<string | null | undefined>): void {\n const theme = useTheme()\n\n let adopted = false\n\n watch(\n stored,\n (next) => {\n if (adopted || next === null || next === undefined) return\n\n adopted = true\n\n if (isThemePreference(next) && next !== theme.value) {\n theme.value = next\n }\n },\n { immediate: true },\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;GAwBE,OAAA,UAAA,GAAA,mBAYM,OAZN,YAYM;IATJ,WAAqB,KAAA,QAAA,OAAA;IAErB,mBAEO,QAFP,YAEO,CADL,WAAQ,KAAA,QAAA,SAAA,CAAA,CAAA;IAGCA,KAAAA,OAAO,QAAlB,UAAA,GAAA,mBAEM,OAFN,YAEM,CADJ,WAAoB,KAAA,QAAA,MAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEC1B,SAAgB,oBAAsC,OAAqB;CACzE,MAAM,YAAY,IAAoB,MAAM;CAC5C,IAAI,WAAkC;CAEtC,OAAO;;EAEL,WAAW,SAAS,SAAS;;;;;;;EAQ7B,QAAQ,IAAmB,MAA2B;GACpD,IAAI,UAAU;IACZ,UAAU,QAAQ;IAClB,WAAW;IAEX;GACF;GAEA,IAAI,CAAC,MAAM,CAAC,QAAQ,OAAO,MAAM;IAC/B,UAAU,QAAQ;IAElB;GACF;GAEA,UAAU,QAAQ,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,IAAI,IAAI,YAAY;EAC1E;;;;;;;;;EAUA,MAAM,MAA4B;GAChC,WAAW;EACb;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;ACpDA,SAAgB,aAAa,QAA8C;CACzE,MAAM,QAAQ,SAAS;CAEvB,IAAI,UAAU;CAEd,MACE,SACC,SAAS;EACR,IAAI,WAAW,SAAS,QAAQ,SAAS,KAAA,GAAW;EAEpD,UAAU;EAEV,IAAI,kBAAkB,IAAI,KAAK,SAAS,MAAM,OAC5C,MAAM,QAAQ;CAElB,GACA,EAAE,WAAW,KAAK,CACpB;AACF"}
|
package/dist/index.js
CHANGED
|
@@ -1,158 +1,10 @@
|
|
|
1
|
+
import { a as needsIosInstall, c as fromDateKey, d as startOfWeek, f as toDateKey, i as isInstalled, l as lastNDays, n as BaseButton_default, o as addDays, p as todayKey, r as isApplePortable, s as eachDayOfYear, t as _plugin_vue_export_helper_default, u as leadingBlanks } from "./_plugin-vue_export-helper-3AcMDTtW.js";
|
|
1
2
|
import { n as registerErrorMapper, r as toAppError, t as AppError } from "./app-error-DF9cijE0.js";
|
|
3
|
+
import { a as useTheme, i as setThemeStorageKey, n as isThemePreference, r as readStoredTheme, t as applyTheme } from "./use-theme-_MnaDD5v.js";
|
|
2
4
|
import { Fragment, Teleport, Transition, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createStaticVNode, createTextVNode, createVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, onScopeDispose, onUnmounted, openBlock, readonly, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, useModel, vModelCheckbox, vModelDynamic, vModelRadio, vModelSelect, vModelText, watch, watchEffect, withCtx, withDirectives } from "vue";
|
|
3
5
|
import { ArrowDown, ArrowRight, ArrowUp, CheckCircle2, ChevronDown, ChevronRight, Info, TriangleAlert, X, XCircle } from "lucide-vue-next";
|
|
4
6
|
import { RouterLink } from "vue-router";
|
|
5
7
|
import { createI18n } from "vue-i18n";
|
|
6
|
-
//#region src/utils/date.ts
|
|
7
|
-
/**
|
|
8
|
-
* Local calendar-day helpers.
|
|
9
|
-
*
|
|
10
|
-
* Every function is pure and works on `YYYY-MM-DD` keys, the same shape as the
|
|
11
|
-
* `date` columns in Postgres. Nothing here calls `toISOString`: that converts to
|
|
12
|
-
* UTC, so in a UTC+9 timezone every entry made between midnight and 09:00 would
|
|
13
|
-
* be written to the previous day.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Formats a `Date` as a local `YYYY-MM-DD` key.
|
|
17
|
-
*
|
|
18
|
-
* @param date - Any `Date`; only its local year, month and day are read.
|
|
19
|
-
* @returns The calendar day in the runtime's own timezone.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* // 2026-08-23 01:30 in Tokyo
|
|
24
|
-
* toDateKey(new Date()) // '2026-08-23'
|
|
25
|
-
* new Date().toISOString() // '2026-08-22T16:30…' ← the bug
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
function toDateKey(date) {
|
|
29
|
-
return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
30
|
-
}
|
|
31
|
-
/** Today's key in the user's own timezone. */
|
|
32
|
-
function todayKey() {
|
|
33
|
-
return toDateKey(/* @__PURE__ */ new Date());
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Parses a `YYYY-MM-DD` key into a `Date` at local midnight.
|
|
37
|
-
*
|
|
38
|
-
* @param key - A key produced by {@link toDateKey}.
|
|
39
|
-
* @returns Local midnight of that calendar day.
|
|
40
|
-
* @throws If the key is not three numeric parts.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```ts
|
|
44
|
-
* fromDateKey('2026-08-23') // local midnight, correct
|
|
45
|
-
* new Date('2026-08-23') // UTC midnight — shifts a day in some zones
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
function fromDateKey(key) {
|
|
49
|
-
const [year, month, day] = key.split("-").map(Number);
|
|
50
|
-
if (year === void 0 || month === void 0 || day === void 0) throw new Error(`Invalid date key: ${key}`);
|
|
51
|
-
return new Date(year, month - 1, day);
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Shifts a date key by whole calendar days.
|
|
55
|
-
*
|
|
56
|
-
* Uses `setDate`, which is calendar-aware: it rolls over month and year ends,
|
|
57
|
-
* and stays correct across daylight-saving transitions. Adding
|
|
58
|
-
* `days * 86_400_000` milliseconds would not — a DST day is 23 or 25 hours long.
|
|
59
|
-
*
|
|
60
|
-
* @param key - Starting `YYYY-MM-DD` key.
|
|
61
|
-
* @param days - Days to add; negative goes back.
|
|
62
|
-
* @returns The resulting key.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```ts
|
|
66
|
-
* addDays('2026-01-31', 1) // '2026-02-01'
|
|
67
|
-
* addDays('2026-01-01', -1) // '2025-12-31'
|
|
68
|
-
* addDays('2028-02-28', 1) // '2028-02-29' — leap year
|
|
69
|
-
* ```
|
|
70
|
-
*/
|
|
71
|
-
function addDays(key, days) {
|
|
72
|
-
const date = fromDateKey(key);
|
|
73
|
-
date.setDate(date.getDate() + days);
|
|
74
|
-
return toDateKey(date);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* The last `count` days ending today, oldest first.
|
|
78
|
-
*
|
|
79
|
-
* `today` is a parameter so the function stays pure and testable; call sites
|
|
80
|
-
* normally omit it.
|
|
81
|
-
*
|
|
82
|
-
* @param count - How many days to return, including `today`.
|
|
83
|
-
* @param today - End of the range. Defaults to the real today.
|
|
84
|
-
* @returns Keys in ascending order.
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* ```ts
|
|
88
|
-
* lastNDays(3, '2026-08-23') // ['2026-08-21', '2026-08-22', '2026-08-23']
|
|
89
|
-
* ```
|
|
90
|
-
*/
|
|
91
|
-
function lastNDays(count, today = todayKey()) {
|
|
92
|
-
const keys = [];
|
|
93
|
-
for (let offset = count - 1; offset >= 0; offset -= 1) keys.push(addDays(today, -offset));
|
|
94
|
-
return keys;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* The first day of the week containing `key`.
|
|
98
|
-
*
|
|
99
|
-
* The user's preference is a parameter, not a module-level setting: changing it
|
|
100
|
-
* in Profile has to re-render the week grid and the year heatmap immediately,
|
|
101
|
-
* and a global would make that a hidden dependency.
|
|
102
|
-
*
|
|
103
|
-
* @param key - Any day in the week.
|
|
104
|
-
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
105
|
-
* @returns Key of that week's first day.
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* ```ts
|
|
109
|
-
* // 2026-08-23 is a Sunday
|
|
110
|
-
* startOfWeek('2026-08-23', 1) // '2026-08-17' — previous Monday
|
|
111
|
-
* startOfWeek('2026-08-23', 0) // '2026-08-23' — already Sunday
|
|
112
|
-
* ```
|
|
113
|
-
*/
|
|
114
|
-
function startOfWeek(key, weekStartsOn) {
|
|
115
|
-
return addDays(key, -((fromDateKey(key).getDay() - weekStartsOn + 7) % 7));
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Every day of a calendar year, in order.
|
|
119
|
-
*
|
|
120
|
-
* Leap years fall out of the loop for free: it walks day by day until the year
|
|
121
|
-
* rolls over, so February 29 is included when it exists.
|
|
122
|
-
*
|
|
123
|
-
* @param year - Four-digit year.
|
|
124
|
-
* @returns 365 or 366 keys, oldest first.
|
|
125
|
-
*/
|
|
126
|
-
function eachDayOfYear(year) {
|
|
127
|
-
const keys = [];
|
|
128
|
-
const date = new Date(year, 0, 1);
|
|
129
|
-
while (date.getFullYear() === year) {
|
|
130
|
-
keys.push(toDateKey(date));
|
|
131
|
-
date.setDate(date.getDate() + 1);
|
|
132
|
-
}
|
|
133
|
-
return keys;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Empty cells before a block's first day in a seven-row column grid.
|
|
137
|
-
*
|
|
138
|
-
* The grid fills column by column, so the first column is only partly used
|
|
139
|
-
* unless the block starts exactly on the week's first day. An off-by-one here
|
|
140
|
-
* shifts the whole block by a row, so this is unit tested.
|
|
141
|
-
*
|
|
142
|
-
* @param firstDayKey - First day of the block, e.g. `'2026-02-01'`.
|
|
143
|
-
* @param weekStartsOn - 0 for Sunday, 1 for Monday.
|
|
144
|
-
* @returns 0-6 blank cells.
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```ts
|
|
148
|
-
* leadingBlanks('2026-01-01', 1) // 3 — a Thursday, Mon-Wed are blank
|
|
149
|
-
* leadingBlanks('2024-01-01', 1) // 0 — a Monday
|
|
150
|
-
* ```
|
|
151
|
-
*/
|
|
152
|
-
function leadingBlanks(firstDayKey, weekStartsOn) {
|
|
153
|
-
return (fromDateKey(firstDayKey).getDay() - weekStartsOn + 7) % 7;
|
|
154
|
-
}
|
|
155
|
-
//#endregion
|
|
156
8
|
//#region src/utils/format.ts
|
|
157
9
|
/**
|
|
158
10
|
* The locale `Intl` formatting uses.
|
|
@@ -300,129 +152,6 @@ function tapFeedback(duration = 10) {
|
|
|
300
152
|
navigator.vibrate?.(duration);
|
|
301
153
|
}
|
|
302
154
|
//#endregion
|
|
303
|
-
//#region src/utils/platform.ts
|
|
304
|
-
/**
|
|
305
|
-
* Whether the app is running from the Home Screen rather than a browser tab.
|
|
306
|
-
*
|
|
307
|
-
* Two checks because iOS predates the standard one: `display-mode: standalone`
|
|
308
|
-
* is the modern signal, `navigator.standalone` is Safari's own.
|
|
309
|
-
*/
|
|
310
|
-
function isInstalled() {
|
|
311
|
-
if (typeof window === "undefined") return false;
|
|
312
|
-
return window.matchMedia("(display-mode: standalone)").matches || navigator.standalone === true;
|
|
313
|
-
}
|
|
314
|
-
/** iPhone and iPad, including iPadOS reporting itself as a Mac. */
|
|
315
|
-
function isApplePortable() {
|
|
316
|
-
if (typeof window === "undefined") return false;
|
|
317
|
-
return /iPad|iPhone|iPod/.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Whether this device can only receive notifications once the app is installed.
|
|
321
|
-
*
|
|
322
|
-
* Safari on iOS grants notification permission to an installed web app and to
|
|
323
|
-
* nothing else — in a normal tab the request does not even prompt. Telling the
|
|
324
|
-
* user to allow notifications there is asking for something the browser will
|
|
325
|
-
* not offer, so the UI has to say "add to Home Screen" instead.
|
|
326
|
-
*
|
|
327
|
-
* @example
|
|
328
|
-
* ```ts
|
|
329
|
-
* if (needsIosInstall()) // show the Home Screen instruction, not the button
|
|
330
|
-
* ```
|
|
331
|
-
*/
|
|
332
|
-
function needsIosInstall() {
|
|
333
|
-
return isApplePortable() && !isInstalled();
|
|
334
|
-
}
|
|
335
|
-
//#endregion
|
|
336
|
-
//#region src/composables/use-theme.ts
|
|
337
|
-
/**
|
|
338
|
-
* Namespaced by the app, not by this package.
|
|
339
|
-
*
|
|
340
|
-
* Two rei-kit apps served from the same origin would otherwise share one theme
|
|
341
|
-
* setting — and during development on localhost, they will be.
|
|
342
|
-
*/
|
|
343
|
-
var storageKey = "rei-theme";
|
|
344
|
-
function isThemePreference(value) {
|
|
345
|
-
return value === "system" || value === "light" || value === "dark";
|
|
346
|
-
}
|
|
347
|
-
/** Reads the stored preference, falling back to `system`. */
|
|
348
|
-
function readStoredTheme() {
|
|
349
|
-
try {
|
|
350
|
-
const stored = localStorage.getItem(storageKey);
|
|
351
|
-
return isThemePreference(stored) ? stored : "system";
|
|
352
|
-
} catch {
|
|
353
|
-
return "system";
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
function storeTheme(preference) {
|
|
357
|
-
try {
|
|
358
|
-
localStorage.setItem(storageKey, preference);
|
|
359
|
-
} catch {}
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Does the environment prefer a dark scheme?
|
|
363
|
-
*
|
|
364
|
-
* `matchMedia` is checked for on its own rather than inferred from `document`.
|
|
365
|
-
* Having one does not imply having the other: jsdom supplies a document and no
|
|
366
|
-
* `matchMedia`, so a consumer's component test that so much as mounts something
|
|
367
|
-
* calling `useTheme` threw — and some embedded webviews are the same. Where
|
|
368
|
-
* there is nothing to ask, the answer is no rather than an exception.
|
|
369
|
-
*/
|
|
370
|
-
function prefersDarkScheme() {
|
|
371
|
-
return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false;
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Adds or removes `.dark` on `<html>`, resolving `system` against the OS.
|
|
375
|
-
*
|
|
376
|
-
* A no-op without a document. There is no OS preference to read on a server and
|
|
377
|
-
* no `<html>` to write to, so a prerender leaves the class off and the app
|
|
378
|
-
* decides the theme before hydration — see the note in the README.
|
|
379
|
-
*/
|
|
380
|
-
function applyTheme(preference) {
|
|
381
|
-
if (typeof document === "undefined") return;
|
|
382
|
-
const isDark = preference === "dark" || preference === "system" && prefersDarkScheme();
|
|
383
|
-
document.documentElement.classList.toggle("dark", isDark);
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* The shared preference, created on first use rather than at import.
|
|
387
|
-
*
|
|
388
|
-
* Lazy on purpose: reading storage at import time would lock in the default key
|
|
389
|
-
* before an app had a chance to set its own, leaving the controller reading one
|
|
390
|
-
* key and writing another.
|
|
391
|
-
*/
|
|
392
|
-
var preference = null;
|
|
393
|
-
function controller() {
|
|
394
|
-
if (preference) return preference;
|
|
395
|
-
preference = ref(readStoredTheme());
|
|
396
|
-
watch(preference, (next) => {
|
|
397
|
-
storeTheme(next);
|
|
398
|
-
applyTheme(next);
|
|
399
|
-
}, { immediate: true });
|
|
400
|
-
if (typeof window !== "undefined" && typeof window.matchMedia === "function") window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
|
401
|
-
if (preference?.value === "system") applyTheme("system");
|
|
402
|
-
});
|
|
403
|
-
return preference;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Sets where the preference is stored.
|
|
407
|
-
*
|
|
408
|
-
* Safe in either order: called before the first `useTheme()` it simply changes
|
|
409
|
-
* the key, and called after it re-reads under the new one, so the controller
|
|
410
|
-
* never reads from one key while writing to another.
|
|
411
|
-
*
|
|
412
|
-
* @example
|
|
413
|
-
* ```ts
|
|
414
|
-
* setThemeStorageKey('hibi-theme') // once, at startup
|
|
415
|
-
* ```
|
|
416
|
-
*/
|
|
417
|
-
function setThemeStorageKey(key) {
|
|
418
|
-
storageKey = key;
|
|
419
|
-
if (preference) preference.value = readStoredTheme();
|
|
420
|
-
}
|
|
421
|
-
/** @returns The shared preference ref; assigning to it stores and applies it. */
|
|
422
|
-
function useTheme() {
|
|
423
|
-
return controller();
|
|
424
|
-
}
|
|
425
|
-
//#endregion
|
|
426
155
|
//#region src/composables/use-today.ts
|
|
427
156
|
/**
|
|
428
157
|
* Today's date key, kept current while the app stays open.
|
|
@@ -838,7 +567,7 @@ function useToast() {
|
|
|
838
567
|
}
|
|
839
568
|
//#endregion
|
|
840
569
|
//#region src/components/BaseAlert.vue?vue&type=script&setup=true&lang.ts
|
|
841
|
-
var _hoisted_1$
|
|
570
|
+
var _hoisted_1$21 = ["role", "aria-live"];
|
|
842
571
|
var _hoisted_2$16 = { class: "min-w-0 flex-1" };
|
|
843
572
|
var _hoisted_3$11 = {
|
|
844
573
|
key: 0,
|
|
@@ -883,7 +612,7 @@ var BaseAlert_default = /* @__PURE__ */ defineComponent({
|
|
|
883
612
|
}, [renderSlot(_ctx.$slots, "mark")], 2)) : createCommentVNode("", true),
|
|
884
613
|
createElementVNode("div", _hoisted_2$16, [_ctx.$slots.title ? (openBlock(), createElementBlock("p", _hoisted_3$11, [renderSlot(_ctx.$slots, "title")])) : createCommentVNode("", true), createElementVNode("div", { class: normalizeClass(_ctx.$slots.title ? "mt-1" : "") }, [renderSlot(_ctx.$slots, "default")], 2)]),
|
|
885
614
|
renderSlot(_ctx.$slots, "action")
|
|
886
|
-
], 10, _hoisted_1$
|
|
615
|
+
], 10, _hoisted_1$21);
|
|
887
616
|
};
|
|
888
617
|
}
|
|
889
618
|
});
|
|
@@ -907,151 +636,6 @@ var BaseBadge_default = /* @__PURE__ */ defineComponent({
|
|
|
907
636
|
}
|
|
908
637
|
});
|
|
909
638
|
//#endregion
|
|
910
|
-
//#region src/components/BaseButton.vue?vue&type=script&setup=true&lang.ts
|
|
911
|
-
var _hoisted_1$21 = {
|
|
912
|
-
key: 0,
|
|
913
|
-
class: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent",
|
|
914
|
-
"aria-hidden": "true"
|
|
915
|
-
};
|
|
916
|
-
//#endregion
|
|
917
|
-
//#region src/components/BaseButton.vue
|
|
918
|
-
var BaseButton_default = /* @__PURE__ */ defineComponent({
|
|
919
|
-
__name: "BaseButton",
|
|
920
|
-
props: {
|
|
921
|
-
as: { default: "button" },
|
|
922
|
-
variant: { default: "primary" },
|
|
923
|
-
size: { default: "md" },
|
|
924
|
-
loading: {
|
|
925
|
-
type: Boolean,
|
|
926
|
-
default: false
|
|
927
|
-
},
|
|
928
|
-
disabled: {
|
|
929
|
-
type: Boolean,
|
|
930
|
-
default: false
|
|
931
|
-
},
|
|
932
|
-
type: { default: "button" },
|
|
933
|
-
icon: {
|
|
934
|
-
type: Boolean,
|
|
935
|
-
default: false
|
|
936
|
-
},
|
|
937
|
-
block: {
|
|
938
|
-
type: Boolean,
|
|
939
|
-
default: false
|
|
940
|
-
},
|
|
941
|
-
to: { default: () => void 0 },
|
|
942
|
-
href: { default: () => void 0 },
|
|
943
|
-
pill: {
|
|
944
|
-
type: Boolean,
|
|
945
|
-
default: false
|
|
946
|
-
},
|
|
947
|
-
pressed: {
|
|
948
|
-
type: Boolean,
|
|
949
|
-
default: () => void 0
|
|
950
|
-
}
|
|
951
|
-
},
|
|
952
|
-
setup(__props) {
|
|
953
|
-
const VARIANT_CLASS = {
|
|
954
|
-
primary: "bg-primary text-white hover:bg-primary/90",
|
|
955
|
-
secondary: "border-hair bg-surface text-ink border hover:bg-muted",
|
|
956
|
-
ghost: "bg-transparent text-ink hover:bg-muted",
|
|
957
|
-
destructive: "bg-transparent text-ink-soft hover:text-negative",
|
|
958
|
-
row: "w-full justify-start text-left bg-transparent text-ink hover:bg-muted",
|
|
959
|
-
quiet: "bg-transparent text-ink-soft hover:bg-muted hover:text-ink",
|
|
960
|
-
danger: "bg-negative text-white hover:bg-negative/90",
|
|
961
|
-
positive: "bg-positive text-white hover:bg-positive/90",
|
|
962
|
-
warning: "bg-warning text-white hover:bg-warning/90",
|
|
963
|
-
accent: "bg-accent text-white hover:bg-accent/90",
|
|
964
|
-
link: "bg-transparent underline underline-offset-2 hover:opacity-80",
|
|
965
|
-
unstyled: ""
|
|
966
|
-
};
|
|
967
|
-
const SIZE_CLASS = {
|
|
968
|
-
xs: "h-8 px-3 text-xs",
|
|
969
|
-
sm: "h-9 px-3 text-sm",
|
|
970
|
-
md: "h-11 px-4 text-base",
|
|
971
|
-
lg: "h-14 px-6 text-lg"
|
|
972
|
-
};
|
|
973
|
-
const ICON_SIZE_CLASS = {
|
|
974
|
-
xs: "size-8 text-xs",
|
|
975
|
-
sm: "size-9 text-sm",
|
|
976
|
-
md: "size-11 text-base",
|
|
977
|
-
lg: "size-14 text-lg"
|
|
978
|
-
};
|
|
979
|
-
const ROW_SIZE_CLASS = {
|
|
980
|
-
xs: "px-2 py-1.5 text-xs",
|
|
981
|
-
sm: "px-3 py-2 text-sm",
|
|
982
|
-
md: "px-3 py-2.5 text-base",
|
|
983
|
-
lg: "px-4 py-3 text-lg"
|
|
984
|
-
};
|
|
985
|
-
const LINK_SIZE_CLASS = {
|
|
986
|
-
xs: "text-xs",
|
|
987
|
-
sm: "text-sm",
|
|
988
|
-
md: "text-base",
|
|
989
|
-
lg: "text-lg"
|
|
990
|
-
};
|
|
991
|
-
const sizing = computed(() => {
|
|
992
|
-
if (__props.variant === "unstyled") return "";
|
|
993
|
-
if (__props.variant === "link") return LINK_SIZE_CLASS[__props.size];
|
|
994
|
-
if (__props.variant === "row") return ROW_SIZE_CLASS[__props.size];
|
|
995
|
-
return __props.icon ? ICON_SIZE_CLASS[__props.size] : SIZE_CLASS[__props.size];
|
|
996
|
-
});
|
|
997
|
-
const PRESSED_CLASS = {
|
|
998
|
-
ghost: "bg-primary text-white hover:bg-primary/90",
|
|
999
|
-
quiet: "bg-primary text-white hover:bg-primary/90",
|
|
1000
|
-
secondary: "bg-primary border-primary text-white hover:bg-primary/90",
|
|
1001
|
-
row: "w-full justify-start text-left bg-muted text-ink hover:bg-muted"
|
|
1002
|
-
};
|
|
1003
|
-
const surface = computed(() => {
|
|
1004
|
-
if (__props.pressed === true) return PRESSED_CLASS[__props.variant] ?? VARIANT_CLASS[__props.variant];
|
|
1005
|
-
if (__props.variant === "destructive") return `${VARIANT_CLASS.destructive} hover:bg-negative/10`;
|
|
1006
|
-
return VARIANT_CLASS[__props.variant];
|
|
1007
|
-
});
|
|
1008
|
-
const shell = computed(() => {
|
|
1009
|
-
if (__props.variant === "unstyled") return "";
|
|
1010
|
-
const feel = "transition-[transform,color,background-color,border-color] duration-100 select-none";
|
|
1011
|
-
if (__props.variant === "row") return `inline-flex items-center gap-2 font-medium ${feel}`;
|
|
1012
|
-
return `inline-flex items-center justify-center gap-2 font-medium ${feel} active:scale-95`;
|
|
1013
|
-
});
|
|
1014
|
-
const radius = computed(() => {
|
|
1015
|
-
if (__props.variant === "unstyled") return "";
|
|
1016
|
-
if (__props.variant === "link") return "rounded-xs";
|
|
1017
|
-
return __props.pill ? "rounded-full" : "rounded-card";
|
|
1018
|
-
});
|
|
1019
|
-
/** Anything that is not a `<button>` cannot be `disabled`; it has to be told. */
|
|
1020
|
-
const inactive = computed(() => __props.disabled || __props.loading);
|
|
1021
|
-
const linkProps = computed(() => {
|
|
1022
|
-
if (__props.as === "router-link") return { to: __props.to };
|
|
1023
|
-
if (__props.as === "a") return inactive.value ? {} : { href: __props.href };
|
|
1024
|
-
return {};
|
|
1025
|
-
});
|
|
1026
|
-
return (_ctx, _cache) => {
|
|
1027
|
-
return openBlock(), createBlock(resolveDynamicComponent(__props.as), mergeProps(linkProps.value, {
|
|
1028
|
-
type: __props.as === "button" ? __props.type : void 0,
|
|
1029
|
-
disabled: __props.as === "button" ? inactive.value : void 0,
|
|
1030
|
-
"aria-disabled": __props.as !== "button" && inactive.value ? "true" : void 0,
|
|
1031
|
-
"aria-busy": __props.loading,
|
|
1032
|
-
"aria-pressed": __props.pressed === void 0 ? void 0 : String(__props.pressed),
|
|
1033
|
-
class: ["focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50", [
|
|
1034
|
-
shell.value,
|
|
1035
|
-
surface.value,
|
|
1036
|
-
sizing.value,
|
|
1037
|
-
radius.value,
|
|
1038
|
-
__props.block ? "w-full" : ""
|
|
1039
|
-
]]
|
|
1040
|
-
}), {
|
|
1041
|
-
default: withCtx(() => [__props.loading ? (openBlock(), createElementBlock("span", _hoisted_1$21)) : createCommentVNode("", true), renderSlot(_ctx.$slots, "default")]),
|
|
1042
|
-
_: 3
|
|
1043
|
-
}, 16, [
|
|
1044
|
-
"type",
|
|
1045
|
-
"disabled",
|
|
1046
|
-
"aria-disabled",
|
|
1047
|
-
"aria-busy",
|
|
1048
|
-
"aria-pressed",
|
|
1049
|
-
"class"
|
|
1050
|
-
]);
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
1054
|
-
//#endregion
|
|
1055
639
|
//#region src/components/FormField.vue?vue&type=script&setup=true&lang.ts
|
|
1056
640
|
var _hoisted_1$20 = ["for"];
|
|
1057
641
|
//#endregion
|
|
@@ -1175,7 +759,9 @@ var _hoisted_6$2 = {
|
|
|
1175
759
|
class: "text-ink-soft mt-1 text-sm leading-snug"
|
|
1176
760
|
};
|
|
1177
761
|
var _hoisted_7$2 = { class: "min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]" };
|
|
1178
|
-
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region src/components/BaseSheet.vue
|
|
764
|
+
var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
1179
765
|
__name: "BaseSheet",
|
|
1180
766
|
props: /*@__PURE__*/ mergeModels({
|
|
1181
767
|
title: {},
|
|
@@ -1276,17 +862,7 @@ var BaseSheet_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
|
|
|
1276
862
|
})]);
|
|
1277
863
|
};
|
|
1278
864
|
}
|
|
1279
|
-
});
|
|
1280
|
-
//#endregion
|
|
1281
|
-
//#region \0plugin-vue:export-helper
|
|
1282
|
-
var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
1283
|
-
const target = sfc.__vccOpts || sfc;
|
|
1284
|
-
for (const [key, val] of props) target[key] = val;
|
|
1285
|
-
return target;
|
|
1286
|
-
};
|
|
1287
|
-
//#endregion
|
|
1288
|
-
//#region src/components/BaseSheet.vue
|
|
1289
|
-
var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(BaseSheet_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-51709579"]]);
|
|
865
|
+
}), [["__scopeId", "data-v-51709579"]]);
|
|
1290
866
|
//#endregion
|
|
1291
867
|
//#region src/components/BaseCard.vue
|
|
1292
868
|
var BaseCard_default = /* @__PURE__ */ defineComponent({
|
|
@@ -2437,7 +2013,7 @@ function createI18nRuntime(options) {
|
|
|
2437
2013
|
*
|
|
2438
2014
|
* The fallback keeps `vitest` and `vite dev` honest, where no define runs.
|
|
2439
2015
|
*/
|
|
2440
|
-
var VERSION = "0.
|
|
2016
|
+
var VERSION = "0.13.0";
|
|
2441
2017
|
//#endregion
|
|
2442
2018
|
export { AppError, BaseAlert_default as BaseAlert, BaseBadge_default as BaseBadge, BaseButton_default as BaseButton, BaseCard_default as BaseCard, BaseCheckbox_default as BaseCheckbox, BaseInput_default as BaseInput, BaseRadioGroup_default as BaseRadioGroup, BaseSelect_default as BaseSelect, BaseSheet_default as BaseSheet, BaseTextarea_default as BaseTextarea, EmptyState_default as EmptyState, ErrorBoundary_default as ErrorBoundary, FormField_default as FormField, GoogleButton_default as GoogleButton, LocaleLinks_default as LocaleLinks, PageContainer_default as PageContainer, PageHeader_default as PageHeader, PriceCard_default as PriceCard, ProgressBar_default as ProgressBar, SectionHeading_default as SectionHeading, SegmentedControl_default as SegmentedControl, SettingsGroup_default as SettingsGroup, SettingsRow_default as SettingsRow, SkeletonList_default as SkeletonList, StatCard_default as StatCard, TabBar_default as TabBar, ToastHost_default as ToastHost, ToneDot_default as ToneDot, VERSION, addDays, applyTheme, createI18nRuntime, downloadJson, eachDayOfYear, formatDate, fromDateKey, isApplePortable, isInstalled, isThemePreference, lastNDays, leadingBlanks, needsIosInstall, readStoredTheme, registerErrorMapper, relativeDayLabel, safeRedirect, setFormatLocale, setThemeStorageKey, startOfWeek, tapFeedback, toAppError, toDateKey, todayKey, useDebouncedCallback, useDragScroll, useMediaQuery, useOnline, useTheme, useToast, useToday, useVisualViewport };
|
|
2443
2019
|
|