Skip to content

Commit

Permalink
feat: mark active or ancestor links (#1840)
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister authored Oct 2, 2023
1 parent d638c52 commit 4295dbe
Show file tree
Hide file tree
Showing 11 changed files with 304 additions and 138 deletions.
26 changes: 26 additions & 0 deletions src/runtime/active_url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { VNode } from "preact";

/**
* Mark active or ancestor link
* Note: This function is used both on the server and the client
*/
export function setActiveUrl(vnode: VNode, pathname: string) {
const props = vnode.props as Record<string, unknown>;
const hrefProp = props.href;
if (typeof hrefProp === "string" && hrefProp.startsWith("/")) {
let href = new URL(hrefProp, "http://localhost").pathname;
if (href !== "/" && href.endsWith("/")) {
href = href.slice(0, -1);
}

if (pathname !== "/" && pathname.endsWith("/")) {
pathname = pathname.slice(0, -1);
}

if (pathname === href) {
props["data-current"] = "true";
} else if (pathname.startsWith(href + "/") || href === "/") {
props["data-ancestor"] = "true";
}
}
}
7 changes: 7 additions & 0 deletions src/runtime/entrypoints/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PARTIAL_SEARCH_PARAM,
PartialMode,
} from "../../constants.ts";
import { setActiveUrl } from "../active_url.ts";

function createRootFragment(
parent: Element,
Expand Down Expand Up @@ -704,6 +705,12 @@ export async function applyPartials(res: Response): Promise<void> {
const originalHook = options.vnode;
options.vnode = (vnode) => {
assetHashingHook(vnode);

// Mark active or ancestor links
if (vnode.type === "a") {
setActiveUrl(vnode, location.pathname);
}

if (originalHook) originalHook(vnode);
};

Expand Down
3 changes: 3 additions & 0 deletions src/server/rendering/preact_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { renderToString } from "preact-render-to-string";
import { RenderState } from "./state.ts";
import { Island } from "../types.ts";
import { DATA_KEY_ATTR, LOADING_ATTR, PartialMode } from "../../constants.ts";
import { setActiveUrl } from "../../runtime/active_url.ts";

// See: https://github.com/preactjs/preact/blob/7748dcb83cedd02e37b3713634e35b97b26028fd/src/internal.d.ts#L3C1-L16
enum HookType {
Expand Down Expand Up @@ -262,6 +263,8 @@ options.__b = (vnode: VNode<Record<string, unknown>>) => {
[LOADING_ATTR]: vnode.props[LOADING_ATTR],
});
vnode.props[LOADING_ATTR] = current.islandProps.length - 1;
} else if (vnode.type === "a") {
setActiveUrl(vnode, current.url.pathname);
}
} else if (typeof vnode.type === "function") {
// Detect island vnodes and wrap them with a marker
Expand Down
2 changes: 2 additions & 0 deletions src/server/rendering/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class RenderState {
error: Error | null = null;
isPartial: boolean;
partialCount = 0;
url: URL;

constructor(
routeOptions: RenderStateRouteOptions,
Expand All @@ -51,6 +52,7 @@ export class RenderState {
this.routeOptions = routeOptions;
this.csp = csp;
this.componentStack = componentStack;
this.url = routeOptions.url;
this.isPartial = routeOptions.url.searchParams.has(PARTIAL_SEARCH_PARAM);

if (error) this.routeOptions.error = error;
Expand Down
282 changes: 146 additions & 136 deletions tests/fixture_partials/fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,158 +3,168 @@
// This file is automatically updated during development when running `dev.ts`.

import * as $0 from "./routes/_app.tsx";
import * as $1 from "./routes/client_nav/_layout.tsx";
import * as $2 from "./routes/client_nav/index.tsx";
import * as $3 from "./routes/client_nav/injected.tsx";
import * as $4 from "./routes/client_nav/page-a.tsx";
import * as $5 from "./routes/client_nav/page-b.tsx";
import * as $6 from "./routes/client_nav/page-c.tsx";
import * as $7 from "./routes/client_nav_opt_out/_layout.tsx";
import * as $8 from "./routes/client_nav_opt_out/index.tsx";
import * as $9 from "./routes/client_nav_opt_out/injected.tsx";
import * as $10 from "./routes/client_nav_opt_out/page-a.tsx";
import * as $11 from "./routes/client_nav_opt_out/page-b.tsx";
import * as $12 from "./routes/client_nav_opt_out/page-c.tsx";
import * as $13 from "./routes/form/index.tsx";
import * as $14 from "./routes/form/injected.tsx";
import * as $15 from "./routes/form/update.tsx";
import * as $16 from "./routes/fragment_nav.tsx";
import * as $17 from "./routes/index.tsx";
import * as $18 from "./routes/island_instance/index.tsx";
import * as $19 from "./routes/island_instance/injected.tsx";
import * as $20 from "./routes/island_instance/partial.tsx";
import * as $21 from "./routes/island_instance/partial_remove.tsx";
import * as $22 from "./routes/island_instance/partial_replace.tsx";
import * as $23 from "./routes/island_instance_multiple/index.tsx";
import * as $24 from "./routes/island_instance_multiple/injected.tsx";
import * as $25 from "./routes/island_instance_multiple/partial.tsx";
import * as $26 from "./routes/island_instance_multiple/partial_both.tsx";
import * as $27 from "./routes/island_instance_nested/index.tsx";
import * as $28 from "./routes/island_instance_nested/injected.tsx";
import * as $29 from "./routes/island_instance_nested/partial.tsx";
import * as $30 from "./routes/island_instance_nested/replace.tsx";
import * as $31 from "./routes/island_props/index.tsx";
import * as $32 from "./routes/island_props/injected.tsx";
import * as $33 from "./routes/island_props/partial.tsx";
import * as $34 from "./routes/island_props_signals/index.tsx";
import * as $35 from "./routes/island_props_signals/injected.tsx";
import * as $36 from "./routes/island_props_signals/partial.tsx";
import * as $37 from "./routes/keys/index.tsx";
import * as $38 from "./routes/keys/injected.tsx";
import * as $39 from "./routes/keys/swap.tsx";
import * as $40 from "./routes/keys_components/index.tsx";
import * as $41 from "./routes/keys_components/injected.tsx";
import * as $42 from "./routes/keys_components/swap.tsx";
import * as $43 from "./routes/keys_dom/index.tsx";
import * as $44 from "./routes/keys_dom/injected.tsx";
import * as $45 from "./routes/keys_dom/swap.tsx";
import * as $46 from "./routes/loading/index.tsx";
import * as $47 from "./routes/loading/injected.tsx";
import * as $48 from "./routes/loading/update.tsx";
import * as $49 from "./routes/missing_partial/index.tsx";
import * as $50 from "./routes/missing_partial/injected.tsx";
import * as $51 from "./routes/missing_partial/update.tsx";
import * as $52 from "./routes/mode/append.tsx";
import * as $53 from "./routes/mode/index.tsx";
import * as $54 from "./routes/mode/injected.tsx";
import * as $55 from "./routes/mode/prepend.tsx";
import * as $56 from "./routes/mode/replace.tsx";
import * as $57 from "./routes/no_islands/index.tsx";
import * as $58 from "./routes/no_islands/injected.tsx";
import * as $59 from "./routes/no_islands/update.tsx";
import * as $60 from "./routes/partial_slot_inside_island.tsx";
import * as $1 from "./routes/active_nav/foo/bar.tsx";
import * as $2 from "./routes/active_nav/foo/index.tsx";
import * as $3 from "./routes/active_nav/index.tsx";
import * as $4 from "./routes/active_nav/island.tsx";
import * as $5 from "./routes/client_nav/_layout.tsx";
import * as $6 from "./routes/client_nav/index.tsx";
import * as $7 from "./routes/client_nav/injected.tsx";
import * as $8 from "./routes/client_nav/page-a.tsx";
import * as $9 from "./routes/client_nav/page-b.tsx";
import * as $10 from "./routes/client_nav/page-c.tsx";
import * as $11 from "./routes/client_nav_opt_out/_layout.tsx";
import * as $12 from "./routes/client_nav_opt_out/index.tsx";
import * as $13 from "./routes/client_nav_opt_out/injected.tsx";
import * as $14 from "./routes/client_nav_opt_out/page-a.tsx";
import * as $15 from "./routes/client_nav_opt_out/page-b.tsx";
import * as $16 from "./routes/client_nav_opt_out/page-c.tsx";
import * as $17 from "./routes/form/index.tsx";
import * as $18 from "./routes/form/injected.tsx";
import * as $19 from "./routes/form/update.tsx";
import * as $20 from "./routes/fragment_nav.tsx";
import * as $21 from "./routes/index.tsx";
import * as $22 from "./routes/island_instance/index.tsx";
import * as $23 from "./routes/island_instance/injected.tsx";
import * as $24 from "./routes/island_instance/partial.tsx";
import * as $25 from "./routes/island_instance/partial_remove.tsx";
import * as $26 from "./routes/island_instance/partial_replace.tsx";
import * as $27 from "./routes/island_instance_multiple/index.tsx";
import * as $28 from "./routes/island_instance_multiple/injected.tsx";
import * as $29 from "./routes/island_instance_multiple/partial.tsx";
import * as $30 from "./routes/island_instance_multiple/partial_both.tsx";
import * as $31 from "./routes/island_instance_nested/index.tsx";
import * as $32 from "./routes/island_instance_nested/injected.tsx";
import * as $33 from "./routes/island_instance_nested/partial.tsx";
import * as $34 from "./routes/island_instance_nested/replace.tsx";
import * as $35 from "./routes/island_props/index.tsx";
import * as $36 from "./routes/island_props/injected.tsx";
import * as $37 from "./routes/island_props/partial.tsx";
import * as $38 from "./routes/island_props_signals/index.tsx";
import * as $39 from "./routes/island_props_signals/injected.tsx";
import * as $40 from "./routes/island_props_signals/partial.tsx";
import * as $41 from "./routes/keys/index.tsx";
import * as $42 from "./routes/keys/injected.tsx";
import * as $43 from "./routes/keys/swap.tsx";
import * as $44 from "./routes/keys_components/index.tsx";
import * as $45 from "./routes/keys_components/injected.tsx";
import * as $46 from "./routes/keys_components/swap.tsx";
import * as $47 from "./routes/keys_dom/index.tsx";
import * as $48 from "./routes/keys_dom/injected.tsx";
import * as $49 from "./routes/keys_dom/swap.tsx";
import * as $50 from "./routes/loading/index.tsx";
import * as $51 from "./routes/loading/injected.tsx";
import * as $52 from "./routes/loading/update.tsx";
import * as $53 from "./routes/missing_partial/index.tsx";
import * as $54 from "./routes/missing_partial/injected.tsx";
import * as $55 from "./routes/missing_partial/update.tsx";
import * as $56 from "./routes/mode/append.tsx";
import * as $57 from "./routes/mode/index.tsx";
import * as $58 from "./routes/mode/injected.tsx";
import * as $59 from "./routes/mode/prepend.tsx";
import * as $60 from "./routes/mode/replace.tsx";
import * as $61 from "./routes/no_islands/index.tsx";
import * as $62 from "./routes/no_islands/injected.tsx";
import * as $63 from "./routes/no_islands/update.tsx";
import * as $64 from "./routes/partial_slot_inside_island.tsx";
import * as $$0 from "./islands/Counter.tsx";
import * as $$1 from "./islands/CounterA.tsx";
import * as $$2 from "./islands/CounterB.tsx";
import * as $$3 from "./islands/Fader.tsx";
import * as $$4 from "./islands/InvalidSlot.tsx";
import * as $$5 from "./islands/Logger.tsx";
import * as $$6 from "./islands/Other.tsx";
import * as $$7 from "./islands/PartialTrigger.tsx";
import * as $$8 from "./islands/PassThrough.tsx";
import * as $$9 from "./islands/PropIsland.tsx";
import * as $$10 from "./islands/SignalProp.tsx";
import * as $$11 from "./islands/Spinner.tsx";
import * as $$12 from "./islands/Stateful.tsx";
import * as $$5 from "./islands/LazyLink.tsx";
import * as $$6 from "./islands/Logger.tsx";
import * as $$7 from "./islands/Other.tsx";
import * as $$8 from "./islands/PartialTrigger.tsx";
import * as $$9 from "./islands/PassThrough.tsx";
import * as $$10 from "./islands/PropIsland.tsx";
import * as $$11 from "./islands/SignalProp.tsx";
import * as $$12 from "./islands/Spinner.tsx";
import * as $$13 from "./islands/Stateful.tsx";

const manifest = {
routes: {
"./routes/_app.tsx": $0,
"./routes/client_nav/_layout.tsx": $1,
"./routes/client_nav/index.tsx": $2,
"./routes/client_nav/injected.tsx": $3,
"./routes/client_nav/page-a.tsx": $4,
"./routes/client_nav/page-b.tsx": $5,
"./routes/client_nav/page-c.tsx": $6,
"./routes/client_nav_opt_out/_layout.tsx": $7,
"./routes/client_nav_opt_out/index.tsx": $8,
"./routes/client_nav_opt_out/injected.tsx": $9,
"./routes/client_nav_opt_out/page-a.tsx": $10,
"./routes/client_nav_opt_out/page-b.tsx": $11,
"./routes/client_nav_opt_out/page-c.tsx": $12,
"./routes/form/index.tsx": $13,
"./routes/form/injected.tsx": $14,
"./routes/form/update.tsx": $15,
"./routes/fragment_nav.tsx": $16,
"./routes/index.tsx": $17,
"./routes/island_instance/index.tsx": $18,
"./routes/island_instance/injected.tsx": $19,
"./routes/island_instance/partial.tsx": $20,
"./routes/island_instance/partial_remove.tsx": $21,
"./routes/island_instance/partial_replace.tsx": $22,
"./routes/island_instance_multiple/index.tsx": $23,
"./routes/island_instance_multiple/injected.tsx": $24,
"./routes/island_instance_multiple/partial.tsx": $25,
"./routes/island_instance_multiple/partial_both.tsx": $26,
"./routes/island_instance_nested/index.tsx": $27,
"./routes/island_instance_nested/injected.tsx": $28,
"./routes/island_instance_nested/partial.tsx": $29,
"./routes/island_instance_nested/replace.tsx": $30,
"./routes/island_props/index.tsx": $31,
"./routes/island_props/injected.tsx": $32,
"./routes/island_props/partial.tsx": $33,
"./routes/island_props_signals/index.tsx": $34,
"./routes/island_props_signals/injected.tsx": $35,
"./routes/island_props_signals/partial.tsx": $36,
"./routes/keys/index.tsx": $37,
"./routes/keys/injected.tsx": $38,
"./routes/keys/swap.tsx": $39,
"./routes/keys_components/index.tsx": $40,
"./routes/keys_components/injected.tsx": $41,
"./routes/keys_components/swap.tsx": $42,
"./routes/keys_dom/index.tsx": $43,
"./routes/keys_dom/injected.tsx": $44,
"./routes/keys_dom/swap.tsx": $45,
"./routes/loading/index.tsx": $46,
"./routes/loading/injected.tsx": $47,
"./routes/loading/update.tsx": $48,
"./routes/missing_partial/index.tsx": $49,
"./routes/missing_partial/injected.tsx": $50,
"./routes/missing_partial/update.tsx": $51,
"./routes/mode/append.tsx": $52,
"./routes/mode/index.tsx": $53,
"./routes/mode/injected.tsx": $54,
"./routes/mode/prepend.tsx": $55,
"./routes/mode/replace.tsx": $56,
"./routes/no_islands/index.tsx": $57,
"./routes/no_islands/injected.tsx": $58,
"./routes/no_islands/update.tsx": $59,
"./routes/partial_slot_inside_island.tsx": $60,
"./routes/active_nav/foo/bar.tsx": $1,
"./routes/active_nav/foo/index.tsx": $2,
"./routes/active_nav/index.tsx": $3,
"./routes/active_nav/island.tsx": $4,
"./routes/client_nav/_layout.tsx": $5,
"./routes/client_nav/index.tsx": $6,
"./routes/client_nav/injected.tsx": $7,
"./routes/client_nav/page-a.tsx": $8,
"./routes/client_nav/page-b.tsx": $9,
"./routes/client_nav/page-c.tsx": $10,
"./routes/client_nav_opt_out/_layout.tsx": $11,
"./routes/client_nav_opt_out/index.tsx": $12,
"./routes/client_nav_opt_out/injected.tsx": $13,
"./routes/client_nav_opt_out/page-a.tsx": $14,
"./routes/client_nav_opt_out/page-b.tsx": $15,
"./routes/client_nav_opt_out/page-c.tsx": $16,
"./routes/form/index.tsx": $17,
"./routes/form/injected.tsx": $18,
"./routes/form/update.tsx": $19,
"./routes/fragment_nav.tsx": $20,
"./routes/index.tsx": $21,
"./routes/island_instance/index.tsx": $22,
"./routes/island_instance/injected.tsx": $23,
"./routes/island_instance/partial.tsx": $24,
"./routes/island_instance/partial_remove.tsx": $25,
"./routes/island_instance/partial_replace.tsx": $26,
"./routes/island_instance_multiple/index.tsx": $27,
"./routes/island_instance_multiple/injected.tsx": $28,
"./routes/island_instance_multiple/partial.tsx": $29,
"./routes/island_instance_multiple/partial_both.tsx": $30,
"./routes/island_instance_nested/index.tsx": $31,
"./routes/island_instance_nested/injected.tsx": $32,
"./routes/island_instance_nested/partial.tsx": $33,
"./routes/island_instance_nested/replace.tsx": $34,
"./routes/island_props/index.tsx": $35,
"./routes/island_props/injected.tsx": $36,
"./routes/island_props/partial.tsx": $37,
"./routes/island_props_signals/index.tsx": $38,
"./routes/island_props_signals/injected.tsx": $39,
"./routes/island_props_signals/partial.tsx": $40,
"./routes/keys/index.tsx": $41,
"./routes/keys/injected.tsx": $42,
"./routes/keys/swap.tsx": $43,
"./routes/keys_components/index.tsx": $44,
"./routes/keys_components/injected.tsx": $45,
"./routes/keys_components/swap.tsx": $46,
"./routes/keys_dom/index.tsx": $47,
"./routes/keys_dom/injected.tsx": $48,
"./routes/keys_dom/swap.tsx": $49,
"./routes/loading/index.tsx": $50,
"./routes/loading/injected.tsx": $51,
"./routes/loading/update.tsx": $52,
"./routes/missing_partial/index.tsx": $53,
"./routes/missing_partial/injected.tsx": $54,
"./routes/missing_partial/update.tsx": $55,
"./routes/mode/append.tsx": $56,
"./routes/mode/index.tsx": $57,
"./routes/mode/injected.tsx": $58,
"./routes/mode/prepend.tsx": $59,
"./routes/mode/replace.tsx": $60,
"./routes/no_islands/index.tsx": $61,
"./routes/no_islands/injected.tsx": $62,
"./routes/no_islands/update.tsx": $63,
"./routes/partial_slot_inside_island.tsx": $64,
},
islands: {
"./islands/Counter.tsx": $$0,
"./islands/CounterA.tsx": $$1,
"./islands/CounterB.tsx": $$2,
"./islands/Fader.tsx": $$3,
"./islands/InvalidSlot.tsx": $$4,
"./islands/Logger.tsx": $$5,
"./islands/Other.tsx": $$6,
"./islands/PartialTrigger.tsx": $$7,
"./islands/PassThrough.tsx": $$8,
"./islands/PropIsland.tsx": $$9,
"./islands/SignalProp.tsx": $$10,
"./islands/Spinner.tsx": $$11,
"./islands/Stateful.tsx": $$12,
"./islands/LazyLink.tsx": $$5,
"./islands/Logger.tsx": $$6,
"./islands/Other.tsx": $$7,
"./islands/PartialTrigger.tsx": $$8,
"./islands/PassThrough.tsx": $$9,
"./islands/PropIsland.tsx": $$10,
"./islands/SignalProp.tsx": $$11,
"./islands/Spinner.tsx": $$12,
"./islands/Stateful.tsx": $$13,
},
baseUrl: import.meta.url,
};
Expand Down
Loading

0 comments on commit 4295dbe

Please sign in to comment.