solarite 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Template.js CHANGED
@@ -1,7 +1,7 @@
1
- import {assert} from "./assert.js";
1
+ import assert from "./assert.js";
2
2
  import {getObjectHash, getObjectId} from "./hash.js";
3
3
  import Globals from "./Globals.js";
4
- import {RootNodeGroup} from "./NodeGroup.js";
4
+ import RootNodeGroup from "./RootNodeGroup.js";
5
5
 
6
6
  /**
7
7
  * The html strings and evaluated expressions from an html tagged template.
@@ -9,24 +9,24 @@ import {RootNodeGroup} from "./NodeGroup.js";
9
9
  * Although the reference to the html strings is shared among templates. */
10
10
  export default class Template {
11
11
 
12
- /** @type {(Template|string|function)|(Template|string|function)[]} Evaulated expressions. */
13
- exprs = []
12
+ /** @type {Expr[]} Evaulated expressions. */
13
+ 'exprs' = []
14
14
 
15
15
  /** @type {string[]} */
16
- html = [];
16
+ 'html' = [];
17
17
 
18
18
  /** @type {Array} Used for toJSON() and getObjectHash(). Stores values used to quickly create a string hash of this template. */
19
19
  hashedFields;
20
20
 
21
- /** @type {NodeGroup} */
22
- nodeGroup;
21
+ isText;
23
22
 
24
23
  /**
25
24
  *
26
25
  * @param htmlStrings {string[]}
27
26
  * @param exprs {*[]} */
28
- constructor(htmlStrings, exprs) {
27
+ constructor(htmlStrings=[''], exprs=[]) {
29
28
  this.html = htmlStrings;
29
+
30
30
  this.exprs = exprs;
31
31
 
32
32
  //this.trace = new Error().stack.split(/\n/g)
@@ -50,51 +50,50 @@ export default class Template {
50
50
  * Called by JSON.serialize when it encounters a Template.
51
51
  * This prevents the hashed version from being too large. */
52
52
  toJSON() {
53
- if (!this.hashedFields)
53
+ if (this.hashedFields===undefined)
54
54
  this.hashedFields = [getObjectId(this.html), this.exprs];
55
55
 
56
56
  return this.hashedFields
57
57
  }
58
58
 
59
59
  /**
60
- * Render the main template, which may indirectly call renderTemplate() to create children.
61
- * @param el {HTMLElement}
60
+ * Render the main (root) template.
61
+ * @param el {?HTMLElement} Null if we're rendering to a standalone element.
62
62
  * @param options {RenderOptions}
63
63
  * @return {?DocumentFragment|HTMLElement} */
64
- render(el=null, options={}) {
65
- let ng;
66
- let standalone = !el;
67
- let firstTime = false;
68
-
69
- // Rendering a standalone element.
70
- // TODO: figure out when to not use RootNodeGroup
71
- if (standalone) {
72
- ng = new RootNodeGroup(this, null, options);
73
- el = ng.getRootNode();
74
- Globals.nodeGroups.set(el, ng); // Why was this commented out?
75
- firstTime = true;
64
+ 'render'(el=null, options={}) {
65
+
66
+
67
+
68
+ let ng = el && Globals.rootNodeGroups.get(el);
69
+ if (!ng) {
70
+ ng = new RootNodeGroup(this, null, el, options);
71
+ if (!el) // null if it's a standalone elment.
72
+ el = ng.getRootNode();
73
+ Globals.rootNodeGroups.set(el, ng); // All tests still pass if this is commented out!
76
74
  }
77
- else {
78
- ng = Globals.nodeGroups.get(el);
79
- if (!ng) {
80
- ng = new RootNodeGroup(this, el, options);
81
- Globals.nodeGroups.set(el, ng); // Why was this commented out?
82
- firstTime = true;
83
- }
84
75
 
85
- // This can happen if we try manually rendering one template to a NodeGroup that was created expecting a different template.
86
- // These don't always have the same length, for example if one attribute has multiple expressions.
87
- if (ng.paths.length === 0 && this.exprs.length || ng.paths.length > this.exprs.length)
88
- throw new Error(`Solarite Error: Parent HTMLElement ${ng.template.html.join('${...}')} and ${ng.paths.length} \${value} placeholders can't accomodate a Template with ${this.exprs.length} values.`); }
76
+ // Make sure the expresion count matches match the Path "hole" count.
77
+ // This can happen if we try manually rendering one template to a NodeGroup that was created expecting a different template.
78
+ // These don't always have the same length, for example if one attribute has multiple expressions.
79
+ // if (ng.paths.length === 0 && this.exprs.length || ng.paths.length > this.exprs.length)
80
+ // throw new Error(
81
+ // `Solarite Error: Parent HTMLElement ${ng.template.html.join('${...}')} and ${ng.paths.length} \${value} ` +
82
+ // `placeholders can't accomodate a Template with ${this.exprs.length} values.`);
89
83
 
90
84
  // Creating the root nodegroup also renders it.
91
85
  // If we didn't just create it, we need to render it.
92
- if (!firstTime) {
93
- if (this.html?.length === 1 && !this.html[0])
94
- el.innerHTML = ''; // Fast path for empty component.
95
- else {
96
- ng.applyExprs(this.exprs);
97
- }
86
+ if (this.html?.length === 1 && !this.html[0]) // An empty string.
87
+ el.innerHTML = ''; // Fast path for empty component.
88
+ else {
89
+
90
+ let oldKey = ng.exactKey;
91
+ let newKey = this.getExactKey();
92
+ ng.applyExprs(this.exprs, oldKey !== newKey);
93
+ ng.exactKey = newKey;
94
+
95
+ //if (firstTime)
96
+ // ng.instantiateStaticComponents(ng.staticComponents);
98
97
  }
99
98
 
100
99
  ng.exprsToRender = new Map();
@@ -102,7 +101,7 @@ export default class Template {
102
101
  }
103
102
 
104
103
  getExactKey() {
105
- if (!this.exactKey) {
104
+ if (this.exactKey===undefined) {
106
105
  if (this.exprs.length)
107
106
  this.exactKey = getObjectHash(this);// calls this.toJSON().
108
107
  else // Don't hash plain html.
@@ -113,7 +112,7 @@ export default class Template {
113
112
 
114
113
  getCloseKey() {
115
114
  //console.log(this.exprs.length)
116
- if (!this.closeKey) {
115
+ if (this.closeKey===undefined) {
117
116
  if (this.exprs.length)
118
117
  this.closeKey = /*'@' + */this.toJSON()[0];
119
118
  else
@@ -124,6 +123,138 @@ export default class Template {
124
123
 
125
124
  return this.closeKey;
126
125
  }
126
+
127
+ /**
128
+ * @param tag {string}
129
+ * @param props {?Record<string, any>}
130
+ * @param children
131
+ * @returns {Template} */
132
+ static fromJsx(tag, props, children) {
133
+
134
+ // HTML void elements that must not have closing tags
135
+ const isVoid = selfClosingTags.has(tag.toLowerCase());
136
+
137
+ // Build htmlStrings/exprs so Shell can place placeholders in attribute values and child content.
138
+ let htmlStrings = [];
139
+ let templateExprs = [];
140
+
141
+ // Opening tag
142
+ let open = `<${tag}`;
143
+
144
+ // Attributes
145
+ if (props && typeof props === 'object') {
146
+ for (let name in props) {
147
+ let value = props[name];
148
+
149
+ // id and data-id are static in templates — never expressions
150
+ if (name === 'id' || name === 'data-id') {
151
+ // Write directly into the opening string with quotes
152
+ open += ` ${name}="${value}"`;
153
+ continue;
154
+ }
155
+
156
+ // Dynamic attribute value: functions are unquoted (e.g., onclick=${fn}), others quoted
157
+ if (typeof value === 'function') {
158
+ open += ` ${name}=`;
159
+ htmlStrings.push(open);
160
+ templateExprs.push(value);
161
+ // reset so subsequent attributes start fresh (e.g., ' title=')
162
+ open = ``;
163
+ }
164
+ else {
165
+ open += ` ${name}=`;
166
+ htmlStrings.push(open);
167
+ templateExprs.push(value);
168
+ // reset so subsequent attributes start fresh (e.g., ' title=')
169
+ open = ``;
170
+ }
171
+ }
172
+ }
173
+
174
+ // Finalize opening tag precisely to match tagged template splitting
175
+ if (!isVoid) {
176
+ const pushedAny = htmlStrings.length > 0;
177
+ // If nothing pushed yet (no dynamic attrs), push the entire open + '>'
178
+ if (!pushedAny)
179
+ htmlStrings.push(open + '>');
180
+ else {
181
+ // If we were in a quoted attr (open === '"'), then the string after expr is '">' ;
182
+ // Otherwise (function-valued attr), the string after expr is just '>'
183
+ htmlStrings.push(open === '"' ? '">' : '>');
184
+ }
185
+
186
+ for (let child of children)
187
+ addChild(child, htmlStrings, templateExprs);
188
+ }
189
+
190
+ // Closing tag (not for void tags)
191
+ if (!isVoid) {
192
+ // If we never emitted the '>' for the open tag (no children were added),
193
+ // then it was appended above before children. Now just add the closing tag to the last html segment.
194
+ let lastIdx = htmlStrings.length - 1;
195
+ htmlStrings[lastIdx] += `</${tag}>`;
196
+ }
197
+ else {
198
+ // Void element: ensure we emitted a trailing '>' segment
199
+ const pushedAny = htmlStrings.length > 0;
200
+ if (!pushedAny)
201
+ htmlStrings.push(open + '>');
202
+ else
203
+ htmlStrings.push('>');
204
+ }
205
+
206
+ // Ensure invariant
207
+ //assert(htmlStrings.length === templateExprs.length + 1);
208
+ //console.log([htmlStrings, templateExprs])
209
+ return new Template(htmlStrings, templateExprs);
210
+ }
211
+ }
212
+
213
+
214
+ const selfClosingTags = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
215
+
216
+
217
+ /**
218
+ * Add child Templates that were already created via h() and Template.fromJsx()
219
+ * @param template {Template}
220
+ * @param html {string[]}
221
+ * @param exprs {any[]} */
222
+ const addChild = (template, html, exprs) => {
223
+
224
+ if (Array.isArray(template)) {
225
+ for (let c of template)
226
+ addChild(c, html, exprs);
227
+ }
228
+ else {
229
+ let flatten = false;
230
+ if (template instanceof Template) {
231
+ // Heuristic to match tagged-template splitting:
232
+ // - Flatten if the child has expressions (so JSX can inline attribute/value placeholders like tagged literals would).
233
+ // - Also flatten void elements (e.g., <img>) so they inline like literals.
234
+ // - Otherwise, keep as a dynamic child placeholder to match cases where the tagged template used an expression child.
235
+ const childHasExprs = template.exprs.length > 0;
236
+ if (childHasExprs)
237
+ flatten = true;
238
+ else {
239
+ const m = (template.html[0] || '').match(/^<([a-zA-Z][\w:-]*)/);
240
+ const childTag = m ? m[1].toLowerCase() : '';
241
+ flatten = selfClosingTags.has(childTag);
242
+ }
243
+ }
244
+
245
+ if (flatten) {
246
+ // Flatten/interleave into current segment to match tagged template splitting
247
+ html[html.length - 1] += template.html[0];
248
+ for (let i = 0; i < template.exprs.length; i++) {
249
+ exprs.push(template.exprs[i]);
250
+ html.push(template.html[i + 1] ?? '');
251
+ }
252
+ } else {
253
+ // Keep as dynamic child
254
+ exprs.push(template);
255
+ html.push('');
256
+ }
257
+ }
127
258
  }
128
259
 
129
260
 
package/src/Util.js CHANGED
@@ -18,12 +18,27 @@ let Util = {
18
18
  return true; // the same.
19
19
  },
20
20
 
21
+ /**
22
+ * Convert HTMLElement attributes to an object.
23
+ * Converts dash (kebob-case) attribute names to camelCase.
24
+ * See also Solarite.getAttribs()
25
+ * @param el {HTMLElement}
26
+ * @param ignore {?string} Optionally ignore this attribute.
27
+ * @return {Object} */
28
+ attribsToObject(el, ignore=null) {
29
+ let result = {};
30
+ for (let attrib of el.attributes)
31
+ if (attrib.name !== ignore)
32
+ result[Util.dashesToCamel(attrib.name)] = attrib.value;
33
+ return result;
34
+ },
35
+
21
36
  bindId(root, el) {
22
37
  let id = el.getAttribute('data-id') || el.getAttribute('id');
23
38
  if (id) { // If something hasn't removed the id.
24
39
 
25
40
  // Don't allow overwriting existing class properties if they already have a non-Node value.
26
- if (root[id] && !(root[id] instanceof Node))
41
+ if (root[id] && !(root[id]?.nodeType))
27
42
  throw new Error(`${root.constructor.name}.${id} already has a value. ` +
28
43
  `Can't set it as a reference to <${el.tagName.toLowerCase()} id="${id}">`);
29
44
 
@@ -32,26 +47,50 @@ let Util = {
32
47
  },
33
48
 
34
49
  /**
50
+ * If the style tab has a global attribute:
51
+ * 1. Put it in the document head as <style data-style="tag-name">...</style>
52
+ * 2. Replace the :host {...} CSS selector as tag-name {...}.
53
+ * Otherwise keep it where it is and:
54
+ * 1. Add data-style="1" attribute to the root element.
55
+ * 2. Replace the :host {...} selector in the style as tag-name[data-style='1'] {...}
35
56
  * @param style {HTMLStyleElement}
36
57
  * @param root {HTMLElement} */
37
58
  bindStyles(style, root) {
38
- let styleId = root.getAttribute('data-style');
39
- if (!styleId) {
40
- // Keep track of one style id for each class.
41
- // TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
42
- if (!root.constructor.styleId)
43
- root.constructor.styleId = 1;
44
- styleId = root.constructor.styleId++;
45
-
46
- root.setAttribute('data-style', styleId)
59
+
60
+ let tagName = root.tagName.toLowerCase();
61
+ let styleId, attribSelector;
62
+
63
+ if (style.hasAttribute('global') || style.hasAttribute('data-global')) {
64
+ styleId = tagName;
65
+ attribSelector = '';
66
+ let doc = Globals.doc || root.ownerDocument || document;
67
+ if (!doc.head.querySelector(`style[data-style="${styleId}"]`)) {
68
+ doc.head.append(style)
69
+ style.setAttribute('data-style', styleId);
70
+ }
71
+ else // TODO: Make sure the style has no expressions.
72
+ style.remove(); // already in the head.
73
+ }
74
+ else {
75
+ let styleId = root.getAttribute('data-style');
76
+ if (!styleId) {
77
+ // Keep track of one style id for each class.
78
+ // TODO: Put this outside the class in a map, so it doesn't conflict with static properties.
79
+ if (!root.constructor.styleId)
80
+ root.constructor.styleId = 1;
81
+ styleId = root.constructor.styleId++;
82
+
83
+ root.setAttribute('data-style', styleId);
84
+ }
85
+
86
+ attribSelector = `[data-style="${styleId}"]`;
47
87
  }
48
88
 
49
89
  // Replace ":host" with "tagName[data-style=...]" in the css.
50
- let tagName = root.tagName.toLowerCase();
51
90
  for (let child of style.childNodes) {
52
91
  if (child.nodeType === 3) {
53
92
  let oldText = child.textContent;
54
- let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}[data-style="${styleId}"]`)
93
+ let newText = oldText.replace(/:host(?=[^a-z0-9_])/gi, `${tagName}${attribSelector}`)
55
94
  if (oldText !== newText)
56
95
  child.textContent = newText;
57
96
  }
@@ -98,48 +137,18 @@ let Util = {
98
137
  return str.replace(/-([a-z])/g, g => g[1].toUpperCase());
99
138
  },
100
139
 
101
-
102
- /**
103
- * A generator function that recursively traverses and flattens a value.
104
- *
105
- * - If the input is an array, it recursively traverses and flattens the array.
106
- * - If the input is a function, it calls the function, replaces the function
107
- * with its result, and flattens the result if necessary. It will recursively
108
- * call functions that return other functions.
109
- * - Otherwise it yields the value as is.
110
- *
111
- * This function does not create a new array for the flattened values. Instead,
112
- * it lazily yields each item as it is encountered. This can be more memory-efficient
113
- * for large or deeply nested structures.
114
- *
115
- * @param {any} value - The value to flatten. Can be an array, object, function, or primitive.
116
- * @yields {any} - The next item in the flattened structure.
117
- *
118
- * @example
119
- * const complexArray = [
120
- * 1,
121
- * [2, () => 3, [4, () => [5, 6]], { a: 'object' }],
122
- * () => () => 7,
123
- * () => [() => 8, 9],
124
- * ]; *
125
- * for (const item of flatten(complexArray))
126
- * console.log(item); // Outputs: 1, 2, 3, 4, 5, 6, { a: 'object' }, 7, 8, 9
127
- */
128
- // *flatten(value) {
129
- // if (Array.isArray(value)) {
130
- // for (const item of value) {
131
- // yield* Util.flatten(item); // Recursively flatten arrays
132
- // }
133
- // } else if (typeof value === 'function') {
134
- // const result = value();
135
- // yield* Util.flatten(result); // Recursively flatten the result of a function
136
- // } else
137
- // yield value; // Yield primitive values as is
138
- // },
140
+ defineClass(Class, tagName) {
141
+ if (!customElements[getName](Class)) { // If not previously defined.
142
+ tagName = tagName || Util.camelToDashes(Class.name)
143
+ if (!tagName.includes('-')) // Browsers require that web components always have a dash in the name.
144
+ tagName += '-element';
145
+ customElements[define](tagName, Class)
146
+ }
147
+ },
139
148
 
140
149
  /**
141
150
  * Get the value of an input as the most appropriate JavaScript type.
142
- * @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLDivElement}
151
+ * @param node {HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement|HTMLElement}
143
152
  * @return {string|string[]|number|[]|File[]|Date|boolean} */
144
153
  getInputValue(node) {
145
154
  // .type is a built-in DOM property
@@ -153,6 +162,8 @@ let Util = {
153
162
  return node.valueAsDate; // Date Object
154
163
  if (node.type === 'select-multiple') // <select multiple>
155
164
  return [...node.selectedOptions].map(option => option.value); // Array of Strings
165
+ if (node.hasAttribute('contenteditable'))
166
+ return node.innerHTML;
156
167
 
157
168
  return node.value; // String
158
169
  },
@@ -223,7 +234,7 @@ let Util = {
223
234
 
224
235
  /**
225
236
  * Use an array as the value of a map, appending to it when we add.
226
- * Used by watch.js.
237
+ * Used only by watch.js.
227
238
  * @param map {Map|WeakMap|Object}
228
239
  * @param key
229
240
  * @param value */
@@ -237,6 +248,11 @@ let Util = {
237
248
  result.push(value);
238
249
  },
239
250
 
251
+ saveOrphans(nodes) {
252
+ let fragment = Globals.doc.createDocumentFragment();
253
+ fragment.append(...nodes);
254
+ },
255
+
240
256
  /**
241
257
  * Remove nodes from the beginning and end that are not:
242
258
  * 1. Elements.
@@ -263,6 +279,12 @@ let Util = {
263
279
  }
264
280
  };
265
281
 
282
+
283
+
284
+ // Trick to prevent minifier from renaming these methods.
285
+ let define = 'define';
286
+ let getName = 'getName';
287
+
266
288
  export default Util;
267
289
 
268
290
 
package/src/assert.js CHANGED
@@ -1,10 +1,10 @@
1
- //#IFDEV
1
+
2
2
  /*@__NO_SIDE_EFFECTS__*/
3
- export function assert(val) {
3
+ export default function assert(val) {
4
+ //#IFDEV
4
5
  if (!val) {
5
- debugger;
6
+ //debugger;
6
7
  throw new Error('Assertion failed: ' + val);
7
8
  }
9
+ //#ENDIF
8
10
  }
9
-
10
- //#ENDIF
package/src/getArg.js CHANGED
@@ -2,10 +2,11 @@ import Util from "./Util.js";
2
2
 
3
3
 
4
4
  /**
5
+ * @deprecated Inherit from Solarite and pass arribs to super() instead.
5
6
  * There are three ways to create an instance of a Solarite Component:
6
- * 1. new ComponentName(); // direct class instantiation
7
- * 2. this.html = r`<div><component-name></component-name></div>; // as a child of another RedComponent.
8
- * 3. <body><component-name></component-name></body> // in the Document html.
7
+ * 1. new ComponentName(3); // direct class instantiation
8
+ * 2. h(this)`<div><component-name user-id=${3}></component-name></div>; // as a child of another Component.
9
+ * 3. <body><component-name user-id="3"></component-name></body> // in the Document html.
9
10
  *
10
11
  * When created via #3, Solarite has no way to pass attributes as arguments to the constructor. So to make
11
12
  * sure we get the correct value via all three paths, we write our constructors according to the following
@@ -13,40 +14,38 @@ import Util from "./Util.js";
13
14
  * Browsers make all html attribute names lowercase.
14
15
  *
15
16
  * @example
16
- * constructor({name, userid=1}={}) {
17
+ * constructor({name, userId=1}={}) {
17
18
  * super();
18
19
  *
19
20
  * // Get value from "name" attriute if persent, otherwise from name constructor arg.
20
21
  * this.name = getArg(this, 'name', name);
21
22
  *
22
23
  * // Optionally convert the value to an integer.
23
- * this.userId = getArg(this, 'userid', userid, ArgType.Int);
24
+ * this.userId = getArg(this, 'user-id', userId, ArgType.Int);
24
25
  * }
25
26
  *
26
27
  * @param el {HTMLElement}
27
28
  * @param attributeName {string} Attribute name. Not case-sensitive.
28
- * @param defaultValue {*} Default value to use if attribute doesn't exist.
29
+ * @param defaultValue {*} Default value to use if attribute doesn't exist. Typically the argument from the constructor.
29
30
  * @param type {ArgType|function|Class|*[]}
30
31
  * If an array, use the value if it's in the array, otherwise return undefined.
31
32
  * If it's a function, pass the value to the function and return the result.
32
- * @param fallback {*} If the defaultValue is undefiend and type can't be parsed as the given type, use this value.
33
- * TODO: Should this be merged with the defaultValue argument?
34
- * @return {*} Undefined if attribute isn't set. */
35
- export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String, fallback=undefined) {
33
+ * @return {*} Undefined if attribute isn't set and there's no defaultValue, or if the value couldn't be parsed as the type. */
34
+ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.String) {
36
35
  let val = defaultValue;
37
36
  let attrVal = el.getAttribute(attributeName) || el.getAttribute(Util.camelToDashes(attributeName));
38
37
  if (attrVal !== null) // If attribute doesn't exist.
39
38
  val = attrVal;
40
-
39
+
41
40
  if (Array.isArray(type))
42
- return type.includes(val) ? val : fallback;
43
-
41
+ return type.includes(val) ? val : undefined;
42
+
44
43
  if (typeof type === 'function') {
45
44
  return type.constructor
46
45
  ? new type(val) // arg type is custom Class
47
46
  : type(val); // arg type is custom function
48
47
  }
49
-
48
+
50
49
  // If bool, it's true as long as it exists and its value isn't falsey.
51
50
  if (type===ArgType.Bool) {
52
51
  let lAttrVal = typeof val === 'string' ? val.toLowerCase() : val;
@@ -54,20 +53,17 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
54
53
  return false;
55
54
  if (['true', true].includes(lAttrVal) || parseFloat(lAttrVal) !== 0)
56
55
  return true;
57
- return fallback;
56
+ return undefined;
58
57
  }
59
-
58
+
60
59
  // Attribute doesn't exist
61
- let result;
62
60
  switch (type) {
63
61
  case ArgType.Int:
64
- result = parseInt(val);
65
- return isNaN(result) ? fallback : result;
62
+ return parseInt(val);
66
63
  case ArgType.Float:
67
- result = parseFloat(val);
68
- return isNaN(result) ? fallback : result;
64
+ return parseFloat(val);
69
65
  case ArgType.String:
70
- return [undefined, null, false].includes(val) ? '' : val+'';
66
+ return [undefined, null, false].includes(val) ? '' : (val+'');
71
67
  case ArgType.Json:
72
68
  case ArgType.Eval:
73
69
  if (typeof val === 'string' && val.length)
@@ -89,6 +85,7 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
89
85
 
90
86
 
91
87
  /**
88
+ * @deprecated for Solarite.getAttribs()
92
89
  * Experimental. Set multiple arguments/attributes all at once.
93
90
  * @param el {HTMLElement}
94
91
  * @param args {Record<string, any>}
@@ -97,6 +94,10 @@ export function getArg(el, attributeName, defaultValue=undefined, type=ArgType.S
97
94
  * @example
98
95
  * constructor({user, path}={}) {
99
96
  * setArgs(this, arguments[0], {user: User, path: ArgType.String});
97
+ *
98
+ * // Equivalent to:
99
+ * this.user = getArg(this, user, 'user', User); // or new User(user);
100
+ * this.path = getArg(this, path, 'path', ArgType.String);
100
101
  * }
101
102
  */
102
103
  export function setArgs(el, args, types) {
@@ -106,15 +107,16 @@ export function setArgs(el, args, types) {
106
107
 
107
108
 
108
109
  /**
110
+ * @deprecated
109
111
  * @enum */
110
112
  var ArgType = {
111
-
113
+
112
114
  /**
113
115
  * false, 0, null, undefined, '0', and 'false' (case-insensitive) become false.
114
116
  * Anything else, including empty string becomes true.
115
117
  * Empty string is true because attributes with no value should be evaulated as true. */
116
118
  Bool: 'Bool',
117
-
119
+
118
120
  Int: 'Int',
119
121
  Float: 'Float',
120
122
  String: 'String',