@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.
- package/package.json +1 -1
- package/src/fntags.mjs +91 -86
package/package.json
CHANGED
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
|
|
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(
|
|
24
|
-
const attrs =
|
|
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 (
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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')
|
|
162
|
+
throw new Error('Invalid path')
|
|
160
163
|
}
|
|
161
164
|
if (typeof ctx.currentValue !== 'object') {
|
|
162
|
-
throw new Error('Value is not an object')
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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}`)
|
|
205
|
+
throw new Error(`No object at path ${path}`)
|
|
203
206
|
}
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
/**
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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')
|
|
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')
|
|
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.')
|
|
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')
|
|
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')
|
|
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.')
|
|
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'
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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 === '
|
|
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
|
|
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.
|