@stefanobalocco/jfsmrouter 2.0.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/jFSMRouter.js ADDED
@@ -0,0 +1,398 @@
1
+ class jFSMRouter {
2
+ static _instance;
3
+ static get instance() {
4
+ return (jFSMRouter._instance ??= new jFSMRouter(window));
5
+ }
6
+ _regexDuplicatePathId = /\/(:\w+)(?:\[(?:09|AZ|AZ09)])?\/(?:.+\/)?(\1)(?:\[(?:09|AZ|AZ09)])?(?:\/|$)/g;
7
+ _regexSearchVariables = /(?<=^|\/):(\w+)(?:\[(09|AZ|AZ09)])?(?=\/|$)/g;
8
+ _routes = [];
9
+ _routeFunction403;
10
+ _routeFunction404;
11
+ _routeFunction500;
12
+ _routing = false;
13
+ _queue = [];
14
+ _inTransition = false;
15
+ _currentState;
16
+ _states = {};
17
+ _transitions = {};
18
+ _window;
19
+ constructor(window) {
20
+ this._window = window;
21
+ this._window.addEventListener("hashchange", this.checkHash.bind(this));
22
+ }
23
+ static _CheckRouteEquivalence(path1, path2) {
24
+ const generateVariants = (path) => {
25
+ let returnValue = [path];
26
+ if (path.includes(':AZ09')) {
27
+ returnValue.push(...generateVariants(path.replace(/:AZ09/, ':AZ')), ...generateVariants(path.replace(/:AZ09/, ':09')));
28
+ }
29
+ return returnValue;
30
+ };
31
+ const variants = new Set(generateVariants(path1));
32
+ return [...generateVariants(path2)].some(x => variants.has(x));
33
+ }
34
+ stateAdd(state) {
35
+ let returnValue = false;
36
+ if (!this._states[state]) {
37
+ this._states[state] = {
38
+ OnEnter: [],
39
+ OnLeave: []
40
+ };
41
+ this._transitions[state] = {};
42
+ if (!this._currentState) {
43
+ this._currentState = state;
44
+ }
45
+ returnValue = true;
46
+ }
47
+ return returnValue;
48
+ }
49
+ stateDel(state) {
50
+ let returnValue = false;
51
+ if (this._states[state]) {
52
+ delete this._states[state];
53
+ if (this._transitions[state]) {
54
+ delete this._transitions[state];
55
+ }
56
+ for (const tmpState in this._transitions) {
57
+ if (this._transitions[tmpState][state]) {
58
+ delete this._transitions[tmpState][state];
59
+ }
60
+ }
61
+ returnValue = true;
62
+ }
63
+ return returnValue;
64
+ }
65
+ stateOnEnterAdd(state, func) {
66
+ let returnValue = false;
67
+ if (this._states[state] && !this._states[state].OnEnter.includes(func)) {
68
+ this._states[state].OnEnter.push(func);
69
+ returnValue = true;
70
+ }
71
+ return returnValue;
72
+ }
73
+ stateOnEnterDel(state, func) {
74
+ let returnValue = false;
75
+ if (this._states[state]) {
76
+ const position = this._states[state].OnEnter.indexOf(func);
77
+ if (-1 !== position) {
78
+ this._states[state].OnEnter.splice(position, 1);
79
+ returnValue = true;
80
+ }
81
+ }
82
+ return returnValue;
83
+ }
84
+ stateOnLeaveAdd(state, func) {
85
+ let returnValue = false;
86
+ if (this._states[state] && !this._states[state].OnLeave.includes(func)) {
87
+ this._states[state].OnLeave.push(func);
88
+ returnValue = true;
89
+ }
90
+ return returnValue;
91
+ }
92
+ stateOnLeaveDel(state, func) {
93
+ let returnValue = false;
94
+ if (this._states[state]) {
95
+ const position = this._states[state].OnLeave.indexOf(func);
96
+ if (-1 !== position) {
97
+ this._states[state].OnLeave.splice(position, 1);
98
+ returnValue = true;
99
+ }
100
+ }
101
+ return returnValue;
102
+ }
103
+ transitionAdd(from, to) {
104
+ let returnValue = false;
105
+ if (this._states[from] && this._states[to] && !this._transitions[from][to]) {
106
+ this._transitions[from][to] = {
107
+ OnBefore: [],
108
+ OnAfter: []
109
+ };
110
+ returnValue = true;
111
+ }
112
+ return returnValue;
113
+ }
114
+ transitionDel(from, to) {
115
+ let returnValue = false;
116
+ if (this._states[from] && this._states[to] && this._transitions[from][to]) {
117
+ delete this._transitions[from][to];
118
+ returnValue = true;
119
+ }
120
+ return returnValue;
121
+ }
122
+ transitionOnBeforeAdd(from, to, func) {
123
+ let returnValue = false;
124
+ if (this._states[from] && this._states[to] && this._transitions[from][to]) {
125
+ this._transitions[from][to].OnBefore.push(func);
126
+ returnValue = true;
127
+ }
128
+ return returnValue;
129
+ }
130
+ transitionOnBeforeDel(from, to, func) {
131
+ let returnValue = false;
132
+ if (this._states[from] && this._states[to] && this._transitions[from][to]) {
133
+ const pos = this._transitions[from][to].OnBefore.indexOf(func);
134
+ if (-1 !== pos) {
135
+ this._transitions[from][to].OnBefore.splice(pos, 1);
136
+ returnValue = true;
137
+ }
138
+ }
139
+ return returnValue;
140
+ }
141
+ transitionOnAfterAdd(from, to, func) {
142
+ let returnValue = false;
143
+ if (this._states[from] && this._states[to] && this._transitions[from][to]) {
144
+ this._transitions[from][to].OnAfter.push(func);
145
+ returnValue = true;
146
+ }
147
+ return returnValue;
148
+ }
149
+ transitionOnAfterDel(from, to, func) {
150
+ let returnValue = false;
151
+ if (this._states[from] && this._states[to] && this._transitions[from][to]) {
152
+ const pos = this._transitions[from][to].OnAfter.indexOf(func);
153
+ if (-1 !== pos) {
154
+ this._transitions[from][to].OnAfter.splice(pos, 1);
155
+ returnValue = true;
156
+ }
157
+ }
158
+ return returnValue;
159
+ }
160
+ get state() {
161
+ return this._currentState;
162
+ }
163
+ async stateSet(nextState) {
164
+ let returnValue = false;
165
+ if (!this._inTransition) {
166
+ this._inTransition = true;
167
+ if (this._currentState && this._states[nextState] && this._transitions[this._currentState] && this._transitions[this._currentState][nextState]) {
168
+ returnValue = true;
169
+ let cFL = this._transitions[this._currentState][nextState].OnBefore.length;
170
+ for (let iFL = 0; (returnValue && (iFL < cFL)); iFL++) {
171
+ if ('function' === typeof this._transitions[this._currentState][nextState].OnBefore[iFL]) {
172
+ let tmpValue = null;
173
+ if ('AsyncFunction' === this._transitions[this._currentState][nextState].OnBefore[iFL].constructor.name) {
174
+ tmpValue = await this._transitions[this._currentState][nextState].OnBefore[iFL]();
175
+ }
176
+ else {
177
+ tmpValue = this._transitions[this._currentState][nextState].OnBefore[iFL]();
178
+ }
179
+ returnValue = (false !== tmpValue);
180
+ }
181
+ }
182
+ if (returnValue) {
183
+ cFL = this._states[this._currentState].OnLeave.length;
184
+ for (let iFL = 0; iFL < cFL; iFL++) {
185
+ if ('function' === typeof this._states[this._currentState].OnLeave[iFL]) {
186
+ if ('AsyncFunction' === this._states[this._currentState].OnLeave[iFL].constructor.name) {
187
+ await this._states[this._currentState].OnLeave[iFL](this._currentState, nextState);
188
+ }
189
+ else {
190
+ this._states[this._currentState].OnLeave[iFL](this._currentState, nextState);
191
+ }
192
+ }
193
+ }
194
+ let previousState = this._currentState;
195
+ this._currentState = nextState;
196
+ cFL = this._transitions[previousState][this._currentState].OnAfter.length;
197
+ for (let iFL = 0; iFL < cFL; iFL++) {
198
+ if ('function' === typeof this._transitions[previousState][this._currentState].OnAfter[iFL]) {
199
+ if ('AsyncFunction' === this._transitions[previousState][this._currentState].OnAfter[iFL].constructor.name) {
200
+ await this._transitions[previousState][this._currentState].OnAfter[iFL]();
201
+ }
202
+ else {
203
+ this._transitions[previousState][this._currentState].OnAfter[iFL]();
204
+ }
205
+ }
206
+ }
207
+ cFL = this._states[this._currentState].OnEnter.length;
208
+ for (let iFL = 0; iFL < cFL; iFL++) {
209
+ if ('function' === typeof this._states[this._currentState].OnEnter[iFL]) {
210
+ if ('AsyncFunction' === this._states[this._currentState].OnEnter[iFL].constructor.name) {
211
+ await this._states[this._currentState].OnEnter[iFL](this._currentState, previousState);
212
+ }
213
+ else {
214
+ this._states[this._currentState].OnEnter[iFL](this._currentState, previousState);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ this._inTransition = false;
221
+ }
222
+ return returnValue;
223
+ }
224
+ checkTransition(nextState) {
225
+ return !!(!this._inTransition && this._currentState && this._states[nextState] && this._transitions[this._currentState] && this._transitions[this._currentState][nextState]);
226
+ }
227
+ routeSpecialAdd(code, routeFunction) {
228
+ let returnValue = false;
229
+ switch (code) {
230
+ case 403: {
231
+ this._routeFunction403 = routeFunction;
232
+ returnValue = true;
233
+ break;
234
+ }
235
+ case 404: {
236
+ this._routeFunction404 = routeFunction;
237
+ returnValue = true;
238
+ break;
239
+ }
240
+ case 500: {
241
+ this._routeFunction500 = routeFunction;
242
+ returnValue = true;
243
+ break;
244
+ }
245
+ default: {
246
+ throw new RangeError();
247
+ }
248
+ }
249
+ return returnValue;
250
+ }
251
+ routeAdd(validState, path, routeFunction, available, routeFunction403) {
252
+ let returnValue = false;
253
+ if (this._states[validState]) {
254
+ if (path.match(this._regexDuplicatePathId)) {
255
+ throw new SyntaxError('Duplicate path id');
256
+ }
257
+ else {
258
+ let weight = 0;
259
+ const regex = new RegExp('^' + path.replace(this._regexSearchVariables, function (_unused, name, type) {
260
+ let returnValue = '(?<' + name + '>[';
261
+ switch (type) {
262
+ case '09': {
263
+ returnValue += '\\d';
264
+ break;
265
+ }
266
+ case 'AZ': {
267
+ returnValue += 'a-zA-Z';
268
+ break;
269
+ }
270
+ case 'AZ09':
271
+ default: {
272
+ returnValue += 'a-zA-Z\\d';
273
+ }
274
+ }
275
+ returnValue += ']+)';
276
+ return returnValue;
277
+ }).replace(/\//g, '\\\/') + '$');
278
+ const reducedPath = path.replace(this._regexSearchVariables, (_, __, component) => `:${component ?? 'AZ09'}`);
279
+ const paths = path.split('/');
280
+ const cFL = paths.length;
281
+ for (let iFL = 0; iFL < cFL; iFL++) {
282
+ if (!paths[iFL].startsWith(':')) {
283
+ weight += 2 ** (cFL - iFL - 1);
284
+ }
285
+ }
286
+ if (!this._routes.find((route) => jFSMRouter._CheckRouteEquivalence(reducedPath, route.path))) {
287
+ this._routes.push({
288
+ path: reducedPath,
289
+ validState: validState,
290
+ match: regex,
291
+ weight: weight,
292
+ routeFunction: routeFunction,
293
+ available: available,
294
+ routeFunction403: routeFunction403
295
+ });
296
+ this._routes.sort((a, b) => ((a.weight > b.weight) ? -1 : ((b.weight > a.weight) ? 1 : 0)));
297
+ returnValue = true;
298
+ }
299
+ }
300
+ }
301
+ else {
302
+ throw new SyntaxError('Non-existent state');
303
+ }
304
+ return returnValue;
305
+ }
306
+ routeDel(path) {
307
+ let returnValue = false;
308
+ if (!path.match(this._regexDuplicatePathId)) {
309
+ const reducedPath = path.replace(this._regexSearchVariables, (_, __, component) => `:${component ?? 'AZ09'}`);
310
+ const index = this._routes.findIndex((route) => jFSMRouter._CheckRouteEquivalence(reducedPath, route.path));
311
+ if (-1 < index) {
312
+ this._routes.splice(index, 1);
313
+ returnValue = true;
314
+ }
315
+ }
316
+ else {
317
+ throw new SyntaxError('Duplicate path id');
318
+ }
319
+ return returnValue;
320
+ }
321
+ trigger(path) {
322
+ if ('#' + path != this._window.location.hash) {
323
+ this._window.location.hash = '#' + path;
324
+ }
325
+ }
326
+ async route(path) {
327
+ this._routing = true;
328
+ let routeFunction;
329
+ let routePath = '';
330
+ let result = null;
331
+ for (const route of this._routes) {
332
+ if ((result = route.match.exec(path))) {
333
+ routePath = route.path;
334
+ let available = true;
335
+ if (route.available) {
336
+ if ('function' === typeof route.available) {
337
+ if ('AsyncFunction' === route.available.constructor.name) {
338
+ available = await route.available(routePath, path, (result.groups ?? {}));
339
+ }
340
+ else {
341
+ available = route.available(routePath, path, (result.groups ?? {}));
342
+ }
343
+ }
344
+ else {
345
+ available = false;
346
+ }
347
+ }
348
+ if (available) {
349
+ if (!route.validState || (this._currentState === route.validState)) {
350
+ routeFunction = route.routeFunction;
351
+ }
352
+ else if (this._routeFunction500) {
353
+ routeFunction = this._routeFunction500;
354
+ }
355
+ }
356
+ else if (route.routeFunction403) {
357
+ routeFunction = route.routeFunction403;
358
+ }
359
+ else if (this._routeFunction403) {
360
+ routeFunction = this._routeFunction403;
361
+ }
362
+ break;
363
+ }
364
+ }
365
+ if (!routeFunction && this._routeFunction404) {
366
+ routeFunction = this._routeFunction404;
367
+ }
368
+ if (('function' !== typeof routeFunction) && this._routeFunction500) {
369
+ routeFunction = this._routeFunction500;
370
+ }
371
+ if (routeFunction && ('function' === typeof routeFunction)) {
372
+ if ('AsyncFunction' === routeFunction.constructor.name) {
373
+ await routeFunction(routePath, path, (result?.groups ?? {}));
374
+ }
375
+ else {
376
+ routeFunction(routePath, path, (result?.groups ?? {}));
377
+ }
378
+ }
379
+ if (this._queue.length) {
380
+ await this.route(this._queue.shift());
381
+ }
382
+ else {
383
+ this._routing = false;
384
+ }
385
+ }
386
+ async checkHash() {
387
+ const hash = (this._window.location.hash.startsWith('#') ? this._window.location.hash.substring(1) : '');
388
+ if ('' != hash) {
389
+ if (this._routing) {
390
+ this._queue.push(hash);
391
+ }
392
+ else {
393
+ await this.route(hash);
394
+ }
395
+ }
396
+ }
397
+ }
398
+ export default jFSMRouter.instance;
@@ -0,0 +1 @@
1
+ class t{static t;static get instance(){return t.t??=new t(window)}i=/\/(:\w+)(?:\[(?:09|AZ|AZ09)])?\/(?:.+\/)?(\1)(?:\[(?:09|AZ|AZ09)])?(?:\/|$)/g;h=/(?<=^|\/):(\w+)(?:\[(09|AZ|AZ09)])?(?=\/|$)/g;o=[];u;l;A;p=!1;Z=[];_=!1;O;F={};D={};k;constructor(t){this.k=t,this.k.addEventListener("hashchange",this.checkHash.bind(this))}static S(t,i){const s=t=>{let i=[t];return t.includes(":AZ09")&&i.push(...s(t.replace(/:AZ09/,":AZ")),...s(t.replace(/:AZ09/,":09"))),i},e=new Set(s(t));return[...s(i)].some(t=>e.has(t))}stateAdd(t){let i=!1;return this.F[t]||(this.F[t]={OnEnter:[],OnLeave:[]},this.D[t]={},this.O||(this.O=t),i=!0),i}stateDel(t){let i=!1;if(this.F[t]){delete this.F[t],this.D[t]&&delete this.D[t];for(const i in this.D)this.D[i][t]&&delete this.D[i][t];i=!0}return i}stateOnEnterAdd(t,i){let s=!1;return this.F[t]&&!this.F[t].OnEnter.includes(i)&&(this.F[t].OnEnter.push(i),s=!0),s}stateOnEnterDel(t,i){let s=!1;if(this.F[t]){const e=this.F[t].OnEnter.indexOf(i);-1!==e&&(this.F[t].OnEnter.splice(e,1),s=!0)}return s}stateOnLeaveAdd(t,i){let s=!1;return this.F[t]&&!this.F[t].OnLeave.includes(i)&&(this.F[t].OnLeave.push(i),s=!0),s}stateOnLeaveDel(t,i){let s=!1;if(this.F[t]){const e=this.F[t].OnLeave.indexOf(i);-1!==e&&(this.F[t].OnLeave.splice(e,1),s=!0)}return s}transitionAdd(t,i){let s=!1;return this.F[t]&&this.F[i]&&!this.D[t][i]&&(this.D[t][i]={OnBefore:[],OnAfter:[]},s=!0),s}transitionDel(t,i){let s=!1;return this.F[t]&&this.F[i]&&this.D[t][i]&&(delete this.D[t][i],s=!0),s}transitionOnBeforeAdd(t,i,s){let e=!1;return this.F[t]&&this.F[i]&&this.D[t][i]&&(this.D[t][i].OnBefore.push(s),e=!0),e}transitionOnBeforeDel(t,i,s){let e=!1;if(this.F[t]&&this.F[i]&&this.D[t][i]){const h=this.D[t][i].OnBefore.indexOf(s);-1!==h&&(this.D[t][i].OnBefore.splice(h,1),e=!0)}return e}transitionOnAfterAdd(t,i,s){let e=!1;return this.F[t]&&this.F[i]&&this.D[t][i]&&(this.D[t][i].OnAfter.push(s),e=!0),e}transitionOnAfterDel(t,i,s){let e=!1;if(this.F[t]&&this.F[i]&&this.D[t][i]){const h=this.D[t][i].OnAfter.indexOf(s);-1!==h&&(this.D[t][i].OnAfter.splice(h,1),e=!0)}return e}get state(){return this.O}async stateSet(t){let i=!1;if(!this._){if(this._=!0,this.O&&this.F[t]&&this.D[this.O]&&this.D[this.O][t]){i=!0;let s=this.D[this.O][t].OnBefore.length;for(let e=0;i&&e<s;e++)if("function"==typeof this.D[this.O][t].OnBefore[e]){let s=null;s="AsyncFunction"===this.D[this.O][t].OnBefore[e].constructor.name?await this.D[this.O][t].OnBefore[e]():this.D[this.O][t].OnBefore[e](),i=!1!==s}if(i){s=this.F[this.O].OnLeave.length;for(let i=0;i<s;i++)"function"==typeof this.F[this.O].OnLeave[i]&&("AsyncFunction"===this.F[this.O].OnLeave[i].constructor.name?await this.F[this.O].OnLeave[i](this.O,t):this.F[this.O].OnLeave[i](this.O,t));let i=this.O;this.O=t,s=this.D[i][this.O].OnAfter.length;for(let t=0;t<s;t++)"function"==typeof this.D[i][this.O].OnAfter[t]&&("AsyncFunction"===this.D[i][this.O].OnAfter[t].constructor.name?await this.D[i][this.O].OnAfter[t]():this.D[i][this.O].OnAfter[t]());s=this.F[this.O].OnEnter.length;for(let t=0;t<s;t++)"function"==typeof this.F[this.O].OnEnter[t]&&("AsyncFunction"===this.F[this.O].OnEnter[t].constructor.name?await this.F[this.O].OnEnter[t](this.O,i):this.F[this.O].OnEnter[t](this.O,i))}}this._=!1}return i}checkTransition(t){return!!(!this._&&this.O&&this.F[t]&&this.D[this.O]&&this.D[this.O][t])}routeSpecialAdd(t,i){let s=!1;switch(t){case 403:this.u=i,s=!0;break;case 404:this.l=i,s=!0;break;case 500:this.A=i,s=!0;break;default:throw new RangeError}return s}routeAdd(i,s,e,h,n){let r=!1;if(!this.F[i])throw new SyntaxError("Non-existent state");if(s.match(this.i))throw new SyntaxError("Duplicate path id");{let a=0;const o=new RegExp("^"+s.replace(this.h,function(t,i,s){let e="(?<"+i+">[";switch(s){case"09":e+="\\d";break;case"AZ":e+="a-zA-Z";break;default:e+="a-zA-Z\\d"}return e+="]+)",e}).replace(/\//g,"\\/")+"$"),c=s.replace(this.h,(t,i,s)=>`:${s??"AZ09"}`),u=s.split("/"),l=u.length;for(let t=0;t<l;t++)u[t].startsWith(":")||(a+=2**(l-t-1));this.o.find(i=>t.S(c,i.path))||(this.o.push({path:c,validState:i,match:o,weight:a,routeFunction:e,available:h,routeFunction403:n}),this.o.sort((t,i)=>t.weight>i.weight?-1:i.weight>t.weight?1:0),r=!0)}return r}routeDel(i){let s=!1;if(i.match(this.i))throw new SyntaxError("Duplicate path id");{const e=i.replace(this.h,(t,i,s)=>`:${s??"AZ09"}`),h=this.o.findIndex(i=>t.S(e,i.path));-1<h&&(this.o.splice(h,1),s=!0)}return s}trigger(t){"#"+t!=this.k.location.hash&&(this.k.location.hash="#"+t)}async route(t){let i;this.p=!0;let s="",e=null;for(const h of this.o)if(e=h.match.exec(t)){s=h.path;let n=!0;h.available&&(n="function"==typeof h.available&&("AsyncFunction"===h.available.constructor.name?await h.available(s,t,e.groups??{}):h.available(s,t,e.groups??{}))),n?h.validState&&this.O!==h.validState?this.A&&(i=this.A):i=h.routeFunction:h.routeFunction403?i=h.routeFunction403:this.u&&(i=this.u);break}!i&&this.l&&(i=this.l),"function"!=typeof i&&this.A&&(i=this.A),i&&"function"==typeof i&&("AsyncFunction"===i.constructor.name?await i(s,t,e?.groups??{}):i(s,t,e?.groups??{})),this.Z.length?await this.route(this.Z.shift()):this.p=!1}async checkHash(){const t=this.k.location.hash.startsWith("#")?this.k.location.hash.substring(1):"";""!=t&&(this.p?this.Z.push(t):await this.route(t))}}export default t.instance;