@muze-labs/simplyflow 0.9.0 → 0.10.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 +72 -16
- package/dist/simply.flow.js +458 -298
- package/dist/simply.flow.min.js +1 -1
- package/dist/simply.flow.min.js.map +4 -4
- package/package.json +29 -42
- package/src/action.mjs +1 -64
- package/src/app.mjs +1 -282
- package/src/behavior.mjs +1 -121
- package/src/bind-render.mjs +1 -0
- package/src/bind-transformers.mjs +1 -0
- package/src/bind.mjs +1 -522
- package/src/command.mjs +1 -225
- package/src/dom.mjs +1 -274
- package/src/highlight.mjs +1 -11
- package/src/include.mjs +1 -239
- package/src/{flow.mjs → index.mjs} +13 -13
- package/src/model.mjs +1 -290
- package/src/path.mjs +1 -47
- package/src/route.mjs +1 -418
- package/src/shortcut.mjs +1 -146
- package/src/state.mjs +1 -1347
- package/src/suggest.mjs +1 -68
- package/src/symbols.mjs +1 -9
- package/MUZE_ALIGNMENT.md +0 -118
- package/src/bind.render.mjs +0 -694
- package/src/bind.transformers.mjs +0 -25
package/src/bind.mjs
CHANGED
|
@@ -1,522 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { escape_html, fixed_content } from './bind.transformers.mjs'
|
|
3
|
-
import * as render from './bind.render.mjs'
|
|
4
|
-
import { DEP } from './symbols.mjs'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Implements one way databinding, updating dom elements with matching attributes
|
|
8
|
-
* to changes in signals (see state.mjs)
|
|
9
|
-
*
|
|
10
|
-
* @class
|
|
11
|
-
*/
|
|
12
|
-
class SimplyBind
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param Object options - a set of options for this instance, options may include:
|
|
17
|
-
* - root (signal) (required) - the root data object that contains al signals that can be bound
|
|
18
|
-
* - container (HTMLElement) - the dom element to use as the root for all bindings
|
|
19
|
-
* - attribute (string) - the prefix for the field, edit, list and map attributes, e.g. 'data-bind'
|
|
20
|
-
* - transformers (object name:function) - a map of transformer names and functions
|
|
21
|
-
* - render (object with field, list and map properties); edit uses field renderers
|
|
22
|
-
*/
|
|
23
|
-
constructor(options)
|
|
24
|
-
{
|
|
25
|
-
/**
|
|
26
|
-
* A map of HTMLElements and the data bindings on each, in the form of
|
|
27
|
-
* the connectedSignal returned by the (throttled)Effect.
|
|
28
|
-
* @type {Map}
|
|
29
|
-
* @public
|
|
30
|
-
*/
|
|
31
|
-
this.bindings = new Map()
|
|
32
|
-
|
|
33
|
-
const defaultTransformers = {
|
|
34
|
-
escape_html,
|
|
35
|
-
fixed_content
|
|
36
|
-
}
|
|
37
|
-
const defaultOptions = {
|
|
38
|
-
container: document.body,
|
|
39
|
-
attribute: 'data-flow',
|
|
40
|
-
transformers: defaultTransformers,
|
|
41
|
-
render: {
|
|
42
|
-
field: [render.field],
|
|
43
|
-
list: [render.list],
|
|
44
|
-
map: [render.map]
|
|
45
|
-
},
|
|
46
|
-
renderers: {
|
|
47
|
-
'INPUT':render.input,
|
|
48
|
-
'TEXTAREA':render.input,
|
|
49
|
-
'BUTTON':render.button,
|
|
50
|
-
'SELECT':render.select,
|
|
51
|
-
'A':render.anchor,
|
|
52
|
-
'IMG':render.image,
|
|
53
|
-
'IFRAME':render.iframe,
|
|
54
|
-
'META':render.meta,
|
|
55
|
-
'TEMPLATE':null,
|
|
56
|
-
'*':render.element
|
|
57
|
-
},
|
|
58
|
-
twoway: false
|
|
59
|
-
}
|
|
60
|
-
if (!options?.root) {
|
|
61
|
-
throw new Error('bind needs at least options.root set')
|
|
62
|
-
}
|
|
63
|
-
this.options = Object.assign({}, defaultOptions, options)
|
|
64
|
-
if (options.transformers) {
|
|
65
|
-
this.options.transformers = Object.assign({}, defaultTransformers, options?.transformers)
|
|
66
|
-
}
|
|
67
|
-
const attribute = this.options.attribute
|
|
68
|
-
const bindAttributes = [attribute+'-field',attribute+'-edit',attribute+'-list',attribute+'-map']
|
|
69
|
-
const transformAttribute = attribute+'-transform'
|
|
70
|
-
|
|
71
|
-
const getBindingAttribute = (el) => {
|
|
72
|
-
const foundAttribute = bindAttributes.find(attr => el.hasAttribute(attr))
|
|
73
|
-
if (!foundAttribute) {
|
|
74
|
-
console.error('No matching attribute found',el,bindAttributes)
|
|
75
|
-
}
|
|
76
|
-
return foundAttribute
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// sets up the effect that updates the element if its
|
|
80
|
-
// data binding value changes
|
|
81
|
-
const renderElement = (el) => {
|
|
82
|
-
this.bindings.set(el, throttledEffect(() => {
|
|
83
|
-
if (!el.isConnected) {
|
|
84
|
-
// el is no longer part of this document
|
|
85
|
-
untrack(el, this.getBindingPath(el))
|
|
86
|
-
const binding = this.bindings.get(el)
|
|
87
|
-
if (binding) {
|
|
88
|
-
destroy(binding)
|
|
89
|
-
this.bindings.delete(el)
|
|
90
|
-
}
|
|
91
|
-
// doing this here instead of in a mutationobserver
|
|
92
|
-
// allows an element to be temporary removed and then inserted
|
|
93
|
-
// without the binding having to be reset
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
let context = {
|
|
97
|
-
templates: el.querySelectorAll(':scope > template'),
|
|
98
|
-
attribute: getBindingAttribute(el)
|
|
99
|
-
}
|
|
100
|
-
context.edit = context.attribute === this.options.attribute+'-edit'
|
|
101
|
-
context.path = this.getBindingPath(el)
|
|
102
|
-
context.value = getValueByPath(this.options.root, context.path)
|
|
103
|
-
context.element = el
|
|
104
|
-
track(el, context)
|
|
105
|
-
runTransformers(context)
|
|
106
|
-
}, 50))
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// finds and runs applicable transformers
|
|
110
|
-
// creates a stack of transformers, calls the topmost
|
|
111
|
-
// each transformer can opt to call the next or not
|
|
112
|
-
// transformers should return the context object (possibly altered)
|
|
113
|
-
const runTransformers = (context) => {
|
|
114
|
-
let transformers
|
|
115
|
-
switch(context.attribute) {
|
|
116
|
-
case this.options.attribute+'-field':
|
|
117
|
-
case this.options.attribute+'-edit':
|
|
118
|
-
transformers = Array.from(this.options.render.field)
|
|
119
|
-
break
|
|
120
|
-
case this.options.attribute+'-list':
|
|
121
|
-
transformers = Array.from(this.options.render.list)
|
|
122
|
-
break
|
|
123
|
-
case this.options.attribute+'-map':
|
|
124
|
-
transformers = Array.from(this.options.render.map)
|
|
125
|
-
break
|
|
126
|
-
default:
|
|
127
|
-
throw new Error('no valid context attribute specified',context)
|
|
128
|
-
break
|
|
129
|
-
}
|
|
130
|
-
if (context.element.hasAttribute(transformAttribute)) {
|
|
131
|
-
context.element.getAttribute(transformAttribute)
|
|
132
|
-
.split(' ').filter(Boolean)
|
|
133
|
-
.forEach(t => {
|
|
134
|
-
if (this.options.transformers[t]) {
|
|
135
|
-
transformers.push(this.options.transformers[t])
|
|
136
|
-
} else {
|
|
137
|
-
console.warn('No transformer with name '+t+' configured', {cause:context.element})
|
|
138
|
-
}
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
let next
|
|
142
|
-
for (let transformer of transformers) {
|
|
143
|
-
next = ((next, transformer) => {
|
|
144
|
-
return (context) => {
|
|
145
|
-
return transformer.call(this, context, next)
|
|
146
|
-
}
|
|
147
|
-
})(next, transformer)
|
|
148
|
-
}
|
|
149
|
-
next(context)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// given a set of elements with data bind attribute
|
|
153
|
-
// this renders each of those elements
|
|
154
|
-
const applyBindings = (bindings) => {
|
|
155
|
-
for (let bindingEl of bindings) {
|
|
156
|
-
if (!this.bindings.get(bindingEl)) { // bindingEl may have moved from somewhere else in this document
|
|
157
|
-
renderElement(bindingEl)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// this handles the mutation observer changes
|
|
163
|
-
// if any element is added, and has a data bind attribute
|
|
164
|
-
// it applies that data binding
|
|
165
|
-
const updateBindings = (changes) => {
|
|
166
|
-
const selector = `[${attribute}-field],[${attribute}-edit],[${attribute}-list],[${attribute}-map]`
|
|
167
|
-
for (const change of changes) {
|
|
168
|
-
if (change.type=="childList" && change.addedNodes) {
|
|
169
|
-
for (let node of change.addedNodes) {
|
|
170
|
-
if (node instanceof HTMLElement) {
|
|
171
|
-
let bindings = Array.from(node.querySelectorAll(selector))
|
|
172
|
-
if (node.matches(selector)) {
|
|
173
|
-
bindings.unshift(node)
|
|
174
|
-
}
|
|
175
|
-
if (bindings.length) {
|
|
176
|
-
applyBindings(bindings)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// this responds to elements getting added to the dom
|
|
185
|
-
// and if any have data bind attributes, it applies those bindings
|
|
186
|
-
this.observer = new MutationObserver((changes) => {
|
|
187
|
-
updateBindings(changes)
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
this.observer.observe(this.options.container, {
|
|
191
|
-
subtree: true,
|
|
192
|
-
childList: true
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
// this finds elements with data binding attributes and applies those bindings
|
|
196
|
-
// must come after setting up the observer, or included templates
|
|
197
|
-
// won't trigger their own bindings
|
|
198
|
-
const bindings = this.options.container.querySelectorAll(
|
|
199
|
-
':is(['+this.options.attribute+'-field]'+
|
|
200
|
-
',['+this.options.attribute+'-edit]'+
|
|
201
|
-
',['+this.options.attribute+'-list]'+
|
|
202
|
-
',['+this.options.attribute+'-map]):not(template)'
|
|
203
|
-
)
|
|
204
|
-
try {
|
|
205
|
-
if (bindings.length) {
|
|
206
|
-
applyBindings(bindings)
|
|
207
|
-
}
|
|
208
|
-
} catch (error) {
|
|
209
|
-
this.destroy()
|
|
210
|
-
throw error
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Finds the first matching template and creates a new DocumentFragment
|
|
217
|
-
* with the correct data bind attributes in it (prepends the current path)
|
|
218
|
-
* @param Context context
|
|
219
|
-
* @return DocumentFragment
|
|
220
|
-
*/
|
|
221
|
-
applyTemplate(context)
|
|
222
|
-
{
|
|
223
|
-
const path = context.path
|
|
224
|
-
const parent = context.parent
|
|
225
|
-
const templates = context.templates
|
|
226
|
-
const list = context.list
|
|
227
|
-
const index = context.index
|
|
228
|
-
const value = list ? list[index] : context.value
|
|
229
|
-
|
|
230
|
-
let template = this.findTemplate(templates, value)
|
|
231
|
-
if (!template) {
|
|
232
|
-
let result = new DocumentFragment()
|
|
233
|
-
result.innerHTML = '<!-- no matching template -->'
|
|
234
|
-
return result
|
|
235
|
-
}
|
|
236
|
-
let clone = template.content.cloneNode(true)
|
|
237
|
-
if (!clone.children?.length) {
|
|
238
|
-
return clone
|
|
239
|
-
}
|
|
240
|
-
if (clone.children.length>1) {
|
|
241
|
-
throw new Error('template must contain a single root node', { cause: template })
|
|
242
|
-
}
|
|
243
|
-
const attribute = this.options.attribute
|
|
244
|
-
|
|
245
|
-
const attributes = [attribute+'-field',attribute+'-edit',attribute+'-list',attribute+'-map']
|
|
246
|
-
const bindings = clone.querySelectorAll(`[${attribute}-field],[${attribute}-edit],[${attribute}-list],[${attribute}-map]`)
|
|
247
|
-
for (let binding of bindings) {
|
|
248
|
-
if (binding.tagName=='TEMPLATE') {
|
|
249
|
-
continue
|
|
250
|
-
}
|
|
251
|
-
const attr = attributes.find(attr => binding.hasAttribute(attr))
|
|
252
|
-
let bind = binding.getAttribute(attr)
|
|
253
|
-
bind = this.applyLinks(template.links, bind)
|
|
254
|
-
if (bind.substring(0, ':root.'.length)==':root.') {
|
|
255
|
-
binding.setAttribute(attr, bind.substring(':root.'.length))
|
|
256
|
-
} else if (bind==':value' && index!=null) {
|
|
257
|
-
binding.setAttribute(attr, path+'.'+index)
|
|
258
|
-
} else if (index!=null) {
|
|
259
|
-
binding.setAttribute(attr, path+'.'+index+'.'+bind)
|
|
260
|
-
} else {
|
|
261
|
-
binding.setAttribute(attr, parent+bind)
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
this.applyTemplateCommandValues(clone, template.links, path, index)
|
|
265
|
-
if (typeof index !== 'undefined') {
|
|
266
|
-
clone.children[0].setAttribute(attribute+'-key',index)
|
|
267
|
-
}
|
|
268
|
-
// keep track of the used template and value reference, so list items can be
|
|
269
|
-
// reused when an array insertion shifts their numeric index.
|
|
270
|
-
clone.children[0][DEP.TEMPLATE] = template
|
|
271
|
-
clone.children[0][DEP.VALUE] = value
|
|
272
|
-
|
|
273
|
-
// return clone, not the firstChild, so that all whitespace is cloned as well
|
|
274
|
-
return clone
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
applyTemplateCommandValues(fragment, links, path, index)
|
|
278
|
-
{
|
|
279
|
-
const valueAttribute = this.options.attribute+'-value'
|
|
280
|
-
const valuePathAttribute = this.options.attribute+'-value-path'
|
|
281
|
-
const valueSelector = '['+valueAttribute+']'
|
|
282
|
-
const elements = Array.from(fragment.querySelectorAll(valueSelector))
|
|
283
|
-
|
|
284
|
-
for (const element of elements) {
|
|
285
|
-
let value = element.getAttribute(valueAttribute)
|
|
286
|
-
value = this.applyLinks(links, value)
|
|
287
|
-
const resolved = templateCommandValue(value, path, index)
|
|
288
|
-
if (!resolved) {
|
|
289
|
-
continue
|
|
290
|
-
}
|
|
291
|
-
if (Object.hasOwn(resolved, 'path')) {
|
|
292
|
-
element.setAttribute(valuePathAttribute, resolved.path)
|
|
293
|
-
} else {
|
|
294
|
-
element.setAttribute(valueAttribute, resolved.value)
|
|
295
|
-
element.removeAttribute(valuePathAttribute)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
parseLinks(links)
|
|
301
|
-
{
|
|
302
|
-
let result = {}
|
|
303
|
-
links = links.split(';').map(link => link.trim())
|
|
304
|
-
for (let link of links) {
|
|
305
|
-
link = link.split('=')
|
|
306
|
-
result[link[0].trim()] = link[1].trim()
|
|
307
|
-
}
|
|
308
|
-
return result
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
applyLinks(links, value)
|
|
312
|
-
{
|
|
313
|
-
for (let link in links) {
|
|
314
|
-
if (value.startsWith(link+'.')) {
|
|
315
|
-
return links[link] + value.substr(link.length)
|
|
316
|
-
} else if (value==link) {
|
|
317
|
-
return links[link]
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return value
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Returns the path referenced in either the field, list or map attribute
|
|
325
|
-
* @param HTMLElement el
|
|
326
|
-
* @return string The path referenced, or void
|
|
327
|
-
*/
|
|
328
|
-
getBindingPath(el)
|
|
329
|
-
{
|
|
330
|
-
const attributes = [
|
|
331
|
-
this.options.attribute+'-field',
|
|
332
|
-
this.options.attribute+'-edit',
|
|
333
|
-
this.options.attribute+'-list',
|
|
334
|
-
this.options.attribute+'-map'
|
|
335
|
-
]
|
|
336
|
-
for (let attr of attributes) {
|
|
337
|
-
if (el.hasAttribute(attr)) {
|
|
338
|
-
return el.getAttribute(attr)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Finds the first template from an array of templates that
|
|
345
|
-
* matches the given value.
|
|
346
|
-
*/
|
|
347
|
-
findTemplate(templates, value)
|
|
348
|
-
{
|
|
349
|
-
const templateMatches = t => {
|
|
350
|
-
// find the value to match against (e.g. data-bind="foo")
|
|
351
|
-
let path = this.getBindingPath(t)
|
|
352
|
-
let currentItem
|
|
353
|
-
if (path) {
|
|
354
|
-
if (path.substr(0,6)==':root.') {
|
|
355
|
-
currentItem = getValueByPath(this.options.root, path.substring(6))
|
|
356
|
-
} else {
|
|
357
|
-
currentItem = getValueByPath(value, path)
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
currentItem = value
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// then check the value against pattern, if set (e.g. data-bind-match="bar")
|
|
364
|
-
const strItem = ''+currentItem
|
|
365
|
-
let matches = t.getAttribute(this.options.attribute+'-match')
|
|
366
|
-
if (matches) {
|
|
367
|
-
if (matches===':empty' && !currentItem) {
|
|
368
|
-
return t
|
|
369
|
-
} else if (matches===':notempty' && currentItem) {
|
|
370
|
-
return t
|
|
371
|
-
}
|
|
372
|
-
if (strItem == matches) {
|
|
373
|
-
return t
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
if (!matches) {
|
|
377
|
-
// no data-bind-match is set, so return this template
|
|
378
|
-
return t
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
let template = Array.from(templates).find(templateMatches)
|
|
382
|
-
let links = null
|
|
383
|
-
if (template?.hasAttribute(this.options.attribute+'-link')) {
|
|
384
|
-
links = this.parseLinks(template.getAttribute(this.options.attribute+'-link'))
|
|
385
|
-
}
|
|
386
|
-
let rel = template?.getAttribute('rel')
|
|
387
|
-
if (rel) {
|
|
388
|
-
let replacement = document.querySelector('template#'+rel)
|
|
389
|
-
if (!replacement) {
|
|
390
|
-
throw new Error('Could not find template with id '+rel)
|
|
391
|
-
}
|
|
392
|
-
template = replacement
|
|
393
|
-
}
|
|
394
|
-
if (template) {
|
|
395
|
-
template.links = links
|
|
396
|
-
}
|
|
397
|
-
return template
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
destroy()
|
|
401
|
-
{
|
|
402
|
-
this.bindings.forEach((binding, element) => {
|
|
403
|
-
untrack(element, this.getBindingPath(element))
|
|
404
|
-
destroy(binding)
|
|
405
|
-
})
|
|
406
|
-
this.bindings = new Map()
|
|
407
|
-
this.observer.disconnect()
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Returns a new instance of SimplyBind. This is the normal start
|
|
414
|
-
* of a data bind flow
|
|
415
|
-
*/
|
|
416
|
-
export function bind(options)
|
|
417
|
-
{
|
|
418
|
-
return new SimplyBind(options)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
const tracking = new Map()
|
|
422
|
-
|
|
423
|
-
export function trace(path)
|
|
424
|
-
{
|
|
425
|
-
return tracking.get(path)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function track(el, context) {
|
|
429
|
-
untrack(el)
|
|
430
|
-
if (!tracking.has(context.path)) {
|
|
431
|
-
tracking.set(context.path, [context])
|
|
432
|
-
} else {
|
|
433
|
-
tracking.get(context.path).push(context)
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function untrack(el, path) {
|
|
438
|
-
if (path) {
|
|
439
|
-
let list = tracking.get(path)
|
|
440
|
-
if (list) {
|
|
441
|
-
list = list.filter(context => context.element !== el)
|
|
442
|
-
tracking.set(path, list)
|
|
443
|
-
}
|
|
444
|
-
return
|
|
445
|
-
}
|
|
446
|
-
tracking.forEach((list, trackedPath) => {
|
|
447
|
-
list = list.filter(context => context.element !== el)
|
|
448
|
-
tracking.set(trackedPath, list)
|
|
449
|
-
})
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
function templateCommandValue(value, path, index)
|
|
455
|
-
{
|
|
456
|
-
if (!value || value[0] !== ':') {
|
|
457
|
-
return null
|
|
458
|
-
}
|
|
459
|
-
if (value === ':key') {
|
|
460
|
-
return { value: ''+index }
|
|
461
|
-
}
|
|
462
|
-
if (value === ':value') {
|
|
463
|
-
return { path: templateItemPath(path, index) }
|
|
464
|
-
}
|
|
465
|
-
if (value.startsWith(':value.')) {
|
|
466
|
-
return { path: joinPath(templateItemPath(path, index), value.substring(':value'.length)) }
|
|
467
|
-
}
|
|
468
|
-
if (value.startsWith(':root.')) {
|
|
469
|
-
return { path: value.substring(':root.'.length) }
|
|
470
|
-
}
|
|
471
|
-
return null
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function templateItemPath(path, index)
|
|
475
|
-
{
|
|
476
|
-
if (typeof index === 'undefined') {
|
|
477
|
-
return path
|
|
478
|
-
}
|
|
479
|
-
return joinPath(path, '.'+index)
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function joinPath(path, suffix)
|
|
483
|
-
{
|
|
484
|
-
if (!path) {
|
|
485
|
-
return suffix.replace(/^\./, '')
|
|
486
|
-
}
|
|
487
|
-
return path+suffix
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Returns the value by walking the given path as a json pointer, starting at root
|
|
492
|
-
* if you have a property with a '.' in its name urlencode the '.', e.g: %46
|
|
493
|
-
*
|
|
494
|
-
* @param HTMLElement root
|
|
495
|
-
* @param string path e.g. 'foo.bar'
|
|
496
|
-
* @return mixed the value found by walking the path from the root object or undefined
|
|
497
|
-
*/
|
|
498
|
-
export function getValueByPath(root, path)
|
|
499
|
-
{
|
|
500
|
-
let parts = path.split('.')
|
|
501
|
-
let curr = root
|
|
502
|
-
let part
|
|
503
|
-
part = parts.shift()
|
|
504
|
-
let prevPart = null
|
|
505
|
-
while (part && curr) {
|
|
506
|
-
part = decodeURIComponent(part)
|
|
507
|
-
if (part=='0' && !Array.isArray(curr)) {
|
|
508
|
-
// ignore so that data-flow-list="nonarray" will work
|
|
509
|
-
} else if (part==':key') {
|
|
510
|
-
curr = prevPart
|
|
511
|
-
} else if (part==':value') {
|
|
512
|
-
// do nothing
|
|
513
|
-
} else if (Array.isArray(curr) && typeof curr[part]=='undefined' && curr[0]) {
|
|
514
|
-
curr = curr[0][part] // so that data-flow-field="array.foo" works
|
|
515
|
-
} else {
|
|
516
|
-
curr = curr[part]
|
|
517
|
-
}
|
|
518
|
-
prevPart = part
|
|
519
|
-
part = parts.shift()
|
|
520
|
-
}
|
|
521
|
-
return curr
|
|
522
|
-
}
|
|
1
|
+
export * from '@muze-labs/simplyflow-bind'
|