@srfnstack/fntags 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/fntags.mjs +91 -86
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srfnstack/fntags",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "author": "Robert Kempton <r@snow87.com>",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/srfnstack/fntags",
package/src/fntags.mjs CHANGED
@@ -10,9 +10,10 @@
10
10
  * @param children optional attrs and children for the element
11
11
  * @returns HTMLElement an html element
12
12
  *
13
-
14
13
  */
15
- export const h = (tag, ...children) => {
14
+ export function h () {
15
+ const tag = arguments[0]
16
+ let firstChildIdx = 1
16
17
  let element
17
18
  if (tag.startsWith('ns=')) {
18
19
  element = document.createElementNS(...(tag.slice(3).split('|')))
@@ -20,8 +21,9 @@ export const h = (tag, ...children) => {
20
21
  element = document.createElement(tag)
21
22
  }
22
23
 
23
- if (isAttrs(children[0])) {
24
- const attrs = children.shift()
24
+ if (isAttrs(arguments[firstChildIdx])) {
25
+ const attrs = arguments[firstChildIdx]
26
+ firstChildIdx += 1
25
27
  for (const a in attrs) {
26
28
  let attr = attrs[a]
27
29
  if (typeof attr === 'function' && attr.isBoundAttribute) {
@@ -31,7 +33,8 @@ export const h = (tag, ...children) => {
31
33
  setAttribute(a, attr, element)
32
34
  }
33
35
  }
34
- for (const child of children) {
36
+ for (let i = firstChildIdx; i < arguments.length; i++) {
37
+ const child = arguments[i]
35
38
  if (Array.isArray(child)) {
36
39
  for (const c of child) {
37
40
  element.append(renderNode(c))
@@ -76,23 +79,23 @@ export const fnstate = (initialValue, mapKey) => {
76
79
  }
77
80
 
78
81
  /**
79
- * Bind the values of this state to the given element.
80
- * Values are items/elements of an array.
81
- * If the current value is not an array, this will behave the same as bindAs.
82
- *
83
- * @param parent The parent to bind the children to.
84
- * @param element The element to bind to. If not a function, an update function must be passed
85
- * @param update If passed this will be executed directly when the state of any value changes with no other intervention
86
- */
82
+ * Bind the values of this state to the given element.
83
+ * Values are items/elements of an array.
84
+ * If the current value is not an array, this will behave the same as bindAs.
85
+ *
86
+ * @param parent The parent to bind the children to.
87
+ * @param element The element to bind to. If not a function, an update function must be passed
88
+ * @param update If passed this will be executed directly when the state of any value changes with no other intervention
89
+ */
87
90
  ctx.state.bindChildren = (parent, element, update) => doBindChildren(ctx, parent, element, update)
88
91
 
89
92
  /**
90
- * Bind this state to the given element
91
- *
92
- * @param element The element to bind to. If not a function, an update function must be passed
93
- * @param update If passed this will be executed directly when the state changes with no other intervention
94
- * @returns {(HTMLDivElement|Text)[]|HTMLDivElement|Text}
95
- */
93
+ * Bind this state to the given element
94
+ *
95
+ * @param element The element to bind to. If not a function, an update function must be passed
96
+ * @param update If passed this will be executed directly when the state changes with no other intervention
97
+ * @returns {(HTMLDivElement|Text)[]|HTMLDivElement|Text}
98
+ */
96
99
  ctx.state.bindAs = (element, update) => doBindAs(ctx, element, update)
97
100
 
98
101
  /**
@@ -103,63 +106,63 @@ export const fnstate = (initialValue, mapKey) => {
103
106
  ctx.state.bindSelf = () => doBindAs(ctx, ctx.state)
104
107
 
105
108
  /**
106
- * Bind attribute values to state changes
107
- * @param attribute A function that returns an attribute value
108
- * @returns {function(): *} A function that calls the passed function, with some extra metadata
109
- */
109
+ * Bind attribute values to state changes
110
+ * @param attribute A function that returns an attribute value
111
+ * @returns {function(): *} A function that calls the passed function, with some extra metadata
112
+ */
110
113
  ctx.state.bindAttr = (attribute) => doBindAttr(ctx.state, attribute)
111
114
 
112
115
  /**
113
- * Bind style values to state changes
114
- * @param style A function that returns a style's value
115
- * @returns {function(): *} A function that calls the passed function, with some extra metadata
116
- */
116
+ * Bind style values to state changes
117
+ * @param style A function that returns a style's value
118
+ * @returns {function(): *} A function that calls the passed function, with some extra metadata
119
+ */
117
120
  ctx.state.bindStyle = (style) => doBindStyle(ctx.state, style)
118
121
 
119
122
  /**
120
- * Bind select and deselect to an element
121
- * @param element The element to bind to. If not a function, an update function must be passed
122
- * @param update If passed this will be executed directly when the state changes with no other intervention
123
- */
123
+ * Bind select and deselect to an element
124
+ * @param element The element to bind to. If not a function, an update function must be passed
125
+ * @param update If passed this will be executed directly when the state changes with no other intervention
126
+ */
124
127
  ctx.state.bindSelect = (element, update) => doBindSelect(ctx, element, update)
125
128
 
126
129
  /**
127
- * Bind select and deselect to an attribute
128
- * @param attribute A function that returns an attribute value
129
- * @returns {function(): *} A function that calls the passed function, with some extra metadata
130
- */
130
+ * Bind select and deselect to an attribute
131
+ * @param attribute A function that returns an attribute value
132
+ * @returns {function(): *} A function that calls the passed function, with some extra metadata
133
+ */
131
134
  ctx.state.bindSelectAttr = (attribute) => doBindSelectAttr(ctx, attribute)
132
135
 
133
136
  /**
134
- * Mark the element with the given key as selected. This causes the bound select functions to be executed.
135
- */
137
+ * Mark the element with the given key as selected. This causes the bound select functions to be executed.
138
+ */
136
139
  ctx.state.select = (key) => doSelect(ctx, key)
137
140
 
138
141
  /**
139
- * Get the currently selected key
140
- * @returns {*}
141
- */
142
+ * Get the currently selected key
143
+ * @returns {*}
144
+ */
142
145
  ctx.state.selected = () => ctx.selected
143
146
 
144
147
  ctx.state.isFnState = true
145
148
 
146
149
  /**
147
- * Perform an Object.assign on the current state using the provided update
148
- */
150
+ * Perform an Object.assign on the current state using the provided update
151
+ */
149
152
  ctx.state.assign = (update) => ctx.state(Object.assign(ctx.currentValue, update))
150
153
 
151
154
  /**
152
- * Get a value at the given property path, an error is thrown if the value is not an object
153
- *
154
- * This returns a reference to the real current value. If you perform any modifications to the object, be sure to call setPath after you're done or the changes
155
- * will not be reflected correctly.
156
- */
155
+ * Get a value at the given property path, an error is thrown if the value is not an object
156
+ *
157
+ * This returns a reference to the real current value. If you perform any modifications to the object, be sure to call setPath after you're done or the changes
158
+ * will not be reflected correctly.
159
+ */
157
160
  ctx.state.getPath = (path) => {
158
161
  if (typeof path !== 'string') {
159
- throw new Error('Invalid path').stack
162
+ throw new Error('Invalid path')
160
163
  }
161
164
  if (typeof ctx.currentValue !== 'object') {
162
- throw new Error('Value is not an object').stack
165
+ throw new Error('Value is not an object')
163
166
  }
164
167
  return path
165
168
  .split('.')
@@ -176,11 +179,11 @@ export const fnstate = (initialValue, mapKey) => {
176
179
  }
177
180
 
178
181
  /**
179
- * Set a value at the given property path
180
- * @param path The JSON path of the value to set
181
- * @param value The value to set the path to
182
- * @param fillWithObjects Whether to non object values with new empty objects.
183
- */
182
+ * Set a value at the given property path
183
+ * @param path The JSON path of the value to set
184
+ * @param value The value to set the path to
185
+ * @param fillWithObjects Whether to non object values with new empty objects.
186
+ */
184
187
  ctx.state.setPath = (path, value, fillWithObjects = false) => {
185
188
  const s = path.split('.')
186
189
  const parent = s
@@ -199,19 +202,19 @@ export const fnstate = (initialValue, mapKey) => {
199
202
  parent[s.slice(-1)] = value
200
203
  ctx.state(ctx.currentValue)
201
204
  } else {
202
- throw new Error(`No object at path ${path}`).stack
205
+ throw new Error(`No object at path ${path}`)
203
206
  }
204
207
  }
205
208
 
206
209
  /**
207
- * Register a callback that will be executed whenever the state is changed
208
- * @return a function to stop the subscription
209
- */
210
+ * Register a callback that will be executed whenever the state is changed
211
+ * @return a function to stop the subscription
212
+ */
210
213
  ctx.state.subscribe = (callback) => doSubscribe(ctx, ctx.observers, callback)
211
214
 
212
215
  /**
213
- * Remove all of the observers and optionally reset the value to it's initial value
214
- */
216
+ * Remove all of the observers and optionally reset the value to it's initial value
217
+ */
215
218
  ctx.state.reset = (reInit) => doReset(ctx, reInit, initialValue)
216
219
 
217
220
  return ctx.state
@@ -244,7 +247,7 @@ const doBindSelectAttr = function (ctx, attribute) {
244
247
 
245
248
  function createBoundAttr (attr) {
246
249
  if (typeof attr !== 'function') {
247
- throw new Error('You must pass a function to bindAttr').stack
250
+ throw new Error('You must pass a function to bindAttr')
248
251
  }
249
252
  const boundAttr = () => attr()
250
253
  boundAttr.isBoundAttribute = true
@@ -259,7 +262,7 @@ function doBindAttr (state, attribute) {
259
262
 
260
263
  function doBindStyle (state, style) {
261
264
  if (typeof style !== 'function') {
262
- throw new Error('You must pass a function to bindStyle').stack
265
+ throw new Error('You must pass a function to bindStyle')
263
266
  }
264
267
  const boundStyle = () => style()
265
268
  boundStyle.isBoundStyle = true
@@ -289,10 +292,10 @@ function doSelect (ctx, key) {
289
292
  function doBindChildren (ctx, parent, element, update) {
290
293
  parent = renderNode(parent)
291
294
  if (parent === undefined) {
292
- throw new Error('You must provide a parent element to bind the children to. aka Need Bukkit.').stack
295
+ throw new Error('You must provide a parent element to bind the children to. aka Need Bukkit.')
293
296
  }
294
297
  if (typeof element !== 'function' && typeof update !== 'function') {
295
- throw new Error('You must pass an update function when passing a non function element').stack
298
+ throw new Error('You must pass an update function when passing a non function element')
296
299
  }
297
300
  if (typeof ctx.mapKey !== 'function') {
298
301
  console.warn('Using value index as key, may not work correctly when moving items...')
@@ -327,7 +330,7 @@ function doBindChildren (ctx, parent, element, update) {
327
330
 
328
331
  const doBind = function (ctx, element, update, handleUpdate, handleReplace) {
329
332
  if (typeof element !== 'function' && typeof update !== 'function') {
330
- throw new Error('You must pass an update function when passing a non function element').stack
333
+ throw new Error('You must pass an update function when passing a non function element')
331
334
  }
332
335
  if (typeof update === 'function') {
333
336
  const boundElement = renderNode(evaluateElement(element, ctx.currentValue))
@@ -426,7 +429,7 @@ function arrangeElements (ctx, bindContext) {
426
429
  }
427
430
  const key = keyMapper(ctx.mapKey, valueState())
428
431
  if (keys[key]) {
429
- throw new Error('Duplicate keys in a bound array are not allowed.').stack
432
+ throw new Error('Duplicate keys in a bound array are not allowed.')
430
433
  }
431
434
  keys[key] = i
432
435
  keysArr[i] = key
@@ -510,12 +513,17 @@ const evaluateElement = (element, value) => {
510
513
  * Convert non objects (objects are assumed to be nodes) to text nodes and allow promises to resolve to nodes
511
514
  */
512
515
  export const renderNode = (node) => {
513
- if (node && typeof node === 'object' && node.then === undefined) {
514
- return node
515
- } else if (node && typeof node === 'object' && typeof node.then === 'function') {
516
- const temp = marker()
517
- node.then(el => temp.replaceWith(renderNode(el))).catch(e => console.error('Caught failed node promise.', e))
518
- return temp
516
+ if (node && typeof node === 'object') {
517
+ if (typeof node.then === 'function') {
518
+ let temp = h('div', { style: 'display:none', class: 'fntags-promise-marker' })
519
+ node.then(el => {
520
+ temp.replaceWith(renderNode(el))
521
+ temp = null
522
+ }).catch(e => console.error('Caught failed node promise.', e))
523
+ return temp
524
+ } else {
525
+ return node
526
+ }
519
527
  } else if (typeof node === 'function') {
520
528
  return renderNode(node())
521
529
  } else {
@@ -523,6 +531,9 @@ export const renderNode = (node) => {
523
531
  }
524
532
  }
525
533
 
534
+ /**
535
+ * All of these attributes must be set to an actual boolean to function correctly
536
+ */
526
537
  const booleanAttributes = {
527
538
  allowfullscreen: true,
528
539
  allowpaymentrequest: true,
@@ -552,13 +563,7 @@ const booleanAttributes = {
552
563
  }
553
564
 
554
565
  const setAttribute = function (attrName, attr, element) {
555
- if (attrName === 'value') {
556
- element.setAttribute('value', attr)
557
- // html5 nodes like range don't update unless the value property on the object is set
558
- element.value = attr
559
- } else if (booleanAttributes[attrName]) {
560
- element[attrName] = !!attr
561
- } else if (attrName === 'style' && typeof attr === 'object') {
566
+ if (attrName === 'style' && typeof attr === 'object') {
562
567
  for (const style in attr) {
563
568
  if (typeof attr[style] === 'function' && attr[style].isBoundStyle) {
564
569
  attr[style].init(style, element)
@@ -566,6 +571,12 @@ const setAttribute = function (attrName, attr, element) {
566
571
  }
567
572
  element.style[style] = attr[style] && attr[style].toString()
568
573
  }
574
+ } else if (attrName === 'value') {
575
+ element.setAttribute('value', attr)
576
+ // html5 nodes like range don't update unless the value property on the object is set
577
+ element.value = attr
578
+ } else if (booleanAttributes[attrName]) {
579
+ element[attrName] = !!attr
569
580
  } else if (typeof attr === 'function' && attrName.startsWith('on')) {
570
581
  element.addEventListener(attrName.substring(2), attr)
571
582
  } else {
@@ -577,18 +588,12 @@ const setAttribute = function (attrName, attr, element) {
577
588
  }
578
589
  }
579
590
 
580
- export const isAttrs = (val) => val !== null && typeof val === 'object' && val.nodeType === undefined && !Array.isArray(val) && typeof val.then !== 'function'
591
+ export const isAttrs = (val) => val && typeof val === 'object' && val.nodeType === undefined && !Array.isArray(val) && typeof val.then !== 'function'
581
592
  /**
582
593
  * helper to get the attr object
583
594
  */
584
595
  export const getAttrs = (children) => Array.isArray(children) && isAttrs(children[0]) ? children[0] : {}
585
596
 
586
- /**
587
- * A hidden div node to mark your place in the dom
588
- * @returns {HTMLDivElement}
589
- */
590
- const marker = (attrs) => h('div', Object.assign(attrs || {}, { style: 'display:none' }))
591
-
592
597
  /**
593
598
  * A function to create an element with a pre-defined style.
594
599
  * For example, the flex* elements in fnelements.