This commit is contained in:
Ben Grynhaus 2018-06-28 19:36:31 +03:00
Родитель a92a90dc3c
Коммит 8967593512
21 изменённых файлов: 354 добавлений и 85 удалений

2
.vscode/settings.json поставляемый
Просмотреть файл

@ -1,8 +1,10 @@
{
"cSpell.words": [
"Focusable",
"Injectable",
"Packagr",
"Renderable",
"nrwl",
"unmount",
"whitelisted"
]

Просмотреть файл

@ -3,21 +3,40 @@
<h2>Getting up and running...</h2>
<ol>
<li>Add <i>AngularReactBrowserModule</i>> to <i>app.module.ts</i> in place of the default <i>BrowserModule</i>.</li>
<li>Add <i>Fab[component]Module</i> or <i>Mat[component]Module</i> to <i>app.module.ts</i> imports.</li>
<li>Add
<i>AngularReactBrowserModule</i>> to
<i>app.module.ts</i> in place of the default
<i>BrowserModule</i>.</li>
<li>Add
<i>Fab[component]Module</i> or
<i>Mat[component]Module</i> to
<i>app.module.ts</i> imports.</li>
<li>Add Fabric or Material components to your views.</li>
</ol>
</div>
<div>
<fab-button label="Test Dialog" (onClick)="toggleDialog()"></fab-button>
<fab-dialog [hidden]="dialogHidden" (onDismiss)="toggleDialog()" key="d2" title="Fabric [React] Dialog" subText="Use Fabric React components inside your Angular app!" [type]="DialogType.largeHeader">
<fab-button text="Test Dialog" (onClick)="toggleDialog()"></fab-button>
<fab-dialog [hidden]="dialogHidden" (onDismiss)="toggleDialog()" key="d2" title="Fabric [React] Dialog" subText="Use Fabric React components inside your Angular app!"
[type]="DialogType.largeHeader">
Hello world! {{ counter }}
<fab-dialog-footer>
<fab-button (onClick)="incrementCounter()" label='Save' style="margin-right:10px;"></fab-button>
<fab-button (onClick)="toggleDialog()" label='Cancel'></fab-button>
<fab-button (onClick)="incrementCounter()" text='Save' style="margin-right:10px;"></fab-button>
<fab-button (onClick)="toggleDialog()" text='Cancel'></fab-button>
</fab-dialog-footer>
</fab-dialog>
</div>
<img src="assets/app-module-dif.png">
<!-- <fab-button [primary]="true" [split]="true" [menuProps]="menuProps" [iconProps]="iconProps" [text]="'Test Dialog'" (onClick)="onClick($event)"></fab-button>
<fab-icon [iconName]="'Add'"></fab-icon> -->
<!-- <fab-image src="http://placehold.it/350x150" alt="Example implementation with no image fit property and no height or width is specified."></fab-image> -->
<fab-button (onClick)="isPanelOpen = !isPanelOpen" text="Open panel"></fab-button>
<fab-panel [isOpen]="isPanelOpen">
<fab-panel-header>
<div>Hello header!</div>
</fab-panel-header>
</fab-panel>
</div>

Просмотреть файл

@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { DialogType } from 'office-ui-fabric-react/lib/Dialog';
import { IButtonProps } from '../../../../node_modules/office-ui-fabric-react/lib/Button';
@Component({
selector: 'app-root',
@ -12,10 +13,47 @@ export class AppComponent {
dialogHidden = true;
counter = 0;
isPanelOpen = false;
menuProps: IButtonProps['menuProps'] = {
items: [
{
key: 'emailMessage',
text: 'Email message',
onClick: () => alert('email clicked!'),
iconProps: {
iconName: 'Mail',
style: {
color: 'red',
},
}
},
{
key: 'calendarEvent',
text: 'Calendar event',
iconProps: {
iconName: 'Calendar'
}
}
]
}
iconProps = {
iconName: 'Add',
styles: {
root: { fontSize: 'x-large' }
}
};
toggleDialog() {
this.dialogHidden = !this.dialogHidden;
}
onClick() {
alert('clicked!');
}
incrementCounter() {
this.counter += 1;
}

Просмотреть файл

@ -1,14 +1,25 @@
import { AngularReactBrowserModule } from '@angular-react/core';
import { FabButtonModule, FabDialogModule, FabIconModule, FabImageModule, FabPanelModule } from '@angular-react/fabric';
import { NgModule } from '@angular/core';
import { NxModule } from '@nrwl/nx';
import { AngularReactBrowserModule } from '@angular-react/core';
import { FabDialogModule, FabButtonModule } from '@angular-react/fabric';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
import { AppComponent } from './app.component';
import { WrapperComponent } from './wrapper.component';
@NgModule({
imports: [AngularReactBrowserModule, NxModule.forRoot(), FabButtonModule, FabDialogModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
imports: [AngularReactBrowserModule,
NxModule.forRoot(),
FabIconModule,
FabButtonModule,
FabDialogModule,
FabImageModule,
FabPanelModule,
],
declarations: [AppComponent, WrapperComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
export class AppModule {
constructor() {
initializeIcons();
}
}

Просмотреть файл

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
selector: 'wrapper',
template: `<ng-content></ng-content>`,
styles: ['react-renderer'],
})
export class WrapperComponent { }

Просмотреть файл

@ -4,6 +4,10 @@
"version": "0.1.12",
"ngPackage": {
"lib": {
"languageLevel": [
"dom",
"es2017"
],
"entryFile": "public-api.ts",
"umdModuleIds": {
"react": "React",

Просмотреть файл

@ -1,2 +1,3 @@
export { registerElement } from './src/renderer/registry';
export { AngularReactBrowserModule } from './src/angular-react-browser.module';
export { ReactWrapperComponent } from './src/components/wrapper-component';
export { registerElement } from './src/renderer/registry';

Просмотреть файл

@ -1,5 +1,5 @@
import { isReactNode } from "@angular-react/core/src/renderer/react-node";
import { AfterViewInit, ElementRef } from "@angular/core";
import { isReactNode } from "../renderer/react-node";
const blacklistedAttributesAsProps = [
'class',
@ -7,7 +7,7 @@ const blacklistedAttributesAsProps = [
];
const blacklistedAttributeMatchers = [
/^_ng.*/
/^_?ng-?.*/
]

Просмотреть файл

@ -2,7 +2,7 @@ import * as React from 'react';
import * as ReactDOM from 'react-dom';
const DEBUG = false;
const DEBUG = true;
export const CHILDREN_TO_APPEND_PROP = 'children-to-append';
export class ReactContent extends React.Component {

Просмотреть файл

@ -1,10 +1,12 @@
/// <reference path="../types/StringMap.d.ts" />
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import removeUndefinedProperties from '../utils/object/remove-undefined-properties';
import { CHILDREN_TO_APPEND_PROP } from './react-content';
import { getComponentClass, ReactComponentClass } from "./registry";
const DEBUG = false;
const DEBUG = true;
export function isReactNode(node: any): node is ReactNode {
return (<ReactNode>node).setRenderPendingCallback !== undefined;
@ -182,12 +184,37 @@ export class ReactNode {
this.props[CHILDREN_TO_APPEND_PROP] = this.childrenToAppend;
const clearedProps = removeUndefinedProperties(this.props);
const clearedProps = this.transformProps(
removeUndefinedProperties(this.props)
);
if (DEBUG) { console.warn('ReactNode > renderRecursive > type:', this.toString(), 'props:', this.props, 'children:', children); }
return React.createElement(this.type, clearedProps, children.length > 0 ? children : undefined);
}
private transformProps(props: object) {
return Object.entries(props).reduce((acc, [key, value]) => {
const [transformKey, transformValue] = this.transformProp(key, value);
return {
...acc,
[transformKey]: transformValue,
};
}, {});
}
private transformProp<TValue = any>(name: string, value: TValue): [string, TValue] {
// prop name is camelCased already
const firstLetter = name[0];
if (firstLetter === firstLetter.toLowerCase()) {
return [name, value];
}
// prop name is PascalCased & is a function - assuming render prop
if (typeof value === 'function') {
return [`on${name}`, value];
}
}
// This is called by Angular core when projected content is being added.
appendChild(projectedContent: HTMLElement) {
if (DEBUG) { console.error('ReactNode > appendChild > node:', this.toString(), 'projectedContent:', projectedContent.toString().trim()); }

Просмотреть файл

@ -1,25 +1,10 @@
// tslint:disable:no-bitwise
import {
Injectable,
RendererType2,
Renderer2,
RendererStyleFlags2
} from '@angular/core';
import { BrowserModule, EventManager } from '@angular/platform-browser';
import {
ɵDomRendererFactory2,
ɵDomSharedStylesHost,
ɵNAMESPACE_URIS
} from '@angular/platform-browser';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Injectable, Renderer2, RendererStyleFlags2, RendererType2 } from '@angular/core';
import { EventManager, ɵDomRendererFactory2, ɵDomSharedStylesHost } from '@angular/platform-browser';
import { isReactNode, ReactNode } from './react-node';
import { ReactComponentClass, getComponentClass } from './registry';
import { ReactNode, isReactNode } from './react-node';
const DEBUG = false;
const DEBUG = true;
@Injectable()
export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
@ -52,7 +37,7 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
return this.defaultReactRenderer;
}
return super.createRenderer(element, type);
return super.createRenderer(element, type);
}
begin() { }
@ -73,9 +58,9 @@ export class AngularReactRendererFactory extends ɵDomRendererFactory2 {
class ReactRenderer implements Renderer2 {
data: { [key: string]: any } = Object.create(null);
constructor(private rootRenderer: AngularReactRendererFactory) {}
constructor(private rootRenderer: AngularReactRendererFactory) { }
destroy(): void {}
destroy(): void { }
destroyNode(node: ReactNode): void {
if (DEBUG) { console.error('Renderer > destroyNode > node:', node.toString()); }
@ -171,7 +156,7 @@ class ReactRenderer implements Renderer2 {
if (DEBUG) { console.log('NOT IMPLEMENTED - Renderer > nextSibling > node:', node.toString()); }
}
setAttribute(node: ReactNode, name: string, value: string, namespace?: string ): void {
setAttribute(node: ReactNode, name: string, value: string, namespace?: string): void {
if (DEBUG) { console.log('Renderer > setAttribute > node:', node.toString(), 'name:', name, 'value:', value, namespace ? 'namespace:' : '', namespace); }
node.setProperty(name, value);
}
@ -208,7 +193,7 @@ class ReactRenderer implements Renderer2 {
}
removeStyle(node: ReactNode, style: string, flags: RendererStyleFlags2): void {
if (DEBUG) { console.log( 'Renderer > removeStyle > node:', node.toString(), 'style:', style, 'flags:', flags); }
if (DEBUG) { console.log('Renderer > removeStyle > node:', node.toString(), 'style:', style, 'flags:', flags); }
node.removeProperty('style', style);
}

Просмотреть файл

Просмотреть файл

@ -1,8 +1,10 @@
export * from './src/button/button.module';
export * from './src/button/button.component';
export * from './src/dialog/dialog.module'
export * from './src/button/button.module';
export * from './src/dialog/dialog.component';
export * from './src/dialog/dialog.module';
export * from './src/icon/icon.component';
export * from './src/icon/icon.module';
export * from './src/image/image.component';
export * from './src/image/image.module';
export * from './src/panel/panel.component';
export * from './src/panel/panel.module';

Просмотреть файл

@ -4,35 +4,41 @@
// tslint:disable:use-host-property-decorator
// tslint:disable:no-output-on-prefix
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output } from '@angular/core';
import { IButtonProps, DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { IButtonProps } from 'office-ui-fabric-react/lib/Button';
@Component({
selector: 'fab-button',
exportAs: 'fabButton',
template: `
<DefaultButton
key="c1"
data-automation-id='test_automation_id'
[primary]="primary"
[disabled]="disabled"
[text]="text"
(onClick)="onClick.emit($event)"></DefaultButton>
[split]="split"
[href]="href"
[menuProps]="menuProps"
[iconProps]="iconProps"
(onClick)="onClick.emit($event)">
</DefaultButton>
`,
styles: [
'react-renderer',
':host { display: inline-block; background: red; }' // TODO: this isn't working. Problem with react-renderer.
],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { 'class': 'fab-button' }
})
export class FabButtonComponent {
@Input() disabled = false;
@Input() primary = true;
@Input('label') text = '';
@Input() disabled?: IButtonProps['disabled'];
@Input() primary?: IButtonProps['primary'];
@Input() checked?: IButtonProps['checked'];
@Input() href?: IButtonProps['href'];
@Input() text?: IButtonProps['text'];
@Input() split?: IButtonProps['split'];
@Input() menuProps?: IButtonProps['menuProps'];
@Input() iconProps?: IButtonProps['iconProps'];
@Input() primaryDisabled?: IButtonProps['primaryDisabled'];
@Output() onClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();

Просмотреть файл

@ -3,23 +3,16 @@
// tslint:disable:no-output-rename
// tslint:disable:use-host-property-decorator
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
} from '@angular/core';
import { IDialogProps, Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/components/Dialog';
import { ReactWrapperComponent } from '@angular-react/core';
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { DialogType, IDialogProps } from 'office-ui-fabric-react/lib/components/Dialog';
@Component({
selector: 'fab-dialog',
exportAs: 'fabDialog',
template: `
<Dialog
#reactNode
[hidden]="hidden"
(onDismiss)="onDismiss($event)"
[dialogContentProps]="{
@ -39,7 +32,8 @@ import { IDialogProps, Dialog, DialogType, DialogFooter } from 'office-ui-fabric
changeDetection: ChangeDetectionStrategy.OnPush,
host: { 'class': 'fab-dialog' }
})
export class FabDialogComponent {
export class FabDialogComponent extends ReactWrapperComponent<IDialogProps> {
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
@Input() hidden = false;
@Input() type = DialogType.normal;
@ -49,16 +43,22 @@ export class FabDialogComponent {
@Input() isBlocking = false;
@Input() containerClassName = 'ms-dialogMainOverride';
@Output('onDismiss') dismiss: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
onDismiss = ev => this.dismiss.emit(ev as any);
}
@Output('onDismiss') dismiss = new EventEmitter<MouseEvent>();
constructor(elementRef: ElementRef) {
super(elementRef);
}
onDismiss(reactEvent: React.MouseEvent<HTMLButtonElement>) {
this.dismiss.emit(reactEvent.nativeEvent);
}
}
@Component({
selector: 'fab-dialog-footer',
exportAs: 'fabDialogFooter',
template: `
<DialogFooter key="4">
<DialogFooter>
<ReactContent><ng-content></ng-content></ReactContent>
</DialogFooter>
`,

Просмотреть файл

@ -1,4 +1,4 @@
import { ReactWrapperComponent } from '@angular-react/core/src/components/wrapper-component';
import { ReactWrapperComponent } from '@angular-react/core';
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { IImageProps, ImageLoadState } from 'office-ui-fabric-react/lib/Image';

Просмотреть файл

@ -0,0 +1 @@
export * from './public-api';

Просмотреть файл

@ -0,0 +1,145 @@
// tslint:disable:component-selector
// tslint:disable:no-input-rename
// tslint:disable:no-output-rename
// tslint:disable:use-host-property-decorator
// tslint:disable:no-output-on-prefix
import { ReactWrapperComponent } from '@angular-react/core';
import { ChangeDetectionStrategy, Component, ContentChild, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { IPanelHeaderRenderer, IPanelProps } from 'office-ui-fabric-react/lib/Panel';
@Component({
selector: 'fab-panel',
exportAs: 'fabPanel',
template: `
<Panel
#reactNode
[componentRef]="componentRef"
[isOpen]="isOpen"
[hasCloseButton]="hasCloseButton"
[isLightDismiss]="isLightDismiss"
[isHiddenOnDismiss]="isHiddenOnDismiss"
[isBlocking]="isBlocking"
[isFooterAtBottom]="isFooterAtBottom"
[headerText]="headerText"
[className]="className"
[type]="type"
[customWidth]="customWidth"
[closeButtonAriaLabel]="closeButtonAriaLabel"
[headerClassName]="headerClassName"
[elementToFocusOnDismiss]="elementToFocusOnDismiss"
[ignoreExternalFocusing]="ignoreExternalFocusing"
[forceFocusInsideTrap]="forceFocusInsideTrap"
[firstFocusableSelector]="firstFocusableSelector"
[focusTrapZoneProps]="focusTrapZoneProps"
[layerProps]="layerProps"
[componentId]="componentId"
(onDismiss)="onDismiss.emit($event)"
(onDismissed)="onDismissed.emit($event)"
(onLightDismissClick)="onLightDismissClick.emit($event)">
<!--
[RenderHeader]="onRenderHeader"
(onRenderBody)="onRenderBody.emit($event)"
(onRenderFooter)="onRenderFooter.emit($event)"
(onRenderFooterContent)="onRenderFooterContent.emit($event)"
(onRenderNavigation)="onRenderNavigation.emit($event)"
-->
</Panel>
<ng-content select="[fab-panel-header]"></ng-content>
<ng-template>
</ng-template>
`,
styles: [
'react-renderer',
],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { 'class': 'fab-panel' }
})
export class FabPanelComponent extends ReactWrapperComponent<IPanelProps> {
@ViewChild('reactNode') protected reactNodeRef: ElementRef;
@Input() componentRef?: IPanelProps['componentRef'];
@Input() isOpen?: IPanelProps['isOpen'];
@Input() hasCloseButton?: IPanelProps['hasCloseButton'];
@Input() isLightDismiss?: IPanelProps['isLightDismiss'];
@Input() isHiddenOnDismiss?: IPanelProps['isHiddenOnDismiss'];
@Input() isBlocking?: IPanelProps['isBlocking'];
@Input() isFooterAtBottom?: IPanelProps['isFooterAtBottom'];
@Input() headerText?: IPanelProps['headerText'];
@Input() className?: IPanelProps['className'];
@Input() type?: IPanelProps['type'];
@Input() customWidth?: IPanelProps['customWidth'];
@Input() closeButtonAriaLabel?: IPanelProps['closeButtonAriaLabel'];
@Input() headerClassName?: IPanelProps['headerClassName'];
@Input() elementToFocusOnDismiss?: IPanelProps['elementToFocusOnDismiss'];
@Input() ignoreExternalFocusing?: IPanelProps['ignoreExternalFocusing'];
@Input() forceFocusInsideTrap?: IPanelProps['forceFocusInsideTrap'];
@Input() firstFocusableSelector?: IPanelProps['firstFocusableSelector'];
@Input() focusTrapZoneProps?: IPanelProps['focusTrapZoneProps'];
@Input() layerProps?: IPanelProps['layerProps'];
@Input() componentId?: IPanelProps['componentId'];
// @Input() headerTemplate?: TemplateRef<IPanelHeaderTemplateContext>
@Output() onLightDismissClick = new EventEmitter<void>();
@Output() onDismiss = new EventEmitter<void>();
@Output() onDismissed = new EventEmitter<void>();
@Output() onRenderNavigation = new EventEmitter<IPanelProps>();
// @Output() onRenderHeader = new EventEmitter<IPanelProps['onRenderHeader']>();
// @Output() onRenderBody = new EventEmitter<IPanelProps['onRenderBody']>();
// @Output() onRenderFooter = new EventEmitter<IPanelProps['onRenderFooter']>();
// @Output() onRenderFooterContent = new EventEmitter<IPanelProps['onRenderFooterContent']>();
@ContentChild('[fab-panel-header]') headerTemplate?: ElementRef;
constructor(elementRef: ElementRef) {
super(elementRef);
// coming from React context - we need to bind to this so we can access the Angular Component properties
this.onRenderHeader = this.onRenderHeader.bind(this);
}
onRenderHeader(props?: IPanelProps, defaultRender?: IPanelHeaderRenderer, headerTextId?: string | undefined) {
if (!this.headerTemplate) {
return null;
}
// FIXME: temp
return null;
/*
const tagName = (this.headerTemplate.nativeElement as HTMLElement).tagName;
return React.createElement(
tagName,
{},
undefined
); */
/* this.headerTemplate.createEmbeddedView({
props: ren
}) */
}
}
/**
* Counterpart of `IPanelHeaderRenderer`.
*/
export interface IPanelHeaderTemplateContext {
props?: IPanelProps
headerTextId?: string | undefined
}
@Component({
selector: 'fab-panel-header',
exportAs: 'fabPanelHeader',
template: `
<PanelHeader>
<ReactContent><ng-content></ng-content></ReactContent>
</PanelHeader>
`,
styles: ['react-renderer'],
changeDetection: ChangeDetectionStrategy.OnPush,
host: { 'class': 'fab-panel-header' }
})
export class FabPanelHeaderComponent { }

Просмотреть файл

@ -0,0 +1,25 @@
import { registerElement } from '@angular-react/core';
import { CommonModule } from '@angular/common';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { Panel } from 'office-ui-fabric-react/lib/Panel';
import { FabPanelComponent, FabPanelHeaderComponent } from './panel.component';
const components = [
FabPanelComponent,
FabPanelHeaderComponent,
];
@NgModule({
imports: [CommonModule],
declarations: components,
exports: components,
schemas: [NO_ERRORS_SCHEMA]
})
export class FabPanelModule {
constructor() {
// Add any React elements to the registry (used by the renderer).
registerElement('Panel', () => Panel);
}
}

Просмотреть файл

@ -0,0 +1,2 @@
export * from './panel.component';
export * from './panel.module';

Просмотреть файл

@ -5,15 +5,8 @@
"module": "es2015",
"baseUrl": ".",
"paths": {
"@angular-react/*": [
"@angular-react/*"
]
"@angular-react/*": ["@angular-react/*"]
}
},
"exclude": [
"**/*.spec.ts",
"**/*.e2e-spec.ts",
"node_modules",
"tmp"
]
"exclude": ["**/*.spec.ts", "**/*.e2e-spec.ts", "node_modules", "tmp"]
}