simplyview 3.0.2 → 3.0.4

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/src/bind.mjs CHANGED
@@ -7,14 +7,28 @@ class SimplyBind {
7
7
  container: document.body,
8
8
  attribute: 'data-bind',
9
9
  transformers: [],
10
- defaultTransformers: [defaultTransformer]
10
+ defaultTransformers: {
11
+ field: [defaultFieldTransformer],
12
+ list: [defaultListTransformer],
13
+ map: [defaultMapTransformer]
14
+ }
11
15
  }
12
16
  if (!options?.root) {
13
17
  throw new Error('bind needs at least options.root set')
14
18
  }
15
19
  this.options = Object.assign({}, defaultOptions, options)
16
20
 
17
- const attribute = this.options.attribute
21
+ const attribute = this.options.attribute
22
+ const bindAttributes = [attribute+'-field',attribute+'-list',attribute+'-map']
23
+ const bindSelector = `[${attribute}-field],[${attribute}-list],[${attribute}-map]`
24
+
25
+ const getBindingAttribute = (el) => {
26
+ const foundAttribute = bindAttributes.find(attr => el.hasAttribute(attr))
27
+ if (!foundAttribute) {
28
+ console.error('No matching attribute found',el)
29
+ }
30
+ return foundAttribute
31
+ }
18
32
 
19
33
  // sets up the effect that updates the element if its
20
34
  // data binding value changes
@@ -23,8 +37,9 @@ class SimplyBind {
23
37
  this.bindings.set(el, throttledEffect(() => {
24
38
  const context = {
25
39
  templates: el.querySelectorAll(':scope > template'),
26
- path: this.getBindingPath(el)
40
+ attribute: getBindingAttribute(el)
27
41
  }
42
+ context.path = this.getBindingPath(el)
28
43
  context.value = getValueByPath(this.options.root, context.path)
29
44
  context.element = el
30
45
  runTransformers(context)
@@ -36,7 +51,18 @@ class SimplyBind {
36
51
  // each transformer can opt to call the next or not
37
52
  // transformers should return the context object (possibly altered)
38
53
  const runTransformers = (context) => {
39
- let transformers = this.options.defaultTransformers || []
54
+ let transformers
55
+ switch(context.attribute) {
56
+ case this.options.attribute+'-field':
57
+ transformers = this.options.defaultTransformers.field || []
58
+ break
59
+ case this.options.attribute+'-list':
60
+ transformers = this.options.defaultTransformers.list || []
61
+ break
62
+ case this.options.attribute+'-map':
63
+ transformers = this.options.defaultTransformers.map || []
64
+ break
65
+ }
40
66
  if (context.element.dataset.transform) {
41
67
  context.element.dataset.transform.split(' ').filter(Boolean).forEach(t => {
42
68
  if (this.options.transformers[t]) {
@@ -69,12 +95,13 @@ class SimplyBind {
69
95
  // if any element is added, and has a data bind attribute
70
96
  // it applies that data binding
71
97
  const updateBindings = (changes) => {
98
+ const selector = `[${attribute}-field],[${attribute}-list],[${attribute}-map]`
72
99
  for (const change of changes) {
73
100
  if (change.type=="childList" && change.addedNodes) {
74
101
  for (let node of change.addedNodes) {
75
102
  if (node instanceof HTMLElement) {
76
- let bindings = Array.from(node.querySelectorAll(`[${attribute}]`))
77
- if (node.matches(`[${attribute}]`)) {
103
+ let bindings = Array.from(node.querySelectorAll(selector))
104
+ if (node.matches(selector)) {
78
105
  bindings.unshift(node)
79
106
  }
80
107
  if (bindings.length) {
@@ -100,7 +127,11 @@ class SimplyBind {
100
127
  // this finds elements with data binding attributes and applies those bindings
101
128
  // must come after setting up the observer, or included templates
102
129
  // won't trigger their own bindings
103
- const bindings = this.options.container.querySelectorAll('['+this.options.attribute+']:not(template)')
130
+ const bindings = this.options.container.querySelectorAll(
131
+ '['+this.options.attribute+'-field]'+
132
+ ',['+this.options.attribute+'-list]'+
133
+ ',['+this.options.attribute+'-map]'
134
+ )
104
135
  if (bindings.length) {
105
136
  applyBindings(bindings)
106
137
  }
@@ -127,23 +158,25 @@ class SimplyBind {
127
158
  }
128
159
  let clone = template.content.cloneNode(true)
129
160
  if (!clone.children?.length) {
130
- throw new Error('template must contain a single html element', { cause: template })
161
+ return clone
131
162
  }
132
163
  if (clone.children.length>1) {
133
164
  throw new Error('template must contain a single root node', { cause: template })
134
165
  }
135
- const bindings = clone.querySelectorAll('['+this.options.attribute+']')
136
166
  const attribute = this.options.attribute
167
+ const attributes = [attribute+'-field',attribute+'-list',attribute+'-map']
168
+ const bindings = clone.querySelectorAll(`[${attribute}-field],[${attribute}-list],[${attribute}-map]`)
137
169
  for (let binding of bindings) {
138
- const bind = binding.getAttribute(attribute)
139
- if (bind.substring(0, '#root.'.length)=='#root.') {
140
- binding.setAttribute(attribute, bind.substring('#root.'.length))
141
- } else if (bind=='#value' && index!=null) {
142
- binding.setAttribute(attribute, path+'.'+index)
170
+ const attr = attributes.find(attr => binding.hasAttribute(attr))
171
+ const bind = binding.getAttribute(attr)
172
+ if (bind.substring(0, ':root.'.length)==':root.') {
173
+ binding.setAttribute(attr, bind.substring(':root.'.length))
174
+ } else if (bind==':value' && index!=null) {
175
+ binding.setAttribute(attr, path+'.'+index)
143
176
  } else if (index!=null) {
144
- binding.setAttribute(attribute, path+'.'+index+'.'+bind)
177
+ binding.setAttribute(attr, path+'.'+index+'.'+bind)
145
178
  } else {
146
- binding.setAttribute(attribute, parent+'.'+bind)
179
+ binding.setAttribute(attr, parent+'.'+bind)
147
180
  }
148
181
  }
149
182
  if (typeof index !== 'undefined') {
@@ -156,7 +189,16 @@ class SimplyBind {
156
189
  }
157
190
 
158
191
  getBindingPath(el) {
159
- return el.getAttribute(this.options.attribute)
192
+ const attributes = [
193
+ this.options.attribute+'-field',
194
+ this.options.attribute+'-list',
195
+ this.options.attribute+'-map'
196
+ ]
197
+ for (let attr of attributes) {
198
+ if (el.hasAttribute(attr)) {
199
+ return el.getAttribute(attr)
200
+ }
201
+ }
160
202
  }
161
203
 
162
204
  /**
@@ -169,7 +211,7 @@ class SimplyBind {
169
211
  let path = this.getBindingPath(t)
170
212
  let currentItem
171
213
  if (path) {
172
- if (path.substr(0,6)=='#root.') {
214
+ if (path.substr(0,6)==':root.') {
173
215
  currentItem = getValueByPath(this.options.root, path)
174
216
  } else {
175
217
  currentItem = getValueByPath(value, path)
@@ -182,16 +224,19 @@ class SimplyBind {
182
224
  const strItem = ''+currentItem
183
225
  let matches = t.getAttribute(this.options.attribute+'-match')
184
226
  if (matches) {
185
- if (matches==='#empty' && !currentItem) {
227
+ if (matches===':empty' && !currentItem) {
186
228
  return t
187
- } else if (matches==='#notempty' && currentItem) {
229
+ } else if (matches===':notempty' && currentItem) {
188
230
  return t
189
231
  }
190
232
  if (strItem.match(matches)) {
191
233
  return t
192
234
  }
193
235
  }
194
- if (!matches) {
236
+ if (!matches && currentItem!==null && currentItem!==undefined) {
237
+ //FIXME: this doesn't run templates in lists where list entry is null
238
+ //which messes up the count
239
+ //
195
240
  // no data-bind-match is set, so return this template
196
241
  return t
197
242
  }
@@ -229,13 +274,13 @@ export function bind(options)
229
274
 
230
275
  /**
231
276
  * Returns true if a matches b, either by having the
232
- * same string value, or matching string #empty against a falsy value
277
+ * same string value, or matching string :empty against a falsy value
233
278
  */
234
279
  export function matchValue(a,b) {
235
- if (a=='#empty' && !b) {
280
+ if (a==':empty' && !b) {
236
281
  return true
237
282
  }
238
- if (b=='#empty' && !a) {
283
+ if (b==':empty' && !a) {
239
284
  return true
240
285
  }
241
286
  if (''+a == ''+b) {
@@ -257,11 +302,11 @@ export function getValueByPath(root, path)
257
302
  let part, prevPart;
258
303
  while (parts.length && curr) {
259
304
  part = parts.shift()
260
- if (part=='#key') {
305
+ if (part==':key') {
261
306
  return prevPart
262
- } else if (part=='#value') {
307
+ } else if (part==':value') {
263
308
  return curr
264
- } else if (part=='#root') {
309
+ } else if (part==':root') {
265
310
  curr = root
266
311
  } else {
267
312
  part = decodeURIComponent(part)
@@ -276,7 +321,7 @@ export function getValueByPath(root, path)
276
321
  * Default transformer for data binding
277
322
  * Will be used unless overriden in the SimplyBind options parameter
278
323
  */
279
- export function defaultTransformer(context) {
324
+ export function defaultFieldTransformer(context) {
280
325
  const el = context.element
281
326
  const templates = context.templates
282
327
  const templatesCount = templates.length
@@ -284,11 +329,7 @@ export function defaultTransformer(context) {
284
329
  const value = context.value
285
330
  const attribute = this.options.attribute
286
331
 
287
- if (Array.isArray(value) && templates?.length) {
288
- transformArrayByTemplates.call(this, context)
289
- } else if (typeof value == 'object' && templates?.length) {
290
- transformObjectByTemplates.call(this, context)
291
- } else if (templates?.length) {
332
+ if (templates?.length) {
292
333
  transformLiteralByTemplates.call(this, context)
293
334
  } else if (el.tagName=='INPUT') {
294
335
  transformInput.call(this, context)
@@ -304,10 +345,49 @@ export function defaultTransformer(context) {
304
345
  return context
305
346
  }
306
347
 
348
+ export function defaultListTransformer(context) {
349
+ const el = context.element
350
+ const templates = context.templates
351
+ const templatesCount = templates.length
352
+ const path = context.path
353
+ const value = context.value
354
+ const attribute = this.options.attribute
355
+
356
+ if (!Array.isArray(value)) {
357
+ console.error('Value is not an array.', el, value)
358
+ } else if (!templates?.length) {
359
+ console.error('No templates found in', el)
360
+ } else {
361
+ transformArrayByTemplates.call(this, context)
362
+ }
363
+ return context
364
+ }
365
+
366
+ export function defaultMapTransformer(context) {
367
+ const el = context.element
368
+ const templates = context.templates
369
+ const templatesCount = templates.length
370
+ const path = context.path
371
+ const value = context.value
372
+ const attribute = this.options.attribute
373
+
374
+ if (typeof value != 'object') {
375
+ console.error('Value is not an object.', el, value)
376
+ } else if (!templates?.length) {
377
+ console.error('No templates found in', el)
378
+ } else {
379
+ transformObjectByTemplates.call(this, context)
380
+ }
381
+ return context
382
+ }
383
+
384
+
307
385
  /**
308
386
  * Renders an array value by applying templates for each entry
309
387
  * Replaces or removes existing DOM children if needed
310
388
  * Reuses (doesn't touch) DOM children if template doesn't change
389
+ * FIXME: this doesn't handle situations where there is no matching template
390
+ * this messes up self healing. check transformObjectByTemplates for a better implementation
311
391
  */
312
392
  export function transformArrayByTemplates(context) {
313
393
  const el = context.element
@@ -333,14 +413,14 @@ export function transformArrayByTemplates(context) {
333
413
  // remove this
334
414
  item.remove()
335
415
  } else {
336
- // check that all data-bind params start with current json path or a '#', otherwise replaceChild
416
+ // check that all data-bind params start with current json path or ':root', otherwise replaceChild
337
417
  let bindings = Array.from(item.querySelectorAll(`[${attribute}]`))
338
418
  if (item.matches(`[${attribute}]`)) {
339
419
  bindings.unshift(item)
340
420
  }
341
421
  let needsReplacement = bindings.find(b => {
342
422
  let databind = b.getAttribute(attribute)
343
- return (databind.substr(0,5)!=='#root'
423
+ return (databind.substr(0,5)!==':root'
344
424
  && databind.substr(0, path.length)!==path)
345
425
  })
346
426
  if (!needsReplacement) {
@@ -400,7 +480,10 @@ export function transformObjectByTemplates(context) {
400
480
  context.index = key
401
481
  let item = items.shift()
402
482
  if (!item) { // more properties than rendered items
403
- el.appendChild(this.applyTemplate(context))
483
+ let clone = this.applyTemplate(context)
484
+ if (clone.firstElementChild) {
485
+ el.appendChild(clone)
486
+ }
404
487
  continue
405
488
  }
406
489
  if (item.getAttribute[attribute+'-key']!=key) {
@@ -432,6 +515,17 @@ export function transformObjectByTemplates(context) {
432
515
  }
433
516
  }
434
517
 
518
+ function getParentPath(el, attribute) {
519
+ const parentEl = el.parentElement?.closest(`[${attribute}-list],[${attribute}-map]`)
520
+ if (!parentEl) {
521
+ return ':root'
522
+ }
523
+ if (parentEl.hasAttribute(`${attribute}-list`)) {
524
+ return parentEl.getAttribute(`${attribute}-list`)
525
+ }
526
+ return parentEl.getAttribute(`${attribute}-map`)
527
+ }
528
+
435
529
  /**
436
530
  * transforms the contents of an html element by rendering
437
531
  * a matching template, once.
@@ -446,7 +540,8 @@ export function transformLiteralByTemplates(context) {
446
540
 
447
541
  const rendered = el.querySelector(':scope > :not(template)')
448
542
  const template = this.findTemplate(templates, value)
449
- context.parent = el.parentElement?.closest(`[${attribute}]`)?.getAttribute(attribute) || '#root'
543
+
544
+ context.parent = getParentPath(el, attribute)
450
545
  if (rendered) {
451
546
  if (template) {
452
547
  if (rendered?.$bindTemplate != template) {
package/src/command.mjs CHANGED
@@ -21,7 +21,7 @@ class SimplyCommands {
21
21
  return
22
22
  }
23
23
  const shouldContinue = this[command.name].call(options.app, command.source, command.value)
24
- if (shouldContinue===false) {
24
+ if (shouldContinue!==true) {
25
25
  evt.preventDefault()
26
26
  evt.stopPropagation()
27
27
  return false
@@ -1,25 +1,21 @@
1
1
  import { activate } from './activate.mjs'
2
- import * as action from './action.mjs'
2
+ import { actions as action } from './action.mjs'
3
3
  import { app } from './app.mjs'
4
- import { bind } from './bind.mjs'
5
- import * as command from './command.mjs'
4
+ import { commands as command } from './command.mjs'
6
5
  import { include } from './include.mjs'
7
- import * as key from './key.mjs'
8
- import * as model from './model.mjs'
9
- import * as route from './route.mjs'
10
- import * as state from './state.mjs'
6
+ import { keys as key } from './key.mjs'
7
+ import { routes as route } from './route.mjs'
8
+ import { view } from './view.mjs'
11
9
 
12
10
  const simply = {
13
11
  activate,
14
12
  action,
15
13
  app,
16
- bind,
17
14
  command,
18
15
  include,
19
16
  key,
20
- model,
21
17
  route,
22
- state
18
+ view
23
19
  }
24
20
 
25
21
  window.simply = simply
package/src/include.mjs CHANGED
@@ -53,7 +53,7 @@ const waitForPreviousScripts = async () => {
53
53
  // that triggers the Promise.resolve method
54
54
  return new Promise(function(resolve) {
55
55
  var next = globalThis.document.createElement('script')
56
- next.src = "javascript:document.dispatchEvent(new Event('simply-include-next'))"
56
+ next.src = "https://cdn.jsdelivr.net/gh/simplyedit/simplyview/dist/simply.include.next.js"
57
57
  next.async = false
58
58
  globalThis.document.addEventListener('simply-include-next', () => {
59
59
  head.removeChild(next)
@@ -122,18 +122,21 @@ export const include = {
122
122
  // order in which they are defined
123
123
  let scriptsFragment = globalThis.document.createDocumentFragment()
124
124
  const scripts = fragment.querySelectorAll('script')
125
- for (let script of scripts) {
126
- let placeholder = globalThis.document.createComment(script.src || 'inline script')
127
- script.parentNode.insertBefore(placeholder, script)
128
- script.dataset.simplyLocation = scriptLocations.length
129
- scriptLocations.push(placeholder)
130
- scriptsFragment.appendChild(script)
125
+ if (scripts.length) {
126
+ for (let script of scripts) {
127
+ let placeholder = globalThis.document.createComment(script.src || 'inline script')
128
+ script.parentNode.insertBefore(placeholder, script)
129
+ script.dataset.simplyLocation = scriptLocations.length
130
+ scriptLocations.push(placeholder)
131
+ scriptsFragment.appendChild(script)
132
+ }
133
+ globalThis.setTimeout(function() {
134
+ include.scripts(Array.from(scriptsFragment.children), link ? link.href : globalThis.location.href )
135
+ }, 10)
131
136
  }
132
137
  // add the remainder before the include link
133
138
  link.parentNode.insertBefore(fragment, link ? link : null)
134
- globalThis.setTimeout(function() {
135
- include.scripts(scriptsFragment.childNodes, link ? link.href : globalThis.location.href )
136
- }, 10)
139
+
137
140
  }
138
141
  }
139
142
 
package/src/key.mjs CHANGED
@@ -1,4 +1,12 @@
1
- class SimplyKeys {
1
+ const KEY = Object.freeze({
2
+ Compose: 229,
3
+ Control: 17,
4
+ Meta: 224,
5
+ Alt: 18,
6
+ Shift: 16
7
+ })
8
+
9
+ class SimplyKey {
2
10
  constructor(options = {}) {
3
11
  if (!options.app) {
4
12
  options.app = {}
@@ -9,38 +17,83 @@ class SimplyKeys {
9
17
  Object.assign(this, options.keys)
10
18
 
11
19
  const keyHandler = (e) => {
12
- if (e.isComposing || e.keyCode === 229) {
13
- return;
20
+ if (e.isComposing || e.keyCode === KEY.Compose) {
21
+ return
14
22
  }
15
23
  if (e.defaultPrevented) {
16
- return;
24
+ return
17
25
  }
18
26
  if (!e.target) {
19
- return;
27
+ return
20
28
  }
21
29
 
22
- let selectedKeyboard = 'default';
30
+ let selectedKeyboard = 'default'
23
31
  if (e.target.closest('[data-simply-keyboard]')) {
24
- selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
32
+ selectedKeyboard = e.target.closest('[data-simply-keyboard]')
33
+ .dataset.simplyKeyboard
34
+ }
35
+ let keyCombination = []
36
+ if (e.ctrlKey && e.keyCode!=KEY.Control) {
37
+ keyCombination.push('Control')
25
38
  }
26
- let key = '';
27
- if (e.ctrlKey && e.keyCode!=17) {
28
- key+='Control+';
39
+ if (e.metaKey && e.keyCode!=KEY.Meta) {
40
+ keyCombination.push('Meta')
29
41
  }
30
- if (e.metaKey && e.keyCode!=224) {
31
- key+='Meta+';
42
+ if (e.altKey && e.keyCode!=KEY.Alt) {
43
+ keyCombination.push('Alt')
32
44
  }
33
- if (e.altKey && e.keyCode!=18) {
34
- key+='Alt+';
45
+ if (e.shiftKey && e.keyCode!=KEY.Shift) {
46
+ keyCombination.push('Shift')
35
47
  }
36
- if (e.shiftKey && e.keyCode!=16) {
37
- key+='Shift+';
48
+ keyCombination.push(e.key.toLowerCase())
49
+
50
+ let keyboards = []
51
+ let keyboardElement = event.target.closest('[data-simply-keyboard]')
52
+ while (keyboardElement) {
53
+ keyboards.push(keyboardElement.dataset.simplyKeyboard)
54
+ keyboardElement = keyboardElement.parentNode.closest('[data-simply-keyboard]')
38
55
  }
39
- key+=e.key;
56
+ keyboards.push('')
57
+
58
+ let keyboard, subkeyboard
59
+ let separators = ['+','-']
60
+
61
+ for (i in keyboards) {
62
+ keyboard = keyboards[i]
63
+ if (keyboard == '') {
64
+ subkeyboard = 'default'
65
+ } else {
66
+ subkeyboard = keyboard
67
+ keyboard += '.'
68
+ }
69
+ for (let separator of separators) {
70
+ let keyString = keyCombination.join(separator)
71
+
72
+ if (this[subkeyboard] && (typeof this[subkeyboard][keyString]=='function')) {
73
+ let _continue = this[subkeyboard][keyString].call(this[subkeyboard], e)
74
+ if (!_continue) {
75
+ e.preventDefault()
76
+ return
77
+ }
78
+ }
79
+ if (typeof this[subkeyboard + keyString] == 'function') {
80
+ let _continue = this[subkeyboard + keyString].call(this, e)
81
+ if (!_continue) {
82
+ e.preventDefault()
83
+ return
84
+ }
85
+ }
86
+
87
+ if (this[selectedKeyboard] && this[selectedKeyboard][keyString]) {
88
+ let targets = options.app.container.querySelectorAll('[data-simply-accesskey="'
89
+ + keyboard + keyString + '"]')
90
+ if (targets.length) {
91
+ targets.forEach(t => t.click())
92
+ e.preventDefault()
93
+ }
94
+ }
40
95
 
41
- if (this[selectedKeyboard] && this[selectedKeyboard][key]) {
42
- let keyboard = this[selectedKeyboard]
43
- keyboard[key].call(options.app,e);
96
+ }
44
97
  }
45
98
  }
46
99
 
@@ -50,6 +103,6 @@ class SimplyKeys {
50
103
  }
51
104
 
52
105
  export function keys(options={}) {
53
- return new SimplyKeys(options)
106
+ return new SimplyKey(options)
54
107
  }
55
108
 
package/src/route.mjs CHANGED
@@ -6,6 +6,8 @@ class SimplyRoute {
6
6
  constructor(options={}) {
7
7
  this.root = options.root || '/'
8
8
  this.app = options.app
9
+ this.addMissingSlash = !!options.addMissingSlash
10
+ this.matchExact = !!options.matchExact
9
11
  this.clear()
10
12
  if (options.routes) {
11
13
  this.load(options.routes)
@@ -13,7 +15,7 @@ class SimplyRoute {
13
15
  }
14
16
 
15
17
  load(routes) {
16
- parseRoutes(routes, this.routeInfo)
18
+ parseRoutes(routes, this.routeInfo, this.matchExact)
17
19
  }
18
20
 
19
21
  clear() {
@@ -44,6 +46,15 @@ class SimplyRoute {
44
46
  path = getPath(path);
45
47
  for ( let route of this.routeInfo) {
46
48
  matches = route.match.exec(path)
49
+ if (this.addMissingSlash && !matches?.length) {
50
+ if (path && path[path.length-1]!='/') {
51
+ matches = route.match.exec(path+'/')
52
+ if (matches) {
53
+ path+='/'
54
+ history.replaceState({}, '', getURL(path))
55
+ }
56
+ }
57
+ }
47
58
  if (matches && matches.length) {
48
59
  var params = {};
49
60
  route.params.forEach((key, i) => {
@@ -62,9 +73,6 @@ class SimplyRoute {
62
73
  return args.result
63
74
  }
64
75
  }
65
- if (path && path[path.length-1]!='/') {
66
- return this.match(path+'/', options)
67
- }
68
76
  return false
69
77
  }
70
78
 
@@ -93,7 +101,7 @@ class SimplyRoute {
93
101
  this.match(getPath(document.location.pathname, this.root))
94
102
  }
95
103
  })
96
- globalThis.document.addEventListener('click', (evt) => {
104
+ this.app.container.addEventListener('click', (evt) => {
97
105
  if (evt.ctrlKey) {
98
106
  return;
99
107
  }
@@ -117,10 +125,12 @@ class SimplyRoute {
117
125
  if ( this.has(path) ) {
118
126
  let params = this.runListeners('goto', { path: path});
119
127
  if (params.path) {
120
- this.goto(params.path);
128
+ if (this.goto(params.path)) {
129
+ // now cancel the browser navigation, since a route handler was found
130
+ evt.preventDefault();
131
+ return false;
132
+ }
121
133
  }
122
- evt.preventDefault();
123
- return false;
124
134
  }
125
135
  }
126
136
  })
@@ -195,12 +205,14 @@ function getURL(path, root) {
195
205
  return root + path;
196
206
  }
197
207
 
198
- function getRegexpFromRoute(route) {
199
- return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)'));
208
+ function getRegexpFromRoute(route, exact=false) {
209
+ if (exact) {
210
+ return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)')+'(\\?|$)')
211
+ }
212
+ return new RegExp('^'+route.replace(/:\w+/g, '([^/]+)').replace(/:\*/, '(.*)'))
200
213
  }
201
214
 
202
- function parseRoutes(routes) {
203
- let routeInfo = []
215
+ function parseRoutes(routes, routeInfo, exact=false) {
204
216
  const paths = Object.keys(routes)
205
217
  const matchParams = /:(\w+|\*)/g
206
218
  for (let path of paths) {
@@ -213,7 +225,7 @@ function parseRoutes(routes) {
213
225
  }
214
226
  } while(matches)
215
227
  routeInfo.push({
216
- match: getRegexpFromRoute(path),
228
+ match: getRegexpFromRoute(path, exact),
217
229
  params: params,
218
230
  action: routes[path]
219
231
  })
package/src/view.mjs ADDED
@@ -0,0 +1,20 @@
1
+ export function view(options) {
2
+ if (options.app) {
3
+ options.app.view = options.view || {}
4
+
5
+ const load = () => {
6
+ const data = options.app.view
7
+ const path = globalThis.editor.data.getDataPath(options.app.container || document.body)
8
+ options.app.view = globalThis.editor.currentData[path]
9
+ Object.assign(options.app.view, data)
10
+ }
11
+ if (globalThis.editor && globalThis.editor.currentData) {
12
+ load()
13
+ } else {
14
+ document.addEventListener('simply-content-loaded', load)
15
+ }
16
+ return options.app.view
17
+ } else {
18
+ return options.view
19
+ }
20
+ }
package/src/changes.md DELETED
@@ -1,18 +0,0 @@
1
- # changes
2
-
3
- removed:
4
- - render, observe
5
- these are replaced with bind.mjs and state.mjs (signal, effect)
6
- - view
7
- replaced by state.mjs (signal)
8
- - collect
9
- replaced by state.mjs (effect)
10
- - keyboard
11
- renamed to key.mjs
12
- - path
13
- was never used, use jsonPointer instead if you need it
14
- - viewmodel
15
- replaced wiht model.mjs
16
- - resize
17
- use @container queries instead
18
-