rasti 4.0.0-alpha.5 → 4.0.0-alpha.6
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 +8 -6
- package/dist/rasti.js +137 -67
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +49 -29
- package/es/View.js +2 -2
- package/es/core/Interpolation.js +61 -33
- package/es/index.js +1 -0
- package/es/utils/replaceNode.js +24 -0
- package/lib/Component.cjs +49 -29
- package/lib/View.cjs +2 -2
- package/lib/core/Interpolation.cjs +61 -33
- package/lib/index.cjs +1 -0
- package/lib/utils/replaceNode.cjs +26 -0
- package/package.json +1 -1
- package/src/Component.js +49 -29
- package/src/View.js +2 -2
- package/src/core/Interpolation.js +61 -33
- package/src/utils/replaceNode.js +22 -0
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.
|
|
4
|
-
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.6/docs/logo-dark.svg">
|
|
4
|
+
<img alt="Rasti.js" src="https://cdn.jsdelivr.net/gh/8tentaculos/rasti@v4.0.0-alpha.6/docs/logo.svg" height="120">
|
|
5
5
|
</picture>
|
|
6
6
|
</p>
|
|
7
7
|
|
|
@@ -182,10 +182,12 @@ For detailed information on how to use **Rasti**, refer to the [API documentatio
|
|
|
182
182
|
|
|
183
183
|
## Version History
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **[
|
|
188
|
-
- **[
|
|
185
|
+
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.
|
|
186
|
+
|
|
187
|
+
- **[v4.0.0](https://github.com/8tentaculos/rasti/releases/tag/v4.0.0)**
|
|
188
|
+
- **[v3.0.0](https://github.com/8tentaculos/rasti/releases/tag/v3.0.0)**
|
|
189
|
+
- **[v2.0.0](https://github.com/8tentaculos/rasti/releases/tag/v2.0.0)**
|
|
190
|
+
- **[v1.0.0](https://github.com/8tentaculos/rasti/releases/tag/v1.0.0)**
|
|
189
191
|
|
|
190
192
|
## License
|
|
191
193
|
|
package/dist/rasti.js
CHANGED
|
@@ -830,7 +830,7 @@
|
|
|
830
830
|
* Ensure that the view has a unique id at `this.uid`.
|
|
831
831
|
*/
|
|
832
832
|
ensureUid() {
|
|
833
|
-
if (!this.uid) this.uid = `r
|
|
833
|
+
if (!this.uid) this.uid = `r${++View.uid}`;
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
/**
|
|
@@ -879,7 +879,7 @@
|
|
|
879
879
|
* @return {Rasti.View} Return `this` for chaining.
|
|
880
880
|
*/
|
|
881
881
|
removeElement() {
|
|
882
|
-
this.el.
|
|
882
|
+
this.el.remove();
|
|
883
883
|
// Return `this` for chaining.
|
|
884
884
|
return this;
|
|
885
885
|
}
|
|
@@ -1258,7 +1258,7 @@
|
|
|
1258
1258
|
* @param {Function} options.getAttributes Function that returns the attributes object for the element.
|
|
1259
1259
|
* @private
|
|
1260
1260
|
*/
|
|
1261
|
-
class Element {
|
|
1261
|
+
let Element$1 = class Element {
|
|
1262
1262
|
constructor(options) {
|
|
1263
1263
|
this.getSelector = options.getSelector;
|
|
1264
1264
|
this.getAttributes = options.getAttributes;
|
|
@@ -1299,7 +1299,7 @@
|
|
|
1299
1299
|
}
|
|
1300
1300
|
});
|
|
1301
1301
|
}
|
|
1302
|
-
}
|
|
1302
|
+
};
|
|
1303
1303
|
|
|
1304
1304
|
/**
|
|
1305
1305
|
* Properties that should be mirrored from source to target.
|
|
@@ -1459,7 +1459,7 @@
|
|
|
1459
1459
|
* @param {Function} options.getEnd Function that returns the end comment marker text.
|
|
1460
1460
|
* @param {any} options.expression The expression to be evaluated for the interpolation.
|
|
1461
1461
|
* @param {Function} options.isComponent Function that checks if an element is a component root element.
|
|
1462
|
-
* @param {Function} options.
|
|
1462
|
+
* @param {Function} options.containsComponent Function that checks if an element contains a component.
|
|
1463
1463
|
* @private
|
|
1464
1464
|
*/
|
|
1465
1465
|
class Interpolation {
|
|
@@ -1467,8 +1467,8 @@
|
|
|
1467
1467
|
this.getStart = options.getStart;
|
|
1468
1468
|
this.getEnd = options.getEnd;
|
|
1469
1469
|
this.expression = options.expression;
|
|
1470
|
-
this.
|
|
1471
|
-
this.
|
|
1470
|
+
this.shouldSkipFind = options.shouldSkipFind;
|
|
1471
|
+
this.shouldSkipSync = options.shouldSkipSync;
|
|
1472
1472
|
this.previousItems = [];
|
|
1473
1473
|
}
|
|
1474
1474
|
|
|
@@ -1478,12 +1478,12 @@
|
|
|
1478
1478
|
* @param {Node} parent The parent node to search in.
|
|
1479
1479
|
*/
|
|
1480
1480
|
hydrate(parent) {
|
|
1481
|
-
const
|
|
1482
|
-
const
|
|
1481
|
+
const startMark = findComment(parent, this.getStart(), this.shouldSkipFind);
|
|
1482
|
+
const endMark = findComment(parent, this.getEnd(), this.shouldSkipFind, startMark);
|
|
1483
1483
|
|
|
1484
1484
|
this.ref = [
|
|
1485
|
-
|
|
1486
|
-
|
|
1485
|
+
startMark,
|
|
1486
|
+
endMark
|
|
1487
1487
|
];
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
@@ -1493,39 +1493,67 @@
|
|
|
1493
1493
|
* @param {DocumentFragment} fragment The new content fragment to insert.
|
|
1494
1494
|
* @param {Element} targetElement The target element to replace with the new fragment.
|
|
1495
1495
|
*/
|
|
1496
|
-
update(fragment,
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
const [startComment, endComment] = this.ref;
|
|
1496
|
+
update(fragment, handleComponents) {
|
|
1497
|
+
let divider;
|
|
1498
|
+
// Reference to marker elements.
|
|
1499
|
+
const [startMark, endMark] = this.ref;
|
|
1503
1500
|
|
|
1504
|
-
const currentFirstElement =
|
|
1505
|
-
|
|
1506
|
-
const
|
|
1507
|
-
|
|
1501
|
+
const currentFirstElement = startMark.nextSibling;
|
|
1502
|
+
// Check if the interpolation is empty.
|
|
1503
|
+
const currentEmpty = currentFirstElement === endMark;
|
|
1504
|
+
// Check if the interpolation has a single child element.
|
|
1505
|
+
const currentSingleChildElement = !currentEmpty && currentFirstElement.nextSibling === endMark;
|
|
1508
1506
|
|
|
1509
1507
|
if (currentEmpty) {
|
|
1510
|
-
//
|
|
1511
|
-
|
|
1512
|
-
} else if (
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1508
|
+
// The interpolation is empty. Insert the fragment.
|
|
1509
|
+
endMark.before(fragment);
|
|
1510
|
+
} else if (
|
|
1511
|
+
currentSingleChildElement &&
|
|
1512
|
+
fragment.children.length === 1 &&
|
|
1513
|
+
!this.shouldSkipSync(currentFirstElement) &&
|
|
1514
|
+
!this.shouldSkipSync(fragment.firstChild)
|
|
1515
|
+
) {
|
|
1516
|
+
// The interpolation has a single child element and the new fragment has a single child element.
|
|
1517
|
+
// The elements doesn't contain a component. Sync node attributes and content.
|
|
1518
|
+
syncNode(currentFirstElement, fragment.firstChild);
|
|
1519
|
+
} else {
|
|
1520
|
+
// The interpolation has multiple child elements or the new fragment has multiple child elements.
|
|
1521
|
+
// Insert a divider comment before the end marker and insert the new fragment after the divider.
|
|
1522
|
+
divider = document.createComment('');
|
|
1523
|
+
endMark.before(divider);
|
|
1524
|
+
endMark.before(fragment);
|
|
1525
|
+
}
|
|
1526
|
+
// Handle the components in the fragment. Execute the handler function.
|
|
1527
|
+
handleComponents();
|
|
1528
|
+
|
|
1529
|
+
if (divider) {
|
|
1530
|
+
// Remove old content and divider.
|
|
1531
|
+
const startMark = this.ref[0];
|
|
1532
|
+
if (startMark.nextSibling === divider) {
|
|
1533
|
+
divider.remove();
|
|
1516
1534
|
} else {
|
|
1517
|
-
|
|
1518
|
-
|
|
1535
|
+
const range = document.createRange();
|
|
1536
|
+
range.setStartAfter(this.ref[0]);
|
|
1537
|
+
range.setEndAfter(divider);
|
|
1538
|
+
range.deleteContents();
|
|
1519
1539
|
}
|
|
1520
|
-
} else {
|
|
1521
|
-
// Interpolation is not empty. Replace the interpolation content.
|
|
1522
|
-
const range = document.createRange();
|
|
1523
|
-
range.setStartAfter(startComment);
|
|
1524
|
-
range.setEndBefore(endComment);
|
|
1525
|
-
range.deleteContents();
|
|
1526
|
-
range.insertNode(fragment);
|
|
1527
1540
|
}
|
|
1528
1541
|
}
|
|
1542
|
+
|
|
1543
|
+
/**
|
|
1544
|
+
* Update the interpolation content with a new fragment. This is used for container components.
|
|
1545
|
+
* @param {Element} element The element to replace with the new fragment.
|
|
1546
|
+
* @param {DocumentFragment} fragment The new content fragment to insert.
|
|
1547
|
+
* @param {Function} handleComponents Function to handle the components in the fragment before removing the old content.
|
|
1548
|
+
*/
|
|
1549
|
+
updateElement(element, fragment, handleComponents) {
|
|
1550
|
+
const divider = document.createComment('');
|
|
1551
|
+
element.after(divider);
|
|
1552
|
+
divider.after(fragment.firstChild);
|
|
1553
|
+
handleComponents();
|
|
1554
|
+
element.remove();
|
|
1555
|
+
divider.remove();
|
|
1556
|
+
}
|
|
1529
1557
|
}
|
|
1530
1558
|
|
|
1531
1559
|
/**
|
|
@@ -1578,6 +1606,29 @@
|
|
|
1578
1606
|
return html.join(' ');
|
|
1579
1607
|
}
|
|
1580
1608
|
|
|
1609
|
+
/**
|
|
1610
|
+
* Replaces an existing DOM node with a new node, preserving internal DOM state.
|
|
1611
|
+
* Uses moveBefore if available, otherwise falls back to before.
|
|
1612
|
+
*
|
|
1613
|
+
* @param {Node} oldNode The existing DOM node to replace.
|
|
1614
|
+
* @param {Node} newNode The new DOM node to replace the old node with.
|
|
1615
|
+
* @module
|
|
1616
|
+
* @private
|
|
1617
|
+
*/
|
|
1618
|
+
function replaceNode(oldNode, newNode) {
|
|
1619
|
+
if (Element.prototype.moveBefore) {
|
|
1620
|
+
oldNode.parentNode.moveBefore(newNode, oldNode);
|
|
1621
|
+
oldNode.remove();
|
|
1622
|
+
} else {
|
|
1623
|
+
const activeElement = document.activeElement;
|
|
1624
|
+
oldNode.before(newNode);
|
|
1625
|
+
oldNode.remove();
|
|
1626
|
+
if (activeElement && activeElement !== document.activeElement && newNode.contains(activeElement)) {
|
|
1627
|
+
activeElement.focus();
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1581
1632
|
/**
|
|
1582
1633
|
* Same as getResult, but pass context as argument to the expression.
|
|
1583
1634
|
* Used to evaluate expressions in the context of a component.
|
|
@@ -1598,13 +1649,13 @@
|
|
|
1598
1649
|
const isComponent = (el) => !!(el && el.dataset && el.dataset[Component.DATASET_ELEMENT] && el.dataset[Component.DATASET_ELEMENT].endsWith('-1'));
|
|
1599
1650
|
|
|
1600
1651
|
/**
|
|
1601
|
-
* Check if an element
|
|
1602
|
-
*
|
|
1652
|
+
* Check if an element contains a component.
|
|
1653
|
+
* It checks if the element is a component root element or if it contains a component.
|
|
1603
1654
|
* @param {Element} el The element to check.
|
|
1604
|
-
* @return {boolean} True if the element
|
|
1655
|
+
* @return {boolean} True if the element contains a component.
|
|
1605
1656
|
* @private
|
|
1606
1657
|
*/
|
|
1607
|
-
const
|
|
1658
|
+
const containsElement = (el) => !!(el && el.dataset && el.dataset[Component.DATASET_ELEMENT]) || !!el.querySelector(`[${Component.ATTRIBUTE_ELEMENT}]`);
|
|
1608
1659
|
|
|
1609
1660
|
/**
|
|
1610
1661
|
* Generate string with placeholders for interpolated expressions.
|
|
@@ -2175,9 +2226,8 @@
|
|
|
2175
2226
|
recycle(parent, props) {
|
|
2176
2227
|
if (parent) {
|
|
2177
2228
|
// Locate the placeholder comment and replace it with the real nodes
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
toBeReplaced.replaceWith(this.el);
|
|
2229
|
+
const placeholder = findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
|
|
2230
|
+
replaceNode(placeholder, this.el);
|
|
2181
2231
|
}
|
|
2182
2232
|
// Update props.
|
|
2183
2233
|
if (props) {
|
|
@@ -2253,8 +2303,8 @@
|
|
|
2253
2303
|
/**
|
|
2254
2304
|
* Render a template part.
|
|
2255
2305
|
* @param {any} part - The template part.
|
|
2256
|
-
* @param {function} addChild - The addChild function.
|
|
2257
|
-
* @param {Array} items - The items array.
|
|
2306
|
+
* @param {function} addChild - The addChild function. It should handle children and return a HTML string.
|
|
2307
|
+
* @param {Array} items - The items array. It is used to store the items that are parsed.
|
|
2258
2308
|
* @return {string} The rendered template part.
|
|
2259
2309
|
* @private
|
|
2260
2310
|
*/
|
|
@@ -2316,6 +2366,24 @@
|
|
|
2316
2366
|
* Used internally on the render process.
|
|
2317
2367
|
* Use it for server-side rendering or static site generation.
|
|
2318
2368
|
* @return {string} The rendered component.
|
|
2369
|
+
* @example
|
|
2370
|
+
* import { Component } from 'rasti';
|
|
2371
|
+
* const Button = Component.create`
|
|
2372
|
+
* <button class="button">Click me</button>
|
|
2373
|
+
* `;
|
|
2374
|
+
* const App = Component.create`
|
|
2375
|
+
* <div>
|
|
2376
|
+
* <${Button}>Click me</${Button}>
|
|
2377
|
+
* </div>
|
|
2378
|
+
* `;
|
|
2379
|
+
*
|
|
2380
|
+
* const app = new App();
|
|
2381
|
+
*
|
|
2382
|
+
* console.log(app.toString());
|
|
2383
|
+
* // <div data-rasti-el="r1-1"><!--rasti-s-r1-1--><button class="button" data-rasti-el="r2-1">Click me</button><!--rasti-e-r1-1--></div>
|
|
2384
|
+
*
|
|
2385
|
+
* console.log(`${app}`);
|
|
2386
|
+
* // <div data-rasti-el="r1-1"><!--rasti-s-r1-1--><button class="button" data-rasti-el="r2-1">Click me</button><!--rasti-e-r1-1--></div>
|
|
2319
2387
|
*/
|
|
2320
2388
|
toString() {
|
|
2321
2389
|
// Normally there won't be any children, but if there are, destroy them.
|
|
@@ -2359,8 +2427,6 @@
|
|
|
2359
2427
|
this.eventsManager.reset();
|
|
2360
2428
|
// Reset position tracking.
|
|
2361
2429
|
this.pathManager.reset();
|
|
2362
|
-
// Store active element.
|
|
2363
|
-
const activeElement = this.isContainer() ? null : document.activeElement;
|
|
2364
2430
|
// Update elements.
|
|
2365
2431
|
this.template.elements.forEach(element => element.update());
|
|
2366
2432
|
// Store previous children.
|
|
@@ -2373,7 +2439,9 @@
|
|
|
2373
2439
|
const recycledChildren = [];
|
|
2374
2440
|
|
|
2375
2441
|
this.pathManager.increment();
|
|
2376
|
-
|
|
2442
|
+
// `addChild` handler is called from `renderTemplatePart` for every component.
|
|
2443
|
+
// It should handle children and return a HTML string.
|
|
2444
|
+
// In this case, where the component updates, it handles children recycling.
|
|
2377
2445
|
const addChild = component => {
|
|
2378
2446
|
let out = component;
|
|
2379
2447
|
let found = null;
|
|
@@ -2422,13 +2490,19 @@
|
|
|
2422
2490
|
}
|
|
2423
2491
|
// Parse the rendered content and get the fragment.
|
|
2424
2492
|
const fragment = parseHTML(rendered);
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2493
|
+
|
|
2494
|
+
const handleComponents = () => {
|
|
2495
|
+
// Add recycled components to children and move them to the new template in the fragment.
|
|
2496
|
+
recycledChildren.forEach(recycled => recycle(recycled, this.el));
|
|
2497
|
+
// Add new children. Hydrate them and update the interpolation.
|
|
2498
|
+
nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
if (this.isContainer()) {
|
|
2502
|
+
interpolation.updateElement(this.el, fragment, handleComponents);
|
|
2503
|
+
} else {
|
|
2504
|
+
interpolation.update(fragment, handleComponents);
|
|
2505
|
+
}
|
|
2432
2506
|
});
|
|
2433
2507
|
// Destroy unused children.
|
|
2434
2508
|
previousChildren.forEach(prev => {
|
|
@@ -2443,10 +2517,6 @@
|
|
|
2443
2517
|
this.delegateEvents();
|
|
2444
2518
|
}
|
|
2445
2519
|
}
|
|
2446
|
-
// Restore focus.
|
|
2447
|
-
if (activeElement && this.el.contains(activeElement)) {
|
|
2448
|
-
activeElement.focus();
|
|
2449
|
-
}
|
|
2450
2520
|
// Call onUpdate lifecycle method.
|
|
2451
2521
|
this.onUpdate.call(this);
|
|
2452
2522
|
// Return this for chaining.
|
|
@@ -2723,7 +2793,7 @@
|
|
|
2723
2793
|
return this.extend({
|
|
2724
2794
|
template() {
|
|
2725
2795
|
return {
|
|
2726
|
-
elements : elements.map(element => new Element({
|
|
2796
|
+
elements : elements.map(element => new Element$1({
|
|
2727
2797
|
getSelector : element.getSelector.bind(this),
|
|
2728
2798
|
getAttributes : element.getAttributes.bind(this)
|
|
2729
2799
|
})),
|
|
@@ -2731,8 +2801,8 @@
|
|
|
2731
2801
|
getStart : interpolation.getStart.bind(this),
|
|
2732
2802
|
getEnd : interpolation.getEnd.bind(this),
|
|
2733
2803
|
expression : interpolation.expression,
|
|
2734
|
-
isComponent,
|
|
2735
|
-
|
|
2804
|
+
shouldSkipFind : isComponent,
|
|
2805
|
+
shouldSkipSync : containsElement
|
|
2736
2806
|
})),
|
|
2737
2807
|
parts,
|
|
2738
2808
|
};
|
|
@@ -2755,14 +2825,14 @@
|
|
|
2755
2825
|
/*
|
|
2756
2826
|
* Placeholders used to temporarily replace expressions in the template.
|
|
2757
2827
|
*/
|
|
2758
|
-
Component.PLACEHOLDER = (idx) => `
|
|
2828
|
+
Component.PLACEHOLDER = (idx) => `__RASTI_PH_${idx}__`;
|
|
2759
2829
|
|
|
2760
2830
|
/*
|
|
2761
2831
|
* Markers used to identify interpolation and recycled components.
|
|
2762
2832
|
*/
|
|
2763
|
-
Component.MARKER_RECYCLED = (uid) => `rasti-
|
|
2764
|
-
Component.MARKER_START = (uid) => `rasti-
|
|
2765
|
-
Component.MARKER_END = (uid) => `rasti-
|
|
2833
|
+
Component.MARKER_RECYCLED = (uid) => `rasti-r-${uid}`;
|
|
2834
|
+
Component.MARKER_START = (uid) => `rasti-s-${uid}`;
|
|
2835
|
+
Component.MARKER_END = (uid) => `rasti-e-${uid}`;
|
|
2766
2836
|
|
|
2767
2837
|
/**
|
|
2768
2838
|
* Components are a special kind of `View` that is designed to be easily composable,
|
package/dist/rasti.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";function e(t){if("function"!=typeof t)throw new TypeError("Listener must be a function")}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((t=>t(...e)))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter((i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1))),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach((e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))})),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureUid(){this.uid||(this.uid="r-"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let s={};return Object.keys(t).forEach((i=>{const n=i.split(" "),r=n.shift(),h=n.join(" ");let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push({selector:h,listener:o})})),Object.keys(s).forEach((t=>{const e=e=>{s[t].forEach((({selector:t,listener:s})=>{if(!t)return void s.call(this,e,this,this.el);let i=e.target;for(;i&&i!==this.el;)i.matches&&i.matches(t)&&s.call(this,e,this,i),i=i.parentElement}))};this.delegatedEventListeners.push({type:t,listener:e}),this.el.addEventListener(t,e)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}class c{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&e.constructor===t&&!e.key?e:null}}const u=["value","checked","selected"];class p{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach((i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))})),Object.keys(e).forEach((s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)})),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach((t=>{this.ref.removeAttribute(t),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")})),Object.keys(s).forEach((t=>{const e=s[t];this.ref.setAttribute(t,e),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)}))}}const d=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=d.length;s<i;s++){const i=d[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function g(t,e,s=(()=>!1),i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class E{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.isComponent=t.isComponent,this.isElement=t.isElement,this.previousItems=[]}hydrate(t){const e=g(t,this.getStart(),this.isComponent),s=g(t,this.getEnd(),this.isComponent,e);this.ref=[e,s]}update(t,e){if(e)return void e.replaceWith(t.firstChild);const[s,i]=this.ref,n=s.nextSibling,r=n===i,h=!r&&n.nextSibling===i,o=t.children;if(r)i.parentNode.insertBefore(t,i);else if(h&&1===o.length)this.isElement(n)?n.replaceWith(o[0]):f(n,o[0]);else{const e=document.createRange();e.setStartAfter(s),e.setEndBefore(i),e.deleteContents(),e.insertNode(t)}}}const m=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t)),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function T(t){const e=[];return Object.keys(t).forEach((s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))})),e.join(" ")}const b=(t,e)=>i(t,e,e),v=t=>!!(t&&t.dataset&&t.dataset[_.DATASET_ELEMENT]&&t.dataset[_.DATASET_ELEMENT].endsWith("-1")),A=t=>!!(t&&t.dataset&&t.dataset[_.DATASET_ELEMENT]),R=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(_.PLACEHOLDER(i)),t)),[]).join(""),L=(t,e)=>{const s=_.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(_.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(_.markAsSafeHTML(t.slice(o))),r},C=(t,e)=>t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t}),{}),$=(t,e,s)=>{const i={};return Object.keys(t).forEach((n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]})),i},S=(t,e,s=!1)=>{const i=_.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),((t,s)=>{const i=e[s];if(i&&i.prototype instanceof _){if(n.has(i))return n.get(i);n.set(i,t)}return t}))),t.replace(new RegExp(`<(${i})([^>]*)>([\\s\\S]*?)</\\1>|<(${i})([^>]*)/>`,"g"),((t,s,i,n,r,h,o,l)=>{let c,u,p;if(s?(c=e[i],u=n):(c=void 0!==o?e[o]:h,u=l),!(c.prototype instanceof _))return t;if(s){const t=S(r,e,!0),s=x(t,e);p=L(s,e)}const d=w(u,e);return e.push((function(){const t=C(d,(t=>b(t,this)));return p&&(t.renderChildren=()=>new a(p.map((t=>b(t,this))))),c.mount(t)})),_.PLACEHOLDER(e.length-1)}))},M=(t,e)=>{const s=_.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},O=(t,e,s)=>{const n=_.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;if(!t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`)))throw new SyntaxError(`Template must have a single root element or be a container component: "${t.trim()}"`);let r=0;return M(t,((t,h,o,a)=>{const l=0===r,c=++r;if(!l&&!o.match(new RegExp(n)))return t;const u=w(o,e),p=t=>`${t}-${c}`,d=s.length;s.push({getSelector:function(){return`[${_.ATTRIBUTE_ELEMENT}="${p(this.uid)}"]`},getAttributes:function(){const t=$(C(u,(t=>b(t,this))),this.eventsManager,(t=>_.ATTRIBUTE_EVENT(t,this.uid)));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[_.ATTRIBUTE_ELEMENT]=p(this.uid),t}}),e.push((function(){const t=this.template.elements[d],e=t.getAttributes.call(this);return t.previousAttributes=e,_.markAsSafeHTML(T(e))}));return`<${h} ${_.PLACEHOLDER(e.length-1)}${a}`}))},x=(t,e)=>{const s=_.PLACEHOLDER("(?:\\d+)");return M(t,((t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=w(n,e),o=function(){return $(C(h,(t=>b(t,this))),this.eventsManager,(t=>_.ATTRIBUTE_EVENT(t,this.uid)))};e.push((function(){const t=o.call(this);return _.markAsSafeHTML(T(t))}));return`<${i} ${_.PLACEHOLDER(e.length-1)}${r}`}))},k=(t,e,s)=>{const i=_.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),(function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return _.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return _.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push((function(){return this.template.interpolations[l]})),_.PLACEHOLDER(e.length-1)}))},w=(t,e)=>{const s=_.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=void 0!==s?e[parseInt(s,10)]:t,l=void 0!==h?e[parseInt(h,10)]:o;void 0!==l?i.push([a,l,!!n]):i.push([a])}return i},N=["key","state","onCreate","onChange","onHydrate","onRecycle","onUpdate"];class _ extends h{constructor(t={}){super(...arguments),this.componentOptions=[],N.forEach((e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))}));const e={};Object.keys(t).forEach((s=>{this.viewOptions.includes(s)||this.componentOptions.includes(s)||(e[s]=t[s])})),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach((s=>{const i=_.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n})),t}ensureElement(){this.eventsManager=new l,this.pathManager=new c,this.template=i(this.template,this),this.el&&(this.el=i(this.el,this),this.toString(),this.hydrate(this.el.parentNode))}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach((t=>{this[t]&&this.subscribe(this[t])})),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach(((e,s)=>{0===s?(e.hydrate(t),this.el?e.ref=this.el:this.el=e.ref):e.hydrate(this.el)})),this.delegateEvents(),this.template.interpolations.forEach((t=>t.hydrate(this.el))),this.children.forEach((t=>t.hydrate(this.el)))),this.onHydrate.call(this),this}recycle(t,e){if(t){g(t,_.MARKER_RECYCLED(this.uid),v).replaceWith(this.el)}return e&&this.props.set(e),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${_.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=L(x(S(R(t,e).trim(),e),e),e).map((t=>b(t,this)));return new a(s)}renderTemplatePart(t,e,s=[]){const i=b(t,this),n=t=>{if(null==t||!1===t||!0===t)return"";if(t instanceof o)return s.push(t),t;if(t instanceof _)return s.push(t),e(t);if(t instanceof a){if(1===t.items.length)return n(t.items[0]);this.pathManager.push();const e=t.items.map((t=>(this.pathManager.increment(),n(t)))).join("");return this.pathManager.pop(),e}if(Array.isArray(t)){this.pathManager.pause();const e=m(t).map(n).join("");return this.pathManager.resume(),e}if(t instanceof E){this.pathManager.increment();const e=this.isContainer()?"":`\x3c!--${t.getStart()}--\x3e`,i=this.isContainer()?"":`\x3c!--${t.getEnd()}--\x3e`,r=n(b(t.expression,this));return t.previousItems=s,`${e}${r}${i}`}const i=_.sanitize(t);return s.push(i),i};return`${n(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset(),this.pathManager.reset();const t=t=>(this.pathManager.track(t),this.addChild(t));return this.template.parts.map((e=>this.renderTemplatePart(e,t))).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.eventsManager.reset(),this.pathManager.reset();const t=this.isContainer()?null:document.activeElement;this.template.elements.forEach((t=>t.update()));const e=this.children;return this.children=[],this.template.interpolations.forEach((t=>{const s=[],i=[];this.pathManager.increment();const n=t.previousItems,r=[],h=this.renderTemplatePart(t.expression,(t=>{let n=t,r=null;return r=t.key?e.find((e=>e.key===t.key)):this.pathManager.findRecyclable(t.constructor),r?(n=r.getRecycledMarker(),i.push([r,t]),r.key||this.pathManager.track(r)):(s.push(t),this.pathManager.track(t)),n}),r);t.previousItems=r;const o=([t,e],s)=>{this.addChild(t).recycle(s,e.props.toJSON()),e.destroy()};if(1===n.length&&n[0]instanceof _&&1===r.length&&r[0]instanceof _&&1===i.length)return void o(i[0],null);const a=y(h);i.forEach((t=>o(t,a))),s.forEach((t=>this.addChild(t).hydrate(a)));const l=this.isContainer()?this.el:null;t.update(a,l)})),e.forEach((t=>{this.children.indexOf(t)<0&&t.destroy()})),this.isContainer()?this.el=this.children[0].el:this.eventsManager.hasPendingTypes()&&this.delegateEvents(),t&&this.el.contains(t)&&t.focus(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onRecycle(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.append(i.render().el)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=L(k(O(S(R(t,e).trim(),e),e,s),e,i),e);return this.extend({template(){return{elements:s.map((t=>new p({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)}))),interpolations:i.map((t=>new E({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,isComponent:v,isElement:A}))),parts:n}}})}}_.ATTRIBUTE_ELEMENT="data-rasti-el",_.ATTRIBUTE_EVENT=(t,e)=>`data-rasti-on-${t}-${e}`,_.DATASET_ELEMENT="rastiEl",_.PLACEHOLDER=t=>`__RASTI-${t}__`,_.MARKER_RECYCLED=t=>`rasti-recycled-${t}`,_.MARKER_START=t=>`rasti-start-${t}`,_.MARKER_END=t=>`rasti-end-${t}`;var j=_.create`<div></div>`;t.Component=j,t.Emitter=s,t.Model=n,t.View=h}));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";function e(t){if("function"!=typeof t)throw new TypeError("Listener must be a function")}class s{on(t,s){return e(s),this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(s),()=>this.off(t,s)}once(t,s){e(s);const i=(...e)=>{s(...e),this.off(t,i)};return this.on(t,i)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((t=>t(...e)))}listenTo(t,e,s){return t.on(e,s),this.listeningTo||(this.listeningTo=[]),this.listeningTo.push({emitter:t,type:e,listener:s}),()=>this.stopListening(t,e,s)}listenToOnce(t,s,i){e(i);const n=(...e)=>{i(...e),this.stopListening(t,s,n)};return this.listenTo(t,s,n)}stopListening(t,e,s){this.listeningTo&&(this.listeningTo=this.listeningTo.filter((i=>!(!t||t===i.emitter&&!e||t===i.emitter&&e===i.type&&!s||t===i.emitter&&e===i.type&&s===i.listener)||(i.emitter.off(i.type,i.listener),!1))),this.listeningTo.length||delete this.listeningTo)}}const i=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);class n extends s{constructor(){super(),this.preinitialize.apply(this,arguments),this.attributes=Object.assign({},i(this.defaults,this),this.parse.apply(this,arguments)),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,`${this.constructor.attributePrefix}${t}`,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}parse(t){return t}toJSON(){return Object.assign({},this.attributes)}}n.attributePrefix="";const r=["el","tag","attributes","events","model","template","onDestroy"];class h extends s{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],this.viewOptions=[],r.forEach((e=>{e in t&&(this[e]=t[e],this.viewOptions.push(e))})),this.ensureUid(),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.stopListening(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this.destroyed=!0,this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureUid(){this.uid||(this.uid="r"+ ++h.uid)}ensureElement(){if(this.el)this.el=i(this.el,this);else{const t=i(this.tag,this),e=i(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.remove(),this}delegateEvents(t){if(t||(t=i(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let s={};return Object.keys(t).forEach((i=>{const n=i.split(" "),r=n.shift(),h=n.join(" ");let o=t[i];"string"==typeof o&&(o=this[o]),e(o),s[r]||(s[r]=[]),s[r].push({selector:h,listener:o})})),Object.keys(s).forEach((t=>{const e=e=>{s[t].forEach((({selector:t,listener:s})=>{if(!t)return void s.call(this,e,this,this.el);let i=e.target;for(;i&&i!==this.el;)i.matches&&i.matches(t)&&s.call(this,e,this,i),i=i.parentElement}))};this.delegatedEventListeners.push({type:t,listener:e}),this.el.addEventListener(t,e)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}h.uid=0;class o{constructor(t){this.value=t}toString(){return this.value}}class a{constructor(t){this.items=t}}class l{constructor(){this.listeners=[],this.types=new Set,this.previousSize=0}addListener(t,e){return this.types.add(e),this.listeners.push(t),this.listeners.length-1}reset(){this.listeners=[],this.previousSize=this.types.size}hasPendingTypes(){return this.types.size>this.previousSize}}class c{constructor(){}reset(){this.paused=0,this.previous=this.tracked||new Map,this.tracked=new Map,this.positionStack=[0]}push(){this.positionStack.push(0)}pop(){this.positionStack.pop()}increment(){this.positionStack[this.positionStack.length-1]++}pause(){this.paused++}resume(){this.paused--}getPath(){return this.positionStack.join("-")}track(t){return 0===this.paused&&this.tracked.set(this.getPath(),t),t}findRecyclable(t){const e=this.previous.get(this.getPath());return e&&e.constructor===t&&!e.key?e:null}}const u=["value","checked","selected"];let d=class{constructor(t){this.getSelector=t.getSelector,this.getAttributes=t.getAttributes,this.previousAttributes={}}hydrate(t){this.ref=t.querySelector(this.getSelector())}update(){const t=this.getAttributes(),{remove:e,add:s}=function(t,e={}){const s={},i=[];return Object.keys(t).forEach((i=>{let n=t[i];n!==e[i]&&(!0===n?s[i]="":!1!==n&&(null==n&&(n=""),s[i]=n))})),Object.keys(e).forEach((s=>{(!(s in t)||e[s]!==t[s]&&!1===t[s])&&i.push(s)})),{add:s,remove:i}}(t,this.previousAttributes);this.previousAttributes=t,e.forEach((t=>{this.ref.removeAttribute(t),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t&&"")})),Object.keys(s).forEach((t=>{const e=s[t];this.ref.setAttribute(t,e),u.includes(t)&&t in this.ref&&(this.ref[t]="value"===t?e:!1!==e&&"false"!==e)}))}};const p=["value","checked","selected"];function f(t,e){t.nodeType===e.nodeType?t.nodeType!==Node.TEXT_NODE?t.tagName===e.tagName?(((t,e)=>{const s=e.attributes,i=t.attributes,n=new Set;for(let e=0,i=s.length;e<i;e++){const{name:i,value:r}=s[e];n.add(i),t.getAttribute(i)!==r&&t.setAttribute(i,r)}for(let e=i.length-1;e>=0;e--){const{name:s}=i[e];n.has(s)||t.removeAttribute(s)}for(let s=0,i=p.length;s<i;s++){const i=p[s];i in t&&t[i]!==e[i]&&(t[i]=e[i])}})(t,e),((t,e)=>{const s=t.childNodes,i=e.childNodes,n=s.length;if(n!==i.length)return!1;for(let t=0;t<n;t++)if(!s[t].isEqualNode(i[t]))return!1;return!0})(t,e)||((t,e)=>{const s=Array.from(e.childNodes);t.replaceChildren(...s)})(t,e)):t.replaceWith(e):t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue):t.replaceWith(e)}function g(t,e,s=(()=>!1),i){let n=i||t.firstChild;for(;n;){if(n.nodeType===Node.COMMENT_NODE&&n.data.trim()===e)return n;if(n.nodeType!==Node.ELEMENT_NODE||s(n)||!n.firstChild){for(;n&&!n.nextSibling;)if(n=n.parentNode,!n||n===t)return null;n&&(n=n.nextSibling)}else n=n.firstChild}return null}class E{constructor(t){this.getStart=t.getStart,this.getEnd=t.getEnd,this.expression=t.expression,this.shouldSkipFind=t.shouldSkipFind,this.shouldSkipSync=t.shouldSkipSync,this.previousItems=[]}hydrate(t){const e=g(t,this.getStart(),this.shouldSkipFind),s=g(t,this.getEnd(),this.shouldSkipFind,e);this.ref=[e,s]}update(t,e){let s;const[i,n]=this.ref,r=i.nextSibling,h=r===n,o=!h&&r.nextSibling===n;if(h?n.before(t):!o||1!==t.children.length||this.shouldSkipSync(r)||this.shouldSkipSync(t.firstChild)?(s=document.createComment(""),n.before(s),n.before(t)):f(r,t.firstChild),e(),s){if(this.ref[0].nextSibling===s)s.remove();else{const t=document.createRange();t.setStartAfter(this.ref[0]),t.setEndAfter(s),t.deleteContents()}}}updateElement(t,e,s){const i=document.createComment("");t.after(i),i.after(e.firstChild),s(),t.remove(),i.remove()}}const m=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...m(e)):t.push(e),t)),[]);function y(t){const e=document.createElement("template");return e.innerHTML=`${t}`.trim(),e.content}function T(t){const e=[];return Object.keys(t).forEach((s=>{let i=t[s];!0===i?e.push(s):!1!==i&&(null==i&&(i=""),e.push(`${s}="${i}"`))})),e.join(" ")}const b=(t,e)=>i(t,e,e),v=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT]&&t.dataset[N.DATASET_ELEMENT].endsWith("-1")),A=t=>!!(t&&t.dataset&&t.dataset[N.DATASET_ELEMENT])||!!t.querySelector(`[${N.ATTRIBUTE_ELEMENT}]`),S=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(N.PLACEHOLDER(i)),t)),[]).join(""),R=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=t.match(new RegExp(`^${s}$`));if(i)return[e[parseInt(i[1],10)]];const n=new RegExp(`${s}`,"g"),r=[];let h,o=0;for(;null!==(h=n.exec(t));){const s=t.slice(o,h.index);r.push(N.markAsSafeHTML(s),e[parseInt(h[1],10)]),o=h.index+h[0].length}return r.push(N.markAsSafeHTML(t.slice(o))),r},L=(t,e)=>t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t=Object.assign(t,i):"string"==typeof i&&(t[i]=!0);else{const n=s[2]?e(s[1]):s[1];t[i]=n}return t}),{}),$=(t,e,s)=>{const i={};return Object.keys(t).forEach((n=>{const r=n.match(/on(([A-Z]{1}[a-z]+)+)/);if(r&&r[1]){const h=r[1].toLowerCase(),o=t[n];if(o){const t=e.addListener(o,h);i[s(h)]=t}}else i[n]=t[n]})),i},C=(t,e,s=!1)=>{const i=N.PLACEHOLDER("(\\d+)"),n=new Map;return s||(t=t.replace(new RegExp(i,"g"),((t,s)=>{const i=e[s];if(i&&i.prototype instanceof N){if(n.has(i))return n.get(i);n.set(i,t)}return t}))),t.replace(new RegExp(`<(${i})([^>]*)>([\\s\\S]*?)</\\1>|<(${i})([^>]*)/>`,"g"),((t,s,i,n,r,h,o,l)=>{let c,u,d;if(s?(c=e[i],u=n):(c=void 0!==o?e[o]:h,u=l),!(c.prototype instanceof N))return t;if(s){const t=C(r,e,!0),s=O(t,e);d=R(s,e)}const p=_(u,e);return e.push((function(){const t=L(p,(t=>b(t,this)));return d&&(t.renderChildren=()=>new a(d.map((t=>b(t,this))))),c.mount(t)})),N.PLACEHOLDER(e.length-1)}))},M=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return t.replace(new RegExp(`<(${s}|[a-z]+[1-6]?)(?:\\s*)((?:"[^"]*"|'[^']*'|[^>])*)(/?>)`,"gi"),e)},k=(t,e,s)=>{const n=N.PLACEHOLDER("(?:\\d+)");if(t.match(new RegExp(`^\\s*${n}\\s*$`)))return t;if(!t.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${n})([^>]*)>([\\s\\S]*?)</(\\1|${n})>\\s*$|^\\s*<([a-z]+[1-6]?|${n})([^>]*)/>\\s*$`)))throw new SyntaxError(`Template must have a single root element or be a container component: "${t.trim()}"`);let r=0;return M(t,((t,h,o,a)=>{const l=0===r,c=++r;if(!l&&!o.match(new RegExp(n)))return t;const u=_(o,e),d=t=>`${t}-${c}`,p=s.length;s.push({getSelector:function(){return`[${N.ATTRIBUTE_ELEMENT}="${d(this.uid)}"]`},getAttributes:function(){const t=$(L(u,(t=>b(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)));return l&&this.attributes&&Object.assign(t,i(this.attributes,this)),t[N.ATTRIBUTE_ELEMENT]=d(this.uid),t}}),e.push((function(){const t=this.template.elements[p],e=t.getAttributes.call(this);return t.previousAttributes=e,N.markAsSafeHTML(T(e))}));return`<${h} ${N.PLACEHOLDER(e.length-1)}${a}`}))},O=(t,e)=>{const s=N.PLACEHOLDER("(?:\\d+)");return M(t,((t,i,n,r)=>{if(!n.match(new RegExp(s)))return t;const h=_(n,e),o=function(){return $(L(h,(t=>b(t,this))),this.eventsManager,(t=>N.ATTRIBUTE_EVENT(t,this.uid)))};e.push((function(){const t=o.call(this);return N.markAsSafeHTML(T(t))}));return`<${i} ${N.PLACEHOLDER(e.length-1)}${r}`}))},x=(t,e,s)=>{const i=N.PLACEHOLDER("(\\d+)");let n=0;return t.replace(new RegExp(i,"g"),(function(i,r,h){const o=t.substring(0,h);if(o.lastIndexOf("<")>o.lastIndexOf(">"))return i;const a=++n;const l=s.length;return s.push({getStart:function(){return N.MARKER_START(`${this.uid}-${a}`)},getEnd:function(){return N.MARKER_END(`${this.uid}-${a}`)},expression:e[r]}),e.push((function(){return this.template.interpolations[l]})),N.PLACEHOLDER(e.length-1)}))},_=(t,e)=>{const s=N.PLACEHOLDER("(\\d+)"),i=[],n=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let r;for(;null!==(r=n.exec(t));){const[,t,s,n,h,o]=r,a=void 0!==s?e[parseInt(s,10)]:t,l=void 0!==h?e[parseInt(h,10)]:o;void 0!==l?i.push([a,l,!!n]):i.push([a])}return i},w=["key","state","onCreate","onChange","onHydrate","onRecycle","onUpdate"];class N extends h{constructor(t={}){super(...arguments),this.componentOptions=[],w.forEach((e=>{e in t&&(this[e]=t[e],this.componentOptions.push(e))}));const e={};Object.keys(t).forEach((s=>{this.viewOptions.includes(s)||this.componentOptions.includes(s)||(e[s]=t[s])})),this.props=new n(e),this.options=t,this.partial=this.partial.bind(this),this.onChange=this.onChange.bind(this),this.onCreate.apply(this,arguments)}events(){const t={};return this.eventsManager.types.forEach((s=>{const i=N.ATTRIBUTE_EVENT(s,this.uid),n=function(t,s,n){const r=n.getAttribute(i);if(r){let i=this.eventsManager.listeners[parseInt(r,10)];"string"==typeof i&&(i=this[i]),e(i),i.call(this,t,s,n)}};t[`${s} [${i}]`]=n,t[s]=n})),t}ensureElement(){this.eventsManager=new l,this.pathManager=new c,this.template=i(this.template,this),this.el&&(this.el=i(this.el,this),this.toString(),this.hydrate(this.el.parentNode))}isContainer(){return 0===this.template.elements.length&&1===this.template.interpolations.length}subscribe(t,e="change",s=this.onChange){return t.on&&this.listenTo(t,e,s),this}hydrate(t){return["model","state","props"].forEach((t=>{this[t]&&this.subscribe(this[t])})),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.template.elements.forEach(((e,s)=>{0===s?(e.hydrate(t),this.el?e.ref=this.el:this.el=e.ref):e.hydrate(this.el)})),this.delegateEvents(),this.template.interpolations.forEach((t=>t.hydrate(this.el))),this.children.forEach((t=>t.hydrate(this.el)))),this.onHydrate.call(this),this}recycle(t,e){if(t){!function(t,e){if(Element.prototype.moveBefore)t.parentNode.moveBefore(e,t),t.remove();else{const s=document.activeElement;t.before(e),t.remove(),s&&s!==document.activeElement&&e.contains(s)&&s.focus()}}(g(t,N.MARKER_RECYCLED(this.uid),v),this.el)}return e&&this.props.set(e),this.onRecycle.call(this),this}getRecycledMarker(){return`\x3c!--${N.MARKER_RECYCLED(this.uid)}--\x3e`}partial(t,...e){const s=R(O(C(S(t,e).trim(),e),e),e).map((t=>b(t,this)));return new a(s)}renderTemplatePart(t,e,s=[]){const i=b(t,this),n=t=>{if(null==t||!1===t||!0===t)return"";if(t instanceof o)return s.push(t),t;if(t instanceof N)return s.push(t),e(t);if(t instanceof a){if(1===t.items.length)return n(t.items[0]);this.pathManager.push();const e=t.items.map((t=>(this.pathManager.increment(),n(t)))).join("");return this.pathManager.pop(),e}if(Array.isArray(t)){this.pathManager.pause();const e=m(t).map(n).join("");return this.pathManager.resume(),e}if(t instanceof E){this.pathManager.increment();const e=this.isContainer()?"":`\x3c!--${t.getStart()}--\x3e`,i=this.isContainer()?"":`\x3c!--${t.getEnd()}--\x3e`,r=n(b(t.expression,this));return t.previousItems=s,`${e}${r}${i}`}const i=N.sanitize(t);return s.push(i),i};return`${n(i)}`}toString(){this.destroyChildren(),this.eventsManager.reset(),this.pathManager.reset();const t=t=>(this.pathManager.track(t),this.addChild(t));return this.template.parts.map((e=>this.renderTemplatePart(e,t))).join("")}render(){if(this.destroyed)return this;if(!this.el){const t=y(this);return this.hydrate(t),this}this.eventsManager.reset(),this.pathManager.reset(),this.template.elements.forEach((t=>t.update()));const t=this.children;return this.children=[],this.template.interpolations.forEach((e=>{const s=[],i=[];this.pathManager.increment();const n=e.previousItems,r=[],h=this.renderTemplatePart(e.expression,(e=>{let n=e,r=null;return r=e.key?t.find((t=>t.key===e.key)):this.pathManager.findRecyclable(e.constructor),r?(n=r.getRecycledMarker(),i.push([r,e]),r.key||this.pathManager.track(r)):(s.push(e),this.pathManager.track(e)),n}),r);e.previousItems=r;const o=([t,e],s)=>{this.addChild(t).recycle(s,e.props.toJSON()),e.destroy()};if(1===n.length&&n[0]instanceof N&&1===r.length&&r[0]instanceof N&&1===i.length)return void o(i[0],null);const a=y(h),l=()=>{i.forEach((t=>o(t,this.el))),s.forEach((t=>this.addChild(t).hydrate(this.el)))};this.isContainer()?e.updateElement(this.el,a,l):e.update(a,l)})),t.forEach((t=>{this.children.indexOf(t)<0&&t.destroy()})),this.isContainer()?this.el=this.children[0].el:this.eventsManager.hasPendingTypes()&&this.delegateEvents(),this.onUpdate.call(this),this}onCreate(){}onChange(){this.render()}onHydrate(){}onRecycle(){}onUpdate(){}onDestroy(){}static markAsSafeHTML(t){return new o(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.append(i.render().el)),i}static create(t,...e){"function"==typeof t&&(e=[t],t=["",""]);const s=[],i=[],n=R(x(k(C(S(t,e).trim(),e),e,s),e,i),e);return this.extend({template(){return{elements:s.map((t=>new d({getSelector:t.getSelector.bind(this),getAttributes:t.getAttributes.bind(this)}))),interpolations:i.map((t=>new E({getStart:t.getStart.bind(this),getEnd:t.getEnd.bind(this),expression:t.expression,shouldSkipFind:v,shouldSkipSync:A}))),parts:n}}})}}N.ATTRIBUTE_ELEMENT="data-rasti-el",N.ATTRIBUTE_EVENT=(t,e)=>`data-rasti-on-${t}-${e}`,N.DATASET_ELEMENT="rastiEl",N.PLACEHOLDER=t=>`__RASTI_PH_${t}__`,N.MARKER_RECYCLED=t=>`rasti-r-${t}`,N.MARKER_START=t=>`rasti-s-${t}`,N.MARKER_END=t=>`rasti-e-${t}`;var j=N.create`<div></div>`;t.Component=j,t.Emitter=s,t.Model=n,t.View=h}));
|
package/es/Component.js
CHANGED
|
@@ -13,6 +13,7 @@ import deepFlat from './utils/deepFlat.js';
|
|
|
13
13
|
import parseHTML from './utils/parseHTML.js';
|
|
14
14
|
import findComment from './utils/findComment.js';
|
|
15
15
|
import getAttributesHTML from './utils/getAttributesHTML.js';
|
|
16
|
+
import replaceNode from './utils/replaceNode.js';
|
|
16
17
|
import './utils/getAttributesDiff.js';
|
|
17
18
|
import './utils/syncNode.js';
|
|
18
19
|
|
|
@@ -36,13 +37,13 @@ const getExpressionResult = (expression, context) => getResult(expression, conte
|
|
|
36
37
|
const isComponent = (el) => !!(el && el.dataset && el.dataset[Component.DATASET_ELEMENT] && el.dataset[Component.DATASET_ELEMENT].endsWith('-1'));
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
|
-
* Check if an element
|
|
40
|
-
*
|
|
40
|
+
* Check if an element contains a component.
|
|
41
|
+
* It checks if the element is a component root element or if it contains a component.
|
|
41
42
|
* @param {Element} el The element to check.
|
|
42
|
-
* @return {boolean} True if the element
|
|
43
|
+
* @return {boolean} True if the element contains a component.
|
|
43
44
|
* @private
|
|
44
45
|
*/
|
|
45
|
-
const
|
|
46
|
+
const containsElement = (el) => !!(el && el.dataset && el.dataset[Component.DATASET_ELEMENT]) || !!el.querySelector(`[${Component.ATTRIBUTE_ELEMENT}]`);
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Generate string with placeholders for interpolated expressions.
|
|
@@ -613,9 +614,8 @@ class Component extends View {
|
|
|
613
614
|
recycle(parent, props) {
|
|
614
615
|
if (parent) {
|
|
615
616
|
// Locate the placeholder comment and replace it with the real nodes
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
toBeReplaced.replaceWith(this.el);
|
|
617
|
+
const placeholder = findComment(parent, Component.MARKER_RECYCLED(this.uid), isComponent);
|
|
618
|
+
replaceNode(placeholder, this.el);
|
|
619
619
|
}
|
|
620
620
|
// Update props.
|
|
621
621
|
if (props) {
|
|
@@ -691,8 +691,8 @@ class Component extends View {
|
|
|
691
691
|
/**
|
|
692
692
|
* Render a template part.
|
|
693
693
|
* @param {any} part - The template part.
|
|
694
|
-
* @param {function} addChild - The addChild function.
|
|
695
|
-
* @param {Array} items - The items array.
|
|
694
|
+
* @param {function} addChild - The addChild function. It should handle children and return a HTML string.
|
|
695
|
+
* @param {Array} items - The items array. It is used to store the items that are parsed.
|
|
696
696
|
* @return {string} The rendered template part.
|
|
697
697
|
* @private
|
|
698
698
|
*/
|
|
@@ -754,6 +754,24 @@ class Component extends View {
|
|
|
754
754
|
* Used internally on the render process.
|
|
755
755
|
* Use it for server-side rendering or static site generation.
|
|
756
756
|
* @return {string} The rendered component.
|
|
757
|
+
* @example
|
|
758
|
+
* import { Component } from 'rasti';
|
|
759
|
+
* const Button = Component.create`
|
|
760
|
+
* <button class="button">Click me</button>
|
|
761
|
+
* `;
|
|
762
|
+
* const App = Component.create`
|
|
763
|
+
* <div>
|
|
764
|
+
* <${Button}>Click me</${Button}>
|
|
765
|
+
* </div>
|
|
766
|
+
* `;
|
|
767
|
+
*
|
|
768
|
+
* const app = new App();
|
|
769
|
+
*
|
|
770
|
+
* console.log(app.toString());
|
|
771
|
+
* // <div data-rasti-el="r1-1"><!--rasti-s-r1-1--><button class="button" data-rasti-el="r2-1">Click me</button><!--rasti-e-r1-1--></div>
|
|
772
|
+
*
|
|
773
|
+
* console.log(`${app}`);
|
|
774
|
+
* // <div data-rasti-el="r1-1"><!--rasti-s-r1-1--><button class="button" data-rasti-el="r2-1">Click me</button><!--rasti-e-r1-1--></div>
|
|
757
775
|
*/
|
|
758
776
|
toString() {
|
|
759
777
|
// Normally there won't be any children, but if there are, destroy them.
|
|
@@ -797,8 +815,6 @@ class Component extends View {
|
|
|
797
815
|
this.eventsManager.reset();
|
|
798
816
|
// Reset position tracking.
|
|
799
817
|
this.pathManager.reset();
|
|
800
|
-
// Store active element.
|
|
801
|
-
const activeElement = this.isContainer() ? null : document.activeElement;
|
|
802
818
|
// Update elements.
|
|
803
819
|
this.template.elements.forEach(element => element.update());
|
|
804
820
|
// Store previous children.
|
|
@@ -811,7 +827,9 @@ class Component extends View {
|
|
|
811
827
|
const recycledChildren = [];
|
|
812
828
|
|
|
813
829
|
this.pathManager.increment();
|
|
814
|
-
|
|
830
|
+
// `addChild` handler is called from `renderTemplatePart` for every component.
|
|
831
|
+
// It should handle children and return a HTML string.
|
|
832
|
+
// In this case, where the component updates, it handles children recycling.
|
|
815
833
|
const addChild = component => {
|
|
816
834
|
let out = component;
|
|
817
835
|
let found = null;
|
|
@@ -860,13 +878,19 @@ class Component extends View {
|
|
|
860
878
|
}
|
|
861
879
|
// Parse the rendered content and get the fragment.
|
|
862
880
|
const fragment = parseHTML(rendered);
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
881
|
+
|
|
882
|
+
const handleComponents = () => {
|
|
883
|
+
// Add recycled components to children and move them to the new template in the fragment.
|
|
884
|
+
recycledChildren.forEach(recycled => recycle(recycled, this.el));
|
|
885
|
+
// Add new children. Hydrate them and update the interpolation.
|
|
886
|
+
nextChildren.forEach(child => this.addChild(child).hydrate(this.el));
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
if (this.isContainer()) {
|
|
890
|
+
interpolation.updateElement(this.el, fragment, handleComponents);
|
|
891
|
+
} else {
|
|
892
|
+
interpolation.update(fragment, handleComponents);
|
|
893
|
+
}
|
|
870
894
|
});
|
|
871
895
|
// Destroy unused children.
|
|
872
896
|
previousChildren.forEach(prev => {
|
|
@@ -881,10 +905,6 @@ class Component extends View {
|
|
|
881
905
|
this.delegateEvents();
|
|
882
906
|
}
|
|
883
907
|
}
|
|
884
|
-
// Restore focus.
|
|
885
|
-
if (activeElement && this.el.contains(activeElement)) {
|
|
886
|
-
activeElement.focus();
|
|
887
|
-
}
|
|
888
908
|
// Call onUpdate lifecycle method.
|
|
889
909
|
this.onUpdate.call(this);
|
|
890
910
|
// Return this for chaining.
|
|
@@ -1169,8 +1189,8 @@ class Component extends View {
|
|
|
1169
1189
|
getStart : interpolation.getStart.bind(this),
|
|
1170
1190
|
getEnd : interpolation.getEnd.bind(this),
|
|
1171
1191
|
expression : interpolation.expression,
|
|
1172
|
-
isComponent,
|
|
1173
|
-
|
|
1192
|
+
shouldSkipFind : isComponent,
|
|
1193
|
+
shouldSkipSync : containsElement
|
|
1174
1194
|
})),
|
|
1175
1195
|
parts,
|
|
1176
1196
|
};
|
|
@@ -1193,14 +1213,14 @@ Component.DATASET_ELEMENT = 'rastiEl';
|
|
|
1193
1213
|
/*
|
|
1194
1214
|
* Placeholders used to temporarily replace expressions in the template.
|
|
1195
1215
|
*/
|
|
1196
|
-
Component.PLACEHOLDER = (idx) => `
|
|
1216
|
+
Component.PLACEHOLDER = (idx) => `__RASTI_PH_${idx}__`;
|
|
1197
1217
|
|
|
1198
1218
|
/*
|
|
1199
1219
|
* Markers used to identify interpolation and recycled components.
|
|
1200
1220
|
*/
|
|
1201
|
-
Component.MARKER_RECYCLED = (uid) => `rasti-
|
|
1202
|
-
Component.MARKER_START = (uid) => `rasti-
|
|
1203
|
-
Component.MARKER_END = (uid) => `rasti-
|
|
1221
|
+
Component.MARKER_RECYCLED = (uid) => `rasti-r-${uid}`;
|
|
1222
|
+
Component.MARKER_START = (uid) => `rasti-s-${uid}`;
|
|
1223
|
+
Component.MARKER_END = (uid) => `rasti-e-${uid}`;
|
|
1204
1224
|
|
|
1205
1225
|
/**
|
|
1206
1226
|
* Components are a special kind of `View` that is designed to be easily composable,
|