rei-kit 0.12.0 → 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/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
@@ -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"}
@@ -6,6 +6,13 @@
6
6
  * so a card on one screen had a heavier border than a card on the next and
7
7
  * nobody could say why.
8
8
  *
9
+ * With no head and no foot there is no wrapper div: the padding lands on the
10
+ * card itself and the card *is* the element. That matters more than a saved
11
+ * node — most cards in the apps lay their contents out (`flex items-center
12
+ * gap-4`, `flex flex-col gap-3`), and with a wrapper in the way those classes
13
+ * reach the border and not the content, so the app has to add back the div
14
+ * this component exists to remove. One that makes you do that is one you skip.
15
+ *
9
16
  * `interactive` is for a card that is a link or a button: it adds the lift and
10
17
  * the press, and it is opt-in because a card holding a form should not move
11
18
  * when the pointer crosses it.
@@ -28,13 +35,15 @@ type __VLS_Props = {
28
35
  */
29
36
  padding?: 'none' | 'sm' | 'md' | 'lg' | undefined;
30
37
  };
31
- declare var __VLS_8: {}, __VLS_10: {}, __VLS_12: {};
38
+ declare var __VLS_8: {}, __VLS_10: {}, __VLS_12: {}, __VLS_14: {};
32
39
  type __VLS_Slots = {} & {
33
40
  head?: (props: typeof __VLS_8) => any;
34
41
  } & {
35
42
  default?: (props: typeof __VLS_10) => any;
36
43
  } & {
37
- foot?: (props: typeof __VLS_12) => any;
44
+ default?: (props: typeof __VLS_12) => any;
45
+ } & {
46
+ foot?: (props: typeof __VLS_14) => any;
38
47
  };
39
48
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
40
49
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;