@devansharora18/tardisjs 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/dist/bin/bin/create-tardis-app.d.ts +2 -0
  4. package/dist/bin/bin/tardis.d.ts +15 -0
  5. package/dist/bin/create-tardis-app.d.ts +2 -0
  6. package/dist/bin/create-tardis-app.js +2102 -0
  7. package/dist/bin/create-tardis-app.js.map +1 -0
  8. package/dist/bin/src/compiler/compiler.d.ts +3 -0
  9. package/dist/bin/src/lexer/lexer.d.ts +2 -0
  10. package/dist/bin/src/lexer/tokens.d.ts +8 -0
  11. package/dist/bin/src/parser/errors.d.ts +6 -0
  12. package/dist/bin/src/parser/parser.d.ts +3 -0
  13. package/dist/bin/src/parser/types.d.ts +67 -0
  14. package/dist/bin/src/runtime/events.d.ts +9 -0
  15. package/dist/bin/src/runtime/fetch.d.ts +11 -0
  16. package/dist/bin/src/runtime/index.d.ts +52 -0
  17. package/dist/bin/src/runtime/registry.d.ts +17 -0
  18. package/dist/bin/src/runtime/render.d.ts +11 -0
  19. package/dist/bin/src/runtime/router.d.ts +15 -0
  20. package/dist/bin/src/runtime/selector.d.ts +67 -0
  21. package/dist/bin/src/runtime/state.d.ts +9 -0
  22. package/dist/bin/src/runtime/styles.d.ts +9 -0
  23. package/dist/bin/tardis.d.ts +15 -0
  24. package/dist/bin/tardis.js +2086 -0
  25. package/dist/bin/tardis.js.map +1 -0
  26. package/dist/index.cjs +3 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.mjs +2 -0
  29. package/dist/index.mjs.map +1 -0
  30. package/dist/runtime/bin/create-tardis-app.d.ts +2 -0
  31. package/dist/runtime/bin/tardis.d.ts +15 -0
  32. package/dist/runtime/index.cjs +829 -0
  33. package/dist/runtime/index.cjs.map +1 -0
  34. package/dist/runtime/index.mjs +791 -0
  35. package/dist/runtime/index.mjs.map +1 -0
  36. package/dist/runtime/src/compiler/compiler.d.ts +3 -0
  37. package/dist/runtime/src/lexer/lexer.d.ts +2 -0
  38. package/dist/runtime/src/lexer/tokens.d.ts +8 -0
  39. package/dist/runtime/src/parser/errors.d.ts +6 -0
  40. package/dist/runtime/src/parser/parser.d.ts +3 -0
  41. package/dist/runtime/src/parser/types.d.ts +67 -0
  42. package/dist/runtime/src/runtime/events.d.ts +9 -0
  43. package/dist/runtime/src/runtime/fetch.d.ts +11 -0
  44. package/dist/runtime/src/runtime/index.d.ts +52 -0
  45. package/dist/runtime/src/runtime/registry.d.ts +17 -0
  46. package/dist/runtime/src/runtime/render.d.ts +11 -0
  47. package/dist/runtime/src/runtime/router.d.ts +15 -0
  48. package/dist/runtime/src/runtime/selector.d.ts +67 -0
  49. package/dist/runtime/src/runtime/state.d.ts +9 -0
  50. package/dist/runtime/src/runtime/styles.d.ts +9 -0
  51. package/dist/src/compiler/compiler.d.ts +3 -0
  52. package/dist/src/lexer/lexer.d.ts +2 -0
  53. package/dist/src/lexer/tokens.d.ts +8 -0
  54. package/dist/src/parser/errors.d.ts +6 -0
  55. package/dist/src/parser/parser.d.ts +3 -0
  56. package/dist/src/parser/types.d.ts +67 -0
  57. package/dist/src/runtime/events.d.ts +9 -0
  58. package/dist/src/runtime/fetch.d.ts +11 -0
  59. package/dist/src/runtime/index.d.ts +52 -0
  60. package/dist/src/runtime/registry.d.ts +17 -0
  61. package/dist/src/runtime/render.d.ts +11 -0
  62. package/dist/src/runtime/router.d.ts +15 -0
  63. package/dist/src/runtime/selector.d.ts +67 -0
  64. package/dist/src/runtime/state.d.ts +9 -0
  65. package/dist/src/runtime/styles.d.ts +9 -0
  66. package/package.json +67 -0
@@ -0,0 +1,791 @@
1
+ // src/runtime/state.ts
2
+ // internal dep map — keyed by state object identity
3
+ const depMap = new WeakMap();
4
+ let activeEffect = null;
5
+ let batchQueue = null;
6
+ function $batch(fn) {
7
+ batchQueue = new Set();
8
+ fn();
9
+ const queue = batchQueue;
10
+ batchQueue = null;
11
+ requestAnimationFrame(() => {
12
+ queue.forEach(updater => updater());
13
+ });
14
+ }
15
+ function createState(initial) {
16
+ const raw = { ...initial };
17
+ const deps = new Map();
18
+ for (const key of Object.keys(raw)) {
19
+ deps.set(key, new Set());
20
+ }
21
+ const proxy = new Proxy(raw, {
22
+ get(target, key) {
23
+ if (activeEffect) {
24
+ if (!deps.has(key))
25
+ deps.set(key, new Set());
26
+ deps.get(key).add(activeEffect);
27
+ }
28
+ return target[key];
29
+ },
30
+ set(target, key, value) {
31
+ target[key] = value;
32
+ const updaters = deps.get(key);
33
+ if (updaters) {
34
+ if (batchQueue) {
35
+ updaters.forEach(fn => batchQueue.add(fn));
36
+ }
37
+ else {
38
+ updaters.forEach(fn => fn());
39
+ }
40
+ }
41
+ return true;
42
+ }
43
+ });
44
+ depMap.set(proxy, deps);
45
+ return proxy;
46
+ }
47
+ function registerDep(state, key, updater) {
48
+ const deps = depMap.get(state);
49
+ if (deps) {
50
+ if (!deps.has(key))
51
+ deps.set(key, new Set());
52
+ deps.get(key).add(updater);
53
+ }
54
+ }
55
+ function withEffect(effect, fn) {
56
+ const prev = activeEffect;
57
+ activeEffect = effect;
58
+ try {
59
+ return fn();
60
+ }
61
+ finally {
62
+ activeEffect = prev;
63
+ }
64
+ }
65
+ function $update(state, key, value) {
66
+ state[key] = value;
67
+ }
68
+ function $toggle(state, key) {
69
+ state[key] = !state[key];
70
+ }
71
+ function $reset(state, initial) {
72
+ for (const [key, val] of Object.entries(initial)) {
73
+ state[key] = val;
74
+ }
75
+ }
76
+
77
+ // src/runtime/fetch.ts
78
+ function $fetch(url) {
79
+ let _method = 'GET';
80
+ let _body = undefined;
81
+ let _headers = {};
82
+ let _done = null;
83
+ let _fail = null;
84
+ let _always = null;
85
+ let _executed = false;
86
+ function execute() {
87
+ if (_executed)
88
+ return;
89
+ _executed = true;
90
+ const options = {
91
+ method: _method,
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ ..._headers,
95
+ },
96
+ };
97
+ if (_body !== undefined && _method !== 'GET') {
98
+ options.body = JSON.stringify(_body);
99
+ }
100
+ fetch(url, options)
101
+ .then(res => {
102
+ if (!res.ok)
103
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
104
+ return res.json();
105
+ })
106
+ .then(data => {
107
+ _done?.(data);
108
+ })
109
+ .catch(err => {
110
+ _fail?.(err instanceof Error ? err : new Error(String(err)));
111
+ })
112
+ .finally(() => {
113
+ _always?.();
114
+ });
115
+ }
116
+ const chain = {
117
+ method(m) {
118
+ _method = m;
119
+ return chain;
120
+ },
121
+ body(b) {
122
+ _body = b;
123
+ return chain;
124
+ },
125
+ headers(h) {
126
+ _headers = { ..._headers, ...h };
127
+ return chain;
128
+ },
129
+ done(fn) {
130
+ _done = fn;
131
+ execute();
132
+ return chain;
133
+ },
134
+ fail(fn) {
135
+ _fail = fn;
136
+ execute();
137
+ return chain;
138
+ },
139
+ always(fn) {
140
+ _always = fn;
141
+ execute();
142
+ return chain;
143
+ },
144
+ };
145
+ return chain;
146
+ }
147
+
148
+ // src/runtime/render.ts
149
+ // ── $if ────────────────────────────────────────────────────────────────────
150
+ function $if(conditionFn, builderFn) {
151
+ const anchor = document.createComment('$if');
152
+ let currentEl = null;
153
+ let isMounted = conditionFn();
154
+ // initial render
155
+ if (isMounted) {
156
+ currentEl = builderFn();
157
+ }
158
+ return currentEl ?? anchor;
159
+ }
160
+ // ── $each ──────────────────────────────────────────────────────────────────
161
+ function $each(arrayFn, itemBuilderFn) {
162
+ const container = document.createElement('div');
163
+ container.setAttribute('data-each', '');
164
+ function render() {
165
+ const items = arrayFn();
166
+ // clear existing children
167
+ while (container.firstChild) {
168
+ container.removeChild(container.firstChild);
169
+ }
170
+ // render new children
171
+ items.forEach((item, index) => {
172
+ container.appendChild(itemBuilderFn(item, index));
173
+ });
174
+ }
175
+ render();
176
+ return container;
177
+ }
178
+ // ── $show ──────────────────────────────────────────────────────────────────
179
+ function $show(conditionFn, builderFn) {
180
+ const el = builderFn();
181
+ function update() {
182
+ el.style.display = conditionFn() ? '' : 'none';
183
+ }
184
+ update();
185
+ return el;
186
+ }
187
+ // ── $portal ────────────────────────────────────────────────────────────────
188
+ function $portal(selector, builderFn) {
189
+ const target = document.querySelector(selector);
190
+ const el = builderFn();
191
+ if (target) {
192
+ target.appendChild(el);
193
+ }
194
+ else {
195
+ console.warn(`$portal: target "${selector}" not found`);
196
+ }
197
+ return el;
198
+ }
199
+ // ── bind ───────────────────────────────────────────────────────────────────
200
+ // binds a DOM property to a reactive expression
201
+ // re-runs the expression and updates the DOM whenever state changes
202
+ function bind(el, prop, valueFn) {
203
+ function update() {
204
+ const val = withEffect(update, valueFn);
205
+ if (prop === 'textContent') {
206
+ el.textContent = String(val ?? '');
207
+ }
208
+ else if (prop in el) {
209
+ el[prop] = val;
210
+ }
211
+ }
212
+ // initial render
213
+ update();
214
+ el.__update = update;
215
+ }
216
+ // ── bindClass ──────────────────────────────────────────────────────────────
217
+ function bindClass(el, classFn) {
218
+ function update() {
219
+ el.className = withEffect(update, classFn);
220
+ }
221
+ update();
222
+ el.__updateClass = update;
223
+ }
224
+ // ── bindAttr ───────────────────────────────────────────────────────────────
225
+ function bindAttr(el, attr, valueFn) {
226
+ function update() {
227
+ const val = withEffect(update, valueFn);
228
+ if (val === null || val === undefined || val === false) {
229
+ el.removeAttribute(attr);
230
+ }
231
+ else {
232
+ el.setAttribute(attr, String(val));
233
+ }
234
+ }
235
+ update();
236
+ el[`__updateAttr_${attr}`] = update;
237
+ }
238
+ // ── resolveStyles ──────────────────────────────────────────────────────────
239
+ function resolveStyles(styles, key) {
240
+ const maybeRules = styles.rules;
241
+ const styleMap = maybeRules && typeof maybeRules === 'object' && !Array.isArray(maybeRules)
242
+ ? maybeRules
243
+ : styles;
244
+ return styleMap[key] ?? key;
245
+ }
246
+ // ── text ───────────────────────────────────────────────────────────────────
247
+ function text(value) {
248
+ const node = document.createTextNode('');
249
+ if (typeof value === 'function') {
250
+ const valueFn = value;
251
+ function update() { node.textContent = withEffect(update, valueFn); }
252
+ update();
253
+ node.__update = update;
254
+ }
255
+ else {
256
+ node.textContent = value;
257
+ }
258
+ return node;
259
+ }
260
+
261
+ // src/runtime/styles.ts
262
+ function createStyles(mode, rules, props, state) {
263
+ function resolve() {
264
+ const classes = [];
265
+ for (const [key, value] of Object.entries(rules)) {
266
+ if (key === 'base') {
267
+ classes.push(value);
268
+ continue;
269
+ }
270
+ // dotted key — variant.primary, disabled.true, size.sm
271
+ const dotIdx = key.indexOf('.');
272
+ if (dotIdx >= 0) {
273
+ const propOrState = key.slice(0, dotIdx);
274
+ const expectedVal = key.slice(dotIdx + 1);
275
+ // check props first then state
276
+ const actualVal = props[propOrState] !== undefined
277
+ ? props[propOrState]
278
+ : state[propOrState];
279
+ if (actualVal === undefined)
280
+ continue;
281
+ // match boolean shorthand — disabled.true
282
+ if (expectedVal === 'true' && actualVal === true) {
283
+ classes.push(value);
284
+ }
285
+ else if (expectedVal === 'false' && actualVal === false) {
286
+ classes.push(value);
287
+ }
288
+ else if (String(actualVal) === expectedVal) {
289
+ // match string value — variant.primary
290
+ classes.push(value);
291
+ }
292
+ }
293
+ }
294
+ if (mode === 'tailwind') {
295
+ return classes.join(' ');
296
+ }
297
+ else {
298
+ // css mode — join as inline style string
299
+ return classes.join('; ');
300
+ }
301
+ }
302
+ return {
303
+ mode,
304
+ rules,
305
+ resolve,
306
+ };
307
+ }
308
+
309
+ // src/runtime/registry.ts
310
+ // global registry of all mounted component instances
311
+ const registry = new Map();
312
+ function register(name, instance) {
313
+ const id = `${name}_${Math.random().toString(36).slice(2, 9)}`;
314
+ const full = {
315
+ name,
316
+ id,
317
+ el: null,
318
+ state: instance.state ?? {},
319
+ methods: instance.methods ?? {},
320
+ props: instance.props ?? {},
321
+ };
322
+ if (!registry.has(name))
323
+ registry.set(name, []);
324
+ registry.get(name).push(full);
325
+ return id;
326
+ }
327
+ function unregister(id) {
328
+ for (const [name, instances] of registry.entries()) {
329
+ const idx = instances.findIndex(i => i.id === id);
330
+ if (idx >= 0) {
331
+ instances.splice(idx, 1);
332
+ if (instances.length === 0)
333
+ registry.delete(name);
334
+ return;
335
+ }
336
+ }
337
+ }
338
+ function getAll(name) {
339
+ return registry.get(name) ?? [];
340
+ }
341
+ function getById(id) {
342
+ for (const instances of registry.values()) {
343
+ const found = instances.find(i => i.id === id);
344
+ if (found)
345
+ return found;
346
+ }
347
+ return null;
348
+ }
349
+ function getByClass(name, cls) {
350
+ return getAll(name).filter(i => i.el?.classList.contains(cls));
351
+ }
352
+ function getByPropValue(name, prop, value) {
353
+ return getAll(name).filter(i => i.props[prop] === value);
354
+ }
355
+ function getByStateValue(name, key, value) {
356
+ return getAll(name).filter(i => i.state[key] === value);
357
+ }
358
+ function clearRegistry() {
359
+ registry.clear();
360
+ }
361
+
362
+ // src/runtime/selector.ts
363
+ // jQuery-tribute: unified selector for DOM elements and Tardis component instances
364
+ function parseAttrFilter(raw) {
365
+ const match = raw.match(/^([\w.]+)\s*(>=|<=|>|<|=)\s*(.+)$/);
366
+ if (!match)
367
+ return null;
368
+ const [, key, op, rawVal] = match;
369
+ let value;
370
+ if (rawVal === 'true')
371
+ value = true;
372
+ else if (rawVal === 'false')
373
+ value = false;
374
+ else if (/^-?\d+(\.\d+)?$/.test(rawVal))
375
+ value = parseFloat(rawVal);
376
+ else
377
+ value = rawVal.replace(/^["']|["']$/g, ''); // strip surrounding quotes
378
+ return { key, op, value };
379
+ }
380
+ function instanceMatchesAttr(inst, filter) {
381
+ const { key, op, value } = filter;
382
+ let actual;
383
+ if (key.startsWith('state.')) {
384
+ actual = inst.state[key.slice(6)];
385
+ }
386
+ else {
387
+ actual = inst.props[key] !== undefined ? inst.props[key] : inst.state[key];
388
+ }
389
+ switch (op) {
390
+ case '=': return actual === value;
391
+ case '>': return actual > value;
392
+ case '<': return actual < value;
393
+ case '>=': return actual >= value;
394
+ case '<=': return actual <= value;
395
+ default: return false;
396
+ }
397
+ }
398
+ // ── DOMSelection ─────────────────────────────────────────────────────────────
399
+ class DOMSelection {
400
+ constructor(elements) {
401
+ this.elements = elements;
402
+ }
403
+ each(fn) {
404
+ this.elements.forEach(fn);
405
+ return this;
406
+ }
407
+ focus() { return this.each(el => el.focus()); }
408
+ blur() { return this.each(el => el.blur()); }
409
+ hide() { return this.each(el => { el.style.display = 'none'; }); }
410
+ show() { return this.each(el => { el.style.display = ''; }); }
411
+ remove() { return this.each(el => el.parentNode?.removeChild(el)); }
412
+ toggle() {
413
+ return this.each(el => {
414
+ el.style.display = el.style.display === 'none' ? '' : 'none';
415
+ });
416
+ }
417
+ disable() { return this.each(el => el.setAttribute('disabled', '')); }
418
+ enable() { return this.each(el => el.removeAttribute('disabled')); }
419
+ addClass(cls) { return this.each(el => el.classList.add(cls)); }
420
+ removeClass(cls) { return this.each(el => el.classList.remove(cls)); }
421
+ toggleClass(cls) { return this.each(el => el.classList.toggle(cls)); }
422
+ attr(key, val) {
423
+ if (val === undefined)
424
+ return this.elements[0]?.getAttribute(key) ?? null;
425
+ return this.each(el => el.setAttribute(key, val));
426
+ }
427
+ val(value) {
428
+ if (value === undefined)
429
+ return this.elements[0]?.value ?? '';
430
+ return this.each(el => { el.value = value; });
431
+ }
432
+ rect() { return this.elements[0]?.getBoundingClientRect() ?? null; }
433
+ width() { return this.elements[0]?.offsetWidth ?? 0; }
434
+ height() { return this.elements[0]?.offsetHeight ?? 0; }
435
+ scrollIntoView() { return this.each(el => el.scrollIntoView?.()); }
436
+ scrollTo(opts) { return this.each(el => el.scrollTo?.(opts)); }
437
+ fadeIn() { return this.each(el => { el.style.display = ''; el.style.opacity = '1'; }); }
438
+ fadeOut() { return this.each(el => { el.style.opacity = '0'; }); }
439
+ slideDown() {
440
+ return this.each(el => { el.style.overflow = 'hidden'; el.style.maxHeight = ''; });
441
+ }
442
+ slideUp() {
443
+ return this.each(el => { el.style.overflow = 'hidden'; el.style.maxHeight = '0px'; });
444
+ }
445
+ // no-ops for component-only API — keeps chaining safe on DOM selections
446
+ $update(_key, _value) { return this; }
447
+ $reset(_initial) { return this; }
448
+ get methods() {
449
+ const self = this;
450
+ return new Proxy({}, {
451
+ get: () => () => self,
452
+ });
453
+ }
454
+ }
455
+ // ── ComponentSelection ────────────────────────────────────────────────────────
456
+ class ComponentSelection {
457
+ constructor(instances) {
458
+ this.instances = instances;
459
+ }
460
+ eachEl(fn) {
461
+ this.instances.forEach(inst => { if (inst.el)
462
+ fn(inst.el); });
463
+ return this;
464
+ }
465
+ focus() { return this.eachEl(el => el.focus()); }
466
+ blur() { return this.eachEl(el => el.blur()); }
467
+ hide() { return this.eachEl(el => { el.style.display = 'none'; }); }
468
+ show() { return this.eachEl(el => { el.style.display = ''; }); }
469
+ remove() { return this.eachEl(el => el.parentNode?.removeChild(el)); }
470
+ toggle() {
471
+ return this.eachEl(el => {
472
+ el.style.display = el.style.display === 'none' ? '' : 'none';
473
+ });
474
+ }
475
+ disable() { return this.eachEl(el => el.setAttribute('disabled', '')); }
476
+ enable() { return this.eachEl(el => el.removeAttribute('disabled')); }
477
+ addClass(cls) { return this.eachEl(el => el.classList.add(cls)); }
478
+ removeClass(cls) { return this.eachEl(el => el.classList.remove(cls)); }
479
+ toggleClass(cls) { return this.eachEl(el => el.classList.toggle(cls)); }
480
+ attr(key, val) {
481
+ if (val === undefined)
482
+ return this.instances[0]?.el?.getAttribute(key) ?? null;
483
+ return this.eachEl(el => el.setAttribute(key, val));
484
+ }
485
+ val(value) {
486
+ if (value === undefined)
487
+ return this.instances[0]?.el?.value ?? '';
488
+ return this.eachEl(el => { el.value = value; });
489
+ }
490
+ rect() { return this.instances[0]?.el?.getBoundingClientRect() ?? null; }
491
+ width() { return this.instances[0]?.el?.offsetWidth ?? 0; }
492
+ height() { return this.instances[0]?.el?.offsetHeight ?? 0; }
493
+ scrollIntoView() { return this.eachEl(el => el.scrollIntoView?.()); }
494
+ scrollTo(opts) { return this.eachEl(el => el.scrollTo?.(opts)); }
495
+ fadeIn() { return this.eachEl(el => { el.style.display = ''; el.style.opacity = '1'; }); }
496
+ fadeOut() { return this.eachEl(el => { el.style.opacity = '0'; }); }
497
+ slideDown() {
498
+ return this.eachEl(el => { el.style.overflow = 'hidden'; el.style.maxHeight = ''; });
499
+ }
500
+ slideUp() {
501
+ return this.eachEl(el => { el.style.overflow = 'hidden'; el.style.maxHeight = '0px'; });
502
+ }
503
+ // component-specific
504
+ $update(key, value) {
505
+ this.instances.forEach(inst => $update(inst.state, key, value));
506
+ return this;
507
+ }
508
+ $reset(initial) {
509
+ this.instances.forEach(inst => $reset(inst.state, initial));
510
+ return this;
511
+ }
512
+ get methods() {
513
+ const self = this;
514
+ return new Proxy({}, {
515
+ get(_, methodName) {
516
+ return (...args) => {
517
+ self.instances.forEach(inst => inst.methods[methodName]?.(...args));
518
+ return self;
519
+ };
520
+ },
521
+ });
522
+ }
523
+ }
524
+ // ── $ ────────────────────────────────────────────────────────────────────────
525
+ function $(selector) {
526
+ const s = selector.trim();
527
+ // Component selectors start with an uppercase letter
528
+ if (/^[A-Z]/.test(s)) {
529
+ const m = s.match(/^([A-Za-z]\w*)(?:#([\w-]+))?(?:\.([\w-]+))?(?:\[([^\]]+)\])?$/);
530
+ if (m) {
531
+ const [, name, idFilter, classFilter, attrFilter] = m;
532
+ let instances = getAll(name);
533
+ if (idFilter)
534
+ instances = instances.filter(inst => inst.el?.id === idFilter);
535
+ if (classFilter)
536
+ instances = instances.filter(inst => inst.el?.classList.contains(classFilter));
537
+ if (attrFilter) {
538
+ const parsed = parseAttrFilter(attrFilter);
539
+ if (parsed)
540
+ instances = instances.filter(inst => instanceMatchesAttr(inst, parsed));
541
+ }
542
+ return new ComponentSelection(instances);
543
+ }
544
+ }
545
+ // DOM selector — delegate to querySelectorAll
546
+ const nodes = document.querySelectorAll(s);
547
+ return new DOMSelection(Array.from(nodes));
548
+ }
549
+
550
+ // src/runtime/events.ts
551
+ // stores pending mount callbacks to be called after DOM insertion
552
+ const mountCallbacks = [];
553
+ const destroyCallbacks = [];
554
+ function registerEvents(handlers) {
555
+ if (handlers.onMount) {
556
+ mountCallbacks.push(handlers.onMount);
557
+ }
558
+ if (handlers.onDestroy) {
559
+ destroyCallbacks.push(handlers.onDestroy);
560
+ }
561
+ }
562
+ function flushMountCallbacks() {
563
+ while (mountCallbacks.length > 0) {
564
+ const cb = mountCallbacks.shift();
565
+ cb();
566
+ }
567
+ }
568
+ function flushDestroyCallbacks() {
569
+ while (destroyCallbacks.length > 0) {
570
+ const cb = destroyCallbacks.shift();
571
+ cb();
572
+ }
573
+ }
574
+
575
+ const $params = {};
576
+ let activeRouter = null;
577
+ function normalizePath(path) {
578
+ if (!path)
579
+ return '/';
580
+ const [pathname] = path.split('?');
581
+ const normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;
582
+ if (normalized !== '/' && normalized.endsWith('/'))
583
+ return normalized.slice(0, -1);
584
+ return normalized;
585
+ }
586
+ function splitPath(path) {
587
+ const normalized = normalizePath(path);
588
+ if (normalized === '/')
589
+ return [];
590
+ return normalized.slice(1).split('/').filter(Boolean);
591
+ }
592
+ function setParams(next) {
593
+ for (const key of Object.keys($params)) {
594
+ delete $params[key];
595
+ }
596
+ Object.assign($params, next);
597
+ }
598
+ function matchRoute(routePath, currentPath) {
599
+ const routeParts = splitPath(routePath);
600
+ const currentParts = splitPath(currentPath);
601
+ if (routeParts.length !== currentParts.length) {
602
+ return { matched: false, params: {} };
603
+ }
604
+ const params = {};
605
+ for (let i = 0; i < routeParts.length; i++) {
606
+ const routePart = routeParts[i];
607
+ const currentPart = currentParts[i];
608
+ if (routePart.startsWith(':')) {
609
+ params[routePart.slice(1)] = decodeURIComponent(currentPart);
610
+ continue;
611
+ }
612
+ if (routePart !== currentPart) {
613
+ return { matched: false, params: {} };
614
+ }
615
+ }
616
+ return { matched: true, params };
617
+ }
618
+ function resolveRoute(routes, path) {
619
+ const normalized = normalizePath(path);
620
+ for (const route of routes) {
621
+ const result = matchRoute(route.path, normalized);
622
+ if (result.matched)
623
+ return { route, params: result.params };
624
+ }
625
+ return { route: null, params: {} };
626
+ }
627
+ function createRouter(routes, mountTarget) {
628
+ const target = mountTarget ?? document.getElementById('app') ?? document.body;
629
+ let currentNode = null;
630
+ function render(path) {
631
+ const { route, params } = resolveRoute(routes, path);
632
+ if (!route) {
633
+ target.innerHTML = '<h1>404</h1>';
634
+ setParams({});
635
+ currentNode = null;
636
+ return;
637
+ }
638
+ flushDestroyCallbacks();
639
+ if (currentNode && currentNode.parentNode === target) {
640
+ target.removeChild(currentNode);
641
+ }
642
+ const nextNode = route.component();
643
+ currentNode = nextNode;
644
+ setParams(params);
645
+ target.innerHTML = '';
646
+ target.appendChild(nextNode);
647
+ }
648
+ function navigate(path, replace = false) {
649
+ const nextPath = normalizePath(path);
650
+ if (replace) {
651
+ window.history.replaceState({}, '', nextPath);
652
+ }
653
+ else {
654
+ window.history.pushState({}, '', nextPath);
655
+ }
656
+ render(nextPath);
657
+ }
658
+ function back() {
659
+ window.history.back();
660
+ }
661
+ function forward() {
662
+ window.history.forward();
663
+ }
664
+ function handleLinkClick(event) {
665
+ if (event.defaultPrevented)
666
+ return;
667
+ if (event.button !== 0)
668
+ return;
669
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
670
+ return;
671
+ const targetEl = event.target;
672
+ const anchor = targetEl?.closest('a[href]');
673
+ if (!anchor)
674
+ return;
675
+ const href = anchor.getAttribute('href');
676
+ if (!href || href.startsWith('http') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('#'))
677
+ return;
678
+ if (anchor.target && anchor.target !== '_self')
679
+ return;
680
+ const nextPath = normalizePath(href);
681
+ event.preventDefault();
682
+ navigate(nextPath);
683
+ }
684
+ const router = {
685
+ start() {
686
+ render(window.location.pathname);
687
+ document.addEventListener('click', handleLinkClick);
688
+ window.addEventListener('popstate', () => {
689
+ render(window.location.pathname);
690
+ });
691
+ activeRouter = router;
692
+ },
693
+ navigate,
694
+ back,
695
+ forward,
696
+ };
697
+ return router;
698
+ }
699
+ function $navigate(path) {
700
+ if (activeRouter) {
701
+ activeRouter.navigate(path);
702
+ return;
703
+ }
704
+ const nextPath = normalizePath(path);
705
+ window.history.pushState({}, '', nextPath);
706
+ }
707
+ function $back() {
708
+ if (activeRouter) {
709
+ activeRouter.back();
710
+ return;
711
+ }
712
+ window.history.back();
713
+ }
714
+ function $forward() {
715
+ if (activeRouter) {
716
+ activeRouter.forward();
717
+ return;
718
+ }
719
+ window.history.forward();
720
+ }
721
+
722
+ // src/runtime/index.ts
723
+ // the ~5KB runtime that ships with every tardisjs app
724
+ const $runtime = {
725
+ // selector
726
+ $,
727
+ params: $params,
728
+ navigate: $navigate,
729
+ back: $back,
730
+ forward: $forward,
731
+ // state
732
+ state: createState,
733
+ update: $update,
734
+ toggle: $toggle,
735
+ reset: $reset,
736
+ batch: $batch,
737
+ registerDep,
738
+ // fetch
739
+ fetch: $fetch,
740
+ // render
741
+ if: $if,
742
+ each: $each,
743
+ show: $show,
744
+ portal: $portal,
745
+ bind,
746
+ bindClass,
747
+ bindAttr,
748
+ resolveStyles,
749
+ text,
750
+ // styles
751
+ styles: createStyles,
752
+ // registry
753
+ register,
754
+ unregister,
755
+ getAll,
756
+ getById,
757
+ // events
758
+ events: registerEvents,
759
+ flush: flushMountCallbacks,
760
+ // placeholder — used during compilation before ui is wired up
761
+ placeholder(name) {
762
+ const el = document.createElement('div');
763
+ el.setAttribute('data-component', name);
764
+ return el;
765
+ },
766
+ // component instantiation
767
+ component(name, props) {
768
+ // looks up the component factory from a global registry
769
+ // populated when components are imported
770
+ const globalRegistry = window;
771
+ const factory = globalRegistry[`__tardis_${name}`];
772
+ if (factory)
773
+ return factory(props);
774
+ const el = document.createElement('div');
775
+ el.setAttribute('data-component', name);
776
+ el.setAttribute('data-props', JSON.stringify(props));
777
+ return el;
778
+ },
779
+ // chain — attaches event to a chained element
780
+ chain(el, event, handler) {
781
+ if (event === '__mount') {
782
+ flushMountCallbacks();
783
+ }
784
+ else {
785
+ el.addEventListener(event, handler);
786
+ }
787
+ },
788
+ };
789
+
790
+ export { $, $back, $batch, $each, $fetch, $forward, $if, $navigate, $params, $portal, $reset, $runtime, $show, $toggle, $update, ComponentSelection, DOMSelection, bind, bindAttr, bindClass, clearRegistry, createRouter, createState, createStyles, flushDestroyCallbacks, flushMountCallbacks, getAll, getByClass, getById, getByPropValue, getByStateValue, register, registerDep, registerEvents, resolveStyles, text, unregister };
791
+ //# sourceMappingURL=index.mjs.map