@srfnstack/fntags 0.4.1 → 0.4.2
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 +28 -35
- package/package.json +10 -4
- package/src/fnroute.mjs +72 -20
- package/src/fntags.mjs +84 -29
package/README.md
CHANGED
|
@@ -38,52 +38,45 @@ Check out the [documentation](https://srfnstack.github.io/fntags) to learn more!
|
|
|
38
38
|
### f'n examples
|
|
39
39
|
<hr>
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
|
|
41
|
+
Start a new app with one file
|
|
42
|
+
```html
|
|
43
|
+
<html><body>
|
|
44
|
+
<script type="module">
|
|
45
|
+
import { div } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.4.1/src/fnelements.min.mjs'
|
|
46
|
+
|
|
47
|
+
document.body.append(div('Hello World!'))
|
|
48
|
+
</script>
|
|
49
|
+
</body></html>
|
|
50
|
+
```
|
|
44
51
|
|
|
52
|
+
Make re-usable, customizable components using plain js functions
|
|
53
|
+
```javascript
|
|
45
54
|
const hello = name => div('Hello ', name)
|
|
46
55
|
|
|
47
56
|
document.body.append( hello('world!') )
|
|
48
57
|
```
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
Explicit two-way state binding
|
|
51
60
|
```javascript
|
|
52
|
-
import { fnstate } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.
|
|
53
|
-
import { div, input, br } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.
|
|
54
|
-
|
|
55
|
-
export const userName = fnstate('world')
|
|
56
|
-
export const appColor = fnstate('MediumTurquoise')
|
|
57
|
-
|
|
58
|
-
document.body.append(
|
|
59
|
-
div( { style: { color: appColor.bindStyle() } },
|
|
60
|
-
'Hello ', userName.bindAs(), '!'
|
|
61
|
-
),
|
|
62
|
-
br(),
|
|
63
|
-
input({
|
|
64
|
-
value: userName.bindAttr(),
|
|
65
|
-
oninput: e => userName(e.target.value)
|
|
66
|
-
}),
|
|
67
|
-
br(),
|
|
68
|
-
input({
|
|
69
|
-
value: appColor.bindAttr(),
|
|
70
|
-
oninput: e => appColor(e.target.value)
|
|
71
|
-
}),
|
|
72
|
-
)
|
|
73
|
-
```
|
|
61
|
+
import { fnstate } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.4.1/src/fntags.min.mjs'
|
|
62
|
+
import { div, input, br } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.4.1/src/fnelements.min.mjs'
|
|
74
63
|
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
const helloInput = () => {
|
|
65
|
+
const name = fnstate('World')
|
|
77
66
|
|
|
78
|
-
|
|
67
|
+
const nameInput = input({
|
|
68
|
+
value: name.bindAttr(),
|
|
69
|
+
oninput (){ name(nameInput.value) }
|
|
70
|
+
})
|
|
79
71
|
|
|
80
|
-
|
|
72
|
+
return div(
|
|
73
|
+
div('Hello ', name.bindAs(), '!'),
|
|
74
|
+
br(),
|
|
75
|
+
nameInput
|
|
76
|
+
)
|
|
77
|
+
}
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
<html><body><script type="module">
|
|
84
|
-
import { div } from 'https://cdn.jsdelivr.net/npm/@srfnstack/fntags@0.3.3/src/fnelements.min.mjs'
|
|
85
|
-
document.body.append(div('hello world!'))
|
|
86
|
-
</script></body></html>
|
|
79
|
+
document.body.append(helloInput())
|
|
87
80
|
```
|
|
88
81
|
|
|
89
82
|
### Benchmark
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srfnstack/fntags",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"author": "Robert Kempton <r@snow87.com>",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/srfnstack/fntags",
|
|
@@ -30,15 +30,21 @@
|
|
|
30
30
|
"state-management"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"cypress": "
|
|
33
|
+
"cypress": "13.6.1",
|
|
34
34
|
"pre-commit": "^1.2.2",
|
|
35
|
-
"standard": "^
|
|
35
|
+
"standard": "^17.1.0",
|
|
36
|
+
"typedoc": "^0.25.4",
|
|
37
|
+
"typedoc-plugin-markdown": "^3.17.1",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
36
39
|
},
|
|
37
40
|
"scripts": {
|
|
38
41
|
"test": "cp src/* docs/lib/ && npm run lint && cypress run --spec test/** --headless -b chrome",
|
|
39
42
|
"cypress": "cypress run --spec test/** -b chrome",
|
|
40
43
|
"lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test docs",
|
|
41
|
-
"lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test docs"
|
|
44
|
+
"lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test docs",
|
|
45
|
+
"typedef": "tsc",
|
|
46
|
+
"docs": "typedoc --plugin typedoc-plugin-markdown --out docs/types ./src/*.mjs",
|
|
47
|
+
"build": "npm run lint:fix && npm run typedef && npm run docs && npm run test"
|
|
42
48
|
},
|
|
43
49
|
"pre-commit": [
|
|
44
50
|
"lint",
|
package/src/fnroute.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
/// <reference path="fntags.mjs" name="fntags"/>
|
|
2
|
+
/**
|
|
3
|
+
* @module fnroute
|
|
4
|
+
*/
|
|
1
5
|
import { fnstate, getAttrs, h, isAttrs, renderNode } from './fntags.mjs'
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
|
-
* An element that is displayed only if the
|
|
8
|
+
* An element that is displayed only if the current route starts with elements path attribute.
|
|
5
9
|
*
|
|
6
10
|
* For example,
|
|
7
11
|
* route({path: "/proc"},
|
|
@@ -24,10 +28,10 @@ import { fnstate, getAttrs, h, isAttrs, renderNode } from './fntags.mjs'
|
|
|
24
28
|
* )
|
|
25
29
|
* )
|
|
26
30
|
*
|
|
27
|
-
* @param children The attributes and children of this element.
|
|
28
|
-
* @returns HTMLDivElement
|
|
31
|
+
* @param {any} children The attributes and children of this element.
|
|
32
|
+
* @returns {HTMLDivElement} A div element that will only be displayed if the current route starts with the path attribute.
|
|
29
33
|
*/
|
|
30
|
-
export
|
|
34
|
+
export function route (...children) {
|
|
31
35
|
const attrs = getAttrs(children)
|
|
32
36
|
children = children.filter(c => !isAttrs(c))
|
|
33
37
|
const routeEl = h('div', attrs)
|
|
@@ -50,9 +54,10 @@ export const route = (...children) => {
|
|
|
50
54
|
/**
|
|
51
55
|
* An element that only renders the first route that matches and updates when the route is changed
|
|
52
56
|
* The primary purpose of this element is to provide catchall routes for not found pages and path variables
|
|
53
|
-
* @param children
|
|
57
|
+
* @param {any} children
|
|
58
|
+
* @returns {HTMLDivElement}
|
|
54
59
|
*/
|
|
55
|
-
export
|
|
60
|
+
export function routeSwitch (...children) {
|
|
56
61
|
const sw = h('div', getAttrs(children))
|
|
57
62
|
|
|
58
63
|
return pathState.bindAs(
|
|
@@ -89,7 +94,18 @@ function stripParameterValues (currentRoute) {
|
|
|
89
94
|
|
|
90
95
|
const moduleCache = {}
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The main function of this library. It will load the route at the specified path and render it into the container element.
|
|
99
|
+
* @param {object} options
|
|
100
|
+
* @param {string} options.routePath The path to the root of the routes. This is used to resolve the paths of the routes.
|
|
101
|
+
* @param {object} options.attrs The attributes of the container element
|
|
102
|
+
* @param {(error: Error, newPathState: object)=>void} options.onerror A function that will be called if the route fails to load. The function receives the error and the current pathState object.
|
|
103
|
+
* @param {(node: Node, module: object)=>Node} options.frame A function that will be called with the rendered route element and the module that was loaded. The function should return a new element to be rendered.
|
|
104
|
+
* @param {boolean} options.sendRawPath If true, the raw path will be sent to the route. Otherwise, the path will be stripped of parameter values.
|
|
105
|
+
* @param {(path: string)=>string} options.formatPath A function that will be called with the raw path before it is used to load the route. The function should return a new path.
|
|
106
|
+
* @return {HTMLElement} The container element
|
|
107
|
+
*/
|
|
108
|
+
export function modRouter ({ routePath, attrs, onerror, frame, sendRawPath, formatPath }) {
|
|
93
109
|
const container = h('div', attrs || {})
|
|
94
110
|
if (!routePath) {
|
|
95
111
|
throw new Error('You must provide a root url for modRouter. Routes in the ui will be looked up relative to this url.')
|
|
@@ -104,8 +120,8 @@ export const modRouter = ({ routePath, attrs, onerror, frame, sendRawPath, forma
|
|
|
104
120
|
}
|
|
105
121
|
const filePath = path ? routePath + ensureOnlyLeadingSlash(path) : routePath
|
|
106
122
|
|
|
107
|
-
const p = moduleCache[filePath]
|
|
108
|
-
? Promise.resolve(moduleCache[filePath])
|
|
123
|
+
const p = moduleCache[filePath]
|
|
124
|
+
? Promise.resolve(moduleCache[filePath])
|
|
109
125
|
: import(filePath).then(m => {
|
|
110
126
|
moduleCache[filePath] = m
|
|
111
127
|
return m
|
|
@@ -171,9 +187,10 @@ function updatePathParameters () {
|
|
|
171
187
|
|
|
172
188
|
/**
|
|
173
189
|
* A link element that is a link to another route in this single page app
|
|
174
|
-
* @param children The attributes of the anchor element and any children
|
|
190
|
+
* @param {any} children The attributes of the anchor element and any children
|
|
191
|
+
* @returns {HTMLAnchorElement} An anchor element that will navigate to the specified route when clicked
|
|
175
192
|
*/
|
|
176
|
-
export
|
|
193
|
+
export function fnlink (...children) {
|
|
177
194
|
let context = null
|
|
178
195
|
if (children[0] && children[0].context) {
|
|
179
196
|
context = children[0].context
|
|
@@ -198,12 +215,12 @@ export const fnlink = (...children) => {
|
|
|
198
215
|
|
|
199
216
|
/**
|
|
200
217
|
* A function to navigate to the specified route
|
|
201
|
-
* @param route The route to navigate to
|
|
202
|
-
* @param context Data related to the route change
|
|
203
|
-
* @param replace Whether to replace the state or push it. pushState is used by default.
|
|
204
|
-
* @param silent Prevent route change events from being emitted for this route change
|
|
218
|
+
* @param {string} route The route to navigate to
|
|
219
|
+
* @param {any} context Data related to the route change
|
|
220
|
+
* @param {boolean} replace Whether to replace the state or push it. pushState is used by default.
|
|
221
|
+
* @param {boolean} silent Prevent route change events from being emitted for this route change
|
|
205
222
|
*/
|
|
206
|
-
export
|
|
223
|
+
export function goTo (route, context, replace = false, silent = false) {
|
|
207
224
|
const newPath = window.location.origin + makePath(route)
|
|
208
225
|
|
|
209
226
|
const patch = {
|
|
@@ -252,8 +269,26 @@ const ensureOnlyLeadingSlash = (part) => removeTrailingSlash(part.startsWith('/'
|
|
|
252
269
|
|
|
253
270
|
const removeTrailingSlash = part => part.endsWith('/') && part.length > 1 ? part.slice(0, -1) : part
|
|
254
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Key value pairs of path parameter names to their values
|
|
274
|
+
* @typedef {Object} PathParameters
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The path parameters of the current route
|
|
279
|
+
* @type {import("./fntags.mjs").FnState<PathParameters>}
|
|
280
|
+
*/
|
|
255
281
|
export const pathParameters = fnstate({})
|
|
256
282
|
|
|
283
|
+
/**
|
|
284
|
+
* The path information for a route
|
|
285
|
+
* @typedef {{currentRoute: string, rootPath: string, context: any}} PathState
|
|
286
|
+
*/
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The current path state
|
|
290
|
+
* @type {import("./fntags.mjs").FnState<PathState>}
|
|
291
|
+
*/
|
|
257
292
|
export const pathState = fnstate(
|
|
258
293
|
{
|
|
259
294
|
rootPath: ensureOnlyLeadingSlash(window.location.pathname),
|
|
@@ -261,8 +296,23 @@ export const pathState = fnstate(
|
|
|
261
296
|
context: null
|
|
262
297
|
})
|
|
263
298
|
|
|
299
|
+
/**
|
|
300
|
+
* @typedef {string} RouteEvent
|
|
301
|
+
*/
|
|
302
|
+
/**
|
|
303
|
+
* Before the route is changed
|
|
304
|
+
* @type {RouteEvent}
|
|
305
|
+
*/
|
|
264
306
|
export const beforeRouteChange = 'beforeRouteChange'
|
|
307
|
+
/**
|
|
308
|
+
* After the route is changed
|
|
309
|
+
* @type {RouteEvent}
|
|
310
|
+
*/
|
|
265
311
|
export const afterRouteChange = 'afterRouteChange'
|
|
312
|
+
/**
|
|
313
|
+
* After the route is changed and the route element is rendered
|
|
314
|
+
* @type {RouteEvent}
|
|
315
|
+
*/
|
|
266
316
|
export const routeChangeComplete = 'routeChangeComplete'
|
|
267
317
|
const eventListeners = {
|
|
268
318
|
[beforeRouteChange]: [],
|
|
@@ -279,9 +329,9 @@ const emit = (event, newPathState, oldPathState) => {
|
|
|
279
329
|
* @param event a string event to listen for
|
|
280
330
|
* @param handler A function that will be called when the event occurs.
|
|
281
331
|
* The function receives the new and old pathState objects, in that order.
|
|
282
|
-
* @return {
|
|
332
|
+
* @return {()=>void} a function to stop listening with the passed handler.
|
|
283
333
|
*/
|
|
284
|
-
export
|
|
334
|
+
export function listenFor (event, handler) {
|
|
285
335
|
if (!eventListeners[event]) {
|
|
286
336
|
throw new Error(`Invalid event. Must be one of ${Object.keys(eventListeners)}`)
|
|
287
337
|
}
|
|
@@ -296,12 +346,14 @@ export const listenFor = (event, handler) => {
|
|
|
296
346
|
|
|
297
347
|
/**
|
|
298
348
|
* Set the root path of the app. This is necessary to make deep linking work in cases where the same html file is served from all paths.
|
|
349
|
+
* @param {string} rootPath The root path of the app
|
|
299
350
|
*/
|
|
300
|
-
export
|
|
301
|
-
pathState.assign({
|
|
351
|
+
export function setRootPath (rootPath) {
|
|
352
|
+
return pathState.assign({
|
|
302
353
|
rootPath: ensureOnlyLeadingSlash(rootPath),
|
|
303
354
|
currentRoute: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + rootPath), '')) || '/'
|
|
304
355
|
})
|
|
356
|
+
}
|
|
305
357
|
|
|
306
358
|
window.addEventListener(
|
|
307
359
|
'popstate',
|
package/src/fntags.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module fntags
|
|
3
|
+
*/
|
|
1
4
|
/**
|
|
2
5
|
* A function to create dom elements with the given attributes and children.
|
|
3
6
|
*
|
|
@@ -12,9 +15,9 @@
|
|
|
12
15
|
*
|
|
13
16
|
* The rest of the arguments will be considered children of this element and appended to it in the same order as passed.
|
|
14
17
|
*
|
|
15
|
-
* @param tag html tag to use when created the element
|
|
16
|
-
* @param children optional attributes object and children for the element
|
|
17
|
-
* @
|
|
18
|
+
* @param {string} tag html tag to use when created the element
|
|
19
|
+
* @param {object[]?|Node[]?} children optional attributes object and children for the element
|
|
20
|
+
* @return {HTMLElement} an html element
|
|
18
21
|
*
|
|
19
22
|
*/
|
|
20
23
|
export function h (tag, ...children) {
|
|
@@ -79,10 +82,12 @@ function hasNs (val) {
|
|
|
79
82
|
* You cannot bind state to the initial template. If you attempt to, the state will be read, but the elements will
|
|
80
83
|
* not be updated when the state changes because they will not be bound to the cloned element.
|
|
81
84
|
* All state bindings must be passed in the context to the compiled template to work correctly.
|
|
82
|
-
*
|
|
83
|
-
* @
|
|
85
|
+
*
|
|
86
|
+
* @param {(any)=>Node} templateFn A function that returns an html node.
|
|
87
|
+
* @return {(any)=>Node} A function that takes a context object and returns a rendered node.
|
|
88
|
+
*
|
|
84
89
|
*/
|
|
85
|
-
export
|
|
90
|
+
export function fntemplate (templateFn) {
|
|
86
91
|
if (typeof templateFn !== 'function') {
|
|
87
92
|
throw new Error('You must pass a function to fntemplate. The function must return an html node.')
|
|
88
93
|
}
|
|
@@ -139,17 +144,53 @@ export const fntemplate = templateFn => {
|
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @template T The type of data stored in the state container
|
|
149
|
+
* @typedef FnStateObj A container for a state value that can be bound to.
|
|
150
|
+
* @property {(element: (T)=>void|Node|any?, update: (Node)=>void?) => Node|() => Node} bindAs Bind this state to the given element. This causes the element to update when state changes.
|
|
151
|
+
* If called with no parameters, the state's value will be rendered as an element. If the first parameters is not a function,
|
|
152
|
+
* the second parameter (the update function) must be provided and must be a function. This function receives the node the state is bound to.
|
|
153
|
+
* @property {(parent: Node,element: Node|any, update: (Node)=>void?)=> Node|()=> Node} bindChildren Bind the values of this state to the given element.
|
|
154
|
+
* Values are items/elements of an array.
|
|
155
|
+
* If the current value is not an array, this will behave the same as bindAs.
|
|
156
|
+
* @property {(prop: string)=>Node|()=>Node} bindProp Bind to a property of an object stored in this state instead of the state itself.
|
|
157
|
+
* Shortcut for `mystate.bindAs((current)=> current[prop])`
|
|
158
|
+
* @property {(attribute: string)=>any} bindAttr Bind attribute values to state changes
|
|
159
|
+
* @property {(style: string)=> string} bindStyle Bind style values to state changes
|
|
160
|
+
* @property {(element: Node|any, update: (Node)=>void?)=>Node|()=>Node} bindSelect Bind selected state to an element
|
|
161
|
+
* @property {(attribute: string)=>any} bindSelectAttr Bind selected state to an attribute
|
|
162
|
+
* @property {(key: any)=>void} select Mark the element with the given key as selected
|
|
163
|
+
* where the key is identified using the mapKey function passed on creation of the fnstate.
|
|
164
|
+
* This causes the bound select functions to be executed.
|
|
165
|
+
* @property {()=> any} selected Get the currently selected key
|
|
166
|
+
* @property {(update: T)=>void} assign Perform an Object.assign() on the current state using the provided update, triggers
|
|
167
|
+
* a state change and is a shortcut for `mystate(Object.assign(mystate(), update))`
|
|
168
|
+
* @property {(path: string)=>any} getPath Get a value at the given property path, an error is thrown if the value is not an object
|
|
169
|
+
* 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
|
|
170
|
+
* will not be reflected correctly.
|
|
171
|
+
* @property {(path: string, value: any, fillWithObjects: boolean)=>void} setPath Set a value at the given property path
|
|
172
|
+
* @property {((newState: T, oldState: T)=>void)=>void} subscribe Register a callback that will be executed whenever the state is changed
|
|
173
|
+
* @property {(reinit: boolean)=>{}} reset Remove all of the observers and optionally reset the value to it's initial value
|
|
174
|
+
* @property {} isFnState A flag to indicate that this is an fnstate object
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @template T The type of data stored in the state container
|
|
179
|
+
* @typedef {FnStateObj<T> & (newState: T?)=>T} FnState A container for a state value that can be bound to.
|
|
180
|
+
*/
|
|
181
|
+
|
|
142
182
|
/**
|
|
143
183
|
* Create a state object that can be bound to.
|
|
144
|
-
* @
|
|
145
|
-
* @param
|
|
146
|
-
* @
|
|
184
|
+
* @template T
|
|
185
|
+
* @param {T|any} initialValue The initial state
|
|
186
|
+
* @param {function(T): any?} mapKey A map function to extract a key from an element in the array. Receives the array value to extract the key from.
|
|
187
|
+
* A key can be any unique value.
|
|
188
|
+
* @return {FnState<T>} A function that can be used to get and set the state.
|
|
147
189
|
* When getting the state, you get the actual reference to the underlying value.
|
|
148
190
|
* If you perform modifications to the value, be sure to call the state function with the updated value when you're done
|
|
149
191
|
* or the changes won't be reflected correctly and binding updates won't be triggered even though the state appears to be correct.
|
|
150
|
-
*
|
|
151
192
|
*/
|
|
152
|
-
export
|
|
193
|
+
export function fnstate (initialValue, mapKey) {
|
|
153
194
|
const ctx = {
|
|
154
195
|
currentValue: initialValue,
|
|
155
196
|
observers: [],
|
|
@@ -171,6 +212,15 @@ export const fnstate = (initialValue, mapKey) => {
|
|
|
171
212
|
}
|
|
172
213
|
}
|
|
173
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Bind this state to the given element
|
|
217
|
+
*
|
|
218
|
+
* @param [element] The element to bind to. If not a function, an update function must be passed. If not passed, defaults to the state's value
|
|
219
|
+
* @param [update] If passed this will be executed directly when the state changes with no other intervention
|
|
220
|
+
* @returns {(HTMLDivElement|Text)[]|HTMLDivElement|Text}
|
|
221
|
+
*/
|
|
222
|
+
ctx.state.bindAs = (element, update) => doBindAs(ctx, element ?? ctx.state, update)
|
|
223
|
+
|
|
174
224
|
/**
|
|
175
225
|
* Bind the values of this state to the given element.
|
|
176
226
|
* Values are items/elements of an array.
|
|
@@ -183,16 +233,7 @@ export const fnstate = (initialValue, mapKey) => {
|
|
|
183
233
|
ctx.state.bindChildren = (parent, element, update) => doBindChildren(ctx, parent, element, update)
|
|
184
234
|
|
|
185
235
|
/**
|
|
186
|
-
* Bind this state
|
|
187
|
-
*
|
|
188
|
-
* @param [element] The element to bind to. If not a function, an update function must be passed. If not passed, defaults to the state's value
|
|
189
|
-
* @param [update] If passed this will be executed directly when the state changes with no other intervention
|
|
190
|
-
* @returns {(HTMLDivElement|Text)[]|HTMLDivElement|Text}
|
|
191
|
-
*/
|
|
192
|
-
ctx.state.bindAs = (element, update) => doBindAs(ctx, element ?? ctx.state, update)
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Bind a property of an object stored in this state as a simple value.
|
|
236
|
+
* Bind to a property of an object stored in this state instead of the state itself.
|
|
196
237
|
*
|
|
197
238
|
* Shortcut for `mystate.bindAs((current)=> current[prop])`
|
|
198
239
|
*
|
|
@@ -609,8 +650,10 @@ const evaluateElement = (element, value) => {
|
|
|
609
650
|
|
|
610
651
|
/**
|
|
611
652
|
* Convert non objects (objects are assumed to be nodes) to text nodes and allow promises to resolve to nodes
|
|
653
|
+
* @param {any} node The node to render
|
|
654
|
+
* @returns {Node} The rendered node
|
|
612
655
|
*/
|
|
613
|
-
export
|
|
656
|
+
export function renderNode (node) {
|
|
614
657
|
if (node && node.isTemplatePlaceholder) {
|
|
615
658
|
const element = h('div')
|
|
616
659
|
node(element, 'node')
|
|
@@ -719,22 +762,34 @@ const setStyle = (style, styleValue, element) => {
|
|
|
719
762
|
element.style[style] = styleValue && styleValue.toString()
|
|
720
763
|
}
|
|
721
764
|
|
|
722
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Check if the given value is an object that can be used as attributes
|
|
767
|
+
* @param {any} val The value to check
|
|
768
|
+
* @returns {boolean} true if the value is an object that can be used as attributes
|
|
769
|
+
*/
|
|
770
|
+
export function isAttrs (val) {
|
|
771
|
+
return val && typeof val === 'object' && val.nodeType === undefined && !Array.isArray(val) && typeof val.then !== 'function'
|
|
772
|
+
}
|
|
773
|
+
|
|
723
774
|
/**
|
|
724
775
|
* helper to get the attr object
|
|
776
|
+
* @param {any} children
|
|
777
|
+
* @return {object} the attr object or an empty object
|
|
725
778
|
*/
|
|
726
|
-
export
|
|
779
|
+
export function getAttrs (children) {
|
|
780
|
+
return Array.isArray(children) && isAttrs(children[0]) ? children[0] : {}
|
|
781
|
+
}
|
|
727
782
|
|
|
728
783
|
/**
|
|
729
784
|
* A function to create an element with a pre-defined style.
|
|
730
785
|
* For example, the flex* elements in fnelements.
|
|
731
786
|
*
|
|
732
|
-
* @param style
|
|
733
|
-
* @param tag
|
|
734
|
-
* @param children
|
|
735
|
-
* @return {*}
|
|
787
|
+
* @param {object|string} style The style to apply to the element
|
|
788
|
+
* @param {string} tag The tag to use when creating the element
|
|
789
|
+
* @param {object|object[]?} children The children to append to the element
|
|
790
|
+
* @return {*} The styled element
|
|
736
791
|
*/
|
|
737
|
-
export
|
|
792
|
+
export function styled (style, tag, children) {
|
|
738
793
|
const firstChild = children[0]
|
|
739
794
|
if (isAttrs(firstChild)) {
|
|
740
795
|
if (typeof firstChild.style === 'string') {
|