The Garage SDKs, in the open
garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// A label handed down to whatever control sits in a labelled row.
|
||||
//
|
||||
// Most controls in this app are not labelled at the call site — they sit in a
|
||||
// PropertyRow (or a settings row) that already draws the name beside them, so
|
||||
// a sighted user reads "Show grid" and then the box. A screen reader walking
|
||||
// the tree gets the Text and the checkbox as two unrelated things, and the
|
||||
// checkbox itself says nothing.
|
||||
//
|
||||
// The obvious fix — merging the whole row into one semantics node — falls over
|
||||
// on rows holding more than one control (a Size row is two fields in a button
|
||||
// group; merged, it becomes one unreadable blob). So instead the row publishes
|
||||
// its label down the subtree and each control picks it up as a FALLBACK for
|
||||
// its own semanticLabel. Explicit always wins.
|
||||
|
||||
import "package:flutter/widgets.dart";
|
||||
|
||||
class PropertyLabelScope extends InheritedWidget {
|
||||
const PropertyLabelScope({
|
||||
super.key,
|
||||
required this.label,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final String label;
|
||||
|
||||
/// nearest enclosing row label, or null if this control isn't in one.
|
||||
static String? maybeOf(BuildContext context) {
|
||||
return context
|
||||
.dependOnInheritedWidgetOfExactType<PropertyLabelScope>()
|
||||
?.label;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(PropertyLabelScope old) => label != old.label;
|
||||
}
|
||||
|
||||
/// Resolves the label a control should announce: its own if it was given one,
|
||||
/// otherwise the row it lives in. Returns null when neither exists, which is
|
||||
/// the case a control should be given an explicit label to fix.
|
||||
String? resolveSemanticLabel(BuildContext context, String? explicit) {
|
||||
if (explicit != null) return explicit;
|
||||
return PropertyLabelScope.maybeOf(context);
|
||||
}
|
||||
|
||||
// Marks the control slot of a labelled row, so the controls inside it can stop
|
||||
// asking the call site what they should look like.
|
||||
//
|
||||
// A property section is a docked surface: its rows sit ON a panel, not on the
|
||||
// page. An outline field in there reads as floating - it draws its own border
|
||||
// and background against a background that already has one. Secondary is the
|
||||
// pairing that reads as docked, and it is the ONLY correct answer inside a
|
||||
// row, which makes `variant:` at those call sites a parameter whose every
|
||||
// value but one is a bug.
|
||||
//
|
||||
// So the row publishes the fact, and TextField/Select resolve against it. This
|
||||
// is the same trick PropertyLabelScope plays one widget up - the row knows
|
||||
// something the control cant see, and hands it down rather than making every
|
||||
// call site repeat it. Unlike the label, though, this one is NOT a fallback:
|
||||
// an explicit `variant:` inside a row loses. Thats the point.
|
||||
class PropertySlotScope extends InheritedWidget {
|
||||
const PropertySlotScope({super.key, required super.child});
|
||||
|
||||
/// true when this control sits in a property row's control slot.
|
||||
static bool of(BuildContext context) {
|
||||
return context.dependOnInheritedWidgetOfExactType<PropertySlotScope>() !=
|
||||
null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(PropertySlotScope old) => false;
|
||||
}
|
||||
Reference in New Issue
Block a user