From 031276737c0f57655350e99493817d147305d716 Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Wed, 24 Jun 2026 13:32:41 -0700 Subject: [PATCH 1/3] Add ControlStyles to PaModule YAML persistence Adds a name-keyed ControlStyles collection to the SchemaV3 PaModule. A control style is a reusable, named set of property formulas applied to a single target control type. - New ControlStyleDefinition record (Control + Properties) - NamedObjectMapping ControlStyles on PaModule (style name is the unique key) - Round-trip serialization test + sample ControlStyles.pa.yaml Work item: AB#38567538 --- .../Serialization/PaYamlSerializerTests.cs | 42 +++++++++++++++++++ .../Models/SchemaV3/ControlStyleDefinition.cs | 37 ++++++++++++++++ .../PaYaml/Models/SchemaV3/PaModule.cs | 2 + .../v3.0/FullSchemaUses/ControlStyles.pa.yaml | 18 ++++++++ 4 files changed, 99 insertions(+) create mode 100644 src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs create mode 100644 src/schemas-tests/pa-yaml/v3.0/FullSchemaUses/ControlStyles.pa.yaml diff --git a/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs b/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs index d99c5449..e9695fce 100644 --- a/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs +++ b/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs @@ -29,6 +29,47 @@ public void DeserializeNamedObjectSetsLocationInfo() paModule.App.Properties.Should().ContainName("Bar").WhoseNamedObject.Start.Should().Be(new(4, 9)); } + [TestMethod] + public void DeserializeControlStyles() + { + var paModule = PaYamlSerializer.Deserialize(""" + ControlStyles: + PrimaryButton: + Control: Button + Properties: + Fill: =Color.Blue + Emphasis: + Control: Label + Properties: + Color: =Color.Red + """); + + paModule.ShouldNotBeNull(); + paModule.ControlStyles.ShouldNotBeNull(); + paModule.ControlStyles.Should().HaveCount(2); + + // Each style is keyed by its (unique) name and targets a single control type. + paModule.ControlStyles["PrimaryButton"].ControlType.Should().Be("Button"); + paModule.ControlStyles["PrimaryButton"].Properties.Should().ContainName("Fill"); + + paModule.ControlStyles["Emphasis"].ControlType.Should().Be("Label"); + paModule.ControlStyles["Emphasis"].Properties.Should().ContainName("Color"); + + // Round-trips back to equivalent YAML. + var roundTrippedYaml = PaYamlSerializer.Serialize(paModule); + roundTrippedYaml.Should().BeYamlEquivalentTo(""" + ControlStyles: + PrimaryButton: + Control: Button + Properties: + Fill: =Color.Blue + Emphasis: + Control: Label + Properties: + Color: =Color.Red + """); + } + #region Deserialize Examples [TestMethod] @@ -193,6 +234,7 @@ public void DeserializeDuplicateControlNamesShouldFail() [DataRow(@"_TestData/SchemaV3_0/FullSchemaUses/Screens-general-controls.pa.yaml")] [DataRow(@"_TestData/SchemaV3_0/FullSchemaUses/Screens-with-components.pa.yaml")] [DataRow(@"_TestData/SchemaV3_0/FullSchemaUses/Themes.pa.yaml")] + [DataRow(@"_TestData/SchemaV3_0/FullSchemaUses/ControlStyles.pa.yaml")] [DataRow(@"_TestData/SchemaV3_0/Examples/Src/DataSources/Dataversedatasources1.pa.yaml")] [DataRow(@"_TestData/SchemaV3_0/Examples/Src/_EditorState.pa.yaml")] public void RoundTripFromYaml(string path) diff --git a/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs new file mode 100644 index 00000000..3cea503f --- /dev/null +++ b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models.PowerFx; +using YamlDotNet.Serialization; + +namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models.SchemaV3; + +/// +/// Represents a reusable control style that can be applied to controls of a specific type. +/// +/// +/// A control style is identified by its name, which is unique within the containing mapping. +/// Each style applies only to the single target control type specified by . +/// +public record ControlStyleDefinition +{ + public ControlStyleDefinition() { } + + [SetsRequiredMembers] + public ControlStyleDefinition(string controlType) + { + ControlType = controlType ?? throw new ArgumentNullException(nameof(controlType)); + } + + /// + /// The target control type that this style applies to (e.g. "Button"). Matches . + /// + [property: YamlMember(Alias = "Control")] + public required string? ControlType { get; init; } + + /// + /// The set of property formulas defined by this style. + /// + public NamedObjectMapping? Properties { get; init; } +} diff --git a/src/Persistence/PaYaml/Models/SchemaV3/PaModule.cs b/src/Persistence/PaYaml/Models/SchemaV3/PaModule.cs index f104d030..2e2cae06 100644 --- a/src/Persistence/PaYaml/Models/SchemaV3/PaModule.cs +++ b/src/Persistence/PaYaml/Models/SchemaV3/PaModule.cs @@ -19,4 +19,6 @@ public record PaModule public EditorStateInstance? EditorState { get; init; } public NamedObjectMapping? Themes { get; init; } + + public NamedObjectMapping? ControlStyles { get; init; } } diff --git a/src/schemas-tests/pa-yaml/v3.0/FullSchemaUses/ControlStyles.pa.yaml b/src/schemas-tests/pa-yaml/v3.0/FullSchemaUses/ControlStyles.pa.yaml new file mode 100644 index 00000000..3e33ad28 --- /dev/null +++ b/src/schemas-tests/pa-yaml/v3.0/FullSchemaUses/ControlStyles.pa.yaml @@ -0,0 +1,18 @@ +ControlStyles: + PrimaryButton: + Control: Button + Properties: + Fill: =Color.Blue + Color: =Color.White + BorderThickness: =2 + + Emphasis: + Control: Label@1.2.3 + Properties: + Color: =Color.Red + Bold: =true + + SubtleLabel: + Control: Label + Properties: + Color: =Color.Gray From 1dded6b6d565c6c558c40c68ffd4c0a4a55fb5f9 Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:46:26 -0700 Subject: [PATCH 2/3] Simplify ControlStyleDefinition to a property-only record --- .../PaYaml/Models/SchemaV3/ControlStyleDefinition.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs index 3cea503f..b09cca0c 100644 --- a/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs +++ b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs @@ -16,14 +16,6 @@ namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models.SchemaV3; /// public record ControlStyleDefinition { - public ControlStyleDefinition() { } - - [SetsRequiredMembers] - public ControlStyleDefinition(string controlType) - { - ControlType = controlType ?? throw new ArgumentNullException(nameof(controlType)); - } - /// /// The target control type that this style applies to (e.g. "Button"). Matches . /// From d1a2e370dac52190de2771684e208135566ab1b1 Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Wed, 24 Jun 2026 15:50:46 -0700 Subject: [PATCH 3/3] Remove unused using in ControlStyleDefinition --- src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs index b09cca0c..670be9e7 100644 --- a/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs +++ b/src/Persistence/PaYaml/Models/SchemaV3/ControlStyleDefinition.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Diagnostics.CodeAnalysis; using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models.PowerFx; using YamlDotNet.Serialization;