tova 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -363,9 +363,9 @@ export function watch(getter, callback, options = {}) {
363
363
  const effect = createEffect(() => {
364
364
  const newValue = getter();
365
365
  if (initialized) {
366
- callback(newValue, oldValue);
366
+ untrack(() => callback(newValue, oldValue));
367
367
  } else if (options.immediate) {
368
- callback(newValue, undefined);
368
+ untrack(() => callback(newValue, undefined));
369
369
  }
370
370
  oldValue = newValue;
371
371
  initialized = true;
@@ -463,13 +463,9 @@ export function ErrorBoundary({ fallback, children, onError, onReset, retry = 0
463
463
  if (onError) onError({ error: e, componentStack: stack });
464
464
  }
465
465
 
466
- pushErrorHandler(handleError);
467
-
468
466
  // Return a reactive wrapper that switches between children and fallback
469
467
  const childContent = children && children.length === 1 ? children[0] : tova_fragment(children || []);
470
468
 
471
- popErrorHandler();
472
-
473
469
  const vnode = {
474
470
  __tova: true,
475
471
  tag: '__dynamic',
@@ -477,6 +473,7 @@ export function ErrorBoundary({ fallback, children, onError, onReset, retry = 0
477
473
  children: [],
478
474
  _fallback: fallback,
479
475
  _componentName: 'ErrorBoundary',
476
+ _errorHandler: handleError, // Active during __dynamic effect render cycle
480
477
  compute: () => {
481
478
  const err = error();
482
479
  if (err) {
@@ -547,6 +544,7 @@ export function Portal({ target, children }) {
547
544
 
548
545
  export function lazy(loader) {
549
546
  let resolved = null;
547
+ let loadError = null;
550
548
  let promise = null;
551
549
 
552
550
  return function LazyWrapper(props) {
@@ -554,28 +552,28 @@ export function lazy(loader) {
554
552
  return resolved(props);
555
553
  }
556
554
 
557
- const [comp, setComp] = createSignal(null);
558
- const [err, setErr] = createSignal(null);
559
-
560
555
  if (!promise) {
561
556
  promise = loader()
562
557
  .then(mod => {
563
558
  resolved = mod.default || mod;
564
- setComp(() => resolved);
565
559
  })
566
- .catch(e => setErr(e));
560
+ .catch(e => { loadError = e; });
567
561
  }
568
562
 
563
+ const [tick, setTick] = createSignal(0);
564
+
565
+ // Trigger re-render when promise settles
566
+ promise.then(() => setTick(1)).catch(() => setTick(1));
567
+
569
568
  return {
570
569
  __tova: true,
571
570
  tag: '__dynamic',
572
571
  props: {},
573
572
  children: [],
574
573
  compute: () => {
575
- const e = err();
576
- if (e) return tova_el('span', { className: 'tova-error' }, [String(e)]);
577
- const c = comp();
578
- if (c) return c(props);
574
+ tick(); // Track for reactivity
575
+ if (loadError) return tova_el('span', { className: 'tova-error' }, [String(loadError)]);
576
+ if (resolved) return resolved(props);
579
577
  // Fallback while loading
580
578
  return props && props.fallback ? props.fallback : null;
581
579
  },
@@ -861,22 +859,36 @@ export function render(vnode) {
861
859
  frag.appendChild(marker);
862
860
 
863
861
  let prevDispose = null;
862
+ const errHandler = vnode._errorHandler || null;
864
863
  createEffect(() => {
865
- const inner = vnode.compute();
866
- const parent = marker.parentNode;
867
- const ref = nextSiblingAfterMarker(marker);
864
+ if (errHandler) pushErrorHandler(errHandler);
865
+ try {
866
+ const inner = vnode.compute();
867
+ const parent = marker.parentNode;
868
+ const ref = nextSiblingAfterMarker(marker);
869
+
870
+ if (prevDispose) {
871
+ prevDispose();
872
+ prevDispose = null;
873
+ }
874
+ clearMarkerContent(marker);
868
875
 
869
- if (prevDispose) {
870
- prevDispose();
871
- prevDispose = null;
876
+ createRoot((dispose) => {
877
+ prevDispose = dispose;
878
+ const rendered = render(inner);
879
+ marker.__tovaNodes = insertRendered(parent, rendered, ref, marker);
880
+ });
881
+ } catch (e) {
882
+ if (errHandler) {
883
+ errHandler(e);
884
+ } else if (currentErrorHandler) {
885
+ currentErrorHandler(e);
886
+ } else {
887
+ console.error('Uncaught error during render:', e);
888
+ }
889
+ } finally {
890
+ if (errHandler) popErrorHandler();
872
891
  }
873
- clearMarkerContent(marker);
874
-
875
- createRoot((dispose) => {
876
- prevDispose = dispose;
877
- const rendered = render(inner);
878
- marker.__tovaNodes = insertRendered(parent, rendered, ref, marker);
879
- });
880
892
  });
881
893
 
882
894
  return frag;
@@ -965,6 +977,14 @@ function applyPropValue(el, key, val) {
965
977
  } else if (key === 'disabled' || key === 'readOnly' || key === 'hidden') {
966
978
  el[key] = !!val;
967
979
  } else if (key === 'style' && typeof val === 'object') {
980
+ // Clear old properties not present in new style object
981
+ for (let i = el.style.length - 1; i >= 0; i--) {
982
+ const prop = el.style[i];
983
+ const camel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
984
+ if (!(prop in val) && !(camel in val)) {
985
+ el.style.removeProperty(prop);
986
+ }
987
+ }
968
988
  Object.assign(el.style, val);
969
989
  } else {
970
990
  const s = val == null ? '' : String(val);
@@ -1150,11 +1170,7 @@ function patchPositionalInMarker(marker, newChildren) {
1150
1170
  if (oldNodes[i].parentNode === parent) parent.removeChild(oldNodes[i]);
1151
1171
  }
1152
1172
 
1153
- marker.__tovaNodes = oldNodes.slice(0, Math.max(newCount, oldCount > newCount ? newCount : oldNodes.length));
1154
- // Simplify: rebuild __tovaNodes from what should remain
1155
- if (newCount <= oldCount) {
1156
- marker.__tovaNodes = oldNodes.slice(0, newCount);
1157
- }
1173
+ marker.__tovaNodes = oldNodes.slice(0, newCount);
1158
1174
  }
1159
1175
 
1160
1176
  // Keyed reconciliation for children of an element (not marker-based)
@@ -25,8 +25,8 @@ export const BUILTIN_FUNCTIONS = {
25
25
  sorted: `function sorted(a, k) { const c = [...a]; if (k) c.sort((x, y) => { const kx = k(x), ky = k(y); return kx < ky ? -1 : kx > ky ? 1 : 0; }); else c.sort((x, y) => x < y ? -1 : x > y ? 1 : 0); return c; }`,
26
26
  reversed: `function reversed(a) { return [...a].reverse(); }`,
27
27
  zip: `function zip(...as) { if (as.length === 0) return []; const m = Math.min(...as.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) r.push(as.map(a => a[i])); return r; }`,
28
- min: `function min(a) { return a.length === 0 ? null : Math.min(...a); }`,
29
- max: `function max(a) { return a.length === 0 ? null : Math.max(...a); }`,
28
+ min: `function min(a) { if (a.length === 0) return null; let m = a[0]; for (let i = 1; i < a.length; i++) if (a[i] < m) m = a[i]; return m; }`,
29
+ max: `function max(a) { if (a.length === 0) return null; let m = a[0]; for (let i = 1; i < a.length; i++) if (a[i] > m) m = a[i]; return m; }`,
30
30
  type_of: `function type_of(v) { if (v === null) return 'Nil'; if (Array.isArray(v)) return 'List'; if (v?.__tag) return v.__tag; const t = typeof v; switch(t) { case 'number': return Number.isInteger(v) ? 'Int' : 'Float'; case 'string': return 'String'; case 'boolean': return 'Bool'; case 'function': return 'Function'; case 'object': return 'Object'; default: return 'Unknown'; } }`,
31
31
  filter: `function filter(arr, fn) { return arr.filter(fn); }`,
32
32
  map: `function map(arr, fn) { return arr.map(fn); }`,
@@ -163,12 +163,12 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
163
163
  agg_count: `function agg_count(fn) { if (!fn) return (rows) => rows.length; return (rows) => rows.filter(fn).length; }`,
164
164
  agg_mean: `function agg_mean(fn) { return (rows) => { if (rows.length === 0) return 0; return rows.reduce((a, r) => a + (typeof fn === 'function' ? fn(r) : r[fn]), 0) / rows.length; }; }`,
165
165
  agg_median: `function agg_median(fn) { return (rows) => { if (rows.length === 0) return 0; const vs = rows.map(r => typeof fn === 'function' ? fn(r) : r[fn]).sort((a, b) => a - b); const m = Math.floor(vs.length / 2); return vs.length % 2 !== 0 ? vs[m] : (vs[m - 1] + vs[m]) / 2; }; }`,
166
- agg_min: `function agg_min(fn) { return (rows) => rows.length === 0 ? null : Math.min(...rows.map(r => typeof fn === 'function' ? fn(r) : r[fn])); }`,
167
- agg_max: `function agg_max(fn) { return (rows) => rows.length === 0 ? null : Math.max(...rows.map(r => typeof fn === 'function' ? fn(r) : r[fn])); }`,
166
+ agg_min: `function agg_min(fn) { return (rows) => { if (rows.length === 0) return null; let m = typeof fn === 'function' ? fn(rows[0]) : rows[0][fn]; for (let i = 1; i < rows.length; i++) { const v = typeof fn === 'function' ? fn(rows[i]) : rows[i][fn]; if (v < m) m = v; } return m; }; }`,
167
+ agg_max: `function agg_max(fn) { return (rows) => { if (rows.length === 0) return null; let m = typeof fn === 'function' ? fn(rows[0]) : rows[0][fn]; for (let i = 1; i < rows.length; i++) { const v = typeof fn === 'function' ? fn(rows[i]) : rows[i][fn]; if (v > m) m = v; } return m; }; }`,
168
168
 
169
169
  // ── Data exploration ────────────────────────────────
170
170
  peek: `function peek(table, opts) { const o = typeof opts === 'object' ? opts : {}; console.log(table._format ? table._format(o.n || 10, o.title) : String(table)); return table; }`,
171
- describe: `function describe(table) { const stats = []; for (const col of table._columns) { const vals = table._rows.map(r => r[col]).filter(v => v != null); const st = { Column: col, Type: 'Unknown', 'Non-Null': vals.length }; if (vals.length > 0) { const s = vals[0]; if (typeof s === 'number') { st.Type = Number.isInteger(s) ? 'Int' : 'Float'; st.Mean = vals.reduce((a, b) => a + b, 0) / vals.length; st.Min = Math.min(...vals); st.Max = Math.max(...vals); } else if (typeof s === 'string') { st.Type = 'String'; st.Unique = new Set(vals).size; } else if (typeof s === 'boolean') { st.Type = 'Bool'; } } stats.push(st); } const dt = Table(stats); console.log(dt._format(100, 'describe()')); return dt; }`,
171
+ describe: `function describe(table) { const stats = []; for (const col of table._columns) { const vals = table._rows.map(r => r[col]).filter(v => v != null); const st = { Column: col, Type: 'Unknown', 'Non-Null': vals.length }; if (vals.length > 0) { const s = vals[0]; if (typeof s === 'number') { st.Type = Number.isInteger(s) ? 'Int' : 'Float'; st.Mean = vals.reduce((a, b) => a + b, 0) / vals.length; let mn = vals[0], mx = vals[0]; for (let i = 1; i < vals.length; i++) { if (vals[i] < mn) mn = vals[i]; if (vals[i] > mx) mx = vals[i]; } st.Min = mn; st.Max = mx; } else if (typeof s === 'string') { st.Type = 'String'; st.Unique = new Set(vals).size; } else if (typeof s === 'boolean') { st.Type = 'Bool'; } } stats.push(st); } const dt = Table(stats); console.log(dt._format(100, 'describe()')); return dt; }`,
172
172
  schema_of: `function schema_of(table) { const sc = {}; if (table._rows.length === 0) { for (const c of table._columns) sc[c] = 'Unknown'; } else { const s = table._rows[0]; for (const c of table._columns) { const v = s[c]; if (v == null) sc[c] = 'Nil'; else if (typeof v === 'number') sc[c] = Number.isInteger(v) ? 'Int' : 'Float'; else if (typeof v === 'string') sc[c] = 'String'; else if (typeof v === 'boolean') sc[c] = 'Bool'; else if (Array.isArray(v)) sc[c] = 'Array'; else sc[c] = 'Object'; } } console.log('Schema:'); for (const [c, t] of Object.entries(sc)) console.log(' ' + c + ': ' + t); return sc; }`,
173
173
 
174
174
  // ── Data cleaning ───────────────────────────────────
@@ -302,7 +302,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
302
302
  // ── Math (new) ─────────────────────────────────────────
303
303
  hypot: `function hypot(a, b) { return Math.hypot(a, b); }`,
304
304
  lerp: `function lerp(a, b, t) { return a + (b - a) * t; }`,
305
- divmod: `function divmod(a, b) { return [Math.floor(a / b), a % b]; }`,
305
+ divmod: `function divmod(a, b) { const q = Math.floor(a / b); return [q, a - q * b]; }`,
306
306
  avg: `function avg(arr) { return arr.length === 0 ? 0 : arr.reduce((a, b) => a + b, 0) / arr.length; }`,
307
307
 
308
308
  // ── Date/Time (new) ────────────────────────────────────
@@ -374,7 +374,7 @@ Table.prototype = { get rows() { return this._rows.length; }, get columns() { re
374
374
  combinations: `function combinations(arr, r) { const result = []; const combo = []; function gen(start, depth) { if (depth === r) { result.push([...combo]); return; } for (let i = start; i < arr.length; i++) { combo.push(arr[i]); gen(i + 1, depth + 1); combo.pop(); } } gen(0, 0); return result; }`,
375
375
  permutations: `function permutations(arr, r) { const n = r === undefined ? arr.length : r; const result = []; const perm = []; const used = new Array(arr.length).fill(false); function gen() { if (perm.length === n) { result.push([...perm]); return; } for (let i = 0; i < arr.length; i++) { if (!used[i]) { used[i] = true; perm.push(arr[i]); gen(); perm.pop(); used[i] = false; } } } gen(); return result; }`,
376
376
  intersperse: `function intersperse(arr, sep) { if (arr.length <= 1) return [...arr]; const r = [arr[0]]; for (let i = 1; i < arr.length; i++) { r.push(sep, arr[i]); } return r; }`,
377
- interleave: `function interleave(...arrs) { const m = Math.max(...arrs.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) { for (const a of arrs) { if (i < a.length) r.push(a[i]); } } return r; }`,
377
+ interleave: `function interleave(...arrs) { if (arrs.length === 0) return []; const m = Math.max(...arrs.map(a => a.length)); const r = []; for (let i = 0; i < m; i++) { for (const a of arrs) { if (i < a.length) r.push(a[i]); } } return r; }`,
378
378
  repeat_value: `function repeat_value(val, n) { return Array(n).fill(val); }`,
379
379
 
380
380
  // ── Array Utilities ────────────────────────────────────
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/embed-runtime.js — do not edit
2
- export const VERSION = "0.2.8";
2
+ export const VERSION = "0.3.0";