@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 CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./src/fntags.mjs";
2
2
  export * from "./src/fnroute.mjs";
3
3
  export * from "./src/fnelements.mjs";
4
+ export * from "./src/svgelements.mjs";
4
5
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './src/fntags.mjs'
2
2
  export * from './src/fnroute.mjs'
3
3
  export * from './src/fnelements.mjs'
4
+ export * from './src/svgelements.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@srfnstack/fntags",
3
- "version": "0.5.2",
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: any, replace?: boolean, silent?: boolean): void;
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("./fntags.mjs").FnState<PathParameters>}
69
+ * @type {import('./fntags.mjs').FnState<PathParameters>}
89
70
  */
90
- export const pathParameters: import("./fntags.mjs").FnState<PathParameters>;
71
+ export const pathParameters: import('./fntags.mjs').FnState<PathParameters>;
91
72
  /**
92
73
  * The path information for a route
93
- * @typedef {{currentRoute: string, rootPath: string, context: any}} PathState
74
+ * @typedef {{currentPath: string, rootPath: string, context: any}} PathState
94
75
  */
95
76
  /**
96
77
  * The current path state
97
- * @type {import("./fntags.mjs").FnState<PathState>}
78
+ * @type {import('./fntags.mjs').FnState<PathState>}
98
79
  */
99
- export const pathState: import("./fntags.mjs").FnState<PathState>;
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
- currentRoute: string;
107
+ currentPath: string;
127
108
  rootPath: string;
128
109
  context: any;
129
110
  };
@@ -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,CAwB3B;AAeD;;;;;;;;;;GAUG;AACH;eARW,MAAM;WACN,MAAM;qBACE,KAAK,gBAAgB,MAAM,KAAG,IAAI,GAAC,IAAI;kBACxC,IAAI,UAAU,MAAM,KAAG,IAAI;iBAClC,OAAO;uBACA,MAAM,KAAG,MAAM;IACrB,WAAW,CA0DtB;AAwBD;;;;GAIG;AACH,oCAHW,CAAC,MAAO,IAAI,CAAC,EAAE,GACb,iBAAiB,CAuB7B;AAED;;;;;;GAMG;AACH,4BALW,MAAM,WACN,GAAG,YACH,OAAO,WACP,OAAO,QA6CjB;AA6DD;;;;;;GAMG;AACH,qDAFY,MAAI,IAAI,CAanB;AAED;;;GAGG;AACH,sCAFW,MAAM,QAOhB;AApFD;;;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;AAEJ;;GAEG;AACH;;;GAGG;AACH,gCAFU,UAAU,CAEgC;AACpD;;;GAGG;AACH,+BAFU,UAAU,CAE8B;AAClD;;;GAGG;AACH,kCAFU,UAAU,CAEoC;;;;;;;;wBA/B3C;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,GAAG,CAAA;CAAC;yBAetD,MAAM"}
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 = pathState().currentRoute
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
- const paramStart = part.indexOf(':')
176
- if (paramStart > -1) {
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
- currentRoute: route.split(/[#?]/)[0],
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
- currentRoute: route.split(/[#?]/)[0],
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("./fntags.mjs").FnState<PathParameters>}
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 {{currentRoute: string, rootPath: string, context: any}} PathState
199
+ * @typedef {{currentPath: string, rootPath: string, context: any}} PathState
286
200
  */
287
201
 
288
202
  /**
289
203
  * The current path state
290
- * @type {import("./fntags.mjs").FnState<PathState>}
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
- currentRoute: ensureOnlyLeadingSlash(window.location.pathname),
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
- currentRoute: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + rootPath), '')) || '/'
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
- currentRoute: ensureOnlyLeadingSlash(window.location.pathname.replace(new RegExp('^' + pathState().rootPath), '')) || '/'
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.currentRoute, oldPathState.context, true, true)
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((path).replace(/\/\$[^/]+(\/?)/g, '/[^/]+$1') + '$')
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
  }