mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 12:57:44 +00:00
Local code (#876)
This PR fixes #1 by adding an initial feature to include local code to our repo, which will then be opened by StackBlitz. We can add other providers in the future or even change all of our articles to use the same provider if they'd like
This commit is contained in:
@@ -2,4 +2,7 @@ dist
|
||||
node_modules
|
||||
package-lock.json
|
||||
*.md
|
||||
*.min.js
|
||||
*.min.js
|
||||
|
||||
content/blog/**/*
|
||||
public/content/blog/**/*
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"demo": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/demo",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "demo:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "demo"
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "angular",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "9.1.0",
|
||||
"@angular/common": "^9.1.0",
|
||||
"@angular/compiler": "^9.1.0",
|
||||
"@angular/core": "^9.1.0",
|
||||
"@angular/forms": "^9.1.0",
|
||||
"@angular/platform-browser": "^9.1.0",
|
||||
"@angular/platform-browser-dynamic": "^9.1.0",
|
||||
"@angular/router": "^9.1.0",
|
||||
"core-js": "^3.6.4",
|
||||
"rxjs": "^6.5.4",
|
||||
"tslib": "^1.10.0",
|
||||
"zone.js": "^0.10.3"
|
||||
},
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.10.0",
|
||||
"@angular/cli": "~7.0.2",
|
||||
"@angular/compiler-cli": "~7.0.0",
|
||||
"@angular/language-service": "~7.0.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.0.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
p {
|
||||
font-family: Lato;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<app-example-input placeholder="What's your favorite animal?"></app-example-input>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
})
|
||||
export class AppComponent {
|
||||
name = 'Angular';
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { HelloComponent } from './hello.component';
|
||||
import { ExampleInputComponent } from './example-input/example-input.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, FormsModule ],
|
||||
declarations: [ AppComponent, HelloComponent, ExampleInputComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,67 @@
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputLabel {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.inputInput {
|
||||
color: #132a58;
|
||||
padding: 1rem 0.5rem;
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
border: 1px solid rgba(0, 51, 153, 0.2);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hiddenMessage.hideTheMessage {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.hiddenMessage {
|
||||
animation: shake 2s ease-in-out;
|
||||
animation-iteration-count:infinite;
|
||||
opacity: 1;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hiddenMessage {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-2px, -2px, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0px, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 4px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
white-space: nowrap; /* added line */
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-example-input",
|
||||
template: `
|
||||
<label class="inputContainer">
|
||||
<span class="inputLabel">{{ placeholder }}</span>
|
||||
<input
|
||||
placeholder=""
|
||||
class="inputInput"
|
||||
[(ngModel)]="value"
|
||||
/>
|
||||
</label>
|
||||
<p
|
||||
class="hiddenMessage"
|
||||
[class.hideTheMessage]="!isSecretValue"
|
||||
aria-hidden="true"
|
||||
>
|
||||
You unlocked the secret unicorn rave!<span>🦄🦄🦄</span>
|
||||
</p>
|
||||
<p aria-live="assertive" class="visually-hidden">
|
||||
{{
|
||||
isSecretValue
|
||||
? "You discovered the secret unicorn rave! They're all having a party now that you summoned them by typing their name"
|
||||
: ""
|
||||
}}
|
||||
</p>
|
||||
`,
|
||||
styleUrls: ["./example-input.component.css"]
|
||||
})
|
||||
export class ExampleInputComponent {
|
||||
@Input() placeholder: string;
|
||||
value: any = "";
|
||||
|
||||
get isSecretValue() {
|
||||
return /unicorns/.exec(this.value.toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hello',
|
||||
template: `<h1>Hello {{name}}!</h1>`,
|
||||
styles: [`h1 { font-family: Lato; }`]
|
||||
})
|
||||
export class HelloComponent {
|
||||
@Input() name: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular App</title>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>loading</my-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/my-app'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import './polyfills';
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
||||
// Ensure Angular destroys itself on hot reloads.
|
||||
if (window['ngRef']) {
|
||||
window['ngRef'].destroy();
|
||||
}
|
||||
window['ngRef'] = ref;
|
||||
|
||||
// Otherwise, log the boot error
|
||||
}).catch(err => console.error(err));
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
// import 'core-js/es6/reflect';
|
||||
// import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date, currency, decimal and percent pipes.
|
||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
@@ -0,0 +1 @@
|
||||
/* Add application styles & imports to this file! */
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"main.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"demo": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/demo",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "demo:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "demo"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "angular",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "9.1.9",
|
||||
"@angular/common": "^9.1.4",
|
||||
"@angular/compiler": "9.1.9",
|
||||
"@angular/core": "^9.1.4",
|
||||
"@angular/forms": "^9.1.4",
|
||||
"@angular/platform-browser": "^9.1.4",
|
||||
"@angular/platform-browser-dynamic": "^9.1.9",
|
||||
"@angular/router": "^9.1.4",
|
||||
"rxjs": "^6.5.5",
|
||||
"tslib": "^1.11.1",
|
||||
"zone.js": "^0.10.3"
|
||||
},
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.10.0",
|
||||
"@angular/cli": "9.0.0-rc.7",
|
||||
"@angular/compiler-cli": "9.0.0-rc.7",
|
||||
"@angular/language-service": "~7.0.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.0.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
p {
|
||||
font-family: Lato;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1>Form Control</h1>
|
||||
<app-example-input placeholder="What's your favorite animal?" [formControl]="control"></app-example-input>
|
||||
<p>The value of the input is: {{control.value}}</p>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {FormControl, Validators} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
})
|
||||
export class AppComponent {
|
||||
control = new FormControl('', Validators.required);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ExampleInputComponent } from './example-input/example-input.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ ReactiveFormsModule, FormsModule, BrowserModule ],
|
||||
declarations: [ AppComponent, ExampleInputComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,75 @@
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputLabel {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.inputInput {
|
||||
color: #132a58;
|
||||
padding: 1rem 0.5rem;
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
border: 1px solid rgba(0, 51, 153, 0.2);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.redtext {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.redoutline {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.hiddenMessage.hideTheMessage {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.hiddenMessage {
|
||||
animation: shake 2s ease-in-out;
|
||||
animation-iteration-count:infinite;
|
||||
opacity: 1;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hiddenMessage {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-2px, -2px, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0px, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 4px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
white-space: nowrap; /* added line */
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<label class="inputContainer">
|
||||
<span class="inputLabel" [class.redtext]="errors"
|
||||
>{{ placeholder }}</span>
|
||||
<input
|
||||
placeholder=""
|
||||
class="inputInput"
|
||||
[class.redoutline]="errors"
|
||||
[disabled]="disabled"
|
||||
[(ngModel)]="value"
|
||||
(change)="onChange($event)"
|
||||
(blur)="onTouched()"
|
||||
/>
|
||||
</label>
|
||||
<p class="hiddenMessage" [class.hideTheMessage]="!isSecretValue" aria-hidden="true">
|
||||
You unlocked the secret unicorn
|
||||
rave!<span>🦄🦄🦄</span></p>
|
||||
<p aria-live="assertive" class="visually-hidden">
|
||||
{{isSecretValue ? "You discovered the secret unicorn rave! They're all having a party now that you summoned them by typing their name" : ""}}
|
||||
</p>
|
||||
<p aria-live="assertive">You have the following errors: {{errors | json}}</p>
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
ChangeDetectorRef,
|
||||
Optional,
|
||||
Self,
|
||||
OnInit
|
||||
} from "@angular/core";
|
||||
import { ControlValueAccessor, NgControl } from "@angular/forms";
|
||||
|
||||
function coerceBooleanProperty(value: any): boolean {
|
||||
return value != null && `${value}` !== "false";
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "app-example-input",
|
||||
templateUrl: "./example-input.component.html",
|
||||
styleUrls: ["./example-input.component.css"]
|
||||
})
|
||||
export class ExampleInputComponent
|
||||
implements OnInit, ControlValueAccessor {
|
||||
constructor(
|
||||
@Optional() @Self() public ngControl: NgControl,
|
||||
private _changeDetector: ChangeDetectorRef
|
||||
) {
|
||||
if (ngControl != null) {
|
||||
// Setting the value accessor directly (instead of using
|
||||
// the providers) to avoid running into a circular import.
|
||||
ngControl.valueAccessor = this;
|
||||
}
|
||||
}
|
||||
|
||||
@Input() placeholder: string;
|
||||
|
||||
private _value: any = "";
|
||||
private _disabled: boolean = false;
|
||||
|
||||
/** The method to be called in order to update ngModel */
|
||||
_controlValueAccessorChangeFn: (value: any) => void = () => {};
|
||||
|
||||
/**
|
||||
* onTouch function registered via registerOnTouch (ControlValueAccessor).
|
||||
*/
|
||||
onTouched: () => any = () => {};
|
||||
|
||||
get isSecretValue() {
|
||||
return /unicorns/.exec(this._value);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const control = this.ngControl && this.ngControl.control;
|
||||
if (control) {
|
||||
console.log("ngOnInit", control);
|
||||
// FormControl should be available here
|
||||
}
|
||||
}
|
||||
|
||||
get errors() {
|
||||
const control = this.ngControl && this.ngControl.control;
|
||||
if (control) {
|
||||
return control.touched && control.errors;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters and setters for internal values
|
||||
*/
|
||||
|
||||
@Input()
|
||||
get value(): any {
|
||||
return this._value;
|
||||
}
|
||||
set value(newValue: any) {
|
||||
if (this._value !== newValue) {
|
||||
// Set this before proceeding to ensure no circular loop occurs with selection.
|
||||
this._value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
set disabled(value) {
|
||||
this._disabled = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
onChange(event: any) {
|
||||
this._controlValueAccessorChangeFn(event.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods from the ControlValueAccessor
|
||||
*/
|
||||
writeValue(value: any) {
|
||||
this.value = value;
|
||||
this._changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => void) {
|
||||
this._controlValueAccessorChangeFn = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any) {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
// Optional
|
||||
setDisabledState(isDisabled: boolean) {
|
||||
this.disabled = isDisabled;
|
||||
this._changeDetector.markForCheck();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular App</title>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>loading</my-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/my-app'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import './polyfills';
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
||||
// Ensure Angular destroys itself on hot reloads.
|
||||
if (window['ngRef']) {
|
||||
window['ngRef'].destroy();
|
||||
}
|
||||
window['ngRef'] = ref;
|
||||
|
||||
// Otherwise, log the boot error
|
||||
}).catch(err => console.error(err));
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
// import 'core-js/es6/reflect';
|
||||
// import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date, currency, decimal and percent pipes.
|
||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
@@ -0,0 +1 @@
|
||||
/* Add application styles & imports to this file! */
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"main.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true,
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"demo": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/demo",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "demo:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "demo:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "demo"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "angular",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "9.1.9",
|
||||
"@angular/common": "^9.1.4",
|
||||
"@angular/compiler": "9.1.9",
|
||||
"@angular/core": "^9.1.4",
|
||||
"@angular/forms": "^9.1.4",
|
||||
"@angular/platform-browser": "^9.1.4",
|
||||
"@angular/platform-browser-dynamic": "^9.1.9",
|
||||
"@angular/router": "^9.1.4",
|
||||
"rxjs": "^6.5.5",
|
||||
"tslib": "^1.11.1",
|
||||
"zone.js": "^0.10.3"
|
||||
},
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.10.0",
|
||||
"@angular/cli": "9.0.0-rc.7",
|
||||
"@angular/compiler-cli": "9.0.0-rc.7",
|
||||
"@angular/language-service": "~7.0.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.0.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
p {
|
||||
font-family: Lato;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<h1>Form Control</h1>
|
||||
<app-example-input placeholder="What's your favorite animal?" [formControl]="control"></app-example-input>
|
||||
<p>The value of the input is: {{control.value}}</p>
|
||||
<h1>ngModel</h1>
|
||||
<app-example-input placeholder="What's your favorite animal?" [(ngModel)]="modelValue"></app-example-input>
|
||||
<p>The value of the input is: {{modelValue}}</p>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Component, VERSION } from '@angular/core';
|
||||
import {FormControl} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: [ './app.component.css' ]
|
||||
})
|
||||
export class AppComponent {
|
||||
control = new FormControl('');
|
||||
modelValue = "";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ExampleInputComponent } from './example-input/example-input.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ ReactiveFormsModule, FormsModule, BrowserModule ],
|
||||
declarations: [ AppComponent, ExampleInputComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,67 @@
|
||||
.inputContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputLabel {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.inputInput {
|
||||
color: #132a58;
|
||||
padding: 1rem 0.5rem;
|
||||
flex-grow: 1;
|
||||
display: block;
|
||||
border: 1px solid rgba(0, 51, 153, 0.2);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hiddenMessage.hideTheMessage {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.hiddenMessage {
|
||||
animation: shake 2s ease-in-out;
|
||||
animation-iteration-count:infinite;
|
||||
opacity: 1;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hiddenMessage {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-2px, -2px, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0px, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 4px, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
white-space: nowrap; /* added line */
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<label class="inputContainer">
|
||||
<span class="inputLabel">{{ placeholder }}</span>
|
||||
<input
|
||||
placeholder=""
|
||||
class="inputInput"
|
||||
[disabled]="disabled"
|
||||
[(ngModel)]="value"
|
||||
(change)="onChange($event)"
|
||||
(blur)="onTouched()"
|
||||
/>
|
||||
</label>
|
||||
<p class="hiddenMessage" [class.hideTheMessage]="!isSecretValue" aria-hidden="true">
|
||||
You unlocked the secret unicorn
|
||||
rave!<span>🦄🦄🦄</span></p>
|
||||
<p aria-live="assertive" class="visually-hidden">
|
||||
{{isSecretValue ? "You discovered the secret unicorn rave! They're all having a party now that you summoned them by typing their name" : ""}}
|
||||
</p>
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Component, forwardRef, Input, ChangeDetectorRef } from '@angular/core';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
|
||||
function coerceBooleanProperty(value: any): boolean {
|
||||
return value != null && `${value}` !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider Expression that allows your component to register as a ControlValueAccessor. This
|
||||
* allows it to support [(ngModel)] and ngControl.
|
||||
*/
|
||||
export const EXAMPLE_CONTROL_VALUE_ACCESSOR: any = {
|
||||
/**
|
||||
* Used to provide a `ControlValueAccessor` for form controls.
|
||||
*/
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
/**
|
||||
* Allows to refer to references which are not yet defined.
|
||||
* This is because it's needed to `providers` in the component but references
|
||||
* the component itself. Handles circular dependency issues
|
||||
*/
|
||||
useExisting: forwardRef(() => ExampleInputComponent),
|
||||
multi: true
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-example-input',
|
||||
templateUrl: './example-input.component.html',
|
||||
styleUrls: ['./example-input.component.css'],
|
||||
providers: [EXAMPLE_CONTROL_VALUE_ACCESSOR]
|
||||
})
|
||||
export class ExampleInputComponent implements ControlValueAccessor {
|
||||
@Input() placeholder: string;
|
||||
|
||||
private _value: any = "";
|
||||
private _disabled: boolean = false;
|
||||
|
||||
/** The method to be called in order to update ngModel */
|
||||
_controlValueAccessorChangeFn: (value: any) => void = () => {};
|
||||
|
||||
/**
|
||||
* onTouch function registered via registerOnTouch (ControlValueAccessor).
|
||||
*/
|
||||
onTouched: () => any = () => {};
|
||||
|
||||
constructor(private _changeDetector: ChangeDetectorRef) { }
|
||||
|
||||
get isSecretValue() {
|
||||
return /unicorns/.exec(this._value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters and setters for internal values
|
||||
*/
|
||||
|
||||
@Input()
|
||||
get value(): any { return this._value; }
|
||||
set value(newValue: any) {
|
||||
if (this._value !== newValue) {
|
||||
// Set this before proceeding to ensure no circular loop occurs with selection.
|
||||
this._value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Input()
|
||||
get disabled(): boolean { return this._disabled; }
|
||||
set disabled(value) {
|
||||
this._disabled = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
onChange(event: any) {
|
||||
this._controlValueAccessorChangeFn(event.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods from the ControlValueAccessor
|
||||
*/
|
||||
writeValue(value: any) {
|
||||
this.value = value;
|
||||
this._changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => void) {
|
||||
this._controlValueAccessorChangeFn = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any) {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
// Optional
|
||||
setDisabledState(isDisabled: boolean) {
|
||||
this.disabled = isDisabled;
|
||||
this._changeDetector.markForCheck();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Angular App</title>
|
||||
</head>
|
||||
<body>
|
||||
<my-app>loading</my-app>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/my-app'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import './polyfills';
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
||||
// Ensure Angular destroys itself on hot reloads.
|
||||
if (window['ngRef']) {
|
||||
window['ngRef'].destroy();
|
||||
}
|
||||
window['ngRef'] = ref;
|
||||
|
||||
// Otherwise, log the boot error
|
||||
}).catch(err => console.error(err));
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
|
||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
|
||||
// import 'core-js/es6/symbol';
|
||||
// import 'core-js/es6/object';
|
||||
// import 'core-js/es6/function';
|
||||
// import 'core-js/es6/parse-int';
|
||||
// import 'core-js/es6/parse-float';
|
||||
// import 'core-js/es6/number';
|
||||
// import 'core-js/es6/math';
|
||||
// import 'core-js/es6/string';
|
||||
// import 'core-js/es6/date';
|
||||
// import 'core-js/es6/array';
|
||||
// import 'core-js/es6/regexp';
|
||||
// import 'core-js/es6/map';
|
||||
// import 'core-js/es6/set';
|
||||
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
// import 'classlist.js'; // Run `npm install --save classlist.js`.
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
/** Evergreen browsers require these. **/
|
||||
// import 'core-js/es6/reflect';
|
||||
// import 'core-js/es7/reflect';
|
||||
|
||||
|
||||
/**
|
||||
* Web Animations `@angular/platform-browser/animations`
|
||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
|
||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
|
||||
*/
|
||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
|
||||
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by Angular itself.
|
||||
*/
|
||||
import 'zone.js/dist/zone'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
/**
|
||||
* Date, currency, decimal and percent pipes.
|
||||
* Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
|
||||
*/
|
||||
// import 'intl'; // Run `npm install --save intl`.
|
||||
@@ -0,0 +1 @@
|
||||
/* Add application styles & imports to this file! */
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"main.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"test.ts",
|
||||
"polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true,
|
||||
"fullTemplateTypeCheck": true,
|
||||
"strictInjectionParameters": true
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export class ExampleInputComponent {
|
||||
|
||||
With only a bit of CSS, we have a visually appealing, A11Y friendly, and quirky input component. Look, it even wiggles the unicorns!
|
||||
|
||||
<iframe src="https://stackblitz.com/edit/angular-unicorns-text-input?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
<iframe data-frame-title="Angular Unicorns Text Input - StackBlitz" src="uu-code:./angular-unicorns-text-input?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
|
||||
Now, this component is far from feature complete. There's no way to `disable` the input, there's no way to extract data out from the typed input, there's not a lot of functionality you'd typically expect to see from an input component. Let's change that.
|
||||
|
||||
@@ -303,7 +303,7 @@ Finally, you can pass these options to `ngModel` and `formControl` (or even `for
|
||||
If done properly, you should see something like this:
|
||||
|
||||
|
||||
<iframe src="https://stackblitz.com/edit/angular-value-accessor-example?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
<iframe data-frame-title="Angular Value Accessor Example - StackBlitz" src="uu-code:./angular-value-accessor-example?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
|
||||
# Form Control Classes
|
||||
|
||||
@@ -407,7 +407,7 @@ export class AppComponent {
|
||||
}
|
||||
```
|
||||
|
||||
<iframe src="https://stackblitz.com/edit/angular-value-accessor-dep-inject?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
<iframe data-frame-title="Angular Value Accessor Dep Inject - StackBlitz" src="uu-code:./angular-value-accessor-dep-inject?embed=1&file=src/app/app.component.ts" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>
|
||||
|
||||
|
||||
Not only do you have [a wide range of Angular-built validators at your disposal](https://angular.io/api/forms/Validators), but you're even able to [make your own validator](https://angular.io/api/forms/Validator)!
|
||||
|
||||
272
package-lock.json
generated
272
package-lock.json
generated
@@ -29,6 +29,7 @@
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/preact": "^3.2.3",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/git-branch": "^2.0.5",
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/html-escaper": "^3.0.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
@@ -51,6 +52,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"feed": "^4.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"git-branch": "^2.0.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hast-util-from-html": "^2.0.1",
|
||||
"hast-util-has-property": "^3.0.0",
|
||||
@@ -7111,6 +7113,12 @@
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/git-branch": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/git-branch/-/git-branch-2.0.5.tgz",
|
||||
"integrity": "sha512-gNZyvixgeXQLQnFkMUAO47sBnQBzJ/pPMJNuOZUD4bvh3O6taXjpEbugjz2qBjNz4ZFH2mVQ42WJzImEiio1Qg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
|
||||
@@ -11524,6 +11532,15 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-file": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
|
||||
"integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
|
||||
@@ -12872,6 +12889,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-tilde": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
|
||||
"integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"homedir-polyfill": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expect": {
|
||||
"version": "29.6.1",
|
||||
"resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz",
|
||||
@@ -13340,6 +13369,161 @@
|
||||
"pkg-dir": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz",
|
||||
"integrity": "sha512-vs+3unmJT45eczmcAZ6zMJtxN3l/QXeccaXQx5cu/MeJMhewVfoWZqibRkOxPnmoR59+Zy5hjabfQc6JLSah4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"detect-file": "^1.0.0",
|
||||
"is-glob": "^3.1.0",
|
||||
"micromatch": "^3.0.4",
|
||||
"resolve-dir": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/braces": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"arr-flatten": "^1.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
"extend-shallow": "^2.0.1",
|
||||
"fill-range": "^4.0.0",
|
||||
"isobject": "^3.0.1",
|
||||
"repeat-element": "^1.1.2",
|
||||
"snapdragon": "^0.8.1",
|
||||
"snapdragon-node": "^2.0.1",
|
||||
"split-string": "^3.0.2",
|
||||
"to-regex": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/fill-range": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
"integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"is-number": "^3.0.0",
|
||||
"repeat-string": "^1.6.1",
|
||||
"to-regex-range": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/is-extendable": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
|
||||
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-plain-object": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/is-glob": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
|
||||
"integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/is-number": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
"integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-buffer": "^1.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
"braces": "^2.3.1",
|
||||
"define-property": "^2.0.2",
|
||||
"extend-shallow": "^3.0.2",
|
||||
"extglob": "^2.0.4",
|
||||
"fragment-cache": "^0.2.1",
|
||||
"kind-of": "^6.0.2",
|
||||
"nanomatch": "^1.2.9",
|
||||
"object.pick": "^1.3.0",
|
||||
"regex-not": "^1.0.0",
|
||||
"snapdragon": "^0.8.1",
|
||||
"to-regex": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/micromatch/node_modules/extend-shallow": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
|
||||
"integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"assign-symbols": "^1.0.0",
|
||||
"is-extendable": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/findup-sync/node_modules/to-regex-range": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||
"integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-number": "^3.0.0",
|
||||
"repeat-string": "^1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flat-cache": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
|
||||
@@ -13699,6 +13883,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/git-branch": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/git-branch/-/git-branch-2.0.1.tgz",
|
||||
"integrity": "sha512-jMCT1kjXvsUdZKQd2p8E1uZhKsIuR1pnHgcDYQpQiXBtzE9cmYGvOcCSGqqi58x0B9CPS0lUSu/yti866est8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"findup-sync": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
@@ -13740,6 +13936,48 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/global-modules": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
|
||||
"integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"global-prefix": "^1.0.1",
|
||||
"is-windows": "^1.0.1",
|
||||
"resolve-dir": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/global-prefix": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
|
||||
"integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"expand-tilde": "^2.0.2",
|
||||
"homedir-polyfill": "^1.0.1",
|
||||
"ini": "^1.3.4",
|
||||
"is-windows": "^1.0.1",
|
||||
"which": "^1.2.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/global-prefix/node_modules/which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"which": "bin/which"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -14413,6 +14651,18 @@
|
||||
"integrity": "sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/homedir-polyfill": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
"integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"parse-passwd": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
@@ -23802,6 +24052,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-passwd": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
|
||||
"integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
@@ -25386,6 +25645,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-dir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
|
||||
"integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"expand-tilde": "^2.0.0",
|
||||
"global-modules": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/preact": "^3.2.3",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/git-branch": "^2.0.5",
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/html-escaper": "^3.0.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
@@ -75,6 +76,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"feed": "^4.2.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"git-branch": "^2.0.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hast-util-from-html": "^2.0.1",
|
||||
"hast-util-has-property": "^3.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Root, Element } from "hast";
|
||||
import { VFile } from "vfile";
|
||||
import { Plugin } from "unified";
|
||||
|
||||
import { visit } from "unist-util-visit";
|
||||
@@ -13,7 +14,9 @@ import type { GetPictureResult } from "@astrojs/image/dist/lib/get-picture";
|
||||
import probe from "probe-image-size";
|
||||
import { IFramePlaceholder } from "./iframe-placeholder";
|
||||
|
||||
interface RehypeUnicornIFrameClickToRunProps {}
|
||||
interface RehypeUnicornIFrameClickToRunProps {
|
||||
srcReplacements?: Array<(val: string, root: VFile) => string>;
|
||||
}
|
||||
|
||||
// default icon, used if a frame's favicon cannot be resolved
|
||||
let defaultPageIcon: Promise<GetPictureResult>;
|
||||
@@ -120,7 +123,7 @@ type PageInfo = {
|
||||
icon: GetPictureResult;
|
||||
};
|
||||
|
||||
async function fetchPageInfo(src: string): Promise<PageInfo | null> {
|
||||
export async function fetchPageInfo(src: string): Promise<PageInfo | null> {
|
||||
// fetch origin url, catch any connection timeout errors
|
||||
const url = new URL(src);
|
||||
url.search = ""; // remove any search params
|
||||
@@ -143,8 +146,8 @@ async function fetchPageInfo(src: string): Promise<PageInfo | null> {
|
||||
export const rehypeUnicornIFrameClickToRun: Plugin<
|
||||
[RehypeUnicornIFrameClickToRunProps | never],
|
||||
Root
|
||||
> = () => {
|
||||
return async (tree) => {
|
||||
> = ({ srcReplacements = [], ...props }) => {
|
||||
return async (tree, file) => {
|
||||
const iframeNodes: Element[] = [];
|
||||
visit(tree, (node: Element) => {
|
||||
if (node.tagName === "iframe") {
|
||||
@@ -165,6 +168,10 @@ export const rehypeUnicornIFrameClickToRun: Plugin<
|
||||
...propsToPreserve
|
||||
} = iframeNode.properties;
|
||||
|
||||
for (const replacement of srcReplacements) {
|
||||
src = replacement(src as string, file);
|
||||
}
|
||||
|
||||
width = width ?? EMBED_SIZE.w;
|
||||
height = height ?? EMBED_SIZE.h;
|
||||
const info: PageInfo = (await fetchPageInfo(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Plugin } from "unified";
|
||||
import rehypeSlug from "rehype-slug-custom-id";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import { VFile } from "vfile";
|
||||
import { resolve, relative, dirname } from "path";
|
||||
import * as branch from "git-branch";
|
||||
import { rehypeTabs } from "./tabs/rehype-transform";
|
||||
import { rehypeTooltips } from "./tooltips/rehype-transform";
|
||||
import { rehypeHints } from "./hints/rehype-transform";
|
||||
@@ -20,6 +23,7 @@ import { rehypeHeaderText } from "./rehype-header-text";
|
||||
import { rehypeHeaderClass } from "./rehype-header-class";
|
||||
import { rehypeFileTree } from "./file-tree/rehype-file-tree";
|
||||
import { rehypeTwoslashTabindex } from "./twoslash-tabindex/rehype-transform";
|
||||
import { siteMetadata } from "../../constants/site-config";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type RehypePlugin = Plugin<any[]> | [Plugin<any[]>, any];
|
||||
@@ -67,7 +71,38 @@ export function createRehypePlugins(config: MarkdownConfig): RehypePlugin[] {
|
||||
rehypeHints,
|
||||
rehypeTooltips,
|
||||
rehypeAstroImageMd,
|
||||
rehypeUnicornIFrameClickToRun,
|
||||
[
|
||||
rehypeUnicornIFrameClickToRun,
|
||||
{
|
||||
srcReplacements: [
|
||||
(val: string, file: VFile) => {
|
||||
const iFrameUrl = new URL(val);
|
||||
if (!iFrameUrl.protocol.startsWith("uu-code:")) return val;
|
||||
|
||||
const contentDir = dirname(file.path);
|
||||
const fullPath = resolve(contentDir, iFrameUrl.pathname);
|
||||
|
||||
const fsRelativePath = relative(process.cwd(), fullPath);
|
||||
|
||||
// Windows paths need to be converted to URLs
|
||||
let urlRelativePath = fsRelativePath.replace(/\\/g, "/");
|
||||
|
||||
if (urlRelativePath.startsWith("/")) {
|
||||
urlRelativePath = urlRelativePath.slice(1);
|
||||
}
|
||||
|
||||
const q = iFrameUrl.search;
|
||||
const currentBranch =
|
||||
process.env.VERCEL_GIT_COMMIT_REF ?? branch.sync();
|
||||
const repoPath = siteMetadata.repoPath;
|
||||
const provider = `stackblitz.com/github`;
|
||||
return `
|
||||
https://${provider}/${repoPath}/tree/${currentBranch}/${urlRelativePath}${q}
|
||||
`.trim();
|
||||
},
|
||||
],
|
||||
},
|
||||
] as RehypePlugin,
|
||||
rehypeUnicornElementMap,
|
||||
rehypeTwoslashTabindex,
|
||||
rehypeFileTree,
|
||||
|
||||
@@ -28,5 +28,6 @@
|
||||
"uu-utils": ["./src/utils"],
|
||||
"assets/*": ["./src/assets/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"exclude": ["./content/blog"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user