@kadal/deck 0.0.0-stage → 0.1.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.
Files changed (157) hide show
  1. package/LICENSE +661 -0
  2. package/LICENSING.md +40 -0
  3. package/README.md +170 -2
  4. package/dist/_virtual/_rolldown/runtime.js +13 -0
  5. package/dist/audience/index.d.ts +14 -0
  6. package/dist/audience/index.js +680 -0
  7. package/dist/backgrounds/index.d.ts +65 -0
  8. package/dist/backgrounds/index.js +604 -0
  9. package/dist/backgrounds/shared.d.ts +7 -0
  10. package/dist/backgrounds/shared.js +89 -0
  11. package/dist/cli/bin.d.ts +1 -0
  12. package/dist/cli/bin.js +8 -0
  13. package/dist/cli/host.js +111 -0
  14. package/dist/cli/index.d.ts +4 -0
  15. package/dist/cli/index.js +140 -0
  16. package/dist/cli/login.js +56 -0
  17. package/dist/cli/publish.js +159 -0
  18. package/dist/cli/vite.js +39 -0
  19. package/dist/compiler/code-meta.d.ts +33 -0
  20. package/dist/compiler/code-meta.js +81 -0
  21. package/dist/compiler/index.d.ts +8 -0
  22. package/dist/compiler/index.js +8 -0
  23. package/dist/compiler/magic-move.d.ts +26 -0
  24. package/dist/compiler/magic-move.js +33 -0
  25. package/dist/compiler/plain-text.js +7 -0
  26. package/dist/compiler/rehype-code.d.ts +18 -0
  27. package/dist/compiler/rehype-code.js +66 -0
  28. package/dist/compiler/rehype-glossary.d.ts +35 -0
  29. package/dist/compiler/rehype-glossary.js +211 -0
  30. package/dist/compiler/slots.d.ts +3 -0
  31. package/dist/compiler/slots.js +36 -0
  32. package/dist/compiler/split.d.ts +63 -0
  33. package/dist/compiler/split.js +195 -0
  34. package/dist/components/Background.d.ts +15 -0
  35. package/dist/components/Background.js +24 -0
  36. package/dist/components/Click.d.ts +54 -0
  37. package/dist/components/Click.js +129 -0
  38. package/dist/components/Code.d.ts +27 -0
  39. package/dist/components/Code.js +124 -0
  40. package/dist/components/Motion.d.ts +25 -0
  41. package/dist/components/Motion.js +45 -0
  42. package/dist/components/Sketch.d.ts +44 -0
  43. package/dist/components/Sketch.js +141 -0
  44. package/dist/components/Term.d.ts +63 -0
  45. package/dist/components/Term.js +492 -0
  46. package/dist/components/bling.d.ts +75 -0
  47. package/dist/components/bling.js +146 -0
  48. package/dist/components/content.d.ts +232 -0
  49. package/dist/components/content.js +637 -0
  50. package/dist/components/index.d.ts +10 -0
  51. package/dist/components/index.js +88 -0
  52. package/dist/core/clicks.d.ts +45 -0
  53. package/dist/core/clicks.js +75 -0
  54. package/dist/core/hash.d.ts +33 -0
  55. package/dist/core/hash.js +49 -0
  56. package/dist/core/keyboard.d.ts +17 -0
  57. package/dist/core/keyboard.js +198 -0
  58. package/dist/core/layout.d.ts +16 -0
  59. package/dist/core/layout.js +25 -0
  60. package/dist/core/model.d.ts +259 -0
  61. package/dist/core/model.js +377 -0
  62. package/dist/core/navigation.d.ts +74 -0
  63. package/dist/core/navigation.js +244 -0
  64. package/dist/core/poster.js +30 -0
  65. package/dist/core/store.d.ts +20 -0
  66. package/dist/core/store.js +64 -0
  67. package/dist/core/touch.js +27 -0
  68. package/dist/draw/DrawingLayer.js +315 -0
  69. package/dist/draw/strokes.js +188 -0
  70. package/dist/export/index.d.ts +68 -0
  71. package/dist/export/index.js +251 -0
  72. package/dist/export/run.js +81 -0
  73. package/dist/glossary/model.d.ts +81 -0
  74. package/dist/glossary/model.js +156 -0
  75. package/dist/glossary/parse.d.ts +27 -0
  76. package/dist/glossary/parse.js +90 -0
  77. package/dist/index.d.ts +31 -0
  78. package/dist/index.js +30 -0
  79. package/dist/live/LiveProvider.d.ts +20 -0
  80. package/dist/live/LiveProvider.js +450 -0
  81. package/dist/live/analytics.d.ts +15 -0
  82. package/dist/live/analytics.js +103 -0
  83. package/dist/live/client.d.ts +51 -0
  84. package/dist/live/client.js +193 -0
  85. package/dist/live/components.d.ts +64 -0
  86. package/dist/live/components.js +471 -0
  87. package/dist/live/context.d.ts +15 -0
  88. package/dist/live/context.js +28 -0
  89. package/dist/live/index.d.ts +7 -0
  90. package/dist/live/index.js +7 -0
  91. package/dist/live/protocol.d.ts +284 -0
  92. package/dist/live/protocol.js +42 -0
  93. package/dist/live/turnstile.js +49 -0
  94. package/dist/presenter/index.d.ts +20 -0
  95. package/dist/presenter/index.js +1211 -0
  96. package/dist/react/AutoAnimate.js +92 -0
  97. package/dist/react/Backgrounds.d.ts +11 -0
  98. package/dist/react/Backgrounds.js +154 -0
  99. package/dist/react/ConfettiLayer.js +85 -0
  100. package/dist/react/Deck.d.ts +35 -0
  101. package/dist/react/Deck.js +983 -0
  102. package/dist/react/Frame.d.ts +16 -0
  103. package/dist/react/Frame.js +174 -0
  104. package/dist/react/Hints.d.ts +3 -0
  105. package/dist/react/Hints.js +201 -0
  106. package/dist/react/Lightbox.js +54 -0
  107. package/dist/react/Overview.js +93 -0
  108. package/dist/react/PrintView.js +128 -0
  109. package/dist/react/ScrollView.js +128 -0
  110. package/dist/react/SlideFrame.d.ts +51 -0
  111. package/dist/react/SlideFrame.js +323 -0
  112. package/dist/react/Stage.js +151 -0
  113. package/dist/react/ThemeMenu.d.ts +5 -0
  114. package/dist/react/ThemeMenu.js +124 -0
  115. package/dist/react/Zoom.js +23 -0
  116. package/dist/react/chrome.js +339 -0
  117. package/dist/react/context.d.ts +165 -0
  118. package/dist/react/context.js +19 -0
  119. package/dist/react/hooks.d.ts +33 -0
  120. package/dist/react/hooks.js +70 -0
  121. package/dist/react/layouts.d.ts +21 -0
  122. package/dist/react/layouts.js +104 -0
  123. package/dist/react/mdx.d.ts +4 -0
  124. package/dist/react/mdx.js +33 -0
  125. package/dist/react/terms.d.ts +6 -0
  126. package/dist/react/terms.js +66 -0
  127. package/dist/renderers/Island.d.ts +57 -0
  128. package/dist/renderers/Island.js +223 -0
  129. package/dist/renderers/contract.d.ts +173 -0
  130. package/dist/renderers/contract.js +152 -0
  131. package/dist/renderers/index.d.ts +12 -0
  132. package/dist/renderers/index.js +16 -0
  133. package/dist/renderers/react.d.ts +16 -0
  134. package/dist/renderers/react.js +32 -0
  135. package/dist/renderers/web-components.d.ts +4 -0
  136. package/dist/renderers/web-components.js +103 -0
  137. package/dist/sketch/Canvas.js +49 -0
  138. package/dist/sketch/dsl.d.ts +131 -0
  139. package/dist/sketch/dsl.js +281 -0
  140. package/dist/sketch/index.d.ts +4 -0
  141. package/dist/sketch/index.js +3 -0
  142. package/dist/sketch/palette.d.ts +21 -0
  143. package/dist/sketch/palette.js +66 -0
  144. package/dist/sketch/render.d.ts +43 -0
  145. package/dist/sketch/render.js +198 -0
  146. package/dist/themes/base.css +1704 -0
  147. package/dist/themes/bling.css +592 -0
  148. package/dist/themes/glossary.css +317 -0
  149. package/dist/themes/live.css +1557 -0
  150. package/dist/themes/minimal.css +83 -0
  151. package/dist/themes/rla.css +92 -0
  152. package/dist/themes/sketch.css +121 -0
  153. package/dist/themes/stack.css +62 -0
  154. package/dist/vite/index.d.ts +15 -0
  155. package/dist/vite/index.js +320 -0
  156. package/package.json +159 -4
  157. package/tools/verify-island.mjs +150 -0
@@ -0,0 +1,74 @@
1
+ import { Column, DeckConfig } from "./model.js";
2
+ declare namespace navigation_d_exports {
3
+ export { Direction, NavState, Position, Routes, availableRoutes, clampPosition, down, first, formatSlideNumber, goto, initialNav, key, last, left, next, nextClick, prev, prevClick, progressOf, right, setTotal, up };
4
+ }
5
+ /**
6
+ * ─────────────────────────────────────────────────────────────────────────────
7
+ * NAVIGATION
8
+ * ─────────────────────────────────────────────────────────────────────────────
9
+ *
10
+ * Where the deck is and how it got there. `click` is the step within a slide (reveal.js's
11
+ * fragment index, Slidev's click); `direction` is what the transition reads. Everything is a pure
12
+ * function of the state and the click totals the runtime reports, so the presenter window, the
13
+ * live room and the keyboard all drive the same reducer.
14
+ * ─────────────────────────────────────────────────────────────────────────────
15
+ */
16
+ export type Direction = "none" | "forward" | "back" | "up" | "down";
17
+ export interface Position {
18
+ h: number;
19
+ v: number;
20
+ click: number;
21
+ }
22
+ export interface NavState extends Position {
23
+ direction: Direction;
24
+ overview: boolean;
25
+ paused: boolean;
26
+ autoSliding: boolean;
27
+ /** Per visible slide: how many clicks the runtime has registered (by "h,v"). */
28
+ totals: Record<string, number>;
29
+ /** The position the presenter is at, when this window follows one and is not the presenter. */
30
+ presenter: Position | null;
31
+ /** Whether this window follows the presenter's navigation. */
32
+ follow: boolean;
33
+ /** A visible slide id → clamped ok; set by `goto` for hash routing. */
34
+ seq: number;
35
+ }
36
+ export declare const key: (h: number, v: number) => string;
37
+ export declare function initialNav(): NavState;
38
+ export declare function clampPosition(columns: Column[], p: Partial<Position>): Position;
39
+ export interface Routes {
40
+ left: boolean;
41
+ right: boolean;
42
+ up: boolean;
43
+ down: boolean;
44
+ prevClick: boolean;
45
+ nextClick: boolean;
46
+ }
47
+ export declare function availableRoutes(state: NavState, columns: Column[], config: DeckConfig): Routes;
48
+ /** Move to an exact position (a hash, a link, the presenter). Clicks default to 0. */
49
+ export declare function goto(state: NavState, columns: Column[], to: Partial<Position>): NavState;
50
+ export declare function right(state: NavState, columns: Column[], config: DeckConfig): NavState;
51
+ export declare function left(state: NavState, columns: Column[], config: DeckConfig): NavState;
52
+ export declare function down(state: NavState, columns: Column[]): NavState;
53
+ export declare function up(state: NavState, columns: Column[]): NavState;
54
+ export declare function nextClick(state: NavState): NavState;
55
+ export declare function prevClick(state: NavState): NavState;
56
+ /**
57
+ * `next`: the next click, else the slide beneath, else the next column. In linear mode the deck
58
+ * reads like a book: every vertical slide is a page. Matches reveal.js's space bar.
59
+ */
60
+ export declare function next(state: NavState, columns: Column[], config: DeckConfig): NavState;
61
+ /** `prev`: the previous click, else the slide above, else the previous column. */
62
+ export declare function prev(state: NavState, columns: Column[], config: DeckConfig, opts?: {
63
+ toLastClick?: boolean;
64
+ }): NavState;
65
+ export declare function first(state: NavState, columns: Column[]): NavState;
66
+ export declare function last(state: NavState, columns: Column[]): NavState;
67
+ /** Record the click total the runtime found for a slide; clamps the current click if needed. */
68
+ export declare function setTotal(state: NavState, h: number, v: number, total: number): NavState;
69
+ /** 0 at the first slide, 1 at the last. */
70
+ export declare function progressOf(state: NavState, columns: Column[]): number;
71
+ /** Format the slide number the way reveal.js does. */
72
+ export declare function formatSlideNumber(format: true | "h.v" | "h/v" | "c" | "c/t", state: Position, columns: Column[], oneBased?: boolean): string;
73
+ //#endregion
74
+ export { navigation_d_exports };
@@ -0,0 +1,244 @@
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
+ //#region src/core/navigation.ts
3
+ var navigation_exports = /* @__PURE__ */ __exportAll({
4
+ availableRoutes: () => availableRoutes,
5
+ clampPosition: () => clampPosition,
6
+ down: () => down,
7
+ first: () => first,
8
+ formatSlideNumber: () => formatSlideNumber,
9
+ goto: () => goto,
10
+ initialNav: () => initialNav,
11
+ key: () => key,
12
+ last: () => last,
13
+ left: () => left,
14
+ next: () => next,
15
+ nextClick: () => nextClick,
16
+ prev: () => prev,
17
+ prevClick: () => prevClick,
18
+ progressOf: () => progressOf,
19
+ right: () => right,
20
+ setTotal: () => setTotal,
21
+ up: () => up
22
+ });
23
+ const key = (h, v) => `${h},${v}`;
24
+ function initialNav() {
25
+ return {
26
+ h: 0,
27
+ v: 0,
28
+ click: 0,
29
+ direction: "none",
30
+ overview: false,
31
+ paused: false,
32
+ autoSliding: false,
33
+ totals: {},
34
+ presenter: null,
35
+ follow: true,
36
+ seq: 0
37
+ };
38
+ }
39
+ function clampPosition(columns, p) {
40
+ const h = Math.min(Math.max(0, p.h ?? 0), Math.max(0, columns.length - 1));
41
+ const col = columns[h];
42
+ return {
43
+ h,
44
+ v: Math.min(Math.max(0, p.v ?? 0), Math.max(0, (col?.slides.length ?? 1) - 1)),
45
+ click: Math.max(0, p.click ?? 0)
46
+ };
47
+ }
48
+ function availableRoutes(state, columns, config) {
49
+ const col = columns[state.h];
50
+ const total = state.totals[key(state.h, state.v)] ?? 0;
51
+ const left = state.h > 0 || config.loop;
52
+ const right = state.h < columns.length - 1 || config.loop;
53
+ return {
54
+ left: config.rtl ? right : left,
55
+ right: config.rtl ? left : right,
56
+ up: state.v > 0,
57
+ down: !!col && state.v < col.slides.length - 1,
58
+ prevClick: state.click > 0,
59
+ nextClick: state.click < total
60
+ };
61
+ }
62
+ function step(state, next, direction, columns) {
63
+ const p = clampPosition(columns, {
64
+ ...state,
65
+ ...next
66
+ });
67
+ return {
68
+ ...state,
69
+ ...p,
70
+ direction,
71
+ seq: state.seq + 1
72
+ };
73
+ }
74
+ /** Move to an exact position (a hash, a link, the presenter). Clicks default to 0. */
75
+ function goto(state, columns, to) {
76
+ const p = clampPosition(columns, {
77
+ h: to.h ?? state.h,
78
+ v: to.v ?? state.v,
79
+ click: to.click ?? 0
80
+ });
81
+ const direction = p.h > state.h ? "forward" : p.h < state.h ? "back" : p.v > state.v ? "down" : p.v < state.v ? "up" : "none";
82
+ return {
83
+ ...state,
84
+ ...p,
85
+ direction,
86
+ seq: state.seq + 1
87
+ };
88
+ }
89
+ function right(state, columns, config) {
90
+ const last = columns.length - 1;
91
+ if (state.h < last) {
92
+ const v = config.navigationMode === "grid" ? Math.min(state.v, columns[state.h + 1].slides.length - 1) : 0;
93
+ return step(state, {
94
+ h: state.h + 1,
95
+ v,
96
+ click: 0
97
+ }, "forward", columns);
98
+ }
99
+ if (config.loop) return step(state, {
100
+ h: 0,
101
+ v: 0,
102
+ click: 0
103
+ }, "forward", columns);
104
+ return state;
105
+ }
106
+ function left(state, columns, config) {
107
+ if (state.h > 0) {
108
+ const col = columns[state.h - 1];
109
+ const v = config.navigationMode === "grid" ? Math.min(state.v, col.slides.length - 1) : config.navigationMode === "linear" ? col.slides.length - 1 : 0;
110
+ return step(state, {
111
+ h: state.h - 1,
112
+ v,
113
+ click: 0
114
+ }, "back", columns);
115
+ }
116
+ if (config.loop) return step(state, {
117
+ h: columns.length - 1,
118
+ v: 0,
119
+ click: 0
120
+ }, "back", columns);
121
+ return state;
122
+ }
123
+ function down(state, columns) {
124
+ const col = columns[state.h];
125
+ if (col && state.v < col.slides.length - 1) return step(state, {
126
+ v: state.v + 1,
127
+ click: 0
128
+ }, "down", columns);
129
+ return state;
130
+ }
131
+ function up(state, columns) {
132
+ if (state.v > 0) return step(state, {
133
+ v: state.v - 1,
134
+ click: 0
135
+ }, "up", columns);
136
+ return state;
137
+ }
138
+ function nextClick(state) {
139
+ const total = state.totals[key(state.h, state.v)] ?? 0;
140
+ if (state.click < total) return {
141
+ ...state,
142
+ click: state.click + 1,
143
+ direction: "forward",
144
+ seq: state.seq + 1
145
+ };
146
+ return state;
147
+ }
148
+ function prevClick(state) {
149
+ if (state.click > 0) return {
150
+ ...state,
151
+ click: state.click - 1,
152
+ direction: "back",
153
+ seq: state.seq + 1
154
+ };
155
+ return state;
156
+ }
157
+ /**
158
+ * `next`: the next click, else the slide beneath, else the next column. In linear mode the deck
159
+ * reads like a book: every vertical slide is a page. Matches reveal.js's space bar.
160
+ */
161
+ function next(state, columns, config) {
162
+ const clicked = nextClick(state);
163
+ if (clicked !== state) return clicked;
164
+ const routes = availableRoutes(state, columns, config);
165
+ if (routes.down) return down(state, columns);
166
+ if (config.rtl ? routes.left : routes.right) return config.rtl ? left(state, columns, config) : right(state, columns, config);
167
+ return state;
168
+ }
169
+ /** `prev`: the previous click, else the slide above, else the previous column. */
170
+ function prev(state, columns, config, opts = {}) {
171
+ const unclicked = prevClick(state);
172
+ if (unclicked !== state) return unclicked;
173
+ const routes = availableRoutes(state, columns, config);
174
+ let moved = state;
175
+ if (routes.up) moved = up(state, columns);
176
+ else if (config.rtl ? routes.right : routes.left) moved = config.rtl ? right(state, columns, config) : left(state, columns, config);
177
+ if (moved === state) return state;
178
+ if (opts.toLastClick !== false) {
179
+ const total = moved.totals[key(moved.h, moved.v)] ?? 0;
180
+ return {
181
+ ...moved,
182
+ click: total
183
+ };
184
+ }
185
+ return moved;
186
+ }
187
+ function first(state, columns) {
188
+ return step(state, {
189
+ h: 0,
190
+ v: 0,
191
+ click: 0
192
+ }, "back", columns);
193
+ }
194
+ function last(state, columns) {
195
+ const h = Math.max(0, columns.length - 1);
196
+ return step(state, {
197
+ h,
198
+ v: Math.max(0, (columns[h]?.slides.length ?? 1) - 1),
199
+ click: 0
200
+ }, "forward", columns);
201
+ }
202
+ /** Record the click total the runtime found for a slide; clamps the current click if needed. */
203
+ function setTotal(state, h, v, total) {
204
+ const k = key(h, v);
205
+ if (state.totals[k] === total) return state;
206
+ const totals = {
207
+ ...state.totals,
208
+ [k]: total
209
+ };
210
+ const click = h === state.h && v === state.v ? Math.min(state.click, total) : state.click;
211
+ return {
212
+ ...state,
213
+ totals,
214
+ click
215
+ };
216
+ }
217
+ /** 0 at the first slide, 1 at the last. */
218
+ function progressOf(state, columns) {
219
+ const total = columns.reduce((n, c) => n + c.slides.length, 0);
220
+ if (total <= 1) return 1;
221
+ let flat = 0;
222
+ for (let i = 0; i < state.h; i++) flat += columns[i].slides.length;
223
+ flat += state.v;
224
+ return flat / (total - 1);
225
+ }
226
+ /** Format the slide number the way reveal.js does. */
227
+ function formatSlideNumber(format, state, columns, oneBased = true) {
228
+ const offset = oneBased ? 1 : 0;
229
+ const flat = (() => {
230
+ let n = 0;
231
+ for (let i = 0; i < state.h; i++) n += columns[i].slides.length;
232
+ return n + state.v + offset;
233
+ })();
234
+ const total = columns.reduce((n, c) => n + c.slides.length, 0);
235
+ const hasVertical = columns.some((c) => c.slides.length > 1);
236
+ switch (format) {
237
+ case "c": return String(flat);
238
+ case "c/t": return `${flat} / ${total}`;
239
+ case "h/v": return hasVertical && state.v > 0 ? `${state.h + offset} / ${state.v + offset}` : String(state.h + offset);
240
+ default: return hasVertical && state.v > 0 ? `${state.h + offset}.${state.v + offset}` : String(state.h + offset);
241
+ }
242
+ }
243
+ //#endregion
244
+ export { availableRoutes, clampPosition, down, first, formatSlideNumber, goto, initialNav, key, last, left, navigation_exports, next, nextClick, prev, prevClick, progressOf, right, setTotal, up };
@@ -0,0 +1,30 @@
1
+ //#region src/core/poster.ts
2
+ /**
3
+ * Posters stand in for an embedded page wherever it cannot be live: the print pages, the
4
+ * overview tiles, the presenter's next-slide box, the scroll view — and behind the shield while
5
+ * the real page loads. A deck names one (`poster: shots/map.jpg` on an iframe slide, or
6
+ * `<Iframe poster>`); without one, `deck export` captures a screenshot of the URL at the pane's
7
+ * size into `public/posters/` under a name both sides derive from the URL, so nothing else has
8
+ * to be wired.
9
+ */
10
+ const POSTER_DEFAULT_SIZE = [1600, 900];
11
+ /** FNV-1a over the URL, in base 36: short, stable, filename-safe. */
12
+ function posterHash(url) {
13
+ let h = 2166136261;
14
+ for (let i = 0; i < url.length; i++) {
15
+ h ^= url.charCodeAt(i);
16
+ h = Math.imul(h, 16777619);
17
+ }
18
+ return (h >>> 0).toString(36);
19
+ }
20
+ /** The path under `public/` where a captured poster for this URL at this pane size lives. */
21
+ function posterName(url, width, height) {
22
+ return `posters/${posterHash(url)}@${Math.round(width)}x${Math.round(height)}.jpg`;
23
+ }
24
+ /** A poster path as written in a deck → a URL the page can load. */
25
+ function resolvePoster(path, base) {
26
+ if (/^(https?:)?\/\//.test(path) || path.startsWith("data:") || path.startsWith("/")) return path;
27
+ return `${base.replace(/\/?$/, "/")}${path}`;
28
+ }
29
+ //#endregion
30
+ export { POSTER_DEFAULT_SIZE, posterHash, posterName, resolvePoster };
@@ -0,0 +1,20 @@
1
+ //#region src/core/store.d.ts
2
+ /**
3
+ * A minimal external store: one value, subscribers, selectors through `useSyncExternalStore`.
4
+ * The deck keeps its navigation state here rather than in React state so the keyboard, the hash,
5
+ * the live room and the presenter window can all read and write it without a render in between.
6
+ */
7
+ export interface Store<T> {
8
+ get(): T;
9
+ set(next: T): void;
10
+ update(fn: (prev: T) => T): void;
11
+ subscribe(listener: (value: T, prev: T) => void): () => void;
12
+ }
13
+ export declare function createStore<T>(initial: T): Store<T>;
14
+ /**
15
+ * Read a slice of a store; re-renders only when the selected slice changes. Selectors may return
16
+ * fresh objects (`(s) => ({ h: s.h, v: s.v })`): the last result is kept and returned again while
17
+ * it is shallow-equal, which is what keeps `useSyncExternalStore` from looping.
18
+ */
19
+ export declare function useStore<T, S = T>(store: Store<T>, selector?: (value: T) => S): S;
20
+ //#endregion
@@ -0,0 +1,64 @@
1
+ import { useRef, useSyncExternalStore } from "react";
2
+ //#region src/core/store.ts
3
+ function createStore(initial) {
4
+ let value = initial;
5
+ const listeners = /* @__PURE__ */ new Set();
6
+ return {
7
+ get: () => value,
8
+ set(next) {
9
+ if (Object.is(next, value)) return;
10
+ const prev = value;
11
+ value = next;
12
+ for (const l of listeners) l(value, prev);
13
+ },
14
+ update(fn) {
15
+ this.set(fn(value));
16
+ },
17
+ subscribe(listener) {
18
+ listeners.add(listener);
19
+ return () => {
20
+ listeners.delete(listener);
21
+ };
22
+ }
23
+ };
24
+ }
25
+ const identity = (v) => v;
26
+ function shallowEqual(a, b) {
27
+ if (Object.is(a, b)) return true;
28
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
29
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
30
+ const ka = Object.keys(a);
31
+ const kb = Object.keys(b);
32
+ if (ka.length !== kb.length) return false;
33
+ for (const k of ka) if (!Object.is(a[k], b[k])) return false;
34
+ return true;
35
+ }
36
+ /**
37
+ * Read a slice of a store; re-renders only when the selected slice changes. Selectors may return
38
+ * fresh objects (`(s) => ({ h: s.h, v: s.v })`): the last result is kept and returned again while
39
+ * it is shallow-equal, which is what keeps `useSyncExternalStore` from looping.
40
+ */
41
+ function useStore(store, selector = identity) {
42
+ const last = useRef(null);
43
+ const snapshot = () => {
44
+ const input = store.get();
45
+ const cached = last.current;
46
+ if (cached && Object.is(cached.input, input)) return cached.output;
47
+ const output = selector(input);
48
+ if (cached && shallowEqual(cached.output, output)) {
49
+ last.current = {
50
+ input,
51
+ output: cached.output
52
+ };
53
+ return cached.output;
54
+ }
55
+ last.current = {
56
+ input,
57
+ output
58
+ };
59
+ return output;
60
+ };
61
+ return useSyncExternalStore((cb) => store.subscribe(() => cb()), snapshot, snapshot);
62
+ }
63
+ //#endregion
64
+ export { createStore, useStore };
@@ -0,0 +1,27 @@
1
+ //#region src/core/touch.ts
2
+ function createSwipeTracker(threshold = 40, maxDuration = 800) {
3
+ let sx = 0;
4
+ let sy = 0;
5
+ let st = 0;
6
+ let active = false;
7
+ return {
8
+ start(x, y, t) {
9
+ sx = x;
10
+ sy = y;
11
+ st = t;
12
+ active = true;
13
+ },
14
+ end(x, y, t) {
15
+ if (!active) return null;
16
+ active = false;
17
+ if (t - st > maxDuration) return null;
18
+ const dx = x - sx;
19
+ const dy = y - sy;
20
+ if (Math.abs(dx) < threshold && Math.abs(dy) < threshold) return null;
21
+ if (Math.abs(dx) > Math.abs(dy)) return dx < 0 ? "left" : "right";
22
+ return dy < 0 ? "up" : "down";
23
+ }
24
+ };
25
+ }
26
+ //#endregion
27
+ export { createSwipeTracker };