Skip to content

Commit

Permalink
feat(qwik-city): usePreventNavigate()
Browse files Browse the repository at this point in the history
  • Loading branch information
wmertens committed Sep 5, 2024
1 parent adc715c commit 6f0c2cb
Show file tree
Hide file tree
Showing 17 changed files with 473 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-olives-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/qwik-city': minor
---

`usePreventNavigate` lets you prevent navigation while your app's state is unsaved. It works asynchronously for SPA navigation and falls back to the browser's default dialogs for other navigations.
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ The base pathname of the request, which can be configured at build time. Default
Convenience method to set the Cache-Control header. Depending on your CDN, you may want to add another cacheControl with the second argument set to `CDN-Cache-Control` or any other value (we provide the most common values for auto-complete, but you can use any string you want).
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control and https://qwik.dev/docs/caching/#CDN-Cache-Controls for more information.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control and https://qwik.dev/docs/caching/\#CDN-Cache-Controls for more information.
</td></tr>
<tr><td>
Expand Down
30 changes: 29 additions & 1 deletion packages/docs/src/routes/api/qwik-city/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts",
"mdFile": "qwik-city.pathparams.md"
},
{
"name": "PreventNavigateCallback",
"id": "preventnavigatecallback",
"hierarchy": [
{
"name": "PreventNavigateCallback",
"id": "preventnavigatecallback"
}
],
"kind": "TypeAlias",
"content": "```typescript\nexport type PreventNavigateCallback = (url?: number | URL) => ValueOrPromise<boolean>;\n```",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts",
"mdFile": "qwik-city.preventnavigatecallback.md"
},
{
"name": "QWIK_CITY_SCROLLER",
"id": "qwik_city_scroller",
Expand Down Expand Up @@ -642,7 +656,7 @@
}
],
"kind": "TypeAlias",
"content": "```typescript\nexport type RouteNavigate = QRL<(path?: string | number, options?: {\n type?: Exclude<NavigationType, 'initial'>;\n forceReload?: boolean;\n replaceState?: boolean;\n scroll?: boolean;\n} | boolean) => Promise<void>>;\n```\n**References:** [NavigationType](#navigationtype)",
"content": "```typescript\nexport type RouteNavigate = QRL<(path?: string | number | URL, options?: {\n type?: Exclude<NavigationType, 'initial'>;\n forceReload?: boolean;\n replaceState?: boolean;\n scroll?: boolean;\n} | boolean) => Promise<void>>;\n```\n**References:** [NavigationType](#navigationtype)",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts",
"mdFile": "qwik-city.routenavigate.md"
},
Expand Down Expand Up @@ -842,6 +856,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts",
"mdFile": "qwik-city.usenavigate.md"
},
{
"name": "usePreventNavigate$",
"id": "usepreventnavigate_",
"hierarchy": [
{
"name": "usePreventNavigate$",
"id": "usepreventnavigate_"
}
],
"kind": "Function",
"content": "Prevent navigation attempts. This hook registers a callback that will be called before SPA or browser navigation.\n\nReturn `true` to prevent navigation.\n\n\\#\\#\\#\\# SPA Navigation\n\nFor Single-Page-App (SPA) navigation (via `<Link />`<!-- -->, `const nav = useNavigate()`<!-- -->, and browser backwards/forwards inside SPA history), the callback will be provided with the target, either a URL or a number. It will only be a number if `nav(number)` was called to navigate forwards or backwards in SPA history.\n\nIf you return a Promise, the navigation will be blocked until the promise resolves.\n\nThis can be used to show a nice dialog to the user, and wait for the user to confirm, or to record the url, prevent the navigation, and navigate there later via `nav(url)`<!-- -->.\n\n\\#\\#\\#\\# Browser Navigation\n\nHowever, when the user navigates away by clicking on a regular `<a />`<!-- -->, reloading, or moving backwards/forwards outside SPA history, this callback will not be awaited. This is because the browser does not provide a way to asynchronously prevent these navigations.\n\nIn this case, returning returning `true` will tell the browser to show a confirmation dialog, which cannot be customized. You are also not able to show your own `window.confirm()` dialog during the callback, the browser won't allow it. If you return a Promise, it will be considered as `true`<!-- -->.\n\nWhen the callback is called from the browser, no url will be provided. Use this to know whether you can show a dialog or just return `true` to prevent the navigation.\n\n\n```typescript\nusePreventNavigate$: (qrl: PreventNavigateCallback) => void\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n[PreventNavigateCallback](#preventnavigatecallback)\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n**Returns:**\n\nvoid",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts",
"mdFile": "qwik-city.usepreventnavigate_.md"
},
{
"name": "validator$",
"id": "validator_",
Expand Down
69 changes: 68 additions & 1 deletion packages/docs/src/routes/api/qwik-city/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,16 @@ export declare type PathParams = Record<string, string>;
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts)
## PreventNavigateCallback
```typescript
export type PreventNavigateCallback = (
url?: number | URL,
) => ValueOrPromise<boolean>;
```
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts)
## QWIK_CITY_SCROLLER
```typescript
Expand Down Expand Up @@ -2100,7 +2110,7 @@ URL
```typescript
export type RouteNavigate = QRL<
(
path?: string | number,
path?: string | number | URL,
options?:
| {
type?: Exclude<NavigationType, "initial">;
Expand Down Expand Up @@ -2387,6 +2397,63 @@ useNavigate: () => RouteNavigate;
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts)
## usePreventNavigate$
Prevent navigation attempts. This hook registers a callback that will be called before SPA or browser navigation.
Return `true` to prevent navigation.
\#### SPA Navigation
For Single-Page-App (SPA) navigation (via `<Link />`, `const nav = useNavigate()`, and browser backwards/forwards inside SPA history), the callback will be provided with the target, either a URL or a number. It will only be a number if `nav(number)` was called to navigate forwards or backwards in SPA history.
If you return a Promise, the navigation will be blocked until the promise resolves.
This can be used to show a nice dialog to the user, and wait for the user to confirm, or to record the url, prevent the navigation, and navigate there later via `nav(url)`.
\#### Browser Navigation
However, when the user navigates away by clicking on a regular `<a />`, reloading, or moving backwards/forwards outside SPA history, this callback will not be awaited. This is because the browser does not provide a way to asynchronously prevent these navigations.
In this case, returning returning `true` will tell the browser to show a confirmation dialog, which cannot be customized. You are also not able to show your own `window.confirm()` dialog during the callback, the browser won't allow it. If you return a Promise, it will be considered as `true`.
When the callback is called from the browser, no url will be provided. Use this to know whether you can show a dialog or just return `true` to prevent the navigation.
```typescript
usePreventNavigate$: (qrl: PreventNavigateCallback) => void
```
<table><thead><tr><th>
Parameter
</th><th>
Type
</th><th>
Description
</th></tr></thead>
<tbody><tr><td>
qrl
</td><td>
[PreventNavigateCallback](#preventnavigatecallback)
</td><td>
</td></tr>
</tbody></table>
**Returns:**
void
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/use-functions.ts)
## validator$
```typescript
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik-city/src/buildtime/build-layout.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assert, testAppSuite } from '../utils/test-suite';
const test = testAppSuite('Build Layout');

test('total layouts', ({ ctx: { layouts } }) => {
assert.equal(layouts.length, 10, JSON.stringify(layouts, null, 2));
assert.equal(layouts.length, 11, JSON.stringify(layouts, null, 2));
});

test('nested named layout', ({ assertLayout }) => {
Expand Down
13 changes: 12 additions & 1 deletion packages/qwik-city/src/runtime/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@ export interface PageModule extends RouteModule {
// @public (undocumented)
export type PathParams = Record<string, string>;

// @public (undocumented)
export type PreventNavigateCallback = (url?: number | URL) => ValueOrPromise<boolean>;

// @public (undocumented)
export const QWIK_CITY_SCROLLER = "_qCityScroller";

Expand Down Expand Up @@ -402,7 +405,7 @@ export interface RouteLocation {
}

// @public (undocumented)
export type RouteNavigate = QRL<(path?: string | number, options?: {
export type RouteNavigate = QRL<(path?: string | number | URL, options?: {
type?: Exclude<NavigationType, 'initial'>;
forceReload?: boolean;
replaceState?: boolean;
Expand Down Expand Up @@ -471,6 +474,14 @@ export const useLocation: () => RouteLocation;
// @public (undocumented)
export const useNavigate: () => RouteNavigate;

// @public
export const usePreventNavigate$: (qrl: PreventNavigateCallback) => void;

// Warning: (ae-internal-missing-underscore) The name "usePreventNavigateQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal
export const usePreventNavigateQrl: (fn: QRL<PreventNavigateCallback>) => void;

// Warning: (ae-forgotten-export) The symbol "ValidatorConstructor" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
Expand Down
4 changes: 4 additions & 0 deletions packages/qwik-city/src/runtime/src/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
RouteAction,
RouteLocation,
RouteNavigate,
RoutePreventNavigate,
RouteStateInternal,
} from './types';

Expand All @@ -25,3 +26,6 @@ export const RouteActionContext = /*#__PURE__*/ createContextId<RouteAction>('qc

export const RouteInternalContext =
/*#__PURE__*/ createContextId<Signal<RouteStateInternal>>('qc-ir');

export const RoutePreventNavigateContext =
/*#__PURE__*/ createContextId<RoutePreventNavigate>('qc-p');
63 changes: 31 additions & 32 deletions packages/qwik-city/src/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,57 @@
export type { FormSubmitCompletedDetail as FormSubmitSuccessDetail } from './form-component';

export type {
MenuData,
Action,
ActionConstructor,
ActionReturn,
ActionStore,
ContentHeading,
ContentMenu,
Cookie,
CookieOptions,
CookieValue,
DataValidator,
DeferReturn,
DocumentHead,
DocumentHeadProps,
DocumentHeadValue,
DocumentLink,
DocumentMeta,
DocumentStyle,
DocumentScript,
DocumentStyle,
FailOfRest,
FailReturn,
GetValidatorType,
JSONObject,
JSONValue,
Loader,
LoaderSignal,
MenuData,
NavigationType,
PageModule,
PathParams,
RequestHandler,
PreventNavigateCallback,
QwikCityPlan,
RequestEvent,
RequestEventLoader,
RequestEventAction,
RequestEventBase,
RequestEventCommon,
QwikCityPlan,
RequestEventLoader,
RequestHandler,
ResolvedDocumentHead,
RouteData,
RouteLocation,
StaticGenerateHandler,
Action,
Loader,
ActionStore,
LoaderSignal,
ActionConstructor,
FailReturn,
ZodConstructor,
StaticGenerate,
RouteNavigate,
NavigationType,
DeferReturn,
RequestEventBase,
JSONObject,
JSONValue,
ValidatorErrorType,
ServerFunction,
ServerQRL,
StaticGenerate,
StaticGenerateHandler,
StrictUnion,
TypedDataValidator,
ValidatorErrorKeyDotNotation,
ValidatorErrorType,
ValidatorReturn,
ZodConstructor,
} from './types';

export { RouterOutlet } from './router-outlet-component';
Expand All @@ -55,6 +65,7 @@ export {
export { type LinkProps, Link } from './link-component';
export { ServiceWorkerRegister } from './sw-component';
export { useDocumentHead, useLocation, useContent, useNavigate } from './use-functions';
export { usePreventNavigate$, usePreventNavigateQrl } from './use-functions';
export { routeAction$, routeActionQrl } from './server-functions';
export { globalAction$, globalActionQrl } from './server-functions';
export { routeLoader$, routeLoaderQrl } from './server-functions';
Expand All @@ -66,15 +77,3 @@ export { z } from 'zod';

export { Form } from './form-component';
export type { FormProps } from './form-component';

export type {
TypedDataValidator,
DataValidator,
GetValidatorType,
FailOfRest,
ActionReturn,
StrictUnion,
ValidatorReturn,
ServerQRL,
ServerFunction,
} from './types';
Loading

0 comments on commit 6f0c2cb

Please sign in to comment.