[infra-sk] Add multi-input-sk module

Behaves similarly to <input type="text"> but has string[] as its value.

Change-Id: Ia8741dc7c0ce085cc57c0189c55e1ce0b3fd9c1c
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/328898
Commit-Queue: Eric Boren <borenet@google.com>
Reviewed-by: Weston Tracey <westont@google.com>
diff --git a/infra-sk/modules/multi-input-sk/index.ts b/infra-sk/modules/multi-input-sk/index.ts
new file mode 100644
index 0000000..eb72365
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/index.ts
@@ -0,0 +1,2 @@
+import './multi-input-sk';
+import './multi-input-sk.scss';
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk-demo.html b/infra-sk/modules/multi-input-sk/multi-input-sk-demo.html
new file mode 100644
index 0000000..c500009
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk-demo.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>multi-input-sk</title>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  </head>
+  <body>
+    <h1>multi-input-sk</h1>
+    <multi-input-sk></multi-input-sk>
+
+    <h2>Events</h2>
+    <pre id="events"></pre>
+  </body>
+</html>
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk-demo.ts b/infra-sk/modules/multi-input-sk/multi-input-sk-demo.ts
new file mode 100644
index 0000000..84a8916
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk-demo.ts
@@ -0,0 +1,8 @@
+import './index';
+
+document.querySelector('multi-input-sk')!.addEventListener('change', (e) => {
+  console.log(e);
+  const pre = document.createElement('pre');
+  pre.innerText = JSON.stringify(e, null, '  ');
+  document.querySelector('#events')!.appendChild(pre);
+});
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk.scss b/infra-sk/modules/multi-input-sk/multi-input-sk.scss
new file mode 100644
index 0000000..d353561
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk.scss
@@ -0,0 +1,42 @@
+@import '~elements-sk/themes/themes';
+
+multi-input-sk {
+  .input-container {
+    border: 1px solid black;
+    border-radius: 4px;
+    display: flex;
+    justify-content: flex-start;
+    flex-direction: row;
+    align-items: stretch;
+    padding: 4px;
+    height: 24px;
+  }
+
+  .input-item {
+    background-color: var(--surface-1dp);
+    border-radius: 4px;
+    padding: 5px;
+    margin-right: 4px;
+    user-select: none;
+    white-space: nowrap;
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+
+    a {
+      cursor: pointer;
+      text-decoration: none;
+    }
+
+    close-icon-sk svg.icon-sk-svg {
+      width: 12px;
+      height: 12px;
+    }
+  }
+
+  input {
+    border: none;
+    outline: none;
+    flex: 1;
+  }
+}
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk.ts b/infra-sk/modules/multi-input-sk/multi-input-sk.ts
new file mode 100644
index 0000000..26bf5f7
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk.ts
@@ -0,0 +1,64 @@
+/**
+ * @module modules/multi-input-sk
+ * @description <h2><code>multi-input-sk</code></h2>
+ *
+ * multi-input-sk behaves similarly to <input type="text"> but its value is a
+ * string[].
+ */
+import { define } from 'elements-sk/define';
+import { html } from 'lit-html';
+import { ElementSk } from '../../../infra-sk/modules/ElementSk';
+import { $$ } from 'common-sk/modules/dom';
+import 'elements-sk/icon/close-icon-sk';
+
+export class MultiInputSk extends ElementSk {
+  private static template = (ele: MultiInputSk) => html`
+    <div class="input-container">
+      ${ele._values.map(
+        (value: string, index: number) => html`
+          <div class="input-item">
+            ${value}
+            <a
+              @click=${() => {
+                ele._values.splice(index, 1);
+                ele._render();
+                ele.dispatchEvent(new Event('change', { bubbles: true }));
+              }}
+            >
+              <close-icon-sk></close-icon-sk>
+            </a>
+          </div>
+        `
+      )}
+      <input type="text" @change=${(ev: Event) => {
+        ev.stopPropagation();
+        const inp = $$<HTMLInputElement>('input', ele)!;
+        ele._values.push(inp.value);
+        inp.value = '';
+        ele._render();
+        ele.dispatchEvent(new Event('change', { bubbles: true }));
+      }}></input>
+    </div>
+  `;
+
+  private _values: string[] = [];
+
+  get values(): string[] {
+    return this._values.slice();
+  }
+  set values(values: string[]) {
+    this._values = values.slice();
+    this._render();
+  }
+
+  constructor() {
+    super(MultiInputSk.template);
+  }
+
+  connectedCallback() {
+    super.connectedCallback();
+    this._render();
+  }
+}
+
+define('multi-input-sk', MultiInputSk);
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk_puppeteer_test.ts b/infra-sk/modules/multi-input-sk/multi-input-sk_puppeteer_test.ts
new file mode 100644
index 0000000..65a0012
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk_puppeteer_test.ts
@@ -0,0 +1,38 @@
+import * as path from 'path';
+import { expect } from 'chai';
+import {
+  setUpPuppeteerAndDemoPageServer,
+  takeScreenshot,
+} from '../../../puppeteer-tests/util';
+
+describe('multi-input-sk', () => {
+  const testBed = setUpPuppeteerAndDemoPageServer(
+    path.join(__dirname, '..', '..', 'webpack.config.ts')
+  );
+
+  beforeEach(async () => {
+    await testBed.page.goto(`${testBed.baseUrl}/multi-input-sk.html`);
+    await testBed.page.setViewport({ width: 400, height: 400 });
+  });
+
+  it('should render the demo page (smoke test)', async () => {
+    expect(await testBed.page.$$('multi-input-sk')).to.have.length(1);
+  });
+
+  describe('screenshots', () => {
+    it('shows the default view', async () => {
+      await takeScreenshot(testBed.page, 'infra-sk', 'multi-input-sk');
+    });
+    it('type to add values', async () => {
+      await testBed.page.type('input', 'blahblah');
+      await takeScreenshot(testBed.page, 'infra-sk', 'multi-input-sk_typing');
+      await testBed.page.click('h2');
+      await takeScreenshot(testBed.page, 'infra-sk', 'multi-input-sk_added');
+      await testBed.page.type('input', 'another item');
+      await testBed.page.click('h2');
+      await takeScreenshot(testBed.page, 'infra-sk', 'multi-input-sk_added2');
+      await testBed.page.click('close-icon-sk');
+      await takeScreenshot(testBed.page, 'infra-sk', 'multi-input-sk_removed');
+    });
+  });
+});
diff --git a/infra-sk/modules/multi-input-sk/multi-input-sk_test.ts b/infra-sk/modules/multi-input-sk/multi-input-sk_test.ts
new file mode 100644
index 0000000..cc5cf03
--- /dev/null
+++ b/infra-sk/modules/multi-input-sk/multi-input-sk_test.ts
@@ -0,0 +1,30 @@
+import './index';
+import { MultiInputSk } from './multi-input-sk';
+
+import { setUpElementUnderTest } from '../../../infra-sk/modules/test_util';
+import { expect } from 'chai';
+import { $ } from 'common-sk/modules/dom';
+
+describe('multi-input-sk', () => {
+  const newInstance = setUpElementUnderTest<MultiInputSk>('multi-input-sk');
+
+  let ele: MultiInputSk;
+  beforeEach(() => {
+    ele = newInstance();
+  });
+
+  describe('input behavior', () => {
+    it('gets and sets values', () => {
+      expect(ele.values.length).to.equal(0);
+      expect($('.input-item', ele).length).to.equal(0);
+      ele.values = ['abc', '123'];
+      expect(ele.values.length).to.equal(2);
+      expect(ele.values[0]).to.equal('abc');
+      expect(ele.values[1]).to.equal('123');
+      const inputItems = $<HTMLDivElement>('.input-item', ele);
+      expect(inputItems.length).to.equal(2);
+      expect(inputItems[0].innerText).to.equal('abc ');
+      expect(inputItems[1].innerText).to.equal('123 ');
+    });
+  });
+});