diff --git a/renderers/angular/src/v0_9/catalog/basic/column.component.ts b/renderers/angular/src/v0_9/catalog/basic/column.component.ts index 2bae36f54..5a1552c30 100644 --- a/renderers/angular/src/v0_9/catalog/basic/column.component.ts +++ b/renderers/angular/src/v0_9/catalog/basic/column.component.ts @@ -17,7 +17,7 @@ import { Component, input, computed, ChangeDetectionStrategy } from '@angular/core'; import { ComponentHostComponent } from '../../core/component-host.component'; import { BoundProperty } from '../../core/types'; - +import { mapJustify, mapAlign } from '@a2ui/web_core/v0_9'; import { getNormalizedPath } from '../../core/utils'; /** @@ -76,8 +76,8 @@ export class ColumnComponent { componentId = input(); dataContextPath = input('/'); - protected justify = computed(() => this.props()['justify']?.value()); - protected align = computed(() => this.props()['align']?.value()); + protected justify = computed(() => mapJustify(this.props()['justify']?.value())); + protected align = computed(() => mapAlign(this.props()['align']?.value())); protected children = computed(() => { const raw = this.props()['children']?.value() || []; diff --git a/renderers/angular/src/v0_9/catalog/basic/row.component.ts b/renderers/angular/src/v0_9/catalog/basic/row.component.ts index d4fc623ff..fe5c0796c 100644 --- a/renderers/angular/src/v0_9/catalog/basic/row.component.ts +++ b/renderers/angular/src/v0_9/catalog/basic/row.component.ts @@ -17,6 +17,7 @@ import { Component, input, computed, ChangeDetectionStrategy } from '@angular/core'; import { ComponentHostComponent } from '../../core/component-host.component'; import { BoundProperty } from '../../core/types'; +import { mapJustify, mapAlign } from '@a2ui/web_core/v0_9'; import { getNormalizedPath } from '../../core/utils'; /** @@ -75,8 +76,8 @@ export class RowComponent { componentId = input(); dataContextPath = input('/'); - protected justify = computed(() => this.props()['justify']?.value()); - protected align = computed(() => this.props()['align']?.value()); + protected justify = computed(() => mapJustify(this.props()['justify']?.value())); + protected align = computed(() => mapAlign(this.props()['align']?.value())); protected children = computed(() => { const raw = this.props()['children']?.value() || []; diff --git a/renderers/lit/src/v0_9/catalogs/minimal/components/Column.ts b/renderers/lit/src/v0_9/catalogs/minimal/components/Column.ts index 2ddadec18..5fe43c052 100644 --- a/renderers/lit/src/v0_9/catalogs/minimal/components/Column.ts +++ b/renderers/lit/src/v0_9/catalogs/minimal/components/Column.ts @@ -19,44 +19,9 @@ import { customElement } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; import { styleMap } from "lit/directives/style-map.js"; import { ColumnApi } from "@a2ui/web_core/v0_9/basic_catalog"; +import { mapJustify, mapAlign } from "@a2ui/web_core/v0_9"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; -function mapJustify(justify: string | undefined): string { - switch (justify) { - case "start": - return "flex-start"; - case "center": - return "center"; - case "end": - return "flex-end"; - case "spaceBetween": - return "space-between"; - case "spaceAround": - return "space-around"; - case "spaceEvenly": - return "space-evenly"; - case "stretch": - return "stretch"; - default: - return "flex-start"; - } -} - -function mapAlign(align: string | undefined): string { - switch (align) { - case "start": - return "flex-start"; - case "center": - return "center"; - case "end": - return "flex-end"; - case "stretch": - return "stretch"; - default: - return "stretch"; - } -} - @customElement("a2ui-column") export class A2uiColumnElement extends A2uiLitElement { protected createController() { diff --git a/renderers/lit/src/v0_9/catalogs/minimal/components/Row.ts b/renderers/lit/src/v0_9/catalogs/minimal/components/Row.ts index 541bf0bda..470b56370 100644 --- a/renderers/lit/src/v0_9/catalogs/minimal/components/Row.ts +++ b/renderers/lit/src/v0_9/catalogs/minimal/components/Row.ts @@ -19,44 +19,9 @@ import { customElement } from "lit/decorators.js"; import { map } from "lit/directives/map.js"; import { styleMap } from "lit/directives/style-map.js"; import { RowApi } from "@a2ui/web_core/v0_9/basic_catalog"; +import { mapJustify, mapAlign } from "@a2ui/web_core/v0_9"; import { A2uiLitElement, A2uiController } from "@a2ui/lit/v0_9"; -function mapJustify(justify: string | undefined): string { - switch (justify) { - case "start": - return "flex-start"; - case "center": - return "center"; - case "end": - return "flex-end"; - case "spaceBetween": - return "space-between"; - case "spaceAround": - return "space-around"; - case "spaceEvenly": - return "space-evenly"; - case "stretch": - return "stretch"; - default: - return "flex-start"; - } -} - -function mapAlign(align: string | undefined): string { - switch (align) { - case "start": - return "flex-start"; - case "center": - return "center"; - case "end": - return "flex-end"; - case "stretch": - return "stretch"; - default: - return "stretch"; - } -} - @customElement("a2ui-row") export class A2uiRowElement extends A2uiLitElement { protected createController() { diff --git a/renderers/react/src/v0_9/catalog/basic/utils.ts b/renderers/react/src/v0_9/catalog/basic/utils.ts index 375bac00d..692e8b359 100644 --- a/renderers/react/src/v0_9/catalog/basic/utils.ts +++ b/renderers/react/src/v0_9/catalog/basic/utils.ts @@ -28,41 +28,7 @@ export const STANDARD_BORDER = '1px solid #ccc'; /** Standard border radius. */ export const STANDARD_RADIUS = '8px'; -export const mapJustify = (j?: string) => { - switch (j) { - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'spaceAround': - return 'space-around'; - case 'spaceBetween': - return 'space-between'; - case 'spaceEvenly': - return 'space-evenly'; - case 'start': - return 'flex-start'; - case 'stretch': - return 'stretch'; - default: - return 'flex-start'; - } -}; - -export const mapAlign = (a?: string) => { - switch (a) { - case 'start': - return 'flex-start'; - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'stretch': - return 'stretch'; - default: - return 'stretch'; - } -}; +export {mapJustify, mapAlign} from '@a2ui/web_core/v0_9'; export const getBaseLeafStyle = (): React.CSSProperties => ({ margin: LEAF_MARGIN, diff --git a/renderers/react/src/v0_9/catalog/minimal/components/Column.tsx b/renderers/react/src/v0_9/catalog/minimal/components/Column.tsx index b137f372c..f130af8b1 100644 --- a/renderers/react/src/v0_9/catalog/minimal/components/Column.tsx +++ b/renderers/react/src/v0_9/catalog/minimal/components/Column.tsx @@ -16,7 +16,7 @@ import {createReactComponent} from '../../../adapter'; import {z} from 'zod'; -import {CommonSchemas} from '@a2ui/web_core/v0_9'; +import {CommonSchemas, mapJustify, mapAlign} from '@a2ui/web_core/v0_9'; import {ChildList} from './ChildList'; export const ColumnSchema = z.object({ @@ -27,42 +27,6 @@ export const ColumnSchema = z.object({ align: z.enum(['center', 'end', 'start', 'stretch']).optional(), }); -const mapJustify = (j?: string) => { - switch (j) { - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'spaceAround': - return 'space-around'; - case 'spaceBetween': - return 'space-between'; - case 'spaceEvenly': - return 'space-evenly'; - case 'start': - return 'flex-start'; - case 'stretch': - return 'stretch'; - default: - return 'flex-start'; - } -}; - -const mapAlign = (a?: string) => { - switch (a) { - case 'start': - return 'flex-start'; - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'stretch': - return 'stretch'; - default: - return 'stretch'; - } -}; - export const ColumnApiDef = { name: 'Column', schema: ColumnSchema, diff --git a/renderers/react/src/v0_9/catalog/minimal/components/Row.tsx b/renderers/react/src/v0_9/catalog/minimal/components/Row.tsx index 7e8cd9bad..34e1ee765 100644 --- a/renderers/react/src/v0_9/catalog/minimal/components/Row.tsx +++ b/renderers/react/src/v0_9/catalog/minimal/components/Row.tsx @@ -16,7 +16,7 @@ import {createReactComponent} from '../../../adapter'; import {z} from 'zod'; -import {CommonSchemas} from '@a2ui/web_core/v0_9'; +import {CommonSchemas, mapJustify, mapAlign} from '@a2ui/web_core/v0_9'; import {ChildList} from './ChildList'; export const RowSchema = z.object({ @@ -27,42 +27,6 @@ export const RowSchema = z.object({ align: z.enum(['start', 'center', 'end', 'stretch']).optional(), }); -const mapJustify = (j?: string) => { - switch (j) { - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'spaceAround': - return 'space-around'; - case 'spaceBetween': - return 'space-between'; - case 'spaceEvenly': - return 'space-evenly'; - case 'start': - return 'flex-start'; - case 'stretch': - return 'stretch'; - default: - return 'flex-start'; - } -}; - -const mapAlign = (a?: string) => { - switch (a) { - case 'start': - return 'flex-start'; - case 'center': - return 'center'; - case 'end': - return 'flex-end'; - case 'stretch': - return 'stretch'; - default: - return 'stretch'; - } -}; - export const RowApiDef = { name: 'Row', schema: RowSchema, diff --git a/renderers/web_core/src/v0_9/common/layout.test.ts b/renderers/web_core/src/v0_9/common/layout.test.ts new file mode 100644 index 000000000..7de8a233e --- /dev/null +++ b/renderers/web_core/src/v0_9/common/layout.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import assert from 'node:assert'; +import {describe, it} from 'node:test'; +import {mapAlign, mapJustify} from './layout.js'; + +describe('mapJustify', () => { + it('maps center to center', () => { + assert.strictEqual(mapJustify('center'), 'center'); + }); + it('maps end to flex-end', () => { + assert.strictEqual(mapJustify('end'), 'flex-end'); + }); + it('maps spaceAround to space-around', () => { + assert.strictEqual(mapJustify('spaceAround'), 'space-around'); + }); + it('maps spaceBetween to space-between', () => { + assert.strictEqual(mapJustify('spaceBetween'), 'space-between'); + }); + it('maps spaceEvenly to space-evenly', () => { + assert.strictEqual(mapJustify('spaceEvenly'), 'space-evenly'); + }); + it('maps start to flex-start', () => { + assert.strictEqual(mapJustify('start'), 'flex-start'); + }); + it('maps stretch to stretch', () => { + assert.strictEqual(mapJustify('stretch'), 'stretch'); + }); + it('returns flex-start for undefined input', () => { + assert.strictEqual(mapJustify(undefined), 'flex-start'); + }); + it('returns flex-start for unknown string input', () => { + assert.strictEqual(mapJustify('unknown'), 'flex-start'); + }); +}); + +describe('mapAlign', () => { + it('maps center to center', () => { + assert.strictEqual(mapAlign('center'), 'center'); + }); + it('maps end to flex-end', () => { + assert.strictEqual(mapAlign('end'), 'flex-end'); + }); + it('maps start to flex-start', () => { + assert.strictEqual(mapAlign('start'), 'flex-start'); + }); + it('maps stretch to stretch', () => { + assert.strictEqual(mapAlign('stretch'), 'stretch'); + }); + it('returns stretch for undefined input', () => { + assert.strictEqual(mapAlign(undefined), 'stretch'); + }); + it('returns stretch for unknown string input', () => { + assert.strictEqual(mapAlign('unknown'), 'stretch'); + }); +}); diff --git a/renderers/web_core/src/v0_9/common/layout.ts b/renderers/web_core/src/v0_9/common/layout.ts new file mode 100644 index 000000000..90a52b6f0 --- /dev/null +++ b/renderers/web_core/src/v0_9/common/layout.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Centralized layout mapping utilities for Web/CSS-based renderers. + * + * Maps A2UI layout enum values (e.g., `spaceBetween`) to their corresponding + * CSS values (e.g., `space-between`). These functions are shared across all + * web renderers (React, Lit, Angular) to ensure consistent behavior. + */ + +/** + * Maps an A2UI justify enum value to its CSS `justify-content` equivalent. + * + * @param value - An A2UI justify value such as `'start'`, `'center'`, + * `'spaceBetween'`, etc. + * @returns The corresponding CSS `justify-content` value. Defaults to + * `'flex-start'` for unrecognized or undefined input. + */ +const justifyMap: Record = { + center: 'center', + end: 'flex-end', + spaceAround: 'space-around', + spaceBetween: 'space-between', + spaceEvenly: 'space-evenly', + start: 'flex-start', + stretch: 'stretch', +}; + +export function mapJustify(value?: string): string { + return (value && justifyMap[value]) || 'flex-start'; +} + +/** + * Maps an A2UI align enum value to its CSS `align-items` equivalent. + * + * @param value - An A2UI align value such as `'start'`, `'center'`, `'end'`, + * or `'stretch'`. + * @returns The corresponding CSS `align-items` value. Defaults to `'stretch'` + * for unrecognized or undefined input. + */ +const alignMap: Record = { + center: 'center', + end: 'flex-end', + start: 'flex-start', + stretch: 'stretch', +}; + +export function mapAlign(value?: string): string { + return (value && alignMap[value]) || 'stretch'; +} diff --git a/renderers/web_core/src/v0_9/index.ts b/renderers/web_core/src/v0_9/index.ts index 51be3ccbb..b7a9bbdcd 100644 --- a/renderers/web_core/src/v0_9/index.ts +++ b/renderers/web_core/src/v0_9/index.ts @@ -24,6 +24,7 @@ export * from './catalog/function_invoker.js'; export * from './catalog/types.js'; export * from './common/events.js'; +export * from './common/layout.js'; export * from './processing/message-processor.js'; export * from './rendering/component-context.js'; export * from './rendering/data-context.js';