@srfnstack/fntags 0.5.2 → 0.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.
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +2 -2
- package/src/fnroute.d.mts +7 -26
- package/src/fnroute.d.mts.map +1 -1
- package/src/fnroute.mjs +33 -105
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srfnstack/fntags",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"author": "Robert Kempton <r@snow87.com>",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/srfnstack/fntags",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"test": "cp src/*.mjs docs/lib/ && npm run lint && cypress run --spec test/** --headless -b chrome",
|
|
44
|
-
"cypress": "cypress run --spec test/** -b chrome",
|
|
44
|
+
"cypress": "cypress run --headed --spec test/** -b chrome",
|
|
45
45
|
"lint": "standard --env browser src && standard --env browser --env jest --global Prism --global cy test docs",
|
|
46
46
|
"lint:fix": "standard --env browser --fix src && standard --env browser --env jest --global Prism --global cy --fix test docs",
|
|
47
47
|
"typedef": "rm -rf src/*.mts* && tsc",
|
package/src/fnroute.d.mts
CHANGED
|
@@ -33,25 +33,6 @@ export function route(...children: (any | Node)[]): HTMLDivElement;
|
|
|
33
33
|
* @returns {Node|(()=>Node)}
|
|
34
34
|
*/
|
|
35
35
|
export function routeSwitch(...children: (any | Node)[]): Node | (() => Node);
|
|
36
|
-
/**
|
|
37
|
-
* The main function of this library. It will load the route at the specified path and render it into the container element.
|
|
38
|
-
* @param {object} options
|
|
39
|
-
* @param {string} options.routePath The path to the root of the routes. This is used to resolve the paths of the routes.
|
|
40
|
-
* @param {object} options.attrs The attributes of the container element
|
|
41
|
-
* @param {(error: Error, newPathState: object)=>void|Node} options.onerror A function that will be called if the route fails to load. The function receives the error and the current pathState object. Should return an error to display if it's not handled.
|
|
42
|
-
* @param {(node: Node, module: object)=>Node} options.frame A function that will be called with the rendered route element and the module that was loaded. The function should return a new element to be rendered.
|
|
43
|
-
* @param {boolean} options.sendRawPath If true, the raw path will be sent to the route. Otherwise, the path will be stripped of parameter values.
|
|
44
|
-
* @param {(path: string)=>string} options.formatPath A function that will be called with the raw path before it is used to load the route. The function should return a new path.
|
|
45
|
-
* @return {HTMLElement} The container element
|
|
46
|
-
*/
|
|
47
|
-
export function modRouter({ routePath, attrs, onerror, frame, sendRawPath, formatPath }: {
|
|
48
|
-
routePath: string;
|
|
49
|
-
attrs: object;
|
|
50
|
-
onerror: (error: Error, newPathState: object) => void | Node;
|
|
51
|
-
frame: (node: Node, module: object) => Node;
|
|
52
|
-
sendRawPath: boolean;
|
|
53
|
-
formatPath: (path: string) => string;
|
|
54
|
-
}): HTMLElement;
|
|
55
36
|
/**
|
|
56
37
|
* A link element that is a link to another route in this single page app
|
|
57
38
|
* @param {(Object|Node)[]} children The attributes of the anchor element and any children
|
|
@@ -65,7 +46,7 @@ export function fnlink(...children: (any | Node)[]): HTMLAnchorElement;
|
|
|
65
46
|
* @param {boolean} replace Whether to replace the state or push it. pushState is used by default.
|
|
66
47
|
* @param {boolean} silent Prevent route change events from being emitted for this route change
|
|
67
48
|
*/
|
|
68
|
-
export function goTo(route: string, context
|
|
49
|
+
export function goTo(route: string, context?: any, replace?: boolean, silent?: boolean): void;
|
|
69
50
|
/**
|
|
70
51
|
* Listen for routing events
|
|
71
52
|
* @param event a string event to listen for
|
|
@@ -85,18 +66,18 @@ export function setRootPath(rootPath: string): void;
|
|
|
85
66
|
*/
|
|
86
67
|
/**
|
|
87
68
|
* The path parameters of the current route
|
|
88
|
-
* @type {import(
|
|
69
|
+
* @type {import('./fntags.mjs').FnState<PathParameters>}
|
|
89
70
|
*/
|
|
90
|
-
export const pathParameters: import(
|
|
71
|
+
export const pathParameters: import('./fntags.mjs').FnState<PathParameters>;
|
|
91
72
|
/**
|
|
92
73
|
* The path information for a route
|
|
93
|
-
* @typedef {{
|
|
74
|
+
* @typedef {{currentPath: string, rootPath: string, context: any}} PathState
|
|
94
75
|
*/
|
|
95
76
|
/**
|
|
96
77
|
* The current path state
|
|
97
|
-
* @type {import(
|
|
78
|
+
* @type {import('./fntags.mjs').FnState<PathState>}
|
|
98
79
|
*/
|
|
99
|
-
export const pathState: import(
|
|
80
|
+
export const pathState: import('./fntags.mjs').FnState<PathState>;
|
|
100
81
|
/**
|
|
101
82
|
* @typedef {string} RouteEvent
|
|
102
83
|
*/
|
|
@@ -123,7 +104,7 @@ export type PathParameters = any;
|
|
|
123
104
|
* The path information for a route
|
|
124
105
|
*/
|
|
125
106
|
export type PathState = {
|
|
126
|
-
|
|
107
|
+
currentPath: string;
|
|
127
108
|
rootPath: string;
|
|
128
109
|
context: any;
|
|
129
110
|
};
|
package/src/fnroute.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fnroute.d.mts","sourceRoot":"","sources":["fnroute.mjs"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,mCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,cAAc,CAoB1B;AAED;;;;;GAKG;AACH,yCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,IAAI,GAAC,CAAC,MAAI,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"fnroute.d.mts","sourceRoot":"","sources":["fnroute.mjs"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,mCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,cAAc,CAoB1B;AAED;;;;;GAKG;AACH,yCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,IAAI,GAAC,CAAC,MAAI,IAAI,CAAC,CAyB3B;AAoBD;;;;GAIG;AACH,oCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,iBAAiB,CAuB7B;AAED;;;;;;GAMG;AACH,4BALW,MAAM,YACN,GAAG,YACH,OAAO,WACP,OAAO,QA4CjB;AAiED;;;;;;GAMG;AACH,qDAFY,MAAI,IAAI,CAanB;AAED;;;GAGG;AACH,sCAFW,MAAM,QAOhB;AAxFD;;;GAGG;AAEH;;;GAGG;AACH,6BAFU,OAAO,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,CAEf;AAEzC;;;GAGG;AAEH;;;GAGG;AACH,wBAFU,OAAO,cAAc,EAAE,OAAO,CAAC,SAAS,CAAC,CAO/C;AAMJ;;GAEG;AACH;;;GAGG;AACH,gCAFU,UAAU,CAEgC;AACpD;;;GAGG;AACH,+BAFU,UAAU,CAE8B;AAClD;;;GAGG;AACH,kCAFU,UAAU,CAEoC;;;;;;;;wBAnC3C;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAC;yBAmBrD,MAAM"}
|
package/src/fnroute.mjs
CHANGED
|
@@ -70,6 +70,7 @@ export function routeSwitch (...children) {
|
|
|
70
70
|
if (path) {
|
|
71
71
|
const shouldDisplay = shouldDisplayRoute(path, !!child.absolute || child.getAttribute('absolute') === 'true')
|
|
72
72
|
if (shouldDisplay) {
|
|
73
|
+
routeState.currentRoute = path
|
|
73
74
|
updatePathParameters()
|
|
74
75
|
child.updateRoute(true)
|
|
75
76
|
sw.append(child)
|
|
@@ -81,105 +82,19 @@ export function routeSwitch (...children) {
|
|
|
81
82
|
)
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
function stripParameterValues (currentRoute) {
|
|
85
|
-
return removeTrailingSlash(currentRoute.substring(1)).split('/').reduce((res, part) => {
|
|
86
|
-
const paramStart = part.indexOf(':')
|
|
87
|
-
let value = part
|
|
88
|
-
if (paramStart > -1) {
|
|
89
|
-
value = part.substring(0, paramStart)
|
|
90
|
-
}
|
|
91
|
-
return `${res}/${value}`
|
|
92
|
-
}, '')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const moduleCache = {}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* The main function of this library. It will load the route at the specified path and render it into the container element.
|
|
99
|
-
* @param {object} options
|
|
100
|
-
* @param {string} options.routePath The path to the root of the routes. This is used to resolve the paths of the routes.
|
|
101
|
-
* @param {object} options.attrs The attributes of the container element
|
|
102
|
-
* @param {(error: Error, newPathState: object)=>void|Node} options.onerror A function that will be called if the route fails to load. The function receives the error and the current pathState object. Should return an error to display if it's not handled.
|
|
103
|
-
* @param {(node: Node, module: object)=>Node} options.frame A function that will be called with the rendered route element and the module that was loaded. The function should return a new element to be rendered.
|
|
104
|
-
* @param {boolean} options.sendRawPath If true, the raw path will be sent to the route. Otherwise, the path will be stripped of parameter values.
|
|
105
|
-
* @param {(path: string)=>string} options.formatPath A function that will be called with the raw path before it is used to load the route. The function should return a new path.
|
|
106
|
-
* @return {HTMLElement} The container element
|
|
107
|
-
*/
|
|
108
|
-
export function modRouter ({ routePath, attrs, onerror, frame, sendRawPath, formatPath }) {
|
|
109
|
-
const container = h('div', attrs || {})
|
|
110
|
-
if (!routePath) {
|
|
111
|
-
throw new Error('You must provide a root url for modRouter. Routes in the ui will be looked up relative to this url.')
|
|
112
|
-
}
|
|
113
|
-
const loadRoute = (newPathState) => {
|
|
114
|
-
let path = newPathState.currentRoute
|
|
115
|
-
if (!sendRawPath) {
|
|
116
|
-
path = stripParameterValues(newPathState.currentRoute)
|
|
117
|
-
}
|
|
118
|
-
if (typeof formatPath === 'function') {
|
|
119
|
-
path = formatPath(path)
|
|
120
|
-
}
|
|
121
|
-
const filePath = path ? routePath + ensureOnlyLeadingSlash(path) : routePath
|
|
122
|
-
|
|
123
|
-
const p = moduleCache[filePath]
|
|
124
|
-
? Promise.resolve(moduleCache[filePath])
|
|
125
|
-
: import(filePath).then(m => {
|
|
126
|
-
moduleCache[filePath] = m
|
|
127
|
-
return m
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
p.then(module => {
|
|
131
|
-
const route = module.default
|
|
132
|
-
if (route) {
|
|
133
|
-
while (container.firstChild) {
|
|
134
|
-
container.removeChild(container.firstChild)
|
|
135
|
-
}
|
|
136
|
-
let node = renderNode(route)
|
|
137
|
-
if (typeof frame === 'function') {
|
|
138
|
-
node = renderNode(frame(node, module))
|
|
139
|
-
}
|
|
140
|
-
if (node) {
|
|
141
|
-
container.append(node)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
.catch(err => {
|
|
146
|
-
while (container.firstChild) {
|
|
147
|
-
container.removeChild(container.firstChild)
|
|
148
|
-
}
|
|
149
|
-
if (typeof onerror === 'function') {
|
|
150
|
-
err = onerror(err, newPathState)
|
|
151
|
-
if (err) {
|
|
152
|
-
container.append(err)
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
console.error('Failed to load route: ', err)
|
|
156
|
-
container.append('Failed to load route.')
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
listenFor(afterRouteChange, loadRoute)
|
|
161
|
-
updatePathParameters()
|
|
162
|
-
loadRoute(pathState())
|
|
163
|
-
return container
|
|
164
|
-
}
|
|
165
|
-
|
|
166
85
|
function updatePathParameters () {
|
|
167
|
-
const path =
|
|
86
|
+
const path = routeState.currentRoute
|
|
87
|
+
const currentPath = pathState().currentPath
|
|
168
88
|
const pathParts = path.split('/')
|
|
89
|
+
const currentPathParts = currentPath.split('/')
|
|
169
90
|
|
|
170
91
|
const parameters = {
|
|
171
92
|
idx: []
|
|
172
93
|
}
|
|
173
94
|
for (let i = 0; i < pathParts.length; i++) {
|
|
174
95
|
const part = pathParts[i]
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const paramName = part.substring(0, paramStart)
|
|
178
|
-
const paramValue = part.substring(paramStart + 1)
|
|
179
|
-
parameters.idx.push(paramValue)
|
|
180
|
-
if (paramName) {
|
|
181
|
-
parameters[paramName] = paramValue
|
|
182
|
-
}
|
|
96
|
+
if (part.startsWith(':')) {
|
|
97
|
+
parameters[part.substring(1)] = currentPathParts[i]
|
|
183
98
|
}
|
|
184
99
|
}
|
|
185
100
|
pathParameters(parameters)
|
|
@@ -220,11 +135,11 @@ export function fnlink (...children) {
|
|
|
220
135
|
* @param {boolean} replace Whether to replace the state or push it. pushState is used by default.
|
|
221
136
|
* @param {boolean} silent Prevent route change events from being emitted for this route change
|
|
222
137
|
*/
|
|
223
|
-
export function goTo (route, context, replace = false, silent = false) {
|
|
138
|
+
export function goTo (route, context = {}, replace = false, silent = false) {
|
|
224
139
|
const newPath = window.location.origin + makePath(route)
|
|
225
140
|
|
|
226
141
|
const patch = {
|
|
227
|
-
|
|
142
|
+
currentPath: route.split(/[#?]/)[0],
|
|
228
143
|
context
|
|
229
144
|
}
|
|
230
145
|
|
|
@@ -246,10 +161,9 @@ export function goTo (route, context, replace = false, silent = false) {
|
|
|
246
161
|
|
|
247
162
|
setTimeout(() => {
|
|
248
163
|
pathState.assign({
|
|
249
|
-
|
|
164
|
+
currentPath: route.split(/[#?]/)[0],
|
|
250
165
|
context
|
|
251
166
|
})
|
|
252
|
-
updatePathParameters()
|
|
253
167
|
if (!silent) {
|
|
254
168
|
emit(afterRouteChange, newPathState, oldPathState)
|
|
255
169
|
}
|
|
@@ -276,26 +190,30 @@ const removeTrailingSlash = part => part.endsWith('/') && part.length > 1 ? part
|
|
|
276
190
|
|
|
277
191
|
/**
|
|
278
192
|
* The path parameters of the current route
|
|
279
|
-
* @type {import(
|
|
193
|
+
* @type {import('./fntags.mjs').FnState<PathParameters>}
|
|
280
194
|
*/
|
|
281
195
|
export const pathParameters = fnstate({})
|
|
282
196
|
|
|
283
197
|
/**
|
|
284
198
|
* The path information for a route
|
|
285
|
-
* @typedef {{
|
|
199
|
+
* @typedef {{currentPath: string, rootPath: string, context: any}} PathState
|
|
286
200
|
*/
|
|
287
201
|
|
|
288
202
|
/**
|
|
289
203
|
* The current path state
|
|
290
|
-
* @type {import(
|
|
204
|
+
* @type {import('./fntags.mjs').FnState<PathState>}
|
|
291
205
|
*/
|
|
292
206
|
export const pathState = fnstate(
|
|
293
207
|
{
|
|
294
208
|
rootPath: ensureOnlyLeadingSlash(window.location.pathname),
|
|
295
|
-
|
|
209
|
+
currentPath: ensureOnlyLeadingSlash(window.location.pathname),
|
|
296
210
|
context: null
|
|
297
211
|
})
|
|
298
212
|
|
|
213
|
+
const routeState = {
|
|
214
|
+
currentRoute: null
|
|
215
|
+
}
|
|
216
|
+
|
|
299
217
|
/**
|
|
300
218
|
* @typedef {string} RouteEvent
|
|
301
219
|
*/
|
|
@@ -351,7 +269,7 @@ export function listenFor (event, handler) {
|
|
|
351
269
|
export function setRootPath (rootPath) {
|
|
352
270
|
return pathState.assign({
|
|
353
271
|
rootPath: ensureOnlyLeadingSlash(rootPath),
|
|
354
|
-
|
|
272
|
+
currentPath: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + rootPath), '')) || '/'
|
|
355
273
|
})
|
|
356
274
|
}
|
|
357
275
|
|
|
@@ -360,18 +278,17 @@ window.addEventListener(
|
|
|
360
278
|
() => {
|
|
361
279
|
const oldPathState = pathState()
|
|
362
280
|
const patch = {
|
|
363
|
-
|
|
281
|
+
currentPath: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + pathState().rootPath), '')) || '/'
|
|
364
282
|
}
|
|
365
283
|
const newPathState = Object.assign({}, oldPathState, patch)
|
|
366
284
|
try {
|
|
367
285
|
emit(beforeRouteChange, newPathState, oldPathState)
|
|
368
286
|
} catch (e) {
|
|
369
287
|
console.trace('Path change cancelled', e)
|
|
370
|
-
goTo(oldPathState.
|
|
288
|
+
goTo(oldPathState.currentPath, oldPathState.context, true, true)
|
|
371
289
|
return
|
|
372
290
|
}
|
|
373
291
|
pathState.assign(patch)
|
|
374
|
-
updatePathParameters()
|
|
375
292
|
emit(afterRouteChange, newPathState, oldPathState)
|
|
376
293
|
emit(routeChangeComplete, newPathState, oldPathState)
|
|
377
294
|
}
|
|
@@ -379,13 +296,24 @@ window.addEventListener(
|
|
|
379
296
|
|
|
380
297
|
const makePath = path => (pathState().rootPath === '/' ? '' : pathState().rootPath) + ensureOnlyLeadingSlash(path)
|
|
381
298
|
|
|
299
|
+
const makePathPattern = (path, absolute) => {
|
|
300
|
+
const pathParts = path.replace(/[?#].*/, '').replace(/\/$/, '').split('/')
|
|
301
|
+
return '^' + pathParts.map(part => {
|
|
302
|
+
if (part.startsWith(':')) {
|
|
303
|
+
return '([^/]+)'
|
|
304
|
+
} else {
|
|
305
|
+
return part
|
|
306
|
+
}
|
|
307
|
+
}).join('/') + (absolute ? '/?$' : '(/.*|$)')
|
|
308
|
+
}
|
|
309
|
+
|
|
382
310
|
const shouldDisplayRoute = (route, isAbsolute) => {
|
|
383
311
|
const path = makePath(route)
|
|
384
312
|
const currPath = window.location.pathname
|
|
313
|
+
const pattern = makePathPattern(path, isAbsolute)
|
|
385
314
|
if (isAbsolute) {
|
|
386
|
-
return currPath === path || currPath === (path + '/') || currPath.match(
|
|
315
|
+
return currPath === path || currPath === (path + '/') || currPath.match(pattern)
|
|
387
316
|
} else {
|
|
388
|
-
const pattern = path.replace(/\/\$[^/]+(\/|$)/, '/[^/]+$1').replace(/^(.*)\/([^/]*)$/, '$1/?$2([/?#]|$)')
|
|
389
317
|
return !!currPath.match(pattern)
|
|
390
318
|
}
|
|
391
319
|
}
|