@jupyter/web-components
Version:
A component library for building extensions in Jupyter frontends.
256 lines (255 loc) • 8.04 kB
JavaScript
// 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);