@manyducks.co/dolla 2.0.0-alpha.8 → 2.0.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 (108) hide show
  1. package/README.md +222 -512
  2. package/dist/core/app.d.ts +24 -0
  3. package/dist/core/context.d.ts +147 -0
  4. package/dist/core/env.d.ts +3 -0
  5. package/dist/core/hooks.d.ts +70 -0
  6. package/dist/core/hooks.test.d.ts +1 -0
  7. package/dist/core/index.d.ts +25 -0
  8. package/dist/core/logger.d.ts +42 -0
  9. package/dist/core/logger.test.d.ts +0 -0
  10. package/dist/core/markup.d.ts +82 -0
  11. package/dist/core/markup.test.d.ts +0 -0
  12. package/dist/core/nodes/_markup.d.ts +36 -0
  13. package/dist/core/nodes/dom.d.ts +13 -0
  14. package/dist/core/nodes/dynamic.d.ts +22 -0
  15. package/dist/core/nodes/element.d.ts +27 -0
  16. package/dist/core/nodes/portal.d.ts +18 -0
  17. package/dist/core/nodes/repeat.d.ts +27 -0
  18. package/dist/core/nodes/view.d.ts +25 -0
  19. package/dist/core/ref.d.ts +19 -0
  20. package/dist/core/ref.test.d.ts +1 -0
  21. package/dist/core/signals.d.ts +100 -0
  22. package/dist/core/signals.test.d.ts +1 -0
  23. package/dist/{views → core/views}/default-crash-view.d.ts +11 -4
  24. package/dist/core/views/for.d.ts +21 -0
  25. package/dist/core/views/fragment.d.ts +7 -0
  26. package/dist/core/views/portal.d.ts +16 -0
  27. package/dist/core/views/show.d.ts +25 -0
  28. package/dist/fragment-BahD_BJA.js +7 -0
  29. package/dist/fragment-BahD_BJA.js.map +1 -0
  30. package/dist/{modules/http.d.ts → http/index.d.ts} +3 -5
  31. package/dist/http.js +150 -0
  32. package/dist/http.js.map +1 -0
  33. package/dist/i18n/index.d.ts +134 -0
  34. package/dist/i18n.js +309 -0
  35. package/dist/i18n.js.map +1 -0
  36. package/dist/index-DRJlxs-Q.js +535 -0
  37. package/dist/index-DRJlxs-Q.js.map +1 -0
  38. package/dist/index.js +160 -1386
  39. package/dist/index.js.map +1 -1
  40. package/dist/jsx-dev-runtime.d.ts +3 -2
  41. package/dist/jsx-dev-runtime.js +5 -12
  42. package/dist/jsx-dev-runtime.js.map +1 -1
  43. package/dist/jsx-runtime.d.ts +4 -3
  44. package/dist/jsx-runtime.js +9 -15
  45. package/dist/jsx-runtime.js.map +1 -1
  46. package/dist/logger-Aqi9m1CF.js +565 -0
  47. package/dist/logger-Aqi9m1CF.js.map +1 -0
  48. package/dist/markup-8jNhoqDe.js +1089 -0
  49. package/dist/markup-8jNhoqDe.js.map +1 -0
  50. package/dist/router/hooks.d.ts +2 -0
  51. package/dist/router/index.d.ts +3 -0
  52. package/dist/router/router.d.ts +166 -0
  53. package/dist/{routing.d.ts → router/router.utils.d.ts} +17 -3
  54. package/dist/router/router.utils.test.d.ts +1 -0
  55. package/dist/router.js +6 -0
  56. package/dist/router.js.map +1 -0
  57. package/dist/typeChecking-5kmX0ulW.js +65 -0
  58. package/dist/typeChecking-5kmX0ulW.js.map +1 -0
  59. package/dist/typeChecking.d.ts +2 -98
  60. package/dist/typeChecking.test.d.ts +1 -0
  61. package/dist/types.d.ts +97 -25
  62. package/dist/utils.d.ts +25 -3
  63. package/docs/buildless.md +132 -0
  64. package/docs/components.md +238 -0
  65. package/docs/hooks.md +356 -0
  66. package/docs/http.md +178 -0
  67. package/docs/i18n.md +220 -0
  68. package/docs/index.md +10 -0
  69. package/docs/markup.md +136 -0
  70. package/docs/mixins.md +176 -0
  71. package/docs/ref.md +77 -0
  72. package/docs/router.md +281 -0
  73. package/docs/setup.md +137 -0
  74. package/docs/signals.md +262 -0
  75. package/docs/stores.md +113 -0
  76. package/docs/views.md +356 -0
  77. package/index.d.ts +2 -2
  78. package/notes/atomic.md +452 -0
  79. package/notes/elimination.md +33 -0
  80. package/notes/observable.md +180 -0
  81. package/notes/scratch.md +350 -18
  82. package/notes/splitting.md +5 -0
  83. package/package.json +29 -15
  84. package/vite.config.js +5 -11
  85. package/build.js +0 -34
  86. package/dist/index.d.ts +0 -21
  87. package/dist/markup.d.ts +0 -108
  88. package/dist/modules/dolla.d.ts +0 -111
  89. package/dist/modules/language.d.ts +0 -41
  90. package/dist/modules/render.d.ts +0 -17
  91. package/dist/modules/router.d.ts +0 -152
  92. package/dist/nodes/cond.d.ts +0 -26
  93. package/dist/nodes/html.d.ts +0 -31
  94. package/dist/nodes/observer.d.ts +0 -29
  95. package/dist/nodes/outlet.d.ts +0 -22
  96. package/dist/nodes/portal.d.ts +0 -19
  97. package/dist/nodes/repeat.d.ts +0 -34
  98. package/dist/nodes/text.d.ts +0 -19
  99. package/dist/passthrough-9kwwjgWk.js +0 -1279
  100. package/dist/passthrough-9kwwjgWk.js.map +0 -1
  101. package/dist/state.d.ts +0 -101
  102. package/dist/view.d.ts +0 -65
  103. package/dist/views/passthrough.d.ts +0 -5
  104. package/notes/context-vars.md +0 -21
  105. package/notes/readme-scratch.md +0 -222
  106. package/notes/route-middleware.md +0 -42
  107. package/tests/state.test.js +0 -135
  108. /package/dist/{routing.test.d.ts → core/context.test.d.ts} +0 -0
package/docs/mixins.md ADDED
@@ -0,0 +1,176 @@
1
+ # A Deep Dive into Dolla Mixins
2
+
3
+ Aight, let's talk about one of Dolla's most lowkey powerful features: **Mixins**. They're a sick way to add reusable superpowers directly to your HTML elements. Once you get the hang of them, you'll find all sorts of cool uses for 'em.
4
+
5
+ ## So, what even IS a Mixin?
6
+
7
+ A Mixin is a function that you can slap onto any DOM element to give it extra behavior. It doesn't render any new HTML itself; instead, it attaches to an existing element (that a View spit out) and enhances it.
8
+
9
+ Think of it like this:
10
+
11
+ - A **View** is like the car itself. It has a structure, seats, a steering wheel. It's the thing you see.
12
+ - A **Mixin** is like adding a turbocharger, a custom sound system, or underglow lighting to that car. It doesn't change the car's structure, but it gives it new abilities and makes it do cool stuff.
13
+
14
+ ## Why use a Mixin instead of just wrapping everything in a View?
15
+
16
+ That's the million-dollar question, fr. You _could_ make a `<HoverHighlightable>` View that wraps a `<div>`, but that gets messy fast.
17
+
18
+ Here's when you should reach for a Mixin:
19
+
20
+ 1. **The logic is all about ONE element.** If your code is just focused on making a single `<div>` or `<button>` do something cool, a Mixin is perfect. No need to create a whole new component just for that.
21
+ 2. **You're not adding new HTML.** Mixins are for adding behavior, not for rendering more stuff. If you need to spit out more `divs` and `spans`, that's a job for a View.
22
+ 3. **You wanna share the same behavior everywhere.** Got a cool "fade in on scroll" effect? Make it a mixin, and you can slap it on images, paragraphs, whole sections—anything\! It's way cleaner than copy-pasting logic or making a dozen different wrapper components.
23
+
24
+ Basically, Mixins are for **behavior**, and Views are for **structure**.
25
+
26
+ ## How to Make a Mixin
27
+
28
+ Making a mixin is a two-step function dance. It sounds weird, but it's super useful.
29
+
30
+ 1. The **outer function** is for configuration. It's where you can pass in options to customize the mixin's behavior. This function's job is to return...
31
+ 2. The **inner function**. This is the real meat of the mixin. It gets the actual DOM element as an argument, and inside this function, you can use all the Dolla hooks you know and love (`useMount`, `useEffect`, `useSignal`, etc.).
32
+
33
+ <!-- end list -->
34
+
35
+ ```jsx
36
+ // The outer "configuration" function
37
+ function myMixin(options) {
38
+ // The inner "attachment" function
39
+ return (element) => {
40
+ // `element` is the real HTML element!
41
+ // You can use hooks in here.
42
+ useMount(() => {
43
+ console.log(`${options.greeting} from my mixin! I'm attached to:`, element);
44
+ });
45
+ };
46
+ }
47
+ ```
48
+
49
+ ### Applying a Mixin
50
+
51
+ You apply a mixin to an element in your View using the `mixin` prop. Just call the outer function to get the inner function, and Dolla handles the rest.
52
+
53
+ ```jsx
54
+ function MyView() {
55
+ return <div mixin={myMixin({ greeting: "What up" })}>I have a mixin!</div>;
56
+ }
57
+ ```
58
+
59
+ ## Examples to Make it Click
60
+
61
+ ### Basic Example: `autofocus`
62
+
63
+ Let's start with a super simple one. Sometimes you just want an input to be focused the second it appears on the page.
64
+
65
+ ```jsx
66
+ import { useMount } from "@manyducks.co/dolla";
67
+
68
+ function autofocus() {
69
+ return (element) => {
70
+ useMount(() => {
71
+ // Just tell the element to focus itself when it mounts. Done.
72
+ element.focus();
73
+ });
74
+ };
75
+ }
76
+
77
+ // How to use it:
78
+ function LoginForm() {
79
+ return <input type="text" placeholder="Username" mixin={autofocus()} />;
80
+ }
81
+ ```
82
+
83
+ ### Real-World Example: `clickOutside`
84
+
85
+ This is a classic\! You need to close a dropdown or a modal when the user clicks anywhere else on the page. This is a total pain to do with Views, but it's a breeze with a mixin.
86
+
87
+ ```jsx
88
+ import { useMount, useSignal, Show } from "@manyducks.co/dolla";
89
+
90
+ function clickOutside(onOutsideClick) {
91
+ return (element) => {
92
+ useMount(() => {
93
+ const handleClick = (event) => {
94
+ // If the click is outside the element...
95
+ if (!element.contains(event.target)) {
96
+ // ...call the function we were given!
97
+ onOutsideClick();
98
+ }
99
+ };
100
+
101
+ document.addEventListener("mousedown", handleClick);
102
+ return () => document.removeEventListener("mousedown", handleClick);
103
+ });
104
+ };
105
+ }
106
+
107
+ // How to use it for a dropdown:
108
+ function DropdownMenu() {
109
+ const [$isOpen, setOpen] = useSignal(false);
110
+
111
+ return (
112
+ <div class="dropdown-container">
113
+ <button onClick={() => setOpen(true)}>Open Menu</button>
114
+ <Show when={$isOpen}>
115
+ <div class="menu" mixin={clickOutside(() => setOpen(false))}>
116
+ <p>Item 1</p>
117
+ <p>Item 2</p>
118
+ </div>
119
+ </Show>
120
+ </div>
121
+ );
122
+ }
123
+ ```
124
+
125
+ See how clean that is? The dropdown's logic for opening is in the View, but the reusable "closing" logic is tucked away in a mixin.
126
+
127
+ ### Advanced Example: `lazyLoadImage`
128
+
129
+ Let's make a mixin that uses the browser's `IntersectionObserver` API to only load an image when it scrolls into view. This is a huge performance win and a perfect job for a mixin.
130
+
131
+ ```jsx
132
+ import { useMount } from "@manyducks.co/dolla";
133
+
134
+ function lazyLoadImage() {
135
+ return (imgElement) => {
136
+ useMount(() => {
137
+ const observer = new IntersectionObserver((entries) => {
138
+ entries.forEach((entry) => {
139
+ // When the image is visible on screen...
140
+ if (entry.isIntersecting) {
141
+ // ...swap the real image src into place!
142
+ imgElement.src = imgElement.dataset.src;
143
+ // And we're done, so stop watching.
144
+ observer.unobserve(imgElement);
145
+ }
146
+ });
147
+ });
148
+
149
+ observer.observe(imgElement);
150
+
151
+ return () => observer.disconnect();
152
+ });
153
+ };
154
+ }
155
+
156
+ // How to use it:
157
+ function ImageGallery() {
158
+ return (
159
+ <div class="gallery">
160
+ {/* The initial src can be a tiny placeholder */}
161
+ <img mixin={lazyLoadImage()} src="/placeholder.gif" data-src="/real-image-1.jpg" alt="A cool pic" />
162
+ <img mixin={lazyLoadImage()} src="/placeholder.gif" data-src="/real-image-2.jpg" alt="Another cool pic" />
163
+ </div>
164
+ );
165
+ }
166
+ ```
167
+
168
+ This is peak mixin usage. The logic is 100% about that `<img>` element and its behavior. Making a `<LazyImage>` View for this would be total overkill.
169
+
170
+ ---
171
+
172
+ End.
173
+
174
+ - [🗂️ Docs](./index.md)
175
+ - [🏠 README](../README.md)
176
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/docs/ref.md ADDED
@@ -0,0 +1,77 @@
1
+ # Refs: For When You Gotta Get Your Hands Dirty
2
+
3
+ Aight, so what's a **ref**? It's basically a little box for holding onto a value. It's a function, so `myRef()` gets you the value, and `myRef(newValue)` sets a new one. If you try to get a value from an empty ref, it'll throw a fit, so heads up.
4
+
5
+ Think of it like a signal's uncool cousin—it holds stuff, but it's **not reactive**. It won't trigger updates. It's for when you need to break the rules a little.
6
+
7
+ ## Use Case \#1: Grabbing DOM elements
8
+
9
+ The main character energy of refs is grabbing actual HTML elements. Just yeet a `ref={myRef}` prop onto any element, and bam, the real DOM node lands in `myRef.current`. Now you can mess with it directly using regular JS, no cap. It's your secret backdoor to the DOM.
10
+
11
+ ```tsx
12
+ import { useRef } from "@manyducks.co/dolla";
13
+
14
+ function ExampleView() {
15
+ const element = useRef<HTMLElement>();
16
+
17
+ useMount(() => {
18
+ // We're just changing the text directly on the element. Wild.
19
+ element.current.innerText = "GOODBYE WORLD";
20
+ });
21
+
22
+ return <div ref={element}>HELLO WORLD</div>;
23
+ }
24
+ ```
25
+
26
+ ## Use Case \#2: Parent Controlling a Child
27
+
28
+ This is some next-level strats, fr. You can use a ref to let a parent component control a child. The child makes a little API object—like a remote control—and sends it up to the parent via a ref. Then the parent can just be like `controls.current.increment()` and make the child do stuff. It's a total power move for when you need to break the rules.
29
+
30
+ ```tsx
31
+ import { useRef, useSignal } from "@manyducks.co/dolla";
32
+
33
+ // The child component that's gonna get controlled
34
+ interface CounterViewControls {
35
+ increment(): void;
36
+ decrement(): void;
37
+ reset(): void;
38
+ }
39
+
40
+ function CounterView({ controls }: { controls: Ref<CounterViewControls> }) {
41
+ const [$count, setCount] = useSignal(0);
42
+
43
+ // We're making our "remote control" and giving it to the parent's ref
44
+ controls.current = {
45
+ increment: () => setCount((c) => c + 1),
46
+ decrement: () => setCount((c) => c - 1),
47
+ reset: () => setCount(0),
48
+ };
49
+
50
+ return <span>Count: {$count}</span>;
51
+ }
52
+
53
+ // The parent component doing the controlling
54
+ function ParentView() {
55
+ const controls = useRef<CounterViewControls>();
56
+
57
+ return (
58
+ <section>
59
+ <h1>Counter</h1>
60
+ <CounterView controls={controls} />
61
+
62
+ {/* Now we can use the remote control from the parent! */}
63
+ <button onClick={() => controls.current.increment()}>+1</button>
64
+ <button onClick={() => controls.current.decrement()}>-1</button>
65
+ <button onClick={() => controls.current.reset()}>=0</button>
66
+ </section>
67
+ );
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ End.
74
+
75
+ - [🗂️ Docs](./index.md)
76
+ - [🏠 README](../README.md)
77
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/docs/router.md ADDED
@@ -0,0 +1,281 @@
1
+ # The 411 on Dolla Routing
2
+
3
+ Aight, let's talk routing. If you're building a single-page app (aka an SPA), you're gonna need a router. It's the thing that lets you have different "pages" like `/home`, `/about`, and `/users/123` without the browser doing a full page refresh every time. It makes your app feel fast and snappy, and it gives you shareable links and a working back button, which is a must. Lowkey, an app without a router is a major L.
4
+
5
+ Dolla's router is built right in, and it's designed to be super intuitive but also powerful enough to handle whatever you throw at it.
6
+
7
+ ## Setting Up Your Router
8
+
9
+ First things first, you gotta create your router. You do this with the `createRouter` function. You give it a list of all the routes in your app. Then, instead of giving `createApp` your main component, you just give it the router you just made. Dolla handles the rest.
10
+
11
+ ```jsx
12
+ import { createApp } from "@manyducks.co/dolla";
13
+ import { createRouter } from "@manyducks.co/dolla/router";
14
+ import { HomePage, AboutPage, NotFoundPage } from "./views.js";
15
+
16
+ const router = createRouter({
17
+ // Use hash routing if you don't have a fancy server setup
18
+ // hash: true,
19
+ routes: [
20
+ { path: "/", view: HomePage },
21
+ { path: "/about", view: AboutPage },
22
+ { path: "*", view: NotFoundPage },
23
+ ],
24
+ });
25
+
26
+ const app = createApp(router);
27
+ app.mount(document.body);
28
+ ```
29
+
30
+ That's the basic setup. Now, when you go to `/about`, Dolla will automatically show the `AboutPage` component. Easy peasy.
31
+
32
+ ## Defining Routes
33
+
34
+ The `routes` array is the heart of your router. Each object in the array defines one route.
35
+
36
+ ### Route Patterns
37
+
38
+ The `path` property tells the router what the URL should look like. It can be simple or have dynamic parts.
39
+
40
+ - **Static**: `/dashboard/settings` - a plain old path.
41
+ - **Numeric Param**: `/users/{#id}` - The `{#id}` part will only match numbers, and the `id` param will be a number.
42
+ - **Generic Param**: `/users/{name}` - The `{name}` part will match any string.
43
+ - **Wildcard**: `/files/*` - This is a catch-all. It'll match `/files/` and anything that comes after it. It can only be at the end of a path.
44
+
45
+ Dolla uses **specificity-based matching**. That means the most specific route always wins, no matter what order you define them in. So `/users/new` will always match before `/users/{name}`. No more weird ordering bugs\! No cap.
46
+
47
+ ### Redirects
48
+
49
+ Instead of a `view`, you can give a route a `redirect` property. This is perfect for old URLs or for sending users from `/` to `/dashboard`.
50
+
51
+ ```jsx
52
+ const router = createRouter({
53
+ routes: [
54
+ { path: "/", redirect: "/dashboard" },
55
+ { path: "/dashboard", view: DashboardPage },
56
+ ],
57
+ });
58
+ ```
59
+
60
+ ### `beforeMatch`: The Gatekeeper
61
+
62
+ This is a super powerful one. `beforeMatch` is a function that runs _after_ a route matches but _before_ the view shows up. It's the perfect spot to do stuff like:
63
+
64
+ - Checking if a user is logged in.
65
+ - Fetching data for the page before it renders.
66
+ - Redirecting somewhere else based on some logic.
67
+
68
+ The `beforeMatch` function gets a context object (`ctx`) where you can call `ctx.redirect()` to send the user away, or `ctx.setState()` to pass data down to the view.
69
+
70
+ ```jsx
71
+ import { sessionStore } from "./stores"; // Pretend we have a global store
72
+
73
+ const router = createRouter({
74
+ routes: [
75
+ { path: "/login", view: LoginPage },
76
+ {
77
+ path: "/dashboard",
78
+ view: DashboardPage,
79
+ beforeMatch: (ctx) => {
80
+ // Gatekeep this route!
81
+ if (!sessionStore.isLoggedIn()) {
82
+ // If they're not logged in, yeet 'em to the login page.
83
+ ctx.redirect("/login");
84
+ }
85
+ },
86
+ },
87
+ ],
88
+ });
89
+ ```
90
+
91
+ ### `data`: Stashing Info on a Route
92
+
93
+ You can also just stick a `data` object on any route. It's a chill way to attach extra info, like a page title or breadcrumbs. This data will show up in the `$match` signal from the router.
94
+
95
+ ```jsx
96
+ const router = createRouter({
97
+ routes: [
98
+ {
99
+ path: "/",
100
+ view: HomePage,
101
+ data: { title: "Welcome Home" },
102
+ },
103
+ {
104
+ path: "/about",
105
+ view: AboutPage,
106
+ data: { title: "About Us" },
107
+ },
108
+ ],
109
+ });
110
+
111
+ // In some other component...
112
+ function DocumentTitle() {
113
+ const router = useRouter();
114
+ useEffect(() => {
115
+ // Grab the title from the matched route's data!
116
+ document.title = router.$match().data.title || "My App";
117
+ });
118
+ return null; // This component doesn't render anything
119
+ }
120
+ ```
121
+
122
+ ## Layout Routes: This is where it gets spicy
123
+
124
+ Okay, this is where it gets really cool. Most apps have a main layout—a navbar, a sidebar, a footer—and the actual page content changes inside that layout. Dolla makes this super easy.
125
+
126
+ To create a layout, you just make a route that has a `view` _and_ a nested `routes` array. That view becomes the layout for all the nested routes.
127
+
128
+ The best part? The child component that matches the nested route gets passed down to your layout component as the `children` prop. You can place it wherever you want\!
129
+
130
+ ### Example
131
+
132
+ Let's make a main app layout with a navbar and a footer.
133
+
134
+ **1. Create the Layout View:**
135
+
136
+ Notice how it just renders `props.children` wherever the page content should go.
137
+
138
+ ```jsx
139
+ // views/MainLayout.jsx
140
+ function MainLayout(props) {
141
+ return (
142
+ <div class="app-container">
143
+ <nav>
144
+ <a href="/">Home</a> | <a href="/about">About</a>
145
+ </nav>
146
+
147
+ <main>
148
+ {/* The child route will be rendered right here! */}
149
+ {props.children}
150
+ </main>
151
+
152
+ <footer>
153
+ <p>&copy; 2024 My Awesome App</p>
154
+ </footer>
155
+ </div>
156
+ );
157
+ }
158
+ ```
159
+
160
+ **2. Set up the Router to use the Layout:**
161
+
162
+ ```jsx
163
+ // router.js
164
+ import { createRouter } from "@manyducks.co/dolla/router";
165
+ import { MainLayout } from "./views/MainLayout.jsx";
166
+ import { HomePage, AboutPage } from "./views.js";
167
+
168
+ const router = createRouter({
169
+ routes: [
170
+ {
171
+ // This is our layout route
172
+ path: "/",
173
+ view: MainLayout,
174
+ // These routes will be rendered inside MainLayout
175
+ routes: [
176
+ { path: "/", view: HomePage },
177
+ { path: "/about", view: AboutPage },
178
+ ],
179
+ },
180
+ ],
181
+ });
182
+ ```
183
+
184
+ Now, when you go to `/` or `/about`, you'll see the `MainLayout` with either `HomePage` or `AboutPage` rendered inside that `<main>` tag. It's clean, it's simple, and it just works. It's giving... effortless.
185
+
186
+ ## Hopping Around Your App
187
+
188
+ Sometimes you need to change the page from your code, like after a form submission. For that, you use the `useRouter` hook.
189
+
190
+ ### `useRouter()`
191
+
192
+ This hook gives you the router instance, which has a bunch of useful methods and signals.
193
+
194
+ - `router.go(path, options?)`: The main way to navigate. You can also pass an `options` object.
195
+ - `replace: true`: Replaces the current page in history instead of adding a new one. The back button will skip it.
196
+ - `preserveQuery: true`: Keeps the current query params and merges them with any new ones.
197
+ - `router.back()`: Goes back one step in the browser history.
198
+ - `router.forward()`: Goes forward one step.
199
+ - `router.updateQuery(params)`: Just changes the query params in the URL without a full navigation. Super useful for filters and sorting.
200
+
201
+ <!-- end list -->
202
+
203
+ ```jsx
204
+ import { useRouter } from "@manyducks.co/dolla/router";
205
+
206
+ function SomeComponent() {
207
+ const router = useRouter();
208
+
209
+ const goToUser = () => {
210
+ // This will go to /users/42 but won't add a new history entry
211
+ router.go("/users/42", { replace: true });
212
+ };
213
+
214
+ const setSort = () => {
215
+ // This will just change the URL to ?sort=name without reloading
216
+ router.updateQuery({ sort: "name" });
217
+ };
218
+
219
+ return (
220
+ <>
221
+ <button onClick={goToUser}>Go to User 42</button>
222
+ <button onClick={setSort}>Sort by Name</button>
223
+ </>
224
+ );
225
+ }
226
+ ```
227
+
228
+ ## Spying on the Route
229
+
230
+ Your components often need to know what the current URL is, especially to get params like a user's ID. The `useRouter` hook gives you reactive signals for this too\!
231
+
232
+ - `$match`: A signal with the whole match object, including path, params, query, and any `data` you added to the route.
233
+ - `$path`: A signal with the current path (e.g., `/users/123`).
234
+ - `$params`: A signal with an object of the dynamic parts of the URL (e.g., `{ id: 123 }`).
235
+ - `$query`: A signal with an object of the query params (e.g., `?sort=asc` becomes `{ sort: 'asc' }`).
236
+ - `$pattern`: A signal with the full route pattern that was matched (e.g., `/users/{#id}`). If you're in a nested route, this will be the full joined path, like `/users/{#id}/posts`.
237
+
238
+ <!-- end list -->
239
+
240
+ ```jsx
241
+ import { useRouter } from "@manyducks.co/dolla/router";
242
+ import { useEffect, useSignal } from "@manyducks.co/dolla";
243
+
244
+ function UserProfilePage() {
245
+ const router = useRouter();
246
+ const { $params } = router;
247
+ const [$user, setUser] = useSignal(null);
248
+
249
+ // This effect will automatically re-run if the user ID in the URL changes!
250
+ useEffect(() => {
251
+ const userId = $params().id;
252
+ // You can't pass an async function directly to useEffect,
253
+ // so we define one inside and call it.
254
+ const fetchUser = async () => {
255
+ const data = await fetch(`/api/users/${userId}`).then((res) => res.json());
256
+ setUser(data);
257
+ };
258
+
259
+ if (userId) {
260
+ fetchUser();
261
+ }
262
+ });
263
+
264
+ return (
265
+ <div>
266
+ <h1>User Profile</h1>
267
+ <Show when={$user}>
268
+ <p>Name: {() => $user().name}</p>
269
+ </Show>
270
+ </div>
271
+ );
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ End.
278
+
279
+ - [🗂️ Docs](./index.md)
280
+ - [🏠 README](../README.md)
281
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)
package/docs/setup.md ADDED
@@ -0,0 +1,137 @@
1
+ # Dolla Setup Guide: Let's Get Cookin'
2
+
3
+ Aight, so you're ready to build something sick with Dolla. Bet. This guide will get you from zero to a running "Hello World" app in just a few minutes.
4
+
5
+ We're gonna use [Vite](https://vitejs.dev/) as our build tool. It's fast af, super easy to set up, and it works perfectly with Dolla's JSX.
6
+
7
+ ## Step 1: Make a New Vite Project
8
+
9
+ First things first, you need a place for your project to live. Pop open your terminal and run this command:
10
+
11
+ ```bash
12
+ npm create vite@latest
13
+ ```
14
+
15
+ Vite will ask you a few questions. Here's what you should pick:
16
+
17
+ 1. **Project name:** Go wild. Let's use `my-dolla-app` for this guide.
18
+ 2. **Select a framework:** Choose **Vanilla**. (Yeah, I know, but trust the process. We're adding Dolla ourselves).
19
+ 3. **Select a variant:** Choose **TypeScript** (or JavaScript if that's more your vibe).
20
+
21
+ Once it's done, hop into your new project directory:
22
+
23
+ ```bash
24
+ cd my-dolla-app
25
+ ```
26
+
27
+ ## Step 2: Install Dolla
28
+
29
+ Now that you're in your project, you just need to install Dolla from npm.
30
+
31
+ ```bash
32
+ npm install @manyducks.co/dolla
33
+ ```
34
+
35
+ This will add Dolla to your `package.json` and `node_modules`.
36
+
37
+ ## Step 3: Set Up JSX
38
+
39
+ You're gonna want to write JSX, right? It's way better than trying to write UI with just functions. To make it work, you just need to tell TypeScript (or JavaScript) how to handle it.
40
+
41
+ Open up your `tsconfig.json` file (or `jsconfig.json` if you're using plain JS) and add these two lines to the `compilerOptions`:
42
+
43
+ ```json
44
+ {
45
+ "compilerOptions": {
46
+ // ... a bunch of other options will be here ...
47
+
48
+ "jsx": "react-jsx",
49
+ "jsxImportSource": "@manyducks.co/dolla"
50
+ }
51
+ }
52
+ ```
53
+
54
+ - `"jsx": "react-jsx"` tells the compiler to use the modern JSX transform.
55
+ - `"jsxImportSource": "@manyducks.co/dolla"` tells it to use Dolla's functions when it sees JSX, not React's.
56
+
57
+ Vite will see this and automatically know what to do. No extra plugins needed. It's a whole vibe.
58
+
59
+ ## Step 4: Write Your First Component
60
+
61
+ Okay, time for the fun part. Let's clear out the default Vite stuff and write a real Dolla component.
62
+
63
+ Open up the main file at `src/main.ts` (or `main.js`) and replace everything in it with this:
64
+
65
+ ```jsx
66
+ import { createApp, useSignal } from "@manyducks.co/dolla";
67
+
68
+ // This is our main View component!
69
+ function App() {
70
+ const [$message, setMessage] = useSignal("Hello, Dolla!");
71
+
72
+ return (
73
+ <div>
74
+ <h1>{$message}</h1>
75
+ <p>Welcome to your new app.</p>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ // This is how we tell Dolla to start up.
81
+ // It builds your App component and sticks it into the #app element.
82
+ createApp(App).mount("#app");
83
+ ```
84
+
85
+ ## Step 5: Peep Your HTML
86
+
87
+ Vite gives you a basic `index.html` in the root of your project. It'll look something like this:
88
+
89
+ ```html
90
+ <!doctype html>
91
+ <html lang="en">
92
+ <head>
93
+ <meta charset="UTF-8" />
94
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
95
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
96
+ <title>Vite + TS</title>
97
+ </head>
98
+ <body>
99
+ <div id="app">
100
+ <!-- Your app mounts in here! -->
101
+ </div>
102
+ <script type="module" src="/src/main.ts"></script>
103
+ </body>
104
+ </html>
105
+ ```
106
+
107
+ The important line is `<script type="module" src="/src/main.ts"></script>`. That's what loads your app.
108
+
109
+ You can see we mounted our main view in `#app`, so your message will show up there.
110
+
111
+ ## Step 6: Run It\!
112
+
113
+ That's it, you're ready to go. Back in your terminal, run the dev server:
114
+
115
+ ```bash
116
+ npm run dev
117
+ ```
118
+
119
+ Vite will spit out a local URL (usually `http://localhost:5173`). Open that up in your browser, and you should see your "Hello, Dolla\!" message.
120
+
121
+ You're officially a Dolla developer. Slay.
122
+
123
+ ## What's Next?
124
+
125
+ Now that you're set up, you can start building for real. Here's where you should probably go next:
126
+
127
+ - [**Views**](./views.md): A deep dive into the main character of your app.
128
+ - [**Signals**](./signals.md) Learn all about the magic that makes your app reactive.
129
+ - [**Stores**](./stores.md): Figure out how to manage state without losing your mind.
130
+
131
+ ---
132
+
133
+ End.
134
+
135
+ - [🗂️ Docs](./index.md)
136
+ - [🏠 README](../README.md)
137
+ - [🦆 That's a lot of ducks.](https://www.manyducks.co)