rasti 3.0.0-alpha.3 → 3.0.1

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/es/View.js CHANGED
@@ -4,15 +4,7 @@ import getResult from './utils/getResult.js';
4
4
  /*
5
5
  * These option keys will be extended on the view instance.
6
6
  */
7
- const viewOptions = {
8
- el : true,
9
- tag : true,
10
- attributes : true,
11
- events : true,
12
- model : true,
13
- template : true,
14
- onDestroy : true
15
- };
7
+ const viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];
16
8
 
17
9
  /**
18
10
  * - Listens for changes and renders the UI.
@@ -35,6 +27,7 @@ const viewOptions = {
35
27
  * @property {object|function} events Object in the format `{'event selector' : 'listener'}`. It will be used to bind delegated event listeners to the root element. If it is a function, it will be called to get the events object, bound to the view instance. See {@link module_view_delegateevents View.delegateEvents}.
36
28
  * @property {object} model A model or any object containing data and business logic.
37
29
  * @property {function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
30
+ * @property {number} uid Unique identifier for the view instance. This can be used to generate unique IDs for elements within the view. It is automatically generated and should not be set manually.
38
31
  * @example
39
32
  * import { View } from 'rasti';
40
33
  *
@@ -63,7 +56,7 @@ class View extends Emitter {
63
56
  this.preinitialize.apply(this, arguments);
64
57
  // Generate unique id.
65
58
  // Useful to generate element ids.
66
- this.uid = `uid${++View.uid}`;
59
+ this.uid = `rasti-${++View.uid}`;
67
60
  // Store delegated event listeners,
68
61
  // so they can be unbound later.
69
62
  this.delegatedEventListeners = [];
@@ -72,9 +65,9 @@ class View extends Emitter {
72
65
  this.children = [];
73
66
  // Mutable array to store handlers to be called on destroy.
74
67
  this.destroyQueue = [];
75
- // Extend "this" with options, mapping viewOptions keys.
76
- Object.keys(options).forEach(key => {
77
- if (viewOptions[key]) this[key] = options[key];
68
+ // Extend "this" with options.
69
+ viewOptions.forEach(key => {
70
+ if (key in options) this[key] = options[key];
78
71
  });
79
72
  // Ensure that the view has a root element at `this.el`.
80
73
  this.ensureElement();
@@ -303,17 +296,16 @@ class View extends Emitter {
303
296
  }
304
297
 
305
298
  /**
306
- * Renders the view.
299
+ * Renders the view.
307
300
  * This method should be overridden with custom logic.
308
301
  * The only convention is to manipulate the DOM within the scope of `this.el`,
309
- * and to return `this` for chaining.
310
- * If you add any child views, you should call `this.destroyChildren` before re-rendering.
311
- * The default implementation sets the innerHTML of `this.el` with the result
312
- * of calling `this.template`, passing `this.model` as an argument.
313
- * <br><br> &#9888; **Security Notice:** The default implementation utilizes `innerHTML` on the root element
314
- * for rendering, which may introduce Cross-Site Scripting (XSS) risks. Ensure that any user-generated
315
- * content is properly sanitized before inserting it into the DOM. You can use the @link{#module_view_sanitize View.sanitize}
316
- * static method to escape HTML entities in a string.
302
+ * and to return `this` for chaining.
303
+ * If you add any child views, you should call `this.destroyChildren` before re-rendering.
304
+ * The default implementation updates `this.el`'s innerHTML with the result
305
+ * of calling `this.template`, passing `this.model` as the argument.
306
+ * <br><br> &#9888; **Security Notice:** The default implementation utilizes `innerHTML`, which may introduce Cross-Site Scripting (XSS) risks.
307
+ * Ensure that any user-generated content is properly sanitized before inserting it into the DOM.
308
+ * You can use the {@link #module_view_sanitize View.sanitize} static method to escape HTML entities in a string.
317
309
  * For best practices on secure data handling, refer to the
318
310
  * [OWASP's XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).<br><br>
319
311
  * @return {Rasti.View} Returns `this` for chaining.
@@ -326,9 +318,9 @@ class View extends Emitter {
326
318
 
327
319
  /**
328
320
  * Escape HTML entities in a string.
329
- * Use method to sanitize user-generated content before inserting it into the DOM.
321
+ * Use this method to sanitize user-generated content before inserting it into the DOM.
330
322
  * Override this method to provide a custom escape function.
331
- * This method is used by `Component` to escape template interpolations.
323
+ * This method is inherited by {@link #module_component Component} and used to escape template interpolations.
332
324
  * @static
333
325
  * @param {string} str String to escape.
334
326
  * @return {string} Escaped string.
@@ -344,8 +336,16 @@ class View extends Emitter {
344
336
  }
345
337
  }
346
338
 
347
- /*
348
- * Unique Id
339
+ /**
340
+ * Counter for generating unique IDs for view instances.
341
+ * This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like
342
+ * generating element IDs.
343
+ * {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration.
344
+ * For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated
345
+ * unique IDs match those on the client, enabling seamless hydration of components.
346
+ * @static
347
+ * @type {number}
348
+ * @default 0
349
349
  */
350
350
  View.uid = 0;
351
351
 
package/lib/Component.cjs CHANGED
@@ -5,8 +5,12 @@ var utils_getResult = require('./utils/getResult.cjs');
5
5
  var utils_deepFlat = require('./utils/deepFlat.cjs');
6
6
  require('./Emitter.cjs');
7
7
 
8
- /*
8
+ /**
9
9
  * Wrapper class for HTML strings marked as safe.
10
+ * @class SafeHTML
11
+ * @param {string} value The HTML string to be marked as safe.
12
+ * @property {string} value The HTML string.
13
+ * @private
10
14
  */
11
15
  class SafeHTML {
12
16
  constructor(value) {
@@ -18,20 +22,22 @@ class SafeHTML {
18
22
  }
19
23
  }
20
24
 
21
- /*
25
+ /**
22
26
  * Same as getResult, but pass context as argument to the expression.
23
27
  * Used to evaluate expressions in the context of a component.
24
28
  * @param {any} expression The expression to be evaluated.
25
29
  * @param {any} context The context to call the expression with.
26
30
  * @return {any} The result of the evaluated expression.
31
+ * @private
27
32
  */
28
33
  const getExpressionResult = (expression, context) => utils_getResult(expression, context, context);
29
34
 
30
- /*
35
+ /**
31
36
  * Generate string with placeholders for interpolated expressions.
32
37
  * @param strings {array} Array of strings.
33
38
  * @param expressions {array} Array of expressions.
34
39
  * @return {string} String with placeholders.
40
+ * @private
35
41
  */
36
42
  const addPlaceholders = (strings, expressions) =>
37
43
  strings.reduce((out, string, i) => {
@@ -44,11 +50,12 @@ const addPlaceholders = (strings, expressions) =>
44
50
  return out;
45
51
  }, []).join('');
46
52
 
47
- /*
53
+ /**
48
54
  * Generate one dimensional array with strings and expressions.
49
55
  * @param main {string} The main template containing placeholders.
50
56
  * @param expressions {array} Array of expressions to replace placeholders.
51
57
  * @return {array} Array containing strings and expressions.
58
+ * @private
52
59
  */
53
60
  const splitPlaceholders = (main, expressions) => {
54
61
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -68,7 +75,7 @@ const splitPlaceholders = (main, expressions) => {
68
75
  return out;
69
76
  };
70
77
 
71
- /*
78
+ /**
72
79
  * Expand attributes.
73
80
  * @param attributes {array} Array of attributes as key, value pairs.
74
81
  * @param getExpressionResult {function} Function to render expressions.
@@ -76,6 +83,7 @@ const splitPlaceholders = (main, expressions) => {
76
83
  * @property {object} all All attributes.
77
84
  * @property {object} events Event listeners.
78
85
  * @property {object} attributes Attributes.
86
+ * @private
79
87
  */
80
88
  const expandAttributes = (attributes, getExpressionResult) => {
81
89
  const out = attributes.reduce((out, pair) => {
@@ -113,7 +121,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
113
121
  return out;
114
122
  };
115
123
 
116
- /*
124
+ /**
117
125
  * Replace component tags with expressions.
118
126
  * `<${Component} />` or `<${Component}></${Component}>` will be replaced
119
127
  * by a function that mounts the component.
@@ -122,6 +130,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
122
130
  * @param main {string} The main template.
123
131
  * @return {string} The template with components tags replaced by expressions
124
132
  * placeholders.
133
+ * @private
125
134
  */
126
135
  const expandComponents = (main, expressions) => {
127
136
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -161,7 +170,7 @@ const expandComponents = (main, expressions) => {
161
170
  );
162
171
  };
163
172
 
164
- /*
173
+ /**
165
174
  * Parse match data to get tag, attributes, inner html and close tag.
166
175
  * @param match {array}
167
176
  * @return {object}
@@ -170,6 +179,7 @@ const expandComponents = (main, expressions) => {
170
179
  * @property {string} close The closing tag.
171
180
  * @property {array} attributes Array of attributes as key, value pairs.
172
181
  * @property {string} raw The whole match.
182
+ * @private
173
183
  */
174
184
  const parseMatch = (match, expressions) => {
175
185
  const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
@@ -222,13 +232,7 @@ const selfClosingTags = {
222
232
  /*
223
233
  * These option keys will be extended on the component instance.
224
234
  */
225
- const componentOptions = {
226
- key : true,
227
- state : true,
228
- onCreate : true,
229
- onChange : true,
230
- onRender : true
231
- };
235
+ const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onRender'];
232
236
 
233
237
  /**
234
238
  * Components are a special kind of `View` that is designed to be easily composable,
@@ -261,12 +265,14 @@ const componentOptions = {
261
265
  class Component extends View {
262
266
  constructor(options = {}) {
263
267
  super(...arguments);
264
- // Extend "this" with options, mapping componentOptions keys.
265
- Object.keys(options).forEach(key => {
266
- if (componentOptions[key]) this[key] = options[key];
268
+ // Extend "this" with options.
269
+ componentOptions.forEach(key => {
270
+ if (key in options) this[key] = options[key];
267
271
  });
268
272
  // Store options by default.
269
273
  this.options = options;
274
+ // Bind `partial` method to `this`.
275
+ this.partial = this.partial.bind(this);
270
276
  // Call lifecycle method.
271
277
  this.onCreate.apply(this, arguments);
272
278
  }
@@ -296,20 +302,22 @@ class Component extends View {
296
302
  return this;
297
303
  }
298
304
 
299
- /*
300
- * Tell if component is a container.
305
+ /**
306
+ * Tell if `Component` is a container.
301
307
  * In which case, it will not have an element by itself.
302
308
  * It will render a single expression which is expected to return a single component as child.
303
309
  * `this.el` will be a reference to that child component's element.
304
310
  * @return {boolean}
311
+ * @private
305
312
  */
306
313
  isContainer() {
307
314
  return !!(!this.tag && this.template);
308
315
  }
309
316
 
310
- /*
311
- * Override. We don't want to ensure an element on instantiation.
317
+ /**
318
+ * Override super method. We don't want to ensure an element on instantiation.
312
319
  * We will provide it later.
320
+ * @private
313
321
  */
314
322
  ensureElement() {
315
323
  // If el is provided, delegate events.
@@ -320,18 +328,25 @@ class Component extends View {
320
328
  }
321
329
  }
322
330
 
323
- /*
324
- * Find view's element on parent node, using its data attribute.
325
- * @param parent {node} The parent node.
326
- * @return {node} The component's element.
331
+ /**
332
+ * Locate the root element of the `Component` within a specified parent node.
333
+ * This is achieved by searching for the element using the unique data attribute assigned to the `Component`.
334
+ * @param {Node} parent - The parent node to search within.
335
+ * @return {Node} The root element of the component, or `null` if not found.
336
+ * @private
327
337
  */
328
338
  findElement(parent) {
329
339
  return (parent || document).querySelector(`[${Component.DATA_ATTRIBUTE_UID}="${this.uid}"]`);
330
340
  }
331
341
 
332
- /*
333
- * Eval attributes expressions.
334
- * @return {object} Object containing add, remove and html properties.
342
+ /**
343
+ * Retrieve the attributes to be applied to the element.
344
+ * This includes attributes to be added, removed, and their HTML representation.
345
+ * @return {object} An object containing the following properties:
346
+ * @property {object} add - Attributes to be added to the element, with their values.
347
+ * @property {object} remove - Attributes to be removed from the element.
348
+ * @property {string} html - A string representation of the attributes for use in HTML.
349
+ * @private
335
350
  */
336
351
  getAttributes() {
337
352
  const add = {};
@@ -370,11 +385,13 @@ class Component extends View {
370
385
  return { add, remove, html : html.join(' ') };
371
386
  }
372
387
 
373
- /*
388
+ /**
374
389
  * Used internally on the render process.
375
- * Attach the view to the dom element.
390
+ * Attach the `Component` to the dom element providing `this.el`, delegate events,
391
+ * subscribe to model changes and call `onRender` lifecycle method with `Component.RENDER_TYPE_HYDRATE` as argument.
376
392
  * @param parent {node} The parent node.
377
393
  * @return {Rasti.Component} The component instance.
394
+ * @private
378
395
  */
379
396
  hydrate(parent) {
380
397
  // Listen to model changes and call onChange.
@@ -382,13 +399,13 @@ class Component extends View {
382
399
  // Listen to state changes and call onChange.
383
400
  if (this.state) this.subscribe(this.state);
384
401
 
385
- if (!this.isContainer()) {
402
+ if (this.isContainer()) {
403
+ this.children[0].hydrate(parent);
404
+ this.el = this.children[0].el;
405
+ } else {
386
406
  this.el = this.findElement(parent);
387
407
  this.delegateEvents();
388
408
  this.children.forEach(child => child.hydrate(this.el));
389
- } else {
390
- this.children[0].hydrate(parent);
391
- this.el = this.children[0].el;
392
409
  }
393
410
  // Call `onRender` lifecycle method.
394
411
  this.onRender.call(this, Component.RENDER_TYPE_HYDRATE);
@@ -396,30 +413,38 @@ class Component extends View {
396
413
  return this;
397
414
  }
398
415
 
399
- /*
400
- * Used internally in the render process.
401
- * Reuse a view that has `key` when its parent is rendered.
416
+ /**
417
+ * Used internally on the render process.
418
+ * Reuse a `Component` that has `key` when its parent is rendered.
419
+ * Call `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as argument.
402
420
  * @param parent {node} The parent node.
403
421
  * @return {Rasti.Component} The component instance.
422
+ * @private
404
423
  */
405
424
  recycle(parent) {
406
425
  // If component is a container, call recycle on its child.
407
- if (this.isContainer()) return this.children[0].recycle(parent);
408
- // Find placeholder element to be replaced. It has same data attribute as this component.
409
- const toBeReplaced = this.findElement(parent);
410
- // Replace it with this.el.
411
- toBeReplaced.replaceWith(this.el);
426
+ if (this.isContainer()) {
427
+ this.children[0].recycle(parent);
428
+ } else {
429
+ // Find placeholder element to be replaced. It has same data attribute as this component.
430
+ const toBeReplaced = this.findElement(parent);
431
+ // Replace it with this.el.
432
+ toBeReplaced.replaceWith(this.el);
433
+ }
412
434
  // Call `onRender` lifecycle method.
413
435
  this.onRender.call(this, Component.RENDER_TYPE_RECYCLE);
414
436
  // Return `this` for chaining.
415
437
  return this;
416
438
  }
417
439
 
418
- /*
419
- * Override. Add some custom logic to super `destroy` method.
440
+ /**
441
+ * Destroy the `Component`.
442
+ * Destroy children components if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
420
443
  * @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
444
+ * @return {Rasti.View} Return `this` for chaining.
421
445
  */
422
446
  destroy() {
447
+ // Call super destroy method.
423
448
  super.destroy.apply(this, arguments);
424
449
  // Set destroyed flag to prevent a last render after destroyed.
425
450
  this.destroyed = true;
@@ -448,8 +473,11 @@ class Component extends View {
448
473
  }
449
474
 
450
475
  /**
451
- * Lifecycle method. Called when the view is rendered.
452
- * @param type {string} The render type. Can be `render`, `hydrate` or `recycle`.
476
+ * Lifecycle method. Called after the component is rendered.
477
+ * - When the component is rendered for the first time, this method is called with `Component.RENDER_TYPE_HYDRATE` as the argument.
478
+ * - When the component is updated or re-rendered, this method is called with `Component.RENDER_TYPE_RENDER` as the argument.
479
+ * - When the component is recycled (reused with the same key), this method is called with `Component.RENDER_TYPE_RECYCLE` as the argument.
480
+ * @param {string} type - The render type. Possible values are: `Component.RENDER_TYPE_HYDRATE`, `Component.RENDER_TYPE_RENDER` and `Component.RENDER_TYPE_RECYCLE`.
453
481
  */
454
482
  onRender() {}
455
483
 
@@ -463,7 +491,9 @@ class Component extends View {
463
491
  * Tagged template helper method.
464
492
  * Used to create a partial template.
465
493
  * It will return a one-dimensional array with strings and expressions.
466
- * Components will be added as children by the parent component. Template strings will be marked as safe HTML to be rendered.
494
+ * Components will be added as children by the parent component. Template strings literals
495
+ * will be marked as safe HTML to be rendered.
496
+ * This method is bound to the component instance by default.
467
497
  * @param {TemplateStringsArray} strings - Template strings.
468
498
  * @param {...any} expressions - Template expressions.
469
499
  * @return {Array} Array containing strings and expressions.
@@ -484,7 +514,7 @@ class Component extends View {
484
514
  * renderHeader() {
485
515
  * return this.partial`
486
516
  * <header>
487
- * <${Title}>${self => self.model.title}</${Title}>
517
+ * <${Title}>${({ model }) => model.title}</${Title}>
488
518
  * </header>
489
519
  * `;
490
520
  * }
@@ -529,22 +559,26 @@ class Component extends View {
529
559
  `<${tag} ${attributes} />`;
530
560
  }
531
561
 
532
- /*
533
- * View render method.
562
+ /**
563
+ * Render the `Component`.
564
+ * - If `this.el` is not present, the `Component` will be rendered as a string inside a `DocumentFragment` and hydrated, making `this.el` available. The `onRender` lifecycle method will be called with `Component.RENDER_TYPE_HYDRATE` as an argument.
565
+ * - 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 `onRender` lifecycle method will be called with `Component.RENDER_TYPE_RENDER` as an argument.
566
+ * - When rendering child components, if the new children have the same key as the previous ones, they will be recycled. A recycled `Component` will call the `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as an argument.
567
+ * - If the active element is inside the component, it will retain focus after the render.
568
+ * @return {Rasti.Component} The component instance.
534
569
  */
535
570
  render() {
536
571
  // Prevent a last re render if view is already destroyed.
537
572
  if (this.destroyed) return this;
538
-
573
+ // If `this.el` is not present, render the view as a string and hydrate it.
574
+ if (!this.el) {
575
+ const fragment = this.createElement('template');
576
+ fragment.innerHTML = this;
577
+ this.hydrate(fragment.content);
578
+ return this;
579
+ }
580
+ // Update attributes.
539
581
  if (!this.isContainer()) {
540
- // If `this.el` is not present, render the view as a string and hydrate it.
541
- if (!this.el) {
542
- const fragment = this.createElement('template');
543
- fragment.innerHTML = this;
544
- this.hydrate(fragment.content);
545
- return this;
546
- }
547
- // Set `this.el` attributes.
548
582
  const attributes = this.getAttributes();
549
583
  // Remove attributes.
550
584
  Object.keys(attributes.remove).forEach(key => {
@@ -555,7 +589,7 @@ class Component extends View {
555
589
  this.el.setAttribute(key, attributes.add[key]);
556
590
  });
557
591
  }
558
- // Check for `template` to see if view has innerHTML.
592
+ // Check for `template` to see if view has innerHTML or a child component.
559
593
  if (this.template) {
560
594
  // Store active element.
561
595
  const activeElement = document.activeElement;
@@ -596,8 +630,8 @@ class Component extends View {
596
630
  this.addChild(nextChildren[0]).hydrate(fragment.content);
597
631
  // Get next child element.
598
632
  const nextEl = fragment.content.children[0];
599
- // If `this.el` is present, replace it with nextEl.
600
- if (this.el) this.el.replaceWith(nextEl);
633
+ // Replace `this.el` with nextEl.
634
+ this.el.replaceWith(nextEl);
601
635
  // Set `this.el` to nextEl.
602
636
  this.el = nextEl;
603
637
  } else if (recycledChildren[0]) {
@@ -633,10 +667,10 @@ class Component extends View {
633
667
  }
634
668
 
635
669
  /**
636
- * Mark a string as safe HTML to be rendered.
637
- * Normally you don't need to use this method, as Rasti will automatically mark strings
638
- * as safe HTML when the component is @link{#module_component_create created} and when
639
- * using the @link{#module_component__partial Component.partial} method.
670
+ * Mark a string as safe HTML to be rendered.
671
+ * Normally you don't need to use this method, as Rasti will automatically mark string literals
672
+ * as safe HTML when the component is {@link #module_component_create created} and when
673
+ * using the {@link #module_component__partial Component.partial} method.
640
674
  * Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
641
675
  * @static
642
676
  * @param {string} value
@@ -697,89 +731,89 @@ class Component extends View {
697
731
  * Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
698
732
  * - The template outer tag and attributes will be used to create the view's root element.
699
733
  * - The template inner HTML will be used as the view's template.
700
- * ```javascript
701
- * const Button = Component.create`<button class="button">Click me</button>`;
702
- * ```
734
+ * ```javascript
735
+ * const Button = Component.create`<button class="button">Click me</button>`;
736
+ * ```
703
737
  * - Template interpolations that are functions will be evaluated during the render process, receiving the view instance as an argument and being bound to it. If the function returns `null`, `undefined`, `false`, or an empty string, the interpolation won't render any content.
704
- * ```javascript
705
- * const Button = Component.create`
706
- * <button class="${({ options }) => options.className}">
707
- * ${({ options }) => options.renderChildren()}
708
- * </button>
709
- * `;
710
- * ```
738
+ * ```javascript
739
+ * const Button = Component.create`
740
+ * <button class="${({ options }) => options.className}">
741
+ * ${({ options }) => options.renderChildren()}
742
+ * </button>
743
+ * `;
744
+ * ```
711
745
  * - Event handlers should be passed, at the root element as camelized attributes, in the format `onEventName=${{'selector' : listener }}`. They will be transformed to an event object and delegated to the root element. See {@link #module_view__delegateevents View.delegateEvents}.
712
- * - Boolean attributes should be passed in the form of `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
713
- * ```javascript
714
- * const Input = Component.create`
715
- * <input type="text" disabled=${({ options }) => options.disabled} />
716
- * `;
717
- * ```
746
+ * - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
747
+ * ```javascript
748
+ * const Input = Component.create`
749
+ * <input type="text" disabled=${({ options }) => options.disabled} />
750
+ * `;
751
+ * ```
718
752
  * - If the interpolated function returns a component instance, it will be added as a child component.
719
753
  * - If the interpolated function returns an array, each item will be evaluated as above.
720
- * ```javascript
721
- * // Create a button component.
722
- * const Button = Component.create`
723
- * <button class="button">
724
- * ${({ options }) => options.renderChildren()}
725
- * </button>
726
- * `;
727
- * // Create a navigation component. Add buttons as children. Iterate over items.
728
- * const Navigation = Component.create`
729
- * <nav>
730
- * ${({ options }) => options.items.map(
731
- * item => Button.mount({ renderChildren: () => item.label })
732
- * )}
733
- * </nav>
734
- * `;
735
- * // Create a header component. Add navigation as a child.
736
- * const Header = Component.create`
737
- * <header>
738
- * ${({ options }) => Navigation.mount({ items : options.items})}
739
- * </header>
740
- * `;
741
- * ```
754
+ * ```javascript
755
+ * // Create a button component.
756
+ * const Button = Component.create`
757
+ * <button class="button">
758
+ * ${({ options }) => options.renderChildren()}
759
+ * </button>
760
+ * `;
761
+ * // Create a navigation component. Add buttons as children. Iterate over items.
762
+ * const Navigation = Component.create`
763
+ * <nav>
764
+ * ${({ options }) => options.items.map(
765
+ * item => Button.mount({ renderChildren: () => item.label })
766
+ * )}
767
+ * </nav>
768
+ * `;
769
+ * // Create a header component. Add navigation as a child.
770
+ * const Header = Component.create`
771
+ * <header>
772
+ * ${({ options }) => Navigation.mount({ items : options.items})}
773
+ * </header>
774
+ * `;
775
+ * ```
742
776
  * - Child components can be added using a component tag.
743
- * ```javascript
744
- * // Create a button component.
745
- * const Button = Component.create`
746
- * <button class="button">
747
- * ${({ options }) => options.renderChildren()}
748
- * </button>
749
- * `;
750
- * // Create a navigation component. Add buttons as children. Iterate over items.
751
- * const Navigation = Component.create`
752
- * <nav>
753
- * ${self => self.options.items.map(
754
- * item => self.partial`<${Button}>${item.label}</${Button}>`
755
- * )}
756
- * </nav>
757
- * `;
758
- * // Create a header component. Add navigation as a child.
759
- * const Header = Component.create`
760
- * <header>
761
- * <${Navigation} items="${({ options }) => options.items}" />
762
- * </header>
763
- * `;
764
- * ```
777
+ * ```javascript
778
+ * // Create a button component.
779
+ * const Button = Component.create`
780
+ * <button class="button">
781
+ * ${({ options }) => options.renderChildren()}
782
+ * </button>
783
+ * `;
784
+ * // Create a navigation component. Add buttons as children. Iterate over items.
785
+ * const Navigation = Component.create`
786
+ * <nav>
787
+ * ${({ options, partial }) => options.items.map(
788
+ * item => partial`<${Button}>${item.label}</${Button}>`
789
+ * )}
790
+ * </nav>
791
+ * `;
792
+ * // Create a header component. Add navigation as a child.
793
+ * const Header = Component.create`
794
+ * <header>
795
+ * <${Navigation} items="${({ options }) => options.items}" />
796
+ * </header>
797
+ * `;
798
+ * ```
765
799
  * - If the tagged template contains only one expression that mounts a component, or the tags are references to a component, the component will be considered a <b>container</b>. It will render a single component as a child. `this.el` will be a reference to that child component's element.
766
- * ```javascript
767
- * // Create a button component.
768
- * const Button = Component.create`
769
- * <button class="${({ options }) => options.className}">
770
- * ${self => self.renderChildren()}
771
- * </button>
772
- * `;
773
- * // Create a container using the button component
774
- * const ButtonOk = Component.create`
775
- * <${Button} className="ok">Ok</${Button}>
776
- * `;
777
- * // Create a button component using a function
778
- * const ButtonCancel = Component.create(() => Button.mount({
779
- * className: 'cancel',
780
- * renderChildren: () => 'Cancel'
781
- * }));
782
- * ```
800
+ * ```javascript
801
+ * // Create a button component.
802
+ * const Button = Component.create`
803
+ * <button class="${({ options }) => options.className}">
804
+ * ${self => self.renderChildren()}
805
+ * </button>
806
+ * `;
807
+ * // Create a container that renders a Button component.
808
+ * const ButtonOk = Component.create`
809
+ * <${Button} className="ok">Ok</${Button}>
810
+ * `;
811
+ * // Create a container that renders a Button component, using a function.
812
+ * const ButtonCancel = Component.create(() => Button.mount({
813
+ * className: 'cancel',
814
+ * renderChildren: () => 'Cancel'
815
+ * }));
816
+ * ```
783
817
  * @static
784
818
  * @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
785
819
  * @param {...*} expressions - The expressions to be interpolated within the template.
@@ -834,7 +868,7 @@ class Component extends View {
834
868
  if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
835
869
  if (item instanceof SafeHTML) return item;
836
870
  if (item instanceof Component) return addChild(item);
837
- return Component.sanitize(`${item}`);
871
+ return Component.sanitize(item);
838
872
  }
839
873
  return '';
840
874
  }).join('');