halfcab 16.0.2 → 16.0.3

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.
Files changed (3) hide show
  1. package/halfcab.mjs +16 -76
  2. package/package.json +3 -3
  3. package/test.js +9 -104
package/halfcab.mjs CHANGED
@@ -18,7 +18,7 @@ let cssTag = cssInject
18
18
  let componentCSSString = ''
19
19
  let routesArray = []
20
20
  let externalRoutes = []
21
- let rawState = {}
21
+ let state = {}
22
22
  let router
23
23
  let rootEl
24
24
  let components
@@ -39,14 +39,14 @@ function b64DecodeUnicode (str) {
39
39
  if (typeof window !== 'undefined') {
40
40
  dataInitial = document.querySelector('[data-initial]')
41
41
  if (!!dataInitial) {
42
- rawState = (dataInitial && dataInitial.dataset.initial) && Object.assign({}, JSON.parse(b64DecodeUnicode(dataInitial.dataset.initial)))
42
+ state = (dataInitial && dataInitial.dataset.initial) && Object.assign({}, JSON.parse(b64DecodeUnicode(dataInitial.dataset.initial)))
43
43
 
44
- if (!rawState.router) {
45
- rawState.router = {}
44
+ if (!state.router) {
45
+ state.router = {}
46
46
  }
47
47
 
48
- if (!rawState.router.pathname) {
49
- Object.assign(rawState.router, {
48
+ if (!state.router.pathname) {
49
+ Object.assign(state.router, {
50
50
  pathname: window.location.pathname,
51
51
  hash: window.location.hash,
52
52
  query: qs.parse(window.location.search)
@@ -62,50 +62,6 @@ if (typeof window !== 'undefined') {
62
62
  }
63
63
  }
64
64
 
65
- const proxyCache = new WeakMap()
66
-
67
- const ARRAY_MUTATING_METHODS = new Set(['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse', 'fill', 'copyWithin'])
68
-
69
- function createState (target) {
70
- if (proxyCache.has(target)) {
71
- return proxyCache.get(target)
72
- }
73
- const proxy = new Proxy(target, {
74
- get (obj, prop) {
75
- const value = obj[prop]
76
- if (Array.isArray(obj) && typeof value === 'function' && ARRAY_MUTATING_METHODS.has(prop)) {
77
- return function (...args) {
78
- const result = Array.prototype[prop].apply(obj, args)
79
- debounce(stateUpdated)
80
- return result
81
- }
82
- }
83
- if (value !== null && typeof value === 'object') {
84
- const descriptor = Object.getOwnPropertyDescriptor(obj, prop)
85
- if (descriptor && descriptor.configurable === false && descriptor.writable === false) {
86
- return value
87
- }
88
- return createState(value)
89
- }
90
- return value
91
- },
92
- set (obj, prop, value) {
93
- obj[prop] = value
94
- debounce(stateUpdated)
95
- return true
96
- },
97
- deleteProperty (obj, prop) {
98
- delete obj[prop]
99
- debounce(stateUpdated)
100
- return true
101
- }
102
- })
103
- proxyCache.set(target, proxy)
104
- return proxy
105
- }
106
-
107
- let state = createState(rawState)
108
-
109
65
  let geb = new eventEmitter({state})
110
66
 
111
67
  const stringsCache = new WeakMap()
@@ -360,30 +316,19 @@ function stateUpdated () {
360
316
  }
361
317
  }
362
318
 
363
- function mergeInPlace (target, source) {
364
- // Recursively merge source into target, mutating target in-place so that
365
- // existing object references (and their Proxy wrappers) are preserved.
366
- // Arrays are always replaced entirely — merging arrays is complex and error-prone.
367
- for (const key of Object.keys(source)) {
368
- const srcVal = source[key]
369
- const tgtVal = target[key]
370
- if (srcVal !== null && typeof srcVal === 'object' && !Array.isArray(srcVal) &&
371
- tgtVal !== null && typeof tgtVal === 'object' && !Array.isArray(tgtVal)) {
372
- mergeInPlace(tgtVal, srcVal)
373
- } else {
374
- target[key] = srcVal
375
- }
376
- }
377
- }
378
-
379
319
  function updateState (updateObject, options) {
380
320
  if (updateObject) {
381
321
  if (options && options.deepMerge === false) {
382
- Object.assign(rawState, updateObject)
322
+ Object.assign(state, updateObject)
383
323
  } else {
384
- // Merge in-place so existing nested object references stay intact,
385
- // keeping Proxy cache entries valid.
386
- mergeInPlace(rawState, updateObject, options)
324
+ let deepMergeOptions = {clone: false}
325
+ if (options && options.arrayMerge === false) {
326
+ deepMergeOptions.arrayMerge = (destinationArray, sourceArray, options) => {
327
+ //don't merge arrays, just return the new one
328
+ return sourceArray
329
+ }
330
+ }
331
+ Object.assign(state, merge(state, updateObject, deepMergeOptions))
387
332
  }
388
333
  }
389
334
 
@@ -399,7 +344,7 @@ function updateState (updateObject, options) {
399
344
  console.log(updateObject)
400
345
  console.log(' ')
401
346
  console.log('------NEW STATE------')
402
- console.log(rawState)
347
+ console.log(state)
403
348
  console.log(' ')
404
349
  }
405
350
 
@@ -568,10 +513,6 @@ export default (config, {shiftyRouter = shiftyRouterModule, href = hrefModule, h
568
513
  })
569
514
  }
570
515
 
571
- /**
572
- * @deprecated The Proxy-based state now triggers rerenders automatically via set traps.
573
- * This function is kept for backwards compatibility but is no longer needed in most cases.
574
- */
575
516
  function rerender () {
576
517
  debounce(stateUpdated)
577
518
  }
@@ -588,7 +529,6 @@ export {
588
529
  html,
589
530
  defineRoute,
590
531
  updateState,
591
- createState,
592
532
  state,
593
533
  formField,
594
534
  gotoRoute,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcab",
3
- "version": "16.0.2",
3
+ "version": "16.0.3",
4
4
  "type": "module",
5
5
  "description": "A simple universal JavaScript framework focused on making use of es2015 template strings to build components.",
6
6
  "main": "halfcab.mjs",
@@ -11,7 +11,7 @@
11
11
  "test:coverage": "c8 --reporter=html --check-coverage --lines 75 --functions 75 --branches 75 npm test",
12
12
  "test:coveralls": "c8 npm test && c8 report --reporter=text-lcov | coveralls",
13
13
  "versionbump:fix": "npm version patch --no-git-tag-version",
14
- "versionbump:feature": "npm version minor --no-git-tag-version",
14
+ "versionbump:feature": "npm version major --no-git-tag-version",
15
15
  "versionbump:breakingchanges": "npm version major --no-git-tag-version",
16
16
  "npm-publish": "npm publish --tag beta",
17
17
  "start:example": "npx --yes serve . -l 5173"
@@ -60,7 +60,7 @@
60
60
  "lit": "^3.3.2",
61
61
  "marked": "^0.7.0",
62
62
  "qs": "^6.5.2",
63
- "shifty-router": "^0.1.1"
63
+ "shifty-router": "^1.0.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "esm": "^3.2.22"
package/test.js CHANGED
@@ -377,7 +377,12 @@ describe('halfcab', () => {
377
377
 
378
378
  })
379
379
 
380
- it(`Throws an error when a route doesn't exist`, () => {
380
+ it(`Switches to the 404 route when a route doesn't exist`, () => {
381
+ defineRoute({
382
+ path: '/404',
383
+ title: 'Not Found',
384
+ component: '404Component'
385
+ })
381
386
 
382
387
  return halfcab({
383
388
  el: '#root',
@@ -385,11 +390,9 @@ describe('halfcab', () => {
385
390
  return html `<div></div>`
386
391
  }
387
392
  })
388
- .then(rootEl => {
389
- let routing = () => {
390
- gotoRoute('/thisIsAFakeRoute')
391
- }
392
- expect(routing).to.throw()
393
+ .then(({state}) => {
394
+ gotoRoute('/thisIsAFakeRoute')
395
+ expect(state.router.component).to.equal('404Component')
393
396
  })
394
397
 
395
398
  })
@@ -403,104 +406,6 @@ describe('halfcab', () => {
403
406
  .true()
404
407
  })
405
408
 
406
- it('Keeps sub-object references live after overwriting via updateState', (done) => {
407
- halfcab({
408
- el: '#root',
409
- components () {
410
- return html `<div></div>`
411
- }
412
- })
413
- .then(({rootEl, state}) => {
414
- updateState({
415
- user: { name: 'Original Name', age: 25 }
416
- })
417
-
418
- nextTick(() => {
419
- // Grab a reference to the sub-object proxy
420
- const user = state.user
421
-
422
- // Wipe out the user object entirely via a merge
423
- updateState({ user: { name: 'New Name', age: 30 } })
424
-
425
- nextTick(() => {
426
- // The reference should reflect the new values
427
- expect(user.name).to.equal('New Name')
428
- expect(user.age).to.equal(30)
429
- done()
430
- })
431
- })
432
- })
433
- })
434
-
435
- it('Replaces arrays entirely instead of merging them', (done) => {
436
- halfcab({
437
- el: '#root',
438
- components () {
439
- return html `<div></div>`
440
- }
441
- })
442
- .then(({rootEl, state}) => {
443
- updateState({ list: [1, 2, 3] })
444
-
445
- nextTick(() => {
446
- updateState({ list: [4, 5] })
447
-
448
- nextTick(() => {
449
- expect(state.list).to.deep.equal([4, 5])
450
- done()
451
- })
452
- })
453
- })
454
- })
455
-
456
- it('Triggers rerender when a property is deleted from state', (done) => {
457
- halfcab({
458
- el: '#root',
459
- components () {
460
- return html `<div></div>`
461
- }
462
- })
463
- .then(({rootEl, state}) => {
464
- updateState({ toDelete: 'bye' })
465
-
466
- nextTick(() => {
467
- expect(state.toDelete).to.equal('bye')
468
- delete state.toDelete
469
-
470
- nextTick(() => {
471
- expect(state.toDelete).to.be.undefined()
472
- done()
473
- })
474
- })
475
- })
476
- })
477
-
478
- it('Triggers rerender when array mutation methods are used on state', (done) => {
479
- halfcab({
480
- el: '#root',
481
- components () {
482
- return html `<div></div>`
483
- }
484
- })
485
- .then(({rootEl, state}) => {
486
- updateState({ items: [1, 2, 3] })
487
-
488
- nextTick(() => {
489
- state.items.push(4)
490
-
491
- nextTick(() => {
492
- expect(state.items).to.deep.equal([1, 2, 3, 4])
493
- state.items.pop()
494
-
495
- nextTick(() => {
496
- expect(state.items).to.deep.equal([1, 2, 3])
497
- done()
498
- })
499
- })
500
- })
501
- })
502
- })
503
-
504
409
  it(`Doesn't clone when merging`, (done) => {
505
410
  halfcab({
506
411
  el: '#root',