rasti 3.0.0 → 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/README.md +3 -3
- package/dist/rasti.js +122 -92
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +98 -68
- package/es/View.js +24 -24
- package/lib/Component.cjs +98 -68
- package/lib/View.cjs +24 -24
- package/package.json +1 -1
- package/src/Component.js +98 -68
- package/src/View.js +24 -24
package/README.md
CHANGED
|
@@ -138,14 +138,14 @@ Main.mount({ routes, model }, document.body);
|
|
|
138
138
|
|
|
139
139
|
## Why Choose **Rasti**?
|
|
140
140
|
|
|
141
|
-
**Rasti** is built for developers who want a simple yet powerful way to create UI components without the complexity of heavy frameworks. Whether you're
|
|
141
|
+
**Rasti** is built for developers who want a simple yet powerful way to create UI components without the complexity of heavy frameworks. Whether you're building a high-performance dashboard, or embedding a lightweight widget, **Rasti** lets you:
|
|
142
142
|
|
|
143
143
|
- **Skip the Setup**
|
|
144
144
|
No installations, no build tools—just load it and start coding.
|
|
145
145
|
- **Lightweight and Efficient**
|
|
146
146
|
Minimal footprint with optimized performance, ensuring smooth updates.
|
|
147
|
-
- **
|
|
148
|
-
|
|
147
|
+
- **Just the Right Abstraction**
|
|
148
|
+
Keeps you close to the DOM with no over-engineering. Fully hackable—if you're curious about how something works, just check the source code.
|
|
149
149
|
|
|
150
150
|
## Example
|
|
151
151
|
|
package/dist/rasti.js
CHANGED
|
@@ -323,15 +323,7 @@
|
|
|
323
323
|
/*
|
|
324
324
|
* These option keys will be extended on the view instance.
|
|
325
325
|
*/
|
|
326
|
-
const viewOptions =
|
|
327
|
-
el : true,
|
|
328
|
-
tag : true,
|
|
329
|
-
attributes : true,
|
|
330
|
-
events : true,
|
|
331
|
-
model : true,
|
|
332
|
-
template : true,
|
|
333
|
-
onDestroy : true
|
|
334
|
-
};
|
|
326
|
+
const viewOptions = ['el', 'tag', 'attributes', 'events', 'model', 'template', 'onDestroy'];
|
|
335
327
|
|
|
336
328
|
/**
|
|
337
329
|
* - Listens for changes and renders the UI.
|
|
@@ -354,6 +346,7 @@
|
|
|
354
346
|
* @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}.
|
|
355
347
|
* @property {object} model A model or any object containing data and business logic.
|
|
356
348
|
* @property {function} template A function that returns a string with the view's inner HTML. See {@link module_view__render View.render}.
|
|
349
|
+
* @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.
|
|
357
350
|
* @example
|
|
358
351
|
* import { View } from 'rasti';
|
|
359
352
|
*
|
|
@@ -382,7 +375,7 @@
|
|
|
382
375
|
this.preinitialize.apply(this, arguments);
|
|
383
376
|
// Generate unique id.
|
|
384
377
|
// Useful to generate element ids.
|
|
385
|
-
this.uid = `
|
|
378
|
+
this.uid = `rasti-${++View.uid}`;
|
|
386
379
|
// Store delegated event listeners,
|
|
387
380
|
// so they can be unbound later.
|
|
388
381
|
this.delegatedEventListeners = [];
|
|
@@ -391,9 +384,9 @@
|
|
|
391
384
|
this.children = [];
|
|
392
385
|
// Mutable array to store handlers to be called on destroy.
|
|
393
386
|
this.destroyQueue = [];
|
|
394
|
-
// Extend "this" with options
|
|
395
|
-
|
|
396
|
-
if (
|
|
387
|
+
// Extend "this" with options.
|
|
388
|
+
viewOptions.forEach(key => {
|
|
389
|
+
if (key in options) this[key] = options[key];
|
|
397
390
|
});
|
|
398
391
|
// Ensure that the view has a root element at `this.el`.
|
|
399
392
|
this.ensureElement();
|
|
@@ -622,17 +615,16 @@
|
|
|
622
615
|
}
|
|
623
616
|
|
|
624
617
|
/**
|
|
625
|
-
* Renders the view.
|
|
618
|
+
* Renders the view.
|
|
626
619
|
* This method should be overridden with custom logic.
|
|
627
620
|
* The only convention is to manipulate the DOM within the scope of `this.el`,
|
|
628
|
-
* and to return `this` for chaining.
|
|
629
|
-
* If you add any child views, you should call `this.destroyChildren` before re-rendering.
|
|
630
|
-
* The default implementation
|
|
631
|
-
* of calling `this.template`, passing `this.model` as
|
|
632
|
-
* <br><br> ⚠ **Security Notice:** The default implementation utilizes `innerHTML
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* static method to escape HTML entities in a string.
|
|
621
|
+
* and to return `this` for chaining.
|
|
622
|
+
* If you add any child views, you should call `this.destroyChildren` before re-rendering.
|
|
623
|
+
* The default implementation updates `this.el`'s innerHTML with the result
|
|
624
|
+
* of calling `this.template`, passing `this.model` as the argument.
|
|
625
|
+
* <br><br> ⚠ **Security Notice:** The default implementation utilizes `innerHTML`, which may introduce Cross-Site Scripting (XSS) risks.
|
|
626
|
+
* Ensure that any user-generated content is properly sanitized before inserting it into the DOM.
|
|
627
|
+
* You can use the {@link #module_view_sanitize View.sanitize} static method to escape HTML entities in a string.
|
|
636
628
|
* For best practices on secure data handling, refer to the
|
|
637
629
|
* [OWASP's XSS Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html).<br><br>
|
|
638
630
|
* @return {Rasti.View} Returns `this` for chaining.
|
|
@@ -663,8 +655,16 @@
|
|
|
663
655
|
}
|
|
664
656
|
}
|
|
665
657
|
|
|
666
|
-
|
|
667
|
-
*
|
|
658
|
+
/**
|
|
659
|
+
* Counter for generating unique IDs for view instances.
|
|
660
|
+
* This is primarily used to assign unique identifiers to each view instance (`this.uid`), which can be helpful for tasks like
|
|
661
|
+
* generating element IDs.
|
|
662
|
+
* {@link #module_component Component}s use `this.uid` to generate data attributes for their elements, to be looked up on hydration.
|
|
663
|
+
* For server-side rendering, this counter should be reset to `0` on every request to ensure that the generated
|
|
664
|
+
* unique IDs match those on the client, enabling seamless hydration of components.
|
|
665
|
+
* @static
|
|
666
|
+
* @type {number}
|
|
667
|
+
* @default 0
|
|
668
668
|
*/
|
|
669
669
|
View.uid = 0;
|
|
670
670
|
|
|
@@ -679,8 +679,12 @@
|
|
|
679
679
|
return acc;
|
|
680
680
|
}, []);
|
|
681
681
|
|
|
682
|
-
|
|
682
|
+
/**
|
|
683
683
|
* Wrapper class for HTML strings marked as safe.
|
|
684
|
+
* @class SafeHTML
|
|
685
|
+
* @param {string} value The HTML string to be marked as safe.
|
|
686
|
+
* @property {string} value The HTML string.
|
|
687
|
+
* @private
|
|
684
688
|
*/
|
|
685
689
|
class SafeHTML {
|
|
686
690
|
constructor(value) {
|
|
@@ -692,20 +696,22 @@
|
|
|
692
696
|
}
|
|
693
697
|
}
|
|
694
698
|
|
|
695
|
-
|
|
699
|
+
/**
|
|
696
700
|
* Same as getResult, but pass context as argument to the expression.
|
|
697
701
|
* Used to evaluate expressions in the context of a component.
|
|
698
702
|
* @param {any} expression The expression to be evaluated.
|
|
699
703
|
* @param {any} context The context to call the expression with.
|
|
700
704
|
* @return {any} The result of the evaluated expression.
|
|
705
|
+
* @private
|
|
701
706
|
*/
|
|
702
707
|
const getExpressionResult = (expression, context) => getResult(expression, context, context);
|
|
703
708
|
|
|
704
|
-
|
|
709
|
+
/**
|
|
705
710
|
* Generate string with placeholders for interpolated expressions.
|
|
706
711
|
* @param strings {array} Array of strings.
|
|
707
712
|
* @param expressions {array} Array of expressions.
|
|
708
713
|
* @return {string} String with placeholders.
|
|
714
|
+
* @private
|
|
709
715
|
*/
|
|
710
716
|
const addPlaceholders = (strings, expressions) =>
|
|
711
717
|
strings.reduce((out, string, i) => {
|
|
@@ -718,11 +724,12 @@
|
|
|
718
724
|
return out;
|
|
719
725
|
}, []).join('');
|
|
720
726
|
|
|
721
|
-
|
|
727
|
+
/**
|
|
722
728
|
* Generate one dimensional array with strings and expressions.
|
|
723
729
|
* @param main {string} The main template containing placeholders.
|
|
724
730
|
* @param expressions {array} Array of expressions to replace placeholders.
|
|
725
731
|
* @return {array} Array containing strings and expressions.
|
|
732
|
+
* @private
|
|
726
733
|
*/
|
|
727
734
|
const splitPlaceholders = (main, expressions) => {
|
|
728
735
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -742,7 +749,7 @@
|
|
|
742
749
|
return out;
|
|
743
750
|
};
|
|
744
751
|
|
|
745
|
-
|
|
752
|
+
/**
|
|
746
753
|
* Expand attributes.
|
|
747
754
|
* @param attributes {array} Array of attributes as key, value pairs.
|
|
748
755
|
* @param getExpressionResult {function} Function to render expressions.
|
|
@@ -750,6 +757,7 @@
|
|
|
750
757
|
* @property {object} all All attributes.
|
|
751
758
|
* @property {object} events Event listeners.
|
|
752
759
|
* @property {object} attributes Attributes.
|
|
760
|
+
* @private
|
|
753
761
|
*/
|
|
754
762
|
const expandAttributes = (attributes, getExpressionResult) => {
|
|
755
763
|
const out = attributes.reduce((out, pair) => {
|
|
@@ -787,7 +795,7 @@
|
|
|
787
795
|
return out;
|
|
788
796
|
};
|
|
789
797
|
|
|
790
|
-
|
|
798
|
+
/**
|
|
791
799
|
* Replace component tags with expressions.
|
|
792
800
|
* `<${Component} />` or `<${Component}></${Component}>` will be replaced
|
|
793
801
|
* by a function that mounts the component.
|
|
@@ -796,6 +804,7 @@
|
|
|
796
804
|
* @param main {string} The main template.
|
|
797
805
|
* @return {string} The template with components tags replaced by expressions
|
|
798
806
|
* placeholders.
|
|
807
|
+
* @private
|
|
799
808
|
*/
|
|
800
809
|
const expandComponents = (main, expressions) => {
|
|
801
810
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -835,7 +844,7 @@
|
|
|
835
844
|
);
|
|
836
845
|
};
|
|
837
846
|
|
|
838
|
-
|
|
847
|
+
/**
|
|
839
848
|
* Parse match data to get tag, attributes, inner html and close tag.
|
|
840
849
|
* @param match {array}
|
|
841
850
|
* @return {object}
|
|
@@ -844,6 +853,7 @@
|
|
|
844
853
|
* @property {string} close The closing tag.
|
|
845
854
|
* @property {array} attributes Array of attributes as key, value pairs.
|
|
846
855
|
* @property {string} raw The whole match.
|
|
856
|
+
* @private
|
|
847
857
|
*/
|
|
848
858
|
const parseMatch = (match, expressions) => {
|
|
849
859
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -896,13 +906,7 @@
|
|
|
896
906
|
/*
|
|
897
907
|
* These option keys will be extended on the component instance.
|
|
898
908
|
*/
|
|
899
|
-
const componentOptions =
|
|
900
|
-
key : true,
|
|
901
|
-
state : true,
|
|
902
|
-
onCreate : true,
|
|
903
|
-
onChange : true,
|
|
904
|
-
onRender : true
|
|
905
|
-
};
|
|
909
|
+
const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onRender'];
|
|
906
910
|
|
|
907
911
|
/**
|
|
908
912
|
* Components are a special kind of `View` that is designed to be easily composable,
|
|
@@ -935,9 +939,9 @@
|
|
|
935
939
|
class Component extends View {
|
|
936
940
|
constructor(options = {}) {
|
|
937
941
|
super(...arguments);
|
|
938
|
-
// Extend "this" with options
|
|
939
|
-
|
|
940
|
-
if (
|
|
942
|
+
// Extend "this" with options.
|
|
943
|
+
componentOptions.forEach(key => {
|
|
944
|
+
if (key in options) this[key] = options[key];
|
|
941
945
|
});
|
|
942
946
|
// Store options by default.
|
|
943
947
|
this.options = options;
|
|
@@ -972,20 +976,22 @@
|
|
|
972
976
|
return this;
|
|
973
977
|
}
|
|
974
978
|
|
|
975
|
-
|
|
976
|
-
* Tell if
|
|
979
|
+
/**
|
|
980
|
+
* Tell if `Component` is a container.
|
|
977
981
|
* In which case, it will not have an element by itself.
|
|
978
982
|
* It will render a single expression which is expected to return a single component as child.
|
|
979
983
|
* `this.el` will be a reference to that child component's element.
|
|
980
984
|
* @return {boolean}
|
|
985
|
+
* @private
|
|
981
986
|
*/
|
|
982
987
|
isContainer() {
|
|
983
988
|
return !!(!this.tag && this.template);
|
|
984
989
|
}
|
|
985
990
|
|
|
986
|
-
|
|
987
|
-
* Override. We don't want to ensure an element on instantiation.
|
|
991
|
+
/**
|
|
992
|
+
* Override super method. We don't want to ensure an element on instantiation.
|
|
988
993
|
* We will provide it later.
|
|
994
|
+
* @private
|
|
989
995
|
*/
|
|
990
996
|
ensureElement() {
|
|
991
997
|
// If el is provided, delegate events.
|
|
@@ -996,18 +1002,25 @@
|
|
|
996
1002
|
}
|
|
997
1003
|
}
|
|
998
1004
|
|
|
999
|
-
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
* @
|
|
1005
|
+
/**
|
|
1006
|
+
* Locate the root element of the `Component` within a specified parent node.
|
|
1007
|
+
* This is achieved by searching for the element using the unique data attribute assigned to the `Component`.
|
|
1008
|
+
* @param {Node} parent - The parent node to search within.
|
|
1009
|
+
* @return {Node} The root element of the component, or `null` if not found.
|
|
1010
|
+
* @private
|
|
1003
1011
|
*/
|
|
1004
1012
|
findElement(parent) {
|
|
1005
1013
|
return (parent || document).querySelector(`[${Component.DATA_ATTRIBUTE_UID}="${this.uid}"]`);
|
|
1006
1014
|
}
|
|
1007
1015
|
|
|
1008
|
-
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1016
|
+
/**
|
|
1017
|
+
* Retrieve the attributes to be applied to the element.
|
|
1018
|
+
* This includes attributes to be added, removed, and their HTML representation.
|
|
1019
|
+
* @return {object} An object containing the following properties:
|
|
1020
|
+
* @property {object} add - Attributes to be added to the element, with their values.
|
|
1021
|
+
* @property {object} remove - Attributes to be removed from the element.
|
|
1022
|
+
* @property {string} html - A string representation of the attributes for use in HTML.
|
|
1023
|
+
* @private
|
|
1011
1024
|
*/
|
|
1012
1025
|
getAttributes() {
|
|
1013
1026
|
const add = {};
|
|
@@ -1046,11 +1059,13 @@
|
|
|
1046
1059
|
return { add, remove, html : html.join(' ') };
|
|
1047
1060
|
}
|
|
1048
1061
|
|
|
1049
|
-
|
|
1062
|
+
/**
|
|
1050
1063
|
* Used internally on the render process.
|
|
1051
|
-
* Attach the
|
|
1064
|
+
* Attach the `Component` to the dom element providing `this.el`, delegate events,
|
|
1065
|
+
* subscribe to model changes and call `onRender` lifecycle method with `Component.RENDER_TYPE_HYDRATE` as argument.
|
|
1052
1066
|
* @param parent {node} The parent node.
|
|
1053
1067
|
* @return {Rasti.Component} The component instance.
|
|
1068
|
+
* @private
|
|
1054
1069
|
*/
|
|
1055
1070
|
hydrate(parent) {
|
|
1056
1071
|
// Listen to model changes and call onChange.
|
|
@@ -1058,13 +1073,13 @@
|
|
|
1058
1073
|
// Listen to state changes and call onChange.
|
|
1059
1074
|
if (this.state) this.subscribe(this.state);
|
|
1060
1075
|
|
|
1061
|
-
if (
|
|
1076
|
+
if (this.isContainer()) {
|
|
1077
|
+
this.children[0].hydrate(parent);
|
|
1078
|
+
this.el = this.children[0].el;
|
|
1079
|
+
} else {
|
|
1062
1080
|
this.el = this.findElement(parent);
|
|
1063
1081
|
this.delegateEvents();
|
|
1064
1082
|
this.children.forEach(child => child.hydrate(this.el));
|
|
1065
|
-
} else {
|
|
1066
|
-
this.children[0].hydrate(parent);
|
|
1067
|
-
this.el = this.children[0].el;
|
|
1068
1083
|
}
|
|
1069
1084
|
// Call `onRender` lifecycle method.
|
|
1070
1085
|
this.onRender.call(this, Component.RENDER_TYPE_HYDRATE);
|
|
@@ -1072,30 +1087,38 @@
|
|
|
1072
1087
|
return this;
|
|
1073
1088
|
}
|
|
1074
1089
|
|
|
1075
|
-
|
|
1076
|
-
* Used internally
|
|
1077
|
-
* Reuse a
|
|
1090
|
+
/**
|
|
1091
|
+
* Used internally on the render process.
|
|
1092
|
+
* Reuse a `Component` that has `key` when its parent is rendered.
|
|
1093
|
+
* Call `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as argument.
|
|
1078
1094
|
* @param parent {node} The parent node.
|
|
1079
1095
|
* @return {Rasti.Component} The component instance.
|
|
1096
|
+
* @private
|
|
1080
1097
|
*/
|
|
1081
1098
|
recycle(parent) {
|
|
1082
1099
|
// If component is a container, call recycle on its child.
|
|
1083
|
-
if (this.isContainer())
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1100
|
+
if (this.isContainer()) {
|
|
1101
|
+
this.children[0].recycle(parent);
|
|
1102
|
+
} else {
|
|
1103
|
+
// Find placeholder element to be replaced. It has same data attribute as this component.
|
|
1104
|
+
const toBeReplaced = this.findElement(parent);
|
|
1105
|
+
// Replace it with this.el.
|
|
1106
|
+
toBeReplaced.replaceWith(this.el);
|
|
1107
|
+
}
|
|
1088
1108
|
// Call `onRender` lifecycle method.
|
|
1089
1109
|
this.onRender.call(this, Component.RENDER_TYPE_RECYCLE);
|
|
1090
1110
|
// Return `this` for chaining.
|
|
1091
1111
|
return this;
|
|
1092
1112
|
}
|
|
1093
1113
|
|
|
1094
|
-
|
|
1095
|
-
*
|
|
1114
|
+
/**
|
|
1115
|
+
* Destroy the `Component`.
|
|
1116
|
+
* Destroy children components if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
1096
1117
|
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
1118
|
+
* @return {Rasti.View} Return `this` for chaining.
|
|
1097
1119
|
*/
|
|
1098
1120
|
destroy() {
|
|
1121
|
+
// Call super destroy method.
|
|
1099
1122
|
super.destroy.apply(this, arguments);
|
|
1100
1123
|
// Set destroyed flag to prevent a last render after destroyed.
|
|
1101
1124
|
this.destroyed = true;
|
|
@@ -1124,8 +1147,11 @@
|
|
|
1124
1147
|
}
|
|
1125
1148
|
|
|
1126
1149
|
/**
|
|
1127
|
-
* Lifecycle method. Called
|
|
1128
|
-
*
|
|
1150
|
+
* Lifecycle method. Called after the component is rendered.
|
|
1151
|
+
* - When the component is rendered for the first time, this method is called with `Component.RENDER_TYPE_HYDRATE` as the argument.
|
|
1152
|
+
* - When the component is updated or re-rendered, this method is called with `Component.RENDER_TYPE_RENDER` as the argument.
|
|
1153
|
+
* - When the component is recycled (reused with the same key), this method is called with `Component.RENDER_TYPE_RECYCLE` as the argument.
|
|
1154
|
+
* @param {string} type - The render type. Possible values are: `Component.RENDER_TYPE_HYDRATE`, `Component.RENDER_TYPE_RENDER` and `Component.RENDER_TYPE_RECYCLE`.
|
|
1129
1155
|
*/
|
|
1130
1156
|
onRender() {}
|
|
1131
1157
|
|
|
@@ -1207,22 +1233,26 @@
|
|
|
1207
1233
|
`<${tag} ${attributes} />`;
|
|
1208
1234
|
}
|
|
1209
1235
|
|
|
1210
|
-
|
|
1211
|
-
*
|
|
1236
|
+
/**
|
|
1237
|
+
* Render the `Component`.
|
|
1238
|
+
* - 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.
|
|
1239
|
+
* - 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.
|
|
1240
|
+
* - 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.
|
|
1241
|
+
* - If the active element is inside the component, it will retain focus after the render.
|
|
1242
|
+
* @return {Rasti.Component} The component instance.
|
|
1212
1243
|
*/
|
|
1213
1244
|
render() {
|
|
1214
1245
|
// Prevent a last re render if view is already destroyed.
|
|
1215
1246
|
if (this.destroyed) return this;
|
|
1216
|
-
|
|
1247
|
+
// If `this.el` is not present, render the view as a string and hydrate it.
|
|
1248
|
+
if (!this.el) {
|
|
1249
|
+
const fragment = this.createElement('template');
|
|
1250
|
+
fragment.innerHTML = this;
|
|
1251
|
+
this.hydrate(fragment.content);
|
|
1252
|
+
return this;
|
|
1253
|
+
}
|
|
1254
|
+
// Update attributes.
|
|
1217
1255
|
if (!this.isContainer()) {
|
|
1218
|
-
// If `this.el` is not present, render the view as a string and hydrate it.
|
|
1219
|
-
if (!this.el) {
|
|
1220
|
-
const fragment = this.createElement('template');
|
|
1221
|
-
fragment.innerHTML = this;
|
|
1222
|
-
this.hydrate(fragment.content);
|
|
1223
|
-
return this;
|
|
1224
|
-
}
|
|
1225
|
-
// Set `this.el` attributes.
|
|
1226
1256
|
const attributes = this.getAttributes();
|
|
1227
1257
|
// Remove attributes.
|
|
1228
1258
|
Object.keys(attributes.remove).forEach(key => {
|
|
@@ -1233,7 +1263,7 @@
|
|
|
1233
1263
|
this.el.setAttribute(key, attributes.add[key]);
|
|
1234
1264
|
});
|
|
1235
1265
|
}
|
|
1236
|
-
// Check for `template` to see if view has innerHTML.
|
|
1266
|
+
// Check for `template` to see if view has innerHTML or a child component.
|
|
1237
1267
|
if (this.template) {
|
|
1238
1268
|
// Store active element.
|
|
1239
1269
|
const activeElement = document.activeElement;
|
|
@@ -1274,8 +1304,8 @@
|
|
|
1274
1304
|
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
1275
1305
|
// Get next child element.
|
|
1276
1306
|
const nextEl = fragment.content.children[0];
|
|
1277
|
-
//
|
|
1278
|
-
|
|
1307
|
+
// Replace `this.el` with nextEl.
|
|
1308
|
+
this.el.replaceWith(nextEl);
|
|
1279
1309
|
// Set `this.el` to nextEl.
|
|
1280
1310
|
this.el = nextEl;
|
|
1281
1311
|
} else if (recycledChildren[0]) {
|
|
@@ -1311,10 +1341,10 @@
|
|
|
1311
1341
|
}
|
|
1312
1342
|
|
|
1313
1343
|
/**
|
|
1314
|
-
* Mark a string as safe HTML to be rendered.
|
|
1315
|
-
* Normally you don't need to use this method, as Rasti will automatically mark
|
|
1316
|
-
* as safe HTML when the component is @link
|
|
1317
|
-
* using the @link
|
|
1344
|
+
* Mark a string as safe HTML to be rendered.
|
|
1345
|
+
* Normally you don't need to use this method, as Rasti will automatically mark string literals
|
|
1346
|
+
* as safe HTML when the component is {@link #module_component_create created} and when
|
|
1347
|
+
* using the {@link #module_component__partial Component.partial} method.
|
|
1318
1348
|
* Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
|
|
1319
1349
|
* @static
|
|
1320
1350
|
* @param {string} value
|
|
@@ -1428,8 +1458,8 @@
|
|
|
1428
1458
|
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
1429
1459
|
* const Navigation = Component.create`
|
|
1430
1460
|
* <nav>
|
|
1431
|
-
* ${
|
|
1432
|
-
* item =>
|
|
1461
|
+
* ${({ options, partial }) => options.items.map(
|
|
1462
|
+
* item => partial`<${Button}>${item.label}</${Button}>`
|
|
1433
1463
|
* )}
|
|
1434
1464
|
* </nav>
|
|
1435
1465
|
* `;
|
|
@@ -1448,11 +1478,11 @@
|
|
|
1448
1478
|
* ${self => self.renderChildren()}
|
|
1449
1479
|
* </button>
|
|
1450
1480
|
* `;
|
|
1451
|
-
* // Create a container
|
|
1481
|
+
* // Create a container that renders a Button component.
|
|
1452
1482
|
* const ButtonOk = Component.create`
|
|
1453
1483
|
* <${Button} className="ok">Ok</${Button}>
|
|
1454
1484
|
* `;
|
|
1455
|
-
* // Create a
|
|
1485
|
+
* // Create a container that renders a Button component, using a function.
|
|
1456
1486
|
* const ButtonCancel = Component.create(() => Button.mount({
|
|
1457
1487
|
* className: 'cancel',
|
|
1458
1488
|
* renderChildren: () => 'Cancel'
|
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";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}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((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(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=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),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])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const l=(t,e)=>s(t,e,e),o=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let o;if(n){if(t!==n)return h;const s=a(u(i,e),e);o=function(){return r(s.map((t=>l(t,this))))}}return e.push((function(){const e=c(s,(t=>l(t,this))).all;return o&&(e.renderChildren=o.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,l,o,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=l,E.close=void 0!==a?e[a]:o,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class p extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{f[e]&&(this[e]=t[e])})),this.options=t,this.partial=this.partial.bind(this),this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(o(t,e),e),e).map((t=>l(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(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.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(o(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:o,close:u}=d(g,e);if(i=function(){return p.sanitize(l(t,this))},n=function(){return c(s,(t=>l(t,this))).attributes},E=function(){const t=c(s,(t=>l(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=l(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=o?a(o,e):[];f=function(e){return r(t.map((t=>l(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(t):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(l(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,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 l=Object.keys(h);if(l.length&&(this._pending=["change",this,h,...n]),l.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}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|
|
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";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}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((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i=["el","tag","attributes","events","model","template","onDestroy"];class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="rasti-"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],i.forEach((e=>{e in t&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(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=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),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])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const l=(t,e)=>s(t,e,e),o=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let o;if(n){if(t!==n)return h;const s=a(u(i,e),e);o=function(){return r(s.map((t=>l(t,this))))}}return e.push((function(){const e=c(s,(t=>l(t,this))).all;return o&&(e.renderChildren=o.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,l,o,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=l,E.close=void 0!==a?e[a]:o,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f=["key","state","onCreate","onChange","onRender"];class p extends n{constructor(t={}){super(...arguments),f.forEach((e=>{e in t&&(this[e]=t[e])})),this.options=t,this.partial=this.partial.bind(this),this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())this.children[0].recycle(t);else{this.findElement(t).replaceWith(this.el)}return this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(o(t,e),e),e).map((t=>l(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}if(!this.isContainer()){const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(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.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(o(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:o,close:u}=d(g,e);if(i=function(){return p.sanitize(l(t,this))},n=function(){return c(s,(t=>l(t,this))).attributes},E=function(){const t=c(s,(t=>l(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=l(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=o?a(o,e):[];f=function(e){return r(t.map((t=>l(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(t):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(l(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,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 l=Object.keys(h);if(l.length&&(this._pending=["change",this,h,...n]),l.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}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|