Skip to content

Commit

Permalink
Reorganize router utils (#5659)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobbe committed Jun 10, 2022
1 parent 012bc15 commit 39e86c6
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 75 deletions.
2 changes: 1 addition & 1 deletion packages/router/src/__tests__/route-announcer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { render, waitFor, act } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'

import { getAnnouncement } from '../a11yUtils'
import { navigate } from '../history'
import RouteAnnouncement from '../route-announcement'
import { Router, Route, routes } from '../router'
import { getAnnouncement } from '../util'

// SETUP
const HomePage = () => <h1>Home Page</h1>
Expand Down
2 changes: 1 addition & 1 deletion packages/router/src/__tests__/route-focus.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { render, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'

import { getFocus } from '../a11yUtils'
import RouteFocus from '../route-focus'
import { Router, Route, routes } from '../router'
import { getFocus } from '../util'

// SETUP
const RouteFocusPage = () => (
Expand Down
52 changes: 52 additions & 0 deletions packages/router/src/a11yUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* gets the announcement for the new page.
* called in one of active-route-loader's `useEffect`.
*
* the order of priority is:
* 1. RouteAnnouncement (the most specific one)
* 2. h1
* 3. document.title
* 4. location.pathname
*/
export const getAnnouncement = () => {
const routeAnnouncement = global?.document.querySelectorAll(
'[data-redwood-route-announcement]'
)?.[0]
if (routeAnnouncement?.textContent) {
return routeAnnouncement.textContent
}

const pageHeading = global?.document.querySelector(`h1`)
if (pageHeading?.textContent) {
return pageHeading.textContent
}

if (global?.document.title) {
return document.title
}

return `new page at ${global?.location.pathname}`
}

export const getFocus = () => {
const routeFocus = global?.document.querySelectorAll(
'[data-redwood-route-focus]'
)?.[0]

if (
!routeFocus ||
!routeFocus.children.length ||
(routeFocus.children[0] as HTMLElement).tabIndex < 0
) {
return null
}

return routeFocus.children[0] as HTMLElement
}

// note: tried document.activeElement.blur(), but that didn't reset the focus flow
export const resetFocus = () => {
global?.document.body.setAttribute('tabindex', '-1')
global?.document.body.focus()
global?.document.body.removeAttribute('tabindex')
}
3 changes: 2 additions & 1 deletion packages/router/src/active-route-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React, { useRef, useState, useEffect } from 'react'

import { unstable_batchedUpdates } from 'react-dom'

import { getAnnouncement, getFocus, resetFocus } from './a11yUtils'
import {
ActivePageContextProvider,
LoadingStateRecord,
} from './ActivePageContext'
import { PageLoadingContextProvider } from './PageLoadingContext'
import { useIsMounted } from './useIsMounted'
import { Spec, getAnnouncement, getFocus, resetFocus } from './util'
import { Spec } from './util'

import { ParamsProvider, useLocation } from '.'

Expand Down
85 changes: 13 additions & 72 deletions packages/router/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Children, ReactElement, ReactNode } from 'react'

/** Create a React Context with the given name. */
const createNamedContext = <T>(name: string, defaultValue?: T) => {
export const createNamedContext = <T>(name: string, defaultValue?: T) => {
const Ctx = React.createContext<T | undefined>(defaultValue)
Ctx.displayName = name
return Ctx
Expand All @@ -17,6 +17,8 @@ const createNamedContext = <T>(name: string, defaultValue?: T) => {
* ['day', 'Int', '{day:Int}'],
* ['filePath', 'Glob', '{filePath...}']
* ]
*
* Only exported to be able to test it
*/
export const paramsForRoute = (route: string) => {
// Match the strings between `{` and `}`.
Expand Down Expand Up @@ -95,7 +97,7 @@ type SupportedRouterParamTypes = keyof typeof coreParamTypes
* matchPath('/post/{id:Int}', '/post/7')
* => { match: true, params: { id: 7 }}
*/
const matchPath = (
export const matchPath = (
route: string,
pathname: string,
paramTypes?: Record<string, ParamType>
Expand Down Expand Up @@ -161,7 +163,7 @@ const matchPath = (
* @fixme
* This utility ignores keys with multiple values such as `?foo=1&foo=2`.
*/
const parseSearch = (
export const parseSearch = (
search:
| string
| string[][]
Expand All @@ -185,7 +187,7 @@ const parseSearch = (
* are found, a descriptive Error will be thrown, as problems with routes are
* critical enough to be considered fatal.
*/
const validatePath = (path: string) => {
export const validatePath = (path: string) => {
// Check that path begins with a slash.
if (!path.startsWith('/')) {
throw new Error(`Route path does not begin with a slash: "${path}"`)
Expand Down Expand Up @@ -219,7 +221,10 @@ const validatePath = (path: string) => {
* replaceParams('/tags/{tag}', { tag: 'code', extra: 'foo' })
* => '/tags/code?extra=foo
*/
const replaceParams = (route: string, args: Record<string, unknown> = {}) => {
export const replaceParams = (
route: string,
args: Record<string, unknown> = {}
) => {
const params = paramsForRoute(route)
let path = route

Expand Down Expand Up @@ -251,15 +256,15 @@ const replaceParams = (route: string, args: Record<string, unknown> = {}) => {
return path
}

function isReactElement(node: ReactNode): node is ReactElement {
export function isReactElement(node: ReactNode): node is ReactElement {
return (
node !== undefined &&
node !== null &&
(node as ReactElement).type !== undefined
)
}

function flattenAll(children: ReactNode): ReactNode[] {
export function flattenAll(children: ReactNode): ReactNode[] {
const childrenArray = Children.toArray(children)

return childrenArray.flatMap((child) => {
Expand Down Expand Up @@ -287,7 +292,7 @@ function flattenAll(children: ReactNode): ReactNode[] {
* => [ { key1: 'val1' }, { key2: 'val2' } ]
*
*/
function flattenSearchParams(
export function flattenSearchParams(
queryString: string
): Array<string | Record<string, any>> {
const searchParams = []
Expand All @@ -299,70 +304,6 @@ function flattenSearchParams(
return searchParams
}

export {
createNamedContext,
matchPath,
parseSearch,
validatePath,
replaceParams,
isReactElement,
flattenAll,
flattenSearchParams,
}

/**
* gets the announcement for the new page.
* called in one of active-route-loader's `useEffect`.
*
* the order of priority is:
* 1. RouteAnnouncement (the most specific one)
* 2. h1
* 3. document.title
* 4. location.pathname
*/
export const getAnnouncement = () => {
const routeAnnouncement = global?.document.querySelectorAll(
'[data-redwood-route-announcement]'
)?.[0]
if (routeAnnouncement?.textContent) {
return routeAnnouncement.textContent
}

const pageHeading = global?.document.querySelector(`h1`)
if (pageHeading?.textContent) {
return pageHeading.textContent
}

if (global?.document.title) {
return document.title
}

return `new page at ${global?.location.pathname}`
}

export const getFocus = () => {
const routeFocus = global?.document.querySelectorAll(
'[data-redwood-route-focus]'
)?.[0]

if (
!routeFocus ||
!routeFocus.children.length ||
(routeFocus.children[0] as HTMLElement).tabIndex < 0
) {
return null
}

return routeFocus.children[0] as HTMLElement
}

// note: tried document.activeElement.blur(), but that didn't reset the focus flow
export const resetFocus = () => {
global?.document.body.setAttribute('tabindex', '-1')
global?.document.body.focus()
global?.document.body.removeAttribute('tabindex')
}

export interface Spec {
name: string
loader: () => Promise<{ default: React.ComponentType<unknown> }>
Expand Down

0 comments on commit 39e86c6

Please sign in to comment.