[perf] calendar-sk

Change-Id: I421e48106ed6649fb490b22c0ca5b3cdc2c11d04
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/299440
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
diff --git a/perf/modules/calendar-sk/calendar-sk-demo.html b/perf/modules/calendar-sk/calendar-sk-demo.html
new file mode 100644
index 0000000..8e5b3a5
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk-demo.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <title>calendar-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" />
+    <style>
+        section {
+            padding: 16px;
+        }
+    </style>
+</head>
+
+<body>
+    <section class="body-sk">
+        <h2>themes.css</h2>
+        <calendar-sk></calendar-sk>
+    </section>
+
+    <section class="body-sk darkmode">
+        <h2>themes.css - darkmode</h2>
+        <calendar-sk></calendar-sk>
+        <h2>zh-Hans-CN</h2>
+        <calendar-sk></calendar-sk>
+    </section>
+    <pre><code id=evt></code></pre>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/perf/modules/calendar-sk/calendar-sk-demo.ts b/perf/modules/calendar-sk/calendar-sk-demo.ts
new file mode 100644
index 0000000..72f689e
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk-demo.ts
@@ -0,0 +1,16 @@
+import './index.ts';
+import { CalendarSk } from './calendar-sk';
+
+const evt = document.getElementById('evt')!;
+const locales = [undefined, undefined, 'zh-Hans-CN'];
+document.querySelectorAll<CalendarSk>('calendar-sk').forEach((ele, i) => {
+  ele.displayDate = new Date(2020, 4, 21);
+  ele.locale = locales[i];
+  ele.addEventListener('change', (e) => {
+    evt.innerText = (e as CustomEvent<Date>).detail.toString();
+  });
+});
+
+document.addEventListener('keydown', (e) =>
+  document.querySelector<CalendarSk>('calendar-sk')!.keyboardHandler(e)
+);
diff --git a/perf/modules/calendar-sk/calendar-sk.scss b/perf/modules/calendar-sk/calendar-sk.scss
new file mode 100644
index 0000000..0230608
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk.scss
@@ -0,0 +1,54 @@
+@import '~elements-sk/themes/themes.css';
+
+calendar-sk {
+  h2 {
+    margin: 0;
+    padding: 0;
+  }
+
+  td {
+    font-size: 120%;
+    text-align: center;
+    border: solid 2px var(--background);
+  }
+
+  td.today button {
+    color: var(--secondary);
+  }
+
+  td.selected {
+    border: solid 2px var(--secondary);
+  }
+
+  th.clickable {
+    cursor: pointer;
+  }
+
+  th.clickable:hover {
+    background: var(--surface-1dp);
+  }
+
+  tr.weekdayHeader {
+    opacity: 0.6;
+  }
+
+  button {
+    cursor: pointer;
+    margin: 0;
+    padding: 0em;
+    min-width: 16px;
+    min-height: 0;
+    height: 20px;
+    width: 20px;
+    text-transform: none;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    navigate-next-icon-sk svg.icon-sk-svg,
+    navigate-before-icon-sk svg.icon-sk-svg {
+      width: 16px;
+      height: 16px;
+    }
+  }
+}
diff --git a/perf/modules/calendar-sk/calendar-sk.ts b/perf/modules/calendar-sk/calendar-sk.ts
new file mode 100644
index 0000000..583b6e5
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk.ts
@@ -0,0 +1,419 @@
+/**
+ * @module modules/calendar-sk
+ * @description <h2><code>calendar-sk</code></h2>
+ *
+ * Displays an accessible calendar, one month at a time, and allows selecting a
+ * single day. Offers the ability to navigate by both month and year. Also is
+ * themeable and offers keyboard navigation.
+ *
+ * Why not use input type="date"? It doesn't work on Safari, and the pop-up
+ * calendar isn't styleable.
+ *
+ * Why not use the Elix web component? It is not themeable (at least not
+ * easily), and it is also inaccessible.
+ *
+ * Accessibility advice was derived from this page:
+ *   https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/datepicker-dialog.html
+ *
+ * @evt change - A CustomEvent with the selected Date in the detail.
+ *
+ * This element provides a keyboardHandler callback that should be attached and
+ * detached to/from the appropriate containing element when it is used, for
+ * example, a containing 'dialog' element.
+ */
+import {html, TemplateResult} from 'lit-html';
+import {ElementSk} from '../../../infra-sk/modules/ElementSk';
+import 'elements-sk/styles/buttons';
+import 'elements-sk/icon/navigate-before-icon-sk';
+import 'elements-sk/icon/navigate-next-icon-sk';
+
+/*
+ * Most of the Date[1] object's methods return zero-indexed values, with exceptions such as
+ * Date.prototype.getDate() and Date.prototype.getYear().
+ *
+ * To make things easier to follow, we suffix zero-indexed values returned by a
+ * Date object with "Index", e.g.:
+ *
+ *   const dayIndex = date.getDay();     // (0-6).
+ *   const monthIndex = date.getMonth(); // (0-11).
+ *
+ * [1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
+ */
+
+const getNumberOfDaysInMonth = (year: number, monthIndex: number) => {
+  // Jump forward one month, and back one day to get the last day of the month.
+  // Since days are 1-indexed, a value of 0 represents the last day of the
+  // previous month.
+  return new Date(year, monthIndex + 1, 0).getDate();
+};
+
+// Returns a day of the week [0-6]
+const firstDayIndexOfMonth = (year: number, monthIndex: number): number => {
+  return new Date(year, monthIndex).getDay();
+};
+
+// Used in templates.
+const sevenDaysInAWeek = [0, 1, 2, 3, 4, 5, 6];
+
+// To display a month we need to display up to 6 weeks. Used in templates.
+const sixWeeks = [0, 1, 2, 3, 4, 5];
+
+// The dates that CalendarSk manipulates, always in local time.
+class CalendarDate {
+  year: number;
+  monthIndex: number;
+  date: number;
+
+  constructor(d: Date) {
+    this.year = d.getFullYear();
+    this.monthIndex = d.getMonth();
+    this.date = d.getDate();
+  }
+
+  equal(d: CalendarDate) {
+    return (
+      this.year === d.year &&
+      this.monthIndex === d.monthIndex &&
+      this.date === d.date
+    );
+  }
+}
+
+export class CalendarSk extends ElementSk {
+  private static template = (ele: CalendarSk) => html`
+    <table>
+      <tr>
+        <th>
+          <button
+            @click=${ele.decYear}
+            aria-label="Previous year"
+            title="Previous year"
+            id="previous-year"
+          >
+            <navigate-before-icon-sk></navigate-before-icon-sk>
+          </button>
+        </th>
+        <th colspan="5">
+          <h2 aria-live="polite" id="calendar-year">
+            ${new Intl.DateTimeFormat(ele._locale, {year: 'numeric'}).format(
+              ele._displayDate
+            )}
+          </h2>
+        </th>
+        <th>
+          <button
+            @click=${ele.incYear}
+            aria-label="Next year"
+            title="Next year"
+            id="next-year"
+          >
+            <navigate-next-icon-sk></navigate-next-icon-sk>
+          </button>
+        </th>
+      </tr>
+      <tr>
+        <th>
+          <button
+            @click=${ele.decMonth}
+            aria-label="Previous month"
+            title="Previous month"
+            id="previous-month"
+          >
+            <navigate-before-icon-sk></navigate-before-icon-sk>
+          </button>
+        </th>
+        <th colspan="5">
+          <h2 aria-live="polite" id="calendar-month">
+            ${new Intl.DateTimeFormat(ele._locale, {month: 'long'}).format(
+              ele._displayDate
+            )}
+          </h2>
+        </th>
+        <th>
+          <button
+            @click=${ele.incMonth}
+            aria-label="Next month"
+            title="Next month"
+            id="next-month"
+          >
+            <navigate-next-icon-sk></navigate-next-icon-sk>
+          </button>
+        </th>
+      </tr>
+      ${ele._weekDayHeader}
+      ${sixWeeks.map((i) => CalendarSk.rowTemplate(ele, i))}
+    </table>
+  `;
+
+  private static buttonForDateTemplate = (
+    ele: CalendarSk,
+    date: number,
+    daysInMonth: number,
+    selected: boolean
+  ) => {
+    if (date < 1 || date > daysInMonth) {
+      return html``;
+    }
+    return html`<button
+      @click=${ele.dateClick}
+      data-date=${date}
+      tabindex=${selected ? 0 : -1}
+      aria-selected=${selected}
+    >
+      ${date}
+    </button>`;
+  };
+
+  private static rowTemplate = (ele: CalendarSk, weekIndex: number) => {
+    const year = ele.displayYear();
+    const monthIndex = ele.displayMonthIndex();
+    const today = new CalendarDate(new Date());
+
+    // If June starts on a Tuesday then IndexOfTheFirstDayOfTheMonth = 2,
+    // which means if we are filling in the first row we want to leave the first
+    // two days (Sunday and Monday) blank.
+    const firstDayOfTheMonthIndex = firstDayIndexOfMonth(year, monthIndex);
+    const daysInMonth = getNumberOfDaysInMonth(year, monthIndex);
+    const selectedDate = ele._displayDate.getDate();
+    const currentDate = new CalendarDate(ele._displayDate);
+    return html`<tr>
+      ${sevenDaysInAWeek.map((i) => {
+        const date = 7 * weekIndex + i + 1 - firstDayOfTheMonthIndex;
+        currentDate.date = date;
+        const selected = selectedDate === date;
+        return html`<td
+          class="
+            ${currentDate.equal(today) ? 'today' : ''}
+            ${selected ? 'selected' : ''}
+          "
+        >
+          ${CalendarSk.buttonForDateTemplate(ele, date, daysInMonth, selected)}
+        </td>`;
+      })}
+    </tr>`;
+  };
+
+  private _displayDate: Date = new Date();
+  private _weekDayHeader: TemplateResult = html``;
+  private _locale: string | string[] | undefined = undefined;
+
+  constructor() {
+    super(CalendarSk.template);
+  }
+
+  connectedCallback() {
+    super.connectedCallback();
+    this.buildWeekDayHeader();
+    this._render();
+  }
+
+  /**
+   * Attach this handler to the 'keydown' event on the appropriate elements
+   * parent, such as document or a 'dialog' element.
+   *
+   * Allows finer grained control of keyboard events on a page with more
+   * than one keyboard listener.
+   */
+  keyboardHandler(e: KeyboardEvent) {
+    let keyHandled = true;
+    switch (e.code) {
+      case 'PageUp':
+        this.decMonth();
+        break;
+
+      case 'PageDown':
+        this.incMonth();
+        break;
+
+      case 'ArrowRight':
+        this.incDay();
+        break;
+
+      case 'ArrowLeft':
+        this.decDay();
+        break;
+
+      case 'ArrowUp':
+        this.decWeek();
+        break;
+
+      case 'ArrowDown':
+        this.incWeek();
+        break;
+
+      default:
+        keyHandled = false;
+        break;
+    }
+    if (keyHandled) {
+      e.stopPropagation();
+      e.preventDefault();
+      this.querySelector<HTMLButtonElement>(
+        'button[aria-selected="true"]'
+      )!.focus();
+    }
+  }
+
+  buildWeekDayHeader() {
+    // March 1, 2020 falls on a Sunday, use that to generate the week day headers.
+    const narrowFormatter = new Intl.DateTimeFormat(this._locale, {
+      weekday: 'narrow',
+    });
+    const longFormatter = new Intl.DateTimeFormat(this._locale, {
+      weekday: 'long',
+    });
+    this._weekDayHeader = html`<tr class="weekdayHeader">
+      ${sevenDaysInAWeek.map(
+        (i) =>
+          html`<td>
+            <span abbr="${longFormatter.format(new Date(2020, 2, i + 1))}">
+              ${narrowFormatter.format(new Date(2020, 2, i + 1))}
+            </span>
+          </td>`
+      )}
+    </tr>`;
+  }
+
+  private dateClick(e: MouseEvent) {
+    const d = new Date(this._displayDate);
+    d.setDate(+(e.target as HTMLButtonElement).dataset.date!);
+    this.dispatchEvent(
+      new CustomEvent<Date>('change', {
+        detail: d,
+        bubbles: true,
+      })
+    );
+    this._displayDate = d;
+    this._render();
+  }
+
+  private displayYear(): number {
+    return this._displayDate.getFullYear();
+  }
+
+  private displayMonthIndex(): number {
+    return this._displayDate.getMonth();
+  }
+
+  private incYear() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    let date = this._displayDate.getDate();
+    const daysInMonth = getNumberOfDaysInMonth(year + 1, monthIndex);
+    if (date > daysInMonth) {
+      date = daysInMonth;
+    }
+    this._displayDate = new Date(year + 1, monthIndex, date);
+    this._render();
+  }
+
+  private decYear() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    let date = this._displayDate.getDate();
+    const daysInMonth = getNumberOfDaysInMonth(year - 1, monthIndex);
+    if (date > daysInMonth) {
+      date = daysInMonth;
+    }
+    this._displayDate = new Date(year - 1, monthIndex, date);
+    this._render();
+  }
+
+  private incMonth() {
+    let year = this.displayYear();
+    let monthIndex = this.displayMonthIndex();
+    let date = this._displayDate.getDate();
+
+    monthIndex += 1;
+    if (monthIndex > 11) {
+      monthIndex = 0;
+      year += 1;
+    }
+
+    const daysInMonth = getNumberOfDaysInMonth(year, monthIndex);
+    if (date > daysInMonth) {
+      date = daysInMonth;
+    }
+
+    this._displayDate = new Date(year, monthIndex, date);
+    this._render();
+  }
+
+  private decMonth() {
+    let year = this.displayYear();
+    let monthIndex = this.displayMonthIndex();
+    let date = this._displayDate.getDate();
+
+    monthIndex -= 1;
+    if (monthIndex < 0) {
+      monthIndex = 11;
+      year -= 1;
+    }
+
+    const daysInMonth = getNumberOfDaysInMonth(year, monthIndex);
+    if (date > daysInMonth) {
+      date = daysInMonth;
+    }
+
+    this._displayDate = new Date(year, monthIndex, date);
+    this._render();
+  }
+
+  private incDay() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    const date = this._displayDate.getDate();
+    this._displayDate = new Date(year, monthIndex, date + 1);
+    this._render();
+  }
+
+  private decDay() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    const date = this._displayDate.getDate();
+    this._displayDate = new Date(year, monthIndex, date - 1);
+    this._render();
+  }
+
+  private incWeek() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    const date = this._displayDate.getDate();
+    this._displayDate = new Date(year, monthIndex, date + 7);
+    this._render();
+  }
+
+  private decWeek() {
+    const year = this.displayYear();
+    const monthIndex = this.displayMonthIndex();
+    const date = this._displayDate.getDate();
+    this._displayDate = new Date(year, monthIndex, date - 7);
+    this._render();
+  }
+
+  /** The date to display on the calendar. */
+  get displayDate() {
+    return this._displayDate;
+  }
+
+  set displayDate(v) {
+    this._displayDate = v;
+    this._render();
+  }
+
+  /**
+   * Leave as undefined to use the browser settings. Only really used for
+   * testing.
+   */
+  public get locale() {
+    return this._locale;
+  }
+
+  public set locale(v) {
+    this._locale = v;
+    this.buildWeekDayHeader();
+    this._render();
+  }
+}
+
+window.customElements.define('calendar-sk', CalendarSk);
diff --git a/perf/modules/calendar-sk/calendar-sk_puppeteer_test.ts b/perf/modules/calendar-sk/calendar-sk_puppeteer_test.ts
new file mode 100644
index 0000000..582f82c
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk_puppeteer_test.ts
@@ -0,0 +1,28 @@
+import * as path from 'path';
+import { expect } from 'chai';
+import {
+  setUpPuppeteerAndDemoPageServer,
+  takeScreenshot,
+} from '../../../puppeteer-tests/util';
+
+describe('calendar-sk', () => {
+  const testBed = setUpPuppeteerAndDemoPageServer(
+    path.join(__dirname, '..', '..', 'webpack.config.ts')
+  );
+
+  beforeEach(async () => {
+    await testBed.page.goto(`${testBed.baseUrl}/dist/calendar-sk.html`);
+    await testBed.page.setViewport({ width: 600, height: 1200 });
+  });
+
+  it('should render the demo page', async () => {
+    // Smoke test.
+    expect(await testBed.page.$$('calendar-sk')).to.have.length(3);
+  });
+
+  describe('screenshots', () => {
+    it('shows the default view', async () => {
+      await takeScreenshot(testBed.page, 'perf', 'calendar-sk');
+    });
+  });
+});
diff --git a/perf/modules/calendar-sk/calendar-sk_test.ts b/perf/modules/calendar-sk/calendar-sk_test.ts
new file mode 100644
index 0000000..ce09a87
--- /dev/null
+++ b/perf/modules/calendar-sk/calendar-sk_test.ts
@@ -0,0 +1,173 @@
+import './index';
+import {assert} from 'chai';
+import {CalendarSk} from './calendar-sk';
+import {
+  setUpElementUnderTest,
+  eventPromise,
+} from '../../../infra-sk/modules/test_util';
+
+const container = document.createElement('div');
+document.body.appendChild(container);
+
+afterEach(() => {
+  container.innerHTML = '';
+});
+
+describe('calendar-sk', () => {
+  const newInstance = setUpElementUnderTest<CalendarSk>('calendar-sk');
+  let calendarSk: CalendarSk;
+  beforeEach(() => {
+    calendarSk = newInstance();
+    calendarSk.displayDate = new Date(2020, 4, 21);
+  });
+
+  describe('event', () => {
+    it('fires when button is clicked', () =>
+      window.customElements.whenDefined('calendar-sk').then(async () => {
+        const event = eventPromise<CustomEvent<Date>>('change');
+        calendarSk
+          .querySelector<HTMLButtonElement>('button[data-date="19"]')!
+          .click();
+
+        const detail = (await event).detail;
+
+        assert.equal(
+          new Date(2020, 4, 19).toDateString(),
+          detail.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 19, 'Selected date has changed.');
+      }));
+  });
+  describe('year', () => {
+    it('decrements when button is clicked', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.querySelector<HTMLButtonElement>('#previous-year')!.click();
+
+        const year = calendarSk.querySelector<HTMLHeadingElement>(
+          '#calendar-year'
+        )!.innerText;
+        assert.equal(year, '2019', 'Year has changed.');
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+    it('increments when button is clicked', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.querySelector<HTMLButtonElement>('#next-year')!.click();
+
+        const year = calendarSk.querySelector<HTMLHeadingElement>(
+          '#calendar-year'
+        )!.innerText;
+        assert.equal(year, '2021', 'Year has changed.');
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+  });
+  describe('month', () => {
+    it('decrements when button is clicked', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.querySelector<HTMLButtonElement>('#previous-month')!.click();
+
+        const year = calendarSk.querySelector<HTMLHeadingElement>(
+          '#calendar-month'
+        )!.innerText;
+        assert.equal(year, 'April', 'Month has changed.');
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+    it('increments when button is clicked', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.querySelector<HTMLButtonElement>('#next-month')!.click();
+
+        const year = calendarSk.querySelector<HTMLHeadingElement>(
+          '#calendar-month'
+        )!.innerText;
+        assert.equal(year, 'June', 'Month has changed.');
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+  });
+  describe('keyboard', () => {
+    it('moves to next day when ArrowRight is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'ArrowRight'})
+        );
+
+        assert.equal(
+          new Date(2020, 4, 22).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 22, 'Selected date has changed.');
+      }));
+    it('moves to previous day when ArrowLeft is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'ArrowLeft'})
+        );
+
+        assert.equal(
+          new Date(2020, 4, 20).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 20, 'Selected date has changed.');
+      }));
+    it('moves to previous week when ArrowUp is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'ArrowUp'})
+        );
+
+        assert.equal(
+          new Date(2020, 4, 14).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 14, 'Selected date has changed.');
+      }));
+    it('moves to next week when ArrowDown is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'ArrowDown'})
+        );
+
+        assert.equal(
+          new Date(2020, 4, 28).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 28, 'Selected date has changed.');
+      }));
+
+    it('moves to previous month when PageUp is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'PageUp'})
+        );
+
+        assert.equal(
+          new Date(2020, 3, 21).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+    it('moves to next month when PageDown is pressed', () =>
+      window.customElements.whenDefined('calendar-sk').then(() => {
+        calendarSk.keyboardHandler(
+          new KeyboardEvent('keydown', {code: 'PageDown'})
+        );
+
+        assert.equal(
+          new Date(2020, 5, 21).toDateString(),
+          calendarSk.displayDate.toDateString(),
+          'Date has changed.'
+        );
+        assert.equal(getSelectedDate(), 21, 'Selected date has not changed.');
+      }));
+  });
+
+  const getSelectedDate = () => {
+    return +calendarSk.querySelector<HTMLButtonElement>(
+      '[aria-selected="true"]'
+    )!.innerText;
+  };
+});
diff --git a/perf/modules/calendar-sk/index.ts b/perf/modules/calendar-sk/index.ts
new file mode 100644
index 0000000..79fdbfd
--- /dev/null
+++ b/perf/modules/calendar-sk/index.ts
@@ -0,0 +1,2 @@
+import './calendar-sk';
+import './calendar-sk.scss';