├── .gitignore ├── Gulpfile.js ├── LICENSE ├── README.md ├── SimpleDemo ├── .gitignore ├── Assets │ ├── product1.png │ ├── product2.png │ ├── product3.png │ ├── style.css │ └── style.scss ├── Libs │ ├── polyfills.js │ └── uibuilder-1.7.0.js ├── Models │ └── Product.ts ├── SimpleDemo.csproj ├── Views │ ├── .gitignore │ ├── DemoPage.tsx │ ├── LoginPanel.tsx │ ├── Product.tsx │ ├── ProductList.tsx │ └── ReusableComponents │ │ ├── Dropdown.tsx │ │ ├── Keys.ts │ │ ├── PopupMenu.tsx │ │ └── Tabs.tsx ├── app.ts ├── index.html ├── tsconfig.json ├── typings │ └── uibuilder │ │ └── uibuilder-1.7.0.d.ts ├── web.Debug.config ├── web.Release.config └── web.config ├── UIBuilder.sln ├── UIBuilder ├── JSX.ts ├── UIBuilder.csproj ├── UIBuilder.ts └── tsconfig.json ├── WebComponentsDemo ├── .gitignore ├── Assets │ ├── app.css │ └── font-awesome.min.css ├── Libs │ └── uibuilder-1.7.0.js ├── Views │ ├── .gitignore │ └── DemoPage.tsx ├── WebComponentsDemo.csproj ├── app.ts ├── elements │ ├── .gitignore │ └── ZxListEditor.tsx ├── fonts │ └── fontawesome-webfont.ttf ├── index.html ├── tsconfig.json ├── typings │ └── browser │ │ └── ambient │ │ ├── shadow-dom │ │ └── index.d.ts │ │ └── uibuilder │ │ └── uibuilder-1.7.0.d.ts ├── web.Debug.config ├── web.Release.config └── web.config ├── dist ├── uibuilder-1.7.0.d.ts └── uibuilder-1.7.0.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | 238 | *.map 239 | 240 | !Release/ 241 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var sass = require('gulp-sass'); 5 | 6 | gulp.task('sass', function () { 7 | var paths = ['./SimpleDemo/assets/*.scss']; 8 | return gulp.src(paths, { base: "./" }) 9 | .pipe(sass.sync({outputStyle: 'expanded'}).on('error', sass.logError)) 10 | .pipe(gulp.dest('./')) 11 | }); 12 | 13 | gulp.task('sass:watch', function () { 14 | gulp.watch('./SimpleDemo/assets/*.scss', ['sass']); 15 | }); 16 | 17 | gulp.task('default', ['sass'], function () { 18 | }); 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 wisercoder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UIBuilder 2 | Typed HTML templates using TypeScript's TSX files. 3 | 4 | ## Typed Templates 5 | 6 | UIBuilder is a HTML templating library in the style of Mustache and Handlebars, for Web application development using TypeScript. Because of compile-time checking and "Intellisense" UIBuilder is a better choice than Mustache or Handlebars. 7 | 8 | UIBuilder templates are written using TypeScript's TSX syntax which mixes HTML with TypeScript. If you know HTML and TypeScript you already know TSX. You use regular HTML tags for composing UI elements, and regular TypeScript for loops and conditionals. Building custom elements (Components) is supported. 9 | 10 | TypeScript compiler is needed to compile TSX files. Visual Studio is not needed. However, if you use Visual Studio you get benefits such as: 11 | * Auto-indent 12 | * Syntax coloring 13 | * Type checking 14 | * Intellisense suggestions for TypeScript expressions 15 | * Refactoring: you can rename variables without worrying about breaking your templates 16 | * Error checking squiggly lines as you type: 17 | * Mismatched HTML tags 18 | * TypeScript syntax errors 19 | * Put breakpoints inside your template 20 | 21 | ## JSX for Web Components 22 | UIBuilder brings the power and convenience of JSX to Web Components. 23 | 24 | ### What is JSX? 25 | JSX is an extension to JavaScript that allows you to build dynamic user interfaces by embedding HTML-like syntax within JavaScript code. Other templating languages either embed HTML as strings within JavaScript code, or embed code as strings within HTML, which means tools are only able to provide compile-time checking for either code or markup, not both. In JSX both code and markup are first-class citizens, which enables tools to provide compile-time checking, syntax coloring and "Intellisense" for code as well as markup. More information about JSX can be found [here](https://facebook.github.io/jsx/). 26 | 27 | TypeScript's implementation of JSX is [TSX](https://basarat.gitbooks.io/typescript/content/docs/jsx/tsx.html). UIBuilder leverages TSX and lets the TypeScript compiler do all of the heavy lifting. 28 | 29 | ### What are Web Components? 30 | 31 | Web Components are user interface widgets that are written once and can be reused reliably on multiple pages and Web sites. The reliability comes from the fact that the DOM tree inside a Web Component is encapsulated from the rest of the page, so you don't have to worry about things like conflicting id and style class names or JavaScript variables. The technology that enables this isolation is Shadow DOM. Read more about it [here](https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom). 32 | 33 | Web Components are a W3 standard. The W3 page for Web Components can be found [here](https://www.w3.org/standards/techs/components). 34 | 35 | Shadow DOM is currently implemented by all browsers, including Chrome, Edge, Firefox and Safari. 36 | 37 | ## What it doesn't do 38 | Unlike React.js UIBuilder does not do incremental screen updates. 39 | 40 | A valid approach to update the screen is to divide your page into multiple components. When data changes just replace the component that contains stale data. For example, you can update just a single component on your page like this: 41 | 42 | ```typescript 43 | const element = UIBuilder.createElement(CustomerPanel, { customerInfo: freshCustomerInfo }); 44 | document.querySelector(".customer-panel").replaceWith(element); 45 | ``` 46 | 47 | If you think this is not as convenient as a setState call in React you're correct but you're forgetting how much rigmarole React puts you through. For small applications React's setState works very well because you don't have to know what portions of the UI need to be updated. Larger React applications need to implement shouldComponentUpdate for performance optimization. At that point the convenience is gone, and all the incantations, rituals and ceremony of React got you nothing. 48 | 49 | ## Why not just use React? 50 | 51 | React does not have the equivalent of a Shadow DOM. React components are brittle. The brittleness comes from the global nature of HTML, CSS, and JS. The DOM tree inside a React component isn't encapsulated from the rest of the page. This lack of encapsulation means your document stylesheet might accidentally apply to parts inside the widget; your JavaScript might accidentally modify parts inside the widget; your IDs might overlap with IDs inside the component; and so on. (More on Shadow DOM [here](https://www.html5rocks.com/en/tutorials/webcomponents/shadowdom).) 52 | 53 | Unlike React, UIBuilder is compatible with [Web Components](https://www.w3.org/standards/techs/components) which does not have the above problems. 54 | 55 | Web Components also support multiple [named slots](https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom#composition_slot) for placing child elements. React components have just one, unnamed slot. 56 | 57 | Web Components are a W3 open standard. React is opensource but it is not based on standards. 58 | 59 | UIBuilder creates real DOM nodes, not virtual nodes, which makes it easier to implement advanced features that require manipulating the DOM directly, such as drag & drop and animation. It is also easier to integrate with DOM-mutating libraries such as d3.js and take full advantage of its features, such as transitions. 60 | 61 | UIBuilder does not use any React.js code. 62 | 63 | ## License 64 | Please see file named LICENSE 65 | -------------------------------------------------------------------------------- /SimpleDemo/.gitignore: -------------------------------------------------------------------------------- 1 | Generated/ 2 | -------------------------------------------------------------------------------- /SimpleDemo/Assets/product1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wisercoder/uibuilder/6bf743ede9227a61b000f3ad0a3e167e06b3cae7/SimpleDemo/Assets/product1.png -------------------------------------------------------------------------------- /SimpleDemo/Assets/product2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wisercoder/uibuilder/6bf743ede9227a61b000f3ad0a3e167e06b3cae7/SimpleDemo/Assets/product2.png -------------------------------------------------------------------------------- /SimpleDemo/Assets/product3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wisercoder/uibuilder/6bf743ede9227a61b000f3ad0a3e167e06b3cae7/SimpleDemo/Assets/product3.png -------------------------------------------------------------------------------- /SimpleDemo/Assets/style.css: -------------------------------------------------------------------------------- 1 | body, button, input { 2 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 3 | } 4 | 5 | div { 6 | box-sizing: border-box; 7 | } 8 | 9 | .panel, .login-panel { 10 | background-color: #ebf1f5; 11 | } 12 | 13 | button { 14 | background-color: #0072C6; 15 | color: white; 16 | border: none; 17 | padding: 5px 25px 5px 25px; 18 | } 19 | 20 | button:hover { 21 | background-color: #0081e0; 22 | } 23 | 24 | .caret, .dropdown-button::after { 25 | height: 0; 26 | width: 0; 27 | border-bottom: none; 28 | border-top: 5px solid #000000; 29 | border-left: 4px solid transparent; 30 | border-right: 4px solid transparent; 31 | display: inline-block; 32 | vertical-align: middle; 33 | } 34 | 35 | .login-panel { 36 | display: inline-block; 37 | margin-bottom: 20px; 38 | padding: 10px; 39 | } 40 | 41 | .login-panel .button-panel { 42 | margin-top: 10px; 43 | text-align: right; 44 | } 45 | 46 | .login-panel .button-panel button { 47 | margin-left: 5px; 48 | } 49 | 50 | .login-panel label { 51 | display: inline-block; 52 | min-width: 100px; 53 | } 54 | 55 | .login-panel input { 56 | width: 200px; 57 | } 58 | 59 | .login-panel .dialog-row { 60 | margin-bottom: 5px; 61 | } 62 | 63 | .product .product-photo { 64 | display: inline-block; 65 | } 66 | 67 | .product .product-photo img { 68 | width: 150px; 69 | height: 100px; 70 | } 71 | 72 | .product .product-details { 73 | background-color: #ebf1f5; 74 | width: 150px; 75 | vertical-align: top; 76 | display: inline-block; 77 | height: 100px; 78 | padding: 7px; 79 | } 80 | 81 | .product .product-name { 82 | font-size: 20px; 83 | color: #444; 84 | } 85 | 86 | .product .product-price { 87 | font-size: 14px; 88 | color: #666; 89 | } 90 | 91 | .tabs { 92 | margin-bottom: 25px; 93 | } 94 | 95 | .tabs .tab { 96 | display: inline-block; 97 | padding: 5px 0 5px 0; 98 | margin: 0 10px 0 10px; 99 | cursor: default; 100 | } 101 | 102 | .tabs .tab.selected { 103 | border-bottom: 3px solid #0072C6; 104 | } 105 | 106 | .popup-menu { 107 | outline: 0; 108 | box-shadow: 0px 1px 3px #666; 109 | background-color: white; 110 | display: inline-block; 111 | } 112 | 113 | .popup-menu .menu-item { 114 | cursor: default; 115 | padding: 3px 15px 4px 15px; 116 | } 117 | 118 | .popup-menu .menu-item.current { 119 | background-color: #0072C6; 120 | color: white; 121 | } 122 | 123 | .dropdown-button { 124 | padding-bottom: 2px; 125 | cursor: default; 126 | } 127 | 128 | .dropdown-button::after { 129 | content: ' '; 130 | margin-left: 5px; 131 | } 132 | 133 | .dropdown { 134 | display: inline-block; 135 | } 136 | 137 | .dropdown .popup-menu { 138 | position: absolute; 139 | } 140 | 141 | .hidden { 142 | display: none; 143 | } 144 | -------------------------------------------------------------------------------- /SimpleDemo/Assets/style.scss: -------------------------------------------------------------------------------- 1 | $brandColor: #0072C6; 2 | 3 | body, button, input { 4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 5 | } 6 | 7 | div { 8 | box-sizing: border-box; 9 | } 10 | 11 | .panel { 12 | background-color: #ebf1f5; 13 | } 14 | 15 | button { 16 | background-color: $brandColor; 17 | color: white; 18 | border: none; 19 | padding: 5px 25px 5px 25px; 20 | &:hover { 21 | background-color: lighten($brandColor, 5%); 22 | } 23 | } 24 | 25 | .caret { 26 | height: 0; 27 | width: 0; 28 | border-bottom: none; 29 | border-top: 5px solid #000000; 30 | border-left: 4px solid transparent; 31 | border-right: 4px solid transparent; 32 | display: inline-block; 33 | vertical-align: middle; 34 | } 35 | 36 | .login-panel { 37 | @extend .panel; 38 | display: inline-block; 39 | margin-bottom: 20px; 40 | padding: 10px; 41 | .button-panel { 42 | margin-top: 10px; 43 | text-align: right; 44 | button { 45 | margin-left: 5px; 46 | } 47 | } 48 | label { 49 | display: inline-block; 50 | min-width: 100px; 51 | } 52 | input { 53 | width: 200px; 54 | } 55 | .dialog-row { 56 | margin-bottom: 5px; 57 | } 58 | } 59 | 60 | .product { 61 | .product-photo { 62 | display: inline-block; 63 | img { 64 | width: 150px; 65 | height: 100px; 66 | } 67 | } 68 | .product-details { 69 | background-color: #ebf1f5; 70 | width: 150px; 71 | vertical-align: top; 72 | display: inline-block; 73 | height: 100px; 74 | padding: 7px; 75 | } 76 | .product-name { 77 | font-size: 20px; 78 | color: #444; 79 | } 80 | .product-price { 81 | font-size: 14px; 82 | color: #666; 83 | } 84 | } 85 | 86 | .tabs { 87 | margin-bottom: 25px; 88 | .tab { 89 | display: inline-block; 90 | padding: 5px 0 5px 0; 91 | margin: 0 10px 0 10px; 92 | cursor: default; 93 | &.selected { 94 | border-bottom: 3px solid #0072C6; 95 | } 96 | } 97 | } 98 | 99 | .popup-menu { 100 | outline: 0; 101 | box-shadow: 0px 1px 3px #666; 102 | background-color: white; 103 | display: inline-block; 104 | .menu-item { 105 | cursor: default; 106 | padding: 3px 15px 4px 15px; 107 | &.current { 108 | background-color: $brandColor; 109 | color: white; 110 | } 111 | } 112 | } 113 | 114 | .dropdown-button { 115 | padding-bottom: 2px; 116 | cursor: default; 117 | &::after { 118 | @extend .caret; 119 | content: ' '; 120 | margin-left: 5px; 121 | } 122 | } 123 | 124 | .dropdown { 125 | display: inline-block; 126 | .popup-menu { 127 | position: absolute; 128 | } 129 | } 130 | 131 | .hidden { 132 | display: none; 133 | } 134 | -------------------------------------------------------------------------------- /SimpleDemo/Libs/polyfills.js: -------------------------------------------------------------------------------- 1 | // Production steps of ECMA-262, Edition 6, 22.1.2.1 2 | if (!Array.from) { 3 | Array.from = (function () { 4 | var toStr = Object.prototype.toString; 5 | var isCallable = function (fn) { 6 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 7 | }; 8 | var toInteger = function (value) { 9 | var number = Number(value); 10 | if (isNaN(number)) { return 0; } 11 | if (number === 0 || !isFinite(number)) { return number; } 12 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 13 | }; 14 | var maxSafeInteger = Math.pow(2, 53) - 1; 15 | var toLength = function (value) { 16 | var len = toInteger(value); 17 | return Math.min(Math.max(len, 0), maxSafeInteger); 18 | }; 19 | 20 | // The length property of the from method is 1. 21 | return function from(arrayLike/*, mapFn, thisArg */) { 22 | // 1. Let C be the this value. 23 | var C = this; 24 | 25 | // 2. Let items be ToObject(arrayLike). 26 | var items = Object(arrayLike); 27 | 28 | // 3. ReturnIfAbrupt(items). 29 | if (arrayLike == null) { 30 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 31 | } 32 | 33 | // 4. If mapfn is undefined, then let mapping be false. 34 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 35 | var T; 36 | if (typeof mapFn !== 'undefined') { 37 | // 5. else 38 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 39 | if (!isCallable(mapFn)) { 40 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 41 | } 42 | 43 | // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 44 | if (arguments.length > 2) { 45 | T = arguments[2]; 46 | } 47 | } 48 | 49 | // 10. Let lenValue be Get(items, "length"). 50 | // 11. Let len be ToLength(lenValue). 51 | var len = toLength(items.length); 52 | 53 | // 13. If IsConstructor(C) is true, then 54 | // 13. a. Let A be the result of calling the [[Construct]] internal method 55 | // of C with an argument list containing the single item len. 56 | // 14. a. Else, Let A be ArrayCreate(len). 57 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); 58 | 59 | // 16. Let k be 0. 60 | var k = 0; 61 | // 17. Repeat, while k < len… (also steps a - h) 62 | var kValue; 63 | while (k < len) { 64 | kValue = items[k]; 65 | if (mapFn) { 66 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 67 | } else { 68 | A[k] = kValue; 69 | } 70 | k += 1; 71 | } 72 | // 18. Let putStatus be Put(A, "length", len, true). 73 | A.length = len; 74 | // 20. Return A. 75 | return A; 76 | }; 77 | }()); 78 | } 79 | -------------------------------------------------------------------------------- /SimpleDemo/Libs/uibuilder-1.7.0.js: -------------------------------------------------------------------------------- 1 | var __assign = (this && this.__assign) || function () { 2 | __assign = Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | return __assign.apply(this, arguments); 11 | }; 12 | var UIBuilder; 13 | (function (UIBuilder) { 14 | var attribMap = { 15 | 'htmlFor': 'for', 16 | 'className': 'class', 17 | 'defaultValue': 'value', 18 | 'defaultChecked': 'checked' 19 | }; 20 | var eventMap = { 21 | // Clipboard events 22 | 'onCopy': 'oncopy', 23 | 'onCut': 'oncut', 24 | 'onPaste': 'onpaste', 25 | // Keyboard events 26 | 'onKeyDown': 'onkeydown', 27 | 'onKeyPress': 'onkeypress', 28 | 'onKeyUp': 'onkeyup', 29 | // Focus events 30 | 'onFocus': 'onfocus', 31 | 'onBlur': 'onblur', 32 | // Form events 33 | 'onChange': 'onchange', 34 | 'onInput': 'oninput', 35 | 'onSubmit': 'onsubmit', 36 | // Mouse events 37 | 'onClick': 'onclick', 38 | 'onContextMenu': 'oncontextmenu', 39 | 'onDoubleClick': 'ondblclick', 40 | 'onDrag': 'ondrag', 41 | 'onDragEnd': 'ondragend', 42 | 'onDragEnter': 'ondragenter', 43 | 'onDragExit': 'ondragexit', 44 | 'onDragLeave': 'ondragleave', 45 | 'onDragOver': 'ondragover', 46 | 'onDragStart': 'ondragstart', 47 | 'onDrop': 'ondrop', 48 | 'onMouseDown': 'onmousedown', 49 | 'onMouseEnter': 'onmouseenter', 50 | 'onMouseLeave': 'onmouseleave', 51 | 'onMouseMove': 'onmousemove', 52 | 'onMouseOut': 'onmouseout', 53 | 'onMouseOver': 'onmouseover', 54 | 'onMouseUp': 'onmouseup', 55 | // Selection events 56 | 'onSelect': 'onselect', 57 | // Touch events 58 | 'onTouchCancel': 'ontouchcancel', 59 | 'onTouchEnd': 'ontouchend', 60 | 'onTouchMove': 'ontouchmove', 61 | 'onTouchStart': 'ontouchstart', 62 | // UI events 63 | 'onScroll': 'onscroll', 64 | // Wheel events 65 | 'onWheel': 'onwheel', 66 | // Media events 67 | 'onAbort': 'onabort', 68 | 'onCanPlay': 'oncanplay', 69 | 'onCanPlayThrough': 'oncanplaythrough', 70 | 'onDurationChange': 'ondurationchange', 71 | 'onEmptied': 'onemptied', 72 | 'onEncrypted': 'onencrypted', 73 | 'onEnded': 'onended', 74 | 'onLoadedData': 'onloadeddata', 75 | 'onLoadedMetadata': 'onloadedmetadata', 76 | 'onLoadStart': 'onloadstart', 77 | 'onPause': 'onpause', 78 | 'onPlay': 'onplay', 79 | 'onPlaying': 'onplaying', 80 | 'onProgress': 'onprogress', 81 | 'onRateChange': 'onratechange', 82 | 'onSeeked': 'onseeked', 83 | 'onSeeking': 'onseeking', 84 | 'onStalled': 'onstalled', 85 | 'onSuspend': 'onsuspend', 86 | 'onTimeUpdate': 'ontimeupdate', 87 | 'onVolumeChange': 'onvolumechange', 88 | 'onWaiting': 'onwaiting', 89 | // Image events 90 | 'onLoad': 'onload', 91 | 'onError': 'onerror' 92 | }; 93 | var svgElements = { 94 | 'circle': true, 95 | 'clipPath': true, 96 | 'defs': true, 97 | 'ellipse': true, 98 | 'g': true, 99 | 'image': true, 100 | 'line': true, 101 | 'linearGradient': true, 102 | 'mask': true, 103 | 'path': true, 104 | 'pattern': true, 105 | 'polygon': true, 106 | 'polyline': true, 107 | 'radialGradient': true, 108 | 'rect': true, 109 | 'stop': true, 110 | 'svg': true, 111 | 'text': true, 112 | 'tspan': true 113 | }; 114 | UIBuilder.Fragment = "--fragment--"; 115 | var Component = /** @class */ (function () { 116 | function Component(props) { 117 | this.props = props; 118 | } 119 | Component.prototype.render = function () { 120 | return null; 121 | }; 122 | return Component; 123 | }()); 124 | UIBuilder.Component = Component; 125 | function createElement(type, props) { 126 | var children = []; 127 | for (var _i = 2; _i < arguments.length; _i++) { 128 | children[_i - 2] = arguments[_i]; 129 | } 130 | props = props || {}; 131 | var node; 132 | if (type === UIBuilder.Fragment) { 133 | return children; 134 | } 135 | else if (typeof type === 'function') { // Is it either a component class or a functional component? 136 | var _props = __assign(__assign({}, props), { children: children }); 137 | if (type.prototype.render) { // Is it a component class? 138 | var component = new type(_props); 139 | node = component.render(); 140 | applyComponentProps(component, props); 141 | } 142 | else { // It is a functional component 143 | node = type(_props); 144 | } 145 | } 146 | else { // It is an HTML or SVG element 147 | if (svgElements[type]) { 148 | node = document.createElementNS("http://www.w3.org/2000/svg", type); 149 | } 150 | else { 151 | node = document.createElement(type); 152 | } 153 | applyElementProps(node, props); 154 | appendChildrenRecursively(node, children); 155 | } 156 | return node; 157 | } 158 | UIBuilder.createElement = createElement; 159 | function appendChildrenRecursively(node, children) { 160 | for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { 161 | var child = children_1[_i]; 162 | if (child instanceof Node) { // Is it an HTML or SVG element? 163 | node.appendChild(child); 164 | } 165 | else if (Array.isArray(child)) { // example:
{items}
166 | appendChildrenRecursively(node, child); 167 | } 168 | else if (child === false) { 169 | // The value false is ignored, to allow conditional display using && operator 170 | } 171 | else if (child != null) { // if item is not null or undefined 172 | node.appendChild(document.createTextNode(child)); 173 | } 174 | } 175 | } 176 | function applyElementProps(node, props) { 177 | for (var prop in props) { 178 | var value = props[prop]; 179 | if (value == null) // if value is null or undefined 180 | continue; 181 | if (prop === 'ref') { 182 | if (typeof value === 'function') { 183 | value(node); 184 | } 185 | else { 186 | throw new Error("'ref' must be a function"); 187 | } 188 | } 189 | else if (eventMap.hasOwnProperty(prop)) { 190 | node[eventMap[prop]] = value; 191 | } 192 | else if (typeof value === 'function') { 193 | node.addEventListener(prop, value); 194 | } 195 | else if (prop === 'style' && typeof value === 'object') { // Example:
196 | for (var styleName in value) { 197 | node.style[styleName] = value[styleName]; 198 | } 199 | } 200 | else { 201 | var name_1 = attribMap.hasOwnProperty(prop) ? attribMap[prop] : prop; 202 | if (name_1 in node && typeof value === 'object') { 203 | // pass object-valued attributes to Web Components 204 | node[name_1] = value; // value is set without any type conversion 205 | } 206 | else { 207 | node.setAttribute(name_1, value); // value will be converted to string 208 | } 209 | } 210 | } 211 | } 212 | function applyComponentProps(component, props) { 213 | var ref = props['ref']; 214 | if (ref) { 215 | if (typeof ref === 'function') { 216 | ref(component); 217 | } 218 | else { 219 | throw new Error("'ref' must be a function"); 220 | } 221 | } 222 | } 223 | })(UIBuilder || (UIBuilder = {})); 224 | -------------------------------------------------------------------------------- /SimpleDemo/Models/Product.ts: -------------------------------------------------------------------------------- 1 | namespace Demo.Models { 2 | export class Product { 3 | constructor(public name: string, public price: string, public photoUrl: string) { 4 | } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SimpleDemo/SimpleDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0} 7 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 8 | Library 9 | bin 10 | v4.5.2 11 | full 12 | true 13 | Latest 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | web.config 54 | 55 | 56 | web.config 57 | 58 | 59 | 60 | 61 | 12.0 62 | 63 | 64 | UIBuilder 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | True 73 | True 74 | 53583 75 | / 76 | http://localhost:53583/ 77 | False 78 | False 79 | 80 | 81 | False 82 | 83 | 84 | 85 | 86 | 87 | false 88 | true 89 | 90 | 91 | true 92 | false 93 | 94 | 95 | 96 | copy $(SolutionDir)dist\*.js $(ProjectDir)Libs\. 97 | copy $(SolutionDir)dist\*.ts $(ProjectDir)typings\uibuilder\. 98 | 99 | -------------------------------------------------------------------------------- /SimpleDemo/Views/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /SimpleDemo/Views/DemoPage.tsx: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | function onMenuItemSelected(item: HTMLElement): void { 3 | console.log(`menu item clicked: ${item.innerText}`); 4 | } 5 | 6 | function Banner(props: { message: string }): JSX.Element { 7 | return ( 8 | <> 9 |
10 | {props.message} 11 |
12 | 13 | ); 14 | } 15 | 16 | export function demoPage(userId: string, products: Models.Product[]): JSX.Element { 17 | return ( 18 |
19 | {Views.loginPanel(userId)} 20 | 21 |
22 | 23 | 24 |
Monday
25 |
Tuesday
26 |
Wednesday
27 |
28 |
29 |
30 | 31 | 33 | 34 | 35 |
36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SimpleDemo/Views/LoginPanel.tsx: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | export function loginPanel(userId: string): JSX.Element { 3 | return ( 4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /SimpleDemo/Views/Product.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Demo.Views { 4 | interface ProductProps { 5 | product: Models.Product; 6 | } 7 | 8 | export class Product extends UIBuilder.Component { 9 | constructor(props: ProductProps) { 10 | super(props); 11 | } 12 | 13 | public render(): JSX.Element { 14 | return ( 15 |
16 |
17 |
{this.props.product.name}
18 |
{this.props.product.price}
19 |
20 |
21 |
22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SimpleDemo/Views/ProductList.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Demo.Views { 4 | export interface ProductListProps extends UIBuilder.Props { 5 | products: Models.Product[]; 6 | } 7 | 8 | export class ProductList extends UIBuilder.Component { 9 | constructor(props: ProductListProps) { 10 | super(props); 11 | } 12 | 13 | public render(): JSX.Element { 14 | const items = this.props.products.map(p => ); 15 | return ( 16 |
17 | {items} 18 |
19 | ); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SimpleDemo/Views/ReusableComponents/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | export interface DropdownProps extends UIBuilder.Props { 3 | title: string; 4 | } 5 | 6 | /** 7 | * A dropdown control has exactly one child which the dropdown will initially hide. 8 | * The child will be shown when the dropdown button is clicked. 9 | * The child must be focusable and it will be given focus when shown. 10 | * The child must be set to position: absolute so that it will be displayed over adjacent elements. 11 | * The child must hide itself on blur. 12 | */ 13 | export class Dropdown extends UIBuilder.Component { 14 | private child: HTMLElement; 15 | 16 | constructor(props: DropdownProps) { 17 | super(props); 18 | } 19 | 20 | private onClick(event: Event): void { 21 | this.child.classList.remove('hidden'); 22 | this.child.focus(); 23 | } 24 | 25 | public render(): JSX.Element { 26 | if (Array.isArray(this.props.children) && this.props.children.length != 1) { 27 | throw new Error("Dropdown must have exactly one child."); 28 | } 29 | 30 | this.child = this.props.children[0]; 31 | if (!this.child.style) { 32 | throw new Error("Child of Dropdown must be an HTMLElement"); 33 | } 34 | 35 | this.child.classList.add('hidden'); 36 | 37 | return ( 38 |
39 |
this.onClick(ev)}>{this.props.title}
40 | {this.child} 41 |
42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SimpleDemo/Views/ReusableComponents/Keys.ts: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | export const enum KeyCodes { 3 | Backspace = 8, 4 | Tab = 9, 5 | Enter = 13, 6 | Escape = 27, 7 | PageUp = 33, 8 | PageDown = 34, 9 | End = 35, 10 | Home = 36, 11 | LeftArrow = 37, 12 | UpArrow = 38, 13 | RightArrow = 39, 14 | DownArrow = 40, 15 | Delete = 46 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SimpleDemo/Views/ReusableComponents/PopupMenu.tsx: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | export interface PopupMenuProps extends UIBuilder.Props { 3 | onMenuItemSelected: (item: HTMLElement) => void; 4 | } 5 | 6 | export class PopupMenu extends UIBuilder.Component { 7 | private el: HTMLElement; 8 | 9 | constructor(props: PopupMenuProps) { 10 | super(props); 11 | } 12 | 13 | private onFocus(): void { 14 | const items = Array.from(this.el.querySelectorAll('.menu-item')); 15 | items.forEach(item => item.classList.remove('current')); 16 | items[0].classList.add('current'); 17 | } 18 | 19 | private onBlur(): void { 20 | this.el.classList.add("hidden"); 21 | } 22 | 23 | private setCurrentItem(index: number): void { 24 | const items = Array.from(this.el.querySelectorAll('.menu-item')); 25 | items.forEach(item => item.classList.remove('current')); 26 | if (index !== -1) 27 | items[index].classList.add('current'); 28 | } 29 | 30 | private onKeyDown(event: KeyboardEvent): void { 31 | const items = Array.from(this.el.querySelectorAll('.menu-item')); 32 | const current = this.el.querySelector('.current') as HTMLElement; 33 | let currentIndex = items.indexOf(current); 34 | switch (event.keyCode) { 35 | case KeyCodes.DownArrow: 36 | currentIndex = (currentIndex === -1 || currentIndex === items.length - 1) ? 0 : (currentIndex + 1); 37 | this.setCurrentItem(currentIndex); 38 | break; 39 | case KeyCodes.UpArrow: 40 | currentIndex = (currentIndex === -1 || currentIndex === 0) ? (items.length - 1) : (currentIndex - 1); 41 | this.setCurrentItem(currentIndex); 42 | break; 43 | case KeyCodes.Escape: 44 | this.el.classList.add('hidden'); 45 | break; 46 | case KeyCodes.Enter: 47 | if (current) { 48 | this.el.classList.add('hidden'); 49 | this.props.onMenuItemSelected(current); 50 | } 51 | break; 52 | } 53 | event.preventDefault(); 54 | } 55 | 56 | private onMouseOver(event: Event): void { 57 | const item = event.target as HTMLElement; 58 | if (item.classList.contains('menu-item')) { 59 | this.setCurrentItem(-1); 60 | item.classList.add('current'); 61 | } 62 | } 63 | 64 | private onClick(event: Event): void { 65 | const item = event.target as HTMLElement; 66 | if (item.classList.contains('menu-item')) { 67 | this.el.classList.add('hidden'); 68 | this.props.onMenuItemSelected(item); 69 | } 70 | } 71 | 72 | public render(): JSX.Element { 73 | return ( 74 |
this.el = el} 76 | onFocus={() => this.onFocus()} 77 | onBlur={() => this.onBlur()} 78 | onKeyDown={ev => this.onKeyDown(ev)} 79 | onMouseOver={ev => this.onMouseOver(ev)} 80 | onClick={ev => this.onClick(ev)}> 81 | {this.props.children} 82 |
83 | ); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /SimpleDemo/Views/ReusableComponents/Tabs.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Demo.Views { 4 | export interface TabsProps extends UIBuilder.Props { 5 | tabs: any[]; 6 | selectedIndex: number; 7 | onTabSelected: (index: number) => void; 8 | } 9 | 10 | export class Tabs extends UIBuilder.Component { 11 | constructor(props: TabsProps) { 12 | super(props); 13 | } 14 | 15 | private onClick(event: Event): void { 16 | const tab = event.target as HTMLElement; 17 | if (tab.classList.contains('tab')) { 18 | const tabs = event.currentTarget as HTMLElement; 19 | Array.from(tabs.querySelectorAll('.tab')).forEach(tab => tab.classList.remove('selected')); 20 | 21 | tab.classList.add('selected'); 22 | 23 | const index = tab.dataset['index']; 24 | this.props.onTabSelected(+index); 25 | } 26 | } 27 | 28 | public render(): JSX.Element { 29 | const items = [] as JSX.Element[]; 30 | for (let i = 0; i < this.props.tabs.length; i++) { 31 | const tab = this.props.tabs[i]; 32 | const className = (i === this.props.selectedIndex) ? "tab selected" : "tab"; 33 | items.push(
{tab.toString()}
); 34 | } 35 | return
this.onClick(ev)}>{items}
; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SimpleDemo/app.ts: -------------------------------------------------------------------------------- 1 | namespace Demo { 2 | export class DemoApp { 3 | private tabs = ["Store", "Products", "Support"]; 4 | 5 | constructor(private appBody: HTMLElement) { 6 | const products = [ 7 | new Models.Product('Hello Kitty', '$45.00', 'Assets/product1.png'), 8 | new Models.Product('Piggy Bank', '$57.00', 'Assets/product2.png'), 9 | new Models.Product('Trinket Box', '$63.00', 'Assets/product3.png') 10 | ]; 11 | 12 | const tabs = UIBuilder.createElement(Views.Tabs, { 13 | tabs: this.tabs, 14 | selectedIndex: 0, 15 | onTabSelected: index => this.onTabSelected(index) 16 | }) as HTMLElement; 17 | this.appBody.appendChild(tabs); 18 | 19 | const page = Demo.Views.demoPage("someone@example.com", products); 20 | this.appBody.appendChild(page); 21 | } 22 | 23 | private onTabSelected(index: number): void { 24 | console.log(`${this.tabs[index]} tab selected`); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SimpleDemo/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | UIBuilder Demo 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /SimpleDemo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "out": "Generated/demo.js", 5 | "sourceMap": true, 6 | "lib": [ "es6", "dom" ], 7 | "jsx": "react", 8 | "reactNamespace": "UIBuilder" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SimpleDemo/typings/uibuilder/uibuilder-1.7.0.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | type Element = HTMLElement | SVGAElement; 3 | } 4 | declare namespace UIBuilder { 5 | const Fragment = "--fragment--"; 6 | class Component

{ 7 | protected props: P; 8 | constructor(props: P); 9 | render(): JSX.Element; 10 | } 11 | interface Props { 12 | children?: any; 13 | ref?: (instance: T) => void; 14 | } 15 | function createElement

>>(type: any, props: P, ...children: any[]): JSX.Element | JSX.Element[]; 16 | } 17 | -------------------------------------------------------------------------------- /SimpleDemo/web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /SimpleDemo/web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /SimpleDemo/web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /UIBuilder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleDemo", "SimpleDemo\SimpleDemo.csproj", "{3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {CB43FCC4-8472-4576-A522-4DCDA54A7040} = {CB43FCC4-8472-4576-A522-4DCDA54A7040} 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIBuilder", "UIBuilder\UIBuilder.csproj", "{CB43FCC4-8472-4576-A522-4DCDA54A7040}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebComponentsDemo", "WebComponentsDemo\WebComponentsDemo.csproj", "{AB32527F-84DA-4A26-96AB-85D35F3C494F}" 14 | ProjectSection(ProjectDependencies) = postProject 15 | {CB43FCC4-8472-4576-A522-4DCDA54A7040} = {CB43FCC4-8472-4576-A522-4DCDA54A7040} 16 | EndProjectSection 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Demos", "Demos", "{DBDEB588-6609-4A40-97A4-0F683057E97A}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {CB43FCC4-8472-4576-A522-4DCDA54A7040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {CB43FCC4-8472-4576-A522-4DCDA54A7040}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {CB43FCC4-8472-4576-A522-4DCDA54A7040}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {CB43FCC4-8472-4576-A522-4DCDA54A7040}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {AB32527F-84DA-4A26-96AB-85D35F3C494F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {AB32527F-84DA-4A26-96AB-85D35F3C494F}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {AB32527F-84DA-4A26-96AB-85D35F3C494F}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {AB32527F-84DA-4A26-96AB-85D35F3C494F}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(NestedProjects) = preSolution 43 | {3BF1ECBF-4EF7-4B3A-A4D5-8E604F4ECDA0} = {DBDEB588-6609-4A40-97A4-0F683057E97A} 44 | {AB32527F-84DA-4A26-96AB-85D35F3C494F} = {DBDEB588-6609-4A40-97A4-0F683057E97A} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /UIBuilder/JSX.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | type Element = HTMLElement | SVGAElement; 3 | } 4 | -------------------------------------------------------------------------------- /UIBuilder/UIBuilder.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | {CB43FCC4-8472-4576-A522-4DCDA54A7040} 7 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 8 | Library 9 | bin 10 | v4.5.2 11 | full 12 | true 13 | Latest 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 12.0 34 | 35 | 36 | UIBuilder 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | True 46 | 53663 47 | / 48 | http://localhost:53663/ 49 | False 50 | False 51 | 52 | 53 | False 54 | 55 | 56 | 57 | 58 | 59 | false 60 | true 61 | 62 | 63 | true 64 | false 65 | 66 | 67 | -------------------------------------------------------------------------------- /UIBuilder/UIBuilder.ts: -------------------------------------------------------------------------------- 1 | namespace UIBuilder { 2 | const attribMap = { 3 | 'htmlFor': 'for', 4 | 'className': 'class', 5 | 'defaultValue': 'value', 6 | 'defaultChecked': 'checked' 7 | }; 8 | 9 | const eventMap = { 10 | // Clipboard events 11 | 'onCopy': 'oncopy', 12 | 'onCut': 'oncut', 13 | 'onPaste': 'onpaste', 14 | // Keyboard events 15 | 'onKeyDown': 'onkeydown', 16 | 'onKeyPress': 'onkeypress', 17 | 'onKeyUp': 'onkeyup', 18 | // Focus events 19 | 'onFocus': 'onfocus', 20 | 'onBlur': 'onblur', 21 | // Form events 22 | 'onChange': 'onchange', 23 | 'onInput': 'oninput', 24 | 'onSubmit': 'onsubmit', 25 | // Mouse events 26 | 'onClick': 'onclick', 27 | 'onContextMenu': 'oncontextmenu', 28 | 'onDoubleClick': 'ondblclick', 29 | 'onDrag': 'ondrag', 30 | 'onDragEnd': 'ondragend', 31 | 'onDragEnter': 'ondragenter', 32 | 'onDragExit': 'ondragexit', 33 | 'onDragLeave': 'ondragleave', 34 | 'onDragOver': 'ondragover', 35 | 'onDragStart': 'ondragstart', 36 | 'onDrop': 'ondrop', 37 | 'onMouseDown': 'onmousedown', 38 | 'onMouseEnter': 'onmouseenter', 39 | 'onMouseLeave': 'onmouseleave', 40 | 'onMouseMove': 'onmousemove', 41 | 'onMouseOut': 'onmouseout', 42 | 'onMouseOver': 'onmouseover', 43 | 'onMouseUp': 'onmouseup', 44 | // Selection events 45 | 'onSelect': 'onselect', 46 | // Touch events 47 | 'onTouchCancel': 'ontouchcancel', 48 | 'onTouchEnd': 'ontouchend', 49 | 'onTouchMove': 'ontouchmove', 50 | 'onTouchStart': 'ontouchstart', 51 | // UI events 52 | 'onScroll': 'onscroll', 53 | // Wheel events 54 | 'onWheel': 'onwheel', 55 | // Media events 56 | 'onAbort': 'onabort', 57 | 'onCanPlay': 'oncanplay', 58 | 'onCanPlayThrough': 'oncanplaythrough', 59 | 'onDurationChange': 'ondurationchange', 60 | 'onEmptied': 'onemptied', 61 | 'onEncrypted': 'onencrypted', 62 | 'onEnded': 'onended', 63 | 'onLoadedData': 'onloadeddata', 64 | 'onLoadedMetadata': 'onloadedmetadata', 65 | 'onLoadStart': 'onloadstart', 66 | 'onPause': 'onpause', 67 | 'onPlay': 'onplay', 68 | 'onPlaying': 'onplaying', 69 | 'onProgress': 'onprogress', 70 | 'onRateChange': 'onratechange', 71 | 'onSeeked': 'onseeked', 72 | 'onSeeking': 'onseeking', 73 | 'onStalled': 'onstalled', 74 | 'onSuspend': 'onsuspend', 75 | 'onTimeUpdate': 'ontimeupdate', 76 | 'onVolumeChange': 'onvolumechange', 77 | 'onWaiting': 'onwaiting', 78 | // Image events 79 | 'onLoad': 'onload', 80 | 'onError': 'onerror' 81 | }; 82 | 83 | const svgElements = { 84 | 'circle': true, 85 | 'clipPath': true, 86 | 'defs': true, 87 | 'ellipse': true, 88 | 'g': true, 89 | 'image': true, 90 | 'line': true, 91 | 'linearGradient': true, 92 | 'mask': true, 93 | 'path': true, 94 | 'pattern': true, 95 | 'polygon': true, 96 | 'polyline': true, 97 | 'radialGradient': true, 98 | 'rect': true, 99 | 'stop': true, 100 | 'svg': true, 101 | 'text': true, 102 | 'tspan': true 103 | }; 104 | 105 | export const Fragment = "--fragment--"; 106 | 107 | export class Component

{ 108 | constructor(protected props: P) { 109 | } 110 | 111 | public render(): JSX.Element { 112 | return null; 113 | } 114 | } 115 | 116 | export interface Props { 117 | children?: any; 118 | ref?: (instance: T) => void; 119 | } 120 | 121 | export function createElement

>>(type: any, props: P, ...children: any[]): JSX.Element | JSX.Element[] { 122 | props = props ||

{}; 123 | let node: JSX.Element; 124 | if (type === Fragment) { 125 | return children; 126 | } 127 | else if (typeof type === 'function') { // Is it either a component class or a functional component? 128 | const _props = { ...props, children } as P; 129 | if (type.prototype.render) { // Is it a component class? 130 | const component: Component

= new type(_props); 131 | node = component.render(); 132 | applyComponentProps

(component, props); 133 | } 134 | else { // It is a functional component 135 | node = type(_props); 136 | } 137 | } 138 | else { // It is an HTML or SVG element 139 | if (svgElements[type]) { 140 | node = document.createElementNS("http://www.w3.org/2000/svg", type); 141 | } 142 | else { 143 | node = document.createElement(type); 144 | } 145 | applyElementProps(node, props); 146 | appendChildrenRecursively(node, children); 147 | } 148 | return node; 149 | } 150 | 151 | function appendChildrenRecursively(node: JSX.Element, children: any[]): void { 152 | for (const child of children) { 153 | if (child instanceof Node) { // Is it an HTML or SVG element? 154 | node.appendChild(child); 155 | } 156 | else if (Array.isArray(child)) { // example:

{items}
157 | appendChildrenRecursively(node, child); 158 | } 159 | else if (child === false) { 160 | // The value false is ignored, to allow conditional display using && operator 161 | } 162 | else if (child != null) { // if item is not null or undefined 163 | node.appendChild(document.createTextNode(child)); 164 | } 165 | } 166 | } 167 | 168 | function applyElementProps(node: JSX.Element, props: Object): void { 169 | for (const prop in props) { 170 | const value = props[prop]; 171 | if (value == null) // if value is null or undefined 172 | continue; 173 | if (prop === 'ref') { 174 | if (typeof value === 'function') { 175 | value(node); 176 | } 177 | else { 178 | throw new Error("'ref' must be a function"); 179 | } 180 | } 181 | else if (eventMap.hasOwnProperty(prop)) { 182 | node[eventMap[prop]] = value; 183 | } 184 | else if (typeof value === 'function') { 185 | node.addEventListener(prop, value); 186 | } 187 | else if (prop === 'style' && typeof value === 'object') { // Example:
188 | for (const styleName in value) { 189 | (node).style[styleName] = value[styleName]; 190 | } 191 | } 192 | else { 193 | const name = attribMap.hasOwnProperty(prop) ? attribMap[prop] : prop; 194 | if (name in node && typeof value === 'object') { 195 | // pass object-valued attributes to Web Components 196 | node[name] = value; // value is set without any type conversion 197 | } 198 | else { 199 | node.setAttribute(name, value); // value will be converted to string 200 | } 201 | } 202 | } 203 | } 204 | 205 | function applyComponentProps

(component: Component

, props: Object): void { 206 | const ref = props['ref']; 207 | if (ref) { 208 | if (typeof ref === 'function') { 209 | ref(component); 210 | } 211 | else { 212 | throw new Error("'ref' must be a function"); 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /UIBuilder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "out": "../dist/uibuilder-1.7.0.js", 5 | "declaration": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /WebComponentsDemo/.gitignore: -------------------------------------------------------------------------------- 1 | app.js 2 | -------------------------------------------------------------------------------- /WebComponentsDemo/Assets/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Segoe UI', sans-serif; 3 | } 4 | 5 | h1 { 6 | color: #2b2b2b; 7 | } 8 | 9 | .prompt { 10 | margin-bottom: 5px; 11 | } 12 | 13 | .button-bar { 14 | display: inline-block; 15 | width: 500px; 16 | text-align: right; 17 | margin-top: 7px; 18 | } 19 | 20 | button { 21 | background-color: #0072C6; 22 | color: white; 23 | border: none; 24 | padding: 5px 35px 5px 35px; 25 | font-size: 14px; 26 | margin-left: 7px; 27 | } 28 | 29 | button:hover { 30 | background-color: #0081e0; 31 | } 32 | 33 | .results { 34 | margin-top: 30px; 35 | font-family: Consolas, Courier New, Courier, monospace; 36 | } 37 | 38 | .browser-warning { 39 | border: 1px solid #999; 40 | border-radius: 5px; 41 | padding: 15px; 42 | } 43 | 44 | .browser-warning i { 45 | color: firebrick; 46 | font-size: 18px; 47 | } 48 | -------------------------------------------------------------------------------- /WebComponentsDemo/Assets/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} -------------------------------------------------------------------------------- /WebComponentsDemo/Libs/uibuilder-1.7.0.js: -------------------------------------------------------------------------------- 1 | var __assign = (this && this.__assign) || function () { 2 | __assign = Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | return __assign.apply(this, arguments); 11 | }; 12 | var UIBuilder; 13 | (function (UIBuilder) { 14 | var attribMap = { 15 | 'htmlFor': 'for', 16 | 'className': 'class', 17 | 'defaultValue': 'value', 18 | 'defaultChecked': 'checked' 19 | }; 20 | var eventMap = { 21 | // Clipboard events 22 | 'onCopy': 'oncopy', 23 | 'onCut': 'oncut', 24 | 'onPaste': 'onpaste', 25 | // Keyboard events 26 | 'onKeyDown': 'onkeydown', 27 | 'onKeyPress': 'onkeypress', 28 | 'onKeyUp': 'onkeyup', 29 | // Focus events 30 | 'onFocus': 'onfocus', 31 | 'onBlur': 'onblur', 32 | // Form events 33 | 'onChange': 'onchange', 34 | 'onInput': 'oninput', 35 | 'onSubmit': 'onsubmit', 36 | // Mouse events 37 | 'onClick': 'onclick', 38 | 'onContextMenu': 'oncontextmenu', 39 | 'onDoubleClick': 'ondblclick', 40 | 'onDrag': 'ondrag', 41 | 'onDragEnd': 'ondragend', 42 | 'onDragEnter': 'ondragenter', 43 | 'onDragExit': 'ondragexit', 44 | 'onDragLeave': 'ondragleave', 45 | 'onDragOver': 'ondragover', 46 | 'onDragStart': 'ondragstart', 47 | 'onDrop': 'ondrop', 48 | 'onMouseDown': 'onmousedown', 49 | 'onMouseEnter': 'onmouseenter', 50 | 'onMouseLeave': 'onmouseleave', 51 | 'onMouseMove': 'onmousemove', 52 | 'onMouseOut': 'onmouseout', 53 | 'onMouseOver': 'onmouseover', 54 | 'onMouseUp': 'onmouseup', 55 | // Selection events 56 | 'onSelect': 'onselect', 57 | // Touch events 58 | 'onTouchCancel': 'ontouchcancel', 59 | 'onTouchEnd': 'ontouchend', 60 | 'onTouchMove': 'ontouchmove', 61 | 'onTouchStart': 'ontouchstart', 62 | // UI events 63 | 'onScroll': 'onscroll', 64 | // Wheel events 65 | 'onWheel': 'onwheel', 66 | // Media events 67 | 'onAbort': 'onabort', 68 | 'onCanPlay': 'oncanplay', 69 | 'onCanPlayThrough': 'oncanplaythrough', 70 | 'onDurationChange': 'ondurationchange', 71 | 'onEmptied': 'onemptied', 72 | 'onEncrypted': 'onencrypted', 73 | 'onEnded': 'onended', 74 | 'onLoadedData': 'onloadeddata', 75 | 'onLoadedMetadata': 'onloadedmetadata', 76 | 'onLoadStart': 'onloadstart', 77 | 'onPause': 'onpause', 78 | 'onPlay': 'onplay', 79 | 'onPlaying': 'onplaying', 80 | 'onProgress': 'onprogress', 81 | 'onRateChange': 'onratechange', 82 | 'onSeeked': 'onseeked', 83 | 'onSeeking': 'onseeking', 84 | 'onStalled': 'onstalled', 85 | 'onSuspend': 'onsuspend', 86 | 'onTimeUpdate': 'ontimeupdate', 87 | 'onVolumeChange': 'onvolumechange', 88 | 'onWaiting': 'onwaiting', 89 | // Image events 90 | 'onLoad': 'onload', 91 | 'onError': 'onerror' 92 | }; 93 | var svgElements = { 94 | 'circle': true, 95 | 'clipPath': true, 96 | 'defs': true, 97 | 'ellipse': true, 98 | 'g': true, 99 | 'image': true, 100 | 'line': true, 101 | 'linearGradient': true, 102 | 'mask': true, 103 | 'path': true, 104 | 'pattern': true, 105 | 'polygon': true, 106 | 'polyline': true, 107 | 'radialGradient': true, 108 | 'rect': true, 109 | 'stop': true, 110 | 'svg': true, 111 | 'text': true, 112 | 'tspan': true 113 | }; 114 | UIBuilder.Fragment = "--fragment--"; 115 | var Component = /** @class */ (function () { 116 | function Component(props) { 117 | this.props = props; 118 | } 119 | Component.prototype.render = function () { 120 | return null; 121 | }; 122 | return Component; 123 | }()); 124 | UIBuilder.Component = Component; 125 | function createElement(type, props) { 126 | var children = []; 127 | for (var _i = 2; _i < arguments.length; _i++) { 128 | children[_i - 2] = arguments[_i]; 129 | } 130 | props = props || {}; 131 | var node; 132 | if (type === UIBuilder.Fragment) { 133 | return children; 134 | } 135 | else if (typeof type === 'function') { // Is it either a component class or a functional component? 136 | var _props = __assign(__assign({}, props), { children: children }); 137 | if (type.prototype.render) { // Is it a component class? 138 | var component = new type(_props); 139 | node = component.render(); 140 | applyComponentProps(component, props); 141 | } 142 | else { // It is a functional component 143 | node = type(_props); 144 | } 145 | } 146 | else { // It is an HTML or SVG element 147 | if (svgElements[type]) { 148 | node = document.createElementNS("http://www.w3.org/2000/svg", type); 149 | } 150 | else { 151 | node = document.createElement(type); 152 | } 153 | applyElementProps(node, props); 154 | appendChildrenRecursively(node, children); 155 | } 156 | return node; 157 | } 158 | UIBuilder.createElement = createElement; 159 | function appendChildrenRecursively(node, children) { 160 | for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { 161 | var child = children_1[_i]; 162 | if (child instanceof Node) { // Is it an HTML or SVG element? 163 | node.appendChild(child); 164 | } 165 | else if (Array.isArray(child)) { // example:

{items}
166 | appendChildrenRecursively(node, child); 167 | } 168 | else if (child === false) { 169 | // The value false is ignored, to allow conditional display using && operator 170 | } 171 | else if (child != null) { // if item is not null or undefined 172 | node.appendChild(document.createTextNode(child)); 173 | } 174 | } 175 | } 176 | function applyElementProps(node, props) { 177 | for (var prop in props) { 178 | var value = props[prop]; 179 | if (value == null) // if value is null or undefined 180 | continue; 181 | if (prop === 'ref') { 182 | if (typeof value === 'function') { 183 | value(node); 184 | } 185 | else { 186 | throw new Error("'ref' must be a function"); 187 | } 188 | } 189 | else if (eventMap.hasOwnProperty(prop)) { 190 | node[eventMap[prop]] = value; 191 | } 192 | else if (typeof value === 'function') { 193 | node.addEventListener(prop, value); 194 | } 195 | else if (prop === 'style' && typeof value === 'object') { // Example:
196 | for (var styleName in value) { 197 | node.style[styleName] = value[styleName]; 198 | } 199 | } 200 | else { 201 | var name_1 = attribMap.hasOwnProperty(prop) ? attribMap[prop] : prop; 202 | if (name_1 in node && typeof value === 'object') { 203 | // pass object-valued attributes to Web Components 204 | node[name_1] = value; // value is set without any type conversion 205 | } 206 | else { 207 | node.setAttribute(name_1, value); // value will be converted to string 208 | } 209 | } 210 | } 211 | } 212 | function applyComponentProps(component, props) { 213 | var ref = props['ref']; 214 | if (ref) { 215 | if (typeof ref === 'function') { 216 | ref(component); 217 | } 218 | else { 219 | throw new Error("'ref' must be a function"); 220 | } 221 | } 222 | } 223 | })(UIBuilder || (UIBuilder = {})); 224 | -------------------------------------------------------------------------------- /WebComponentsDemo/Views/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /WebComponentsDemo/Views/DemoPage.tsx: -------------------------------------------------------------------------------- 1 | namespace Demo.Views { 2 | function onItemAdded(ev): void { 3 | console.log(`item added: ${ev.detail}`); 4 | } 5 | 6 | export function demoPage(items: string[]): JSX.Element { 7 | return ( 8 |
9 |

Web Components Demo

10 |
11 |
Enter your favorite artists, separated by commas:
12 | onItemAdded(ev)}> 13 |
14 |
15 | 16 | 17 |
18 |

19 |             
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WebComponentsDemo/WebComponentsDemo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | {AB32527F-84DA-4A26-96AB-85D35F3C494F} 7 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 8 | Library 9 | bin 10 | v4.5.2 11 | full 12 | true 13 | Latest 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | web.config 44 | 45 | 46 | web.config 47 | 48 | 49 | 50 | 51 | 12.0 52 | 53 | 54 | WebComponentsDemo 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | True 63 | True 64 | 58828 65 | / 66 | http://localhost:58828/ 67 | False 68 | False 69 | 70 | 71 | False 72 | 73 | 74 | 75 | 76 | 77 | false 78 | true 79 | 80 | 81 | true 82 | false 83 | 84 | 85 | 86 | copy $(SolutionDir)dist\*.js $(ProjectDir)Libs\. 87 | copy $(SolutionDir)dist\*.ts $(ProjectDir)typings\browser\ambient\uibuilder\. 88 | 89 | 90 | -------------------------------------------------------------------------------- /WebComponentsDemo/app.ts: -------------------------------------------------------------------------------- 1 | namespace Demo { 2 | export class App { 3 | constructor(private appContainer: HTMLElement) { 4 | const items = [ 5 | 'Taylor Swift', 6 | 'Rihanna', 7 | 'Beyoncé', 8 | 'Britney Spears' 9 | ]; 10 | const page = Demo.Views.demoPage(items); 11 | this.appContainer.appendChild(page); 12 | 13 | if (window.customElements && this.appContainer.attachShadow) { 14 | document.querySelector(".browser-warning").remove(); 15 | } 16 | 17 | this.appContainer.addEventListener('click', ev => this.onAppContainerClick(ev)); 18 | this.appContainer.querySelector("#artists").addEventListener("onitemadded", ev => this.onItemAdded(ev)); 19 | } 20 | 21 | private onAppContainerClick(ev: MouseEvent): void { 22 | const target = ev.target as HTMLElement; 23 | if (target.id === "set") 24 | this.onSetClick(); 25 | else if (target.id === "get") 26 | this.onGetClick(); 27 | } 28 | 29 | private onItemAdded(ev: Event): void { 30 | const item = (ev as any).detail; 31 | document.querySelector('.result').textContent = `item added: ${item}`; 32 | } 33 | 34 | private onSetClick(): void { 35 | const artists = [ 36 | 'Angus Young', 37 | 'Slash', 38 | 'Alex Lifeson', 39 | 'Richie Sambora' 40 | ]; 41 | (document.getElementById("artists") as any).items = artists; 42 | } 43 | 44 | private onGetClick(): void { 45 | const items = (document.getElementById("artists") as any).items; 46 | document.querySelector('.result').textContent = JSON.stringify(items); 47 | } 48 | } 49 | } 50 | 51 | window.onload = () => { 52 | const el = document.getElementById('app-container'); 53 | const app = new Demo.App(el); 54 | }; 55 | -------------------------------------------------------------------------------- /WebComponentsDemo/elements/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | -------------------------------------------------------------------------------- /WebComponentsDemo/elements/ZxListEditor.tsx: -------------------------------------------------------------------------------- 1 | class ZxListEditor extends HTMLElement { 2 | private container: HTMLDivElement; 3 | private textInput: HTMLInputElement; 4 | private minInputWidth = 75; 5 | private _items: any[]; 6 | 7 | constructor() { 8 | super(); 9 | this.attachShadow({ mode: 'open' }); 10 | 11 | const container = ( 12 |
this.container = el} 13 | onKeyDown={ev => this.onContainerKeyDown(ev)} onClick={ev => this.onContainerClick(ev)}> 14 | this.textInput = el} onKeyDown={ev => this.onTextInputKeyDown(ev)} /> 15 |
16 | ); 17 | this.shadowRoot.appendChild(container); 18 | 19 | this.shadowRoot.appendChild(); 20 | } 21 | 22 | public get items(): any[] { 23 | if (this.isConnected) { 24 | const items = Array.from(this.container.querySelectorAll('.item')); 25 | this._items = items.map(item => item.textContent); 26 | } 27 | return this._items; 28 | } 29 | 30 | public set items(value: any[]) { 31 | this._items = value; 32 | if (this.isConnected) { 33 | this.displayItems(); 34 | } 35 | } 36 | 37 | public connectedCallback(): void { 38 | this.displayItems(); 39 | 40 | this.resizeTextInput(); 41 | } 42 | 43 | public disconnectedCallback(): void { 44 | } 45 | 46 | private displayItems(): void { 47 | const items = this.container.querySelectorAll(".item"); 48 | items.forEach(item => item.remove()); 49 | if (this._items instanceof Array) { 50 | this._items.forEach(item => this.finalizeItem(item)); 51 | } 52 | } 53 | 54 | private onContainerClick(ev: MouseEvent): void { 55 | const target = ev.target as HTMLElement; 56 | if (target.classList.contains('delete-item-button')) { 57 | this.onDeleteItem(ev); 58 | return; 59 | } 60 | let item: HTMLElement; 61 | if (target.classList.contains('item')) 62 | item = target; 63 | else if (target.parentElement && target.parentElement.classList.contains('item')) 64 | item = target.parentElement; 65 | if (item) { 66 | this.deselectAll(); 67 | item.classList.add('selected-item'); 68 | this.textInput.blur(); 69 | } 70 | else { 71 | this.deselectAll(); 72 | this.textInput.focus(); 73 | } 74 | } 75 | 76 | private onContainerKeyDown(ev: KeyboardEvent): void { 77 | const selected = this.container.querySelector('.selected-item'); 78 | let items = Array.from(this.container.querySelectorAll('.item')); 79 | let index = items.indexOf(selected); 80 | if (index == -1) 81 | return; 82 | switch (ev.key) { 83 | case "Backspace": 84 | case "Delete": 85 | selected.remove(); 86 | this.resizeTextInput(); 87 | break; 88 | case "LeftArrow": 89 | if (index > 0) 90 | index--; 91 | break; 92 | case "RightArrow": 93 | index++; 94 | break; 95 | case "Home": 96 | index = 0; 97 | break; 98 | case "End": 99 | index = items.length - 1; 100 | break; 101 | } 102 | this.deselectAll(); 103 | items = Array.from(this.container.querySelectorAll('.item')); 104 | if (index >= 0 && index < items.length) { 105 | const selectedItem = items[index]; 106 | selectedItem.classList.add('selected-item'); 107 | this.container.focus(); 108 | } 109 | else { 110 | this.textInput.focus(); 111 | } 112 | } 113 | 114 | private onTextInputKeyDown(ev: KeyboardEvent): void { 115 | const text = this.textInput.value; 116 | if (ev.key === "Backspace") { 117 | if (text.length === 0) { 118 | // Pressing backspace when text input is empty should select the item before the text input. 119 | this.deselectAll(); 120 | const items = Array.from(this.container.querySelectorAll('.item')); 121 | if (items.length) { 122 | const last = items[items.length - 1]; 123 | last.classList.add('selected-item'); 124 | this.container.focus(); 125 | } 126 | ev.preventDefault(); 127 | } 128 | } 129 | else if (this.isItemSeparator(ev.key)) { 130 | ev.preventDefault(); 131 | if (text) { 132 | this.finalizeItem(text); 133 | 134 | const event = new CustomEvent('onitemadded', { detail: text }); 135 | this.dispatchEvent(event); 136 | } 137 | } 138 | else { 139 | this.deselectAll(); 140 | } 141 | } 142 | 143 | private deselectAll(): void { 144 | this.container.querySelectorAll('.selected-item').forEach(item => item.classList.remove('selected-item')); 145 | } 146 | 147 | private finalizeItem(item: any): void { 148 | if (!item) { 149 | return; 150 | } 151 | const el = this.createItemElement(item.toString()); 152 | this.container.insertBefore(el, this.textInput); 153 | this.textInput.value = ''; 154 | this.resizeTextInput(); 155 | this.scrollToBottom(); 156 | } 157 | 158 | private scrollToBottom(): void { 159 | this.container.scrollTop = this.container.scrollHeight; 160 | } 161 | 162 | private onDeleteItem(ev: MouseEvent): void { 163 | const item = (ev.target as HTMLElement).parentElement; 164 | if (item.classList.contains('item')) { 165 | item.remove(); 166 | this.resizeTextInput(); 167 | window.setTimeout(() => this.textInput.focus(), 0); 168 | } 169 | } 170 | 171 | private createItemElement(text: string): Element { 172 | return ( 173 |
174 | {text} 175 | 176 |
177 | ); 178 | } 179 | 180 | private resizeTextInput(): void { 181 | this.textInput.style.width = this.minInputWidth + 'px'; 182 | const containerWidth = this.container.clientWidth; 183 | const availableWidth = containerWidth - this.textInput.offsetLeft; 184 | this.textInput.style.width = availableWidth + 'px'; 185 | } 186 | 187 | private isItemSeparator(key: string): boolean { 188 | return key === "," || key === "Enter"; 189 | } 190 | } 191 | 192 | window.customElements.define('zx-listeditor', ZxListEditor); 193 | 194 | const css = ` 195 | :host { 196 | } 197 | 198 | .container { 199 | border: 1px solid #b3b3b3; 200 | display: inline-block; 201 | box-sizing: border-box; 202 | font-size: 0; 203 | width: 500px; 204 | height: 175px; 205 | padding: 3px; 206 | overflow-y: auto; 207 | overflow-x: hidden; 208 | outline: none; 209 | } 210 | 211 | input[type=text] { 212 | outline: none; 213 | border: none; 214 | margin: 0; 215 | padding: 0; 216 | font-size: 14px; 217 | } 218 | 219 | .item { 220 | display: inline-block; 221 | font-size: 14px; 222 | background-color: #E4EEFA; 223 | border: 1px solid #B3CAF1; 224 | padding: 0px 5px 1px 5px; 225 | border-radius: 10px; 226 | margin-right: 3px; 227 | margin-bottom: 3px; 228 | cursor: default; 229 | } 230 | 231 | .selected-item { 232 | background-color: #C7DEF9; 233 | border-color: #A0BCE8; 234 | } 235 | 236 | .delete-item-button:after { 237 | margin-left: 3px; 238 | color: #999; 239 | font-family: 'FontAwesome'; 240 | content: "\f00d"; 241 | } 242 | 243 | .delete-item-button:hover:after { 244 | color: #555; 245 | } 246 | `; -------------------------------------------------------------------------------- /WebComponentsDemo/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wisercoder/uibuilder/6bf743ede9227a61b000f3ad0a3e167e06b3cae7/WebComponentsDemo/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /WebComponentsDemo/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Web Components Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 18 | This demo requires a browser that supports 19 | Web Components. 20 | As of June 2019, only Chrome, Firefox, Safari and Opera meet this requirement. 21 | More information can be found on webcomponents.org 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /WebComponentsDemo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "target": "es6", 6 | "jsx": "react", 7 | "reactNamespace": "UIBuilder" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /WebComponentsDemo/typings/browser/ambient/shadow-dom/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Element { 2 | content: any; 3 | } 4 | 5 | interface ShadowRoot extends DocumentFragment { 6 | } 7 | 8 | interface HTMLElement extends Element { 9 | isConnected: boolean; 10 | shadowRoot: ShadowRoot; 11 | attachShadow(options: any): any; 12 | } 13 | -------------------------------------------------------------------------------- /WebComponentsDemo/typings/browser/ambient/uibuilder/uibuilder-1.7.0.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | type Element = HTMLElement | SVGAElement; 3 | } 4 | declare namespace UIBuilder { 5 | const Fragment = "--fragment--"; 6 | class Component

{ 7 | protected props: P; 8 | constructor(props: P); 9 | render(): JSX.Element; 10 | } 11 | interface Props { 12 | children?: any; 13 | ref?: (instance: T) => void; 14 | } 15 | function createElement

>>(type: any, props: P, ...children: any[]): JSX.Element | JSX.Element[]; 16 | } 17 | -------------------------------------------------------------------------------- /WebComponentsDemo/web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /WebComponentsDemo/web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /WebComponentsDemo/web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dist/uibuilder-1.7.0.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace JSX { 2 | type Element = HTMLElement | SVGAElement; 3 | } 4 | declare namespace UIBuilder { 5 | const Fragment = "--fragment--"; 6 | class Component

{ 7 | protected props: P; 8 | constructor(props: P); 9 | render(): JSX.Element; 10 | } 11 | interface Props { 12 | children?: any; 13 | ref?: (instance: T) => void; 14 | } 15 | function createElement

>>(type: any, props: P, ...children: any[]): JSX.Element | JSX.Element[]; 16 | } 17 | -------------------------------------------------------------------------------- /dist/uibuilder-1.7.0.js: -------------------------------------------------------------------------------- 1 | var __assign = (this && this.__assign) || function () { 2 | __assign = Object.assign || function(t) { 3 | for (var s, i = 1, n = arguments.length; i < n; i++) { 4 | s = arguments[i]; 5 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 6 | t[p] = s[p]; 7 | } 8 | return t; 9 | }; 10 | return __assign.apply(this, arguments); 11 | }; 12 | var UIBuilder; 13 | (function (UIBuilder) { 14 | var attribMap = { 15 | 'htmlFor': 'for', 16 | 'className': 'class', 17 | 'defaultValue': 'value', 18 | 'defaultChecked': 'checked' 19 | }; 20 | var eventMap = { 21 | // Clipboard events 22 | 'onCopy': 'oncopy', 23 | 'onCut': 'oncut', 24 | 'onPaste': 'onpaste', 25 | // Keyboard events 26 | 'onKeyDown': 'onkeydown', 27 | 'onKeyPress': 'onkeypress', 28 | 'onKeyUp': 'onkeyup', 29 | // Focus events 30 | 'onFocus': 'onfocus', 31 | 'onBlur': 'onblur', 32 | // Form events 33 | 'onChange': 'onchange', 34 | 'onInput': 'oninput', 35 | 'onSubmit': 'onsubmit', 36 | // Mouse events 37 | 'onClick': 'onclick', 38 | 'onContextMenu': 'oncontextmenu', 39 | 'onDoubleClick': 'ondblclick', 40 | 'onDrag': 'ondrag', 41 | 'onDragEnd': 'ondragend', 42 | 'onDragEnter': 'ondragenter', 43 | 'onDragExit': 'ondragexit', 44 | 'onDragLeave': 'ondragleave', 45 | 'onDragOver': 'ondragover', 46 | 'onDragStart': 'ondragstart', 47 | 'onDrop': 'ondrop', 48 | 'onMouseDown': 'onmousedown', 49 | 'onMouseEnter': 'onmouseenter', 50 | 'onMouseLeave': 'onmouseleave', 51 | 'onMouseMove': 'onmousemove', 52 | 'onMouseOut': 'onmouseout', 53 | 'onMouseOver': 'onmouseover', 54 | 'onMouseUp': 'onmouseup', 55 | // Selection events 56 | 'onSelect': 'onselect', 57 | // Touch events 58 | 'onTouchCancel': 'ontouchcancel', 59 | 'onTouchEnd': 'ontouchend', 60 | 'onTouchMove': 'ontouchmove', 61 | 'onTouchStart': 'ontouchstart', 62 | // UI events 63 | 'onScroll': 'onscroll', 64 | // Wheel events 65 | 'onWheel': 'onwheel', 66 | // Media events 67 | 'onAbort': 'onabort', 68 | 'onCanPlay': 'oncanplay', 69 | 'onCanPlayThrough': 'oncanplaythrough', 70 | 'onDurationChange': 'ondurationchange', 71 | 'onEmptied': 'onemptied', 72 | 'onEncrypted': 'onencrypted', 73 | 'onEnded': 'onended', 74 | 'onLoadedData': 'onloadeddata', 75 | 'onLoadedMetadata': 'onloadedmetadata', 76 | 'onLoadStart': 'onloadstart', 77 | 'onPause': 'onpause', 78 | 'onPlay': 'onplay', 79 | 'onPlaying': 'onplaying', 80 | 'onProgress': 'onprogress', 81 | 'onRateChange': 'onratechange', 82 | 'onSeeked': 'onseeked', 83 | 'onSeeking': 'onseeking', 84 | 'onStalled': 'onstalled', 85 | 'onSuspend': 'onsuspend', 86 | 'onTimeUpdate': 'ontimeupdate', 87 | 'onVolumeChange': 'onvolumechange', 88 | 'onWaiting': 'onwaiting', 89 | // Image events 90 | 'onLoad': 'onload', 91 | 'onError': 'onerror' 92 | }; 93 | var svgElements = { 94 | 'circle': true, 95 | 'clipPath': true, 96 | 'defs': true, 97 | 'ellipse': true, 98 | 'g': true, 99 | 'image': true, 100 | 'line': true, 101 | 'linearGradient': true, 102 | 'mask': true, 103 | 'path': true, 104 | 'pattern': true, 105 | 'polygon': true, 106 | 'polyline': true, 107 | 'radialGradient': true, 108 | 'rect': true, 109 | 'stop': true, 110 | 'svg': true, 111 | 'text': true, 112 | 'tspan': true 113 | }; 114 | UIBuilder.Fragment = "--fragment--"; 115 | var Component = /** @class */ (function () { 116 | function Component(props) { 117 | this.props = props; 118 | } 119 | Component.prototype.render = function () { 120 | return null; 121 | }; 122 | return Component; 123 | }()); 124 | UIBuilder.Component = Component; 125 | function createElement(type, props) { 126 | var children = []; 127 | for (var _i = 2; _i < arguments.length; _i++) { 128 | children[_i - 2] = arguments[_i]; 129 | } 130 | props = props || {}; 131 | var node; 132 | if (type === UIBuilder.Fragment) { 133 | return children; 134 | } 135 | else if (typeof type === 'function') { // Is it either a component class or a functional component? 136 | var _props = __assign(__assign({}, props), { children: children }); 137 | if (type.prototype.render) { // Is it a component class? 138 | var component = new type(_props); 139 | node = component.render(); 140 | applyComponentProps(component, props); 141 | } 142 | else { // It is a functional component 143 | node = type(_props); 144 | } 145 | } 146 | else { // It is an HTML or SVG element 147 | if (svgElements[type]) { 148 | node = document.createElementNS("http://www.w3.org/2000/svg", type); 149 | } 150 | else { 151 | node = document.createElement(type); 152 | } 153 | applyElementProps(node, props); 154 | appendChildrenRecursively(node, children); 155 | } 156 | return node; 157 | } 158 | UIBuilder.createElement = createElement; 159 | function appendChildrenRecursively(node, children) { 160 | for (var _i = 0, children_1 = children; _i < children_1.length; _i++) { 161 | var child = children_1[_i]; 162 | if (child instanceof Node) { // Is it an HTML or SVG element? 163 | node.appendChild(child); 164 | } 165 | else if (Array.isArray(child)) { // example:

{items}
166 | appendChildrenRecursively(node, child); 167 | } 168 | else if (child === false) { 169 | // The value false is ignored, to allow conditional display using && operator 170 | } 171 | else if (child != null) { // if item is not null or undefined 172 | node.appendChild(document.createTextNode(child)); 173 | } 174 | } 175 | } 176 | function applyElementProps(node, props) { 177 | for (var prop in props) { 178 | var value = props[prop]; 179 | if (value == null) // if value is null or undefined 180 | continue; 181 | if (prop === 'ref') { 182 | if (typeof value === 'function') { 183 | value(node); 184 | } 185 | else { 186 | throw new Error("'ref' must be a function"); 187 | } 188 | } 189 | else if (eventMap.hasOwnProperty(prop)) { 190 | node[eventMap[prop]] = value; 191 | } 192 | else if (typeof value === 'function') { 193 | node.addEventListener(prop, value); 194 | } 195 | else if (prop === 'style' && typeof value === 'object') { // Example:
196 | for (var styleName in value) { 197 | node.style[styleName] = value[styleName]; 198 | } 199 | } 200 | else { 201 | var name_1 = attribMap.hasOwnProperty(prop) ? attribMap[prop] : prop; 202 | if (name_1 in node && typeof value === 'object') { 203 | // pass object-valued attributes to Web Components 204 | node[name_1] = value; // value is set without any type conversion 205 | } 206 | else { 207 | node.setAttribute(name_1, value); // value will be converted to string 208 | } 209 | } 210 | } 211 | } 212 | function applyComponentProps(component, props) { 213 | var ref = props['ref']; 214 | if (ref) { 215 | if (typeof ref === 'function') { 216 | ref(component); 217 | } 218 | else { 219 | throw new Error("'ref' must be a function"); 220 | } 221 | } 222 | } 223 | })(UIBuilder || (UIBuilder = {})); 224 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UIBuilder", 3 | "version": "1.0.0", 4 | "description": "Typed HTML templates using TypeScript's TSX files", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/wisercoder/uibuilder" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "devDependencies": { 15 | "gulp": "^3.9.1", 16 | "gulp-sass": "^3.1.0" 17 | }, 18 | "license": "MIT" 19 | } 20 | --------------------------------------------------------------------------------