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/dist/surf.js ADDED
@@ -0,0 +1,911 @@
1
+ var SurfModule = (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+
23
+ // src/cell.js
24
+ var cell_exports = {};
25
+ __export(cell_exports, {
26
+ clearPreserved: () => clearPreserved,
27
+ default: () => cell_default,
28
+ findAll: () => findAll2,
29
+ getCellId: () => getCellId,
30
+ getState: () => getState,
31
+ init: () => init2,
32
+ initAll: () => initAll,
33
+ restore: () => restore,
34
+ setProperty: () => setProperty,
35
+ setState: () => setState,
36
+ snapshot: () => snapshot
37
+ });
38
+ function parseSeed(seedExpr) {
39
+ if (!seedExpr)
40
+ return {};
41
+ const trimmed = seedExpr.trim();
42
+ try {
43
+ const expression = trimmed.startsWith("{") ? `return ${trimmed}` : `return { ${trimmed} }`;
44
+ return new Function(expression)();
45
+ } catch (e) {
46
+ console.warn(`[Surf] Invalid seed expression: "${trimmed}"`, e);
47
+ return {};
48
+ }
49
+ }
50
+ function getCellId(element) {
51
+ return element.getAttribute(CELL_ID_ATTR) || element.id || null;
52
+ }
53
+ function findAll2(container = document) {
54
+ return container.querySelectorAll(`[${CELL_ATTR}]`);
55
+ }
56
+ function init2(element) {
57
+ if (!element.hasAttribute(CELL_ATTR)) {
58
+ console.warn("[Surf] Element is not a cell:", element);
59
+ return {};
60
+ }
61
+ const cellId = getCellId(element);
62
+ if (cellId && cellIdStates.has(cellId)) {
63
+ const preservedState = cellIdStates.get(cellId);
64
+ cellStates.set(element, preservedState);
65
+ return preservedState;
66
+ }
67
+ const cellExpr = element.getAttribute(CELL_ATTR);
68
+ const state = parseSeed(cellExpr || "{}");
69
+ cellStates.set(element, state);
70
+ if (cellId) {
71
+ cellIdStates.set(cellId, state);
72
+ }
73
+ return state;
74
+ }
75
+ function getState(element) {
76
+ if (!cellStates.has(element)) {
77
+ return init2(element);
78
+ }
79
+ return cellStates.get(element);
80
+ }
81
+ function setState(element, newState) {
82
+ const currentState = getState(element);
83
+ const updatedState = { ...currentState, ...newState };
84
+ cellStates.set(element, updatedState);
85
+ const cellId = getCellId(element);
86
+ if (cellId) {
87
+ cellIdStates.set(cellId, updatedState);
88
+ }
89
+ return updatedState;
90
+ }
91
+ function setProperty(element, key, value) {
92
+ return setState(element, { [key]: value });
93
+ }
94
+ function snapshot(container = document) {
95
+ const cells = findAll2(container);
96
+ const snap = /* @__PURE__ */ new Map();
97
+ cells.forEach((cell) => {
98
+ const cellId = getCellId(cell);
99
+ if (cellId) {
100
+ snap.set(cellId, { ...getState(cell) });
101
+ }
102
+ });
103
+ return snap;
104
+ }
105
+ function restore(snap) {
106
+ snap.forEach((state, cellId) => {
107
+ cellIdStates.set(cellId, state);
108
+ });
109
+ }
110
+ function initAll(container = document) {
111
+ const cells = findAll2(container);
112
+ cells.forEach((cell) => init2(cell));
113
+ }
114
+ function clearPreserved(cellId) {
115
+ cellIdStates.delete(cellId);
116
+ }
117
+ var CELL_ATTR, CELL_ID_ATTR, cellStates, cellIdStates, cell_default;
118
+ var init_cell = __esm({
119
+ "src/cell.js"() {
120
+ CELL_ATTR = "d-cell";
121
+ CELL_ID_ATTR = "d-cell-id";
122
+ cellStates = /* @__PURE__ */ new WeakMap();
123
+ cellIdStates = /* @__PURE__ */ new Map();
124
+ cell_default = {
125
+ findAll: findAll2,
126
+ getCellId,
127
+ init: init2,
128
+ getState,
129
+ setState,
130
+ setProperty,
131
+ snapshot,
132
+ restore,
133
+ initAll,
134
+ clearPreserved
135
+ };
136
+ }
137
+ });
138
+
139
+ // src/surf.js
140
+ var surf_exports = {};
141
+ __export(surf_exports, {
142
+ Cell: () => cell_exports,
143
+ Echo: () => echo_exports,
144
+ Patch: () => patch_exports,
145
+ Pulse: () => pulse_exports,
146
+ Signal: () => signal_exports,
147
+ Surface: () => surface_exports,
148
+ default: () => surf_default
149
+ });
150
+
151
+ // src/surface.js
152
+ var surface_exports = {};
153
+ __export(surface_exports, {
154
+ append: () => append,
155
+ default: () => surface_default,
156
+ findAll: () => findAll,
157
+ getById: () => getById,
158
+ getBySelector: () => getBySelector,
159
+ init: () => init,
160
+ prepend: () => prepend,
161
+ replace: () => replace
162
+ });
163
+ var SURFACE_ATTR = "d-surface";
164
+ function findAll() {
165
+ return document.querySelectorAll(`[${SURFACE_ATTR}]`);
166
+ }
167
+ function getById(id) {
168
+ const cleanId = id.startsWith("#") ? id.slice(1) : id;
169
+ const element = document.getElementById(cleanId);
170
+ if (element && element.hasAttribute(SURFACE_ATTR)) {
171
+ return element;
172
+ }
173
+ return null;
174
+ }
175
+ function getBySelector(selector) {
176
+ const element = document.querySelector(selector);
177
+ if (element && element.hasAttribute(SURFACE_ATTR)) {
178
+ return element;
179
+ }
180
+ return null;
181
+ }
182
+ function replace(selectorOrElement, html) {
183
+ let surface;
184
+ if (typeof selectorOrElement === "string") {
185
+ surface = getBySelector(selectorOrElement) || document.querySelector(selectorOrElement);
186
+ } else {
187
+ surface = selectorOrElement;
188
+ }
189
+ if (!surface) {
190
+ console.warn(`[Surf] Surface not found: ${selectorOrElement}`);
191
+ return null;
192
+ }
193
+ const template = document.createElement("template");
194
+ template.innerHTML = html.trim();
195
+ surface.innerHTML = "";
196
+ while (template.content.firstChild) {
197
+ surface.appendChild(template.content.firstChild);
198
+ }
199
+ return surface;
200
+ }
201
+ function append(selectorOrElement, html) {
202
+ return inject(selectorOrElement, html, "beforeend");
203
+ }
204
+ function prepend(selectorOrElement, html) {
205
+ return inject(selectorOrElement, html, "afterbegin");
206
+ }
207
+ function inject(selectorOrElement, html, position) {
208
+ let surface;
209
+ if (typeof selectorOrElement === "string") {
210
+ surface = getBySelector(selectorOrElement) || document.querySelector(selectorOrElement);
211
+ } else {
212
+ surface = selectorOrElement;
213
+ }
214
+ if (!surface) {
215
+ console.warn(`[Surf] Surface not found: ${selectorOrElement}`);
216
+ return null;
217
+ }
218
+ const template = document.createElement("template");
219
+ template.innerHTML = html.trim();
220
+ const fragment = document.createDocumentFragment();
221
+ while (template.content.firstChild) {
222
+ fragment.appendChild(template.content.firstChild);
223
+ }
224
+ if (position === "beforeend") {
225
+ surface.appendChild(fragment);
226
+ } else {
227
+ surface.insertBefore(fragment, surface.firstChild);
228
+ }
229
+ return surface;
230
+ }
231
+ function init() {
232
+ const surfaces = findAll();
233
+ surfaces.forEach((surface) => {
234
+ surface.setAttribute("data-surf-ready", "true");
235
+ });
236
+ }
237
+ var surface_default = {
238
+ findAll,
239
+ getById,
240
+ getBySelector,
241
+ replace,
242
+ append,
243
+ prepend,
244
+ init
245
+ };
246
+
247
+ // src/surf.js
248
+ init_cell();
249
+
250
+ // src/signal.js
251
+ var signal_exports = {};
252
+ __export(signal_exports, {
253
+ cleanup: () => cleanup,
254
+ default: () => signal_default,
255
+ initAll: () => initAll2,
256
+ register: () => register,
257
+ updateBindings: () => updateBindings
258
+ });
259
+ init_cell();
260
+ var SIGNAL_ATTR = "d-signal";
261
+ var TEXT_ATTR = "d-text";
262
+ var SHOW_ATTR = "d-show";
263
+ var ATTR_ATTR = "d-attr";
264
+ var modules = {};
265
+ function register(name, module) {
266
+ modules[name] = module;
267
+ }
268
+ var boundListeners = /* @__PURE__ */ new WeakMap();
269
+ function findParentCell(element) {
270
+ return element.closest("[d-cell]");
271
+ }
272
+ function evaluate(expr, state) {
273
+ if (!expr || !state)
274
+ return void 0;
275
+ const trimmed = expr.trim();
276
+ if (trimmed in state) {
277
+ return state[trimmed];
278
+ }
279
+ if (trimmed.startsWith("!")) {
280
+ const prop = trimmed.slice(1).trim();
281
+ return !state[prop];
282
+ }
283
+ const eqMatch = trimmed.match(/^(\w+)\s*={2,3}\s*(.+)$/);
284
+ if (eqMatch) {
285
+ const [, prop, value] = eqMatch;
286
+ const propVal = state[prop];
287
+ const compareVal = value === "true" ? true : value === "false" ? false : value.startsWith('"') || value.startsWith("'") ? value.slice(1, -1) : Number(value);
288
+ return propVal === compareVal;
289
+ }
290
+ const neqMatch = trimmed.match(/^(\w+)\s*!=\s*(.+)$/);
291
+ if (neqMatch) {
292
+ const [, prop, value] = neqMatch;
293
+ const propVal = state[prop];
294
+ const compareVal = value === "true" ? true : value === "false" ? false : Number(value);
295
+ return propVal !== compareVal;
296
+ }
297
+ const gtMatch = trimmed.match(/^(\w+)\s*>\s*(\d+)$/);
298
+ if (gtMatch) {
299
+ return state[gtMatch[1]] > Number(gtMatch[2]);
300
+ }
301
+ const ltMatch = trimmed.match(/^(\w+)\s*<\s*(\d+)$/);
302
+ if (ltMatch) {
303
+ return state[ltMatch[1]] < Number(ltMatch[2]);
304
+ }
305
+ return void 0;
306
+ }
307
+ function executeAssignment(expr, state, event, element) {
308
+ const trimmed = expr.trim();
309
+ const methodMatch = trimmed.match(/^(\w+)\.(\w+)\((.*)\)$/);
310
+ if (methodMatch) {
311
+ const [, moduleName, methodName, argsStr] = methodMatch;
312
+ const module = modules[moduleName];
313
+ if (!module || typeof module[methodName] !== "function") {
314
+ console.warn(`[Surf] Unknown module method: ${moduleName}.${methodName}`);
315
+ return {};
316
+ }
317
+ const args = parseArguments(argsStr, state, event, element);
318
+ const result = module[methodName](...args);
319
+ return typeof result === "object" ? result : {};
320
+ }
321
+ const match = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
322
+ if (!match)
323
+ return {};
324
+ const [, prop, valueExpr] = match;
325
+ const valueExprTrimmed = valueExpr.trim();
326
+ if (valueExprTrimmed === `!${prop}`) {
327
+ return { [prop]: !state[prop] };
328
+ }
329
+ if (valueExprTrimmed === "true") {
330
+ return { [prop]: true };
331
+ }
332
+ if (valueExprTrimmed === "false") {
333
+ return { [prop]: false };
334
+ }
335
+ const addMatch = valueExprTrimmed.match(/^(\w+)\s*\+\s*(\d+)$/);
336
+ if (addMatch) {
337
+ const [, srcProp, num] = addMatch;
338
+ return { [prop]: (state[srcProp] || 0) + Number(num) };
339
+ }
340
+ const subMatch = valueExprTrimmed.match(/^(\w+)\s*-\s*(\d+)$/);
341
+ if (subMatch) {
342
+ const [, srcProp, num] = subMatch;
343
+ return { [prop]: (state[srcProp] || 0) - Number(num) };
344
+ }
345
+ const maxMatch = valueExprTrimmed.match(/^Math\.max\((\w+)\s*-\s*(\d+),\s*(\d+)\)$/);
346
+ if (maxMatch) {
347
+ const [, srcProp, delta, min] = maxMatch;
348
+ const newVal = (state[srcProp] || 0) - Number(delta);
349
+ return { [prop]: Math.max(newVal, Number(min)) };
350
+ }
351
+ const minMatch = valueExprTrimmed.match(/^Math\.min\((\w+)\s*\+\s*(\d+),\s*(\d+)\)$/);
352
+ if (minMatch) {
353
+ const [, srcProp, delta, max] = minMatch;
354
+ const newVal = (state[srcProp] || 0) + Number(delta);
355
+ return { [prop]: Math.min(newVal, Number(max)) };
356
+ }
357
+ if (valueExprTrimmed.startsWith('"') || valueExprTrimmed.startsWith("'")) {
358
+ return { [prop]: valueExprTrimmed.slice(1, -1) };
359
+ }
360
+ if (/^\d+$/.test(valueExprTrimmed)) {
361
+ return { [prop]: Number(valueExprTrimmed) };
362
+ }
363
+ if (valueExprTrimmed in state) {
364
+ return { [prop]: state[valueExprTrimmed] };
365
+ }
366
+ return {};
367
+ }
368
+ function parseArguments(argsStr, state, event, element) {
369
+ if (argsStr.trim() === "")
370
+ return [];
371
+ return argsStr.split(",").map((arg) => {
372
+ const a = arg.trim();
373
+ if (a === "")
374
+ return void 0;
375
+ if (a === "true")
376
+ return true;
377
+ if (a === "false")
378
+ return false;
379
+ if (a === "event")
380
+ return event;
381
+ if (a === "this")
382
+ return element;
383
+ if (a.startsWith("'") || a.startsWith('"'))
384
+ return a.slice(1, -1);
385
+ if (!isNaN(a) && a !== "")
386
+ return Number(a);
387
+ if (a.startsWith("this.")) {
388
+ return resolvePath(element, a.slice(5));
389
+ }
390
+ return resolvePath(state, a);
391
+ }).filter((a) => a !== void 0 && a !== "");
392
+ }
393
+ function resolvePath(obj, path) {
394
+ const parts = path.split(".");
395
+ let current = obj;
396
+ for (const part of parts) {
397
+ if (current === void 0 || current === null)
398
+ return void 0;
399
+ current = current[part];
400
+ }
401
+ return current;
402
+ }
403
+ function updateBindings(cellElement) {
404
+ const state = getState(cellElement);
405
+ const textElements = cellElement.querySelectorAll(`[${TEXT_ATTR}]`);
406
+ textElements.forEach((el) => {
407
+ const prop = el.getAttribute(TEXT_ATTR);
408
+ const value = evaluate(prop, state);
409
+ if (value !== void 0) {
410
+ el.textContent = String(value);
411
+ }
412
+ });
413
+ const showElements = cellElement.querySelectorAll(`[${SHOW_ATTR}]`);
414
+ showElements.forEach((el) => {
415
+ const expr = el.getAttribute(SHOW_ATTR);
416
+ const visible = evaluate(expr, state);
417
+ el.style.display = visible ? "" : "none";
418
+ });
419
+ const attrElements = cellElement.querySelectorAll(`[${ATTR_ATTR}]`);
420
+ attrElements.forEach((el) => {
421
+ const attrExpr = el.getAttribute(ATTR_ATTR);
422
+ const classMatch = attrExpr.match(/^class\.([\\w-]+):\\s*(.+)$/);
423
+ if (classMatch) {
424
+ const [, className, expr2] = classMatch;
425
+ const shouldAdd = evaluate(expr2.trim(), state);
426
+ if (shouldAdd) {
427
+ el.classList.add(className);
428
+ } else {
429
+ el.classList.remove(className);
430
+ }
431
+ return;
432
+ }
433
+ const match = attrExpr.match(/^(\\w+):(.+)$/);
434
+ if (!match)
435
+ return;
436
+ const [, attrName, expr] = match;
437
+ const value = evaluate(expr.trim(), state);
438
+ if (value === void 0)
439
+ return;
440
+ if (typeof value !== "boolean") {
441
+ el.setAttribute(attrName, String(value));
442
+ return;
443
+ }
444
+ if (value) {
445
+ el.setAttribute(attrName, "");
446
+ } else {
447
+ el.removeAttribute(attrName);
448
+ }
449
+ });
450
+ }
451
+ function bindSignalElement(element) {
452
+ const signalExpr = element.getAttribute(SIGNAL_ATTR);
453
+ if (!signalExpr)
454
+ return;
455
+ const match = signalExpr.match(/^(\w+):\s*(.+)$/);
456
+ if (!match) {
457
+ console.warn(`[Surf] Invalid signal expression: ${signalExpr}`);
458
+ return;
459
+ }
460
+ const [, eventName, actionExpr] = match;
461
+ const cellElement = findParentCell(element);
462
+ if (!cellElement) {
463
+ console.warn("[Surf] Signal element has no parent cell:", element);
464
+ return;
465
+ }
466
+ const existing = boundListeners.get(element);
467
+ if (existing) {
468
+ element.removeEventListener(existing.event, existing.handler);
469
+ }
470
+ const handler = (e) => {
471
+ const state = getState(cellElement);
472
+ const changes = executeAssignment(actionExpr, state, e, element);
473
+ if (Object.keys(changes).length > 0) {
474
+ setState(cellElement, changes);
475
+ updateBindings(cellElement);
476
+ }
477
+ };
478
+ element.addEventListener(eventName, handler);
479
+ boundListeners.set(element, { event: eventName, handler });
480
+ }
481
+ function initAll2(container = document) {
482
+ const signalElements = container.querySelectorAll(`[${SIGNAL_ATTR}]`);
483
+ signalElements.forEach((el) => bindSignalElement(el));
484
+ const cells = findAll2(container);
485
+ cells.forEach((cell) => updateBindings(cell));
486
+ }
487
+ function cleanup(container) {
488
+ const signalElements = container.querySelectorAll(`[${SIGNAL_ATTR}]`);
489
+ signalElements.forEach((el) => {
490
+ const existing = boundListeners.get(el);
491
+ if (existing) {
492
+ el.removeEventListener(existing.event, existing.handler);
493
+ boundListeners.delete(el);
494
+ }
495
+ });
496
+ }
497
+ var signal_default = {
498
+ updateBindings,
499
+ initAll: initAll2,
500
+ cleanup,
501
+ evaluate,
502
+ executeAssignment,
503
+ register
504
+ };
505
+
506
+ // src/pulse.js
507
+ var pulse_exports = {};
508
+ __export(pulse_exports, {
509
+ action: () => action,
510
+ commit: () => commit,
511
+ default: () => pulse_default,
512
+ go: () => go,
513
+ init: () => init3,
514
+ navigate: () => navigate,
515
+ off: () => off,
516
+ on: () => on,
517
+ refresh: () => refresh
518
+ });
519
+
520
+ // src/patch.js
521
+ var patch_exports = {};
522
+ __export(patch_exports, {
523
+ create: () => create,
524
+ default: () => patch_default,
525
+ isPatch: () => isPatch,
526
+ parse: () => parse
527
+ });
528
+ function parse(html) {
529
+ const patches = [];
530
+ const parser = new DOMParser();
531
+ const doc = parser.parseFromString(html, "text/html");
532
+ const patchElement = doc.querySelector("d-patch");
533
+ if (!patchElement) {
534
+ console.warn("[Surf] No <d-patch> element found in response");
535
+ return patches;
536
+ }
537
+ const surfaceElements = patchElement.querySelectorAll("surface");
538
+ surfaceElements.forEach((surface) => {
539
+ const target = surface.getAttribute("target");
540
+ if (!target) {
541
+ console.warn("[Surf] Surface element missing target attribute");
542
+ return;
543
+ }
544
+ patches.push({
545
+ target,
546
+ content: surface.innerHTML
547
+ });
548
+ });
549
+ return patches;
550
+ }
551
+ function isPatch(html) {
552
+ return html.includes("<d-patch>") || html.includes("<d-patch ");
553
+ }
554
+ function create(surfaces) {
555
+ const surfaceHtml = surfaces.map((s) => ` <surface target="${s.target}">${s.content}</surface>`).join("\n");
556
+ return `<d-patch>
557
+ ${surfaceHtml}
558
+ </d-patch>`;
559
+ }
560
+ var patch_default = {
561
+ parse,
562
+ isPatch,
563
+ create
564
+ };
565
+
566
+ // src/echo.js
567
+ var echo_exports = {};
568
+ __export(echo_exports, {
569
+ after: () => after,
570
+ before: () => before,
571
+ default: () => echo_default,
572
+ withPreservation: () => withPreservation
573
+ });
574
+ init_cell();
575
+ function before(surface) {
576
+ cleanup(surface);
577
+ return snapshot(surface);
578
+ }
579
+ function after(surface, snapshot2) {
580
+ restore(snapshot2);
581
+ initAll(surface);
582
+ initAll2(surface);
583
+ }
584
+ function withPreservation(surface, newContent, replaceFn) {
585
+ const snapshot2 = before(surface);
586
+ replaceFn();
587
+ after(surface, snapshot2);
588
+ }
589
+ var echo_default = {
590
+ before,
591
+ after,
592
+ withPreservation
593
+ };
594
+
595
+ // src/pulse.js
596
+ var PULSE_ATTR = "d-pulse";
597
+ var TARGET_ATTR = "d-target";
598
+ var ACTION_ATTR = "d-action";
599
+ var listeners = {
600
+ "before:pulse": [],
601
+ "after:patch": [],
602
+ "error:network": []
603
+ };
604
+ function emit(event, detail) {
605
+ if (listeners[event]) {
606
+ listeners[event].forEach((cb) => {
607
+ try {
608
+ cb(detail);
609
+ } catch (e) {
610
+ console.error(`[Surf] Error in ${event} listener:`, e);
611
+ }
612
+ });
613
+ }
614
+ }
615
+ function on(event, callback) {
616
+ if (listeners[event]) {
617
+ listeners[event].push(callback);
618
+ }
619
+ }
620
+ function off(event, callback) {
621
+ if (listeners[event]) {
622
+ const index = listeners[event].indexOf(callback);
623
+ if (index > -1) {
624
+ listeners[event].splice(index, 1);
625
+ }
626
+ }
627
+ }
628
+ function applyPatches(patches) {
629
+ patches.forEach(({ target, content }) => {
630
+ const surface = getBySelector(target) || document.querySelector(target);
631
+ if (!surface) {
632
+ console.warn(`[Surf] Target not found for patch: ${target}`);
633
+ return;
634
+ }
635
+ withPreservation(surface, content, () => {
636
+ replace(target, content);
637
+ });
638
+ });
639
+ }
640
+ async function sendPulse(url, options, targetSelector) {
641
+ emit("before:pulse", { url, options, target: targetSelector });
642
+ try {
643
+ const response = await fetch(url, {
644
+ ...options,
645
+ headers: {
646
+ "Accept": "text/html",
647
+ "X-Surf-Request": "true",
648
+ ...options.headers
649
+ }
650
+ });
651
+ if (!response.ok) {
652
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
653
+ }
654
+ const html = await response.text();
655
+ if (isPatch(html)) {
656
+ const patches = parse(html);
657
+ applyPatches(patches);
658
+ } else {
659
+ if (targetSelector) {
660
+ const surface = getBySelector(targetSelector) || document.querySelector(targetSelector);
661
+ if (surface) {
662
+ const swapMode = options.swap || surface.getAttribute("d-swap") || "inner";
663
+ withPreservation(surface, html, () => {
664
+ if (swapMode === "append") {
665
+ append(surface, html);
666
+ } else if (swapMode === "prepend") {
667
+ prepend(surface, html);
668
+ } else {
669
+ replace(surface, html);
670
+ }
671
+ });
672
+ }
673
+ }
674
+ }
675
+ emit("after:patch", { url, target: targetSelector });
676
+ } catch (error) {
677
+ console.error("[Surf] Pulse error:", error);
678
+ emit("error:network", { url, error });
679
+ }
680
+ }
681
+ async function navigate(url, targetSelector, options = {}) {
682
+ await sendPulse(url, { method: "GET", ...options }, targetSelector);
683
+ if (targetSelector) {
684
+ history.pushState({ surf: true, url, target: targetSelector }, "", url);
685
+ }
686
+ }
687
+ async function commit(form, targetSelector) {
688
+ const method = form.method?.toUpperCase() || "POST";
689
+ let url = form.action || window.location.href;
690
+ const formData = new FormData(form);
691
+ const swap = form.getAttribute("d-swap");
692
+ const options = swap ? { swap } : {};
693
+ const params = new URLSearchParams();
694
+ formData.forEach((value, key) => params.append(key, value));
695
+ if (method === "GET") {
696
+ const separator = url.includes("?") ? "&" : "?";
697
+ url = url + separator + params.toString();
698
+ await sendPulse(url, { method: "GET", ...options }, targetSelector);
699
+ } else {
700
+ await sendPulse(url, {
701
+ method,
702
+ headers: {
703
+ "Content-Type": "application/x-www-form-urlencoded"
704
+ },
705
+ body: params.toString(),
706
+ ...options
707
+ }, targetSelector);
708
+ }
709
+ }
710
+ async function refresh(targetSelector) {
711
+ const url = window.location.href;
712
+ const surface = getBySelector(targetSelector) || document.querySelector(targetSelector);
713
+ const swap = surface?.getAttribute("d-swap");
714
+ await sendPulse(url, { method: "GET", swap }, targetSelector);
715
+ }
716
+ async function action(url, data = {}, targetSelector, options = {}) {
717
+ await sendPulse(url, {
718
+ method: "POST",
719
+ headers: {
720
+ "Content-Type": "application/json"
721
+ },
722
+ body: JSON.stringify(data),
723
+ ...options
724
+ }, targetSelector);
725
+ }
726
+ async function handleClick(event) {
727
+ const element = event.target.closest(`[${PULSE_ATTR}]`);
728
+ if (!element)
729
+ return;
730
+ const pulseType = element.getAttribute(PULSE_ATTR);
731
+ const targetSelector = element.getAttribute(TARGET_ATTR);
732
+ const actionUrl = element.getAttribute(ACTION_ATTR);
733
+ const swap = element.getAttribute("d-swap");
734
+ const options = swap ? { swap } : {};
735
+ if (element.tagName === "A" && pulseType === "navigate") {
736
+ event.preventDefault();
737
+ const url = element.href;
738
+ navigate(url, targetSelector, options);
739
+ return;
740
+ }
741
+ if (pulseType === "refresh") {
742
+ event.preventDefault();
743
+ refresh(targetSelector);
744
+ return;
745
+ }
746
+ if (pulseType === "action" && actionUrl) {
747
+ event.preventDefault();
748
+ const data = {};
749
+ for (const attr of element.attributes) {
750
+ if (attr.name.startsWith("data-") && attr.name !== "data-surf-ready") {
751
+ const key = attr.name.slice(5);
752
+ data[key] = attr.value;
753
+ }
754
+ }
755
+ const parentCell = element.closest("[d-cell]");
756
+ if (parentCell) {
757
+ const Cell = await Promise.resolve().then(() => (init_cell(), cell_exports));
758
+ const cellState = Cell.default ? Cell.default.getState(parentCell) : Cell.getState(parentCell);
759
+ if (cellState) {
760
+ Object.assign(data, cellState);
761
+ }
762
+ }
763
+ action(actionUrl, data, targetSelector, options);
764
+ return;
765
+ }
766
+ }
767
+ function handleSubmit(event) {
768
+ const form = event.target;
769
+ if (!form.hasAttribute(PULSE_ATTR))
770
+ return;
771
+ const pulseType = form.getAttribute(PULSE_ATTR);
772
+ const targetSelector = form.getAttribute(TARGET_ATTR);
773
+ if (pulseType === "commit") {
774
+ event.preventDefault();
775
+ commit(form, targetSelector);
776
+ }
777
+ }
778
+ function handlePopState(event) {
779
+ if (event.state?.surf) {
780
+ sendPulse(event.state.url, { method: "GET" }, event.state.target);
781
+ }
782
+ }
783
+ function init3() {
784
+ document.addEventListener("click", handleClick);
785
+ document.addEventListener("submit", handleSubmit);
786
+ window.addEventListener("popstate", handlePopState);
787
+ window.addEventListener("popstate", handlePopState);
788
+ }
789
+ async function go(url, options = {}) {
790
+ const target = options.target || "body";
791
+ await navigate(url, target, options);
792
+ }
793
+ var pulse_default = {
794
+ on,
795
+ off,
796
+ navigate,
797
+ commit,
798
+ refresh,
799
+ action,
800
+ go,
801
+ init: init3
802
+ };
803
+
804
+ // src/surf.js
805
+ var Surf = {
806
+ /**
807
+ * Framework version
808
+ */
809
+ version: "0.1.0",
810
+ /**
811
+ * Navigate to a URL
812
+ * @param {string} url - The URL to navigate to
813
+ * @param {Object} options - Optional settings (target, swap, etc)
814
+ */
815
+ go(url, options = {}) {
816
+ return go(url, options);
817
+ },
818
+ /**
819
+ * Refresh a surface's content from the server
820
+ * @param {string} selector - The surface selector to refresh
821
+ */
822
+ refresh(selector) {
823
+ return refresh(selector);
824
+ },
825
+ /**
826
+ * Subscribe to framework events
827
+ * @param {string} event - Event name: 'before:pulse', 'after:patch', 'error:network'
828
+ * @param {function} callback - Event handler
829
+ */
830
+ on(event, callback) {
831
+ on(event, callback);
832
+ },
833
+ /**
834
+ * Unsubscribe from framework events
835
+ * @param {string} event - Event name
836
+ * @param {function} callback - Event handler to remove
837
+ */
838
+ off(event, callback) {
839
+ off(event, callback);
840
+ },
841
+ /**
842
+ * Get cell state for an element
843
+ * @param {Element|string} cellOrSelector - Cell element or selector
844
+ * @returns {Object} The cell's current state
845
+ */
846
+ getState(cellOrSelector) {
847
+ const cell = typeof cellOrSelector === "string" ? document.querySelector(cellOrSelector) : cellOrSelector;
848
+ return getState(cell);
849
+ },
850
+ /**
851
+ * Set cell state for an element
852
+ * @param {Element|string} cellOrSelector - Cell element or selector
853
+ * @param {Object} state - State to merge
854
+ */
855
+ setState(cellOrSelector, state) {
856
+ const cell = typeof cellOrSelector === "string" ? document.querySelector(cellOrSelector) : cellOrSelector;
857
+ setState(cell, state);
858
+ updateBindings(cell);
859
+ },
860
+ /**
861
+ * Manually apply a patch response
862
+ * @param {string} patchHtml - The patch HTML string
863
+ */
864
+ applyPatch(patchHtml) {
865
+ const patches = parse(patchHtml);
866
+ patches.forEach(({ target, content }) => {
867
+ const surface = getBySelector(target) || document.querySelector(target);
868
+ if (surface) {
869
+ withPreservation(surface, content, () => {
870
+ replace(target, content);
871
+ });
872
+ }
873
+ });
874
+ },
875
+ /**
876
+ * Register a module for signal expressions
877
+ * @param {string} name - Module namespace
878
+ * @param {Object} module - Object with methods
879
+ */
880
+ register(name, module) {
881
+ register(name, module);
882
+ },
883
+ // Expose modules for advanced usage
884
+ _modules: {
885
+ Surface: surface_exports,
886
+ Cell: cell_exports,
887
+ Signal: signal_exports,
888
+ Pulse: pulse_exports,
889
+ Patch: patch_exports,
890
+ Echo: echo_exports
891
+ }
892
+ };
893
+ function init4() {
894
+ init();
895
+ initAll();
896
+ initAll2();
897
+ init3();
898
+ console.log(`[Surf] Initialized v${Surf.version}`);
899
+ }
900
+ if (document.readyState === "loading") {
901
+ document.addEventListener("DOMContentLoaded", init4);
902
+ } else {
903
+ init4();
904
+ }
905
+ var surf_default = Surf;
906
+ if (typeof window !== "undefined") {
907
+ window.Surf = Surf;
908
+ }
909
+ return __toCommonJS(surf_exports);
910
+ })();
911
+ window.Surf=SurfModule.default