Bug 1320647 - Implement min and max for date picker. r=mconley
authorScott Wu <scottcwwu@gmail.com>
Tue, 02 May 2017 18:37:36 +0800
changeset 357235 ece97e873769c5892bf720ca12a2fbc29956526a
parent 357234 e3983f525a1f20237018a4a0f896a8f589ffefd9
child 357236 3bc93cae8482f672720ca636e3dee5a65aabdcb3
push id31788
push userkwierso@gmail.com
push dateTue, 09 May 2017 20:48:49 +0000
treeherdermozilla-central@2b6f6881a24a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1320647
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1320647 - Implement min and max for date picker. r=mconley MozReview-Commit-ID: Hps2CbziJqy
toolkit/content/widgets/calendar.js
toolkit/content/widgets/datekeeper.js
toolkit/content/widgets/datepicker.js
toolkit/content/widgets/datetimepopup.xml
toolkit/themes/shared/datetimeinputpickers.css
--- a/toolkit/content/widgets/calendar.js
+++ b/toolkit/content/widgets/calendar.js
@@ -48,33 +48,31 @@ Calendar.prototype = {
    *            {Number} dateValue: Date in milliseconds
    *            {Number} textContent
    *            {Array<String>} classNames
    *          }
    *          {Array<Object>} weekHeaders: Data for weekHeaders
    *          {
    *            {Number} textContent
    *            {Array<String>} classNames
+   *            {Boolean} enabled
    *          }
    *          {Function} getDayString: Transform day number to string
    *          {Function} getWeekHeaderString: Transform day of week number to string
-   *          {Function} setValue: Set value for dateKeeper
-   *          {Number} selectionValue: The selection date value
+   *          {Function} setSelection: Set selection for dateKeeper
    *        }
    */
   setProps(props) {
     if (props.isVisible) {
       // Transform the days and weekHeaders array for rendering
-      const days = props.days.map(({ dateValue, textContent, classNames }) => {
+      const days = props.days.map(({ dateObj, classNames, enabled }) => {
         return {
-          dateValue,
-          textContent: props.getDayString(textContent),
-          className: dateValue == props.selectionValue ?
-                     classNames.concat("selection").join(" ") :
-                     classNames.join(" ")
+          textContent: props.getDayString(dateObj.getUTCDate()),
+          className: classNames.join(" "),
+          enabled
         };
       });
       const weekHeaders = props.weekHeaders.map(({ textContent, classNames }) => {
         return {
           textContent: props.getWeekHeaderString(textContent),
           className: classNames.join(" ")
         };
       });
@@ -146,20 +144,22 @@ Calendar.prototype = {
    * Handle events
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "click": {
         if (event.target.parentNode == this.context.daysView) {
           let targetId = event.target.dataset.id;
-          this.props.setValue({
-            selectionValue: this.props.days[targetId].dateValue,
-            dateValue: this.props.days[targetId].dateValue
-          });
+          let targetObj = this.props.days[targetId];
+          if (targetObj.enabled) {
+            this.props.setSelection({
+              selection: targetObj.dateObj
+            });
+          }
         }
         break;
       }
     }
   },
 
   /**
    * Attach event listener to daysView
--- a/toolkit/content/widgets/datekeeper.js
+++ b/toolkit/content/widgets/datekeeper.js
@@ -1,122 +1,141 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * DateKeeper keeps track of the date states.
- *
- * @param {Object} date parts
- *        {
- *          {Number} year
- *          {Number} month
- *          {Number} day
- *        }
- *        {Object} options
- *        {
- *          {Number} firstDayOfWeek [optional]
- *          {Array<Number>} weekends [optional]
- *          {Number} calViewSize [optional]
- *        }
  */
-function DateKeeper({ year, month, day }, { firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
-  this.state = {
-    firstDayOfWeek, weekends, calViewSize,
-    dateObj: new Date(0),
-    years: [],
-    months: [],
-    days: []
-  };
-  this.state.weekHeaders = this._getWeekHeaders(firstDayOfWeek);
-  this._update(year, month, day);
+function DateKeeper(props) {
+  this.init(props);
 }
 
 {
   const DAYS_IN_A_WEEK = 7,
         MONTHS_IN_A_YEAR = 12,
         YEAR_VIEW_SIZE = 200,
-        YEAR_BUFFER_SIZE = 10;
+        YEAR_BUFFER_SIZE = 10,
+        // The min and max values are derived from the ECMAScript spec:
+        // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
+        MIN_DATE = -8640000000000000,
+        MAX_DATE = 8640000000000000;
 
   DateKeeper.prototype = {
+    get year() {
+      return this.state.dateObj.getUTCFullYear();
+    },
+
+    get month() {
+      return this.state.dateObj.getUTCMonth();
+    },
+
+    get day() {
+      return this.state.dateObj.getUTCDate();
+    },
+
     /**
-     * Set new date
+     * Initialize DateKeeper
+     * @param  {Number} year
+     * @param  {Number} month
+     * @param  {Number} day
+     * @param  {String} min
+     * @param  {String} max
+     * @param  {Number} firstDayOfWeek
+     * @param  {Array<Number>} weekends
+     * @param  {Number} calViewSize
+     */
+    init({ year, month, day, min, max, firstDayOfWeek = 0, weekends = [0], calViewSize = 42 }) {
+      const today = new Date();
+      const isDateSet = year != undefined && month != undefined && day != undefined;
+
+      this.state = {
+        firstDayOfWeek, weekends, calViewSize,
+        min: new Date(min != undefined ? min : MIN_DATE),
+        max: new Date(max != undefined ? max : MAX_DATE),
+        today: this._newUTCDate(today.getFullYear(), today.getMonth(), today.getDate()),
+        weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
+        years: [],
+        months: [],
+        days: [],
+      };
+
+      this.state.dateObj = isDateSet ?
+                           this._newUTCDate(year, month, day) :
+                           new Date(this.state.today);
+    },
+    /**
+     * Set new date. The year is always treated as full year, so the short-form
+     * is not supported.
      * @param {Object} date parts
      *        {
      *          {Number} year [optional]
      *          {Number} month [optional]
      *          {Number} date [optional]
      *        }
      */
-    set({ year = this.state.year, month = this.state.month, day = this.state.day }) {
-      this._update(year, month, day);
+    set({ year = this.year, month = this.month, day = this.day }) {
+      // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
+      this.state.dateObj.setUTCFullYear(year, month, day);
     },
 
     /**
-     * Set date with value
-     * @param {Number} value: Date value
+     * Set selection date
+     * @param {Date} selection
      */
-    setValue(value) {
-      const dateObj = new Date(value);
-      this._update(dateObj.getUTCFullYear(), dateObj.getUTCMonth(), dateObj.getUTCDate());
+    setSelection(selection) {
+      this.set({ year: selection.getUTCFullYear(),
+                 month: selection.getUTCMonth(),
+                 day: selection.getUTCDate() });
     },
 
     /**
      * Set month. Makes sure the day is <= the last day of the month
      * @param {Number} month
      */
     setMonth(month) {
-      const lastDayOfMonth = this._newUTCDate(this.state.year, month + 1, 0).getUTCDate();
-      this._update(this.state.year, month, Math.min(this.state.day, lastDayOfMonth));
+      const lastDayOfMonth = this._newUTCDate(this.year, month + 1, 0).getUTCDate();
+      this.set({ year: this.year,
+                 month,
+                 day: Math.min(this.day, lastDayOfMonth) });
     },
 
     /**
      * Set year. Makes sure the day is <= the last day of the month
      * @param {Number} year
      */
     setYear(year) {
-      const lastDayOfMonth = this._newUTCDate(year, this.state.month + 1, 0).getUTCDate();
-      this._update(year, this.state.month, Math.min(this.state.day, lastDayOfMonth));
+      const lastDayOfMonth = this._newUTCDate(year, this.month + 1, 0).getUTCDate();
+      this.set({ year,
+                 month: this.month,
+                 day: Math.min(this.day, lastDayOfMonth) });
     },
 
     /**
      * Set month by offset. Makes sure the day is <= the last day of the month
      * @param {Number} offset
      */
     setMonthByOffset(offset) {
-      const lastDayOfMonth = this._newUTCDate(this.state.year, this.state.month + offset + 1, 0).getUTCDate();
-      this._update(this.state.year, this.state.month + offset, Math.min(this.state.day, lastDayOfMonth));
-    },
-
-    /**
-     * Update the states.
-     * @param  {Number} year  [description]
-     * @param  {Number} month [description]
-     * @param  {Number} day  [description]
-     */
-    _update(year, month, day) {
-      // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
-      this.state.dateObj.setUTCFullYear(year, month, day);
-      this.state.year = this.state.dateObj.getUTCFullYear();
-      this.state.month = this.state.dateObj.getUTCMonth();
-      this.state.day = this.state.dateObj.getUTCDate();
+      const lastDayOfMonth = this._newUTCDate(this.year, this.month + offset + 1, 0).getUTCDate();
+      this.set({ year: this.year,
+                 month: this.month + offset,
+                 day: Math.min(this.day, lastDayOfMonth) });
     },
 
     /**
      * Generate the array of months
      * @return {Array<Object>}
      *         {
      *           {Number} value: Month in int
      *           {Boolean} enabled
      *         }
      */
     getMonths() {
-      // TODO: add min/max and step support
       let months = [];
 
       for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
         months.push({
           value: i,
           enabled: true
         });
       }
@@ -128,22 +147,21 @@ function DateKeeper({ year, month, day }
      * Generate the array of years
      * @return {Array<Object>}
      *         {
      *           {Number} value: Year in int
      *           {Boolean} enabled
      *         }
      */
     getYears() {
-      // TODO: add min/max and step support
       let years = [];
 
       const firstItem = this.state.years[0];
       const lastItem = this.state.years[this.state.years.length - 1];
-      const currentYear = this.state.dateObj.getUTCFullYear();
+      const currentYear = this.year;
 
       // Generate new years array when the year is outside of the first &
       // last item range. If not, return the cached result.
       if (!firstItem || !lastItem ||
           currentYear <= firstItem.value + YEAR_BUFFER_SIZE ||
           currentYear >= lastItem.value - YEAR_BUFFER_SIZE) {
         // The year is set in the middle with items on both directions
         for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) {
@@ -156,62 +174,71 @@ function DateKeeper({ year, month, day }
       }
       return this.state.years;
     },
 
     /**
      * Get days for calendar
      * @return {Array<Object>}
      *         {
-     *           {Number} dateValue
-     *           {Number} textContent
+     *           {Date} dateObj
      *           {Array<String>} classNames
+     *           {Boolean} enabled
      *         }
      */
     getDays() {
-      // TODO: add min/max and step support
-      let firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
+      // TODO: add step support
+      const firstDayOfMonth = this._getFirstCalendarDate(this.state.dateObj, this.state.firstDayOfWeek);
+      const month = this.month;
       let days = [];
-      let month = this.state.dateObj.getUTCMonth();
 
       for (let i = 0; i < this.state.calViewSize; i++) {
-        let dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i);
+        const dateObj = this._newUTCDate(firstDayOfMonth.getUTCFullYear(), firstDayOfMonth.getUTCMonth(), firstDayOfMonth.getUTCDate() + i);
         let classNames = [];
+        let enabled = true;
         if (this.state.weekends.includes(dateObj.getUTCDay())) {
           classNames.push("weekend");
         }
         if (month != dateObj.getUTCMonth()) {
           classNames.push("outside");
         }
+        if (dateObj.getTime() < this.state.min.getTime() || dateObj.getTime() > this.state.max.getTime()) {
+          classNames.push("out-of-range");
+          enabled = false;
+        }
+        if (this.state.today.getTime() == dateObj.getTime()) {
+          classNames.push("today");
+        }
         days.push({
-          dateValue: dateObj.getTime(),
-          textContent: dateObj.getUTCDate(),
-          classNames
+          dateObj,
+          classNames,
+          enabled,
         });
       }
       return days;
     },
 
     /**
      * Get week headers for calendar
      * @param  {Number} firstDayOfWeek
+     * @param  {Array<Number>} weekends
      * @return {Array<Object>}
      *         {
      *           {Number} textContent
      *           {Array<String>} classNames
      *         }
      */
-    _getWeekHeaders(firstDayOfWeek) {
+    _getWeekHeaders(firstDayOfWeek, weekends) {
       let headers = [];
       let dayOfWeek = firstDayOfWeek;
 
       for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
         headers.push({
           textContent: dayOfWeek % DAYS_IN_A_WEEK,
-          classNames: this.state.weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
+          classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK) ? ["weekend"] : []
         });
         dayOfWeek++;
       }
       return headers;
     },
 
     /**
      * Get the first day on a calendar month
--- a/toolkit/content/widgets/datepicker.js
+++ b/toolkit/content/widgets/datepicker.js
@@ -19,16 +19,18 @@ function DatePicker(context) {
   DatePicker.prototype = {
     /**
      * Initializes the date picker. Set the default states and properties.
      * @param  {Object} props
      *         {
      *           {Number} year [optional]
      *           {Number} month [optional]
      *           {Number} date [optional]
+     *           {String} min
+     *           {String} max
      *           {Number} firstDayOfWeek
      *           {Array<Number>} weekends
      *           {Array<String>} monthStrings
      *           {Array<String>} weekdayStrings
      *           {String} locale [optional]: User preferred locale
      *         }
      */
     init(props = {}) {
@@ -37,31 +39,20 @@ function DatePicker(context) {
       this._createComponents();
       this._update();
     },
 
     /*
      * Set initial date picker states.
      */
     _setDefaultState() {
-      const now = new Date();
-      const { year = now.getFullYear(),
-              month = now.getMonth(),
-              day = now.getDate(),
-              firstDayOfWeek,
-              weekends,
-              monthStrings,
-              weekdayStrings,
-              locale,
-              dir } = this.props;
+      const { year, month, day, min, max, firstDayOfWeek, weekends,
+              monthStrings, weekdayStrings, locale, dir } = this.props;
       const dateKeeper = new DateKeeper({
-        year, month, day
-      }, {
-        firstDayOfWeek,
-        weekends,
+        year, month, day, min, max, firstDayOfWeek, weekends,
         calViewSize: CAL_VIEW_SIZE
       });
 
       document.dir = dir;
 
       this.state = {
         dateKeeper,
         locale,
@@ -69,19 +60,18 @@ function DatePicker(context) {
         isYearSet: false,
         isMonthSet: false,
         isDateSet: false,
         datetimeOrders: new Intl.DateTimeFormat(locale)
                           .formatToParts(new Date(0)).map(part => part.type),
         getDayString: new Intl.NumberFormat(locale).format,
         getWeekHeaderString: weekday => weekdayStrings[weekday],
         getMonthString: month => monthStrings[month],
-        setValue: ({ dateValue, selectionValue }) => {
-          dateKeeper.setValue(dateValue);
-          this.state.selectionValue = selectionValue;
+        setSelection: ({ selection }) => {
+          dateKeeper.setSelection(selection);
           this.state.isYearSet = true;
           this.state.isMonthSet = true;
           this.state.isDateSet = true;
           this._update();
           this._dispatchState();
           this._closePopup();
         },
         setYear: year => {
@@ -127,42 +117,39 @@ function DatePicker(context) {
         })
       };
     },
 
     /**
      * Update date picker and its components.
      */
     _update() {
-      const { dateKeeper, selectionValue, isMonthPickerVisible } = this.state;
+      const { dateKeeper, isMonthPickerVisible } = this.state;
 
       if (isMonthPickerVisible) {
         this.state.months = dateKeeper.getMonths();
         this.state.years = dateKeeper.getYears();
       } else {
         this.state.days = dateKeeper.getDays();
       }
 
       this.components.monthYear.setProps({
         isVisible: isMonthPickerVisible,
         dateObj: dateKeeper.state.dateObj,
-        month: dateKeeper.state.month,
         months: this.state.months,
-        year: dateKeeper.state.year,
         years: this.state.years,
         toggleMonthPicker: this.state.toggleMonthPicker
       });
       this.components.calendar.setProps({
         isVisible: !isMonthPickerVisible,
         days: this.state.days,
         weekHeaders: dateKeeper.state.weekHeaders,
-        setValue: this.state.setValue,
+        setSelection: this.state.setSelection,
         getDayString: this.state.getDayString,
-        getWeekHeaderString: this.state.getWeekHeaderString,
-        selectionValue
+        getWeekHeaderString: this.state.getWeekHeaderString
       });
 
       isMonthPickerVisible ?
         this.context.monthYearView.classList.remove("hidden") :
         this.context.monthYearView.classList.add("hidden");
     },
 
     /**
@@ -173,17 +160,17 @@ function DatePicker(context) {
         name: "ClosePopup"
       }, "*");
     },
 
     /**
      * Use postMessage to pass the state of picker to the panel.
      */
     _dispatchState() {
-      const { year, month, day } = this.state.dateKeeper.state;
+      const { year, month, day } = this.state.dateKeeper;
       const { isYearSet, isMonthSet, isDaySet } = this.state;
       // The panel is listening to window for postMessage event, so we
       // do postMessage to itself to send data to input boxes.
       window.postMessage({
         name: "PickerPopupChanged",
         detail: {
           year,
           month,
@@ -342,37 +329,35 @@ function DatePicker(context) {
 
     /**
      * Set new properties and pass them to components
      *
      * @param {Object} props
      *        {
      *          {Boolean} isVisible
      *          {Date} dateObj
-     *          {Number} month
-     *          {Number} year
      *          {Array<Object>} months
      *          {Array<Object>} years
      *          {Function} toggleMonthPicker
      *         }
      */
     setProps(props) {
       this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
 
       if (props.isVisible) {
         this.context.monthYear.classList.add("active");
         this.components.month.setState({
-          value: props.month,
+          value: props.dateObj.getUTCMonth(),
           items: props.months,
           isInfiniteScroll: true,
           isValueSet: this.state.isMonthSet,
           smoothScroll: !this.state.firstOpened
         });
         this.components.year.setState({
-          value: props.year,
+          value: props.dateObj.getUTCFullYear(),
           items: props.years,
           isInfiniteScroll: false,
           isValueSet: this.state.isYearSet,
           smoothScroll: !this.state.firstOpened
         });
         this.state.firstOpened = false;
       } else {
         this.context.monthYear.classList.remove("active");
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -161,16 +161,18 @@
                   month: month == undefined ? undefined : month - 1,
                   day,
                   firstDayOfWeek,
                   weekends,
                   monthStrings,
                   weekdayStrings,
                   locale,
                   dir,
+                  min: detail.min,
+                  max: detail.max,
                 }
               });
               break;
             }
           }
         ]]></body>
       </method>
       <method name="setInputBoxValue">
--- a/toolkit/themes/shared/datetimeinputpickers.css
+++ b/toolkit/themes/shared/datetimeinputpickers.css
@@ -7,37 +7,46 @@
   --spinner-width: 3rem;
   --spinner-margin-top-bottom: 0.4rem;
   --spinner-item-height: 2.4rem;
   --spinner-item-margin-bottom: 0.1rem;
   --spinner-button-height: 1.2rem;
   --colon-width: 2rem;
   --day-period-spacing-width: 1rem;
   --calendar-width: 23.1rem;
-  --date-picker-item-height: 2.4rem;
+  --date-picker-item-height: 2.5rem;
+  --date-picker-item-width: 3.3rem;
 
   --border: 0.1rem solid #D6D6D6;
   --border-radius: 0.3rem;
   --border-active-color: #B1B1B1;
 
   --font-color: #191919;
   --fill-color: #EBEBEB;
 
+  --today-fill-color: rgb(212, 212, 212);
+
   --selected-font-color: #FFFFFF;
   --selected-fill-color: #0996F8;
 
   --button-font-color: #858585;
   --button-font-color-hover: #4D4D4D;
   --button-font-color-active: #191919;
   --button-fill-color-active: #D4D4D4;
 
-  --weekday-font-color: #6C6C6C;
-  --weekday-outside-font-color: #6C6C6C;
-  --weekend-font-color: #DA4E44;
-  --weekend-outside-font-color: #FF988F;
+  --weekday-header-font-color: #6C6C6C;
+  --weekend-header-font-color: rgb(218, 78, 68);
+
+  --weekend-font-color: rgb(218, 78, 68);
+  --weekday-outside-font-color: rgb(153, 153, 153);
+  --weekend-outside-font-color: rgb(255, 152, 143);
+
+  --weekday-disabled-font-color: rgba(25, 25, 25, 0.2);
+  --weekend-disabled-font-color: rgba(218, 78, 68, 0.2);
+  --disabled-fill-color: rgba(235, 235, 235, 0.8);
 
   --disabled-opacity: 0.2;
 }
 
 html {
   font-size: 10px;
 }
 
@@ -194,21 +203,21 @@ button.month-year.active::after {
   width: var(--calendar-width);
 }
 
 .week-header {
   display: flex;
 }
 
 .week-header > div {
-  color: var(--weekday-font-color);
+  color: var(--weekday-header-font-color);
 }
 
 .week-header > div.weekend {
-  color: var(--weekend-font-color);
+  color: var(--weekend-header-font-color);
 }
 
 .days-viewport {
   height: 15rem;
   overflow: hidden;
   position: relative;
 }
 
@@ -219,34 +228,55 @@ button.month-year.active::after {
   flex-direction: row;
 }
 
 .week-header > div,
 .days-view > div {
   align-items: center;
   display: flex;
   height: var(--date-picker-item-height);
-  margin: 0.05rem 0.15rem;
   position: relative;
   justify-content: center;
-  width: 3rem;
+  width: var(--date-picker-item-width);
 }
 
-.days-view > div.outside {
+.days-view > .outside {
   color: var(--weekday-outside-font-color);
 }
 
-.days-view > div.weekend {
+.days-view > .weekend {
   color: var(--weekend-font-color);
 }
 
-.days-view > div.weekend.outside {
+.days-view > .weekend.outside {
   color: var(--weekend-outside-font-color);
 }
 
+.days-view > .out-of-range {
+  color: var(--weekday-disabled-font-color);
+  background: var(--disabled-fill-color);
+}
+
+.days-view > .out-of-range.weekend {
+  color: var(--weekend-disabled-font-color);
+}
+
+.days-view > .out-of-range::before {
+  display: none;
+}
+
+.days-view > div:hover::before,
+.days-view > .select::before,
+.days-view > .today::before {
+  top: 5%;
+  bottom: 5%;
+  left: 5%;
+  right: 5%;
+}
+
 #time-picker,
 .month-year-view {
   display: flex;
   flex-direction: row;
   justify-content: center;
 }
 
 .spinner-container {
@@ -304,20 +334,16 @@ button.month-year.active::after {
 
 .spinner-container > .spinner > div:hover::before,
 .calendar-container .days-view > div:hover::before {
   background: var(--fill-color);
   border: var(--border);
   border-radius: var(--border-radius);
   content: "";
   position: absolute;
-  top: 0%;
-  bottom: 0%;
-  left: 0%;
-  right: 0%;
   z-index: -10;
 }
 
 .spinner-container > .spinner:not(.scrolling) > div.selection,
 .calendar-container .days-view > div.selection {
   color: var(--selected-font-color);
 }