Skip to content

Commit d92c3d0

Browse files
committedApr 22, 2024
Use dynamic context for "card context"
1 parent 0b48d91 commit d92c3d0

File tree

17 files changed

+161
-155
lines changed

17 files changed

+161
-155
lines changed
 

‎packages/base/card-api.gts

+17-35
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
isNotLoadedError,
2323
isNotReadyError,
2424
CardError,
25+
CardContextName,
2526
NotLoaded,
2627
NotReady,
2728
getField,
@@ -283,11 +284,7 @@ export interface Field<
283284
): Promise<any>;
284285
emptyValue(instance: BaseDef): any;
285286
validate(instance: BaseDef, value: any): void;
286-
component(
287-
model: Box<BaseDef>,
288-
defaultFormat: Format,
289-
context?: CardContext,
290-
): BoxComponent;
287+
component(model: Box<BaseDef>, defaultFormat: Format): BoxComponent;
291288
getter(instance: BaseDef): BaseInstanceType<CardT>;
292289
queryableValue(value: any, stack: BaseDef[]): SearchT;
293290
queryMatcher(
@@ -753,12 +750,8 @@ class Contains<CardT extends FieldDefConstructor> implements Field<CardT, any> {
753750
);
754751
}
755752

756-
component(
757-
model: Box<BaseDef>,
758-
format: Format,
759-
context?: CardContext,
760-
): BoxComponent {
761-
return fieldComponent(this, model, format, context);
753+
component(model: Box<BaseDef>, format: Format): BoxComponent {
754+
return fieldComponent(this, model, format);
762755
}
763756
}
764757

@@ -1049,18 +1042,14 @@ class LinksTo<CardT extends CardDefConstructor> implements Field<CardT> {
10491042
return fieldInstance;
10501043
}
10511044

1052-
component(
1053-
model: Box<CardDef>,
1054-
format: Format,
1055-
context?: CardContext,
1056-
): BoxComponent {
1045+
component(model: Box<CardDef>, format: Format): BoxComponent {
10571046
if (format === 'edit' && !this.computeVia) {
10581047
let innerModel = model.field(
10591048
this.name as keyof BaseDef,
10601049
) as unknown as Box<CardDef | null>;
1061-
return getLinksToEditor(innerModel, this, context);
1050+
return getLinksToEditor(innerModel, this);
10621051
}
1063-
return fieldComponent(this, model, format, context);
1052+
return fieldComponent(this, model, format);
10641053
}
10651054
}
10661055

@@ -1435,11 +1424,7 @@ class LinksToMany<FieldT extends CardDefConstructor>
14351424
return fieldInstances;
14361425
}
14371426

1438-
component(
1439-
model: Box<CardDef>,
1440-
format: Format,
1441-
context?: CardContext,
1442-
): BoxComponent {
1427+
component(model: Box<CardDef>, format: Format): BoxComponent {
14431428
let fieldName = this.name as keyof BaseDef;
14441429
let arrayField = model.field(
14451430
fieldName,
@@ -1462,7 +1447,6 @@ class LinksToMany<FieldT extends CardDefConstructor>
14621447
field: this,
14631448
format: renderFormat ?? format,
14641449
cardTypeFor,
1465-
context,
14661450
});
14671451
}
14681452
}
@@ -1471,7 +1455,6 @@ function fieldComponent(
14711455
field: Field<typeof BaseDef>,
14721456
model: Box<BaseDef>,
14731457
defaultFormat: Format,
1474-
context?: CardContext,
14751458
): BoxComponent {
14761459
let fieldName = field.name as keyof BaseDef;
14771460
let card: typeof BaseDef;
@@ -1482,7 +1465,7 @@ function fieldComponent(
14821465
(model.value[fieldName]?.constructor as typeof BaseDef) ?? field.card;
14831466
}
14841467
let innerModel = model.field(fieldName) as unknown as Box<BaseDef>;
1485-
return getBoxComponent(card, defaultFormat, innerModel, field, context);
1468+
return getBoxComponent(card, defaultFormat, innerModel, field);
14861469
}
14871470

14881471
// our decorators are implemented by Babel, not TypeScript, so they have a
@@ -1660,13 +1643,8 @@ export class BaseDef {
16601643
return _createFromSerialized(this, data, doc, relativeTo, identityContext);
16611644
}
16621645

1663-
static getComponent(
1664-
card: BaseDef,
1665-
format: Format,
1666-
field?: Field,
1667-
context?: CardContext,
1668-
) {
1669-
return getComponent(card, format, field, context);
1646+
static getComponent(card: BaseDef, format: Format, field?: Field) {
1647+
return getComponent(card, format, field);
16701648
}
16711649

16721650
static assignInitialFieldValue(
@@ -2738,15 +2716,13 @@ export function getComponent(
27382716
model: BaseDef,
27392717
format: Format,
27402718
field?: Field,
2741-
context?: CardContext,
27422719
): BoxComponent {
27432720
let box = Box.create(model);
27442721
let boxComponent = getBoxComponent(
27452722
model.constructor as BaseDefConstructor,
27462723
format,
27472724
box,
27482725
field,
2749-
context,
27502726
);
27512727
return boxComponent;
27522728
}
@@ -3101,3 +3077,9 @@ type ElementType<T> = T extends (infer V)[] ? V : never;
31013077
function makeRelativeURL(maybeURL: string, opts?: SerializeOpts): string {
31023078
return opts?.maybeRelativeURL ? opts.maybeRelativeURL(maybeURL) : maybeURL;
31033079
}
3080+
3081+
declare module 'ember-provide-consume-context/context-registry' {
3082+
export default interface ContextRegistry {
3083+
[CardContextName]: CardContext;
3084+
}
3085+
}

‎packages/base/field-component.gts

+86-72
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import {
1212
isCompoundField,
1313
formats,
1414
} from './card-api';
15-
import { getField } from '@cardstack/runtime-common';
15+
import { CardContextName, getField } from '@cardstack/runtime-common';
1616
import type { ComponentLike } from '@glint/template';
1717
import { CardContainer } from '@cardstack/boxel-ui/components';
1818
import Modifier from 'ember-modifier';
1919
import { initSharedState } from './shared-state';
2020
import { eq } from '@cardstack/boxel-ui/helpers';
21+
import { consume } from 'ember-provide-consume-context';
22+
import Component from '@glimmer/component';
2123

2224
interface BoxComponentSignature {
2325
Args: { Named: { format?: Format; displayContainer?: boolean } };
@@ -26,6 +28,33 @@ interface BoxComponentSignature {
2628

2729
export type BoxComponent = ComponentLike<BoxComponentSignature>;
2830

31+
interface CardContextConsumerSignature {
32+
Blocks: { default: [CardContext] };
33+
}
34+
35+
// cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements
36+
const DEFAULT_CARD_CONTEXT = {
37+
cardComponentModifier: class NoOpModifier extends Modifier<any> {
38+
modify() {}
39+
},
40+
actions: undefined,
41+
};
42+
43+
export class CardContextConsumer extends Component<CardContextConsumerSignature> {
44+
@consume(CardContextName) declare dynamicCardContext: CardContext;
45+
46+
get context(): CardContext {
47+
return {
48+
...DEFAULT_CARD_CONTEXT,
49+
...this.dynamicCardContext,
50+
};
51+
}
52+
53+
<template>
54+
{{yield this.context}}
55+
</template>
56+
}
57+
2958
const componentCache = initSharedState(
3059
'componentCache',
3160
() => new WeakMap<Box<BaseDef>, BoxComponent>(),
@@ -36,7 +65,6 @@ export function getBoxComponent(
3665
defaultFormat: Format,
3766
model: Box<BaseDef>,
3867
field: Field | undefined,
39-
context: CardContext = {},
4068
): BoxComponent {
4169
let stable = componentCache.get(model);
4270
if (stable) {
@@ -46,13 +74,6 @@ export function getBoxComponent(
4674
| { fields: FieldsTypeFor<BaseDef>; format: Format }
4775
| undefined;
4876

49-
// cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements
50-
let cardComponentModifier =
51-
context.cardComponentModifier ??
52-
class NoOpModifier extends Modifier<any> {
53-
modify() {}
54-
};
55-
5677
function lookupFormat(userFormat: Format | undefined): {
5778
Implementation: BaseDefComponent;
5879
fields: FieldsTypeFor<BaseDef>;
@@ -78,12 +99,7 @@ export function getBoxComponent(
7899
if (internalFieldsCache?.format === format) {
79100
fields = internalFieldsCache.fields;
80101
} else {
81-
fields = fieldsComponentsFor(
82-
{},
83-
model,
84-
defaultFieldFormat(format),
85-
context,
86-
);
102+
fields = fieldsComponentsFor({}, model, defaultFieldFormat(format));
87103
internalFieldsCache = { fields, format };
88104
}
89105

@@ -97,26 +113,57 @@ export function getBoxComponent(
97113
let component: TemplateOnlyComponent<{
98114
Args: { format?: Format; displayContainer?: boolean };
99115
}> = <template>
100-
{{#let
101-
(lookupFormat @format) (if (eq @displayContainer false) false true)
102-
as |f displayContainer|
103-
}}
104-
{{#if (isCard model.value)}}
105-
<CardContainer
106-
@displayBoundaries={{displayContainer}}
107-
class='field-component-card
108-
{{f.format}}-format display-container-{{displayContainer}}'
109-
{{cardComponentModifier
110-
card=model.value
111-
format=f.format
112-
fieldType=field.fieldType
113-
fieldName=field.name
114-
}}
115-
data-test-card-format={{f.format}}
116-
data-test-field-component-card
117-
{{! @glint-ignore Argument of type 'unknown' is not assignable to parameter of type 'Element'}}
118-
...attributes
119-
>
116+
<CardContextConsumer as |context|>
117+
{{#let
118+
(lookupFormat @format) (if (eq @displayContainer false) false true)
119+
as |f displayContainer|
120+
}}
121+
{{#if (isCard model.value)}}
122+
<CardContainer
123+
@displayBoundaries={{displayContainer}}
124+
class='field-component-card
125+
{{f.format}}-format display-container-{{displayContainer}}'
126+
{{context.cardComponentModifier
127+
card=model.value
128+
format=f.format
129+
fieldType=field.fieldType
130+
fieldName=field.name
131+
}}
132+
data-test-card-format={{f.format}}
133+
data-test-field-component-card
134+
{{! @glint-ignore Argument of type 'unknown' is not assignable to parameter of type 'Element'}}
135+
...attributes
136+
>
137+
<f.Implementation
138+
@cardOrField={{card}}
139+
@model={{model.value}}
140+
@fields={{f.fields}}
141+
@format={{f.format}}
142+
@displayContainer={{@displayContainer}}
143+
@set={{model.set}}
144+
@fieldName={{model.name}}
145+
@context={{context}}
146+
/>
147+
</CardContainer>
148+
{{else if (isCompoundField model.value)}}
149+
<div
150+
data-test-compound-field-format={{f.format}}
151+
data-test-compound-field-component
152+
{{! @glint-ignore Argument of type 'unknown' is not assignable to parameter of type 'Element'}}
153+
...attributes
154+
>
155+
<f.Implementation
156+
@cardOrField={{card}}
157+
@model={{model.value}}
158+
@fields={{f.fields}}
159+
@format={{f.format}}
160+
@displayContainer={{@displayContainer}}
161+
@set={{model.set}}
162+
@fieldName={{model.name}}
163+
@context={{context}}
164+
/>
165+
</div>
166+
{{else}}
120167
<f.Implementation
121168
@cardOrField={{card}}
122169
@model={{model.value}}
@@ -127,38 +174,9 @@ export function getBoxComponent(
127174
@fieldName={{model.name}}
128175
@context={{context}}
129176
/>
130-
</CardContainer>
131-
{{else if (isCompoundField model.value)}}
132-
<div
133-
data-test-compound-field-format={{f.format}}
134-
data-test-compound-field-component
135-
{{! @glint-ignore Argument of type 'unknown' is not assignable to parameter of type 'Element'}}
136-
...attributes
137-
>
138-
<f.Implementation
139-
@cardOrField={{card}}
140-
@model={{model.value}}
141-
@fields={{f.fields}}
142-
@format={{f.format}}
143-
@displayContainer={{@displayContainer}}
144-
@set={{model.set}}
145-
@fieldName={{model.name}}
146-
@context={{context}}
147-
/>
148-
</div>
149-
{{else}}
150-
<f.Implementation
151-
@cardOrField={{card}}
152-
@model={{model.value}}
153-
@fields={{f.fields}}
154-
@format={{f.format}}
155-
@displayContainer={{@displayContainer}}
156-
@set={{model.set}}
157-
@fieldName={{model.name}}
158-
@context={{context}}
159-
/>
160-
{{/if}}
161-
{{/let}}
177+
{{/if}}
178+
{{/let}}
179+
</CardContextConsumer>
162180
<style>
163181
.field-component-card.embedded-format {
164182
padding: var(--boxel-sp);
@@ -187,7 +205,6 @@ export function getBoxComponent(
187205
component,
188206
model,
189207
defaultFieldFormat(defaultFormat),
190-
context,
191208
);
192209

193210
// This cast is safe because we're returning a proxy that wraps component.
@@ -212,7 +229,6 @@ function fieldsComponentsFor<T extends BaseDef>(
212229
target: object,
213230
model: Box<T>,
214231
defaultFormat: Format,
215-
context?: CardContext,
216232
): FieldsTypeFor<T> {
217233
// This is a cache of the fields we've already created components for
218234
// so that they do not get recreated
@@ -248,7 +264,6 @@ function fieldsComponentsFor<T extends BaseDef>(
248264
let result = field.component(
249265
model as unknown as Box<BaseDef>,
250266
defaultFormat,
251-
context,
252267
);
253268
stableComponents.set(property, result);
254269
return result;
@@ -302,11 +317,10 @@ export function getPluralViewComponent(
302317
field: Field<typeof BaseDef>,
303318
boxedElement: Box<BaseDef>,
304319
) => typeof BaseDef,
305-
context?: CardContext,
306320
): BoxComponent {
307321
let getComponents = () =>
308322
model.children.map((child) =>
309-
getBoxComponent(cardTypeFor(field, child), format, child, field, context),
323+
getBoxComponent(cardTypeFor(field, child), format, child, field),
310324
); // Wrap the the components in a function so that the template is reactive to changes in the model (this is essentially a helper)
311325
let pluralViewComponent: TemplateOnlyComponent<BoxComponentSignature> =
312326
<template>

0 commit comments

Comments
 (0)