UNPKG

@jupyter/web-components

Version:

A component library for building extensions in Jupyter frontends.

256 lines (255 loc) 8.04 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { __decorate } from "tslib"; import { attr, DOM, nullableNumberConverter, observable } from '@microsoft/fast-element'; import { keyArrowDown, keyArrowUp } from '@microsoft/fast-web-utilities'; import { StartEnd, applyMixins, DelegatesARIATextbox } from '@microsoft/fast-foundation'; import { FormAssociatedDateField } from './date-field.form-associated.js'; import { nullableDateConverter } from '../converters.js'; const INVALID_DATE = 'Invalid Date'; /** * A Dext Field Custom HTML Element. * Based largely on the {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date | <input type="date" /> element }. * * @public * @tagname jp-date-field * * @fires input - Fires a custom 'input' event when the value has changed * @fires change - Fires a custom 'change' event when the value has changed */ export class DateField extends FormAssociatedDateField { constructor() { super(...arguments); /** * Amount to increment or decrement the value by * @public * @remarks * HTMLAttribute: step */ this.step = 1; /** * Flag to indicate that the value change is from the user input * @internal */ this.isUserInput = false; } readOnlyChanged() { if (this.proxy instanceof HTMLInputElement) { this.proxy.readOnly = this.readOnly; this.validate(); } } autofocusChanged() { if (this.proxy instanceof HTMLInputElement) { this.proxy.autofocus = this.autofocus; this.validate(); } } listChanged() { if (this.proxy instanceof HTMLInputElement) { this.proxy.setAttribute('list', this.list); this.validate(); } } /** * Ensures that the max is greater than the min and that the value * is less than the max * @param previous - the previous max value * @param next - updated max value * * @internal */ maxChanged(previous, next) { var _a; this.max = next < ((_a = this.min) !== null && _a !== void 0 ? _a : next) ? this.min : next; this.value = this.getValidValue(this.value); } /** * Ensures that the min is less than the max and that the value * is greater than the min * @param previous - previous min value * @param next - updated min value * * @internal */ minChanged(previous, next) { var _a; this.min = next > ((_a = this.max) !== null && _a !== void 0 ? _a : next) ? this.max : next; this.value = this.getValidValue(this.value); } /** * The value property, typed as a number. * * @public */ get valueAsNumber() { return new Date(super.value).valueOf(); } set valueAsNumber(next) { this.value = new Date(next).toString(); } /** * The value property, typed as a date. * * @public */ get valueAsDate() { return new Date(super.value); } set valueAsDate(next) { this.value = next.toString(); } /** * Validates that the value is a number between the min and max * @param previous - previous stored value * @param next - value being updated * @param updateControl - should the text field be updated with value, defaults to true * @internal */ valueChanged(previous, next) { this.value = this.getValidValue(next); if (next !== this.value) { return; } if (this.control && !this.isUserInput) { this.control.value = this.value; } super.valueChanged(previous, this.value); if (previous !== undefined && !this.isUserInput) { this.$emit('change'); } this.isUserInput = false; } /** * Sets the internal value to a valid number between the min and max properties * @param value - user input * * @internal */ getValidValue(value) { var _a, _b; let validValue = new Date(value); if (validValue.toString() === INVALID_DATE) { validValue = ''; } else { validValue = validValue > ((_a = this.max) !== null && _a !== void 0 ? _a : validValue) ? this.max : validValue; validValue = validValue < ((_b = this.min) !== null && _b !== void 0 ? _b : validValue) ? this.min : validValue; validValue = `${validValue.getFullYear().toString().padStart(4, '0')}-${(validValue.getMonth() + 1) .toString() .padStart(2, '0')}-${validValue.getDate().toString().padStart(2, '0')}`; } return validValue; } /** * Increments the value using the step value * * @public */ stepUp() { // Step is given in days: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#step const step = 86400000 * this.step; const value = new Date(this.value); this.value = new Date(value.toString() !== INVALID_DATE ? value.valueOf() + step : 0).toString(); } /** * Decrements the value using the step value * * @public */ stepDown() { // Step is given in days: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date#step const step = 86400000 * this.step; const value = new Date(this.value); this.value = new Date(value.toString() !== INVALID_DATE ? Math.max(value.valueOf() - step, 0) : 0).toString(); } /** * @internal */ connectedCallback() { super.connectedCallback(); this.validate(); this.control.value = this.value; if (this.autofocus) { DOM.queueUpdate(() => { this.focus(); }); } if (!this.appearance) { this.appearance = 'outline'; } } /** * Handles the internal control's `input` event * @internal */ handleTextInput() { this.isUserInput = true; this.value = this.control.value; } /** * Change event handler for inner control. * @remarks * "Change" events are not `composable` so they will not * permeate the shadow DOM boundary. This fn effectively proxies * the change event, emitting a `change` event whenever the internal * control emits a `change` event * @internal */ handleChange() { this.$emit('change'); } /** * Handles the internal control's `keydown` event * @internal */ handleKeyDown(e) { const key = e.key; switch (key) { case keyArrowUp: this.stepUp(); return false; case keyArrowDown: this.stepDown(); return false; } return true; } /** * Handles populating the input field with a validated value when * leaving the input field. * @internal */ handleBlur() { this.control.value = this.value; } } __decorate([ attr ], DateField.prototype, "appearance", void 0); __decorate([ attr({ attribute: 'readonly', mode: 'boolean' }) ], DateField.prototype, "readOnly", void 0); __decorate([ attr({ mode: 'boolean' }) ], DateField.prototype, "autofocus", void 0); __decorate([ attr ], DateField.prototype, "list", void 0); __decorate([ attr({ converter: nullableNumberConverter }) ], DateField.prototype, "step", void 0); __decorate([ attr({ converter: nullableDateConverter }) ], DateField.prototype, "max", void 0); __decorate([ attr({ converter: nullableDateConverter }) ], DateField.prototype, "min", void 0); __decorate([ observable ], DateField.prototype, "defaultSlottedNodes", void 0); applyMixins(DateField, StartEnd, DelegatesARIATextbox);