tina4js 1.0.1 → 1.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.
package/TINA4.md CHANGED
@@ -156,6 +156,20 @@ tina4 build --target python
156
156
  - `formToken` injected into POST/PUT/PATCH/DELETE body
157
157
  - Token auto-rotated via `FreshToken` response header
158
158
 
159
+ ## Debug Overlay
160
+
161
+ ```ts
162
+ // Always-on
163
+ import 'tina4js/debug';
164
+
165
+ // Dev-only (recommended — stripped from production builds)
166
+ if (import.meta.env.DEV) import('tina4js/debug');
167
+ ```
168
+
169
+ - Toggle with **Ctrl+Shift+D**
170
+ - Shows: live signal values + subscriber counts, mounted components, route navigation history with timing, API request/response log
171
+ - Zero cost in production when using the `import.meta.env.DEV` guard (tree-shaken by Vite/Rollup)
172
+
159
173
  ## Framework Size
160
174
 
161
175
  | Module | Gzipped |
@@ -1 +1 @@
1
- "use strict";let u=null,a=null,d=null;function m(o,t){a=o,d=t}let c=0;const p=new Set;function b(o,t){let e=o;const s=new Set,r={_t4:!0,get value(){return u&&s.add(u),e},set value(n){if(Object.is(n,e))return;const C=e;if(e=n,r._debugInfo&&r._debugInfo.updateCount++,d&&d(r,C,n),c>0)for(const l of s)p.add(l);else for(const l of[...s])l()},_subscribe(n){return s.add(n),()=>{s.delete(n)}},peek(){return e}};return a&&(r._debugInfo={label:t,createdAt:Date.now(),updateCount:0,subs:s},a(r,t)),r}function w(o){const t=b(void 0);return g(()=>{t.value=o()}),{_t4:!0,get value(){return t.value},set value(e){throw new Error("[tina4] computed signals are read-only")},_subscribe(e){return t._subscribe(e)},peek(){return t.peek()}}}function g(o){let t=!1;const e=()=>{if(t)return;const s=u;u=e;try{o()}finally{u=s}};return e(),()=>{t=!0}}function y(o){c++;try{o()}finally{if(c--,c===0){const t=[...p];p.clear();for(const e of t)e()}}}function k(o){return o!==null&&typeof o=="object"&&o._t4===!0}let h=null,f=null;function S(o,t){h=o,f=t}const i=class i extends HTMLElement{constructor(){super(),this._props={},this._rendered=!1;const t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(const[e,s]of Object.entries(t.props))this._props[e]=b(this._coerce(this.getAttribute(e),s))}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;const t=this.constructor;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){const s=document.createElement("style");s.textContent=t.styles,this._root.appendChild(s)}const e=this.render();e&&this._root.appendChild(e),this.onMount(),h&&h(this)}disconnectedCallback(){this.onUnmount(),f&&f(this)}attributeChangedCallback(t,e,s){const n=this.constructor.props[t];n&&this._props[t]&&(this._props[t].value=this._coerce(s,n))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,e){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...e}))}onMount(){}onUnmount(){}_coerce(t,e){return e===Boolean?t!==null:e===Number?t!==null?Number(t):0:t??""}};i.props={},i.styles="",i.shadow=!0;let _=i;exports.Tina4Element=_;exports.__setDebugComponentHooks=S;exports.__setDebugSignalHooks=m;exports.batch=y;exports.computed=w;exports.effect=g;exports.isSignal=k;exports.signal=b;
1
+ "use strict";const p=require("./signal.cjs.js");let r=null,n=null;function u(h,t){r=h,n=t}const e=class e extends HTMLElement{constructor(){super(),this._props={},this._rendered=!1;const t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(const[o,s]of Object.entries(t.props))this._props[o]=p.signal(this._coerce(this.getAttribute(o),s))}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;const t=this.constructor;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){const s=document.createElement("style");s.textContent=t.styles,this._root.appendChild(s)}const o=this.render();o&&this._root.appendChild(o),this.onMount(),r&&r(this)}disconnectedCallback(){this.onUnmount(),n&&n(this)}attributeChangedCallback(t,o,s){const c=this.constructor.props[t];c&&this._props[t]&&(this._props[t].value=this._coerce(s,c))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,o){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...o}))}onMount(){}onUnmount(){}_coerce(t,o){return o===Boolean?t!==null:o===Number?t!==null?Number(t):0:t??""}};e.props={},e.styles="",e.shadow=!0;let i=e;exports.Tina4Element=i;exports.__setDebugComponentHooks=u;
@@ -1,97 +1,15 @@
1
- let u = null, l = null, d = null;
2
- function w(o, t) {
3
- l = o, d = t;
1
+ import { s as p } from "./signal.es.js";
2
+ let r = null, n = null;
3
+ function a(h, t) {
4
+ r = h, n = t;
4
5
  }
5
- let c = 0;
6
- const p = /* @__PURE__ */ new Set();
7
- function _(o, t) {
8
- let e = o;
9
- const s = /* @__PURE__ */ new Set(), n = {
10
- _t4: !0,
11
- get value() {
12
- return u && s.add(u), e;
13
- },
14
- set value(r) {
15
- if (Object.is(r, e)) return;
16
- const g = e;
17
- if (e = r, n._debugInfo && n._debugInfo.updateCount++, d && d(n, g, r), c > 0)
18
- for (const a of s) p.add(a);
19
- else
20
- for (const a of [...s]) a();
21
- },
22
- _subscribe(r) {
23
- return s.add(r), () => {
24
- s.delete(r);
25
- };
26
- },
27
- peek() {
28
- return e;
29
- }
30
- };
31
- return l && (n._debugInfo = { label: t, createdAt: Date.now(), updateCount: 0, subs: s }, l(n, t)), n;
32
- }
33
- function y(o) {
34
- const t = _(void 0);
35
- return C(() => {
36
- t.value = o();
37
- }), {
38
- _t4: !0,
39
- get value() {
40
- return t.value;
41
- },
42
- set value(e) {
43
- throw new Error("[tina4] computed signals are read-only");
44
- },
45
- _subscribe(e) {
46
- return t._subscribe(e);
47
- },
48
- peek() {
49
- return t.peek();
50
- }
51
- };
52
- }
53
- function C(o) {
54
- let t = !1;
55
- const e = () => {
56
- if (t) return;
57
- const s = u;
58
- u = e;
59
- try {
60
- o();
61
- } finally {
62
- u = s;
63
- }
64
- };
65
- return e(), () => {
66
- t = !0;
67
- };
68
- }
69
- function m(o) {
70
- c++;
71
- try {
72
- o();
73
- } finally {
74
- if (c--, c === 0) {
75
- const t = [...p];
76
- p.clear();
77
- for (const e of t) e();
78
- }
79
- }
80
- }
81
- function k(o) {
82
- return o !== null && typeof o == "object" && o._t4 === !0;
83
- }
84
- let h = null, f = null;
85
- function v(o, t) {
86
- h = o, f = t;
87
- }
88
- const i = class i extends HTMLElement {
6
+ const e = class e extends HTMLElement {
89
7
  constructor() {
90
8
  super(), this._props = {}, this._rendered = !1;
91
9
  const t = this.constructor;
92
10
  this._root = t.shadow ? this.attachShadow({ mode: "open" }) : this;
93
- for (const [e, s] of Object.entries(t.props))
94
- this._props[e] = _(this._coerce(this.getAttribute(e), s));
11
+ for (const [o, s] of Object.entries(t.props))
12
+ this._props[o] = p(this._coerce(this.getAttribute(o), s));
95
13
  }
96
14
  static get observedAttributes() {
97
15
  return Object.keys(this.props);
@@ -104,15 +22,15 @@ const i = class i extends HTMLElement {
104
22
  const s = document.createElement("style");
105
23
  s.textContent = t.styles, this._root.appendChild(s);
106
24
  }
107
- const e = this.render();
108
- e && this._root.appendChild(e), this.onMount(), h && h(this);
25
+ const o = this.render();
26
+ o && this._root.appendChild(o), this.onMount(), r && r(this);
109
27
  }
110
28
  disconnectedCallback() {
111
- this.onUnmount(), f && f(this);
29
+ this.onUnmount(), n && n(this);
112
30
  }
113
- attributeChangedCallback(t, e, s) {
114
- const r = this.constructor.props[t];
115
- r && this._props[t] && (this._props[t].value = this._coerce(s, r));
31
+ attributeChangedCallback(t, o, s) {
32
+ const i = this.constructor.props[t];
33
+ i && this._props[t] && (this._props[t].value = this._coerce(s, i));
116
34
  }
117
35
  /**
118
36
  * Get a reactive signal for a declared prop.
@@ -135,12 +53,12 @@ const i = class i extends HTMLElement {
135
53
  * this.emit('activate', { detail: 42 });
136
54
  * ```
137
55
  */
138
- emit(t, e) {
56
+ emit(t, o) {
139
57
  this.dispatchEvent(new CustomEvent(t, {
140
58
  bubbles: !0,
141
59
  composed: !0,
142
60
  // crosses shadow DOM boundary
143
- ...e
61
+ ...o
144
62
  }));
145
63
  }
146
64
  // ── Lifecycle hooks (override in subclass) ──────────────────────
@@ -151,19 +69,13 @@ const i = class i extends HTMLElement {
151
69
  onUnmount() {
152
70
  }
153
71
  // ── Private ─────────────────────────────────────────────────────
154
- _coerce(t, e) {
155
- return e === Boolean ? t !== null : e === Number ? t !== null ? Number(t) : 0 : t ?? "";
72
+ _coerce(t, o) {
73
+ return o === Boolean ? t !== null : o === Number ? t !== null ? Number(t) : 0 : t ?? "";
156
74
  }
157
75
  };
158
- i.props = {}, i.styles = "", i.shadow = !0;
159
- let b = i;
76
+ e.props = {}, e.styles = "", e.shadow = !0;
77
+ let c = e;
160
78
  export {
161
- b as T,
162
- w as _,
163
- v as a,
164
- m as b,
165
- y as c,
166
- C as e,
167
- k as i,
168
- _ as s
79
+ c as T,
80
+ a as _
169
81
  };
package/dist/core.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("./component.cjs.js"),h=new WeakMap,d="t4:";function _(t,...e){let n=h.get(t);if(!n){n=document.createElement("template");let c="";for(let s=0;s<t.length;s++)c+=t[s],s<e.length&&(S(c)?c+=`__t4_${s}__`:c+=`<!--${d}${s}-->`);n.innerHTML=c,h.set(t,n)}const o=n.content.cloneNode(!0),i=A(o);for(const{marker:c,index:s}of i)b(c,e[s]);const a=N(o);for(const c of a)y(c,e);return o}function A(t){const e=[];return u(t,n=>{if(n.nodeType===8){const o=n.data;if(o&&o.startsWith(d)){const i=parseInt(o.slice(d.length),10);e.push({marker:n,index:i})}}}),e}function N(t){const e=[];return u(t,n=>{n.nodeType===1&&e.push(n)}),e}function u(t,e){const n=t.childNodes;for(let o=0;o<n.length;o++){const i=n[o];e(i),u(i,e)}}function b(t,e){const n=t.parentNode;if(n)if(f.isSignal(e)){const o=document.createTextNode("");n.replaceChild(o,t),f.effect(()=>{o.data=String(e.value??"")})}else if(typeof e=="function"){const o=document.createComment("");n.replaceChild(o,t);let i=[];f.effect(()=>{var l;const a=e();for(const r of i)(l=r.parentNode)==null||l.removeChild(r);i=[];const c=m(a),s=o.parentNode;for(const r of c)s.insertBefore(r,o),i.push(r)})}else if(p(e))n.replaceChild(e,t);else if(e instanceof Node)n.replaceChild(e,t);else if(Array.isArray(e)){const o=document.createDocumentFragment();for(const i of e){const a=m(i);for(const c of a)o.appendChild(c)}n.replaceChild(o,t)}else{const o=document.createTextNode(String(e??""));n.replaceChild(o,t)}}function y(t,e){const n=[];for(const o of Array.from(t.attributes)){const i=o.name,a=o.value;if(i.startsWith("@")){const s=i.slice(1),l=a.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];typeof r=="function"&&t.addEventListener(s,r)}n.push(i);continue}if(i.startsWith("?")){const s=i.slice(1),l=a.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];if(f.isSignal(r)){const g=r;f.effect(()=>{g.value?t.setAttribute(s,""):t.removeAttribute(s)})}else r&&t.setAttribute(s,"")}n.push(i);continue}if(i.startsWith(".")){const s=i.slice(1),l=a.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];f.isSignal(r)?f.effect(()=>{t[s]=r.value}):t[s]=r}n.push(i);continue}const c=a.match(/__t4_(\d+)__/);if(c){const s=e[parseInt(c[1],10)];if(f.isSignal(s)){const l=s;f.effect(()=>{t.setAttribute(i,String(l.value??""))})}else typeof s=="function"?f.effect(()=>{t.setAttribute(i,String(s()??""))}):t.setAttribute(i,String(s??""))}}for(const o of n)t.removeAttribute(o)}function m(t){if(t==null||t===!1)return[];if(p(t))return Array.from(t.childNodes);if(t instanceof Node)return[t];if(Array.isArray(t)){const e=[];for(const n of t)e.push(...m(n));return e}return[document.createTextNode(String(t))]}function p(t){return t!=null&&typeof t=="object"&&t.nodeType===11}function S(t){let e=!1,n=!1,o=!1;for(let i=0;i<t.length;i++){const a=t[i];a==="<"&&!e&&!n&&(o=!0),a===">"&&!e&&!n&&(o=!1),o&&(a==='"'&&!e&&(n=!n),a==="'"&&!n&&(e=!e))}return o}exports.Tina4Element=f.Tina4Element;exports.batch=f.batch;exports.computed=f.computed;exports.effect=f.effect;exports.isSignal=f.isSignal;exports.signal=f.signal;exports.html=_;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./signal.cjs.js"),_=require("./component.cjs.js"),h=new WeakMap,d="t4:";function A(t,...e){let n=h.get(t);if(!n){n=document.createElement("template");let c="";for(let s=0;s<t.length;s++)c+=t[s],s<e.length&&(T(c)?c+=`__t4_${s}__`:c+=`<!--${d}${s}-->`);n.innerHTML=c,h.set(t,n)}const o=n.content.cloneNode(!0),i=N(o);for(const{marker:c,index:s}of i)y(c,e[s]);const f=b(o);for(const c of f)S(c,e);return o}function N(t){const e=[];return u(t,n=>{if(n.nodeType===8){const o=n.data;if(o&&o.startsWith(d)){const i=parseInt(o.slice(d.length),10);e.push({marker:n,index:i})}}}),e}function b(t){const e=[];return u(t,n=>{n.nodeType===1&&e.push(n)}),e}function u(t,e){const n=t.childNodes;for(let o=0;o<n.length;o++){const i=n[o];e(i),u(i,e)}}function y(t,e){const n=t.parentNode;if(n)if(a.isSignal(e)){const o=document.createTextNode("");n.replaceChild(o,t),a.effect(()=>{o.data=String(e.value??"")})}else if(typeof e=="function"){const o=document.createComment("");n.replaceChild(o,t);let i=[];a.effect(()=>{var l;const f=e();for(const r of i)(l=r.parentNode)==null||l.removeChild(r);i=[];const c=m(f),s=o.parentNode;for(const r of c)s.insertBefore(r,o),i.push(r)})}else if(p(e))n.replaceChild(e,t);else if(e instanceof Node)n.replaceChild(e,t);else if(Array.isArray(e)){const o=document.createDocumentFragment();for(const i of e){const f=m(i);for(const c of f)o.appendChild(c)}n.replaceChild(o,t)}else{const o=document.createTextNode(String(e??""));n.replaceChild(o,t)}}function S(t,e){const n=[];for(const o of Array.from(t.attributes)){const i=o.name,f=o.value;if(i.startsWith("@")){const s=i.slice(1),l=f.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];typeof r=="function"&&t.addEventListener(s,r)}n.push(i);continue}if(i.startsWith("?")){const s=i.slice(1),l=f.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];if(a.isSignal(r)){const g=r;a.effect(()=>{g.value?t.setAttribute(s,""):t.removeAttribute(s)})}else r&&t.setAttribute(s,"")}n.push(i);continue}if(i.startsWith(".")){const s=i.slice(1),l=f.match(/__t4_(\d+)__/);if(l){const r=e[parseInt(l[1],10)];a.isSignal(r)?a.effect(()=>{t[s]=r.value}):t[s]=r}n.push(i);continue}const c=f.match(/__t4_(\d+)__/);if(c){const s=e[parseInt(c[1],10)];if(a.isSignal(s)){const l=s;a.effect(()=>{t.setAttribute(i,String(l.value??""))})}else typeof s=="function"?a.effect(()=>{t.setAttribute(i,String(s()??""))}):t.setAttribute(i,String(s??""))}}for(const o of n)t.removeAttribute(o)}function m(t){if(t==null||t===!1)return[];if(p(t))return Array.from(t.childNodes);if(t instanceof Node)return[t];if(Array.isArray(t)){const e=[];for(const n of t)e.push(...m(n));return e}return[document.createTextNode(String(t))]}function p(t){return t!=null&&typeof t=="object"&&t.nodeType===11}function T(t){let e=!1,n=!1,o=!1;for(let i=0;i<t.length;i++){const f=t[i];f==="<"&&!e&&!n&&(o=!0),f===">"&&!e&&!n&&(o=!1),o&&(f==='"'&&!e&&(n=!n),f==="'"&&!n&&(e=!e))}return o}exports.batch=a.batch;exports.computed=a.computed;exports.effect=a.effect;exports.isSignal=a.isSignal;exports.signal=a.signal;exports.Tina4Element=_.Tina4Element;exports.html=A;
package/dist/core.es.js CHANGED
@@ -1,5 +1,6 @@
1
- import { i as l, e as d } from "./component.es.js";
2
- import { T as I, b as W, c as V, s as w } from "./component.es.js";
1
+ import { i as l, e as d } from "./signal.es.js";
2
+ import { b as I, c as W, s as V } from "./signal.es.js";
3
+ import { T as D } from "./component.es.js";
3
4
  const u = /* @__PURE__ */ new WeakMap(), m = "t4:";
4
5
  function x(t, ...e) {
5
6
  let n = u.get(t);
@@ -20,7 +21,7 @@ function x(t, ...e) {
20
21
  }
21
22
  function A(t) {
22
23
  const e = [];
23
- return p(t, (n) => {
24
+ return h(t, (n) => {
24
25
  if (n.nodeType === 8) {
25
26
  const o = n.data;
26
27
  if (o && o.startsWith(m)) {
@@ -32,15 +33,15 @@ function A(t) {
32
33
  }
33
34
  function N(t) {
34
35
  const e = [];
35
- return p(t, (n) => {
36
+ return h(t, (n) => {
36
37
  n.nodeType === 1 && e.push(n);
37
38
  }), e;
38
39
  }
39
- function p(t, e) {
40
+ function h(t, e) {
40
41
  const n = t.childNodes;
41
42
  for (let o = 0; o < n.length; o++) {
42
43
  const s = n[o];
43
- e(s), p(s, e);
44
+ e(s), h(s, e);
44
45
  }
45
46
  }
46
47
  function b(t, e) {
@@ -60,7 +61,7 @@ function b(t, e) {
60
61
  const f = e();
61
62
  for (const c of s) (a = c.parentNode) == null || a.removeChild(c);
62
63
  s = [];
63
- const r = h(f), i = o.parentNode;
64
+ const r = p(f), i = o.parentNode;
64
65
  for (const c of r)
65
66
  i.insertBefore(c, o), s.push(c);
66
67
  });
@@ -71,7 +72,7 @@ function b(t, e) {
71
72
  else if (Array.isArray(e)) {
72
73
  const o = document.createDocumentFragment();
73
74
  for (const s of e) {
74
- const f = h(s);
75
+ const f = p(s);
75
76
  for (const r of f) o.appendChild(r);
76
77
  }
77
78
  n.replaceChild(o, t);
@@ -134,13 +135,13 @@ function y(t, e) {
134
135
  }
135
136
  for (const o of n) t.removeAttribute(o);
136
137
  }
137
- function h(t) {
138
+ function p(t) {
138
139
  if (t == null || t === !1) return [];
139
140
  if (_(t)) return Array.from(t.childNodes);
140
141
  if (t instanceof Node) return [t];
141
142
  if (Array.isArray(t)) {
142
143
  const e = [];
143
- for (const n of t) e.push(...h(n));
144
+ for (const n of t) e.push(...p(n));
144
145
  return e;
145
146
  }
146
147
  return [document.createTextNode(String(t))];
@@ -157,11 +158,11 @@ function T(t) {
157
158
  return o;
158
159
  }
159
160
  export {
160
- I as Tina4Element,
161
- W as batch,
162
- V as computed,
161
+ D as Tina4Element,
162
+ I as batch,
163
+ W as computed,
163
164
  d as effect,
164
165
  x as html,
165
166
  l as isSignal,
166
- w as signal
167
+ V as signal
167
168
  };
package/dist/debug.cjs.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("./component.cjs.js"),$=require("./index.cjs.js"),x=require("./api.cjs.js"),d=[],p={add(t,o){const e=t._debugInfo;d.push({ref:new WeakRef(t),label:o,createdAt:(e==null?void 0:e.createdAt)??Date.now(),updateCount:0,subs:new WeakRef((e==null?void 0:e.subs)??new Set)})},onUpdate(t){for(const o of d)if(o.ref.deref()===t){o.updateCount++;break}},getAll(){var o;const t=[];for(let e=d.length-1;e>=0;e--){const n=d[e],r=n.ref.deref();if(!r){d.splice(e,1);continue}const s=n.subs.deref();t.push({label:n.label,value:r.peek(),subscriberCount:s?s.size:0,updateCount:((o=r._debugInfo)==null?void 0:o.updateCount)??n.updateCount,alive:!0})}return t},get count(){return d.length}},a=[],b={onMount(t){a.push({ref:new WeakRef(t),tagName:t.tagName.toLowerCase(),mountedAt:Date.now()})},onUnmount(t){const o=a.findIndex(e=>e.ref.deref()===t);o>=0&&a.splice(o,1)},getAll(){const t=[];for(let o=a.length-1;o>=0;o--){const e=a[o],n=e.ref.deref();if(!n||!n.isConnected){a.splice(o,1);continue}const r={},s=n.constructor;if(s.props)for(const i of Object.keys(s.props))try{r[i]=n.prop(i).peek()}catch{}t.push({tagName:e.tagName,props:r,alive:!0})}return t},get count(){return a.length}},l=[],A=50,f={onNavigate(t){l.unshift({path:t.path,pattern:t.pattern,params:t.params,durationMs:t.durationMs,timestamp:Date.now()}),l.length>A&&l.pop()},getHistory(){return l},get count(){return l.length}};let D=0;const c=[],g=new Map,R=100,h={onRequest(t){var r;const o=++D,e={id:o,method:t.method??"GET",url:"",hasAuth:!!((r=t.headers)!=null&&r.Authorization),timestamp:Date.now(),pending:!0},n=String(o);g.set(n,e),c.unshift(e),c.length>R&&c.pop()},onResponse(t){for(const[o,e]of g)if(e.pending){e.status=t.status,e.durationMs=Date.now()-e.timestamp,e.pending=!1,t.ok||(e.error=`HTTP ${t.status}`),g.delete(o);break}},getLog(){return c},get count(){return c.length}},y=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const C=require("./signal.cjs.js"),A=require("./component.cjs.js"),_=require("./index.cjs.js"),m=require("./api.cjs.js"),d=[],p={add(t,o){const e=t._debugInfo;d.push({ref:new WeakRef(t),label:o,createdAt:(e==null?void 0:e.createdAt)??Date.now(),updateCount:0,subs:new WeakRef((e==null?void 0:e.subs)??new Set)})},onUpdate(t){for(const o of d)if(o.ref.deref()===t){o.updateCount++;break}},getAll(){var o;const t=[];for(let e=d.length-1;e>=0;e--){const n=d[e],r=n.ref.deref();if(!r){d.splice(e,1);continue}const s=n.subs.deref();t.push({label:n.label,value:r.peek(),subscriberCount:s?s.size:0,updateCount:((o=r._debugInfo)==null?void 0:o.updateCount)??n.updateCount,alive:!0})}return t},get count(){return d.length}},a=[],b={onMount(t){a.push({ref:new WeakRef(t),tagName:t.tagName.toLowerCase(),mountedAt:Date.now()})},onUnmount(t){const o=a.findIndex(e=>e.ref.deref()===t);o>=0&&a.splice(o,1)},getAll(){const t=[];for(let o=a.length-1;o>=0;o--){const e=a[o],n=e.ref.deref();if(!n||!n.isConnected){a.splice(o,1);continue}const r={},s=n.constructor;if(s.props)for(const i of Object.keys(s.props))try{r[i]=n.prop(i).peek()}catch{}t.push({tagName:e.tagName,props:r,alive:!0})}return t},get count(){return a.length}},l=[],D=50,f={onNavigate(t){l.unshift({path:t.path,pattern:t.pattern,params:t.params,durationMs:t.durationMs,timestamp:Date.now()}),l.length>D&&l.pop()},getHistory(){return l},get count(){return l.length}};let R=0;const c=[],g=new Map,M=100,h={onRequest(t){var r;const o=++R,e={id:o,method:t.method??"GET",url:"",hasAuth:!!((r=t.headers)!=null&&r.Authorization),timestamp:Date.now(),pending:!0},n=String(o);g.set(n,e),c.unshift(e),c.length>M&&c.pop()},onResponse(t){for(const[o,e]of g)if(e.pending){e.status=t.status,e.durationMs=Date.now()-e.timestamp,e.pending=!1,t.ok||(e.error=`HTTP ${t.status}`),g.delete(o);break}},getLog(){return c},get count(){return c.length}},x=`
2
2
  :host {
3
3
  all: initial;
4
4
  position: fixed;
@@ -204,46 +204,46 @@ tr:hover td { background: rgba(255,255,255,0.02); }
204
204
  border-radius: 50%;
205
205
  background: #66bb6a;
206
206
  }
207
- `;function M(t){if(t==null)return{text:String(t),cls:"val-null"};if(typeof t=="string")return{text:`"${t.length>30?t.slice(0,30)+"...":t}"`,cls:"val-string"};if(typeof t=="number")return{text:String(t),cls:"val-number"};if(typeof t=="boolean")return{text:String(t),cls:"val-boolean"};if(Array.isArray(t))return{text:`Array(${t.length})`,cls:"val-object"};if(typeof t=="object")try{return{text:JSON.stringify(t).slice(0,40),cls:"val-object"}}catch{}return{text:String(t),cls:"val-object"}}function E(){const t=p.getAll();if(t.length===0)return'<div class="t4-empty">No signals tracked yet.<br>Signals created after debug is enabled will appear here.</div>';let o="";for(let e=0;e<t.length;e++){const n=t[e],{text:r,cls:s}=M(n.value);o+=`<tr>
207
+ `;function E(t){if(t==null)return{text:String(t),cls:"val-null"};if(typeof t=="string")return{text:`"${t.length>30?t.slice(0,30)+"...":t}"`,cls:"val-string"};if(typeof t=="number")return{text:String(t),cls:"val-number"};if(typeof t=="boolean")return{text:String(t),cls:"val-boolean"};if(Array.isArray(t))return{text:`Array(${t.length})`,cls:"val-object"};if(typeof t=="object")try{return{text:JSON.stringify(t).slice(0,40),cls:"val-object"}}catch{}return{text:String(t),cls:"val-object"}}function L(){const t=p.getAll();if(t.length===0)return'<div class="t4-empty">No signals tracked yet.<br>Signals created after debug is enabled will appear here.</div>';let o="";for(let e=0;e<t.length;e++){const n=t[e],{text:r,cls:s}=E(n.value);o+=`<tr>
208
208
  <td>${n.label||`signal_${e}`}</td>
209
- <td><span class="${s}">${L(r)}</span></td>
209
+ <td><span class="${s}">${j(r)}</span></td>
210
210
  <td>${n.subscriberCount}</td>
211
211
  <td>${n.updateCount}</td>
212
212
  </tr>`}return`<table>
213
213
  <thead><tr><th>Label</th><th>Value</th><th>Subs</th><th>Updates</th></tr></thead>
214
214
  <tbody>${o}</tbody>
215
- </table>`}function L(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function j(){const t=b.getAll();if(t.length===0)return'<div class="t4-empty">No Tina4Elements mounted.<br>Custom elements extending Tina4Element will appear here.</div>';let o="";for(const e of t){const n=Object.keys(e.props).length>0?Object.entries(e.props).map(([r,s])=>`${r}=${JSON.stringify(s)??"null"}`).join(", "):"—";o+=`<tr>
216
- <td>&lt;${w(e.tagName)}&gt;</td>
217
- <td>${w(n.length>60?n.slice(0,60)+"...":n)}</td>
215
+ </table>`}function j(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function H(){const t=b.getAll();if(t.length===0)return'<div class="t4-empty">No Tina4Elements mounted.<br>Custom elements extending Tina4Element will appear here.</div>';let o="";for(const e of t){const n=Object.keys(e.props).length>0?Object.entries(e.props).map(([r,s])=>`${r}=${JSON.stringify(s)??"null"}`).join(", "):"—";o+=`<tr>
216
+ <td>&lt;${y(e.tagName)}&gt;</td>
217
+ <td>${y(n.length>60?n.slice(0,60)+"...":n)}</td>
218
218
  </tr>`}return`<table>
219
219
  <thead><tr><th>Element</th><th>Props</th></tr></thead>
220
220
  <tbody>${o}</tbody>
221
- </table>`}function w(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function H(t){const o=t<1?"<1ms":`${Math.round(t)}ms`,e=t<5?"duration fast":t<50?"duration":t<200?"duration slow":"duration very-slow";return{text:o,cls:e}}function z(t){return new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function N(){const t=$._getRoutes(),o=f.getHistory();let e="";if(t.length>0){let n="";for(const r of t)n+=`<tr>
222
- <td><span class="route-pattern">${v(r.pattern)}</span></td>
221
+ </table>`}function y(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function z(t){const o=t<1?"<1ms":`${Math.round(t)}ms`,e=t<5?"duration fast":t<50?"duration":t<200?"duration slow":"duration very-slow";return{text:o,cls:e}}function N(t){return new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function P(){const t=_._getRoutes(),o=f.getHistory();let e="";if(t.length>0){let n="";for(const r of t)n+=`<tr>
222
+ <td><span class="route-pattern">${w(r.pattern)}</span></td>
223
223
  <td>${r.hasGuard?"Yes":"—"}</td>
224
224
  </tr>`;e+=`<table>
225
225
  <thead><tr><th>Pattern</th><th>Guard</th></tr></thead>
226
226
  <tbody>${n}</tbody>
227
- </table>`}if(o.length>0){e+='<div style="margin-top:8px;padding-top:8px;border-top:1px solid #333;">';let n="";for(const r of o){const{text:s,cls:i}=H(r.durationMs),T=Object.keys(r.params).length>0?Object.entries(r.params).map(([S,C])=>`<span class="route-param">${S}=${C}</span>`).join(" "):"";n+=`<tr>
228
- <td>${z(r.timestamp)}</td>
229
- <td>${v(r.path)}</td>
230
- <td>${T||"—"}</td>
227
+ </table>`}if(o.length>0){e+='<div style="margin-top:8px;padding-top:8px;border-top:1px solid #333;">';let n="";for(const r of o){const{text:s,cls:i}=z(r.durationMs),k=Object.keys(r.params).length>0?Object.entries(r.params).map(([T,S])=>`<span class="route-param">${T}=${S}</span>`).join(" "):"";n+=`<tr>
228
+ <td>${N(r.timestamp)}</td>
229
+ <td>${w(r.path)}</td>
230
+ <td>${k||"—"}</td>
231
231
  <td><span class="${i}">${s}</span></td>
232
232
  </tr>`}e+=`<table>
233
233
  <thead><tr><th>Time</th><th>Path</th><th>Params</th><th>Duration</th></tr></thead>
234
234
  <tbody>${n}</tbody>
235
- </table></div>`}else t.length===0&&(e='<div class="t4-empty">No routes registered yet.</div>');return e}function v(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function P(t){if(t===void 0)return{text:"...",cls:"status-pending"};const o=t<1?"<1ms":`${Math.round(t)}ms`,e=t<100?"duration fast":t<500?"duration":t<2e3?"duration slow":"duration very-slow";return{text:o,cls:e}}function I(t,o){return o?{text:"pending",cls:"status-pending"}:t?t>=200&&t<300?{text:String(t),cls:"status-ok"}:{text:String(t),cls:"status-err"}:{text:"—",cls:""}}function O(t){return new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function q(){const t=h.getLog();if(t.length===0)return'<div class="t4-empty">No API calls yet.<br>Requests made via api.get/post/put/patch/delete will appear here.</div>';let o="";for(const e of t){const{text:n,cls:r}=I(e.status,e.pending),{text:s,cls:i}=P(e.durationMs);o+=`<tr>
235
+ </table></div>`}else t.length===0&&(e='<div class="t4-empty">No routes registered yet.</div>');return e}function w(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function I(t){if(t===void 0)return{text:"...",cls:"status-pending"};const o=t<1?"<1ms":`${Math.round(t)}ms`,e=t<100?"duration fast":t<500?"duration":t<2e3?"duration slow":"duration very-slow";return{text:o,cls:e}}function q(t,o){return o?{text:"pending",cls:"status-pending"}:t?t>=200&&t<300?{text:String(t),cls:"status-ok"}:{text:String(t),cls:"status-err"}:{text:"—",cls:""}}function O(t){return new Date(t).toLocaleTimeString("en-US",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}function U(){const t=h.getLog();if(t.length===0)return'<div class="t4-empty">No API calls yet.<br>Requests made via api.get/post/put/patch/delete will appear here.</div>';let o="";for(const e of t){const{text:n,cls:r}=q(e.status,e.pending),{text:s,cls:i}=I(e.durationMs);o+=`<tr>
236
236
  <td>${O(e.timestamp)}</td>
237
237
  <td><strong>${e.method}</strong></td>
238
- <td>${U(e.url||"(url)")}</td>
238
+ <td>${B(e.url||"(url)")}</td>
239
239
  <td><span class="${r}">${n}</span></td>
240
240
  <td><span class="${i}">${s}</span></td>
241
241
  <td>${e.hasAuth?"Bearer":"—"}</td>
242
242
  </tr>`}return`<table>
243
243
  <thead><tr><th>Time</th><th>Method</th><th>URL</th><th>Status</th><th>Duration</th><th>Auth</th></tr></thead>
244
244
  <tbody>${o}</tbody>
245
- </table>`}function U(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}class B extends HTMLElement{constructor(){super(),this._visible=!0,this._activeTab="signals",this._refreshTimer=null,this._shadow=this.attachShadow({mode:"open"})}connectedCallback(){this._render(),this._startAutoRefresh()}disconnectedCallback(){this._stopAutoRefresh()}toggle(){this._visible=!this._visible,this._render()}show(){this._visible=!0,this._render()}hide(){this._visible=!1,this._render()}_startAutoRefresh(){this._refreshTimer=window.setInterval(()=>{this._visible&&this._renderBody()},1e3)}_stopAutoRefresh(){this._refreshTimer!==null&&(clearInterval(this._refreshTimer),this._refreshTimer=null)}_switchTab(o){this._activeTab=o,this._render()}_getTabContent(){switch(this._activeTab){case"signals":return E();case"components":return j();case"routes":return N();case"api":return q()}}_renderBody(){const o=this._shadow.querySelector(".t4-body");o&&(o.innerHTML=this._getTabContent()),this._updateTabCounts()}_updateTabCounts(){const o={signals:p.count,components:b.count,routes:f.count,api:h.count};for(const[e,n]of Object.entries(o)){const r=this._shadow.querySelector(`[data-tab-count="${e}"]`);r&&(r.textContent=n>0?`(${n})`:"")}}_render(){var n,r;const o=[{id:"signals",label:"Signals"},{id:"components",label:"Components"},{id:"routes",label:"Routes"},{id:"api",label:"API"}];if(!this._visible){this._shadow.innerHTML=`
246
- <style>${y}</style>
245
+ </table>`}function B(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}class G extends HTMLElement{constructor(){super(),this._visible=!0,this._activeTab="signals",this._refreshTimer=null,this._shadow=this.attachShadow({mode:"open"})}connectedCallback(){this._render(),this._startAutoRefresh()}disconnectedCallback(){this._stopAutoRefresh()}toggle(){this._visible=!this._visible,this._render()}show(){this._visible=!0,this._render()}hide(){this._visible=!1,this._render()}_startAutoRefresh(){this._refreshTimer=window.setInterval(()=>{this._visible&&this._renderBody()},1e3)}_stopAutoRefresh(){this._refreshTimer!==null&&(clearInterval(this._refreshTimer),this._refreshTimer=null)}_switchTab(o){this._activeTab=o,this._render()}_getTabContent(){switch(this._activeTab){case"signals":return L();case"components":return H();case"routes":return P();case"api":return U()}}_renderBody(){const o=this._shadow.querySelector(".t4-body");o&&(o.innerHTML=this._getTabContent()),this._updateTabCounts()}_updateTabCounts(){const o={signals:p.count,components:b.count,routes:f.count,api:h.count};for(const[e,n]of Object.entries(o)){const r=this._shadow.querySelector(`[data-tab-count="${e}"]`);r&&(r.textContent=n>0?`(${n})`:"")}}_render(){var n,r;const o=[{id:"signals",label:"Signals"},{id:"components",label:"Components"},{id:"routes",label:"Routes"},{id:"api",label:"API"}];if(!this._visible){this._shadow.innerHTML=`
246
+ <style>${x}</style>
247
247
  <div class="t4-mini" id="t4-mini">
248
248
  <span class="t4-mini-dot"></span>
249
249
  T4 Debug
@@ -251,7 +251,7 @@ tr:hover td { background: rgba(255,255,255,0.02); }
251
251
  `,(n=this._shadow.getElementById("t4-mini"))==null||n.addEventListener("click",()=>this.show());return}const e=o.map(s=>`<button class="t4-tab${this._activeTab===s.id?" active":""}" data-tab="${s.id}">
252
252
  ${s.label}<span class="t4-tab-count" data-tab-count="${s.id}"></span>
253
253
  </button>`).join("");this._shadow.innerHTML=`
254
- <style>${y}</style>
254
+ <style>${x}</style>
255
255
  <div class="t4-debug">
256
256
  <div class="t4-header">
257
257
  <div>
@@ -265,4 +265,4 @@ tr:hover td { background: rgba(255,255,255,0.02); }
265
265
  <div class="t4-tabs">${e}</div>
266
266
  <div class="t4-body">${this._getTabContent()}</div>
267
267
  </div>
268
- `,(r=this._shadow.getElementById("t4-close"))==null||r.addEventListener("click",()=>this.hide());for(const s of this._shadow.querySelectorAll(".t4-tab"))s.addEventListener("click",()=>{this._switchTab(s.dataset.tab)});this._updateTabCounts()}}function G(){typeof customElements<"u"&&!customElements.get("tina4-debug")&&customElements.define("tina4-debug",B)}let u=null,_=!1;function k(){_||(_=!0,m.__setDebugSignalHooks((t,o)=>p.add(t,o),t=>p.onUpdate(t)),m.__setDebugComponentHooks(t=>b.onMount(t),t=>b.onUnmount(t)),$.router.on("change",t=>{f.onNavigate(t)}),x.api.intercept("request",t=>(h.onRequest(t),t)),x.api.intercept("response",t=>(h.onResponse(t),t)),typeof document<"u"&&(G(),u=document.createElement("tina4-debug"),document.body.appendChild(u),document.addEventListener("keydown",t=>{t.ctrlKey&&t.shiftKey&&t.key==="D"&&(t.preventDefault(),u==null||u.toggle())})),console.log("%c[tina4] %cDebug overlay enabled %c(Ctrl+Shift+D to toggle)","color:#00d4ff;font-weight:bold","color:#e0e0e0","color:#888"))}k();exports.enableDebug=k;
268
+ `,(r=this._shadow.getElementById("t4-close"))==null||r.addEventListener("click",()=>this.hide());for(const s of this._shadow.querySelectorAll(".t4-tab"))s.addEventListener("click",()=>{this._switchTab(s.dataset.tab)});this._updateTabCounts()}}function V(){typeof customElements<"u"&&!customElements.get("tina4-debug")&&customElements.define("tina4-debug",G)}let u=null,v=!1;function $(){v||(v=!0,C.__setDebugSignalHooks((t,o)=>p.add(t,o),t=>p.onUpdate(t)),A.__setDebugComponentHooks(t=>b.onMount(t),t=>b.onUnmount(t)),_.router.on("change",t=>{f.onNavigate(t)}),m.api.intercept("request",t=>(h.onRequest(t),t)),m.api.intercept("response",t=>(h.onResponse(t),t)),typeof document<"u"&&(V(),u=document.createElement("tina4-debug"),document.body.appendChild(u),document.addEventListener("keydown",t=>{t.ctrlKey&&t.shiftKey&&t.key==="D"&&(t.preventDefault(),u==null||u.toggle())})),console.log("%c[tina4] %cDebug overlay enabled %c(Ctrl+Shift+D to toggle)","color:#00d4ff;font-weight:bold","color:#e0e0e0","color:#888"))}$();exports.enableDebug=$;
package/dist/debug.es.js CHANGED
@@ -1,4 +1,5 @@
1
- import { _ as T, a as C } from "./component.es.js";
1
+ import { _ as T } from "./signal.es.js";
2
+ import { _ as C } from "./component.es.js";
2
3
  import { _ as S, a as A } from "./index.es.js";
3
4
  import { api as m } from "./api.es.js";
4
5
  const d = [], p = {
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";let f=[],i=null,s="history";const u=[];function m(r,e){const t=[];let o;r==="*"?o=".*":o=r.replace(/\{(\w+)\}/g,(h,n)=>(t.push(n),"([^/]+)"));const a=new RegExp(`^${o}$`);typeof e=="function"?f.push({pattern:r,regex:a,paramNames:t,handler:e}):f.push({pattern:r,regex:a,paramNames:t,handler:e.handler,guard:e.guard})}function d(r,e){if(s==="hash")if(e!=null&&e.replace){const t=new URL(location.href);t.hash="#"+r,history.replaceState(null,"",t.toString()),c()}else location.hash="#"+r;else e!=null&&e.replace?history.replaceState(null,"",r):history.pushState(null,"",r),c()}function c(){if(!i)return;const r=performance.now(),e=s==="hash"?location.hash.slice(1)||"/":location.pathname;for(const t of f){const o=e.match(t.regex);if(!o)continue;const a={};if(t.paramNames.forEach((n,l)=>{a[n]=decodeURIComponent(o[l+1])}),t.guard){const n=t.guard();if(n===!1)return;if(typeof n=="string"){d(n,{replace:!0});return}}const h=t.handler(a);if(i.innerHTML="",h instanceof Promise)h.then(n=>{p(i,n);const l=performance.now()-r;for(const g of u)g({path:e,params:a,pattern:t.pattern,durationMs:l})});else{p(i,h);const n=performance.now()-r;for(const l of u)l({path:e,params:a,pattern:t.pattern,durationMs:n})}return}}function p(r,e){e instanceof DocumentFragment||e instanceof Node?r.appendChild(e):typeof e=="string"?r.innerHTML=e:e!=null&&r.appendChild(document.createTextNode(String(e)))}const y={start(r){if(i=document.querySelector(r.target),!i)throw new Error(`[tina4] Router target '${r.target}' not found in DOM`);s=r.mode??"history",window.addEventListener("popstate",c),s==="hash"&&window.addEventListener("hashchange",c),document.addEventListener("click",e=>{var a;if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;const t=e.target.closest("a[href]");if(!t||t.origin!==location.origin||t.hasAttribute("target")||t.hasAttribute("download")||(a=t.getAttribute("rel"))!=null&&a.includes("external"))return;e.preventDefault();const o=s==="hash"?t.getAttribute("href"):t.pathname;d(o)}),c()},on(r,e){return u.push(e),()=>{const t=u.indexOf(e);t>=0&&u.splice(t,1)}}};function w(){return f.map(r=>({pattern:r.pattern,hasGuard:!!r.guard}))}function R(){f=[],i=null,s="history",u.length=0}exports._getRoutes=w;exports._resetRouter=R;exports.navigate=d;exports.route=m;exports.router=y;
1
+ "use strict";const y=require("./signal.cjs.js");let p=[],a=null,l="history";const c=[];let u=[],w=0;function R(t,e){const r=[];let n;t==="*"?n=".*":n=t.replace(/\{(\w+)\}/g,(f,i)=>(r.push(i),"([^/]+)"));const s=new RegExp(`^${n}$`);typeof e=="function"?p.push({pattern:t,regex:s,paramNames:r,handler:e}):p.push({pattern:t,regex:s,paramNames:r,handler:e.handler,guard:e.guard})}function v(t,e){if(l==="hash")if(e!=null&&e.replace){const r=new URL(location.href);r.hash="#"+t,history.replaceState(null,"",r.toString()),d()}else location.hash="#"+t;else e!=null&&e.replace?history.replaceState(null,"",t):history.pushState(null,"",t),d()}function d(){if(!a)return;const t=performance.now(),e=++w,r=l==="hash"?location.hash.slice(1)||"/":location.pathname;for(const n of p){const s=r.match(n.regex);if(!s)continue;const f={};if(n.paramNames.forEach((o,h)=>{f[o]=decodeURIComponent(s[h+1])}),n.guard){const o=n.guard();if(o===!1)return;if(typeof o=="string"){v(o,{replace:!0});return}}for(const o of u)o();u=[],a.innerHTML="";const i=[];y._setEffectCollector(i);const g=n.handler(f);if(g instanceof Promise)g.then(o=>{if(y._setEffectCollector(null),e!==w){for(const m of i)m();return}E(a,o),u=i;const h=performance.now()-t;for(const m of c)m({path:r,params:f,pattern:n.pattern,durationMs:h})});else{y._setEffectCollector(null),E(a,g),u=i;const o=performance.now()-t;for(const h of c)h({path:r,params:f,pattern:n.pattern,durationMs:o})}return}}function E(t,e){e instanceof DocumentFragment||e instanceof Node?t.appendChild(e):typeof e=="string"?t.innerHTML=e:e!=null&&t.appendChild(document.createTextNode(String(e)))}const _={start(t){if(a=document.querySelector(t.target),!a)throw new Error(`[tina4] Router target '${t.target}' not found in DOM`);l=t.mode??"history",window.addEventListener("popstate",d),l==="hash"&&window.addEventListener("hashchange",d),document.addEventListener("click",e=>{var s;if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;const r=e.target.closest("a[href]");if(!r||r.origin!==location.origin||r.hasAttribute("target")||r.hasAttribute("download")||(s=r.getAttribute("rel"))!=null&&s.includes("external"))return;e.preventDefault();const n=l==="hash"?r.getAttribute("href"):r.pathname;v(n)}),d()},on(t,e){return c.push(e),()=>{const r=c.indexOf(e);r>=0&&c.splice(r,1)}}};function x(){return p.map(t=>({pattern:t.pattern,hasGuard:!!t.guard}))}function S(){for(const t of u)t();u=[],w=0,p=[],a=null,l="history",c.length=0}exports._getRoutes=x;exports._resetRouter=S;exports.navigate=v;exports.route=R;exports.router=_;
package/dist/index.es.js CHANGED
@@ -1,94 +1,105 @@
1
- let f = [], i = null, s = "history";
2
- const u = [];
3
- function g(r, e) {
4
- const t = [];
5
- let o;
6
- r === "*" ? o = ".*" : o = r.replace(/\{(\w+)\}/g, (h, n) => (t.push(n), "([^/]+)"));
7
- const a = new RegExp(`^${o}$`);
8
- typeof e == "function" ? f.push({ pattern: r, regex: a, paramNames: t, handler: e }) : f.push({
9
- pattern: r,
10
- regex: a,
11
- paramNames: t,
1
+ import { a as y } from "./signal.es.js";
2
+ let p = [], a = null, l = "history";
3
+ const c = [];
4
+ let u = [], w = 0;
5
+ function R(t, e) {
6
+ const r = [];
7
+ let n;
8
+ t === "*" ? n = ".*" : n = t.replace(/\{(\w+)\}/g, (f, i) => (r.push(i), "([^/]+)"));
9
+ const s = new RegExp(`^${n}$`);
10
+ typeof e == "function" ? p.push({ pattern: t, regex: s, paramNames: r, handler: e }) : p.push({
11
+ pattern: t,
12
+ regex: s,
13
+ paramNames: r,
12
14
  handler: e.handler,
13
15
  guard: e.guard
14
16
  });
15
17
  }
16
- function p(r, e) {
17
- if (s === "hash")
18
+ function x(t, e) {
19
+ if (l === "hash")
18
20
  if (e != null && e.replace) {
19
- const t = new URL(location.href);
20
- t.hash = "#" + r, history.replaceState(null, "", t.toString()), c();
21
+ const r = new URL(location.href);
22
+ r.hash = "#" + t, history.replaceState(null, "", r.toString()), d();
21
23
  } else
22
- location.hash = "#" + r;
24
+ location.hash = "#" + t;
23
25
  else
24
- e != null && e.replace ? history.replaceState(null, "", r) : history.pushState(null, "", r), c();
26
+ e != null && e.replace ? history.replaceState(null, "", t) : history.pushState(null, "", t), d();
25
27
  }
26
- function c() {
27
- if (!i) return;
28
- const r = performance.now(), e = s === "hash" ? location.hash.slice(1) || "/" : location.pathname;
29
- for (const t of f) {
30
- const o = e.match(t.regex);
31
- if (!o) continue;
32
- const a = {};
33
- if (t.paramNames.forEach((n, l) => {
34
- a[n] = decodeURIComponent(o[l + 1]);
35
- }), t.guard) {
36
- const n = t.guard();
37
- if (n === !1) return;
38
- if (typeof n == "string") {
39
- p(n, { replace: !0 });
28
+ function d() {
29
+ if (!a) return;
30
+ const t = performance.now(), e = ++w, r = l === "hash" ? location.hash.slice(1) || "/" : location.pathname;
31
+ for (const n of p) {
32
+ const s = r.match(n.regex);
33
+ if (!s) continue;
34
+ const f = {};
35
+ if (n.paramNames.forEach((o, h) => {
36
+ f[o] = decodeURIComponent(s[h + 1]);
37
+ }), n.guard) {
38
+ const o = n.guard();
39
+ if (o === !1) return;
40
+ if (typeof o == "string") {
41
+ x(o, { replace: !0 });
40
42
  return;
41
43
  }
42
44
  }
43
- const h = t.handler(a);
44
- if (i.innerHTML = "", h instanceof Promise)
45
- h.then((n) => {
46
- d(i, n);
47
- const l = performance.now() - r;
48
- for (const m of u) m({ path: e, params: a, pattern: t.pattern, durationMs: l });
45
+ for (const o of u) o();
46
+ u = [], a.innerHTML = "";
47
+ const i = [];
48
+ y(i);
49
+ const m = n.handler(f);
50
+ if (m instanceof Promise)
51
+ m.then((o) => {
52
+ if (y(null), e !== w) {
53
+ for (const g of i) g();
54
+ return;
55
+ }
56
+ v(a, o), u = i;
57
+ const h = performance.now() - t;
58
+ for (const g of c) g({ path: r, params: f, pattern: n.pattern, durationMs: h });
49
59
  });
50
60
  else {
51
- d(i, h);
52
- const n = performance.now() - r;
53
- for (const l of u) l({ path: e, params: a, pattern: t.pattern, durationMs: n });
61
+ y(null), v(a, m), u = i;
62
+ const o = performance.now() - t;
63
+ for (const h of c) h({ path: r, params: f, pattern: n.pattern, durationMs: o });
54
64
  }
55
65
  return;
56
66
  }
57
67
  }
58
- function d(r, e) {
59
- e instanceof DocumentFragment || e instanceof Node ? r.appendChild(e) : typeof e == "string" ? r.innerHTML = e : e != null && r.appendChild(document.createTextNode(String(e)));
68
+ function v(t, e) {
69
+ e instanceof DocumentFragment || e instanceof Node ? t.appendChild(e) : typeof e == "string" ? t.innerHTML = e : e != null && t.appendChild(document.createTextNode(String(e)));
60
70
  }
61
- const y = {
62
- start(r) {
63
- if (i = document.querySelector(r.target), !i)
64
- throw new Error(`[tina4] Router target '${r.target}' not found in DOM`);
65
- s = r.mode ?? "history", window.addEventListener("popstate", c), s === "hash" && window.addEventListener("hashchange", c), document.addEventListener("click", (e) => {
66
- var a;
71
+ const S = {
72
+ start(t) {
73
+ if (a = document.querySelector(t.target), !a)
74
+ throw new Error(`[tina4] Router target '${t.target}' not found in DOM`);
75
+ l = t.mode ?? "history", window.addEventListener("popstate", d), l === "hash" && window.addEventListener("hashchange", d), document.addEventListener("click", (e) => {
76
+ var s;
67
77
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
68
- const t = e.target.closest("a[href]");
69
- if (!t || t.origin !== location.origin || t.hasAttribute("target") || t.hasAttribute("download") || (a = t.getAttribute("rel")) != null && a.includes("external")) return;
78
+ const r = e.target.closest("a[href]");
79
+ if (!r || r.origin !== location.origin || r.hasAttribute("target") || r.hasAttribute("download") || (s = r.getAttribute("rel")) != null && s.includes("external")) return;
70
80
  e.preventDefault();
71
- const o = s === "hash" ? t.getAttribute("href") : t.pathname;
72
- p(o);
73
- }), c();
81
+ const n = l === "hash" ? r.getAttribute("href") : r.pathname;
82
+ x(n);
83
+ }), d();
74
84
  },
75
- on(r, e) {
76
- return u.push(e), () => {
77
- const t = u.indexOf(e);
78
- t >= 0 && u.splice(t, 1);
85
+ on(t, e) {
86
+ return c.push(e), () => {
87
+ const r = c.indexOf(e);
88
+ r >= 0 && c.splice(r, 1);
79
89
  };
80
90
  }
81
91
  };
82
- function w() {
83
- return f.map((r) => ({ pattern: r.pattern, hasGuard: !!r.guard }));
92
+ function L() {
93
+ return p.map((t) => ({ pattern: t.pattern, hasGuard: !!t.guard }));
84
94
  }
85
- function x() {
86
- f = [], i = null, s = "history", u.length = 0;
95
+ function b() {
96
+ for (const t of u) t();
97
+ u = [], w = 0, p = [], a = null, l = "history", c.length = 0;
87
98
  }
88
99
  export {
89
- w as _,
90
- y as a,
91
- x as b,
92
- p as n,
93
- g as r
100
+ L as _,
101
+ S as a,
102
+ b,
103
+ x as n,
104
+ R as r
94
105
  };
@@ -0,0 +1 @@
1
+ "use strict";let s=null,f=null;function p(e){f=e}let i=null,a=null;function S(e,t){i=e,a=t}let c=0;const d=new Set;function b(e,t){let n=e;const u=new Set,o={_t4:!0,get value(){return s&&u.add(s),n},set value(r){if(Object.is(r,n))return;const g=n;if(n=r,o._debugInfo&&o._debugInfo.updateCount++,a&&a(o,g,r),c>0)for(const l of u)d.add(l);else for(const l of[...u])l()},_subscribe(r){return u.add(r),()=>{u.delete(r)}},peek(){return n}};return i&&(o._debugInfo={label:t,createdAt:Date.now(),updateCount:0,subs:u},i(o,t)),o}function h(e){const t=b(void 0);return _(()=>{t.value=e()}),{_t4:!0,get value(){return t.value},set value(n){throw new Error("[tina4] computed signals are read-only")},_subscribe(n){return t._subscribe(n)},peek(){return t.peek()}}}function _(e){let t=!1;const n=()=>{if(t)return;const o=s;s=n;try{e()}finally{s=o}};n();const u=()=>{t=!0};return f&&f.push(u),u}function v(e){c++;try{e()}finally{if(c--,c===0){const t=[...d];d.clear();for(const n of t)n()}}}function y(e){return e!==null&&typeof e=="object"&&e._t4===!0}exports.__setDebugSignalHooks=S;exports._setEffectCollector=p;exports.batch=v;exports.computed=h;exports.effect=_;exports.isSignal=y;exports.signal=b;
@@ -0,0 +1,98 @@
1
+ let s = null, a = null;
2
+ function g(e) {
3
+ a = e;
4
+ }
5
+ let f = null, i = null;
6
+ function v(e, t) {
7
+ f = e, i = t;
8
+ }
9
+ let c = 0;
10
+ const d = /* @__PURE__ */ new Set();
11
+ function _(e, t) {
12
+ let n = e;
13
+ const u = /* @__PURE__ */ new Set(), r = {
14
+ _t4: !0,
15
+ get value() {
16
+ return s && u.add(s), n;
17
+ },
18
+ set value(o) {
19
+ if (Object.is(o, n)) return;
20
+ const b = n;
21
+ if (n = o, r._debugInfo && r._debugInfo.updateCount++, i && i(r, b, o), c > 0)
22
+ for (const l of u) d.add(l);
23
+ else
24
+ for (const l of [...u]) l();
25
+ },
26
+ _subscribe(o) {
27
+ return u.add(o), () => {
28
+ u.delete(o);
29
+ };
30
+ },
31
+ peek() {
32
+ return n;
33
+ }
34
+ };
35
+ return f && (r._debugInfo = { label: t, createdAt: Date.now(), updateCount: 0, subs: u }, f(r, t)), r;
36
+ }
37
+ function h(e) {
38
+ const t = _(void 0);
39
+ return p(() => {
40
+ t.value = e();
41
+ }), {
42
+ _t4: !0,
43
+ get value() {
44
+ return t.value;
45
+ },
46
+ set value(n) {
47
+ throw new Error("[tina4] computed signals are read-only");
48
+ },
49
+ _subscribe(n) {
50
+ return t._subscribe(n);
51
+ },
52
+ peek() {
53
+ return t.peek();
54
+ }
55
+ };
56
+ }
57
+ function p(e) {
58
+ let t = !1;
59
+ const n = () => {
60
+ if (t) return;
61
+ const r = s;
62
+ s = n;
63
+ try {
64
+ e();
65
+ } finally {
66
+ s = r;
67
+ }
68
+ };
69
+ n();
70
+ const u = () => {
71
+ t = !0;
72
+ };
73
+ return a && a.push(u), u;
74
+ }
75
+ function y(e) {
76
+ c++;
77
+ try {
78
+ e();
79
+ } finally {
80
+ if (c--, c === 0) {
81
+ const t = [...d];
82
+ d.clear();
83
+ for (const n of t) n();
84
+ }
85
+ }
86
+ }
87
+ function S(e) {
88
+ return e !== null && typeof e == "object" && e._t4 === !0;
89
+ }
90
+ export {
91
+ v as _,
92
+ g as a,
93
+ y as b,
94
+ h as c,
95
+ p as e,
96
+ S as i,
97
+ _ as s
98
+ };
package/dist/tina4.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./component.cjs.js"),r=require("./core.cjs.js"),t=require("./index.cjs.js"),i=require("./api.cjs.js"),a=require("./pwa.cjs.js");exports.Tina4Element=e.Tina4Element;exports.batch=e.batch;exports.computed=e.computed;exports.effect=e.effect;exports.isSignal=e.isSignal;exports.signal=e.signal;exports.html=r.html;exports.navigate=t.navigate;exports.route=t.route;exports.router=t.router;exports.api=i.api;exports.pwa=a.pwa;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signal.cjs.js"),r=require("./core.cjs.js"),i=require("./component.cjs.js"),t=require("./index.cjs.js"),a=require("./api.cjs.js"),n=require("./pwa.cjs.js");exports.batch=e.batch;exports.computed=e.computed;exports.effect=e.effect;exports.isSignal=e.isSignal;exports.signal=e.signal;exports.html=r.html;exports.Tina4Element=i.Tina4Element;exports.navigate=t.navigate;exports.route=t.route;exports.router=t.router;exports.api=a.api;exports.pwa=n.pwa;
package/dist/tina4.es.js CHANGED
@@ -1,19 +1,20 @@
1
- import { T as r, b as o, c as t, e as s, i as m, s as p } from "./component.es.js";
2
- import { html as i } from "./core.es.js";
3
- import { n as x, r as c, a as l } from "./index.es.js";
4
- import { api as u } from "./api.es.js";
5
- import { pwa as h } from "./pwa.es.js";
1
+ import { b as e, c as o, e as t, i as s, s as m } from "./signal.es.js";
2
+ import { html as f } from "./core.es.js";
3
+ import { T as n } from "./component.es.js";
4
+ import { n as c, r as l, a as g } from "./index.es.js";
5
+ import { api as b } from "./api.es.js";
6
+ import { pwa as T } from "./pwa.es.js";
6
7
  export {
7
- r as Tina4Element,
8
- u as api,
9
- o as batch,
10
- t as computed,
11
- s as effect,
12
- i as html,
13
- m as isSignal,
14
- x as navigate,
15
- h as pwa,
16
- c as route,
17
- l as router,
18
- p as signal
8
+ n as Tina4Element,
9
+ b as api,
10
+ e as batch,
11
+ o as computed,
12
+ t as effect,
13
+ f as html,
14
+ s as isSignal,
15
+ c as navigate,
16
+ T as pwa,
17
+ l as route,
18
+ g as router,
19
+ m as signal
19
20
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4js",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Sub-3KB reactive framework — signals, web components, routing, PWA",
5
5
  "type": "module",
6
6
  "main": "dist/tina4.cjs.js",
package/readme.md CHANGED
@@ -193,6 +193,29 @@ pwa.register({
193
193
  });
194
194
  ```
195
195
 
196
+ ### Debug Overlay
197
+
198
+ A built-in debug overlay that shows live signal values, component tree, route history, and API calls.
199
+
200
+ ```ts
201
+ // Always-on (remove for production)
202
+ import 'tina4js/debug';
203
+
204
+ // Dev-only (recommended) — tree-shaken out of production builds
205
+ if (import.meta.env.DEV) import('tina4js/debug');
206
+ ```
207
+
208
+ Once enabled, toggle the overlay with **Ctrl+Shift+D**.
209
+
210
+ The overlay shows four tabs:
211
+
212
+ | Tab | What it shows |
213
+ |-----|---------------|
214
+ | **Signals** | All signals with current value, subscriber count, and update count |
215
+ | **Components** | Mounted `Tina4Element` web components |
216
+ | **Routes** | Navigation history with timing |
217
+ | **API** | Intercepted `api.*` requests and responses |
218
+
196
219
  ---
197
220
 
198
221
  ## Deployment Modes
@@ -215,6 +238,18 @@ npm run build # production build
215
238
  npm run dev # dev server
216
239
  ```
217
240
 
241
+ ## Changelog
242
+
243
+ ### 1.0.2
244
+ - **Fix:** Router now disposes reactive effects when navigating between routes. Previously, signal subscriptions created by `html` templates survived DOM removal via `innerHTML = ''`, causing duplicate renders when revisiting a page.
245
+ - **Fix:** Stale async route handlers are discarded if navigation occurs before they resolve.
246
+
247
+ ### 1.0.1
248
+ - Debug overlay module with signal, component, route, and API inspectors
249
+ - Todo app example and exports map file extension fixes
250
+ - CLI scaffolding tool and TINA4.md AI context file
251
+ - Fetch, PWA, integration, and size tests (102 total)
252
+
218
253
  ## License
219
254
 
220
255
  MIT