Skip to content
Merged
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
45 changes: 29 additions & 16 deletions packages/base/contains-many-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -361,23 +361,36 @@ export function getContainsManyComponent({
(coalesce @format defaultFormats.fieldDef)
as |effectiveFormat|
}}
<div
class='plural-field containsMany-field
{{effectiveFormat}}-format
{{unless arrayField.children.length "empty"}}'
data-test-plural-view={{field.fieldType}}
data-test-plural-view-field={{field.name}}
data-test-plural-view-format={{effectiveFormat}}
...attributes
>
{{#each (getComponents) as |Item i|}}
<div class='containsMany-item' data-test-plural-view-item={{i}}>
<Item
@format={{getPluralChildFormat effectiveFormat model}}
/>
</div>
{{#if (coalesce @displayContainer true)}}
<div
class='plural-field containsMany-field
{{effectiveFormat}}-format
{{unless arrayField.children.length "empty"}}'
data-test-plural-view={{field.fieldType}}
data-test-plural-view-field={{field.name}}
data-test-plural-view-format={{effectiveFormat}}
...attributes
>
{{#each (getComponents) as |Item i|}}
<div
class='containsMany-item'
data-test-plural-view-item={{i}}
>
<Item
@format={{getPluralChildFormat effectiveFormat model}}
/>
</div>

{{/each}}
</div>
{{else}}
{{#each (getComponents) as |Item|}}
<Item
@format={{getPluralChildFormat effectiveFormat model}}
@displayContainer={{false}}
/>
{{/each}}
</div>
{{/if}}
{{/let}}
{{/if}}
</DefaultFormatsConsumer>
Expand Down
8 changes: 7 additions & 1 deletion packages/base/field-component.gts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import type { ComponentLike } from '@glint/template';
import { CardContainer } from '@cardstack/boxel-ui/components';
import {
coalesce,
extractCssVariables,
sanitizeHtmlSafe,
} from '@cardstack/boxel-ui/helpers';
Expand Down Expand Up @@ -371,7 +372,12 @@ export function getBoxComponent(
</CardContainer>
</DefaultFormatsProvider>
{{/let}}
{{else if (isCompoundField model.value)}}
{{else if
(and
(isCompoundField model.value)
(coalesce @displayContainer true)
)
}}
<DefaultFormatsProvider
@value={{defaultFieldFormats effectiveFormats.fieldDef}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"data": {
"meta": {
"adoptsFrom": {
"name": "Family",
"module": "../family"
}
},
"type": "card",
"attributes": {
"people": [
{
"firstName": "Mango",
"lastName": "A."
},
{
"firstName": "Marco",
"lastName": "N."
}
],
"cardInfo": {
"name": null,
"notes": null,
"summary": null,
"cardThumbnailURL": null
}
},
"relationships": {
"cardInfo.theme": {
"links": {
"self": null
}
},
"cardInfo.cardThumbnail": {
"links": {
"self": null
}
}
}
}
}
56 changes: 56 additions & 0 deletions packages/experiments-realm/family.gts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
CardDef,
FieldDef,
Component,
field,
contains,
containsMany,
StringField,
} from 'https://cardstack.com/base/card-api';
import { BoxelContainer } from '@cardstack/boxel-ui/components';

class FamilyMember extends FieldDef {
static displayName = 'Family Member';
@field firstName = contains(StringField);
@field lastName = contains(StringField);
static embedded = class Embedded extends Component<typeof this> {
<template>
<span class='first-name'><@fields.firstName /></span>
<span class='last-name'><@fields.lastName /></span>
</template>
};
static fitted = this.embedded;
}

export class Family extends CardDef {
static displayName = 'Family';
@field people = containsMany(FamilyMember);
static isolated = class Isolated extends Component<typeof this> {
<template>
<BoxelContainer>
<h3>Family (no chrome `containsMany`):</h3>
<div class='family'>
<@fields.people @displayContainer={{false}} />
</div>
</BoxelContainer>
<BoxelContainer>
<h3>Family (default `containsMany`):</h3>
<div class='family'>
<@fields.people />
</div>
</BoxelContainer>
<hr />
<BoxelContainer>
<h3>Family List</h3>
<ul class='family-list'>
{{#each @fields.people as |FamilyMember|}}
<li>
<FamilyMember @displayContainer={{false}} />
</li>
{{/each}}
</ul>
</BoxelContainer>
<hr />
</template>
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const PlaygroundPreview: TemplateOnlyComponent<Signature> = <template>
class='atom-preview'
@card={{@card}}
@format={{@format}}
@displayContainer={{false}}
@displayContainer={{unless @isFieldDef false}}
/>
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
Expand Down
95 changes: 95 additions & 0 deletions packages/host/tests/integration/components/card-basics-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -2991,6 +2991,101 @@ module('Integration | card-basics', function (hooks) {
.hasStyle({ height: '32px' });
});

test('renders containsMany composite field without chrome when displayContainer is false', async function (this: RenderingTestContext, assert) {
class Person extends FieldDef {
@field firstName = contains(StringField);
@field lastName = contains(StringField);
static embedded = class Embedded extends Component<typeof this> {
<template>
<span data-test-person-firstName><@fields.firstName /></span>
<span data-test-person-lastName><@fields.lastName /></span>
</template>
};
}

class Family extends CardDef {
@field people = containsMany(Person);
static isolated = class Isolated extends Component<typeof this> {
<template>
<div data-test-family>
<@fields.people @displayContainer={{false}} />
</div>
</template>
};
}
loader.shimModule(`${testRealmURL}test-cards`, { Family, Person });

let abdelRahmans = new Family({
people: [
new Person({ firstName: 'Mango', lastName: 'Abdel-Rahman' }),
new Person({ firstName: 'Van Gogh', lastName: 'Abdel-Rahman' }),
],
});

await renderCard(loader, abdelRahmans, 'isolated');

assert
.dom('.plural-field')
.doesNotExist('containsMany wrapper div is not rendered');
assert
.dom('.containsMany-item')
.doesNotExist('per-item wrapper divs are not rendered');
assert
.dom('[data-test-compound-field-component]')
.doesNotExist('compound field wrapper is not rendered');
assert
.dom('[data-test-person-firstName]')
.exists({ count: 2 }, 'items are rendered');
assert.deepEqual(
[...this.element.querySelectorAll('[data-test-person-firstName]')].map(
(el) => el.textContent?.trim(),
),
['Mango', 'Van Gogh'],
'item content is rendered in order',
);
});

test('renders compound field without chrome when displayContainer is false', async function (assert) {
class Name extends FieldDef {
@field firstName = contains(StringField);
@field lastName = contains(StringField);
static embedded = class Embedded extends Component<typeof this> {
<template>
<span data-test-name>
<@fields.firstName />
<@fields.lastName />
</span>
</template>
};
}

class Person extends CardDef {
@field name = contains(Name);
static isolated = class Isolated extends Component<typeof this> {
<template>
<div data-test-person>
<@fields.name @displayContainer={{false}} />
</div>
</template>
};
}
loader.shimModule(`${testRealmURL}test-cards`, { Person, Name });

let person = new Person({
name: new Name({ firstName: 'Arthur', lastName: 'Dent' }),
});

await renderCard(loader, person, 'isolated');

assert
.dom('[data-test-compound-field-component]')
.doesNotExist('compound field wrapper div is not rendered');
assert
.dom('[data-test-name]')
.exists('field content is still rendered')
.containsText('Arthur Dent');
});

test('can #each over a containsMany primitive @fields', async function (assert) {
class Person extends CardDef {
@field firstName = contains(StringField);
Expand Down
Loading