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
72 lines
3.0 KiB
Dart
72 lines
3.0 KiB
Dart
// 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;
|
|
}
|