Skip to content

Commit

Permalink
[FEATURE modernized-built-in-components] First-pass implementation
Browse files Browse the repository at this point in the history
First-pass implementation for converting the `<Input>` component
into an internal (not classic) component compatibly. This passes
all the existing tests.

Some follow-up work before this is ready for prime time:

- Implement any remaining classic component features not covered
  by the existing tests.

- Detect any unimplementable classic component features and deopt
  into the previous implementation.

- Write the second deprecation RFC and get the proposal merged.
  • Loading branch information
chancancode committed Oct 27, 2020
1 parent 05e278a commit 63f6024
Show file tree
Hide file tree
Showing 10 changed files with 917 additions and 93 deletions.
685 changes: 685 additions & 0 deletions packages/@ember/-internals/glimmer/lib/components/input.ts

Large diffs are not rendered by default.

98 changes: 91 additions & 7 deletions packages/@ember/-internals/glimmer/lib/templates/input.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,91 @@
{{~#let (component '-checkbox') (component '-text-field') as |Checkbox TextField|~}}
{{~#if this.isCheckbox~}}
<Checkbox @target={{this.caller}} @__ARGS__={{this.args}} ...attributes />
{{~else~}}
<TextField @target={{this.caller}} @__ARGS__={{this.args}} ...attributes />
{{~/if~}}
{{~/let~}}
{{~#if this.moderinzed ~}}
<input
{{!-- for compatibility --}}
id={{this.id}}
class={{this.class}}

{{!-- deprecated attribute bindings --}}
autocapitalize={{this._autocapitalize}}
autocorrect={{this._autocorrect}}
autofocus={{this._autofocus}}
disabled={{this._disabled}}
form={{this._form}}
maxlength={{this._maxlength}}
minlength={{this._minlength}}
placeholder={{this._placeholder}}
readonly={{this._readonly}}
required={{this._required}}
selectionDirection={{this._selectionDirection}}
spellcheck={{this._spellcheck}}
tabindex={{this._tabindex}}
title={{this._title}}
accept={{this._accept}}
autocomplete={{this._autocomplete}}
autosave={{this._autosave}}
dir={{this._dir}}
formaction={{this._formaction}}
formenctype={{this._formenctype}}
formmethod={{this._formmethod}}
formnovalidate={{this._formnovalidate}}
formtarget={{this._formtarget}}
height={{this._height}}
inputmode={{this._inputmode}}
lang={{this._lang}}
list={{this._list}}
max={{this._max}}
min={{this._min}}
multiple={{this._multiple}}
name={{this._name}}
pattern={{this._pattern}}
size={{this._size}}
step={{this._step}}
width={{this._width}}
indeterminate={{this._indeterminate}}

...attributes

type={{this.type}}
checked={{this.checked}}
value={{this.value}}

{{on "change" this.change}}
{{on "input" this.input}}
{{on "keyup" this.keyUp}}
{{on "paste" this.valueDidChange}}
{{on "cut" this.valueDidChange}}

{{!-- deprecated native event callbacks --}}
{{on "touchstart" this._touchStart}}
{{on "touchmove" this._touchMove}}
{{on "touchend" this._touchEnd}}
{{on "touchcancel" this._touchCancel}}
{{on "keydown" this._keyDown}}
{{on "keypress" this._keyPress}}
{{on "mousedown" this._mouseDown}}
{{on "mouseup" this._mouseUp}}
{{on "contextmenu" this._contextMenu}}
{{on "click" this._click}}
{{on "dblclick" this._doubleClick}}
{{on "focusin" this._focusIn}}
{{on "focusout" this._focusOut}}
{{on "submit" this._submit}}
{{on "dragstart" this._dragStart}}
{{on "drag" this._drag}}
{{on "dragenter" this._dragEnter}}
{{on "dragleave" this._dragLeave}}
{{on "dragover" this._dragOver}}
{{on "drop" this._drop}}
{{on "dragend" this._dragEnd}}
{{on "mouseenter" this._mouseEnter}}
{{on "mouseleave" this._mouseLeave}}
{{on "mousemove" this._mouseMove}}
/>
{{~else~}}
{{~#let (component '-checkbox') (component '-text-field') as |Checkbox TextField|~}}
{{~#if this.isCheckbox~}}
<Checkbox @target={{this.caller}} @__ARGS__={{this.args}} ...attributes />
{{~else~}}
<TextField @target={{this.caller}} @__ARGS__={{this.args}} ...attributes />
{{~/if~}}
{{~/let~}}
{{/if}}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@ember/-internals/glimmer';
import { EngineInstanceOptions, Owner } from '@ember/-internals/owner';
import { Route } from '@ember/-internals/routing';
import { EMBER_MODERNIZED_BUILT_IN_COMPONENTS } from '@ember/canary-features';
import templateOnly from '@ember/component/template-only';
import Controller from '@ember/controller';
import { captureRenderTree } from '@ember/debug';
Expand Down Expand Up @@ -1276,17 +1277,19 @@ if (ENV._DEBUG_RENDER_TREE) {
instance: (instance: object) => inputToString.test(instance.toString()),
template: 'packages/@ember/-internals/glimmer/lib/templates/input.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
children: EMBER_MODERNIZED_BUILT_IN_COMPONENTS
? []
: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
},
]);

Expand All @@ -1300,17 +1303,19 @@ if (ENV._DEBUG_RENDER_TREE) {
instance: (instance: object) => inputToString.test(instance.toString()),
template: 'packages/@ember/-internals/glimmer/lib/templates/input.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
children: EMBER_MODERNIZED_BUILT_IN_COMPONENTS
? []
: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
},
{
type: 'component',
Expand All @@ -1319,17 +1324,19 @@ if (ENV._DEBUG_RENDER_TREE) {
instance: (instance: object) => inputToString.test(instance.toString()),
template: 'packages/@ember/-internals/glimmer/lib/templates/input.hbs',
bounds: this.nodeBounds(this.element.lastChild),
children: [
{
type: 'component',
name: '-checkbox',
args: { positional: [], named: { target, type: 'checkbox', checked: false } },
instance: (instance: object) => instance['checked'] === false,
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.lastChild),
children: [],
},
],
children: EMBER_MODERNIZED_BUILT_IN_COMPONENTS
? []
: [
{
type: 'component',
name: '-checkbox',
args: { positional: [], named: { target, type: 'checkbox', checked: false } },
instance: (instance: object) => instance['checked'] === false,
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.lastChild),
children: [],
},
],
},
]);

Expand All @@ -1343,17 +1350,19 @@ if (ENV._DEBUG_RENDER_TREE) {
instance: (instance: object) => inputToString.test(instance.toString()),
template: 'packages/@ember/-internals/glimmer/lib/templates/input.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
children: EMBER_MODERNIZED_BUILT_IN_COMPONENTS
? []
: [
{
type: 'component',
name: '-text-field',
args: { positional: [], named: { target, type: 'text', value: 'first' } },
instance: (instance: object) => instance['value'] === 'first',
template: 'packages/@ember/-internals/glimmer/lib/templates/empty.hbs',
bounds: this.nodeBounds(this.element.firstChild),
children: [],
},
],
},
]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,19 @@ class InputRenderingTest extends RenderingTestCase {
this.assert.equal($standard.type, $custom.type);

Object.keys(events).forEach((event) => {
this.triggerEvent(event, null, '#standard');
this.triggerEvent(event, null, '#custom');
// triggerEvent does not seem to work with focusin and focusout events
if (event !== 'focusin' && event !== 'focusout') {
this.triggerEvent(event, null, '#standard');
this.triggerEvent(event, null, '#custom');
}
});

// test focusin and focusout by actually moving focus
$standard[0].focus();
$standard[0].blur();
$custom[0].focus();
$custom[0].blur();

this.assert.ok(
triggered.standard.length > 10,
'sanity check that most events are triggered (standard)'
Expand Down Expand Up @@ -958,14 +967,35 @@ moduleFor(
this.assertAttr('tabindex', '10');
}

['@test `value` property assertion']() {
['@feature(!EMBER_MODERNIZED_BUILT_IN_COMPONENTS) `value` property assertion']() {
expectAssertion(() => {
this.render(`<Input @type="checkbox" @value={{value}} />`, {
value: 'value',
});
}, /checkbox.+@value.+not supported.+use.+@checked.+instead/);
}

['@feature(EMBER_MODERNIZED_BUILT_IN_COMPONENTS) `value` property warning']() {
let message =
'`<Input @type="checkbox" />` reflects its checked state via the `@checked` argument. ' +
'You wrote `<Input @type="checkbox" @value={{...}} />` which is likely not what you intended. ' +
'Did you mean `<Input @type="checkbox" @checked={{...}} />`?';

expectWarning(() => {
this.render(`<Input @type="checkbox" @value={{value}} />`, {
value: true,
});
}, message);

this.assert.strictEqual(this.context.value, true);
this.assertCheckboxIsNotChecked();

expectWarning(() => this.$input()[0].click(), message);

this.assert.strictEqual(this.context.value, true);
this.assertCheckboxIsChecked();
}

['@test with a bound type']() {
this.render(`<Input @type={{inputType}} @checked={{isChecked}} />`, {
inputType: 'checkbox',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -761,14 +761,35 @@ moduleFor(
this.assertAttr('tabindex', '10');
}

['@test `value` property assertion']() {
['@feature(!EMBER_MODERNIZED_BUILT_IN_COMPONENTS) `value` property assertion']() {
expectAssertion(() => {
this.render(`{{input type="checkbox" value=value}}`, {
value: 'value',
});
}, /checkbox.+value.+not supported.+use.+checked.+instead/);
}

['@feature(EMBER_MODERNIZED_BUILT_IN_COMPONENTS) `value` property warning']() {
let message =
'`<Input @type="checkbox" />` reflects its checked state via the `@checked` argument. ' +
'You wrote `<Input @type="checkbox" @value={{...}} />` which is likely not what you intended. ' +
'Did you mean `<Input @type="checkbox" @checked={{...}} />`?';

expectWarning(() => {
this.render(`{{input type="checkbox" value=value}}`, {
value: true,
});
}, message);

this.assert.strictEqual(this.context.value, true);
this.assertCheckboxIsNotChecked();

expectWarning(() => this.$input()[0].click(), message);

this.assert.strictEqual(this.context.value, true);
this.assertCheckboxIsChecked();
}

['@test with a bound type']() {
this.render(`{{input type=inputType checked=isChecked}}`, {
inputType: 'checkbox',
Expand Down
1 change: 1 addition & 0 deletions packages/@ember/-internals/metal/lib/tracked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import { SELF_TAG } from './tags';
@param dependencies Optional dependents to be tracked.
*/
export function tracked(propertyDesc: { value: any; initializer: () => any }): Decorator;
export function tracked(target: object, key: string): void;
export function tracked(
target: object,
key: string,
Expand Down
2 changes: 2 additions & 0 deletions packages/@ember/-internals/views/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Factory, Owner } from '@ember/-internals/owner';
import { TemplateFactory } from '@ember/-internals/glimmer';
import { SimpleElement } from '@simple-dom/interface';

export { jQuery, jQueryDisabled } from './lib/system/jquery';

export interface StaticTemplateMeta {
moduleName: string;
managerId?: string;
Expand Down
4 changes: 1 addition & 3 deletions packages/@ember/debug/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ export type DebugFunctionType =
| 'runInDebug'
| 'deprecateFunc';

export type AssertFunc =
| ((desc: string) => never)
| ((desc: string, condition?: unknown) => asserts condition);
export type AssertFunc = (desc: string, condition?: unknown) => asserts condition;
export type DebugFunc = (message: string) => void;
export type DebugSealFunc = (obj: object) => void;
export type DebugFreezeFunc = (obj: object) => void;
Expand Down
1 change: 1 addition & 0 deletions packages/@ember/object/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export let action: MethodDecorator;
Loading

0 comments on commit 63f6024

Please sign in to comment.