surf-core 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berkan Çetinkaya
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,205 @@
1
+ # SURF [![Build Status](https://ci.berkan.cc/api/badges/2/status.svg)](https://ci.berkan.cc/repos/2)
2
+
3
+ HTML-first, server-driven UI framework with local client-side state.
4
+
5
+ > **Mental Model**: "Surface changes, Cell lives."
6
+
7
+ ## Philosophy
8
+
9
+ - **Server is the source of truth** — All data and validation lives on the server
10
+ - **Client handles local motion** — Only temporary, interactive state on the client
11
+ - **HTML is the data format** — UI changes through HTML patches, not JSON APIs
12
+ - **No build step required** — Works directly in the browser, no transpilation needed
13
+ - **Progressive enhancement** — Works without JS, enhanced with JS
14
+
15
+ ## Non-Goals
16
+
17
+ - ❌ Global client-side store
18
+ - ❌ Virtual DOM
19
+ - ❌ Mandatory JSON APIs
20
+ - ❌ Hidden magic or implicit behavior
21
+
22
+ ## Installation
23
+
24
+ ```html
25
+ <script src="https://unpkg.com/surf@latest/dist/surf.min.js"></script>
26
+ ```
27
+
28
+ Or install via npm:
29
+
30
+ ```bash
31
+ npm install surf
32
+ ```
33
+
34
+ ## Core Concepts
35
+
36
+ ### Surface
37
+
38
+ A Surface is a DOM region that can be replaced by server responses.
39
+
40
+ ```html
41
+ <main id="main" d-surface>
42
+ <!-- Content that can be replaced -->
43
+ </main>
44
+ ```
45
+
46
+ ### Cell
47
+
48
+ A Cell is a local, client-side state container. Cells survive Surface updates.
49
+
50
+ ```html
51
+ <div d-cell="{ count: 0 }">
52
+ <span d-text="count"></span>
53
+ <button d-signal="click: count = count + 1">+</button>
54
+ </div>
55
+ ```
56
+
57
+ ### Signal
58
+
59
+ Signals define reactive behavior inside a Cell.
60
+
61
+ | `d-signal` | Event handler | `d-signal="click: open = true"` |
62
+ | `d-text` | Text binding | `d-text="count"` |
63
+ | `d-show` | Conditional display | `d-show="open"` |
64
+ | `d-attr` | Attribute binding | `d-attr="disabled: loading"` |
65
+ | `d-attr` | Class toggling | `d-attr="class.active: isActive"` |
66
+
67
+ ### Signal Features
68
+
69
+ **Form Resetting:**
70
+ Use the `reset` keyword to clear forms declaratively. Surf automatically ensures this happens *after* submission data is captured.
71
+
72
+ ```html
73
+ <form d-pulse="commit" d-signal="submit: reset">
74
+ <input name="msg">
75
+ </form>
76
+ ```
77
+
78
+ **Native Methods (`this.method()`):**
79
+ Call native DOM methods on the element triggering the signal.
80
+
81
+ ```html
82
+ <input type="text" d-signal="focus: this.select()">
83
+ <video d-signal="mouseenter: this.play()"></video>
84
+ ```
85
+
86
+ ### Plugins
87
+
88
+ Surf supports a lightweight plugin system.
89
+
90
+ ```javascript
91
+ import Surf from './surf.js';
92
+ import DragAndDrop from './plugins/drag-and-drop.js';
93
+
94
+ Surf.use(DragAndDrop);
95
+ ```
96
+
97
+ **Drag & Drop Plugin:**
98
+ Enable drag-and-drop reordering with simple attributes.
99
+
100
+ ```html
101
+ <div d-drag-zone="group-name" d-drag-url="/api/move">
102
+ <div d-drag-handle>...</div>
103
+ </div>
104
+ ```
105
+
106
+ ### Pulse
107
+
108
+ A Pulse triggers server interaction.
109
+
110
+ ```html
111
+ <!-- Navigation (GET) -->
112
+ <a href="/page" d-pulse="navigate" d-target="#main">Go to Page</a>
113
+
114
+ <!-- Action (POST) -->
115
+ <!-- Sends Cell state + data attributes -->
116
+ <button d-pulse="action" d-action="/api/like" data-id="123">Like</button>
117
+
118
+ <!-- Form submission (POST) -->
119
+ <form d-pulse="commit" d-target="#form">
120
+ <input name="email" required>
121
+ <button type="submit">Submit</button>
122
+ </form>
123
+
124
+ <!-- Refresh content -->
125
+ <button d-pulse="refresh" d-target="#main">Refresh</button>
126
+ ```
127
+
128
+ ### Auto-Refresh
129
+
130
+ Surfaces can automatically poll the server for updates.
131
+
132
+ ```html
133
+ <div d-surface d-auto-refresh="5000" d-auto-refresh-url="/api/news">
134
+ <!-- Content updates every 5 seconds -->
135
+ </div>
136
+ ```
137
+
138
+ ### Patch
139
+
140
+ Server returns HTML patches to update Surfaces.
141
+
142
+ ```html
143
+ <d-patch>
144
+ <surface target="#main">
145
+ <h1>Updated Content</h1>
146
+ </surface>
147
+ <surface target="#toast">
148
+ <div class="toast">Saved!</div>
149
+ </surface>
150
+ </d-patch>
151
+ ```
152
+
153
+ ## JavaScript API
154
+
155
+ ```javascript
156
+ // Navigate to URL
157
+ Surf.go('/page', { target: '#main' });
158
+
159
+ // Refresh a surface
160
+ Surf.refresh('#main');
161
+
162
+ // Listen to events
163
+ Surf.on('before:pulse', (e) => console.log('Loading...'));
164
+ Surf.on('after:patch', (e) => console.log('Done!'));
165
+ Surf.on('error:network', (e) => console.error(e.error));
166
+
167
+ // Manual state access
168
+ Surf.getState('#my-cell');
169
+ Surf.setState('#my-cell', { count: 5 });
170
+ ```
171
+
172
+ ## Echo Rule
173
+
174
+ > "Surface changes, Cell lives."
175
+
176
+ When a Surface is patched, Cell states are preserved. If a Cell with the same `id` or `d-cell-id` exists in the new content, its state is restored automatically.
177
+
178
+ ```html
179
+ <!-- Before patch: count = 5 -->
180
+ <div id="counter" d-cell="{ count: 0 }">
181
+ <span d-text="count">5</span>
182
+ </div>
183
+
184
+ <!-- After patch: count still = 5 (preserved by Echo) -->
185
+ ```
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ # Install dependencies
191
+ npm install
192
+
193
+ # Build
194
+ npm run build
195
+
196
+ # Watch mode
197
+ npm run dev
198
+
199
+ # Serve examples
200
+ npm run serve
201
+ ```
202
+
203
+ ## License
204
+
205
+ MIT
@@ -0,0 +1,2 @@
1
+ var SurfDebounce=(()=>{var i=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var f=(n,t)=>{for(var o in t)i(n,o,{get:t[o],enumerable:!0})},A=(n,t,o,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of b(t))!h.call(n,r)&&r!==o&&i(n,r,{get:()=>t[r],enumerable:!(e=p(t,r))||e.enumerable});return n};var $=n=>A(i({},"__esModule",{value:!0}),n);var I={};f(I,{default:()=>v});var v={install(n){let t=new WeakMap;document.addEventListener("input",o=>{let e=o.target;if(!e.hasAttribute("d-input"))return;let r=e.getAttribute("d-input"),u=parseInt(e.getAttribute("d-debounce")||"300",10),a=e.getAttribute("d-target"),c=parseInt(e.getAttribute("d-min-length")||"3",10),s=e.value.trim();t.has(e)&&clearTimeout(t.get(e));let l=setTimeout(()=>{if(s.length>0&&s.length<c)return;let g=r.includes("?")?"&":"?",d=e.name||"q",m=`${r}${g}${d}=${encodeURIComponent(s)}`;n.go(m,{target:a})},u);t.set(e,l)}),console.log("[Surf] Debounce Plugin installed")}};return $(I);})();
2
+ window.SurfDebounce=SurfDebounce.default
@@ -0,0 +1,2 @@
1
+ var SurfDragAndDrop=(()=>{var u=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var h=(a,o)=>{for(var n in o)u(a,n,{get:o[n],enumerable:!0})},L=(a,o,n,c)=>{if(o&&typeof o=="object"||typeof o=="function")for(let s of m(o))!D.call(a,s)&&s!==n&&u(a,s,{get:()=>o[s],enumerable:!(c=b(o,s))||c.enumerable});return a};var y=a=>L(u({},"__esModule",{value:!0}),a);var E={};h(E,{default:()=>A});var w={name:"drag-and-drop",install(a,o={}){let n=null,c=(t=document)=>{t.querySelectorAll('[d-draggable="true"]').forEach(e=>{e.setAttribute("draggable","true")})};c(),new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(r=>{r.nodeType===1&&(r.hasAttribute("d-draggable")&&r.setAttribute("draggable","true"),c(r))})})}).observe(document.body,{childList:!0,subtree:!0}),document.addEventListener("dragstart",t=>{if(t.target.closest("[d-no-drag]")){t.preventDefault();return}if(!t.target.closest('[d-draggable="true"]'))return;let e=t.target.closest('[d-draggable="true"]');n=e;let r=e.getAttribute("d-drag-data");r&&t.dataTransfer.setData("text/plain",r),t.dataTransfer.effectAllowed="move",e.classList.add("dragging")}),document.addEventListener("dragend",t=>{n&&(n.classList.remove("dragging"),n=null),document.querySelectorAll(".drag-over").forEach(e=>{e.classList.remove("drag-over")})}),document.addEventListener("dragover",t=>{let e=t.target.closest("[d-drop-zone]");e&&(t.preventDefault(),t.dataTransfer.dropEffect="move",e.classList.add("drag-over"))}),document.addEventListener("dragleave",t=>{let e=t.target.closest("[d-drop-zone]");if(!e)return;let r=e.getBoundingClientRect(),d=t.clientX,l=t.clientY;(d<r.left||d>=r.right||l<r.top||l>=r.bottom)&&e.classList.remove("drag-over")}),document.addEventListener("drop",async t=>{let e=t.target.closest("[d-drop-zone]");if(!e)return;t.preventDefault(),e.classList.remove("drag-over");let r=e.getAttribute("d-drop-url");if(r)try{let d=t.dataTransfer.getData("text/plain");if(!d)return;let l=JSON.parse(d),f=e.getAttribute("d-drop-data");if(f){let i=JSON.parse(f);Object.assign(l,i)}let p=new URLSearchParams;for(let[i,v]of Object.entries(l))p.append(i,v);let g=await fetch(r,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:p});if(g.ok){let i=await g.text();a&&a.applyPatch&&a.applyPatch(i)}else console.error("[Surf DnD] Drop request failed",g.status)}catch(d){console.error("[Surf DnD] Drop error",d)}}),console.log("[Surf] Drag & Drop plugin installed")}},A=w;return y(E);})();
2
+ window.SurfDragAndDrop=SurfDragAndDrop.default