From a260436b42330bb0f1c45e154e86eb83aebbec18 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 22:18:34 +0000 Subject: [PATCH 01/14] Accept a bare number as a cornerRadius Found rebuilding the Tidepool reference app in CodelesslyEditor. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- lib/src/api/models/corner_radius.dart | 8 ++++++ test/corner_radius_json_test.dart | 37 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/corner_radius_json_test.dart diff --git a/lib/src/api/models/corner_radius.dart b/lib/src/api/models/corner_radius.dart index 688a05c1..95aad9ab 100644 --- a/lib/src/api/models/corner_radius.dart +++ b/lib/src/api/models/corner_radius.dart @@ -119,6 +119,14 @@ class CornerRadius extends Equatable with DynamicSerializableMixin { /// Factory constructor for creating a new [CornerRadius] instance from /// JSON data. factory CornerRadius.fromJson(dynamic json) { + // A bare number is the same circular radius on every corner, the way + // padding accepts a single number. + if (json is num) { + return CornerRadius.all( + RadiusModel.circular(json.toDouble()), + linked: true, + ); + } if (json case [num radius, bool linked, String type]) { return CornerRadius.all( RadiusModel.circular(radius.toDouble()), diff --git a/test/corner_radius_json_test.dart b/test/corner_radius_json_test.dart new file mode 100644 index 00000000..8c67bd84 --- /dev/null +++ b/test/corner_radius_json_test.dart @@ -0,0 +1,37 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + group('CornerRadius.fromJson', () { + test('a bare number is a circular radius on every corner', () { + final CornerRadius r = CornerRadius.fromJson(4); + expect(r, CornerRadius.all(RadiusModel.circular(4), linked: true)); + expect(CornerRadius.fromJson(2.5).tl, RadiusModel.circular(2.5)); + }); + + test('a node accepts a numeric cornerRadius', () { + final node = RectangleNode.fromJson({ + 'id': 'r', + 'name': 'Card', + 'type': 'rectangle', + 'basicBoxLocal': [0, 0, 100, 50], + 'cornerRadius': 6, + }); + expect(node.cornerRadius.br, RadiusModel.circular(6)); + }); + + test('the compact and map forms still decode', () { + expect(CornerRadius.fromJson([8, false, 'circular']).tl, + RadiusModel.circular(8)); + expect( + CornerRadius.fromJson({ + 'tl': {'x': 1, 'y': 1}, + 'tr': {'x': 2, 'y': 2}, + 'bl': {'x': 3, 'y': 3}, + 'br': {'x': 4, 'y': 4}, + }).br, + RadiusModel.circular(4), + ); + }); + }); +} From 491edef59c83702ffe530604e772dc1d574e77b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 22:41:54 +0000 Subject: [PATCH 02/14] Add onSuccess and onError actions to API calls Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- .../api/models/action/api_call_action.dart | 39 ++++++++++++++----- .../api/models/action/api_call_action.g.dart | 10 +++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/src/api/models/action/api_call_action.dart b/lib/src/api/models/action/api_call_action.dart index cb64018a..f9f5722f 100644 --- a/lib/src/api/models/action/api_call_action.dart +++ b/lib/src/api/models/action/api_call_action.dart @@ -2,13 +2,16 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:equatable/equatable.dart'; import '../../mixins.dart'; -import '../condition.dart'; -import 'action.dart'; -import '../../json_defaults.dart'; +import '../models.dart'; part 'api_call_action.g.dart'; /// Holds information about an API call action to perform on a user interaction. +/// +/// The response is the chain's `${result}` afterwards (`${result.data}`, +/// `${result.isError}`, `${result.statusCode}`, ...). [onSuccess] runs after a +/// 2xx response and [onError] after any other response or a failed request. +/// With [onError] actions, a failed request doesn't stop the chain. @JsonSerializable() class ApiCallAction extends ActionModel with Equatable, SerializableMixin { /// ID of the API to call. @@ -17,25 +20,43 @@ class ApiCallAction extends ActionModel with Equatable, SerializableMixin { /// Parameters to pass to the API. final Map parameters; + /// Actions to run, in order, after a successful (2xx) response. + final List onSuccess; + + /// Actions to run, in order, after an error response or a failed request. + final List onError; + /// Creates an [ApiCallAction] with the given data. ApiCallAction({ this.apiId, Map? parameters, + List? onSuccess, + List? onError, super.nonBlocking, super.enabled, }) : parameters = parameters ?? {}, + onSuccess = onSuccess ?? [], + onError = onError ?? [], super(type: ActionType.callApi); @override - List get props => [apiId, parameters]; + List get props => [apiId, parameters, onSuccess, onError]; /// Creates a copy of this [ApiCallAction] but with the given fields replaced /// with the new values. - ApiCallAction copyWith({String? apiId, Map? parameters}) => - ApiCallAction( - apiId: apiId ?? this.apiId, - parameters: parameters ?? this.parameters, - ); + ApiCallAction copyWith({ + String? apiId, + Map? parameters, + List? onSuccess, + List? onError, + }) => ApiCallAction( + apiId: apiId ?? this.apiId, + parameters: parameters ?? this.parameters, + onSuccess: onSuccess ?? this.onSuccess, + onError: onError ?? this.onError, + nonBlocking: nonBlocking, + enabled: enabled, + ); /// Creates a new [ApiCallAction] from a JSON object. factory ApiCallAction.fromJson(Map json) => _$ApiCallActionFromJson(json); diff --git a/lib/src/api/models/action/api_call_action.g.dart b/lib/src/api/models/action/api_call_action.g.dart index e84f5dd1..af04039f 100644 --- a/lib/src/api/models/action/api_call_action.g.dart +++ b/lib/src/api/models/action/api_call_action.g.dart @@ -11,6 +11,8 @@ Map _$ApiCallActionOmitDefaults( Map json, ) { if (isJsonDefault(instance.parameters, {})) json.remove('parameters'); + if (isJsonDefault(instance.onSuccess, [])) json.remove('onSuccess'); + if (isJsonDefault(instance.onError, [])) json.remove('onError'); if (isJsonDefault(instance.nonBlocking, false)) json.remove('nonBlocking'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); return json; @@ -25,6 +27,12 @@ ApiCallAction _$ApiCallActionFromJson(Map json) => ApiCallAction( parameters: (json['parameters'] as Map?)?.map( (k, e) => MapEntry(k as String, e as String), ), + onSuccess: (json['onSuccess'] as List?) + ?.map((e) => ActionModel.fromJson(e as Map)) + .toList(), + onError: (json['onError'] as List?) + ?.map((e) => ActionModel.fromJson(e as Map)) + .toList(), nonBlocking: json['nonBlocking'] as bool? ?? false, enabled: json['enabled'] as bool? ?? true, )..type = $enumDecode(_$ActionTypeEnumMap, json['type']); @@ -36,6 +44,8 @@ Map _$ApiCallActionToJson(ApiCallAction instance) => 'enabled': instance.enabled, 'apiId': ?instance.apiId, 'parameters': instance.parameters, + 'onSuccess': instance.onSuccess.map((e) => e.toJson()).toList(), + 'onError': instance.onError.map((e) => e.toJson()).toList(), }; const _$ActionTypeEnumMap = { From 2e23c645896394954e892063d943dbfc7e5a9fcd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 22:42:28 +0000 Subject: [PATCH 03/14] Add LineChartNode, a data-driven line chart node LineChartNode (type lineChart) and LineChartProperties: a series read from variables['data'] through xField/yField (numbers or wall-clock dates), an optional domain, plot insets, bands, y ticks, area gradient, area bands, a polyline or Catmull-Rom curve, reference lines, x ticks, markers, vertical lines and a scrub readout. List bindings and their JSON shapes are documented on the class. Properties decode with defaults when missing, and default text style paints have fixed ids so defaults compare equal. Register it with NodeJsonConverter, cover it in the every-node-type round-trip test and add JSON round-trip and default tests. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 8 + lib/src/api/node_json_converter.dart | 1 + lib/src/api/nodes/line_chart_node.dart | 734 +++++++++++++++++++ lib/src/api/nodes/line_chart_node.g.dart | 349 +++++++++ lib/src/api/nodes/nodes.dart | 1 + test/json_defaults_test.dart | 1 + test/nodes/display/line_chart_node_test.dart | 127 ++++ 7 files changed, 1221 insertions(+) create mode 100644 lib/src/api/nodes/line_chart_node.dart create mode 100644 lib/src/api/nodes/line_chart_node.g.dart create mode 100644 test/nodes/display/line_chart_node_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb1e325..006e042c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## Unreleased + +- `LineChartNode` (`lineChart`) and `LineChartProperties`: a data-driven + line chart. The series, domain, bands, grid lines, area bands, reference + lines, x ticks, markers and vertical lines are bound through `variables` + (see the class docs for their JSON shapes); colors, insets, curve style, + text styles and the scrub readout are properties. + ## 6.1.0 ### States and conditions diff --git a/lib/src/api/node_json_converter.dart b/lib/src/api/node_json_converter.dart index 311acd7c..72e79985 100644 --- a/lib/src/api/node_json_converter.dart +++ b/lib/src/api/node_json_converter.dart @@ -66,6 +66,7 @@ class NodeJsonConverter implements JsonConverter { 'webView': WebViewNode.fromJson, 'dropdown': DropdownNode.fromJson, 'progressBar': ProgressBarNode.fromJson, + 'lineChart': LineChartNode.fromJson, 'variance': VarianceNode.fromJson, 'listView': ListViewNode.fromJson, 'gridView': GridViewNode.fromJson, diff --git a/lib/src/api/nodes/line_chart_node.dart b/lib/src/api/nodes/line_chart_node.dart new file mode 100644 index 00000000..f164c4dc --- /dev/null +++ b/lib/src/api/nodes/line_chart_node.dart @@ -0,0 +1,734 @@ +import 'package:json_annotation/json_annotation.dart'; + +import '../field_access.dart'; +import '../mixins.dart'; +import '../models/models.dart'; +import 'nodes.dart'; + +part 'line_chart_node.g.dart'; + +/// The default width of a line chart. +const double kLineChartDefaultWidth = 360; + +/// The default height of a line chart. +const double kLineChartDefaultHeight = 240; + +/// A data-driven line chart: a series of points drawn as a polyline or a +/// Catmull-Rom curve with an area gradient, optional bands, grid lines, +/// reference lines, markers, vertical lines and an interactive scrub cursor +/// with a readout card. +/// +/// Everything that depends on data is bound through [variables], each a +/// path such as `${chart.points}` or a formula such as `${= ... }`: +/// +/// - `data`: the series, a list of maps. Each item's x is read from +/// [LineChartProperties.xField] and y from [LineChartProperties.yField]. +/// x is a number or a date (an ISO string such as `2026-10-22 00:30` or +/// `2026-10-22T00:30:00`, read as wall-clock time without any timezone +/// conversion). y is a number or a numeric string. +/// - `xMin`, `xMax`, `yMin`, `yMax`: the domain. Each defaults to +/// [LineChartProperties.xMin] etc., then to the data's extent. x values +/// may be dates. +/// - `bands`: full-height rectangles drawn first, e.g. night shading. +/// `[{"from": x, "to": x, "color": "#2A3A44", "opacity": 0.08}]` +/// - `yTicks`: horizontal grid lines with labels right-aligned 10 px left +/// of the plot. `[{"y": 0, "label": "0", "color": "#CFC3AA"}]`; `label` +/// and `color` are optional (the color defaults to +/// [LineChartProperties.gridColor], or +/// [LineChartProperties.zeroLineColor] when y is 0). +/// - `areaBands`: parts of the area under the curve, inside x in +/// [from, to] and below y = `below`, filled with `color`, plus an +/// optional 4 px bar on the x axis in `barColor`. +/// `[{"from": x, "to": x, "below": 1.0, "color": "#D9573B", +/// "opacity": 0.38, "barColor": "#D9573B"}]` +/// - `referenceLines`: dashed horizontal lines across the plot. +/// `[{"y": 1.0, "color": "#D9573B", "width": 1.5, "dash": [5, 5]}]` +/// - `xTicks`: ticks and labels below the x axis. The first label is +/// left-aligned to its tick, the last right-aligned, the rest centered. +/// `[{"x": "2026-10-22 00:00", "label": "12a"}]` +/// - `markers`: points with a circle and a label above or below. +/// `[{"x": x, "y": 5.0, "label": "H 5.0", "filled": false, +/// "above": true}]` +/// - `verticalLines`: dashed vertical lines with an optional tag above the +/// plot and an optional dot on the curve. `[{"x": x, "color": "#1F2B2E", +/// "dash": [3, 3], "label": "NOW", "dotOnCurve": true, +/// "dotColor": "#D9573B"}]` +/// - `semanticLabel`: the chart's accessibility label. +/// - Any color property, e.g. `lineColor`, can be bound too. +/// +/// Colors in the lists are hex strings (`#RRGGBB` or `#AARRGGBB`). +@JsonSerializable() +class LineChartNode extends SceneNode with CustomPropertiesMixin { + @override + final String type = 'lineChart'; + + /// Holds configurable properties of the chart. + @override + covariant LineChartProperties properties; + + @override + bool get supportsPadding => false; + + /// Creates a line chart preview. Strictly used for previews, e.g. in the + /// components panel. + LineChartNode.empty() + : properties = LineChartProperties(), + super( + id: 'lineChart', + name: 'Line Chart', + basicBoxLocal: NodeBox( + 0, + 0, + kLineChartDefaultWidth, + kLineChartDefaultHeight, + ), + ); + + /// Creates a line chart preview from the given [properties]. + LineChartNode.fromProperties(this.properties) + : super( + id: 'lineChart', + name: 'Line Chart', + basicBoxLocal: NodeBox( + 0, + 0, + kLineChartDefaultWidth, + kLineChartDefaultHeight, + ), + ); + + /// Creates a [LineChartNode] with the given data. [properties] defaults to + /// [LineChartProperties.new]. + LineChartNode({ + required super.id, + required super.name, + required super.basicBoxLocal, + super.outerBoxLocal, + super.retainedOuterBoxLocal, + super.visible, + super.enabled, + super.rotationDegrees, + super.alignment, + super.margin, + super.padding, + super.horizontalFit, + super.verticalFit, + super.flex, + super.constraints, + super.edgePins, + super.aspectRatioLock, + super.positioningMode, + super.parentID, + super.reactions, + LineChartProperties? properties, + super.variables, + super.multipleVariables, + }) : properties = properties ?? LineChartProperties() { + setVariablesMixin( + variables: variables, + multipleVariables: multipleVariables, + ); + } + + @override + FieldsMap generateFields() => { + ...super.generateFields(), + 'properties': ObjectFieldAccess( + 'Properties', + 'Properties of the line chart', + () => properties, + (value) => properties = value, + ), + }; + + @override + List get props => [ + ...super.props, + properties, + variables, + multipleVariables, + ]; + + /// Creates a [LineChartNode] from a JSON object. + factory LineChartNode.fromJson(Map json) => _$LineChartNodeFromJson(json); + + @override + Map toJson() => + _$LineChartNodeOmitDefaults(this, _$LineChartNodeToJson(this)); +} + +/// Holds configurable properties of a [LineChartNode]. See [LineChartNode] +/// for the data bindings and the JSON shapes of the list bindings. +@JsonSerializable() +class LineChartProperties extends CustomProperties with FieldsHolder { + /// The key of each data item that holds its x value. + String xField; + + /// The key of each data item that holds its y value. + String yField; + + /// The lower bound of the x axis. Defaults to the data's minimum x. + /// Bind `xMin` to give a date. + double? xMin; + + /// The upper bound of the x axis. Defaults to the data's maximum x. + /// Bind `xMax` to give a date. + double? xMax; + + /// The lower bound of the y axis. Defaults to the data's minimum y. + double? yMin; + + /// The upper bound of the y axis. Defaults to the data's maximum y. + double? yMax; + + /// Space between the left edge and the plot, holding the y labels. + double padLeft; + + /// Space between the plot and the right edge. + double padRight; + + /// Space between the top edge and the plot, holding vertical line tags. + double padTop; + + /// Space between the plot and the bottom edge, holding the x labels. + double padBottom; + + /// Whether the curve is a Catmull-Rom spline through the points instead + /// of a polyline. + bool smooth; + + /// Color of the curve, marker outlines and curve dots. + ColorRGBA lineColor; + + /// Stroke width of the curve. + double lineWidth; + + /// Color at the top of the plot of the area under the curve. + ColorRGBA areaTopColor; + + /// Color at the bottom of the plot of the area under the curve. + ColorRGBA areaBottomColor; + + /// Color of horizontal grid lines. + ColorRGBA gridColor; + + /// Color of the horizontal grid line at y = 0. + ColorRGBA zeroLineColor; + + /// Color of the x axis, its ticks and, by default, of vertical lines. + ColorRGBA axisColor; + + /// Fill of markers that are not filled. + ColorRGBA markerHollowColor; + + /// Background of vertical line tags. + ColorRGBA tagBackground; + + /// Color of the scrub cursor's line and ring. + ColorRGBA cursorColor; + + /// Fill of the scrub cursor's ring. + ColorRGBA cursorRingFill; + + /// Background of the scrub readout card. + ColorRGBA tooltipBackground; + + /// Color of the readout card's (3, 3) offset shadow. + ColorRGBA tooltipShadowColor; + + /// Text above the y labels, e.g. a unit like "FT". + String yAxisTitle; + + /// Style of x and y axis labels. + TextProp axisLabelStyle; + + /// Style of [yAxisTitle]. + TextProp axisTitleStyle; + + /// Style of marker labels. + TextProp markerLabelStyle; + + /// Style of vertical line tags. + TextProp tagTextStyle; + + /// Style of the readout's first line, the x value. + TextProp tooltipLabelStyle; + + /// Style of the readout's second line, the y value. + TextProp tooltipValueStyle; + + /// Whether hovering, tapping, dragging or the arrow keys move a cursor + /// that reads out the curve. + bool scrubEnabled; + + /// How far the arrow keys move the cursor, in x units; minutes for a date + /// axis. + double scrubStep; + + /// Date pattern of the readout's x value on a date axis, as in the + /// `formatDate` formula function, e.g. `h:mm a`. + String tooltipXFormat; + + /// Factor the readout applies to y, e.g. 0.3048 for feet to metres. + double tooltipYScale; + + /// Decimals of the readout's y value. + int tooltipYDecimals; + + /// Text after the readout's y value, e.g. " ft". + String tooltipYSuffix; + + /// Whether the readout ends with " ↑" or " ↓" for a rising or falling + /// curve. + bool tooltipTrend; + + /// The chart's accessibility label. Bind `semanticLabel` to compute it. + String semanticLabel; + + /// Creates [LineChartProperties] with the given data. The paints of the + /// default text styles have fixed ids, so default properties are equal. + LineChartProperties({ + this.xField = 'x', + this.yField = 'y', + this.xMin, + this.xMax, + this.yMin, + this.yMax, + this.padLeft = 44, + this.padRight = 14, + this.padTop = 30, + this.padBottom = 30, + this.smooth = false, + this.lineColor = ColorRGBA.black, + this.lineWidth = 2, + this.areaTopColor = ColorRGBA.black10, + this.areaBottomColor = ColorRGBA.transparent, + this.gridColor = ColorRGBA.black10, + this.zeroLineColor = ColorRGBA.black54, + this.axisColor = ColorRGBA.black, + this.markerHollowColor = ColorRGBA.white, + this.tagBackground = ColorRGBA.black, + this.cursorColor = ColorRGBA.black, + this.cursorRingFill = ColorRGBA.white, + this.tooltipBackground = ColorRGBA.black, + this.tooltipShadowColor = ColorRGBA.transparent, + this.yAxisTitle = '', + TextProp? axisLabelStyle, + TextProp? axisTitleStyle, + TextProp? markerLabelStyle, + TextProp? tagTextStyle, + TextProp? tooltipLabelStyle, + TextProp? tooltipValueStyle, + this.scrubEnabled = false, + this.scrubStep = 1, + this.tooltipXFormat = 'h:mm a', + this.tooltipYScale = 1, + this.tooltipYDecimals = 1, + this.tooltipYSuffix = '', + this.tooltipTrend = false, + this.semanticLabel = '', + }) : axisLabelStyle = + axisLabelStyle ?? + TextProp( + fontSize: 11, + fills: [ColorRGBA.black54.toPaint('axisLabel')], + ), + axisTitleStyle = + axisTitleStyle ?? + TextProp( + fontSize: 10, + fills: [ColorRGBA.black54.toPaint('axisTitle')], + ), + markerLabelStyle = + markerLabelStyle ?? + TextProp( + fontSize: 11, + fills: [ColorRGBA.black.toPaint('markerLabel')], + ), + tagTextStyle = + tagTextStyle ?? + TextProp(fontSize: 10, fills: [ColorRGBA.white.toPaint('tagText')]), + tooltipLabelStyle = + tooltipLabelStyle ?? + TextProp( + fontSize: 10, + fills: [ColorRGBA.white.toPaint('tooltipLabel', 0.7)], + ), + tooltipValueStyle = + tooltipValueStyle ?? + TextProp( + fontSize: 15, + fills: [ColorRGBA.white.toPaint('tooltipValue')], + ); + + /// Duplicates these properties with the given data overrides. + LineChartProperties copyWith({ + String? xField, + String? yField, + double? xMin, + double? xMax, + double? yMin, + double? yMax, + double? padLeft, + double? padRight, + double? padTop, + double? padBottom, + bool? smooth, + ColorRGBA? lineColor, + double? lineWidth, + ColorRGBA? areaTopColor, + ColorRGBA? areaBottomColor, + ColorRGBA? gridColor, + ColorRGBA? zeroLineColor, + ColorRGBA? axisColor, + ColorRGBA? markerHollowColor, + ColorRGBA? tagBackground, + ColorRGBA? cursorColor, + ColorRGBA? cursorRingFill, + ColorRGBA? tooltipBackground, + ColorRGBA? tooltipShadowColor, + String? yAxisTitle, + TextProp? axisLabelStyle, + TextProp? axisTitleStyle, + TextProp? markerLabelStyle, + TextProp? tagTextStyle, + TextProp? tooltipLabelStyle, + TextProp? tooltipValueStyle, + bool? scrubEnabled, + double? scrubStep, + String? tooltipXFormat, + double? tooltipYScale, + int? tooltipYDecimals, + String? tooltipYSuffix, + bool? tooltipTrend, + String? semanticLabel, + }) => LineChartProperties( + xField: xField ?? this.xField, + yField: yField ?? this.yField, + xMin: xMin ?? this.xMin, + xMax: xMax ?? this.xMax, + yMin: yMin ?? this.yMin, + yMax: yMax ?? this.yMax, + padLeft: padLeft ?? this.padLeft, + padRight: padRight ?? this.padRight, + padTop: padTop ?? this.padTop, + padBottom: padBottom ?? this.padBottom, + smooth: smooth ?? this.smooth, + lineColor: lineColor ?? this.lineColor, + lineWidth: lineWidth ?? this.lineWidth, + areaTopColor: areaTopColor ?? this.areaTopColor, + areaBottomColor: areaBottomColor ?? this.areaBottomColor, + gridColor: gridColor ?? this.gridColor, + zeroLineColor: zeroLineColor ?? this.zeroLineColor, + axisColor: axisColor ?? this.axisColor, + markerHollowColor: markerHollowColor ?? this.markerHollowColor, + tagBackground: tagBackground ?? this.tagBackground, + cursorColor: cursorColor ?? this.cursorColor, + cursorRingFill: cursorRingFill ?? this.cursorRingFill, + tooltipBackground: tooltipBackground ?? this.tooltipBackground, + tooltipShadowColor: tooltipShadowColor ?? this.tooltipShadowColor, + yAxisTitle: yAxisTitle ?? this.yAxisTitle, + axisLabelStyle: axisLabelStyle ?? this.axisLabelStyle, + axisTitleStyle: axisTitleStyle ?? this.axisTitleStyle, + markerLabelStyle: markerLabelStyle ?? this.markerLabelStyle, + tagTextStyle: tagTextStyle ?? this.tagTextStyle, + tooltipLabelStyle: tooltipLabelStyle ?? this.tooltipLabelStyle, + tooltipValueStyle: tooltipValueStyle ?? this.tooltipValueStyle, + scrubEnabled: scrubEnabled ?? this.scrubEnabled, + scrubStep: scrubStep ?? this.scrubStep, + tooltipXFormat: tooltipXFormat ?? this.tooltipXFormat, + tooltipYScale: tooltipYScale ?? this.tooltipYScale, + tooltipYDecimals: tooltipYDecimals ?? this.tooltipYDecimals, + tooltipYSuffix: tooltipYSuffix ?? this.tooltipYSuffix, + tooltipTrend: tooltipTrend ?? this.tooltipTrend, + semanticLabel: semanticLabel ?? this.semanticLabel, + ); + + @override + FieldsMap generateFields() { + NumFieldAccess number( + String label, + String description, + double Function() get, + void Function(double) set, + ) => NumFieldAccess(label, description, get, set); + ColorFieldAccess color( + String label, + String description, + ColorRGBA Function() get, + void Function(ColorRGBA) set, + ) => ColorFieldAccess(label, description, get, set); + TextStyleFieldAccess style( + String label, + String description, + TextProp Function() get, + void Function(TextProp) set, + ) => TextStyleFieldAccess(label, description, get, set); + return { + ...super.generateFields(), + 'xField': StringFieldAccess( + 'X Field', + 'Key of each data item holding its x value', + () => xField, + (value) => xField = value, + ), + 'yField': StringFieldAccess( + 'Y Field', + 'Key of each data item holding its y value', + () => yField, + (value) => yField = value, + ), + 'smooth': BoolFieldAccess( + 'Smooth', + 'Whether the curve is a spline through the points', + () => smooth, + (value) => smooth = value, + ), + 'lineWidth': number( + 'Line Width', + 'Stroke width of the curve', + () => lineWidth, + (value) => lineWidth = value, + ), + 'padLeft': number( + 'Left Inset', + 'Space left of the plot', + () => padLeft, + (value) => padLeft = value, + ), + 'padRight': number( + 'Right Inset', + 'Space right of the plot', + () => padRight, + (value) => padRight = value, + ), + 'padTop': number( + 'Top Inset', + 'Space above the plot', + () => padTop, + (value) => padTop = value, + ), + 'padBottom': number( + 'Bottom Inset', + 'Space below the plot', + () => padBottom, + (value) => padBottom = value, + ), + 'lineColor': color( + 'Line Color', + 'Color of the curve', + () => lineColor, + (value) => lineColor = value, + ), + 'areaTopColor': color( + 'Area Top Color', + 'Top color of the area gradient', + () => areaTopColor, + (value) => areaTopColor = value, + ), + 'areaBottomColor': color( + 'Area Bottom Color', + 'Bottom color of the area gradient', + () => areaBottomColor, + (value) => areaBottomColor = value, + ), + 'gridColor': color( + 'Grid Color', + 'Color of horizontal grid lines', + () => gridColor, + (value) => gridColor = value, + ), + 'zeroLineColor': color( + 'Zero Line Color', + 'Color of the grid line at zero', + () => zeroLineColor, + (value) => zeroLineColor = value, + ), + 'axisColor': color( + 'Axis Color', + 'Color of the x axis and ticks', + () => axisColor, + (value) => axisColor = value, + ), + 'markerHollowColor': color( + 'Hollow Marker Color', + 'Fill of markers that are not filled', + () => markerHollowColor, + (value) => markerHollowColor = value, + ), + 'tagBackground': color( + 'Tag Background', + 'Background of vertical line tags', + () => tagBackground, + (value) => tagBackground = value, + ), + 'cursorColor': color( + 'Cursor Color', + 'Color of the scrub cursor', + () => cursorColor, + (value) => cursorColor = value, + ), + 'cursorRingFill': color( + 'Cursor Ring Fill', + 'Fill of the scrub cursor ring', + () => cursorRingFill, + (value) => cursorRingFill = value, + ), + 'tooltipBackground': color( + 'Readout Background', + 'Background of the scrub readout', + () => tooltipBackground, + (value) => tooltipBackground = value, + ), + 'tooltipShadowColor': color( + 'Readout Shadow', + 'Color of the readout shadow', + () => tooltipShadowColor, + (value) => tooltipShadowColor = value, + ), + 'yAxisTitle': StringFieldAccess( + 'Y Axis Title', + 'Text above the y labels', + () => yAxisTitle, + (value) => yAxisTitle = value, + ), + 'axisLabelStyle': style( + 'Axis Label Style', + 'Style of axis labels', + () => axisLabelStyle, + (value) => axisLabelStyle = value, + ), + 'axisTitleStyle': style( + 'Axis Title Style', + 'Style of the y axis title', + () => axisTitleStyle, + (value) => axisTitleStyle = value, + ), + 'markerLabelStyle': style( + 'Marker Label Style', + 'Style of marker labels', + () => markerLabelStyle, + (value) => markerLabelStyle = value, + ), + 'tagTextStyle': style( + 'Tag Text Style', + 'Style of vertical line tags', + () => tagTextStyle, + (value) => tagTextStyle = value, + ), + 'tooltipLabelStyle': style( + 'Readout Label Style', + 'Style of the readout x value', + () => tooltipLabelStyle, + (value) => tooltipLabelStyle = value, + ), + 'tooltipValueStyle': style( + 'Readout Value Style', + 'Style of the readout y value', + () => tooltipValueStyle, + (value) => tooltipValueStyle = value, + ), + 'scrubEnabled': BoolFieldAccess( + 'Scrub', + 'Whether a cursor reads out the curve', + () => scrubEnabled, + (value) => scrubEnabled = value, + ), + 'scrubStep': number( + 'Scrub Step', + 'Arrow key step in x units (minutes for dates)', + () => scrubStep, + (value) => scrubStep = value, + ), + 'tooltipXFormat': StringFieldAccess( + 'Readout X Format', + 'Date pattern of the readout x value', + () => tooltipXFormat, + (value) => tooltipXFormat = value, + ), + 'tooltipYScale': number( + 'Readout Y Scale', + 'Factor applied to the readout y value', + () => tooltipYScale, + (value) => tooltipYScale = value, + ), + 'tooltipYDecimals': NumFieldAccess( + 'Readout Y Decimals', + 'Decimals of the readout y value', + () => tooltipYDecimals, + (value) => tooltipYDecimals = value, + ), + 'tooltipYSuffix': StringFieldAccess( + 'Readout Y Suffix', + 'Text after the readout y value', + () => tooltipYSuffix, + (value) => tooltipYSuffix = value, + ), + 'tooltipTrend': BoolFieldAccess( + 'Readout Trend', + 'Whether the readout shows a rising or falling arrow', + () => tooltipTrend, + (value) => tooltipTrend = value, + ), + 'semanticLabel': StringFieldAccess( + 'Semantic Label', + 'Accessibility label of the chart', + () => semanticLabel, + (value) => semanticLabel = value, + ), + }; + } + + @override + List get props => [ + xField, + yField, + xMin, + xMax, + yMin, + yMax, + padLeft, + padRight, + padTop, + padBottom, + smooth, + lineColor, + lineWidth, + areaTopColor, + areaBottomColor, + gridColor, + zeroLineColor, + axisColor, + markerHollowColor, + tagBackground, + cursorColor, + cursorRingFill, + tooltipBackground, + tooltipShadowColor, + yAxisTitle, + axisLabelStyle, + axisTitleStyle, + markerLabelStyle, + tagTextStyle, + tooltipLabelStyle, + tooltipValueStyle, + scrubEnabled, + scrubStep, + tooltipXFormat, + tooltipYScale, + tooltipYDecimals, + tooltipYSuffix, + tooltipTrend, + semanticLabel, + ]; + + /// Creates [LineChartProperties] from a JSON object. + factory LineChartProperties.fromJson(Map json) => + _$LineChartPropertiesFromJson(json); + + @override + Map toJson() => _$LineChartPropertiesOmitDefaults( + this, + _$LineChartPropertiesToJson(this), + ); +} diff --git a/lib/src/api/nodes/line_chart_node.g.dart b/lib/src/api/nodes/line_chart_node.g.dart new file mode 100644 index 00000000..dc50efe6 --- /dev/null +++ b/lib/src/api/nodes/line_chart_node.g.dart @@ -0,0 +1,349 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'line_chart_node.dart'; + +// ************************************************************************** +// JsonDefaultsGenerator +// ************************************************************************** + +Map _$LineChartNodeOmitDefaults( + LineChartNode instance, + Map json, +) { + if (isJsonDefault(instance.visible, true)) json.remove('visible'); + if (excludeConstraintsIf(instance)) json.remove('constraints'); + if (isJsonDefault(instance.constraints, const BoxConstraintsModel())) + json.remove('constraints'); + if (isJsonDefault(instance.edgePins, EdgePinsModel.standard)) + json.remove('edgePins'); + if (isJsonDefault(instance.positioningMode, PositioningMode.align)) + json.remove('positioningMode'); + if (isJsonDefault(instance.horizontalFit, SizeFit.fixed)) + json.remove('horizontalFit'); + if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) + json.remove('verticalFit'); + if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.aspectRatioLock, false)) + json.remove('aspectRatioLock'); + if (isJsonDefault(instance.alignment, AlignmentModel.none)) + json.remove('alignment'); + if (isJsonDefault(instance.margin, EdgeInsetsModel.zero)) + json.remove('margin'); + if (isJsonDefault(instance.padding, EdgeInsetsModel.zero)) + json.remove('padding'); + if (isJsonDefault(instance.rotationDegrees, 0)) json.remove('rotation'); + if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); + if (isJsonDefault(instance.variables, {})) json.remove('variables'); + if (isJsonDefault(instance.multipleVariables, {})) + json.remove('multipleVariables'); + if (isJsonDefault(instance.componentVersion, 1)) + json.remove('componentVersion'); + if (isJsonDefault(instance.componentSchema, {})) + json.remove('componentSchema'); + if (isJsonDefault(instance.reactions, const [])) json.remove('reactions'); + return json; +} + +Map _$LineChartPropertiesOmitDefaults( + LineChartProperties instance, + Map json, +) { + if (isJsonDefault(instance.xField, 'x')) json.remove('xField'); + if (isJsonDefault(instance.yField, 'y')) json.remove('yField'); + if (isJsonDefault(instance.padLeft, 44)) json.remove('padLeft'); + if (isJsonDefault(instance.padRight, 14)) json.remove('padRight'); + if (isJsonDefault(instance.padTop, 30)) json.remove('padTop'); + if (isJsonDefault(instance.padBottom, 30)) json.remove('padBottom'); + if (isJsonDefault(instance.smooth, false)) json.remove('smooth'); + if (isJsonDefault(instance.lineColor, ColorRGBA.black)) + json.remove('lineColor'); + if (isJsonDefault(instance.lineWidth, 2)) json.remove('lineWidth'); + if (isJsonDefault(instance.areaTopColor, ColorRGBA.black10)) + json.remove('areaTopColor'); + if (isJsonDefault(instance.areaBottomColor, ColorRGBA.transparent)) + json.remove('areaBottomColor'); + if (isJsonDefault(instance.gridColor, ColorRGBA.black10)) + json.remove('gridColor'); + if (isJsonDefault(instance.zeroLineColor, ColorRGBA.black54)) + json.remove('zeroLineColor'); + if (isJsonDefault(instance.axisColor, ColorRGBA.black)) + json.remove('axisColor'); + if (isJsonDefault(instance.markerHollowColor, ColorRGBA.white)) + json.remove('markerHollowColor'); + if (isJsonDefault(instance.tagBackground, ColorRGBA.black)) + json.remove('tagBackground'); + if (isJsonDefault(instance.cursorColor, ColorRGBA.black)) + json.remove('cursorColor'); + if (isJsonDefault(instance.cursorRingFill, ColorRGBA.white)) + json.remove('cursorRingFill'); + if (isJsonDefault(instance.tooltipBackground, ColorRGBA.black)) + json.remove('tooltipBackground'); + if (isJsonDefault(instance.tooltipShadowColor, ColorRGBA.transparent)) + json.remove('tooltipShadowColor'); + if (isJsonDefault(instance.yAxisTitle, '')) json.remove('yAxisTitle'); + if (isJsonDefault(instance.scrubEnabled, false)) json.remove('scrubEnabled'); + if (isJsonDefault(instance.scrubStep, 1)) json.remove('scrubStep'); + if (isJsonDefault(instance.tooltipXFormat, 'h:mm a')) + json.remove('tooltipXFormat'); + if (isJsonDefault(instance.tooltipYScale, 1)) json.remove('tooltipYScale'); + if (isJsonDefault(instance.tooltipYDecimals, 1)) + json.remove('tooltipYDecimals'); + if (isJsonDefault(instance.tooltipYSuffix, '')) json.remove('tooltipYSuffix'); + if (isJsonDefault(instance.tooltipTrend, false)) json.remove('tooltipTrend'); + if (isJsonDefault(instance.semanticLabel, '')) json.remove('semanticLabel'); + return json; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LineChartNode _$LineChartNodeFromJson(Map json) => + LineChartNode( + id: json['id'] as String, + name: json['name'] as String, + basicBoxLocal: NodeBox.fromJson(json['basicBoxLocal']), + visible: json['visible'] as bool? ?? true, + enabled: json['enabled'] as bool? ?? true, + rotationDegrees: json['rotation'] == null + ? 0 + : castRotation(json['rotation']), + alignment: json['alignment'] == null + ? AlignmentModel.none + : AlignmentModel.fromJson(json['alignment'] as Map), + margin: json['margin'] == null + ? EdgeInsetsModel.zero + : EdgeInsetsModel.fromJson(json['margin']), + padding: json['padding'] == null + ? EdgeInsetsModel.zero + : EdgeInsetsModel.fromJson(json['padding']), + horizontalFit: + $enumDecodeNullable(_$SizeFitEnumMap, json['horizontalFit']) ?? + SizeFit.fixed, + verticalFit: + $enumDecodeNullable(_$SizeFitEnumMap, json['verticalFit']) ?? + SizeFit.fixed, + flex: (json['flex'] as num?)?.toInt() ?? 1, + constraints: json['constraints'] == null + ? const BoxConstraintsModel() + : BoxConstraintsModel.fromJson(json['constraints'] as Map), + edgePins: json['edgePins'] == null + ? EdgePinsModel.standard + : EdgePinsModel.fromJson( + Map.from(json['edgePins'] as Map), + ), + aspectRatioLock: json['aspectRatioLock'] as bool? ?? false, + positioningMode: + $enumDecodeNullable( + _$PositioningModeEnumMap, + json['positioningMode'], + ) ?? + PositioningMode.align, + reactions: + (json['reactions'] as List?) + ?.map((e) => Reaction.fromJson(e as Map)) + .toList() ?? + const [], + properties: json['properties'] == null + ? null + : LineChartProperties.fromJson(json['properties'] as Map), + variables: + (json['variables'] as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + ) ?? + {}, + multipleVariables: + (json['multipleVariables'] as Map?)?.map( + (k, e) => MapEntry( + k as String, + (e as List).map((e) => e as String).toList(), + ), + ) ?? + {}, + ) + ..componentId = json['componentId'] as String? + ..markerType = $enumDecodeNullable( + _$ComponentMarkerTypeEnumMap, + json['markerType'], + ) + ..componentVersion = (json['componentVersion'] as num?)?.toInt() ?? 1 + ..componentSchema = + (json['componentSchema'] as Map?)?.map( + (k, e) => MapEntry(k as String, e), + ) ?? + {} + ..widthFactor = (json['widthFactor'] as num?)?.toDouble() + ..heightFactor = (json['heightFactor'] as num?)?.toDouble() + ..type = json['type'] as String; + +Map _$LineChartNodeToJson(LineChartNode instance) => + { + 'variables': instance.variables, + 'multipleVariables': instance.multipleVariables, + 'componentId': ?instance.componentId, + 'markerType': ?_$ComponentMarkerTypeEnumMap[instance.markerType], + 'componentVersion': instance.componentVersion, + 'componentSchema': instance.componentSchema, + 'id': instance.id, + 'name': instance.name, + 'visible': instance.visible, + 'constraints': instance.constraints.toJson(), + 'edgePins': instance.edgePins.toJson(), + 'positioningMode': _$PositioningModeEnumMap[instance.positioningMode]!, + 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, + 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, + 'flex': instance.flex, + 'aspectRatioLock': instance.aspectRatioLock, + 'alignment': instance.alignment.toJson(), + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'basicBoxLocal': instance.basicBoxLocal.toJson(), + 'margin': instance.margin.toJson(), + 'padding': instance.padding.toJson(), + 'rotation': instance.rotationDegrees, + 'widthFactor': ?instance.widthFactor, + 'heightFactor': ?instance.heightFactor, + 'enabled': instance.enabled, + 'type': instance.type, + 'properties': instance.properties.toJson(), + }; + +const _$SizeFitEnumMap = { + SizeFit.locked: 'locked', + SizeFit.fixed: 'fixed', + SizeFit.expanded: 'expanded', + SizeFit.flexible: 'flexible', + SizeFit.shrinkWrap: 'shrinkWrap', +}; + +const _$PositioningModeEnumMap = { + PositioningMode.align: 'align', + PositioningMode.pin: 'pin', +}; + +const _$ComponentMarkerTypeEnumMap = { + ComponentMarkerType.component: 'component', + ComponentMarkerType.instance: 'instance', +}; + +LineChartProperties _$LineChartPropertiesFromJson(Map json) => + LineChartProperties( + xField: json['xField'] as String? ?? 'x', + yField: json['yField'] as String? ?? 'y', + xMin: (json['xMin'] as num?)?.toDouble(), + xMax: (json['xMax'] as num?)?.toDouble(), + yMin: (json['yMin'] as num?)?.toDouble(), + yMax: (json['yMax'] as num?)?.toDouble(), + padLeft: (json['padLeft'] as num?)?.toDouble() ?? 44, + padRight: (json['padRight'] as num?)?.toDouble() ?? 14, + padTop: (json['padTop'] as num?)?.toDouble() ?? 30, + padBottom: (json['padBottom'] as num?)?.toDouble() ?? 30, + smooth: json['smooth'] as bool? ?? false, + lineColor: json['lineColor'] == null + ? ColorRGBA.black + : ColorRGBA.fromJson(json['lineColor']), + lineWidth: (json['lineWidth'] as num?)?.toDouble() ?? 2, + areaTopColor: json['areaTopColor'] == null + ? ColorRGBA.black10 + : ColorRGBA.fromJson(json['areaTopColor']), + areaBottomColor: json['areaBottomColor'] == null + ? ColorRGBA.transparent + : ColorRGBA.fromJson(json['areaBottomColor']), + gridColor: json['gridColor'] == null + ? ColorRGBA.black10 + : ColorRGBA.fromJson(json['gridColor']), + zeroLineColor: json['zeroLineColor'] == null + ? ColorRGBA.black54 + : ColorRGBA.fromJson(json['zeroLineColor']), + axisColor: json['axisColor'] == null + ? ColorRGBA.black + : ColorRGBA.fromJson(json['axisColor']), + markerHollowColor: json['markerHollowColor'] == null + ? ColorRGBA.white + : ColorRGBA.fromJson(json['markerHollowColor']), + tagBackground: json['tagBackground'] == null + ? ColorRGBA.black + : ColorRGBA.fromJson(json['tagBackground']), + cursorColor: json['cursorColor'] == null + ? ColorRGBA.black + : ColorRGBA.fromJson(json['cursorColor']), + cursorRingFill: json['cursorRingFill'] == null + ? ColorRGBA.white + : ColorRGBA.fromJson(json['cursorRingFill']), + tooltipBackground: json['tooltipBackground'] == null + ? ColorRGBA.black + : ColorRGBA.fromJson(json['tooltipBackground']), + tooltipShadowColor: json['tooltipShadowColor'] == null + ? ColorRGBA.transparent + : ColorRGBA.fromJson(json['tooltipShadowColor']), + yAxisTitle: json['yAxisTitle'] as String? ?? '', + axisLabelStyle: json['axisLabelStyle'] == null + ? null + : TextProp.fromJson(json['axisLabelStyle'] as Map), + axisTitleStyle: json['axisTitleStyle'] == null + ? null + : TextProp.fromJson(json['axisTitleStyle'] as Map), + markerLabelStyle: json['markerLabelStyle'] == null + ? null + : TextProp.fromJson(json['markerLabelStyle'] as Map), + tagTextStyle: json['tagTextStyle'] == null + ? null + : TextProp.fromJson(json['tagTextStyle'] as Map), + tooltipLabelStyle: json['tooltipLabelStyle'] == null + ? null + : TextProp.fromJson(json['tooltipLabelStyle'] as Map), + tooltipValueStyle: json['tooltipValueStyle'] == null + ? null + : TextProp.fromJson(json['tooltipValueStyle'] as Map), + scrubEnabled: json['scrubEnabled'] as bool? ?? false, + scrubStep: (json['scrubStep'] as num?)?.toDouble() ?? 1, + tooltipXFormat: json['tooltipXFormat'] as String? ?? 'h:mm a', + tooltipYScale: (json['tooltipYScale'] as num?)?.toDouble() ?? 1, + tooltipYDecimals: (json['tooltipYDecimals'] as num?)?.toInt() ?? 1, + tooltipYSuffix: json['tooltipYSuffix'] as String? ?? '', + tooltipTrend: json['tooltipTrend'] as bool? ?? false, + semanticLabel: json['semanticLabel'] as String? ?? '', + ); + +Map _$LineChartPropertiesToJson( + LineChartProperties instance, +) => { + 'xField': instance.xField, + 'yField': instance.yField, + 'xMin': ?instance.xMin, + 'xMax': ?instance.xMax, + 'yMin': ?instance.yMin, + 'yMax': ?instance.yMax, + 'padLeft': instance.padLeft, + 'padRight': instance.padRight, + 'padTop': instance.padTop, + 'padBottom': instance.padBottom, + 'smooth': instance.smooth, + 'lineColor': instance.lineColor.toJson(), + 'lineWidth': instance.lineWidth, + 'areaTopColor': instance.areaTopColor.toJson(), + 'areaBottomColor': instance.areaBottomColor.toJson(), + 'gridColor': instance.gridColor.toJson(), + 'zeroLineColor': instance.zeroLineColor.toJson(), + 'axisColor': instance.axisColor.toJson(), + 'markerHollowColor': instance.markerHollowColor.toJson(), + 'tagBackground': instance.tagBackground.toJson(), + 'cursorColor': instance.cursorColor.toJson(), + 'cursorRingFill': instance.cursorRingFill.toJson(), + 'tooltipBackground': instance.tooltipBackground.toJson(), + 'tooltipShadowColor': instance.tooltipShadowColor.toJson(), + 'yAxisTitle': instance.yAxisTitle, + 'axisLabelStyle': instance.axisLabelStyle.toJson(), + 'axisTitleStyle': instance.axisTitleStyle.toJson(), + 'markerLabelStyle': instance.markerLabelStyle.toJson(), + 'tagTextStyle': instance.tagTextStyle.toJson(), + 'tooltipLabelStyle': instance.tooltipLabelStyle.toJson(), + 'tooltipValueStyle': instance.tooltipValueStyle.toJson(), + 'scrubEnabled': instance.scrubEnabled, + 'scrubStep': instance.scrubStep, + 'tooltipXFormat': instance.tooltipXFormat, + 'tooltipYScale': instance.tooltipYScale, + 'tooltipYDecimals': instance.tooltipYDecimals, + 'tooltipYSuffix': instance.tooltipYSuffix, + 'tooltipTrend': instance.tooltipTrend, + 'semanticLabel': instance.semanticLabel, +}; diff --git a/lib/src/api/nodes/nodes.dart b/lib/src/api/nodes/nodes.dart index 440e5adc..c5e893f0 100644 --- a/lib/src/api/nodes/nodes.dart +++ b/lib/src/api/nodes/nodes.dart @@ -18,6 +18,7 @@ export 'freeform_placeholder_node.dart'; export 'grid_view_node.dart'; export 'icon_node.dart'; export 'list_tile_node.dart'; +export 'line_chart_node.dart'; export 'list_view_node.dart'; export 'loading_indicator_node.dart'; export 'navigation_bar_node.dart'; diff --git a/test/json_defaults_test.dart b/test/json_defaults_test.dart index bb2e1328..367042fd 100644 --- a/test/json_defaults_test.dart +++ b/test/json_defaults_test.dart @@ -177,6 +177,7 @@ List sampleNodes() { IconNode.empty(), ListTileNode.empty(), ProgressBarNode.empty(), + LineChartNode.empty(), RadioNode.empty(), SegmentedButtonNode.empty(), SliderNode.empty(), diff --git a/test/nodes/display/line_chart_node_test.dart b/test/nodes/display/line_chart_node_test.dart new file mode 100644 index 00000000..adffe957 --- /dev/null +++ b/test/nodes/display/line_chart_node_test.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; + +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +Object? encodeDecode(Object? json) => jsonDecode(jsonEncode(json)); + +void main() { + group('LineChartNode', () { + test('is registered as lineChart', () { + expect(LineChartNode.empty().type, 'lineChart'); + expect(NodeJsonConverter.isNodeRegistered('lineChart'), isTrue); + }); + + test('has the documented defaults', () { + final props = LineChartProperties(); + expect(props.xField, 'x'); + expect(props.yField, 'y'); + expect(props.xMin, isNull); + expect(props.xMax, isNull); + expect(props.yMin, isNull); + expect(props.yMax, isNull); + expect(props.padLeft, 44); + expect(props.padRight, 14); + expect(props.padTop, 30); + expect(props.padBottom, 30); + expect(props.smooth, isFalse); + expect(props.lineWidth, 2); + expect(props.scrubEnabled, isFalse); + expect(props.scrubStep, 1); + expect(props.tooltipXFormat, 'h:mm a'); + expect(props.tooltipYScale, 1); + expect(props.tooltipYDecimals, 1); + expect(props.tooltipYSuffix, ''); + expect(props.tooltipTrend, isFalse); + expect(props.semanticLabel, ''); + }); + + test('decodes with only a type, id, name and box', () { + final node = NodeJsonConverter().fromJson({ + 'type': 'lineChart', + 'id': 'chart', + 'name': 'Chart', + 'basicBoxLocal': [0, 0, 300, 200], + }) as LineChartNode; + expect(node.properties, LineChartProperties()); + expect(node.variables, isEmpty); + }); + + test('leaves default scalar properties out of its JSON', () { + final json = encodeDecode(LineChartNode.empty().toJson()) as Map; + final props = json['properties'] as Map; + for (final key in [ + 'xField', + 'yField', + 'padLeft', + 'padTop', + 'smooth', + 'lineColor', + 'lineWidth', + 'scrubEnabled', + 'tooltipXFormat', + 'tooltipYScale', + 'semanticLabel', + ]) { + expect(props.containsKey(key), isFalse, reason: key); + } + expect(props.containsKey('xMin'), isFalse); + }); + + test('round-trips a tide-like chart', () { + final node = LineChartNode( + id: 'tide', + name: 'Tide chart', + basicBoxLocal: NodeBox(0, 0, 600, 280), + properties: LineChartProperties( + xField: 't', + yField: 'ft', + yMin: -1, + yMax: 7, + smooth: true, + lineColor: ColorRGBA.fromHex('#1F2B2E')!, + lineWidth: 2.5, + areaTopColor: ColorRGBA.fromHex('#6B1F6F78')!, + areaBottomColor: ColorRGBA.fromHex('#1F9CC5B5')!, + yAxisTitle: 'FT', + scrubEnabled: true, + scrubStep: 30, + tooltipYSuffix: ' ft', + tooltipTrend: true, + axisLabelStyle: TextProp( + fontSize: 11, + fontName: const FontName( + family: 'IBM Plex Mono', + weight: FontWeightNumeric.w600, + ), + fills: [ColorRGBA.fromHex('#7D8584')!.toPaint('muted')], + ), + ), + variables: { + 'data': r'${tides.points}', + 'xMin': r'${= tides.day + " 00:00"}', + 'bands': r'${tides.night}', + 'semanticLabel': r'${= "Tide chart for " + tides.dayName}', + }, + ); + final json = encodeDecode(node.toJson()) as Map; + expect(json['type'], 'lineChart'); + expect((json['properties'] as Map)['smooth'], isTrue); + expect((json['properties'] as Map)['yMin'], -1); + final back = NodeJsonConverter().fromJson(json)! as LineChartNode; + expect(back, node); + expect(back.properties.axisLabelStyle.fontName.family, 'IBM Plex Mono'); + expect(back.variables['data'], r'${tides.points}'); + expect(encodeDecode(back.toJson()), json); + }); + + test('copyWith overrides only the given fields', () { + final props = LineChartProperties(smooth: true); + final copy = props.copyWith(lineWidth: 3); + expect(copy.smooth, isTrue); + expect(copy.lineWidth, 3); + expect(copy, isNot(props)); + expect(props.copyWith(), props); + }); + }); +} From c90e76b6c21c20842e7c52657269d8ae03bad8ce Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 23:05:38 +0000 Subject: [PATCH 04/14] Walk actions nested in other actions (selfAndNestedActions) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- lib/src/api/models/action/action.dart | 24 +++++++++++++++++ test/nested_actions_test.dart | 39 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/nested_actions_test.dart diff --git a/lib/src/api/models/action/action.dart b/lib/src/api/models/action/action.dart index 117200e9..860da781 100644 --- a/lib/src/api/models/action/action.dart +++ b/lib/src/api/models/action/action.dart @@ -326,3 +326,27 @@ abstract interface class DataOperationInterface { /// Operation to be performed on the int/double type variable. abstract final NumberOperation numberOperation; } + +/// Walks the actions nested in other actions. +extension NestedActions on ActionModel { + /// The actions this action runs itself: a conditional's branches, a + /// for-each body, an API call's onSuccess/onError, a confirm's or toast's + /// buttons and a cloud files action's follow-ups. + List get childActions => switch (this) { + final ConditionalAction a => [...a.actions, ...a.elseActions], + final ForEachAction a => a.actions, + final ApiCallAction a => [...a.onSuccess, ...a.onError], + final ConfirmAction a => [...a.actions, ...a.cancelActions], + final ShowToastAction a => a.actions, + final CloudFilesAction a => a.actions, + _ => const [], + }; + + /// This action followed by every action nested in it, depth first. + Iterable get selfAndNestedActions sync* { + yield this; + for (final ActionModel child in childActions) { + yield* child.selfAndNestedActions; + } + } +} diff --git a/test/nested_actions_test.dart b/test/nested_actions_test.dart new file mode 100644 index 00000000..9075efd3 --- /dev/null +++ b/test/nested_actions_test.dart @@ -0,0 +1,39 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('selfAndNestedActions finds API calls at any depth', () { + final ActionModel load = ConditionalAction( + expression: Expression( + leftPart: VariablePart(valueString: r'${= true}'), + operator: ConditionOperation.isTrue, + rightPart: RawValuePart(''), + ), + actions: [ + ApiCallAction( + apiId: 'curve', + onError: [ShowToastAction(message: 'Offline')], + ), + ForEachAction( + items: r'${= range(0, 6)}', + actions: [ + ApiCallAction( + apiId: 'sun', + onSuccess: [ApiCallAction(apiId: 'nested')], + ), + ], + ), + ], + elseActions: [ApiCallAction(apiId: 'else')], + ); + expect( + load.selfAndNestedActions.whereType().map((a) => a.apiId), + ['curve', 'sun', 'nested', 'else'], + ); + expect(load.selfAndNestedActions.first, same(load)); + }); + + test('a leaf action has no children', () { + expect(ApiCallAction(apiId: 'a').childActions, isEmpty); + }); +} From 038c268f4d3fe542049d6fe85ef30be3ca980a07 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:08:47 +0000 Subject: [PATCH 05/14] Make a pop navigation's destination optional A pop doesn't need a destination; it now defaults to '' so actions written without one parse. For a pop, a destination means where to go when there is nothing to go back to. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- lib/src/api/models/action/navigation_action.dart | 5 +++-- .../api/models/action/navigation_action.g.dart | 2 +- test/navigation_action_json_test.dart | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 test/navigation_action_json_test.dart diff --git a/lib/src/api/models/action/navigation_action.dart b/lib/src/api/models/action/navigation_action.dart index b74cd1ba..90fe29eb 100644 --- a/lib/src/api/models/action/navigation_action.dart +++ b/lib/src/api/models/action/navigation_action.dart @@ -38,7 +38,8 @@ class NavigationAction extends ActionModel with Equatable, SerializableMixin { /// Defines how the navigation should be performed. final NavigationType navigationType; - /// Destination canvas node's ID. + /// Destination canvas node's ID. For a pop it's optional: where to go + /// instead when there is no previous screen to go back to. final String destinationId; /// Parameters to pass to the destination page. @@ -47,7 +48,7 @@ class NavigationAction extends ActionModel with Equatable, SerializableMixin { /// Creates a new [NavigationAction]. NavigationAction({ this.navigationType = NavigationType.push, - required this.destinationId, + this.destinationId = '', Map? params, super.nonBlocking, super.enabled, diff --git a/lib/src/api/models/action/navigation_action.g.dart b/lib/src/api/models/action/navigation_action.g.dart index d486f57f..a8948914 100644 --- a/lib/src/api/models/action/navigation_action.g.dart +++ b/lib/src/api/models/action/navigation_action.g.dart @@ -25,7 +25,7 @@ NavigationAction _$NavigationActionFromJson(Map json) => NavigationAction( navigationType: $enumDecodeNullable(_$NavigationTypeEnumMap, json['navigationType']) ?? NavigationType.push, - destinationId: json['destinationId'] as String, + destinationId: json['destinationId'] as String? ?? '', params: (json['params'] as Map?)?.map((k, e) => MapEntry(k as String, e)), nonBlocking: json['nonBlocking'] as bool? ?? false, enabled: json['enabled'] as bool? ?? true, diff --git a/test/navigation_action_json_test.dart b/test/navigation_action_json_test.dart new file mode 100644 index 00000000..a5c749ae --- /dev/null +++ b/test/navigation_action_json_test.dart @@ -0,0 +1,15 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('a pop needs no destination', () { + final action = NavigationAction.fromJson({ + 'type': 'navigation', + 'navigationType': 'pop', + }); + expect(action.navigationType, NavigationType.pop); + expect(action.destinationId, ''); + // Still written out, for SDKs that require it. + expect(action.toJson()['destinationId'], ''); + }); +} From 6181751038d16d0ea9cba63df0e62ce5bb440e10 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:11:20 +0000 Subject: [PATCH 06/14] Add saturation to image paints; always write a navigation's destination PaintModel.saturation (default 1) scales an image fill's color saturation; 0 makes it greyscale. NavigationAction keeps writing destinationId even when empty, for SDKs that require the key. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- .../api/models/action/navigation_action.dart | 2 ++ .../models/action/navigation_action.g.dart | 1 + lib/src/api/models/paint.dart | 15 +++++++++++ lib/src/api/models/paint.g.dart | 3 +++ test/paint_saturation_test.dart | 25 +++++++++++++++++++ 5 files changed, 46 insertions(+) create mode 100644 test/paint_saturation_test.dart diff --git a/lib/src/api/models/action/navigation_action.dart b/lib/src/api/models/action/navigation_action.dart index 90fe29eb..a72ae684 100644 --- a/lib/src/api/models/action/navigation_action.dart +++ b/lib/src/api/models/action/navigation_action.dart @@ -40,6 +40,8 @@ class NavigationAction extends ActionModel with Equatable, SerializableMixin { /// Destination canvas node's ID. For a pop it's optional: where to go /// instead when there is no previous screen to go back to. + // Always written: SDKs before the '' default require the key. + @JsonLegacyDefault(null) final String destinationId; /// Parameters to pass to the destination page. diff --git a/lib/src/api/models/action/navigation_action.g.dart b/lib/src/api/models/action/navigation_action.g.dart index a8948914..c3be7016 100644 --- a/lib/src/api/models/action/navigation_action.g.dart +++ b/lib/src/api/models/action/navigation_action.g.dart @@ -12,6 +12,7 @@ Map _$NavigationActionOmitDefaults( ) { if (isJsonDefault(instance.navigationType, NavigationType.push)) json.remove('navigationType'); + if (isJsonDefault(instance.destinationId, null)) json.remove('destinationId'); if (isJsonDefault(instance.nonBlocking, false)) json.remove('nonBlocking'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); return json; diff --git a/lib/src/api/models/paint.dart b/lib/src/api/models/paint.dart index 85644755..a3311c15 100644 --- a/lib/src/api/models/paint.dart +++ b/lib/src/api/models/paint.dart @@ -169,6 +169,10 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { /// Tile mode for image fill. final ImageRepeatEnum imageRepeat; + /// Color saturation of an image fill: 1 keeps the image's colors, 0 makes + /// it greyscale, values between fade the colors. + final double saturation; + /// Whether image fill type has source size. bool get hasImageSourceSize => sourceWidth != null && sourceHeight != null; @@ -261,6 +265,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { scaleX = 1, scaleY = 1, imageRepeat = ImageRepeatEnum.noRepeat, + saturation = 1, sourceWidth = null, sourceHeight = null, assetID = null; @@ -282,6 +287,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { this.cropData, this.croppedImageURL, this.imageRepeat = ImageRepeatEnum.noRepeat, + this.saturation = 1, this.imageName, required this.sourceWidth, required this.sourceHeight, @@ -316,6 +322,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { scaleY = 1, cropData = null, imageRepeat = ImageRepeatEnum.noRepeat, + saturation = 1, croppedImageURL = null, sourceWidth = null, sourceHeight = null, @@ -343,6 +350,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { cropData = null, croppedImageURL = null, imageRepeat = ImageRepeatEnum.noRepeat, + saturation = 1, sourceWidth = null, sourceHeight = null, assetID = null; @@ -364,6 +372,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { fit = Fit.none, imageName = null, imageRepeat = ImageRepeatEnum.noRepeat, + saturation = 1, alignment = AlignmentModel.none, scaleX = 1, scaleY = 1, @@ -398,6 +407,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { this.sourceHeight, this.assetID, this.imageRepeat = ImageRepeatEnum.noRepeat, + this.saturation = 1, }); /// Duplicates this instance with given data overrides. @@ -427,6 +437,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { double? sourceWidth, double? sourceHeight, ImageRepeatEnum? imageRepeat, + double? saturation, bool forceSourceSize = false, }) => PaintModel( id: id, @@ -458,6 +469,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { : croppedImageURL ?? this.croppedImageURL, assetID: assetID ?? this.assetID, imageRepeat: imageRepeat ?? this.imageRepeat, + saturation: saturation ?? this.saturation, ); /// [PaintModel] can store all types of paints at the same time to allow @@ -507,6 +519,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { imageHash: imageHash, imageName: imageName, imageRepeat: imageRepeat, + saturation: saturation, imageTransform: imageTransform, assetID: assetID, ); @@ -536,6 +549,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { if (scaleX != other.scaleX) return false; if (scaleY != other.scaleY) return false; if (imageRepeat != other.imageRepeat) return false; + if (saturation != other.saturation) return false; if (assetID != other.assetID) return false; if (sourceWidth != other.sourceWidth) return false; if (sourceHeight != other.sourceHeight) return false; @@ -564,6 +578,7 @@ class PaintModel with Equatable, SerializableMixin, FieldsHolder { scaleX, scaleY, imageRepeat, + saturation, assetID, sourceWidth, sourceHeight, diff --git a/lib/src/api/models/paint.g.dart b/lib/src/api/models/paint.g.dart index e0ee399f..599783df 100644 --- a/lib/src/api/models/paint.g.dart +++ b/lib/src/api/models/paint.g.dart @@ -21,6 +21,7 @@ Map _$PaintModelOmitDefaults( if (isJsonDefault(instance.scaleY, 1)) json.remove('scaleY'); if (isJsonDefault(instance.imageRepeat, ImageRepeatEnum.noRepeat)) json.remove('imageRepeat'); + if (isJsonDefault(instance.saturation, 1)) json.remove('saturation'); return json; } @@ -76,6 +77,7 @@ PaintModel _$PaintModelFromJson(Map json) => PaintModel( imageRepeat: $enumDecodeNullable(_$ImageRepeatEnumEnumMap, json['imageRepeat']) ?? ImageRepeatEnum.noRepeat, + saturation: (json['saturation'] as num?)?.toDouble() ?? 1, ); Map _$PaintModelToJson(PaintModel instance) => @@ -102,6 +104,7 @@ Map _$PaintModelToJson(PaintModel instance) => 'sourceWidth': ?instance.sourceWidth, 'sourceHeight': ?instance.sourceHeight, 'imageRepeat': _$ImageRepeatEnumEnumMap[instance.imageRepeat]!, + 'saturation': instance.saturation, }; const _$PaintTypeEnumMap = { diff --git a/test/paint_saturation_test.dart b/test/paint_saturation_test.dart new file mode 100644 index 00000000..0e30ce4e --- /dev/null +++ b/test/paint_saturation_test.dart @@ -0,0 +1,25 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('an image paint keeps its saturation through JSON', () { + final paint = PaintModel.image( + id: 'plate', + downloadUrl: 'https://example.com/star.png', + fit: Fit.cover, + sourceWidth: 100, + sourceHeight: 100, + saturation: 0, + ); + final json = paint.toJson(); + expect(json['saturation'], 0); + expect(PaintModel.fromJson(json).saturation, 0); + expect(paint.isEquivalentTo(paint.copyWith(saturation: 1)), isFalse); + }); + + test('full saturation is the default and is left out', () { + final paint = PaintModel.fromJson({'id': 'a', 'type': 'solid'}); + expect(paint.saturation, 1); + expect(paint.toJson().containsKey('saturation'), isFalse); + }); +} From 7885c50928026d00916dc57c88a7dd51043b3a0b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:13:41 +0000 Subject: [PATCH 07/14] Add an optional URL path to canvases CanvasProperties.path holds the URL path a screen opens at on a published site, such as journal or guide/:id, where :name segments become route params. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- lib/src/api/nodes/canvas_node.dart | 11 +++++++++++ lib/src/api/nodes/canvas_node.g.dart | 2 ++ test/canvas_path_json_test.dart | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 test/canvas_path_json_test.dart diff --git a/lib/src/api/nodes/canvas_node.dart b/lib/src/api/nodes/canvas_node.dart index 9634c1f0..d2fc9bf9 100644 --- a/lib/src/api/nodes/canvas_node.dart +++ b/lib/src/api/nodes/canvas_node.dart @@ -197,6 +197,15 @@ class CanvasProperties extends CustomProperties { /// to the canvas with this ID instead, such as a sign-in screen. String? signInRedirectId; + /// The URL path this screen opens at on a published site, relative to the + /// site's root, such as `journal` or `guide/:id`. A `:name` segment matches + /// any value and passes it to the screen as the route param `name`. Null or + /// empty means the screen has no URL path of its own. + String? path; + + /// Whether this screen has a URL path. + bool get hasPath => path?.trim().isNotEmpty ?? false; + /// Whether this screen needs a signed-in user. bool get requiresSignIn => signInRedirectId?.isNotEmpty ?? false; @@ -217,6 +226,7 @@ class CanvasProperties extends CustomProperties { this.safeArea = const SafeAreaModel.all(true), this.brightness = BrightnessModel.system, this.signInRedirectId, + this.path, }); /// Checks if a node is symbiotic with its canvas, ie the node acts as the @@ -234,6 +244,7 @@ class CanvasProperties extends CustomProperties { safeArea, brightness, signInRedirectId, + path, ]; /// Creates a new [CanvasProperties] from a JSON data. diff --git a/lib/src/api/nodes/canvas_node.g.dart b/lib/src/api/nodes/canvas_node.g.dart index 70aeb784..b0b3c197 100644 --- a/lib/src/api/nodes/canvas_node.g.dart +++ b/lib/src/api/nodes/canvas_node.g.dart @@ -501,6 +501,7 @@ CanvasProperties _$CanvasPropertiesFromJson(Map json) => CanvasProperties( $enumDecodeNullable(_$BrightnessModelEnumMap, json['brightness']) ?? BrightnessModel.system, signInRedirectId: json['signInRedirectId'] as String?, + path: json['path'] as String?, ); Map _$CanvasPropertiesToJson(CanvasProperties instance) => @@ -512,6 +513,7 @@ Map _$CanvasPropertiesToJson(CanvasProperties instance) => 'safeArea': instance.safeArea.toJson(), 'brightness': _$BrightnessModelEnumMap[instance.brightness]!, 'signInRedirectId': ?instance.signInRedirectId, + 'path': ?instance.path, }; const _$BrightnessModelEnumMap = { diff --git a/test/canvas_path_json_test.dart b/test/canvas_path_json_test.dart new file mode 100644 index 00000000..608ce7b8 --- /dev/null +++ b/test/canvas_path_json_test.dart @@ -0,0 +1,23 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('a canvas path round-trips through JSON', () { + final properties = CanvasProperties.fromJson({ + 'bodyId': 'body', + 'path': 'guide/:id', + }); + expect(properties.path, 'guide/:id'); + expect(properties.hasPath, isTrue); + expect(properties.toJson()['path'], 'guide/:id'); + expect(CanvasProperties.fromJson(properties.toJson()), properties); + }); + + test('a canvas without a path writes none', () { + final properties = CanvasProperties(bodyId: 'body'); + expect(properties.path, isNull); + expect(properties.hasPath, isFalse); + expect(properties.toJson().containsKey('path'), isFalse); + expect(CanvasProperties(bodyId: 'body', path: ' ').hasPath, isFalse); + }); +} From 76933bfe4c8569e26cdd8a0e195aa9f3c6b0df0c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:37:30 +0000 Subject: [PATCH 08/14] Add BaseNode.squeezable for content-sized flex children A node that wraps its content on its Row/Column's main axis can opt in to shrinking below that size when the Row/Column runs out of space, like Flutter's Flexible(fit: FlexFit.loose) around a shrink-wrapped child. It defaults to false and is left out of the JSON when unset. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 4 ++++ lib/src/api/nodes/accordion_node.g.dart | 3 +++ lib/src/api/nodes/app_bar_node.g.dart | 3 +++ lib/src/api/nodes/auto_placeholder_node.g.dart | 3 +++ lib/src/api/nodes/badge_node.g.dart | 3 +++ lib/src/api/nodes/base_node.dart | 13 +++++++++++++ lib/src/api/nodes/button_node.g.dart | 3 +++ lib/src/api/nodes/canvas_node.g.dart | 3 +++ lib/src/api/nodes/checkbox_node.g.dart | 3 +++ lib/src/api/nodes/chip_node.g.dart | 3 +++ lib/src/api/nodes/divider_node.g.dart | 3 +++ lib/src/api/nodes/dropdown_node.g.dart | 3 +++ lib/src/api/nodes/embedded_video_node.g.dart | 3 +++ lib/src/api/nodes/expansion_tile_node.g.dart | 3 +++ lib/src/api/nodes/external_component_node.g.dart | 3 +++ .../api/nodes/floating_action_button_node.g.dart | 3 +++ lib/src/api/nodes/frame_node.g.dart | 3 +++ .../api/nodes/freeform_placeholder_node.g.dart | 3 +++ lib/src/api/nodes/grid_view_node.g.dart | 3 +++ lib/src/api/nodes/icon_node.g.dart | 3 +++ lib/src/api/nodes/line_chart_node.g.dart | 3 +++ lib/src/api/nodes/list_tile_node.g.dart | 3 +++ lib/src/api/nodes/list_view_node.g.dart | 3 +++ lib/src/api/nodes/loading_indicator_node.g.dart | 3 +++ lib/src/api/nodes/navigation_bar_node.g.dart | 3 +++ lib/src/api/nodes/page_view_node.g.dart | 3 +++ lib/src/api/nodes/placeholder_node.g.dart | 3 +++ lib/src/api/nodes/progressbar_node.g.dart | 3 +++ lib/src/api/nodes/radio_node.g.dart | 3 +++ lib/src/api/nodes/rectangle_node.g.dart | 3 +++ lib/src/api/nodes/row_column_node.g.dart | 3 +++ lib/src/api/nodes/segmented_button_node.g.dart | 3 +++ lib/src/api/nodes/single_placeholder_node.g.dart | 3 +++ lib/src/api/nodes/slider_node.g.dart | 3 +++ lib/src/api/nodes/spacer_node.g.dart | 3 +++ lib/src/api/nodes/switch_node.g.dart | 3 +++ lib/src/api/nodes/tab_bar_node.g.dart | 3 +++ lib/src/api/nodes/text_field_node.g.dart | 3 +++ lib/src/api/nodes/text_node.g.dart | 3 +++ lib/src/api/nodes/variance_node.g.dart | 3 +++ lib/src/api/nodes/web_view_node.g.dart | 3 +++ test/json_defaults_test.dart | 15 +++++++++++++++ 42 files changed, 149 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 006e042c..40ef1b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ lines, x ticks, markers and vertical lines are bound through `variables` (see the class docs for their JSON shapes); colors, insets, curve style, text styles and the scrub readout are properties. +- `BaseNode.squeezable` (`"squeezable": true`, default `false` and left out + of the JSON): a node that wraps its content (`SizeFit.shrinkWrap`) on its + Row/Column's main axis may shrink below it when the Row/Column runs out of + space, like `Flexible(fit: FlexFit.loose)` around a shrink-wrapped child. ## 6.1.0 diff --git a/lib/src/api/nodes/accordion_node.g.dart b/lib/src/api/nodes/accordion_node.g.dart index 77f4f620..73e09ebb 100644 --- a/lib/src/api/nodes/accordion_node.g.dart +++ b/lib/src/api/nodes/accordion_node.g.dart @@ -23,6 +23,7 @@ Map _$AccordionNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -164,6 +165,7 @@ AccordionNode _$AccordionNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -200,6 +202,7 @@ Map _$AccordionNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/app_bar_node.g.dart b/lib/src/api/nodes/app_bar_node.g.dart index de0fa8ba..2f6abc03 100644 --- a/lib/src/api/nodes/app_bar_node.g.dart +++ b/lib/src/api/nodes/app_bar_node.g.dart @@ -23,6 +23,7 @@ Map _$AppBarNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -147,6 +148,7 @@ AppBarNode _$AppBarNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -181,6 +183,7 @@ Map _$AppBarNodeToJson(AppBarNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/auto_placeholder_node.g.dart b/lib/src/api/nodes/auto_placeholder_node.g.dart index 29e36b8f..03f6d079 100644 --- a/lib/src/api/nodes/auto_placeholder_node.g.dart +++ b/lib/src/api/nodes/auto_placeholder_node.g.dart @@ -24,6 +24,7 @@ Map _$AutoPlaceholderNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -216,6 +217,7 @@ AutoPlaceholderNode _$AutoPlaceholderNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -256,6 +258,7 @@ Map _$AutoPlaceholderNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/badge_node.g.dart b/lib/src/api/nodes/badge_node.g.dart index 675e8d90..1a748823 100644 --- a/lib/src/api/nodes/badge_node.g.dart +++ b/lib/src/api/nodes/badge_node.g.dart @@ -23,6 +23,7 @@ Map _$BadgeNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -149,6 +150,7 @@ BadgeNode _$BadgeNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -182,6 +184,7 @@ Map _$BadgeNodeToJson(BadgeNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/base_node.dart b/lib/src/api/nodes/base_node.dart index f37c42ec..c2c9defe 100644 --- a/lib/src/api/nodes/base_node.dart +++ b/lib/src/api/nodes/base_node.dart @@ -116,6 +116,19 @@ abstract class BaseNode /// [SizeFit.flexible] in the given [RowColumnNode]. int flex; + /// Whether a node that wraps its content ([SizeFit.shrinkWrap]) on its + /// parent Row/Column's main axis may shrink below that size when the + /// Row/Column runs out of space, like Flutter's + /// `Flexible(fit: FlexFit.loose)` around a shrink-wrapped child: a text + /// that hugs its content and ellipsizes only when the row is full, or a + /// card that is as tall as its content up to the height it has. + /// + /// The node then takes part in sharing the free space by its [flex], so + /// next to an expanded sibling it gets at most its share. Has no effect on + /// other fits ([SizeFit.flexible] is the same for a fixed size). + @JsonKey(defaultValue: false) + bool squeezable = false; + /// Whether the node preserves its aspect ratio while resizing. bool aspectRatioLock; diff --git a/lib/src/api/nodes/button_node.g.dart b/lib/src/api/nodes/button_node.g.dart index 443ae349..861347f3 100644 --- a/lib/src/api/nodes/button_node.g.dart +++ b/lib/src/api/nodes/button_node.g.dart @@ -23,6 +23,7 @@ Map _$ButtonNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -158,6 +159,7 @@ ButtonNode _$ButtonNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -192,6 +194,7 @@ Map _$ButtonNodeToJson(ButtonNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/canvas_node.g.dart b/lib/src/api/nodes/canvas_node.g.dart index b0b3c197..1dc26486 100644 --- a/lib/src/api/nodes/canvas_node.g.dart +++ b/lib/src/api/nodes/canvas_node.g.dart @@ -25,6 +25,7 @@ Map _$CanvasNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -270,6 +271,7 @@ CanvasNode _$CanvasNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -311,6 +313,7 @@ Map _$CanvasNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/checkbox_node.g.dart b/lib/src/api/nodes/checkbox_node.g.dart index 1ba0fa1d..a55218c0 100644 --- a/lib/src/api/nodes/checkbox_node.g.dart +++ b/lib/src/api/nodes/checkbox_node.g.dart @@ -23,6 +23,7 @@ Map _$CheckboxNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -153,6 +154,7 @@ CheckboxNode _$CheckboxNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -187,6 +189,7 @@ Map _$CheckboxNodeToJson(CheckboxNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/chip_node.g.dart b/lib/src/api/nodes/chip_node.g.dart index 4b930f59..11974102 100644 --- a/lib/src/api/nodes/chip_node.g.dart +++ b/lib/src/api/nodes/chip_node.g.dart @@ -23,6 +23,7 @@ Map _$ChipNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -147,6 +148,7 @@ ChipNode _$ChipNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -180,6 +182,7 @@ Map _$ChipNodeToJson(ChipNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/divider_node.g.dart b/lib/src/api/nodes/divider_node.g.dart index aa478db5..0ab1bb70 100644 --- a/lib/src/api/nodes/divider_node.g.dart +++ b/lib/src/api/nodes/divider_node.g.dart @@ -23,6 +23,7 @@ Map _$DividerNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -136,6 +137,7 @@ DividerNode _$DividerNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -170,6 +172,7 @@ Map _$DividerNodeToJson(DividerNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/dropdown_node.g.dart b/lib/src/api/nodes/dropdown_node.g.dart index 8739ea00..a9b8ded1 100644 --- a/lib/src/api/nodes/dropdown_node.g.dart +++ b/lib/src/api/nodes/dropdown_node.g.dart @@ -23,6 +23,7 @@ Map _$DropdownNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -164,6 +165,7 @@ DropdownNode _$DropdownNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -198,6 +200,7 @@ Map _$DropdownNodeToJson(DropdownNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/embedded_video_node.g.dart b/lib/src/api/nodes/embedded_video_node.g.dart index 4b4ef740..680ae534 100644 --- a/lib/src/api/nodes/embedded_video_node.g.dart +++ b/lib/src/api/nodes/embedded_video_node.g.dart @@ -23,6 +23,7 @@ Map _$EmbeddedVideoNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -173,6 +174,7 @@ EmbeddedVideoNode _$EmbeddedVideoNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -207,6 +209,7 @@ Map _$EmbeddedVideoNodeToJson(EmbeddedVideoNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/expansion_tile_node.g.dart b/lib/src/api/nodes/expansion_tile_node.g.dart index 50dde0fe..43df52d3 100644 --- a/lib/src/api/nodes/expansion_tile_node.g.dart +++ b/lib/src/api/nodes/expansion_tile_node.g.dart @@ -23,6 +23,7 @@ Map _$ExpansionTileNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -178,6 +179,7 @@ ExpansionTileNode _$ExpansionTileNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -225,6 +227,7 @@ Map _$ExpansionTileNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/external_component_node.g.dart b/lib/src/api/nodes/external_component_node.g.dart index 7eeceb4a..22f17601 100644 --- a/lib/src/api/nodes/external_component_node.g.dart +++ b/lib/src/api/nodes/external_component_node.g.dart @@ -23,6 +23,7 @@ Map _$ExternalComponentNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -135,6 +136,7 @@ ExternalComponentNode _$ExternalComponentNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -170,6 +172,7 @@ Map _$ExternalComponentNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/floating_action_button_node.g.dart b/lib/src/api/nodes/floating_action_button_node.g.dart index d969096e..719b4486 100644 --- a/lib/src/api/nodes/floating_action_button_node.g.dart +++ b/lib/src/api/nodes/floating_action_button_node.g.dart @@ -23,6 +23,7 @@ Map _$FloatingActionButtonNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -156,6 +157,7 @@ FloatingActionButtonNode _$FloatingActionButtonNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -191,6 +193,7 @@ Map _$FloatingActionButtonNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/frame_node.g.dart b/lib/src/api/nodes/frame_node.g.dart index 48c894de..0fd2f80e 100644 --- a/lib/src/api/nodes/frame_node.g.dart +++ b/lib/src/api/nodes/frame_node.g.dart @@ -23,6 +23,7 @@ Map _$FrameNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -242,6 +243,7 @@ FrameNode _$FrameNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -280,6 +282,7 @@ Map _$FrameNodeToJson(FrameNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/freeform_placeholder_node.g.dart b/lib/src/api/nodes/freeform_placeholder_node.g.dart index 1f9d5453..84f7aac0 100644 --- a/lib/src/api/nodes/freeform_placeholder_node.g.dart +++ b/lib/src/api/nodes/freeform_placeholder_node.g.dart @@ -23,6 +23,7 @@ Map _$FreeformPlaceholderNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -166,6 +167,7 @@ FreeformPlaceholderNode _$FreeformPlaceholderNodeFromJson(Map json) => $enumDecodeNullable(_$StrokeSideEnumMap, json['strokeSide']) ?? StrokeSide.all, ) + ..squeezable = json['squeezable'] as bool? ?? false ..variables = (json['variables'] as Map?)?.map( (k, e) => MapEntry(k as String, e as String), @@ -224,6 +226,7 @@ Map _$FreeformPlaceholderNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/grid_view_node.g.dart b/lib/src/api/nodes/grid_view_node.g.dart index eb9c6485..039b401c 100644 --- a/lib/src/api/nodes/grid_view_node.g.dart +++ b/lib/src/api/nodes/grid_view_node.g.dart @@ -24,6 +24,7 @@ Map _$GridViewNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -227,6 +228,7 @@ GridViewNode _$GridViewNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -282,6 +284,7 @@ Map _$GridViewNodeToJson(GridViewNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/icon_node.g.dart b/lib/src/api/nodes/icon_node.g.dart index 59174871..b221cdf5 100644 --- a/lib/src/api/nodes/icon_node.g.dart +++ b/lib/src/api/nodes/icon_node.g.dart @@ -23,6 +23,7 @@ Map _$IconNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -157,6 +158,7 @@ IconNode _$IconNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -190,6 +192,7 @@ Map _$IconNodeToJson(IconNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/line_chart_node.g.dart b/lib/src/api/nodes/line_chart_node.g.dart index dc50efe6..e5f07d32 100644 --- a/lib/src/api/nodes/line_chart_node.g.dart +++ b/lib/src/api/nodes/line_chart_node.g.dart @@ -23,6 +23,7 @@ Map _$LineChartNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -172,6 +173,7 @@ LineChartNode _$LineChartNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..type = json['type'] as String; @@ -193,6 +195,7 @@ Map _$LineChartNodeToJson(LineChartNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/list_tile_node.g.dart b/lib/src/api/nodes/list_tile_node.g.dart index 326836c6..635b5adf 100644 --- a/lib/src/api/nodes/list_tile_node.g.dart +++ b/lib/src/api/nodes/list_tile_node.g.dart @@ -23,6 +23,7 @@ Map _$ListTileNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -163,6 +164,7 @@ ListTileNode _$ListTileNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -197,6 +199,7 @@ Map _$ListTileNodeToJson(ListTileNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/list_view_node.g.dart b/lib/src/api/nodes/list_view_node.g.dart index 2dce408d..cea83cab 100644 --- a/lib/src/api/nodes/list_view_node.g.dart +++ b/lib/src/api/nodes/list_view_node.g.dart @@ -24,6 +24,7 @@ Map _$ListViewNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -206,6 +207,7 @@ ListViewNode _$ListViewNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -261,6 +263,7 @@ Map _$ListViewNodeToJson(ListViewNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/loading_indicator_node.g.dart b/lib/src/api/nodes/loading_indicator_node.g.dart index 6e5dadc3..6a12d878 100644 --- a/lib/src/api/nodes/loading_indicator_node.g.dart +++ b/lib/src/api/nodes/loading_indicator_node.g.dart @@ -23,6 +23,7 @@ Map _$LoadingIndicatorNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -146,6 +147,7 @@ LoadingIndicatorNode _$LoadingIndicatorNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -181,6 +183,7 @@ Map _$LoadingIndicatorNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/navigation_bar_node.g.dart b/lib/src/api/nodes/navigation_bar_node.g.dart index a441b172..d41d5487 100644 --- a/lib/src/api/nodes/navigation_bar_node.g.dart +++ b/lib/src/api/nodes/navigation_bar_node.g.dart @@ -24,6 +24,7 @@ Map _$NavigationBarNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -171,6 +172,7 @@ NavigationBarNode _$NavigationBarNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -205,6 +207,7 @@ Map _$NavigationBarNodeToJson(NavigationBarNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/page_view_node.g.dart b/lib/src/api/nodes/page_view_node.g.dart index bdc21484..41ac63d4 100644 --- a/lib/src/api/nodes/page_view_node.g.dart +++ b/lib/src/api/nodes/page_view_node.g.dart @@ -24,6 +24,7 @@ Map _$PageViewNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -180,6 +181,7 @@ PageViewNode _$PageViewNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -227,6 +229,7 @@ Map _$PageViewNodeToJson(PageViewNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/placeholder_node.g.dart b/lib/src/api/nodes/placeholder_node.g.dart index da90e37b..7cdbcaa9 100644 --- a/lib/src/api/nodes/placeholder_node.g.dart +++ b/lib/src/api/nodes/placeholder_node.g.dart @@ -23,6 +23,7 @@ Map _$PlaceholderNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -125,6 +126,7 @@ PlaceholderNode _$PlaceholderNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -159,6 +161,7 @@ Map _$PlaceholderNodeToJson(PlaceholderNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/progressbar_node.g.dart b/lib/src/api/nodes/progressbar_node.g.dart index faaf5a17..014e83fb 100644 --- a/lib/src/api/nodes/progressbar_node.g.dart +++ b/lib/src/api/nodes/progressbar_node.g.dart @@ -24,6 +24,7 @@ Map _$ProgressBarNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -144,6 +145,7 @@ ProgressBarNode _$ProgressBarNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -178,6 +180,7 @@ Map _$ProgressBarNodeToJson(ProgressBarNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/radio_node.g.dart b/lib/src/api/nodes/radio_node.g.dart index d1ff5b62..ee1cd355 100644 --- a/lib/src/api/nodes/radio_node.g.dart +++ b/lib/src/api/nodes/radio_node.g.dart @@ -24,6 +24,7 @@ Map _$RadioNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -146,6 +147,7 @@ RadioNode _$RadioNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -179,6 +181,7 @@ Map _$RadioNodeToJson(RadioNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/rectangle_node.g.dart b/lib/src/api/nodes/rectangle_node.g.dart index 3e3fc760..2e739252 100644 --- a/lib/src/api/nodes/rectangle_node.g.dart +++ b/lib/src/api/nodes/rectangle_node.g.dart @@ -23,6 +23,7 @@ Map _$RectangleNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -194,6 +195,7 @@ RectangleNode _$RectangleNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -234,6 +236,7 @@ Map _$RectangleNodeToJson(RectangleNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/row_column_node.g.dart b/lib/src/api/nodes/row_column_node.g.dart index 4385e68c..4cccfc16 100644 --- a/lib/src/api/nodes/row_column_node.g.dart +++ b/lib/src/api/nodes/row_column_node.g.dart @@ -23,6 +23,7 @@ Map _$RowColumnNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -252,6 +253,7 @@ RowColumnNode _$RowColumnNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -292,6 +294,7 @@ Map _$RowColumnNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/segmented_button_node.g.dart b/lib/src/api/nodes/segmented_button_node.g.dart index 7229d2d8..35728140 100644 --- a/lib/src/api/nodes/segmented_button_node.g.dart +++ b/lib/src/api/nodes/segmented_button_node.g.dart @@ -23,6 +23,7 @@ Map _$SegmentedButtonNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -162,6 +163,7 @@ SegmentedButtonNode _$SegmentedButtonNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -197,6 +199,7 @@ Map _$SegmentedButtonNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/single_placeholder_node.g.dart b/lib/src/api/nodes/single_placeholder_node.g.dart index d8ce6f51..8f9101b7 100644 --- a/lib/src/api/nodes/single_placeholder_node.g.dart +++ b/lib/src/api/nodes/single_placeholder_node.g.dart @@ -24,6 +24,7 @@ Map _$SinglePlaceholderNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -140,6 +141,7 @@ SinglePlaceholderNode _$SinglePlaceholderNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -175,6 +177,7 @@ Map _$SinglePlaceholderNodeToJson( 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/slider_node.g.dart b/lib/src/api/nodes/slider_node.g.dart index 93ef55f9..c3c694b5 100644 --- a/lib/src/api/nodes/slider_node.g.dart +++ b/lib/src/api/nodes/slider_node.g.dart @@ -23,6 +23,7 @@ Map _$SliderNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -182,6 +183,7 @@ SliderNode _$SliderNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -216,6 +218,7 @@ Map _$SliderNodeToJson(SliderNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/spacer_node.g.dart b/lib/src/api/nodes/spacer_node.g.dart index 9d7b529c..6e1b6ebe 100644 --- a/lib/src/api/nodes/spacer_node.g.dart +++ b/lib/src/api/nodes/spacer_node.g.dart @@ -25,6 +25,7 @@ Map _$SpacerNodeOmitDefaults( json.remove('interactionStates'); if (isJsonDefault(instance.tooltip, '')) json.remove('tooltip'); if (isJsonDefault(instance.zoomable, false)) json.remove('zoomable'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); if (isJsonDefault(instance.variables, {})) json.remove('variables'); if (isJsonDefault(instance.multipleVariables, {})) @@ -91,6 +92,7 @@ SpacerNode _$SpacerNodeFromJson(Map json) => _$PositioningModeEnumMap, json['positioningMode'], ) + ..squeezable = json['squeezable'] as bool? ?? false ..aspectRatioLock = json['aspectRatioLock'] as bool ..reactions = (json['reactions'] as List) .map((e) => Reaction.fromJson(e as Map)) @@ -129,6 +131,7 @@ Map _$SpacerNodeToJson(SpacerNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/switch_node.g.dart b/lib/src/api/nodes/switch_node.g.dart index 3c34fb4e..ece10e95 100644 --- a/lib/src/api/nodes/switch_node.g.dart +++ b/lib/src/api/nodes/switch_node.g.dart @@ -24,6 +24,7 @@ Map _$SwitchNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -155,6 +156,7 @@ SwitchNode _$SwitchNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -189,6 +191,7 @@ Map _$SwitchNodeToJson(SwitchNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/tab_bar_node.g.dart b/lib/src/api/nodes/tab_bar_node.g.dart index b93405cc..fe07dca0 100644 --- a/lib/src/api/nodes/tab_bar_node.g.dart +++ b/lib/src/api/nodes/tab_bar_node.g.dart @@ -24,6 +24,7 @@ Map _$TabBarNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -190,6 +191,7 @@ TabBarNode _$TabBarNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -232,6 +234,7 @@ Map _$TabBarNodeToJson(TabBarNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/text_field_node.g.dart b/lib/src/api/nodes/text_field_node.g.dart index 1ca7bd8b..648ead42 100644 --- a/lib/src/api/nodes/text_field_node.g.dart +++ b/lib/src/api/nodes/text_field_node.g.dart @@ -24,6 +24,7 @@ Map _$TextFieldNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -179,6 +180,7 @@ TextFieldNode _$TextFieldNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -213,6 +215,7 @@ Map _$TextFieldNodeToJson(TextFieldNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/text_node.g.dart b/lib/src/api/nodes/text_node.g.dart index 01949d07..43a8a22d 100644 --- a/lib/src/api/nodes/text_node.g.dart +++ b/lib/src/api/nodes/text_node.g.dart @@ -30,6 +30,7 @@ Map _$TextNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -201,6 +202,7 @@ TextNode _$TextNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -235,6 +237,7 @@ Map _$TextNodeToJson(TextNode instance) => { 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'textMixedProps': instance.textMixedProps.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/variance_node.g.dart b/lib/src/api/nodes/variance_node.g.dart index be7a515d..d64ed43e 100644 --- a/lib/src/api/nodes/variance_node.g.dart +++ b/lib/src/api/nodes/variance_node.g.dart @@ -26,6 +26,7 @@ Map _$VarianceNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -153,6 +154,7 @@ VarianceNode _$VarianceNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -194,6 +196,7 @@ Map _$VarianceNodeToJson(VarianceNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/lib/src/api/nodes/web_view_node.g.dart b/lib/src/api/nodes/web_view_node.g.dart index 48c3c29e..1954931f 100644 --- a/lib/src/api/nodes/web_view_node.g.dart +++ b/lib/src/api/nodes/web_view_node.g.dart @@ -23,6 +23,7 @@ Map _$WebViewNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.aspectRatioLock, false)) json.remove('aspectRatioLock'); if (isJsonDefault(instance.alignment, AlignmentModel.none)) @@ -136,6 +137,7 @@ WebViewNode _$WebViewNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} + ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -170,6 +172,7 @@ Map _$WebViewNodeToJson(WebViewNode instance) => 'horizontalFit': _$SizeFitEnumMap[instance.horizontalFit]!, 'verticalFit': _$SizeFitEnumMap[instance.verticalFit]!, 'flex': instance.flex, + 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), diff --git a/test/json_defaults_test.dart b/test/json_defaults_test.dart index 367042fd..626add3c 100644 --- a/test/json_defaults_test.dart +++ b/test/json_defaults_test.dart @@ -355,6 +355,21 @@ void main() { expect(TextFieldProperties().toJson(), isNot(contains('autofillHints'))); }); + test('squeezable is written only when set', () { + TextNode node() => TextNode( + id: 't', + name: 'T', + basicBoxLocal: NodeBox(0, 0, 10, 10), + characters: 'a', + horizontalFit: SizeFit.shrinkWrap, + ); + expect(node().toJson(), isNot(contains('squeezable'))); + final json = encodeDecode((node()..squeezable = true).toJson()) as Map; + expect(json['squeezable'], true); + expect(TextNode.fromJson(json).squeezable, isTrue); + expect(TextNode.fromJson(node().toJson()).squeezable, isFalse); + }); + test('non-default values are still written', () { expect( ConditionalAction( From 37e733ab4f943eb489b1c15cbda9b79a78756914 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 03:41:47 +0000 Subject: [PATCH 09/14] Add styling for toasts, confirm dialogs and dropdown menus - ShowToastAction: actionColor, showClose and closeColor, cornerRadius, border, shadows, padding, maxWidth, messageStyle, actionStyle, iconSize and per-screen-size bottomOffsets (bottomOffsetFor). - ConfirmAction: border, shadows, title/message/button text styles, buttonRadius, buttonBorder, buttonShadows, confirmTextColor, cancelColor and cancelTextColor. - DropdownProperties.menuStyle (DropdownMenuStyleModel) for a menu card anchored under the field, and itemImage/itemImageSize/itemImageRadius for a per-item image template such as ${item.image}. - TextStyleModel, a partial text style (family, weight, italic, size, line height, letter spacing, color). All new fields are optional and left out of the JSON when unset, so saved designs keep their look. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 17 ++ lib/src/api/models/action/confirm_action.dart | 102 +++++++++++- .../api/models/action/confirm_action.g.dart | 42 +++++ .../api/models/action/show_toast_action.dart | 108 ++++++++++++- .../models/action/show_toast_action.g.dart | 55 +++++++ lib/src/api/models/dropdown_menu_style.dart | 153 ++++++++++++++++++ lib/src/api/models/dropdown_menu_style.g.dart | 81 ++++++++++ lib/src/api/models/models.dart | 2 + lib/src/api/models/text_style_model.dart | 97 +++++++++++ lib/src/api/models/text_style_model.g.dart | 55 +++++++ lib/src/api/nodes/dropdown_node.dart | 45 ++++++ lib/src/api/nodes/dropdown_node.g.dart | 14 ++ test/confirm_action_test.dart | 54 +++++++ test/dropdown_menu_style_test.dart | 70 ++++++++ test/show_toast_action_test.dart | 67 ++++++++ 15 files changed, 959 insertions(+), 3 deletions(-) create mode 100644 lib/src/api/models/dropdown_menu_style.dart create mode 100644 lib/src/api/models/dropdown_menu_style.g.dart create mode 100644 lib/src/api/models/text_style_model.dart create mode 100644 lib/src/api/models/text_style_model.g.dart create mode 100644 test/dropdown_menu_style_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ef1b17..e5c6e7cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ ## Unreleased +- Styling for the runtime's own UI, all optional and left out of the JSON + when unset, so saved designs keep their look: + - `ShowToastAction`: `actionColor`, `showClose`, `closeColor`, + `cornerRadius`, `border` (`BorderSideModel`), `shadows` (drop shadow + `Effect`s), `padding`, `maxWidth`, `messageStyle`, `actionStyle`, + `iconSize`, and `bottomOffsets` (per `ScreenBreakpoint`, e.g. + `{"mobile": 84}`, see `bottomOffsetFor`). + - `ConfirmAction`: `border`, `shadows`, `titleStyle`, `messageStyle`, + `buttonStyle`, `buttonRadius`, `buttonBorder`, `buttonShadows`, + `confirmTextColor`, `cancelColor`, `cancelTextColor`. + - `DropdownProperties`: `menuStyle` (`DropdownMenuStyleModel`: an anchored + menu card's color, border, radius, shadows, offset, sizes, paddings, + selected and hover row colors, selected text style and check mark) and + `itemImage` (a URL or item template such as `${item.image}`), + `itemImageSize`, `itemImageRadius`. + - `TextStyleModel`: a partial text style (font family, weight, italic, + size, line height, letter spacing, color). - `LineChartNode` (`lineChart`) and `LineChartProperties`: a data-driven line chart. The series, domain, bands, grid lines, area bands, reference lines, x ticks, markers and vertical lines are bound through `variables` diff --git a/lib/src/api/models/action/confirm_action.dart b/lib/src/api/models/action/confirm_action.dart index 7d39e4ae..a40c50a2 100644 --- a/lib/src/api/models/action/confirm_action.dart +++ b/lib/src/api/models/action/confirm_action.dart @@ -11,7 +11,8 @@ part 'confirm_action.g.dart'; /// /// By default the dialog is built by the runtime and takes the app's look: /// its font, primary color and surface come from the layout that shows it. -/// The optional color and radius fields override that look. +/// The optional color, radius, border, shadow and text style fields +/// override that look. /// /// With a [dialogId] the dialog is that canvas instead, like /// [ShowDialogAction]. A button in it answers the dialog with a "Navigate @@ -71,6 +72,44 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin { /// Corner radius of the built-in dialog. Null follows the app's buttons. final double? cornerRadius; + /// Outline of the built-in dialog. Null draws none. + final BorderSideModel? border; + + /// Drop shadows of the built-in dialog, e.g. a hard offset shadow (radius + /// 0). Null keeps the default elevation shadow; an empty list draws none. + final List? shadows; + + /// Font, size and color of the title. Unset values follow the app. + final TextStyleModel? titleStyle; + + /// Font, size and color of the message. Unset values follow the app. + final TextStyleModel? messageStyle; + + /// Font, size and color of both buttons' labels. Unset values follow the + /// app; the label colors come from [confirmTextColor] and + /// [cancelTextColor] when this sets none. + final TextStyleModel? buttonStyle; + + /// Corner radius of the buttons. Null follows the app's buttons. + final double? buttonRadius; + + /// Outline of both buttons. Null draws none. + final BorderSideModel? buttonBorder; + + /// Drop shadows of the confirm button, and of the cancel button when it + /// has a [cancelColor]. Null draws none. + final List? buttonShadows; + + /// Label color of the confirm button. Null picks one that reads on its + /// fill. + final ColorRGBA? confirmTextColor; + + /// Fill of the cancel button. Null leaves it a text button. + final ColorRGBA? cancelColor; + + /// Label color of the cancel button. Null uses the dialog's text color. + final ColorRGBA? cancelTextColor; + /// The key of the "Navigate back" params that answers a [dialogId] dialog. static const String resultKey = 'confirmed'; @@ -107,6 +146,17 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin { this.textColor, this.confirmColor, this.cornerRadius, + this.border, + this.shadows, + this.titleStyle, + this.messageStyle, + this.buttonStyle, + this.buttonRadius, + this.buttonBorder, + this.buttonShadows, + this.confirmTextColor, + this.cancelColor, + this.cancelTextColor, super.nonBlocking, super.enabled, }) : actions = actions ?? [], @@ -137,6 +187,28 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin { bool clearConfirmColor = false, double? cornerRadius, bool clearCornerRadius = false, + BorderSideModel? border, + bool clearBorder = false, + List? shadows, + bool clearShadows = false, + TextStyleModel? titleStyle, + bool clearTitleStyle = false, + TextStyleModel? messageStyle, + bool clearMessageStyle = false, + TextStyleModel? buttonStyle, + bool clearButtonStyle = false, + double? buttonRadius, + bool clearButtonRadius = false, + BorderSideModel? buttonBorder, + bool clearButtonBorder = false, + List? buttonShadows, + bool clearButtonShadows = false, + ColorRGBA? confirmTextColor, + bool clearConfirmTextColor = false, + ColorRGBA? cancelColor, + bool clearCancelColor = false, + ColorRGBA? cancelTextColor, + bool clearCancelTextColor = false, }) => ConfirmAction( title: title ?? this.title, message: message ?? this.message, @@ -155,6 +227,23 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin { textColor: clearTextColor ? null : textColor ?? this.textColor, confirmColor: clearConfirmColor ? null : confirmColor ?? this.confirmColor, cornerRadius: clearCornerRadius ? null : cornerRadius ?? this.cornerRadius, + border: clearBorder ? null : border ?? this.border, + shadows: clearShadows ? null : shadows ?? this.shadows, + titleStyle: clearTitleStyle ? null : titleStyle ?? this.titleStyle, + messageStyle: clearMessageStyle ? null : messageStyle ?? this.messageStyle, + buttonStyle: clearButtonStyle ? null : buttonStyle ?? this.buttonStyle, + buttonRadius: clearButtonRadius ? null : buttonRadius ?? this.buttonRadius, + buttonBorder: clearButtonBorder ? null : buttonBorder ?? this.buttonBorder, + buttonShadows: clearButtonShadows + ? null + : buttonShadows ?? this.buttonShadows, + confirmTextColor: clearConfirmTextColor + ? null + : confirmTextColor ?? this.confirmTextColor, + cancelColor: clearCancelColor ? null : cancelColor ?? this.cancelColor, + cancelTextColor: clearCancelTextColor + ? null + : cancelTextColor ?? this.cancelTextColor, nonBlocking: nonBlocking, enabled: enabled, ); @@ -176,6 +265,17 @@ class ConfirmAction extends ActionModel with Equatable, SerializableMixin { textColor, confirmColor, cornerRadius, + border, + shadows, + titleStyle, + messageStyle, + buttonStyle, + buttonRadius, + buttonBorder, + buttonShadows, + confirmTextColor, + cancelColor, + cancelTextColor, ]; /// Creates an instance from a JSON object. diff --git a/lib/src/api/models/action/confirm_action.g.dart b/lib/src/api/models/action/confirm_action.g.dart index 93096cc1..7fe8b5f8 100644 --- a/lib/src/api/models/action/confirm_action.g.dart +++ b/lib/src/api/models/action/confirm_action.g.dart @@ -56,6 +56,37 @@ ConfirmAction _$ConfirmActionFromJson(Map json) => ConfirmAction( ? null : ColorRGBA.fromJson(json['confirmColor']), cornerRadius: (json['cornerRadius'] as num?)?.toDouble(), + border: json['border'] == null + ? null + : BorderSideModel.fromJson(json['border'] as Map), + shadows: (json['shadows'] as List?) + ?.map((e) => Effect.fromJson(e as Map)) + .toList(), + titleStyle: json['titleStyle'] == null + ? null + : TextStyleModel.fromJson(json['titleStyle'] as Map), + messageStyle: json['messageStyle'] == null + ? null + : TextStyleModel.fromJson(json['messageStyle'] as Map), + buttonStyle: json['buttonStyle'] == null + ? null + : TextStyleModel.fromJson(json['buttonStyle'] as Map), + buttonRadius: (json['buttonRadius'] as num?)?.toDouble(), + buttonBorder: json['buttonBorder'] == null + ? null + : BorderSideModel.fromJson(json['buttonBorder'] as Map), + buttonShadows: (json['buttonShadows'] as List?) + ?.map((e) => Effect.fromJson(e as Map)) + .toList(), + confirmTextColor: json['confirmTextColor'] == null + ? null + : ColorRGBA.fromJson(json['confirmTextColor']), + cancelColor: json['cancelColor'] == null + ? null + : ColorRGBA.fromJson(json['cancelColor']), + cancelTextColor: json['cancelTextColor'] == null + ? null + : ColorRGBA.fromJson(json['cancelTextColor']), nonBlocking: json['nonBlocking'] as bool? ?? false, enabled: json['enabled'] as bool? ?? true, )..type = $enumDecode(_$ActionTypeEnumMap, json['type']); @@ -80,6 +111,17 @@ Map _$ConfirmActionToJson(ConfirmAction instance) => 'textColor': ?instance.textColor?.toJson(), 'confirmColor': ?instance.confirmColor?.toJson(), 'cornerRadius': ?instance.cornerRadius, + 'border': ?instance.border?.toJson(), + 'shadows': ?instance.shadows?.map((e) => e.toJson()).toList(), + 'titleStyle': ?instance.titleStyle?.toJson(), + 'messageStyle': ?instance.messageStyle?.toJson(), + 'buttonStyle': ?instance.buttonStyle?.toJson(), + 'buttonRadius': ?instance.buttonRadius, + 'buttonBorder': ?instance.buttonBorder?.toJson(), + 'buttonShadows': ?instance.buttonShadows?.map((e) => e.toJson()).toList(), + 'confirmTextColor': ?instance.confirmTextColor?.toJson(), + 'cancelColor': ?instance.cancelColor?.toJson(), + 'cancelTextColor': ?instance.cancelTextColor?.toJson(), }; const _$ActionTypeEnumMap = { diff --git a/lib/src/api/models/action/show_toast_action.dart b/lib/src/api/models/action/show_toast_action.dart index a7deaf5a..e8f1974f 100644 --- a/lib/src/api/models/action/show_toast_action.dart +++ b/lib/src/api/models/action/show_toast_action.dart @@ -53,7 +53,9 @@ enum ToastPosition { /// [actions]. /// /// Its colors come from [style] and the app's theme unless [backgroundColor] -/// or [textColor] are set. Its font is the app's. +/// or [textColor] are set. Its font is the app's unless [messageStyle] or +/// [actionStyle] set one. [cornerRadius], [border], [shadows], [padding] and +/// [maxWidth] shape the toast; [showClose] adds a dismiss (×) button. @JsonSerializable() class ShowToastAction extends ActionModel with Equatable, SerializableMixin { /// The message. Supports variables and formulas, e.g. @@ -84,13 +86,54 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin { /// Actions to run, in order, when the toast's button is tapped. final List actions; - /// Optional icon before the message. + /// Optional icon before the message. Its own color wins over the + /// message's; an image icon shows as is, e.g. a stamp. final MultiSourceIconModel? icon; + /// Width and height of [icon]. + final double iconSize; + /// Distance from the bottom of the screen when [position] is /// [ToastPosition.bottom]. final double bottomOffset; + /// [bottomOffset] per screen size, e.g. `{"mobile": 84}` to sit above a + /// mobile tab bar. A screen size without an entry uses [bottomOffset]. + final Map bottomOffsets; + + /// Color of the button's label. Null uses the message's color. + final ColorRGBA? actionColor; + + /// Whether the toast shows a close (×) button that dismisses it. + final bool showClose; + + /// Color of the close button. Null uses the message's color. + final ColorRGBA? closeColor; + + /// Corner radius. Null keeps the default: a pill without a button, and the + /// app's button radius (at least 8) with one. + final double? cornerRadius; + + /// Outline. Null draws none. + final BorderSideModel? border; + + /// Drop shadows, e.g. a hard offset shadow (radius 0). Null keeps the + /// default soft shadow; an empty list draws none. + final List? shadows; + + /// Space around the content. Null keeps the default. + final EdgeInsetsModel? padding; + + /// The widest the toast grows. + final double maxWidth; + + /// Font, size and color of the message. Unset values follow the app. + final TextStyleModel? messageStyle; + + /// Font, size and color of the button's label. Unset values follow the + /// app. + final TextStyleModel? actionStyle; + /// Creates a new [ShowToastAction]. ShowToastAction({ required this.message, @@ -102,15 +145,32 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin { this.actionLabel = '', List? actions, this.icon, + this.iconSize = 18, this.bottomOffset = 104, + Map? bottomOffsets, + this.actionColor, + this.showClose = false, + this.closeColor, + this.cornerRadius, + this.border, + this.shadows, + this.padding, + this.maxWidth = 560, + this.messageStyle, + this.actionStyle, super.nonBlocking, super.enabled, }) : actions = actions ?? [], + bottomOffsets = bottomOffsets ?? {}, super(type: ActionType.showToast); /// Whether the toast shows a button. bool get hasAction => actionLabel.trim().isNotEmpty; + /// The distance from the bottom of a screen [width] wide. + double bottomOffsetFor(double width) => + bottomOffsets[ScreenBreakpoint.forWidth(width)] ?? bottomOffset; + @override List get props => [ message, @@ -122,7 +182,19 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin { actionLabel, actions, icon, + iconSize, bottomOffset, + bottomOffsets, + actionColor, + showClose, + closeColor, + cornerRadius, + border, + shadows, + padding, + maxWidth, + messageStyle, + actionStyle, ]; /// Duplicates this [ShowToastAction] with given data overrides. @@ -139,7 +211,27 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin { List? actions, MultiSourceIconModel? icon, bool clearIcon = false, + double? iconSize, double? bottomOffset, + Map? bottomOffsets, + ColorRGBA? actionColor, + bool clearActionColor = false, + bool? showClose, + ColorRGBA? closeColor, + bool clearCloseColor = false, + double? cornerRadius, + bool clearCornerRadius = false, + BorderSideModel? border, + bool clearBorder = false, + List? shadows, + bool clearShadows = false, + EdgeInsetsModel? padding, + bool clearPadding = false, + double? maxWidth, + TextStyleModel? messageStyle, + bool clearMessageStyle = false, + TextStyleModel? actionStyle, + bool clearActionStyle = false, }) => ShowToastAction( message: message ?? this.message, duration: duration ?? this.duration, @@ -152,7 +244,19 @@ class ShowToastAction extends ActionModel with Equatable, SerializableMixin { actionLabel: actionLabel ?? this.actionLabel, actions: actions ?? this.actions, icon: clearIcon ? null : icon ?? this.icon, + iconSize: iconSize ?? this.iconSize, bottomOffset: bottomOffset ?? this.bottomOffset, + bottomOffsets: bottomOffsets ?? this.bottomOffsets, + actionColor: clearActionColor ? null : actionColor ?? this.actionColor, + showClose: showClose ?? this.showClose, + closeColor: clearCloseColor ? null : closeColor ?? this.closeColor, + cornerRadius: clearCornerRadius ? null : cornerRadius ?? this.cornerRadius, + border: clearBorder ? null : border ?? this.border, + shadows: clearShadows ? null : shadows ?? this.shadows, + padding: clearPadding ? null : padding ?? this.padding, + maxWidth: maxWidth ?? this.maxWidth, + messageStyle: clearMessageStyle ? null : messageStyle ?? this.messageStyle, + actionStyle: clearActionStyle ? null : actionStyle ?? this.actionStyle, nonBlocking: nonBlocking, enabled: enabled, ); diff --git a/lib/src/api/models/action/show_toast_action.g.dart b/lib/src/api/models/action/show_toast_action.g.dart index 684de2c2..d5cb77bf 100644 --- a/lib/src/api/models/action/show_toast_action.g.dart +++ b/lib/src/api/models/action/show_toast_action.g.dart @@ -16,7 +16,11 @@ Map _$ShowToastActionOmitDefaults( json.remove('position'); if (isJsonDefault(instance.actionLabel, '')) json.remove('actionLabel'); if (isJsonDefault(instance.actions, [])) json.remove('actions'); + if (isJsonDefault(instance.iconSize, 18)) json.remove('iconSize'); if (isJsonDefault(instance.bottomOffset, 104)) json.remove('bottomOffset'); + if (isJsonDefault(instance.bottomOffsets, {})) json.remove('bottomOffsets'); + if (isJsonDefault(instance.showClose, false)) json.remove('showClose'); + if (isJsonDefault(instance.maxWidth, 560)) json.remove('maxWidth'); if (isJsonDefault(instance.nonBlocking, false)) json.remove('nonBlocking'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); return json; @@ -56,7 +60,38 @@ ShowToastAction _$ShowToastActionFromJson(Map json) => ShowToastAction( icon: json['icon'] == null ? null : MultiSourceIconModel.fromJson(json['icon'] as Map), + iconSize: (json['iconSize'] as num?)?.toDouble() ?? 18, bottomOffset: (json['bottomOffset'] as num?)?.toDouble() ?? 104, + bottomOffsets: (json['bottomOffsets'] as Map?)?.map( + (k, e) => MapEntry( + $enumDecode(_$ScreenBreakpointEnumMap, k), + (e as num).toDouble(), + ), + ), + actionColor: json['actionColor'] == null + ? null + : ColorRGBA.fromJson(json['actionColor']), + showClose: json['showClose'] as bool? ?? false, + closeColor: json['closeColor'] == null + ? null + : ColorRGBA.fromJson(json['closeColor']), + cornerRadius: (json['cornerRadius'] as num?)?.toDouble(), + border: json['border'] == null + ? null + : BorderSideModel.fromJson(json['border'] as Map), + shadows: (json['shadows'] as List?) + ?.map((e) => Effect.fromJson(e as Map)) + .toList(), + padding: json['padding'] == null + ? null + : EdgeInsetsModel.fromJson(json['padding']), + maxWidth: (json['maxWidth'] as num?)?.toDouble() ?? 560, + messageStyle: json['messageStyle'] == null + ? null + : TextStyleModel.fromJson(json['messageStyle'] as Map), + actionStyle: json['actionStyle'] == null + ? null + : TextStyleModel.fromJson(json['actionStyle'] as Map), nonBlocking: json['nonBlocking'] as bool? ?? false, enabled: json['enabled'] as bool? ?? true, )..type = $enumDecode(_$ActionTypeEnumMap, json['type']); @@ -75,7 +110,21 @@ Map _$ShowToastActionToJson(ShowToastAction instance) => 'actionLabel': instance.actionLabel, 'actions': instance.actions.map((e) => e.toJson()).toList(), 'icon': ?instance.icon?.toJson(), + 'iconSize': instance.iconSize, 'bottomOffset': instance.bottomOffset, + 'bottomOffsets': instance.bottomOffsets.map( + (k, e) => MapEntry(_$ScreenBreakpointEnumMap[k]!, e), + ), + 'actionColor': ?instance.actionColor?.toJson(), + 'showClose': instance.showClose, + 'closeColor': ?instance.closeColor?.toJson(), + 'cornerRadius': ?instance.cornerRadius, + 'border': ?instance.border?.toJson(), + 'shadows': ?instance.shadows?.map((e) => e.toJson()).toList(), + 'padding': ?instance.padding?.toJson(), + 'maxWidth': instance.maxWidth, + 'messageStyle': ?instance.messageStyle?.toJson(), + 'actionStyle': ?instance.actionStyle?.toJson(), }; const _$ToastStyleEnumMap = { @@ -91,6 +140,12 @@ const _$ToastPositionEnumMap = { ToastPosition.top: 'top', }; +const _$ScreenBreakpointEnumMap = { + ScreenBreakpoint.mobile: 'mobile', + ScreenBreakpoint.tablet: 'tablet', + ScreenBreakpoint.desktop: 'desktop', +}; + const _$ActionTypeEnumMap = { ActionType.navigation: 'navigation', ActionType.showDialog: 'showDialog', diff --git a/lib/src/api/models/dropdown_menu_style.dart b/lib/src/api/models/dropdown_menu_style.dart new file mode 100644 index 00000000..fce91d4c --- /dev/null +++ b/lib/src/api/models/dropdown_menu_style.dart @@ -0,0 +1,153 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../mixins.dart'; +import 'models.dart'; + +part 'dropdown_menu_style.g.dart'; + +/// The look of a dropdown's menu when it is drawn as a card anchored under +/// the field instead of the platform's dropdown menu. +/// +/// Unset values follow the dropdown's own properties: its dropdown color, +/// corner radius, hover color and item text style. +@JsonSerializable() +class DropdownMenuStyleModel with Equatable, SerializableMixin { + /// Background of the menu. Null uses the dropdown color. + final ColorRGBA? color; + + /// Outline of the menu. Null draws none. + final BorderSideModel? border; + + /// Corner radius of the menu. Null uses the dropdown's border radius. + final double? radius; + + /// Drop shadows of the menu, e.g. a hard offset shadow (radius 0). Null + /// draws a soft shadow from the dropdown's elevation; an empty list draws + /// none. + final List? shadows; + + /// Gap between the field and the menu. + final double offset; + + /// The tallest the menu grows before it scrolls. + final double maxHeight; + + /// The narrowest the menu is. It is at least as wide as the field. + final double minWidth; + + /// Space inside the menu around its rows. Null is 6 at the top and bottom. + final EdgeInsetsModel? padding; + + /// Space inside each row. Null is 14 at the sides. + final EdgeInsetsModel? itemPadding; + + /// Height of each row. + final double itemHeight; + + /// Background of the selected row. Null leaves it unmarked. + final ColorRGBA? selectedItemColor; + + /// Background of a hovered row. Null uses the dropdown's hover color. + final ColorRGBA? hoverItemColor; + + /// Changes to the item text style for the selected row, e.g. + /// `{"fontWeight": "w600"}`. + final TextStyleModel? selectedTextStyle; + + /// Whether the selected row shows a check mark at its end. + final bool showCheck; + + /// Color of the check mark. Null uses the item text color. + final ColorRGBA? checkColor; + + /// Size of the check mark. + final double checkSize; + + /// Creates a [DropdownMenuStyleModel]. + const DropdownMenuStyleModel({ + this.color, + this.border, + this.radius, + this.shadows, + this.offset = 6, + this.maxHeight = 320, + this.minWidth = 0, + this.padding, + this.itemPadding, + this.itemHeight = 40, + this.selectedItemColor, + this.hoverItemColor, + this.selectedTextStyle, + this.showCheck = false, + this.checkColor, + this.checkSize = 18, + }); + + /// Duplicates this style with the given values replaced. + DropdownMenuStyleModel copyWith({ + ColorRGBA? color, + BorderSideModel? border, + double? radius, + List? shadows, + double? offset, + double? maxHeight, + double? minWidth, + EdgeInsetsModel? padding, + EdgeInsetsModel? itemPadding, + double? itemHeight, + ColorRGBA? selectedItemColor, + ColorRGBA? hoverItemColor, + TextStyleModel? selectedTextStyle, + bool? showCheck, + ColorRGBA? checkColor, + double? checkSize, + }) => DropdownMenuStyleModel( + color: color ?? this.color, + border: border ?? this.border, + radius: radius ?? this.radius, + shadows: shadows ?? this.shadows, + offset: offset ?? this.offset, + maxHeight: maxHeight ?? this.maxHeight, + minWidth: minWidth ?? this.minWidth, + padding: padding ?? this.padding, + itemPadding: itemPadding ?? this.itemPadding, + itemHeight: itemHeight ?? this.itemHeight, + selectedItemColor: selectedItemColor ?? this.selectedItemColor, + hoverItemColor: hoverItemColor ?? this.hoverItemColor, + selectedTextStyle: selectedTextStyle ?? this.selectedTextStyle, + showCheck: showCheck ?? this.showCheck, + checkColor: checkColor ?? this.checkColor, + checkSize: checkSize ?? this.checkSize, + ); + + @override + List get props => [ + color, + border, + radius, + shadows, + offset, + maxHeight, + minWidth, + padding, + itemPadding, + itemHeight, + selectedItemColor, + hoverItemColor, + selectedTextStyle, + showCheck, + checkColor, + checkSize, + ]; + + /// Creates a [DropdownMenuStyleModel] from JSON. + factory DropdownMenuStyleModel.fromJson(Map json) => + _$DropdownMenuStyleModelFromJson(json); + + @override + Map toJson() => _$DropdownMenuStyleModelOmitDefaults( + this, + _$DropdownMenuStyleModelToJson(this), + ); +} diff --git a/lib/src/api/models/dropdown_menu_style.g.dart b/lib/src/api/models/dropdown_menu_style.g.dart new file mode 100644 index 00000000..a44b3c3d --- /dev/null +++ b/lib/src/api/models/dropdown_menu_style.g.dart @@ -0,0 +1,81 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dropdown_menu_style.dart'; + +// ************************************************************************** +// JsonDefaultsGenerator +// ************************************************************************** + +Map _$DropdownMenuStyleModelOmitDefaults( + DropdownMenuStyleModel instance, + Map json, +) { + if (isJsonDefault(instance.offset, 6)) json.remove('offset'); + if (isJsonDefault(instance.maxHeight, 320)) json.remove('maxHeight'); + if (isJsonDefault(instance.minWidth, 0)) json.remove('minWidth'); + if (isJsonDefault(instance.itemHeight, 40)) json.remove('itemHeight'); + if (isJsonDefault(instance.showCheck, false)) json.remove('showCheck'); + if (isJsonDefault(instance.checkSize, 18)) json.remove('checkSize'); + return json; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DropdownMenuStyleModel _$DropdownMenuStyleModelFromJson(Map json) => + DropdownMenuStyleModel( + color: json['color'] == null ? null : ColorRGBA.fromJson(json['color']), + border: json['border'] == null + ? null + : BorderSideModel.fromJson(json['border'] as Map), + radius: (json['radius'] as num?)?.toDouble(), + shadows: (json['shadows'] as List?) + ?.map((e) => Effect.fromJson(e as Map)) + .toList(), + offset: (json['offset'] as num?)?.toDouble() ?? 6, + maxHeight: (json['maxHeight'] as num?)?.toDouble() ?? 320, + minWidth: (json['minWidth'] as num?)?.toDouble() ?? 0, + padding: json['padding'] == null + ? null + : EdgeInsetsModel.fromJson(json['padding']), + itemPadding: json['itemPadding'] == null + ? null + : EdgeInsetsModel.fromJson(json['itemPadding']), + itemHeight: (json['itemHeight'] as num?)?.toDouble() ?? 40, + selectedItemColor: json['selectedItemColor'] == null + ? null + : ColorRGBA.fromJson(json['selectedItemColor']), + hoverItemColor: json['hoverItemColor'] == null + ? null + : ColorRGBA.fromJson(json['hoverItemColor']), + selectedTextStyle: json['selectedTextStyle'] == null + ? null + : TextStyleModel.fromJson(json['selectedTextStyle'] as Map), + showCheck: json['showCheck'] as bool? ?? false, + checkColor: json['checkColor'] == null + ? null + : ColorRGBA.fromJson(json['checkColor']), + checkSize: (json['checkSize'] as num?)?.toDouble() ?? 18, + ); + +Map _$DropdownMenuStyleModelToJson( + DropdownMenuStyleModel instance, +) => { + 'color': ?instance.color?.toJson(), + 'border': ?instance.border?.toJson(), + 'radius': ?instance.radius, + 'shadows': ?instance.shadows?.map((e) => e.toJson()).toList(), + 'offset': instance.offset, + 'maxHeight': instance.maxHeight, + 'minWidth': instance.minWidth, + 'padding': ?instance.padding?.toJson(), + 'itemPadding': ?instance.itemPadding?.toJson(), + 'itemHeight': instance.itemHeight, + 'selectedItemColor': ?instance.selectedItemColor?.toJson(), + 'hoverItemColor': ?instance.hoverItemColor?.toJson(), + 'selectedTextStyle': ?instance.selectedTextStyle?.toJson(), + 'showCheck': instance.showCheck, + 'checkColor': ?instance.checkColor?.toJson(), + 'checkSize': instance.checkSize, +}; diff --git a/lib/src/api/models/models.dart b/lib/src/api/models/models.dart index 3b86dfac..da932d25 100644 --- a/lib/src/api/models/models.dart +++ b/lib/src/api/models/models.dart @@ -37,6 +37,7 @@ export 'color_stop.dart'; export 'collection_schema.dart'; export 'condition.dart'; export 'corner_radius.dart'; +export 'dropdown_menu_style.dart'; export 'edge_insets.dart'; export 'effect.dart'; export 'fab_props.dart'; @@ -72,6 +73,7 @@ export 'text_input_type.dart'; export 'text_input_validator_model.dart'; export 'text_overflow.dart'; export 'theme_tokens.dart'; +export 'text_style_model.dart'; export 'trigger.dart'; export 'twitter.dart'; export 'variables_model.dart'; diff --git a/lib/src/api/models/text_style_model.dart b/lib/src/api/models/text_style_model.dart new file mode 100644 index 00000000..0c0d9969 --- /dev/null +++ b/lib/src/api/models/text_style_model.dart @@ -0,0 +1,97 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +import '../mixins.dart'; +import 'models.dart'; + +part 'text_style_model.g.dart'; + +/// A partial text style for text the runtime draws itself, such as a toast's +/// message or a confirm dialog's title. +/// +/// Each value that is set replaces that part of the look the text would take +/// otherwise (the app's font, its size and color); the rest stays. +/// +/// JSON, e.g. `{"fontFamily": "Work Sans", "fontWeight": "w600", +/// "fontSize": 13}`. +@JsonSerializable() +class TextStyleModel with Equatable, SerializableMixin { + /// Font family, e.g. `Work Sans`. A Google Font loads by its name. + final String? fontFamily; + + /// Font weight. + @JsonKey(unknownEnumValue: FontWeightNumeric.w400) + final FontWeightNumeric? fontWeight; + + /// Whether the text is italic. + final bool? italic; + + /// Font size in logical pixels. + final double? fontSize; + + /// Line height as a multiple of [fontSize], e.g. 1.4. + final double? lineHeight; + + /// Space between letters in logical pixels. + final double? letterSpacing; + + /// Text color. Wins over the color fields of the action it belongs to. + final ColorRGBA? color; + + /// Creates a [TextStyleModel]. + const TextStyleModel({ + this.fontFamily, + this.fontWeight, + this.italic, + this.fontSize, + this.lineHeight, + this.letterSpacing, + this.color, + }); + + /// The font as a [FontName], or null when [fontFamily] is not set. + FontName? get fontName => fontFamily == null || fontFamily!.isEmpty + ? null + : FontName( + family: fontFamily!, + weight: fontWeight ?? FontWeightNumeric.w400, + style: italic == true ? 'Italic' : 'Normal', + ); + + /// Duplicates this style with the given values replaced. + TextStyleModel copyWith({ + String? fontFamily, + FontWeightNumeric? fontWeight, + bool? italic, + double? fontSize, + double? lineHeight, + double? letterSpacing, + ColorRGBA? color, + }) => TextStyleModel( + fontFamily: fontFamily ?? this.fontFamily, + fontWeight: fontWeight ?? this.fontWeight, + italic: italic ?? this.italic, + fontSize: fontSize ?? this.fontSize, + lineHeight: lineHeight ?? this.lineHeight, + letterSpacing: letterSpacing ?? this.letterSpacing, + color: color ?? this.color, + ); + + @override + List get props => [ + fontFamily, + fontWeight, + italic, + fontSize, + lineHeight, + letterSpacing, + color, + ]; + + /// Creates a [TextStyleModel] from JSON. + factory TextStyleModel.fromJson(Map json) => _$TextStyleModelFromJson(json); + + @override + Map toJson() => + _$TextStyleModelOmitDefaults(this, _$TextStyleModelToJson(this)); +} diff --git a/lib/src/api/models/text_style_model.g.dart b/lib/src/api/models/text_style_model.g.dart new file mode 100644 index 00000000..2113ccec --- /dev/null +++ b/lib/src/api/models/text_style_model.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'text_style_model.dart'; + +// ************************************************************************** +// JsonDefaultsGenerator +// ************************************************************************** + +Map _$TextStyleModelOmitDefaults( + TextStyleModel instance, + Map json, +) { + return json; +} + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TextStyleModel _$TextStyleModelFromJson(Map json) => TextStyleModel( + fontFamily: json['fontFamily'] as String?, + fontWeight: $enumDecodeNullable( + _$FontWeightNumericEnumMap, + json['fontWeight'], + unknownValue: FontWeightNumeric.w400, + ), + italic: json['italic'] as bool?, + fontSize: (json['fontSize'] as num?)?.toDouble(), + lineHeight: (json['lineHeight'] as num?)?.toDouble(), + letterSpacing: (json['letterSpacing'] as num?)?.toDouble(), + color: json['color'] == null ? null : ColorRGBA.fromJson(json['color']), +); + +Map _$TextStyleModelToJson(TextStyleModel instance) => + { + 'fontFamily': ?instance.fontFamily, + 'fontWeight': ?_$FontWeightNumericEnumMap[instance.fontWeight], + 'italic': ?instance.italic, + 'fontSize': ?instance.fontSize, + 'lineHeight': ?instance.lineHeight, + 'letterSpacing': ?instance.letterSpacing, + 'color': ?instance.color?.toJson(), + }; + +const _$FontWeightNumericEnumMap = { + FontWeightNumeric.w100: 'w100', + FontWeightNumeric.w200: 'w200', + FontWeightNumeric.w300: 'w300', + FontWeightNumeric.w400: 'w400', + FontWeightNumeric.w500: 'w500', + FontWeightNumeric.w600: 'w600', + FontWeightNumeric.w700: 'w700', + FontWeightNumeric.w800: 'w800', + FontWeightNumeric.w900: 'w900', +}; diff --git a/lib/src/api/nodes/dropdown_node.dart b/lib/src/api/nodes/dropdown_node.dart index 2caef62b..7541d8ae 100644 --- a/lib/src/api/nodes/dropdown_node.dart +++ b/lib/src/api/nodes/dropdown_node.dart @@ -163,6 +163,20 @@ class DropdownProperties extends CustomProperties with FieldsHolder { /// is true. Allows to specify a template for item builder. String itemLabel; + /// The look of the menu. When set, the menu is a card anchored under the + /// field, styled by it, instead of the platform's dropdown menu. + DropdownMenuStyleModel? menuStyle; + + /// Image shown before each item's label: a URL, or a template the item + /// fills like [itemLabel], e.g. `${item.image}`. Empty shows none. + String itemImage; + + /// Width and height of [itemImage]. + double itemImageSize; + + /// Corner radius of [itemImage]. + double itemImageRadius; + /// Creates a [DropdownProperties] instance with the given data. DropdownProperties({ this.enabled = true, @@ -190,6 +204,10 @@ class DropdownProperties extends CustomProperties with FieldsHolder { this.underline = true, this.useDataSource = false, this.itemLabel = '', + this.menuStyle, + this.itemImage = '', + this.itemImageSize = 28, + this.itemImageRadius = 2, }) : itemTextStyle = itemTextStyle ?? TextProp.general( @@ -240,6 +258,11 @@ class DropdownProperties extends CustomProperties with FieldsHolder { bool? expanded, bool? autoFocus, bool? enableFeedback, + DropdownMenuStyleModel? menuStyle, + bool clearMenuStyle = false, + String? itemImage, + double? itemImageSize, + double? itemImageRadius, }) { return DropdownProperties( items: items ?? this.items, @@ -269,6 +292,10 @@ class DropdownProperties extends CustomProperties with FieldsHolder { expanded: expanded ?? this.expanded, autoFocus: autoFocus ?? this.autoFocus, enableFeedback: enableFeedback ?? this.enableFeedback, + menuStyle: clearMenuStyle ? null : menuStyle ?? this.menuStyle, + itemImage: itemImage ?? this.itemImage, + itemImageSize: itemImageSize ?? this.itemImageSize, + itemImageRadius: itemImageRadius ?? this.itemImageRadius, ); } @@ -431,6 +458,20 @@ class DropdownProperties extends CustomProperties with FieldsHolder { (value) => itemLabel = value, requiresLayout: true, ), + 'itemImage': StringFieldAccess( + 'Item Image', + 'Image before each item\'s label: a URL or a template the item fills, e.g. \${item.image}.', + () => itemImage, + (value) => itemImage = value, + requiresLayout: true, + ), + 'itemImageSize': NumFieldAccess( + 'Item Image Size', + 'Width and height of the item image.', + () => itemImageSize, + (value) => itemImageSize = value, + requiresLayout: true, + ), }; @override @@ -460,6 +501,10 @@ class DropdownProperties extends CustomProperties with FieldsHolder { expanded, autoFocus, enableFeedback, + menuStyle, + itemImage, + itemImageSize, + itemImageRadius, ]; /// Creates a [DropdownProperties] instance from a JSON object. diff --git a/lib/src/api/nodes/dropdown_node.g.dart b/lib/src/api/nodes/dropdown_node.g.dart index a9b8ded1..4876b4e6 100644 --- a/lib/src/api/nodes/dropdown_node.g.dart +++ b/lib/src/api/nodes/dropdown_node.g.dart @@ -85,6 +85,10 @@ Map _$DropdownPropertiesOmitDefaults( if (isJsonDefault(instance.useDataSource, false)) json.remove('useDataSource'); if (isJsonDefault(instance.itemLabel, '')) json.remove('itemLabel'); + if (isJsonDefault(instance.itemImage, '')) json.remove('itemImage'); + if (isJsonDefault(instance.itemImageSize, 28)) json.remove('itemImageSize'); + if (isJsonDefault(instance.itemImageRadius, 2)) + json.remove('itemImageRadius'); return json; } @@ -299,6 +303,12 @@ DropdownProperties _$DropdownPropertiesFromJson(Map json) => DropdownProperties( underline: json['underline'] as bool? ?? true, useDataSource: json['useDataSource'] as bool? ?? false, itemLabel: json['itemLabel'] as String? ?? '', + menuStyle: json['menuStyle'] == null + ? null + : DropdownMenuStyleModel.fromJson(json['menuStyle'] as Map), + itemImage: json['itemImage'] as String? ?? '', + itemImageSize: (json['itemImageSize'] as num?)?.toDouble() ?? 28, + itemImageRadius: (json['itemImageRadius'] as num?)?.toDouble() ?? 2, ); Map _$DropdownPropertiesToJson(DropdownProperties instance) => @@ -328,4 +338,8 @@ Map _$DropdownPropertiesToJson(DropdownProperties instance) => 'underline': instance.underline, 'useDataSource': instance.useDataSource, 'itemLabel': instance.itemLabel, + 'menuStyle': ?instance.menuStyle?.toJson(), + 'itemImage': instance.itemImage, + 'itemImageSize': instance.itemImageSize, + 'itemImageRadius': instance.itemImageRadius, }; diff --git a/test/confirm_action_test.dart b/test/confirm_action_test.dart index ca677669..7f7c3806 100644 --- a/test/confirm_action_test.dart +++ b/test/confirm_action_test.dart @@ -70,6 +70,60 @@ void main() { ); }); + test('ConfirmAction round-trips its border, shadows, fonts and buttons', () { + final Map stamp = { + 'id': 'stamp', + 'type': 'dropShadow', + 'radius': 0, + 'spread': 0, + 'color': '#1F2B2E', + 'offset': {'x': 6, 'y': 6}, + }; + final confirm = ActionModel.fromJson({ + 'type': 'confirm', + 'title': 'Delete this sighting?', + 'destructive': true, + 'backgroundColor': '#F3ECDD', + 'cornerRadius': 6, + 'border': {'color': '#1F2B2E', 'width': 1.5}, + 'shadows': [stamp], + 'titleStyle': { + 'fontFamily': 'Fraunces', + 'fontWeight': 'w600', + 'fontSize': 19, + }, + 'messageStyle': {'fontFamily': 'Work Sans', 'color': '#4A5558'}, + 'buttonStyle': {'fontFamily': 'Work Sans', 'fontWeight': 'w600'}, + 'buttonRadius': 4, + 'buttonBorder': {'color': '#1F2B2E', 'width': 1.5}, + 'buttonShadows': [ + { + ...stamp, + 'offset': {'x': 3, 'y': 3}, + }, + ], + 'confirmColor': '#B3392B', + 'confirmTextColor': '#F3ECDD', + 'cancelColor': '#FBF7EE', + 'cancelTextColor': '#1F2B2E', + }) as ConfirmAction; + expect(confirm.border!.width, 1.5); + expect(confirm.shadows!.single.offset, const Vec(6, 6)); + expect(confirm.titleStyle!.fontName!.family, 'Fraunces'); + expect(confirm.messageStyle!.color, ColorRGBA.fromHex('#4A5558')); + expect(confirm.buttonRadius, 4); + expect(confirm.buttonShadows!.single.offset, const Vec(3, 3)); + expect(confirm.cancelColor, ColorRGBA.fromHex('#FBF7EE')); + expect(ActionModel.fromJson(confirm.toJson()), confirm); + expect(confirm.copyWith(clearShadows: true).shadows, isNull); + + // An existing confirm writes none of it. + expect(ConfirmAction(title: 'Leave?').toJson(), { + 'type': 'confirm', + 'title': 'Leave?', + }); + }); + test('ConfirmAction reads a dialog result', () { expect(ConfirmAction.isConfirmResult({'confirmed': true}), isTrue); expect(ConfirmAction.isConfirmResult({'confirmed': 'true'}), isTrue); diff --git a/test/dropdown_menu_style_test.dart b/test/dropdown_menu_style_test.dart new file mode 100644 index 00000000..b4e153c8 --- /dev/null +++ b/test/dropdown_menu_style_test.dart @@ -0,0 +1,70 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + test('A dropdown round-trips its menu style and item image', () { + final DropdownProperties props = DropdownProperties.fromJson({ + 'items': ['Ochre sea star', 'Giant green anemone'], + 'useDataSource': true, + 'itemLabel': r'${item.name}', + 'itemImage': r'${item.image}', + 'itemImageSize': 32, + 'itemImageRadius': 4, + 'menuStyle': { + 'color': '#FBF7EE', + 'border': {'color': '#1F2B2E', 'width': 1.5}, + 'radius': 4, + 'shadows': [ + { + 'id': 'stamp', + 'type': 'dropShadow', + 'radius': 0, + 'spread': 0, + 'color': '#1F2B2E', + 'offset': {'x': 4, 'y': 4}, + }, + ], + 'itemHeight': 44, + 'itemPadding': [14, 0], + 'selectedItemColor': '#D4E7DF', + 'hoverItemColor': '#E9DFCB', + 'selectedTextStyle': {'fontWeight': 'w600'}, + 'showCheck': true, + 'checkColor': '#1F6F78', + 'minWidth': 240, + }, + }); + final DropdownMenuStyleModel style = props.menuStyle!; + expect(style.radius, 4); + expect(style.border!.width, 1.5); + expect(style.shadows!.single.offset, const Vec(4, 4)); + expect(style.itemHeight, 44); + expect(style.itemPadding, const EdgeInsetsModel.symmetric(horizontal: 14)); + expect(style.selectedItemColor, ColorRGBA.fromHex('#D4E7DF')); + expect(style.selectedTextStyle!.fontWeight, FontWeightNumeric.w600); + expect(style.showCheck, isTrue); + expect(style.offset, 6); + expect(style.maxHeight, 320); + expect(props.itemImage, r'${item.image}'); + expect(props.itemImageSize, 32); + + final Map json = props.toJson(); + expect((json['menuStyle'] as Map).containsKey('offset'), isFalse); + final DropdownProperties decoded = DropdownProperties.fromJson(json); + expect(decoded, props); + expect(decoded.menuStyle, style); + + // A dropdown saved before keeps the platform menu and writes none of it. + final DropdownProperties plain = DropdownProperties(items: ['a']); + expect(plain.menuStyle, isNull); + expect(plain.itemImage, isEmpty); + final Map plainJson = plain.toJson(); + expect(plainJson.containsKey('menuStyle'), isFalse); + expect(plainJson.containsKey('itemImage'), isFalse); + expect(plainJson.containsKey('itemImageSize'), isFalse); + expect( + plain.copyWith(menuStyle: style).copyWith(clearMenuStyle: true).menuStyle, + isNull, + ); + }); +} diff --git a/test/show_toast_action_test.dart b/test/show_toast_action_test.dart index 05ef88e0..14ef6c1e 100644 --- a/test/show_toast_action_test.dart +++ b/test/show_toast_action_test.dart @@ -75,4 +75,71 @@ void main() { expect(toast.copyWith(clearBackgroundColor: true).backgroundColor, isNull); }); + + test('ShowToastAction round-trips its look', () { + final Map json = { + 'type': 'showToast', + 'message': r'Spotted ${item.name}', + 'actionLabel': 'View', + 'backgroundColor': '#1F2B2E', + 'textColor': '#F3ECDD', + 'actionColor': '#9CC5B5', + 'showClose': true, + 'closeColor': '#F3ECDD', + 'cornerRadius': 4, + 'border': {'color': '#1F2B2E', 'width': 1.5}, + 'shadows': [ + { + 'id': 'stamp', + 'type': 'dropShadow', + 'radius': 0, + 'spread': 0, + 'color': '#1F2B2E', + 'offset': {'x': 4, 'y': 4}, + }, + ], + 'padding': [16, 0, 6, 0], + 'maxWidth': 480, + 'iconSize': 34, + 'messageStyle': { + 'fontFamily': 'Work Sans', + 'fontWeight': 'w600', + 'fontSize': 13, + 'lineHeight': 1.4, + }, + 'actionStyle': {'fontFamily': 'Work Sans', 'fontWeight': 'w700'}, + 'bottomOffset': 24, + 'bottomOffsets': {'mobile': 84}, + }; + final toast = ActionModel.fromJson(json) as ShowToastAction; + expect(toast.actionColor, ColorRGBA.fromHex('#9CC5B5')); + expect(toast.showClose, isTrue); + expect(toast.cornerRadius, 4); + expect(toast.border!.width, 1.5); + expect(toast.shadows!.single.offset, const Vec(4, 4)); + expect(toast.messageStyle!.fontName!.family, 'Work Sans'); + expect(toast.messageStyle!.fontName!.weight, FontWeightNumeric.w600); + expect(toast.actionStyle!.fontSize, isNull); + expect(toast.maxWidth, 480); + expect(toast.iconSize, 34); + // Per screen size offsets, falling back to bottomOffset. + expect(toast.bottomOffsetFor(390), 84); + expect(toast.bottomOffsetFor(834), 24); + expect(toast.bottomOffsetFor(1440), 24); + + final Map out = toast.toJson(); + expect(out['bottomOffsets'], {'mobile': 84.0}); + expect(out['messageStyle'], { + 'fontFamily': 'Work Sans', + 'fontWeight': 'w600', + 'fontSize': 13.0, + 'lineHeight': 1.4, + }); + expect(ActionModel.fromJson(out), toast); + + // None of it is written for a plain toast. + final Map plain = ShowToastAction(message: 'Saved').toJson(); + expect(plain, {'type': 'showToast', 'message': 'Saved'}); + expect(ShowToastAction(message: 'x').bottomOffsetFor(390), 104); + }); } From f99ddcb8f338633936b49104d956c811bdfcc259 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 16:45:49 +0000 Subject: [PATCH 10/14] Regenerate JSON code after rebasing onto master Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- lib/src/api/nodes/app_bar_node.g.dart | 2 +- .../api/nodes/auto_placeholder_node.g.dart | 2 +- lib/src/api/nodes/badge_node.g.dart | 2 +- lib/src/api/nodes/button_node.g.dart | 2 +- lib/src/api/nodes/checkbox_node.g.dart | 2 +- lib/src/api/nodes/chip_node.g.dart | 2 +- lib/src/api/nodes/divider_node.g.dart | 2 +- lib/src/api/nodes/dropdown_node.g.dart | 2 +- lib/src/api/nodes/embedded_video_node.g.dart | 2 +- lib/src/api/nodes/expansion_tile_node.g.dart | 2 +- .../api/nodes/external_component_node.g.dart | 2 +- .../nodes/floating_action_button_node.g.dart | 2 +- lib/src/api/nodes/frame_node.g.dart | 2 +- .../nodes/freeform_placeholder_node.g.dart | 2 +- lib/src/api/nodes/grid_view_node.g.dart | 2 +- lib/src/api/nodes/icon_node.g.dart | 2 +- lib/src/api/nodes/line_chart_node.g.dart | 32 ++++++++++++++++++- lib/src/api/nodes/list_tile_node.g.dart | 2 +- lib/src/api/nodes/list_view_node.g.dart | 2 +- .../api/nodes/loading_indicator_node.g.dart | 2 +- lib/src/api/nodes/navigation_bar_node.g.dart | 2 +- lib/src/api/nodes/placeholder_node.g.dart | 2 +- lib/src/api/nodes/progressbar_node.g.dart | 2 +- lib/src/api/nodes/radio_node.g.dart | 2 +- lib/src/api/nodes/rectangle_node.g.dart | 16 +++++----- lib/src/api/nodes/row_column_node.g.dart | 2 +- .../api/nodes/segmented_button_node.g.dart | 2 +- .../api/nodes/single_placeholder_node.g.dart | 2 +- lib/src/api/nodes/slider_node.g.dart | 2 +- lib/src/api/nodes/spacer_node.g.dart | 2 +- lib/src/api/nodes/switch_node.g.dart | 2 +- lib/src/api/nodes/tab_bar_node.g.dart | 2 +- lib/src/api/nodes/text_field_node.g.dart | 2 +- lib/src/api/nodes/text_node.g.dart | 4 +-- lib/src/api/nodes/variance_node.g.dart | 2 +- lib/src/api/nodes/web_view_node.g.dart | 2 +- 36 files changed, 74 insertions(+), 44 deletions(-) diff --git a/lib/src/api/nodes/app_bar_node.g.dart b/lib/src/api/nodes/app_bar_node.g.dart index 2f6abc03..0e1a7c49 100644 --- a/lib/src/api/nodes/app_bar_node.g.dart +++ b/lib/src/api/nodes/app_bar_node.g.dart @@ -185,8 +185,8 @@ Map _$AppBarNodeToJson(AppBarNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/auto_placeholder_node.g.dart b/lib/src/api/nodes/auto_placeholder_node.g.dart index 03f6d079..61bf189d 100644 --- a/lib/src/api/nodes/auto_placeholder_node.g.dart +++ b/lib/src/api/nodes/auto_placeholder_node.g.dart @@ -260,8 +260,8 @@ Map _$AutoPlaceholderNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/badge_node.g.dart b/lib/src/api/nodes/badge_node.g.dart index 1a748823..d539fc22 100644 --- a/lib/src/api/nodes/badge_node.g.dart +++ b/lib/src/api/nodes/badge_node.g.dart @@ -186,8 +186,8 @@ Map _$BadgeNodeToJson(BadgeNode instance) => { 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/button_node.g.dart b/lib/src/api/nodes/button_node.g.dart index 861347f3..aee0030b 100644 --- a/lib/src/api/nodes/button_node.g.dart +++ b/lib/src/api/nodes/button_node.g.dart @@ -196,8 +196,8 @@ Map _$ButtonNodeToJson(ButtonNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/checkbox_node.g.dart b/lib/src/api/nodes/checkbox_node.g.dart index a55218c0..46268542 100644 --- a/lib/src/api/nodes/checkbox_node.g.dart +++ b/lib/src/api/nodes/checkbox_node.g.dart @@ -174,6 +174,7 @@ CheckboxNode _$CheckboxNodeFromJson(Map json) => Map _$CheckboxNodeToJson(CheckboxNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -192,7 +193,6 @@ Map _$CheckboxNodeToJson(CheckboxNode instance) => 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/chip_node.g.dart b/lib/src/api/nodes/chip_node.g.dart index 11974102..d2ae1030 100644 --- a/lib/src/api/nodes/chip_node.g.dart +++ b/lib/src/api/nodes/chip_node.g.dart @@ -184,8 +184,8 @@ Map _$ChipNodeToJson(ChipNode instance) => { 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/divider_node.g.dart b/lib/src/api/nodes/divider_node.g.dart index 0ab1bb70..0ab635ec 100644 --- a/lib/src/api/nodes/divider_node.g.dart +++ b/lib/src/api/nodes/divider_node.g.dart @@ -174,8 +174,8 @@ Map _$DividerNodeToJson(DividerNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/dropdown_node.g.dart b/lib/src/api/nodes/dropdown_node.g.dart index 4876b4e6..52b06bed 100644 --- a/lib/src/api/nodes/dropdown_node.g.dart +++ b/lib/src/api/nodes/dropdown_node.g.dart @@ -206,8 +206,8 @@ Map _$DropdownNodeToJson(DropdownNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/embedded_video_node.g.dart b/lib/src/api/nodes/embedded_video_node.g.dart index 680ae534..1455b4e2 100644 --- a/lib/src/api/nodes/embedded_video_node.g.dart +++ b/lib/src/api/nodes/embedded_video_node.g.dart @@ -211,8 +211,8 @@ Map _$EmbeddedVideoNodeToJson(EmbeddedVideoNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/expansion_tile_node.g.dart b/lib/src/api/nodes/expansion_tile_node.g.dart index 43df52d3..98b67f45 100644 --- a/lib/src/api/nodes/expansion_tile_node.g.dart +++ b/lib/src/api/nodes/expansion_tile_node.g.dart @@ -212,6 +212,7 @@ ExpansionTileNode _$ExpansionTileNodeFromJson(Map json) => Map _$ExpansionTileNodeToJson( ExpansionTileNode instance, ) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -230,7 +231,6 @@ Map _$ExpansionTileNodeToJson( 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/external_component_node.g.dart b/lib/src/api/nodes/external_component_node.g.dart index 22f17601..829f77ba 100644 --- a/lib/src/api/nodes/external_component_node.g.dart +++ b/lib/src/api/nodes/external_component_node.g.dart @@ -174,8 +174,8 @@ Map _$ExternalComponentNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/floating_action_button_node.g.dart b/lib/src/api/nodes/floating_action_button_node.g.dart index 719b4486..c2fe9567 100644 --- a/lib/src/api/nodes/floating_action_button_node.g.dart +++ b/lib/src/api/nodes/floating_action_button_node.g.dart @@ -178,6 +178,7 @@ FloatingActionButtonNode _$FloatingActionButtonNodeFromJson(Map json) => Map _$FloatingActionButtonNodeToJson( FloatingActionButtonNode instance, ) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -196,7 +197,6 @@ Map _$FloatingActionButtonNodeToJson( 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/frame_node.g.dart b/lib/src/api/nodes/frame_node.g.dart index 0fd2f80e..e051c10b 100644 --- a/lib/src/api/nodes/frame_node.g.dart +++ b/lib/src/api/nodes/frame_node.g.dart @@ -284,8 +284,8 @@ Map _$FrameNodeToJson(FrameNode instance) => { 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/freeform_placeholder_node.g.dart b/lib/src/api/nodes/freeform_placeholder_node.g.dart index 84f7aac0..4dcee120 100644 --- a/lib/src/api/nodes/freeform_placeholder_node.g.dart +++ b/lib/src/api/nodes/freeform_placeholder_node.g.dart @@ -228,7 +228,6 @@ Map _$FreeformPlaceholderNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, @@ -236,6 +235,7 @@ Map _$FreeformPlaceholderNodeToJson( 'markerType': ?_$ComponentMarkerTypeEnumMap[instance.markerType], 'componentVersion': instance.componentVersion, 'componentSchema': instance.componentSchema, + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/grid_view_node.g.dart b/lib/src/api/nodes/grid_view_node.g.dart index 039b401c..557cb9c7 100644 --- a/lib/src/api/nodes/grid_view_node.g.dart +++ b/lib/src/api/nodes/grid_view_node.g.dart @@ -268,7 +268,6 @@ GridViewNode _$GridViewNodeFromJson(Map json) => Map _$GridViewNodeToJson(GridViewNode instance) => { - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -286,6 +285,7 @@ Map _$GridViewNodeToJson(GridViewNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), diff --git a/lib/src/api/nodes/icon_node.g.dart b/lib/src/api/nodes/icon_node.g.dart index b221cdf5..925c1d25 100644 --- a/lib/src/api/nodes/icon_node.g.dart +++ b/lib/src/api/nodes/icon_node.g.dart @@ -177,6 +177,7 @@ IconNode _$IconNodeFromJson(Map json) => ..type = json['type'] as String; Map _$IconNodeToJson(IconNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -195,7 +196,6 @@ Map _$IconNodeToJson(IconNode instance) => { 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/line_chart_node.g.dart b/lib/src/api/nodes/line_chart_node.g.dart index e5f07d32..14eab24a 100644 --- a/lib/src/api/nodes/line_chart_node.g.dart +++ b/lib/src/api/nodes/line_chart_node.g.dart @@ -33,6 +33,10 @@ Map _$LineChartNodeOmitDefaults( if (isJsonDefault(instance.padding, EdgeInsetsModel.zero)) json.remove('padding'); if (isJsonDefault(instance.rotationDegrees, 0)) json.remove('rotation'); + if (isJsonDefault(instance.interactionStates, {})) + json.remove('interactionStates'); + if (isJsonDefault(instance.tooltip, '')) json.remove('tooltip'); + if (isJsonDefault(instance.zoomable, false)) json.remove('zoomable'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); if (isJsonDefault(instance.variables, {})) json.remove('variables'); if (isJsonDefault(instance.multipleVariables, {})) @@ -176,6 +180,19 @@ LineChartNode _$LineChartNodeFromJson(Map json) => ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() + ..interactionStates = + (json['interactionStates'] as Map?)?.map( + (k, e) => MapEntry( + $enumDecode(_$InteractionStateEnumMap, k), + (e as Map).map( + (k, e) => + MapEntry(k as String, Map.from(e as Map)), + ), + ), + ) ?? + {} + ..tooltip = json['tooltip'] as String? ?? '' + ..zoomable = json['zoomable'] as bool? ?? false ..type = json['type'] as String; Map _$LineChartNodeToJson(LineChartNode instance) => @@ -197,14 +214,19 @@ Map _$LineChartNodeToJson(LineChartNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), 'rotation': instance.rotationDegrees, 'widthFactor': ?instance.widthFactor, 'heightFactor': ?instance.heightFactor, + 'interactionStates': instance.interactionStates.map( + (k, e) => MapEntry(_$InteractionStateEnumMap[k]!, e), + ), + 'tooltip': instance.tooltip, + 'zoomable': instance.zoomable, 'enabled': instance.enabled, 'type': instance.type, 'properties': instance.properties.toJson(), @@ -228,6 +250,14 @@ const _$ComponentMarkerTypeEnumMap = { ComponentMarkerType.instance: 'instance', }; +const _$InteractionStateEnumMap = { + InteractionState.hovered: 'hovered', + InteractionState.focused: 'focused', + InteractionState.pressed: 'pressed', + InteractionState.selected: 'selected', + InteractionState.disabled: 'disabled', +}; + LineChartProperties _$LineChartPropertiesFromJson(Map json) => LineChartProperties( xField: json['xField'] as String? ?? 'x', diff --git a/lib/src/api/nodes/list_tile_node.g.dart b/lib/src/api/nodes/list_tile_node.g.dart index 635b5adf..45b0d204 100644 --- a/lib/src/api/nodes/list_tile_node.g.dart +++ b/lib/src/api/nodes/list_tile_node.g.dart @@ -201,8 +201,8 @@ Map _$ListTileNodeToJson(ListTileNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/list_view_node.g.dart b/lib/src/api/nodes/list_view_node.g.dart index cea83cab..ef95724f 100644 --- a/lib/src/api/nodes/list_view_node.g.dart +++ b/lib/src/api/nodes/list_view_node.g.dart @@ -247,7 +247,6 @@ ListViewNode _$ListViewNodeFromJson(Map json) => Map _$ListViewNodeToJson(ListViewNode instance) => { - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -265,6 +264,7 @@ Map _$ListViewNodeToJson(ListViewNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), diff --git a/lib/src/api/nodes/loading_indicator_node.g.dart b/lib/src/api/nodes/loading_indicator_node.g.dart index 6a12d878..18e09b11 100644 --- a/lib/src/api/nodes/loading_indicator_node.g.dart +++ b/lib/src/api/nodes/loading_indicator_node.g.dart @@ -185,8 +185,8 @@ Map _$LoadingIndicatorNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/navigation_bar_node.g.dart b/lib/src/api/nodes/navigation_bar_node.g.dart index d41d5487..aff33ff4 100644 --- a/lib/src/api/nodes/navigation_bar_node.g.dart +++ b/lib/src/api/nodes/navigation_bar_node.g.dart @@ -192,6 +192,7 @@ NavigationBarNode _$NavigationBarNodeFromJson(Map json) => Map _$NavigationBarNodeToJson(NavigationBarNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -210,7 +211,6 @@ Map _$NavigationBarNodeToJson(NavigationBarNode instance) => 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/placeholder_node.g.dart b/lib/src/api/nodes/placeholder_node.g.dart index 7cdbcaa9..f4bf3b04 100644 --- a/lib/src/api/nodes/placeholder_node.g.dart +++ b/lib/src/api/nodes/placeholder_node.g.dart @@ -163,8 +163,8 @@ Map _$PlaceholderNodeToJson(PlaceholderNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/progressbar_node.g.dart b/lib/src/api/nodes/progressbar_node.g.dart index 014e83fb..485d28e8 100644 --- a/lib/src/api/nodes/progressbar_node.g.dart +++ b/lib/src/api/nodes/progressbar_node.g.dart @@ -182,8 +182,8 @@ Map _$ProgressBarNodeToJson(ProgressBarNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/radio_node.g.dart b/lib/src/api/nodes/radio_node.g.dart index ee1cd355..26b0ad3b 100644 --- a/lib/src/api/nodes/radio_node.g.dart +++ b/lib/src/api/nodes/radio_node.g.dart @@ -166,6 +166,7 @@ RadioNode _$RadioNodeFromJson(Map json) => ..type = json['type'] as String; Map _$RadioNodeToJson(RadioNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -184,7 +185,6 @@ Map _$RadioNodeToJson(RadioNode instance) => { 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/rectangle_node.g.dart b/lib/src/api/nodes/rectangle_node.g.dart index 2e739252..e3fd0870 100644 --- a/lib/src/api/nodes/rectangle_node.g.dart +++ b/lib/src/api/nodes/rectangle_node.g.dart @@ -171,6 +171,7 @@ RectangleNode _$RectangleNodeFromJson(Map json) => : CornerRadius.fromJson(json['cornerRadius']), clipsContent: json['clipsContent'] as bool? ?? true, ) + ..squeezable = json['squeezable'] as bool? ?? false ..variables = (json['variables'] as Map?)?.map( (k, e) => MapEntry(k as String, e as String), @@ -195,7 +196,6 @@ RectangleNode _$RectangleNodeFromJson(Map json) => (k, e) => MapEntry(k as String, e), ) ?? {} - ..squeezable = json['squeezable'] as bool? ?? false ..widthFactor = (json['widthFactor'] as num?)?.toDouble() ..heightFactor = (json['heightFactor'] as num?)?.toDouble() ..interactionStates = @@ -220,13 +220,6 @@ RectangleNode _$RectangleNodeFromJson(Map json) => Map _$RectangleNodeToJson(RectangleNode instance) => { - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), - 'variables': instance.variables, - 'multipleVariables': instance.multipleVariables, - 'componentId': ?instance.componentId, - 'markerType': ?_$ComponentMarkerTypeEnumMap[instance.markerType], - 'componentVersion': instance.componentVersion, - 'componentSchema': instance.componentSchema, 'id': instance.id, 'name': instance.name, 'visible': instance.visible, @@ -238,6 +231,13 @@ Map _$RectangleNodeToJson(RectangleNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'variables': instance.variables, + 'multipleVariables': instance.multipleVariables, + 'componentId': ?instance.componentId, + 'markerType': ?_$ComponentMarkerTypeEnumMap[instance.markerType], + 'componentVersion': instance.componentVersion, + 'componentSchema': instance.componentSchema, 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), diff --git a/lib/src/api/nodes/row_column_node.g.dart b/lib/src/api/nodes/row_column_node.g.dart index 4cccfc16..72ea86a5 100644 --- a/lib/src/api/nodes/row_column_node.g.dart +++ b/lib/src/api/nodes/row_column_node.g.dart @@ -296,8 +296,8 @@ Map _$RowColumnNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/segmented_button_node.g.dart b/lib/src/api/nodes/segmented_button_node.g.dart index 35728140..a9245624 100644 --- a/lib/src/api/nodes/segmented_button_node.g.dart +++ b/lib/src/api/nodes/segmented_button_node.g.dart @@ -201,8 +201,8 @@ Map _$SegmentedButtonNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/single_placeholder_node.g.dart b/lib/src/api/nodes/single_placeholder_node.g.dart index 8f9101b7..d85db6cc 100644 --- a/lib/src/api/nodes/single_placeholder_node.g.dart +++ b/lib/src/api/nodes/single_placeholder_node.g.dart @@ -179,8 +179,8 @@ Map _$SinglePlaceholderNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/slider_node.g.dart b/lib/src/api/nodes/slider_node.g.dart index c3c694b5..d07f35d4 100644 --- a/lib/src/api/nodes/slider_node.g.dart +++ b/lib/src/api/nodes/slider_node.g.dart @@ -220,8 +220,8 @@ Map _$SliderNodeToJson(SliderNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/spacer_node.g.dart b/lib/src/api/nodes/spacer_node.g.dart index 6e1b6ebe..7f96bcbc 100644 --- a/lib/src/api/nodes/spacer_node.g.dart +++ b/lib/src/api/nodes/spacer_node.g.dart @@ -21,11 +21,11 @@ Map _$SpacerNodeOmitDefaults( if (isJsonDefault(instance.verticalFit, SizeFit.fixed)) json.remove('verticalFit'); if (isJsonDefault(instance.flex, 1)) json.remove('flex'); + if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.interactionStates, {})) json.remove('interactionStates'); if (isJsonDefault(instance.tooltip, '')) json.remove('tooltip'); if (isJsonDefault(instance.zoomable, false)) json.remove('zoomable'); - if (isJsonDefault(instance.squeezable, false)) json.remove('squeezable'); if (isJsonDefault(instance.enabled, true)) json.remove('enabled'); if (isJsonDefault(instance.variables, {})) json.remove('variables'); if (isJsonDefault(instance.multipleVariables, {})) diff --git a/lib/src/api/nodes/switch_node.g.dart b/lib/src/api/nodes/switch_node.g.dart index ece10e95..3966cbe9 100644 --- a/lib/src/api/nodes/switch_node.g.dart +++ b/lib/src/api/nodes/switch_node.g.dart @@ -176,6 +176,7 @@ SwitchNode _$SwitchNodeFromJson(Map json) => Map _$SwitchNodeToJson(SwitchNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -194,7 +195,6 @@ Map _$SwitchNodeToJson(SwitchNode instance) => 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/tab_bar_node.g.dart b/lib/src/api/nodes/tab_bar_node.g.dart index fe07dca0..14c4062f 100644 --- a/lib/src/api/nodes/tab_bar_node.g.dart +++ b/lib/src/api/nodes/tab_bar_node.g.dart @@ -219,6 +219,7 @@ TabBarNode _$TabBarNodeFromJson(Map json) => Map _$TabBarNodeToJson(TabBarNode instance) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -237,7 +238,6 @@ Map _$TabBarNodeToJson(TabBarNode instance) => 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, 'alignment': instance.alignment.toJson(), - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/text_field_node.g.dart b/lib/src/api/nodes/text_field_node.g.dart index 648ead42..e509ef5e 100644 --- a/lib/src/api/nodes/text_field_node.g.dart +++ b/lib/src/api/nodes/text_field_node.g.dart @@ -217,8 +217,8 @@ Map _$TextFieldNodeToJson(TextFieldNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/text_node.g.dart b/lib/src/api/nodes/text_node.g.dart index 43a8a22d..912fe15e 100644 --- a/lib/src/api/nodes/text_node.g.dart +++ b/lib/src/api/nodes/text_node.g.dart @@ -222,6 +222,7 @@ TextNode _$TextNodeFromJson(Map json) => Map _$TextNodeToJson(TextNode instance) => { 'characters': instance.characters, + 'textMixedProps': instance.textMixedProps.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -239,8 +240,6 @@ Map _$TextNodeToJson(TextNode instance) => { 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), - 'textMixedProps': instance.textMixedProps.map((e) => e.toJson()).toList(), 'textAlignHorizontal': _$TextAlignHorizontalEnumEnumMap[instance.textAlignHorizontal]!, 'textAlignVertical': @@ -250,6 +249,7 @@ Map _$TextNodeToJson(TextNode instance) => { 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'maxLines': ?instance.maxLines, 'overflow': _$TextOverflowCEnumMap[instance.overflow]!, + 'alignment': instance.alignment.toJson(), 'softWrap': instance.softWrap, 'textScaleFactor': ?instance.textScaleFactor, 'basicBoxLocal': instance.basicBoxLocal.toJson(), diff --git a/lib/src/api/nodes/variance_node.g.dart b/lib/src/api/nodes/variance_node.g.dart index d64ed43e..81e82640 100644 --- a/lib/src/api/nodes/variance_node.g.dart +++ b/lib/src/api/nodes/variance_node.g.dart @@ -198,8 +198,8 @@ Map _$VarianceNodeToJson(VarianceNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), diff --git a/lib/src/api/nodes/web_view_node.g.dart b/lib/src/api/nodes/web_view_node.g.dart index 1954931f..94f7c6ce 100644 --- a/lib/src/api/nodes/web_view_node.g.dart +++ b/lib/src/api/nodes/web_view_node.g.dart @@ -174,8 +174,8 @@ Map _$WebViewNodeToJson(WebViewNode instance) => 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'alignment': instance.alignment.toJson(), 'reactions': instance.reactions.map((e) => e.toJson()).toList(), + 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), 'padding': instance.padding.toJson(), From ac70ef7e27ec58c54e89615663b24633d16a4a3d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 23:28:27 +0000 Subject: [PATCH 11/14] Changelog for the branch's API additions; format and const fixes Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 13 +++++++++++++ test/corner_radius_json_test.dart | 14 ++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5c6e7cd..f5547bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ## Unreleased +- `CornerRadius.fromJson` accepts a bare number (a circular radius on every + corner), like `EdgeInsetsModel` does. +- `ApiCallAction.onSuccess` and `onError`: actions to run after the call + answers with a 2xx status, or after it fails or answers with another. +- `ActionModel.childActions` and `selfAndNestedActions` walk actions nested + in conditionals, for-each loops and API call handlers. +- `NavigationAction.destinationId` defaults to `''`: a pop needs none, and + for a pop it names where to go when there is nothing to go back to. It is + still always written, for SDKs that require it. +- `PaintModel.saturation` (default 1, left out of the JSON when 1): an image + fill's color saturation; 0 is greyscale. +- `CanvasProperties.path`: an optional URL path for a screen, such as + `journal` or `guide/:id`, for links that open it with its parameters. - Styling for the runtime's own UI, all optional and left out of the JSON when unset, so saved designs keep their look: - `ShowToastAction`: `actionColor`, `showClose`, `closeColor`, diff --git a/test/corner_radius_json_test.dart b/test/corner_radius_json_test.dart index 8c67bd84..2a7ed26f 100644 --- a/test/corner_radius_json_test.dart +++ b/test/corner_radius_json_test.dart @@ -5,8 +5,8 @@ void main() { group('CornerRadius.fromJson', () { test('a bare number is a circular radius on every corner', () { final CornerRadius r = CornerRadius.fromJson(4); - expect(r, CornerRadius.all(RadiusModel.circular(4), linked: true)); - expect(CornerRadius.fromJson(2.5).tl, RadiusModel.circular(2.5)); + expect(r, const CornerRadius.all(RadiusModel.circular(4), linked: true)); + expect(CornerRadius.fromJson(2.5).tl, const RadiusModel.circular(2.5)); }); test('a node accepts a numeric cornerRadius', () { @@ -17,12 +17,14 @@ void main() { 'basicBoxLocal': [0, 0, 100, 50], 'cornerRadius': 6, }); - expect(node.cornerRadius.br, RadiusModel.circular(6)); + expect(node.cornerRadius.br, const RadiusModel.circular(6)); }); test('the compact and map forms still decode', () { - expect(CornerRadius.fromJson([8, false, 'circular']).tl, - RadiusModel.circular(8)); + expect( + CornerRadius.fromJson([8, false, 'circular']).tl, + const RadiusModel.circular(8), + ); expect( CornerRadius.fromJson({ 'tl': {'x': 1, 'y': 1}, @@ -30,7 +32,7 @@ void main() { 'bl': {'x': 3, 'y': 3}, 'br': {'x': 4, 'y': 4}, }).br, - RadiusModel.circular(4), + const RadiusModel.circular(4), ); }); }); From 9102cbe5a83496b2fc0388811b1eb10d206ce757 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 23:29:45 +0000 Subject: [PATCH 12/14] codelessly_api 6.2.0 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5547bcf..160d014d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 6.2.0 - `CornerRadius.fromJson` accepts a bare number (a circular radius on every corner), like `EdgeInsetsModel` does. diff --git a/pubspec.yaml b/pubspec.yaml index 93e60aa4..4efa4b73 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: codelessly_api description: Flutter's layouts and widgets represented as models and JSON data. This API is used by the Codelessly Editor and CloudUI SDK to render Flutter Widgets. -version: 6.1.0 +version: 6.2.0 homepage: https://codelessly.com/ repository: https://github.com/Codelessly/CodelesslyAPI From c2e79c48ce90c954963c3e35d9d17bbfc7c1f101 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 23:54:42 +0000 Subject: [PATCH 13/14] Add a wrap layout to RowColumnNode RowColumnNode.wrap lets a row or column flow its children onto a new run when they don't fit, like Flutter's Wrap, with runSpacing between runs and runAlignment placing them. All three default to today's behaviour and are left out of the JSON. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 4 ++ lib/src/api/nodes/row_column_node.dart | 52 +++++++++++++++++++++ lib/src/api/nodes/row_column_node.g.dart | 17 ++++++- test/row_column_wrap_json_test.dart | 58 ++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 test/row_column_wrap_json_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 160d014d..1c51115c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,10 @@ of the JSON): a node that wraps its content (`SizeFit.shrinkWrap`) on its Row/Column's main axis may shrink below it when the Row/Column runs out of space, like `Flexible(fit: FlexFit.loose)` around a shrink-wrapped child. +- `RowColumnNode.wrap` (`"wrap": true`), `runSpacing` and `runAlignment` + (`MainAxisAlignmentC`): a row or column whose children flow onto a new run + when they don't fit, like Flutter's `Wrap`. Defaults (`false`, `0`, + `start`) are left out of the JSON. ## 6.1.0 diff --git a/lib/src/api/nodes/row_column_node.dart b/lib/src/api/nodes/row_column_node.dart index 2094d2f7..772e8bd6 100644 --- a/lib/src/api/nodes/row_column_node.dart +++ b/lib/src/api/nodes/row_column_node.dart @@ -1,5 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; +import '../field_access.dart'; import '../mixins.dart'; import '../models/models.dart'; @@ -24,6 +25,23 @@ class RowColumnNode extends ParentNode @override set scrollDirection(AxisC value) {} + /// Whether the children flow onto a new run (a line for a row, a column of + /// items for a column) when they don't fit on the main axis, like Flutter's + /// [Wrap]. Items keep their own size: a child that flexes on the main axis + /// can't share free space in a run, so it takes its design size there. + /// [mainAxisAlignment] places the items within each run and + /// [crossAxisAlignment] places them within their run's thickness (stretch + /// and baseline aren't available in a wrap and align to the start). + /// The space between items is the children's margins. + bool wrap; + + /// The space between runs on the cross axis when [wrap] is true. + double runSpacing; + + /// How the runs are placed on the cross axis when [wrap] is true and the + /// node is thicker than its runs. + MainAxisAlignmentC runAlignment; + /// Creates a [RowColumnNode] with the given data. RowColumnNode({ required super.id, @@ -76,6 +94,9 @@ class RowColumnNode extends ParentNode ScrollViewKeyboardDismissBehaviorC.manual, bool useFlutterListView = false, bool shouldAlwaysScroll = true, + this.wrap = false, + this.runSpacing = 0, + this.runAlignment = MainAxisAlignmentC.start, }) { setCornerMixin( cornerRadius: cornerRadius, @@ -107,4 +128,35 @@ class RowColumnNode extends ParentNode @override Map toJson() => _$RowColumnNodeOmitDefaults(this, _$RowColumnNodeToJson(this)); + + @override + FieldsMap generateFields() => { + ...super.generateFields(), + 'wrap': BoolFieldAccess( + 'Wrap', + 'Whether the children flow onto a new run when they don\'t fit.', + () => wrap, + (value) => wrap = value, + defaultValue: () => false, + requiresLayout: true, + ), + 'runSpacing': NumFieldAccess( + 'Run Spacing', + 'The space between runs when the children wrap.', + () => runSpacing, + (value) => runSpacing = value, + min: 0, + defaultValue: () => 0, + requiresLayout: true, + ), + 'runAlignment': EnumFieldAccess( + 'Run Alignment', + 'How the runs are placed on the cross axis when the children wrap.', + () => runAlignment, + (value) => runAlignment = value, + defaultValue: () => MainAxisAlignmentC.start, + options: () => MainAxisAlignmentC.values, + requiresLayout: true, + ), + }; } diff --git a/lib/src/api/nodes/row_column_node.g.dart b/lib/src/api/nodes/row_column_node.g.dart index 72ea86a5..d56cea82 100644 --- a/lib/src/api/nodes/row_column_node.g.dart +++ b/lib/src/api/nodes/row_column_node.g.dart @@ -10,6 +10,10 @@ Map _$RowColumnNodeOmitDefaults( RowColumnNode instance, Map json, ) { + if (isJsonDefault(instance.wrap, false)) json.remove('wrap'); + if (isJsonDefault(instance.runSpacing, 0)) json.remove('runSpacing'); + if (isJsonDefault(instance.runAlignment, MainAxisAlignmentC.start)) + json.remove('runAlignment'); if (isJsonDefault(instance.visible, true)) json.remove('visible'); if (excludeConstraintsIf(instance)) json.remove('constraints'); if (isJsonDefault(instance.constraints, const BoxConstraintsModel())) @@ -228,6 +232,14 @@ RowColumnNode _$RowColumnNodeFromJson(Map json) => ScrollViewKeyboardDismissBehaviorC.manual, useFlutterListView: json['useFlutterListView'] as bool? ?? false, shouldAlwaysScroll: json['shouldAlwaysScroll'] as bool? ?? true, + wrap: json['wrap'] as bool? ?? false, + runSpacing: (json['runSpacing'] as num?)?.toDouble() ?? 0, + runAlignment: + $enumDecodeNullable( + _$MainAxisAlignmentCEnumMap, + json['runAlignment'], + ) ?? + MainAxisAlignmentC.start, ) ..variables = (json['variables'] as Map?)?.map( @@ -279,6 +291,7 @@ RowColumnNode _$RowColumnNodeFromJson(Map json) => Map _$RowColumnNodeToJson( RowColumnNode instance, ) => { + 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'variables': instance.variables, 'multipleVariables': instance.multipleVariables, 'componentId': ?instance.componentId, @@ -296,7 +309,6 @@ Map _$RowColumnNodeToJson( 'flex': instance.flex, 'squeezable': instance.squeezable, 'aspectRatioLock': instance.aspectRatioLock, - 'reactions': instance.reactions.map((e) => e.toJson()).toList(), 'alignment': instance.alignment.toJson(), 'basicBoxLocal': instance.basicBoxLocal.toJson(), 'margin': instance.margin.toJson(), @@ -341,6 +353,9 @@ Map _$RowColumnNodeToJson( 'useFlutterListView': instance.useFlutterListView, 'shouldAlwaysScroll': instance.shouldAlwaysScroll, 'type': instance.type, + 'wrap': instance.wrap, + 'runSpacing': instance.runSpacing, + 'runAlignment': _$MainAxisAlignmentCEnumMap[instance.runAlignment]!, }; const _$RowColumnTypeEnumMap = { diff --git a/test/row_column_wrap_json_test.dart b/test/row_column_wrap_json_test.dart new file mode 100644 index 00000000..e3b6ef28 --- /dev/null +++ b/test/row_column_wrap_json_test.dart @@ -0,0 +1,58 @@ +import 'package:codelessly_api/codelessly_api.dart'; +import 'package:test/test.dart'; + +void main() { + RowColumnNode row({ + bool wrap = false, + double runSpacing = 0, + MainAxisAlignmentC runAlignment = MainAxisAlignmentC.start, + }) => RowColumnNode( + id: 'row', + name: 'Row', + basicBoxLocal: NodeBox(0, 0, 200, 40), + children: [], + rowColumnType: RowColumnType.row, + wrap: wrap, + runSpacing: runSpacing, + runAlignment: runAlignment, + ); + + test('a wrapping row round-trips through JSON', () { + final json = row( + wrap: true, + runSpacing: 8, + runAlignment: MainAxisAlignmentC.center, + ).toJson(); + expect(json['wrap'], isTrue); + expect(json['runSpacing'], 8); + expect(json['runAlignment'], 'center'); + + final node = RowColumnNode.fromJson(json); + expect(node.wrap, isTrue); + expect(node.runSpacing, 8); + expect(node.runAlignment, MainAxisAlignmentC.center); + }); + + test('a row that does not wrap writes none of the wrap keys', () { + final json = row().toJson(); + expect(json.containsKey('wrap'), isFalse); + expect(json.containsKey('runSpacing'), isFalse); + expect(json.containsKey('runAlignment'), isFalse); + + final node = RowColumnNode.fromJson(json); + expect(node.wrap, isFalse); + expect(node.runSpacing, 0); + expect(node.runAlignment, MainAxisAlignmentC.start); + }); + + test('a patch with only wrap and runSpacing reads', () { + final node = RowColumnNode.fromJson({ + ...row().toJson(), + 'wrap': true, + 'runSpacing': 8, + }); + expect(node.wrap, isTrue); + expect(node.runSpacing, 8.0); + expect(node.runAlignment, MainAxisAlignmentC.start); + }); +} From 32ade465d0b20fc2de9ec90e494d2056362c1c04 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 01:04:27 +0000 Subject: [PATCH 14/14] Leave the version to the next release; changes stay under Unreleased Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01F3B1XCZZ7QGvZLUGgSG5H4 --- CHANGELOG.md | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c51115c..357a96ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 6.2.0 +## Unreleased - `CornerRadius.fromJson` accepts a bare number (a circular radius on every corner), like `EdgeInsetsModel` does. diff --git a/pubspec.yaml b/pubspec.yaml index 4efa4b73..93e60aa4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: codelessly_api description: Flutter's layouts and widgets represented as models and JSON data. This API is used by the Codelessly Editor and CloudUI SDK to render Flutter Widgets. -version: 6.2.0 +version: 6.1.0 homepage: https://codelessly.com/ repository: https://github.com/Codelessly/CodelesslyAPI