Skip to content

Commit 0cf9e05

Browse files
committedFeb 24, 2025
add typeConstraint to spec card st field contained examples can be re-rendered
1 parent 5e6b566 commit 0cf9e05

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed
 

‎packages/base/contains-many-component.gts

+33-4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ import {
1717
PermissionsConsumer,
1818
} from './field-component';
1919
import { AddButton, IconButton } from '@cardstack/boxel-ui/components';
20-
import { getPlural } from '@cardstack/runtime-common';
20+
import {
21+
getPlural,
22+
type ResolvedCodeRef,
23+
Loader,
24+
loadCard,
25+
} from '@cardstack/runtime-common';
2126
import { IconTrash } from '@cardstack/boxel-ui/icons';
2227
import { TemplateOnlyComponent } from '@ember/component/template-only';
28+
import { restartableTask } from 'ember-concurrency';
2329

2430
interface ContainsManyEditorSignature {
2531
Args: {
@@ -30,6 +36,7 @@ interface ContainsManyEditorSignature {
3036
field: Field<typeof BaseDef>,
3137
boxedElement: Box<BaseDef>,
3238
): typeof BaseDef;
39+
typeConstraint?: ResolvedCodeRef;
3340
};
3441
}
3542

@@ -131,11 +138,21 @@ class ContainsManyEditor extends GlimmerComponent<ContainsManyEditorSignature> {
131138
</style>
132139
</template>
133140

134-
add = () => {
135-
// TODO probably each field card should have the ability to say what a new item should be
136-
let newValue =
141+
addField = restartableTask(async () => {
142+
let newValue: FieldDef | null =
137143
primitive in this.args.field.card ? null : new this.args.field.card();
144+
// TODO probably each field card should have the ability to say what a new item should be
145+
if (this.args.typeConstraint) {
146+
let subclassField = await loadCard(this.args.typeConstraint, {
147+
loader: myLoader(),
148+
});
149+
newValue = new subclassField();
150+
}
138151
(this.args.model.value as any)[this.args.field.name].push(newValue);
152+
});
153+
154+
add = () => {
155+
this.addField.perform();
139156
};
140157

141158
remove = (index: number) => {
@@ -202,6 +219,7 @@ export function getContainsManyComponent({
202219
@arrayField={{arrayField}}
203220
@field={{field}}
204221
@cardTypeFor={{cardTypeFor}}
222+
@typeConstraint={{@typeConstraint}}
205223
/>
206224
{{else}}
207225
{{#let
@@ -270,3 +288,14 @@ export function getContainsManyComponent({
270288
},
271289
});
272290
}
291+
292+
function myLoader(): Loader {
293+
// we know this code is always loaded by an instance of our Loader, which sets
294+
// import.meta.loader.
295+
296+
// When type-checking realm-server, tsc sees this file and thinks
297+
// it will be transpiled to CommonJS and so it complains about this line. But
298+
// this file is always loaded through our loader and always has access to import.meta.
299+
// @ts-ignore
300+
return (import.meta as any).loader;
301+
}

‎packages/base/field-component.gts

+2
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export function getBoxComponent(
281281
(not field.computeVia)
282282
permissions.canWrite
283283
}}
284+
@typeConstraint={{@typeConstraint}}
284285
/>
285286
</div>
286287
</DefaultFormatsProvider>
@@ -297,6 +298,7 @@ export function getBoxComponent(
297298
@fieldName={{model.name}}
298299
@context={{context}}
299300
@canEdit={{and (not field.computeVia) permissions.canWrite}}
301+
@typeConstraint={{@typeConstraint}}
300302
...attributes
301303
/>
302304
</DefaultFormatsProvider>

‎packages/base/spec.gts

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export class Spec extends CardDef {
157157
<h2 id='examples'>Examples</h2>
158158
</header>
159159
{{#if (eq @model.specType 'field')}}
160-
<@fields.containedExamples />
160+
<@fields.containedExamples @typeConstraint={{this.absoluteRef}} />
161161
{{else}}
162162
<@fields.linkedExamples @typeConstraint={{this.absoluteRef}} />
163163
{{/if}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
Component,
3+
CardDef,
4+
field,
5+
contains,
6+
StringField,
7+
FieldDef,
8+
} from 'https://cardstack.com/base/card-api';
9+
import { on } from '@ember/modifier';
10+
11+
export class TestField extends FieldDef {
12+
static displayName = 'TestField';
13+
@field firstName = contains(StringField);
14+
15+
static fitted = class Fitted extends Component<typeof this> {
16+
<template>
17+
<div data-test-baseclass>
18+
BaseClass
19+
<@fields.firstName />
20+
</div>
21+
</template>
22+
};
23+
24+
static embedded = class Embedded extends Component<typeof this> {
25+
<template>
26+
<div data-test-baseclass>
27+
Embedded BaseClass
28+
<@fields.firstName />
29+
</div>
30+
</template>
31+
};
32+
}
33+
export class SubTestField extends TestField {
34+
static displayName = 'SubTestField';
35+
36+
static fitted = class Fitted extends Component<typeof this> {
37+
<template>
38+
<div data-test-subclass>
39+
SubClass
40+
<@fields.firstName />
41+
</div>
42+
</template>
43+
};
44+
45+
static embedded = class Embedded extends Component<typeof this> {
46+
<template>
47+
<div data-test-subclass>
48+
Embedded SubClass
49+
<@fields.firstName />
50+
</div>
51+
</template>
52+
};
53+
54+
static edit = class Edit extends Component<typeof this> {
55+
<template>
56+
<div data-test-edit>
57+
Edit
58+
<@fields.firstName />
59+
</div>
60+
</template>
61+
};
62+
}
63+
export class TestCard extends CardDef {
64+
static displayName = 'TestCard';
65+
@field specialField = contains(TestField);
66+
67+
static isolated = class Isolated extends Component<typeof TestCard> {
68+
setSubclass = () => {
69+
this.args.model.specialField = new SubTestField({
70+
firstName: 'New Name',
71+
});
72+
};
73+
<template>
74+
<button {{on 'click' this.setSubclass}} data-test-set-subclass>Set
75+
Subclass From Outside</button>
76+
<@fields.specialField />
77+
</template>
78+
};
79+
}

0 commit comments

Comments
 (0)