rasti 4.0.0-alpha.9 → 4.0.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <p align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.9/docs/logo-dark.svg">
4
- <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.9/docs/logo.svg" height="120">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0/docs/logo-dark.svg">
4
+ <img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0/docs/logo.svg" height="120">
5
5
  </picture>
6
6
  </p>
7
7
 
@@ -9,7 +9,9 @@
9
9
  <b>Modern MVC for building user interfaces</b>
10
10
  </p>
11
11
 
12
- **Rasti** is a lightweight MVC library for building fast, reactive user interfaces. Inspired by **Backbone.js**, it retains a familiar API while removing non-essential features and introducing modern, declarative, and composable components to simplify complex UI development.
12
+ **Rasti is a lightweight MVC library for building fast, reactive user interfaces.**
13
+ It provides declarative, composable **components** for building state-driven UIs.
14
+ Its low-level MVC core, inspired by **Backbone.js**’s architecture, provides **models**, **views** and **event emitters** as the fundamental building blocks.
13
15
 
14
16
  [![Travis (.com)](https://img.shields.io/travis/com/8tentaculos/rasti)](https://app.travis-ci.com/8tentaculos/rasti)
15
17
  [![npm version](https://img.shields.io/npm/v/rasti.svg)](https://www.npmjs.com/package/rasti)
@@ -30,7 +32,7 @@
30
32
  - **Lightweight and Fast** ⚡
31
33
  Minimal overhead with efficient rendering.
32
34
  - **Legacy Compatibility** 🕰️
33
- Seamlessly integrates into existing **Backbone.js** projects.
35
+ Seamlessly integrates into existing **Backbone.js** legacy projects.
34
36
  - **Standards-Based** 📐
35
37
  Built on modern web standards, no tooling required.
36
38
 
@@ -166,7 +168,7 @@ Counter.mount({ model }, document.body);
166
168
  // When buttons are clicked, only the text node gets updated, not the entire component.
167
169
  ```
168
170
 
169
- [Try it on CodePen](https://https://codepen.io/8tentaculos/pen/XJXVQOR?editors=0010)
171
+ [Try it on CodePen](https://codepen.io/8tentaculos/pen/XJXVQOR?editors=0010)
170
172
 
171
173
  ## Why Choose **Rasti**?
172
174
 
@@ -187,6 +189,10 @@ You can find a sample **TODO application** in the [example folder](https://githu
187
189
 
188
190
  For detailed information on how to use **Rasti**, refer to the [API documentation](/docs/api.md).
189
191
 
192
+ ## Working with LLMs
193
+
194
+ For those working with LLMs, there is an [AI Agents reference guide](/docs/AGENTS.md) that provides API patterns, lifecycle methods, and best practices, optimized for LLM context. You can share this guide with AI assistants to help them understand **Rasti**'s architecture and component APIs.
195
+
190
196
  ## Version History
191
197
 
192
198
  We strive to minimize breaking changes between major versions. However, if you're migrating between major versions, please refer to the release notes below for details on any breaking changes and migration tips.
package/dist/rasti.js CHANGED
@@ -71,6 +71,18 @@
71
71
  .join('\n');
72
72
  }
73
73
 
74
+ /**
75
+ * Development mode flag.
76
+ * This will be replaced during build:
77
+ * - ESM/CJS: replaced with process.env.NODE_ENV !== 'production'
78
+ * - UMD dev: replaced with true
79
+ * - UMD prod: replaced with false
80
+ * @type {boolean}
81
+ * @module
82
+ * @private
83
+ */
84
+ const __DEV__ = true;
85
+
74
86
  /**
75
87
  * Validates that the listener is a function.
76
88
  * @param {Function} listener The listener to validate.
@@ -782,7 +794,14 @@
782
794
  * }
783
795
  *
784
796
  * template(model) {
785
- * return `Seconds: <span>${model.seconds}</span>`;
797
+ * return `Seconds: <span>${View.sanitize(model.seconds)}</span>`;
798
+ * }
799
+ *
800
+ * render() {
801
+ * if (this.template) {
802
+ * this.el.innerHTML = this.template(this.model);
803
+ * }
804
+ * return this;
786
805
  * }
787
806
  * }
788
807
  * // Render view and append view's element into the body.
@@ -1076,23 +1095,29 @@
1076
1095
  }
1077
1096
 
1078
1097
  /**
1079
- * Renders the view.
1080
- * This method should be overridden with custom logic.
1081
- * The only convention is to manipulate the DOM within the scope of `this.el`,
1082
- * and to return `this` for chaining.
1083
- * If you add any child views, you should call `this.destroyChildren` before re-rendering.
1084
- * The default implementation updates `this.el`'s innerHTML with the result
1085
- * of calling `this.template`, passing `this.model` as the argument.
1086
- * <br><br> &#9888; **Security Notice:** The default implementation utilizes `innerHTML`, which may introduce Cross-Site Scripting (XSS) risks.
1087
- * Ensure that any user-generated content is properly sanitized before inserting it into the DOM.
1088
- * You can use the {@link #module_view_sanitize View.sanitize} static method to escape HTML entities in a string.
1089
- * For best practices on secure data handling, refer to the
1090
- * [OWASP's XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).<br><br>
1098
+ * `render` is the core function that your view should override, in order to populate its element (`this.el`), with the appropriate HTML. The convention is for `render` to always return `this`.
1099
+ * Views are low-level building blocks for creating user interfaces. For most use cases, we recommend using {@link #module_component Component} instead, which provides a more declarative template syntax, automatic DOM updates, and a more efficient render pipeline.
1100
+ * If you add any child views, you should call `this.destroyChildren` before re-rendering.
1101
+ *
1091
1102
  * @return {View} Returns `this` for chaining.
1103
+ * @example
1104
+ * class UserView extends View {
1105
+ * render() {
1106
+ * if (this.template) {
1107
+ * const model = this.model;
1108
+ * // Sanitize model attributes to prevent XSS attacks.
1109
+ * const safeData = {
1110
+ * name : View.sanitize(model.name),
1111
+ * email : View.sanitize(model.email),
1112
+ * bio : View.sanitize(model.bio)
1113
+ * };
1114
+ * this.el.innerHTML = this.template(safeData);
1115
+ * }
1116
+ * return this;
1117
+ * }
1118
+ * }
1092
1119
  */
1093
1120
  render() {
1094
- if (this.template) this.el.innerHTML = this.template(this.model);
1095
- // Return `this` for chaining.
1096
1121
  return this;
1097
1122
  }
1098
1123
 
@@ -1702,9 +1727,24 @@
1702
1727
  return html.join(' ');
1703
1728
  }
1704
1729
 
1730
+ let isChrome, moveBeforeSupported, preserveFocus, resetFocus;
1731
+
1732
+ // Browser compatibility notes (as of 2025):
1733
+ // - Safari: Does not support moveBefore.
1734
+ // - Firefox: moveBefore preserves focus but loses scroll position.
1735
+ // - Chrome: moveBefore preserves scroll position but loses focus.
1736
+ if (typeof document !== 'undefined') {
1737
+ isChrome = !!navigator.userAgent.match(/Chrome/);
1738
+ moveBeforeSupported = !!Element.prototype.moveBefore;
1739
+ preserveFocus = !moveBeforeSupported || isChrome;
1740
+ // When using moveBefore, Chrome resets the focus but preserves the active element.
1741
+ // So we need to blur the active element before setting the focus again.
1742
+ resetFocus = moveBeforeSupported && isChrome;
1743
+ }
1744
+
1705
1745
  /**
1706
1746
  * Replaces an existing DOM node with a new node, preserving internal DOM state.
1707
- * Uses moveBefore if available, otherwise falls back to before.
1747
+ * Uses moveBefore if available, otherwise falls back to insertBefore.
1708
1748
  *
1709
1749
  * @param {Node} oldNode The existing DOM node to replace.
1710
1750
  * @param {Node} newNode The new DOM node to replace the old node with.
@@ -1712,16 +1752,18 @@
1712
1752
  * @private
1713
1753
  */
1714
1754
  function replaceNode(oldNode, newNode) {
1715
- if (Element.prototype.moveBefore) {
1716
- oldNode.parentNode.moveBefore(newNode, oldNode);
1717
- oldNode.parentNode.removeChild(oldNode);
1718
- } else {
1719
- const activeElement = document.activeElement;
1720
- oldNode.parentNode.insertBefore(newNode, oldNode);
1721
- oldNode.parentNode.removeChild(oldNode);
1722
- if (activeElement && activeElement !== document.activeElement && newNode.contains(activeElement)) {
1723
- activeElement.focus();
1724
- }
1755
+ const activeElement = preserveFocus &&
1756
+ document.activeElement &&
1757
+ newNode.contains(document.activeElement) ?
1758
+ document.activeElement : null;
1759
+
1760
+ if (activeElement && resetFocus) activeElement.blur();
1761
+
1762
+ oldNode.parentNode[moveBeforeSupported ? 'moveBefore' : 'insertBefore'](newNode, oldNode);
1763
+ oldNode.parentNode.removeChild(oldNode);
1764
+
1765
+ if (activeElement && activeElement !== document.activeElement && newNode.contains(activeElement)) {
1766
+ activeElement.focus();
1725
1767
  }
1726
1768
  }
1727
1769
 
@@ -1836,7 +1878,7 @@
1836
1878
  // If error expression is multi-line, show full details.
1837
1879
  if (typeof errorExpression === 'function') {
1838
1880
  const fullSource = errorExpression.toString();
1839
- if (fullSource.includes('\n')) {
1881
+ if (fullSource.match(/\n/)) {
1840
1882
  formattedLines.push('');
1841
1883
  formattedLines.push(' | Expression details:');
1842
1884
  fullSource.split('\n').forEach(line => {
@@ -1859,19 +1901,35 @@
1859
1901
  */
1860
1902
  const getExpressionResult = (expression, context, meta) => {
1861
1903
  try {
1862
- return getResult(expression, context, context);
1904
+ if (typeof expression !== 'function') return expression;
1905
+ // In development, detect uninstantiated Component classes and provide a helpful error.
1906
+ // This typically happens when a component tag is malformed and not properly expanded.
1907
+ if (__DEV__ && expression.prototype instanceof Component) {
1908
+ throw new Error(
1909
+ `Received uninstantiated Component class "${expression.name || 'Anonymous'}". ` +
1910
+ 'This usually happens when a component tag is malformed (e.g., missing closing tag or typo). ' +
1911
+ 'If that\'s not the case, make sure to instantiate child components using a component tag, mount(), or new.'
1912
+ );
1913
+ }
1914
+
1915
+ return expression.call(context, context);
1863
1916
  } catch (error) {
1864
- if (meta && !error.cause) {
1917
+ if (meta && !error._rasti) {
1865
1918
  let message;
1866
1919
 
1867
1920
  {
1868
1921
  const formattedSource = formatTemplateSource(context.source, expression);
1869
- message = createDevelopmentErrorMessage(`Error in ${context.constructor.name}#${context.uid} (${meta})\n${error.message}\n\nTemplate source:\n\n${formattedSource}`);
1922
+ message = createDevelopmentErrorMessage(
1923
+ `Error in ${context.constructor.name}#${context.uid} (${meta})\n${error.message}\n\nTemplate source:\n\n${formattedSource}`
1924
+ );
1870
1925
  }
1926
+
1871
1927
  const enhancedError = new Error(message, { cause : error });
1872
- enhancedError.stack = error.stack;
1928
+ enhancedError._rasti = true;
1929
+
1873
1930
  throw enhancedError;
1874
1931
  }
1932
+
1875
1933
  throw error;
1876
1934
  }
1877
1935
  };
@@ -1892,7 +1950,9 @@
1892
1950
  * @return {boolean} True if the element contains a component.
1893
1951
  * @private
1894
1952
  */
1895
- const containsElement = (el) => !!(el && el.dataset && el.dataset[Component.DATASET_ELEMENT]) || !!el.querySelector(`[${Component.ATTRIBUTE_ELEMENT}]`);
1953
+ const containsElement = (el) => !!(
1954
+ el && ((el.dataset && el.dataset[Component.DATASET_ELEMENT]) || (el.querySelector && el.querySelector(`[${Component.ATTRIBUTE_ELEMENT}]`)))
1955
+ );
1896
1956
 
1897
1957
  /**
1898
1958
  * Generate string with placeholders for interpolated expressions.
@@ -2035,8 +2095,8 @@
2035
2095
  }
2036
2096
  // Match component tags with backreference to ensure correct pairing.
2037
2097
  return main.replace(
2038
- new RegExp(`<(${PH})([^>]*)>([\\s\\S]*?)</\\1>|<(${PH})([^>]*)/>`,'g'),
2039
- (match, openTag, openIdx, nonVoidAttrs, inner, selfClosingTag, selfClosingIdx, selfClosingAttrs) => {
2098
+ new RegExp(`<(${PH})([^>]*)/>|<(${PH})([^>]*)>([\\s\\S]*?)</\\4>`,'g'),
2099
+ (match, selfClosingTag, selfClosingIdx, selfClosingAttrs, openTag, openIdx, nonVoidAttrs, inner) => {
2040
2100
  let tag, attributesStr, innerList;
2041
2101
 
2042
2102
  if (openTag) {
@@ -2066,7 +2126,7 @@
2066
2126
  // Add `renderChildren` function to options.
2067
2127
  if (innerList) {
2068
2128
  // Evaluate items in parent context and create Partial.
2069
- options.renderChildren = () => new Partial(innerList.map(item => getExpressionResult(item, this)));
2129
+ options.renderChildren = () => new Partial(innerList.map(item => getExpressionResult(item, this, 'children')));
2070
2130
  }
2071
2131
  // Mount component.
2072
2132
  return tag.mount(options);
@@ -2281,7 +2341,7 @@
2281
2341
  const PH = Component.PLACEHOLDER('(\\d+)');
2282
2342
  const attributes = [];
2283
2343
  // Parse attributes string with support for placeholders in both names and values.
2284
- const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))?\\3)?`, 'g');
2344
+ const regExp = new RegExp(`(?:${PH}|([\\w-]+))(?:=(["']?)(?:${PH}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/>|\\s*[>"']))+.))?\\3)?`, 'g');
2285
2345
 
2286
2346
  let attributeMatch;
2287
2347
  while ((attributeMatch = regExp.exec(attributesStr)) !== null) {
@@ -2309,7 +2369,7 @@
2309
2369
  /*
2310
2370
  * These option keys will be extended on the component instance.
2311
2371
  */
2312
- const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onRecycle', 'onUpdate'];
2372
+ const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onHydrate', 'onBeforeRecycle', 'onRecycle', 'onBeforeUpdate', 'onUpdate'];
2313
2373
 
2314
2374
  /**
2315
2375
  * @lends module:Component
@@ -2480,22 +2540,34 @@
2480
2540
  /**
2481
2541
  * Used internally on the render process.
2482
2542
  * Reuse a `Component` by replacing the placeholder comment with the real nodes.
2483
- * Call `onRecycle` lifecycle method.
2484
- * @param parent {node} The parent node.
2485
- * @param props {object} The props to set on the recycled component.
2543
+ * Calls `onBeforeRecycle` lifecycle method at the beginning, before any recycling operations occur.
2544
+ * @param parent {node} The parent node. If not provided, the node is already in the correct position and won't be moved.
2486
2545
  * @return {Component} The component instance.
2487
2546
  * @private
2488
2547
  */
2489
- recycle(parent, props) {
2548
+ recycle(parent) {
2549
+ // Call `onBeforeRecycle` lifecycle method.
2550
+ this.onBeforeRecycle.call(this);
2551
+ // No parent means the node is already in the correct position. So we don't need to replace it.
2490
2552
  if (parent) {
2491
2553
  // Locate the placeholder comment and replace it with the real nodes
2492
2554
  const placeholder = findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
2493
2555
  replaceNode(placeholder, this.el);
2494
2556
  }
2495
- // Update props.
2496
- if (props) {
2497
- this.props.set(props);
2498
- }
2557
+ // Return `this` for chaining.
2558
+ return this;
2559
+ }
2560
+
2561
+ /**
2562
+ * Update the component's props.
2563
+ * Sets the props and calls the `onRecycle` lifecycle method.
2564
+ * @param props {object} The props to set on the component.
2565
+ * @return {Component} The component instance.
2566
+ * @private
2567
+ */
2568
+ updateProps(props) {
2569
+ // Set the props.
2570
+ this.props.set(props);
2499
2571
  // Call `onRecycle` lifecycle method.
2500
2572
  this.onRecycle.call(this);
2501
2573
  // Return `this` for chaining.
@@ -2526,7 +2598,7 @@
2526
2598
  * import { Component } from 'rasti';
2527
2599
  * // Create a Title component.
2528
2600
  * const Title = Component.create`
2529
- * <h1>${({ props }) => props.children}</h1>
2601
+ * <h1>${({ props }) => props.renderChildren()}</h1>
2530
2602
  * `;
2531
2603
  * // Create Main component.
2532
2604
  * const Main = Component.create`
@@ -2660,14 +2732,42 @@
2660
2732
  }
2661
2733
 
2662
2734
  /**
2663
- * Render the `Component`.
2664
- * - If `this.el` is not present, the `Component` will be rendered as a string inside a `DocumentFragment` and hydrated, making `this.el` available. The `onHydrate` lifecycle method will be called.
2665
- * - If `this.el` is present, the method will update the attributes and inner HTML of the element, or recreate its child component in the case of a container. The `onUpdate` lifecycle method will be called.
2666
- * - When rendering child components, recycling happens in two ways:
2667
- * - Components with a `key` are recycled if a previous child with the same key exists.
2668
- * - Unkeyed components are recycled if they have the same type and position in the template or partial.
2669
- * A recycled `Component` will call the `onRecycle` lifecycle method.
2670
- * - If the active element is inside the component, it will retain focus after the render.
2735
+ * Render the `Component`.
2736
+ *
2737
+ * **First render (when `this.el` is not present):**
2738
+ * This is the initial render call. The component will be rendered as a string inside a `DocumentFragment` and hydrated,
2739
+ * making `this.el` available. `this.el` is the root DOM element of the component that can be applied to the DOM.
2740
+ * The `onHydrate` lifecycle method will be called.
2741
+ *
2742
+ * **Note:** Typically, you don't need to call `render()` directly for the first render. The static method `Component.mount()`
2743
+ * handles this process automatically, creating the component instance, rendering it, and appending it to the DOM.
2744
+ *
2745
+ * **Update render (when `this.el` is present):**
2746
+ * This indicates the component is being updated. The method will:
2747
+ * - Update only the attributes of the root element and child elements
2748
+ * - Update only the content of interpolations (the dynamic parts of the template)
2749
+ * - For container components (components that render a single child component), update the single interpolation
2750
+ *
2751
+ * The `onBeforeUpdate` lifecycle method will be called at the beginning, followed by the `onUpdate` lifecycle method at the end.
2752
+ *
2753
+ * **Child component handling:**
2754
+ * When rendering child components, they can be either recreated or recycled:
2755
+ *
2756
+ * - **Recreation:** A new component instance is created, running the constructor again. This happens when no matching component
2757
+ * is found for recycling.
2758
+ *
2759
+ * - **Recycling:** The same component instance is reused. Recycling happens in two ways:
2760
+ * - Components with a `key` are recycled if a previous child with the same key exists
2761
+ * - Unkeyed components are recycled if they have the same type and position in the template or partial
2762
+ *
2763
+ * When a component is recycled:
2764
+ * - The `onBeforeRecycle` lifecycle method is called when recycling starts
2765
+ * - The component's `this.props` is updated with the new props from the parent
2766
+ * - The `onRecycle` lifecycle method is called after props are updated
2767
+ *
2768
+ * A recycled component may not use props at all and remain unchanged, or it may be subscribed to a different model
2769
+ * (or even the same model as the parent) and update independently in subsequent render cycles.
2770
+ *
2671
2771
  * @return {Component} The component instance.
2672
2772
  */
2673
2773
  render() {
@@ -2679,14 +2779,16 @@
2679
2779
  this.hydrate(fragment);
2680
2780
  return this;
2681
2781
  }
2782
+ // Call `onBeforeUpdate` lifecycle method.
2783
+ this.onBeforeUpdate.call(this);
2682
2784
  // Clear event listeners.
2683
2785
  this.eventsManager.reset();
2684
- // Update elements.
2685
- this.template.elements.forEach(element => element.update());
2686
2786
  // Store previous children.
2687
2787
  const previousChildren = this.children;
2688
2788
  // Clear current children.
2689
2789
  this.children = [];
2790
+ // Store props to update.
2791
+ const propsQueue = [];
2690
2792
  // Update interpolations.
2691
2793
  this.template.interpolations.forEach(interpolation => {
2692
2794
  // Reset the tracker.
@@ -2730,8 +2832,10 @@
2730
2832
  const rendered = this.renderTemplatePart(interpolation.expression, addChild, tracker);
2731
2833
 
2732
2834
  const recycle = ([recycled, discarded], fragment) => {
2733
- // Add child, update props and recycle (move to new position if needed).
2734
- this.addChild(recycled).recycle(fragment, discarded.props.toJSON());
2835
+ // Store props to update.
2836
+ propsQueue.push([recycled, discarded.props.toJSON()]);
2837
+ // Add child and recycle (move to new position if needed).
2838
+ this.addChild(recycled).recycle(fragment);
2735
2839
  // Destroy discarded component.
2736
2840
  discarded.destroy();
2737
2841
  };
@@ -2760,10 +2864,17 @@
2760
2864
  previousChildren.forEach(prev => {
2761
2865
  if (this.children.indexOf(prev) < 0) prev.destroy();
2762
2866
  });
2763
- // If container, set el to the child element.
2867
+ // Update recycled children props.
2868
+ propsQueue.forEach(([recycled, props]) => {
2869
+ recycled.updateProps(props);
2870
+ });
2871
+ // If this component is a container, set el to the child element.
2872
+ // Otherwise, update elements attributes and delegate events.
2764
2873
  if (this.isContainer()) {
2765
2874
  this.el = this.children[0].el;
2766
2875
  } else {
2876
+ // Update elements attributes.
2877
+ this.template.elements.forEach(element => element.update());
2767
2878
  // If there are pending event types, delegate events again.
2768
2879
  if (this.eventsManager.hasPendingTypes()) {
2769
2880
  this.delegateEvents();
@@ -2805,6 +2916,19 @@
2805
2916
  */
2806
2917
  onHydrate() {}
2807
2918
 
2919
+ /**
2920
+ * Lifecycle method. Called before the component is recycled and reused between renders.
2921
+ * This method is called at the beginning of the `recycle` method, before any recycling operations occur.
2922
+ *
2923
+ * A component is recycled when:
2924
+ * - It has a `key` and a previous child with the same key exists
2925
+ * - It doesn't have a `key` but has the same type and position in the template or partial
2926
+ *
2927
+ * Use this method to perform operations that need to happen before the component is recycled,
2928
+ * such as storing previous state or preparing for the recycling.
2929
+ */
2930
+ onBeforeRecycle() {}
2931
+
2808
2932
  /**
2809
2933
  * Lifecycle method. Called when the component is recycled and reused between renders.
2810
2934
  *
@@ -2817,6 +2941,14 @@
2817
2941
  */
2818
2942
  onRecycle() {}
2819
2943
 
2944
+ /**
2945
+ * Lifecycle method. Called before the component is updated or re-rendered.
2946
+ * This method is called at the beginning of the `render` method when the component's state, model, or props change and trigger a re-render.
2947
+ * Use this method to perform operations that need to happen before the component is updated,
2948
+ * such as saving previous state or preparing for the update.
2949
+ */
2950
+ onBeforeUpdate() {}
2951
+
2820
2952
  /**
2821
2953
  * Lifecycle method. Called when the component is updated or re-rendered.
2822
2954
  * This method is called when the component's state, model, or props change and trigger a re-render.
@@ -2938,7 +3070,7 @@
2938
3070
  * ```javascript
2939
3071
  * const Button = Component.create`
2940
3072
  * <button class="${({ props }) => props.className}">
2941
- * ${({ props }) => props.children}
3073
+ * ${({ props }) => props.renderChildren()}
2942
3074
  * </button>
2943
3075
  * `;
2944
3076
  * ```
@@ -2984,14 +3116,14 @@
2984
3116
  * // Create a button component.
2985
3117
  * const Button = Component.create`
2986
3118
  * <button class="button">
2987
- * ${({ props }) => props.children}
3119
+ * ${({ props }) => props.renderChildren()}
2988
3120
  * </button>
2989
3121
  * `;
2990
3122
  * // Create a navigation component. Add buttons as children. Iterate over items.
2991
3123
  * const Navigation = Component.create`
2992
3124
  * <nav>
2993
3125
  * ${({ props }) => props.items.map(
2994
- * item => Button.mount({ children : item.label })
3126
+ * item => Button.mount({ renderChildren : () => item.label })
2995
3127
  * )}
2996
3128
  * </nav>
2997
3129
  * `;
@@ -3007,7 +3139,7 @@
3007
3139
  * // Create a button component.
3008
3140
  * const Button = Component.create`
3009
3141
  * <button class="button">
3010
- * ${({ props }) => props.children}
3142
+ * ${({ props }) => props.renderChildren()}
3011
3143
  * </button>
3012
3144
  * `;
3013
3145
  * // Create a navigation component. Add buttons as children. Iterate over items.
@@ -3030,7 +3162,7 @@
3030
3162
  * // Create a button component.
3031
3163
  * const Button = Component.create`
3032
3164
  * <button class="${({ props }) => props.className}">
3033
- * ${({ props }) => props.children}
3165
+ * ${({ props }) => props.renderChildren()}
3034
3166
  * </button>
3035
3167
  * `;
3036
3168
  * // Create a container that renders a Button component.
@@ -3040,7 +3172,7 @@
3040
3172
  * // Create a container that renders a Button component, using a function.
3041
3173
  * const ButtonCancel = Component.create(() => Button.mount({
3042
3174
  * className : 'cancel',
3043
- * children : 'Cancel'
3175
+ * renderChildren : () => 'Cancel'
3044
3176
  * }));
3045
3177
  * ```
3046
3178
  * @static
@@ -3130,7 +3262,7 @@
3130
3262
  * Components are defined with the {@link #module_component_create Component.create} static method, which takes a tagged template string or a function that returns another component.
3131
3263
  * @module
3132
3264
  * @extends View
3133
- * @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onHydrate, onRecycle, onUpdate, onCreate, onChange. Any additional options not in the component or view options list will be automatically extracted as props and stored as `this.props`.
3265
+ * @param {object} options Object containing options. The following keys will be merged to `this`: model, state, key, onDestroy, onHydrate, onBeforeRecycle, onRecycle, onBeforeUpdate, onUpdate, onCreate, onChange. Any additional options not in the component or view options list will be automatically extracted as props and stored as `this.props`.
3134
3266
  * @property {string} [key] A unique key to identify the component. Components with keys are recycled when the same key is found in the previous render. Unkeyed components are recycled based on type and position.
3135
3267
  * @property {Model} [model] A `Model` or any emitter object containing data and business logic. The component will listen to `change` events and call `onChange` lifecycle method.
3136
3268
  * @property {Model} [state] A `Model` or any emitter object containing data and business logic, to be used as internal state. The component will listen to `change` events and call `onChange` lifecycle method.