@sesionario/blocks 1.40.0 → 1.40.1-rc.1

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.
@@ -25,8 +25,59 @@ var NAV_PROGRESS_CSS = `
25
25
  }
26
26
  }
27
27
  `;
28
+ var startListeners = /* @__PURE__ */ new Set();
29
+ var navigationSuppressed = false;
30
+ var suppressTimer = 0;
31
+ function normalizeSearch(search) {
32
+ const params = new URLSearchParams(search);
33
+ const entries = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));
34
+ const sorted = new URLSearchParams();
35
+ for (const [key, value] of entries) {
36
+ sorted.append(key, value);
37
+ }
38
+ return sorted.toString();
39
+ }
28
40
  function routeKey(pathname, search) {
29
- return `${pathname}?${search}`;
41
+ return `${pathname}?${normalizeSearch(search)}`;
42
+ }
43
+ function routeKeyFromHref(href) {
44
+ try {
45
+ const url = new URL(href, window.location.origin);
46
+ return routeKey(url.pathname, url.search.replace(/^\?/, ""));
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function isShown(element) {
52
+ if (!(element instanceof HTMLElement)) return false;
53
+ if (element.hidden) return false;
54
+ const display = element.style.display || getComputedStyle(element).display;
55
+ if (display === "none") return false;
56
+ const visibility = element.style.visibility || getComputedStyle(element).visibility;
57
+ if (visibility === "hidden") return false;
58
+ if (display === "contents") {
59
+ return Array.from(element.children).some((child) => isShown(child));
60
+ }
61
+ return true;
62
+ }
63
+ function routeLoadingIsActive() {
64
+ for (const node of document.querySelectorAll(ROUTE_LOADING_SELECTOR)) {
65
+ if (isShown(node)) return true;
66
+ }
67
+ return false;
68
+ }
69
+ function suppressNavigationProgress() {
70
+ navigationSuppressed = true;
71
+ window.clearTimeout(suppressTimer);
72
+ suppressTimer = window.setTimeout(() => {
73
+ navigationSuppressed = false;
74
+ }, 0);
75
+ }
76
+ function signalNavigationStart(href) {
77
+ if (navigationSuppressed) return;
78
+ for (const listener of startListeners) {
79
+ listener(href);
80
+ }
30
81
  }
31
82
  function isInternalNavigationClick(event) {
32
83
  if (event.button !== 0) return false;
@@ -61,6 +112,7 @@ function NavigationProgress({
61
112
  forceVisible = false
62
113
  }) {
63
114
  const [visible, setVisible] = react.useState(forceVisible);
115
+ const [runId, setRunId] = react.useState(0);
64
116
  const startedAt = react.useRef(0);
65
117
  const startedRoute = react.useRef(null);
66
118
  const committedRoute = react.useRef(null);
@@ -70,38 +122,60 @@ function NavigationProgress({
70
122
  react.useEffect(() => {
71
123
  if (forceVisible) return;
72
124
  function begin(route) {
125
+ if (navigationSuppressed) return;
73
126
  startedAt.current = Date.now();
74
127
  startedRoute.current = route;
128
+ setRunId((id) => id + 1);
75
129
  setVisible(true);
76
130
  }
77
131
  function onClick(event) {
78
132
  if (!isInternalNavigationClick(event)) return;
79
- begin(
80
- routeKey(
81
- window.location.pathname,
82
- window.location.search.replace(/^\?/, "")
83
- )
84
- );
133
+ begin(committedRoute.current ?? routeKey(pathname, search));
85
134
  }
86
135
  function onPopState() {
136
+ const landed = routeKey(
137
+ window.location.pathname,
138
+ window.location.search.replace(/^\?/, "")
139
+ );
140
+ if (landed === (committedRoute.current ?? "")) return;
87
141
  begin(committedRoute.current ?? "");
88
142
  }
143
+ function onStart(href) {
144
+ if (href) {
145
+ const target = routeKeyFromHref(href);
146
+ if (target !== null && target === committedRoute.current) return;
147
+ }
148
+ begin(committedRoute.current ?? routeKey(pathname, search));
149
+ }
150
+ startListeners.add(onStart);
89
151
  document.addEventListener("click", onClick, true);
90
152
  window.addEventListener("popstate", onPopState);
91
153
  return () => {
154
+ startListeners.delete(onStart);
92
155
  document.removeEventListener("click", onClick, true);
93
156
  window.removeEventListener("popstate", onPopState);
94
157
  };
95
- }, [forceVisible]);
158
+ }, [forceVisible, pathname, search]);
159
+ react.useEffect(() => {
160
+ if (forceVisible || !visible) return;
161
+ const safety = window.setTimeout(() => setVisible(false), SAFETY_MS);
162
+ return () => window.clearTimeout(safety);
163
+ }, [forceVisible, runId, visible]);
96
164
  react.useEffect(() => {
97
165
  if (forceVisible || !visible) return;
98
166
  const started = startedRoute.current;
99
- if (!started || routeKey(pathname, search) === started) return;
167
+ if (!started) return;
100
168
  let hideTimer = 0;
101
169
  let stopped = false;
170
+ let sawLoading = false;
102
171
  function hide() {
103
172
  if (stopped) return;
104
- if (document.querySelector(ROUTE_LOADING_SELECTOR)) return;
173
+ if (routeLoadingIsActive()) {
174
+ sawLoading = true;
175
+ return;
176
+ }
177
+ const routeChanged = routeKey(pathname, search) !== started;
178
+ if (!routeChanged && !sawLoading) return;
105
179
  stopped = true;
106
180
  const wait = Math.max(
107
181
  0,
@@ -111,12 +185,15 @@ function NavigationProgress({
111
185
  }
112
186
  hide();
113
187
  const observer = new MutationObserver(hide);
114
- observer.observe(document.body, { childList: true, subtree: true });
115
- const safety = window.setTimeout(() => setVisible(false), SAFETY_MS);
188
+ observer.observe(document.body, {
189
+ childList: true,
190
+ subtree: true,
191
+ attributes: true,
192
+ attributeFilter: ["style", "hidden", "class"]
193
+ });
116
194
  return () => {
117
195
  stopped = true;
118
196
  window.clearTimeout(hideTimer);
119
- window.clearTimeout(safety);
120
197
  observer.disconnect();
121
198
  };
122
199
  }, [forceVisible, pathname, search, visible]);
@@ -136,5 +213,7 @@ function NavigationProgress({
136
213
  }
137
214
 
138
215
  exports.NavigationProgress = NavigationProgress;
216
+ exports.signalNavigationStart = signalNavigationStart;
217
+ exports.suppressNavigationProgress = suppressNavigationProgress;
139
218
  //# sourceMappingURL=navigation-progress.cjs.map
140
219
  //# sourceMappingURL=navigation-progress.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/NavigationProgress.tsx"],"names":["useState","useRef","useEffect","jsxs","Fragment","jsx"],"mappings":"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","file":"navigation-progress.cjs","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nconst MIN_VISIBLE_MS = 200;\nconst SAFETY_MS = 10_000;\n\nexport const ROUTE_LOADING_SELECTOR = \"[data-route-loading]\";\n\n/** Indeterminate bar motion. Inlined so apps do not copy keyframes into their CSS. */\nexport const NAV_PROGRESS_CSS = `\n@keyframes nav-progress-indeterminate {\n\t0% { transform: translateX(-100%); }\n\t100% { transform: translateX(400%); }\n}\n.nav-progress-indeterminate {\n\twidth: 33%;\n\theight: 100%;\n\tanimation: nav-progress-indeterminate 1.1s ease-in-out infinite;\n}\n@media (prefers-reduced-motion: reduce) {\n\t.nav-progress-indeterminate {\n\t\tanimation: none;\n\t\twidth: 100%;\n\t\ttransform: none;\n\t}\n}\n`;\n\nfunction routeKey(pathname: string, search: string): string {\n\treturn `${pathname}?${search}`;\n}\n\nexport function isInternalNavigationClick(event: MouseEvent): boolean {\n\tif (event.button !== 0) return false;\n\tif (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\treturn false;\n\t}\n\tconst target = event.target;\n\tif (!(target instanceof Element)) return false;\n\tconst anchor = target.closest(\"a\");\n\tif (!(anchor instanceof HTMLAnchorElement)) return false;\n\tif (anchor.target && anchor.target !== \"_self\") return false;\n\tif (anchor.hasAttribute(\"download\")) return false;\n\tconst raw = anchor.getAttribute(\"href\");\n\tif (\n\t\t!raw ||\n\t\traw.startsWith(\"#\") ||\n\t\traw.startsWith(\"mailto:\") ||\n\t\traw.startsWith(\"tel:\")\n\t) {\n\t\treturn false;\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(anchor.href, window.location.href);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.origin !== window.location.origin) return false;\n\tif (\n\t\turl.pathname === window.location.pathname &&\n\t\turl.search === window.location.search\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport type NavigationProgressProps = {\n\t/** App-supplied pathname. Blocks does not import `next/navigation`. */\n\tpathname: string;\n\t/** Query string without the leading `?`. */\n\tsearch?: string;\n\t/** Storybook / tests. Skips click listeners and stays visible. */\n\tforceVisible?: boolean;\n};\n\n/**\n * Indeterminate bar fixed to the top of the viewport. Apps pass the current\n * route from their router and wrap `useSearchParams` in `<Suspense>`.\n * The bar starts on a same-origin link click or back/forward, and stays up\n * while a `[data-route-loading]` node (see `RouteLoading`) is mounted.\n */\nexport function NavigationProgress({\n\tpathname,\n\tsearch = \"\",\n\tforceVisible = false,\n}: NavigationProgressProps) {\n\tconst [visible, setVisible] = useState(forceVisible);\n\tconst startedAt = useRef(0);\n\tconst startedRoute = useRef<string | null>(null);\n\tconst committedRoute = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tcommittedRoute.current = routeKey(pathname, search);\n\t}, [pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible) return;\n\t\tfunction begin(route: string) {\n\t\t\tstartedAt.current = Date.now();\n\t\t\tstartedRoute.current = route;\n\t\t\tsetVisible(true);\n\t\t}\n\t\tfunction onClick(event: MouseEvent) {\n\t\t\tif (!isInternalNavigationClick(event)) return;\n\t\t\t// Capture runs before Next's <Link> handler, which may push history\n\t\t\t// before a bubble listener would see the click.\n\t\t\tbegin(\n\t\t\t\trouteKey(\n\t\t\t\t\twindow.location.pathname,\n\t\t\t\t\twindow.location.search.replace(/^\\?/, \"\"),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tfunction onPopState() {\n\t\t\t// The URL has already changed. Keep the route React last committed\n\t\t\t// so the bar can end once the destination commits.\n\t\t\tbegin(committedRoute.current ?? \"\");\n\t\t}\n\t\tdocument.addEventListener(\"click\", onClick, true);\n\t\twindow.addEventListener(\"popstate\", onPopState);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"click\", onClick, true);\n\t\t\twindow.removeEventListener(\"popstate\", onPopState);\n\t\t};\n\t}, [forceVisible]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst started = startedRoute.current;\n\t\tif (!started || routeKey(pathname, search) === started) return;\n\n\t\tlet hideTimer = 0;\n\t\tlet stopped = false;\n\t\tfunction hide() {\n\t\t\tif (stopped) return;\n\t\t\tif (document.querySelector(ROUTE_LOADING_SELECTOR)) return;\n\t\t\tstopped = true;\n\t\t\tconst wait = Math.max(\n\t\t\t\t0,\n\t\t\t\tMIN_VISIBLE_MS - (Date.now() - startedAt.current),\n\t\t\t);\n\t\t\thideTimer = window.setTimeout(() => setVisible(false), wait);\n\t\t}\n\t\thide();\n\t\tconst observer = new MutationObserver(hide);\n\t\tobserver.observe(document.body, { childList: true, subtree: true });\n\t\tconst safety = window.setTimeout(() => setVisible(false), SAFETY_MS);\n\t\treturn () => {\n\t\t\tstopped = true;\n\t\t\twindow.clearTimeout(hideTimer);\n\t\t\twindow.clearTimeout(safety);\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [forceVisible, pathname, search, visible]);\n\n\tif (!visible) return null;\n\n\treturn (\n\t\t<>\n\t\t\t<style dangerouslySetInnerHTML={{ __html: NAV_PROGRESS_CSS }} />\n\t\t\t<div\n\t\t\t\trole=\"progressbar\"\n\t\t\t\taria-label=\"Cargando\"\n\t\t\t\tclassName=\"pointer-events-none fixed inset-x-0 top-0 z-[60] h-0.5 overflow-hidden bg-primary/20\"\n\t\t\t>\n\t\t\t\t<div className=\"nav-progress-indeterminate bg-primary\" />\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"]}
1
+ {"version":3,"sources":["../src/components/NavigationProgress.tsx"],"names":["useState","useRef","useEffect","jsxs","Fragment","jsx"],"mappings":"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","file":"navigation-progress.cjs","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nconst MIN_VISIBLE_MS = 200;\nconst SAFETY_MS = 10_000;\n\nexport const ROUTE_LOADING_SELECTOR = \"[data-route-loading]\";\n\n/** Indeterminate bar motion. Inlined so apps do not copy keyframes into their CSS. */\nexport const NAV_PROGRESS_CSS = `\n@keyframes nav-progress-indeterminate {\n\t0% { transform: translateX(-100%); }\n\t100% { transform: translateX(400%); }\n}\n.nav-progress-indeterminate {\n\twidth: 33%;\n\theight: 100%;\n\tanimation: nav-progress-indeterminate 1.1s ease-in-out infinite;\n}\n@media (prefers-reduced-motion: reduce) {\n\t.nav-progress-indeterminate {\n\t\tanimation: none;\n\t\twidth: 100%;\n\t\ttransform: none;\n\t}\n}\n`;\n\ntype StartListener = (href: string | undefined) => void;\n\nconst startListeners = new Set<StartListener>();\nlet navigationSuppressed = false;\nlet suppressTimer = 0;\n\nfunction normalizeSearch(search: string): string {\n\tconst params = new URLSearchParams(search);\n\tconst entries = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));\n\tconst sorted = new URLSearchParams();\n\tfor (const [key, value] of entries) {\n\t\tsorted.append(key, value);\n\t}\n\treturn sorted.toString();\n}\n\nfunction routeKey(pathname: string, search: string): string {\n\treturn `${pathname}?${normalizeSearch(search)}`;\n}\n\nfunction routeKeyFromHref(href: string): string | null {\n\ttry {\n\t\tconst url = new URL(href, window.location.origin);\n\t\treturn routeKey(url.pathname, url.search.replace(/^\\?/, \"\"));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isShown(element: Element): boolean {\n\tif (!(element instanceof HTMLElement)) return false;\n\tif (element.hidden) return false;\n\tconst display = element.style.display || getComputedStyle(element).display;\n\tif (display === \"none\") return false;\n\tconst visibility =\n\t\telement.style.visibility || getComputedStyle(element).visibility;\n\tif (visibility === \"hidden\") return false;\n\tif (display === \"contents\") {\n\t\treturn Array.from(element.children).some((child) => isShown(child));\n\t}\n\treturn true;\n}\n\n/** A route skeleton counts only while it is actually on screen. */\nexport function routeLoadingIsActive(): boolean {\n\tfor (const node of document.querySelectorAll(ROUTE_LOADING_SELECTOR)) {\n\t\tif (isShown(node)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Ignore navigation-start signals for the rest of this turn. Used by the\n * fullscreen post viewer, whose permalink `pushState` / `history.back()` is\n * not a page load. An in-flight bar is left alone.\n */\nexport function suppressNavigationProgress(): void {\n\tnavigationSuppressed = true;\n\twindow.clearTimeout(suppressTimer);\n\tsuppressTimer = window.setTimeout(() => {\n\t\tnavigationSuppressed = false;\n\t}, 0);\n}\n\n/**\n * Start the top bar for a navigation that did not come from a link click.\n * Records the route React has committed, not `window.location` (already the\n * destination on back/forward). No-ops when `href` is that same route, or\n * when {@link suppressNavigationProgress} is set.\n */\nexport function signalNavigationStart(href?: string): void {\n\tif (navigationSuppressed) return;\n\tfor (const listener of startListeners) {\n\t\tlistener(href);\n\t}\n}\n\nexport function isInternalNavigationClick(event: MouseEvent): boolean {\n\tif (event.button !== 0) return false;\n\tif (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\treturn false;\n\t}\n\tconst target = event.target;\n\tif (!(target instanceof Element)) return false;\n\tconst anchor = target.closest(\"a\");\n\tif (!(anchor instanceof HTMLAnchorElement)) return false;\n\tif (anchor.target && anchor.target !== \"_self\") return false;\n\tif (anchor.hasAttribute(\"download\")) return false;\n\tconst raw = anchor.getAttribute(\"href\");\n\tif (\n\t\t!raw ||\n\t\traw.startsWith(\"#\") ||\n\t\traw.startsWith(\"mailto:\") ||\n\t\traw.startsWith(\"tel:\")\n\t) {\n\t\treturn false;\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(anchor.href, window.location.href);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.origin !== window.location.origin) return false;\n\tif (\n\t\turl.pathname === window.location.pathname &&\n\t\turl.search === window.location.search\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport type NavigationProgressProps = {\n\t/** App-supplied pathname. Blocks does not import `next/navigation`. */\n\tpathname: string;\n\t/** Query string without the leading `?`. */\n\tsearch?: string;\n\t/** Storybook / tests. Skips click listeners and stays visible. */\n\tforceVisible?: boolean;\n};\n\n/**\n * Indeterminate bar fixed to the top of the viewport. Apps pass the current\n * route from their router and wrap `useSearchParams` in `<Suspense>`.\n * The bar starts on a same-origin link click, back/forward, or\n * {@link signalNavigationStart}, and stays up while a visible\n * `[data-route-loading]` node (see `RouteLoading`) is on screen.\n */\nexport function NavigationProgress({\n\tpathname,\n\tsearch = \"\",\n\tforceVisible = false,\n}: NavigationProgressProps) {\n\tconst [visible, setVisible] = useState(forceVisible);\n\tconst [runId, setRunId] = useState(0);\n\tconst startedAt = useRef(0);\n\tconst startedRoute = useRef<string | null>(null);\n\tconst committedRoute = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tcommittedRoute.current = routeKey(pathname, search);\n\t}, [pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible) return;\n\t\tfunction begin(route: string) {\n\t\t\tif (navigationSuppressed) return;\n\t\t\tstartedAt.current = Date.now();\n\t\t\tstartedRoute.current = route;\n\t\t\tsetRunId((id) => id + 1);\n\t\t\tsetVisible(true);\n\t\t}\n\t\tfunction onClick(event: MouseEvent) {\n\t\t\tif (!isInternalNavigationClick(event)) return;\n\t\t\t// Capture runs before Next's <Link> handler, which may push history\n\t\t\t// before a bubble listener would see the click.\n\t\t\tbegin(committedRoute.current ?? routeKey(pathname, search));\n\t\t}\n\t\tfunction onPopState() {\n\t\t\t// The URL has already changed. Skip when it still matches the route\n\t\t\t// React committed (post-viewer history.back). Otherwise keep that\n\t\t\t// committed route so the bar can end once the destination commits.\n\t\t\tconst landed = routeKey(\n\t\t\t\twindow.location.pathname,\n\t\t\t\twindow.location.search.replace(/^\\?/, \"\"),\n\t\t\t);\n\t\t\tif (landed === (committedRoute.current ?? \"\")) return;\n\t\t\tbegin(committedRoute.current ?? \"\");\n\t\t}\n\t\tfunction onStart(href: string | undefined) {\n\t\t\tif (href) {\n\t\t\t\tconst target = routeKeyFromHref(href);\n\t\t\t\tif (target !== null && target === committedRoute.current) return;\n\t\t\t}\n\t\t\tbegin(committedRoute.current ?? routeKey(pathname, search));\n\t\t}\n\t\tstartListeners.add(onStart);\n\t\tdocument.addEventListener(\"click\", onClick, true);\n\t\twindow.addEventListener(\"popstate\", onPopState);\n\t\treturn () => {\n\t\t\tstartListeners.delete(onStart);\n\t\t\tdocument.removeEventListener(\"click\", onClick, true);\n\t\t\twindow.removeEventListener(\"popstate\", onPopState);\n\t\t};\n\t}, [forceVisible, pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst safety = window.setTimeout(() => setVisible(false), SAFETY_MS);\n\t\treturn () => window.clearTimeout(safety);\n\t}, [forceVisible, runId, visible]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst started = startedRoute.current;\n\t\tif (!started) return;\n\n\t\tlet hideTimer = 0;\n\t\tlet stopped = false;\n\t\tlet sawLoading = false;\n\t\tfunction hide() {\n\t\t\tif (stopped) return;\n\t\t\tif (routeLoadingIsActive()) {\n\t\t\t\tsawLoading = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst routeChanged = routeKey(pathname, search) !== started;\n\t\t\tif (!routeChanged && !sawLoading) return;\n\t\t\tstopped = true;\n\t\t\tconst wait = Math.max(\n\t\t\t\t0,\n\t\t\t\tMIN_VISIBLE_MS - (Date.now() - startedAt.current),\n\t\t\t);\n\t\t\thideTimer = window.setTimeout(() => setVisible(false), wait);\n\t\t}\n\t\thide();\n\t\tconst observer = new MutationObserver(hide);\n\t\tobserver.observe(document.body, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"hidden\", \"class\"],\n\t\t});\n\t\treturn () => {\n\t\t\tstopped = true;\n\t\t\twindow.clearTimeout(hideTimer);\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [forceVisible, pathname, search, visible]);\n\n\tif (!visible) return null;\n\n\treturn (\n\t\t<>\n\t\t\t<style dangerouslySetInnerHTML={{ __html: NAV_PROGRESS_CSS }} />\n\t\t\t<div\n\t\t\t\trole=\"progressbar\"\n\t\t\t\taria-label=\"Cargando\"\n\t\t\t\tclassName=\"pointer-events-none fixed inset-x-0 top-0 z-[60] h-0.5 overflow-hidden bg-primary/20\"\n\t\t\t>\n\t\t\t\t<div className=\"nav-progress-indeterminate bg-primary\" />\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"]}
@@ -1,5 +1,18 @@
1
1
  import * as React from 'react';
2
2
 
3
+ /**
4
+ * Ignore navigation-start signals for the rest of this turn. Used by the
5
+ * fullscreen post viewer, whose permalink `pushState` / `history.back()` is
6
+ * not a page load. An in-flight bar is left alone.
7
+ */
8
+ declare function suppressNavigationProgress(): void;
9
+ /**
10
+ * Start the top bar for a navigation that did not come from a link click.
11
+ * Records the route React has committed, not `window.location` (already the
12
+ * destination on back/forward). No-ops when `href` is that same route, or
13
+ * when {@link suppressNavigationProgress} is set.
14
+ */
15
+ declare function signalNavigationStart(href?: string): void;
3
16
  type NavigationProgressProps = {
4
17
  /** App-supplied pathname. Blocks does not import `next/navigation`. */
5
18
  pathname: string;
@@ -11,9 +24,10 @@ type NavigationProgressProps = {
11
24
  /**
12
25
  * Indeterminate bar fixed to the top of the viewport. Apps pass the current
13
26
  * route from their router and wrap `useSearchParams` in `<Suspense>`.
14
- * The bar starts on a same-origin link click or back/forward, and stays up
15
- * while a `[data-route-loading]` node (see `RouteLoading`) is mounted.
27
+ * The bar starts on a same-origin link click, back/forward, or
28
+ * {@link signalNavigationStart}, and stays up while a visible
29
+ * `[data-route-loading]` node (see `RouteLoading`) is on screen.
16
30
  */
17
31
  declare function NavigationProgress({ pathname, search, forceVisible, }: NavigationProgressProps): React.JSX.Element | null;
18
32
 
19
- export { NavigationProgress, type NavigationProgressProps };
33
+ export { NavigationProgress, type NavigationProgressProps, signalNavigationStart, suppressNavigationProgress };
@@ -1,5 +1,18 @@
1
1
  import * as React from 'react';
2
2
 
3
+ /**
4
+ * Ignore navigation-start signals for the rest of this turn. Used by the
5
+ * fullscreen post viewer, whose permalink `pushState` / `history.back()` is
6
+ * not a page load. An in-flight bar is left alone.
7
+ */
8
+ declare function suppressNavigationProgress(): void;
9
+ /**
10
+ * Start the top bar for a navigation that did not come from a link click.
11
+ * Records the route React has committed, not `window.location` (already the
12
+ * destination on back/forward). No-ops when `href` is that same route, or
13
+ * when {@link suppressNavigationProgress} is set.
14
+ */
15
+ declare function signalNavigationStart(href?: string): void;
3
16
  type NavigationProgressProps = {
4
17
  /** App-supplied pathname. Blocks does not import `next/navigation`. */
5
18
  pathname: string;
@@ -11,9 +24,10 @@ type NavigationProgressProps = {
11
24
  /**
12
25
  * Indeterminate bar fixed to the top of the viewport. Apps pass the current
13
26
  * route from their router and wrap `useSearchParams` in `<Suspense>`.
14
- * The bar starts on a same-origin link click or back/forward, and stays up
15
- * while a `[data-route-loading]` node (see `RouteLoading`) is mounted.
27
+ * The bar starts on a same-origin link click, back/forward, or
28
+ * {@link signalNavigationStart}, and stays up while a visible
29
+ * `[data-route-loading]` node (see `RouteLoading`) is on screen.
16
30
  */
17
31
  declare function NavigationProgress({ pathname, search, forceVisible, }: NavigationProgressProps): React.JSX.Element | null;
18
32
 
19
- export { NavigationProgress, type NavigationProgressProps };
33
+ export { NavigationProgress, type NavigationProgressProps, signalNavigationStart, suppressNavigationProgress };
@@ -23,8 +23,59 @@ var NAV_PROGRESS_CSS = `
23
23
  }
24
24
  }
25
25
  `;
26
+ var startListeners = /* @__PURE__ */ new Set();
27
+ var navigationSuppressed = false;
28
+ var suppressTimer = 0;
29
+ function normalizeSearch(search) {
30
+ const params = new URLSearchParams(search);
31
+ const entries = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));
32
+ const sorted = new URLSearchParams();
33
+ for (const [key, value] of entries) {
34
+ sorted.append(key, value);
35
+ }
36
+ return sorted.toString();
37
+ }
26
38
  function routeKey(pathname, search) {
27
- return `${pathname}?${search}`;
39
+ return `${pathname}?${normalizeSearch(search)}`;
40
+ }
41
+ function routeKeyFromHref(href) {
42
+ try {
43
+ const url = new URL(href, window.location.origin);
44
+ return routeKey(url.pathname, url.search.replace(/^\?/, ""));
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function isShown(element) {
50
+ if (!(element instanceof HTMLElement)) return false;
51
+ if (element.hidden) return false;
52
+ const display = element.style.display || getComputedStyle(element).display;
53
+ if (display === "none") return false;
54
+ const visibility = element.style.visibility || getComputedStyle(element).visibility;
55
+ if (visibility === "hidden") return false;
56
+ if (display === "contents") {
57
+ return Array.from(element.children).some((child) => isShown(child));
58
+ }
59
+ return true;
60
+ }
61
+ function routeLoadingIsActive() {
62
+ for (const node of document.querySelectorAll(ROUTE_LOADING_SELECTOR)) {
63
+ if (isShown(node)) return true;
64
+ }
65
+ return false;
66
+ }
67
+ function suppressNavigationProgress() {
68
+ navigationSuppressed = true;
69
+ window.clearTimeout(suppressTimer);
70
+ suppressTimer = window.setTimeout(() => {
71
+ navigationSuppressed = false;
72
+ }, 0);
73
+ }
74
+ function signalNavigationStart(href) {
75
+ if (navigationSuppressed) return;
76
+ for (const listener of startListeners) {
77
+ listener(href);
78
+ }
28
79
  }
29
80
  function isInternalNavigationClick(event) {
30
81
  if (event.button !== 0) return false;
@@ -59,6 +110,7 @@ function NavigationProgress({
59
110
  forceVisible = false
60
111
  }) {
61
112
  const [visible, setVisible] = useState(forceVisible);
113
+ const [runId, setRunId] = useState(0);
62
114
  const startedAt = useRef(0);
63
115
  const startedRoute = useRef(null);
64
116
  const committedRoute = useRef(null);
@@ -68,38 +120,60 @@ function NavigationProgress({
68
120
  useEffect(() => {
69
121
  if (forceVisible) return;
70
122
  function begin(route) {
123
+ if (navigationSuppressed) return;
71
124
  startedAt.current = Date.now();
72
125
  startedRoute.current = route;
126
+ setRunId((id) => id + 1);
73
127
  setVisible(true);
74
128
  }
75
129
  function onClick(event) {
76
130
  if (!isInternalNavigationClick(event)) return;
77
- begin(
78
- routeKey(
79
- window.location.pathname,
80
- window.location.search.replace(/^\?/, "")
81
- )
82
- );
131
+ begin(committedRoute.current ?? routeKey(pathname, search));
83
132
  }
84
133
  function onPopState() {
134
+ const landed = routeKey(
135
+ window.location.pathname,
136
+ window.location.search.replace(/^\?/, "")
137
+ );
138
+ if (landed === (committedRoute.current ?? "")) return;
85
139
  begin(committedRoute.current ?? "");
86
140
  }
141
+ function onStart(href) {
142
+ if (href) {
143
+ const target = routeKeyFromHref(href);
144
+ if (target !== null && target === committedRoute.current) return;
145
+ }
146
+ begin(committedRoute.current ?? routeKey(pathname, search));
147
+ }
148
+ startListeners.add(onStart);
87
149
  document.addEventListener("click", onClick, true);
88
150
  window.addEventListener("popstate", onPopState);
89
151
  return () => {
152
+ startListeners.delete(onStart);
90
153
  document.removeEventListener("click", onClick, true);
91
154
  window.removeEventListener("popstate", onPopState);
92
155
  };
93
- }, [forceVisible]);
156
+ }, [forceVisible, pathname, search]);
157
+ useEffect(() => {
158
+ if (forceVisible || !visible) return;
159
+ const safety = window.setTimeout(() => setVisible(false), SAFETY_MS);
160
+ return () => window.clearTimeout(safety);
161
+ }, [forceVisible, runId, visible]);
94
162
  useEffect(() => {
95
163
  if (forceVisible || !visible) return;
96
164
  const started = startedRoute.current;
97
- if (!started || routeKey(pathname, search) === started) return;
165
+ if (!started) return;
98
166
  let hideTimer = 0;
99
167
  let stopped = false;
168
+ let sawLoading = false;
100
169
  function hide() {
101
170
  if (stopped) return;
102
- if (document.querySelector(ROUTE_LOADING_SELECTOR)) return;
171
+ if (routeLoadingIsActive()) {
172
+ sawLoading = true;
173
+ return;
174
+ }
175
+ const routeChanged = routeKey(pathname, search) !== started;
176
+ if (!routeChanged && !sawLoading) return;
103
177
  stopped = true;
104
178
  const wait = Math.max(
105
179
  0,
@@ -109,12 +183,15 @@ function NavigationProgress({
109
183
  }
110
184
  hide();
111
185
  const observer = new MutationObserver(hide);
112
- observer.observe(document.body, { childList: true, subtree: true });
113
- const safety = window.setTimeout(() => setVisible(false), SAFETY_MS);
186
+ observer.observe(document.body, {
187
+ childList: true,
188
+ subtree: true,
189
+ attributes: true,
190
+ attributeFilter: ["style", "hidden", "class"]
191
+ });
114
192
  return () => {
115
193
  stopped = true;
116
194
  window.clearTimeout(hideTimer);
117
- window.clearTimeout(safety);
118
195
  observer.disconnect();
119
196
  };
120
197
  }, [forceVisible, pathname, search, visible]);
@@ -133,6 +210,6 @@ function NavigationProgress({
133
210
  ] });
134
211
  }
135
212
 
136
- export { NavigationProgress };
213
+ export { NavigationProgress, signalNavigationStart, suppressNavigationProgress };
137
214
  //# sourceMappingURL=navigation-progress.js.map
138
215
  //# sourceMappingURL=navigation-progress.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/NavigationProgress.tsx"],"names":[],"mappings":"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","file":"navigation-progress.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nconst MIN_VISIBLE_MS = 200;\nconst SAFETY_MS = 10_000;\n\nexport const ROUTE_LOADING_SELECTOR = \"[data-route-loading]\";\n\n/** Indeterminate bar motion. Inlined so apps do not copy keyframes into their CSS. */\nexport const NAV_PROGRESS_CSS = `\n@keyframes nav-progress-indeterminate {\n\t0% { transform: translateX(-100%); }\n\t100% { transform: translateX(400%); }\n}\n.nav-progress-indeterminate {\n\twidth: 33%;\n\theight: 100%;\n\tanimation: nav-progress-indeterminate 1.1s ease-in-out infinite;\n}\n@media (prefers-reduced-motion: reduce) {\n\t.nav-progress-indeterminate {\n\t\tanimation: none;\n\t\twidth: 100%;\n\t\ttransform: none;\n\t}\n}\n`;\n\nfunction routeKey(pathname: string, search: string): string {\n\treturn `${pathname}?${search}`;\n}\n\nexport function isInternalNavigationClick(event: MouseEvent): boolean {\n\tif (event.button !== 0) return false;\n\tif (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\treturn false;\n\t}\n\tconst target = event.target;\n\tif (!(target instanceof Element)) return false;\n\tconst anchor = target.closest(\"a\");\n\tif (!(anchor instanceof HTMLAnchorElement)) return false;\n\tif (anchor.target && anchor.target !== \"_self\") return false;\n\tif (anchor.hasAttribute(\"download\")) return false;\n\tconst raw = anchor.getAttribute(\"href\");\n\tif (\n\t\t!raw ||\n\t\traw.startsWith(\"#\") ||\n\t\traw.startsWith(\"mailto:\") ||\n\t\traw.startsWith(\"tel:\")\n\t) {\n\t\treturn false;\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(anchor.href, window.location.href);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.origin !== window.location.origin) return false;\n\tif (\n\t\turl.pathname === window.location.pathname &&\n\t\turl.search === window.location.search\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport type NavigationProgressProps = {\n\t/** App-supplied pathname. Blocks does not import `next/navigation`. */\n\tpathname: string;\n\t/** Query string without the leading `?`. */\n\tsearch?: string;\n\t/** Storybook / tests. Skips click listeners and stays visible. */\n\tforceVisible?: boolean;\n};\n\n/**\n * Indeterminate bar fixed to the top of the viewport. Apps pass the current\n * route from their router and wrap `useSearchParams` in `<Suspense>`.\n * The bar starts on a same-origin link click or back/forward, and stays up\n * while a `[data-route-loading]` node (see `RouteLoading`) is mounted.\n */\nexport function NavigationProgress({\n\tpathname,\n\tsearch = \"\",\n\tforceVisible = false,\n}: NavigationProgressProps) {\n\tconst [visible, setVisible] = useState(forceVisible);\n\tconst startedAt = useRef(0);\n\tconst startedRoute = useRef<string | null>(null);\n\tconst committedRoute = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tcommittedRoute.current = routeKey(pathname, search);\n\t}, [pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible) return;\n\t\tfunction begin(route: string) {\n\t\t\tstartedAt.current = Date.now();\n\t\t\tstartedRoute.current = route;\n\t\t\tsetVisible(true);\n\t\t}\n\t\tfunction onClick(event: MouseEvent) {\n\t\t\tif (!isInternalNavigationClick(event)) return;\n\t\t\t// Capture runs before Next's <Link> handler, which may push history\n\t\t\t// before a bubble listener would see the click.\n\t\t\tbegin(\n\t\t\t\trouteKey(\n\t\t\t\t\twindow.location.pathname,\n\t\t\t\t\twindow.location.search.replace(/^\\?/, \"\"),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tfunction onPopState() {\n\t\t\t// The URL has already changed. Keep the route React last committed\n\t\t\t// so the bar can end once the destination commits.\n\t\t\tbegin(committedRoute.current ?? \"\");\n\t\t}\n\t\tdocument.addEventListener(\"click\", onClick, true);\n\t\twindow.addEventListener(\"popstate\", onPopState);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"click\", onClick, true);\n\t\t\twindow.removeEventListener(\"popstate\", onPopState);\n\t\t};\n\t}, [forceVisible]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst started = startedRoute.current;\n\t\tif (!started || routeKey(pathname, search) === started) return;\n\n\t\tlet hideTimer = 0;\n\t\tlet stopped = false;\n\t\tfunction hide() {\n\t\t\tif (stopped) return;\n\t\t\tif (document.querySelector(ROUTE_LOADING_SELECTOR)) return;\n\t\t\tstopped = true;\n\t\t\tconst wait = Math.max(\n\t\t\t\t0,\n\t\t\t\tMIN_VISIBLE_MS - (Date.now() - startedAt.current),\n\t\t\t);\n\t\t\thideTimer = window.setTimeout(() => setVisible(false), wait);\n\t\t}\n\t\thide();\n\t\tconst observer = new MutationObserver(hide);\n\t\tobserver.observe(document.body, { childList: true, subtree: true });\n\t\tconst safety = window.setTimeout(() => setVisible(false), SAFETY_MS);\n\t\treturn () => {\n\t\t\tstopped = true;\n\t\t\twindow.clearTimeout(hideTimer);\n\t\t\twindow.clearTimeout(safety);\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [forceVisible, pathname, search, visible]);\n\n\tif (!visible) return null;\n\n\treturn (\n\t\t<>\n\t\t\t<style dangerouslySetInnerHTML={{ __html: NAV_PROGRESS_CSS }} />\n\t\t\t<div\n\t\t\t\trole=\"progressbar\"\n\t\t\t\taria-label=\"Cargando\"\n\t\t\t\tclassName=\"pointer-events-none fixed inset-x-0 top-0 z-[60] h-0.5 overflow-hidden bg-primary/20\"\n\t\t\t>\n\t\t\t\t<div className=\"nav-progress-indeterminate bg-primary\" />\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"]}
1
+ {"version":3,"sources":["../src/components/NavigationProgress.tsx"],"names":[],"mappings":"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","file":"navigation-progress.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\nconst MIN_VISIBLE_MS = 200;\nconst SAFETY_MS = 10_000;\n\nexport const ROUTE_LOADING_SELECTOR = \"[data-route-loading]\";\n\n/** Indeterminate bar motion. Inlined so apps do not copy keyframes into their CSS. */\nexport const NAV_PROGRESS_CSS = `\n@keyframes nav-progress-indeterminate {\n\t0% { transform: translateX(-100%); }\n\t100% { transform: translateX(400%); }\n}\n.nav-progress-indeterminate {\n\twidth: 33%;\n\theight: 100%;\n\tanimation: nav-progress-indeterminate 1.1s ease-in-out infinite;\n}\n@media (prefers-reduced-motion: reduce) {\n\t.nav-progress-indeterminate {\n\t\tanimation: none;\n\t\twidth: 100%;\n\t\ttransform: none;\n\t}\n}\n`;\n\ntype StartListener = (href: string | undefined) => void;\n\nconst startListeners = new Set<StartListener>();\nlet navigationSuppressed = false;\nlet suppressTimer = 0;\n\nfunction normalizeSearch(search: string): string {\n\tconst params = new URLSearchParams(search);\n\tconst entries = [...params.entries()].sort(([a], [b]) => a.localeCompare(b));\n\tconst sorted = new URLSearchParams();\n\tfor (const [key, value] of entries) {\n\t\tsorted.append(key, value);\n\t}\n\treturn sorted.toString();\n}\n\nfunction routeKey(pathname: string, search: string): string {\n\treturn `${pathname}?${normalizeSearch(search)}`;\n}\n\nfunction routeKeyFromHref(href: string): string | null {\n\ttry {\n\t\tconst url = new URL(href, window.location.origin);\n\t\treturn routeKey(url.pathname, url.search.replace(/^\\?/, \"\"));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isShown(element: Element): boolean {\n\tif (!(element instanceof HTMLElement)) return false;\n\tif (element.hidden) return false;\n\tconst display = element.style.display || getComputedStyle(element).display;\n\tif (display === \"none\") return false;\n\tconst visibility =\n\t\telement.style.visibility || getComputedStyle(element).visibility;\n\tif (visibility === \"hidden\") return false;\n\tif (display === \"contents\") {\n\t\treturn Array.from(element.children).some((child) => isShown(child));\n\t}\n\treturn true;\n}\n\n/** A route skeleton counts only while it is actually on screen. */\nexport function routeLoadingIsActive(): boolean {\n\tfor (const node of document.querySelectorAll(ROUTE_LOADING_SELECTOR)) {\n\t\tif (isShown(node)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Ignore navigation-start signals for the rest of this turn. Used by the\n * fullscreen post viewer, whose permalink `pushState` / `history.back()` is\n * not a page load. An in-flight bar is left alone.\n */\nexport function suppressNavigationProgress(): void {\n\tnavigationSuppressed = true;\n\twindow.clearTimeout(suppressTimer);\n\tsuppressTimer = window.setTimeout(() => {\n\t\tnavigationSuppressed = false;\n\t}, 0);\n}\n\n/**\n * Start the top bar for a navigation that did not come from a link click.\n * Records the route React has committed, not `window.location` (already the\n * destination on back/forward). No-ops when `href` is that same route, or\n * when {@link suppressNavigationProgress} is set.\n */\nexport function signalNavigationStart(href?: string): void {\n\tif (navigationSuppressed) return;\n\tfor (const listener of startListeners) {\n\t\tlistener(href);\n\t}\n}\n\nexport function isInternalNavigationClick(event: MouseEvent): boolean {\n\tif (event.button !== 0) return false;\n\tif (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n\t\treturn false;\n\t}\n\tconst target = event.target;\n\tif (!(target instanceof Element)) return false;\n\tconst anchor = target.closest(\"a\");\n\tif (!(anchor instanceof HTMLAnchorElement)) return false;\n\tif (anchor.target && anchor.target !== \"_self\") return false;\n\tif (anchor.hasAttribute(\"download\")) return false;\n\tconst raw = anchor.getAttribute(\"href\");\n\tif (\n\t\t!raw ||\n\t\traw.startsWith(\"#\") ||\n\t\traw.startsWith(\"mailto:\") ||\n\t\traw.startsWith(\"tel:\")\n\t) {\n\t\treturn false;\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(anchor.href, window.location.href);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.origin !== window.location.origin) return false;\n\tif (\n\t\turl.pathname === window.location.pathname &&\n\t\turl.search === window.location.search\n\t) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nexport type NavigationProgressProps = {\n\t/** App-supplied pathname. Blocks does not import `next/navigation`. */\n\tpathname: string;\n\t/** Query string without the leading `?`. */\n\tsearch?: string;\n\t/** Storybook / tests. Skips click listeners and stays visible. */\n\tforceVisible?: boolean;\n};\n\n/**\n * Indeterminate bar fixed to the top of the viewport. Apps pass the current\n * route from their router and wrap `useSearchParams` in `<Suspense>`.\n * The bar starts on a same-origin link click, back/forward, or\n * {@link signalNavigationStart}, and stays up while a visible\n * `[data-route-loading]` node (see `RouteLoading`) is on screen.\n */\nexport function NavigationProgress({\n\tpathname,\n\tsearch = \"\",\n\tforceVisible = false,\n}: NavigationProgressProps) {\n\tconst [visible, setVisible] = useState(forceVisible);\n\tconst [runId, setRunId] = useState(0);\n\tconst startedAt = useRef(0);\n\tconst startedRoute = useRef<string | null>(null);\n\tconst committedRoute = useRef<string | null>(null);\n\n\tuseEffect(() => {\n\t\tcommittedRoute.current = routeKey(pathname, search);\n\t}, [pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible) return;\n\t\tfunction begin(route: string) {\n\t\t\tif (navigationSuppressed) return;\n\t\t\tstartedAt.current = Date.now();\n\t\t\tstartedRoute.current = route;\n\t\t\tsetRunId((id) => id + 1);\n\t\t\tsetVisible(true);\n\t\t}\n\t\tfunction onClick(event: MouseEvent) {\n\t\t\tif (!isInternalNavigationClick(event)) return;\n\t\t\t// Capture runs before Next's <Link> handler, which may push history\n\t\t\t// before a bubble listener would see the click.\n\t\t\tbegin(committedRoute.current ?? routeKey(pathname, search));\n\t\t}\n\t\tfunction onPopState() {\n\t\t\t// The URL has already changed. Skip when it still matches the route\n\t\t\t// React committed (post-viewer history.back). Otherwise keep that\n\t\t\t// committed route so the bar can end once the destination commits.\n\t\t\tconst landed = routeKey(\n\t\t\t\twindow.location.pathname,\n\t\t\t\twindow.location.search.replace(/^\\?/, \"\"),\n\t\t\t);\n\t\t\tif (landed === (committedRoute.current ?? \"\")) return;\n\t\t\tbegin(committedRoute.current ?? \"\");\n\t\t}\n\t\tfunction onStart(href: string | undefined) {\n\t\t\tif (href) {\n\t\t\t\tconst target = routeKeyFromHref(href);\n\t\t\t\tif (target !== null && target === committedRoute.current) return;\n\t\t\t}\n\t\t\tbegin(committedRoute.current ?? routeKey(pathname, search));\n\t\t}\n\t\tstartListeners.add(onStart);\n\t\tdocument.addEventListener(\"click\", onClick, true);\n\t\twindow.addEventListener(\"popstate\", onPopState);\n\t\treturn () => {\n\t\t\tstartListeners.delete(onStart);\n\t\t\tdocument.removeEventListener(\"click\", onClick, true);\n\t\t\twindow.removeEventListener(\"popstate\", onPopState);\n\t\t};\n\t}, [forceVisible, pathname, search]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst safety = window.setTimeout(() => setVisible(false), SAFETY_MS);\n\t\treturn () => window.clearTimeout(safety);\n\t}, [forceVisible, runId, visible]);\n\n\tuseEffect(() => {\n\t\tif (forceVisible || !visible) return;\n\t\tconst started = startedRoute.current;\n\t\tif (!started) return;\n\n\t\tlet hideTimer = 0;\n\t\tlet stopped = false;\n\t\tlet sawLoading = false;\n\t\tfunction hide() {\n\t\t\tif (stopped) return;\n\t\t\tif (routeLoadingIsActive()) {\n\t\t\t\tsawLoading = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst routeChanged = routeKey(pathname, search) !== started;\n\t\t\tif (!routeChanged && !sawLoading) return;\n\t\t\tstopped = true;\n\t\t\tconst wait = Math.max(\n\t\t\t\t0,\n\t\t\t\tMIN_VISIBLE_MS - (Date.now() - startedAt.current),\n\t\t\t);\n\t\t\thideTimer = window.setTimeout(() => setVisible(false), wait);\n\t\t}\n\t\thide();\n\t\tconst observer = new MutationObserver(hide);\n\t\tobserver.observe(document.body, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"hidden\", \"class\"],\n\t\t});\n\t\treturn () => {\n\t\t\tstopped = true;\n\t\t\twindow.clearTimeout(hideTimer);\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [forceVisible, pathname, search, visible]);\n\n\tif (!visible) return null;\n\n\treturn (\n\t\t<>\n\t\t\t<style dangerouslySetInnerHTML={{ __html: NAV_PROGRESS_CSS }} />\n\t\t\t<div\n\t\t\t\trole=\"progressbar\"\n\t\t\t\taria-label=\"Cargando\"\n\t\t\t\tclassName=\"pointer-events-none fixed inset-x-0 top-0 z-[60] h-0.5 overflow-hidden bg-primary/20\"\n\t\t\t>\n\t\t\t\t<div className=\"nav-progress-indeterminate bg-primary\" />\n\t\t\t</div>\n\t\t</>\n\t);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sesionario/blocks",
3
- "version": "1.40.0",
3
+ "version": "1.40.1-rc.1",
4
4
  "description": "Shared UI components for Sesionario applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",