@singularsystems/neo-react 1.4.3 → 1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file details all new features and changes to functionality. If you only need to see changes that require updates to your project, see the [breaking changes](./Breaking.md) readme.
4
4
 
5
+ ## Version 1.6.0 (23 June 2026)
6
+
7
+ - Routing changes
8
+ - Routes parameters can now be defined in the route path. E.g. `/products/:id/sales`.
9
+ - Parameters not defined in the path will continue to be appended at the end of the path.
10
+ - Internal overhaul of routing system to allow better unit testing.
11
+
12
+ ## Version 1.5.0 (28 May 2026)
13
+
14
+ - Fixed routes with the same prefix, but different parameter counts not being registered.
15
+ - E.g. a products list view with path `/products`, and product item view with path `/products/{itemId}` were previously seen as a duplicate route.
16
+ - A url of `/products/123` would have matched against the first route.
17
+ - ViewBase - allow `viewParamsUpdated` to be called on initialise even if all the parameters have default values.
18
+
5
19
  ## Version 1.4.3 (22 April 2026)
6
20
 
7
21
  - Fix variable usage in neo-react styles.
@@ -42,7 +42,7 @@ declare const NeoReactTypes: {
42
42
  };
43
43
  Core: {
44
44
  AutoRunnerFactory: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Model/IAutoRunner").IAutoRunnerFactory>;
45
- ConnectionManager: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/ConnectionManager").IConnectionManager>;
45
+ ConnectionManager: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Network").IConnectionManager>;
46
46
  DecimalService: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Numeric").IDecimalService<import("@singularsystems/neo-core/dist/Numeric").IDecimal>>;
47
47
  ModelCreator: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Model/IModelCreatorBase").default>;
48
48
  };
@@ -50,9 +50,13 @@ declare const NeoReactTypes: {
50
50
  LocalisationService: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Localisation").ILocalisationService>;
51
51
  };
52
52
  Messaging: {
53
- ConnectionManager: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/ConnectionManager").IConnectionManager>;
53
+ ConnectionManager: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Network").IConnectionManager>;
54
54
  ServerMessageSubscriber: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/SignalR").IServerMessageSubscriber>;
55
55
  };
56
+ Network: {
57
+ AppConnectionMonitor: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Network/IAppConnectionMonitor").IAppConnectionMonitor>;
58
+ ConnectionManager: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Network").IConnectionManager>;
59
+ };
56
60
  Security: {
57
61
  AuthenticationService: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Security").IAuthenticationService>;
58
62
  AuthorisationService: AppServices.ServiceIdentifier<import("@singularsystems/neo-core/dist/Security").IAuthorisationService>;
@@ -16,5 +16,6 @@ export default class Link extends React.Component<ILinkProps> {
16
16
  constructor(props: ILinkProps);
17
17
  private onClick;
18
18
  render(): React.JSX.Element;
19
+ static isMatch(linkPath: string, currentPath: string, exact?: boolean): boolean;
19
20
  }
20
21
  export {};
@@ -15,6 +15,7 @@ var Link = /** @class */ (function (_super) {
15
15
  _this.basePath = Misc.Globals.appService.get(NeoReactTypes.Routing.GlobalRoutingState).basePath;
16
16
  return _this;
17
17
  }
18
+ Link_1 = Link;
18
19
  Link.prototype.onClick = function (e) {
19
20
  if (this.props.onClick) {
20
21
  this.props.onClick(e);
@@ -37,22 +38,28 @@ var Link = /** @class */ (function (_super) {
37
38
  if (isNav) {
38
39
  // Cause re-render if current route changes.
39
40
  void Misc.Globals.appService.get(NeoReactTypes.Routing.GlobalRoutingState).currentRoute;
40
- var path = window.location.pathname.toLowerCase();
41
- var toLc = to.toLowerCase();
42
- if (path.endsWith("/")) {
43
- path = path.substring(0, path.length - 1);
44
- }
45
- var isMatch = exact === true ?
46
- toLc === path :
47
- path.startsWith(toLc);
48
- if (isMatch) {
41
+ if (Link_1.isMatch(to, window.location.pathname, exact)) {
49
42
  rest.className = Utils.joinWithSpaces(rest.className, "active");
50
43
  }
51
44
  }
52
45
  }
53
46
  return React.createElement("a", __assign({}, rest, { href: href, onClick: this.onClick }), this.props.children);
54
47
  };
55
- Link = __decorate([
48
+ Link.isMatch = function (linkPath, currentPath, exact) {
49
+ linkPath = linkPath.toLowerCase();
50
+ currentPath = currentPath.toLowerCase();
51
+ if (!currentPath.endsWith("/")) {
52
+ currentPath += "/";
53
+ }
54
+ if (!linkPath.endsWith("/")) {
55
+ linkPath += "/";
56
+ }
57
+ return exact === true ?
58
+ linkPath === currentPath :
59
+ currentPath.startsWith(linkPath);
60
+ };
61
+ var Link_1;
62
+ Link = Link_1 = __decorate([
56
63
  observer,
57
64
  __metadata("design:paramtypes", [Object])
58
65
  ], Link);
@@ -20,3 +20,7 @@ export type RouteParameters = {
20
20
  export type RouteParameterObject<T> = {
21
21
  [P in keyof T]: IRouteParameter;
22
22
  };
23
+ export type ValidParameterValue = number | string | string[] | null;
24
+ export type ParamValues<TParams> = {
25
+ [P in keyof TParams]?: ValidParameterValue;
26
+ };
@@ -2,8 +2,16 @@ import ViewBase from '../Views/ViewBase';
2
2
  import { Routing } from '@singularsystems/neo-core';
3
3
  import { RouteParameterObject } from './IRouteParameter';
4
4
  export interface IMenuRoute extends Routing.IMenuRoute {
5
+ /** The base route. Can be used instead of specifying path */
6
+ baseRoute?: Routing.IRoute;
5
7
  }
6
8
  export declare class MenuRoute<TParams extends RouteParameterObject<TParams>> implements IMenuRoute {
9
+ baseRoute: {
10
+ path: string;
11
+ component: new (...args: any) => ViewBase<any, TParams>;
12
+ };
13
+ private viewParamValues?;
14
+ private _path?;
7
15
  /**
8
16
  * Creates a menu route with view parameters specified.
9
17
  * @param baseRoute Path and component
@@ -13,13 +21,9 @@ export declare class MenuRoute<TParams extends RouteParameterObject<TParams>> im
13
21
  constructor(baseRoute: {
14
22
  path: string;
15
23
  component: new (...args: any) => ViewBase<any, TParams>;
16
- }, menuParams: Routing.IMenuRoute, viewParams?: {
17
- [P in keyof TParams]?: number | string | null;
18
- });
24
+ }, menuParams: Routing.IMenuRoute, viewParamValues?: { [P in keyof TParams]?: number | string | null; } | undefined);
19
25
  /** Path including view param values. */
20
- path: string;
21
- /** Path without parameter values or routing template. */
22
- basePath: string;
26
+ get path(): string;
23
27
  /** Name to show in the menu item. */
24
28
  name: string;
25
29
  }
@@ -1,4 +1,6 @@
1
- import { ViewParameterList } from './ViewParameterList';
1
+ import { Misc } from '@singularsystems/neo-core';
2
+ import { NeoReactTypes } from "../Modules/Types";
3
+ import { RouteHelper } from "./RouteHelper";
2
4
  var MenuRoute = /** @class */ (function () {
3
5
  /**
4
6
  * Creates a menu route with view parameters specified.
@@ -6,21 +8,36 @@ var MenuRoute = /** @class */ (function () {
6
8
  * @param menuParams Other menu options.
7
9
  * @param viewParams View parameter values.
8
10
  */
9
- function MenuRoute(baseRoute, menuParams, viewParams) {
11
+ function MenuRoute(baseRoute, menuParams, viewParamValues) {
10
12
  var _this = this;
13
+ this.baseRoute = baseRoute;
14
+ this.viewParamValues = viewParamValues;
11
15
  Object.getOwnPropertyNames(baseRoute).forEach(function (propertyName) {
12
- // @ts-ignore
13
- _this[propertyName] = baseRoute[propertyName];
16
+ if (propertyName !== "path") {
17
+ // @ts-ignore
18
+ _this[propertyName] = baseRoute[propertyName];
19
+ }
14
20
  });
15
21
  Object.getOwnPropertyNames(menuParams).forEach(function (propertyName) {
16
- // @ts-ignore
17
- _this[propertyName] = menuParams[propertyName];
22
+ if (propertyName !== "path") {
23
+ // @ts-ignore
24
+ _this[propertyName] = menuParams[propertyName];
25
+ }
18
26
  });
19
- this.basePath = this.path;
20
- if (viewParams) {
21
- this.path = ViewParameterList.getPathForValues(this.basePath, baseRoute.component.params, viewParams);
22
- }
23
27
  }
28
+ Object.defineProperty(MenuRoute.prototype, "path", {
29
+ /** Path including view param values. */
30
+ get: function () {
31
+ if (!this._path) {
32
+ var routeProvider = Misc.Globals.appService.get(NeoReactTypes.Routing.GlobalRoutingState).appRouteProvider;
33
+ var processedRoute = routeProvider.getProcessedRoute(this.baseRoute);
34
+ this._path = RouteHelper.hydrateRoute(processedRoute, this.viewParamValues);
35
+ }
36
+ return this._path;
37
+ },
38
+ enumerable: false,
39
+ configurable: true
40
+ });
24
41
  return MenuRoute;
25
42
  }());
26
43
  export { MenuRoute };
@@ -1,12 +1,9 @@
1
1
  import ViewBase from '../Views/ViewBase';
2
2
  import { RoutingState } from './RoutingState';
3
3
  import { INavigationHelper as INeoNavigationHelper } from '@singularsystems/neo-core/dist/Routing/INavigationHelper';
4
- import { RouteParameterObject } from './IRouteParameter';
4
+ import { ParamValues, RouteParameterObject } from './IRouteParameter';
5
5
  import type { IPageLeaveHandler } from "./PageLeaveHandler";
6
6
  export type ViewConstructor<TParams extends RouteParameterObject<TParams>> = new (...args: any) => ViewBase<any, TParams>;
7
- export type ParamValues<TParams> = {
8
- [P in keyof TParams]?: number | string | null;
9
- };
10
7
  export interface INavigationHelper extends INeoNavigationHelper {
11
8
  /**
12
9
  * Navigates to the view, if the view component is found in the routes definition.
@@ -1,8 +1,8 @@
1
1
  import { __awaiter, __decorate, __generator, __metadata, __param } from "tslib";
2
- import { ViewParameterList } from './ViewParameterList';
3
2
  import { injectable, inject } from 'inversify';
4
3
  import { NeoReactTypes } from '../Modules/Types';
5
4
  import { RoutingState } from './RoutingState';
5
+ import { RouteHelper } from "./RouteHelper";
6
6
  var NavigationHelper = /** @class */ (function () {
7
7
  function NavigationHelper(routingState, pageLeaveHandler) {
8
8
  this.routingState = routingState;
@@ -98,14 +98,9 @@ var NavigationHelper = /** @class */ (function () {
98
98
  return openerState;
99
99
  };
100
100
  NavigationHelper.prototype.getPathForView = function (view, paramValues) {
101
- var basePath = this.routingState.appRouteProvider.getPathForComponent(view);
102
- if (basePath !== undefined) {
103
- if (paramValues) {
104
- return ViewParameterList.getPathForValues(basePath, view.params, paramValues);
105
- }
106
- else {
107
- return basePath;
108
- }
101
+ var route = this.routingState.appRouteProvider.getRouteForComponent(view);
102
+ if (route !== undefined) {
103
+ return RouteHelper.hydrateRoute(route, paramValues);
109
104
  }
110
105
  else {
111
106
  throw Error("Cannot find route for component " + view.name);
@@ -113,7 +108,7 @@ var NavigationHelper = /** @class */ (function () {
113
108
  };
114
109
  NavigationHelper.prototype.getCurrentViewPath = function () {
115
110
  if (this.routingState.currentRoute) {
116
- return this.routingState.currentRoute.route.path;
111
+ return this.routingState.currentRoute.route.defaultPath;
117
112
  }
118
113
  else {
119
114
  return "";
@@ -0,0 +1,8 @@
1
+ import { ParamValues, ValidParameterValue } from "./IRouteParameter";
2
+ import { IProcessedRoute } from "./RouteProvider";
3
+ export declare class RouteHelper {
4
+ /** Populates the route's path with the provided parameter values. */
5
+ static hydrateRoute<TParams>(route: IProcessedRoute, paramValues?: ParamValues<TParams>, includeQueryParams?: boolean): string;
6
+ static getQueryString<TParams>(route: IProcessedRoute, paramValues: ParamValues<TParams>): string;
7
+ static stringifyValue(value?: ValidParameterValue): string | null;
8
+ }
@@ -0,0 +1,54 @@
1
+ var RouteHelper = /** @class */ (function () {
2
+ function RouteHelper() {
3
+ }
4
+ /** Populates the route's path with the provided parameter values. */
5
+ RouteHelper.hydrateRoute = function (route, paramValues, includeQueryParams) {
6
+ var _a;
7
+ if (includeQueryParams === void 0) { includeQueryParams = true; }
8
+ paramValues !== null && paramValues !== void 0 ? paramValues : (paramValues = {});
9
+ var path = "";
10
+ var pending = "";
11
+ var index = 0;
12
+ for (var _i = 0, _b = route.segments; _i < _b.length; _i++) {
13
+ var segment = _b[_i];
14
+ if (segment.param) {
15
+ var rawValue = paramValues[segment.param.name];
16
+ var value = encodeURIComponent((_a = this.stringifyValue(rawValue)) !== null && _a !== void 0 ? _a : "");
17
+ pending += "/" + value;
18
+ if ((rawValue !== undefined && rawValue !== null) || index < route.requiredSegments) {
19
+ path += pending;
20
+ pending = "";
21
+ }
22
+ }
23
+ else {
24
+ path += "/" + segment.value;
25
+ }
26
+ index++;
27
+ }
28
+ var queryString = includeQueryParams ? this.getQueryString(route, paramValues) : "";
29
+ return path + queryString;
30
+ };
31
+ RouteHelper.getQueryString = function (route, paramValues) {
32
+ var _a;
33
+ var queryString = "";
34
+ for (var _i = 0, _b = route.queryParams; _i < _b.length; _i++) {
35
+ var queryParam = _b[_i];
36
+ var rawValue = paramValues[queryParam.name];
37
+ if (rawValue !== undefined && rawValue !== null) {
38
+ var value = encodeURIComponent((_a = this.stringifyValue(rawValue)) !== null && _a !== void 0 ? _a : "");
39
+ queryString += (queryString ? "&" : "?") + queryParam.name + "=" + value;
40
+ }
41
+ }
42
+ return queryString;
43
+ };
44
+ RouteHelper.stringifyValue = function (value) {
45
+ if (value instanceof Array) {
46
+ return value.join(",");
47
+ }
48
+ else {
49
+ return value !== null && value !== undefined ? value.toString() : null;
50
+ }
51
+ };
52
+ return RouteHelper;
53
+ }());
54
+ export { RouteHelper };
@@ -5,16 +5,23 @@ interface INamedRouteParameter extends IRouteParameter {
5
5
  name: string;
6
6
  }
7
7
  export interface IProcessedRoute extends Routing.IRoute {
8
- key: number;
9
- /** Lower case base path without trailing / */
10
- path: string;
8
+ /** Path with all parameters replaced by their default values. */
9
+ defaultPath: string;
11
10
  /** Route params in order, excluding query parameters. */
12
- params: INamedRouteParameter[];
13
- order: number;
11
+ routeParams: INamedRouteParameter[];
12
+ queryParams: INamedRouteParameter[];
13
+ /** The path split into segments, including parameter segments. */
14
+ segments: RouteSegment[];
15
+ /** The number of required segments in the path for this route to match. */
16
+ requiredSegments: number;
14
17
  }
15
18
  type ParamsObject = {
16
19
  [index: string]: string | undefined;
17
20
  };
21
+ type RouteSegment = {
22
+ value: string;
23
+ param?: INamedRouteParameter;
24
+ };
18
25
  /**
19
26
  * A route which has matched, with its parameter values populated from the path.
20
27
  */
@@ -29,10 +36,8 @@ export default class RouteProvider {
29
36
  menuRoutes: Routing.IMenuRoute[];
30
37
  pureRoutes?: Routing.IRoute[] | undefined;
31
38
  notFoundComponent?: React.ComponentType | undefined;
32
- private key;
33
- private routeIndex;
34
- private staticRoutes;
35
- private parameterisedRoutes;
39
+ private basePaths;
40
+ private rawRoutes;
36
41
  readonly allRoutes: IProcessedRoute[];
37
42
  /**
38
43
  * Creates a route provider which provides a list of flattened routes.
@@ -41,12 +46,14 @@ export default class RouteProvider {
41
46
  * @param notFoundComponent Component to render when the url doesn't match any of the above routes.
42
47
  */
43
48
  constructor(menuRoutes: Routing.IMenuRoute[], pureRoutes?: Routing.IRoute[] | undefined, notFoundComponent?: React.ComponentType | undefined);
44
- /** Gets the path the component is bound to. If the component is bound to multiple paths, the first path is returned. */
49
+ /** Gets the raw route path the component is bound to. If the component is bound to multiple paths, the first path is returned. */
45
50
  getPathForComponent(component: any): string | undefined;
51
+ /** Gets the route the component is bound to. If the component is bound to multiple routes, the first route is returned. */
52
+ getRouteForComponent(component: any): IProcessedRoute | undefined;
46
53
  private flatten;
47
54
  /**
48
55
  * Create a copy of the route info, and process it.
49
- * Adds a unique key, and adds route parameters extracted from the component static params object.
56
+ * Adds route parameters extracted from the component static params object.
50
57
  */
51
58
  private processRoute;
52
59
  }
@@ -1,4 +1,5 @@
1
1
  import { __assign, __spreadArray } from "tslib";
2
+ import { RouteHelper } from "./RouteHelper";
2
3
  var RouteProvider = /** @class */ (function () {
3
4
  /**
4
5
  * Creates a route provider which provides a list of flattened routes.
@@ -10,123 +11,165 @@ var RouteProvider = /** @class */ (function () {
10
11
  this.menuRoutes = menuRoutes;
11
12
  this.pureRoutes = pureRoutes;
12
13
  this.notFoundComponent = notFoundComponent;
13
- this.key = 1;
14
- this.routeIndex = {};
15
- this.staticRoutes = [];
16
- this.parameterisedRoutes = [];
14
+ this.basePaths = new Set();
15
+ this.rawRoutes = new Map();
17
16
  this.allRoutes = [];
18
- this.flatten(this.allRoutes, menuRoutes);
19
17
  if (pureRoutes)
20
- this.flatten(this.allRoutes, pureRoutes);
21
- this.staticRoutes = this.allRoutes.filter(function (c) { return c.params.length === 0; });
22
- this.parameterisedRoutes = this.allRoutes
23
- .filter(function (c) { return c.params.length > 0; })
24
- .sortBy(function (c) { return c.order; }, "desc");
18
+ this.flatten(pureRoutes);
19
+ this.flatten(menuRoutes);
25
20
  }
26
- /** Gets the path the component is bound to. If the component is bound to multiple paths, the first path is returned. */
21
+ /** Gets the raw route path the component is bound to. If the component is bound to multiple paths, the first path is returned. */
27
22
  RouteProvider.prototype.getPathForComponent = function (component) {
28
23
  var _a;
29
- return (_a = this.allRoutes.find(function (c) { return c.component === component; })) === null || _a === void 0 ? void 0 : _a.path;
24
+ return (_a = this.getRouteForComponent(component)) === null || _a === void 0 ? void 0 : _a.defaultPath;
25
+ };
26
+ /** Gets the route the component is bound to. If the component is bound to multiple routes, the first route is returned. */
27
+ RouteProvider.prototype.getRouteForComponent = function (component) {
28
+ return this.allRoutes.find(function (c) { return c.component === component; });
30
29
  };
31
30
  /** @internal */
32
- RouteProvider.prototype.getRouteForPath = function (path) {
33
- return RouteProvider.getRouteForPath(this.staticRoutes, this.parameterisedRoutes, path);
31
+ RouteProvider.prototype.getProcessedRoute = function (route) {
32
+ var _a;
33
+ var processed = this.rawRoutes.get(route);
34
+ if (!processed) {
35
+ throw new Error("Route not found: ".concat((_a = route.basePath) !== null && _a !== void 0 ? _a : route.path));
36
+ }
37
+ return processed;
34
38
  };
35
39
  /** @internal */
36
- RouteProvider.getRouteForPath = function (staticRoutes, parameterisedRoutes, path) {
40
+ RouteProvider.prototype.getRouteForPath = function (path) {
41
+ var _a;
37
42
  var queryIndex = path.indexOf("?");
38
43
  if (queryIndex >= 0) {
39
44
  path = path.substring(0, queryIndex);
40
45
  }
41
- var lcPath = path.toLowerCase();
42
- var found = staticRoutes.find(function (route) { return route.path === lcPath; });
43
- if (found) {
44
- return { route: found, path: path };
45
- }
46
- if (path.endsWith("/")) {
47
- // special case for where an extra "/" is added to the url by mistake.
48
- var trimmedPath_1 = lcPath.substring(0, path.length - 1);
49
- var found_1 = staticRoutes.find(function (route) { return route.path === trimmedPath_1; });
50
- if (found_1) {
51
- return { route: found_1, path: path };
52
- }
53
- }
54
- var matches = parameterisedRoutes.filter(function (route) { return lcPath.startsWith(route.path); });
55
- for (var _i = 0, matches_1 = matches; _i < matches_1.length; _i++) {
56
- var match = matches_1[_i];
57
- var paramString = path.substring(match.path.length);
58
- // Special case for root path which has its leading "/" removed by the navigation helper.
59
- if (match.path === "/") {
60
- paramString = "/" + paramString;
61
- }
62
- var parts = paramString.split("/").slice(1);
63
- var partCount = parts.length;
64
- // Allow extra "/"
65
- if (partCount > 0 && parts[partCount - 1] === "") {
66
- partCount -= 1;
67
- }
68
- if (match.params.length >= partCount) {
69
- var paramValues = {};
70
- var isValid = true;
71
- for (var i = 0; i < match.params.length; i++) {
72
- var param = match.params[i];
73
- var value = parts[i];
74
- paramValues[param.name] = value;
75
- if (param.required && !value) {
76
- isValid = false;
77
- break;
46
+ var providedSegments = path.split("/").slice(1);
47
+ var currentRoutes = this.allRoutes.filter(function (r) { return providedSegments.length >= r.requiredSegments; });
48
+ var params = [];
49
+ for (var i = 0; i < providedSegments.length; i++) {
50
+ var segmentValue = providedSegments[i];
51
+ var matchedRoutes = [];
52
+ for (var _i = 0, currentRoutes_1 = currentRoutes; _i < currentRoutes_1.length; _i++) {
53
+ var route = currentRoutes_1[_i];
54
+ var routeSegment = route.segments.length > i ? route.segments[i] : undefined;
55
+ if (segmentValue.toLowerCase() === ((_a = routeSegment === null || routeSegment === void 0 ? void 0 : routeSegment.value) !== null && _a !== void 0 ? _a : "")) {
56
+ matchedRoutes.push(route);
57
+ }
58
+ else if (routeSegment && routeSegment.param) {
59
+ params.push({ param: routeSegment.param, value: segmentValue, index: i });
60
+ if (!routeSegment.param.required || segmentValue) {
61
+ matchedRoutes.push(route);
78
62
  }
79
63
  }
80
- if (isValid) {
81
- return { route: match, path: path, params: paramValues };
64
+ }
65
+ currentRoutes = matchedRoutes;
66
+ }
67
+ if (currentRoutes.length > 0) {
68
+ var route = currentRoutes[0];
69
+ var paramValues = {};
70
+ var _loop_1 = function (i) {
71
+ var segment = route.segments[i];
72
+ if (segment.param) {
73
+ var paramInfo = params.find(function (p) { return p.param == segment.param && p.index === i; });
74
+ paramValues[segment.param.name] = paramInfo === null || paramInfo === void 0 ? void 0 : paramInfo.value;
82
75
  }
76
+ };
77
+ for (var i = 0; i < route.segments.length; i++) {
78
+ _loop_1(i);
83
79
  }
80
+ return { route: route, path: path, params: paramValues };
84
81
  }
85
82
  return null;
86
83
  };
87
- RouteProvider.prototype.flatten = function (into, routes, parent) {
88
- var _a, _b;
84
+ RouteProvider.prototype.flatten = function (routes, parent) {
85
+ var _a, _b, _c;
89
86
  for (var _i = 0, routes_1 = routes; _i < routes_1.length; _i++) {
90
87
  var route = routes_1[_i];
91
- var hasComponent = route.path !== undefined && route.component;
88
+ var baseRoute = (_a = route.baseRoute) !== null && _a !== void 0 ? _a : route;
89
+ var hasComponent = baseRoute.path !== undefined && baseRoute.component;
92
90
  if (hasComponent) {
93
91
  if (parent && !route.breadCrumbParent) {
92
+ baseRoute.breadCrumbParent = parent;
94
93
  route.breadCrumbParent = parent;
95
94
  }
96
- this.processRoute(into, route);
95
+ this.processRoute(baseRoute);
97
96
  }
98
97
  var menuRoute = route;
99
- var children = __spreadArray(__spreadArray([], ((_a = route.routeChildren) !== null && _a !== void 0 ? _a : []), true), ((_b = menuRoute.children) !== null && _b !== void 0 ? _b : []), true);
100
- this.flatten(into, children, (hasComponent && menuRoute.showInBreadCrumb !== false) ? menuRoute : undefined);
98
+ var children = __spreadArray(__spreadArray([], ((_b = route.routeChildren) !== null && _b !== void 0 ? _b : []), true), ((_c = menuRoute.children) !== null && _c !== void 0 ? _c : []), true);
99
+ this.flatten(children, (hasComponent && menuRoute.showInBreadCrumb !== false) ? menuRoute : undefined);
101
100
  }
102
101
  };
103
102
  /**
104
103
  * Create a copy of the route info, and process it.
105
- * Adds a unique key, and adds route parameters extracted from the component static params object.
104
+ * Adds route parameters extracted from the component static params object.
106
105
  */
107
- RouteProvider.prototype.processRoute = function (into, route) {
106
+ RouteProvider.prototype.processRoute = function (route) {
108
107
  var _a;
109
- var processed = __assign(__assign({}, route), { path: route.path, params: [], key: this.key++, order: 0 });
110
- processed.path = (_a = route.basePath) !== null && _a !== void 0 ? _a : route.path;
111
- processed.path = processed.path.toLowerCase();
112
- if (processed.path.length > 1 && processed.path.endsWith("/")) {
113
- processed.path = processed.path.substring(0, processed.path.length - 1);
108
+ var processed = this.rawRoutes.get(route);
109
+ if (processed) {
110
+ return;
114
111
  }
115
- if (processed.path.length > 1) {
116
- processed.order = processed.path.split("/").length;
112
+ processed = __assign(__assign({}, route), { defaultPath: ((_a = route.basePath) !== null && _a !== void 0 ? _a : route.path), routeParams: [], queryParams: [], segments: [], requiredSegments: 0 });
113
+ var normalizedPath = processed.defaultPath.toLowerCase();
114
+ var fullPath = normalizedPath;
115
+ var hasParamIdentifier = false;
116
+ if (normalizedPath.endsWith("/")) {
117
+ normalizedPath = normalizedPath.substring(0, normalizedPath.length - 1);
117
118
  }
118
119
  var viewBaseComponent = route.component;
119
120
  if (viewBaseComponent.params) {
120
121
  for (var param in viewBaseComponent.params) {
121
122
  var routeParam = viewBaseComponent.params[param];
122
123
  if (!routeParam.isQuery) {
123
- processed.params.push(__assign(__assign({}, routeParam), { name: param }));
124
+ processed.routeParams.push(__assign(__assign({}, routeParam), { name: param }));
125
+ }
126
+ else {
127
+ processed.queryParams.push(__assign(__assign({}, routeParam), { name: param }));
124
128
  }
125
129
  }
126
130
  }
127
- if (!this.routeIndex[processed.path]) {
128
- this.routeIndex[processed.path] = true;
129
- into.push(processed);
131
+ // Split the path into segments, and identify which segments are parameters.
132
+ var segmentValues = normalizedPath.split("/").slice(1);
133
+ var params = __spreadArray([], processed.routeParams, true);
134
+ var _loop_2 = function (segmentValue) {
135
+ var segment = { value: segmentValue };
136
+ processed.segments.push(segment);
137
+ if (segmentValue.startsWith(":")) {
138
+ hasParamIdentifier = true;
139
+ var paramName_1 = segmentValue.substring(1);
140
+ segment.param = processed.routeParams.find(function (p) { return p.name.toLowerCase() === paramName_1; });
141
+ if (segment.param) {
142
+ params.remove(segment.param);
143
+ }
144
+ else {
145
+ throw Error("Route parameter \"".concat(paramName_1, "\" in path \"").concat(processed.defaultPath, "\" does not have a corresponding view parameter definition."));
146
+ }
147
+ }
148
+ if (!segment.param || segment.param.required) {
149
+ processed.requiredSegments = processed.segments.length;
150
+ }
151
+ };
152
+ for (var _i = 0, segmentValues_1 = segmentValues; _i < segmentValues_1.length; _i++) {
153
+ var segmentValue = segmentValues_1[_i];
154
+ _loop_2(segmentValue);
155
+ }
156
+ // Params that aren't in the path are added as segments at the end of the path.
157
+ for (var _b = 0, params_1 = params; _b < params_1.length; _b++) {
158
+ var param = params_1[_b];
159
+ var segmentValue = ":".concat(param.name);
160
+ processed.segments.push({ value: segmentValue, param: param });
161
+ fullPath += "/".concat(segmentValue);
162
+ if (param.required) {
163
+ processed.requiredSegments = processed.segments.length;
164
+ }
165
+ }
166
+ if (hasParamIdentifier) {
167
+ processed.defaultPath = RouteHelper.hydrateRoute(processed, {});
168
+ }
169
+ this.rawRoutes.set(route, processed);
170
+ if (!this.basePaths.has(fullPath)) {
171
+ this.basePaths.add(fullPath);
172
+ this.allRoutes.push(processed);
130
173
  }
131
174
  };
132
175
  return RouteProvider;
@@ -1,11 +1,11 @@
1
- import { IRouteParameter } from './IRouteParameter';
2
- import { IViewParameterListInternal } from './ViewParameterList';
1
+ import { IRouteParameter, ValidParameterValue } from './IRouteParameter';
2
+ import { IViewParameterList } from './ViewParameterList';
3
3
  import { Model } from '@singularsystems/neo-core';
4
4
  export interface IViewParameter {
5
5
  /**
6
6
  * Gets or sets the current parameter value.
7
7
  */
8
- value: number | string | string[] | null;
8
+ value: ValidParameterValue;
9
9
  /**
10
10
  * Clears the value, replacing the url instead of adding to the history.
11
11
  * This prevents the back button returning the params to the current state.
@@ -42,7 +42,7 @@ export interface IViewParameter {
42
42
  /**
43
43
  * Allows this view parameter to automatically update when the value returned in the provided callback changes.
44
44
  */
45
- bindTo(callback: () => (string | number | null | undefined)): void;
45
+ bindTo(callback: () => ValidParameterValue): void;
46
46
  }
47
47
  export declare class ViewParameter implements IViewParameter {
48
48
  private parent;
@@ -52,10 +52,10 @@ export declare class ViewParameter implements IViewParameter {
52
52
  private pendingValue;
53
53
  private _value;
54
54
  prefixes: IViewParameterPrefix[];
55
- constructor(parent: IViewParameterListInternal, name: string, routeParameter: IRouteParameter);
56
- bindTo(propertyOrCallback: Model.IPropertyInstance | (() => (string | number | null | undefined))): void;
57
- get value(): number | string | string[] | null;
58
- set value(value: number | string | string[] | null);
55
+ constructor(parent: IViewParameterList, name: string, routeParameter: IRouteParameter);
56
+ bindTo(propertyOrCallback: Model.IPropertyInstance | (() => ValidParameterValue)): void;
57
+ get value(): ValidParameterValue;
58
+ set value(value: ValidParameterValue);
59
59
  reset(): void;
60
60
  _description: string;
61
61
  get description(): string;
@@ -66,17 +66,12 @@ export declare class ViewParameter implements IViewParameter {
66
66
  asStringList(): string[];
67
67
  /** Updates the current value, and returns true if the value changed. */
68
68
  update(value: string | null): boolean;
69
- getParamString(state: {
70
- hasQuery: boolean;
71
- }): string;
72
- get latestValue(): string | null;
73
- private getStringValue;
74
69
  }
75
70
  interface IViewParameterPrefix {
76
71
  /** Label to show on breadcrumb. */
77
72
  label: string;
78
73
  /** Value to use in the breadcrumb link. Clicking on the link will set the view parameter to this value. */
79
- value?: number | string | null;
74
+ value?: ValidParameterValue;
80
75
  /** Custom link to use in the breadcrumb. */
81
76
  link?: string;
82
77
  /** Click event for the breadcrumb. */
@@ -1,5 +1,6 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import { makeObservable, observable } from 'mobx';
3
+ import { RouteHelper } from './RouteHelper';
3
4
  var ViewParameter = /** @class */ (function () {
4
5
  function ViewParameter(parent, name, routeParameter) {
5
6
  this.parent = parent;
@@ -15,22 +16,22 @@ var ViewParameter = /** @class */ (function () {
15
16
  ViewParameter.prototype.bindTo = function (propertyOrCallback) {
16
17
  var _this = this;
17
18
  if (propertyOrCallback.propertyInfo) {
18
- this.parent.view.viewModel.registerReaction(function () { return propertyOrCallback.value; }, function (val) { return _this.value = val; });
19
+ this.parent._view.viewModel.registerReaction(function () { return propertyOrCallback.value; }, function (val) { return _this.value = val; });
19
20
  }
20
21
  else if (propertyOrCallback instanceof Function) {
21
- this.parent.view.viewModel.registerReaction(propertyOrCallback, function (val) { return _this.value = val; });
22
+ this.parent._view.viewModel.registerReaction(propertyOrCallback, function (val) { return _this.value = val; });
22
23
  }
23
24
  };
24
25
  Object.defineProperty(ViewParameter.prototype, "value", {
25
26
  get: function () {
26
- return this._value;
27
+ return this.hasPendingValue ? this.pendingValue : this._value;
27
28
  },
28
29
  set: function (value) {
29
- var stringValue = this.getStringValue(value);
30
+ var stringValue = RouteHelper.stringifyValue(value);
30
31
  if (stringValue !== this._value) {
31
32
  this.hasPendingValue = true;
32
33
  this.pendingValue = stringValue;
33
- this.parent.setUrl(this);
34
+ this.parent.setUrl(this, value);
34
35
  }
35
36
  },
36
37
  enumerable: false,
@@ -40,7 +41,7 @@ var ViewParameter = /** @class */ (function () {
40
41
  if (this._value) {
41
42
  this.hasPendingValue = true;
42
43
  this.pendingValue = null;
43
- this.parent.setUrl(this, true);
44
+ this.parent.setUrl(this, null, true);
44
45
  }
45
46
  };
46
47
  Object.defineProperty(ViewParameter.prototype, "description", {
@@ -94,36 +95,6 @@ var ViewParameter = /** @class */ (function () {
94
95
  }
95
96
  return false;
96
97
  };
97
- ViewParameter.prototype.getParamString = function (state) {
98
- var value = this.latestValue;
99
- if (value) {
100
- value = encodeURIComponent(value);
101
- if (this.routeParameter.isQuery) {
102
- var hasQuery = state.hasQuery;
103
- state.hasQuery = true;
104
- return (!hasQuery ? "?" : "&") + this.name + "=" + value;
105
- }
106
- else {
107
- return "/" + value;
108
- }
109
- }
110
- return this.routeParameter.isQuery ? "" : "/";
111
- };
112
- Object.defineProperty(ViewParameter.prototype, "latestValue", {
113
- get: function () {
114
- return this.hasPendingValue ? this.pendingValue : this._value;
115
- },
116
- enumerable: false,
117
- configurable: true
118
- });
119
- ViewParameter.prototype.getStringValue = function (value) {
120
- if (value instanceof Array) {
121
- return value.join(",");
122
- }
123
- else {
124
- return value ? value.toString() : null;
125
- }
126
- };
127
98
  __decorate([
128
99
  observable.ref,
129
100
  __metadata("design:type", String)
@@ -1,8 +1,9 @@
1
1
  import { ViewParameter, IViewParameter } from './ViewParameter';
2
- import { RouteParameterObject } from './IRouteParameter';
2
+ import { RouteParameterObject, ParamValues, ValidParameterValue } from './IRouteParameter';
3
3
  import { BreadCrumbItem } from './BreadCrumbItem';
4
4
  import { IViewBase } from '../Views/ViewBase';
5
- import { ParamValues } from "./NavigationHelper";
5
+ import { IProcessedRoute } from "./RouteProvider";
6
+ import { INavigationHelper } from './NavigationHelper';
6
7
  export type TransformParamsType<T> = {
7
8
  [P in keyof T]: IViewParameter;
8
9
  };
@@ -16,39 +17,24 @@ export interface IViewParameterList {
16
17
  /**
17
18
  * Returns the breadcrumb items for all url parameters which have a value.
18
19
  */
19
- getBreadCrumbList(upTo?: ViewParameter): BreadCrumbItem[];
20
+ getBreadCrumbList(): BreadCrumbItem[];
20
21
  }
21
22
  export interface IViewParameterListOfT<TParams extends RouteParameterObject<TParams>> extends IViewParameterList {
22
23
  /**
23
24
  * Set the view params for each of the provided values. Only 1 navigation event will occur.
24
25
  * @param values New values.
25
26
  */
26
- setValues(values: {
27
- [P in keyof TParams]?: number | string | string[] | null;
28
- }, options?: ISetValuesOptions | boolean): void;
27
+ setValues(values: ParamValues<TParams>, options?: ISetValuesOptions | boolean): void;
29
28
  }
30
- export interface IViewParameterListInternal extends IViewParameterList {
31
- setUrl(viewParameter: ViewParameter | null, replace?: boolean): void;
32
- view?: IViewBase;
33
- }
34
- export declare class ViewParameterList<TParams extends RouteParameterObject<TParams>> implements IViewParameterListInternal, IViewParameterListOfT<TParams> {
35
- readonly view?: IViewBase | undefined;
29
+ export declare class ViewParameterList<TParams extends RouteParameterObject<TParams>> implements IViewParameterListOfT<TParams> {
30
+ private _processedRoute;
31
+ private _navigationHelper;
32
+ readonly _view?: IViewBase | undefined;
36
33
  readonly params: ViewParameter[];
37
- private suppressNavigation;
38
- private hasSuppressedNavigation;
39
- constructor(routeParameterObject: TParams, view?: IViewBase | undefined);
40
- getBreadCrumbList(upTo?: ViewParameter): BreadCrumbItem[];
41
- private getQueryString;
42
- setValues(values: {
43
- [P in keyof TParams]?: number | string | string[] | null;
44
- }, options?: ISetValuesOptions | boolean): void;
45
- setUrl(viewParameter: ViewParameter | null, replace?: boolean): void;
46
- getUrl(viewParameter: ViewParameter): string | undefined;
47
- /**
48
- * Gets a url for an object with view params specified.
49
- * @param basePath Base path without parameters
50
- * @param values Object of param names and values.
51
- */
52
- static getPathForValues<TParams extends RouteParameterObject<TParams>>(basePath: string, params: TParams, values: ParamValues<TParams>): string;
34
+ constructor(_processedRoute: IProcessedRoute, _navigationHelper: INavigationHelper, _view?: IViewBase | undefined);
35
+ getBreadCrumbList(): BreadCrumbItem[];
36
+ setValues(values: ParamValues<TParams>, options?: ISetValuesOptions | boolean): void;
37
+ setUrl(viewParameter: ViewParameter | null, newValue: ValidParameterValue, replace?: boolean): void;
38
+ private navigate;
53
39
  }
54
40
  export {};
@@ -1,123 +1,112 @@
1
+ import { __spreadArray } from "tslib";
1
2
  import { ViewParameter } from './ViewParameter';
2
3
  import { BreadCrumbSelectMode } from './IRouteParameter';
3
4
  import { BreadCrumbItem } from './BreadCrumbItem';
4
- import { Misc } from '@singularsystems/neo-core';
5
- import { NeoReactTypes } from '../Modules/Types';
5
+ import { RouteHelper } from './RouteHelper';
6
6
  var ViewParameterList = /** @class */ (function () {
7
- function ViewParameterList(routeParameterObject, view) {
8
- this.view = view;
7
+ function ViewParameterList(_processedRoute, _navigationHelper, _view) {
8
+ this._processedRoute = _processedRoute;
9
+ this._navigationHelper = _navigationHelper;
10
+ this._view = _view;
9
11
  this.params = [];
10
- this.suppressNavigation = false;
11
- this.hasSuppressedNavigation = false;
12
- if (routeParameterObject) {
13
- for (var param in routeParameterObject) {
14
- var viewParam = new ViewParameter(this, param, routeParameterObject[param]);
15
- //@ts-ignore
16
- this[param] = viewParam;
17
- this.params.push(viewParam);
18
- }
12
+ for (var _i = 0, _a = __spreadArray(__spreadArray([], _processedRoute.routeParams, true), _processedRoute.queryParams, true); _i < _a.length; _i++) {
13
+ var param = _a[_i];
14
+ var viewParam = new ViewParameter(this, param.name, param);
15
+ //@ts-ignore
16
+ this[param.name] = viewParam;
17
+ this.params.push(viewParam);
19
18
  }
20
19
  }
21
- ViewParameterList.prototype.getBreadCrumbList = function (upTo) {
20
+ ViewParameterList.prototype.getBreadCrumbList = function () {
22
21
  var _a;
23
- var basePath = Misc.Globals.appService.get(NeoReactTypes.Routing.NavigationHelper).getCurrentViewPath();
24
22
  var breadCrumbs = [];
25
- var state = { hasQuery: false };
26
- var queryPath = this.getQueryString(state);
27
- var paramPath = basePath;
28
- var selectablePath = basePath;
23
+ var values = {};
29
24
  for (var _i = 0, _b = this.params; _i < _b.length; _i++) {
30
- var viewParam = _b[_i];
25
+ var param = _b[_i];
26
+ if (param.routeParameter.isQuery) {
27
+ values[param.name] = param.value;
28
+ }
29
+ }
30
+ var queryPath = RouteHelper.getQueryString(this._processedRoute, values);
31
+ var paramPath;
32
+ var selectablePath;
33
+ for (var _c = 0, _d = this.params; _c < _d.length; _c++) {
34
+ var viewParam = _d[_c];
31
35
  if (!viewParam.routeParameter.isQuery) {
32
- if (!upTo) {
33
- for (var _c = 0, _d = viewParam.prefixes; _c < _d.length; _c++) {
34
- var prefix = _d[_c];
35
- var breadcrumbItem = new BreadCrumbItem(prefix.label);
36
- if (prefix.onClick) {
37
- breadcrumbItem.onClick = prefix.onClick;
38
- }
39
- else if (prefix.link) {
40
- breadcrumbItem.link = prefix.link;
41
- }
42
- else if (prefix.value) {
43
- breadcrumbItem.link = paramPath + "/" + prefix.value;
44
- }
45
- breadCrumbs.push(breadcrumbItem);
36
+ for (var _e = 0, _f = viewParam.prefixes; _e < _f.length; _e++) {
37
+ var prefix = _f[_e];
38
+ var breadcrumbItem = new BreadCrumbItem(prefix.label);
39
+ if (prefix.onClick) {
40
+ breadcrumbItem.onClick = prefix.onClick;
41
+ }
42
+ else if (prefix.link) {
43
+ breadcrumbItem.link = prefix.link;
46
44
  }
45
+ else if (prefix.value) {
46
+ values[viewParam.name] = prefix.value;
47
+ breadcrumbItem.link = RouteHelper.hydrateRoute(this._processedRoute, values, false);
48
+ }
49
+ breadCrumbs.push(breadcrumbItem);
47
50
  }
48
- paramPath += viewParam.getParamString(state);
51
+ values[viewParam.name] = viewParam.value;
52
+ paramPath = RouteHelper.hydrateRoute(this._processedRoute, values, false);
49
53
  if (((_a = viewParam.routeParameter.selection) !== null && _a !== void 0 ? _a : BreadCrumbSelectMode.Default) === BreadCrumbSelectMode.Default) {
50
54
  selectablePath = paramPath;
51
55
  }
52
- if (viewParam.description || (upTo && viewParam.latestValue !== null)) {
53
- breadCrumbs.push(new BreadCrumbItem(viewParam.description, (!upTo && viewParam.routeParameter.selection === BreadCrumbSelectMode.None) ? "" : (selectablePath + queryPath)));
56
+ if (viewParam.description || viewParam.value !== null) {
57
+ breadCrumbs.push(new BreadCrumbItem(viewParam.description, (viewParam.routeParameter.selection === BreadCrumbSelectMode.None) ? "" : (selectablePath + queryPath)));
54
58
  }
55
59
  }
56
- if (upTo && upTo === viewParam) {
57
- break;
58
- }
59
- }
60
- if (breadCrumbs.length === 0 && state.hasQuery) {
61
- breadCrumbs.push(new BreadCrumbItem("", basePath + queryPath));
62
60
  }
63
61
  return breadCrumbs;
64
62
  };
65
- ViewParameterList.prototype.getQueryString = function (state) {
66
- var queryPath = "";
67
- for (var _i = 0, _a = this.params; _i < _a.length; _i++) {
68
- var viewParam = _a[_i];
69
- if (viewParam.routeParameter.isQuery) {
70
- queryPath += viewParam.getParamString(state);
71
- }
72
- }
73
- return queryPath;
74
- };
75
63
  ViewParameterList.prototype.setValues = function (values, options) {
76
- var _a;
77
- this.suppressNavigation = true;
78
- this.hasSuppressedNavigation = false;
79
64
  options !== null && options !== void 0 ? options : (options = {});
80
65
  if (typeof options === "boolean") {
81
66
  options = { force: true }; // parameter used to be named 'force'- backwards compatibility.
82
67
  }
83
- for (var paramName in values) {
84
- //@ts-ignore
85
- this[paramName].value = (_a = values[paramName]) !== null && _a !== void 0 ? _a : null;
68
+ var hasChange = false;
69
+ var newValues = {};
70
+ for (var _i = 0, _a = this.params; _i < _a.length; _i++) {
71
+ var param = _a[_i];
72
+ var hasNewValue = values[param.name] !== undefined;
73
+ var newValue = RouteHelper.stringifyValue(values[param.name]);
74
+ if (hasNewValue && newValue != param.value) {
75
+ hasChange = true;
76
+ }
77
+ newValues[param.name] = hasNewValue ? newValue : param.value;
86
78
  }
87
- this.suppressNavigation = false;
88
- if (this.hasSuppressedNavigation || options.force) {
89
- this.setUrl(this.params[this.params.length - 1], options.replace);
79
+ if (hasChange || options.force) {
80
+ this.navigate(newValues, options.replace);
90
81
  }
91
82
  };
92
- ViewParameterList.prototype.setUrl = function (viewParameter, replace) {
83
+ ViewParameterList.prototype.setUrl = function (viewParameter, newValue, replace) {
93
84
  if (replace === void 0) { replace = false; }
94
- if (this.suppressNavigation) {
95
- this.hasSuppressedNavigation = true;
96
- }
97
- else {
98
- var navigationHelper = Misc.Globals.appService.get(NeoReactTypes.Routing.NavigationHelper);
99
- if (viewParameter) {
100
- var url = this.getUrl(viewParameter);
101
- if (url) {
102
- navigationHelper.navigateInternal(url, replace);
103
- return;
85
+ var canAdd = true;
86
+ var newValues = {};
87
+ for (var _i = 0, _a = this.params; _i < _a.length; _i++) {
88
+ var param = _a[_i];
89
+ if (canAdd || param.routeParameter.isQuery) {
90
+ if (param === viewParameter) {
91
+ newValues[param.name] = newValue;
92
+ canAdd = false;
93
+ }
94
+ else {
95
+ newValues[param.name] = param.value;
104
96
  }
105
97
  }
106
- navigationHelper.navigateInternal(navigationHelper.getCurrentViewPath(), replace);
107
98
  }
99
+ this.navigate(newValues, replace);
108
100
  };
109
- ViewParameterList.prototype.getUrl = function (viewParameter) {
110
- var allItems = this.getBreadCrumbList(viewParameter);
111
- if (allItems.length > 0) {
112
- return allItems[allItems.length - 1].link;
113
- }
114
- return undefined;
101
+ ViewParameterList.prototype.navigate = function (values, replace) {
102
+ var path = RouteHelper.hydrateRoute(this._processedRoute, values);
103
+ this._navigationHelper.navigateInternal(path, replace);
115
104
  };
116
105
  /**
117
106
  * @internal
118
107
  * Updates the parameter values from the values in the url. Returns true if any of the values have changed.
119
108
  */
120
- ViewParameterList.prototype.update = function (match) {
109
+ ViewParameterList.prototype.update = function (match, queryString) {
121
110
  var hasChanged = false;
122
111
  //Get url params
123
112
  for (var param in match.params) {
@@ -130,7 +119,7 @@ var ViewParameterList = /** @class */ (function () {
130
119
  }
131
120
  }
132
121
  //Get query params
133
- var queryParams = new URLSearchParams(location.search);
122
+ var queryParams = new URLSearchParams(queryString);
134
123
  for (var _i = 0, _a = this.params; _i < _a.length; _i++) {
135
124
  var viewParam = _a[_i];
136
125
  if (viewParam.routeParameter.isQuery) {
@@ -140,39 +129,6 @@ var ViewParameterList = /** @class */ (function () {
140
129
  }
141
130
  return hasChanged;
142
131
  };
143
- /**
144
- * Gets a url for an object with view params specified.
145
- * @param basePath Base path without parameters
146
- * @param values Object of param names and values.
147
- */
148
- ViewParameterList.getPathForValues = function (basePath, params, values) {
149
- // TODO: This needs a rework.
150
- var viewParameterList = new ViewParameterList(params);
151
- for (var name_1 in values) {
152
- //@ts-ignore
153
- var viewParam = viewParameterList[name_1];
154
- if (viewParam) {
155
- var value = values[name_1];
156
- if (value === undefined)
157
- value = null;
158
- if (typeof value === "number") {
159
- value = value.toString();
160
- }
161
- viewParam.update(value);
162
- }
163
- }
164
- var state = { hasQuery: false };
165
- var paramPath = basePath;
166
- for (var _i = 0, _a = viewParameterList.params.filter(function (c) { return !c.routeParameter.isQuery; }); _i < _a.length; _i++) {
167
- var viewParam = _a[_i];
168
- paramPath += viewParam.getParamString(state);
169
- }
170
- // trim trailing slashes
171
- while (paramPath.endsWith("/")) {
172
- paramPath = paramPath.substring(0, paramPath.length - 1);
173
- }
174
- return paramPath + viewParameterList.getQueryString(state);
175
- };
176
132
  return ViewParameterList;
177
133
  }());
178
134
  export { ViewParameterList };
@@ -32,6 +32,8 @@ export default abstract class ViewBase<TViewModel extends IViewModel = EmptyView
32
32
  navigation: import("../Routing").INavigationHelper;
33
33
  private breadCrumbStartItems?;
34
34
  protected viewState: ViewState<TViewModel, TParams>;
35
+ /** If true, viewParamsUpdated() will be called on initialisation even if all parameters have default values. */
36
+ protected callViewParamsUpdatedOnInitialise: boolean;
35
37
  /**
36
38
  * Creates a view
37
39
  * @param props React props.
@@ -24,6 +24,8 @@ var ViewBase = /** @class */ (function (_super) {
24
24
  _this.hasInitialised = false;
25
25
  _this.routingState = Misc.Globals.appService.get(NeoReactTypes.Routing.GlobalRoutingState);
26
26
  _this.navigation = Misc.Globals.appService.get(NeoReactTypes.Routing.NavigationHelper);
27
+ /** If true, viewParamsUpdated() will be called on initialisation even if all parameters have default values. */
28
+ _this.callViewParamsUpdatedOnInitialise = false;
27
29
  if (typeof viewName !== "string") {
28
30
  throw "Error: When inheriting ViewBase, you must pass the view name into the base constructor.";
29
31
  }
@@ -66,8 +68,7 @@ var ViewBase = /** @class */ (function (_super) {
66
68
  return this.createViewState(viewModel);
67
69
  };
68
70
  ViewBase.prototype.createViewState = function (viewModel) {
69
- var params = this.constructor.params;
70
- var paramsList = new ViewParameterList(params, this);
71
+ var paramsList = new ViewParameterList(this.routingState.currentRoute.route, this.navigation, this);
71
72
  var viewModelInstance = ViewModelBase.create(viewModel);
72
73
  return new ViewState(paramsList, viewModelInstance);
73
74
  };
@@ -77,7 +78,7 @@ var ViewBase = /** @class */ (function (_super) {
77
78
  return __generator(this, function (_a) {
78
79
  switch (_a.label) {
79
80
  case 0:
80
- paramsUpdated = this.routingState.currentRoute ? (this.viewState.viewParams).update(this.routingState.currentRoute) : false;
81
+ paramsUpdated = this.routingState.currentRoute ? (this.viewState.viewParams).update(this.routingState.currentRoute, location.search) : false;
81
82
  if (!!this.viewState.hasSetup) return [3 /*break*/, 2];
82
83
  this.viewState.hasSetup = true;
83
84
  return [4 /*yield*/, this.initialise()];
@@ -85,7 +86,7 @@ var ViewBase = /** @class */ (function (_super) {
85
86
  _a.sent();
86
87
  _a.label = 2;
87
88
  case 2:
88
- if (!paramsUpdated) return [3 /*break*/, 4];
89
+ if (!(paramsUpdated || this.callViewParamsUpdatedOnInitialise)) return [3 /*break*/, 4];
89
90
  return [4 /*yield*/, this.viewParamsUpdated()];
90
91
  case 3:
91
92
  _a.sent();
@@ -106,7 +107,7 @@ var ViewBase = /** @class */ (function (_super) {
106
107
  return __generator(this, function (_a) {
107
108
  switch (_a.label) {
108
109
  case 0:
109
- if (!(this.viewState.viewParams).update(match)) return [3 /*break*/, 2];
110
+ if (!(this.viewState.viewParams).update(match, location.search)) return [3 /*break*/, 2];
110
111
  return [4 /*yield*/, this.viewParamsUpdated()];
111
112
  case 1:
112
113
  _a.sent();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-react",
3
- "version": "1.4.3",
3
+ "version": "1.6.0",
4
4
  "description": "React application logic and components for the Neo client library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "typescript": "5.9.3"
28
28
  },
29
29
  "dependencies": {
30
- "@singularsystems/neo-core": "1.4.1",
30
+ "@singularsystems/neo-core": "1.6.0",
31
31
  "@types/react-color": "3.0.13",
32
32
  "axios": "1.13.5",
33
33
  "inversify": "6.0.2",
package/styles/Forms.scss CHANGED
@@ -65,7 +65,7 @@ div.neo-form-group-floating {
65
65
  transform: scale(1);
66
66
  }
67
67
  label.neo-floating-label {
68
- color: color.adjust($input-focus-border-color, $lightness: -15%);
68
+ color: mix($input-focus-border-color, black, 85%);
69
69
  }
70
70
  .neo-forms-editor {
71
71
  border-bottom: 1px solid $input-focus-border-color;