rip-lang 3.7.3 → 3.8.8

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/src/components.js CHANGED
@@ -101,8 +101,11 @@ export function installComponentSupport(CodeGenerator) {
101
101
  }
102
102
  current = current[1];
103
103
  }
104
- const tag = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() : 'div');
105
- return { tag, classes };
104
+ let raw = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() : 'div');
105
+ // Split tag#id — e.g. "div#content" → tag: "div", id: "content"
106
+ let [tag, id] = raw.split('#');
107
+ if (!tag) tag = 'div'; // bare #id → div
108
+ return { tag, classes, id };
106
109
  };
107
110
 
108
111
  // ==========================================================================
@@ -118,6 +121,9 @@ export function installComponentSupport(CodeGenerator) {
118
121
  if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
119
122
  return ['.', ['.', 'this', sexpr], 'value'];
120
123
  }
124
+ if (typeof sexpr === 'string' && this.componentMembers && this.componentMembers.has(sexpr)) {
125
+ return ['.', 'this', sexpr];
126
+ }
121
127
  return sexpr;
122
128
  }
123
129
 
@@ -130,6 +136,11 @@ export function installComponentSupport(CodeGenerator) {
130
136
  return sexpr;
131
137
  }
132
138
 
139
+ // Dot access: transform the object but not the property name
140
+ if (sexpr[0] === '.') {
141
+ return ['.', this.transformComponentMembers(sexpr[1]), sexpr[2]];
142
+ }
143
+
133
144
  // Force thin arrows to fat arrows inside components to preserve this binding
134
145
  if (sexpr[0] === '->') {
135
146
  return ['=>', ...sexpr.slice(1).map(item => this.transformComponentMembers(item))];
@@ -236,12 +247,10 @@ export function installComponentSupport(CodeGenerator) {
236
247
  const lines = [];
237
248
  let blockFactoriesCode = '';
238
249
 
239
- lines.push('class {');
250
+ lines.push('class extends __Component {');
240
251
 
241
- // --- Constructor ---
242
- lines.push(' constructor(props = {}) {');
243
- lines.push(' const __prevComponent = __pushComponent(this);');
244
- lines.push('');
252
+ // --- Init (called by __Component constructor) ---
253
+ lines.push(' _init(props) {');
245
254
 
246
255
  // Constants (readonly)
247
256
  for (const { name, value } of readonlyVars) {
@@ -249,10 +258,10 @@ export function installComponentSupport(CodeGenerator) {
249
258
  lines.push(` this.${name} = props.${name} ?? ${val};`);
250
259
  }
251
260
 
252
- // State variables (with isSignal prop merging)
261
+ // State variables (__state handles signal passthrough)
253
262
  for (const { name, value } of stateVars) {
254
263
  const val = this.generateInComponent(value, 'value');
255
- lines.push(` this.${name} = isSignal(props.${name}) ? props.${name} : __state(props.${name} ?? ${val});`);
264
+ lines.push(` this.${name} = __state(props.${name} ?? ${val});`);
256
265
  }
257
266
 
258
267
  // Computed (derived)
@@ -263,13 +272,11 @@ export function installComponentSupport(CodeGenerator) {
263
272
 
264
273
  // Effects
265
274
  for (const effect of effects) {
266
- const effectBody = effect[1];
275
+ const effectBody = effect[2];
267
276
  const effectCode = this.generateInComponent(effectBody, 'value');
268
- lines.push(` __effect(${effectCode});`);
277
+ lines.push(` __effect(() => { ${effectCode}; });`);
269
278
  }
270
279
 
271
- lines.push('');
272
- lines.push(' __popComponent(__prevComponent);');
273
280
  lines.push(' }');
274
281
 
275
282
  // --- Methods ---
@@ -277,8 +284,10 @@ export function installComponentSupport(CodeGenerator) {
277
284
  if (Array.isArray(func) && (func[0] === '->' || func[0] === '=>')) {
278
285
  const [, params, methodBody] = func;
279
286
  const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
280
- const bodyCode = this.generateInComponent(methodBody, 'value');
281
- lines.push(` ${name}(${paramStr}) { return ${bodyCode}; }`);
287
+ const transformed = this.reactiveMembers ? this.transformComponentMembers(methodBody) : methodBody;
288
+ const isAsync = this.containsAwait(methodBody);
289
+ const bodyCode = this.generateFunctionBody(transformed, params || []);
290
+ lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
282
291
  }
283
292
  }
284
293
 
@@ -286,8 +295,10 @@ export function installComponentSupport(CodeGenerator) {
286
295
  for (const { name, value } of lifecycleHooks) {
287
296
  if (Array.isArray(value) && (value[0] === '->' || value[0] === '=>')) {
288
297
  const [, , hookBody] = value;
289
- const bodyCode = this.generateInComponent(hookBody, 'value');
290
- lines.push(` ${name}() { return ${bodyCode}; }`);
298
+ const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
299
+ const isAsync = this.containsAwait(hookBody);
300
+ const bodyCode = this.generateFunctionBody(transformed, []);
301
+ lines.push(` ${isAsync ? 'async ' : ''}${name}() ${bodyCode}`);
291
302
  }
292
303
  }
293
304
 
@@ -316,25 +327,6 @@ export function installComponentSupport(CodeGenerator) {
316
327
  }
317
328
  }
318
329
 
319
- // --- Mount ---
320
- lines.push(' mount(target) {');
321
- lines.push(' if (typeof target === "string") target = document.querySelector(target);');
322
- lines.push(' this._target = target;');
323
- lines.push(' this._root = this._create();');
324
- lines.push(' target.appendChild(this._root);');
325
- lines.push(' if (this._setup) this._setup();');
326
- lines.push(' if (this.mounted) this.mounted();');
327
- lines.push(' return this;');
328
- lines.push(' }');
329
-
330
- // --- Unmount ---
331
- lines.push(' unmount() {');
332
- lines.push(' if (this.unmounted) this.unmounted();');
333
- lines.push(' if (this._root && this._root.parentNode) {');
334
- lines.push(' this._root.parentNode.removeChild(this._root);');
335
- lines.push(' }');
336
- lines.push(' }');
337
-
338
330
  lines.push('}');
339
331
 
340
332
  // Restore context
@@ -356,6 +348,9 @@ export function installComponentSupport(CodeGenerator) {
356
348
  if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
357
349
  return `this.${sexpr}.value`;
358
350
  }
351
+ if (typeof sexpr === 'string' && this.componentMembers && this.componentMembers.has(sexpr)) {
352
+ return `this.${sexpr}`;
353
+ }
359
354
  if (Array.isArray(sexpr) && this.reactiveMembers) {
360
355
  const transformed = this.transformComponentMembers(sexpr);
361
356
  return this.generate(transformed, context);
@@ -445,9 +440,11 @@ export function installComponentSupport(CodeGenerator) {
445
440
  this._setupLines.push(`__effect(() => { ${textVar}.data = this.${str}.value; });`);
446
441
  return textVar;
447
442
  }
448
- // Static tag without content
443
+ // Static tag without content (possibly with #id)
444
+ const [tagStr, idStr] = str.split('#');
449
445
  const elVar = this.newElementVar();
450
- this._createLines.push(`${elVar} = document.createElement('${str}');`);
446
+ this._createLines.push(`${elVar} = document.createElement('${tagStr || 'div'}');`);
447
+ if (idStr) this._createLines.push(`${elVar}.id = '${idStr}';`);
451
448
  return elVar;
452
449
  }
453
450
 
@@ -465,9 +462,10 @@ export function installComponentSupport(CodeGenerator) {
465
462
  return this.generateChildComponent(headStr, rest);
466
463
  }
467
464
 
468
- // HTML tag
465
+ // HTML tag (possibly with #id, e.g. div#content)
469
466
  if (headStr && this.isHtmlTag(headStr)) {
470
- return this.generateTag(headStr, [], rest);
467
+ let [tagName, id] = headStr.split('#');
468
+ return this.generateTag(tagName || 'div', [], rest, id);
471
469
  }
472
470
 
473
471
  // Property chain (div.class or item.name)
@@ -482,17 +480,16 @@ export function installComponentSupport(CodeGenerator) {
482
480
  this._setupLines.push(`__effect(() => { ${textVar}.data = this.${prop}.value; });`);
483
481
  return textVar;
484
482
  }
485
- if (this.componentMembers && this.componentMembers.has(prop)) {
486
- const slotVar = this.newElementVar('slot');
487
- this._createLines.push(`${slotVar} = this.${prop} instanceof Node ? this.${prop} : (this.${prop} != null ? document.createTextNode(String(this.${prop})) : document.createComment(''));`);
488
- return slotVar;
489
- }
483
+ // Slot/prop handle DOM nodes (children) and plain values
484
+ const slotVar = this.newElementVar('slot');
485
+ this._createLines.push(`${slotVar} = this.${prop} instanceof Node ? this.${prop} : (this.${prop} != null ? document.createTextNode(String(this.${prop})) : document.createComment(''));`);
486
+ return slotVar;
490
487
  }
491
488
 
492
- // HTML tag with classes (div.class)
493
- const { tag, classes } = this.collectTemplateClasses(sexpr);
489
+ // HTML tag with classes (div.class) and optional #id
490
+ const { tag, classes, id } = this.collectTemplateClasses(sexpr);
494
491
  if (tag && this.isHtmlTag(tag)) {
495
- return this.generateTag(tag, classes, []);
492
+ return this.generateTag(tag, classes, [], id);
496
493
  }
497
494
 
498
495
  // General property access (e.g., item.name in a loop)
@@ -504,21 +501,21 @@ export function installComponentSupport(CodeGenerator) {
504
501
 
505
502
  // Call expression: (tag.class args...) or ((tag.class) args...)
506
503
  if (Array.isArray(head)) {
507
- // Nested dynamic class call: (((. div __cx__) "classes") children)
504
+ // Nested dynamic class call: (((. div __clsx) "classes") children)
508
505
  if (Array.isArray(head[0]) && head[0][0] === '.' &&
509
- (head[0][2] === '__cx__' || (head[0][2] instanceof String && head[0][2].valueOf() === '__cx__'))) {
506
+ (head[0][2] === '__clsx' || (head[0][2] instanceof String && head[0][2].valueOf() === '__clsx'))) {
510
507
  const tag = typeof head[0][1] === 'string' ? head[0][1] : head[0][1].valueOf();
511
508
  const classExprs = head.slice(1);
512
509
  return this.generateDynamicTag(tag, classExprs, rest);
513
510
  }
514
511
 
515
- const { tag, classes } = this.collectTemplateClasses(head);
512
+ const { tag, classes, id } = this.collectTemplateClasses(head);
516
513
  if (tag && this.isHtmlTag(tag)) {
517
- // Dynamic class syntax: div.("classes") → (. div __cx__) "classes"
518
- if (classes.length === 1 && classes[0] === '__cx__') {
514
+ // Dynamic class syntax: div.("classes") → (. div __clsx) "classes"
515
+ if (classes.length === 1 && classes[0] === '__clsx') {
519
516
  return this.generateDynamicTag(tag, rest, []);
520
517
  }
521
- return this.generateTag(tag, classes, rest);
518
+ return this.generateTag(tag, classes, rest, id);
522
519
  }
523
520
  }
524
521
 
@@ -550,20 +547,12 @@ export function installComponentSupport(CodeGenerator) {
550
547
  };
551
548
 
552
549
  // --------------------------------------------------------------------------
553
- // generateTagHTML element with static classes and children
550
+ // appendChildrenshared child-processing loop for generateTag/generateDynamicTag
554
551
  // --------------------------------------------------------------------------
555
552
 
556
- proto.generateTag = function(tag, classes, args) {
557
- const elVar = this.newElementVar();
558
- this._createLines.push(`${elVar} = document.createElement('${tag}');`);
559
-
560
- if (classes.length > 0) {
561
- this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
562
- }
563
-
553
+ proto.appendChildren = function(elVar, args) {
564
554
  for (const arg of args) {
565
- // Arrow function = children
566
- if (Array.isArray(arg) && (arg[0] === '->' || arg[0] === '=>')) {
555
+ if (this.is(arg, '->') || this.is(arg, '=>')) {
567
556
  const block = arg[2];
568
557
  if (this.is(block, 'block')) {
569
558
  for (const child of block.slice(1)) {
@@ -575,46 +564,47 @@ export function installComponentSupport(CodeGenerator) {
575
564
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
576
565
  }
577
566
  }
578
- // Object = attributes/events
579
567
  else if (this.is(arg, 'object')) {
580
568
  this.generateAttributes(elVar, arg);
581
569
  }
582
- // String = text child
583
- else if (typeof arg === 'string') {
570
+ else if (typeof arg === 'string' || arg instanceof String) {
584
571
  const textVar = this.newTextVar();
585
- if (arg.startsWith('"') || arg.startsWith("'") || arg.startsWith('`')) {
586
- this._createLines.push(`${textVar} = document.createTextNode(${arg});`);
587
- } else if (this.reactiveMembers && this.reactiveMembers.has(arg)) {
588
- this._createLines.push(`${textVar} = document.createTextNode('');`);
589
- this._setupLines.push(`__effect(() => { ${textVar}.data = this.${arg}.value; });`);
590
- } else if (this.componentMembers && this.componentMembers.has(arg)) {
591
- this._createLines.push(`${textVar} = document.createTextNode(String(this.${arg}));`);
592
- } else {
593
- this._createLines.push(`${textVar} = document.createTextNode(String(${arg}));`);
594
- }
595
- this._createLines.push(`${elVar}.appendChild(${textVar});`);
596
- }
597
- // String object (from parser)
598
- else if (arg instanceof String) {
599
572
  const val = arg.valueOf();
600
- const textVar = this.newTextVar();
601
573
  if (val.startsWith('"') || val.startsWith("'") || val.startsWith('`')) {
602
574
  this._createLines.push(`${textVar} = document.createTextNode(${val});`);
603
575
  } else if (this.reactiveMembers && this.reactiveMembers.has(val)) {
604
576
  this._createLines.push(`${textVar} = document.createTextNode('');`);
605
577
  this._setupLines.push(`__effect(() => { ${textVar}.data = this.${val}.value; });`);
578
+ } else if (this.componentMembers && this.componentMembers.has(val)) {
579
+ this._createLines.push(`${textVar} = document.createTextNode(String(this.${val}));`);
606
580
  } else {
607
- this._createLines.push(`${textVar} = document.createTextNode(String(${val}));`);
581
+ this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
608
582
  }
609
583
  this._createLines.push(`${elVar}.appendChild(${textVar});`);
610
584
  }
611
- // Other = nested element
612
585
  else if (arg) {
613
586
  const childVar = this.generateNode(arg);
614
587
  this._createLines.push(`${elVar}.appendChild(${childVar});`);
615
588
  }
616
589
  }
590
+ };
591
+
592
+ // --------------------------------------------------------------------------
593
+ // generateTag — HTML element with static classes and children
594
+ // --------------------------------------------------------------------------
595
+
596
+ proto.generateTag = function(tag, classes, args, id) {
597
+ const elVar = this.newElementVar();
598
+ this._createLines.push(`${elVar} = document.createElement('${tag}');`);
599
+
600
+ if (id) {
601
+ this._createLines.push(`${elVar}.id = '${id}';`);
602
+ }
603
+ if (classes.length > 0) {
604
+ this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
605
+ }
617
606
 
607
+ this.appendChildren(elVar, args);
618
608
  return elVar;
619
609
  };
620
610
 
@@ -628,51 +618,13 @@ export function installComponentSupport(CodeGenerator) {
628
618
 
629
619
  if (classExprs.length > 0) {
630
620
  const classArgs = classExprs.map(e => this.generateInComponent(e, 'value')).join(', ');
631
- const hasReactive = classExprs.some(e => this.hasReactiveDeps(e));
632
- if (hasReactive) {
633
- this._setupLines.push(`__effect(() => { ${elVar}.className = __cx__(${classArgs}); });`);
634
- } else {
635
- this._createLines.push(`${elVar}.className = __cx__(${classArgs});`);
636
- }
637
- }
638
-
639
- for (const arg of children) {
640
- const argHead = Array.isArray(arg) ? (arg[0] instanceof String ? arg[0].valueOf() : arg[0]) : null;
641
- if (argHead === '->' || argHead === '=>') {
642
- const block = arg[2];
643
- const blockHead = Array.isArray(block) ? (block[0] instanceof String ? block[0].valueOf() : block[0]) : null;
644
- if (blockHead === 'block') {
645
- for (const child of block.slice(1)) {
646
- const childVar = this.generateNode(child);
647
- this._createLines.push(`${elVar}.appendChild(${childVar});`);
648
- }
649
- } else if (block) {
650
- const childVar = this.generateNode(block);
651
- this._createLines.push(`${elVar}.appendChild(${childVar});`);
652
- }
653
- }
654
- else if (this.is(arg, 'object')) {
655
- this.generateAttributes(elVar, arg);
656
- }
657
- else if (typeof arg === 'string' || arg instanceof String) {
658
- const textVar = this.newTextVar();
659
- const argStr = arg.valueOf();
660
- if (argStr.startsWith('"') || argStr.startsWith("'") || argStr.startsWith('`')) {
661
- this._createLines.push(`${textVar} = document.createTextNode(${argStr});`);
662
- } else if (this.reactiveMembers && this.reactiveMembers.has(argStr)) {
663
- this._createLines.push(`${textVar} = document.createTextNode('');`);
664
- this._setupLines.push(`__effect(() => { ${textVar}.data = this.${argStr}.value; });`);
665
- } else {
666
- this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
667
- }
668
- this._createLines.push(`${elVar}.appendChild(${textVar});`);
669
- }
670
- else {
671
- const childVar = this.generateNode(arg);
672
- this._createLines.push(`${elVar}.appendChild(${childVar});`);
673
- }
621
+ // Dynamic classes are always wrapped in __effect — the .() syntax exists
622
+ // precisely for reactive class expressions. If a class were static, you'd
623
+ // just write div.foo.bar instead. The effect tracks signal reads at runtime.
624
+ this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${classArgs}); });`);
674
625
  }
675
626
 
627
+ this.appendChildren(elVar, children);
676
628
  return elVar;
677
629
  };
678
630
 
@@ -689,8 +641,13 @@ export function installComponentSupport(CodeGenerator) {
689
641
  // Event handler: @click or (. this eventName)
690
642
  if (this.is(key, '.') && key[1] === 'this') {
691
643
  const eventName = key[2];
692
- const handlerCode = this.generateInComponent(value, 'value');
693
- this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => (${handlerCode})(e));`);
644
+ // Bind method references to this
645
+ if (typeof value === 'string' && this.componentMembers?.has(value)) {
646
+ this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => this.${value}(e));`);
647
+ } else {
648
+ const handlerCode = this.generateInComponent(value, 'value');
649
+ this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => (${handlerCode})(e));`);
650
+ }
694
651
  continue;
695
652
  }
696
653
 
@@ -701,6 +658,13 @@ export function installComponentSupport(CodeGenerator) {
701
658
  key = key.slice(1, -1);
702
659
  }
703
660
 
661
+ // Element ref: ref: "name" → this.name = element
662
+ if (key === 'ref') {
663
+ const refName = String(value).replace(/^["']|["']$/g, '');
664
+ this._createLines.push(`this.${refName} = ${elVar};`);
665
+ continue;
666
+ }
667
+
704
668
  // Two-way binding: __bind_value__ pattern
705
669
  if (key.startsWith(BIND_PREFIX) && key.endsWith(BIND_SUFFIX)) {
706
670
  const prop = key.slice(BIND_PREFIX.length, -BIND_SUFFIX.length);
@@ -1087,6 +1051,7 @@ export function installComponentSupport(CodeGenerator) {
1087
1051
 
1088
1052
  this._createLines.push(`${instVar} = new ${componentName}(${propsCode});`);
1089
1053
  this._createLines.push(`${elVar} = ${instVar}._create();`);
1054
+ this._createLines.push(`(this._children || (this._children = [])).push(${instVar});`);
1090
1055
 
1091
1056
  this._setupLines.push(`if (${instVar}._setup) ${instVar}._setup();`);
1092
1057
 
@@ -1111,7 +1076,12 @@ export function installComponentSupport(CodeGenerator) {
1111
1076
  for (let i = 1; i < arg.length; i++) {
1112
1077
  const [key, value] = arg[i];
1113
1078
  if (typeof key === 'string') {
1079
+ // Pass reactive members as signals (not values) for reactive prop binding.
1080
+ // Child's __state passthrough returns the signal as-is — shared reactivity.
1081
+ const prevReactive = this.reactiveMembers;
1082
+ this.reactiveMembers = new Set();
1114
1083
  const valueCode = this.generateInComponent(value, 'value');
1084
+ this.reactiveMembers = prevReactive;
1115
1085
  props.push(`${key}: ${valueCode}`);
1116
1086
  }
1117
1087
  }
@@ -1149,16 +1119,24 @@ export function installComponentSupport(CodeGenerator) {
1149
1119
  // --------------------------------------------------------------------------
1150
1120
 
1151
1121
  proto.hasReactiveDeps = function(sexpr) {
1152
- if (!this.reactiveMembers || this.reactiveMembers.size === 0) return false;
1153
-
1154
1122
  if (typeof sexpr === 'string') {
1155
- return this.reactiveMembers.has(sexpr);
1123
+ return !!(this.reactiveMembers && this.reactiveMembers.has(sexpr));
1156
1124
  }
1157
1125
 
1158
1126
  if (!Array.isArray(sexpr)) return false;
1159
1127
 
1128
+ // Direct this.X — check reactive members
1160
1129
  if (sexpr[0] === '.' && sexpr[1] === 'this' && typeof sexpr[2] === 'string') {
1161
- return this.reactiveMembers.has(sexpr[2]);
1130
+ return !!(this.reactiveMembers && this.reactiveMembers.has(sexpr[2]));
1131
+ }
1132
+
1133
+ // Property chain through this (e.g., this.router.path, this.app.data.count)
1134
+ // Props and members may hold reactive objects with signal-backed getters,
1135
+ // so treat deeper this.X.Y chains as potentially reactive. The effect
1136
+ // system handles actual tracking at runtime — wrapping a non-reactive
1137
+ // chain in __effect just means it runs once with no overhead.
1138
+ if (sexpr[0] === '.' && this._rootsAtThis(sexpr[1])) {
1139
+ return true;
1162
1140
  }
1163
1141
 
1164
1142
  for (const child of sexpr) {
@@ -1168,6 +1146,15 @@ export function installComponentSupport(CodeGenerator) {
1168
1146
  return false;
1169
1147
  };
1170
1148
 
1149
+ // _rootsAtThis — check if a property-access chain is rooted at 'this'
1150
+ // --------------------------------------------------------------------------
1151
+
1152
+ proto._rootsAtThis = function(sexpr) {
1153
+ if (typeof sexpr === 'string') return sexpr === 'this';
1154
+ if (!Array.isArray(sexpr) || sexpr[0] !== '.') return false;
1155
+ return this._rootsAtThis(sexpr[1]);
1156
+ };
1157
+
1171
1158
  // ==========================================================================
1172
1159
  // Component Runtime
1173
1160
  // ==========================================================================
@@ -1182,10 +1169,6 @@ export function installComponentSupport(CodeGenerator) {
1182
1169
  // Rip Component Runtime
1183
1170
  // ============================================================================
1184
1171
 
1185
- function isSignal(v) {
1186
- return v != null && typeof v === 'object' && typeof v.read === 'function';
1187
- }
1188
-
1189
1172
  let __currentComponent = null;
1190
1173
 
1191
1174
  function __pushComponent(component) {
@@ -1223,13 +1206,43 @@ function hasContext(key) {
1223
1206
  return false;
1224
1207
  }
1225
1208
 
1226
- function __cx__(...args) {
1209
+ function __clsx(...args) {
1227
1210
  return args.filter(Boolean).join(' ');
1228
1211
  }
1229
1212
 
1213
+ class __Component {
1214
+ constructor(props = {}) {
1215
+ Object.assign(this, props);
1216
+ const prev = __pushComponent(this);
1217
+ this._init(props);
1218
+ __popComponent(prev);
1219
+ }
1220
+ _init() {}
1221
+ mount(target) {
1222
+ if (typeof target === "string") target = document.querySelector(target);
1223
+ this._target = target;
1224
+ this._root = this._create();
1225
+ target.appendChild(this._root);
1226
+ if (this._setup) this._setup();
1227
+ if (this.mounted) this.mounted();
1228
+ return this;
1229
+ }
1230
+ unmount() {
1231
+ if (this._children) {
1232
+ for (const child of this._children) {
1233
+ child.unmount();
1234
+ }
1235
+ }
1236
+ if (this.unmounted) this.unmounted();
1237
+ if (this._root && this._root.parentNode) {
1238
+ this._root.parentNode.removeChild(this._root);
1239
+ }
1240
+ }
1241
+ }
1242
+
1230
1243
  // Register on globalThis for runtime deduplication
1231
1244
  if (typeof globalThis !== 'undefined') {
1232
- globalThis.__ripComponent = { isSignal, __pushComponent, __popComponent, setContext, getContext, hasContext, __cx__ };
1245
+ globalThis.__ripComponent = { __pushComponent, __popComponent, setContext, getContext, hasContext, __clsx, __Component };
1233
1246
  }
1234
1247
 
1235
1248
  `;