Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7f9a6cc
Bind a text node's color with a textColor variable or formula
claude Oct 22, 2026
64f0fd4
Formula series/text helpers, API results in ${result}, injectable client
claude Oct 22, 2026
347dc4b
Give baseline-aligned rows a text baseline
claude Oct 22, 2026
25f044f
Send an API's query parameters
claude Oct 22, 2026
32969bf
Run an API call's onSuccess/onError actions
claude Oct 22, 2026
35f4b91
Render LineChartNode with a CustomPainter and scrub cursor
claude Oct 22, 2026
e5545bc
Support bound semanticLabel and semanticSelected on any node
claude Oct 22, 2026
71bfb8d
Run load-time API calls in order with the canvas's other load actions
claude Oct 23, 2026
40a76ec
Pop to a destination when there is nothing to go back to
claude Oct 23, 2026
507f019
Desaturate image fills by their paint's saturation
claude Oct 23, 2026
4495e24
Keep a widget's own semantics under a custom semanticLabel
claude Oct 23, 2026
8d35fac
Open an app at a URL location and report navigation locations
claude Oct 23, 2026
f36f202
Test that a 7-column grid's pinned cells fill their columns
claude Oct 23, 2026
e820425
Let dialogs follow the screen and add loose flexible sizing
claude Oct 23, 2026
5f03191
Style toasts, confirm dialogs and dropdown menus
claude Oct 23, 2026
15f2f91
Let hosts pin the clock formulas read
claude Oct 23, 2026
ae5df44
Let a line chart's axis title and readout units be bound
claude Oct 23, 2026
768fc2b
Fold canvas URL paths into CodelesslyRoute
claude Oct 23, 2026
d8740a4
A labelled button reads only its label and keeps its tap
claude Oct 23, 2026
819278d
Use the semantics controller to tap in the semantics test
claude Oct 23, 2026
0519688
Don't cap an expanded dropdown's value at its design width
claude Oct 23, 2026
af07334
Keep a toast's semantics stable so its button can be tapped on the web
claude Oct 23, 2026
7bb277a
Finish an API call's handlers after the widget that made it unmounts
claude Oct 23, 2026
2b1317f
Changelog for the branch's SDK changes; format
claude Oct 23, 2026
9b7700c
codelessly_sdk 8.3.0
claude Oct 23, 2026
68f723b
Lay out a wrapping Row/Column as a Wrap
claude Oct 23, 2026
60af37d
Leave the version to the next release; changes stay under Unreleased
claude Oct 24, 2026
3e66281
Merge master: keep master's wrap, toast and confirm features alongsid…
claude Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,65 @@ side sheet, confirm presentation, toast styling, `contentFit` and
item, built as a list of rows of `Expanded` cells in an `IntrinsicHeight`
(`GridViewBuilder.intrinsicRows`).

### Runtime UI, semantics, links and charts

- A text's color can be bound with a `textColor` variable or formula.
- Formulas: `range(start, end, step)` includes its end (`range(3, 7)` is
`[3, 4, 5, 6, 7]`; `range(n)` is still n items from 0) and steps through
dates in minutes; `interpolate`, `runs`, `minBy`, `maxBy`, `wallTime`, and
`now(offset)` / `today(offset)` at a UTC offset. `FormulaClock.pinned` lets a host pin the
clock they read (previews, reproducible screenshots).
- API calls: the response (or the error) is the chain's `${result}`; query
parameters are sent; `onSuccess`/`onError` actions run, and with
`onError` a failure no longer stops the chain; the handlers still run when
the widget that made the call has unmounted (a dialog closed mid-load).
`Codelessly(client:)` takes an HTTP client.
- Load-time API calls run in order with the canvas's other load actions.
- A row aligned to text baselines gets a text baseline (it asserted).
- Any node can bind `semanticLabel` and `semanticSelected` for screen
readers. A labelled button reads only its label and keeps its tap; a
labelled field or dropdown keeps its own semantics.
- A pop with a destination goes there when there is nothing to pop.
- Image fills honour `PaintModel.saturation` (greyscale at 0).
- Links: a `CodelesslyRoute` path is matched against the screens' URL paths
(`CanvasProperties.path`, e.g. `guide/:id`) before it is read as a screen
id, and `Codelessly.pathOf` gives the address for a route
(`CanvasLocation` does the matching).
- A line chart's axis title and readout units can be bound.
- An expanded dropdown no longer cuts its value at its design width.
- A toast's semantics are stable from its first frame, so its button can be
tapped on the web.
- Toasts, the built-in confirm dialog and dropdown menus take the new
`ShowToastAction`, `ConfirmAction` and `DropdownProperties.menuStyle`
styling: toast action color, close button, radius, border, hard shadows,
fonts, icon size and per-screen-size bottom offsets; confirm dialog border,
shadows, fonts and outlined, shadowed buttons; and a dropdown menu drawn as
a card anchored under the field with a border, shadows, a highlighted
selected row with a check mark and per-item images (`itemImage`).
- `FeedbackTheme` has `border`, `shadows`, `buttonBorder` and
`buttonShadows`; `FeedbackTheme.fromNodes` reads the outline and shadow
most of a layout's tappable shapes share for the confirm dialog's buttons.
- A data-source dropdown finds its selected item by content, so a map value
selects the equal item.
- Render `LineChartNode` with `PassiveLineChartTransformer` and
`PassiveLineChartWidget`: bands, grid, gradient area, area bands, a
polyline or Catmull-Rom curve, reference lines, x axis, markers, vertical
lines and a hover/tap/drag/arrow-key scrub cursor with a readout card.
`LineChartGeometry` holds the pure parts (dates, domain, interpolation,
Catmull-Rom control points, readout text and placement).
- `FormulaEvaluator.formatDate` exposes the `formatDate` formula function.
- A dialog whose canvas is responsive and holds a responsive layout (a
`VarianceNode` with breakpoints) fills the screen instead of keeping its
design size, so its variant follows the screen width and each variant
places itself (a full-screen sheet on mobile, a centered card on desktop).
Dialogs with plain fixed-size canvases are unchanged.
- In a dialog (`TapThroughScope`), taps on parts of the layout where nothing
paints or reacts (a canvas without a background, an empty frame) reach the
barrier, so a dismissible full-screen dialog closes on them.
- A `squeezable` node that wraps its content on a Row/Column's main axis is
laid out as `Flexible(fit: FlexFit.loose)`: it hugs its content and
shrinks to the room left when the Row/Column runs out of space.

## 8.3.0

Requires codelessly_api 6.2.0.
Expand Down
1 change: 1 addition & 0 deletions lib/codelessly_sdk.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export 'src/transformers/transformers.dart';
export 'src/transformers/utils/blend_mask.dart';
export 'src/transformers/utils/cloud_list_items.dart';
export 'src/transformers/utils/query_status.dart';
export 'src/ui/canvas_location.dart';
export 'src/ui/codelessly_context.dart';
export 'src/ui/codelessly_route.dart';
export 'src/ui/codelessly_widget.dart';
Expand Down
59 changes: 48 additions & 11 deletions lib/src/codelessly.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Codelessly {
/// Initialization is needed before usage.
static Codelessly get instance => _instance;

final Client _client = CodelesslyHttpClient();
final Client _client;

/// Returns the HTTP client used by this SDK instance.
Client get client => _client;
Expand Down Expand Up @@ -262,8 +262,7 @@ class Codelessly {
final Map<String, NavigationListener> _navigationListeners = {};

/// The screen shown and its parameters, for the browser's address: a host
/// listens and writes [CodelesslyRoute.toPath] to the URL. Dialogs don't
/// change it.
/// listens and writes [pathOf] it to the URL. Dialogs don't change it.
final ValueNotifier<CodelesslyRoute?> route = ValueNotifier(null);

/// A screen to open once the app's first screen shows, e.g. the one a
Expand All @@ -285,7 +284,31 @@ class Codelessly {
/// the host's own route, which carries no parameters, so a link to it
/// would otherwise lose them. Null when the link names another screen.
Map<String, String>? linkParamsFor(String? layoutId) {
return _maybeDataManager?.publishModel?.linkParamsFor(_link, layoutId);
return _maybeDataManager?.publishModel?.linkParamsFor(
_resolveLink(_link),
layoutId,
);
}

/// [link] with its URL path, if it has one, matched against the screens'
/// URL paths: the screen it names and the parameters the path and query
/// give. A path no screen's URL path matches is read as a screen id.
CodelesslyRoute? _resolveLink(CodelesslyRoute? link) {
final String? path = link?.path;
final SDKPublishModel? model = _maybeDataManager?.publishModel;
if (link == null || path == null || model == null) return link;
final CanvasLocationMatch? match = CanvasLocation.match(
model,
Uri(
path: path,
queryParameters: link.params.isEmpty ? null : link.params,
).toString(),
);
if (match == null) return link;
return CodelesslyRoute(match.canvasId, {
for (final MapEntry<String, dynamic> param in match.params.entries)
param.key: '${param.value}',
});
}

final Map<String, BreakpointsListener> _breakpointsListeners = {};
Expand All @@ -312,7 +335,10 @@ class Codelessly {
DataManager? publishDataManager,
DataManager? previewDataManager,
DataManager? templateDataManager,
}) {

// The HTTP client for API calls and data loading (tests pass a mock).
Client? client,
}) : _client = client ?? CodelesslyHttpClient() {
_config = config;
_cacheManager = cacheManager;
_authManager = authManager;
Expand Down Expand Up @@ -925,20 +951,31 @@ class Codelessly {
}
}

/// The link for [route]: the screen's URL path with its parameters filled
/// in when it has one (`guide/bat-star` for a screen at `guide/:id`; the
/// app's first screen without a path is the empty path), else
/// [CodelesslyRoute.toPath].
String pathOf(CodelesslyRoute route) {
final SDKPublishModel? model = dataManager.publishModel;
return (model == null
? null
: CanvasLocation.locationOf(model, route.canvasId, route.params)) ??
route.toPath();
}

/// Opens [pendingRoute] over the first screen shown, unless it is that
/// screen.
/// screen. A link's path is matched against the screens' URL paths first,
/// then read as a screen id.
void _openPendingRoute(BuildContext context, String canvasId) {
final CodelesslyRoute? pending = _pendingRoute;
if (pending == null) return;
_pendingRoute = null;
if (pending.canvasId == canvasId) return;
final CodelesslyRoute? link = _resolveLink(pending);
if (link == null || link.canvasId == canvasId) return;
unawaited(
FunctionsRepository.navigate(
context,
NavigationAction(
destinationId: pending.canvasId,
params: pending.params,
),
NavigationAction(destinationId: link.canvasId, params: link.params),
codelessly: this,
),
);
Expand Down
Loading
Loading