From f46cd507c24c813da038400af619519eb2841017 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Tue, 22 Jan 2019 14:09:58 -0600 Subject: [PATCH] feat(react): complete controller integrations and navigation (#16849) * fix(react): correct controller types and reexport AlertOptions. * feat(): add Ion Stack and Tabs navigation items based on React Router. * rework tabs and add router outlet * fixes to the outlet rendering. * add direction as state * fixed transitions * Update to core rc2. --- react/package.json | 21 +- react/src/components/IonActionSheet.tsx | 3 +- react/src/components/IonAlert.tsx | 3 +- react/src/components/IonLoading.tsx | 3 +- react/src/components/IonModal.tsx | 2 +- react/src/components/IonPopover.tsx | 2 +- react/src/components/IonToast.tsx | 3 +- react/src/components/createComponent.tsx | 21 +- .../components/createControllerComponent.tsx | 26 +- .../src/components/createOverlayComponent.tsx | 29 ++- react/src/components/index.ts | 19 +- .../components/navigation/IonRouterOutlet.tsx | 245 ++++++++++++++++++ react/src/components/navigation/IonTabBar.tsx | 88 +++++++ react/src/components/navigation/IonTabs.tsx | 53 ++++ react/src/components/types.ts | 8 + react/src/components/utils.ts | 8 + 16 files changed, 479 insertions(+), 55 deletions(-) create mode 100644 react/src/components/navigation/IonRouterOutlet.tsx create mode 100644 react/src/components/navigation/IonTabBar.tsx create mode 100644 react/src/components/navigation/IonTabs.tsx diff --git a/react/package.json b/react/package.json index 9b839cfadc5..d30c9f6b7f9 100644 --- a/react/package.json +++ b/react/package.json @@ -37,13 +37,22 @@ "@types/node": "10.12.9", "@types/react": "16.7.6", "@types/react-dom": "16.0.9", - "react": "latest", - "react-dom": "latest", - "typescript": "3.1.1" + "@types/react-router": "^4.4.3", + "react": "^16.7.0", + "react-dom": "^16.7.0", + "react-router": "^4.3.1", + "react-router-dom": "^4.3.1", + "typescript": "^3.2.2", + "np": "^3.1.0" }, "dependencies": { - "@ionic/core": "4.0.0-rc.0", - "ionicons": "^4.5.0", - "np": "^3.1.0" + "@ionic/core": "4.0.0-rc.2", + "ionicons": "^4.5.0" + }, + "peerDependencies": { + "react": "^16.7.0", + "react-dom": "^16.7.0", + "react-router": "^4.3.1", + "react-router-dom": "^4.3.1" } } diff --git a/react/src/components/IonActionSheet.tsx b/react/src/components/IonActionSheet.tsx index 76523f17d97..e16b26ab5ca 100644 --- a/react/src/components/IonActionSheet.tsx +++ b/react/src/components/IonActionSheet.tsx @@ -1,8 +1,7 @@ import { Components } from '@ionic/core'; import { createOverlayComponent } from './createOverlayComponent'; -import { Omit } from './types'; -export type ActionSheetOptions = Omit; +export type ActionSheetOptions = Components.IonActionSheetAttributes; const IonActionSheet = createOverlayComponent('ion-action-sheet', 'ion-action-sheet-controller') export default IonActionSheet; diff --git a/react/src/components/IonAlert.tsx b/react/src/components/IonAlert.tsx index 788d9d6e66c..60c4a27035d 100644 --- a/react/src/components/IonAlert.tsx +++ b/react/src/components/IonAlert.tsx @@ -1,8 +1,7 @@ import { Components } from '@ionic/core'; import { createControllerComponent } from './createControllerComponent'; -import { Omit } from './types'; -export type AlertOptions = Omit; +export type AlertOptions = Components.IonAlertAttributes; const IonAlert = createControllerComponent('ion-alert', 'ion-alert-controller') export default IonAlert; diff --git a/react/src/components/IonLoading.tsx b/react/src/components/IonLoading.tsx index 027ee7f06e7..ffd20f0b219 100644 --- a/react/src/components/IonLoading.tsx +++ b/react/src/components/IonLoading.tsx @@ -1,8 +1,7 @@ import { Components } from '@ionic/core'; import { createControllerComponent } from './createControllerComponent'; -import { Omit } from './types'; -export type LoadingOptions = Omit; +export type LoadingOptions = Components.IonLoadingAttributes; const IonActionSheet = createControllerComponent('ion-loading', 'ion-loading-controller') export default IonActionSheet; diff --git a/react/src/components/IonModal.tsx b/react/src/components/IonModal.tsx index 9b970e01337..b2291cbb372 100644 --- a/react/src/components/IonModal.tsx +++ b/react/src/components/IonModal.tsx @@ -2,7 +2,7 @@ import { Components } from '@ionic/core'; import { createOverlayComponent } from './createOverlayComponent'; import { Omit } from './types'; -export type ModalOptions = Omit & { +export type ModalOptions = Omit & { children: React.ReactNode; }; diff --git a/react/src/components/IonPopover.tsx b/react/src/components/IonPopover.tsx index 6bdb8ab0b4e..f43f253235a 100644 --- a/react/src/components/IonPopover.tsx +++ b/react/src/components/IonPopover.tsx @@ -2,7 +2,7 @@ import { Components } from '@ionic/core'; import { createOverlayComponent } from './createOverlayComponent'; import { Omit } from './types'; -export type PopoverOptions = Omit & { +export type PopoverOptions = Omit & { children: React.ReactNode; }; diff --git a/react/src/components/IonToast.tsx b/react/src/components/IonToast.tsx index f1f7d3eef15..952376095fa 100644 --- a/react/src/components/IonToast.tsx +++ b/react/src/components/IonToast.tsx @@ -1,8 +1,7 @@ import { Components } from '@ionic/core'; import { createControllerComponent } from './createControllerComponent'; -import { Omit } from './types'; -export type ToastOptions = Omit; +export type ToastOptions = Components.IonToastAttributes; const IonToast = createControllerComponent('ion-toast', 'ion-toast-controller') export default IonToast; diff --git a/react/src/components/createComponent.tsx b/react/src/components/createComponent.tsx index 7b0bc658853..578482a26c7 100644 --- a/react/src/components/createComponent.tsx +++ b/react/src/components/createComponent.tsx @@ -2,20 +2,21 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { dashToPascalCase, attachEventProps } from './utils'; -export function createReactComponent(tagName: string) { +export function createReactComponent(tagName: string) { const displayName = dashToPascalCase(tagName); type IonicReactInternalProps = { forwardedRef?: React.RefObject; children?: React.ReactNode; } + type InternalProps = T & IonicReactInternalProps; type IonicReactExternalProps = { ref?: React.RefObject; children?: React.ReactNode; } - class ReactComponent extends React.Component { + class ReactComponent extends React.Component { componentRef: React.RefObject; constructor(props: T & IonicReactInternalProps) { @@ -31,7 +32,7 @@ export function createReactComponent(tagName: string) { this.componentWillReceiveProps(this.props); } - componentWillReceiveProps(props: any) { + componentWillReceiveProps(props: InternalProps) { const node = ReactDOM.findDOMNode(this); if (!(node instanceof HTMLElement)) { @@ -42,14 +43,20 @@ export function createReactComponent(tagName: string) { } render() { - const { children, forwardedRef, ...cProps } = this.props as any; - cProps.ref = forwardedRef; + const { children, forwardedRef, ...cProps } = this.props; - return React.createElement(tagName, cProps, children); + return React.createElement( + tagName, + { + ...cProps, + ref: forwardedRef + }, + children + ); } } - function forwardRef(props: T & IonicReactInternalProps, ref: React.RefObject) { + function forwardRef(props: InternalProps, ref: React.RefObject) { return ; } forwardRef.displayName = displayName; diff --git a/react/src/components/createControllerComponent.tsx b/react/src/components/createControllerComponent.tsx index 5aad5364cd5..4b12ac1072d 100644 --- a/react/src/components/createControllerComponent.tsx +++ b/react/src/components/createControllerComponent.tsx @@ -1,21 +1,21 @@ import React from 'react'; import { attachEventProps } from './utils' import { ensureElementInBody, dashToPascalCase } from './utils'; +import { OverlayComponentElement, OverlayControllerComponentElement } from './types'; -export function createControllerComponent(tagName: string, controllerTagName: string) { +export function createControllerComponent>(tagName: string, controllerTagName: string) { const displayName = dashToPascalCase(tagName); - type IonicReactInternalProps = { - forwardedRef?: React.RefObject; - children?: React.ReactNode; - show: boolean + type ReactProps = { + show: boolean; } + type Props = T & ReactProps; - return class ReactControllerComponent extends React.Component { + return class ReactControllerComponent extends React.Component { element: E; controllerElement: C; - constructor(props: T & IonicReactInternalProps) { + constructor(props: Props) { super(props); } @@ -25,20 +25,20 @@ export function createControllerComponent(controllerTagName); - await (this.controllerElement as any).componentOnReady(); + await this.controllerElement.componentOnReady(); } - async componentDidUpdate(prevProps: T & IonicReactInternalProps) { + async componentDidUpdate(prevProps: Props) { if (prevProps.show !== this.props.show && this.props.show === true) { - const { children, show, ...cProps} = this.props as any; + const { show, ...cProps} = this.props; - this.element = await (this.controllerElement as any).create(cProps); - await (this.element as any).present(); + this.element = await this.controllerElement.create(cProps); + await this.element.present(); attachEventProps(this.element, cProps); } if (prevProps.show !== this.props.show && this.props.show === false) { - return await (this.element as any).dismiss(); + await this.element.dismiss(); } } diff --git a/react/src/components/createOverlayComponent.tsx b/react/src/components/createOverlayComponent.tsx index b55c701e545..dafd901b845 100644 --- a/react/src/components/createOverlayComponent.tsx +++ b/react/src/components/createOverlayComponent.tsx @@ -2,22 +2,23 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { attachEventProps } from './utils' import { ensureElementInBody, dashToPascalCase } from './utils'; +import { OverlayComponentElement, OverlayControllerComponentElement } from './types'; -export function createOverlayComponent(tagName: string, controllerTagName: string) { +export function createOverlayComponent>(tagName: string, controllerTagName: string) { const displayName = dashToPascalCase(tagName); - type IonicReactInternalProps = { - forwardedRef?: React.RefObject; + type ReactProps = { children: React.ReactNode; show: boolean; } + type Props = T & ReactProps; - return class ReactControllerComponent extends React.Component { + return class ReactControllerComponent extends React.Component { element: E; controllerElement: C; el: HTMLDivElement; - constructor(props: T & IonicReactInternalProps) { + constructor(props: Props) { super(props); this.el = document.createElement('div'); @@ -29,22 +30,24 @@ export function createOverlayComponent(controllerTagName); - await (this.controllerElement as any).componentOnReady(); + await this.controllerElement.componentOnReady(); } - async componentDidUpdate(prevProps: T & IonicReactInternalProps) { + async componentDidUpdate(prevProps: Props) { if (prevProps.show !== this.props.show && this.props.show === true) { - const { children, show, ...cProps} = this.props as any; - cProps.component = this.el; - cProps.componentProps = {}; + const { children, show, ...cProps} = this.props; - this.element = await (this.controllerElement as any).create(cProps); - await (this.element as any).present(); + this.element = await this.controllerElement.create({ + ...cProps, + component: this.el, + componentProps: {} + }); + await this.element.present(); attachEventProps(this.element, cProps); } if (prevProps.show !== this.props.show && this.props.show === false) { - return await (this.element as any).dismiss(); + await this.element.dismiss(); } } diff --git a/react/src/components/index.ts b/react/src/components/index.ts index 311f1f983ae..3674ad0d3c7 100644 --- a/react/src/components/index.ts +++ b/react/src/components/index.ts @@ -2,12 +2,23 @@ import { Components as IoniconsComponents } from 'ionicons'; import { Components } from '@ionic/core'; import { createReactComponent } from './createComponent'; +export { AlertButton, AlertInput } from '@ionic/core'; + export { default as IonActionSheet } from './IonActionSheet'; export { default as IonAlert } from './IonAlert'; export { default as IonLoading } from './IonLoading'; export { default as IonModal } from './IonModal'; export { default as IonPopover } from './IonPopover'; export { default as IonToast } from './IonToast'; +export { default as IonTabs } from './navigation/IonTabs'; +export { default as IonTabBar } from './navigation/IonTabBar'; +export { IonRouterOutlet, IonBackButton } from './navigation/IonRouterOutlet'; + +export const IonTabBarInner = createReactComponent('ion-tab-bar'); +export const IonRouterOutletInner = createReactComponent('ion-router-outlet'); +export const IonBackButtonInner = createReactComponent('ion-back-button'); +export const IonTab = createReactComponent('ion-tab'); +export const IonTabButton = createReactComponent('ion-tab-button'); export const IonAnchor = createReactComponent('ion-anchor'); export const IonApp = createReactComponent('ion-app'); @@ -56,8 +67,8 @@ export const IonProgressBar = createReactComponent('ion-radio'); export const IonRadioGroup = createReactComponent('ion-radio-group'); export const IonRange = createReactComponent('ion-range'); -export const IonRefresher= createReactComponent('ion-refresher'); -export const IonRefresherContent= createReactComponent('ion-refresher-content'); +export const IonRefresher = createReactComponent('ion-refresher'); +export const IonRefresherContent = createReactComponent('ion-refresher-content'); export const IonReorder = createReactComponent('ion-reorder'); export const IonReorderGroup = createReactComponent('ion-reorder-group'); export const IonRippleEffect = createReactComponent('ion-ripple-effect'); @@ -73,10 +84,6 @@ export const IonSlide = createReactComponent('ion-slides'); export const IonSpinner = createReactComponent('ion-spinner'); export const IonSplitPane = createReactComponent('ion-split-pane'); -export const IonTab = createReactComponent('ion-tab'); -export const IonTabBar = createReactComponent('ion-tab-bar'); -export const IonTabButton = createReactComponent('ion-tab-button'); -export const IonTabs = createReactComponent('ion-tabs'); export const IonText = createReactComponent('ion-text'); export const IonTextarea = createReactComponent('ion-textarea'); export const IonThumbnail = createReactComponent('ion-thumbnail'); diff --git a/react/src/components/navigation/IonRouterOutlet.tsx b/react/src/components/navigation/IonRouterOutlet.tsx new file mode 100644 index 00000000000..4dd0b4b6601 --- /dev/null +++ b/react/src/components/navigation/IonRouterOutlet.tsx @@ -0,0 +1,245 @@ +import React, { Component } from 'react'; +import { withRouter, RouteComponentProps, matchPath, match, RouteProps } from 'react-router'; +import { Components } from '@ionic/core'; +import { generateUniqueId } from '../utils'; +import { Location } from 'history'; +import { IonBackButtonInner, IonRouterOutletInner } from '../index'; + +type ChildProps = RouteProps & { + computedMatch: match +} + +type Props = RouteComponentProps & { + children?: React.ReactElement[] | React.ReactElement; +}; + +interface StackItem { + id: string; + location: Location; + match: match<{ tab: string }>; + element: React.ReactElement; + prevId: string; +} + +interface State { + direction: 'forward' | 'back' | undefined, + inTransition: boolean; + activeId: string | undefined; + prevActiveId: string | undefined; + tabActiveIds: { [tab: string]: string }; + views: StackItem[]; +} + +interface ContextInterface { + goBack: () => void +} + +const Context = React.createContext({ + goBack: () => {} +}); + + +class RouterOutlet extends Component { + + enteringEl: React.RefObject = React.createRef(); + leavingEl: React.RefObject = React.createRef(); + containerEl: React.RefObject = React.createRef(); + + constructor(props: Props) { + super(props); + + this.state = { + direction: undefined, + inTransition: false, + activeId: undefined, + prevActiveId: undefined, + tabActiveIds: {}, + views: [] + }; + } + + static getDerivedStateFromProps(props: Props, state: State): Partial { + const location = props.location; + let match: StackItem['match'] = null; + let element: StackItem['element']; + + /** + * Get the current active view and if the path is the same then do nothing + */ + const activeView = state.views.find(v => v.id === state.activeId); + + /** + * Look at all available paths and find the one that matches + */ + React.Children.forEach(props.children, (child: React.ReactElement) => { + if (match == null) { + element = child; + match = matchPath(location.pathname, child.props); + } + }); + + /** + * If there are no matches then set the active view to null and exit + */ + if (!match) { + return { + direction: undefined, + activeId: undefined, + prevActiveId: undefined + }; + } + + /** + * Get the active view for the tab that matches. + * If the location matches the existing tab path then set that view as active + */ + const id = state.tabActiveIds[match.params.tab]; + const currentActiveTabView = state.views.find(v => v.id === id); + if (currentActiveTabView && currentActiveTabView.location.pathname === props.location.pathname) { + if (currentActiveTabView.id === state.activeId) { + return null; + } + return { + direction: undefined, + activeId: currentActiveTabView.id, + prevActiveId: undefined + }; + } + + /** + * If the new active view is a previous view + */ + if (activeView) { + const prevActiveView = state.views.find(v => v.id === activeView.prevId); + if (prevActiveView && activeView.match.params.tab === match.params.tab && prevActiveView.match.url === match.url) { + return { + direction: 'back', + activeId: prevActiveView.id, + prevActiveId: activeView.id, + tabActiveIds: { + ...state.tabActiveIds, + [match.params.tab]: prevActiveView.id, + }, + } + } + } + + const viewId = generateUniqueId(); + + return { + direction: (state.tabActiveIds[match.params.tab]) ? 'forward' : undefined, + activeId: viewId, + prevActiveId: state.tabActiveIds[match.params.tab], + tabActiveIds: { + ...state.tabActiveIds, + [match.params.tab]: viewId + }, + views: state.views.concat({ + id: viewId, + location, + match, + element, + prevId: state.tabActiveIds[match.params.tab] + }) + }; + } + + renderChild(item: StackItem) { + return React.cloneElement(item.element, { + location: item.location, + computedMatch: item.match + }); + } + + goBack = () => { + const prevView = this.state.views.find(v => v.id === this.state.activeId); + const newView = this.state.views.find(v => v.id === prevView.prevId); + + this.props.history.replace(newView.location.pathname); + } + + componentDidUpdate() { + const enteringEl = (this.enteringEl.current != null) ? this.enteringEl.current : undefined; + const leavingEl = (this.leavingEl.current != null) ? this.leavingEl.current : undefined; + + if (this.state.direction && !this.state.inTransition) { + this.setState({ inTransition: true }); + this.containerEl.current.commit(enteringEl, leavingEl, { + deepWait: true, + duration: this.state.direction === undefined ? 0: undefined, + direction: this.state.direction, + showGoBack: true, + progressAnimation: false + }).then(() => { + this.setState(() => ({ + inTransition: false, + direction: undefined + })); + }); + } + } + + render() { + return ( + + + {this.state.views.map((item) => { + let props: any = {}; + + if (item.id === this.state.prevActiveId) { + props = { + 'ref': this.leavingEl, + 'hidden': this.state.direction == null, + 'className': 'ion-page' + (this.state.direction == null ? ' ion-page-hidden' : '') + }; + } else if (item.id === this.state.activeId) { + props = { + 'ref': this.enteringEl, + 'className': 'ion-page' + (this.state.direction != null ? ' ion-page-invisible' : '') + }; + } else { + props = { + 'aria-hidden': true, + 'className': 'ion-page ion-page-hidden' + }; + } + + return ( +
+ { this.renderChild(item) } +
+ ); + })} +
+
+ ); + } +} + +export const IonRouterOutlet = withRouter(RouterOutlet); + + +type ButtonProps = Components.IonBackButtonAttributes & { + goBack: () => void; +}; + +export class IonBackButton extends Component { + context!: React.ContextType; + + clickButton = (e: MouseEvent) => { + e.stopPropagation(); + + this.context.goBack(); + } + + render() { + return ( + + ); + } +} + +IonBackButton.contextType = Context; diff --git a/react/src/components/navigation/IonTabBar.tsx b/react/src/components/navigation/IonTabBar.tsx new file mode 100644 index 00000000000..9f68e64fbe7 --- /dev/null +++ b/react/src/components/navigation/IonTabBar.tsx @@ -0,0 +1,88 @@ +import React, { Component } from 'react'; +import { IonTabBarInner, IonTabButton } from '../index'; +import { withRouter, RouteComponentProps } from 'react-router'; +import { Components } from '@ionic/core'; + +type Props = RouteComponentProps & Components.IonTabBarAttributes & { + children: React.ReactNode; +} + +type Tab = { + originalHref: string, + currentHref: string +} + +type State = { + activeTab: string | null, + tabs: { [key: string]: Tab } +} + +class IonTabBar extends Component { + + constructor(props: Props) { + super(props); + + const tabActiveUrls: { [key: string]: Tab } = {}; + + React.Children.forEach(this.props.children, (child) => { + if (typeof child === 'object' && child.type === IonTabButton) { + tabActiveUrls[child.props.tab] = { + originalHref: child.props.href, + currentHref: child.props.href + }; + } + }); + + this.state = { + activeTab: null, + tabs: tabActiveUrls + } + } + + static getDerivedStateFromProps(props: Props, state: State) { + const activeTab = Object.keys(state.tabs) + .find(key => { + const href = state.tabs[key].originalHref; + return props.location.pathname.startsWith(href) + }); + + if (!activeTab || (activeTab === state.activeTab && state.tabs[activeTab].currentHref === props.location.pathname)) { + return null; + } + + return { + activeTab, + tabs: { + ...state.tabs, + [activeTab]: { + originalHref: state.tabs[activeTab].originalHref, + currentHref: props.location.pathname + } + } + } + } + + + onTabButtonClick = (e: CustomEvent<{ href: string, selected: boolean, tab: string }>) => { + this.props.history.push(e.detail.href); + } + + renderChild = (activeTab: string) => (child: React.ReactElement void }>) => { + const href = (child.props.tab === activeTab) ? this.props.location.pathname : (this.state.tabs[child.props.tab].currentHref); + + return React.cloneElement(child, { + href, + onIonTabButtonClick: this.onTabButtonClick + }) + } + + render() { + return ( + + { React.Children.map(this.props.children, this.renderChild(this.state.activeTab)) } + + ); + } +} + +export default withRouter(IonTabBar); diff --git a/react/src/components/navigation/IonTabs.tsx b/react/src/components/navigation/IonTabs.tsx new file mode 100644 index 00000000000..812adc05ae8 --- /dev/null +++ b/react/src/components/navigation/IonTabs.tsx @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import { IonTabBar, IonRouterOutlet } from '../index'; + +type Props = { + children: React.ReactNode; +} + +const hostStyles: React.CSSProperties = { + display: 'flex', + position: 'absolute', + top: '0', + left: '0', + right: '0', + bottom: '0', + flexDirection: 'column', + width: '100%', + height: '100%', + contain: 'layout size style' +}; + +const tabsInner: React.CSSProperties = { + position: 'relative', + flex: 1, + contain: 'layout size style' +}; + + +export default class IonTabs extends Component { + + render() { + let outlet: React.ReactElement<{}>; + let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }>; + + React.Children.forEach(this.props.children, child => { + if (typeof child === 'object' && child.type === IonRouterOutlet) { + outlet = child; + } + if (typeof child === 'object' && child.type === IonTabBar) { + tabBar = child; + } + }); + + return ( +
+ { tabBar.props.slot === 'top' ? tabBar : null } +
+ { outlet } +
+ { tabBar.props.slot === 'bottom' ? tabBar : null } +
+ ); + } +} diff --git a/react/src/components/types.ts b/react/src/components/types.ts index ca06d71cf92..57231ede9c6 100644 --- a/react/src/components/types.ts +++ b/react/src/components/types.ts @@ -1,2 +1,10 @@ export type Omit = Pick>; + +export interface OverlayComponentElement extends HTMLStencilElement { + 'present': () => Promise; + 'dismiss': (data?: any, role?: string | undefined) => Promise; +} +export interface OverlayControllerComponentElement extends HTMLStencilElement { + 'create': (opts: any) => Promise; +} diff --git a/react/src/components/utils.ts b/react/src/components/utils.ts index 892329134a8..a9149c4a814 100644 --- a/react/src/components/utils.ts +++ b/react/src/components/utils.ts @@ -41,3 +41,11 @@ export function attachEventProps(node: E, props: any) { } }); } + + +export function generateUniqueId() { + return ([1e7].toString() + -1e3.toString() + -4e3.toString() + -8e3.toString() + -1e11.toString()).replace(/[018]/g, function(c: any) { + const random = window.crypto.getRandomValues(new Uint8Array(1)) as Uint8Array; + return (c ^ random[0] & 15 >> c / 4).toString(16); + }); +}