@thoughtbot/superglue 0.54.0 → 1.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.
@@ -30,25 +30,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // lib/index.tsx
31
31
  var lib_exports = {};
32
32
  __export(lib_exports, {
33
- ApplicationBase: () => ApplicationBase,
34
- BEFORE_FETCH: () => BEFORE_FETCH,
35
- BEFORE_REMOTE: () => BEFORE_REMOTE,
36
- BEFORE_VISIT: () => BEFORE_VISIT,
37
- COPY_PAGE: () => COPY_PAGE,
33
+ Application: () => Application,
38
34
  GRAFTING_ERROR: () => GRAFTING_ERROR,
39
35
  GRAFTING_SUCCESS: () => GRAFTING_SUCCESS,
40
- HISTORY_CHANGE: () => HISTORY_CHANGE,
41
- REMOVE_PAGE: () => REMOVE_PAGE,
42
- SAVE_RESPONSE: () => SAVE_RESPONSE,
43
- UPDATE_FRAGMENTS: () => UPDATE_FRAGMENTS,
44
- fragmentMiddleware: () => fragmentMiddleware,
36
+ NavigationContext: () => NavigationContext,
37
+ NavigationProvider: () => NavigationProvider,
38
+ beforeFetch: () => beforeFetch,
39
+ beforeRemote: () => beforeRemote,
40
+ beforeVisit: () => beforeVisit,
41
+ copyPage: () => copyPage,
45
42
  getIn: () => getIn,
46
- urlToPageKey: () => urlToPageKey
43
+ pageReducer: () => pageReducer,
44
+ prepareStore: () => prepareStore,
45
+ removePage: () => removePage,
46
+ rootReducer: () => rootReducer,
47
+ saveAndProcessPage: () => saveAndProcessPage,
48
+ saveResponse: () => saveResponse,
49
+ setup: () => setup,
50
+ superglueReducer: () => superglueReducer,
51
+ updateFragments: () => updateFragments,
52
+ urlToPageKey: () => urlToPageKey,
53
+ useContent: () => useContent,
54
+ useSuperglue: () => useSuperglue
47
55
  });
48
56
  module.exports = __toCommonJS(lib_exports);
49
57
  var import_react2 = __toESM(require("react"));
50
58
  var import_url_parse4 = __toESM(require("url-parse"));
51
59
 
60
+ // lib/config.ts
61
+ var config = {
62
+ baseUrl: "",
63
+ maxPages: 20
64
+ };
65
+
52
66
  // lib/utils/url.ts
53
67
  var import_url_parse = __toESM(require("url-parse"));
54
68
  function pathQuery(url) {
@@ -59,6 +73,11 @@ function pathQueryHash(url) {
59
73
  const { pathname, query, hash } = new import_url_parse.default(url, {});
60
74
  return pathname + query + hash;
61
75
  }
76
+ function propsAtParam(url) {
77
+ const parsed = new import_url_parse.default(url, {}, true);
78
+ const query = parsed.query;
79
+ return query["props_at"];
80
+ }
62
81
  function withFormatJson(url) {
63
82
  const parsed = new import_url_parse.default(url, {}, true);
64
83
  parsed.query["format"] = "json";
@@ -96,6 +115,13 @@ function formatForXHR(url) {
96
115
  const formats = [withoutHash, withFormatJson];
97
116
  return formats.reduce((memo, f) => f(memo), url);
98
117
  }
118
+ function parsePageKey(pageKey) {
119
+ const { pathname, query } = new import_url_parse.default(pageKey, {}, true);
120
+ return {
121
+ pathname,
122
+ search: query
123
+ };
124
+ }
99
125
 
100
126
  // lib/utils/helpers.ts
101
127
  function argsForHistory(path) {
@@ -224,260 +250,8 @@ function setIn(object, path, value) {
224
250
  return results[0];
225
251
  }
226
252
 
227
- // lib/action_creators/index.ts
228
- var import_url_parse2 = __toESM(require("url-parse"));
229
-
230
- // lib/actions.ts
231
- var actions_exports = {};
232
- __export(actions_exports, {
233
- BEFORE_FETCH: () => BEFORE_FETCH,
234
- BEFORE_REMOTE: () => BEFORE_REMOTE,
235
- BEFORE_VISIT: () => BEFORE_VISIT,
236
- COPY_PAGE: () => COPY_PAGE,
237
- GRAFTING_ERROR: () => GRAFTING_ERROR,
238
- GRAFTING_SUCCESS: () => GRAFTING_SUCCESS,
239
- HANDLE_GRAFT: () => HANDLE_GRAFT,
240
- HISTORY_CHANGE: () => HISTORY_CHANGE,
241
- REMOVE_PAGE: () => REMOVE_PAGE,
242
- SAVE_RESPONSE: () => SAVE_RESPONSE,
243
- SET_CSRF_TOKEN: () => SET_CSRF_TOKEN,
244
- SUPERGLUE_ERROR: () => SUPERGLUE_ERROR,
245
- UPDATE_FRAGMENTS: () => UPDATE_FRAGMENTS
246
- });
247
- var BEFORE_FETCH = "@@superglue/BEFORE_FETCH";
248
- var BEFORE_VISIT = "@@superglue/BEFORE_VISIT";
249
- var BEFORE_REMOTE = "@@superglue/BEFORE_REMOTE";
250
- var SAVE_RESPONSE = "@@superglue/SAVE_RESPONSE";
251
- var HANDLE_GRAFT = "@@superglue/HANDLE_GRAFT";
252
- var SUPERGLUE_ERROR = "@@superglue/ERROR";
253
- var GRAFTING_ERROR = "@@superglue/GRAFTING_ERROR";
254
- var GRAFTING_SUCCESS = "@@superglue/GRAFTING_SUCCESS";
255
- var HISTORY_CHANGE = "@@superglue/HISTORY_CHANGE";
256
- var SET_CSRF_TOKEN = "@@superglue/SET_CSRF_TOKEN";
257
- var REMOVE_PAGE = "@@superglue/REMOVE_PAGE";
258
- var COPY_PAGE = "@@superglue/COPY_PAGE";
259
- var UPDATE_FRAGMENTS = "@@superglue/UPDATE_FRAGMENTS";
260
-
261
- // lib/action_creators/requests.ts
262
- function beforeRemote(payload) {
263
- return {
264
- type: BEFORE_REMOTE,
265
- payload
266
- };
267
- }
268
- function beforeFetch(payload) {
269
- return {
270
- type: BEFORE_FETCH,
271
- payload
272
- };
273
- }
274
- function handleError(err) {
275
- return {
276
- type: SUPERGLUE_ERROR,
277
- payload: {
278
- message: err.message
279
- }
280
- };
281
- }
282
- function handleFetchErr(err, fetchArgs, dispatch) {
283
- dispatch(handleError(err));
284
- throw err;
285
- }
286
- function buildMeta(pageKey, page, state, rsp, fetchArgs) {
287
- const { assets: prevAssets } = state;
288
- const { assets: nextAssets } = page;
289
- return {
290
- pageKey,
291
- page,
292
- redirected: rsp.redirected,
293
- rsp,
294
- fetchArgs,
295
- componentIdentifier: page.componentIdentifier,
296
- needsRefresh: needsRefresh(prevAssets, nextAssets)
297
- };
298
- }
299
- var remote = (path, {
300
- method = "GET",
301
- headers,
302
- body,
303
- pageKey: rawPageKey,
304
- beforeSave = (prevPage, receivedPage) => receivedPage
305
- } = {}) => {
306
- path = withoutBusters(path);
307
- rawPageKey = rawPageKey && urlToPageKey(rawPageKey);
308
- return (dispatch, getState) => {
309
- const fetchArgs = argsForFetch(getState, path, {
310
- method,
311
- headers,
312
- body
313
- });
314
- if (rawPageKey === void 0) {
315
- rawPageKey = getState().superglue.currentPageKey;
316
- }
317
- const pageKey = rawPageKey;
318
- const currentPageKey = getState().superglue.currentPageKey;
319
- dispatch(beforeRemote({ currentPageKey, fetchArgs }));
320
- dispatch(beforeFetch({ fetchArgs }));
321
- return fetch(...fetchArgs).then(parseResponse).then(({ rsp, json }) => {
322
- const { superglue, pages = {} } = getState();
323
- const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
324
- const willReplaceCurrent = pageKey == currentPageKey;
325
- const existingId = pages[currentPageKey]?.componentIdentifier;
326
- const receivedId = json.componentIdentifier;
327
- if (willReplaceCurrent && !!existingId && existingId != receivedId) {
328
- console.warn(
329
- `You're about replace an existing page located at pages["${currentPageKey}"]
330
- that has the componentIdentifier "${existingId}" with the contents of a
331
- received page that has a componentIdentifier of "${receivedId}".
332
-
333
- This can happen if you're using data-sg-remote or remote but your response
334
- redirected to a completely different page. Since remote requests do not
335
- navigate or change the current page component, your current page component may
336
- receive a shape that is unexpected and cause issues with rendering.
337
-
338
- Consider using data-sg-visit, the visit function, or redirect_back.`
339
- );
340
- }
341
- const page = beforeSave(pages[pageKey], json);
342
- return dispatch(saveAndProcessPage(pageKey, page)).then(() => meta);
343
- }).catch((e) => handleFetchErr(e, fetchArgs, dispatch));
344
- };
345
- };
346
-
347
- // lib/action_creators/index.ts
348
- function copyPage({
349
- from,
350
- to
351
- }) {
352
- return {
353
- type: COPY_PAGE,
354
- payload: {
355
- from,
356
- to
357
- }
358
- };
359
- }
360
- function saveResponse({
361
- pageKey,
362
- page
363
- }) {
364
- pageKey = urlToPageKey(pageKey);
365
- return {
366
- type: SAVE_RESPONSE,
367
- payload: {
368
- pageKey,
369
- page
370
- }
371
- };
372
- }
373
- function handleGraft({
374
- pageKey,
375
- page
376
- }) {
377
- pageKey = urlToPageKey(pageKey);
378
- return {
379
- type: HANDLE_GRAFT,
380
- payload: {
381
- pageKey,
382
- page
383
- }
384
- };
385
- }
386
- function fetchDeferments(pageKey, defers = []) {
387
- pageKey = urlToPageKey(pageKey);
388
- return (dispatch) => {
389
- const fetches = defers.filter(({ type }) => type === "auto").map(function({
390
- url,
391
- successAction = GRAFTING_SUCCESS,
392
- failAction = GRAFTING_ERROR
393
- }) {
394
- const parsedUrl = new import_url_parse2.default(url, true);
395
- const keyPath = parsedUrl.query.props_at;
396
- return dispatch(remote(url, { pageKey })).then(() => {
397
- dispatch({
398
- type: successAction,
399
- payload: {
400
- pageKey,
401
- keyPath
402
- }
403
- });
404
- }).catch((err) => {
405
- dispatch({
406
- type: failAction,
407
- payload: {
408
- url,
409
- err,
410
- pageKey,
411
- keyPath
412
- }
413
- });
414
- });
415
- });
416
- return Promise.all(fetches);
417
- };
418
- }
419
- function updateFragmentsUsing(page) {
420
- const changedFragments = {};
421
- page.fragments.forEach((fragment) => {
422
- const { type, path } = fragment;
423
- changedFragments[type] = getIn(page, path);
424
- });
425
- return {
426
- type: UPDATE_FRAGMENTS,
427
- payload: { changedFragments }
428
- };
429
- }
430
- function saveAndProcessPage(pageKey, page) {
431
- return (dispatch, getState) => {
432
- pageKey = urlToPageKey(pageKey);
433
- const { defers = [] } = page;
434
- if ("action" in page) {
435
- dispatch(handleGraft({ pageKey, page }));
436
- } else {
437
- dispatch(saveResponse({ pageKey, page }));
438
- }
439
- const hasFetch = typeof fetch != "undefined";
440
- if (hasFetch) {
441
- return dispatch(fetchDeferments(pageKey, defers)).then(() => {
442
- if (page.fragments.length > 0) {
443
- const finishedPage = getState().pages[pageKey];
444
- dispatch(updateFragmentsUsing(finishedPage));
445
- return Promise.resolve();
446
- }
447
- });
448
- } else {
449
- return Promise.resolve();
450
- }
451
- };
452
- }
453
-
454
- // lib/utils/react.ts
455
- function mapStateToProps(state, ownProps) {
456
- let pageKey = ownProps.pageKey;
457
- const params = ownProps;
458
- const csrfToken = state.superglue.csrfToken;
459
- pageKey = urlToPageKey(pageKey);
460
- const { data, fragments } = state.pages[pageKey] || {
461
- data: {},
462
- fragments: []
463
- };
464
- return { ...data, ...params, pageKey, csrfToken, fragments };
465
- }
466
- var mapDispatchToProps = {
467
- saveAndProcessPage,
468
- copyPage
469
- };
470
-
471
- // lib/utils/request.ts
472
- var import_url_parse3 = __toESM(require("url-parse"));
473
-
474
- // lib/config.ts
475
- var config = {
476
- baseUrl: "",
477
- maxPages: 20
478
- };
479
-
480
253
  // lib/utils/request.ts
254
+ var import_url_parse2 = __toESM(require("url-parse"));
481
255
  function isValidResponse(xhr) {
482
256
  return isValidContent(xhr) && !downloadingFile(xhr);
483
257
  }
@@ -508,7 +282,7 @@ function validateResponse(args) {
508
282
  }
509
283
  function handleServerErrors(args) {
510
284
  const { rsp } = args;
511
- if (!rsp.ok) {
285
+ if (!rsp.ok && rsp.status !== 422) {
512
286
  if (rsp.status === 406) {
513
287
  console.error(
514
288
  "Superglue encountered a 406 Not Acceptable response. This can happen if you used respond_to and didn't specify format.json in the block. Try adding it to your respond_to. For example:\n\nrespond_to do |format|\n format.html\n format.json\n format.csv\nend"
@@ -520,7 +294,13 @@ function handleServerErrors(args) {
520
294
  }
521
295
  return args;
522
296
  }
523
- function argsForFetch(getState, pathQuery2, { method = "GET", headers = {}, body = "", signal } = {}) {
297
+ function argsForFetch(getState, pathQuery2, {
298
+ method = "GET",
299
+ headers = {},
300
+ body = "",
301
+ signal,
302
+ ...rest
303
+ } = {}) {
524
304
  method = method.toUpperCase();
525
305
  const currentState = getState().superglue;
526
306
  const nextHeaders = { ...headers };
@@ -536,7 +316,7 @@ function argsForFetch(getState, pathQuery2, { method = "GET", headers = {}, body
536
316
  if (currentState.csrfToken) {
537
317
  nextHeaders["x-csrf-token"] = currentState.csrfToken;
538
318
  }
539
- const fetchPath = new import_url_parse3.default(
319
+ const fetchPath = new import_url_parse2.default(
540
320
  formatForXHR(pathQuery2),
541
321
  config.baseUrl || {},
542
322
  true
@@ -554,7 +334,7 @@ function argsForFetch(getState, pathQuery2, { method = "GET", headers = {}, body
554
334
  signal
555
335
  };
556
336
  if (currentState.currentPageKey) {
557
- const referrer = new import_url_parse3.default(
337
+ const referrer = new import_url_parse2.default(
558
338
  currentState.currentPageKey,
559
339
  config.baseUrl || {},
560
340
  false
@@ -571,7 +351,7 @@ function argsForFetch(getState, pathQuery2, { method = "GET", headers = {}, body
571
351
  }
572
352
  delete options.body;
573
353
  }
574
- return [fetchPath.toString(), options];
354
+ return [fetchPath.toString(), { ...options, ...rest }];
575
355
  }
576
356
  function extractJSON(rsp) {
577
357
  return rsp.json().then((json) => {
@@ -590,10 +370,12 @@ var HandlerBuilder = class {
590
370
  constructor({
591
371
  ujsAttributePrefix,
592
372
  visit,
593
- remote: remote2
373
+ remote: remote2,
374
+ store
594
375
  }) {
595
376
  this.attributePrefix = ujsAttributePrefix;
596
377
  this.isUJS = this.isUJS.bind(this);
378
+ this.store = store;
597
379
  this.handleSubmit = this.handleSubmit.bind(this);
598
380
  this.handleClick = this.handleClick.bind(this);
599
381
  this.visit = visit;
@@ -607,7 +389,7 @@ var HandlerBuilder = class {
607
389
  }
608
390
  }
609
391
  isNonStandardClick(event) {
610
- return event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
392
+ return event.button > 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
611
393
  }
612
394
  isUJS(node) {
613
395
  const hasVisit = !!node.getAttribute(this.attributePrefix + "-visit");
@@ -652,18 +434,17 @@ var HandlerBuilder = class {
652
434
  this.visitOrRemote(link, url, { method: "GET" });
653
435
  }
654
436
  visitOrRemote(linkOrForm, url, opts) {
437
+ const dataset = { ...linkOrForm.dataset };
655
438
  if (linkOrForm.getAttribute(this.attributePrefix + "-visit")) {
656
- const nextOpts = { ...opts };
657
- const placeholderKey = linkOrForm.getAttribute(
658
- this.attributePrefix + "-placeholder"
659
- );
660
- if (placeholderKey) {
661
- nextOpts.placeholderKey = urlToPageKey(placeholderKey);
662
- }
663
- return this.visit(url, { ...nextOpts });
439
+ this.visit(url, { ...opts, dataset });
664
440
  }
665
441
  if (linkOrForm.getAttribute(this.attributePrefix + "-remote")) {
666
- return this.remote(url, opts);
442
+ const { currentPageKey } = this.store.getState().superglue;
443
+ this.remote(url, {
444
+ ...opts,
445
+ pageKey: currentPageKey,
446
+ dataset
447
+ });
667
448
  }
668
449
  }
669
450
  handlers() {
@@ -676,12 +457,14 @@ var HandlerBuilder = class {
676
457
  var ujsHandlers = ({
677
458
  ujsAttributePrefix,
678
459
  visit,
679
- remote: remote2
460
+ remote: remote2,
461
+ store
680
462
  }) => {
681
463
  const builder = new HandlerBuilder({
682
464
  visit,
683
465
  remote: remote2,
684
- ujsAttributePrefix
466
+ ujsAttributePrefix,
467
+ store
685
468
  });
686
469
  return builder.handlers();
687
470
  };
@@ -696,239 +479,361 @@ function needsRefresh(prevAssets, newAssets) {
696
479
  }
697
480
  }
698
481
 
699
- // lib/reducers/index.ts
700
- function addPlaceholdersToDeferredNodes(existingPage, page) {
701
- const { defers = [] } = existingPage;
702
- const prevDefers = defers.map(({ path }) => {
703
- const node = getIn(existingPage, path);
704
- const copy = JSON.stringify(node);
705
- return [path, JSON.parse(copy)];
706
- });
707
- return prevDefers.reduce((memo, [path, node]) => {
708
- return setIn(page, path, node);
709
- }, page);
710
- }
711
- function constrainPagesSize(state) {
712
- const { maxPages } = config;
713
- const allPageKeys = Object.keys(state);
714
- const cacheTimesRecentFirst = allPageKeys.map((key) => state[key].savedAt).sort((a, b) => b - a);
715
- for (const key of Array.from(allPageKeys)) {
716
- if (state[key].savedAt <= cacheTimesRecentFirst[maxPages - 1]) {
717
- delete state[key];
718
- }
482
+ // lib/action_creators/index.ts
483
+ var import_url_parse3 = __toESM(require("url-parse"));
484
+
485
+ // lib/actions.ts
486
+ var import_toolkit = require("@reduxjs/toolkit");
487
+ var GRAFTING_ERROR = "@@superglue/GRAFTING_ERROR";
488
+ var GRAFTING_SUCCESS = "@@superglue/GRAFTING_SUCCESS";
489
+ var saveResponse = (0, import_toolkit.createAction)(
490
+ "@@superglue/SAVE_RESPONSE",
491
+ ({ pageKey, page }) => {
492
+ pageKey = urlToPageKey(pageKey);
493
+ return {
494
+ payload: {
495
+ pageKey,
496
+ page
497
+ }
498
+ };
499
+ }
500
+ );
501
+ var handleGraft = (0, import_toolkit.createAction)(
502
+ "@@superglue/HANDLE_GRAFT",
503
+ ({ pageKey, page }) => {
504
+ pageKey = urlToPageKey(pageKey);
505
+ return {
506
+ payload: {
507
+ page,
508
+ pageKey
509
+ }
510
+ };
719
511
  }
512
+ );
513
+ var superglueError = (0, import_toolkit.createAction)(
514
+ "@@superglue/ERROR"
515
+ );
516
+ var updateFragments = (0, import_toolkit.createAction)("@@superglue/UPDATE_FRAGMENTS");
517
+ var copyPage = (0, import_toolkit.createAction)(
518
+ "@@superglue/COPY_PAGE"
519
+ );
520
+ var removePage = (0, import_toolkit.createAction)(
521
+ "@@superglue/REMOVE_PAGE"
522
+ );
523
+ var beforeFetch = (0, import_toolkit.createAction)(
524
+ "@@superglue/BEFORE_FETCH"
525
+ );
526
+ var beforeVisit = (0, import_toolkit.createAction)("@@superglue/BEFORE_VISIT");
527
+ var beforeRemote = (0, import_toolkit.createAction)("@@superglue/BEFORE_REMOTE");
528
+ var setCSRFToken = (0, import_toolkit.createAction)("@@superglue/SET_CSRF_TOKEN");
529
+ var historyChange = (0, import_toolkit.createAction)("@@superglue/HISTORY_CHANGE");
530
+ var setActivePage = (0, import_toolkit.createAction)("@@superglue/SET_ACTIVE_PAGE");
531
+
532
+ // lib/action_creators/requests.ts
533
+ function handleFetchErr(err, fetchArgs, dispatch) {
534
+ dispatch(superglueError({ message: err.message }));
535
+ throw err;
720
536
  }
721
- function saveResponse2(state, pageKey, page) {
722
- state = { ...state };
723
- let nextPage = {
537
+ function buildMeta(pageKey, page, state, rsp, fetchArgs) {
538
+ const { assets: prevAssets } = state;
539
+ const { assets: nextAssets } = page;
540
+ return {
724
541
  pageKey,
725
- ...page,
726
- savedAt: Date.now()
542
+ page,
543
+ redirected: rsp.redirected,
544
+ rsp,
545
+ fetchArgs,
546
+ componentIdentifier: page.componentIdentifier,
547
+ needsRefresh: needsRefresh(prevAssets, nextAssets)
727
548
  };
728
- const existingPage = state[pageKey];
729
- if (existingPage) {
730
- nextPage = addPlaceholdersToDeferredNodes(existingPage, nextPage);
731
- }
732
- constrainPagesSize(state);
733
- state[pageKey] = nextPage;
734
- return state;
735
549
  }
736
- function appendReceivedFragmentsOntoPage(state, pageKey, receivedFragments) {
737
- if (!pageKey) {
738
- return state;
739
- }
740
- if (receivedFragments.length === 0) {
741
- return state;
550
+ var MismatchedComponentError = class extends Error {
551
+ constructor(message) {
552
+ super(message);
553
+ this.name = "MismatchedComponentError";
742
554
  }
743
- const currentPage = state[pageKey];
744
- const { fragments: prevFragments = [] } = currentPage;
745
- const nextFragments = [...prevFragments];
746
- const existingKeys = {};
747
- prevFragments.forEach((frag) => existingKeys[frag.path] = true);
748
- receivedFragments.forEach((frag) => {
749
- if (!existingKeys[frag.path]) {
750
- nextFragments.push(frag);
751
- }
752
- });
753
- const nextPage = {
754
- ...currentPage,
755
- fragments: nextFragments
756
- };
757
- const nextState = { ...state };
758
- nextState[pageKey] = nextPage;
759
- return nextState;
760
- }
761
- function graftNodeOntoPage(state, pageKey, node, pathToNode) {
762
- if (!node) {
763
- console.warn(
764
- "There was no node returned in the response. Do you have the correct key path in your props_at?"
765
- );
766
- return state;
555
+ };
556
+ var remote = (path, {
557
+ pageKey: targetPageKey,
558
+ force = false,
559
+ beforeSave = (prevPage, receivedPage) => receivedPage,
560
+ ...rest
561
+ } = {}) => {
562
+ path = withoutBusters(path);
563
+ targetPageKey = targetPageKey && urlToPageKey(targetPageKey);
564
+ return (dispatch, getState) => {
565
+ const fetchArgs = argsForFetch(getState, path, rest);
566
+ const currentPageKey = getState().superglue.currentPageKey;
567
+ dispatch(beforeRemote({ currentPageKey, fetchArgs }));
568
+ dispatch(beforeFetch({ fetchArgs }));
569
+ return fetch(...fetchArgs).then(parseResponse).then(({ rsp, json }) => {
570
+ const { superglue, pages = {} } = getState();
571
+ let pageKey;
572
+ if (targetPageKey === void 0) {
573
+ const isGet = fetchArgs[1].method === "GET";
574
+ pageKey = calculatePageKey(rsp, isGet, currentPageKey);
575
+ } else {
576
+ pageKey = targetPageKey;
577
+ }
578
+ const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
579
+ const existingId = pages[pageKey]?.componentIdentifier;
580
+ const receivedId = json.componentIdentifier;
581
+ if (!!existingId && existingId != receivedId && !force) {
582
+ const message = `You cannot replace or update an existing page
583
+ located at pages["${currentPageKey}"] that has a componentIdentifier
584
+ of "${existingId}" with the contents of a page response that has a
585
+ componentIdentifier of "${receivedId}".
586
+
587
+ This can happen if you're using data-sg-remote or remote but your
588
+ response redirected to a page with a different componentIdentifier
589
+ than the target page.
590
+
591
+ This limitation exists because the resulting page shape from grafting
592
+ "${receivedId}"'s "${propsAtParam(path)}" into "${existingId}" may not be
593
+ compatible with the page component associated with "${existingId}".
594
+
595
+ Consider using data-sg-visit, the visit function, or redirect_back to
596
+ the same page. Or if you're sure you want to proceed, use force: true.
597
+ `;
598
+ throw new MismatchedComponentError(message);
599
+ }
600
+ const page = beforeSave(pages[pageKey], json);
601
+ return dispatch(saveAndProcessPage(pageKey, page)).then(() => meta);
602
+ }).catch((e) => handleFetchErr(e, fetchArgs, dispatch));
603
+ };
604
+ };
605
+ function calculatePageKey(rsp, isGet, currentPageKey) {
606
+ let pageKey = urlToPageKey(rsp.url);
607
+ if (!isGet && !rsp.redirected) {
608
+ pageKey = currentPageKey;
767
609
  }
768
- if (!pathToNode || !pageKey) {
769
- return state;
610
+ const contentLocation = rsp.headers.get("content-location");
611
+ if (contentLocation) {
612
+ pageKey = urlToPageKey(contentLocation);
770
613
  }
771
- const fullPathToNode = [pageKey, pathToNode].join(".");
772
- return setIn(state, fullPathToNode, node);
614
+ return pageKey;
773
615
  }
774
- function handleGraft2(state, pageKey, page) {
775
- const currentPage = state[pageKey];
776
- if (!currentPage) {
777
- const error = new Error(
778
- `Superglue was looking for ${pageKey} in your state, but could not find it in your mapping. Did you forget to pass in a valid pageKey to this.props.remote or this.props.visit?`
779
- );
780
- throw error;
781
- }
782
- const {
783
- data: receivedNode,
784
- path: pathToNode,
785
- fragments: receivedFragments = []
786
- } = page;
787
- return [
788
- (nextState) => graftNodeOntoPage(nextState, pageKey, receivedNode, pathToNode),
789
- (nextState) => appendReceivedFragmentsOntoPage(nextState, pageKey, receivedFragments)
790
- ].reduce((memo, fn) => fn(memo), state);
791
- }
792
- function pageReducer(state = {}, action) {
793
- switch (action.type) {
794
- case SAVE_RESPONSE: {
795
- const { pageKey, page } = action.payload;
796
- return saveResponse2(state, pageKey, page);
797
- }
798
- case HANDLE_GRAFT: {
799
- const { pageKey, page } = action.payload;
800
- return handleGraft2(state, pageKey, page);
801
- }
802
- case UPDATE_FRAGMENTS: {
803
- const { changedFragments } = action.payload;
804
- let nextState = state;
805
- Object.entries(state).forEach(([pageKey, page]) => {
806
- page.fragments.forEach((fragment) => {
807
- const { type, path } = fragment;
808
- const changedNode = changedFragments[type];
809
- const currentNode = getIn(nextState, `${pageKey}.${path}`);
810
- if (type in changedFragments && changedNode !== currentNode) {
811
- const nextNode = JSON.parse(JSON.stringify(changedNode));
812
- nextState = setIn(nextState, `${pageKey}.${path}`, nextNode);
616
+
617
+ // lib/action_creators/index.ts
618
+ function fetchDeferments(pageKey, defers = []) {
619
+ pageKey = urlToPageKey(pageKey);
620
+ return (dispatch) => {
621
+ const fetches = defers.filter(({ type }) => type === "auto").map(function({
622
+ url,
623
+ successAction = GRAFTING_SUCCESS,
624
+ failAction = GRAFTING_ERROR
625
+ }) {
626
+ const parsedUrl = new import_url_parse3.default(url, true);
627
+ const keyPath = parsedUrl.query.props_at;
628
+ return dispatch(remote(url, { pageKey })).then(() => {
629
+ dispatch({
630
+ type: successAction,
631
+ payload: {
632
+ pageKey,
633
+ keyPath
634
+ }
635
+ });
636
+ }).catch((err) => {
637
+ dispatch({
638
+ type: failAction,
639
+ payload: {
640
+ url,
641
+ err,
642
+ pageKey,
643
+ keyPath
813
644
  }
814
645
  });
815
646
  });
816
- return nextState;
817
- }
818
- case COPY_PAGE: {
819
- const nextState = { ...state };
820
- const { from, to } = action.payload;
821
- nextState[urlToPageKey(to)] = JSON.parse(JSON.stringify(nextState[from]));
822
- return nextState;
823
- }
824
- case REMOVE_PAGE: {
825
- const { pageKey } = action.payload;
826
- const nextState = { ...state };
827
- delete nextState[pageKey];
828
- return nextState;
829
- }
830
- default:
831
- return state;
832
- }
647
+ });
648
+ return Promise.all(fetches);
649
+ };
833
650
  }
834
- function superglueReducer(state = {}, action) {
835
- switch (action.type) {
836
- case HISTORY_CHANGE: {
837
- const { pathname, search, hash } = action.payload;
838
- const currentPageKey = urlToPageKey(pathname + search);
839
- return {
840
- ...state,
841
- currentPageKey,
842
- pathname,
843
- search,
844
- hash
845
- };
846
- }
847
- case SAVE_RESPONSE: {
848
- const {
849
- page: { csrfToken, assets }
850
- } = action.payload;
851
- return { ...state, csrfToken, assets };
651
+ function saveAndProcessPage(pageKey, page) {
652
+ return (dispatch, getState) => {
653
+ pageKey = urlToPageKey(pageKey);
654
+ const { defers = [] } = page;
655
+ if ("action" in page) {
656
+ const prevPage = getState().pages[pageKey];
657
+ dispatch(handleGraft({ pageKey, page }));
658
+ const currentPage = getState().pages[pageKey];
659
+ currentPage.fragments.forEach((fragment) => {
660
+ const { type, path } = fragment;
661
+ const currentFragment = getIn(currentPage, path);
662
+ const prevFragment = getIn(prevPage, path);
663
+ if (!prevFragment) {
664
+ dispatch(
665
+ updateFragments({
666
+ name: type,
667
+ pageKey,
668
+ value: currentFragment,
669
+ path
670
+ })
671
+ );
672
+ } else if (currentFragment !== prevFragment) {
673
+ dispatch(
674
+ updateFragments({
675
+ name: type,
676
+ pageKey,
677
+ value: currentFragment,
678
+ previousValue: prevFragment,
679
+ path
680
+ })
681
+ );
682
+ }
683
+ });
684
+ } else {
685
+ dispatch(saveResponse({ pageKey, page }));
686
+ const currentPage = getState().pages[pageKey];
687
+ currentPage.fragments.forEach((fragment) => {
688
+ const { type, path } = fragment;
689
+ const currentFragment = getIn(currentPage, path);
690
+ dispatch(
691
+ updateFragments({
692
+ name: type,
693
+ pageKey,
694
+ value: currentFragment,
695
+ path
696
+ })
697
+ );
698
+ });
852
699
  }
853
- case SET_CSRF_TOKEN: {
854
- const { csrfToken } = action.payload;
855
- return { ...state, csrfToken };
700
+ const hasFetch = typeof fetch != "undefined";
701
+ if (hasFetch) {
702
+ return dispatch(fetchDeferments(pageKey, defers)).then(
703
+ () => Promise.resolve()
704
+ );
705
+ } else {
706
+ return Promise.resolve();
856
707
  }
857
- default:
858
- return state;
859
- }
708
+ };
860
709
  }
861
- var rootReducer = {
862
- superglue: superglueReducer,
863
- pages: pageReducer
864
- };
865
710
 
866
711
  // lib/index.tsx
867
- var import_react_redux = require("react-redux");
712
+ var import_react_redux3 = require("react-redux");
868
713
  var import_history = require("history");
869
714
 
870
- // lib/components/Nav.tsx
715
+ // lib/components/Navigation.tsx
871
716
  var import_react = __toESM(require("react"));
872
- var Nav = class extends import_react.default.Component {
873
- /**
874
- * @ignore
875
- */
876
- constructor(props) {
877
- super(props);
878
- const { history, initialPageKey } = this.props;
879
- this.history = history;
880
- this.navigateTo = this.navigateTo.bind(this);
881
- this.scrollTo = this.scrollTo.bind(this);
882
- this.onHistoryChange = this.onHistoryChange.bind(this);
883
- this.state = {
884
- pageKey: initialPageKey,
885
- ownProps: {}
886
- };
887
- this.hasWindow = typeof window !== "undefined";
888
- }
889
- /**
890
- * @ignore
891
- */
892
- componentDidMount() {
893
- this.unsubscribeHistory = this.history.listen(this.onHistoryChange);
894
- }
895
- /**
896
- * @ignore
897
- */
898
- componentWillUnmount() {
899
- this.unsubscribeHistory();
717
+ var import_react_redux = require("react-redux");
718
+ var NavigationContext = (0, import_react.createContext)(
719
+ {}
720
+ );
721
+ var hasWindow = typeof window !== "undefined";
722
+ var setWindowScroll = (posX, posY) => {
723
+ hasWindow && window.scrollTo(posX, posY);
724
+ };
725
+ var notFound = (identifier) => {
726
+ let reminder = "";
727
+ if (!identifier) {
728
+ reminder = "Did you forget to add `json.componentIdentifier` in your application.json.props layout?";
900
729
  }
901
- /**
902
- * Passed to every page component. Manually navigate using pages that exists
903
- * in the store and restores scroll position. This is what {@link Visit} in
904
- * your `application_visit.js` ultimately calls.
905
- *
906
- * If there is an existing page in your store `navigateTo` will restore the props,
907
- * render the correct component, and return `true`. Otherwise, it will return
908
- * `false`. This is useful if you want to restore an existing page before making a
909
- * call to `visit` or `remote`.
910
- *
911
- * @param path
912
- * @param options when `none`, immediately returns `false`
913
- * @returns `true` if the navigation was a success, `false` if the page was not found in the
914
- * store.
915
- */
916
- navigateTo(path, {
917
- action,
918
- ownProps
919
- } = {
920
- action: "push",
921
- ownProps: {}
922
- }) {
730
+ const error = new Error(
731
+ `Superglue Nav component was looking for ${identifier} but could not find it in your mapping. ${reminder}`
732
+ );
733
+ throw error;
734
+ };
735
+ var NavigationProvider = (0, import_react.forwardRef)(function NavigationProvider2({ history, visit, remote: remote2, mapping }, ref) {
736
+ const dispatch = (0, import_react_redux.useDispatch)();
737
+ const pages = (0, import_react_redux.useSelector)((state) => state.pages);
738
+ const superglue = (0, import_react_redux.useSelector)(
739
+ (state) => state.superglue
740
+ );
741
+ const store = (0, import_react_redux.useStore)();
742
+ (0, import_react.useEffect)(() => {
743
+ return history.listen(onHistoryChange);
744
+ }, []);
745
+ (0, import_react.useImperativeHandle)(
746
+ ref,
747
+ () => {
748
+ return {
749
+ navigateTo
750
+ };
751
+ },
752
+ []
753
+ );
754
+ const visitAndRestore = (pageKey, posX, posY) => {
755
+ return visit(pageKey, { revisit: true }).then((meta) => {
756
+ if (meta) {
757
+ if (meta.navigationAction === "none") {
758
+ dispatch(setActivePage({ pageKey }));
759
+ setWindowScroll(posX, posY);
760
+ }
761
+ } else {
762
+ console.warn(
763
+ `scoll restoration was skipped. Your visit's then funtion
764
+ should return the meta object it recieved if you want your
765
+ application to restore the page's previous scroll.`
766
+ );
767
+ }
768
+ });
769
+ };
770
+ const onHistoryChange = ({ location, action }) => {
771
+ const state = location.state;
772
+ if (!state && location.hash !== "" && action === "POP") {
773
+ const nextPageKey = urlToPageKey(location.pathname + location.search);
774
+ const containsKey = !!pages[nextPageKey];
775
+ if (containsKey) {
776
+ history.replace(
777
+ {
778
+ pathname: location.pathname,
779
+ search: location.search,
780
+ hash: location.hash
781
+ },
782
+ {
783
+ pageKey: nextPageKey,
784
+ superglue: true,
785
+ posY: window.pageYOffset,
786
+ posX: window.pageXOffset
787
+ }
788
+ );
789
+ }
790
+ }
791
+ if (state && "superglue" in state) {
792
+ dispatch(
793
+ historyChange({
794
+ pageKey: state.pageKey
795
+ })
796
+ );
797
+ if (action !== "POP") {
798
+ return;
799
+ }
800
+ const { pageKey, posX, posY } = state;
801
+ const containsKey = !!pages[pageKey];
802
+ if (containsKey) {
803
+ const { restoreStrategy } = pages[pageKey];
804
+ switch (restoreStrategy) {
805
+ case "fromCacheOnly":
806
+ dispatch(setActivePage({ pageKey }));
807
+ setWindowScroll(posX, posY);
808
+ break;
809
+ case "fromCacheAndRevisitInBackground":
810
+ dispatch(setActivePage({ pageKey }));
811
+ setWindowScroll(posX, posY);
812
+ visit(pageKey, { revisit: true });
813
+ break;
814
+ case "revisitOnly":
815
+ default:
816
+ visitAndRestore(pageKey, posX, posY);
817
+ }
818
+ } else {
819
+ visitAndRestore(pageKey, posX, posY);
820
+ }
821
+ }
822
+ };
823
+ const navigateTo = (path, { action } = {
824
+ action: "push"
825
+ }) => {
923
826
  if (action === "none") {
924
827
  return false;
925
828
  }
926
829
  path = pathWithoutBZParams(path);
927
830
  const nextPageKey = urlToPageKey(path);
928
- const { store } = this.props;
929
- const hasPage = !!store.getState().pages[nextPageKey];
831
+ const hasPage = Object.prototype.hasOwnProperty.call(
832
+ store.getState().pages,
833
+ nextPageKey
834
+ );
930
835
  if (hasPage) {
931
- const location = this.history.location;
836
+ const location = history.location;
932
837
  const state = location.state;
933
838
  const prevPageKey = state.pageKey;
934
839
  const historyArgs = [
@@ -941,8 +846,8 @@ var Nav = class extends import_react.default.Component {
941
846
  }
942
847
  ];
943
848
  if (action === "push") {
944
- if (this.hasWindow) {
945
- this.history.replace(
849
+ if (hasWindow) {
850
+ history.replace(
946
851
  {
947
852
  pathname: location.pathname,
948
853
  search: location.search,
@@ -955,324 +860,319 @@ var Nav = class extends import_react.default.Component {
955
860
  }
956
861
  );
957
862
  }
958
- this.history.push(...historyArgs);
863
+ history.push(...historyArgs);
959
864
  }
960
865
  if (action === "replace") {
961
- this.history.replace(...historyArgs);
866
+ history.replace(...historyArgs);
962
867
  }
963
- this.setState({ pageKey: nextPageKey, ownProps });
964
- this.scrollTo(0, 0);
868
+ setActivePage({ pageKey: nextPageKey });
869
+ setWindowScroll(0, 0);
965
870
  if (action === "replace" && prevPageKey && prevPageKey !== nextPageKey) {
966
- store.dispatch({
967
- type: REMOVE_PAGE,
968
- payload: {
969
- pageKey: prevPageKey
970
- }
971
- });
871
+ dispatch(removePage({ pageKey: prevPageKey }));
972
872
  }
973
873
  return true;
974
874
  } else {
975
875
  console.warn(
976
- `\`navigateTo\` was called , but could not find.
876
+ `\`navigateTo\` was called , but could not find
977
877
  the pageKey in the store. This may happen when the wrong
978
878
  content_location was set in your non-get controller action.
979
879
  No navigation will take place`
980
880
  );
981
881
  return false;
982
882
  }
883
+ };
884
+ const { currentPageKey, search } = superglue;
885
+ const { componentIdentifier } = pages[currentPageKey];
886
+ const Component = mapping[componentIdentifier];
887
+ if (Component) {
888
+ return /* @__PURE__ */ import_react.default.createElement(
889
+ NavigationContext.Provider,
890
+ {
891
+ value: { pageKey: currentPageKey, search, navigateTo, visit, remote: remote2 }
892
+ },
893
+ /* @__PURE__ */ import_react.default.createElement(Component, null)
894
+ );
895
+ } else {
896
+ notFound(componentIdentifier);
983
897
  }
984
- /**
985
- * @ignore
986
- */
987
- scrollTo(posX, posY) {
988
- this.hasWindow && window.scrollTo(posX, posY);
989
- }
990
- /**
991
- * @ignore
992
- */
993
- onHistoryChange({ location, action }) {
994
- const { store, visit } = this.props;
995
- const { pathname, search, hash } = location;
996
- const state = location.state;
997
- if (state && "superglue" in state) {
998
- store.dispatch({
999
- type: HISTORY_CHANGE,
1000
- payload: { pathname, search, hash }
1001
- });
1002
- if (action !== "POP") {
1003
- return;
1004
- }
1005
- const { pageKey, posX, posY } = state;
1006
- const containsKey = !!store.getState().pages[pageKey];
1007
- if (containsKey) {
1008
- const { restoreStrategy } = store.getState().pages[pageKey];
1009
- switch (restoreStrategy) {
1010
- case "fromCacheOnly":
1011
- this.setState({ pageKey });
1012
- this.scrollTo(posX, posY);
1013
- break;
1014
- case "fromCacheAndRevisitInBackground":
1015
- this.setState({ pageKey });
1016
- this.scrollTo(posX, posY);
1017
- visit(pageKey, { revisit: true });
1018
- break;
1019
- case "revisitOnly":
1020
- default:
1021
- visit(pageKey, { revisit: true }).then((meta) => {
1022
- if (meta === void 0) {
1023
- console.warn(
1024
- `scoll restoration was skipped. Your visit's then funtion
1025
- should return the meta object it recieved if you want your
1026
- application to restore the page's previous scroll.`
1027
- );
1028
- }
1029
- if (!!meta && meta.suggestedAction === "none") {
1030
- this.setState({ pageKey });
1031
- this.scrollTo(posX, posY);
1032
- }
1033
- });
1034
- }
1035
- } else {
1036
- visit(pageKey, { revisit: true }).then((meta) => {
1037
- if (meta === void 0) {
1038
- console.warn(
1039
- `scoll restoration was skipped. Your visit's then funtion
1040
- should return the meta object it recieved if you want your
1041
- application to restore the page's previous scroll.`
1042
- );
1043
- }
1044
- if (!!meta && meta.suggestedAction === "none") {
1045
- this.setState({ pageKey });
1046
- this.scrollTo(posX, posY);
1047
- }
1048
- });
1049
- }
898
+ });
899
+
900
+ // lib/reducers/index.ts
901
+ function addPlaceholdersToDeferredNodes(existingPage, page) {
902
+ const { defers = [] } = existingPage;
903
+ const prevDefers = defers.map(({ path }) => {
904
+ const node = getIn(existingPage, path);
905
+ const copy = JSON.stringify(node);
906
+ return [path, JSON.parse(copy)];
907
+ });
908
+ return prevDefers.reduce((memo, [path, node]) => {
909
+ return setIn(page, path, node);
910
+ }, page);
911
+ }
912
+ function constrainPagesSize(state) {
913
+ const { maxPages } = config;
914
+ const allPageKeys = Object.keys(state);
915
+ const cacheTimesRecentFirst = allPageKeys.map((key) => state[key].savedAt).sort((a, b) => b - a);
916
+ for (const key of Array.from(allPageKeys)) {
917
+ if (state[key].savedAt <= cacheTimesRecentFirst[maxPages - 1]) {
918
+ delete state[key];
1050
919
  }
1051
920
  }
1052
- /**
1053
- * @ignore
1054
- */
1055
- notFound(identifier) {
1056
- let reminder = "";
1057
- if (!identifier) {
1058
- reminder = "Did you forget to add `json.componentIdentifier` in your application.json.props layout?";
921
+ }
922
+ function handleSaveResponse(state, pageKey, page) {
923
+ state = { ...state };
924
+ let nextPage = {
925
+ ...page,
926
+ savedAt: Date.now()
927
+ };
928
+ const existingPage = state[pageKey];
929
+ if (existingPage) {
930
+ nextPage = addPlaceholdersToDeferredNodes(existingPage, nextPage);
931
+ }
932
+ constrainPagesSize(state);
933
+ state[pageKey] = nextPage;
934
+ return state;
935
+ }
936
+ function appendReceivedFragmentsOntoPage(state, pageKey, receivedFragments) {
937
+ if (!pageKey) {
938
+ return state;
939
+ }
940
+ if (receivedFragments.length === 0) {
941
+ return state;
942
+ }
943
+ const currentPage = state[pageKey];
944
+ const { fragments: prevFragments = [] } = currentPage;
945
+ const nextFragments = [...prevFragments];
946
+ const existingKeys = {};
947
+ prevFragments.forEach((frag) => existingKeys[frag.path] = true);
948
+ receivedFragments.forEach((frag) => {
949
+ if (!existingKeys[frag.path]) {
950
+ nextFragments.push(frag);
1059
951
  }
952
+ });
953
+ const nextPage = {
954
+ ...currentPage,
955
+ fragments: nextFragments
956
+ };
957
+ const nextState = { ...state };
958
+ nextState[pageKey] = nextPage;
959
+ return nextState;
960
+ }
961
+ function graftNodeOntoPage(state, pageKey, node, pathToNode) {
962
+ if (!node) {
963
+ console.warn(
964
+ "There was no node returned in the response. Do you have the correct key path in your props_at?"
965
+ );
966
+ return state;
967
+ }
968
+ if (!pathToNode || !pageKey) {
969
+ return state;
970
+ }
971
+ const fullPathToNode = [pageKey, pathToNode].join(".");
972
+ return setIn(state, fullPathToNode, node);
973
+ }
974
+ function handleGraftResponse(state, pageKey, page) {
975
+ const currentPage = state[pageKey];
976
+ if (!currentPage) {
1060
977
  const error = new Error(
1061
- `Superglue Nav component was looking for ${identifier} but could not find it in your mapping. ${reminder}`
978
+ `Superglue was looking for ${pageKey} in your state, but could not find it in your mapping. Did you forget to pass in a valid pageKey to this.props.remote or this.props.visit?`
1062
979
  );
1063
980
  throw error;
1064
981
  }
1065
- /**
1066
- * @ignore
1067
- */
1068
- render() {
1069
- const { store, visit, remote: remote2 } = this.props;
1070
- const { pageKey, ownProps } = this.state;
1071
- const { componentIdentifier } = store.getState().pages[pageKey];
1072
- const Component = this.props.mapping[componentIdentifier];
1073
- if (Component) {
1074
- return /* @__PURE__ */ import_react.default.createElement(
1075
- Component,
1076
- {
1077
- pageKey,
1078
- navigateTo: this.navigateTo,
1079
- visit,
1080
- remote: remote2,
1081
- ...ownProps
1082
- }
1083
- );
1084
- } else {
1085
- this.notFound(componentIdentifier);
1086
- }
982
+ const {
983
+ data: receivedNode,
984
+ path: pathToNode,
985
+ fragments: receivedFragments = []
986
+ } = page;
987
+ return [
988
+ (nextState) => graftNodeOntoPage(nextState, pageKey, receivedNode, pathToNode),
989
+ (nextState) => appendReceivedFragmentsOntoPage(nextState, pageKey, receivedFragments)
990
+ ].reduce((memo, fn) => fn(memo), state);
991
+ }
992
+ function pageReducer(state = {}, action) {
993
+ if (removePage.match(action)) {
994
+ const { pageKey } = action.payload;
995
+ const nextState = { ...state };
996
+ delete nextState[pageKey];
997
+ return nextState;
1087
998
  }
1088
- };
1089
- var Nav_default = Nav;
1090
-
1091
- // lib/middleware.ts
1092
- var actionValues = Object.values(actions_exports).map((action) => action.toString());
1093
- var fragmentMiddleware = (store) => (next) => (action) => {
1094
- const prevState = store.getState();
1095
- const nextAction = next(action);
1096
- const nextState = store.getState();
1097
- if (!(action instanceof Object && "type" in action && typeof action.type === "string")) {
1098
- return nextAction;
999
+ if (copyPage.match(action)) {
1000
+ const nextState = { ...state };
1001
+ const { from, to } = action.payload;
1002
+ nextState[urlToPageKey(to)] = JSON.parse(JSON.stringify(nextState[from]));
1003
+ return nextState;
1099
1004
  }
1100
- const type = action.type;
1101
- if (actionValues.includes(type)) {
1102
- return nextAction;
1005
+ if (handleGraft.match(action)) {
1006
+ const { pageKey, page } = action.payload;
1007
+ return handleGraftResponse(state, pageKey, page);
1103
1008
  }
1104
- if (prevState.pages === nextState.pages) {
1105
- return nextAction;
1009
+ if (saveResponse.match(action)) {
1010
+ const { pageKey, page } = action.payload;
1011
+ const nextState = handleSaveResponse(state, pageKey, page);
1012
+ return nextState;
1106
1013
  }
1107
- const changedFragments = {};
1108
- const changedKeys = Object.keys(nextState.pages).filter((key) => {
1109
- return prevState.pages[key] !== nextState.pages[key];
1110
- });
1111
- if (changedKeys.length === 0) {
1112
- return nextAction;
1014
+ return state;
1015
+ }
1016
+ function superglueReducer(state = {
1017
+ currentPageKey: "",
1018
+ search: {},
1019
+ assets: []
1020
+ }, action) {
1021
+ if (setCSRFToken.match(action)) {
1022
+ const { csrfToken } = action.payload;
1023
+ return { ...state, csrfToken };
1113
1024
  }
1114
- changedKeys.forEach((key) => {
1115
- nextState.pages[key].fragments.forEach((fragment) => {
1116
- const { type: type2, path } = fragment;
1117
- const nextPage = nextState.pages[key];
1118
- const prevPage = prevState.pages[key];
1119
- let nextFragment, prevFragment;
1120
- try {
1121
- prevFragment = getIn(prevPage, path);
1122
- nextFragment = getIn(nextPage, path);
1123
- } catch (err) {
1124
- if (err instanceof KeyPathError) {
1125
- console.warn(err.message);
1126
- } else {
1127
- throw err;
1128
- }
1129
- }
1130
- if (nextFragment !== void 0 && prevFragment !== void 0 && nextFragment !== prevFragment && nextFragment) {
1131
- changedFragments[type2] = nextFragment;
1132
- }
1133
- });
1134
- });
1135
- if (Object.keys(changedFragments).length === 0) {
1136
- return nextAction;
1025
+ if (setActivePage.match(action)) {
1026
+ const { pageKey } = action.payload;
1027
+ const { search } = parsePageKey(pageKey);
1028
+ return {
1029
+ ...state,
1030
+ search,
1031
+ currentPageKey: pageKey
1032
+ };
1137
1033
  }
1138
- store.dispatch({
1139
- type: UPDATE_FRAGMENTS,
1140
- payload: {
1141
- changedFragments
1142
- }
1143
- });
1144
- return nextAction;
1034
+ if (historyChange.match(action)) {
1035
+ const { pageKey } = action.payload;
1036
+ const { search } = parsePageKey(pageKey);
1037
+ return {
1038
+ ...state,
1039
+ currentPageKey: pageKey,
1040
+ search
1041
+ };
1042
+ }
1043
+ if (saveResponse.match(action)) {
1044
+ const {
1045
+ page: { csrfToken, assets }
1046
+ } = action.payload;
1047
+ return { ...state, csrfToken, assets };
1048
+ }
1049
+ return state;
1050
+ }
1051
+ var rootReducer = {
1052
+ superglue: superglueReducer,
1053
+ pages: pageReducer
1145
1054
  };
1146
1055
 
1147
- // lib/index.tsx
1148
- function pageToInitialState(key, page) {
1149
- const slices = page.slices || {};
1150
- const nextPage = {
1151
- ...page,
1152
- pageKey: key,
1153
- //TODO remove this
1154
- savedAt: Date.now()
1155
- };
1156
- return {
1157
- pages: { [key]: nextPage },
1158
- ...slices
1159
- };
1056
+ // lib/hooks/index.ts
1057
+ var import_react_redux2 = require("react-redux");
1058
+ function useSuperglue() {
1059
+ return (0, import_react_redux2.useSelector)((state) => state.superglue);
1160
1060
  }
1161
- function start({
1162
- initialPage,
1163
- baseUrl = config.baseUrl,
1164
- maxPages = config.maxPages,
1165
- path
1166
- }) {
1167
- const initialPageKey = urlToPageKey((0, import_url_parse4.default)(path).href);
1168
- const { csrfToken } = initialPage;
1061
+ function useContent() {
1062
+ const superglueState = useSuperglue();
1063
+ const currentPageKey = superglueState.currentPageKey;
1064
+ return (0, import_react_redux2.useSelector)(
1065
+ (state) => state.pages[currentPageKey]
1066
+ ).data;
1067
+ }
1068
+
1069
+ // lib/index.tsx
1070
+ var hasWindow2 = typeof window !== "undefined";
1071
+ var createHistory = () => {
1072
+ if (hasWindow2) {
1073
+ return (0, import_history.createBrowserHistory)({});
1074
+ } else {
1075
+ return (0, import_history.createMemoryHistory)({});
1076
+ }
1077
+ };
1078
+ var prepareStore = (store, initialPage, path) => {
1169
1079
  const location = (0, import_url_parse4.default)(path);
1080
+ const initialPageKey = urlToPageKey(location.href);
1081
+ const { csrfToken } = initialPage;
1082
+ store.dispatch(
1083
+ historyChange({
1084
+ pageKey: initialPageKey
1085
+ })
1086
+ );
1087
+ store.dispatch(saveAndProcessPage(initialPageKey, initialPage));
1088
+ store.dispatch(setCSRFToken({ csrfToken }));
1089
+ };
1090
+ var setup = ({
1091
+ initialPage,
1092
+ baseUrl,
1093
+ path,
1094
+ store,
1095
+ buildVisitAndRemote,
1096
+ history,
1097
+ navigatorRef
1098
+ }) => {
1170
1099
  config.baseUrl = baseUrl;
1171
- config.maxPages = maxPages;
1100
+ const { visit, remote: remote2 } = buildVisitAndRemote(navigatorRef, store);
1101
+ const initialPageKey = urlToPageKey((0, import_url_parse4.default)(path).href);
1102
+ const nextHistory = history || createHistory();
1103
+ nextHistory.replace(...argsForHistory(path));
1104
+ prepareStore(store, initialPage, path);
1105
+ const handlers = ujsHandlers({
1106
+ visit,
1107
+ remote: remote2,
1108
+ ujsAttributePrefix: "data-sg",
1109
+ store
1110
+ });
1172
1111
  return {
1173
- reducer: rootReducer,
1174
- prepareStore: function(store) {
1175
- store.dispatch({
1176
- type: HISTORY_CHANGE,
1177
- payload: {
1178
- pathname: location.pathname,
1179
- search: location.query,
1180
- hash: location.hash
1181
- }
1182
- });
1183
- store.dispatch(saveAndProcessPage(initialPageKey, initialPage));
1184
- store.dispatch({ type: SET_CSRF_TOKEN, payload: { csrfToken } });
1185
- },
1186
- initialState: pageToInitialState(initialPageKey, initialPage),
1187
- initialPageKey
1112
+ visit,
1113
+ remote: remote2,
1114
+ nextHistory,
1115
+ initialPageKey,
1116
+ ujs: handlers
1188
1117
  };
1189
- }
1190
- var ApplicationBase = class extends import_react2.default.Component {
1191
- /**
1192
- * The constructor of the `ApplicationBase` class.
1193
- * @param props
1194
- */
1195
- constructor(props) {
1196
- super(props);
1197
- this.hasWindow = typeof window !== "undefined";
1198
- this.navigatorRef = import_react2.default.createRef();
1199
- const { prepareStore, initialState, initialPageKey, reducer } = start({
1200
- initialPage: this.props.initialPage,
1201
- baseUrl: this.props.baseUrl,
1202
- path: this.props.path
1203
- // The max number of pages to keep in the store. Default is 20
1204
- // maxPages: 20
1205
- });
1206
- this.initialPageKey = initialPageKey;
1207
- this.store = this.buildStore(initialState, reducer);
1208
- prepareStore(this.store);
1209
- this.history = this.createHistory();
1210
- this.history.replace(...argsForHistory(this.props.path));
1211
- const unconnectedMapping = this.mapping();
1212
- const nextMapping = {};
1213
- for (const key in unconnectedMapping) {
1214
- const component = unconnectedMapping[key];
1215
- nextMapping[key] = (0, import_react_redux.connect)(mapStateToProps, mapDispatchToProps)(component);
1216
- }
1217
- this.connectedMapping = nextMapping;
1218
- const { visit, remote: remote2 } = this.visitAndRemote(this.navigatorRef, this.store);
1219
- this.visit = visit;
1220
- this.remote = remote2;
1221
- }
1222
- componentDidMount() {
1223
- const { appEl } = this.props;
1224
- this.ujsHandlers = ujsHandlers({
1225
- visit: this.visit,
1226
- remote: this.remote,
1227
- ujsAttributePrefix: "data-sg"
1118
+ };
1119
+ function Application({
1120
+ initialPage,
1121
+ baseUrl,
1122
+ path,
1123
+ store,
1124
+ buildVisitAndRemote,
1125
+ history,
1126
+ mapping,
1127
+ ...rest
1128
+ }) {
1129
+ const navigatorRef = (0, import_react2.useRef)(null);
1130
+ const { visit, remote: remote2, nextHistory, initialPageKey, ujs } = (0, import_react2.useMemo)(() => {
1131
+ return setup({
1132
+ initialPage,
1133
+ baseUrl,
1134
+ path,
1135
+ store,
1136
+ buildVisitAndRemote,
1137
+ history,
1138
+ navigatorRef
1228
1139
  });
1229
- const { onClick, onSubmit } = this.ujsHandlers;
1230
- appEl.addEventListener("click", onClick);
1231
- appEl.addEventListener("submit", onSubmit);
1232
- }
1233
- componentWillUnmount() {
1234
- const { appEl } = this.props;
1235
- const { onClick, onSubmit } = this.ujsHandlers;
1236
- appEl.removeEventListener("click", onClick);
1237
- appEl.removeEventListener("submit", onSubmit);
1238
- }
1239
- createHistory() {
1240
- if (this.hasWindow) {
1241
- return (0, import_history.createBrowserHistory)({});
1242
- } else {
1243
- return (0, import_history.createMemoryHistory)({});
1140
+ }, []);
1141
+ return /* @__PURE__ */ import_react2.default.createElement("div", { onClick: ujs.onClick, onSubmit: ujs.onSubmit, ...rest }, /* @__PURE__ */ import_react2.default.createElement(import_react_redux3.Provider, { store }, /* @__PURE__ */ import_react2.default.createElement(
1142
+ NavigationProvider,
1143
+ {
1144
+ ref: navigatorRef,
1145
+ visit,
1146
+ remote: remote2,
1147
+ mapping,
1148
+ history: nextHistory,
1149
+ initialPageKey
1244
1150
  }
1245
- }
1246
- render() {
1247
- return /* @__PURE__ */ import_react2.default.createElement(import_react_redux.Provider, { store: this.store }, /* @__PURE__ */ import_react2.default.createElement(
1248
- Nav_default,
1249
- {
1250
- store: this.store,
1251
- ref: this.navigatorRef,
1252
- visit: this.visit,
1253
- remote: this.remote,
1254
- mapping: this.connectedMapping,
1255
- history: this.history,
1256
- initialPageKey: this.initialPageKey
1257
- }
1258
- ));
1259
- }
1260
- };
1151
+ )));
1152
+ }
1261
1153
  // Annotate the CommonJS export names for ESM import in node:
1262
1154
  0 && (module.exports = {
1263
- ApplicationBase,
1264
- BEFORE_FETCH,
1265
- BEFORE_REMOTE,
1266
- BEFORE_VISIT,
1267
- COPY_PAGE,
1155
+ Application,
1268
1156
  GRAFTING_ERROR,
1269
1157
  GRAFTING_SUCCESS,
1270
- HISTORY_CHANGE,
1271
- REMOVE_PAGE,
1272
- SAVE_RESPONSE,
1273
- UPDATE_FRAGMENTS,
1274
- fragmentMiddleware,
1158
+ NavigationContext,
1159
+ NavigationProvider,
1160
+ beforeFetch,
1161
+ beforeRemote,
1162
+ beforeVisit,
1163
+ copyPage,
1275
1164
  getIn,
1276
- urlToPageKey
1165
+ pageReducer,
1166
+ prepareStore,
1167
+ removePage,
1168
+ rootReducer,
1169
+ saveAndProcessPage,
1170
+ saveResponse,
1171
+ setup,
1172
+ superglueReducer,
1173
+ updateFragments,
1174
+ urlToPageKey,
1175
+ useContent,
1176
+ useSuperglue
1277
1177
  });
1278
1178
  //# sourceMappingURL=superglue.cjs.map