mithril-lynx 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -206,11 +206,48 @@ Two tiers, matching the real complexity spread in Lynx's own `list` examples:
206
206
 
207
207
  **Deliberately out of scope for v1** (documented, not silently missing): deferred list items (ReactLynx's `defer`/`isReady` promise dance), `componentAtIndexes` batching, and independent per-item redraw after the initial bind — a bound cell's content is recomputed fresh from `renderItem(index)` only when native calls `componentAtIndex` for it (scroll-driven reuse), not automatically when app state changes.
208
208
 
209
+ ## Navigation
210
+
211
+ `mithril-lynx/navigation`'s `createNavigator({ initial, initialAttrs? })` is a stack-based, in-memory screen navigator — deliberately **not** built on `m.route` (see "Known permanent gaps" below: Lynx pages have no URL/History API for `m.route` to hook into). Android's own Activity navigation doesn't need URLs either — it's a plain back-stack — so this is that idea directly, not a URL-shaped abstraction forced onto an environment with no URLs.
212
+
213
+ ```js
214
+ import { createNavigator } from "mithril-lynx/navigation";
215
+ import m from "mithril";
216
+
217
+ const Home = {
218
+ view: (vnode) => m("view", { ontap: () => vnode.attrs.nav.push(Details, { id: 42 }) }, [
219
+ m("text", null, "Go to details"),
220
+ ]),
221
+ };
222
+ const Details = {
223
+ view: (vnode) => m("view", { ontap: () => vnode.attrs.nav.pop() }, [
224
+ m("text", null, "Details for #" + vnode.attrs.id + " — tap to go back"),
225
+ ]),
226
+ };
227
+
228
+ const nav = createNavigator({ initial: Home });
229
+ shim.renderToPage(page, m(nav.Navigator)); // main-thread-owned mode
230
+ ```
231
+
232
+ Every screen the navigator renders receives its own `attrs` plus a `nav` prop (`push`/`pop`/`replace`/`canGoBack`/`depth`), so screens don't need to import the navigator instance separately to navigate onward. Only the top of the stack is ever mounted — previous screens are torn down, not kept alive offscreen (matching how most single-activity/single-page navigators behave); a popped screen that needs to remember its own state should keep that state somewhere the app already owns (a module-level store, `background.js`'s data store, etc.), not rely on its own component instance surviving the pop.
233
+
234
+ Built on `shim.redraw()` alone, so it works unmodified in all three rendering modes (main-thread-owned, data-channel, renderer) — `nav.push()`/`pop()`/`replace()` just trigger whichever redraw mechanism that mode already uses.
235
+
236
+ **Deliberately out of scope for v1**: screen transition animations (left entirely to the app's own CSS/styling on whatever wraps `nav.Navigator`), and hardware back-button integration (no documented Lynx PAPI hook for it was found — wire a screen's own back-affordance to `nav.pop()` instead, as in the example above).
237
+
209
238
  ## Known permanent gaps
210
239
 
211
240
  - `m.trust` / innerHTML vnodes — no Lynx PAPI equivalent to raw innerHTML injection.
212
241
  - `m.route` — Lynx pages aren't URL-addressable the way DOM `history` is.
213
242
 
243
+ ## Known gap, not permanent
244
+
245
+ - `m.request` — throws (`XMLHttpRequest is not defined`) rather than silently misbehaving: it's hard-wired to a real `XMLHttpRequest`, which doesn't exist in Lynx's JS runtime (neither the main-thread Lepus/QuickJS engine nor the background JS thread). Unlike `m.trust`/`m.route` above, this isn't structural — Lynx does have its own networking primitives — it just hasn't been wrapped in a `$window`-shaped compat layer yet. Use Lynx's own networking API directly (wrapped in a `Promise`, if desired) until this exists.
246
+
247
+ ## Rest of the public `m` API — what's actually used
248
+
249
+ Beyond hyperscript (`m(...)`) itself, only `m.fragment` and `m.censor` are used as shipped from the real `mithril` package — both are pure data/diff logic with no DOM dependency, so they work unmodified. `m.render`, `m.mount`, and `m.redraw` are never called from the real package at all: `mithril-lynx` has its own equivalents (`shim.renderToPage()`/`shim.render()`/`shim.redraw()`, this README's own "Usage" sections) that target the Lynx Element PAPI instead of the DOM — calling the *real* `m.mount()`/`m.redraw()` does nothing here, since they're wired to `m.render()`'s own DOM-only render path, which this project's apps never invoke.
250
+
214
251
  ## Compat with the plain-JS ecosystem
215
252
 
216
253
  Mithril was never hooks-based, so — unlike React — there's no special rules-of-hooks compatibility story to build: `m.redraw()` after any state mutation already works with any plain-JS state library (a simple pub/sub store, streams, whatever). Nothing in this package needs to shim a specific state-management library for that reason; if something in the ecosystem doesn't work, it's not because of a hooks-equivalence gap.
@@ -0,0 +1,35 @@
1
+ // Ambient declaration for the ESM navigation.js (the file itself is not
2
+ // type-checked; this describes its runtime export shape for TS consumers).
3
+
4
+ import type { Component, ComponentTypes } from "mithril";
5
+
6
+ export interface Nav {
7
+ /** Pushes a new screen onto the stack and redraws. */
8
+ push(component: ComponentTypes<any, any>, attrs?: Record<string, unknown>): void;
9
+ /** Replaces the current top screen without growing the stack, and redraws. */
10
+ replace(component: ComponentTypes<any, any>, attrs?: Record<string, unknown>): void;
11
+ /** Pops the top screen and redraws. Returns false (a no-op) at the root screen. */
12
+ pop(): boolean;
13
+ /** True if pop() would actually pop something (stack depth > 1). */
14
+ canGoBack(): boolean;
15
+ /** Current stack depth (1 at the root screen). */
16
+ depth(): number;
17
+ }
18
+
19
+ export interface Navigator extends Nav {
20
+ /** Mithril component that always renders whichever screen is on top of the stack. */
21
+ Navigator: Component;
22
+ }
23
+
24
+ export interface CreateNavigatorOptions {
25
+ /** The root screen, mounted first. */
26
+ initial: ComponentTypes<any, any>;
27
+ initialAttrs?: Record<string, unknown>;
28
+ }
29
+
30
+ /**
31
+ * Creates a stack-based, in-memory navigator (no m.route, no URLs — see
32
+ * navigation.js's header comment for why). Every screen the navigator
33
+ * renders receives its own attrs plus a `nav` prop shaped like {@link Nav}.
34
+ */
35
+ export function createNavigator(options: CreateNavigatorOptions): Navigator;
package/navigation.js ADDED
@@ -0,0 +1,76 @@
1
+ // navigation.js
2
+ //
3
+ // Stack-based, in-memory screen navigation (project plan follow-up, "basic
4
+ // Activity" template support) — deliberately NOT built on m.route. Real
5
+ // `m.route` is hard-wired to the browser's URL/History API (see README.md's
6
+ // "Known permanent gaps"), which has no Lynx equivalent: a Lynx page has no
7
+ // address bar, no back/forward, nothing URL-addressable. Android's own
8
+ // Activity navigation doesn't need URLs either — it's a plain back-stack —
9
+ // so this module ports that idea directly instead of forcing a URL-shaped
10
+ // abstraction onto an environment that has no URLs.
11
+ //
12
+ // Only the top of the stack is ever mounted (previous screens are torn
13
+ // down, not kept alive offscreen) — matching how most single-activity /
14
+ // single-page navigators actually behave, and avoiding the cost of keeping
15
+ // arbitrarily many past screens' DOM trees around. A popped screen that
16
+ // needs to remember its own state should keep that state somewhere the app
17
+ // already owns (a module-level store, background.js's data store, etc.),
18
+ // not rely on the screen's own component instance surviving the pop.
19
+
20
+ import shim from "./src/lynx-mithril-shim.js";
21
+ import m from "mithril";
22
+
23
+ /**
24
+ * Creates a navigator: a stack of {component, attrs} screens, plus a
25
+ * `Navigator` Mithril component that always renders whichever screen is on
26
+ * top. Every screen receives its own `attrs` PLUS a `nav` prop (this
27
+ * navigator's push/pop/replace/canGoBack), so screens don't need to import
28
+ * the navigator instance separately to navigate onward.
29
+ */
30
+ export function createNavigator(options) {
31
+ const { initial, initialAttrs } = options;
32
+ if (initial == null) throw new Error("mithril-lynx/navigation: createNavigator() requires an `initial` screen");
33
+
34
+ const stack = [{ component: initial, attrs: initialAttrs }];
35
+
36
+ function top() {
37
+ return stack[stack.length - 1];
38
+ }
39
+
40
+ function push(component, attrs) {
41
+ stack.push({ component, attrs });
42
+ shim.redraw();
43
+ }
44
+
45
+ function replace(component, attrs) {
46
+ stack[stack.length - 1] = { component, attrs };
47
+ shim.redraw();
48
+ }
49
+
50
+ /** Returns false (a no-op) at the root screen, true otherwise. */
51
+ function pop() {
52
+ if (stack.length <= 1) return false;
53
+ stack.pop();
54
+ shim.redraw();
55
+ return true;
56
+ }
57
+
58
+ function canGoBack() {
59
+ return stack.length > 1;
60
+ }
61
+
62
+ function depth() {
63
+ return stack.length;
64
+ }
65
+
66
+ const nav = { push, pop, replace, canGoBack, depth };
67
+
68
+ const Navigator = {
69
+ view() {
70
+ const { component, attrs } = top();
71
+ return m(component, Object.assign({}, attrs, { nav }));
72
+ },
73
+ };
74
+
75
+ return Object.assign({ Navigator }, nav);
76
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mithril-lynx",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Mithril.js rendered through Lynx's Element PAPI — a contract-complete port of mithril/render/render.js@2.3.8 to the Lynx main thread, packaged as a reusable framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,6 +49,10 @@
49
49
  "types": "./list.d.ts",
50
50
  "default": "./list.js"
51
51
  },
52
+ "./navigation": {
53
+ "types": "./navigation.d.ts",
54
+ "default": "./navigation.js"
55
+ },
52
56
  "./testing": {
53
57
  "types": "./testing.d.ts",
54
58
  "default": "./testing.js"
@@ -70,6 +74,8 @@
70
74
  "gesture.d.ts",
71
75
  "list.js",
72
76
  "list.d.ts",
77
+ "navigation.js",
78
+ "navigation.d.ts",
73
79
  "testing.js",
74
80
  "testing.d.ts",
75
81
  "CONTRACT.md"