@srfnstack/fntags 1.0.0 → 1.1.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/package.json +3 -3
- package/src/fntags.d.mts +17 -5
- package/src/fntags.d.mts.map +1 -1
- package/src/fntags.mjs +189 -92
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srfnstack/fntags",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "Robert Kempton <r@snow87.com>",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/srfnstack/fntags",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"scripts": {
|
|
53
53
|
"test": "cp src/*.mjs docs/lib/ && npm run lint && cypress run --spec \"test/**\" --headless -b chrome",
|
|
54
54
|
"cypress": "cypress run --headed --spec test/** -b chrome",
|
|
55
|
-
"lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test
|
|
56
|
-
"lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test
|
|
55
|
+
"lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test",
|
|
56
|
+
"lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test",
|
|
57
57
|
"typedef": "rm -rf src/*.mts* && tsc",
|
|
58
58
|
"docs": "typedoc --plugin typedoc-plugin-markdown --out docs/types --json docs/types.json ./src/*.mjs && node scripts/generateApi.js",
|
|
59
59
|
"generate-api": "node scripts/generateApi.js",
|
package/src/fntags.d.mts
CHANGED
|
@@ -47,7 +47,6 @@ export function h<T extends HTMLElement | SVGElement>(tag: string, ...children:
|
|
|
47
47
|
* will not be reflected correctly.
|
|
48
48
|
* @property {(path: string, value: any, fillWithObjects: boolean)=>void} setPath Set a value at the given property path
|
|
49
49
|
* @property {(subscriber: (newState: T, oldState: T)=>void) => void} subscribe Register a callback that will be executed whenever the state is changed
|
|
50
|
-
* @property {(reinit: boolean)=>{}} reset Remove all of the observers and optionally reset the value to it's initial value
|
|
51
50
|
* @property {boolean} isFnState A flag to indicate that this is a fnstate object
|
|
52
51
|
*/
|
|
53
52
|
/**
|
|
@@ -96,6 +95,23 @@ export function getAttrs(children: any): object;
|
|
|
96
95
|
* @return {T} The styled element
|
|
97
96
|
*/
|
|
98
97
|
export function styled<T extends HTMLElement | SVGElement>(style: object | string, tag: string, children: object[] | Node[]): T;
|
|
98
|
+
/**
|
|
99
|
+
* Create a compiled template function. The returned function takes a single object that contains the properties
|
|
100
|
+
* defined in the template.
|
|
101
|
+
*
|
|
102
|
+
* This allows fast rendering by pre-creating a dom element with the entire template structure then cloning and populating
|
|
103
|
+
* the clone with data from the provided context. This avoids the work of having to re-execute the tag functions
|
|
104
|
+
* one by one and can speed up situations where a similar element is created many times.
|
|
105
|
+
*
|
|
106
|
+
* You cannot bind state to the initial template. If you attempt to, the state will be read, but the elements will
|
|
107
|
+
* not be updated when the state changes because they will not be bound to the cloned element.
|
|
108
|
+
* All state bindings must be passed in the context to the compiled template to work correctly.
|
|
109
|
+
*
|
|
110
|
+
* @param {(any)=>Node} templateFn A function that returns a html node.
|
|
111
|
+
* @return {(any)=>Node} A function that takes a context object and returns a rendered node.
|
|
112
|
+
*
|
|
113
|
+
*/
|
|
114
|
+
export function fntemplate(templateFn: (any: any) => Node): (any: any) => Node;
|
|
99
115
|
/**
|
|
100
116
|
* A container for a state value that can be bound to.
|
|
101
117
|
*/
|
|
@@ -161,10 +177,6 @@ export type FnStateObj<T> = {
|
|
|
161
177
|
* Register a callback that will be executed whenever the state is changed
|
|
162
178
|
*/
|
|
163
179
|
subscribe: (subscriber: (newState: T, oldState: T) => void) => void;
|
|
164
|
-
/**
|
|
165
|
-
* Remove all of the observers and optionally reset the value to it's initial value
|
|
166
|
-
*/
|
|
167
|
-
reset: (reinit: boolean) => {};
|
|
168
180
|
/**
|
|
169
181
|
* A flag to indicate that this is a fnstate object
|
|
170
182
|
*/
|
package/src/fntags.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD
|
|
1
|
+
{"version":3,"file":"fntags.d.mts","sourceRoot":"","sources":["fntags.mjs"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,2DALW,MAAM,eACH,CAAC,IAAI,MAAO,CAAC,OA6C1B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,iEAPgB,GAAG,cAoIlB;AAwWD;;;;GAIG;AACH,iCAHW,GAAG,GACD,IAAI,CAuBhB;AAqFD;;;;GAIG;AACH,6BAHW,GAAG,GACD,OAAO,CAInB;AAED;;;;GAIG;AACH,mCAHW,GAAG,GACF,MAAM,CAIjB;AAED;;;;;;;;;;GAUG;AACH,kEALW,MAAM,GAAC,MAAM,OACb,MAAM,YACN,MAAM,EAAE,GAAC,IAAI,EAAE,KAezB;AAOD;;;;;;;;;;;;;;;GAeG;AACH,qDAJkB,IAAI,iBACH,IAAI,CA+DtB;;;;;;;;;kCAzvBmC,CAAC,YAAY,CAAC,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;;2BAE/C,CAAC,MAAI,CAAC,IAAI,GAAC,GAAG,CAAC,CAAC,GAAC,GAAG,GAAC,IAAI,gCAAkC,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;;qBAG9E,MAAM,KAAG,IAAI;;;;sCAEI,CAAC,YAAY,CAAC,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;mCACvC,CAAC,YAAY,CAAC,KAAG,MAAM,KAAK,MAAM;;;;yCAC7B,GAAG,KAAG,CAAC,IAAI,GAAC,GAAG,CAAC,KAAG,IAAI;;;;+CACrB,GAAG,KAAG,CAAC,MAAM,GAAC,GAAG,CAAC,KAAG,GAAG;;;;;;kBAC7C,GAAG,KAAG,IAAI;;;;cAGhB,MAAK,GAAG;;;;;qBACC,CAAC,KAAG,IAAI;;;;;;oBAEV,MAAM,KAAG,GAAG;;;;oBAGZ,MAAM,SAAS,GAAG,mBAAmB,OAAO,KAAG,IAAI;;;;uCAClC,CAAC,YAAY,CAAC,KAAG,IAAI,KAAK,IAAI;;;;eACtD,OAAO;;;;;sDAKqB,CAAC,KAAG,CAAC"}
|
package/src/fntags.mjs
CHANGED
|
@@ -97,7 +97,6 @@ function hasNs (val) {
|
|
|
97
97
|
* will not be reflected correctly.
|
|
98
98
|
* @property {(path: string, value: any, fillWithObjects: boolean)=>void} setPath Set a value at the given property path
|
|
99
99
|
* @property {(subscriber: (newState: T, oldState: T)=>void) => void} subscribe Register a callback that will be executed whenever the state is changed
|
|
100
|
-
* @property {(reinit: boolean)=>{}} reset Remove all of the observers and optionally reset the value to it's initial value
|
|
101
100
|
* @property {boolean} isFnState A flag to indicate that this is a fnstate object
|
|
102
101
|
*/
|
|
103
102
|
|
|
@@ -138,6 +137,8 @@ export function fnstate (initialValue, mapKey) {
|
|
|
138
137
|
return newState
|
|
139
138
|
}
|
|
140
139
|
}
|
|
140
|
+
// make context available to static functions to avoid declaring functions on every new state
|
|
141
|
+
ctx.state._ctx = ctx
|
|
141
142
|
|
|
142
143
|
/**
|
|
143
144
|
* Bind this state to the given element
|
|
@@ -145,7 +146,7 @@ export function fnstate (initialValue, mapKey) {
|
|
|
145
146
|
* @param {((T)=>(Node|any))?} [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
|
|
146
147
|
* @returns {()=>Node}
|
|
147
148
|
*/
|
|
148
|
-
ctx.state.bindAs =
|
|
149
|
+
ctx.state.bindAs = doBindAs
|
|
149
150
|
|
|
150
151
|
/**
|
|
151
152
|
* Bind the values of this state to the given element.
|
|
@@ -156,7 +157,7 @@ export function fnstate (initialValue, mapKey) {
|
|
|
156
157
|
* @param {(childState: FnState)=>(Node|any)} element A function that receives each element wrapped as a fnstate and produces an element
|
|
157
158
|
* @returns {Node}
|
|
158
159
|
*/
|
|
159
|
-
ctx.state.bindChildren =
|
|
160
|
+
ctx.state.bindChildren = doBindChildren
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
163
|
* Bind to a property of an object stored in this state instead of the state itself.
|
|
@@ -166,46 +167,46 @@ export function fnstate (initialValue, mapKey) {
|
|
|
166
167
|
* @param {string} prop The object property to bind as
|
|
167
168
|
* @returns {()=>Node}
|
|
168
169
|
*/
|
|
169
|
-
ctx.state.bindProp =
|
|
170
|
+
ctx.state.bindProp = doBindProp
|
|
170
171
|
|
|
171
172
|
/**
|
|
172
173
|
* Bind attribute values to state changes
|
|
173
174
|
* @param {(()=>(string|any))?} [attribute] A function that returns an attribute value. If not passed, defaults to the state's value
|
|
174
175
|
* @returns {()=>(string|any)} A function that calls the passed function, with some extra metadata
|
|
175
176
|
*/
|
|
176
|
-
ctx.state.bindAttr =
|
|
177
|
+
ctx.state.bindAttr = doBindAttr
|
|
177
178
|
|
|
178
179
|
/**
|
|
179
180
|
* Bind style values to state changes
|
|
180
181
|
* @param {(()=>string)?} [style] A function that returns a style's value. If not passed, defaults to the state's value
|
|
181
182
|
* @returns {()=>Node} A function that calls the passed function, with some extra metadata
|
|
182
183
|
*/
|
|
183
|
-
ctx.state.bindStyle =
|
|
184
|
+
ctx.state.bindStyle = doBindStyle
|
|
184
185
|
|
|
185
186
|
/**
|
|
186
187
|
* Bind select and deselect to an element
|
|
187
188
|
* @param {(()=>(Node|any))?} [element] The element to bind to. If not passed, defaults to the state's value
|
|
188
189
|
* @returns {()=>Node}
|
|
189
190
|
*/
|
|
190
|
-
ctx.state.bindSelect =
|
|
191
|
+
ctx.state.bindSelect = doBindSelect
|
|
191
192
|
|
|
192
193
|
/**
|
|
193
194
|
* Bind select and deselect to an attribute
|
|
194
195
|
* @param {(()=>(string|any))?} [attribute] A function that returns an attribute value. If not passed, defaults to the state's value
|
|
195
196
|
* @returns {()=>(string|any)} A function that calls the passed function, with some extra metadata
|
|
196
197
|
*/
|
|
197
|
-
ctx.state.bindSelectAttr =
|
|
198
|
+
ctx.state.bindSelectAttr = doBindSelectAttr
|
|
198
199
|
|
|
199
200
|
/**
|
|
200
201
|
* Mark the element with the given key as selected. This causes the bound select functions to be executed.
|
|
201
202
|
*/
|
|
202
|
-
ctx.state.select =
|
|
203
|
+
ctx.state.select = doSelect
|
|
203
204
|
|
|
204
205
|
/**
|
|
205
206
|
* Get the currently selected key
|
|
206
207
|
* @returns {any}
|
|
207
208
|
*/
|
|
208
|
-
ctx.state.selected =
|
|
209
|
+
ctx.state.selected = doSelected
|
|
209
210
|
|
|
210
211
|
ctx.state.isFnState = true
|
|
211
212
|
|
|
@@ -213,7 +214,7 @@ export function fnstate (initialValue, mapKey) {
|
|
|
213
214
|
* Perform an Object.assign() on the current state using the provided update
|
|
214
215
|
* @param {T} [update]
|
|
215
216
|
*/
|
|
216
|
-
ctx.state.assign =
|
|
217
|
+
ctx.state.assign = doAssign
|
|
217
218
|
|
|
218
219
|
/**
|
|
219
220
|
* Get a value at the given property path, an error is thrown if the value is not an object
|
|
@@ -222,26 +223,7 @@ export function fnstate (initialValue, mapKey) {
|
|
|
222
223
|
* will not be reflected correctly.
|
|
223
224
|
* @param {string} [path] a json path type path that points to a property
|
|
224
225
|
*/
|
|
225
|
-
ctx.state.getPath =
|
|
226
|
-
if (typeof path !== 'string') {
|
|
227
|
-
throw new Error('Invalid path')
|
|
228
|
-
}
|
|
229
|
-
if (typeof ctx.currentValue !== 'object') {
|
|
230
|
-
throw new Error('Value is not an object')
|
|
231
|
-
}
|
|
232
|
-
return path
|
|
233
|
-
.split('.')
|
|
234
|
-
.reduce(
|
|
235
|
-
(curr, part) => {
|
|
236
|
-
if (part in curr) {
|
|
237
|
-
return curr[part]
|
|
238
|
-
} else {
|
|
239
|
-
return undefined
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
ctx.currentValue
|
|
243
|
-
)
|
|
244
|
-
}
|
|
226
|
+
ctx.state.getPath = doGetPath
|
|
245
227
|
|
|
246
228
|
/**
|
|
247
229
|
* Set a value at the given property path
|
|
@@ -249,50 +231,24 @@ export function fnstate (initialValue, mapKey) {
|
|
|
249
231
|
* @param {any} value The value to set the path to
|
|
250
232
|
* @param {boolean} fillWithObjects Whether to replace non object values with new empty objects.
|
|
251
233
|
*/
|
|
252
|
-
ctx.state.setPath =
|
|
253
|
-
const s = path.split('.')
|
|
254
|
-
const parent = s
|
|
255
|
-
.slice(0, -1)
|
|
256
|
-
.reduce(
|
|
257
|
-
(current, part) => {
|
|
258
|
-
if (fillWithObjects && typeof current[part] !== 'object') {
|
|
259
|
-
current[part] = {}
|
|
260
|
-
}
|
|
261
|
-
return current[part]
|
|
262
|
-
},
|
|
263
|
-
ctx.currentValue
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
if (parent && typeof parent === 'object') {
|
|
267
|
-
parent[s.slice(-1)] = value
|
|
268
|
-
ctx.state(ctx.currentValue)
|
|
269
|
-
} else {
|
|
270
|
-
throw new Error(`No object at path ${path}`)
|
|
271
|
-
}
|
|
272
|
-
}
|
|
234
|
+
ctx.state.setPath = doSetPath
|
|
273
235
|
|
|
274
236
|
/**
|
|
275
237
|
* Register a callback that will be executed whenever the state is changed
|
|
276
238
|
* @param {(newValue:T,oldValue:T)=>void} callback
|
|
277
239
|
* @return {()=>void} a function to stop the subscription
|
|
278
240
|
*/
|
|
279
|
-
ctx.state.subscribe =
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Remove all the observers and optionally reset the value to it's initial value
|
|
283
|
-
* @param {boolean} reInit whether to reset the state to it's initial value
|
|
284
|
-
*/
|
|
285
|
-
ctx.state.reset = (reInit) => doReset(ctx, reInit, initialValue)
|
|
241
|
+
ctx.state.subscribe = doSubscribe
|
|
286
242
|
|
|
287
243
|
return ctx.state
|
|
288
244
|
}
|
|
289
245
|
|
|
290
|
-
function doSubscribe (
|
|
246
|
+
function doSubscribe (callback) {
|
|
247
|
+
const ctx = this._ctx
|
|
291
248
|
const id = ctx.nextId++
|
|
292
|
-
|
|
249
|
+
ctx.observers.push({ id, fn: callback })
|
|
293
250
|
return () => {
|
|
294
|
-
|
|
295
|
-
list = null
|
|
251
|
+
ctx.observers.splice(ctx.observers.findIndex(l => l.id === id), 1)
|
|
296
252
|
}
|
|
297
253
|
}
|
|
298
254
|
|
|
@@ -305,7 +261,9 @@ const subscribeSelect = (ctx, callback) => {
|
|
|
305
261
|
parentCtx.selectObservers[key].push(callback)
|
|
306
262
|
}
|
|
307
263
|
|
|
308
|
-
|
|
264
|
+
function doBindSelectAttr (attribute) {
|
|
265
|
+
attribute = attribute ?? this
|
|
266
|
+
const ctx = this._ctx
|
|
309
267
|
const attrFn = (attribute && !attribute.isFnState && typeof attribute === 'function')
|
|
310
268
|
? (...args) => attribute(args.length > 0 ? args[0] : ctx.selected)
|
|
311
269
|
: attribute
|
|
@@ -325,37 +283,32 @@ function createBoundAttr (attr) {
|
|
|
325
283
|
return boundAttr
|
|
326
284
|
}
|
|
327
285
|
|
|
328
|
-
function doBindAttr (
|
|
286
|
+
function doBindAttr (attribute) {
|
|
287
|
+
attribute = attribute ?? this
|
|
329
288
|
const boundAttr = createBoundAttr(attribute)
|
|
330
289
|
boundAttr.init = (attrName, element) => {
|
|
331
|
-
setAttribute(attrName, attribute.isFnState ? attribute() : attribute(
|
|
332
|
-
|
|
290
|
+
setAttribute(attrName, attribute.isFnState ? attribute() : attribute(this()), element)
|
|
291
|
+
this.subscribe((newState, oldState) => setAttribute(attrName, attribute.isFnState ? attribute() : attribute(newState, oldState), element))
|
|
333
292
|
}
|
|
334
293
|
return boundAttr
|
|
335
294
|
}
|
|
336
295
|
|
|
337
|
-
function doBindStyle (
|
|
296
|
+
function doBindStyle (style) {
|
|
297
|
+
style = style ?? this
|
|
338
298
|
if (typeof style !== 'function') {
|
|
339
299
|
throw new Error('You must pass a function to bindStyle')
|
|
340
300
|
}
|
|
341
301
|
const boundStyle = () => style()
|
|
342
302
|
boundStyle.isBoundStyle = true
|
|
343
303
|
boundStyle.init = (styleName, element) => {
|
|
344
|
-
element.style[styleName] = style.isFnState ? style() : style(
|
|
345
|
-
|
|
304
|
+
element.style[styleName] = style.isFnState ? style() : style(this())
|
|
305
|
+
this.subscribe((newState, oldState) => { element.style[styleName] = style.isFnState ? style() : style(newState, oldState) })
|
|
346
306
|
}
|
|
347
307
|
return boundStyle
|
|
348
308
|
}
|
|
349
309
|
|
|
350
|
-
function
|
|
351
|
-
ctx
|
|
352
|
-
ctx.selectObservers = {}
|
|
353
|
-
if (reInit) {
|
|
354
|
-
ctx.currentValue = initialValue
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function doSelect (ctx, key) {
|
|
310
|
+
function doSelect (key) {
|
|
311
|
+
const ctx = this._ctx
|
|
359
312
|
const currentSelected = ctx.selected
|
|
360
313
|
ctx.selected = key
|
|
361
314
|
if (ctx.selectObservers[currentSelected] !== undefined) {
|
|
@@ -366,7 +319,65 @@ function doSelect (ctx, key) {
|
|
|
366
319
|
}
|
|
367
320
|
}
|
|
368
321
|
|
|
369
|
-
function
|
|
322
|
+
function doSelected () {
|
|
323
|
+
return this._ctx.selected
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function doAssign (update) {
|
|
327
|
+
return this(Object.assign(this._ctx.currentValue, update))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function doGetPath (path) {
|
|
331
|
+
const ctx = this._ctx
|
|
332
|
+
if (typeof path !== 'string') {
|
|
333
|
+
throw new Error('Invalid path')
|
|
334
|
+
}
|
|
335
|
+
if (typeof ctx.currentValue !== 'object') {
|
|
336
|
+
throw new Error('Value is not an object')
|
|
337
|
+
}
|
|
338
|
+
return path
|
|
339
|
+
.split('.')
|
|
340
|
+
.reduce(
|
|
341
|
+
(curr, part) => {
|
|
342
|
+
if (part in curr) {
|
|
343
|
+
return curr[part]
|
|
344
|
+
} else {
|
|
345
|
+
return undefined
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
ctx.currentValue
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function doSetPath (path, value, fillWithObjects = false) {
|
|
353
|
+
const ctx = this._ctx
|
|
354
|
+
const s = path.split('.')
|
|
355
|
+
const parent = s
|
|
356
|
+
.slice(0, -1)
|
|
357
|
+
.reduce(
|
|
358
|
+
(current, part) => {
|
|
359
|
+
if (fillWithObjects && typeof current[part] !== 'object') {
|
|
360
|
+
current[part] = {}
|
|
361
|
+
}
|
|
362
|
+
return current[part]
|
|
363
|
+
},
|
|
364
|
+
ctx.currentValue
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if (parent && typeof parent === 'object') {
|
|
368
|
+
parent[s.slice(-1)] = value
|
|
369
|
+
this(ctx.currentValue)
|
|
370
|
+
} else {
|
|
371
|
+
throw new Error(`No object at path ${path}`)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function doBindProp (prop) {
|
|
376
|
+
return this.bindAs((st) => st[prop])
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function doBindChildren (parent, element) {
|
|
380
|
+
const ctx = this._ctx
|
|
370
381
|
parent = renderNode(parent)
|
|
371
382
|
if (parent === undefined || parent.nodeType === undefined) {
|
|
372
383
|
throw new Error('You must provide a parent element to bind the children to. aka Need Bukkit.')
|
|
@@ -384,11 +395,11 @@ function doBindChildren (ctx, parent, element) {
|
|
|
384
395
|
}
|
|
385
396
|
ctx.currentValue = ctx.currentValue.map(v => v.isFnState ? v : fnstate(v))
|
|
386
397
|
ctx.bindContexts.push({ element, parent })
|
|
387
|
-
|
|
398
|
+
this.subscribe((_, oldState) => {
|
|
388
399
|
if (!Array.isArray(ctx.currentValue)) {
|
|
389
400
|
console.warn('A state used with bindChildren was updated to a non array value. This will be converted to an array of 1 and the state will be updated.')
|
|
390
401
|
new Promise((resolve) => {
|
|
391
|
-
|
|
402
|
+
this([ctx.currentValue])
|
|
392
403
|
resolve()
|
|
393
404
|
}).catch(e => {
|
|
394
405
|
console.error('Failed to update element: ')
|
|
@@ -441,11 +452,17 @@ const updateReplacer = (ctx, element, elCtx) => (_, oldValue) => {
|
|
|
441
452
|
}
|
|
442
453
|
}
|
|
443
454
|
|
|
444
|
-
|
|
445
|
-
|
|
455
|
+
function doBindSelect (element) {
|
|
456
|
+
element = element ?? this
|
|
457
|
+
const ctx = this._ctx
|
|
458
|
+
return doBind(ctx, element, (elCtx) => subscribeSelect(ctx, updateReplacer(ctx, element, elCtx)))
|
|
459
|
+
}
|
|
446
460
|
|
|
447
|
-
|
|
448
|
-
|
|
461
|
+
function doBindAs (element) {
|
|
462
|
+
const ctx = this._ctx
|
|
463
|
+
const el = element ?? this
|
|
464
|
+
return doBind(ctx, el, (elCtx) => this.subscribe(updateReplacer(ctx, el, elCtx)))
|
|
465
|
+
}
|
|
449
466
|
|
|
450
467
|
/**
|
|
451
468
|
* Reconcile the state of the current array value with the state of the bound elements
|
|
@@ -479,16 +496,18 @@ function arrangeElements (ctx, bindContext, oldState) {
|
|
|
479
496
|
|
|
480
497
|
const keys = {}
|
|
481
498
|
const keysArr = []
|
|
482
|
-
|
|
483
|
-
const key = keyMapper(ctx.mapKey, v.isFnState ? v() : v)
|
|
484
|
-
acc[key] = v
|
|
485
|
-
return acc
|
|
486
|
-
}, {})
|
|
487
|
-
|
|
499
|
+
let oldStateMap = null
|
|
488
500
|
for (const i in ctx.currentValue) {
|
|
489
501
|
let valueState = ctx.currentValue[i]
|
|
490
502
|
// if the value is not a fnstate, we need to wrap it
|
|
491
503
|
if (valueState === null || valueState === undefined || !valueState.isFnState) {
|
|
504
|
+
if (oldStateMap === null) {
|
|
505
|
+
oldStateMap = oldState && oldState.reduce((acc, v) => {
|
|
506
|
+
const key = keyMapper(ctx.mapKey, v.isFnState ? v() : v)
|
|
507
|
+
acc[key] = v
|
|
508
|
+
return acc
|
|
509
|
+
}, {})
|
|
510
|
+
}
|
|
492
511
|
// check if we have an old state for this key
|
|
493
512
|
const key = keyMapper(ctx.mapKey, valueState)
|
|
494
513
|
if (oldStateMap && oldStateMap[key]) {
|
|
@@ -740,3 +759,81 @@ const stringifyStyle = style =>
|
|
|
740
759
|
typeof style === 'string'
|
|
741
760
|
? style
|
|
742
761
|
: Object.keys(style).map(prop => `${prop}:${style[prop]}`).join(';')
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Create a compiled template function. The returned function takes a single object that contains the properties
|
|
765
|
+
* defined in the template.
|
|
766
|
+
*
|
|
767
|
+
* This allows fast rendering by pre-creating a dom element with the entire template structure then cloning and populating
|
|
768
|
+
* the clone with data from the provided context. This avoids the work of having to re-execute the tag functions
|
|
769
|
+
* one by one and can speed up situations where a similar element is created many times.
|
|
770
|
+
*
|
|
771
|
+
* You cannot bind state to the initial template. If you attempt to, the state will be read, but the elements will
|
|
772
|
+
* not be updated when the state changes because they will not be bound to the cloned element.
|
|
773
|
+
* All state bindings must be passed in the context to the compiled template to work correctly.
|
|
774
|
+
*
|
|
775
|
+
* @param {(any)=>Node} templateFn A function that returns a html node.
|
|
776
|
+
* @return {(any)=>Node} A function that takes a context object and returns a rendered node.
|
|
777
|
+
*
|
|
778
|
+
*/
|
|
779
|
+
export function fntemplate (templateFn) {
|
|
780
|
+
if (typeof templateFn !== 'function') {
|
|
781
|
+
throw new Error('You must pass a function to fntemplate.')
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const bindingsByPath = []
|
|
785
|
+
|
|
786
|
+
const initContext = prop => {
|
|
787
|
+
const placeholder = (element, type, attr) => {
|
|
788
|
+
if (!element._tpl_bind) element._tpl_bind = []
|
|
789
|
+
element._tpl_bind.push({ prop, type, attr })
|
|
790
|
+
}
|
|
791
|
+
placeholder.isTemplatePlaceholder = true
|
|
792
|
+
return placeholder
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const root = templateFn(initContext)
|
|
796
|
+
|
|
797
|
+
const traverse = (node, path) => {
|
|
798
|
+
if (node._tpl_bind) {
|
|
799
|
+
bindingsByPath.push({ path: [...path], binds: node._tpl_bind })
|
|
800
|
+
delete node._tpl_bind
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
let child = node.firstChild
|
|
804
|
+
let i = 0
|
|
805
|
+
while (child) {
|
|
806
|
+
traverse(child, [...path, i])
|
|
807
|
+
child = child.nextSibling
|
|
808
|
+
i++
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
traverse(root, [])
|
|
813
|
+
|
|
814
|
+
return (context) => {
|
|
815
|
+
const clone = root.cloneNode(true)
|
|
816
|
+
for (let i = 0; i < bindingsByPath.length; i++) {
|
|
817
|
+
const entry = bindingsByPath[i]
|
|
818
|
+
let target = clone
|
|
819
|
+
const path = entry.path
|
|
820
|
+
for (let j = 0; j < path.length; j++) {
|
|
821
|
+
target = target.childNodes[path[j]]
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const binds = entry.binds
|
|
825
|
+
for (let j = 0; j < binds.length; j++) {
|
|
826
|
+
const b = binds[j]
|
|
827
|
+
const val = context[b.prop]
|
|
828
|
+
if (b.type === 'node') {
|
|
829
|
+
target.replaceWith(renderNode(val))
|
|
830
|
+
} else if (b.type === 'attr') {
|
|
831
|
+
setAttribute(b.attr, val, target)
|
|
832
|
+
} else if (b.type === 'style') {
|
|
833
|
+
setStyle(b.attr, val, target)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return clone
|
|
838
|
+
}
|
|
839
|
+
}
|