cx 26.1.0 → 26.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/ui/Widget.js +0 -5
- package/build/util/Component.js +5 -0
- package/build/util/test/createTestRenderer.d.ts +3 -1
- package/build/util/test/createTestRenderer.js +8 -2
- package/build/widgets/icons/calendar.js +4 -3
- package/build/widgets/icons/check.js +2 -2
- package/build/widgets/icons/clear.js +2 -2
- package/build/widgets/icons/close.js +2 -2
- package/build/widgets/icons/cx.js +2 -2
- package/build/widgets/icons/drop-down.js +2 -2
- package/build/widgets/icons/file.js +2 -2
- package/build/widgets/icons/folder-open.js +2 -2
- package/build/widgets/icons/folder.js +2 -2
- package/build/widgets/icons/forward.js +2 -2
- package/build/widgets/icons/loading.js +2 -2
- package/build/widgets/icons/menu.js +2 -2
- package/build/widgets/icons/pixel-picker.js +2 -2
- package/build/widgets/icons/search.js +2 -2
- package/build/widgets/icons/sort-asc.js +2 -2
- package/build/widgets/icons/square.js +2 -2
- package/dist/manifest.js +896 -896
- package/dist/ui.js +1 -8
- package/dist/util.js +4 -0
- package/dist/widgets.js +377 -313
- package/package.json +3 -1
- package/src/core.d.ts +182 -182
- package/src/data/ArrayElementView.ts +90 -90
- package/src/data/AugmentedViewBase.ts +88 -88
- package/src/data/Binding.ts +104 -104
- package/src/data/ExposedRecordView.ts +95 -95
- package/src/data/ExposedValueView.ts +89 -89
- package/src/data/Grouper.spec.ts +57 -57
- package/src/data/NestedDataView.ts +43 -43
- package/src/data/ReadOnlyDataView.ts +39 -39
- package/src/data/Ref.ts +104 -104
- package/src/data/Store.ts +52 -52
- package/src/data/StoreProxy.ts +19 -19
- package/src/data/StoreRef.ts +66 -66
- package/src/data/StringTemplate.ts +93 -93
- package/src/data/StructuredSelector.spec.ts +113 -113
- package/src/data/SubscribableView.ts +63 -63
- package/src/data/ZoomIntoPropertyView.ts +45 -45
- package/src/data/comparer.ts +78 -78
- package/src/data/ops/updateArray.ts +31 -31
- package/src/hooks/invokeCallback.spec.tsx +4 -4
- package/src/hooks/resolveCallback.spec.tsx +4 -4
- package/src/hooks/store.spec.tsx +15 -15
- package/src/hooks/useTrigger.spec.tsx +16 -10
- package/src/jsx-dev-runtime.ts +4 -4
- package/src/jsx-runtime.ts +79 -79
- package/src/ui/CSS.ts +87 -87
- package/src/ui/ContentResolver.spec.tsx +31 -29
- package/src/ui/Controller.spec.tsx +47 -39
- package/src/ui/Cx.spec.tsx +10 -8
- package/src/ui/DataProxy.spec.tsx +18 -18
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/FocusManager.ts +171 -171
- package/src/ui/IsolatedScope.spec.tsx +16 -9
- package/src/ui/PureContainer.spec.tsx +20 -18
- package/src/ui/Repeater.spec.tsx +8 -6
- package/src/ui/Rescope.spec.tsx +13 -13
- package/src/ui/Rescope.ts +49 -49
- package/src/ui/Restate.spec.tsx +31 -27
- package/src/ui/VDOM.ts +1 -1
- package/src/ui/Widget.tsx +0 -7
- package/src/ui/adapter/ArrayAdapter.spec.ts +55 -55
- package/src/ui/createFunctionalComponent.spec.tsx +20 -18
- package/src/ui/layout/Content.ts +30 -30
- package/src/ui/layout/ContentPlaceholder.spec.tsx +46 -34
- package/src/ui/layout/FirstVisibleChildLayout.spec.tsx +31 -19
- package/src/ui/selection/PropertySelection.ts +87 -87
- package/src/util/Component.spec.ts +30 -0
- package/src/util/Component.ts +301 -296
- package/src/util/DOM.ts +88 -88
- package/src/util/Format.spec.ts +69 -69
- package/src/util/Format.ts +267 -267
- package/src/util/addEventListenerWithOptions.ts +41 -41
- package/src/util/browserSupportsPassiveEventHandlers.ts +20 -20
- package/src/util/color/rgbToHsl.ts +35 -35
- package/src/util/getActiveElement.ts +4 -4
- package/src/util/innerTextTrim.ts +10 -10
- package/src/util/isDataRecord.ts +5 -5
- package/src/util/test/createTestRenderer.tsx +9 -2
- package/src/widgets/AccessorBindings.spec.tsx +4 -4
- package/src/widgets/HtmlElement.spec.tsx +6 -6
- package/src/widgets/ReactElementWrapper.spec.tsx +37 -37
- package/src/widgets/Sandbox.ts +103 -103
- package/src/widgets/form/ValidationGroup.spec.tsx +12 -12
- package/src/widgets/grid/GridCell.ts +143 -143
- package/src/widgets/icons/calendar.tsx +22 -17
- package/src/widgets/icons/check.tsx +14 -13
- package/src/widgets/icons/clear.tsx +16 -15
- package/src/widgets/icons/close.tsx +20 -20
- package/src/widgets/icons/cx.tsx +39 -38
- package/src/widgets/icons/drop-down.tsx +16 -15
- package/src/widgets/icons/file.tsx +14 -13
- package/src/widgets/icons/folder-open.tsx +16 -15
- package/src/widgets/icons/folder.tsx +14 -13
- package/src/widgets/icons/forward.tsx +23 -22
- package/src/widgets/icons/loading.tsx +25 -24
- package/src/widgets/icons/menu.tsx +18 -17
- package/src/widgets/icons/pixel-picker.tsx +18 -18
- package/src/widgets/icons/search.tsx +14 -13
- package/src/widgets/icons/sort-asc.tsx +15 -14
- package/src/widgets/icons/square.tsx +19 -18
- package/src/widgets/nav/Route.spec.tsx +2 -2
- package/src/widgets/nav/Route.ts +142 -142
- package/src/widgets/overlay/Dropdown.tsx +762 -762
- package/src/widgets/overlay/MsgBox.tsx +141 -141
- package/src/widgets/overlay/Toast.ts +111 -111
- package/src/widgets/overlay/Window.tsx +299 -299
- package/src/widgets/overlay/alerts.ts +46 -46
- package/src/widgets/overlay/index.ts +11 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Store } from "../../data/Store";
|
|
2
2
|
import { expr } from "../expr";
|
|
3
|
-
import { createTestRenderer } from "../../util/test/createTestRenderer";
|
|
3
|
+
import { createTestRenderer, act } from "../../util/test/createTestRenderer";
|
|
4
4
|
|
|
5
5
|
import assert from "assert";
|
|
6
6
|
import { FirstVisibleChildLayout } from "./FirstVisibleChildLayout";
|
|
@@ -9,7 +9,7 @@ import { PureContainer } from "../PureContainer";
|
|
|
9
9
|
import { createFunctionalComponent } from "../createFunctionalComponent";
|
|
10
10
|
|
|
11
11
|
describe("FirstVisibleChildLayout", () => {
|
|
12
|
-
it("renders only the first child", () => {
|
|
12
|
+
it("renders only the first child", async () => {
|
|
13
13
|
let widget = (
|
|
14
14
|
<cx>
|
|
15
15
|
<div layout={FirstVisibleChildLayout}>
|
|
@@ -22,7 +22,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
22
22
|
|
|
23
23
|
let store = new Store();
|
|
24
24
|
|
|
25
|
-
const component = createTestRenderer(store, widget);
|
|
25
|
+
const component = await createTestRenderer(store, widget);
|
|
26
26
|
|
|
27
27
|
let tree = component.toJSON();
|
|
28
28
|
assert.deepEqual(tree, {
|
|
@@ -32,7 +32,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it("does not process other widgets", () => {
|
|
35
|
+
it("does not process other widgets", async () => {
|
|
36
36
|
let h = false,
|
|
37
37
|
m = false,
|
|
38
38
|
f = false;
|
|
@@ -61,7 +61,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
61
61
|
|
|
62
62
|
let store = new Store();
|
|
63
63
|
|
|
64
|
-
const component = createTestRenderer(store, widget);
|
|
64
|
+
const component = await createTestRenderer(store, widget);
|
|
65
65
|
|
|
66
66
|
let tree = component.toJSON();
|
|
67
67
|
assert.deepEqual(tree, {
|
|
@@ -75,7 +75,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
75
75
|
assert.equal(f, false);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
it("skips the first child if not visible", () => {
|
|
78
|
+
it("skips the first child if not visible", async () => {
|
|
79
79
|
let widget = (
|
|
80
80
|
<cx>
|
|
81
81
|
<div layout={FirstVisibleChildLayout}>
|
|
@@ -88,7 +88,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
88
88
|
|
|
89
89
|
let store = new Store();
|
|
90
90
|
|
|
91
|
-
const component = createTestRenderer(store, widget);
|
|
91
|
+
const component = await createTestRenderer(store, widget);
|
|
92
92
|
|
|
93
93
|
let tree = component.toJSON();
|
|
94
94
|
assert.deepEqual(tree, {
|
|
@@ -98,7 +98,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
it("skips pure containers which use parent layouts", () => {
|
|
101
|
+
it("skips pure containers which use parent layouts", async () => {
|
|
102
102
|
let widget = (
|
|
103
103
|
<cx>
|
|
104
104
|
<div layout={FirstVisibleChildLayout}>
|
|
@@ -113,7 +113,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
113
113
|
|
|
114
114
|
let store = new Store();
|
|
115
115
|
|
|
116
|
-
const component = createTestRenderer(store, widget);
|
|
116
|
+
const component = await createTestRenderer(store, widget);
|
|
117
117
|
|
|
118
118
|
let tree = component.toJSON();
|
|
119
119
|
assert.deepEqual(tree, {
|
|
@@ -123,7 +123,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
it("works with functional components", () => {
|
|
126
|
+
it("works with functional components", async () => {
|
|
127
127
|
let FC = createFunctionalComponent(({ children }: { children?: any }) => <cx>{children}</cx>);
|
|
128
128
|
|
|
129
129
|
let widget = (
|
|
@@ -140,7 +140,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
140
140
|
|
|
141
141
|
let store = new Store();
|
|
142
142
|
|
|
143
|
-
const component = createTestRenderer(store, widget);
|
|
143
|
+
const component = await createTestRenderer(store, widget);
|
|
144
144
|
|
|
145
145
|
let tree = component.toJSON();
|
|
146
146
|
assert.deepEqual(tree, {
|
|
@@ -150,7 +150,7 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("properly destroys invisible items", () => {
|
|
153
|
+
it("properly destroys invisible items", async () => {
|
|
154
154
|
let destroyList: number[] = [];
|
|
155
155
|
let widget = (
|
|
156
156
|
<cx>
|
|
@@ -166,29 +166,41 @@ describe("FirstVisibleChildLayout", () => {
|
|
|
166
166
|
|
|
167
167
|
let store = new Store();
|
|
168
168
|
|
|
169
|
-
const component = createTestRenderer(store, widget);
|
|
169
|
+
const component = await createTestRenderer(store, widget);
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
await act(async () => {
|
|
172
|
+
store.set("index", 0);
|
|
173
|
+
});
|
|
172
174
|
component.toJSON();
|
|
173
175
|
assert.deepEqual(destroyList, []);
|
|
174
176
|
|
|
175
|
-
|
|
177
|
+
await act(async () => {
|
|
178
|
+
store.set("index", 3);
|
|
179
|
+
});
|
|
176
180
|
component.toJSON();
|
|
177
181
|
assert.deepEqual(destroyList, [0]);
|
|
178
182
|
|
|
179
|
-
|
|
183
|
+
await act(async () => {
|
|
184
|
+
store.set("index", 1);
|
|
185
|
+
});
|
|
180
186
|
component.toJSON();
|
|
181
187
|
assert.deepEqual(destroyList, [0, 3]);
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
await act(async () => {
|
|
190
|
+
store.set("index", 4);
|
|
191
|
+
});
|
|
184
192
|
component.toJSON();
|
|
185
193
|
assert.deepEqual(destroyList, [0, 3, 1]);
|
|
186
194
|
|
|
187
|
-
|
|
195
|
+
await act(async () => {
|
|
196
|
+
store.set("index", 0);
|
|
197
|
+
});
|
|
188
198
|
component.toJSON();
|
|
189
199
|
assert.deepEqual(destroyList, [0, 3, 1, 4]);
|
|
190
200
|
|
|
191
|
-
|
|
201
|
+
await act(async () => {
|
|
202
|
+
store.set("index", -1);
|
|
203
|
+
});
|
|
192
204
|
component.toJSON();
|
|
193
205
|
assert.deepEqual(destroyList, [0, 3, 1, 4, 0]);
|
|
194
206
|
});
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { Selection, SelectionOptions } from "./Selection";
|
|
2
|
-
import { View } from "../../data/View";
|
|
3
|
-
import { isAccessorChain } from "../../data/createAccessorModelProxy";
|
|
4
|
-
import { Binding } from "../Prop";
|
|
5
|
-
|
|
6
|
-
export interface PropertySelectionConfig {
|
|
7
|
-
/** Name of the field used to indicate selection. Default is `selected`. */
|
|
8
|
-
selectedField?: string;
|
|
9
|
-
|
|
10
|
-
/** Set to `true` to allow multiple selection. */
|
|
11
|
-
multiple?: boolean;
|
|
12
|
-
|
|
13
|
-
/** Name of the field used as a key. */
|
|
14
|
-
keyField?: string;
|
|
15
|
-
|
|
16
|
-
/** Record binding. */
|
|
17
|
-
record?: Binding;
|
|
18
|
-
|
|
19
|
-
/** Records binding. */
|
|
20
|
-
records?: Binding;
|
|
21
|
-
|
|
22
|
-
/** Index binding. */
|
|
23
|
-
index?: Binding;
|
|
24
|
-
|
|
25
|
-
/** Binding path for selection state. */
|
|
26
|
-
bind?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class PropertySelection extends Selection {
|
|
30
|
-
declare records: any;
|
|
31
|
-
declare selectedField: string;
|
|
32
|
-
|
|
33
|
-
constructor(config?: PropertySelectionConfig) {
|
|
34
|
-
super(config);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
selectMultiple(store: View, records: any[], indexes: any[], { toggle, add }: SelectionOptions = {}): any {
|
|
38
|
-
if (this.toggle) toggle = true;
|
|
39
|
-
|
|
40
|
-
if (!this.records) return false;
|
|
41
|
-
|
|
42
|
-
let path = isAccessorChain(this.records) ? this.records.toString() : this.records.bind;
|
|
43
|
-
if (!path) return false;
|
|
44
|
-
|
|
45
|
-
let array = store.get(path);
|
|
46
|
-
let newArray = [...array];
|
|
47
|
-
let dirty = false;
|
|
48
|
-
|
|
49
|
-
if (!toggle && !add) {
|
|
50
|
-
newArray.forEach((r, i) => {
|
|
51
|
-
if (r[this.selectedField]) {
|
|
52
|
-
let nr = Object.assign({}, r);
|
|
53
|
-
nr[this.selectedField] = false;
|
|
54
|
-
newArray[i] = nr;
|
|
55
|
-
dirty = true;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
records.forEach((record, i) => {
|
|
61
|
-
let index = indexes[i];
|
|
62
|
-
let rec = newArray[index];
|
|
63
|
-
if (array[index] !== record) throw new Error("Stale data.");
|
|
64
|
-
|
|
65
|
-
let value = rec[this.selectedField];
|
|
66
|
-
let newValue = add ? true : toggle ? !value : true;
|
|
67
|
-
|
|
68
|
-
if (value == newValue) return;
|
|
69
|
-
|
|
70
|
-
let newRec = Object.assign({}, rec);
|
|
71
|
-
newRec[this.selectedField] = newValue;
|
|
72
|
-
newArray[index] = newRec;
|
|
73
|
-
dirty = true;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (dirty) store.set(path, newArray);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
isSelected(store: View, record: any, index: any): boolean {
|
|
80
|
-
return record && record[this.selectedField!];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
PropertySelection.prototype.selectedField = "selected";
|
|
85
|
-
PropertySelection.prototype.multiple = false;
|
|
86
|
-
|
|
87
|
-
(Selection as any).alias("property", PropertySelection);
|
|
1
|
+
import { Selection, SelectionOptions } from "./Selection";
|
|
2
|
+
import { View } from "../../data/View";
|
|
3
|
+
import { isAccessorChain } from "../../data/createAccessorModelProxy";
|
|
4
|
+
import { Binding } from "../Prop";
|
|
5
|
+
|
|
6
|
+
export interface PropertySelectionConfig {
|
|
7
|
+
/** Name of the field used to indicate selection. Default is `selected`. */
|
|
8
|
+
selectedField?: string;
|
|
9
|
+
|
|
10
|
+
/** Set to `true` to allow multiple selection. */
|
|
11
|
+
multiple?: boolean;
|
|
12
|
+
|
|
13
|
+
/** Name of the field used as a key. */
|
|
14
|
+
keyField?: string;
|
|
15
|
+
|
|
16
|
+
/** Record binding. */
|
|
17
|
+
record?: Binding;
|
|
18
|
+
|
|
19
|
+
/** Records binding. */
|
|
20
|
+
records?: Binding;
|
|
21
|
+
|
|
22
|
+
/** Index binding. */
|
|
23
|
+
index?: Binding;
|
|
24
|
+
|
|
25
|
+
/** Binding path for selection state. */
|
|
26
|
+
bind?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class PropertySelection extends Selection {
|
|
30
|
+
declare records: any;
|
|
31
|
+
declare selectedField: string;
|
|
32
|
+
|
|
33
|
+
constructor(config?: PropertySelectionConfig) {
|
|
34
|
+
super(config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
selectMultiple(store: View, records: any[], indexes: any[], { toggle, add }: SelectionOptions = {}): any {
|
|
38
|
+
if (this.toggle) toggle = true;
|
|
39
|
+
|
|
40
|
+
if (!this.records) return false;
|
|
41
|
+
|
|
42
|
+
let path = isAccessorChain(this.records) ? this.records.toString() : this.records.bind;
|
|
43
|
+
if (!path) return false;
|
|
44
|
+
|
|
45
|
+
let array = store.get(path);
|
|
46
|
+
let newArray = [...array];
|
|
47
|
+
let dirty = false;
|
|
48
|
+
|
|
49
|
+
if (!toggle && !add) {
|
|
50
|
+
newArray.forEach((r, i) => {
|
|
51
|
+
if (r[this.selectedField]) {
|
|
52
|
+
let nr = Object.assign({}, r);
|
|
53
|
+
nr[this.selectedField] = false;
|
|
54
|
+
newArray[i] = nr;
|
|
55
|
+
dirty = true;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
records.forEach((record, i) => {
|
|
61
|
+
let index = indexes[i];
|
|
62
|
+
let rec = newArray[index];
|
|
63
|
+
if (array[index] !== record) throw new Error("Stale data.");
|
|
64
|
+
|
|
65
|
+
let value = rec[this.selectedField];
|
|
66
|
+
let newValue = add ? true : toggle ? !value : true;
|
|
67
|
+
|
|
68
|
+
if (value == newValue) return;
|
|
69
|
+
|
|
70
|
+
let newRec = Object.assign({}, rec);
|
|
71
|
+
newRec[this.selectedField] = newValue;
|
|
72
|
+
newArray[index] = newRec;
|
|
73
|
+
dirty = true;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (dirty) store.set(path, newArray);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
isSelected(store: View, record: any, index: any): boolean {
|
|
80
|
+
return record && record[this.selectedField!];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
PropertySelection.prototype.selectedField = "selected";
|
|
85
|
+
PropertySelection.prototype.multiple = false;
|
|
86
|
+
|
|
87
|
+
(Selection as any).alias("property", PropertySelection);
|
|
@@ -211,6 +211,36 @@ describe("Component.create", function () {
|
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
213
|
|
|
214
|
+
describe("type in second argument (more)", function () {
|
|
215
|
+
it("should create instance of type specified in second argument", function () {
|
|
216
|
+
const result = TestWidget.create({ text: "hello" }, { type: TestButton });
|
|
217
|
+
assert.ok(result instanceof TestButton);
|
|
218
|
+
assert.equal(result.text, "hello");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should create instance of $type specified in second argument", function () {
|
|
222
|
+
const result = TestWidget.create({ text: "world" }, { $type: TestButton });
|
|
223
|
+
assert.ok(result instanceof TestButton);
|
|
224
|
+
assert.equal(result.text, "world");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should work when first argument is an array and second has type", function () {
|
|
228
|
+
const results = Component.create([{ text: "A" }, { text: "B" }], { type: TestButton });
|
|
229
|
+
assert.equal(results.length, 2);
|
|
230
|
+
assert.ok(results[0] instanceof TestButton);
|
|
231
|
+
assert.ok(results[1] instanceof TestButton);
|
|
232
|
+
assert.equal(results[0].text, "A");
|
|
233
|
+
assert.equal(results[1].text, "B");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should work when type is passed as first arg with array config and type in more", function () {
|
|
237
|
+
const results = Component.create(TestWidget, [{ text: "X" }], { type: TestButton });
|
|
238
|
+
assert.equal(results.length, 1);
|
|
239
|
+
assert.ok(results[0] instanceof TestButton);
|
|
240
|
+
assert.equal(results[0].text, "X");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
214
244
|
describe("heterogeneous array with type property", function () {
|
|
215
245
|
it("creates array of different component types", function () {
|
|
216
246
|
const results = Component.create([
|