kdu-router 3.4.0-beta.0 → 3.6.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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +11 -9
  3. package/dist/composables.js +253 -0
  4. package/dist/composables.mjs +244 -0
  5. package/dist/kdu-router.common.js +2682 -2572
  6. package/dist/kdu-router.esm.browser.js +2677 -2563
  7. package/dist/kdu-router.esm.browser.min.js +5 -5
  8. package/dist/kdu-router.esm.js +2685 -2572
  9. package/dist/kdu-router.js +2682 -2573
  10. package/dist/kdu-router.min.js +5 -5
  11. package/ketur/attributes.json +38 -38
  12. package/ketur/tags.json +20 -20
  13. package/package.json +115 -111
  14. package/src/components/link.js +224 -197
  15. package/src/components/view.js +155 -149
  16. package/src/composables/globals.js +34 -0
  17. package/src/composables/guards.js +68 -0
  18. package/src/composables/index.js +3 -0
  19. package/src/composables/useLink.js +113 -0
  20. package/src/composables/utils.js +11 -0
  21. package/src/create-matcher.js +226 -200
  22. package/src/create-route-map.js +220 -205
  23. package/src/entries/cjs.js +3 -0
  24. package/src/entries/esm.js +12 -0
  25. package/src/history/abstract.js +72 -68
  26. package/src/history/base.js +379 -400
  27. package/src/history/hash.js +152 -163
  28. package/src/history/html5.js +99 -94
  29. package/src/index.js +3 -277
  30. package/src/install.js +52 -52
  31. package/src/router.js +293 -0
  32. package/src/util/async.js +18 -18
  33. package/src/util/dom.js +3 -3
  34. package/src/util/errors.js +86 -85
  35. package/src/util/location.js +69 -69
  36. package/src/util/misc.js +6 -6
  37. package/src/util/params.js +37 -37
  38. package/src/util/path.js +74 -74
  39. package/src/util/push-state.js +46 -46
  40. package/src/util/query.js +113 -96
  41. package/src/util/resolve-components.js +109 -109
  42. package/src/util/route.js +151 -132
  43. package/src/util/scroll.js +175 -165
  44. package/src/util/state-key.js +22 -22
  45. package/src/util/warn.js +14 -14
  46. package/types/composables.d.ts +53 -0
  47. package/types/index.d.ts +25 -17
  48. package/types/kdu.d.ts +22 -22
  49. package/types/router.d.ts +564 -170
package/src/util/route.js CHANGED
@@ -1,132 +1,151 @@
1
- /* @flow */
2
-
3
- import type KduRouter from '../index'
4
- import { stringifyQuery } from './query'
5
-
6
- const trailingSlashRE = /\/?$/
7
-
8
- export function createRoute (
9
- record: ?RouteRecord,
10
- location: Location,
11
- redirectedFrom?: ?Location,
12
- router?: KduRouter
13
- ): Route {
14
- const stringifyQuery = router && router.options.stringifyQuery
15
-
16
- let query: any = location.query || {}
17
- try {
18
- query = clone(query)
19
- } catch (e) {}
20
-
21
- const route: Route = {
22
- name: location.name || (record && record.name),
23
- meta: (record && record.meta) || {},
24
- path: location.path || '/',
25
- hash: location.hash || '',
26
- query,
27
- params: location.params || {},
28
- fullPath: getFullPath(location, stringifyQuery),
29
- matched: record ? formatMatch(record) : []
30
- }
31
- if (redirectedFrom) {
32
- route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
33
- }
34
- return Object.freeze(route)
35
- }
36
-
37
- function clone (value) {
38
- if (Array.isArray(value)) {
39
- return value.map(clone)
40
- } else if (value && typeof value === 'object') {
41
- const res = {}
42
- for (const key in value) {
43
- res[key] = clone(value[key])
44
- }
45
- return res
46
- } else {
47
- return value
48
- }
49
- }
50
-
51
- // the starting route that represents the initial state
52
- export const START = createRoute(null, {
53
- path: '/'
54
- })
55
-
56
- function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
57
- const res = []
58
- while (record) {
59
- res.unshift(record)
60
- record = record.parent
61
- }
62
- return res
63
- }
64
-
65
- function getFullPath (
66
- { path, query = {}, hash = '' },
67
- _stringifyQuery
68
- ): string {
69
- const stringify = _stringifyQuery || stringifyQuery
70
- return (path || '/') + stringify(query) + hash
71
- }
72
-
73
- export function isSameRoute (a: Route, b: ?Route): boolean {
74
- if (b === START) {
75
- return a === b
76
- } else if (!b) {
77
- return false
78
- } else if (a.path && b.path) {
79
- return (
80
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
81
- a.hash === b.hash &&
82
- isObjectEqual(a.query, b.query)
83
- )
84
- } else if (a.name && b.name) {
85
- return (
86
- a.name === b.name &&
87
- a.hash === b.hash &&
88
- isObjectEqual(a.query, b.query) &&
89
- isObjectEqual(a.params, b.params)
90
- )
91
- } else {
92
- return false
93
- }
94
- }
95
-
96
- function isObjectEqual (a = {}, b = {}): boolean {
97
- // handle null value #1566
98
- if (!a || !b) return a === b
99
- const aKeys = Object.keys(a)
100
- const bKeys = Object.keys(b)
101
- if (aKeys.length !== bKeys.length) {
102
- return false
103
- }
104
- return aKeys.every(key => {
105
- const aVal = a[key]
106
- const bVal = b[key]
107
- // check nested equality
108
- if (typeof aVal === 'object' && typeof bVal === 'object') {
109
- return isObjectEqual(aVal, bVal)
110
- }
111
- return String(aVal) === String(bVal)
112
- })
113
- }
114
-
115
- export function isIncludedRoute (current: Route, target: Route): boolean {
116
- return (
117
- current.path.replace(trailingSlashRE, '/').indexOf(
118
- target.path.replace(trailingSlashRE, '/')
119
- ) === 0 &&
120
- (!target.hash || current.hash === target.hash) &&
121
- queryIncludes(current.query, target.query)
122
- )
123
- }
124
-
125
- function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
126
- for (const key in target) {
127
- if (!(key in current)) {
128
- return false
129
- }
130
- }
131
- return true
132
- }
1
+ /* @flow */
2
+
3
+ import type KduRouter from '../index'
4
+ import { stringifyQuery } from './query'
5
+
6
+ const trailingSlashRE = /\/?$/
7
+
8
+ export function createRoute (
9
+ record: ?RouteRecord,
10
+ location: Location,
11
+ redirectedFrom?: ?Location,
12
+ router?: KduRouter
13
+ ): Route {
14
+ const stringifyQuery = router && router.options.stringifyQuery
15
+
16
+ let query: any = location.query || {}
17
+ try {
18
+ query = clone(query)
19
+ } catch (e) {}
20
+
21
+ const route: Route = {
22
+ name: location.name || (record && record.name),
23
+ meta: (record && record.meta) || {},
24
+ path: location.path || '/',
25
+ hash: location.hash || '',
26
+ query,
27
+ params: location.params || {},
28
+ fullPath: getFullPath(location, stringifyQuery),
29
+ matched: record ? formatMatch(record) : []
30
+ }
31
+ if (redirectedFrom) {
32
+ route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
33
+ }
34
+ return Object.freeze(route)
35
+ }
36
+
37
+ function clone (value) {
38
+ if (Array.isArray(value)) {
39
+ return value.map(clone)
40
+ } else if (value && typeof value === 'object') {
41
+ const res = {}
42
+ for (const key in value) {
43
+ res[key] = clone(value[key])
44
+ }
45
+ return res
46
+ } else {
47
+ return value
48
+ }
49
+ }
50
+
51
+ // the starting route that represents the initial state
52
+ export const START = createRoute(null, {
53
+ path: '/'
54
+ })
55
+
56
+ function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
57
+ const res = []
58
+ while (record) {
59
+ res.unshift(record)
60
+ record = record.parent
61
+ }
62
+ return res
63
+ }
64
+
65
+ function getFullPath (
66
+ { path, query = {}, hash = '' },
67
+ _stringifyQuery
68
+ ): string {
69
+ const stringify = _stringifyQuery || stringifyQuery
70
+ return (path || '/') + stringify(query) + hash
71
+ }
72
+
73
+ export function isSameRoute (a: Route, b: ?Route, onlyPath: ?boolean): boolean {
74
+ if (b === START) {
75
+ return a === b
76
+ } else if (!b) {
77
+ return false
78
+ } else if (a.path && b.path) {
79
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
80
+ a.hash === b.hash &&
81
+ isObjectEqual(a.query, b.query))
82
+ } else if (a.name && b.name) {
83
+ return (
84
+ a.name === b.name &&
85
+ (onlyPath || (
86
+ a.hash === b.hash &&
87
+ isObjectEqual(a.query, b.query) &&
88
+ isObjectEqual(a.params, b.params))
89
+ )
90
+ )
91
+ } else {
92
+ return false
93
+ }
94
+ }
95
+
96
+ function isObjectEqual (a = {}, b = {}): boolean {
97
+ // handle null value #1566
98
+ if (!a || !b) return a === b
99
+ const aKeys = Object.keys(a).sort()
100
+ const bKeys = Object.keys(b).sort()
101
+ if (aKeys.length !== bKeys.length) {
102
+ return false
103
+ }
104
+ return aKeys.every((key, i) => {
105
+ const aVal = a[key]
106
+ const bKey = bKeys[i]
107
+ if (bKey !== key) return false
108
+ const bVal = b[key]
109
+ // query values can be null and undefined
110
+ if (aVal == null || bVal == null) return aVal === bVal
111
+ // check nested equality
112
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
113
+ return isObjectEqual(aVal, bVal)
114
+ }
115
+ return String(aVal) === String(bVal)
116
+ })
117
+ }
118
+
119
+ export function isIncludedRoute (current: Route, target: Route): boolean {
120
+ return (
121
+ current.path.replace(trailingSlashRE, '/').indexOf(
122
+ target.path.replace(trailingSlashRE, '/')
123
+ ) === 0 &&
124
+ (!target.hash || current.hash === target.hash) &&
125
+ queryIncludes(current.query, target.query)
126
+ )
127
+ }
128
+
129
+ function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
130
+ for (const key in target) {
131
+ if (!(key in current)) {
132
+ return false
133
+ }
134
+ }
135
+ return true
136
+ }
137
+
138
+ export function handleRouteEntered (route: Route) {
139
+ for (let i = 0; i < route.matched.length; i++) {
140
+ const record = route.matched[i]
141
+ for (const name in record.instances) {
142
+ const instance = record.instances[name]
143
+ const cbs = record.enteredCbs[name]
144
+ if (!instance || !cbs) continue
145
+ delete record.enteredCbs[name]
146
+ for (let i = 0; i < cbs.length; i++) {
147
+ if (!instance._isBeingDestroyed) cbs[i](instance)
148
+ }
149
+ }
150
+ }
151
+ }
@@ -1,165 +1,175 @@
1
- /* @flow */
2
-
3
- import type Router from '../index'
4
- import { assert } from './warn'
5
- import { getStateKey, setStateKey } from './state-key'
6
- import { extend } from './misc'
7
-
8
- const positionStore = Object.create(null)
9
-
10
- export function setupScroll () {
11
- // Prevent browser scroll behavior on History popstate
12
- if ('scrollRestoration' in window.history) {
13
- window.history.scrollRestoration = 'manual'
14
- }
15
- // Fix for #1585 for Firefox
16
- // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
17
- // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
18
- // window.location.protocol + '//' + window.location.host
19
- // location.host contains the port and location.hostname doesn't
20
- const protocolAndPath = window.location.protocol + '//' + window.location.host
21
- const absolutePath = window.location.href.replace(protocolAndPath, '')
22
- // preserve existing history state as it could be overriden by the user
23
- const stateCopy = extend({}, window.history.state)
24
- stateCopy.key = getStateKey()
25
- window.history.replaceState(stateCopy, '', absolutePath)
26
- window.addEventListener('popstate', handlePopState)
27
- return () => {
28
- window.removeEventListener('popstate', handlePopState)
29
- }
30
- }
31
-
32
- export function handleScroll (
33
- router: Router,
34
- to: Route,
35
- from: Route,
36
- isPop: boolean
37
- ) {
38
- if (!router.app) {
39
- return
40
- }
41
-
42
- const behavior = router.options.scrollBehavior
43
- if (!behavior) {
44
- return
45
- }
46
-
47
- if (process.env.NODE_ENV !== 'production') {
48
- assert(typeof behavior === 'function', `scrollBehavior must be a function`)
49
- }
50
-
51
- // wait until re-render finishes before scrolling
52
- router.app.$nextTick(() => {
53
- const position = getScrollPosition()
54
- const shouldScroll = behavior.call(
55
- router,
56
- to,
57
- from,
58
- isPop ? position : null
59
- )
60
-
61
- if (!shouldScroll) {
62
- return
63
- }
64
-
65
- if (typeof shouldScroll.then === 'function') {
66
- shouldScroll
67
- .then(shouldScroll => {
68
- scrollToPosition((shouldScroll: any), position)
69
- })
70
- .catch(err => {
71
- if (process.env.NODE_ENV !== 'production') {
72
- assert(false, err.toString())
73
- }
74
- })
75
- } else {
76
- scrollToPosition(shouldScroll, position)
77
- }
78
- })
79
- }
80
-
81
- export function saveScrollPosition () {
82
- const key = getStateKey()
83
- if (key) {
84
- positionStore[key] = {
85
- x: window.pageXOffset,
86
- y: window.pageYOffset
87
- }
88
- }
89
- }
90
-
91
- function handlePopState (e) {
92
- saveScrollPosition()
93
- if (e.state && e.state.key) {
94
- setStateKey(e.state.key)
95
- }
96
- }
97
-
98
- function getScrollPosition (): ?Object {
99
- const key = getStateKey()
100
- if (key) {
101
- return positionStore[key]
102
- }
103
- }
104
-
105
- function getElementPosition (el: Element, offset: Object): Object {
106
- const docEl: any = document.documentElement
107
- const docRect = docEl.getBoundingClientRect()
108
- const elRect = el.getBoundingClientRect()
109
- return {
110
- x: elRect.left - docRect.left - offset.x,
111
- y: elRect.top - docRect.top - offset.y
112
- }
113
- }
114
-
115
- function isValidPosition (obj: Object): boolean {
116
- return isNumber(obj.x) || isNumber(obj.y)
117
- }
118
-
119
- function normalizePosition (obj: Object): Object {
120
- return {
121
- x: isNumber(obj.x) ? obj.x : window.pageXOffset,
122
- y: isNumber(obj.y) ? obj.y : window.pageYOffset
123
- }
124
- }
125
-
126
- function normalizeOffset (obj: Object): Object {
127
- return {
128
- x: isNumber(obj.x) ? obj.x : 0,
129
- y: isNumber(obj.y) ? obj.y : 0
130
- }
131
- }
132
-
133
- function isNumber (v: any): boolean {
134
- return typeof v === 'number'
135
- }
136
-
137
- const hashStartsWithNumberRE = /^#\d/
138
-
139
- function scrollToPosition (shouldScroll, position) {
140
- const isObject = typeof shouldScroll === 'object'
141
- if (isObject && typeof shouldScroll.selector === 'string') {
142
- // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
143
- // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
144
- const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
145
- ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
146
- : document.querySelector(shouldScroll.selector)
147
-
148
- if (el) {
149
- let offset =
150
- shouldScroll.offset && typeof shouldScroll.offset === 'object'
151
- ? shouldScroll.offset
152
- : {}
153
- offset = normalizeOffset(offset)
154
- position = getElementPosition(el, offset)
155
- } else if (isValidPosition(shouldScroll)) {
156
- position = normalizePosition(shouldScroll)
157
- }
158
- } else if (isObject && isValidPosition(shouldScroll)) {
159
- position = normalizePosition(shouldScroll)
160
- }
161
-
162
- if (position) {
163
- window.scrollTo(position.x, position.y)
164
- }
165
- }
1
+ /* @flow */
2
+
3
+ import type Router from '../index'
4
+ import { assert } from './warn'
5
+ import { getStateKey, setStateKey } from './state-key'
6
+ import { extend } from './misc'
7
+
8
+ const positionStore = Object.create(null)
9
+
10
+ export function setupScroll () {
11
+ // Prevent browser scroll behavior on History popstate
12
+ if ('scrollRestoration' in window.history) {
13
+ window.history.scrollRestoration = 'manual'
14
+ }
15
+ // Fix for #1585 for Firefox
16
+ // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
17
+ // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
18
+ // window.location.protocol + '//' + window.location.host
19
+ // location.host contains the port and location.hostname doesn't
20
+ const protocolAndPath = window.location.protocol + '//' + window.location.host
21
+ const absolutePath = window.location.href.replace(protocolAndPath, '')
22
+ // preserve existing history state as it could be overriden by the user
23
+ const stateCopy = extend({}, window.history.state)
24
+ stateCopy.key = getStateKey()
25
+ window.history.replaceState(stateCopy, '', absolutePath)
26
+ window.addEventListener('popstate', handlePopState)
27
+ return () => {
28
+ window.removeEventListener('popstate', handlePopState)
29
+ }
30
+ }
31
+
32
+ export function handleScroll (
33
+ router: Router,
34
+ to: Route,
35
+ from: Route,
36
+ isPop: boolean
37
+ ) {
38
+ if (!router.app) {
39
+ return
40
+ }
41
+
42
+ const behavior = router.options.scrollBehavior
43
+ if (!behavior) {
44
+ return
45
+ }
46
+
47
+ if (process.env.NODE_ENV !== 'production') {
48
+ assert(typeof behavior === 'function', `scrollBehavior must be a function`)
49
+ }
50
+
51
+ // wait until re-render finishes before scrolling
52
+ router.app.$nextTick(() => {
53
+ const position = getScrollPosition()
54
+ const shouldScroll = behavior.call(
55
+ router,
56
+ to,
57
+ from,
58
+ isPop ? position : null
59
+ )
60
+
61
+ if (!shouldScroll) {
62
+ return
63
+ }
64
+
65
+ if (typeof shouldScroll.then === 'function') {
66
+ shouldScroll
67
+ .then(shouldScroll => {
68
+ scrollToPosition((shouldScroll: any), position)
69
+ })
70
+ .catch(err => {
71
+ if (process.env.NODE_ENV !== 'production') {
72
+ assert(false, err.toString())
73
+ }
74
+ })
75
+ } else {
76
+ scrollToPosition(shouldScroll, position)
77
+ }
78
+ })
79
+ }
80
+
81
+ export function saveScrollPosition () {
82
+ const key = getStateKey()
83
+ if (key) {
84
+ positionStore[key] = {
85
+ x: window.pageXOffset,
86
+ y: window.pageYOffset
87
+ }
88
+ }
89
+ }
90
+
91
+ function handlePopState (e) {
92
+ saveScrollPosition()
93
+ if (e.state && e.state.key) {
94
+ setStateKey(e.state.key)
95
+ }
96
+ }
97
+
98
+ function getScrollPosition (): ?Object {
99
+ const key = getStateKey()
100
+ if (key) {
101
+ return positionStore[key]
102
+ }
103
+ }
104
+
105
+ function getElementPosition (el: Element, offset: Object): Object {
106
+ const docEl: any = document.documentElement
107
+ const docRect = docEl.getBoundingClientRect()
108
+ const elRect = el.getBoundingClientRect()
109
+ return {
110
+ x: elRect.left - docRect.left - offset.x,
111
+ y: elRect.top - docRect.top - offset.y
112
+ }
113
+ }
114
+
115
+ function isValidPosition (obj: Object): boolean {
116
+ return isNumber(obj.x) || isNumber(obj.y)
117
+ }
118
+
119
+ function normalizePosition (obj: Object): Object {
120
+ return {
121
+ x: isNumber(obj.x) ? obj.x : window.pageXOffset,
122
+ y: isNumber(obj.y) ? obj.y : window.pageYOffset
123
+ }
124
+ }
125
+
126
+ function normalizeOffset (obj: Object): Object {
127
+ return {
128
+ x: isNumber(obj.x) ? obj.x : 0,
129
+ y: isNumber(obj.y) ? obj.y : 0
130
+ }
131
+ }
132
+
133
+ function isNumber (v: any): boolean {
134
+ return typeof v === 'number'
135
+ }
136
+
137
+ const hashStartsWithNumberRE = /^#\d/
138
+
139
+ function scrollToPosition (shouldScroll, position) {
140
+ const isObject = typeof shouldScroll === 'object'
141
+ if (isObject && typeof shouldScroll.selector === 'string') {
142
+ // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
143
+ // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
144
+ const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
145
+ ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
146
+ : document.querySelector(shouldScroll.selector)
147
+
148
+ if (el) {
149
+ let offset =
150
+ shouldScroll.offset && typeof shouldScroll.offset === 'object'
151
+ ? shouldScroll.offset
152
+ : {}
153
+ offset = normalizeOffset(offset)
154
+ position = getElementPosition(el, offset)
155
+ } else if (isValidPosition(shouldScroll)) {
156
+ position = normalizePosition(shouldScroll)
157
+ }
158
+ } else if (isObject && isValidPosition(shouldScroll)) {
159
+ position = normalizePosition(shouldScroll)
160
+ }
161
+
162
+ if (position) {
163
+ // $flow-disable-line
164
+ if ('scrollBehavior' in document.documentElement.style) {
165
+ window.scrollTo({
166
+ left: position.x,
167
+ top: position.y,
168
+ // $flow-disable-line
169
+ behavior: shouldScroll.behavior
170
+ })
171
+ } else {
172
+ window.scrollTo(position.x, position.y)
173
+ }
174
+ }
175
+ }
@@ -1,22 +1,22 @@
1
- /* @flow */
2
- import { inBrowser } from './dom'
3
-
4
- // use User Timing api (if present) for more accurate key precision
5
- const Time =
6
- inBrowser && window.performance && window.performance.now
7
- ? window.performance
8
- : Date
9
-
10
- export function genStateKey (): string {
11
- return Time.now().toFixed(3)
12
- }
13
-
14
- let _key: string = genStateKey()
15
-
16
- export function getStateKey () {
17
- return _key
18
- }
19
-
20
- export function setStateKey (key: string) {
21
- return (_key = key)
22
- }
1
+ /* @flow */
2
+ import { inBrowser } from './dom'
3
+
4
+ // use User Timing api (if present) for more accurate key precision
5
+ const Time =
6
+ inBrowser && window.performance && window.performance.now
7
+ ? window.performance
8
+ : Date
9
+
10
+ export function genStateKey (): string {
11
+ return Time.now().toFixed(3)
12
+ }
13
+
14
+ let _key: string = genStateKey()
15
+
16
+ export function getStateKey () {
17
+ return _key
18
+ }
19
+
20
+ export function setStateKey (key: string) {
21
+ return (_key = key)
22
+ }