From 5098274a05339d28934a5699ece124af0ff9a762 Mon Sep 17 00:00:00 2001 From: emdede Date: Wed, 22 Feb 2023 10:39:09 +0700 Subject: [PATCH 1/3] feat: add exactActive --- README.md | 5 +++-- src/components.tsx | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a156b514e..32f0eb185 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ export default function App() { } ``` -The `` tag also has an `active` class if its href matches the current location, and `inactive` otherwise. **Note:** By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`), use the boolean `end` prop to prevent matching these. This is particularly useful for links to the root route `/` which would match everything. +The `` tag also has an `active` class if its href matches the current location, an `exactActive` class if its href matches the current location exactly and `inactive` class otherwise. **Note:** `active` and `exactActive` are mutually exclusive - there is no class merging! By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`). `exactActive` is particularly useful for links to the root route `/` which would match everything. | prop | type | description | @@ -154,7 +154,8 @@ The `` tag also has an `active` class if its href matches the current locatio | replace | boolean | If true, don't add a new entry to the browser history. (By default, the new page will be added to the browser history, so pressing the back button will take you to the previous route.) | | state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating | | | inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) | -| activeClass | string | The class to show when the link is active | +| activeClass | string | The class to show when the link is active, i.e. the current location _starts with_ `href` | +| exactActiveClass | string | The class to show when the link matches the `href` exactly | | end | boolean | If `true`, only considers the link to be active when the curent location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` | ### The Navigate Component diff --git a/src/components.tsx b/src/components.tsx index 095339bce..787f00687 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -208,16 +208,21 @@ export interface AnchorProps extends Omit props.href); @@ -225,10 +230,11 @@ export function A(props: AnchorProps) { const location = useLocation(); const isActive = createMemo(() => { const to_ = to(); - if (to_ === undefined) return false; + if (to_ === undefined) return [false, false]; const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase(); const loc = normalizePath(location.pathname).toLowerCase(); - return props.end ? path === loc : loc.startsWith(path); + // To be replaced with [loc.startsWith(path), path === loc] when end is patched out + return [props.end ? path === loc : loc.startsWith(path), path === loc]; }); return ( @@ -239,11 +245,12 @@ export function A(props: AnchorProps) { state={JSON.stringify(props.state)} classList={{ ...(props.class && { [props.class]: true }), - [props.inactiveClass!]: !isActive(), - [props.activeClass!]: isActive(), + [props.inactiveClass!]: !isActive()[0], + [props.activeClass!]: isActive()[0] && !isActive()[1], + [props.exactActiveClass!]: isActive()[1], ...rest.classList }} - aria-current={isActive() ? "page" : undefined} + aria-current={isActive()[1] ? "page" : undefined} /> ); } From 269b5721b4c9051659d10114a4fd6940daf96bf3 Mon Sep 17 00:00:00 2001 From: emdede Date: Thu, 23 Feb 2023 08:47:21 +0700 Subject: [PATCH 2/3] refactor: better structure and deprecation --- README.md | 6 +++--- src/components.tsx | 28 +++++++++++++++++----------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 32f0eb185..d31b0086a 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ export default function App() { } ``` -The `` tag also has an `active` class if its href matches the current location, an `exactActive` class if its href matches the current location exactly and `inactive` class otherwise. **Note:** `active` and `exactActive` are mutually exclusive - there is no class merging! By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`). `exactActive` is particularly useful for links to the root route `/` which would match everything. +The `` tag also has an `active` class if its href matches the current location and `inactive` class otherwise. By providing the property `exactActiveClass`, you can opt in to a third state, which is `exactActive` and is set when the href matches the current location exactly. **Note:** By default matching includes locations that are descendents (eg. href `/users` matches locations `/users` and `/users/123`). If no `exactActiveClass` property was provided, `active` class will be set for both partially and exactly matching routes. | prop | type | description | @@ -155,8 +155,8 @@ The `` tag also has an `active` class if its href matches the current locatio | state | unknown | [Push this value](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) to the history stack when navigating | | | inactiveClass | string | The class to show when the link is inactive (when the current location doesn't match the link) | | activeClass | string | The class to show when the link is active, i.e. the current location _starts with_ `href` | -| exactActiveClass | string | The class to show when the link matches the `href` exactly | -| end | boolean | If `true`, only considers the link to be active when the curent location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` | +| exactActiveClass | string or true | The class to show when the link matches the `href` exactly. If `true`, applies `exactActive` class and enables strict matching - i.e. `activeClass` will not apply for an exact match. +| end | boolean | **Deprecated** If `true`, only considers the link to be active when the curent location matches the `href` exactly; if `false`, check if the current location _starts with_ `href` - providing `exactActiveClass` overrides this behavior | | ### The Navigate Component Solid Router provides a `Navigate` component that works similarly to `A`, but it will _immediately_ navigate to the provided path as soon as the component is rendered. It also uses the `href` prop, but you have the additional option of passing a function to `href` that returns a path to navigate to: diff --git a/src/components.tsx b/src/components.tsx index 787f00687..49f12e9f6 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -208,14 +208,14 @@ export interface AnchorProps extends Omit props.href); const href = useHref(to); const location = useLocation(); - const isActive = createMemo(() => { + const matchedHref = createMemo(() => { const to_ = to(); if (to_ === undefined) return [false, false]; const path = normalizePath(to_.split(/[?#]/, 1)[0]).toLowerCase(); const loc = normalizePath(location.pathname).toLowerCase(); - // To be replaced with [loc.startsWith(path), path === loc] when end is patched out - return [props.end ? path === loc : loc.startsWith(path), path === loc]; + return [loc.startsWith(path), path === loc]; }); + const isLooseActive = createMemo(() => matchedHref()[0]) + const isExactActive = createMemo(() => matchedHref()[1] && Boolean(props.exactActiveClass)) + + // Remove together with `end` property + // If end was provided return an exact match, else return loose match (as long as users don't opt in for new behavior) + const isActiveDeprecated = createMemo(() => props.end ? matchedHref()[1] : !props.exactActiveClass && isLooseActive()) + return ( ); } From 595058eea6c6d2c8b9937babf034eb0bb7b8938d Mon Sep 17 00:00:00 2001 From: emdede Date: Thu, 23 Feb 2023 10:17:28 +0700 Subject: [PATCH 3/3] refactor: rename active to match --- src/components.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components.tsx b/src/components.tsx index 49f12e9f6..08840f62c 100644 --- a/src/components.tsx +++ b/src/components.tsx @@ -236,12 +236,12 @@ export function A(props: AnchorProps) { return [loc.startsWith(path), path === loc]; }); - const isLooseActive = createMemo(() => matchedHref()[0]) - const isExactActive = createMemo(() => matchedHref()[1] && Boolean(props.exactActiveClass)) + const isLooseMatch = createMemo(() => matchedHref()[0]) + const isExactMatch = createMemo(() => matchedHref()[1] && Boolean(props.exactActiveClass)) // Remove together with `end` property // If end was provided return an exact match, else return loose match (as long as users don't opt in for new behavior) - const isActiveDeprecated = createMemo(() => props.end ? matchedHref()[1] : !props.exactActiveClass && isLooseActive()) + const isActiveDeprecated = createMemo(() => props.end ? matchedHref()[1] : !props.exactActiveClass && isLooseMatch()) return ( ); }