diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index d06e520c6a..49cedd60a5 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -22,6 +22,7 @@ import { isNotLoadedError, isNotReadyError, CardError, + CardContextName, NotLoaded, NotReady, getField, @@ -116,7 +117,6 @@ export interface CardContext { }; }; }>; - renderedIn?: Component; } function isNotLoadedValue(val: any): val is NotLoadedValue { @@ -284,11 +284,7 @@ export interface Field< ): Promise; emptyValue(instance: BaseDef): any; validate(instance: BaseDef, value: any): void; - component( - model: Box, - defaultFormat: Format, - context?: CardContext, - ): BoxComponent; + component(model: Box, defaultFormat: Format): BoxComponent; getter(instance: BaseDef): BaseInstanceType; queryableValue(value: any, stack: BaseDef[]): SearchT; queryMatcher( @@ -754,12 +750,8 @@ class Contains implements Field { ); } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { - return fieldComponent(this, model, format, context); + component(model: Box, format: Format): BoxComponent { + return fieldComponent(this, model, format); } } @@ -1050,18 +1042,14 @@ class LinksTo implements Field { return fieldInstance; } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { + component(model: Box, format: Format): BoxComponent { if (format === 'edit' && !this.computeVia) { let innerModel = model.field( this.name as keyof BaseDef, ) as unknown as Box; - return getLinksToEditor(innerModel, this, context); + return getLinksToEditor(innerModel, this); } - return fieldComponent(this, model, format, context); + return fieldComponent(this, model, format); } } @@ -1436,11 +1424,7 @@ class LinksToMany return fieldInstances; } - component( - model: Box, - format: Format, - context?: CardContext, - ): BoxComponent { + component(model: Box, format: Format): BoxComponent { let fieldName = this.name as keyof BaseDef; let arrayField = model.field( fieldName, @@ -1463,7 +1447,6 @@ class LinksToMany field: this, format: renderFormat ?? format, cardTypeFor, - context, }); } } @@ -1472,7 +1455,6 @@ function fieldComponent( field: Field, model: Box, defaultFormat: Format, - context?: CardContext, ): BoxComponent { let fieldName = field.name as keyof BaseDef; let card: typeof BaseDef; @@ -1483,7 +1465,7 @@ function fieldComponent( (model.value[fieldName]?.constructor as typeof BaseDef) ?? field.card; } let innerModel = model.field(fieldName) as unknown as Box; - return getBoxComponent(card, defaultFormat, innerModel, field, context); + return getBoxComponent(card, defaultFormat, innerModel, field); } // our decorators are implemented by Babel, not TypeScript, so they have a @@ -1661,13 +1643,8 @@ export class BaseDef { return _createFromSerialized(this, data, doc, relativeTo, identityContext); } - static getComponent( - card: BaseDef, - format: Format, - field?: Field, - context?: CardContext, - ) { - return getComponent(card, format, field, context); + static getComponent(card: BaseDef, format: Format, field?: Field) { + return getComponent(card, format, field); } static assignInitialFieldValue( @@ -2739,7 +2716,6 @@ export function getComponent( model: BaseDef, format: Format, field?: Field, - context?: CardContext, ): BoxComponent { let box = Box.create(model); let boxComponent = getBoxComponent( @@ -2747,7 +2723,6 @@ export function getComponent( format, box, field, - context, ); return boxComponent; } @@ -3102,3 +3077,9 @@ type ElementType = T extends (infer V)[] ? V : never; function makeRelativeURL(maybeURL: string, opts?: SerializeOpts): string { return opts?.maybeRelativeURL ? opts.maybeRelativeURL(maybeURL) : maybeURL; } + +declare module 'ember-provide-consume-context/context-registry' { + export default interface ContextRegistry { + [CardContextName]: CardContext; + } +} diff --git a/packages/base/field-component.gts b/packages/base/field-component.gts index 6eca02d93b..b2cfe5e481 100644 --- a/packages/base/field-component.gts +++ b/packages/base/field-component.gts @@ -12,12 +12,14 @@ import { isCompoundField, formats, } from './card-api'; -import { getField } from '@cardstack/runtime-common'; +import { CardContextName, getField } from '@cardstack/runtime-common'; import type { ComponentLike } from '@glint/template'; import { CardContainer } from '@cardstack/boxel-ui/components'; import Modifier from 'ember-modifier'; import { initSharedState } from './shared-state'; import { eq } from '@cardstack/boxel-ui/helpers'; +import { consume } from 'ember-provide-consume-context'; +import Component from '@glimmer/component'; interface BoxComponentSignature { Args: { Named: { format?: Format; displayContainer?: boolean } }; @@ -26,6 +28,33 @@ interface BoxComponentSignature { export type BoxComponent = ComponentLike; +interface CardContextConsumerSignature { + Blocks: { default: [CardContext] }; +} + +// cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements +const DEFAULT_CARD_CONTEXT = { + cardComponentModifier: class NoOpModifier extends Modifier { + modify() {} + }, + actions: undefined, +}; + +export class CardContextConsumer extends Component { + @consume(CardContextName) declare dynamicCardContext: CardContext; + + get context(): CardContext { + return { + ...DEFAULT_CARD_CONTEXT, + ...this.dynamicCardContext, + }; + } + + +} + const componentCache = initSharedState( 'componentCache', () => new WeakMap, BoxComponent>(), @@ -36,7 +65,6 @@ export function getBoxComponent( defaultFormat: Format, model: Box, field: Field | undefined, - context: CardContext = {}, ): BoxComponent { let stable = componentCache.get(model); if (stable) { @@ -46,13 +74,6 @@ export function getBoxComponent( | { fields: FieldsTypeFor; format: Format } | undefined; - // cardComponentModifier, when provided, is used for the host environment to get access to card's rendered elements - let cardComponentModifier = - context.cardComponentModifier ?? - class NoOpModifier extends Modifier { - modify() {} - }; - function lookupFormat(userFormat: Format | undefined): { Implementation: BaseDefComponent; fields: FieldsTypeFor; @@ -78,12 +99,7 @@ export function getBoxComponent( if (internalFieldsCache?.format === format) { fields = internalFieldsCache.fields; } else { - fields = fieldsComponentsFor( - {}, - model, - defaultFieldFormat(format), - context, - ); + fields = fieldsComponentsFor({}, model, defaultFieldFormat(format)); internalFieldsCache = { fields, format }; } @@ -97,26 +113,57 @@ export function getBoxComponent( let component: TemplateOnlyComponent<{ Args: { format?: Format; displayContainer?: boolean }; }> =