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