From 33e0ae4afa45e1a0883de9e6aed2e3f1d9b84795 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Fri, 14 Dec 2018 14:54:28 -0600 Subject: [PATCH] feat(react): add initial react code. (#16748) --- react/package.json | 14 +++-- react/src/apis/apis.ts | 22 -------- react/src/components/AlertModal.tsx | 20 +++++++ react/src/components/createComponent.ts | 70 +++++++++++++++++++++++++ react/src/components/index.ts | 55 +++++++++++++++++++ react/src/declarations.d.ts | 2 - react/src/index.ts | 29 ++++++++-- react/src/react-framework-delegate.ts | 40 -------------- react/src/utils/helpers.ts | 18 ------- react/src/utils/wc-shim.ts | 24 --------- react/tsconfig.json | 4 +- 11 files changed, 184 insertions(+), 114 deletions(-) delete mode 100644 react/src/apis/apis.ts create mode 100644 react/src/components/AlertModal.tsx create mode 100644 react/src/components/createComponent.ts create mode 100644 react/src/components/index.ts delete mode 100644 react/src/declarations.d.ts delete mode 100644 react/src/react-framework-delegate.ts delete mode 100644 react/src/utils/helpers.ts delete mode 100644 react/src/utils/wc-shim.ts diff --git a/react/package.json b/react/package.json index 8b248626bd..33e16cd61b 100644 --- a/react/package.json +++ b/react/package.json @@ -1,10 +1,11 @@ { "name": "@ionic/react", - "version": "0.0.2-3", + "version": "0.0.1", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", "framework", + "react", "mobile", "app", "hybrid", @@ -32,11 +33,16 @@ "dist/" ], "devDependencies": { - "@ionic/core": "next", + "@types/jest": "23.3.9", + "@types/node": "10.12.9", + "@types/react": "16.7.6", + "@types/react-dom": "16.0.9", "react": "latest", - "react-dom": "latest" + "react-dom": "latest", + "typescript": "3.1.1" }, "dependencies": { - "@stencil/core": "next" + "ionicons": "^4.5.0", + "@ionic/core": "4.0.0-beta.18" } } diff --git a/react/src/apis/apis.ts b/react/src/apis/apis.ts deleted file mode 100644 index c96fb500ae..0000000000 --- a/react/src/apis/apis.ts +++ /dev/null @@ -1,22 +0,0 @@ - -import { ModalOptions } from '@ionic/core'; - -import { Delegate } from '../react-framework-delegate'; -import { getOrAppendElement } from '../utils/helpers'; - -export function createModal(opts: ModalOptions): Promise { - return createOverlayInternal('ion-modal-controller', opts); -} - -export function createPopover(opts: ModalOptions): Promise { - return createOverlayInternal('ion-popover-controller', opts); -} - - -function createOverlayInternal(controllerTagName: string, opts: any) { - opts.delegate = Delegate; - const element = getOrAppendElement(controllerTagName) as HTMLIonModalControllerElement; - return (element as any).componentOnReady().then(() => { - return element.create(opts); - }); -} \ No newline at end of file diff --git a/react/src/components/AlertModal.tsx b/react/src/components/AlertModal.tsx new file mode 100644 index 0000000000..04ee2e0cfc --- /dev/null +++ b/react/src/components/AlertModal.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Components } from '@ionic/core'; + + +const IonAlertModal: React.SFC = () => { + return null; +} + +export default IonAlertModal; + +/* +import IonAlertModal, { Header, SubHeader, Message, Button} from 'IonAlertModal'; + + +
Favorite Added
+ + Favorite info + +
+*/ diff --git a/react/src/components/createComponent.ts b/react/src/components/createComponent.ts new file mode 100644 index 0000000000..6973ea24bd --- /dev/null +++ b/react/src/components/createComponent.ts @@ -0,0 +1,70 @@ +import React, { RefObject } from 'react'; +import ReactDOM from 'react-dom'; + +const dashToPascalCase = (str: string) => str.toLowerCase().split('-').map(segment => segment.charAt(0).toUpperCase() + segment.slice(1)).join(''); + + +function syncEvent(node: Element, eventName: string, newEventHandler: (e: Event) => any) { + const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1); + const eventStore = (node as any).__events || ((node as any).__events = {}); + const oldEventHandler = eventStore[eventNameLc]; + + // Remove old listener so they don't double up. + if (oldEventHandler) { + node.removeEventListener(eventNameLc, oldEventHandler); + } + + // Bind new listener. + if (newEventHandler) { + node.addEventListener(eventNameLc, eventStore[eventNameLc] = function handler(e: Event) { + newEventHandler.call(this, e); + }); + } +} + +export interface IonicReactBaseProps { + ref?: RefObject +} + +export function createReactComponent(tagName: string) { + const displayName = dashToPascalCase(tagName); + + + return class ReactComponent extends React.Component { + constructor(props: T & IonicReactBaseProps) { + super(props); + } + + static get displayName() { + return displayName; + } + + componentDidMount() { + this.componentWillReceiveProps(this.props); + } + componentWillReceiveProps(props: any) { + const node = ReactDOM.findDOMNode(this) as Element | null + + if (node == null) { + return; + } + + Object.keys(props).forEach(name => { + if (name === 'children' || name === 'style') { + return; + } + + if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) { + syncEvent(node, name.substring(2), props[name]); + } else { + (node as any)[name] = props[name]; + } + }); + } + render() { + const { children, className, ...cProps } = this.props as any; + cProps.class = className || cProps.class; + return React.createElement(tagName, cProps, children); + } + } +} diff --git a/react/src/components/index.ts b/react/src/components/index.ts new file mode 100644 index 0000000000..e5974d5820 --- /dev/null +++ b/react/src/components/index.ts @@ -0,0 +1,55 @@ +import { Components as IoniconsComponents } from 'ionicons'; +import { Components } from '@ionic/core'; +import { createReactComponent } from './createComponent'; + +export { default as AlertModal } from './AlertModal'; + +export const IonIcon = createReactComponent('ion-icon'); +export const IonApp = createReactComponent('ion-app'); +export const IonPage = createReactComponent<{}>('ion-page'); +export const IonMenu = createReactComponent('ion-menu'); +export const IonHeader = createReactComponent('ion-header'); +export const IonTitle = createReactComponent('ion-title'); +export const IonNav = createReactComponent('ion-nav'); +export const IonToolbar = createReactComponent('ion-toolbar'); +export const IonButtons = createReactComponent('ion-buttons'); +export const IonSelect = createReactComponent('ion-select'); +export const IonSelectOption = createReactComponent('ion-select-option'); +export const IonButton = createReactComponent('ion-button'); +export const IonContent = createReactComponent('ion-content'); +export const IonList = createReactComponent('ion-list'); +export const IonListHeader = createReactComponent('ion-list-header'); +export const IonItem = createReactComponent('ion-item'); +export const IonLabel = createReactComponent('ion-label'); +export const IonDatetime = createReactComponent('ion-datetime'); +export const IonMenuButton = createReactComponent('ion-menu-button'); +export const IonItemGroup = createReactComponent('ion-item-group'); +export const IonItemDivider = createReactComponent('ion-item-divider'); +export const IonItemSliding = createReactComponent('ion-item-sliding'); +export const IonItemOption = createReactComponent('ion-item-option'); +export const IonItemOptions = createReactComponent('ion-item-options'); +export const IonInput = createReactComponent('ion-input'); +export const IonGrid = createReactComponent('ion-grid'); +export const IonRow = createReactComponent('ion-row'); +export const IonCol = createReactComponent('ion-col'); +export const IonSegment= createReactComponent('ion-segment'); +export const IonSegmentButton= createReactComponent('ion-segment-button'); +export const IonSearchbar= createReactComponent('ion-searchbar'); +export const IonRefresher= createReactComponent('ion-refresher'); +export const IonRefresherContent= createReactComponent('ion-refresher-content'); +export const IonFab= createReactComponent('ion-fab'); +export const IonFabList = createReactComponent('ion-fab-list'); +export const IonFabButton= createReactComponent('ion-fab-button'); +export const IonAvatar = createReactComponent('ion-avatar'); +export const IonCard = createReactComponent('ion-card'); +export const IonCardHeader = createReactComponent('ion-card-header'); +export const IonCardContent = createReactComponent('ion-card-content'); +export const IonTextarea = createReactComponent('ion-textarea'); +export const IonTabs = createReactComponent('ion-tabs'); +export const IonTab = createReactComponent('ion-tab'); +export const IonTabBar = createReactComponent('ion-tab-bar'); +export const IonTabButton = createReactComponent('ion-tab-button'); +export const IonSlides = createReactComponent('ion-slides'); +export const IonSlide = createReactComponent('ion-slide'); +export const IonSplitPane = createReactComponent('ion-split-pane'); +export const IonMenuToggle = createReactComponent('ion-menu-toggle'); diff --git a/react/src/declarations.d.ts b/react/src/declarations.d.ts deleted file mode 100644 index 473bd2e942..0000000000 --- a/react/src/declarations.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare module 'react'; -declare module 'react-dom'; \ No newline at end of file diff --git a/react/src/index.ts b/react/src/index.ts index e19cf69f08..c25b833f67 100644 --- a/react/src/index.ts +++ b/react/src/index.ts @@ -1,3 +1,26 @@ -export { Delegate } from './react-framework-delegate'; -export * from './apis/apis'; -export * from './utils/wc-shim'; \ No newline at end of file +import { addIcons } from 'ionicons'; +import { ICON_PATHS } from 'ionicons/icons'; +import { IonicConfig } from '@ionic/core'; +import { defineCustomElements } from '@ionic/core/loader'; + +export * from './components'; + +export interface IonicGlobal { + config?: any; + ael?: (elm: any, eventName: string, cb: (ev: Event) => void, opts: any) => void; + raf?: (ts: number) => void; + rel?: (elm: any, eventName: string, cb: (ev: Event) => void, opts: any) => void; +} + +export interface IonicWindow extends Window { + Ionic: IonicGlobal; +} + +export function registerIonic(config: IonicConfig = {}) { + const win: IonicWindow = window as any; + const Ionic = (win.Ionic = win.Ionic || {}); + addIcons(ICON_PATHS); + + Ionic.config = config; + defineCustomElements(window); +} diff --git a/react/src/react-framework-delegate.ts b/react/src/react-framework-delegate.ts deleted file mode 100644 index fcd0ff6728..0000000000 --- a/react/src/react-framework-delegate.ts +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; - -import { FrameworkDelegate } from '@ionic/core'; -import { isElementModal, isElementNav } from './utils/helpers'; - -export function attachViewToDom(parentElement: HTMLElement, reactComponent: any, propsOrData: any, classesToAdd: string[]) { - const wrappingDiv = shouldWrapInIonPage(parentElement) ? document.createElement('ion-page') : document.createElement('div'); - if (classesToAdd) { - for (const clazz of classesToAdd) { - wrappingDiv.classList.add(clazz); - } - } - - parentElement.appendChild(wrappingDiv); - - // mount the React component - const reactElement = React.createElement(reactComponent, propsOrData); - ReactDOM.render(reactElement, wrappingDiv); - - return Promise.resolve(wrappingDiv); -} - -export function removeViewFromDom(parentElement: HTMLElement, childElement: HTMLElement): Promise { - ReactDOM.unmountComponentAtNode(childElement); - parentElement.removeChild(childElement); - return Promise.resolve(); -} - -const Delegate: FrameworkDelegate = { - attachViewToDom: attachViewToDom, - removeViewFromDom: removeViewFromDom, -}; - -export { Delegate } - - -export function shouldWrapInIonPage(element: HTMLElement) { - return isElementModal(element) || isElementNav(element); -} diff --git a/react/src/utils/helpers.ts b/react/src/utils/helpers.ts deleted file mode 100644 index b9a696f13a..0000000000 --- a/react/src/utils/helpers.ts +++ /dev/null @@ -1,18 +0,0 @@ - -export function getOrAppendElement(tagName: string): Element { - const element = document.querySelector(tagName); - if (element) { - return element; - } - const tmp = document.createElement(tagName); - document.body.appendChild(tmp); - return tmp; -} - -export function isElementNav(element: HTMLElement) { - return element.tagName.toUpperCase() === 'ION-NAV'; -} - -export function isElementModal(element: HTMLElement) { - return element.classList.contains('modal-wrapper'); -} diff --git a/react/src/utils/wc-shim.ts b/react/src/utils/wc-shim.ts deleted file mode 100644 index 2d4c6f613b..0000000000 --- a/react/src/utils/wc-shim.ts +++ /dev/null @@ -1,24 +0,0 @@ -export function wc(events = {}, obj = {}) { - let storedEl: HTMLElement; - - return function (el: HTMLElement) { - - (Object as any).entries(events).forEach((keyValues: string[]) => { - const name = keyValues[0]; - const value = keyValues[1]; - const action = (el) ? el.addEventListener : storedEl.removeEventListener; - if (typeof value === 'function') { - action(name, value); - return; - } - }); - if (el) { - (Object as any).entries(obj).forEach((keyValues: string[]) => { - const name = keyValues[0]; - const value = keyValues[1]; - (el as any)[name] = value; - }); - } - storedEl = el; - }; -} \ No newline at end of file diff --git a/react/tsconfig.json b/react/tsconfig.json index 29f0b51573..44d40c75c2 100644 --- a/react/tsconfig.json +++ b/react/tsconfig.json @@ -15,10 +15,12 @@ "outDir": "dist", "removeComments": false, "sourceMap": true, + "jsx": "preserve", "target": "es2015" }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "src/**/*.tsx" ], "compileOnSave": false, "buildOnSave": false