@hybridly/core 0.0.1-dev.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Anthony Fu <https://github.com/antfu>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,642 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const axios = require('axios');
6
+ const qs = require('qs');
7
+ const utils = require('@hybridly/utils');
8
+
9
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
10
+
11
+ const axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
12
+ const qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
13
+
14
+ const STORAGE_EXTERNAL_KEY = "hybridly:external";
15
+ const HYBRIDLY_HEADER = "x-hybridly";
16
+ const EXTERNAL_VISIT_HEADER = `${HYBRIDLY_HEADER}-external`;
17
+ const PARTIAL_COMPONENT_HEADER = `${HYBRIDLY_HEADER}-partial-component`;
18
+ const ONLY_DATA_HEADER = `${HYBRIDLY_HEADER}-only-data`;
19
+ const EXCEPT_DATA_HEADER = `${HYBRIDLY_HEADER}-except-data`;
20
+ const CONTEXT_HEADER = `${HYBRIDLY_HEADER}-context`;
21
+ const VERSION_HEADER = `${HYBRIDLY_HEADER}-version`;
22
+ const ERROR_BAG_HEADER = `${HYBRIDLY_HEADER}-error-bag`;
23
+ const SCROLL_REGION_ATTRIBUTE = "scroll-region";
24
+
25
+ const constants = {
26
+ __proto__: null,
27
+ STORAGE_EXTERNAL_KEY: STORAGE_EXTERNAL_KEY,
28
+ HYBRIDLY_HEADER: HYBRIDLY_HEADER,
29
+ EXTERNAL_VISIT_HEADER: EXTERNAL_VISIT_HEADER,
30
+ PARTIAL_COMPONENT_HEADER: PARTIAL_COMPONENT_HEADER,
31
+ ONLY_DATA_HEADER: ONLY_DATA_HEADER,
32
+ EXCEPT_DATA_HEADER: EXCEPT_DATA_HEADER,
33
+ CONTEXT_HEADER: CONTEXT_HEADER,
34
+ VERSION_HEADER: VERSION_HEADER,
35
+ ERROR_BAG_HEADER: ERROR_BAG_HEADER,
36
+ SCROLL_REGION_ATTRIBUTE: SCROLL_REGION_ATTRIBUTE
37
+ };
38
+
39
+ class NotAHybridlyResponseError extends Error {
40
+ constructor(response) {
41
+ super();
42
+ this.response = response;
43
+ }
44
+ }
45
+ class VisitCancelledError extends Error {
46
+ }
47
+
48
+ function saveScrollPositions() {
49
+ const regions = getScrollRegions();
50
+ utils.debug.scroll("Saving scroll positions of:", regions);
51
+ setContext({
52
+ scrollRegions: regions.map(({ scrollTop, scrollLeft }) => ({
53
+ top: scrollTop,
54
+ left: scrollLeft
55
+ }))
56
+ });
57
+ setHistoryState({ replace: true });
58
+ }
59
+ function getScrollRegions() {
60
+ return Array.from(document?.querySelectorAll(`[${SCROLL_REGION_ATTRIBUTE}]`) ?? []);
61
+ }
62
+ function resetScrollPositions() {
63
+ utils.debug.scroll("Resetting scroll positions.");
64
+ getScrollRegions().concat(document.documentElement, document.body).forEach((element) => {
65
+ element.scrollTop = 0;
66
+ element.scrollLeft = 0;
67
+ });
68
+ saveScrollPositions();
69
+ if (window.location.hash) {
70
+ utils.debug.scroll(`Hash is present, scrolling to the element of ID ${window.location.hash}.`);
71
+ document.getElementById(window.location.hash.slice(1))?.scrollIntoView();
72
+ }
73
+ }
74
+ async function restoreScrollPositions() {
75
+ utils.debug.scroll("Restoring scroll positions stored in the context.");
76
+ const context = getRouterContext();
77
+ const regions = getScrollRegions();
78
+ if (!context.scrollRegions) {
79
+ return;
80
+ }
81
+ let tries = 0;
82
+ const timer = setInterval(() => {
83
+ if (context.scrollRegions.length !== regions.length) {
84
+ if (++tries > 20) {
85
+ utils.debug.scroll("The limit of tries has been reached. Cancelling scroll restoration.");
86
+ clearInterval(timer);
87
+ return;
88
+ }
89
+ utils.debug.scroll(`The scroll regions count do not match. Waiting for page to fully load (try #${tries}).`);
90
+ return;
91
+ }
92
+ clearInterval(timer);
93
+ regions.forEach((el, i) => el.scrollTo({
94
+ top: context.scrollRegions.at(i)?.top ?? el.scrollTop,
95
+ left: context.scrollRegions.at(i)?.top ?? el.scrollLeft
96
+ }));
97
+ }, 50);
98
+ }
99
+
100
+ function normalizeUrl(href) {
101
+ return makeUrl(href).toString();
102
+ }
103
+ function makeUrl(href, transformations = {}) {
104
+ try {
105
+ const base = document?.location?.href === "//" ? void 0 : document.location.href;
106
+ const url = new URL(String(href), base);
107
+ Object.entries(transformations ?? {}).forEach(([key, value]) => Reflect.set(url, key, value));
108
+ return url;
109
+ } catch (error) {
110
+ throw new TypeError(`${href} is not resolvable to a valid URL.`);
111
+ }
112
+ }
113
+ function sameUrls(...hrefs) {
114
+ if (hrefs.length < 2) {
115
+ return true;
116
+ }
117
+ try {
118
+ return hrefs.every((href) => {
119
+ return makeUrl(href, { hash: "" }).toJSON() === makeUrl(hrefs.at(0), { hash: "" }).toJSON();
120
+ });
121
+ } catch {
122
+ }
123
+ return false;
124
+ }
125
+ function fillHash(currentUrl, targetUrl) {
126
+ currentUrl = makeUrl(currentUrl);
127
+ targetUrl = makeUrl(targetUrl);
128
+ if (currentUrl.hash && !targetUrl.hash && sameUrls(targetUrl, currentUrl)) {
129
+ targetUrl.hash = currentUrl.hash;
130
+ }
131
+ return targetUrl.toString();
132
+ }
133
+
134
+ function setHistoryState(options = {}) {
135
+ if (!window?.history) {
136
+ throw new Error("The history API is not available, so Hybridly cannot operate.");
137
+ }
138
+ const context = getRouterContext();
139
+ const method = options.replace ? "replaceState" : "pushState";
140
+ const serialized = serializeContext(context);
141
+ utils.debug.history("Setting history state:", {
142
+ method,
143
+ context,
144
+ serialized
145
+ });
146
+ try {
147
+ window.history[method](serialized, "", context.url);
148
+ } catch (error) {
149
+ console.error("Hybridly could not save its current state in the history. This is most likely due to a property being non-serializable, such as a proxy or a reference.");
150
+ throw error;
151
+ }
152
+ }
153
+ function getHistoryState(key) {
154
+ const state = key ? window.history.state?.state?.[key] : window.history.state?.state;
155
+ return getRouterContext().serializer.unserialize(state);
156
+ }
157
+ async function registerEventListeners() {
158
+ const context = getRouterContext();
159
+ utils.debug.history("Registering [popstate] and [scroll] event listeners.");
160
+ window?.addEventListener("popstate", async (event) => {
161
+ utils.debug.history("Navigation detected (popstate event). State:", { state: event.state });
162
+ if (!event.state) {
163
+ utils.debug.history("There is no state. Adding hash if any and restoring scroll positions.");
164
+ return await navigate({
165
+ payload: {
166
+ ...context,
167
+ url: makeUrl(context.url, { hash: window.location.hash }).toString()
168
+ },
169
+ preserveScroll: true,
170
+ preserveState: true,
171
+ replace: true
172
+ });
173
+ }
174
+ await navigate({
175
+ payload: event.state,
176
+ preserveScroll: true,
177
+ preserveState: false,
178
+ updateHistoryState: false,
179
+ isBackForward: true
180
+ });
181
+ });
182
+ window?.addEventListener("scroll", (event) => utils.debounce(() => {
183
+ if (event?.target?.hasAttribute?.(SCROLL_REGION_ATTRIBUTE)) {
184
+ saveScrollPositions();
185
+ }
186
+ }, 100), true);
187
+ }
188
+ function isBackForwardVisit() {
189
+ if (!window.history.state) {
190
+ return false;
191
+ }
192
+ return window.performance?.getEntriesByType("navigation").at(0)?.type === "back_forward";
193
+ }
194
+ async function handleBackForwardVisit() {
195
+ utils.debug.router("Handling a back/forward visit.");
196
+ window.history.state.version = getRouterContext().version;
197
+ await navigate({
198
+ preserveScroll: true,
199
+ preserveState: true
200
+ });
201
+ }
202
+ function remember(key, value) {
203
+ utils.debug.history(`Remembering key "${key}" with value`, value);
204
+ setContext({
205
+ state: {
206
+ ...getRouterContext().state,
207
+ [key]: value
208
+ }
209
+ }, { propagate: false });
210
+ setHistoryState({ replace: true });
211
+ }
212
+ function getKeyFromHistory(key) {
213
+ return getHistoryState(key);
214
+ }
215
+ function serializeContext(context) {
216
+ return {
217
+ url: context.url,
218
+ version: context.version,
219
+ view: context.serializer.serialize(context.view),
220
+ dialog: context.dialog,
221
+ scrollRegions: context.scrollRegions,
222
+ state: context.serializer.serialize(context.state)
223
+ };
224
+ }
225
+ function createSerializer(options) {
226
+ if (options.serializer) {
227
+ return options.serializer;
228
+ }
229
+ return {
230
+ serialize: (view) => JSON.parse(JSON.stringify(view)),
231
+ unserialize: (state) => state
232
+ };
233
+ }
234
+
235
+ const state = {
236
+ initialized: false,
237
+ context: {}
238
+ };
239
+ function getRouterContext() {
240
+ return getInternalRouterContext();
241
+ }
242
+ function getInternalRouterContext() {
243
+ if (!state.initialized) {
244
+ throw new Error("Hybridly is not initialized.");
245
+ }
246
+ return state.context;
247
+ }
248
+ async function initializeContext(options) {
249
+ state.initialized = true;
250
+ state.context = {
251
+ ...options.payload,
252
+ serializer: createSerializer(options),
253
+ url: makeUrl(options.payload.url).toString(),
254
+ adapter: options.adapter,
255
+ scrollRegions: [],
256
+ plugins: options.plugins ?? [],
257
+ hooks: {},
258
+ state: {}
259
+ };
260
+ for (const plugin of state.context.plugins) {
261
+ utils.debug.plugin(plugin.name, 'Calling "initialized" hook.');
262
+ await plugin.initialized?.(state.context);
263
+ }
264
+ return getInternalRouterContext();
265
+ }
266
+ function setContext(merge = {}, options = {}) {
267
+ Object.keys(merge).forEach((key) => {
268
+ Reflect.set(state.context, key, merge[key]);
269
+ });
270
+ if (options.propagate !== false) {
271
+ state.context.adapter.update?.(state.context);
272
+ }
273
+ utils.debug.context("Updated context:", { context: state.context, added: merge });
274
+ }
275
+ function payloadFromContext() {
276
+ return {
277
+ url: getRouterContext().url,
278
+ version: getRouterContext().version,
279
+ view: getRouterContext().view,
280
+ dialog: getRouterContext().dialog
281
+ };
282
+ }
283
+
284
+ async function performExternalVisit(options) {
285
+ utils.debug.external("Making a hard navigation for an external visit:", options);
286
+ window.sessionStorage.setItem(STORAGE_EXTERNAL_KEY, JSON.stringify(options));
287
+ window.location.href = options.url;
288
+ if (sameUrls(window.location, options.url)) {
289
+ utils.debug.external("Manually reloading due to the external URL being the same.");
290
+ window.location.reload();
291
+ }
292
+ }
293
+ function isExternalResponse(response) {
294
+ return response?.status === 409 && !!response?.headers?.[EXTERNAL_VISIT_HEADER];
295
+ }
296
+ async function handleExternalVisit() {
297
+ utils.debug.external("Handling an external visit.");
298
+ const options = JSON.parse(window.sessionStorage.getItem(STORAGE_EXTERNAL_KEY) || "{}");
299
+ window.sessionStorage.removeItem(STORAGE_EXTERNAL_KEY);
300
+ utils.debug.external("Options from the session storage:", options);
301
+ setContext({
302
+ url: makeUrl(getRouterContext().url, { hash: window.location.hash }).toString()
303
+ });
304
+ await navigate({
305
+ preserveScroll: options.preserveScroll,
306
+ preserveState: true
307
+ });
308
+ }
309
+ function isExternalVisit() {
310
+ try {
311
+ return window.sessionStorage.getItem(STORAGE_EXTERNAL_KEY) !== null;
312
+ } catch {
313
+ }
314
+ return false;
315
+ }
316
+
317
+ function definePlugin(plugin) {
318
+ return plugin;
319
+ }
320
+ async function runPluginHooks(hook, ...args) {
321
+ const { plugins } = getRouterContext();
322
+ let result = true;
323
+ for (const plugin of plugins) {
324
+ if (plugin.hooks[hook]) {
325
+ utils.debug.plugin(plugin.name, `Calling "${hook}" hooks.`);
326
+ result = await plugin.hooks[hook]?.(...args) ?? result;
327
+ }
328
+ }
329
+ return result;
330
+ }
331
+ async function runGlobalHooks(hook, ...args) {
332
+ const { hooks } = getRouterContext();
333
+ if (!hooks[hook]) {
334
+ return true;
335
+ }
336
+ let result = true;
337
+ for (const fn of hooks[hook]) {
338
+ utils.debug.hook(`Calling global "${hook}" hooks.`);
339
+ result = await fn(...args) ?? result;
340
+ }
341
+ return result;
342
+ }
343
+ async function runHooks(hook, requestHooks, ...args) {
344
+ const result = await Promise.all([
345
+ requestHooks?.[hook]?.(...args),
346
+ runGlobalHooks(hook, ...args),
347
+ runPluginHooks(hook, ...args)
348
+ ]);
349
+ return !result.includes(false);
350
+ }
351
+
352
+ function registerHook(hook, fn) {
353
+ const hooks = getRouterContext().hooks;
354
+ hooks[hook] = [...hooks[hook] ?? [], fn];
355
+ return () => hooks[hook]?.splice(hooks[hook].indexOf(fn), 1);
356
+ }
357
+ function registerHookOnce(hook, fn) {
358
+ const unregister = registerHook(hook, async (...args) => {
359
+ await fn(...args);
360
+ unregister();
361
+ });
362
+ }
363
+
364
+ const router = {
365
+ abort: async () => getRouterContext().activeVisit?.controller.abort(),
366
+ active: () => !!getRouterContext().activeVisit,
367
+ visit: async (options) => await visit(options),
368
+ reload: async (options) => await visit({ preserveScroll: true, preserveState: true, ...options }),
369
+ get: async (url, options = {}) => await visit({ ...options, url, method: "GET" }),
370
+ post: async (url, options = {}) => await visit({ preserveState: true, ...options, url, method: "POST" }),
371
+ put: async (url, options = {}) => await visit({ preserveState: true, ...options, url, method: "PUT" }),
372
+ patch: async (url, options = {}) => await visit({ preserveState: true, ...options, url, method: "PATCH" }),
373
+ delete: async (url, options = {}) => await visit({ preserveState: true, ...options, url, method: "DELETE" }),
374
+ local: async (url, options) => await performLocalComponentVisit(url, options),
375
+ external: (url, data = {}) => performLocalExternalVisit(url, data),
376
+ history: {
377
+ get: (key) => getKeyFromHistory(key),
378
+ remember: (key, value) => remember(key, value)
379
+ }
380
+ };
381
+ async function createRouter(options) {
382
+ await initializeContext(options);
383
+ return await initializeRouter();
384
+ }
385
+ async function visit(options) {
386
+ const visitId = utils.random();
387
+ const context = getRouterContext();
388
+ utils.debug.router("Making a visit:", { context, options, visitId });
389
+ try {
390
+ if ((utils.hasFiles(options.data) || options.useFormData) && !(options.data instanceof FormData)) {
391
+ options.data = utils.objectToFormData(options.data);
392
+ utils.debug.router("Converted data to FormData.", options.data);
393
+ }
394
+ if (!await runHooks("before", options.hooks, options)) {
395
+ utils.debug.router('"before" event returned false, aborting the visit.');
396
+ throw new VisitCancelledError('The visit was cancelled by the "before" event.');
397
+ }
398
+ if (context.activeVisit) {
399
+ utils.debug.router("Aborting current visit.", context.activeVisit);
400
+ context.activeVisit?.controller.abort();
401
+ }
402
+ saveScrollPositions();
403
+ if (options.url && options.transformUrl) {
404
+ options.url = makeUrl(options.url, options.transformUrl);
405
+ }
406
+ setContext({
407
+ activeVisit: {
408
+ id: visitId,
409
+ url: makeUrl(options.url ?? context.url),
410
+ controller: new AbortController(),
411
+ options
412
+ }
413
+ });
414
+ await runHooks("start", options.hooks, context);
415
+ utils.debug.router("Making request with axios.");
416
+ const response = await axios__default.request({
417
+ url: context.activeVisit.url.toString(),
418
+ method: options.method ?? "GET",
419
+ data: options.method === "GET" ? {} : options.data,
420
+ params: options.method === "GET" ? options.data : {},
421
+ signal: context.activeVisit.controller.signal,
422
+ headers: {
423
+ ...options.headers,
424
+ ...utils.when(options.only !== void 0 || options.except !== void 0, {
425
+ [PARTIAL_COMPONENT_HEADER]: context.view.name,
426
+ ...utils.when(options.only, { [ONLY_DATA_HEADER]: JSON.stringify(options.only) }, {}),
427
+ ...utils.when(options.except, { [EXCEPT_DATA_HEADER]: JSON.stringify(options.except) }, {})
428
+ }, {}),
429
+ ...utils.when(options.errorBag, { [ERROR_BAG_HEADER]: options.errorBag }, {}),
430
+ ...utils.when(context.version, { [VERSION_HEADER]: context.version }, {}),
431
+ [HYBRIDLY_HEADER]: true,
432
+ "X-Requested-With": "XMLHttpRequest",
433
+ "Accept": "text/html, application/xhtml+xml"
434
+ },
435
+ validateStatus: () => true,
436
+ onUploadProgress: async (event) => {
437
+ await runHooks("progress", options.hooks, {
438
+ event,
439
+ percentage: Math.round(event.loaded / (event.total ?? 0) * 100)
440
+ });
441
+ }
442
+ });
443
+ await runHooks("data", options.hooks, response);
444
+ if (isExternalResponse(response)) {
445
+ utils.debug.router("The response is explicitely external.");
446
+ await performExternalVisit({
447
+ url: fillHash(context.activeVisit.url, response.headers[EXTERNAL_VISIT_HEADER]),
448
+ preserveScroll: options.preserveScroll === true
449
+ });
450
+ return { response };
451
+ }
452
+ if (!isHybridlyResponse(response)) {
453
+ throw new NotAHybridlyResponseError(response);
454
+ }
455
+ utils.debug.router("The response respects the hybridly protocol.");
456
+ const payload = response.data;
457
+ if ((options.only?.length ?? options.except?.length) && payload.view.name === context.view.name) {
458
+ utils.debug.router(`Merging ${options.only ? '"only"' : '"except"'} properties.`, payload.view.properties);
459
+ payload.view.properties = utils.merge(context.view.properties, payload.view.properties);
460
+ utils.debug.router("Merged properties:", payload.view.properties);
461
+ }
462
+ await navigate({
463
+ payload: {
464
+ ...payload,
465
+ url: fillHash(context.activeVisit.url, payload.url)
466
+ },
467
+ preserveScroll: options.preserveScroll === true,
468
+ preserveState: options.preserveState,
469
+ preserveUrl: options.preserveUrl,
470
+ replace: options.replace === true || sameUrls(payload.url, window.location.href) || options.preserveUrl
471
+ });
472
+ if (Object.keys(context.view.properties.errors ?? {}).length > 0) {
473
+ const errors = (() => {
474
+ if (options.errorBag && typeof context.view.properties.errors === "object") {
475
+ return context.view.properties.errors[options.errorBag] ?? {};
476
+ }
477
+ return context.view.properties.errors;
478
+ })();
479
+ utils.debug.router("The request returned validation errors.", errors);
480
+ await runHooks("error", options.hooks, errors);
481
+ setContext({
482
+ activeVisit: {
483
+ ...context.activeVisit,
484
+ status: "error"
485
+ }
486
+ });
487
+ } else {
488
+ await runHooks("success", options.hooks, payload);
489
+ setContext({
490
+ activeVisit: {
491
+ ...context.activeVisit,
492
+ status: "success"
493
+ }
494
+ });
495
+ }
496
+ return { response };
497
+ } catch (error) {
498
+ await utils.match(error.constructor.name, {
499
+ VisitCancelledError: async () => {
500
+ utils.debug.router('The request was cancelled through the "before" hook.', error);
501
+ console.warn(error);
502
+ await runHooks("abort", options.hooks, context);
503
+ },
504
+ AbortError: async () => {
505
+ utils.debug.router("The request was cancelled.", error);
506
+ console.warn(error);
507
+ await runHooks("abort", options.hooks, context);
508
+ },
509
+ NotAHybridlyResponseError: async () => {
510
+ utils.debug.router("The request was not hybridly.");
511
+ console.error(error);
512
+ await runHooks("invalid", options.hooks, error);
513
+ utils.showResponseErrorModal(error.response.data);
514
+ },
515
+ default: async () => {
516
+ utils.debug.router("An unknown error occured.", error);
517
+ console.error(error);
518
+ await runHooks("exception", options.hooks, error);
519
+ }
520
+ });
521
+ await runHooks("fail", options.hooks, context);
522
+ return {
523
+ error: {
524
+ type: error.constructor.name,
525
+ actual: error
526
+ }
527
+ };
528
+ } finally {
529
+ utils.debug.router("Ending visit.");
530
+ await runHooks("after", options.hooks, context);
531
+ if (context.activeVisit?.id === visitId) {
532
+ setContext({ activeVisit: void 0 });
533
+ }
534
+ }
535
+ }
536
+ function isHybridlyResponse(response) {
537
+ return !!response?.headers[HYBRIDLY_HEADER];
538
+ }
539
+ async function navigate(options) {
540
+ const context = getRouterContext();
541
+ utils.debug.router("Making an internal navigation:", { context, options });
542
+ options.payload ?? (options.payload = payloadFromContext());
543
+ const evaluateConditionalOption = (option) => typeof option === "function" ? option(options.payload) : option;
544
+ const shouldPreserveState = evaluateConditionalOption(options.preserveState);
545
+ const shouldPreserveScroll = evaluateConditionalOption(options.preserveScroll);
546
+ const shouldReplaceHistory = evaluateConditionalOption(options.replace);
547
+ const shouldReplaceUrl = evaluateConditionalOption(options.preserveUrl);
548
+ if (shouldPreserveState && getHistoryState() && options.payload.view.name === context.view.name) {
549
+ setContext({ state: getHistoryState() });
550
+ }
551
+ if (shouldReplaceUrl) {
552
+ utils.debug.router(`Preserving the current URL (${context.url}) instead of navigating to ${options.payload.url}`);
553
+ options.payload.url = context.url;
554
+ }
555
+ setContext({
556
+ ...options.payload,
557
+ state: {}
558
+ }, { propagate: false });
559
+ if (options.updateHistoryState !== false) {
560
+ utils.debug.router(`Target URL is ${context.url}, current window URL is ${window.location.href}.`, { shouldReplaceHistory });
561
+ setHistoryState({ replace: shouldReplaceHistory });
562
+ }
563
+ const viewComponent = await context.adapter.resolveComponent(context.view.name);
564
+ utils.debug.router(`Component [${context.view.name}] resolved to:`, viewComponent);
565
+ await context.adapter.swapView({
566
+ component: viewComponent,
567
+ preserveState: shouldPreserveState
568
+ });
569
+ if (context.dialog) {
570
+ const dialogComponent = await context.adapter.resolveComponent(context.dialog.name);
571
+ utils.debug.router(`Dialog [${context.view.name}] resolved to:`, dialogComponent);
572
+ await context.adapter.swapDialog({
573
+ component: dialogComponent,
574
+ preserveState: shouldPreserveState
575
+ });
576
+ }
577
+ setContext();
578
+ if (!shouldPreserveScroll) {
579
+ resetScrollPositions();
580
+ } else {
581
+ restoreScrollPositions();
582
+ }
583
+ await runHooks("navigate", {}, options);
584
+ }
585
+ async function initializeRouter() {
586
+ const context = getRouterContext();
587
+ if (isBackForwardVisit()) {
588
+ handleBackForwardVisit();
589
+ } else if (isExternalVisit()) {
590
+ handleExternalVisit();
591
+ } else {
592
+ utils.debug.router("Handling the initial page visit.");
593
+ setContext({
594
+ url: makeUrl(context.url, { hash: window.location.hash }).toString()
595
+ });
596
+ await navigate({
597
+ preserveState: true,
598
+ replace: sameUrls(context.url, window.location.href)
599
+ });
600
+ }
601
+ registerEventListeners();
602
+ return context;
603
+ }
604
+ async function performLocalComponentVisit(targetUrl, options) {
605
+ const context = getRouterContext();
606
+ const url = normalizeUrl(targetUrl);
607
+ return await navigate({
608
+ ...options,
609
+ payload: {
610
+ version: context.version,
611
+ dialog: context.dialog,
612
+ url,
613
+ view: {
614
+ name: options.component ?? context.view.name,
615
+ properties: options.properties
616
+ }
617
+ }
618
+ });
619
+ }
620
+ function performLocalExternalVisit(url, data) {
621
+ document.location.href = makeUrl(url, {
622
+ search: qs__default.stringify(data, {
623
+ encodeValuesOnly: true,
624
+ arrayFormat: "brackets"
625
+ })
626
+ }).toString();
627
+ }
628
+
629
+ function can(resource, action) {
630
+ return resource.authorization?.[action] ?? false;
631
+ }
632
+
633
+ exports.can = can;
634
+ exports.constants = constants;
635
+ exports.createRouter = createRouter;
636
+ exports.definePlugin = definePlugin;
637
+ exports.getRouterContext = getRouterContext;
638
+ exports.makeUrl = makeUrl;
639
+ exports.registerHook = registerHook;
640
+ exports.registerHookOnce = registerHookOnce;
641
+ exports.router = router;
642
+ exports.sameUrls = sameUrls;