DatePicker rework (#737)

* fix(date-picker): append calendar to date picker element #345

Fixes #345

* fix(date-picker): do not import rangePlugin from esm folder

* fix(date-picker): correctly type change event for single/range types

* feat(date-picker): add valueFrom, valueTo for range datepicker

* docs(date-picker): add range type example

* docs(date-picker): extract range example to iframe

* docs(date-picker): extract single type to iframe
This commit is contained in:
Eric Liu 2021-07-08 16:33:03 -07:00 committed by GitHub
commit edefd6429b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 139 additions and 56 deletions

View file

@ -1048,10 +1048,11 @@ None.
### Props ### Props
| Prop name | Kind | Reactive | Type | Default value | Description | | Prop name | Kind | Reactive | Type | Default value | Description |
| :------------- | :--------------- | :------- | :--------------------------------------------------- | ------------------------------------------------ | --------------------------------------------- | | :------------- | :--------------- | :------- | :--------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| valueTo | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the date picker end date value (to)<br />Only works with the "range" date picker type |
| valueFrom | <code>let</code> | Yes | <code>string</code> | <code>""</code> | Specify the date picker start date value (from)<br />Only works with the "range" date picker type |
| value | <code>let</code> | Yes | <code>number &#124; string</code> | <code>""</code> | Specify the date picker input value | | value | <code>let</code> | Yes | <code>number &#124; string</code> | <code>""</code> | Specify the date picker input value |
| datePickerType | <code>let</code> | No | <code>"simple" &#124; "single" &#124; "range"</code> | <code>"simple"</code> | Specify the date picker type | | datePickerType | <code>let</code> | No | <code>"simple" &#124; "single" &#124; "range"</code> | <code>"simple"</code> | Specify the date picker type |
| appendTo | <code>let</code> | No | <code>HTMLElement</code> | -- | Specify the element to append the calendar to |
| dateFormat | <code>let</code> | No | <code>string</code> | <code>"m/d/Y"</code> | Specify the date format | | dateFormat | <code>let</code> | No | <code>string</code> | <code>"m/d/Y"</code> | Specify the date format |
| maxDate | <code>let</code> | No | <code>null &#124; string &#124; Date</code> | <code>null</code> | Specify the maximum date | | maxDate | <code>let</code> | No | <code>null &#124; string &#124; Date</code> | <code>null</code> | Specify the maximum date |
| minDate | <code>let</code> | No | <code>null &#124; string &#124; Date</code> | <code>null</code> | Specify the minimum date | | minDate | <code>let</code> | No | <code>null &#124; string &#124; Date</code> | <code>null</code> | Specify the minimum date |
@ -1069,12 +1070,12 @@ None.
### Events ### Events
| Event name | Type | Detail | | Event name | Type | Detail |
| :--------- | :--------- | :----- | | :--------- | :--------- | :---------------------------------------------------------------------------------------------------------------------------------- |
| change | dispatched | <code>string &#124; { selectedDates: [dateFrom: Date, dateTo?: Date]; dateStr: string &#124; { from: string; to: string; } }</code> |
| click | forwarded | -- | | click | forwarded | -- |
| mouseover | forwarded | -- | | mouseover | forwarded | -- |
| mouseenter | forwarded | -- | | mouseenter | forwarded | -- |
| mouseleave | forwarded | -- | | mouseleave | forwarded | -- |
| change | dispatched | -- |
## `DatePickerInput` ## `DatePickerInput`

View file

@ -2393,13 +2393,24 @@
"reactive": true "reactive": true
}, },
{ {
"name": "appendTo", "name": "valueFrom",
"kind": "let", "kind": "let",
"description": "Specify the element to append the calendar to", "description": "Specify the date picker start date value (from)\nOnly works with the \"range\" date picker type",
"type": "HTMLElement", "type": "string",
"value": "\"\"",
"isFunction": false, "isFunction": false,
"constant": false, "constant": false,
"reactive": false "reactive": true
},
{
"name": "valueTo",
"kind": "let",
"description": "Specify the date picker end date value (to)\nOnly works with the \"range\" date picker type",
"type": "string",
"value": "\"\"",
"isFunction": false,
"constant": false,
"reactive": true
}, },
{ {
"name": "dateFormat", "name": "dateFormat",
@ -2474,11 +2485,15 @@
], ],
"slots": [{ "name": "__default__", "default": true, "slot_props": "{}" }], "slots": [{ "name": "__default__", "default": true, "slot_props": "{}" }],
"events": [ "events": [
{
"type": "dispatched",
"name": "change",
"detail": "string | { selectedDates: [dateFrom: Date, dateTo?: Date]; dateStr: string | { from: string; to: string; } }"
},
{ "type": "forwarded", "name": "click", "element": "div" }, { "type": "forwarded", "name": "click", "element": "div" },
{ "type": "forwarded", "name": "mouseover", "element": "div" }, { "type": "forwarded", "name": "mouseover", "element": "div" },
{ "type": "forwarded", "name": "mouseenter", "element": "div" }, { "type": "forwarded", "name": "mouseenter", "element": "div" },
{ "type": "forwarded", "name": "mouseleave", "element": "div" }, { "type": "forwarded", "name": "mouseleave", "element": "div" }
{ "type": "dispatched", "name": "change" }
], ],
"typedefs": [], "typedefs": [],
"rest_props": { "type": "Element", "name": "div" } "rest_props": { "type": "Element", "name": "div" }

View file

@ -57,9 +57,11 @@ components: ["DatePicker", "DatePickerInput", "DatePickerSkeleton"]
### Single ### Single
<DatePicker datePickerType="single"> <FileSource src="/framed/DatePicker/DatePickerSingle" />
<DatePickerInput labelText="Schedule a meeting" placeholder="mm/dd/yyyy" />
</DatePicker> ### Range
<FileSource src="/framed/DatePicker/DatePickerRange" />
### Skeleton ### Skeleton

View file

@ -0,0 +1,8 @@
<script>
import { DatePicker, DatePickerInput } from "carbon-components-svelte";
</script>
<DatePicker datePickerType="range" on:change>
<DatePickerInput labelText="Start date" placeholder="mm/dd/yyyy" />
<DatePickerInput labelText="End date" placeholder="mm/dd/yyyy" />
</DatePicker>

View file

@ -0,0 +1,7 @@
<script>
import { DatePicker, DatePickerInput } from "carbon-components-svelte";
</script>
<DatePicker datePickerType="single" on:change>
<DatePickerInput labelText="Meeting date" placeholder="mm/dd/yyyy" />
</DatePicker>

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
optimizeDeps: { optimizeDeps: {
include: ["clipboard-copy"], include: ["clipboard-copy", "flatpickr/dist/plugins/rangePlugin"],
exclude: ["@sveltech/routify"], exclude: ["@sveltech/routify"],
}, },
}; };

View file

@ -1,6 +1,6 @@
<script> <script>
/** /**
* @dispatch {string} change * @event {string | { selectedDates: [dateFrom: Date, dateTo?: Date]; dateStr: string | { from: string; to: string; } }} change
*/ */
/** /**
@ -16,10 +16,18 @@
export let value = ""; export let value = "";
/** /**
* Specify the element to append the calendar to * Specify the date picker start date value (from)
* @type {HTMLElement} * Only works with the "range" date picker type
* @type {string}
*/ */
export let appendTo = document.body; export let valueFrom = "";
/**
* Specify the date picker end date value (to)
* Only works with the "range" date picker type
* @type {string}
*/
export let valueTo = "";
/** Specify the date format */ /** Specify the date format */
export let dateFormat = "m/d/Y"; export let dateFormat = "m/d/Y";
@ -65,18 +73,23 @@
(_) => _.filter(({ labelText }) => !!labelText).length === 0 (_) => _.filter(({ labelText }) => !!labelText).length === 0
); );
const inputValue = writable(value); const inputValue = writable(value);
const inputValueFrom = writable(valueFrom);
const inputValueTo = writable(valueTo);
const mode = writable(datePickerType); const mode = writable(datePickerType);
const range = derived(mode, (_) => _ === "range"); const range = derived(mode, (_) => _ === "range");
const hasCalendar = derived(mode, (_) => _ === "single" || _ === "range"); const hasCalendar = derived(mode, (_) => _ === "single" || _ === "range");
let calendar = undefined; let calendar = null;
let datePickerRef = undefined; let datePickerRef = null;
let inputRef = undefined; let inputRef = null;
let inputRefTo = undefined; let inputRefTo = null;
setContext("DatePicker", { setContext("DatePicker", {
range, range,
inputValue, inputValue,
inputValueFrom,
inputValueTo,
inputIds,
hasCalendar, hasCalendar,
add: (data) => { add: (data) => {
inputs.update((_) => [..._, data]); inputs.update((_) => [..._, data]);
@ -119,7 +132,7 @@
if ($hasCalendar && !calendar) { if ($hasCalendar && !calendar) {
calendar = createCalendar({ calendar = createCalendar({
options: { options: {
appendTo, appendTo: datePickerRef,
dateFormat, dateFormat,
defaultDate: $inputValue, defaultDate: $inputValue,
locale, locale,
@ -133,10 +146,16 @@
const detail = { selectedDates: calendar.selectedDates }; const detail = { selectedDates: calendar.selectedDates };
if ($range) { if ($range) {
const from = inputRef.value;
const to = inputRefTo.value;
detail.dateStr = { detail.dateStr = {
from: inputRef.value, from: inputRef.value,
to: inputRefTo.value, to: inputRefTo.value,
}; };
valueFrom = from;
valueTo = to;
} else { } else {
detail.dateStr = inputRef.value; detail.dateStr = inputRef.value;
} }
@ -146,9 +165,16 @@
}); });
} }
if (calendar && !$range) { if (calendar) {
if ($range) {
calendar.setDate([$inputValueFrom, $inputValueTo]);
// workaround to remove the default range plugin separator "to"
inputRef.value = $inputValueFrom;
} else {
calendar.setDate($inputValue); calendar.setDate($inputValue);
} }
}
}); });
onDestroy(() => { onDestroy(() => {
@ -159,19 +185,17 @@
$: inputValue.set(value); $: inputValue.set(value);
$: value = $inputValue; $: value = $inputValue;
$: inputValueFrom.set(valueFrom);
$: valueFrom = $inputValueFrom;
$: inputValueTo.set(valueTo);
$: valueTo = $inputValueTo;
</script> </script>
<svelte:body <svelte:body
on:click="{({ target }) => { on:click="{({ target }) => {
if (!calendar || !calendar.isOpen) { if (!calendar || !calendar.isOpen) return;
return; if (datePickerRef && datePickerRef.contains(target)) return;
} if (!calendar.calendarContainer.contains(target)) calendar.close();
if (datePickerRef && datePickerRef.contains(target)) {
return;
}
if (!calendar.calendarContainer.contains(target)) {
calendar.close();
}
}}" /> }}" />
<div <div

View file

@ -60,11 +60,14 @@
add, add,
hasCalendar, hasCalendar,
declareRef, declareRef,
inputIds,
updateValue, updateValue,
blurInput, blurInput,
openCalendar, openCalendar,
focusCalendar, focusCalendar,
inputValue, inputValue,
inputValueFrom,
inputValueTo,
} = getContext("DatePicker"); } = getContext("DatePicker");
add({ id, labelText }); add({ id, labelText });
@ -105,7 +108,11 @@
pattern="{pattern}" pattern="{pattern}"
disabled="{disabled}" disabled="{disabled}"
{...$$restProps} {...$$restProps}
value="{!$range ? $inputValue : undefined}" value="{$range
? $inputIds.indexOf(id) === 0
? $inputValueFrom
: $inputValueTo
: $inputValue}"
class:bx--date-picker__input="{true}" class:bx--date-picker__input="{true}"
class:bx--date-picker__input--invalid="{invalid}" class:bx--date-picker__input--invalid="{invalid}"
class="{size && `bx--date-picker__input--${size}`}" class="{size && `bx--date-picker__input--${size}`}"

View file

@ -1,5 +1,5 @@
import flatpickr from "flatpickr"; import flatpickr from "flatpickr";
import rangePlugin from "flatpickr/dist/esm/plugins/rangePlugin"; import rangePlugin from "flatpickr/dist/plugins/rangePlugin";
let l10n; let l10n;

View file

@ -2,7 +2,11 @@
import { DatePicker, DatePickerSkeleton, DatePickerInput } from "../types"; import { DatePicker, DatePickerSkeleton, DatePickerInput } from "../types";
</script> </script>
<DatePicker> <DatePicker
on:change="{(e) => {
console.log(e.detail);
}}"
>
<DatePickerInput labelText="Date of birth" placeholder="mm/dd/yyyy" /> <DatePickerInput labelText="Date of birth" placeholder="mm/dd/yyyy" />
</DatePicker> </DatePicker>
@ -51,7 +55,7 @@
/> />
</DatePicker> </DatePicker>
<DatePicker datePickerType="single"> <DatePicker valueFrom="" valueTo="" datePickerType="single">
<DatePickerInput labelText="Schedule a meeting" placeholder="mm/dd/yyyy" /> <DatePickerInput labelText="Schedule a meeting" placeholder="mm/dd/yyyy" />
</DatePicker> </DatePicker>

View file

@ -16,9 +16,18 @@ export interface DatePickerProps
value?: number | string; value?: number | string;
/** /**
* Specify the element to append the calendar to * Specify the date picker start date value (from)
* Only works with the "range" date picker type
* @default ""
*/ */
appendTo?: HTMLElement; valueFrom?: string;
/**
* Specify the date picker end date value (to)
* Only works with the "range" date picker type
* @default ""
*/
valueTo?: string;
/** /**
* Specify the date format * Specify the date format
@ -66,11 +75,17 @@ export interface DatePickerProps
export default class DatePicker extends SvelteComponentTyped< export default class DatePicker extends SvelteComponentTyped<
DatePickerProps, DatePickerProps,
{ {
change: CustomEvent<
| string
| {
selectedDates: [dateFrom: Date, dateTo?: Date];
dateStr: string | { from: string; to: string };
}
>;
click: WindowEventMap["click"]; click: WindowEventMap["click"];
mouseover: WindowEventMap["mouseover"]; mouseover: WindowEventMap["mouseover"];
mouseenter: WindowEventMap["mouseenter"]; mouseenter: WindowEventMap["mouseenter"];
mouseleave: WindowEventMap["mouseleave"]; mouseleave: WindowEventMap["mouseleave"];
change: CustomEvent<any>;
}, },
{ default: {} } { default: {} }
> {} > {}