diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 01849be5ef81..ae3179e84c5c 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -325,6 +325,10 @@ function get_value_from_dom( return `@to_number(this.${name})`; } + if (type === 'date') { + return `@value_as_date(this.${name})`; + } + if ((name === 'buffered' || name === 'seekable' || name === 'played')) { return `@time_ranges_to_array(this.${name})`; } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 146324f2a40a..ab7c7e1948f9 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -64,6 +64,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); + const is_date_input = ( + node.name === 'input' && + node.attributes.some((attribute) => attribute.name === 'type' && attribute.get_static_value() === 'date') + ); + const slot = node.get_static_attribute_value('slot'); const component = node.find_nearest(/InlineComponent/); if (slot && component) { @@ -162,6 +167,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } else if (binding.name === 'value' && node.name === 'textarea') { const snippet = snip(expression); node_contents = '${(' + snippet + ') || ""}'; + } else if (binding.name === 'value' && is_date_input) { + const snippet = snip(expression); + opening_tag += '${@add_attribute("' + name + '", @date_as_value(' + snippet + '), 1)}'; } else { const snippet = snip(expression); opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', 1)}'; diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index d8ebffa2b186..7436380da8e5 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -125,6 +125,22 @@ export function to_number(value) { return value === '' ? undefined : +value; } +export function value_as_date(value) { + const valueAsDate = new Date(value); + return isNaN(valueAsDate.getTime()) ? undefined : valueAsDate; +} + +export function date_as_value(date) { + const validDate = Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date.getTime()); + if (!validDate) { + return ''; + } + const yyyy = date.getUTCFullYear(); + const mm = date.getUTCMonth() + 1 < 10 ? `0${date.getUTCMonth() + 1}` : date.getUTCMonth() + 1; + const dd = date.getUTCDate() < 10 ? `0${date.getUTCDate()}` : date.getUTCDate(); + return `${yyyy}-${mm}-${dd}`; +} + export function time_ranges_to_array(ranges) { const array = []; for (let i = 0; i < ranges.length; i += 1) { @@ -170,7 +186,9 @@ export function set_data(text, data) { } export function set_input_value(input, value) { - if (value != null || input.value) { + if (Object.prototype.toString.call(value) === '[object Date]') { + input.value = date_as_value(value); + } else if (value != null || input.value) { input.value = value; } } @@ -291,4 +309,4 @@ export class HtmlTag { d() { this.n.forEach(detach); } -} \ No newline at end of file +} diff --git a/test/runtime/samples/binding-input-date/_config.js b/test/runtime/samples/binding-input-date/_config.js new file mode 100644 index 000000000000..d74274a2334a --- /dev/null +++ b/test/runtime/samples/binding-input-date/_config.js @@ -0,0 +1,55 @@ +const SEP_03_2019_INPUT_VALUE = '2019-09-03'; +const SEP_03_2019_DATE_VALUE = new Date(SEP_03_2019_INPUT_VALUE); + +const OCT_07_2019_INPUT_VALUE = '2019-10-07'; +const OCT_07_2019_DATE_VALUE = new Date(OCT_07_2019_INPUT_VALUE); + +export default { + props: { + date: SEP_03_2019_DATE_VALUE + }, + + html: ` + +
[object Date] ${SEP_03_2019_DATE_VALUE}
+ `, + + ssrHtml: ` + +[object Date] ${SEP_03_2019_DATE_VALUE}
+ `, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + assert.equal(input.value, SEP_03_2019_INPUT_VALUE); + assert.equal(component.date.toString(), SEP_03_2019_DATE_VALUE.toString()); + + const event = new window.Event('input'); + + input.value = OCT_07_2019_INPUT_VALUE; + await input.dispatchEvent(event); + + assert.equal(component.date.toString(), OCT_07_2019_DATE_VALUE.toString()); + assert.htmlEqual(target.innerHTML, ` + +[object Date] ${OCT_07_2019_DATE_VALUE}
+ `); + + component.date = SEP_03_2019_DATE_VALUE; + assert.equal(input.value, SEP_03_2019_INPUT_VALUE); + assert.htmlEqual(target.innerHTML, ` + +[object Date] ${SEP_03_2019_DATE_VALUE}
+ `); + + // empty string should be treated as undefined + input.value = ''; + await input.dispatchEvent(event); + + assert.equal(component.date, undefined); + assert.htmlEqual(target.innerHTML, ` + +[object Undefined] undefined
+ `); + }, +}; diff --git a/test/runtime/samples/binding-input-date/main.svelte b/test/runtime/samples/binding-input-date/main.svelte new file mode 100644 index 000000000000..f6f0fbc9535c --- /dev/null +++ b/test/runtime/samples/binding-input-date/main.svelte @@ -0,0 +1,6 @@ + + + +{Object.prototype.toString.call(date)} {date}