Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bind:valueAsDate #3527

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,24 @@ export default class Element extends Node {
message: `'files' binding can only be used with <input type="file">`
});
}
} else if (name === 'valueAsDate') {
if (this.name !== 'input') {
component.error(binding, {
code: `invalid-binding`,
message: `'valueAsDate' is not a valid binding on <${this.name}> elements`
});
}

const type = (check_type_attribute() as string);

const valid_date_inputs = new Set(['date', 'time', 'month', 'week']);

if (!valid_date_inputs.has(type)) {
component.error(binding, {
code: `invalid-binding`,
message: `'valueAsDate' binding can only be used with <input type="${Array.from(valid_date_inputs).join('|')}">`
});
}
} else if (name === 'open') {
if (this.name !== 'details') {
component.error(binding, {
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/compile/render_ssr/handlers/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ 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 === 'valueAsDate') {
const snippet = snip(expression);
const type = node.get_static_attribute_value('type');
const fn = `${type}_input_value`;
opening_tag += '${@add_attribute("value", @' + fn + '(' + snippet + '), 1)}';
} else {
const snippet = snip(expression);
opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', 1)}';
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/internal/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,4 @@ export class HtmlTag {
d() {
this.n.forEach(detach);
}
}
}
66 changes: 65 additions & 1 deletion src/runtime/internal/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,68 @@ export function add_attribute(name, value, boolean) {

export function add_classes(classes) {
return classes ? ` class="${classes}"` : ``;
}
}

function is_date(value) {
return value instanceof Date;
}

function pad(n, len = 2) {
n = String(n);
while (n.length < len) n = `0${n}`;
return n;
}

export function date_input_value(date) {
if (!is_date(date)) return '';

const yyyy = date.getUTCFullYear();
const mm = pad(date.getUTCMonth() + 1);
const dd = pad(date.getUTCDate());

return `${yyyy}-${mm}-${dd}`;
}

export function month_input_value(date) {
if (!is_date(date)) return '';

const yyyy = date.getUTCFullYear();
const mm = pad(date.getUTCMonth() + 1);

return `${yyyy}-${mm}`;
}

export function time_input_value(date) {
if (!is_date(date)) return '';

const HH = pad(date.getHours());
const mm = pad(date.getMinutes());

let str = `${HH}:${mm}`;

let s, S;
if (s = date.getSeconds()) str += `:${pad(s)}`;
if (S = date.getMilliseconds()) str += `:${pad(S, 3)}`;

return str;
}

const ONE_WEEK = 1000 * 60 * 60 * 24 * 7;

const to_day = (date, target) => {
const day = date.getUTCDay() || 7;
date.setDate(date.getDate() - (day - target));
};

export const week_input_value = date => {
date = new Date(date);
to_day(date, 4); // pretend it's Thursday to figure out which year we should use

const year = date.getUTCFullYear();
const start = new Date(year, 0, 4); // week 1 always contains Jan 4
to_day(start, 1); // weeks start on Mondays

const elapsed = Math.floor((date - start.getTime()) / ONE_WEEK);

return `${year}-W${pad(elapsed + 1)}`;
};
63 changes: 63 additions & 0 deletions test/runtime/samples/binding-input-valueasdate-date/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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: `
<input type=date>
<p>[object Date] ${SEP_03_2019_DATE_VALUE}</p>
`,

ssrHtml: `
<input type=date value='${SEP_03_2019_INPUT_VALUE}'>
<p>[object Date] ${SEP_03_2019_DATE_VALUE}</p>
`,

async test({ assert, component, target, window }) {
const input = target.querySelector('input');
// https://github.com/jsdom/jsdom/issues/2658
// 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');

// https://github.com/jsdom/jsdom/issues/2658
// input.value = OCT_07_2019_INPUT_VALUE;
input.valueAsDate = OCT_07_2019_DATE_VALUE;
await input.dispatchEvent(event);

assert.equal(component.date.toString(), OCT_07_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='date'>
<p>[object Date] ${OCT_07_2019_DATE_VALUE}</p>
`);

component.date = SEP_03_2019_DATE_VALUE;
// https://github.com/jsdom/jsdom/issues/2658
// assert.equal(input.value, SEP_03_2019_INPUT_VALUE);
assert.equal(input.valueAsDate.toString(), SEP_03_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='date'>
<p>[object Date] ${SEP_03_2019_DATE_VALUE}</p>
`);

// https://github.com/jsdom/jsdom/issues/2658
// empty string should be treated as undefined
// input.value = '';

input.valueAsDate = null;
await input.dispatchEvent(event);

assert.equal(component.date, null);
assert.htmlEqual(target.innerHTML, `
<input type='date'>
<p>[object Null] null</p>
`);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
export let date;
</script>

<input type='date' bind:valueAsDate={date}>
<p>{Object.prototype.toString.call(date)} {date}</p>
63 changes: 63 additions & 0 deletions test/runtime/samples/binding-input-valueasdate-month/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const SEP_2019_INPUT_VALUE = '2019-09';
const SEP_2019_DATE_VALUE = new Date(SEP_2019_INPUT_VALUE);

const OCT_2019_INPUT_VALUE = '2019-10';
const OCT_2019_DATE_VALUE = new Date(OCT_2019_INPUT_VALUE);

export default {
props: {
month: SEP_2019_DATE_VALUE
},

html: `
<input type=month>
<p>[object Date] ${SEP_2019_DATE_VALUE}</p>
`,

ssrHtml: `
<input type=month value='${SEP_2019_INPUT_VALUE}'>
<p>[object Date] ${SEP_2019_DATE_VALUE}</p>
`,

async test({ assert, component, target, window }) {
const input = target.querySelector('input');
// https://github.com/jsdom/jsdom/issues/2658
// assert.equal(input.value, SEP_2019_INPUT_VALUE);
assert.equal(component.month.toString(), SEP_2019_DATE_VALUE.toString());

const event = new window.Event('input');

// https://github.com/jsdom/jsdom/issues/2658
// input.value = OCT_2019_INPUT_VALUE;
input.valueAsDate = OCT_2019_DATE_VALUE;
await input.dispatchEvent(event);

assert.equal(component.month.toString(), OCT_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='month'>
<p>[object Date] ${OCT_2019_DATE_VALUE}</p>
`);

component.month = SEP_2019_DATE_VALUE;
// https://github.com/jsdom/jsdom/issues/2658
// assert.equal(input.value, SEP_2019_INPUT_VALUE);
assert.equal(input.valueAsDate.toString(), SEP_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='month'>
<p>[object Date] ${SEP_2019_DATE_VALUE}</p>
`);

// https://github.com/jsdom/jsdom/issues/2658
// empty string should be treated as undefined
// input.value = '';

input.valueAsDate = null;
await input.dispatchEvent(event);

assert.equal(component.month, null);
assert.htmlEqual(target.innerHTML, `
<input type='month'>
<p>[object Null] null</p>
`);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
export let month;
</script>

<input type="month" bind:valueAsDate={month}>
<p>{Object.prototype.toString.call(month)} {month}</p>
48 changes: 48 additions & 0 deletions test/runtime/samples/binding-input-valueasdate-time/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const LUNCHTIME = new Date(1970, 0, 1, 12, 30);
const TEATIME = new Date(1970, 0, 1, 12, 30);

export default {
props: {
time: LUNCHTIME
},

html: `
<input type=time>
<p>[object Date] ${LUNCHTIME}</p>
`,

ssrHtml: `
<input type=time value='12:30'>
<p>[object Date] ${LUNCHTIME}</p>
`,

async test({ assert, component, target, window }) {
const input = target.querySelector('input');
const event = new window.Event('input');

input.valueAsDate = TEATIME;
await input.dispatchEvent(event);

assert.equal(component.time.toString(), TEATIME.toString());
assert.htmlEqual(target.innerHTML, `
<input type='time'>
<p>[object Date] ${TEATIME}</p>
`);

component.time = LUNCHTIME;
assert.equal(input.valueAsDate.toString(), LUNCHTIME.toString());
assert.htmlEqual(target.innerHTML, `
<input type='time'>
<p>[object Date] ${LUNCHTIME}</p>
`);

input.valueAsDate = null;
await input.dispatchEvent(event);

assert.equal(component.time, null);
assert.htmlEqual(target.innerHTML, `
<input type='time'>
<p>[object Null] null</p>
`);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
export let time;
</script>

<input type="time" bind:valueAsDate={time}>
<p>{Object.prototype.toString.call(time)} {time}</p>
63 changes: 63 additions & 0 deletions test/runtime/samples/binding-input-valueasdate-week/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const SEP_W36_2019_INPUT_VALUE = '2019-W36';
const SEP_W36_2019_DATE_VALUE = new Date('2019-09-03');

const W41_2019_INPUT_VALUE = '2019-10-07';
const W41_2019_DATE_VALUE = new Date(W41_2019_INPUT_VALUE);

export default {
props: {
week: SEP_W36_2019_DATE_VALUE
},

html: `
<input type=week>
<p>[object Date] ${SEP_W36_2019_DATE_VALUE}</p>
`,

ssrHtml: `
<input type=week value='${SEP_W36_2019_INPUT_VALUE}'>
<p>[object Date] ${SEP_W36_2019_DATE_VALUE}</p>
`,

async test({ assert, component, target, window }) {
const input = target.querySelector('input');
// https://github.com/jsdom/jsdom/issues/2658
// assert.equal(input.value, SEP_W36_2019_INPUT_VALUE);
assert.equal(component.week.toString(), SEP_W36_2019_DATE_VALUE.toString());

const event = new window.Event('input');

// https://github.com/jsdom/jsdom/issues/2658
// input.value = W41_2019_INPUT_VALUE;
input.valueAsDate = W41_2019_DATE_VALUE;
await input.dispatchEvent(event);

assert.equal(component.week.toString(), W41_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='week'>
<p>[object Date] ${W41_2019_DATE_VALUE}</p>
`);

component.week = SEP_W36_2019_DATE_VALUE;
// https://github.com/jsdom/jsdom/issues/2658
// assert.equal(input.value, SEP_W36_2019_INPUT_VALUE);
assert.equal(input.valueAsDate.toString(), SEP_W36_2019_DATE_VALUE.toString());
assert.htmlEqual(target.innerHTML, `
<input type='week'>
<p>[object Date] ${SEP_W36_2019_DATE_VALUE}</p>
`);

// https://github.com/jsdom/jsdom/issues/2658
// empty string should be treated as undefined
// input.value = '';

input.valueAsDate = null;
await input.dispatchEvent(event);

assert.equal(component.week, null);
assert.htmlEqual(target.innerHTML, `
<input type='week'>
<p>[object Null] null</p>
`);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
export let week;
</script>

<input type="week" bind:valueAsDate={week}>
<p>{Object.prototype.toString.call(week)} {week}</p>