bonemarrow 1.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 Karthick Raj
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.
@@ -0,0 +1,1068 @@
1
+ "use strict";
2
+ var bone = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ Collection: () => Collection,
25
+ Elements: () => Elements,
26
+ Model: () => Model,
27
+ View: () => View,
28
+ createScope: () => createScope,
29
+ el: () => el,
30
+ fetchJson: () => fetchJson
31
+ });
32
+
33
+ // src/core/scope.ts
34
+ function createScopeInternal(parent) {
35
+ const controller = new AbortController();
36
+ const cleanups = [];
37
+ const children = /* @__PURE__ */ new Set();
38
+ let disposed = false;
39
+ const scope = {
40
+ signal: controller.signal,
41
+ onDispose(fn) {
42
+ if (disposed) {
43
+ try {
44
+ fn();
45
+ } catch (error) {
46
+ console.error("[Scope] Error in dispose callback:", error);
47
+ }
48
+ return;
49
+ }
50
+ cleanups.push(fn);
51
+ },
52
+ createChild() {
53
+ if (disposed) {
54
+ console.warn(
55
+ "[Scope] Attempted to create child from disposed scope"
56
+ );
57
+ const deadScope = createScopeInternal(null);
58
+ deadScope.dispose();
59
+ return deadScope;
60
+ }
61
+ const child = createScopeInternal(scope);
62
+ children.add(child);
63
+ child.onDispose(() => children.delete(child));
64
+ return child;
65
+ },
66
+ dispose() {
67
+ if (disposed) return;
68
+ disposed = true;
69
+ for (const child of children) {
70
+ child.dispose();
71
+ }
72
+ children.clear();
73
+ controller.abort();
74
+ for (let i = cleanups.length - 1; i >= 0; i--) {
75
+ try {
76
+ cleanups[i]();
77
+ } catch (error) {
78
+ console.error(
79
+ "[Scope] Error in cleanup function:",
80
+ error
81
+ );
82
+ }
83
+ }
84
+ cleanups.length = 0;
85
+ if (scope._inFlight) {
86
+ scope._inFlight.clear();
87
+ delete scope._inFlight;
88
+ }
89
+ }
90
+ };
91
+ parent == null ? void 0 : parent.onDispose(() => scope.dispose());
92
+ return scope;
93
+ }
94
+ var windowScope = null;
95
+ function getWindowScope() {
96
+ if (windowScope) return windowScope;
97
+ windowScope = createScopeInternal(null);
98
+ const disposeAll = () => {
99
+ if (!windowScope) return;
100
+ windowScope.dispose();
101
+ windowScope = null;
102
+ };
103
+ window.addEventListener("pagehide", disposeAll);
104
+ window.addEventListener("beforeunload", disposeAll);
105
+ return windowScope;
106
+ }
107
+ function createScope() {
108
+ return getWindowScope().createChild();
109
+ }
110
+
111
+ // src/core/fetch.ts
112
+ function requestKey(url, init) {
113
+ var _a;
114
+ return `${(_a = init == null ? void 0 : init.method) != null ? _a : "GET"}:${url}`;
115
+ }
116
+ async function fetchJson(url, options = {}) {
117
+ var _a;
118
+ const {
119
+ scope,
120
+ abort = false,
121
+ timeout,
122
+ retryOnFailure = 0,
123
+ retryDelay = 0,
124
+ dedupe = false,
125
+ init,
126
+ parse
127
+ } = options;
128
+ const internalScope = scope;
129
+ let inFlight;
130
+ let key;
131
+ if (dedupe && internalScope) {
132
+ inFlight = (_a = internalScope._inFlight) != null ? _a : internalScope._inFlight = /* @__PURE__ */ new Map();
133
+ key = requestKey(url, init);
134
+ const existing = inFlight.get(key);
135
+ if (existing) {
136
+ return existing;
137
+ }
138
+ }
139
+ const promise = (async () => {
140
+ let attempt = 0;
141
+ while (true) {
142
+ const controller = new AbortController();
143
+ if (abort && scope) {
144
+ scope.signal.addEventListener(
145
+ "abort",
146
+ () => controller.abort(),
147
+ { once: true }
148
+ );
149
+ }
150
+ let timeoutId;
151
+ if (typeof timeout === "number") {
152
+ timeoutId = setTimeout(
153
+ () => controller.abort(),
154
+ timeout
155
+ );
156
+ }
157
+ try {
158
+ const res = await fetch(url, {
159
+ ...init,
160
+ signal: controller.signal
161
+ });
162
+ if (!res.ok) {
163
+ throw new Error(
164
+ `HTTP ${res.status}: ${res.statusText}`
165
+ );
166
+ }
167
+ const json = await res.json();
168
+ return parse ? parse(json) : json;
169
+ } catch (err) {
170
+ attempt++;
171
+ if (err instanceof DOMException && err.name === "AbortError") {
172
+ throw err;
173
+ }
174
+ if (attempt > retryOnFailure) {
175
+ throw err;
176
+ }
177
+ if (retryDelay > 0) {
178
+ await new Promise(
179
+ (resolve) => setTimeout(resolve, retryDelay)
180
+ );
181
+ }
182
+ } finally {
183
+ if (timeoutId) {
184
+ clearTimeout(timeoutId);
185
+ }
186
+ }
187
+ }
188
+ })();
189
+ if (inFlight && key && internalScope) {
190
+ inFlight.set(key, promise);
191
+ const cleanup = () => {
192
+ inFlight.delete(key);
193
+ };
194
+ promise.then(cleanup, cleanup);
195
+ internalScope.onDispose(cleanup);
196
+ }
197
+ return promise;
198
+ }
199
+
200
+ // src/core/emitter.ts
201
+ var Emitter = class {
202
+ constructor() {
203
+ this.events = /* @__PURE__ */ new Map();
204
+ }
205
+ on(event, fn, scope) {
206
+ let set = this.events.get(event);
207
+ if (!set) {
208
+ set = /* @__PURE__ */ new Set();
209
+ this.events.set(event, set);
210
+ }
211
+ set.add(fn);
212
+ let disposed = false;
213
+ const cleanup = () => {
214
+ if (disposed) return;
215
+ disposed = true;
216
+ set.delete(fn);
217
+ if (set.size === 0) {
218
+ this.events.delete(event);
219
+ }
220
+ };
221
+ scope == null ? void 0 : scope.onDispose(cleanup);
222
+ return cleanup;
223
+ }
224
+ once(event, fn, scope) {
225
+ const wrapper = (...args) => {
226
+ cleanup();
227
+ fn(...args);
228
+ };
229
+ const cleanup = this.on(event, wrapper, scope);
230
+ return cleanup;
231
+ }
232
+ emit(event, ...args) {
233
+ const handlers = this.events.get(event);
234
+ if (!handlers || handlers.size === 0) return;
235
+ const handlersArray = Array.from(handlers);
236
+ for (const fn of handlersArray) {
237
+ try {
238
+ fn(...args);
239
+ } catch (error) {
240
+ console.error(
241
+ `Error in event handler for "${event}":`,
242
+ error
243
+ );
244
+ }
245
+ }
246
+ }
247
+ off(event) {
248
+ this.events.delete(event);
249
+ }
250
+ clear() {
251
+ this.events.clear();
252
+ }
253
+ hasListeners(event) {
254
+ var _a, _b;
255
+ return ((_b = (_a = this.events.get(event)) == null ? void 0 : _a.size) != null ? _b : 0) > 0;
256
+ }
257
+ listenerCount(event) {
258
+ var _a, _b;
259
+ return (_b = (_a = this.events.get(event)) == null ? void 0 : _a.size) != null ? _b : 0;
260
+ }
261
+ eventNames() {
262
+ return Array.from(this.events.keys());
263
+ }
264
+ };
265
+
266
+ // src/refresh/sequentialRefresh.ts
267
+ function startSequentialRefresh(fn, opts) {
268
+ const {
269
+ interval,
270
+ scope,
271
+ immediate = true,
272
+ onError,
273
+ maxRetries = 0,
274
+ backoff = false
275
+ } = opts;
276
+ let stopped = false;
277
+ let timeoutId;
278
+ let consecutiveErrors = 0;
279
+ let isRunning = false;
280
+ const calculateDelay = () => {
281
+ if (!backoff || consecutiveErrors === 0) {
282
+ return interval;
283
+ }
284
+ const multiplier = Math.min(2 ** consecutiveErrors, 10);
285
+ return interval * multiplier;
286
+ };
287
+ const stop = () => {
288
+ if (stopped) return;
289
+ stopped = true;
290
+ if (timeoutId !== void 0) {
291
+ clearTimeout(timeoutId);
292
+ timeoutId = void 0;
293
+ }
294
+ };
295
+ const loop = async () => {
296
+ if (stopped) return;
297
+ if (isRunning) {
298
+ console.warn(
299
+ "[SequentialRefresh] Previous execution still running"
300
+ );
301
+ return;
302
+ }
303
+ isRunning = true;
304
+ try {
305
+ await fn();
306
+ consecutiveErrors = 0;
307
+ } catch (error) {
308
+ consecutiveErrors++;
309
+ if (onError) {
310
+ try {
311
+ onError(error);
312
+ } catch (handlerError) {
313
+ console.error(
314
+ "[SequentialRefresh] Error in error handler:",
315
+ handlerError
316
+ );
317
+ }
318
+ } else {
319
+ console.error(
320
+ "[SequentialRefresh] Error in refresh function:",
321
+ error
322
+ );
323
+ }
324
+ if (maxRetries > 0 && consecutiveErrors >= maxRetries) {
325
+ console.error(
326
+ `[SequentialRefresh] Max retries (${maxRetries}) exceeded. Stopping refresh.`
327
+ );
328
+ stop();
329
+ return;
330
+ }
331
+ } finally {
332
+ isRunning = false;
333
+ }
334
+ if (!stopped) {
335
+ const delay = calculateDelay();
336
+ timeoutId = setTimeout(loop, delay);
337
+ }
338
+ };
339
+ if (immediate) {
340
+ loop();
341
+ } else {
342
+ timeoutId = setTimeout(loop, interval);
343
+ }
344
+ scope.onDispose(stop);
345
+ return stop;
346
+ }
347
+
348
+ // src/data/model.ts
349
+ var Model = class {
350
+ constructor(initial) {
351
+ this.emitter = new Emitter();
352
+ this.destroyed = false;
353
+ this.initial = { ...initial };
354
+ this.data = { ...initial };
355
+ }
356
+ get(key) {
357
+ this.checkDestroyed();
358
+ return this.data[key];
359
+ }
360
+ getAll() {
361
+ this.checkDestroyed();
362
+ return { ...this.data };
363
+ }
364
+ set(patch) {
365
+ this.checkDestroyed();
366
+ const changes = {};
367
+ let hasChanges = false;
368
+ for (const key of Object.keys(patch)) {
369
+ if (patch[key] !== this.data[key]) {
370
+ changes[key] = patch[key];
371
+ hasChanges = true;
372
+ }
373
+ }
374
+ if (!hasChanges) {
375
+ return false;
376
+ }
377
+ Object.assign(this.data, changes);
378
+ this.emitter.emit("change", changes);
379
+ return true;
380
+ }
381
+ reset() {
382
+ this.checkDestroyed();
383
+ const resetData = { ...this.initial };
384
+ const hasChanges = Object.keys(resetData).some(
385
+ (key) => resetData[key] !== this.data[key]
386
+ );
387
+ if (hasChanges) {
388
+ this.data = resetData;
389
+ this.emitter.emit("change", resetData);
390
+ }
391
+ }
392
+ has(key, value) {
393
+ this.checkDestroyed();
394
+ return this.data[key] === value;
395
+ }
396
+ onChange(fn, scope) {
397
+ this.checkDestroyed();
398
+ return this.emitter.on("change", fn, scope);
399
+ }
400
+ async fetch(url, options) {
401
+ this.checkDestroyed();
402
+ const patch = await fetchJson(url, options);
403
+ this.set(patch);
404
+ return this.getAll();
405
+ }
406
+ autoRefresh(url, options) {
407
+ this.checkDestroyed();
408
+ return startSequentialRefresh(
409
+ () => this.fetch(url, {
410
+ ...options.fetch,
411
+ scope: options.scope
412
+ }),
413
+ options
414
+ );
415
+ }
416
+ destroy() {
417
+ if (this.destroyed) return;
418
+ this.destroyed = true;
419
+ this.emitter.clear();
420
+ }
421
+ isDestroyed() {
422
+ return this.destroyed;
423
+ }
424
+ checkDestroyed() {
425
+ if (this.destroyed) {
426
+ throw new Error("[Model] Cannot use destroyed model");
427
+ }
428
+ }
429
+ };
430
+
431
+ // src/data/collection.ts
432
+ var Collection = class {
433
+ constructor() {
434
+ this.items = [];
435
+ this.emitter = new Emitter();
436
+ this.destroyed = false;
437
+ }
438
+ getAll() {
439
+ this.checkDestroyed();
440
+ return [...this.items];
441
+ }
442
+ get length() {
443
+ this.checkDestroyed();
444
+ return this.items.length;
445
+ }
446
+ at(index) {
447
+ this.checkDestroyed();
448
+ return this.items[index];
449
+ }
450
+ add(...items) {
451
+ this.checkDestroyed();
452
+ this.items.push(...items);
453
+ this.emitter.emit("add", items);
454
+ }
455
+ remove(predicate) {
456
+ this.checkDestroyed();
457
+ const removed = [];
458
+ for (let i = this.items.length - 1; i >= 0; i--) {
459
+ if (predicate(this.items[i], i)) {
460
+ removed.unshift(this.items[i]);
461
+ this.items.splice(i, 1);
462
+ }
463
+ }
464
+ if (removed.length > 0) {
465
+ this.emitter.emit("remove", removed);
466
+ }
467
+ return removed;
468
+ }
469
+ removeAt(index) {
470
+ this.checkDestroyed();
471
+ if (index < 0 || index >= this.items.length) {
472
+ return void 0;
473
+ }
474
+ const [item] = this.items.splice(index, 1);
475
+ this.emitter.emit("remove", [item]);
476
+ return item;
477
+ }
478
+ find(predicate) {
479
+ this.checkDestroyed();
480
+ return this.items.find(predicate);
481
+ }
482
+ findIndex(predicate) {
483
+ this.checkDestroyed();
484
+ return this.items.findIndex(predicate);
485
+ }
486
+ filter(predicate) {
487
+ this.checkDestroyed();
488
+ return this.items.filter(predicate);
489
+ }
490
+ map(fn) {
491
+ this.checkDestroyed();
492
+ return this.items.map(fn);
493
+ }
494
+ forEach(fn) {
495
+ this.checkDestroyed();
496
+ this.items.forEach(fn);
497
+ }
498
+ some(predicate) {
499
+ this.checkDestroyed();
500
+ return this.items.some(predicate);
501
+ }
502
+ every(predicate) {
503
+ this.checkDestroyed();
504
+ return this.items.every(predicate);
505
+ }
506
+ sort(compareFn) {
507
+ this.checkDestroyed();
508
+ this.items.sort(compareFn);
509
+ this.emitter.emit("sort");
510
+ }
511
+ reset(items) {
512
+ this.checkDestroyed();
513
+ this.items = [...items];
514
+ this.emitter.emit("reset", this.items);
515
+ }
516
+ clear() {
517
+ this.checkDestroyed();
518
+ if (this.items.length > 0) {
519
+ this.items = [];
520
+ this.emitter.emit("reset", []);
521
+ }
522
+ }
523
+ async fetch(url, options) {
524
+ this.checkDestroyed();
525
+ const items = await fetchJson(url, options);
526
+ this.reset(items);
527
+ return this.getAll();
528
+ }
529
+ onAdd(fn, scope) {
530
+ this.checkDestroyed();
531
+ return this.emitter.on("add", fn, scope);
532
+ }
533
+ onRemove(fn, scope) {
534
+ this.checkDestroyed();
535
+ return this.emitter.on("remove", fn, scope);
536
+ }
537
+ onReset(fn, scope) {
538
+ this.checkDestroyed();
539
+ return this.emitter.on("reset", fn, scope);
540
+ }
541
+ onSort(fn, scope) {
542
+ this.checkDestroyed();
543
+ return this.emitter.on("sort", fn, scope);
544
+ }
545
+ onChange(fn, scope) {
546
+ this.checkDestroyed();
547
+ const c1 = this.onAdd(fn, scope);
548
+ const c2 = this.onRemove(fn, scope);
549
+ const c3 = this.onReset(fn, scope);
550
+ const c4 = this.onSort(fn, scope);
551
+ return () => {
552
+ c1();
553
+ c2();
554
+ c3();
555
+ c4();
556
+ };
557
+ }
558
+ autoRefresh(url, opts) {
559
+ this.checkDestroyed();
560
+ return startSequentialRefresh(
561
+ () => this.fetch(url, {
562
+ ...opts.fetch,
563
+ scope: opts.scope
564
+ }),
565
+ opts
566
+ );
567
+ }
568
+ destroy() {
569
+ if (this.destroyed) return;
570
+ this.destroyed = true;
571
+ this.items = [];
572
+ this.emitter.clear();
573
+ }
574
+ isDestroyed() {
575
+ return this.destroyed;
576
+ }
577
+ checkDestroyed() {
578
+ if (this.destroyed) {
579
+ throw new Error(
580
+ "[Collection] Cannot use destroyed collection"
581
+ );
582
+ }
583
+ }
584
+ };
585
+
586
+ // src/dom/elements.ts
587
+ var Elements = class _Elements {
588
+ constructor(nodes) {
589
+ this.nodes = nodes;
590
+ }
591
+ get length() {
592
+ return this.nodes.length;
593
+ }
594
+ each(fn) {
595
+ this.nodes.forEach(fn);
596
+ return this;
597
+ }
598
+ get(index = 0) {
599
+ var _a;
600
+ return (_a = this.nodes[index]) != null ? _a : null;
601
+ }
602
+ first() {
603
+ var _a;
604
+ return (_a = this.nodes[0]) != null ? _a : null;
605
+ }
606
+ last() {
607
+ var _a;
608
+ return (_a = this.nodes[this.nodes.length - 1]) != null ? _a : null;
609
+ }
610
+ find(selector) {
611
+ const out = [];
612
+ this.each((el2) => {
613
+ out.push(...Array.from(el2.querySelectorAll(selector)));
614
+ });
615
+ return new _Elements(out);
616
+ }
617
+ filter(predicate) {
618
+ if (typeof predicate === "string") {
619
+ return new _Elements(
620
+ this.nodes.filter((el2) => el2.matches(predicate))
621
+ );
622
+ }
623
+ return new _Elements(this.nodes.filter(predicate));
624
+ }
625
+ parent() {
626
+ const parents = [];
627
+ this.each((el2) => {
628
+ if (el2.parentElement) {
629
+ parents.push(el2.parentElement);
630
+ }
631
+ });
632
+ return new _Elements(parents);
633
+ }
634
+ closest(selector) {
635
+ const matches = [];
636
+ this.each((el2) => {
637
+ const match = el2.closest(selector);
638
+ if (match) {
639
+ matches.push(match);
640
+ }
641
+ });
642
+ return new _Elements(matches);
643
+ }
644
+ children() {
645
+ const children = [];
646
+ this.each((el2) => {
647
+ children.push(...Array.from(el2.children));
648
+ });
649
+ return new _Elements(children);
650
+ }
651
+ text(value) {
652
+ var _a, _b;
653
+ if (value === void 0) {
654
+ return (_b = (_a = this.get()) == null ? void 0 : _a.textContent) != null ? _b : "";
655
+ }
656
+ return this.each((el2) => {
657
+ el2.textContent = value;
658
+ });
659
+ }
660
+ html(value) {
661
+ var _a, _b;
662
+ if (value === void 0) {
663
+ return (_b = (_a = this.get()) == null ? void 0 : _a.innerHTML) != null ? _b : "";
664
+ }
665
+ return this.each((el2) => {
666
+ el2.innerHTML = value;
667
+ });
668
+ }
669
+ attr(name, value) {
670
+ var _a, _b;
671
+ if (value === void 0) {
672
+ return (_b = (_a = this.get()) == null ? void 0 : _a.getAttribute(name)) != null ? _b : "";
673
+ }
674
+ return this.each((el2) => {
675
+ el2.setAttribute(name, value);
676
+ });
677
+ }
678
+ removeAttr(name) {
679
+ return this.each((el2) => {
680
+ el2.removeAttribute(name);
681
+ });
682
+ }
683
+ data(key, value) {
684
+ var _a;
685
+ if (value === void 0) {
686
+ const el2 = this.get();
687
+ return el2 instanceof HTMLElement ? (_a = el2.dataset[key]) != null ? _a : "" : "";
688
+ }
689
+ return this.each((el2) => {
690
+ if (el2 instanceof HTMLElement) {
691
+ el2.dataset[key] = value;
692
+ }
693
+ });
694
+ }
695
+ val(value) {
696
+ if (value === void 0) {
697
+ const el2 = this.get();
698
+ if (el2 instanceof HTMLInputElement || el2 instanceof HTMLTextAreaElement || el2 instanceof HTMLSelectElement) {
699
+ return el2.value;
700
+ }
701
+ return "";
702
+ }
703
+ return this.each((el2) => {
704
+ if (el2 instanceof HTMLInputElement || el2 instanceof HTMLTextAreaElement || el2 instanceof HTMLSelectElement) {
705
+ el2.value = value;
706
+ }
707
+ });
708
+ }
709
+ on(event, handler, scope) {
710
+ return this.each((el2) => {
711
+ el2.addEventListener(event, handler);
712
+ scope == null ? void 0 : scope.onDispose(() => {
713
+ el2.removeEventListener(event, handler);
714
+ });
715
+ });
716
+ }
717
+ once(event, handler) {
718
+ return this.each((el2) => {
719
+ el2.addEventListener(event, handler, { once: true });
720
+ });
721
+ }
722
+ off(event, handler) {
723
+ return this.each((el2) => {
724
+ el2.removeEventListener(event, handler);
725
+ });
726
+ }
727
+ trigger(event, detail) {
728
+ return this.each((el2) => {
729
+ el2.dispatchEvent(
730
+ new CustomEvent(event, { detail })
731
+ );
732
+ });
733
+ }
734
+ addClass(classes) {
735
+ const classList = classes.split(" ").filter(Boolean);
736
+ return this.each((el2) => {
737
+ el2.classList.add(...classList);
738
+ });
739
+ }
740
+ removeClass(classes) {
741
+ const classList = classes.split(" ").filter(Boolean);
742
+ return this.each((el2) => {
743
+ el2.classList.remove(...classList);
744
+ });
745
+ }
746
+ toggleClass(classes, force) {
747
+ const classList = classes.split(" ").filter(Boolean);
748
+ return this.each((el2) => {
749
+ classList.forEach((cls) => {
750
+ el2.classList.toggle(cls, force);
751
+ });
752
+ });
753
+ }
754
+ hasClass(className) {
755
+ return this.nodes.some(
756
+ (el2) => el2.classList.contains(className)
757
+ );
758
+ }
759
+ css(property, value) {
760
+ if (typeof property === "string" && value === void 0) {
761
+ const el2 = this.get();
762
+ return el2 instanceof HTMLElement ? getComputedStyle(el2).getPropertyValue(property) : "";
763
+ }
764
+ if (typeof property === "string") {
765
+ return this.each((el2) => {
766
+ if (el2 instanceof HTMLElement) {
767
+ el2.style.setProperty(property, value);
768
+ }
769
+ });
770
+ }
771
+ return this.each((el2) => {
772
+ if (el2 instanceof HTMLElement) {
773
+ Object.entries(property).forEach(
774
+ ([key, val]) => {
775
+ el2.style.setProperty(key, val);
776
+ }
777
+ );
778
+ }
779
+ });
780
+ }
781
+ show() {
782
+ return this.each((el2) => {
783
+ if (el2 instanceof HTMLElement) {
784
+ el2.style.display = "";
785
+ }
786
+ });
787
+ }
788
+ hide() {
789
+ return this.each((el2) => {
790
+ if (el2 instanceof HTMLElement) {
791
+ el2.style.display = "none";
792
+ }
793
+ });
794
+ }
795
+ toggle(show) {
796
+ return this.each((el2) => {
797
+ if (el2 instanceof HTMLElement) {
798
+ const shouldShow = show != null ? show : el2.style.display === "none";
799
+ el2.style.display = shouldShow ? "" : "none";
800
+ }
801
+ });
802
+ }
803
+ isVisible() {
804
+ return this.nodes.some((el2) => {
805
+ if (el2 instanceof HTMLElement) {
806
+ return el2.style.display !== "none" && el2.offsetParent !== null;
807
+ }
808
+ return false;
809
+ });
810
+ }
811
+ append(content) {
812
+ return this.each((el2) => {
813
+ if (typeof content === "string") {
814
+ el2.insertAdjacentHTML("beforeend", content);
815
+ } else if (content instanceof _Elements) {
816
+ content.each(
817
+ (child) => el2.appendChild(child.cloneNode(true))
818
+ );
819
+ } else {
820
+ el2.appendChild(content);
821
+ }
822
+ });
823
+ }
824
+ prepend(content) {
825
+ return this.each((el2) => {
826
+ if (typeof content === "string") {
827
+ el2.insertAdjacentHTML("afterbegin", content);
828
+ } else if (content instanceof _Elements) {
829
+ const first = el2.firstChild;
830
+ content.each((child) => {
831
+ el2.insertBefore(
832
+ child.cloneNode(true),
833
+ first
834
+ );
835
+ });
836
+ } else {
837
+ el2.insertBefore(content, el2.firstChild);
838
+ }
839
+ });
840
+ }
841
+ remove() {
842
+ return this.each((el2) => {
843
+ el2.remove();
844
+ });
845
+ }
846
+ empty() {
847
+ return this.each((el2) => {
848
+ el2.innerHTML = "";
849
+ });
850
+ }
851
+ clone(deep = true) {
852
+ return new _Elements(
853
+ this.nodes.map(
854
+ (el2) => el2.cloneNode(deep)
855
+ )
856
+ );
857
+ }
858
+ focus() {
859
+ const el2 = this.get();
860
+ if (el2 instanceof HTMLElement) {
861
+ el2.focus();
862
+ }
863
+ return this;
864
+ }
865
+ blur() {
866
+ const el2 = this.get();
867
+ if (el2 instanceof HTMLElement) {
868
+ el2.blur();
869
+ }
870
+ return this;
871
+ }
872
+ };
873
+
874
+ // src/dom/el.ts
875
+ function el(input, root) {
876
+ if (typeof input === "string") {
877
+ const selector = input.trim();
878
+ if (!selector) {
879
+ return new Elements([]);
880
+ }
881
+ try {
882
+ return new Elements(
883
+ Array.from(
884
+ (root != null ? root : document).querySelectorAll(selector)
885
+ )
886
+ );
887
+ } catch (error) {
888
+ console.error(
889
+ `[el] Invalid selector: "${selector}"`,
890
+ error
891
+ );
892
+ return new Elements([]);
893
+ }
894
+ }
895
+ if (input instanceof Element) {
896
+ return new Elements([input]);
897
+ }
898
+ if (input instanceof NodeList) {
899
+ return new Elements(
900
+ Array.from(input)
901
+ );
902
+ }
903
+ if (input instanceof HTMLCollection) {
904
+ return new Elements(Array.from(input));
905
+ }
906
+ if (Array.isArray(input)) {
907
+ const elements = input.filter(
908
+ (item) => item instanceof Element
909
+ );
910
+ if (elements.length !== input.length) {
911
+ console.warn(
912
+ "[el] Some items in array were not Elements and were filtered out"
913
+ );
914
+ }
915
+ return new Elements(elements);
916
+ }
917
+ console.warn(
918
+ "[el] Unrecognized input type:",
919
+ typeof input
920
+ );
921
+ return new Elements([]);
922
+ }
923
+
924
+ // src/view/view.ts
925
+ var View = class {
926
+ constructor(root, model, options = {}) {
927
+ this.root = root;
928
+ this.model = model;
929
+ this.destroyed = false;
930
+ this.children = /* @__PURE__ */ new Set();
931
+ this.options = {
932
+ autoDestroy: true,
933
+ ...options
934
+ };
935
+ this.scope = options.parentScope ? options.parentScope.createChild() : createScope();
936
+ if (this.options.autoDestroy) {
937
+ this.setupAutoDestroy();
938
+ }
939
+ this.init();
940
+ }
941
+ init() {
942
+ }
943
+ $(selector) {
944
+ return el(selector, this.root);
945
+ }
946
+ $root() {
947
+ return el(this.root);
948
+ }
949
+ createChild(ViewClass, root, model) {
950
+ this.checkDestroyed();
951
+ const child = new ViewClass(root, model, {
952
+ parentScope: this.scope,
953
+ autoDestroy: false
954
+ });
955
+ this.children.add(child);
956
+ child.scope.onDispose(() => {
957
+ this.children.delete(child);
958
+ });
959
+ return child;
960
+ }
961
+ createChildren(ViewClass, selector, modelFn) {
962
+ this.checkDestroyed();
963
+ const views = [];
964
+ this.$(selector).each((element, index) => {
965
+ const model = modelFn ? modelFn(element, index) : void 0;
966
+ views.push(this.createChild(ViewClass, element, model));
967
+ });
968
+ return views;
969
+ }
970
+ emit(event, detail) {
971
+ this.checkDestroyed();
972
+ this.$root().trigger(event, detail);
973
+ }
974
+ on(event, handler) {
975
+ this.checkDestroyed();
976
+ this.$root().on(event, handler, this.scope);
977
+ }
978
+ show() {
979
+ this.checkDestroyed();
980
+ this.$root().show();
981
+ }
982
+ hide() {
983
+ this.checkDestroyed();
984
+ this.$root().hide();
985
+ }
986
+ toggle(force) {
987
+ this.checkDestroyed();
988
+ this.$root().toggle(force);
989
+ }
990
+ isVisible() {
991
+ return this.$root().isVisible();
992
+ }
993
+ isDestroyed() {
994
+ return this.destroyed;
995
+ }
996
+ getRoot() {
997
+ return this.root;
998
+ }
999
+ getModel() {
1000
+ return this.model;
1001
+ }
1002
+ destroy() {
1003
+ if (this.destroyed) return;
1004
+ this.destroyed = true;
1005
+ for (const child of this.children) {
1006
+ try {
1007
+ child.destroy();
1008
+ } catch (error) {
1009
+ console.error(
1010
+ "[View] Error destroying child view:",
1011
+ error
1012
+ );
1013
+ }
1014
+ }
1015
+ this.children.clear();
1016
+ try {
1017
+ this.scope.dispose();
1018
+ } catch (error) {
1019
+ console.error(
1020
+ "[View] Error disposing scope:",
1021
+ error
1022
+ );
1023
+ }
1024
+ if (this.model && typeof this.model.destroy === "function") {
1025
+ try {
1026
+ this.model.destroy();
1027
+ } catch (error) {
1028
+ console.error(
1029
+ "[View] Error destroying model:",
1030
+ error
1031
+ );
1032
+ }
1033
+ }
1034
+ }
1035
+ setupAutoDestroy() {
1036
+ const observer = new MutationObserver((mutations) => {
1037
+ for (const mutation of mutations) {
1038
+ for (const removed of Array.from(
1039
+ mutation.removedNodes
1040
+ )) {
1041
+ if (removed === this.root || removed instanceof Element && removed.contains(this.root)) {
1042
+ this.destroy();
1043
+ observer.disconnect();
1044
+ return;
1045
+ }
1046
+ }
1047
+ }
1048
+ });
1049
+ if (this.root.parentElement) {
1050
+ observer.observe(this.root.parentElement, {
1051
+ childList: true,
1052
+ subtree: true
1053
+ });
1054
+ }
1055
+ this.scope.onDispose(() => {
1056
+ observer.disconnect();
1057
+ });
1058
+ }
1059
+ checkDestroyed() {
1060
+ if (this.destroyed) {
1061
+ throw new Error(
1062
+ "[View] Cannot use destroyed view"
1063
+ );
1064
+ }
1065
+ }
1066
+ };
1067
+ return __toCommonJS(index_exports);
1068
+ })();
@@ -0,0 +1 @@
1
+ "use strict";var bone=(()=>{var F=Object.defineProperty;var V=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var H=(n,e)=>{for(var t in e)F(n,t,{get:e[t],enumerable:!0})},P=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of R(e))!O.call(n,s)&&s!==t&&F(n,s,{get:()=>e[s],enumerable:!(r=V(e,s))||r.enumerable});return n};var $=n=>P(F({},"__esModule",{value:!0}),n);var q={};H(q,{Collection:()=>L,Elements:()=>a,Model:()=>x,View:()=>M,createScope:()=>S,el:()=>w,fetchJson:()=>E});function C(n){let e=new AbortController,t=[],r=new Set,s=!1,o={signal:e.signal,onDispose(i){if(s){try{i()}catch(l){console.error("[Scope] Error in dispose callback:",l)}return}t.push(i)},createChild(){if(s){console.warn("[Scope] Attempted to create child from disposed scope");let l=C(null);return l.dispose(),l}let i=C(o);return r.add(i),i.onDispose(()=>r.delete(i)),i},dispose(){if(!s){s=!0;for(let i of r)i.dispose();r.clear(),e.abort();for(let i=t.length-1;i>=0;i--)try{t[i]()}catch(l){console.error("[Scope] Error in cleanup function:",l)}t.length=0,o._inFlight&&(o._inFlight.clear(),delete o._inFlight)}}};return n==null||n.onDispose(()=>o.dispose()),o}var y=null;function K(){if(y)return y;y=C(null);let n=()=>{y&&(y.dispose(),y=null)};return window.addEventListener("pagehide",n),window.addEventListener("beforeunload",n),y}function S(){return K().createChild()}function I(n,e){var t;return`${(t=e==null?void 0:e.method)!=null?t:"GET"}:${n}`}async function E(n,e={}){var v;let{scope:t,abort:r=!1,timeout:s,retryOnFailure:o=0,retryDelay:i=0,dedupe:l=!1,init:m,parse:f}=e,h=t,d,p;if(l&&h){d=(v=h._inFlight)!=null?v:h._inFlight=new Map,p=I(n,m);let c=d.get(p);if(c)return c}let g=(async()=>{let c=0;for(;;){let b=new AbortController;r&&t&&t.signal.addEventListener("abort",()=>b.abort(),{once:!0});let A;typeof s=="number"&&(A=setTimeout(()=>b.abort(),s));try{let u=await fetch(n,{...m,signal:b.signal});if(!u.ok)throw new Error(`HTTP ${u.status}: ${u.statusText}`);let D=await u.json();return f?f(D):D}catch(u){if(c++,u instanceof DOMException&&u.name==="AbortError"||c>o)throw u;i>0&&await new Promise(D=>setTimeout(D,i))}finally{A&&clearTimeout(A)}}})();if(d&&p&&h){d.set(p,g);let c=()=>{d.delete(p)};g.then(c,c),h.onDispose(c)}return g}var T=class{constructor(){this.events=new Map}on(e,t,r){let s=this.events.get(e);s||(s=new Set,this.events.set(e,s)),s.add(t);let o=!1,i=()=>{o||(o=!0,s.delete(t),s.size===0&&this.events.delete(e))};return r==null||r.onDispose(i),i}once(e,t,r){let s=(...i)=>{o(),t(...i)},o=this.on(e,s,r);return o}emit(e,...t){let r=this.events.get(e);if(!r||r.size===0)return;let s=Array.from(r);for(let o of s)try{o(...t)}catch(i){console.error(`Error in event handler for "${e}":`,i)}}off(e){this.events.delete(e)}clear(){this.events.clear()}hasListeners(e){var t,r;return((r=(t=this.events.get(e))==null?void 0:t.size)!=null?r:0)>0}listenerCount(e){var t,r;return(r=(t=this.events.get(e))==null?void 0:t.size)!=null?r:0}eventNames(){return Array.from(this.events.keys())}};function k(n,e){let{interval:t,scope:r,immediate:s=!0,onError:o,maxRetries:i=0,backoff:l=!1}=e,m=!1,f,h=0,d=!1,p=()=>{if(!l||h===0)return t;let c=Math.min(2**h,10);return t*c},g=()=>{m||(m=!0,f!==void 0&&(clearTimeout(f),f=void 0))},v=async()=>{if(!m){if(d){console.warn("[SequentialRefresh] Previous execution still running");return}d=!0;try{await n(),h=0}catch(c){if(h++,o)try{o(c)}catch(b){console.error("[SequentialRefresh] Error in error handler:",b)}else console.error("[SequentialRefresh] Error in refresh function:",c);if(i>0&&h>=i){console.error(`[SequentialRefresh] Max retries (${i}) exceeded. Stopping refresh.`),g();return}}finally{d=!1}if(!m){let c=p();f=setTimeout(v,c)}}};return s?v():f=setTimeout(v,t),r.onDispose(g),g}var x=class{constructor(e){this.emitter=new T;this.destroyed=!1;this.initial={...e},this.data={...e}}get(e){return this.checkDestroyed(),this.data[e]}getAll(){return this.checkDestroyed(),{...this.data}}set(e){this.checkDestroyed();let t={},r=!1;for(let s of Object.keys(e))e[s]!==this.data[s]&&(t[s]=e[s],r=!0);return r?(Object.assign(this.data,t),this.emitter.emit("change",t),!0):!1}reset(){this.checkDestroyed();let e={...this.initial};Object.keys(e).some(r=>e[r]!==this.data[r])&&(this.data=e,this.emitter.emit("change",e))}has(e,t){return this.checkDestroyed(),this.data[e]===t}onChange(e,t){return this.checkDestroyed(),this.emitter.on("change",e,t)}async fetch(e,t){this.checkDestroyed();let r=await E(e,t);return this.set(r),this.getAll()}autoRefresh(e,t){return this.checkDestroyed(),k(()=>this.fetch(e,{...t.fetch,scope:t.scope}),t)}destroy(){this.destroyed||(this.destroyed=!0,this.emitter.clear())}isDestroyed(){return this.destroyed}checkDestroyed(){if(this.destroyed)throw new Error("[Model] Cannot use destroyed model")}};var L=class{constructor(){this.items=[];this.emitter=new T;this.destroyed=!1}getAll(){return this.checkDestroyed(),[...this.items]}get length(){return this.checkDestroyed(),this.items.length}at(e){return this.checkDestroyed(),this.items[e]}add(...e){this.checkDestroyed(),this.items.push(...e),this.emitter.emit("add",e)}remove(e){this.checkDestroyed();let t=[];for(let r=this.items.length-1;r>=0;r--)e(this.items[r],r)&&(t.unshift(this.items[r]),this.items.splice(r,1));return t.length>0&&this.emitter.emit("remove",t),t}removeAt(e){if(this.checkDestroyed(),e<0||e>=this.items.length)return;let[t]=this.items.splice(e,1);return this.emitter.emit("remove",[t]),t}find(e){return this.checkDestroyed(),this.items.find(e)}findIndex(e){return this.checkDestroyed(),this.items.findIndex(e)}filter(e){return this.checkDestroyed(),this.items.filter(e)}map(e){return this.checkDestroyed(),this.items.map(e)}forEach(e){this.checkDestroyed(),this.items.forEach(e)}some(e){return this.checkDestroyed(),this.items.some(e)}every(e){return this.checkDestroyed(),this.items.every(e)}sort(e){this.checkDestroyed(),this.items.sort(e),this.emitter.emit("sort")}reset(e){this.checkDestroyed(),this.items=[...e],this.emitter.emit("reset",this.items)}clear(){this.checkDestroyed(),this.items.length>0&&(this.items=[],this.emitter.emit("reset",[]))}async fetch(e,t){this.checkDestroyed();let r=await E(e,t);return this.reset(r),this.getAll()}onAdd(e,t){return this.checkDestroyed(),this.emitter.on("add",e,t)}onRemove(e,t){return this.checkDestroyed(),this.emitter.on("remove",e,t)}onReset(e,t){return this.checkDestroyed(),this.emitter.on("reset",e,t)}onSort(e,t){return this.checkDestroyed(),this.emitter.on("sort",e,t)}onChange(e,t){this.checkDestroyed();let r=this.onAdd(e,t),s=this.onRemove(e,t),o=this.onReset(e,t),i=this.onSort(e,t);return()=>{r(),s(),o(),i()}}autoRefresh(e,t){return this.checkDestroyed(),k(()=>this.fetch(e,{...t.fetch,scope:t.scope}),t)}destroy(){this.destroyed||(this.destroyed=!0,this.items=[],this.emitter.clear())}isDestroyed(){return this.destroyed}checkDestroyed(){if(this.destroyed)throw new Error("[Collection] Cannot use destroyed collection")}};var a=class n{constructor(e){this.nodes=e}get length(){return this.nodes.length}each(e){return this.nodes.forEach(e),this}get(e=0){var t;return(t=this.nodes[e])!=null?t:null}first(){var e;return(e=this.nodes[0])!=null?e:null}last(){var e;return(e=this.nodes[this.nodes.length-1])!=null?e:null}find(e){let t=[];return this.each(r=>{t.push(...Array.from(r.querySelectorAll(e)))}),new n(t)}filter(e){return typeof e=="string"?new n(this.nodes.filter(t=>t.matches(e))):new n(this.nodes.filter(e))}parent(){let e=[];return this.each(t=>{t.parentElement&&e.push(t.parentElement)}),new n(e)}closest(e){let t=[];return this.each(r=>{let s=r.closest(e);s&&t.push(s)}),new n(t)}children(){let e=[];return this.each(t=>{e.push(...Array.from(t.children))}),new n(e)}text(e){var t,r;return e===void 0?(r=(t=this.get())==null?void 0:t.textContent)!=null?r:"":this.each(s=>{s.textContent=e})}html(e){var t,r;return e===void 0?(r=(t=this.get())==null?void 0:t.innerHTML)!=null?r:"":this.each(s=>{s.innerHTML=e})}attr(e,t){var r,s;return t===void 0?(s=(r=this.get())==null?void 0:r.getAttribute(e))!=null?s:"":this.each(o=>{o.setAttribute(e,t)})}removeAttr(e){return this.each(t=>{t.removeAttribute(e)})}data(e,t){var r;if(t===void 0){let s=this.get();return s instanceof HTMLElement&&(r=s.dataset[e])!=null?r:""}return this.each(s=>{s instanceof HTMLElement&&(s.dataset[e]=t)})}val(e){if(e===void 0){let t=this.get();return t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement?t.value:""}return this.each(t=>{(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)&&(t.value=e)})}on(e,t,r){return this.each(s=>{s.addEventListener(e,t),r==null||r.onDispose(()=>{s.removeEventListener(e,t)})})}once(e,t){return this.each(r=>{r.addEventListener(e,t,{once:!0})})}off(e,t){return this.each(r=>{r.removeEventListener(e,t)})}trigger(e,t){return this.each(r=>{r.dispatchEvent(new CustomEvent(e,{detail:t}))})}addClass(e){let t=e.split(" ").filter(Boolean);return this.each(r=>{r.classList.add(...t)})}removeClass(e){let t=e.split(" ").filter(Boolean);return this.each(r=>{r.classList.remove(...t)})}toggleClass(e,t){let r=e.split(" ").filter(Boolean);return this.each(s=>{r.forEach(o=>{s.classList.toggle(o,t)})})}hasClass(e){return this.nodes.some(t=>t.classList.contains(e))}css(e,t){if(typeof e=="string"&&t===void 0){let r=this.get();return r instanceof HTMLElement?getComputedStyle(r).getPropertyValue(e):""}return typeof e=="string"?this.each(r=>{r instanceof HTMLElement&&r.style.setProperty(e,t)}):this.each(r=>{r instanceof HTMLElement&&Object.entries(e).forEach(([s,o])=>{r.style.setProperty(s,o)})})}show(){return this.each(e=>{e instanceof HTMLElement&&(e.style.display="")})}hide(){return this.each(e=>{e instanceof HTMLElement&&(e.style.display="none")})}toggle(e){return this.each(t=>{if(t instanceof HTMLElement){let r=e!=null?e:t.style.display==="none";t.style.display=r?"":"none"}})}isVisible(){return this.nodes.some(e=>e instanceof HTMLElement?e.style.display!=="none"&&e.offsetParent!==null:!1)}append(e){return this.each(t=>{typeof e=="string"?t.insertAdjacentHTML("beforeend",e):e instanceof n?e.each(r=>t.appendChild(r.cloneNode(!0))):t.appendChild(e)})}prepend(e){return this.each(t=>{if(typeof e=="string")t.insertAdjacentHTML("afterbegin",e);else if(e instanceof n){let r=t.firstChild;e.each(s=>{t.insertBefore(s.cloneNode(!0),r)})}else t.insertBefore(e,t.firstChild)})}remove(){return this.each(e=>{e.remove()})}empty(){return this.each(e=>{e.innerHTML=""})}clone(e=!0){return new n(this.nodes.map(t=>t.cloneNode(e)))}focus(){let e=this.get();return e instanceof HTMLElement&&e.focus(),this}blur(){let e=this.get();return e instanceof HTMLElement&&e.blur(),this}};function w(n,e){if(typeof n=="string"){let t=n.trim();if(!t)return new a([]);try{return new a(Array.from((e!=null?e:document).querySelectorAll(t)))}catch(r){return console.error(`[el] Invalid selector: "${t}"`,r),new a([])}}if(n instanceof Element)return new a([n]);if(n instanceof NodeList)return new a(Array.from(n));if(n instanceof HTMLCollection)return new a(Array.from(n));if(Array.isArray(n)){let t=n.filter(r=>r instanceof Element);return t.length!==n.length&&console.warn("[el] Some items in array were not Elements and were filtered out"),new a(t)}return console.warn("[el] Unrecognized input type:",typeof n),new a([])}var M=class{constructor(e,t,r={}){this.root=e;this.model=t;this.destroyed=!1;this.children=new Set;this.options={autoDestroy:!0,...r},this.scope=r.parentScope?r.parentScope.createChild():S(),this.options.autoDestroy&&this.setupAutoDestroy(),this.init()}init(){}$(e){return w(e,this.root)}$root(){return w(this.root)}createChild(e,t,r){this.checkDestroyed();let s=new e(t,r,{parentScope:this.scope,autoDestroy:!1});return this.children.add(s),s.scope.onDispose(()=>{this.children.delete(s)}),s}createChildren(e,t,r){this.checkDestroyed();let s=[];return this.$(t).each((o,i)=>{let l=r?r(o,i):void 0;s.push(this.createChild(e,o,l))}),s}emit(e,t){this.checkDestroyed(),this.$root().trigger(e,t)}on(e,t){this.checkDestroyed(),this.$root().on(e,t,this.scope)}show(){this.checkDestroyed(),this.$root().show()}hide(){this.checkDestroyed(),this.$root().hide()}toggle(e){this.checkDestroyed(),this.$root().toggle(e)}isVisible(){return this.$root().isVisible()}isDestroyed(){return this.destroyed}getRoot(){return this.root}getModel(){return this.model}destroy(){if(!this.destroyed){this.destroyed=!0;for(let e of this.children)try{e.destroy()}catch(t){console.error("[View] Error destroying child view:",t)}this.children.clear();try{this.scope.dispose()}catch(e){console.error("[View] Error disposing scope:",e)}if(this.model&&typeof this.model.destroy=="function")try{this.model.destroy()}catch(e){console.error("[View] Error destroying model:",e)}}}setupAutoDestroy(){let e=new MutationObserver(t=>{for(let r of t)for(let s of Array.from(r.removedNodes))if(s===this.root||s instanceof Element&&s.contains(this.root)){this.destroy(),e.disconnect();return}});this.root.parentElement&&e.observe(this.root.parentElement,{childList:!0,subtree:!0}),this.scope.onDispose(()=>{e.disconnect()})}checkDestroyed(){if(this.destroyed)throw new Error("[View] Cannot use destroyed view")}};return $(q);})();
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "bonemarrow",
3
+ "version": "1.1.0",
4
+ "description": "A modern jQuery + Backbone replacement",
5
+ "license": "MIT",
6
+
7
+ "main": "dist/bonemarrow.js",
8
+ "unpkg": "dist/bonemarrow.js",
9
+
10
+ "files": ["dist"],
11
+
12
+ "scripts": {
13
+ "build": "npm run build:iife && npm run build:min",
14
+ "build:iife": "esbuild src/index.ts --bundle --format=iife --global-name=bone --target=es2019 --outfile=dist/bonemarrow.js",
15
+ "build:min": "esbuild src/index.ts --bundle --format=iife --global-name=bone --target=es2019 --minify --outfile=dist/bonemarrow.min.js"
16
+ }
17
+ }
package/readme.md ADDED
@@ -0,0 +1,225 @@
1
+ # 🦴 BoneMarrow
2
+
3
+ **BoneMarrow** is a lightweight, lifecycle-aware JavaScript/TypeScript library for building structured UI logic on top of server-rendered HTML — without jQuery, virtual DOMs, or heavyweight frameworks.
4
+
5
+ It provides a small set of **explicit primitives** for managing UI behavior, async operations, and state in a predictable way.
6
+
7
+ ---
8
+
9
+ ## Why BoneMarrow?
10
+
11
+ Modern web apps often fall into two extremes:
12
+
13
+ * **jQuery-style code** that becomes unmaintainable at scale
14
+ * **SPA frameworks** that are too heavy for many server-rendered apps
15
+
16
+ BoneMarrow sits in between.
17
+
18
+ It helps you:
19
+
20
+ * Modernize legacy applications incrementally
21
+ * Replace jQuery patterns safely
22
+ * Keep server-rendered HTML
23
+ * Avoid framework lock-in
24
+ * Write code that is easy to reason about and debug
25
+
26
+ ---
27
+
28
+ ## Core Ideas
29
+
30
+ * **Explicit lifecycles** using scopes
31
+ * **Abort-safe async** by default
32
+ * **No hidden background work**
33
+ * **No global mutable state**
34
+ * **No `$`**
35
+ * **No magic**
36
+
37
+ If something happens, you should know *where*, *when*, and *why*.
38
+
39
+ ---
40
+
41
+ ## What BoneMarrow Provides
42
+
43
+ ### Scope
44
+
45
+ A lifecycle container that:
46
+
47
+ * Owns async work
48
+ * Aborts fetches on disposal
49
+ * Cleans up event listeners
50
+ * Supports parent / child scopes
51
+
52
+ ### Fetch Pipeline
53
+
54
+ A unified fetch helper with optional:
55
+
56
+ * abort
57
+ * timeout
58
+ * retry on failure
59
+ * scope-local request de-duplication
60
+
61
+ ### Model
62
+
63
+ * Simple observable state
64
+ * Patch-based updates
65
+ * Fetch and auto-refresh support
66
+
67
+ ### Collection
68
+
69
+ * List-based state
70
+ * Reset semantics
71
+ * Fetch and auto-refresh support
72
+
73
+ ### Auto Refresh
74
+
75
+ * Sequential polling (no overlapping requests)
76
+ * Next refresh starts only after the previous fetch completes
77
+
78
+ ### DOM Utilities
79
+
80
+ * `el()` selector helper
81
+ * `Elements` wrapper
82
+ * Scoped event handling
83
+
84
+ ### View
85
+
86
+ * UI composition primitive
87
+ * Owns a DOM root and scope
88
+ * Automatic cleanup on destroy
89
+
90
+ ---
91
+
92
+ ## Quick Example
93
+
94
+ ```ts
95
+ const user = new bone.Model({ name: "John" });
96
+
97
+ class UserView extends bone.View<typeof user> {
98
+ protected init() {
99
+ this.$(".name").text(this.model.get("name"));
100
+
101
+ this.model.onChange(patch => {
102
+ if (patch.name) {
103
+ this.$(".name").text(patch.name);
104
+ }
105
+ });
106
+
107
+ this.$(".btn").on(
108
+ "click",
109
+ () => this.model.set({ name: "Smith" }),
110
+ this.scope
111
+ );
112
+ }
113
+ }
114
+
115
+ new UserView(document.getElementById("app")!, user);
116
+ ```
117
+
118
+ Everything created inside the view:
119
+
120
+ * is scoped
121
+ * is cleaned up automatically
122
+ * stops when the view is destroyed
123
+
124
+ ---
125
+
126
+ ## Sequential Auto Refresh (Polling)
127
+
128
+ ```ts
129
+ user.autoRefresh("/api/user/42", {
130
+ scope: this.scope,
131
+ interval: 5000,
132
+ fetch: { abort: true }
133
+ });
134
+ ```
135
+
136
+ Actual behavior:
137
+
138
+ ```
139
+ fetch → complete → wait 5s → fetch → complete → wait 5s
140
+ ```
141
+
142
+ No overlapping requests. No runaway timers.
143
+
144
+ ---
145
+
146
+ ## jQuery-like UI, Without jQuery
147
+
148
+ BoneMarrow is well suited for building:
149
+
150
+ * dialogs
151
+ * dropdowns
152
+ * tabs
153
+ * dashboards
154
+ * admin panels
155
+
156
+ All with:
157
+
158
+ * explicit ownership
159
+ * predictable cleanup
160
+ * no global state
161
+
162
+ ---
163
+
164
+ ## Progressive Enhancement Friendly
165
+
166
+ BoneMarrow works best when:
167
+
168
+ * HTML is rendered by the server
169
+ * JavaScript enhances behavior
170
+ * Pages still work without JS
171
+
172
+ Perfect for:
173
+
174
+ * ASP.NET MVC / Razor
175
+ * Rails
176
+ * PHP
177
+ * Django
178
+ * Large legacy apps
179
+
180
+ ---
181
+
182
+ ## Documentation
183
+
184
+ * Full documentation is available in **`DOCS.md`**
185
+ * Includes detailed API reference
186
+ * Covers scopes, fetch, models, collections, views, and patterns
187
+
188
+ ---
189
+
190
+ ## Size
191
+
192
+ Approximate sizes (production build):
193
+
194
+ * ~9 KB raw
195
+ * ~4–5 KB minified
196
+ * ~2–3 KB gzip
197
+
198
+ Smaller than most “micro” libraries, without sacrificing structure.
199
+
200
+ ---
201
+
202
+ ## What BoneMarrow Is Not
203
+
204
+ * ❌ Not a framework
205
+ * ❌ Not a virtual DOM
206
+ * ❌ Not reactive magic
207
+ * ❌ Not an SPA replacement
208
+
209
+ BoneMarrow provides **primitives**, not rules.
210
+
211
+ ---
212
+
213
+ ## License
214
+
215
+ MIT © Karthick Raj
216
+
217
+ ---
218
+
219
+ ## Final Thought
220
+
221
+ > **BoneMarrow is intentionally boring.
222
+ > Boring code scales.**
223
+
224
+ If you want clarity, explicit lifecycles, and predictable async behavior without the weight of a framework, BoneMarrow is built for that.
225
+