rip-lang 3.13.24 → 3.13.26

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/README.md CHANGED
@@ -9,9 +9,9 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.24-blue.svg" alt="Version"></a>
12
+ <a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.13.26-blue.svg" alt="Version"></a>
13
13
  <a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
14
- <a href="#"><img src="https://img.shields.io/badge/tests-1%2C265%2F1%2C265-brightgreen.svg" alt="Tests"></a>
14
+ <a href="#"><img src="https://img.shields.io/badge/tests-1%2C300%2F1%2C300-brightgreen.svg" alt="Tests"></a>
15
15
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
16
16
  </p>
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.13.24",
3
+ "version": "3.13.26",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/compiler.js CHANGED
@@ -688,9 +688,9 @@ export class CodeGenerator {
688
688
 
689
689
  if (this.usesTemplates && !skip) {
690
690
  if (skipRT) {
691
- code += 'var { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component } = globalThis.__ripComponent;\n';
691
+ code += 'var { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __lis, __reconcile, __transition, __handleComponentError, __Component } = globalThis.__ripComponent;\n';
692
692
  } else if (typeof globalThis !== 'undefined' && globalThis.__ripComponent) {
693
- code += 'const { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component } = globalThis.__ripComponent;\n';
693
+ code += 'const { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __lis, __reconcile, __transition, __handleComponentError, __Component } = globalThis.__ripComponent;\n';
694
694
  } else {
695
695
  code += this.getComponentRuntime();
696
696
  }
package/src/components.js CHANGED
@@ -55,7 +55,7 @@ const TEMPLATE_TAGS = new Set([...HTML_TAGS, ...SVG_TAGS]);
55
55
  const BIND_PREFIX = '__bind_';
56
56
  const BIND_SUFFIX = '__';
57
57
 
58
- const LIFECYCLE_HOOKS = new Set(['beforeMount', 'mounted', 'updated', 'beforeUnmount', 'unmounted']);
58
+ const LIFECYCLE_HOOKS = new Set(['beforeMount', 'mounted', 'updated', 'beforeUnmount', 'unmounted', 'onError']);
59
59
  const BOOLEAN_ATTRS = new Set([
60
60
  'disabled', 'hidden', 'readonly', 'required', 'checked', 'selected',
61
61
  'autofocus', 'autoplay', 'controls', 'loop', 'muted', 'multiple',
@@ -240,6 +240,19 @@ export function installComponentSupport(CodeGenerator, Lexer) {
240
240
  // Only process if we're inside a render block
241
241
  if (!inRender) return 1;
242
242
 
243
+ // ─────────────────────────────────────────────────────────────────────
244
+ // Transition modifier
245
+ // div ~fade → div __transition__: "fade"
246
+ // ─────────────────────────────────────────────────────────────────────
247
+ if (tag === 'UNARY_MATH' && token[1] === '~' && nextToken && nextToken[0] === 'IDENTIFIER') {
248
+ token[0] = 'PROPERTY';
249
+ token[1] = '__transition__';
250
+ let colonToken = gen(':', ':', token);
251
+ let valueToken = gen('STRING', `"${nextToken[1]}"`, nextToken);
252
+ tokens.splice(i + 1, 1, colonToken, valueToken);
253
+ return 1;
254
+ }
255
+
243
256
  // ─────────────────────────────────────────────────────────────────────
244
257
  // Hyphenated attributes
245
258
  // data-lucide: "search" → "data-lucide": "search"
@@ -719,11 +732,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
719
732
  // --- Lifecycle hooks ---
720
733
  for (const { name, value } of lifecycleHooks) {
721
734
  if (Array.isArray(value) && (value[0] === '->' || value[0] === '=>')) {
722
- const [, , hookBody] = value;
735
+ const [, params, hookBody] = value;
736
+ const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
723
737
  const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
724
738
  const isAsync = this.containsAwait(hookBody);
725
- const bodyCode = this.generateFunctionBody(transformed, []);
726
- lines.push(` ${isAsync ? 'async ' : ''}${name}() ${bodyCode}`);
739
+ const bodyCode = this.generateFunctionBody(transformed, params || []);
740
+ lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
727
741
  }
728
742
  }
729
743
 
@@ -1188,6 +1202,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1188
1202
  continue;
1189
1203
  }
1190
1204
 
1205
+ // Transition: __transition__: "fade" → this._t = "fade" (on block, not DOM)
1206
+ if (key === '__transition__') {
1207
+ const transName = String(value).replace(/^["']|["']$/g, '');
1208
+ this._createLines.push(`this._t = "${transName}";`);
1209
+ continue;
1210
+ }
1211
+
1191
1212
  // Element ref: ref: "name" → this.name = element
1192
1213
  if (key === 'ref') {
1193
1214
  const refName = String(value).replace(/^["']|["']$/g, '');
@@ -1330,7 +1351,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1330
1351
  setupLines.push(` if (want === showing) return;`);
1331
1352
  setupLines.push(``);
1332
1353
  setupLines.push(` if (currentBlock) {`);
1333
- setupLines.push(` currentBlock.d(true);`);
1354
+ setupLines.push(` const leaving = currentBlock;`);
1355
+ setupLines.push(` if (leaving._t) { __transition(leaving._first, leaving._t, 'leave', () => leaving.d(true)); }`);
1356
+ setupLines.push(` else { leaving.d(true); }`);
1334
1357
  setupLines.push(` currentBlock = null;`);
1335
1358
  setupLines.push(` }`);
1336
1359
  setupLines.push(` showing = want;`);
@@ -1340,6 +1363,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1340
1363
  setupLines.push(` currentBlock.c();`);
1341
1364
  setupLines.push(` if (anchor.parentNode) currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1342
1365
  setupLines.push(` currentBlock.p(${this._self}${outerExtra});`);
1366
+ setupLines.push(` if (currentBlock._t) __transition(currentBlock._first, currentBlock._t, 'enter');`);
1343
1367
  setupLines.push(` }`);
1344
1368
  if (elseBlock) {
1345
1369
  setupLines.push(` if (want === 'else') {`);
@@ -1347,6 +1371,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1347
1371
  setupLines.push(` currentBlock.c();`);
1348
1372
  setupLines.push(` if (anchor.parentNode) currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1349
1373
  setupLines.push(` currentBlock.p(${this._self}${outerExtra});`);
1374
+ setupLines.push(` if (currentBlock._t) __transition(currentBlock._first, currentBlock._t, 'enter');`);
1350
1375
  setupLines.push(` }`);
1351
1376
  }
1352
1377
  setupLines.push(` ${effClose}`);
@@ -1386,7 +1411,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1386
1411
  // emitBlockFactory — shared factory generation for conditionals and loops
1387
1412
  // --------------------------------------------------------------------------
1388
1413
 
1389
- proto.emitBlockFactory = function(blockName, params, rootVar, createLines, setupLines, factoryVars) {
1414
+ proto.emitBlockFactory = function(blockName, params, rootVar, createLines, setupLines, factoryVars, isStatic) {
1390
1415
  const factoryLines = [];
1391
1416
  factoryLines.push(`function ${blockName}(${params}) {`);
1392
1417
 
@@ -1401,13 +1426,20 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1401
1426
 
1402
1427
  factoryLines.push(` return {`);
1403
1428
 
1429
+ if (isStatic) {
1430
+ factoryLines.push(` _s: true,`);
1431
+ }
1432
+
1433
+ const fragChildren = this._fragChildren.get(rootVar);
1434
+ const firstNode = fragChildren ? fragChildren[0] : rootVar;
1435
+
1404
1436
  factoryLines.push(` c() {`);
1405
1437
  for (const line of createLines) {
1406
1438
  factoryLines.push(` ${line}`);
1407
1439
  }
1440
+ factoryLines.push(` this._first = ${firstNode};`);
1408
1441
  factoryLines.push(` },`);
1409
1442
 
1410
- const fragChildren = this._fragChildren.get(rootVar);
1411
1443
  factoryLines.push(` m(target, anchor) {`);
1412
1444
  if (fragChildren) {
1413
1445
  for (const child of fragChildren) {
@@ -1504,41 +1536,26 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1504
1536
 
1505
1537
  [this._createLines, this._setupLines, this._factoryMode, this._factoryVars] = saved;
1506
1538
 
1539
+ const isStatic = itemSetupLines.length === 0;
1507
1540
  const loopParams = `ctx, ${itemVar}, ${indexVar}${outerExtra}`;
1508
- this.emitBlockFactory(blockName, loopParams, itemNode, itemCreateLines, itemSetupLines, itemFactoryVars);
1541
+ this.emitBlockFactory(blockName, loopParams, itemNode, itemCreateLines, itemSetupLines, itemFactoryVars, isStatic);
1542
+
1543
+ // Build key function argument (null = use item as key)
1544
+ const hasCustomKey = keyExpr !== itemVar;
1545
+ const keyFnCode = hasCustomKey ? `(${itemVar}, ${indexVar}) => ${keyExpr}` : 'null';
1546
+
1547
+ // Build outer vars argument list for nested loops
1548
+ const outerArgs = outerParams ? `, ${outerParams}` : '';
1509
1549
 
1510
1550
  // Generate reconciliation code in _setup()
1511
1551
  const setupLines = [];
1512
1552
  setupLines.push(`// Loop: ${blockName}`);
1513
1553
  setupLines.push(`{`);
1514
- setupLines.push(` const __anchor = ${anchorVar};`);
1515
- setupLines.push(` const __map = new Map();`);
1554
+ setupLines.push(` const __s = { blocks: [], keys: [] };`);
1516
1555
  const effOpen = this._factoryMode ? 'disposers.push(__effect(() => {' : '__effect(() => {';
1517
1556
  const effClose = this._factoryMode ? '}));' : '});';
1518
1557
  setupLines.push(` ${effOpen}`);
1519
- setupLines.push(` const __items = ${collectionCode};`);
1520
- setupLines.push(` const __parent = __anchor.parentNode;`);
1521
- setupLines.push(` const __newMap = new Map();`);
1522
- setupLines.push(``);
1523
- setupLines.push(` for (let ${indexVar} = 0; ${indexVar} < __items.length; ${indexVar}++) {`);
1524
- setupLines.push(` const ${itemVar} = __items[${indexVar}];`);
1525
- setupLines.push(` const __key = ${keyExpr};`);
1526
- setupLines.push(` let __block = __map.get(__key);`);
1527
- setupLines.push(` if (!__block) {`);
1528
- setupLines.push(` __block = ${blockName}(${this._self}, ${itemVar}, ${indexVar}${outerExtra});`);
1529
- setupLines.push(` __block.c();`);
1530
- setupLines.push(` }`);
1531
- setupLines.push(` __block.m(__parent, __anchor);`);
1532
- setupLines.push(` __block.p(${this._self}, ${itemVar}, ${indexVar}${outerExtra});`);
1533
- setupLines.push(` __newMap.set(__key, __block);`);
1534
- setupLines.push(` }`);
1535
- setupLines.push(``);
1536
- setupLines.push(` for (const [__k, __b] of __map) {`);
1537
- setupLines.push(` if (!__newMap.has(__k)) __b.d(true);`);
1538
- setupLines.push(` }`);
1539
- setupLines.push(``);
1540
- setupLines.push(` __map.clear();`);
1541
- setupLines.push(` for (const [__k, __v] of __newMap) __map.set(__k, __v);`);
1558
+ setupLines.push(` __reconcile(${anchorVar}, __s, ${collectionCode}, ${this._self}, ${blockName}, ${keyFnCode}${outerArgs});`);
1542
1559
  setupLines.push(` ${effClose}`);
1543
1560
  setupLines.push(`}`);
1544
1561
 
@@ -1561,8 +1578,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1561
1578
  this._createLines.push(`${elVar} = ${instVar}._create();`);
1562
1579
  this._createLines.push(`(${s}._children || (${s}._children = [])).push(${instVar});`);
1563
1580
 
1564
- this._setupLines.push(`if (${instVar}._setup) ${instVar}._setup();`);
1565
- this._setupLines.push(`if (${instVar}.mounted) ${instVar}.mounted();`);
1581
+ this._setupLines.push(`try { if (${instVar}._setup) ${instVar}._setup(); if (${instVar}.mounted) ${instVar}.mounted(); } catch (__e) { __handleComponentError(__e, ${instVar}); }`);
1566
1582
 
1567
1583
  for (const { key, valueCode } of reactiveProps) {
1568
1584
  this._pushEffect(`if (${instVar}.${key}) ${instVar}.${key}.value = ${valueCode};`);
@@ -1784,21 +1800,203 @@ function __clsx(...args) {
1784
1800
  return args.filter(Boolean).join(' ');
1785
1801
  }
1786
1802
 
1803
+ function __lis(arr) {
1804
+ const n = arr.length;
1805
+ if (n === 0) return [];
1806
+ const tails = [], indices = [], prev = new Array(n).fill(-1);
1807
+ for (let i = 0; i < n; i++) {
1808
+ if (arr[i] === -1) continue;
1809
+ let lo = 0, hi = tails.length;
1810
+ while (lo < hi) {
1811
+ const mid = (lo + hi) >> 1;
1812
+ if (tails[mid] < arr[i]) lo = mid + 1; else hi = mid;
1813
+ }
1814
+ tails[lo] = arr[i];
1815
+ indices[lo] = i;
1816
+ if (lo > 0) prev[i] = indices[lo - 1];
1817
+ }
1818
+ const result = [];
1819
+ let k = indices[tails.length - 1];
1820
+ for (let i = tails.length - 1; i >= 0; i--) { result.push(k); k = prev[k]; }
1821
+ result.reverse();
1822
+ return result;
1823
+ }
1824
+
1825
+ function __reconcile(anchor, state, items, ctx, factory, keyFn, ...outer) {
1826
+ const parent = anchor.parentNode;
1827
+ if (!parent) return;
1828
+
1829
+ const oldKeys = state.keys;
1830
+ const oldBlocks = state.blocks;
1831
+ const oldLen = oldKeys.length;
1832
+ const newLen = items.length;
1833
+ const newBlocks = new Array(newLen);
1834
+ const hasKeyFn = keyFn != null;
1835
+ const newKeys = hasKeyFn ? items.map((item, i) => keyFn(item, i)) : items;
1836
+
1837
+ // Phase 0: first render — batch create via DocumentFragment
1838
+ if (oldLen === 0) {
1839
+ if (newLen > 0) {
1840
+ const frag = document.createDocumentFragment();
1841
+ for (let i = 0; i < newLen; i++) {
1842
+ const block = factory(ctx, items[i], i, ...outer);
1843
+ block.c();
1844
+ block.m(frag, null);
1845
+ if (!block._s) block.p(ctx, items[i], i, ...outer);
1846
+ newBlocks[i] = block;
1847
+ }
1848
+ parent.insertBefore(frag, anchor);
1849
+ }
1850
+ state.keys = hasKeyFn ? newKeys : items.slice();
1851
+ state.blocks = newBlocks;
1852
+ return;
1853
+ }
1854
+
1855
+ // Phase 1: prefix scan — skip p() (item+index identical, effects already live)
1856
+ let start = 0;
1857
+ const minLen = oldLen < newLen ? oldLen : newLen;
1858
+ while (start < minLen && oldKeys[start] === newKeys[start]) {
1859
+ newBlocks[start] = oldBlocks[start];
1860
+ start++;
1861
+ }
1862
+
1863
+ // Phase 2: suffix scan — call p() (index may differ)
1864
+ let oldEnd = oldLen - 1;
1865
+ let newEnd = newLen - 1;
1866
+ while (oldEnd >= start && newEnd >= start && oldKeys[oldEnd] === newKeys[newEnd]) {
1867
+ const block = oldBlocks[oldEnd];
1868
+ if (!block._s) block.p(ctx, items[newEnd], newEnd, ...outer);
1869
+ newBlocks[newEnd] = block;
1870
+ oldEnd--;
1871
+ newEnd--;
1872
+ }
1873
+
1874
+ // Remove old blocks in the middle that aren't in the new set
1875
+ if (start > newEnd) {
1876
+ for (let i = start; i <= oldEnd; i++) oldBlocks[i].d(true);
1877
+ } else if (start > oldEnd) {
1878
+ // Phase 3a: pure insertion — batch via DocumentFragment
1879
+ const next = newEnd + 1 < newLen ? newBlocks[newEnd + 1]._first : anchor;
1880
+ const frag = document.createDocumentFragment();
1881
+ for (let i = start; i <= newEnd; i++) {
1882
+ const block = factory(ctx, items[i], i, ...outer);
1883
+ block.c();
1884
+ block.m(frag, null);
1885
+ if (!block._s) block.p(ctx, items[i], i, ...outer);
1886
+ newBlocks[i] = block;
1887
+ }
1888
+ parent.insertBefore(frag, next);
1889
+ } else {
1890
+ // Phase 4: general case — temp Map + LIS
1891
+ const oldKeyIdx = new Map();
1892
+ for (let i = start; i <= oldEnd; i++) oldKeyIdx.set(oldKeys[i], i);
1893
+
1894
+ const seq = new Array(newEnd - start + 1);
1895
+ for (let i = start; i <= newEnd; i++) {
1896
+ const key = newKeys[i];
1897
+ const oldIdx = oldKeyIdx.get(key);
1898
+ if (oldIdx !== undefined) {
1899
+ seq[i - start] = oldIdx - start;
1900
+ const block = oldBlocks[oldIdx];
1901
+ if (!block._s) block.p(ctx, items[i], i, ...outer);
1902
+ newBlocks[i] = block;
1903
+ oldKeyIdx.delete(key);
1904
+ } else {
1905
+ seq[i - start] = -1;
1906
+ const block = factory(ctx, items[i], i, ...outer);
1907
+ block.c();
1908
+ if (!block._s) block.p(ctx, items[i], i, ...outer);
1909
+ newBlocks[i] = block;
1910
+ }
1911
+ }
1912
+
1913
+ for (const idx of oldKeyIdx.values()) oldBlocks[idx].d(true);
1914
+
1915
+ const lis = __lis(seq);
1916
+ const lisSet = new Set(lis);
1917
+ let next = newEnd + 1 < newLen ? newBlocks[newEnd + 1]._first : anchor;
1918
+ for (let i = newEnd; i >= start; i--) {
1919
+ const block = newBlocks[i];
1920
+ if (!lisSet.has(i - start)) {
1921
+ block.m(parent, next);
1922
+ }
1923
+ next = block._first;
1924
+ }
1925
+ }
1926
+
1927
+ state.keys = hasKeyFn ? newKeys : items.slice();
1928
+ state.blocks = newBlocks;
1929
+ }
1930
+
1931
+ let __cssInjected = false;
1932
+ function __transitionCSS() {
1933
+ if (__cssInjected) return;
1934
+ __cssInjected = true;
1935
+ const s = document.createElement('style');
1936
+ s.textContent = [
1937
+ '.fade-enter-active,.fade-leave-active{transition:opacity .2s ease}',
1938
+ '.fade-enter-from,.fade-leave-to{opacity:0}',
1939
+ '.slide-enter-active,.slide-leave-active{transition:opacity .2s ease,transform .2s ease}',
1940
+ '.slide-enter-from{opacity:0;transform:translateY(-8px)}',
1941
+ '.slide-leave-to{opacity:0;transform:translateY(8px)}',
1942
+ '.scale-enter-active,.scale-leave-active{transition:opacity .2s ease,transform .2s ease}',
1943
+ '.scale-enter-from,.scale-leave-to{opacity:0;transform:scale(.95)}',
1944
+ '.blur-enter-active,.blur-leave-active{transition:opacity .2s ease,filter .2s ease}',
1945
+ '.blur-enter-from,.blur-leave-to{opacity:0;filter:blur(4px)}',
1946
+ '.fly-enter-active,.fly-leave-active{transition:opacity .2s ease,transform .2s ease}',
1947
+ '.fly-enter-from{opacity:0;transform:translateY(-20px)}',
1948
+ '.fly-leave-to{opacity:0;transform:translateY(20px)}',
1949
+ ].join('');
1950
+ document.head.appendChild(s);
1951
+ }
1952
+
1953
+ function __transition(el, name, dir, done) {
1954
+ __transitionCSS();
1955
+ const cl = el.classList;
1956
+ const from = name + '-' + dir + '-from';
1957
+ const active = name + '-' + dir + '-active';
1958
+ const to = name + '-' + dir + '-to';
1959
+ cl.add(from, active);
1960
+ requestAnimationFrame(() => {
1961
+ requestAnimationFrame(() => {
1962
+ cl.remove(from);
1963
+ cl.add(to);
1964
+ const end = () => { cl.remove(active, to); if (done) done(); };
1965
+ el.addEventListener('transitionend', end, { once: true });
1966
+ });
1967
+ });
1968
+ }
1969
+
1970
+ function __handleComponentError(error, component) {
1971
+ let current = component;
1972
+ while (current) {
1973
+ if (current.onError) {
1974
+ try { current.onError(error, component); return; } catch (_) {}
1975
+ }
1976
+ current = current._parent;
1977
+ }
1978
+ throw error;
1979
+ }
1980
+
1787
1981
  class __Component {
1788
1982
  constructor(props = {}) {
1789
1983
  Object.assign(this, props);
1790
1984
  const prev = __pushComponent(this);
1791
- this._init(props);
1985
+ try { this._init(props); } catch (e) { __popComponent(prev); __handleComponentError(e, this); return; }
1792
1986
  __popComponent(prev);
1793
1987
  }
1794
1988
  _init() {}
1795
1989
  mount(target) {
1796
1990
  if (typeof target === "string") target = document.querySelector(target);
1797
1991
  this._target = target;
1798
- this._root = this._create();
1799
- target.appendChild(this._root);
1800
- if (this._setup) this._setup();
1801
- if (this.mounted) this.mounted();
1992
+ try {
1993
+ this._root = this._create();
1994
+ target.appendChild(this._root);
1995
+ if (this._setup) this._setup();
1996
+ if (this.mounted) this.mounted();
1997
+ } catch (error) {
1998
+ __handleComponentError(error, this);
1999
+ }
1802
2000
  return this;
1803
2001
  }
1804
2002
  unmount() {
@@ -1819,7 +2017,7 @@ class __Component {
1819
2017
 
1820
2018
  // Register on globalThis for runtime deduplication
1821
2019
  if (typeof globalThis !== 'undefined') {
1822
- globalThis.__ripComponent = { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component };
2020
+ globalThis.__ripComponent = { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __lis, __reconcile, __transition, __handleComponentError, __Component };
1823
2021
  }
1824
2022
 
1825
2023
  `;