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
5 changes: 5 additions & 0 deletions packages/mix_annotations/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.2.0-beta.1

- **FEAT**: Add `MixWidget.target` for plain widget constructor tear-offs and
`factoryParameters` for independent recipe parameter curation.

## 2.2.0-beta.0

- **FEAT**: Add `MixableField.forwardStyler` and `stylerSurface` for opt-in
Expand Down
30 changes: 25 additions & 5 deletions packages/mix_annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,31 @@ value parameters from becoming public widget parameters automatically:
final editorStyle = EditorStyler();
```

An empty `.only({})` exposes no selectable styler value parameters. Factory
parameters, a valid `Key? key`, and method-level `call<T>()` type parameters
remain automatic in every mode; required styler value parameters must be
selected. Excluded optional parameters are not forwarded, so the styler
method's defaults apply.
An empty `.only({})` exposes no selectable styler value parameters. A valid
`Key? key` and method-level `call<T>()` type parameters remain automatic;
required styler value parameters must be selected. Excluded optional
parameters are not forwarded, so the styler method's defaults apply.

Use `target` to wrap a plain widget constructor directly and
`factoryParameters` to curate recipe controls independently:

```dart
@MixWidget(
name: 'FortalButton',
target: RemixButton.new,
factoryParameters: .only({'variant', 'size'}),
)
ButtonStyler fortalButtonStyler({
ButtonVariant variant = .solid,
ButtonSize size = .medium,
bool highContrast = false,
});
```

The target must be a Widget constructor with a compatible named `style`
parameter. Its `style` and `styleSpec` parameters never surface on the
generated wrapper. Required factory parameters must be selected; omitted
optional parameters use the recipe's defaults.

Generators that also support older `mix_annotations` releases interpret an
annotation without `widgetParameters` as `.all()`. Using `.only(...)` requires
Expand Down
17 changes: 17 additions & 0 deletions packages/mix_annotations/lib/src/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,30 @@ class MixWidget {
/// the name is derived from the annotated element's name.
final String? name;

/// Optional plain widget constructor rendered directly by the generated
/// wrapper.
///
/// When set, widget parameters are read from this constructor instead of a
/// Styler `call()` method. The constructor must expose a compatible named
/// `style` parameter. `style` and `styleSpec` are supplied or omitted by the
/// generator and never become wrapper fields.
final Function? target;

/// Selection of non-`key` styler `call()` value parameters exposed by the
/// generated widget.
final MixWidgetParameterSelection widgetParameters;

/// Selection of recipe factory parameters exposed by the generated widget.
///
/// Required factory parameters must be selected. Optional parameters omitted
/// by `.only(...)` use the factory's own defaults.
final MixWidgetParameterSelection factoryParameters;

const MixWidget({
this.name,
this.target,
this.widgetParameters = const MixWidgetParameterSelection.all(),
this.factoryParameters = const MixWidgetParameterSelection.all(),
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mix_annotations/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mix_annotations
description: Annotations for mix and mix_generator
version: 2.2.0-beta.0
version: 2.2.0-beta.1
repository: https://github.com/btwld/mix/tree/main/packages/mix_annotations

environment:
Expand Down
18 changes: 18 additions & 0 deletions packages/mix_annotations/test/annotations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ void main() {
test('defaults widgetParameters to all', () {
const annotation = MixWidget();

expect(annotation.target, isNull);
expect(annotation.widgetParameters.includesAll, isTrue);
expect(annotation.widgetParameters.names, isEmpty);
expect(annotation.factoryParameters.includesAll, isTrue);
expect(annotation.factoryParameters.names, isEmpty);
});

test('preserves an explicit all selection', () {
Expand All @@ -78,5 +81,20 @@ void main() {
expect(annotation.widgetParameters.includesAll, isFalse);
expect(annotation.widgetParameters.names, isEmpty);
});

test('preserves target and selected factory parameters', () {
const annotation = MixWidget(
target: _Target.new,
factoryParameters: .only({'variant', 'size'}),
);

expect(annotation.target, _Target.new);
expect(annotation.factoryParameters.includesAll, isFalse);
expect(annotation.factoryParameters.names, {'variant', 'size'});
});
});
}

class _Target {
const _Target();
}
8 changes: 8 additions & 0 deletions packages/mix_generator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2.2.0-beta.2

- **FEAT**: Generate `@MixWidget(target:)` wrappers for plain Widget
constructors without requiring `StyleWidget` or extension `call()` methods.
Preserve target generics, key/default forwarding, enum variant
constructors, independent widget/factory parameter curation, and clean
same-build generated Styler support.

## 2.2.0-beta.1

- **FIX**: Generate `@MixWidget` wrappers on clean builds when a factory
Expand Down
32 changes: 27 additions & 5 deletions packages/mix_generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,33 @@ stable as a styler evolves, select the supported parameters explicitly:
final editorStyle = EditorStyler();
```

`.only({})` exposes none of the selectable styler value parameters. Factory
parameters, a valid `Key? key`, and method-level `call<T>()` type parameters are
always automatic, and required styler value parameters must be included in an
`.only(...)` selection. Excluded optional parameters are not forwarded, so the
styler method's defaults apply.
`.only({})` exposes none of the selectable styler value parameters. A valid
`Key? key` and method-level `call<T>()` type parameters remain automatic, and
required styler value parameters must be included in an `.only(...)`
selection. Excluded optional parameters are not forwarded, so the styler
method's defaults apply.

For a plain Widget that accepts a generated Styler through a named `style`
parameter, configure a direct target and curate factory controls separately:

```dart
@MixWidget(
name: 'FortalButton',
target: RemixButton.new,
factoryParameters: .only({'variant', 'size'}),
)
ButtonStyler fortalButtonStyler({
ButtonVariant variant = .solid,
ButtonSize size = .medium,
bool highContrast = false,
});
```

This path reads widget parameters and generic type parameters from the target
constructor, omits `style` and `styleSpec`, and instantiates the target
directly with the recipe result passed through `style`. It does not require
the target to extend `StyleWidget` and does not inspect extension `call()`
methods.

For compatibility, an annotation from an older `mix_annotations` release that
does not define `widgetParameters` is interpreted as `.all()`. Using
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ class MixWidgetBuilder {
? '${model.factoryReference}(${_factoryArgs()})'
: model.factoryReference;

if (model.hasDirectTarget) {
_writeDirectTargetBuild(buffer, invocation);
buffer.writeln(' }');
return;
}

final callArgs = _callArgs();
final callTarget = '$invocation.call${model.typeParameterInvocation}';

Expand All @@ -134,6 +140,29 @@ class MixWidgetBuilder {
buffer.writeln(' }');
}

void _writeDirectTargetBuild(StringBuffer buffer, String styleInvocation) {
final constructorSuffix = model.targetConstructorName == null
? ''
: '.${model.targetConstructorName}';
final target =
'${model.targetTypeReference}${model.typeParameterInvocation}'
'$constructorSuffix';
final args = [
for (final p in model.callParams.where((p) => p.isPositional))
'this.${p.name}',
if (model.stylerCallForwardsKey) 'key: this.key',
'style: $styleInvocation',
for (final p in model.callParams.where((p) => !p.isPositional))
'${p.name}: this.${p.name}',
];

buffer.writeln(' return $target(');
for (final arg in args) {
buffer.writeln(' $arg,');
}
buffer.writeln(' );');
}

/// Renders the comma-separated argument list passed to the factory function.
/// Positionals and named params are read through `this` so generated field
/// names cannot be shadowed by locals in `build`.
Expand Down
20 changes: 19 additions & 1 deletion packages/mix_generator/lib/src/core/models/mix_widget_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ class MixWidgetModel {
/// and the generated `build()` forwards `key: this.key`.
final bool stylerCallForwardsKey;

/// Plain widget type instantiated directly, or `null` for the legacy Styler
/// `call()` path.
final String? targetTypeReference;

/// Named constructor suffix for [targetTypeReference], or `null` for `.new`.
final String? targetConstructorName;

/// Doc comment carried over from the annotated element (with leading
/// `///` markers intact), or `null` when the element has no doc.
final String? doc;
Expand All @@ -129,6 +136,8 @@ class MixWidgetModel {
required this.callParams,
this.callTypeParams = const [],
required this.stylerCallForwardsKey,
this.targetTypeReference,
this.targetConstructorName,
this.doc,
this.variantParamName,
this.variantConstructors = const [],
Expand All @@ -145,12 +154,21 @@ class MixWidgetModel {
///
/// The builder applies Dart constructor syntax ordering when emitting code:
/// all positional params first, then named params.
List<WidgetCallParam> get allParams => [...factoryParams, ...callParams];
List<WidgetCallParam> get allParams {
final seen = <String>{};
return [
for (final parameter in [...factoryParams, ...callParams])
if (seen.add(parameter.name)) parameter,
];
}

/// Type parameter declaration suffix for the generated widget class.
String get typeParameterDeclaration =>
_typeParameterSuffix((p) => p.declarationCode);

/// Type argument suffix for forwarding to the styler `call()` method.
String get typeParameterInvocation => _typeParameterSuffix((p) => p.name);

/// Whether `build()` instantiates a plain target widget directly.
bool get hasDirectTarget => targetTypeReference != null;
}
Loading
Loading