xote 1.0.1

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/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ *.mjs linguist-generated
2
+ dist/* linguist-generated
@@ -0,0 +1,44 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ workflow_dispatch: {}
7
+
8
+ permissions:
9
+ contents: write # create releases, push changelog commits
10
+ issues: write # (optional) if plugins comment on issues
11
+ pull-requests: write # (optional) if plugins post to PRs
12
+ id-token: write
13
+
14
+ concurrency:
15
+ group: release-${{ github.ref }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ release:
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v4
25
+ with:
26
+ fetch-depth: 0 # semantic-release needs full history & tags
27
+
28
+ - name: Use Node.js 24
29
+ uses: actions/setup-node@v4
30
+ with:
31
+ node-version: 24.10.0
32
+ cache: npm
33
+ registry-url: https://registry.npmjs.org
34
+
35
+ - name: Install dependencies
36
+ run: npm install
37
+
38
+ - name: Build
39
+ run: npm run build
40
+ - name: Release
41
+ env:
42
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
44
+ run: npx semantic-release
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bernardo Gurgel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # xote (pronounced [ˈʃɔtʃi])
2
+
3
+ A lightweight, zero-dependency UI library for ReScript with fine-grained reactivity powered by signals. Build reactive web applications with automatic dependency tracking and efficient updates.
4
+
5
+ ## Features
6
+
7
+ - **Zero dependencies** - No runtime dependencies, pure ReScript implementation
8
+ - **Reactive primitives** - Signals, computed values, and effects
9
+ - **Component system** - Declarative UI components with automatic reactive updates
10
+ - **Automatic dependency tracking** - No manual subscription management
11
+ - **Batching and control** - Support for untracked reads and batched updates
12
+ - **Lightweight** - Small bundle size, minimal overhead
13
+
14
+ ## Getting Started
15
+
16
+ Comming soon.
17
+
18
+ ### Installation
19
+
20
+ Comming soon.
21
+
22
+ ### Quick Example
23
+
24
+ ```rescript
25
+ open Xote
26
+
27
+ // Create reactive state
28
+ let count = Signal.make(0)
29
+
30
+ // Create computed values
31
+ let doubled = Computed.make(() => Signal.get(count) * 2)
32
+
33
+ // Define event handlers
34
+ let increment = (_evt: Dom.event) => Signal.update(count, n => n + 1)
35
+
36
+ // Build your UI
37
+ let app = Component.div(
38
+ ~children=[
39
+ Component.h1(~children=[Component.text("Counter")], ()),
40
+ Component.p(~children=[
41
+ Component.textSignal(
42
+ Computed.make(() => "Count: " ++ Int.toString(Signal.get(count)))
43
+ )
44
+ ], ()),
45
+ Component.button(
46
+ ~events=[("click", increment)],
47
+ ~children=[Component.text("Increment")],
48
+ ()
49
+ )
50
+ ],
51
+ ()
52
+ )
53
+
54
+ // Mount to DOM
55
+ Component.mountById(app, "app")
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ ### Signals - Reactive State
61
+
62
+ Signals are the foundation of reactive state management in Xote.
63
+
64
+ ```rescript
65
+ // Create a signal with initial value
66
+ let count = Signal.make(0)
67
+
68
+ // Read signal value (tracks dependencies)
69
+ let value = Signal.get(count) // => 0
70
+
71
+ // Read without tracking
72
+ let value = Signal.peek(count) // => 0
73
+
74
+ // Update signal value
75
+ Signal.set(count, 1)
76
+
77
+ // Update with a function
78
+ Signal.update(count, n => n + 1)
79
+ ```
80
+
81
+ ### Computed - Derived State
82
+
83
+ Computed values automatically update when their dependencies change.
84
+
85
+ ```rescript
86
+ let count = Signal.make(5)
87
+
88
+ // Computed values are signals that derive from other signals
89
+ let doubled = Computed.make(() => Signal.get(count) * 2)
90
+
91
+ Signal.get(doubled) // => 10
92
+
93
+ Signal.set(count, 10)
94
+ Signal.get(doubled) // => 20
95
+ ```
96
+
97
+ ### Effects - Side Effects
98
+
99
+ Effects run automatically when their dependencies change.
100
+
101
+ ```rescript
102
+ let count = Signal.make(0)
103
+
104
+ // Effect runs immediately and re-runs when count changes
105
+ let disposer = Effect.run(() => {
106
+ Console.log("Count is now: " ++ Int.toString(Signal.get(count)))
107
+ })
108
+
109
+ Signal.set(count, 1) // Logs: "Count is now: 1"
110
+
111
+ // Clean up effect
112
+ disposer.dispose()
113
+ ```
114
+
115
+ ### Components - Building UI
116
+
117
+ #### Basic Elements
118
+
119
+ ```rescript
120
+ // Text nodes
121
+ Component.text("Hello, world!")
122
+
123
+ // Reactive text from signals
124
+ let name = Signal.make("Alice")
125
+ Component.textSignal(
126
+ Computed.make(() => "Hello, " ++ Signal.get(name))
127
+ )
128
+
129
+ // HTML elements
130
+ Component.div(
131
+ ~attrs=[("class", "container"), ("id", "main")],
132
+ ~events=[("click", handleClick)],
133
+ ~children=[
134
+ Component.h1(~children=[Component.text("Title")], ()),
135
+ Component.p(~children=[Component.text("Content")], ())
136
+ ],
137
+ ()
138
+ )
139
+ ```
140
+
141
+ #### Reactive Lists
142
+
143
+ Render lists that automatically update when data changes:
144
+
145
+ ```rescript
146
+ let todos = Signal.make([
147
+ {id: 1, text: "Learn Xote"},
148
+ {id: 2, text: "Build an app"}
149
+ ])
150
+
151
+ let todoItem = (todo) => {
152
+ Component.li(~children=[Component.text(todo.text)], ())
153
+ }
154
+
155
+ Component.ul(
156
+ ~children=[Component.list(todos, todoItem)],
157
+ ()
158
+ )
159
+ // List updates automatically when todos signal changes!
160
+ ```
161
+
162
+ #### Event Handling
163
+
164
+ ```rescript
165
+ let handleClick = (evt: Dom.event) => {
166
+ Console.log("Clicked!")
167
+ }
168
+
169
+ Component.button(
170
+ ~events=[("click", handleClick), ("mouseenter", handleHover)],
171
+ ~children=[Component.text("Click me")],
172
+ ()
173
+ )
174
+ ```
175
+
176
+ ### Utilities
177
+
178
+ #### Untracked Reads
179
+
180
+ Read signals without creating dependencies.
181
+
182
+ ```rescript
183
+ Core.untrack(() => {
184
+ let value = Signal.get(count) // Won't track this read
185
+ Console.log(value)
186
+ })
187
+ ```
188
+
189
+ #### Batching Updates
190
+
191
+ Group multiple updates to run effects only once.
192
+
193
+ ```rescript
194
+ Core.batch(() => {
195
+ Signal.set(count1, 10)
196
+ Signal.set(count2, 20)
197
+ Signal.set(count3, 30)
198
+ })
199
+ // Effects run once after all updates
200
+ ```
201
+
202
+ ## Complete Example
203
+
204
+ ### Counter Application
205
+
206
+ ```rescript
207
+ open Xote
208
+
209
+ // State management
210
+ let counterValue = Signal.make(0)
211
+
212
+ // Derived state
213
+ let counterDisplay = Computed.make(() =>
214
+ "Count: " ++ Int.toString(Signal.get(counterValue))
215
+ )
216
+
217
+ let isEven = Computed.make(() =>
218
+ mod(Signal.get(counterValue), 2) == 0
219
+ )
220
+
221
+ // Event handlers
222
+ let increment = (_evt: Dom.event) => {
223
+ Signal.update(counterValue, n => n + 1)
224
+ }
225
+
226
+ let decrement = (_evt: Dom.event) => {
227
+ Signal.update(counterValue, n => n - 1)
228
+ }
229
+
230
+ let reset = (_evt: Dom.event) => {
231
+ Signal.set(counterValue, 0)
232
+ }
233
+
234
+ // Side effects
235
+ let _ = Effect.run(() => {
236
+ Console.log("Counter changed: " ++ Int.toString(Signal.get(counterValue)))
237
+ })
238
+
239
+ // UI Component
240
+ let app = Component.div(
241
+ ~attrs=[("class", "app")],
242
+ ~children=[
243
+ Component.h1(~children=[Component.text("Xote Counter")], ()),
244
+
245
+ Component.div(
246
+ ~attrs=[("class", "counter-display")],
247
+ ~children=[
248
+ Component.h2(~children=[Component.textSignal(counterDisplay)], ()),
249
+ Component.p(~children=[
250
+ Component.textSignal(
251
+ Computed.make(() =>
252
+ Signal.get(isEven) ? "Even" : "Odd"
253
+ )
254
+ )
255
+ ], ())
256
+ ],
257
+ ()
258
+ ),
259
+
260
+ Component.div(
261
+ ~attrs=[("class", "controls")],
262
+ ~children=[
263
+ Component.button(
264
+ ~events=[("click", decrement)],
265
+ ~children=[Component.text("-")],
266
+ ()
267
+ ),
268
+ Component.button(
269
+ ~events=[("click", reset)],
270
+ ~children=[Component.text("Reset")],
271
+ ()
272
+ ),
273
+ Component.button(
274
+ ~events=[("click", increment)],
275
+ ~children=[Component.text("+")],
276
+ ()
277
+ )
278
+ ],
279
+ ()
280
+ )
281
+ ],
282
+ ()
283
+ )
284
+
285
+ // Mount to DOM
286
+ Component.mountById(app, "app")
287
+ ```
288
+
289
+ ## How It Works
290
+
291
+ ### Reactive System
292
+
293
+ - **Automatic dependency tracking** - When you read a signal with `Signal.get()` inside a computed or effect, it automatically tracks that dependency
294
+ - **Fine-grained updates** - Only the specific computeds and effects that depend on changed signals are re-executed
295
+ - **Synchronous by default** - Updates happen immediately and synchronously for predictable behavior
296
+ - **Batching support** - Group multiple updates to minimize re-computation
297
+
298
+ ### Component Rendering
299
+
300
+ - **Initial render** - Components are rendered to real DOM elements on mount
301
+ - **Reactive text** - `textSignal()` creates text nodes that automatically update when their signal changes
302
+ - **Reactive lists** - `Component.list()` creates lists that automatically update when data changes
303
+ - **Direct DOM manipulation** - No virtual DOM diffing, updates are precise and efficient
304
+ - **Effect-based** - Signal changes trigger effects that update specific DOM nodes automatically
305
+
306
+ ## Best Practices
307
+
308
+ 1. **Keep signals at the top level** - Define signals outside component definitions for proper reactivity
309
+ 2. **Use computed for derived state** - Don't repeat calculations, use `Computed.make()`
310
+ 3. **Use `Component.list()` for reactive arrays** - Let the framework handle list updates automatically
311
+ 4. **Batch related updates** - Use `Core.batch()` when updating multiple signals together
312
+ 5. **Dispose effects when done** - Call `disposer.dispose()` for effects you no longer need
313
+ 6. **Use `peek()` when you don't want tracking** - Read signal values without creating dependencies
314
+
315
+ ## License
316
+
317
+ MIT © 2025
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var e,o,t,r,a;exports.Component=e;exports.Computed=o;exports.Core=t;exports.Effect=r;exports.Signal=a;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/Xote.res.mjs"],"sourcesContent":["// Generated by ReScript, PLEASE EDIT WITH CARE\n\n\nvar Component;\n\nvar Computed;\n\nvar Core;\n\nvar Effect;\n\nvar Signal;\n\nexport {\n Component ,\n Computed ,\n Core ,\n Effect ,\n Signal ,\n}\n/* No side effect */\n"],"names":["Component","Computed","Core","Effect","Signal"],"mappings":"gFAGG,IAACA,EAEAC,EAEAC,EAEAC,EAEAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ var r, a, e, o, v;
2
+ export {
3
+ r as Component,
4
+ a as Computed,
5
+ e as Core,
6
+ o as Effect,
7
+ v as Signal
8
+ };
9
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/Xote.res.mjs"],"sourcesContent":["// Generated by ReScript, PLEASE EDIT WITH CARE\n\n\nvar Component;\n\nvar Computed;\n\nvar Core;\n\nvar Effect;\n\nvar Signal;\n\nexport {\n Component ,\n Computed ,\n Core ,\n Effect ,\n Signal ,\n}\n/* No side effect */\n"],"names":["Component","Computed","Core","Effect","Signal"],"mappings":"AAGG,IAACA,GAEAC,GAEAC,GAEAC,GAEAC;"}
@@ -0,0 +1,2 @@
1
+ (function(e,n){typeof exports=="object"&&typeof module!="undefined"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(e=typeof globalThis!="undefined"?globalThis:e||self,n(e.xote={}))})(this,(function(e){"use strict";var n,o,t,f,i;e.Component=n,e.Computed=o,e.Core=t,e.Effect=f,e.Signal=i,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/Xote.res.mjs"],"sourcesContent":["// Generated by ReScript, PLEASE EDIT WITH CARE\n\n\nvar Component;\n\nvar Computed;\n\nvar Core;\n\nvar Effect;\n\nvar Signal;\n\nexport {\n Component ,\n Computed ,\n Core ,\n Effect ,\n Signal ,\n}\n/* No side effect */\n"],"names":["Component","Computed","Core","Effect","Signal"],"mappings":"+OAGG,IAACA,EAEAC,EAEAC,EAEAC,EAEAC"}
@@ -0,0 +1,20 @@
1
+ ## [1.0.1](https://github.com/brnrdog/xote/compare/v1.0.0...v1.0.1) (2025-10-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * version bump for release ([72fb74f](https://github.com/brnrdog/xote/commit/72fb74f2cf3d4a2389daf5363457d5d7ad4eaed1))
7
+
8
+ # 1.0.0 (2025-10-30)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * improve signal reactivity and add todo styling ([495f0bb](https://github.com/brnrdog/xote/commit/495f0bb52eaa8de89214f957f30b078f07029569))
14
+
15
+
16
+ ### Features
17
+
18
+ * add Component system with automatic reactivity ([38815ed](https://github.com/brnrdog/xote/commit/38815ed3d1400c5511b790011d60081b317a69ac))
19
+ * add demo ([cf3faf3](https://github.com/brnrdog/xote/commit/cf3faf34c07d85a60d78d5f9539d2e2132f3b85a))
20
+ * minimal signal implementation based on the TC39 proposal ([9b78d0b](https://github.com/brnrdog/xote/commit/9b78d0b62ba21c953d909459036246f334b6613e))
package/index.html ADDED
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Xote Demo</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ darkMode: 'class',
11
+ theme: {
12
+ extend: {}
13
+ }
14
+ }
15
+ </script>
16
+ <style>
17
+ .todo-list li.completed span {
18
+ text-decoration: line-through;
19
+ @apply text-gray-500 dark:text-gray-400;
20
+ }
21
+ </style>
22
+ </head>
23
+ <body class="bg-gray-50 dark:bg-gray-900 min-h-screen">
24
+ <div id="app"></div>
25
+ <script type="module" src="./src/demo/TodoApp.res.mjs">
26
+ </script>
27
+ </body>
28
+ </html>
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "xote",
3
+ "version": "1.0.1",
4
+ "repository": {
5
+ "url": "https://github.com/brnrdog/xote"
6
+ },
7
+ "scripts": {
8
+ "res:build": "rescript",
9
+ "res:clean": "rescript clean",
10
+ "res:dev": "rescript -w",
11
+ "dev": "vite",
12
+ "build": "vite build",
13
+ "preview": "vite preview"
14
+ },
15
+ "keywords": [
16
+ "rescript",
17
+ "signals"
18
+ ],
19
+ "release": {
20
+ "branches": [
21
+ "main"
22
+ ],
23
+ "plugins": [
24
+ "@semantic-release/commit-analyzer",
25
+ "@semantic-release/release-notes-generator",
26
+ [
27
+ "@semantic-release/changelog",
28
+ {
29
+ "changelogFile": "docs/CHANGELOG.md"
30
+ }
31
+ ],
32
+ [
33
+ "@semantic-release/git",
34
+ {
35
+ "assets": [
36
+ "docs/CHANGELOG.md"
37
+ ]
38
+ }
39
+ ],
40
+ "@semantic-release/npm"
41
+ ]
42
+ },
43
+ "author": "Bernardo Gurgel <brnrdog@hey.com>",
44
+ "license": "MIT",
45
+ "devDependencies": {
46
+ "@rescript/core": "^1.6.1",
47
+ "@semantic-release/changelog": "^6.0.3",
48
+ "@semantic-release/commit-analyzer": "^13.0.1",
49
+ "@semantic-release/git": "^10.0.1",
50
+ "@semantic-release/github": "^12.0.0",
51
+ "@semantic-release/npm": "^13.1.1",
52
+ "@semantic-release/release-notes-generator": "^14.1.0",
53
+ "rescript": "^11.1.4",
54
+ "semantic-release": "^25.0.1",
55
+ "vite": "^7.1.12"
56
+ }
57
+ }
package/rescript.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "xote",
3
+ "sources": {
4
+ "dir": "src",
5
+ "subdirs": true
6
+ },
7
+ "package-specs": {
8
+ "module": "esmodule",
9
+ "in-source": true
10
+ },
11
+ "suffix": ".res.mjs",
12
+ "bs-dependencies": [
13
+ "@rescript/core"
14
+ ],
15
+ "bsc-flags": [
16
+ "-open RescriptCore"
17
+ ]
18
+ }
package/src/Xote.res ADDED
@@ -0,0 +1,5 @@
1
+ module Component = Xote__Component
2
+ module Computed = Xote__Computed
3
+ module Core = Xote__Core
4
+ module Effect = Xote__Effect
5
+ module Signal = Xote__Signal
@@ -0,0 +1,21 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+
4
+ var Component;
5
+
6
+ var Computed;
7
+
8
+ var Core;
9
+
10
+ var Effect;
11
+
12
+ var Signal;
13
+
14
+ export {
15
+ Component ,
16
+ Computed ,
17
+ Core ,
18
+ Effect ,
19
+ Signal ,
20
+ }
21
+ /* No side effect */